Add settings manager and move some things around

This commit is contained in:
Nutcake 2023-05-03 21:55:34 +02:00
parent bc57b20219
commit 9ebe4fc63d
26 changed files with 383 additions and 121 deletions

BIN
assets/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

BIN
assets/images/logo512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -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';

View file

@ -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 {

View file

@ -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 {

View file

@ -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<ClientHolder>();
}
@ -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;
}

View file

@ -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';

View file

@ -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<void> 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<void> changeSettings(Settings newSettings) async {
_currentSettings = newSettings;
await _storage.write(key: _settingsKey, value: jsonEncode(newSettings.toMap()));
}
}

View file

@ -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<ContactsPlusPlus> createState() => _ContactsPlusPlusState();
@ -27,6 +35,7 @@ class _ContactsPlusPlusState extends State<ContactsPlusPlus> {
@override
Widget build(BuildContext context) {
return ClientHolder(
settingsClient: widget.settingsClient,
authenticationData: _authData,
child: MaterialApp(
debugShowCheckedModeBanner: false,
@ -40,6 +49,41 @@ class _ContactsPlusPlusState extends State<ContactsPlusPlus> {
const FriendsList() :
LoginScreen(
onLoginSuccessful: (AuthenticationData authData) async {
final notificationManager = FlutterLocalNotificationsPlugin();
final settings = widget.settingsClient.currentSettings;
final platformNotifications = notificationManager.resolvePlatformSpecificImplementation<AndroidFlutterLocalNotificationsPlugin>();
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;

View file

@ -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';

View file

@ -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,
);
}
}

View file

@ -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"),
),
],
),
),
);
}
}

View file

@ -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();
},
);

View file

@ -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<FriendsList> {
}
void _refreshFriendsList() {
_friendsFuture = FriendApi.getFriendsList(_clientHolder!.client).then((Iterable<Friend> value) async {
final unreadMessages = await MessageApi.getUserMessages(_clientHolder!.client, unreadOnly: true);
_friendsFuture = FriendApi.getFriendsList(_clientHolder!.apiClient).then((Iterable<Friend> 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<FriendsList> {
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<FriendsList> {
);
} 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();

View file

@ -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';

View file

@ -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<MessageAudioPlayer> {
),
),
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),
),

View file

@ -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),
),

View file

@ -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<StatefulWidget> createState() => _MessagesState();
State<StatefulWidget> createState() => _MessagesListState();
}
class _MessagesState extends State<Messages> {
class _MessagesListState extends State<MessagesList> {
Future<MessageCache>? _messageCacheFuture;
final TextEditingController _messageTextController = TextEditingController();
final ScrollController _sessionListScrollController = ScrollController();
@ -79,8 +78,6 @@ class _MessagesState extends State<Messages> {
_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<Messages> {
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<Messages> {
},
);
} 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(

View file

@ -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",
);
},
)
],
),

View file

@ -7,9 +7,13 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_linux/flutter_secure_storage_linux_plugin.h>
#include <url_launcher_linux/url_launcher_plugin.h>
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);
}

View file

@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_linux
url_launcher_linux
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST

View file

@ -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"

View file

@ -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

View file

@ -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);
});
}

View file

@ -7,8 +7,11 @@
#include "generated_plugin_registrant.h"
#include <flutter_secure_storage_windows/flutter_secure_storage_windows_plugin.h>
#include <url_launcher_windows/url_launcher_windows.h>
void RegisterPlugins(flutter::PluginRegistry* registry) {
FlutterSecureStorageWindowsPluginRegisterWithRegistrar(
registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin"));
UrlLauncherWindowsRegisterWithRegistrar(
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
}

View file

@ -4,6 +4,7 @@
list(APPEND FLUTTER_PLUGIN_LIST
flutter_secure_storage_windows
url_launcher_windows
)
list(APPEND FLUTTER_FFI_PLUGIN_LIST