flutter_custom_overlay 0.2.1 copy "flutter_custom_overlay: ^0.2.1" to clipboard
flutter_custom_overlay: ^0.2.1 copied to clipboard

PlatformAndroid

A Flutter plugin for creating customizable overlay windows on Android with bidirectional data communication and dynamic control.

example/lib/main.dart

import 'dart:developer';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_custom_overlay/flutter_custom_overlay.dart';

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

/// Entry point for the overlay
@pragma('vm:entry-point')
void overlayMain() {
  runApp(const OverlayApp());
}

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Custom Overlay Demo',
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple),
        useMaterial3: true,
      ),
      home: const MyHomePage(),
    );
  }
}

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

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  bool _hasPermission = false;
  bool _isOverlayActive = false;
  int _overlayX = 0;
  int _overlayY = 100;
  int _overlayWidth = 300;
  int _overlayHeight = 200;
  OverlayAlignment _overlayAlignment = OverlayAlignment.topLeft;
  String _lastReceivedData = 'No data yet';

  @override
  void initState() {
    super.initState();
    _checkPermission();
    _listenToOverlayData();
    // Check if overlay is already active (auto-initialized)
    _checkOverlayStatus();
  }

  Future<void> _checkOverlayStatus() async {
    final isActive = await FlutterCustomOverlay.isOverlayActive();
    if (isActive) {
      setState(() {
        _isOverlayActive = true;
      });
      // Optionally show a snackbar
      // Removed per user request
    }
  }

  Future<void> _checkPermission() async {
    final hasPermission = await FlutterCustomOverlay.hasOverlayPermission();
    setState(() {
      _hasPermission = hasPermission;
    });
  }

  Future<void> _requestPermission() async {
    await FlutterCustomOverlay.requestOverlayPermission();
    // Wait a bit and check again
    await Future.delayed(const Duration(seconds: 1));
    await _checkPermission();
  }

  void _listenToOverlayData() {
    FlutterCustomOverlay.overlayStream.listen((data) {
      setState(() {
        _lastReceivedData = data.data.toString();
      });
    });
  }

  Future<void> _showOverlay() async {
    final config = OverlayConfig(
      width: _overlayWidth,
      height: _overlayHeight,
      alignment: _overlayAlignment,
      isDraggable: true,
      isClickThrough: false,
    );

    // No need to pass entryPoint if auto-initialized or defaulting to 'overlayMain'
    final success = await FlutterCustomOverlay.showOverlay(
      config: config,
      data: {
        'message': 'Hello from showOverlay!',
        'timestamp': DateTime.now().toIso8601String(),
      },
    );

    if (success) {
      final isActive = await FlutterCustomOverlay.isOverlayActive();
      setState(() {
        _isOverlayActive = isActive;
      });
    }
  }

  Future<void> _hideOverlay() async {
    final success = await FlutterCustomOverlay.hideOverlay();
    if (success) {
      setState(() {
        // We keep it active in background, just window hidden?
        // Example UI logic: "Overlay Visible" vs "Overlay Hidden"
        // _isOverlayActive tracks visibility usually?
        // The checkOverlayStatus tracks SERVICE status.
        // Let's assume _isOverlayActive tracks SERVICE status in this refactor?
        // No, user UI says "Overlay Visible/Hidden".
        // But the icon uses _isOverlayActive.
        // I will decouple if needed, but for now let's assume Hide -> Visible=False.
        // But Service is still running.
      });
    }
  }

  Future<void> _hideAndRestartOverlay() async {
    final success = await FlutterCustomOverlay.hideAndRestartOverlay();
    if (success) {
      setState(() {
        // UI assumes hidden
      });
      if (mounted) {
        // Removed snackbar per user request
      }
    }
  }

  Future<void> _updatePosition() async {
    await FlutterCustomOverlay.updatePosition(x: _overlayX, y: _overlayY);
  }

  Future<void> _updateSize() async {
    await FlutterCustomOverlay.updateSize(
      width: _overlayWidth,
      height: _overlayHeight,
    );
  }

  Future<void> _sendDataToOverlay() async {
    final data = {
      'message': 'Hello from main app!',
      'timestamp': DateTime.now().toIso8601String(),
      'counter': DateTime.now().millisecondsSinceEpoch,
    };
    await FlutterCustomOverlay.shareData(data);
  }

  // Removed _initializeOverlay as it's no longer needed

  Future<void> _closeOverlay() async {
    await FlutterCustomOverlay.closeOverlay();
    setState(() {
      _isOverlayActive = false;
    });
    if (mounted) {
      // Removed snackbar per user request
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Theme.of(context).colorScheme.inversePrimary,
        title: const Text('Flutter Custom Overlay Demo'),
      ),
      body: SingleChildScrollView(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: [
            // Permission section
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Permission Status',
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const SizedBox(height: 8),
                    Row(
                      children: [
                        Icon(
                          _hasPermission ? Icons.check_circle : Icons.cancel,
                          color: _hasPermission ? Colors.green : Colors.red,
                        ),
                        const SizedBox(width: 8),
                        Text(
                          _hasPermission
                              ? 'Overlay permission granted'
                              : 'Overlay permission not granted',
                        ),
                      ],
                    ),
                    if (!_hasPermission) ...[
                      const SizedBox(height: 8),
                      ElevatedButton(
                        onPressed: _requestPermission,
                        child: const Text('Request Permission'),
                      ),
                    ],
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // Overlay control section
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Overlay Control',
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const SizedBox(height: 8),
                    Row(
                      children: [
                        Icon(
                          _isOverlayActive // Use logical OR if we track window separately, but assuming isOverlayActive tracks service
                              ? Icons.visibility
                              : Icons.visibility_off,
                          color: _isOverlayActive ? Colors.green : Colors.grey,
                        ),
                        const SizedBox(width: 8),
                        Text(
                          _isOverlayActive
                              ? 'Overlay Active'
                              : 'Overlay Inactive',
                        ),
                      ],
                    ),
                    const SizedBox(height: 16),
                    Wrap(
                      children: [
                        ElevatedButton(
                          onPressed: _showOverlay,
                          child: const Text('Show Overlay'),
                        ),
                        ElevatedButton(
                          onPressed: _hideOverlay,
                          child: const Text('Hide (Keep State)'),
                        ),
                        ElevatedButton(
                          onPressed: _hideAndRestartOverlay,
                          child: const Text('Hide (Reset)'),
                        ),
                      ],
                    ),
                    const SizedBox(height: 8),
                    Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: [
                        ElevatedButton(
                          onPressed: () async {
                            final isVisible =
                                await FlutterCustomOverlay.isOverlayVisible();
                            if (mounted) {
                              ScaffoldMessenger.of(context).showSnackBar(
                                SnackBar(
                                  content: Text(
                                    isVisible
                                        ? 'Overlay is VISIBLE'
                                        : 'Overlay is HIDDEN',
                                  ),
                                  backgroundColor: isVisible
                                      ? Colors.green
                                      : Colors.grey,
                                ),
                              );
                            }
                          },
                          child: const Text('Check Visibility'),
                        ),
                        ElevatedButton(
                          onPressed: _closeOverlay,
                          child: const Text('Shutdown Service'),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // Position and size controls
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Position & Size',
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const SizedBox(height: 16),
                    Text('X: $_overlayX'),
                    Slider(
                      value: _overlayX.toDouble(),
                      min: -500,
                      max: 500,
                      divisions: 100,
                      label: _overlayX.toString(),
                      onChanged: (value) {
                        setState(() {
                          _overlayX = value.toInt();
                        });
                      },
                    ),
                    Text('Y: $_overlayY'),
                    Slider(
                      value: _overlayY.toDouble(),
                      min: -500,
                      max: 1000,
                      divisions: 100,
                      label: _overlayY.toString(),
                      onChanged: (value) {
                        setState(() {
                          _overlayY = value.toInt();
                        });
                      },
                    ),
                    Text('Width: $_overlayWidth'),
                    Slider(
                      value: _overlayWidth.toDouble(),
                      min: 100,
                      max: 3000,
                      divisions: 50,
                      label: _overlayWidth.toString(),
                      onChanged: (value) {
                        setState(() {
                          _overlayWidth = value.toInt();
                        });
                      },
                    ),
                    Text('Height: $_overlayHeight'),
                    Slider(
                      value: _overlayHeight.toDouble(),
                      min: 100,
                      max: 3000,
                      divisions: 50,
                      label: _overlayHeight.toString(),
                      onChanged: (value) {
                        setState(() {
                          _overlayHeight = value.toInt();
                        });
                      },
                    ),
                    const SizedBox(height: 16),
                    Text(
                      'Alignment: ${_overlayAlignment.name}',
                      style: Theme.of(context).textTheme.titleSmall,
                    ),
                    DropdownButton<OverlayAlignment>(
                      value: _overlayAlignment,
                      isExpanded: true,
                      onChanged: (OverlayAlignment? value) {
                        if (value != null) {
                          setState(() {
                            _overlayAlignment = value;
                          });
                        }
                      },
                      items: OverlayAlignment.values.map((
                        OverlayAlignment alignment,
                      ) {
                        return DropdownMenuItem<OverlayAlignment>(
                          value: alignment,
                          child: Text(alignment.name),
                        );
                      }).toList(),
                    ),
                    const SizedBox(height: 8),
                    Row(
                      children: [
                        Expanded(
                          child: ElevatedButton(
                            onPressed: _isOverlayActive
                                ? _updatePosition
                                : null,
                            child: const Text('Update Position'),
                          ),
                        ),
                        const SizedBox(width: 8),
                        Expanded(
                          child: ElevatedButton(
                            onPressed: _isOverlayActive ? _updateSize : null,
                            child: const Text('Update Size'),
                          ),
                        ),
                      ],
                    ),
                  ],
                ),
              ),
            ),

            const SizedBox(height: 16),

            // Data sharing section
            Card(
              child: Padding(
                padding: const EdgeInsets.all(16),
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      'Data Sharing',
                      style: Theme.of(context).textTheme.titleLarge,
                    ),
                    const SizedBox(height: 8),
                    ElevatedButton.icon(
                      onPressed: _isOverlayActive ? _sendDataToOverlay : null,
                      icon: const Icon(Icons.send),
                      label: const Text('Send Data to Overlay'),
                    ),
                    const SizedBox(height: 16),
                    Text(
                      'Last Received from Overlay:',
                      style: Theme.of(context).textTheme.titleMedium,
                    ),
                    const SizedBox(height: 8),
                    Container(
                      padding: const EdgeInsets.all(12),
                      decoration: BoxDecoration(
                        color: Colors.grey[200],
                        borderRadius: BorderRadius.circular(8),
                      ),
                      child: Text(_lastReceivedData),
                    ),
                  ],
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

// Overlay App
class OverlayApp extends StatelessWidget {
  const OverlayApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: OverlayWidget(),
    );
  }
}

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

  @override
  State<OverlayWidget> createState() => _OverlayWidgetState();
}

