diff --git a/assets/images/logo.png b/assets/images/logo.png new file mode 100644 index 0000000..cd4cde0 Binary files /dev/null and b/assets/images/logo.png differ diff --git a/assets/images/logo512.png b/assets/images/logo512.png new file mode 100644 index 0000000..5c5a1df Binary files /dev/null and b/assets/images/logo512.png differ diff --git a/lib/apis/friend_api.dart b/lib/apis/friend_api.dart index e67cdeb..4402086 100644 --- a/lib/apis/friend_api.dart +++ b/lib/apis/friend_api.dart @@ -1,7 +1,7 @@ import 'dart:convert'; -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/models/friend.dart'; import 'package:contacts_plus_plus/models/user.dart'; diff --git a/lib/apis/message_api.dart b/lib/apis/message_api.dart index 0ab86a2..ff17eaf 100644 --- a/lib/apis/message_api.dart +++ b/lib/apis/message_api.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/models/message.dart'; class MessageApi { diff --git a/lib/apis/user_api.dart b/lib/apis/user_api.dart index 6c99758..53f29dd 100644 --- a/lib/apis/user_api.dart +++ b/lib/apis/user_api.dart @@ -1,6 +1,6 @@ import 'dart:convert'; -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/models/user.dart'; class UserApi { diff --git a/lib/api_client.dart b/lib/clients/api_client.dart similarity index 89% rename from lib/api_client.dart rename to lib/clients/api_client.dart index eea12d8..daededb 100644 --- a/lib/api_client.dart +++ b/lib/clients/api_client.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'dart:convert'; -import 'package:contacts_plus_plus/neos_hub.dart'; +import 'package:contacts_plus_plus/clients/neos_hub.dart'; +import 'package:contacts_plus_plus/clients/settings_client.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; @@ -9,7 +10,7 @@ import 'package:http/http.dart' as http; import 'package:contacts_plus_plus/models/authentication_data.dart'; import 'package:uuid/uuid.dart'; -import 'config.dart'; +import '../config.dart'; class ApiClient { static const String userIdKey = "userId"; @@ -144,15 +145,19 @@ class ApiClient { } class ClientHolder extends InheritedWidget { - final ApiClient client; + final ApiClient apiClient; + final SettingsClient settingsClient; late final NeosHub hub; - ClientHolder({super.key, required AuthenticationData authenticationData, required super.child}) - : client = ApiClient(authenticationData: authenticationData) { - hub = NeosHub(apiClient: client); + ClientHolder({ + super.key, + required AuthenticationData authenticationData, + required this.settingsClient, + required super.child + }) : apiClient = ApiClient(authenticationData: authenticationData) { + hub = NeosHub(apiClient: apiClient); } - static ClientHolder? maybeOf(BuildContext context) { return context.dependOnInheritedWidgetOfExactType(); } @@ -164,5 +169,8 @@ class ClientHolder extends InheritedWidget { } @override - bool updateShouldNotify(covariant ClientHolder oldWidget) => oldWidget.client != client; + bool updateShouldNotify(covariant ClientHolder oldWidget) => + oldWidget.apiClient != apiClient + || oldWidget.settingsClient != settingsClient + || oldWidget.hub != hub; } diff --git a/lib/neos_hub.dart b/lib/clients/neos_hub.dart similarity index 99% rename from lib/neos_hub.dart rename to lib/clients/neos_hub.dart index d65033c..4464d7c 100644 --- a/lib/neos_hub.dart +++ b/lib/clients/neos_hub.dart @@ -3,7 +3,7 @@ import 'dart:io'; import 'package:contacts_plus_plus/apis/message_api.dart'; import 'package:http/http.dart' as http; -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/config.dart'; import 'package:contacts_plus_plus/models/message.dart'; import 'package:logging/logging.dart'; diff --git a/lib/clients/settings_client.dart b/lib/clients/settings_client.dart new file mode 100644 index 0000000..3585cf5 --- /dev/null +++ b/lib/clients/settings_client.dart @@ -0,0 +1,28 @@ +import 'dart:convert'; + +import 'package:contacts_plus_plus/models/settings.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; + + +class SettingsClient { + static const String _settingsKey = "settings"; + static const _storage = FlutterSecureStorage(); + Settings _currentSettings = Settings.def(); + + Settings get currentSettings => _currentSettings; + + Future loadSettings() async { + final data = await _storage.read(key: _settingsKey); + if (data == null) return; + try { + _currentSettings = Settings.fromMap(jsonDecode(data)); + } catch (_) { + _storage.delete(key: _settingsKey); + } + } + + Future changeSettings(Settings newSettings) async { + _currentSettings = newSettings; + await _storage.write(key: _settingsKey, value: jsonEncode(newSettings.toMap())); + } +} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 2c3abfd..b387409 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,20 +1,28 @@ import 'dart:developer'; +import 'package:contacts_plus_plus/clients/settings_client.dart'; import 'package:contacts_plus_plus/widgets/friends_list.dart'; import 'package:contacts_plus_plus/widgets/login_screen.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_phoenix/flutter_phoenix.dart'; +import 'package:flutter_secure_storage/flutter_secure_storage.dart'; import 'package:logging/logging.dart'; -import 'api_client.dart'; +import 'clients/api_client.dart'; import 'models/authentication_data.dart'; -void main() { +void main() async { Logger.root.onRecord.listen((event) => log(event.message, name: event.loggerName)); - runApp(Phoenix(child: const ContactsPlusPlus())); + WidgetsFlutterBinding.ensureInitialized(); + final settingsClient = SettingsClient(); + await settingsClient.loadSettings(); + runApp(Phoenix(child: ContactsPlusPlus(settingsClient: settingsClient,))); } class ContactsPlusPlus extends StatefulWidget { - const ContactsPlusPlus({super.key}); + const ContactsPlusPlus({required this.settingsClient, super.key}); + + final SettingsClient settingsClient; @override State createState() => _ContactsPlusPlusState(); @@ -27,6 +35,7 @@ class _ContactsPlusPlusState extends State { @override Widget build(BuildContext context) { return ClientHolder( + settingsClient: widget.settingsClient, authenticationData: _authData, child: MaterialApp( debugShowCheckedModeBanner: false, @@ -40,6 +49,41 @@ class _ContactsPlusPlusState extends State { const FriendsList() : LoginScreen( onLoginSuccessful: (AuthenticationData authData) async { + final notificationManager = FlutterLocalNotificationsPlugin(); + final settings = widget.settingsClient.currentSettings; + final platformNotifications = notificationManager.resolvePlatformSpecificImplementation(); + if (!settings.notificationsDenied && !(await platformNotifications?.areNotificationsEnabled() ?? true)) { + if (context.mounted) { + await showDialog( + context: context, + builder: (context) { + return AlertDialog( + title: Text("This app needs to ask your permission to send background notifications."), + content: Text("Are you okay with that?"), + actions: [ + TextButton( + onPressed: () async { + Navigator.of(context).pop(); + await widget.settingsClient.changeSettings(settings.copyWith(notificationsDenied: true)); + }, + child: const Text("No"), + ), + TextButton( + onPressed: () async { + Navigator.of(context).pop(); + await widget.settingsClient.changeSettings(settings.copyWith(notificationsDenied: false)); + await notificationManager.resolvePlatformSpecificImplementation< + AndroidFlutterLocalNotificationsPlugin>() + ?.requestPermission(); + }, + child: const Text("Yes"), + ) + ], + ); + }, + ); + } + } if (authData.isAuthenticated) { setState(() { _authData = authData; diff --git a/lib/models/message.dart b/lib/models/message.dart index a74adde..1695c65 100644 --- a/lib/models/message.dart +++ b/lib/models/message.dart @@ -1,7 +1,7 @@ import 'dart:async'; import 'dart:developer'; -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/apis/message_api.dart'; import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:uuid/uuid.dart'; diff --git a/lib/models/settings.dart b/lib/models/settings.dart index d56e1f8..6b92e80 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -1,5 +1,33 @@ class Settings { - // No settings right now. + static const Settings _defaultSettings = Settings(notificationsDenied: true, unreadCheckIntervalMinutes: 0); + final bool notificationsDenied; + final int unreadCheckIntervalMinutes; + const Settings({required this.notificationsDenied, required this.unreadCheckIntervalMinutes}); + + factory Settings.def() => _defaultSettings; + + factory Settings.fromMap(Map map) { + return Settings( + notificationsDenied: map["notificationsDenied"] ?? _defaultSettings.notificationsDenied, + unreadCheckIntervalMinutes: map["unreadCheckIntervalMinutes"] ?? _defaultSettings.unreadCheckIntervalMinutes, + ); + } + + Map toMap() { + return { + "notificationsDenied": notificationsDenied, + "unreadCheckIntervalMinutes": unreadCheckIntervalMinutes, + }; + } + + Settings copy() => copyWith(); + + Settings copyWith({bool? notificationsDenied, int? unreadCheckIntervalMinutes}) { + return Settings( + notificationsDenied: notificationsDenied ?? this.notificationsDenied, + unreadCheckIntervalMinutes: unreadCheckIntervalMinutes ?? this.unreadCheckIntervalMinutes, + ); + } } \ No newline at end of file diff --git a/lib/widgets/default_error_widget.dart b/lib/widgets/default_error_widget.dart new file mode 100644 index 0000000..714ce89 --- /dev/null +++ b/lib/widgets/default_error_widget.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; + +class DefaultErrorWidget extends StatelessWidget { + const DefaultErrorWidget({required this.message, this.onRetry, super.key}); + + final String message; + final void Function()? onRetry; + + @override + Widget build(BuildContext context) { + return Padding( + padding: const EdgeInsets.symmetric(horizontal: 64, vertical: 128,), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Text("Something went wrong: ", style: Theme + .of(context) + .textTheme + .titleMedium,), + Padding( + padding: const EdgeInsets.all(16), + child: Text(message), + ), + if (onRetry != null) TextButton.icon( + onPressed: onRetry, + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(vertical: 16, horizontal: 16), + ), + icon: const Icon(Icons.refresh), + label: const Text("Retry"), + ), + ], + ), + ), + ); + } + +} \ No newline at end of file diff --git a/lib/widgets/friend_list_tile.dart b/lib/widgets/friend_list_tile.dart index 7233b36..37d3291 100644 --- a/lib/widgets/friend_list_tile.dart +++ b/lib/widgets/friend_list_tile.dart @@ -1,7 +1,7 @@ import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/models/friend.dart'; import 'package:contacts_plus_plus/widgets/generic_avatar.dart'; -import 'package:contacts_plus_plus/widgets/messages.dart'; +import 'package:contacts_plus_plus/widgets/messages_list.dart'; import 'package:flutter/material.dart'; class FriendListTile extends StatelessWidget { @@ -23,7 +23,7 @@ class FriendListTile extends StatelessWidget { title: Text(friend.username), subtitle: Text(friend.userStatus.onlineStatus.name), onTap: () async { - Navigator.of(context).push(MaterialPageRoute(builder: (context) => Messages(friend: friend))); + Navigator.of(context).push(MaterialPageRoute(builder: (context) => MessagesList(friend: friend))); await onTap?.call(); }, ); diff --git a/lib/widgets/friends_list.dart b/lib/widgets/friends_list.dart index b387cdb..d28af06 100644 --- a/lib/widgets/friends_list.dart +++ b/lib/widgets/friends_list.dart @@ -1,10 +1,11 @@ import 'dart:async'; -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/apis/friend_api.dart'; import 'package:contacts_plus_plus/apis/message_api.dart'; import 'package:contacts_plus_plus/models/friend.dart'; import 'package:contacts_plus_plus/models/message.dart'; +import 'package:contacts_plus_plus/widgets/default_error_widget.dart'; import 'package:contacts_plus_plus/widgets/expanding_input_fab.dart'; import 'package:contacts_plus_plus/widgets/friend_list_tile.dart'; import 'package:contacts_plus_plus/widgets/settings_page.dart'; @@ -41,12 +42,12 @@ class _FriendsListState extends State { } void _refreshFriendsList() { - _friendsFuture = FriendApi.getFriendsList(_clientHolder!.client).then((Iterable value) async { - final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.client, unreadOnly: true); + _friendsFuture = FriendApi.getFriendsList(_clientHolder!.apiClient).then((Iterable value) async { + final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.apiClient, unreadOnly: true); _unreads.clear(); for (final msg in unreadMessages) { - if (msg.senderId != _clientHolder!.client.userId) { + if (msg.senderId != _clientHolder!.apiClient.userId) { final value = _unreads[msg.senderId]; if (value == null) { _unreads[msg.senderId] = [msg]; @@ -110,7 +111,7 @@ class _FriendsListState extends State { onTap: () async { if (unread.isNotEmpty) { final readBatch = MarkReadBatch( - senderId: _clientHolder!.client.userId, + senderId: _clientHolder!.apiClient.userId, ids: unread.map((e) => e.id).toList(), readTime: DateTime.now(), ); @@ -125,18 +126,11 @@ class _FriendsListState extends State { ); } else if (snapshot.hasError) { FlutterError.reportError(FlutterErrorDetails(exception: snapshot.error!, stack: snapshot.stackTrace)); - return Center( - child: Padding( - padding: const EdgeInsets.all(64), - child: Text( - "Something went wrong: ${snapshot.error}", - softWrap: true, - style: Theme - .of(context) - .textTheme - .labelMedium, - ), - ), + return DefaultErrorWidget( + message: "${snapshot.error}", + onRetry: () { + _refreshFriendsList(); + }, ); } else { return const SizedBox.shrink(); diff --git a/lib/widgets/login_screen.dart b/lib/widgets/login_screen.dart index d2e70bc..3e74ba4 100644 --- a/lib/widgets/login_screen.dart +++ b/lib/widgets/login_screen.dart @@ -1,4 +1,4 @@ -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/models/authentication_data.dart'; import 'package:flutter/material.dart'; diff --git a/lib/widgets/message_audio_player.dart b/lib/widgets/message_audio_player.dart index 99b85a0..a866842 100644 --- a/lib/widgets/message_audio_player.dart +++ b/lib/widgets/message_audio_player.dart @@ -1,9 +1,9 @@ import 'dart:convert'; -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/models/message.dart'; -import 'package:contacts_plus_plus/widgets/messages.dart'; +import 'package:contacts_plus_plus/widgets/messages_list.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:just_audio/just_audio.dart'; @@ -114,7 +114,7 @@ class _MessageAudioPlayerState extends State { ), ), const SizedBox(width: 4,), - if (widget.message.senderId == ClientHolder.of(context).client.userId) Padding( + if (widget.message.senderId == ClientHolder.of(context).apiClient.userId) Padding( padding: const EdgeInsets.only(right: 12.0), child: MessageStateIndicator(messageState: widget.message.state), ), diff --git a/lib/widgets/message_session_invite.dart b/lib/widgets/message_session_invite.dart index e478c76..24e39b5 100644 --- a/lib/widgets/message_session_invite.dart +++ b/lib/widgets/message_session_invite.dart @@ -1,11 +1,11 @@ import 'dart:convert'; -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/models/message.dart'; import 'package:contacts_plus_plus/models/session.dart'; import 'package:contacts_plus_plus/widgets/generic_avatar.dart'; -import 'package:contacts_plus_plus/widgets/messages.dart'; +import 'package:contacts_plus_plus/widgets/messages_list.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; @@ -68,7 +68,7 @@ class MessageSessionInvite extends StatelessWidget { ), ), const SizedBox(width: 4,), - if (message.senderId == ClientHolder.of(context).client.userId) Padding( + if (message.senderId == ClientHolder.of(context).apiClient.userId) Padding( padding: const EdgeInsets.only(right: 12.0), child: MessageStateIndicator(messageState: message.state), ), diff --git a/lib/widgets/messages.dart b/lib/widgets/messages_list.dart similarity index 92% rename from lib/widgets/messages.dart rename to lib/widgets/messages_list.dart index 0c033a3..7840685 100644 --- a/lib/widgets/messages.dart +++ b/lib/widgets/messages_list.dart @@ -1,27 +1,26 @@ -import 'dart:developer'; - import 'package:cached_network_image/cached_network_image.dart'; -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:contacts_plus_plus/auxiliary.dart'; import 'package:contacts_plus_plus/models/friend.dart'; import 'package:contacts_plus_plus/models/message.dart'; import 'package:contacts_plus_plus/models/session.dart'; +import 'package:contacts_plus_plus/widgets/default_error_widget.dart'; import 'package:contacts_plus_plus/widgets/message_audio_player.dart'; import 'package:contacts_plus_plus/widgets/generic_avatar.dart'; import 'package:contacts_plus_plus/widgets/message_session_invite.dart'; import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; -class Messages extends StatefulWidget { - const Messages({required this.friend, super.key}); +class MessagesList extends StatefulWidget { + const MessagesList({required this.friend, super.key}); final Friend friend; @override - State createState() => _MessagesState(); + State createState() => _MessagesListState(); } -class _MessagesState extends State { +class _MessagesListState extends State { Future? _messageCacheFuture; final TextEditingController _messageTextController = TextEditingController(); final ScrollController _sessionListScrollController = ScrollController(); @@ -79,8 +78,6 @@ class _MessagesState extends State { _messageScrollController.addListener(() { if (_messageScrollController.position.atEdge && _messageScrollController.position.pixels > 0 && _messageScrollController.position.maxScrollExtent > 0 && _messageCacheFutureComplete) { - log("Top edge hit."); - setState(() { _messageCacheFutureComplete = false; _messageCacheFuture = _clientHolder?.hub.getCache(widget.friend.id) @@ -94,7 +91,7 @@ class _MessagesState extends State { Widget build(BuildContext context) { final apiClient = ClientHolder .of(context) - .client; + .apiClient; var sessions = widget.friend.userStatus.activeSessions; final appBarColor = Theme .of(context) @@ -195,38 +192,13 @@ class _MessagesState extends State { }, ); } else if (snapshot.hasError) { - return Padding( - padding: const EdgeInsets.symmetric(horizontal: 64, vertical: 128,), - child: Center( - child: Column( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Text("Failed to load messages:", style: Theme - .of(context) - .textTheme - .titleMedium,), - Padding( - padding: const EdgeInsets.all(16), - child: Text("${snapshot.error}"), - ), - TextButton.icon( - onPressed: () { - setState(() { - _loadMessages(); - }); - }, - style: TextButton.styleFrom( - padding: const EdgeInsets.symmetric( - vertical: 16, horizontal: 16), - ), - icon: const Icon(Icons.refresh), - label: const Text("Retry"), - ), - ], - ), - ), + return DefaultErrorWidget( + message: "${snapshot.error}", + onRetry: () { + setState(() { + _loadMessages(); + }); + }, ); } else { return Column( diff --git a/lib/widgets/settings_page.dart b/lib/widgets/settings_page.dart index 782468f..b8c282c 100644 --- a/lib/widgets/settings_page.dart +++ b/lib/widgets/settings_page.dart @@ -1,5 +1,6 @@ -import 'package:contacts_plus_plus/api_client.dart'; +import 'package:contacts_plus_plus/clients/api_client.dart'; import 'package:flutter/material.dart'; +import 'package:url_launcher/url_launcher.dart'; class SettingsPage extends StatelessWidget { const SettingsPage({super.key}); @@ -38,7 +39,7 @@ class SettingsPage extends StatelessWidget { TextButton(onPressed: () => Navigator.of(context).pop(), child: const Text("No")), TextButton( onPressed: () async { - await ClientHolder.of(context).client.logout(context); + await ClientHolder.of(context).apiClient.logout(context); }, child: const Text("Yes"), ), @@ -46,6 +47,35 @@ class SettingsPage extends StatelessWidget { ), ); }, + ), + ListTile( + shape: Border( + bottom: BorderSide(color: Theme.of(context).colorScheme.secondaryContainer, width: 0.5), + top: BorderSide(color: Theme.of(context).colorScheme.secondaryContainer, width: 0.5) + ), + trailing: const Icon(Icons.info_outline), + title: const Text("About Contacts++"), + onTap: () { + showAboutDialog( + context: context, + applicationVersion: "0.0.1", + applicationIcon: InkWell( + onTap: () async { + if (!await launchUrl(Uri.parse("https://github.com/Nutcake/contacts-plus-plus"), mode: LaunchMode.externalApplication)) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text("Failed to open link."))); + } + } + }, + child: Container( + margin: const EdgeInsets.all(16), + constraints: const BoxConstraints(maxWidth: 64), + child: Image.asset("assets/images/logo512.png"), + ), + ), + applicationLegalese: "Created by Nutcake with love <3", + ); + }, ) ], ), diff --git a/linux/flutter/generated_plugin_registrant.cc b/linux/flutter/generated_plugin_registrant.cc index d0e7f79..38dd0bc 100644 --- a/linux/flutter/generated_plugin_registrant.cc +++ b/linux/flutter/generated_plugin_registrant.cc @@ -7,9 +7,13 @@ #include "generated_plugin_registrant.h" #include +#include void fl_register_plugins(FlPluginRegistry* registry) { g_autoptr(FlPluginRegistrar) flutter_secure_storage_linux_registrar = fl_plugin_registry_get_registrar_for_plugin(registry, "FlutterSecureStorageLinuxPlugin"); flutter_secure_storage_linux_plugin_register_with_registrar(flutter_secure_storage_linux_registrar); + g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar = + fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin"); + url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar); } diff --git a/linux/flutter/generated_plugins.cmake b/linux/flutter/generated_plugins.cmake index b29e9ba..65240e9 100644 --- a/linux/flutter/generated_plugins.cmake +++ b/linux/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_linux + url_launcher_linux ) list(APPEND FLUTTER_FFI_PLUGIN_LIST diff --git a/pubspec.lock b/pubspec.lock index 25250ce..dac875f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -1,6 +1,14 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: + args: + dependency: transitive + description: + name: args + sha256: "4cab82a83ffef80b262ddedf47a0a8e56ee6fbf7fe21e6e768b02792034dd440" + url: "https://pub.dev" + source: hosted + version: "2.4.0" async: dependency: transitive description: @@ -97,6 +105,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.5" + dbus: + dependency: transitive + description: + name: dbus + sha256: "6f07cba3f7b3448d42d015bfd3d53fe12e5b36da2423f23838efc1d5fb31a263" + url: "https://pub.dev" + source: hosted + version: "0.7.8" fake_async: dependency: transitive description: @@ -150,6 +166,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.1" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "2876372952b65ca7f684e698eba22bda1cf581fa071dd30ba2f01900f507d0d1" + url: "https://pub.dev" + source: hosted + version: "14.0.0+1" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "909bb95de05a2e793503a2437146285a2f600cd0b3f826e26b870a334d8586d7" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "63235c42de5b6c99846969a27ad0209c401e6b77b0498939813725b5791c107c" + url: "https://pub.dev" + source: hosted + version: "7.0.0" flutter_phoenix: dependency: "direct main" description: @@ -400,6 +440,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.11.1" + petitparser: + dependency: transitive + description: + name: petitparser + sha256: "49392a45ced973e8d94a85fdb21293fbb40ba805fc49f2965101ae748a3683b4" + url: "https://pub.dev" + source: hosted + version: "5.1.0" platform: dependency: transitive description: @@ -525,6 +573,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.4.16" + timezone: + dependency: transitive + description: + name: timezone + sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0" + url: "https://pub.dev" + source: hosted + version: "0.9.2" tuple: dependency: transitive description: @@ -541,6 +597,70 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + url_launcher: + dependency: "direct main" + description: + name: url_launcher + sha256: "75f2846facd11168d007529d6cd8fcb2b750186bea046af9711f10b907e1587e" + url: "https://pub.dev" + source: hosted + version: "6.1.10" + url_launcher_android: + dependency: transitive + description: + name: url_launcher_android + sha256: "22f8db4a72be26e9e3a4aa3f194b1f7afbc76d20ec141f84be1d787db2155cbd" + url: "https://pub.dev" + source: hosted + version: "6.0.31" + url_launcher_ios: + dependency: transitive + description: + name: url_launcher_ios + sha256: "9af7ea73259886b92199f9e42c116072f05ff9bea2dcb339ab935dfc957392c2" + url: "https://pub.dev" + source: hosted + version: "6.1.4" + url_launcher_linux: + dependency: transitive + description: + name: url_launcher_linux + sha256: "207f4ddda99b95b4d4868320a352d374b0b7e05eefad95a4a26f57da413443f5" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + url_launcher_macos: + dependency: transitive + description: + name: url_launcher_macos + sha256: "91ee3e75ea9dadf38036200c5d3743518f4a5eb77a8d13fda1ee5764373f185e" + url: "https://pub.dev" + source: hosted + version: "3.0.5" + url_launcher_platform_interface: + dependency: transitive + description: + name: url_launcher_platform_interface + sha256: "6c9ca697a5ae218ce56cece69d46128169a58aa8653c1b01d26fcd4aad8c4370" + url: "https://pub.dev" + source: hosted + version: "2.1.2" + url_launcher_web: + dependency: transitive + description: + name: url_launcher_web + sha256: "81fe91b6c4f84f222d186a9d23c73157dc4c8e1c71489c4d08be1ad3b228f1aa" + url: "https://pub.dev" + source: hosted + version: "2.0.16" + url_launcher_windows: + dependency: transitive + description: + name: url_launcher_windows + sha256: "254708f17f7c20a9c8c471f67d86d76d4a3f9c1591aad1e15292008aceb82771" + url: "https://pub.dev" + source: hosted + version: "3.0.6" uuid: dependency: "direct main" description: @@ -573,6 +693,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.4" + workmanager: + dependency: "direct main" + description: + name: workmanager + sha256: e0be7e35d644643f164ee45d2ce14414f0e0fdde19456aa66065f35a0b1d2ea1 + url: "https://pub.dev" + source: hosted + version: "0.5.1" xdg_directories: dependency: transitive description: @@ -581,6 +709,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + xml: + dependency: transitive + description: + name: xml + sha256: "979ee37d622dec6365e2efa4d906c37470995871fe9ae080d967e192d88286b5" + url: "https://pub.dev" + source: hosted + version: "6.2.2" sdks: dart: ">=2.19.6 <3.0.0" flutter: ">=3.3.0" diff --git a/pubspec.yaml b/pubspec.yaml index fbe30a8..c258a6a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -47,6 +47,9 @@ dependencies: html: ^0.15.2 just_audio: ^0.9.32 flutter_phoenix: ^1.1.1 + url_launcher: ^6.1.10 + workmanager: ^0.5.1 + flutter_local_notifications: ^14.0.0+1 dev_dependencies: flutter_test: @@ -71,9 +74,8 @@ flutter: uses-material-design: true # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg + assets: + - assets/images/ # An image asset can refer to one or more resolution-specific "variants", see # https://flutter.dev/assets-and-images/#resolution-aware diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index 174c813..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:contacts_plus_plus/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const ContactsPlusPlus()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 0c50753..2048c45 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -7,8 +7,11 @@ #include "generated_plugin_registrant.h" #include +#include void RegisterPlugins(flutter::PluginRegistry* registry) { FlutterSecureStorageWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); + UrlLauncherWindowsRegisterWithRegistrar( + registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index 4fc759c..de626cc 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -4,6 +4,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_secure_storage_windows + url_launcher_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST