flutter_floating_window 1.1.0 copy "flutter_floating_window: ^1.1.0" to clipboard
flutter_floating_window: ^1.1.0 copied to clipboard

PlatformAndroid

A comprehensive Flutter plugin for creating and managing floating window overlays on Android devices. Features include interactive UI elements, real-time data synchronization, persistent storage, and [...]

example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:flutter_floating_window/flutter_floating_window.dart';
import 'package:shared_preferences/shared_preferences.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Floating Window Example',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const FloatingWindowDemo(),
    );
  }
}

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

  @override
  State<FloatingWindowDemo> createState() => _FloatingWindowDemoState();
}

class _FloatingWindowDemoState extends State<FloatingWindowDemo> {
  final FloatingWindowManager _windowManager = FloatingWindowManager.instance;
  List<String> _activeWindows = [];
  bool _hasPermission = false;
  bool _isServiceRunning = false;
  String _lastEvent = 'No events yet';
  int _counter = 0;
  SharedPreferences? _prefs;
  
  @override
  void initState() {
    super.initState();
    _initializePlugin();
    _listenToEvents();
  }

  Future<void> _initializePlugin() async {
    try {
      // Initialize SharedPreferences
      _prefs = await SharedPreferences.getInstance();
      
      // Load saved counter value
      final savedCounter = _prefs?.getInt('counter') ?? 0;
      
      // Check overlay permission
      final hasPermission = await _windowManager.hasOverlayPermission();
      
      // Check if service is running
      final isServiceRunning = await _windowManager.isServiceRunning();
      
      // Get active windows
      final activeWindows = await _windowManager.getActiveWindows();
      
      setState(() {
        _counter = savedCounter;
        _hasPermission = hasPermission;
        _isServiceRunning = isServiceRunning;
        _activeWindows = activeWindows;
      });
    } catch (e) {
      _showError('Failed to initialize: $e');
    }
  }

  void _listenToEvents() {
    _windowManager.eventStream.listen((event) {
      setState(() {
        _lastEvent = '${event.type} - ${event.windowId} at ${event.timestamp}';
      });
      
      // Handle specific events
      switch (event.type) {
        case FloatingWindowEventType.windowCreated:
          _refreshActiveWindows();
          _showSnackBar('Window created: ${event.windowId}');
          break;
        case FloatingWindowEventType.windowClosed:
          _refreshActiveWindows();
          _showSnackBar('Window closed: ${event.windowId}');
          break;
        case FloatingWindowEventType.windowClicked:
          _showSnackBar('Window clicked: ${event.windowId}');
          break;
        case FloatingWindowEventType.windowMoved:
          final data = event.data;
          if (data != null) {
            _showSnackBar('Window moved to (${data['x']}, ${data['y']})');
          }
          break;
        case FloatingWindowEventType.error:
          final data = event.data;
          if (data != null) {
            _showError('Error: ${data['errorMessage']}');
          }
          break;
        default:
          break;
      }
    });
  }

  Future<void> _refreshActiveWindows() async {
    try {
      final windows = await _windowManager.getActiveWindows();
      setState(() {
        _activeWindows = windows;
      });
    } catch (e) {
      _showError('Failed to refresh windows: $e');
    }
  }

  Future<void> _requestPermission() async {
    try {
      await _windowManager.requestOverlayPermission();
      // Wait a bit for user to grant permission, then check again
      await Future.delayed(const Duration(seconds: 2));
      final hasPermission = await _windowManager.hasOverlayPermission();
      setState(() {
        _hasPermission = hasPermission;
      });
      
      if (hasPermission) {
        _showSnackBar('Overlay permission granted!');
        // Automatically start service when permission is granted
        await _windowManager.startService();
        final isRunning = await _windowManager.isServiceRunning();
        setState(() {
          _isServiceRunning = isRunning;
        });
      } else {
        _showSnackBar('Please grant overlay permission in settings');
      }
    } catch (e) {
      _showError('Failed to request permission: $e');
    }
  }

  Future<void> _startService() async {
    try {
      await _windowManager.startService();
      final isRunning = await _windowManager.isServiceRunning();
      setState(() {
        _isServiceRunning = isRunning;
      });
      _showSnackBar('Service started');
    } catch (e) {
      _showError('Failed to start service: $e');
    }
  }

  Future<void> _stopService() async {
    try {
      await _windowManager.stopService();
      setState(() {
        _isServiceRunning = false;
        _activeWindows.clear();
      });
      _showSnackBar('Service stopped');
    } catch (e) {
      _showError('Failed to stop service: $e');
    }
  }

  Future<void> _createSimpleWindow() async {
    try {
      final windowId = await _windowManager.createSimpleWindow(
        width: 300,
        height: 200,
        title: 'Counter: $_counter',
        isDraggable: true,
      );
      _showSnackBar('Counter window created: $windowId');
      await _refreshActiveWindows();
      await _syncCounterToWindows();
    } catch (e) {
      _showError('Failed to create counter window: $e');
    }
  }

  Future<void> _createCustomWindow() async {
    if (!_hasPermission) {
      _showError('Overlay permission required');
      return;
    }
    
    if (!_isServiceRunning) {
      await _startService();
    }
    
    try {
      final config = FloatingWindowConfig(
        width: 300,
        height: 200,
        title: 'Interactive Counter',
        isDraggable: true,
        isResizable: false,
        showCloseButton: true,
        opacity: 0.9,
        initialX: 100,
        initialY: 100,
        backgroundColor: 0xFF2196F3, // Colors.blue
      );
      
      final windowId = await _windowManager.createWindow(config);
      _showSnackBar('Interactive counter window created: $windowId');
      await _refreshActiveWindows();
      
      // Send initial counter data to the new window
      await Future.delayed(const Duration(milliseconds: 500));
      await _syncCounterToWindows();
    } catch (e) {
      _showError('Failed to create interactive window: $e');
    }
  }

  Future<void> _sendDataToWindow(String windowId) async {
    try {
      final data = {
        'title': 'Updated Title',
        'content': 'Data sent at ${DateTime.now()}',
        'timestamp': DateTime.now().millisecondsSinceEpoch,
      };
      
      await _windowManager.sendDataToWindow(windowId, data);
      _showSnackBar('Data sent to window: $windowId');
    } catch (e) {
      _showError('Failed to send data: $e');
    }
  }

  Future<void> _moveWindow(String windowId) async {
    try {
      await _windowManager.moveWindow(windowId, 200, 200);
      _showSnackBar('Moved window: $windowId');
    } catch (e) {
      _showError('Failed to move window: $e');
    }
  }

  Future<void> _resizeWindow(String windowId) async {
    try {
      await _windowManager.resizeWindow(windowId, 500, 400);
      _showSnackBar('Resized window: $windowId');
    } catch (e) {
      _showError('Failed to resize window: $e');
    }
  }

  Future<void> _closeWindow(String windowId) async {
    try {
      await _windowManager.closeWindow(windowId);
      _showSnackBar('Closed window: $windowId');
    } catch (e) {
      _showError('Failed to close window: $e');
    }
  }

  Future<void> _closeAllWindows() async {
    try {
      await _windowManager.closeAllWindows();
      _showSnackBar('All windows closed');
    } catch (e) {
      _showError('Failed to close all windows: $e');
    }
  }

  Future<void> _incrementCounter() async {
    setState(() {
      _counter++;
    });
    await _saveCounter();
    await _syncCounterToWindows();
  }

  Future<void> _decrementCounter() async {
    setState(() {
      _counter = _counter > 0 ? _counter - 1 : 0;
    });
    await _saveCounter();
    await _syncCounterToWindows();
  }

  Future<void> _saveCounter() async {
    await _prefs?.setInt('counter', _counter);
  }

  Future<void> _syncCounterToWindows() async {
    final data = {
      'counter': _counter,
      'timestamp': DateTime.now().millisecondsSinceEpoch,
    };
    
    for (final windowId in _activeWindows) {
      try {
        await _windowManager.sendDataToWindow(windowId, data);
      } catch (e) {
        // Ignore errors for individual windows
      }
    }
  }

