diff --git a/prototype/devtools_options.yaml b/prototype/devtools_options.yaml new file mode 100644 index 00000000..e3348800 --- /dev/null +++ b/prototype/devtools_options.yaml @@ -0,0 +1,7 @@ +# SPDX-FileCopyrightText: 2024 Phoenix R&D GmbH +# +# SPDX-License-Identifier: AGPL-3.0-or-later + +description: This file stores settings for Dart & Flutter DevTools. +documentation: https://docs.flutter.dev/tools/devtools/extensions#configure-extension-enablement-states +extensions: diff --git a/prototype/integration_test/simple_test.dart b/prototype/integration_test/simple_test.dart index 9f774e61..ed72a9ee 100644 --- a/prototype/integration_test/simple_test.dart +++ b/prototype/integration_test/simple_test.dart @@ -3,7 +3,7 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import 'package:flutter_test/flutter_test.dart'; -import 'package:prototype/main.dart'; +import 'package:prototype/app.dart'; import 'package:prototype/core/frb_generated.dart'; import 'package:integration_test/integration_test.dart'; @@ -11,7 +11,7 @@ void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); setUpAll(() async => await RustLib.init()); testWidgets('Can call rust function', (WidgetTester tester) async { - await tester.pumpWidget(const MyApp()); + await tester.pumpWidget(const App()); expect(find.textContaining('Result: `Hello, Tom!`'), findsOneWidget); }); } diff --git a/prototype/lib/app.dart b/prototype/lib/app.dart new file mode 100644 index 00000000..e54414dc --- /dev/null +++ b/prototype/lib/app.dart @@ -0,0 +1,101 @@ +// SPDX-FileCopyrightText: 2024 Phoenix R&D GmbH +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:logging/logging.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:prototype/core_client.dart'; +import 'package:prototype/homescreen.dart'; +import 'package:prototype/platform.dart'; +import 'package:provider/provider.dart'; + +import 'theme/theme.dart'; + +final GlobalKey appNavigator = GlobalKey(); + +final _log = Logger('App'); + +class App extends StatefulWidget { + const App({super.key}); + + @override + State createState() => _AppState(); +} + +class _AppState extends State with WidgetsBindingObserver { + final CoreClient _coreClient = CoreClient(); + + @override + void initState() { + super.initState(); + WidgetsBinding.instance.addObserver(this); + _requestMobileNotifications(); + } + + @override + void dispose() { + WidgetsBinding.instance.removeObserver(this); + super.dispose(); + } + + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + super.didChangeAppLifecycleState(state); + _onStateChanged(state); + } + + Future _onStateChanged(AppLifecycleState state) async { + if (state == AppLifecycleState.paused) { + _log.fine('App is in the background'); + + // iOS only + if (Platform.isIOS) { + // only set the badge count if the user is logged in + if (_coreClient.maybeUser case final user?) { + final count = await user.globalUnreadMessagesCount(); + await setBadgeCount(count); + } + } + } + } + + @override + Widget build(BuildContext context) { + // TODO: This provider should be moved below the `MaterialApp`. This can be + // done when the app router is introduced. We can't just wrap the + // `HomeScreen` because it is replaced in other places by another screens. + return Provider.value( + value: _coreClient, + child: MaterialApp( + title: 'Prototype', + debugShowCheckedModeBanner: false, + theme: themeData(context), + navigatorKey: appNavigator, + home: const HomeScreen(), + ), + ); + } +} + +void _requestMobileNotifications() async { + // Mobile initialization + if (Platform.isAndroid || Platform.isIOS) { + // Initialize the method channel + initMethodChannel(); + + // Ask for notification permission + var status = await Permission.notification.status; + switch (status) { + case PermissionStatus.denied: + _log.info("Notification permission denied, will ask the user"); + var requestStatus = await Permission.notification.request(); + _log.fine("The status is $requestStatus"); + break; + default: + _log.info("Notification permission status: $status"); + } + } +} diff --git a/prototype/lib/conversation_list_pane/conversation_list.dart b/prototype/lib/conversation_list_pane/conversation_list.dart index a0e6d3d1..978cabaf 100644 --- a/prototype/lib/conversation_list_pane/conversation_list.dart +++ b/prototype/lib/conversation_list_pane/conversation_list.dart @@ -11,8 +11,8 @@ import 'package:prototype/core_client.dart'; import 'package:prototype/conversation_pane/conversation_pane.dart'; import 'package:prototype/elements.dart'; import 'package:prototype/messenger_view.dart'; +import 'package:prototype/styles.dart'; import 'package:prototype/theme/theme.dart'; -import '../styles.dart'; import 'package:convert/convert.dart'; import 'package:collection/collection.dart'; @@ -24,6 +24,8 @@ class ConversationList extends StatefulWidget { } class _ConversationListState extends State { + _ConversationListState(); + late List _conversations; UiConversationDetails? _currentConversation; StreamSubscription? _conversationListUpdateListener; @@ -32,19 +34,19 @@ class _ConversationListState extends State { static const double _topBaseline = 12; - _ConversationListState() { + @override + void initState() { + super.initState(); + + final coreClient = context.coreClient; _conversations = coreClient.conversationsList; _currentConversation = coreClient.currentConversation; _conversationListUpdateListener = coreClient.onConversationListUpdate .listen(conversationListUpdateListener); _conversationSwitchListener = coreClient.onConversationSwitch.listen(conversationSwitchListener); - } - @override - void initState() { - super.initState(); - updateConversationList(); + updateConversationList(coreClient); } @override @@ -66,7 +68,10 @@ class _ConversationListState extends State { } } - void selectConversation(ConversationIdBytes conversationId) { + void selectConversation( + CoreClient coreClient, + ConversationIdBytes conversationId, + ) { print("Tapped on conversation ${hex.encode(conversationId.bytes)}"); coreClient.selectConversation(conversationId); if (isSmallScreen(context)) { @@ -75,10 +80,10 @@ class _ConversationListState extends State { } void conversationListUpdateListener(ConversationIdBytes uuid) async { - updateConversationList(); + updateConversationList(context.coreClient); } - void updateConversationList() async { + void updateConversationList(CoreClient coreClient) async { await coreClient.conversations().then((conversations) { setState(() { if (_currentConversation == null && conversations.isNotEmpty) { @@ -147,6 +152,8 @@ class _ConversationListState extends State { final senderStyle = style.copyWith(fontVariations: variationSemiBold); + final coreClient = context.coreClient; + if (lastMessage != null) { lastMessage.message.when( contentFlight: (c) { @@ -290,7 +297,10 @@ class _ConversationListState extends State { selected: isConversationSelected( _currentConversation, _conversations[index], context), focusColor: convListItemSelectedColor, - onTap: () => selectConversation(_conversations[index].id), + onTap: () => selectConversation( + context.coreClient, + _conversations[index].id, + ), ); } diff --git a/prototype/lib/conversation_list_pane/footer.dart b/prototype/lib/conversation_list_pane/footer.dart index 0162a652..dc2149cf 100644 --- a/prototype/lib/conversation_list_pane/footer.dart +++ b/prototype/lib/conversation_list_pane/footer.dart @@ -16,6 +16,7 @@ class ConversationListFooter extends StatelessWidget { @override Widget build(BuildContext context) { + final coreClient = context.coreClient; return Container( alignment: AlignmentDirectional.topStart, padding: const EdgeInsets.fromLTRB(15, 15, 15, 30), @@ -43,8 +44,10 @@ class ConversationListFooter extends StatelessWidget { try { await coreClient.createConnection(connectionUsername); } catch (e) { - showErrorBanner(context, - 'The user $connectionUsername could not be found'); + if (context.mounted) { + showErrorBanner(context, + 'The user $connectionUsername could not be found'); + } } } }, diff --git a/prototype/lib/conversation_list_pane/pane.dart b/prototype/lib/conversation_list_pane/pane.dart index 651841c8..5e580255 100644 --- a/prototype/lib/conversation_list_pane/pane.dart +++ b/prototype/lib/conversation_list_pane/pane.dart @@ -20,12 +20,19 @@ class ConversationView extends StatefulWidget { } class _ConversationViewState extends State { - String? displayName = coreClient.ownProfile.displayName; - Uint8List? profilePicture = coreClient.ownProfile.profilePictureOption; + String? displayName; + Uint8List? profilePicture; @override void initState() { super.initState(); + + final coreClient = context.coreClient; + setState(() { + displayName = coreClient.ownProfile.displayName; + profilePicture = coreClient.ownProfile.profilePictureOption; + }); + // Listen for changes to the user's profile picture coreClient.onOwnProfileUpdate.listen((profile) { if (mounted) { diff --git a/prototype/lib/conversation_list_pane/top.dart b/prototype/lib/conversation_list_pane/top.dart index 1bfa35e3..8473fd73 100644 --- a/prototype/lib/conversation_list_pane/top.dart +++ b/prototype/lib/conversation_list_pane/top.dart @@ -38,7 +38,7 @@ class ConversationListTop extends StatelessWidget { children: [ UserAvatar( size: 32, - username: coreClient.username, + username: context.coreClient.username, image: profilePicture, onPressed: () { Navigator.push( @@ -54,7 +54,7 @@ class ConversationListTop extends StatelessWidget { ); } - Column _usernameSpace() { + Column _usernameSpace(String username) { return Column( children: [ Text( @@ -68,7 +68,7 @@ class ConversationListTop extends StatelessWidget { ), const SizedBox(height: 5), Text( - coreClient.username, + username, style: const TextStyle( color: colorDMB, fontSize: 10, @@ -118,7 +118,7 @@ class ConversationListTop extends StatelessWidget { children: [ _avatar(context), Expanded( - child: _usernameSpace(), + child: _usernameSpace(context.coreClient.username), ), _settingsButton(context), ], diff --git a/prototype/lib/conversation_pane/conversation_content/conversation_content.dart b/prototype/lib/conversation_pane/conversation_content/conversation_content.dart index 963c3bde..2d07a716 100644 --- a/prototype/lib/conversation_pane/conversation_content/conversation_content.dart +++ b/prototype/lib/conversation_pane/conversation_content/conversation_content.dart @@ -41,6 +41,7 @@ class _ConversationContentState extends State { super.initState(); _scrollController.addListener(_onScroll); + final coreClient = context.coreClient; _conversationListener = coreClient.onConversationSwitch.listen(conversationListener); _messageListener = coreClient.onMessageUpdate.listen(messageListener); @@ -100,7 +101,7 @@ class _ConversationContentState extends State { void _onMessageVisible(String timestamp) { if (_currentConversation != null) { - coreClient.user.markMessagesAsReadDebounced( + context.coreClient.user.markMessagesAsReadDebounced( conversationId: _currentConversation!.id, timestamp: timestamp); } } @@ -113,7 +114,7 @@ class _ConversationContentState extends State { Future updateMessages() async { if (_currentConversation != null) { - final messages = await coreClient.user + final messages = await context.coreClient.user .getMessages(conversationId: _currentConversation!.id, lastN: 50); setState(() { print("Number of messages: ${messages.length}"); diff --git a/prototype/lib/conversation_pane/conversation_content/text_message_tile.dart b/prototype/lib/conversation_pane/conversation_content/text_message_tile.dart index e84d6eda..b804a6bb 100644 --- a/prototype/lib/conversation_pane/conversation_content/text_message_tile.dart +++ b/prototype/lib/conversation_pane/conversation_content/text_message_tile.dart @@ -24,7 +24,7 @@ class _TextMessageTileState extends State { @override void initState() { super.initState(); - coreClient.user + context.coreClient.user .userProfile(userName: widget.contentFlight.last.sender) .then((p) { if (mounted) { @@ -36,7 +36,7 @@ class _TextMessageTileState extends State { } bool isSender() { - return widget.contentFlight.last.sender == coreClient.username; + return widget.contentFlight.last.sender == context.coreClient.username; } @override @@ -95,7 +95,7 @@ class _TextMessageTileState extends State { Widget _avatar() { return FutureUserAvatar( - profile: coreClient.user + profile: context.coreClient.user .userProfile(userName: widget.contentFlight.last.sender), ); } diff --git a/prototype/lib/conversation_pane/conversation_details/add_members.dart b/prototype/lib/conversation_pane/conversation_details/add_members.dart index 5dafc2b4..3c5d10eb 100644 --- a/prototype/lib/conversation_pane/conversation_details/add_members.dart +++ b/prototype/lib/conversation_pane/conversation_details/add_members.dart @@ -31,7 +31,7 @@ class _AddMembersState extends State { void initState() { super.initState(); _conversationListener = - coreClient.onConversationSwitch.listen(conversationListener); + context.coreClient.onConversationSwitch.listen(conversationListener); getContacts(); } @@ -42,13 +42,14 @@ class _AddMembersState extends State { } getContacts() async { - contacts = await coreClient.getContacts(); + contacts = await context.coreClient.getContacts(); setState(() {}); } addContacts() async { for (var contact in selectedContacts) { - await coreClient.addUserToConversation(widget.conversation.id, contact); + await context.coreClient + .addUserToConversation(widget.conversation.id, contact); } } @@ -92,7 +93,7 @@ class _AddMembersState extends State { final contact = contacts[index]; return ListTile( leading: FutureUserAvatar( - profile: coreClient.user + profile: context.coreClient.user .userProfile(userName: contact.userName), ), title: Text( diff --git a/prototype/lib/conversation_pane/conversation_details/connection_details.dart b/prototype/lib/conversation_pane/conversation_details/connection_details.dart index ef6f0d19..2ae37c2c 100644 --- a/prototype/lib/conversation_pane/conversation_details/connection_details.dart +++ b/prototype/lib/conversation_pane/conversation_details/connection_details.dart @@ -19,6 +19,7 @@ class ConnectionDetails extends StatelessWidget { @override Widget build(BuildContext context) { + final coreClient = context.coreClient; return Center( child: Column( mainAxisAlignment: MainAxisAlignment.start, diff --git a/prototype/lib/conversation_pane/conversation_details/conversation_details.dart b/prototype/lib/conversation_pane/conversation_details/conversation_details.dart index e04634ad..dcd99afe 100644 --- a/prototype/lib/conversation_pane/conversation_details/conversation_details.dart +++ b/prototype/lib/conversation_pane/conversation_details/conversation_details.dart @@ -26,6 +26,7 @@ class _ConversationDetailsState extends State { @override void initState() { super.initState(); + final coreClient = context.coreClient; _conversationListener = coreClient.onConversationSwitch.listen(conversationListener); diff --git a/prototype/lib/conversation_pane/conversation_details/group_details.dart b/prototype/lib/conversation_pane/conversation_details/group_details.dart index 01dd674e..4466e7f0 100644 --- a/prototype/lib/conversation_pane/conversation_details/group_details.dart +++ b/prototype/lib/conversation_pane/conversation_details/group_details.dart @@ -38,7 +38,7 @@ class _GroupDetailsState extends State { Future fetchMembers() async { // Fetch member list from the core client - members = await coreClient.getMembers(widget.conversation.id); + members = await context.coreClient.getMembers(widget.conversation.id); setState(() {}); } @@ -65,7 +65,7 @@ class _GroupDetailsState extends State { image?.readAsBytes().then((value) { setState(() { avatar = value; - coreClient.user.setConversationPicture( + context.coreClient.user.setConversationPicture( conversationId: widget.conversation.id, conversationPicture: value); }); @@ -107,7 +107,7 @@ class _GroupDetailsState extends State { return ListTile( leading: FutureUserAvatar( size: 24, - profile: coreClient.user + profile: context.coreClient.user .userProfile(userName: members[index]), ), title: Text( diff --git a/prototype/lib/conversation_pane/conversation_details/member_details.dart b/prototype/lib/conversation_pane/conversation_details/member_details.dart index 09ffda0f..2aa55ad7 100644 --- a/prototype/lib/conversation_pane/conversation_details/member_details.dart +++ b/prototype/lib/conversation_pane/conversation_details/member_details.dart @@ -33,8 +33,10 @@ class _MemberDetailsState extends State { super.initState(); // Listen for conversation switch events and close the member details pane // when the conversation changes - _conversationListener = coreClient.onConversationSwitch.listen((event) { - Navigator.of(context).pop(); + final navigator = Navigator.of(context); + _conversationListener = + context.coreClient.onConversationSwitch.listen((event) { + navigator.pop(); }); } @@ -45,7 +47,7 @@ class _MemberDetailsState extends State { } bool isSelf() { - return widget.username == coreClient.username; + return widget.username == context.coreClient.username; } @override @@ -68,8 +70,8 @@ class _MemberDetailsState extends State { const SizedBox(height: _padding), FutureUserAvatar( size: 64, - profile: - coreClient.user.userProfile(userName: widget.username), + profile: context.coreClient.user + .userProfile(userName: widget.username), ), const SizedBox(height: _padding), Text( @@ -100,14 +102,14 @@ class _MemberDetailsState extends State { style: textButtonStyle(context), child: const Text("Cancel")), TextButton( - onPressed: () { - coreClient + onPressed: () async { + await context.coreClient .removeUserFromConversation( widget.conversation.id, - widget.username) - .then((value) => { - Navigator.of(context).pop(true) - }); + widget.username); + if (context.mounted) { + Navigator.of(context).pop(true); + } }, style: textButtonStyle(context), child: const Text("Remove user"), @@ -116,7 +118,7 @@ class _MemberDetailsState extends State { ); }, ); - if (confirmed) { + if (confirmed && context.mounted) { Navigator.of(context).pop(true); } }, diff --git a/prototype/lib/conversation_pane/conversation_pane.dart b/prototype/lib/conversation_pane/conversation_pane.dart index d5235cc9..ea1a788c 100644 --- a/prototype/lib/conversation_pane/conversation_pane.dart +++ b/prototype/lib/conversation_pane/conversation_pane.dart @@ -5,11 +5,11 @@ import 'dart:async'; import 'package:flutter/material.dart'; +import 'package:prototype/app.dart'; import 'package:prototype/conversation_pane/conversation_details/conversation_details.dart'; import 'package:prototype/core/api/types.dart'; import 'package:prototype/core_client.dart'; import 'package:prototype/elements.dart'; -import 'package:prototype/main.dart'; import 'package:prototype/messenger_view.dart'; import 'package:prototype/styles.dart'; import 'conversation_content/conversation_content.dart'; @@ -29,6 +29,7 @@ class _ConversationPaneState extends State { @override void initState() { super.initState(); + final coreClient = context.coreClient; _currentConversation = coreClient.currentConversation; _listener = coreClient.onConversationSwitch.listen((conversation) { setState(() { diff --git a/prototype/lib/conversation_pane/message_composer.dart b/prototype/lib/conversation_pane/message_composer.dart index d83ef48a..d65f2c7f 100644 --- a/prototype/lib/conversation_pane/message_composer.dart +++ b/prototype/lib/conversation_pane/message_composer.dart @@ -116,6 +116,7 @@ class _MessageComposerState extends State { @override void initState() { super.initState(); + final coreClient = context.coreClient; _listener = coreClient.onConversationSwitch.listen(conversationListener); _currentConversation = coreClient.currentConversation; _focusNode.onKeyEvent = onKeyEvent; @@ -156,6 +157,7 @@ class _MessageComposerState extends State { _focusNode.requestFocus(); }); + final coreClient = context.coreClient; await coreClient.sendMessage( coreClient.currentConversation!.id, messageText); } diff --git a/prototype/lib/core_client.dart b/prototype/lib/core_client.dart index 90a3fe52..7244ad34 100644 --- a/prototype/lib/core_client.dart +++ b/prototype/lib/core_client.dart @@ -6,14 +6,14 @@ import 'dart:async'; import 'dart:io'; import 'dart:typed_data'; import 'package:collection/collection.dart'; +import 'package:flutter/widgets.dart'; import 'package:path_provider/path_provider.dart'; -import 'package:prototype/core/api/mobile_logging.dart'; import 'package:prototype/core/api/types.dart'; import 'package:prototype/core/api/user.dart'; import 'package:prototype/core/api/utils.dart'; -import 'package:prototype/core/frb_generated.dart'; import 'package:prototype/core/lib.dart'; import 'package:prototype/platform.dart'; +import 'package:provider/provider.dart'; // Helper definitions Function unOrdDeepEq = const DeepCollectionEquality.unordered().equals; @@ -71,16 +71,6 @@ class CoreClient { _user = user; } - Future init() async { - // FRB - await RustLib.init(); - // Logging - createLogStream().listen((event) { - print( - 'Rust: ${event.level} ${event.tag} ${event.msg} ${event.timeMillis}'); - }); - } - String get username { return ownProfile.userName; } @@ -215,7 +205,7 @@ class CoreClient { await user.fetchMessages(); // iOS only if (Platform.isIOS) { - final count = await coreClient.user.globalUnreadMessagesCount(); + final count = await user.globalUnreadMessagesCount(); await setBadgeCount(count); } conversationListUpdates.add(ConversationIdBytes(bytes: U8Array16.init())); @@ -304,4 +294,6 @@ class CoreClient { } } -final coreClient = CoreClient(); +extension BuildContextExtension on BuildContext { + CoreClient get coreClient => read(); +} diff --git a/prototype/lib/homescreen.dart b/prototype/lib/homescreen.dart index 2bdca17a..a1ccf03b 100644 --- a/prototype/lib/homescreen.dart +++ b/prototype/lib/homescreen.dart @@ -3,12 +3,9 @@ // SPDX-License-Identifier: AGPL-3.0-or-later import 'dart:async'; -import 'dart:io'; import 'package:flutter/material.dart'; -import 'package:permission_handler/permission_handler.dart'; import 'package:prototype/core_client.dart'; import 'package:prototype/messenger_view.dart'; -import 'package:prototype/platform.dart'; import 'package:prototype/registration/server_choice.dart'; import 'package:prototype/settings/developer.dart'; import 'package:prototype/styles.dart'; @@ -35,25 +32,7 @@ class _HomeScreenState extends State { statusText = "Initializing core client..."; }); - // Mobile initialization - if (Platform.isAndroid || Platform.isIOS) { - // Initialize the method channel - initMethodChannel(); - - // Ask for notification permission - var status = await Permission.notification.status; - switch (status) { - case PermissionStatus.denied: - print("Notification permission denied, will ask the user"); - var requestStatus = await Permission.notification.request(); - print("The status is $requestStatus"); - break; - default: - print("Notification permission status: $status"); - } - } - - await coreClient.loadUser().then((exists) { + await context.coreClient.loadUser().then((exists) { if (exists) { print("User loaded successfully"); if (mounted) { diff --git a/prototype/lib/main.dart b/prototype/lib/main.dart index 3122b278..2519aaf2 100644 --- a/prototype/lib/main.dart +++ b/prototype/lib/main.dart @@ -2,111 +2,34 @@ // // SPDX-License-Identifier: AGPL-3.0-or-later -import 'dart:io'; - +import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; -import 'package:prototype/core_client.dart'; -import 'package:prototype/homescreen.dart'; -import 'package:prototype/platform.dart'; -import 'package:prototype/styles.dart'; +import 'package:logging/logging.dart'; +import 'package:prototype/app.dart'; +import 'package:prototype/core/api/mobile_logging.dart'; +import 'package:prototype/core/frb_generated.dart'; void main() async { - // Initialize the FRB - await coreClient.init(); + _initRust(); + _initLogging(); - runApp(const MyApp()); + runApp(const App()); } -final GlobalKey appNavigator = GlobalKey(); - -class MyApp extends StatefulWidget { - const MyApp({super.key}); - - @override - State createState() => _MyAppState(); +void _initLogging() { + Logger.root.level = kDebugMode ? Level.FINE : Level.INFO; + Logger.root.onRecord.listen((record) { + print('${record.level.name}: ${record.time}: ${record.message}'); + }); } -class _MyAppState extends State with WidgetsBindingObserver { - @override - void initState() { - super.initState(); - WidgetsBinding.instance.addObserver(this); - } - - @override - void dispose() { - WidgetsBinding.instance.removeObserver(this); - super.dispose(); - } - - @override - void didChangeAppLifecycleState(AppLifecycleState state) { - super.didChangeAppLifecycleState(state); - - onStateChanged(state); - } - - Future onStateChanged(AppLifecycleState state) async { - if (state == AppLifecycleState.paused) { - // The app is in the background - print('App is in the background'); - - // iOS only - if (Platform.isIOS) { - final count = await coreClient.user.globalUnreadMessagesCount(); - await setBadgeCount(count); - } - } - } - - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Prototype', - debugShowCheckedModeBanner: false, - theme: ThemeData( - appBarTheme: AppBarTheme( - color: Colors.white, - elevation: 0, - iconTheme: const IconThemeData(color: Colors.black), - surfaceTintColor: Colors.black, - titleTextStyle: boldLabelStyle.copyWith(color: Colors.black), - ), - fontFamily: fontFamily, - textTheme: const TextTheme(), - canvasColor: Colors.white, - cardColor: Colors.white, - colorScheme: ColorScheme.fromSwatch( - accentColor: swatchColor, - backgroundColor: Colors.white, - brightness: Brightness.light, - ), - dialogBackgroundColor: Colors.white, - dialogTheme: const DialogTheme( - backgroundColor: Colors.white, - surfaceTintColor: Colors.white, - ), - primaryColor: swatchColor, - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - hoverColor: Colors.transparent, - outlinedButtonTheme: - OutlinedButtonThemeData(style: buttonStyle(context, true)), - iconButtonTheme: IconButtonThemeData( - style: ButtonStyle( - splashFactory: NoSplash.splashFactory, - surfaceTintColor: - WidgetStateProperty.all(Colors.transparent), - overlayColor: WidgetStateProperty.all(Colors.transparent), - ), - ), - textSelectionTheme: - const TextSelectionThemeData(cursorColor: Colors.blue), - ), - navigatorKey: appNavigator, - home: const HomeScreen(), - ); - } +Future _initRust() async { + // FRB + await RustLib.init(); + // Logging + createLogStream().listen((event) { + print('Rust: ${event.level} ${event.tag} ${event.msg} ${event.timeMillis}'); + }); } void showErrorBanner(BuildContext context, String errorDescription) { diff --git a/prototype/lib/registration/display_name_picture.dart b/prototype/lib/registration/display_name_picture.dart index a2d7eae2..e881fb82 100644 --- a/prototype/lib/registration/display_name_picture.dart +++ b/prototype/lib/registration/display_name_picture.dart @@ -6,6 +6,7 @@ import 'dart:typed_data'; import 'package:flutter/material.dart'; import 'package:image_picker/image_picker.dart'; +import 'package:prototype/app.dart'; import 'package:prototype/core_client.dart'; import 'package:prototype/elements.dart'; import 'package:prototype/main.dart'; @@ -48,6 +49,8 @@ class _DisplayNameAvatarChoiceState extends State { } Future signup() async { + final coreClient = context.coreClient; + final domain = widget.domain; final username = widget.username; final password = widget.password; diff --git a/prototype/lib/settings/developer.dart b/prototype/lib/settings/developer.dart index 82b2b0f6..f5cd0a2b 100644 --- a/prototype/lib/settings/developer.dart +++ b/prototype/lib/settings/developer.dart @@ -6,6 +6,7 @@ import 'dart:io'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:prototype/app.dart'; import 'package:prototype/core/api/user.dart'; import 'package:prototype/core_client.dart'; import 'package:prototype/elements.dart'; @@ -36,10 +37,10 @@ class _DeveloperSettingsScreenState extends State { } bool canReRegisterPushToken() { - return isTouch() && coreClient.maybeUser != null; + return isTouch() && context.coreClient.maybeUser != null; } - void reRegisterPushToken() async { + void reRegisterPushToken(CoreClient coreClient) async { if (canReRegisterPushToken()) { final deviceToken = await getDeviceToken(); if (deviceToken != null) { @@ -83,7 +84,7 @@ class _DeveloperSettingsScreenState extends State { void eraseDatabase() { // Perform database erase operation try { - coreClient.deleteDatabase().then((value) { + context.coreClient.deleteDatabase().then((value) { if (appNavigator.currentState != null) { // Remove all routes from the navigator stack and push the HomeScreen var appContext = appNavigator.currentState!.context; @@ -143,7 +144,9 @@ class _DeveloperSettingsScreenState extends State { OutlinedButton( style: buttonStyle(context, canReRegisterPushToken()), onPressed: () async { - if (canReRegisterPushToken()) reRegisterPushToken(); + if (canReRegisterPushToken()) { + reRegisterPushToken(context.coreClient); + } }, child: const Text('Re-register push token'), ), diff --git a/prototype/lib/settings/user.dart b/prototype/lib/settings/user.dart index c212349d..344cb828 100644 --- a/prototype/lib/settings/user.dart +++ b/prototype/lib/settings/user.dart @@ -19,14 +19,19 @@ class UserSettingsScreen extends StatefulWidget { } class _UserSettingsScreenState extends State { - Uint8List? avatar = coreClient.ownProfile.profilePictureOption; - String? displayName = coreClient.ownProfile.displayName; + Uint8List? avatar; + String? displayName; bool imageChanged = false; bool displayNameChanged = false; @override void initState() { super.initState(); + final coreClient = context.coreClient; + setState(() { + displayName = coreClient.ownProfile.displayName; + avatar = coreClient.ownProfile.profilePictureOption; + }); } bool changed() { @@ -35,8 +40,8 @@ class _UserSettingsScreenState extends State { void save(BuildContext context) { try { - coreClient.setOwnProfile(displayName ?? "", avatar).then((value) { - if (mounted) { + context.coreClient.setOwnProfile(displayName ?? "", avatar).then((value) { + if (context.mounted) { Navigator.of(context).pop(); } }); @@ -51,6 +56,7 @@ class _UserSettingsScreenState extends State { @override Widget build(BuildContext context) { + final coreClient = context.coreClient; return Scaffold( appBar: AppBar( title: const Text('User Settings'), diff --git a/prototype/lib/theme/theme.dart b/prototype/lib/theme/theme.dart index 175c3c07..ad293786 100644 --- a/prototype/lib/theme/theme.dart +++ b/prototype/lib/theme/theme.dart @@ -3,3 +3,4 @@ // SPDX-License-Identifier: AGPL-3.0-or-later export 'spacings.dart'; +export 'theme_data.dart'; diff --git a/prototype/lib/theme/theme_data.dart b/prototype/lib/theme/theme_data.dart new file mode 100644 index 00000000..9c71b159 --- /dev/null +++ b/prototype/lib/theme/theme_data.dart @@ -0,0 +1,45 @@ +// SPDX-FileCopyrightText: 2024 Phoenix R&D GmbH +// +// SPDX-License-Identifier: AGPL-3.0-or-later + +import 'package:flutter/material.dart'; +import 'package:prototype/styles.dart'; + +ThemeData themeData(BuildContext context) => ThemeData( + appBarTheme: AppBarTheme( + color: Colors.white, + elevation: 0, + iconTheme: const IconThemeData(color: Colors.black), + surfaceTintColor: Colors.black, + titleTextStyle: boldLabelStyle.copyWith(color: Colors.black), + ), + fontFamily: fontFamily, + textTheme: const TextTheme(), + canvasColor: Colors.white, + cardColor: Colors.white, + colorScheme: ColorScheme.fromSwatch( + accentColor: swatchColor, + backgroundColor: Colors.white, + brightness: Brightness.light, + ), + dialogBackgroundColor: Colors.white, + dialogTheme: const DialogTheme( + backgroundColor: Colors.white, + surfaceTintColor: Colors.white, + ), + primaryColor: swatchColor, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + hoverColor: Colors.transparent, + outlinedButtonTheme: + OutlinedButtonThemeData(style: buttonStyle(context, true)), + iconButtonTheme: IconButtonThemeData( + style: ButtonStyle( + splashFactory: NoSplash.splashFactory, + surfaceTintColor: WidgetStateProperty.all(Colors.transparent), + overlayColor: WidgetStateProperty.all(Colors.transparent), + ), + ), + textSelectionTheme: + const TextSelectionThemeData(cursorColor: Colors.blue), + ); diff --git a/prototype/pubspec.lock b/prototype/pubspec.lock index cb2e98ef..d3d0d64c 100644 --- a/prototype/pubspec.lock +++ b/prototype/pubspec.lock @@ -541,7 +541,7 @@ packages: source: hosted version: "4.0.0" logging: - dependency: transitive + dependency: "direct main" description: name: logging sha256: c8245ada5f1717ed44271ed1c26b8ce85ca3228fd2ffdb75468ab01979309d61 @@ -588,6 +588,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.0" + nested: + dependency: transitive + description: + name: nested + sha256: "03bac4c528c64c95c722ec99280375a6f2fc708eec17c7b3f07253b626cd2a20" + url: "https://pub.dev" + source: hosted + version: "1.0.0" node_preamble: dependency: transitive description: @@ -755,6 +763,14 @@ packages: url: "https://pub.dev" source: hosted version: "5.0.2" + provider: + dependency: "direct main" + description: + name: provider + sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + url: "https://pub.dev" + source: hosted + version: "6.1.2" pub_semver: dependency: transitive description: diff --git a/prototype/pubspec.yaml b/prototype/pubspec.yaml index d6d6f1c9..d3a2972c 100644 --- a/prototype/pubspec.yaml +++ b/prototype/pubspec.yaml @@ -38,6 +38,8 @@ dependencies: permission_handler: ^11.3.1 intl: ^0.19.0 uuid: ^4.4.2 + provider: ^6.1.2 + logging: ^1.3.0 dev_dependencies: build_runner: ^2.4.9