class _OverlayWidgetState extends State<OverlayWidget> {
  String _receivedData = 'Waiting for data...';
  int _counter = 0;

  @override
  void initState() {
    super.initState();
    // Initialize listener for overlay communication
    log('OverlayWidget initState');
    OverlayMessenger.listen();
    _listenToMainApp();
  }

  void _listenToMainApp() {
    // Listen to data from main app via stream
    OverlayMessenger.onDataReceived.listen((data) {
      if (data is Map || data is String) {
        log("data received: $data");
        setState(() {
          _receivedData = data.toString();
        });
      }
    });
  }

  Future<void> _sendDataToMainApp() async {
    // Send data back to main app using the helper
    final success = await OverlayMessenger.sendToMainApp({
      'message': 'Hello from overlay!',
      'counter': _counter++,
      'timestamp': DateTime.now().toIso8601String(),
    });

    if (!success) {
      print('Failed to send data to main app');
    }
  }

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.transparent,
      child: Container(
        decoration: BoxDecoration(
          gradient: LinearGradient(
            colors: [Colors.purple.shade400, Colors.blue.shade400],
            begin: Alignment.topLeft,
            end: Alignment.bottomRight,
          ),
          borderRadius: BorderRadius.circular(16),
          boxShadow: [
            BoxShadow(
              color: Colors.black.withOpacity(0.3),
              blurRadius: 10,
              offset: const Offset(0, 5),
            ),
          ],
        ),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              const Icon(Icons.layers, color: Colors.white, size: 40),
              const SizedBox(height: 8),
              const Text(
                'Custom Overlay',
                style: TextStyle(
                  color: Colors.white,
                  fontSize: 20,
                  fontWeight: FontWeight.bold,
                ),
              ),
              const SizedBox(height: 16),
              Container(
                padding: const EdgeInsets.all(12),
                decoration: BoxDecoration(
                  color: Colors.white.withOpacity(0.2),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: Text(
                  _receivedData,
                  style: const TextStyle(color: Colors.white, fontSize: 12),
                  textAlign: TextAlign.center,
                ),
              ),
              const SizedBox(height: 16),
              ElevatedButton.icon(
                onPressed: _sendDataToMainApp,
                icon: const Icon(Icons.send),
                label: const Text('Send to Main App'),
                style: ElevatedButton.styleFrom(
                  backgroundColor: Colors.white,
                  foregroundColor: Colors.purple,
                ),
              ),
              const SizedBox(height: 8),
              const Text(
                'Drag me around!',
                style: TextStyle(
                  color: Colors.white70,
                  fontSize: 12,
                  fontStyle: FontStyle.italic,
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
0
likes
160
points
--
downloads

Publisher

unverified uploader

Weekly Downloads

A Flutter plugin for creating customizable overlay windows on Android with bidirectional data communication and dynamic control.

Homepage

Documentation

API reference

License

MIT (license)

Dependencies

flutter, plugin_platform_interface

More

Packages that depend on flutter_custom_overlay

Packages that implement flutter_custom_overlay