  void _showSnackBar(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        duration: const Duration(seconds: 2),
      ),
    );
  }

  void _showError(String message) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text(message),
        backgroundColor: Colors.red,
        duration: const Duration(seconds: 3),
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Floating Window Demo'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Counter Display Card
            Card(
              color: Colors.blue.shade50,
              child: Padding(
                padding: const EdgeInsets.all(20.0),
                child: Column(
                  children: [
                    const Text(
                      'Counter Value',
                      style: TextStyle(
                        fontSize: 18,
                        fontWeight: FontWeight.bold,
                      ),
                    ),
                    const SizedBox(height: 12),
                    Text(
                      '$_counter',
                      style: const TextStyle(
                        fontSize: 48,
                        fontWeight: FontWeight.bold,
                        color: Colors.blue,
                      ),
                    ),
                    const SizedBox(height: 16),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: [
                        ElevatedButton(
                          onPressed: _decrementCounter,
                          style: ElevatedButton.styleFrom(
                            backgroundColor: Colors.red,
                            foregroundColor: Colors.white,
                            shape: const CircleBorder(),
                            padding: const EdgeInsets.all(16),
                          ),
                          child: const Icon(Icons.remove, size: 24),
                        ),
                        ElevatedButton(
                          onPressed: _incrementCounter,
                          style: ElevatedButton.styleFrom(
                            backgroundColor: Colors.green,
                            foregroundColor: Colors.white,
                            shape: const CircleBorder(),
                            padding: const EdgeInsets.all(16),
                          ),
                          child: const Icon(Icons.add, size: 24),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),
            const SizedBox(height: 16),
            // Status Cards
            Row(
              children: [
                Expanded(
                  child: Card(
                    color: _hasPermission ? Colors.green.shade100 : Colors.red.shade100,
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        children: [
                          Icon(
                            _hasPermission ? Icons.check_circle : Icons.cancel,
                            color: _hasPermission ? Colors.green : Colors.red,
                            size: 32,
                          ),
                          const SizedBox(height: 8),
                          const Text(
                            'Overlay Permission',
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                          Text(_hasPermission ? 'Granted' : 'Not Granted'),
                        ],
                      ),
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Card(
                    color: _isServiceRunning ? Colors.green.shade100 : Colors.orange.shade100,
                    child: Padding(
                      padding: const EdgeInsets.all(16.0),
                      child: Column(
                        children: [
                          Icon(
                            _isServiceRunning ? Icons.play_circle : Icons.pause_circle,
                            color: _isServiceRunning ? Colors.green : Colors.orange,
                            size: 32,
                          ),
                          const SizedBox(height: 8),
                          const Text(
                            'Service Status',
                            style: TextStyle(fontWeight: FontWeight.bold),
                          ),
                          Text(_isServiceRunning ? 'Running' : 'Stopped'),
                        ],
                      ),
                    ),
                  ),
                ),
              ],
            ),
            
            const SizedBox(height: 16),
            
            // Permission and Service Controls
            if (!_hasPermission)
              ElevatedButton.icon(
                onPressed: _requestPermission,
                icon: const Icon(Icons.security),
                label: const Text('Request Overlay Permission'),
              ),
            
            const SizedBox(height: 8),
            
            Row(
              children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _isServiceRunning ? null : _startService,
                    icon: const Icon(Icons.play_arrow),
                    label: const Text('Start Service'),
                  ),
                ),
                const SizedBox(width: 8),
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _isServiceRunning ? _stopService : null,
                    icon: const Icon(Icons.stop),
                    label: const Text('Stop Service'),
                  ),
                ),
              ],
            ),
            
            const SizedBox(height: 16),
            
            // Window Creation Controls
            Text(
              'Create Counter Windows',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            
            Row(
              children: [
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _hasPermission && _isServiceRunning ? _createSimpleWindow : null,
                    icon: const Icon(Icons.add_box),
                    label: const Text('Simple Counter'),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.all(16),
                    ),
                  ),
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: ElevatedButton.icon(
                    onPressed: _hasPermission && _isServiceRunning ? _createCustomWindow : null,
                    icon: const Icon(Icons.dashboard_customize),
                    label: const Text('Interactive Counter'),
                    style: ElevatedButton.styleFrom(
                      padding: const EdgeInsets.all(16),
                    ),
                  ),
                ),
              ],
            ),
            
            const SizedBox(height: 16),
            
            // Active Windows
            Text(
              'Active Windows (${_activeWindows.length})',
              style: Theme.of(context).textTheme.titleMedium,
            ),
            const SizedBox(height: 8),
            
            if (_activeWindows.isEmpty)
              const Card(
                child: Padding(
                  padding: EdgeInsets.all(16.0),
                  child: Text(
                    'No active windows',
                    textAlign: TextAlign.center,
                    style: TextStyle(color: Colors.grey),
                  ),
                ),
              )
            else
              Expanded(
                child: ListView.builder(
                  itemCount: _activeWindows.length,
                  itemBuilder: (context, index) {
                    final windowId = _activeWindows[index];
                    return Card(
                      child: ListTile(
                        leading: const Icon(Icons.window),
                        title: Text('Window ${index + 1}'),
                        subtitle: Text(windowId),
                        trailing: PopupMenuButton<String>(
                          onSelected: (action) {
                            switch (action) {
                              case 'send_data':
                                _sendDataToWindow(windowId);
                                break;
                              case 'move':
                                _moveWindow(windowId);
                                break;
                              case 'resize':
                                _resizeWindow(windowId);
                                break;
                              case 'close':
                                _closeWindow(windowId);
                                break;
                            }
                          },
                          itemBuilder: (context) => [
                            const PopupMenuItem(
                              value: 'send_data',
                              child: Text('Send Data'),
                            ),
                            const PopupMenuItem(
                              value: 'move',
                              child: Text('Move Window'),
                            ),
                            const PopupMenuItem(
                              value: 'resize',
                              child: Text('Resize Window'),
                            ),
                            const PopupMenuItem(
                              value: 'close',
                              child: Text('Close Window'),
                            ),
                          ],
                        ),
                      ),
                    );
                  },
                ),
              ),
            
            const SizedBox(height: 16),
            
            // Close All Button
            if (_activeWindows.isNotEmpty)
              ElevatedButton.icon(
                onPressed: _closeAllWindows,
                icon: const Icon(Icons.clear_all),
                label: const Text('Close All Windows'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.red,
                  foregroundColor: Colors.white,
                ),
              ),
            
            const SizedBox(height: 16),
            
            // Last Event
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16.0),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Last Event:',
                      style: Theme.of(context).textTheme.titleSmall,
                    ),
                    const SizedBox(height: 4),
                    Text(
                      _lastEvent,
                      style: const TextStyle(fontFamily: 'monospace'),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}
1
likes
140
points
27
downloads

Publisher

unverified uploader

Weekly Downloads

A comprehensive Flutter plugin for creating and managing floating window overlays on Android devices. Features include interactive UI elements, real-time data synchronization, persistent storage, and customizable window properties with proper permission handling.

Homepage
Repository (GitHub)
View/report issues

Documentation

Documentation
API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on flutter_floating_window

Packages that implement flutter_floating_window