flutter_native_overlay 0.0.1
flutter_native_overlay: ^0.0.1 copied to clipboard
A Flutter plugin for displaying truly native system-level overlays on Android and iOS, with support for drag-to-dismiss, progress bars, avatars, and iOS Live Activities.
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter_native_overlay/flutter_native_overlay.dart';
void main() {
WidgetsFlutterBinding.ensureInitialized();
SystemChrome.setEnabledSystemUIMode(SystemUiMode.edgeToEdge);
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
final _flutterNativeOverlayPlugin = FlutterNativeOverlay();
bool _hasOverlayPermission = false;
Map<String, double> _nativePadding = {};
StreamSubscription<Map<String, dynamic>>? _overlayEventSubscription;
@override
void initState() {
super.initState();
_checkPermissions();
_getNativePadding();
_overlayEventSubscription = _flutterNativeOverlayPlugin.overlayEvents
.listen((event) {
debugPrint('Received overlay event: $event');
// Using a global key or just print if scaffold isn't available easily
});
}
@override
void dispose() {
_overlayEventSubscription?.cancel();
super.dispose();
}
Future<void> _checkPermissions() async {
bool hasPermission = false;
try {
hasPermission = await _flutterNativeOverlayPlugin
.checkOverlayPermission();
} on PlatformException {
hasPermission = false;
}
if (!mounted) return;
setState(() {
_hasOverlayPermission = hasPermission;
});
}
Future<void> _requestPermissions() async {
await _flutterNativeOverlayPlugin.requestOverlayPermission();
// After returning from settings, check again
_checkPermissions();
}
Future<void> _getNativePadding() async {
Map<String, double> padding = {};
try {
padding = await _flutterNativeOverlayPlugin.getNativePadding();
} catch (e) {
padding = {};
}
if (!mounted) return;
setState(() {
_nativePadding = padding;
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData.dark().copyWith(
primaryColor: Colors.deepPurple,
scaffoldBackgroundColor: const Color(0xFF121212),
),
home: Builder(
builder: (context) => Scaffold(
appBar: AppBar(
title: const Text('Native Overlay Demo'),
backgroundColor: Colors.deepPurple,
elevation: 0,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
_buildSectionHeader('Edge-to-Edge Native Padding'),
_buildInfoCard(
'Top: ${_nativePadding['top']?.toStringAsFixed(1) ?? '0.0'}\n'
'Bottom: ${_nativePadding['bottom']?.toStringAsFixed(1) ?? '0.0'}\n'
'Left: ${_nativePadding['left']?.toStringAsFixed(1) ?? '0.0'}\n'
'Right: ${_nativePadding['right']?.toStringAsFixed(1) ?? '0.0'}',
Icons.padding,
),
const SizedBox(height: 24),
_buildSectionHeader('Android System Overlay'),
_buildInfoCard(
'Permission: ${_hasOverlayPermission ? "Granted" : "Denied"}',
_hasOverlayPermission ? Icons.check_circle : Icons.error,
color: _hasOverlayPermission
? Colors.green
: Colors.redAccent,
),
const SizedBox(height: 16),
if (!_hasOverlayPermission)
ElevatedButton.icon(
onPressed: _requestPermissions,
icon: const Icon(Icons.security),
label: const Text('Request Overlay Permission'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.deepPurpleAccent,
),
),
if (_hasOverlayPermission) ...[
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
_flutterNativeOverlayPlugin.showOverlay(
title: 'Phone',
isDraggable: true,
style: const NativeOverlayStyle(
container: NativeContainerStyle(
backgroundColor: Colors.deepPurple,
shape: OverlayShape.circle,
height: 70,
width: 70,
padding: NativeEdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
),
content: NativeContentStyle(
titleFontSize: 12,
titleColor: Colors.white,
),
button: NativeButtonStyle(
text: 'Acknowledge',
backgroundColor: Colors.tealAccent,
textColor: Colors.black,
),
),
);
},
icon: const Icon(Icons.visibility),
label: const Text('Show Overlay'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.green,
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: () {
_flutterNativeOverlayPlugin.showOverlay(
title: 'Auto Dismissing',
content: 'This will disappear in 3 seconds',
duration: const Duration(seconds: 3),
style: NativeOverlayStyle(
container: const NativeContainerStyle(
backgroundColor: Colors.blueGrey,
borderRadius: 12,
),
progress: NativeProgressStyle(
color: Colors.orangeAccent.withValues(
alpha: 0.8,
),
height: 4,
),
),
);
},
icon: const Icon(Icons.timer),
label: const Text('Auto Dismiss'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blueGrey[800],
foregroundColor: Colors.white,
),
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () {
_flutterNativeOverlayPlugin.showOverlay(
title: '99+',
isDraggable: true,
duration: const Duration(seconds: 5),
style: NativeOverlayStyle(
container: const NativeContainerStyle(
backgroundColor: Colors.redAccent,
shape: OverlayShape.circle,
width: 70,
height: 70,
),
content: const NativeContentStyle(
titleColor: Colors.white,
titleFontSize: 20,
),
progress: const NativeProgressStyle(
color: Colors.white,
height: 8,
),
),
);
},
icon: const Icon(Icons.radio_button_checked),
label: const Text('Circular Timer'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.red[900],
foregroundColor: Colors.white,
),
),
),
],
),
const SizedBox(height: 8),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: () async {
final ByteData data = await rootBundle.load(
'assets/avatar.png',
);
final Uint8List bytes = data.buffer.asUint8List();
_flutterNativeOverlayPlugin.showOverlay(
title: 'Incoming Call',
content: 'John Doe is calling...',
avatarBytes: bytes,
isDraggable: true,
style: const NativeOverlayStyle(
container: NativeContainerStyle(
backgroundColor: Color(0xFF1E1E1E),
borderRadius: 24.0,
),
avatar: NativeAvatarStyle(
size: 56.0,
borderRadius: 28.0,
),
button: NativeButtonStyle(
showButton: true,
text: 'Decline',
backgroundColor: Colors.redAccent,
),
),
);
},
icon: const Icon(Icons.person),
label: const Text('Avatar Overlay'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blueAccent,
),
),
),
],
),
const SizedBox(height: 8),
ElevatedButton.icon(
onPressed: () => _flutterNativeOverlayPlugin.hideOverlay(),
icon: const Icon(Icons.visibility_off),
label: const Text('Hide Overlay'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.redAccent,
),
),
const SizedBox(height: 12),
ElevatedButton.icon(
onPressed: () async {
_flutterNativeOverlayPlugin.showCustom(
context: context,
widget: Container(
width: 80,
height: 80,
decoration: const BoxDecoration(
shape: BoxShape.circle,
gradient: LinearGradient(
colors: [Colors.deepPurple, Colors.tealAccent],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
child: const Icon(
Icons.phone,
color: Colors.white,
size: 36,
),
),
style: const NativeOverlayStyle(
container: NativeContainerStyle(
shape: OverlayShape.circle,
width: 80,
height: 80,
),
),
);
},
icon: const Icon(Icons.bubble_chart),
label: const Text('Show Widget as Circle Overlay'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.deepPurpleAccent,
),
),
],
const SizedBox(height: 32),
_buildSectionHeader('iOS Live Activity / Dynamic Island'),
ElevatedButton.icon(
onPressed: () {
_flutterNativeOverlayPlugin.startLiveActivity(
title: 'Delivery Status',
content: 'Arriving in 5 mins',
);
},
icon: const Icon(Icons.apple),
label: const Text('Start Live Activity'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
backgroundColor: Colors.blueAccent,
),
),
],
),
),
),
),
);
}
Widget _buildSectionHeader(String title) {
return Padding(
padding: const EdgeInsets.only(bottom: 12.0),
child: Text(
title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.bold,
color: Colors.white70,
),
),
);
}
Widget _buildInfoCard(String text, IconData icon, {Color? color}) {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: const Color(0xFF1E1E1E),
borderRadius: BorderRadius.circular(12),
border: Border.all(color: Colors.white10),
),
child: Row(
children: [
Icon(icon, size: 32, color: color ?? Colors.deepPurpleAccent),
const SizedBox(width: 16),
Expanded(
child: Text(
text,
style: const TextStyle(fontSize: 16, height: 1.5),
),
),
],
),
);
}
}