masterfabric_flutter_quick_actions 0.0.4
masterfabric_flutter_quick_actions: ^0.0.4 copied to clipboard
A Flutter plugin for iOS Quick Actions and Android App Shortcuts
import 'package:flutter/material.dart';
import 'package:masterfabric_flutter_quick_actions/masterfabric_flutter_quick_actions.dart';
import 'package:url_launcher/url_launcher.dart';
import 'dart:io';
// Initial quick actions for first boot
final List<QuickAction> _initialQuickActions = [
QuickAction(
type: 'search',
title: 'Search',
subtitle: 'Find items quickly',
iconType: 'UIApplicationShortcutIconTypeSearch',
iconName: 'ic_search',
),
QuickAction(
type: 'compose',
title: 'New Message',
subtitle: 'Start a conversation',
iconType: 'UIApplicationShortcutIconTypeCompose',
iconName: 'ic_compose',
),
QuickAction(
type: 'favorite',
title: 'Favorites',
subtitle: 'View your favorites',
iconType: 'UIApplicationShortcutIconTypeFavorite',
iconName: 'ic_favorite',
),
QuickAction(
type: 'share',
title: 'Share',
subtitle: 'Share with friends',
iconType: 'UIApplicationShortcutIconTypeShare',
iconName: 'ic_share',
),
];
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// Initialize quick actions with initial actions
await QuickActionsManager.initialize(
actions: _initialQuickActions,
onAction: (action) {
// Handle quick action - will be handled in the widget
debugPrint('Quick action triggered: ${action.type}');
},
);
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'MF Quick Actions',
theme: ThemeData(
scaffoldBackgroundColor: Colors.white,
colorScheme: const ColorScheme.light(
primary: Colors.black,
onPrimary: Colors.white,
surface: Colors.white,
onSurface: Colors.black,
),
appBarTheme: const AppBarTheme(
backgroundColor: Colors.white,
foregroundColor: Colors.black,
elevation: 0,
centerTitle: true,
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
),
),
dialogTheme: DialogThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
),
snackBarTheme: SnackBarThemeData(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
),
textTheme: const TextTheme(
displayLarge: TextStyle(
fontSize: 32,
fontWeight: FontWeight.w300,
color: Colors.black,
letterSpacing: -0.5,
),
displayMedium: TextStyle(
fontSize: 28,
fontWeight: FontWeight.w400,
color: Colors.black,
),
displaySmall: TextStyle(
fontSize: 24,
fontWeight: FontWeight.w400,
color: Colors.black,
),
headlineMedium: TextStyle(
fontSize: 20,
fontWeight: FontWeight.w500,
color: Colors.black,
),
bodyLarge: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w400,
color: Colors.black87,
height: 1.5,
),
bodyMedium: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
color: Colors.black87,
height: 1.5,
),
),
useMaterial3: true,
),
home: const QuickActionsDemo(),
);
}
}
class QuickActionsDemo extends StatefulWidget {
const QuickActionsDemo({super.key});
@override
State<QuickActionsDemo> createState() => _QuickActionsDemoState();
}
class _QuickActionsDemoState extends State<QuickActionsDemo> {
String _lastAction = 'No action triggered yet';
String _launchAction = 'No launch action detected';
int _actionCount = 0;
QuickAction? _currentAction;
@override
void initState() {
super.initState();
_restoreLaunchAction();
_setupActionListener();
}
Future<void> _restoreLaunchAction() async {
// Check if app was launched via quick action (home restore)
final launchAction = await QuickActionsManager.getLaunchAction();
if (launchAction != null && mounted) {
setState(() {
_launchAction = '${launchAction.type}: ${launchAction.title}';
_currentAction = launchAction;
_actionCount++;
});
_showActionDetails(launchAction, isLaunchAction: true);
} else if (mounted) {
setState(() {
_launchAction = 'App launched normally';
});
}
}
void _setupActionListener() {
// Listen to quick action stream for actions triggered while app is running
QuickActionsManager.actionStream.listen((action) {
if (mounted) {
setState(() {
_lastAction = '${action.type}: ${action.title}';
_currentAction = action;
_actionCount++;
});
_showActionDetails(action, isLaunchAction: false);
}
});
}
void _showActionDetails(QuickAction action, {required bool isLaunchAction}) {
showDialog(
context: context,
builder: (context) => Dialog(
backgroundColor: Colors.white,
child: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isLaunchAction ? 'App Launched Via Quick Action' : 'Quick Action Triggered',
style: Theme.of(context).textTheme.headlineMedium,
),
const SizedBox(height: 24),
_buildDetailRow('Type', action.type),
const SizedBox(height: 12),
_buildDetailRow('Title', action.title),
if (action.subtitle != null) ...[
const SizedBox(height: 12),
_buildDetailRow('Subtitle', action.subtitle!),
],
if (action.userInfo != null) ...[
const SizedBox(height: 12),
_buildDetailRow('User Info', action.userInfo.toString()),
],
const SizedBox(height: 24),
SizedBox(
width: double.infinity,
child: TextButton(
onPressed: () => Navigator.of(context).pop(),
style: TextButton.styleFrom(
foregroundColor: Colors.black,
side: const BorderSide(color: Colors.black),
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
),
child: const Text('Close'),
),
),
],
),
),
),
);
}
Widget _buildDetailRow(String label, String value) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
label,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
fontWeight: FontWeight.w500,
color: Colors.black54,
),
),
const SizedBox(height: 4),
Text(
value,
style: Theme.of(context).textTheme.bodyLarge,
),
],
);
}
Future<void> _updateActions() async {
await QuickActionsManager.setActions([
QuickAction(
type: 'updated_action',
title: 'Updated Action',
subtitle: 'This was updated dynamically',
iconType: 'UIApplicationShortcutIconTypeUpdate',
iconName: 'ic_update',
),
]);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Quick actions updated'),
backgroundColor: Colors.black87,
behavior: SnackBarBehavior.floating,
),
);
}
}
Future<void> _clearActions() async {
await QuickActionsManager.clearActions();
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Quick actions cleared'),
backgroundColor: Colors.black87,
behavior: SnackBarBehavior.floating,
),
);
}
}
Future<void> _restoreInitialActions() async {
await QuickActionsManager.setActions(_initialQuickActions);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Quick actions restored to initial state'),
backgroundColor: Colors.black87,
behavior: SnackBarBehavior.floating,
),
);
}
}
Future<void> _requestPinShortcut() async {
if (Platform.isAndroid) {
final success = await QuickActionsManager.requestPinShortcut(
QuickAction(
type: 'pinned_feature',
title: 'Pinned Feature',
subtitle: 'Quick access',
iconName: 'ic_pin',
),
);
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(success
? 'Shortcut pinned successfully'
: 'Failed to pin shortcut'),
backgroundColor: Colors.black87,
behavior: SnackBarBehavior.floating,
),
);
}
} else {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('Pinned shortcuts are only available on Android'),
backgroundColor: Colors.black87,
behavior: SnackBarBehavior.floating,
),
);
}
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(
title: const Text('MF Quick Actions'),
),
body: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 32),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'A Flutter plugin for iOS Quick Actions and Android App Shortcuts',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Colors.black54,
),
),
const SizedBox(height: 4),
GestureDetector(
onTap: () async {
final url = Uri.parse('https://github.com/gurkanfikretgunak/masterfabric_flutter_quick_actions');
if (await canLaunchUrl(url)) {
await launchUrl(url, mode: LaunchMode.externalApplication);
}
},
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.only(top: 2),
child: Icon(
Icons.link,
size: 14,
color: Colors.black54,
),
),
const SizedBox(width: 4),
Expanded(
child: Text(
'Repository: github.com/gurkanfikretgunak/masterfabric_flutter_quick_actions',
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.black54,
fontSize: 12,
decoration: TextDecoration.underline,
decorationColor: Colors.black54,
),
softWrap: true,
),
),
],
),
),
const SizedBox(height: 48),
_buildDetailedSection(
'Launch Action',
_launchAction,
'The action that launched the app (if any)',
Icons.launch,
),
const SizedBox(height: 32),
_buildDetailedSection(
'Last Action',
_lastAction,
'Most recent quick action triggered',
Icons.touch_app,
),
const SizedBox(height: 32),
_buildDetailedSection(
'Action Count',
'$_actionCount',
'Total number of actions triggered',
Icons.numbers,
),
const SizedBox(height: 48),
Row(
children: [
Icon(
Icons.settings,
size: 20,
color: Colors.black87,
),
const SizedBox(width: 8),
Text(
'Actions',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
const SizedBox(height: 16),
_buildTextButton(
'Restore Initial Actions',
_restoreInitialActions,
subtitle: 'Reset to first boot state',
),
const SizedBox(height: 12),
_buildTextButton(
'Update Actions Dynamically',
_updateActions,
),
const SizedBox(height: 12),
_buildTextButton(
'Clear Actions',
_clearActions,
),
const SizedBox(height: 12),
_buildTextButton(
'Request Pin Shortcut',
_requestPinShortcut,
subtitle: Platform.isAndroid ? null : 'Android only',
),
const SizedBox(height: 48),
_buildDetailedSection(
'Instructions',
'1. Long-press the app icon on your home screen\n'
'2. You should see 4 quick actions\n'
'3. Tap any action to trigger it\n'
'4. The action details will be displayed',
null,
Icons.info_outline,
),
const SizedBox(height: 32),
_buildDetailedSection(
'Platform Information',
Platform.isIOS
? 'iOS: Quick actions use system icon types. Supports iOS 12.0+ with 3D Touch or Haptic Touch.'
: Platform.isAndroid
? 'Android: App shortcuts use drawable resources. Requires API level 25+ (Android 7.1+). Pinned shortcuts available on Android 8.0+.'
: 'Quick actions are only available on iOS and Android platforms.',
null,
Platform.isIOS ? Icons.phone_iphone : Platform.isAndroid ? Icons.phone_android : Icons.devices,
),
if (_currentAction != null) ...[
const SizedBox(height: 48),
Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.03),
borderRadius: BorderRadius.circular(7),
border: Border.all(color: Colors.black.withValues(alpha: 0.1), width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Icon(
Icons.details,
size: 20,
color: Colors.black87,
),
const SizedBox(width: 8),
Text(
'Current Action Details',
style: Theme.of(context).textTheme.headlineMedium,
),
],
),
const SizedBox(height: 16),
_buildDetailRow('Type', _currentAction!.type),
const SizedBox(height: 12),
_buildDetailRow('Title', _currentAction!.title),
if (_currentAction!.subtitle != null) ...[
const SizedBox(height: 12),
_buildDetailRow('Subtitle', _currentAction!.subtitle!),
],
if (_currentAction!.iconType != null) ...[
const SizedBox(height: 12),
_buildDetailRow('Icon Type (iOS)', _currentAction!.iconType!),
],
if (_currentAction!.iconName != null) ...[
const SizedBox(height: 12),
_buildDetailRow('Icon Name (Android)', _currentAction!.iconName!),
],
if (_currentAction!.userInfo != null) ...[
const SizedBox(height: 12),
_buildDetailRow('User Info', _currentAction!.userInfo.toString()),
],
],
),
),
],
],
),
),
);
}
Widget _buildDetailedSection(
String title,
String content,
String? subtitle,
IconData? icon,
) {
return Container(
padding: const EdgeInsets.all(20),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(7),
border: Border.all(color: Colors.black.withValues(alpha: 0.1), width: 1),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
if (icon != null) ...[
Icon(
icon,
size: 20,
color: Colors.black87,
),
const SizedBox(width: 8),
],
Expanded(
child: Text(
title,
style: Theme.of(context).textTheme.headlineMedium,
),
),
],
),
if (subtitle != null) ...[
const SizedBox(height: 8),
Text(
subtitle,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.black54,
fontSize: 13,
),
),
],
const SizedBox(height: 12),
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.black.withValues(alpha: 0.02),
borderRadius: BorderRadius.circular(7),
),
child: Text(
content,
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
fontWeight: FontWeight.w500,
),
),
),
],
),
);
}
Widget _buildTextButton(String text, VoidCallback onPressed, {String? subtitle}) {
return SizedBox(
width: double.infinity,
child: TextButton(
onPressed: onPressed,
style: TextButton.styleFrom(
foregroundColor: Colors.black,
side: const BorderSide(color: Colors.black, width: 1),
padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 24),
alignment: Alignment.centerLeft,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(7),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
text,
style: Theme.of(context).textTheme.bodyLarge,
),
if (subtitle != null) ...[
const SizedBox(height: 4),
Text(
subtitle,
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
color: Colors.black54,
),
),
],
],
),
),
);
}
@override
void dispose() {
QuickActionsManager.dispose();
super.dispose();
}
}