Allow OS notifications
This commit is contained in:
parent
98355c3707
commit
cc6fda0e79
|
|
@ -13,6 +13,9 @@ android {
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
sourceCompatibility = JavaVersion.VERSION_17
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
targetCompatibility = JavaVersion.VERSION_17
|
||||||
|
// required by flutter_local_notifications and other libraries using
|
||||||
|
// Java 8+ APIs at runtime
|
||||||
|
isCoreLibraryDesugaringEnabled = true
|
||||||
}
|
}
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
|
|
@ -37,6 +40,14 @@ android {
|
||||||
signingConfig = signingConfigs.getByName("debug")
|
signingConfig = signingConfigs.getByName("debug")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// allow desugaring of Java 8+ library APIs
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// dependencies section for additional compile-only libraries
|
||||||
|
dependencies {
|
||||||
|
coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.4")
|
||||||
}
|
}
|
||||||
|
|
||||||
flutter {
|
flutter {
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,10 @@
|
||||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<!-- Required on Android 13+ to post notifications. Without this the system
|
||||||
|
will automatically block notifications and the user cannot enable them
|
||||||
|
from settings. -->
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
|
||||||
<application
|
<application
|
||||||
android:label="tasq"
|
android:label="tasq"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,8 @@ import 'package:flutter_dotenv/flutter_dotenv.dart';
|
||||||
import 'app.dart';
|
import 'app.dart';
|
||||||
import 'providers/notifications_provider.dart';
|
import 'providers/notifications_provider.dart';
|
||||||
import 'utils/app_time.dart';
|
import 'utils/app_time.dart';
|
||||||
|
import 'utils/notification_permission.dart';
|
||||||
|
import 'services/notification_service.dart';
|
||||||
|
|
||||||
Future<void> main() async {
|
Future<void> main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
@ -25,6 +27,29 @@ Future<void> main() async {
|
||||||
|
|
||||||
await Supabase.initialize(url: supabaseUrl, anonKey: supabaseAnonKey);
|
await Supabase.initialize(url: supabaseUrl, anonKey: supabaseAnonKey);
|
||||||
|
|
||||||
|
// on Android 13+ we must request POST_NOTIFICATIONS at runtime; without it
|
||||||
|
// notifications are automatically denied and cannot be re‑enabled from the
|
||||||
|
// system settings. The helper uses `permission_handler`.
|
||||||
|
final granted = await ensureNotificationPermission();
|
||||||
|
if (!granted) {
|
||||||
|
// we don’t block startup, but it’s worth logging so developers notice. A
|
||||||
|
// real app might show a dialog pointing the user to settings.
|
||||||
|
// debugPrint('notification permission not granted');
|
||||||
|
}
|
||||||
|
|
||||||
|
// initialize the local notifications plugin so we can post alerts later
|
||||||
|
await NotificationService.initialize(
|
||||||
|
onDidReceiveNotificationResponse: (response) {
|
||||||
|
// handle user tapping a notification; the payload format is up to us
|
||||||
|
final payload = response.payload;
|
||||||
|
if (payload != null && payload.startsWith('ticket:')) {
|
||||||
|
// ignore if context not mounted; we might use a navigator key in real
|
||||||
|
// app, but keep this simple for now
|
||||||
|
// TODO: navigate to ticket/task as appropriate
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
runApp(
|
runApp(
|
||||||
ProviderScope(
|
ProviderScope(
|
||||||
observers: [NotificationSoundObserver()],
|
observers: [NotificationSoundObserver()],
|
||||||
|
|
@ -48,6 +73,12 @@ class NotificationSoundObserver extends ProviderObserver {
|
||||||
final next = newValue as int?;
|
final next = newValue as int?;
|
||||||
if (prev != null && next != null && next > prev) {
|
if (prev != null && next != null && next > prev) {
|
||||||
_player.play(AssetSource('tasq_notification.wav'));
|
_player.play(AssetSource('tasq_notification.wav'));
|
||||||
|
// also post a system notification so the user sees it outside the app
|
||||||
|
NotificationService.show(
|
||||||
|
id: DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
|
title: 'New notifications',
|
||||||
|
body: 'You have $next unread notifications.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ import '../screens/dashboard/dashboard_screen.dart';
|
||||||
import '../screens/notifications/notifications_screen.dart';
|
import '../screens/notifications/notifications_screen.dart';
|
||||||
import '../screens/profile/profile_screen.dart';
|
import '../screens/profile/profile_screen.dart';
|
||||||
import '../screens/shared/under_development_screen.dart';
|
import '../screens/shared/under_development_screen.dart';
|
||||||
|
import '../screens/shared/permissions_screen.dart';
|
||||||
import '../screens/tasks/task_detail_screen.dart';
|
import '../screens/tasks/task_detail_screen.dart';
|
||||||
import '../screens/tasks/tasks_list_screen.dart';
|
import '../screens/tasks/tasks_list_screen.dart';
|
||||||
import '../screens/tickets/ticket_detail_screen.dart';
|
import '../screens/tickets/ticket_detail_screen.dart';
|
||||||
|
|
@ -138,6 +139,10 @@ final appRouterProvider = Provider<GoRouter>((ref) {
|
||||||
path: '/settings/geofence-test',
|
path: '/settings/geofence-test',
|
||||||
builder: (context, state) => const GeofenceTestScreen(),
|
builder: (context, state) => const GeofenceTestScreen(),
|
||||||
),
|
),
|
||||||
|
GoRoute(
|
||||||
|
path: '/settings/permissions',
|
||||||
|
builder: (context, state) => const PermissionsScreen(),
|
||||||
|
),
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: '/notifications',
|
path: '/notifications',
|
||||||
builder: (context, state) => const NotificationsScreen(),
|
builder: (context, state) => const NotificationsScreen(),
|
||||||
|
|
|
||||||
94
lib/screens/shared/permissions_screen.dart
Normal file
94
lib/screens/shared/permissions_screen.dart
Normal file
|
|
@ -0,0 +1,94 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
|
import '../../services/permission_service.dart';
|
||||||
|
|
||||||
|
/// A simple screen showing every permission the app declares in
|
||||||
|
/// [appPermissions] along with its current status. Users can tap "Grant" to
|
||||||
|
/// request a permission, or open the system settings if it has been permanently
|
||||||
|
/// denied.
|
||||||
|
class PermissionsScreen extends ConsumerStatefulWidget {
|
||||||
|
const PermissionsScreen({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
ConsumerState<PermissionsScreen> createState() => _PermissionsScreenState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _PermissionsScreenState extends ConsumerState<PermissionsScreen> {
|
||||||
|
late Map<Permission, PermissionStatus> _statuses;
|
||||||
|
bool _loading = true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_refreshStatuses();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _refreshStatuses() async {
|
||||||
|
setState(() => _loading = true);
|
||||||
|
final statuses = await getAllStatuses();
|
||||||
|
setState(() {
|
||||||
|
_statuses = statuses;
|
||||||
|
_loading = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _request(Permission permission) async {
|
||||||
|
final status = await requestPermission(permission);
|
||||||
|
setState(() {
|
||||||
|
_statuses[permission] = status;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (_loading) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Permissions')),
|
||||||
|
body: const Center(child: CircularProgressIndicator()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(title: const Text('Permissions')),
|
||||||
|
body: ListView.separated(
|
||||||
|
padding: const EdgeInsets.all(16),
|
||||||
|
itemCount: appPermissions.length,
|
||||||
|
separatorBuilder: (context, index) => const Divider(),
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final info = appPermissions[index];
|
||||||
|
final status = _statuses[info.permission];
|
||||||
|
final granted = status?.isGranted == true;
|
||||||
|
final permanent = status?.isPermanentlyDenied == true;
|
||||||
|
|
||||||
|
return ListTile(
|
||||||
|
title: Text(info.label),
|
||||||
|
subtitle: Text(status?.toString() ?? 'unknown'),
|
||||||
|
trailing: granted
|
||||||
|
? const Icon(Icons.check, color: Colors.green)
|
||||||
|
: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
if (permanent)
|
||||||
|
TextButton(
|
||||||
|
onPressed: openAppSettings,
|
||||||
|
child: const Text('Settings'),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () => _request(info.permission),
|
||||||
|
child: const Text('Grant'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
floatingActionButton: FloatingActionButton(
|
||||||
|
onPressed: _refreshStatuses,
|
||||||
|
tooltip: 'Refresh',
|
||||||
|
child: const Icon(Icons.refresh),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
56
lib/services/notification_service.dart
Normal file
56
lib/services/notification_service.dart
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
|
||||||
|
|
||||||
|
/// A thin wrapper around `flutter_local_notifications` that centralizes the
|
||||||
|
/// plugin instance and provides easy initialization/show helpers.
|
||||||
|
class NotificationService {
|
||||||
|
NotificationService._();
|
||||||
|
|
||||||
|
static final FlutterLocalNotificationsPlugin _plugin =
|
||||||
|
FlutterLocalNotificationsPlugin();
|
||||||
|
|
||||||
|
/// Call during app startup, after any necessary permissions have been
|
||||||
|
/// granted. The callback receives a [NotificationResponse] when the user
|
||||||
|
/// taps the alert.
|
||||||
|
static Future<void> initialize({
|
||||||
|
void Function(NotificationResponse)? onDidReceiveNotificationResponse,
|
||||||
|
}) async {
|
||||||
|
const androidSettings = AndroidInitializationSettings(
|
||||||
|
'@mipmap/ic_launcher',
|
||||||
|
);
|
||||||
|
const iosSettings = DarwinInitializationSettings();
|
||||||
|
|
||||||
|
await _plugin.initialize(
|
||||||
|
settings: const InitializationSettings(
|
||||||
|
android: androidSettings,
|
||||||
|
iOS: iosSettings,
|
||||||
|
),
|
||||||
|
onDidReceiveNotificationResponse: onDidReceiveNotificationResponse,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Show a simple notification immediately.
|
||||||
|
static Future<void> show({
|
||||||
|
required int id,
|
||||||
|
required String title,
|
||||||
|
required String body,
|
||||||
|
String? payload,
|
||||||
|
}) async {
|
||||||
|
const androidDetails = AndroidNotificationDetails(
|
||||||
|
'default_channel',
|
||||||
|
'General',
|
||||||
|
importance: Importance.high,
|
||||||
|
priority: Priority.high,
|
||||||
|
);
|
||||||
|
const iosDetails = DarwinNotificationDetails();
|
||||||
|
await _plugin.show(
|
||||||
|
id: id,
|
||||||
|
title: title,
|
||||||
|
body: body,
|
||||||
|
notificationDetails: const NotificationDetails(
|
||||||
|
android: androidDetails,
|
||||||
|
iOS: iosDetails,
|
||||||
|
),
|
||||||
|
payload: payload,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
66
lib/services/permission_service.dart
Normal file
66
lib/services/permission_service.dart
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
|
/// Information about a permission that the application cares about.
|
||||||
|
///
|
||||||
|
/// Add new entries to [appPermissions] when the app requires additional
|
||||||
|
/// runtime permissions. The UI that drives the "permissions screen" reads this
|
||||||
|
/// list and will automatically include new permissions without any further
|
||||||
|
/// changes.
|
||||||
|
class PermissionInfo {
|
||||||
|
const PermissionInfo({
|
||||||
|
required this.permission,
|
||||||
|
required this.label,
|
||||||
|
this.description,
|
||||||
|
});
|
||||||
|
|
||||||
|
final Permission permission;
|
||||||
|
final String label;
|
||||||
|
final String? description;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The set of permissions the app will check/request from the user.
|
||||||
|
///
|
||||||
|
/// Currently contains the permissions actually referenced in the codebase, but
|
||||||
|
/// it should be expanded as new features are added. Declaring a permission
|
||||||
|
/// here ensures it appears on the permissions screen and is considered when
|
||||||
|
/// performing bulk checks/requests.
|
||||||
|
const List<PermissionInfo> appPermissions = [
|
||||||
|
PermissionInfo(
|
||||||
|
permission: Permission.notification,
|
||||||
|
label: 'Notifications',
|
||||||
|
description: 'Deliver alerts and sounds',
|
||||||
|
),
|
||||||
|
PermissionInfo(
|
||||||
|
permission: Permission.location,
|
||||||
|
label: 'Location',
|
||||||
|
description: 'Access your location for geofencing',
|
||||||
|
),
|
||||||
|
// add new permissions here as necessary
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Returns the current status for every permission in [appPermissions].
|
||||||
|
Future<Map<Permission, PermissionStatus>> getAllStatuses() async {
|
||||||
|
final map = <Permission, PermissionStatus>{};
|
||||||
|
for (final info in appPermissions) {
|
||||||
|
map[info.permission] = await info.permission.status;
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Requests [permission] and returns the resulting status.
|
||||||
|
Future<PermissionStatus> requestPermission(Permission permission) async {
|
||||||
|
return permission.request();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Convenience that requests every permission that is currently not granted.
|
||||||
|
///
|
||||||
|
/// Returns the full status map after requesting.
|
||||||
|
Future<Map<Permission, PermissionStatus>> requestAllNeeded() async {
|
||||||
|
final statuses = await getAllStatuses();
|
||||||
|
for (final entry in statuses.entries) {
|
||||||
|
if (!entry.value.isGranted) {
|
||||||
|
statuses[entry.key] = await entry.key.request();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return statuses;
|
||||||
|
}
|
||||||
23
lib/utils/notification_permission.dart
Normal file
23
lib/utils/notification_permission.dart
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
import 'package:permission_handler/permission_handler.dart';
|
||||||
|
|
||||||
|
import '../services/permission_service.dart';
|
||||||
|
|
||||||
|
/// Helpers for requesting and checking the platform notification permission.
|
||||||
|
///
|
||||||
|
/// This file exists mostly for backwards‑compatibility; the real logic lives in
|
||||||
|
/// [permission_service].
|
||||||
|
|
||||||
|
Future<PermissionStatus> requestNotificationPermission() async {
|
||||||
|
return requestPermission(Permission.notification);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> ensureNotificationPermission() async {
|
||||||
|
final status = await Permission.notification.status;
|
||||||
|
if (status.isGranted) return true;
|
||||||
|
if (status.isDenied || status.isRestricted || status.isLimited) {
|
||||||
|
final newStatus = await requestNotificationPermission();
|
||||||
|
return newStatus.isGranted;
|
||||||
|
}
|
||||||
|
// permanently denied requires user to open settings
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
@ -387,6 +387,12 @@ List<NavSection> _buildSections(String role) {
|
||||||
icon: Icons.groups_2_outlined,
|
icon: Icons.groups_2_outlined,
|
||||||
selectedIcon: Icons.groups_2,
|
selectedIcon: Icons.groups_2,
|
||||||
),
|
),
|
||||||
|
NavItem(
|
||||||
|
label: 'Permissions',
|
||||||
|
route: '/settings/permissions',
|
||||||
|
icon: Icons.lock_open,
|
||||||
|
selectedIcon: Icons.lock,
|
||||||
|
),
|
||||||
NavItem(
|
NavItem(
|
||||||
label: 'Logout',
|
label: 'Logout',
|
||||||
route: '',
|
route: '',
|
||||||
|
|
@ -398,7 +404,23 @@ List<NavSection> _buildSections(String role) {
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
return [NavSection(label: 'Operations', items: mainItems)];
|
// non-admin users still get a simple Settings section containing only
|
||||||
|
// permissions. this keeps the screen accessible without exposing the
|
||||||
|
// administrative management screens.
|
||||||
|
return [
|
||||||
|
NavSection(label: 'Operations', items: mainItems),
|
||||||
|
NavSection(
|
||||||
|
label: 'Settings',
|
||||||
|
items: [
|
||||||
|
NavItem(
|
||||||
|
label: 'Permissions',
|
||||||
|
route: '/settings/permissions',
|
||||||
|
icon: Icons.lock_open,
|
||||||
|
selectedIcon: Icons.lock,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
List<NavItem> _standardNavItems() {
|
List<NavItem> _standardNavItems() {
|
||||||
|
|
|
||||||
84
pubspec.lock
84
pubspec.lock
|
|
@ -446,6 +446,38 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.0"
|
version: "6.0.0"
|
||||||
|
flutter_local_notifications:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications
|
||||||
|
sha256: "2b50e938a275e1ad77352d6a25e25770f4130baa61eaf02de7a9a884680954ad"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "20.1.0"
|
||||||
|
flutter_local_notifications_linux:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_linux
|
||||||
|
sha256: dce0116868cedd2cdf768af0365fc37ff1cbef7c02c4f51d0587482e625868d0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "7.0.0"
|
||||||
|
flutter_local_notifications_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_platform_interface
|
||||||
|
sha256: "23de31678a48c084169d7ae95866df9de5c9d2a44be3e5915a2ff067aeeba899"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "10.0.0"
|
||||||
|
flutter_local_notifications_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: flutter_local_notifications_windows
|
||||||
|
sha256: e97a1a3016512437d9c0b12fae7d1491c3c7b9aa7f03a69b974308840656b02a
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.1"
|
||||||
flutter_localizations:
|
flutter_localizations:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description: flutter
|
description: flutter
|
||||||
|
|
@ -893,6 +925,54 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.0.4"
|
version: "1.0.4"
|
||||||
|
permission_handler:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: permission_handler
|
||||||
|
sha256: bc917da36261b00137bbc8896bf1482169cd76f866282368948f032c8c1caae1
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "12.0.1"
|
||||||
|
permission_handler_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_android
|
||||||
|
sha256: "1e3bc410ca1bf84662104b100eb126e066cb55791b7451307f9708d4007350e6"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "13.0.1"
|
||||||
|
permission_handler_apple:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_apple
|
||||||
|
sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "9.4.7"
|
||||||
|
permission_handler_html:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_html
|
||||||
|
sha256: "38f000e83355abb3392140f6bc3030660cfaef189e1f87824facb76300b4ff24"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.1.3+5"
|
||||||
|
permission_handler_platform_interface:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_platform_interface
|
||||||
|
sha256: eb99b295153abce5d683cac8c02e22faab63e50679b937fa1bf67d58bb282878
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.3.0"
|
||||||
|
permission_handler_windows:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: permission_handler_windows
|
||||||
|
sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.1"
|
||||||
petitparser:
|
petitparser:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1230,10 +1310,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: timezone
|
name: timezone
|
||||||
sha256: "2236ec079a174ce07434e89fcd3fcda430025eb7692244139a9cf54fdcf1fc7d"
|
sha256: dd14a3b83cfd7cb19e7888f1cbc20f258b8d71b54c06f79ac585f14093a287d1
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.9.4"
|
version: "0.10.1"
|
||||||
typed_data:
|
typed_data:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ dependencies:
|
||||||
google_fonts: ^6.2.1
|
google_fonts: ^6.2.1
|
||||||
audioplayers: ^6.1.0
|
audioplayers: ^6.1.0
|
||||||
geolocator: ^13.0.1
|
geolocator: ^13.0.1
|
||||||
timezone: ^0.9.4
|
timezone: ^0.10.1
|
||||||
flutter_map: ^8.2.2
|
flutter_map: ^8.2.2
|
||||||
latlong2: ^0.9.0
|
latlong2: ^0.9.0
|
||||||
flutter_typeahead: ^4.1.0
|
flutter_typeahead: ^4.1.0
|
||||||
|
|
@ -27,6 +27,8 @@ dependencies:
|
||||||
printing: ^5.10.0
|
printing: ^5.10.0
|
||||||
flutter_keyboard_visibility: ^5.4.1
|
flutter_keyboard_visibility: ^5.4.1
|
||||||
awesome_snackbar_content: ^0.1.8
|
awesome_snackbar_content: ^0.1.8
|
||||||
|
permission_handler: ^12.0.1
|
||||||
|
flutter_local_notifications: ^20.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user