floating_app_icon 0.0.1 copy "floating_app_icon: ^0.0.1" to clipboard
floating_app_icon: ^0.0.1 copied to clipboard

PlatformAndroid

A Flutter plugin that displays the application's launcher icon as a floating overlay above other apps on Android.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:floating_app_icon/floating_app_icon.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Floating App Icon Demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        brightness: Brightness.dark,
        useMaterial3: true,
        colorScheme: ColorScheme.fromSeed(
          seedColor: Colors.deepPurple,
          brightness: Brightness.dark,
        ),
      ),
      home: const HomeScreen(),
    );
  }
}

class HomeScreen extends StatefulWidget {
  const HomeScreen({super.key});

  @override
  State<HomeScreen> createState() => _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
  bool _hasPermission = false;
  bool _isShowing = false;
  bool _isLoading = false;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
    _refreshStates();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    // Automatically refresh permission and visibility states when the app is resumed
    if (state == AppLifecycleState.resumed) {
      _refreshStates();
    }
  }

  Future<void> _refreshStates() async {
    setState(() => _isLoading = true);
    try {
      final granted = await FloatingAppIcon.hasPermission();
      final showing = await FloatingAppIcon.isShowing();
      setState(() {
        _hasPermission = granted;
        _isShowing = showing;
      });
    } catch (e) {
      _showErrorSnackBar('Failed to load status: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _requestPermission() async {
    try {
      await FloatingAppIcon.requestPermission();
      // Settings screen is opened; didChangeAppLifecycleState will refresh state on resume.
    } catch (e) {
      _showErrorSnackBar('Failed to request permission: $e');
    }
  }

  Future<void> _showOverlay() async {
    if (!_hasPermission) {
      _showErrorSnackBar('Please grant overlay permission first!');
      return;
    }
    setState(() => _isLoading = true);
    try {
      await FloatingAppIcon.show();
      await _refreshStates();
    } catch (e) {
      _showErrorSnackBar('Failed to show overlay: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  Future<void> _hideOverlay() async {
    setState(() => _isLoading = true);
    try {
      await FloatingAppIcon.hide();
      await _refreshStates();
    } catch (e) {
      _showErrorSnackBar('Failed to hide overlay: $e');
    } finally {
      setState(() => _isLoading = false);
    }
  }

  void _showErrorSnackBar(String message) {
    if (!mounted) return;
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.redAccent,
        behavior: SnackBarBehavior.floating,
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);

    return Scaffold(
      body: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
            colors: [
              theme.colorScheme.surface,
              theme.colorScheme.primaryContainer.withOpacity(0.15),
              theme.colorScheme.surface,
            ],
          ),
        ),
        child: SafeArea(
          child: Padding(
            padding: const EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0),
            child: Column(
              crossAxisAlignment: CrossAxisAlignment.stretch,
              children: [
                const SizedBox(height: 32),
                // App Header
                Center(
                  child: Column(
                    children: [
                      Container(
                        padding: const EdgeInsets.all(16),
                        decoration: BoxDecoration(
                          color: theme.colorScheme.primary.withOpacity(0.1),
                          shape: BoxShape.circle,
                          border: Border.all(
                            color: theme.colorScheme.primary.withOpacity(0.3),
                            width: 2,
                          ),
                        ),
                        child: Icon(
                          Icons.layers_outlined,
                          size: 48,
                          color: theme.colorScheme.primary,
                        ),
                      ),
                      const SizedBox(height: 16),
                      Text(
                        'Floating App Icon',
                        style: theme.textTheme.headlineMedium?.copyWith(
                          fontWeight: FontWeight.bold,
                          letterSpacing: 0.5,
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'Messenger-style overlay controller',
                        style: theme.textTheme.bodyMedium?.copyWith(
                          color: theme.colorScheme.onSurfaceVariant,
                        ),
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 48),

                // Status Dashboard Card
                Container(
                  padding: const EdgeInsets.all(24),
                  decoration: BoxDecoration(
                    color: theme.colorScheme.surfaceVariant.withOpacity(0.3),
                    borderRadius: BorderRadius.circular(24),
                    border: Border.all(
                      color: theme.colorScheme.onSurface.withOpacity(0.08),
                    ),
                  ),
                  child: Column(
                    children: [
                      _StatusRow(
                        title: 'Overlay Permission',
                        isActive: _hasPermission,
                        activeLabel: 'Granted',
                        inactiveLabel: 'Denied',
                        activeColor: Colors.green,
                        inactiveColor: Colors.amber,
                      ),
                      const Divider(height: 32, thickness: 0.5),
                      _StatusRow(
                        title: 'Overlay Visibility',
                        isActive: _isShowing,
                        activeLabel: 'Showing',
                        inactiveLabel: 'Hidden',
                        activeColor: theme.colorScheme.primary,
                        inactiveColor: theme.colorScheme.onSurfaceVariant.withOpacity(0.5),
                      ),
                    ],
                  ),
                ),
                const SizedBox(height: 48),

                // Action Controls
                Column(
                  crossAxisAlignment: CrossAxisAlignment.stretch,
                  children: [
                    ElevatedButton.icon(
                      onPressed: _isLoading ? null : _requestPermission,
                      icon: const Icon(Icons.security_outlined),
                      label: const Text('Request Overlay Permission'),
                      style: ElevatedButton.styleFrom(
                        padding: const EdgeInsets.symmetric(vertical: 16),
                        shape: RoundedRectangleBorder(
                          borderRadius: BorderRadius.circular(16),
                        ),
                      ),
                    ),
                    const SizedBox(height: 16),
                    Row(
                      children: [
                        Expanded(
                          child: OutlinedButton.icon(
                            onPressed: _isLoading ? null : _showOverlay,
                            icon: const Icon(Icons.play_arrow_outlined),
                            label: const Text('Show Icon'),
                            style: OutlinedButton.styleFrom(
                              padding: const EdgeInsets.symmetric(vertical: 16),
                              side: BorderSide(color: theme.colorScheme.primary),
                              shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(16),
                              ),
                            ),
                          ),
                        ),
                        const SizedBox(width: 16),
                        Expanded(
                          child: OutlinedButton.icon(
                            onPressed: _isLoading ? null : _hideOverlay,
                            icon: const Icon(Icons.stop_outlined),
                            label: const Text('Hide Icon'),
                            style: OutlinedButton.styleFrom(
                              padding: const EdgeInsets.symmetric(vertical: 16),
                              side: BorderSide(color: theme.colorScheme.error.withOpacity(0.5)),
                              foregroundColor: theme.colorScheme.error,
                              shape: RoundedRectangleBorder(
                                borderRadius: BorderRadius.circular(16),
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
                const Spacer(),
                // Refresh Button
                Center(
                  child: TextButton.icon(
                    onPressed: _isLoading ? null : _refreshStates,
                    icon: _isLoading
                        ? const SizedBox(
                            width: 16,
                            height: 16,
                            child: CircularProgressIndicator(
                              strokeWidth: 2,
                            ),
                          )
                        : const Icon(Icons.refresh_outlined),
                    label: const Text('Manual Refresh'),
                  ),
                ),
                const SizedBox(height: 16),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

class _StatusRow extends StatelessWidget {
  final String title;
  final bool isActive;
  final String activeLabel;
  final String inactiveLabel;
  final Color activeColor;
  final Color inactiveColor;

  const _StatusRow({
    required this.title,
    required this.isActive,
    required this.activeLabel,
    required this.inactiveLabel,
    required this.activeColor,
    required this.inactiveColor,
  });

  @override
  Widget build(BuildContext context) {
    final theme = Theme.of(context);
    final statusColor = isActive ? activeColor : inactiveColor;
    final statusLabel = isActive ? activeLabel : inactiveLabel;

    return Row(
      mainAxisAlignment: MainAxisAlignment.spaceBetween,
      children: [
        Text(
          title,
          style: theme.textTheme.titleMedium?.copyWith(
            fontWeight: FontWeight.w500,
          ),
        ),
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6),
          decoration: BoxDecoration(
            color: statusColor.withOpacity(0.12),
            borderRadius: BorderRadius.circular(12),
            border: Border.all(
              color: statusColor.withOpacity(0.3),
              width: 1,
            ),
          ),
          child: Row(
            mainAxisSize: MainAxisSize.min,
            children: [
              Container(
                width: 8,
                height: 8,
                decoration: BoxDecoration(
                  color: statusColor,
                  shape: BoxShape.circle,
                ),
              ),
              const SizedBox(width: 8),
              Text(
                statusLabel,
                style: theme.textTheme.bodyMedium?.copyWith(
                  color: statusColor,
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
        ),
      ],
    );
  }
}
0
likes
160
points
27
downloads

Documentation

API reference

Publisher

verified publisherrvkt.in

Weekly Downloads

A Flutter plugin that displays the application's launcher icon as a floating overlay above other apps on Android.

Homepage

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on floating_app_icon

Packages that implement floating_app_icon