diff --git a/.github/workflows/issue_closed.yaml b/.github/workflows/issue_closed.yaml index c4daabf65..5e32442f7 100644 --- a/.github/workflows/issue_closed.yaml +++ b/.github/workflows/issue_closed.yaml @@ -17,3 +17,33 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} GH_REPO: ${{ github.repository }} NUMBER: ${{ github.event.issue.number }} + - name: Set project ID + run: | + echo "PROJECT_ID=PVT_kwDOBndSo84A7FWL" >> $GITHUB_ENV + - name: Get item ID for issue in project + id: get_item_id + run: | + ITEM_ID=$(gh api graphql -f query='query { repository(owner: "${{ github.repository_owner }}", name: "${{ github.event.repository.name }}") { issue(number: ${{ github.event.issue.number }}) { projectItems(first: 10) { nodes { id project { id } } } } }' --jq '.data.repository.issue.projectItems.nodes[] | select(.project.id==env.PROJECT_ID) | .id') + echo "ITEM_ID=$ITEM_ID" >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Get status field and Done option IDs + id: get_status_ids + run: | + STATUS_FIELD_ID=$(gh api graphql -f query='query { node(id: "'$PROJECT_ID'") { ... on ProjectV2 { fields(first: 20) { nodes { id name } } } } }' --jq '.data.node.fields.nodes[] | select(.name=="Status") | .id') + DONE_OPTION_ID=$(gh api graphql -f query='query { node(id: "'$STATUS_FIELD_ID'") { ... on ProjectV2Field { options { id name } } } }' --jq '.data.node.options[] | select(.name=="Done") | .id') + echo "STATUS_FIELD_ID=$STATUS_FIELD_ID" >> $GITHUB_ENV + echo "DONE_OPTION_ID=$DONE_OPTION_ID" >> $GITHUB_ENV + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + - name: Set status to Done in project + run: | + gh api graphql -f query='mutation($project:ID!, $item:ID!, $field:ID!, $option:ID!) { updateProjectV2ItemFieldValue(input: {projectId: $project, itemId: $item, fieldId: $field, value: { singleSelectOptionId: $option } }) { projectV2Item { id } } }' -f project=$PROJECT_ID -f item=$ITEM_ID -f field=$STATUS_FIELD_ID -f option=$DONE_OPTION_ID + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + PROJECT_ID: ${{ env.PROJECT_ID }} + ITEM_ID: ${{ env.ITEM_ID }} + STATUS_FIELD_ID: ${{ env.STATUS_FIELD_ID }} + DONE_OPTION_ID: ${{ env.DONE_OPTION_ID }} +# To get your project, field, and option IDs, see the instructions in the new issue_opened_project.yaml file. +# You must replace the placeholders with your actual project and field IDs. diff --git a/.github/workflows/issue_opened_project.yaml b/.github/workflows/issue_opened_project.yaml new file mode 100644 index 000000000..6a4e6c137 --- /dev/null +++ b/.github/workflows/issue_opened_project.yaml @@ -0,0 +1,20 @@ +# Auto-add new issues to a GitHub project (replace PROJECT_ID and COLUMN_ID with your values) +name: Add new issues to project +on: + issues: + types: + - opened +jobs: + add_to_project: + runs-on: ubuntu-latest + steps: + - name: Set project ID + run: | + echo "PROJECT_ID=PVT_kwDOBndSo84A7FWL" >> $GITHUB_ENV + - name: Add issue to project + run: | + gh api graphql -f query='mutation($project:ID!, $contentId:ID!) { addProjectV2ItemById(input: {projectId: $project, contentId: $contentId}) { item { id } } }' -f project=$PROJECT_ID -f contentId=$ISSUE_ID + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + ISSUE_ID: ${{ github.event.issue.node_id }} +# To get your project ID, use: gh api graphql -f query='query { organization(login: "") { projectV2(number: ) { id } } }' diff --git a/CHANGELOG.md b/CHANGELOG.md index 58fb6fe53..13b7d34be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,7 +30,7 @@ - chore: Use Cupertino Activity Indicator in ChatEventList (krille-chan) - chore: Use other join endpoint for room upgrades (Krille) - fix(macos): update dependencies to make the build work (Rafał Hirsch) -- fix: Add missing html tag to render (Krille) +- fix: Add missing \ html tag to render (Krille) - fix: Consistent element padding between server picker and login view (xegim) - fix: Index of numbered lists are off (Krille) - fix: never use a transition on the shell route (Rafał Hirsch) diff --git a/assets/l10n/intl_en.arb b/assets/l10n/intl_en.arb index 7a55d66c9..d5ffaae9d 100644 --- a/assets/l10n/intl_en.arb +++ b/assets/l10n/intl_en.arb @@ -705,6 +705,15 @@ } } }, + "countInvited": "{count} invited", + "@countInvited": { + "type": "String", + "placeholders": { + "count": { + "type": "int" + } + } + }, "create": "Create", "@create": { "type": "String", @@ -3199,6 +3208,18 @@ } } }, + "sentVoiceMessage": "\uD83C\uDF99\uFE0F {duration} - {sender}", + "@sentVoiceMessage": { + "type": "String", + "placeholders": { + "sender": { + "type": "String" + }, + "duration": { + "type": "String" + } + } + }, "deletePushRuleCanNotBeUndone": "If you delete this notification setting, this can not be undone.", "more": "More", "shareKeysWith": "Share keys with...", @@ -4975,5 +4996,20 @@ "canBeFoundViaInvitation": "\u2022 invitation", "canBeFoundViaCodeOrLink": "\u2022 code or link", "canBeFoundViaKnock": "\u2022 request to join and admin approval", - "anyoneCanJoin": "Anyone can join! However, admin can kick and ban whoever misbehaves. Those who are banned may not return!" + "anyoneCanJoin": "Anyone can join! However, admin can kick and ban whoever misbehaves. Those who are banned may not return!", + "createYourSpace": "Create your space", + "sendActivities": "Send activities", + "getStarted": "Get Started", + "getStartedBotChatDesc": "Chatting with AI is a great place to start and Pangea reading, writing, listening and speaking tools make it easy!", + "getStartedCommunitiesDesc": "Learning with a community is where Pangea Chat shines!\nYou can join your class, find a school, or even make your own!", + "getStartedFriendsDesc": "Do you have a friend that wants to learn with you?", + "getStartedBotChatComplete": "Well-done! You're chatting with the bot!", + "getStartedCommunitiesComplete": "Great, you have joined a space!", + "getStartedComplete": "You've completed this section!\nKeep exploring our amazing features by chatting with friends!", + "getStartedFriendsComplete": "Woohoo! You've got friends! 😉", + "getStartedBotChatButton": "Start chatting!", + "getStartedFriendsButton": "Invite a friend", + "groupChat": "Group Chat", + "directMessage": "Direct Message", + "newDirectMessage": "New direct message" } diff --git a/assets/l10n/intl_es.arb b/assets/l10n/intl_es.arb index 83010397e..306d249e6 100644 --- a/assets/l10n/intl_es.arb +++ b/assets/l10n/intl_es.arb @@ -6083,4 +6083,4 @@ "type": "String", "placeholders": {} } -} \ No newline at end of file +} diff --git a/assets/l10n/intl_vi.arb b/assets/l10n/intl_vi.arb index 87d240b4d..46fb83fbb 100644 --- a/assets/l10n/intl_vi.arb +++ b/assets/l10n/intl_vi.arb @@ -4382,4 +4382,4 @@ "type": "String", "placeholders": {} } -} \ No newline at end of file +} diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 4557f1a4b..7d65b14fd 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -17,8 +17,6 @@ 97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; }; 97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; }; 97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; }; - A10584DF00E2CBE024A7FEB1 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9F30C00BA233E7CA67AFBED5 /* Pods_Runner.framework */; }; - BCFA6E528F0B53B71B652C77 /* Pods_FluffyChat_Share.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9B1F89C23F73F2B8E7922A37 /* Pods_FluffyChat_Share.framework */; }; C1005C45261071B5002F4F32 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1005C44261071B5002F4F32 /* ShareViewController.swift */; }; C1005C48261071B5002F4F32 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = C1005C46261071B5002F4F32 /* MainInterface.storyboard */; }; C1005C4C261071B5002F4F32 /* FluffyChat Share.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = C1005C42261071B5002F4F32 /* FluffyChat Share.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; @@ -62,7 +60,6 @@ /* Begin PBXFileReference section */ 09545B0C8C397F94966EA956 /* Pods-FluffyChat Share.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-FluffyChat Share.debug.xcconfig"; path = "Target Support Files/Pods-FluffyChat Share/Pods-FluffyChat Share.debug.xcconfig"; sourceTree = ""; }; - 0BDDCB1746F84339AF1A5F40 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = ""; }; 1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = ""; }; 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = ""; }; 23120B990D2B5081843FB313 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = ""; }; @@ -81,8 +78,6 @@ 97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 9B1F89C23F73F2B8E7922A37 /* Pods_FluffyChat_Share.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_FluffyChat_Share.framework; sourceTree = BUILT_PRODUCTS_DIR; }; - 9F30C00BA233E7CA67AFBED5 /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; }; C1005C42261071B5002F4F32 /* FluffyChat Share.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "FluffyChat Share.appex"; sourceTree = BUILT_PRODUCTS_DIR; }; C1005C44261071B5002F4F32 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = ""; }; C1005C47261071B5002F4F32 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = ""; }; diff --git a/lib/config/app_config.dart b/lib/config/app_config.dart index f72f1438c..168fc6172 100644 --- a/lib/config/app_config.dart +++ b/lib/config/app_config.dart @@ -7,6 +7,7 @@ abstract class AppConfig { // static String _applicationName = 'FluffyChat'; static String _applicationName = 'Pangea Chat'; // #Pangea + static String get applicationName => _applicationName; static String? _applicationWelcomeMessage; @@ -14,7 +15,8 @@ abstract class AppConfig { // #Pangea // static String _defaultHomeserver = 'matrix.org'; static String get _defaultHomeserver => Environment.synapseURL; - // #Pangea + // Pangea# + static String get defaultHomeserver => _defaultHomeserver; static double fontSizeFactor = 1; static const Color chatColor = primaryColor; @@ -22,6 +24,7 @@ abstract class AppConfig { static const double messageFontSize = 16.0; static const bool allowOtherHomeservers = true; static const bool enableRegistration = true; + // #Pangea static const double toolbarMaxHeight = 250.0; static const double toolbarMinHeight = 200.0; static const double toolbarMinWidth = 350.0; @@ -80,6 +83,7 @@ abstract class AppConfig { // 'https://gitlab.com/famedly/fluffychat/-/blob/main/PRIVACY.md'; static String _privacyUrl = "https://www.pangeachat.com/privacy"; //Pangea# + static String get privacyUrl => _privacyUrl; // #Pangea // static const String website = 'https://fluffychat.im'; @@ -95,19 +99,21 @@ abstract class AppConfig { // #Pangea // static const String appOpenUrlScheme = 'im.fluffychat'; static const String appOpenUrlScheme = 'matrix.pangea.chat'; - static String _webBaseUrl = 'https://fluffychat.im/web'; // Pangea# + static String _webBaseUrl = 'https://fluffychat.im/web'; + static String get webBaseUrl => _webBaseUrl; - //#Pangea - static const String sourceCodeUrl = 'https://gitlab.com/famedly/fluffychat'; + static const String sourceCodeUrl = + 'https://github.com/krille-chan/fluffychat'; + // #Pangea // static const String supportUrl = - // 'https://gitlab.com/famedly/fluffychat/issues'; + // 'https://github.com/krille-chan/fluffychat/issues'; + // static const String changelogUrl = + // 'https://github.com/krille-chan/fluffychat/blob/main/CHANGELOG.md'; static const String supportUrl = 'https://www.pangeachat.com/faqs'; static const String termsOfServiceUrl = 'https://www.pangeachat.com/terms-of-service'; - // static const String changelogUrl = - // 'https://github.com/krille-chan/fluffychat/blob/main/CHANGELOG.md'; - //Pangea# + // Pangea# static final Uri newIssueUrl = Uri( scheme: 'https', host: 'github.com', @@ -143,21 +149,9 @@ abstract class AppConfig { static const String schemePrefix = 'matrix:'; // #Pangea // static const String pushNotificationsChannelId = 'fluffychat_push'; - // static const String pushNotificationsChannelName = 'FluffyChat push channel'; - // static const String pushNotificationsChannelDescription = - // 'Push notifications for FluffyChat'; // static const String pushNotificationsAppId = 'chat.fluffy.fluffychat'; - // static const String pushNotificationsGatewayUrl = - // 'https://push.fluffychat.im/_matrix/push/v1/notify'; - // static const String pushNotificationsPusherFormat = 'event_id_only'; static const String pushNotificationsChannelId = 'pangeachat_push'; - static const String pushNotificationsChannelName = 'Pangea Chat push channel'; - static const String pushNotificationsChannelDescription = - 'Push notifications for Pangea Chat'; static const String pushNotificationsAppId = 'com.talktolearn.chat'; - static const String pushNotificationsGatewayUrl = - 'https://sygnal.pangea.chat/_matrix/push/v1/notify'; - static const String? pushNotificationsPusherFormat = null; // Pangea# static const double borderRadius = 18.0; static const double columnWidth = 360.0; diff --git a/lib/config/routes.dart b/lib/config/routes.dart index ec16f5c64..1b647b2ae 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -32,17 +32,19 @@ import 'package:fluffychat/pages/settings_style/settings_style.dart'; import 'package:fluffychat/pangea/activity_generator/activity_generator.dart'; import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart'; import 'package:fluffychat/pangea/activity_suggestions/suggestions_page.dart'; +import 'package:fluffychat/pangea/find_your_people/find_your_people.dart'; +import 'package:fluffychat/pangea/find_your_people/find_your_people_side_view.dart'; import 'package:fluffychat/pangea/guard/p_vguard.dart'; import 'package:fluffychat/pangea/learning_settings/pages/settings_learning.dart'; import 'package:fluffychat/pangea/login/pages/login_or_signup_view.dart'; import 'package:fluffychat/pangea/login/pages/signup.dart'; import 'package:fluffychat/pangea/login/pages/space_code_onboarding.dart'; import 'package:fluffychat/pangea/login/pages/user_settings.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding.dart'; import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; import 'package:fluffychat/pangea/spaces/utils/join_with_alias.dart'; import 'package:fluffychat/pangea/spaces/utils/join_with_link.dart'; import 'package:fluffychat/pangea/subscription/pages/settings_subscription.dart'; -import 'package:fluffychat/pangea/user/pages/find_partner.dart'; import 'package:fluffychat/widgets/config_viewer.dart'; import 'package:fluffychat/widgets/layouts/empty_page.dart'; import 'package:fluffychat/widgets/layouts/two_column_layout.dart'; @@ -153,17 +155,11 @@ abstract class AppRoutes { ), GoRoute( path: '/join_with_alias', - pageBuilder: (context, state) => Matrix.of(context).client.isLogged() - ? chatListShellRouteBuilder( - context, - state, - JoinWithAlias(alias: state.uri.queryParameters['alias']), - ) - : defaultPageBuilder( - context, - state, - JoinWithAlias(alias: state.uri.queryParameters['alias']), - ), + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + JoinWithAlias(alias: state.uri.queryParameters['alias']), + ), ), GoRoute( path: '/user_age', @@ -195,8 +191,13 @@ abstract class AppRoutes { pageBuilder: (context, state, child) => noTransitionPageBuilder( context, state, + // #Pangea + // FluffyThemes.isColumnMode(context) && + // state.fullPath?.startsWith('/rooms/settings') == false FluffyThemes.isColumnMode(context) && - state.fullPath?.startsWith('/rooms/settings') == false + state.fullPath?.startsWith('/rooms/settings') == false && + state.fullPath?.startsWith('/rooms/communities') == false + // Pangea# ? TwoColumnLayout( mainView: ChatList( activeChat: state.pathParameters['roomid'], @@ -238,7 +239,7 @@ abstract class AppRoutes { FluffyThemes.isColumnMode(context) // #Pangea // ? const EmptyPage() - ? const SuggestionsPage() + ? const Onboarding() // Pangea# : ChatList( activeChat: state.pathParameters['roomid'], @@ -302,16 +303,30 @@ abstract class AppRoutes { redirect: loggedOutRedirect, ), // #Pangea - GoRoute( - path: 'partner', - pageBuilder: (context, state) => defaultPageBuilder( + ShellRoute( + pageBuilder: (context, state, child) => defaultPageBuilder( context, state, - const FindPartner(), + FluffyThemes.isColumnMode(context) + ? TwoColumnLayout( + mainView: const FindYourPeopleSideView(), + sideView: child, + dividerColor: Colors.transparent, + ) + : child, ), - redirect: loggedOutRedirect, + routes: [ + GoRoute( + path: 'communities', + redirect: loggedOutRedirect, + pageBuilder: (context, state) => defaultPageBuilder( + context, + state, + const FindYourPeople(), + ), + ), + ], ), - // #Pangea GoRoute( path: 'homepage', redirect: loggedOutRedirect, @@ -748,27 +763,5 @@ abstract class AppRoutes { redirect: loggedOutRedirect, ), ]; - - static Page chatListShellRouteBuilder( - context, - state, - child, - ) => - noTransitionPageBuilder( - context, - state, - FluffyThemes.isColumnMode(context) && - state.fullPath?.startsWith('/rooms/settings') == false - ? TwoColumnLayout( - mainView: ChatList( - activeChat: state.pathParameters['roomid'], - activeSpaceId: state.uri.queryParameters['spaceId'], - displayNavigationRail: - state.path?.startsWith('/rooms/settings') != true, - ), - sideView: child, - ) - : child, - ); // Pangea# } diff --git a/lib/config/setting_keys.dart b/lib/config/setting_keys.dart index 8b7bae372..19136fa9f 100644 --- a/lib/config/setting_keys.dart +++ b/lib/config/setting_keys.dart @@ -50,7 +50,10 @@ enum AppSettings { // Pangea# pushNotificationsGatewayUrl( 'pushNotificationsGatewayUrl', - 'https://push.fluffychat.im/_matrix/push/v1/notify', + // #Pangea + // 'https://push.fluffychat.im/_matrix/push/v1/notify', + 'https://sygnal.pangea.chat/_matrix/push/v1/notify', + // Pangea# ), pushNotificationsPusherFormat( 'pushNotificationsPusherFormat', diff --git a/lib/config/themes.dart b/lib/config/themes.dart index 728695131..e98624faa 100644 --- a/lib/config/themes.dart +++ b/lib/config/themes.dart @@ -7,7 +7,10 @@ import 'app_config.dart'; abstract class FluffyThemes { static const double columnWidth = 380.0; - static const double navRailWidth = 80.0; + // #Pangea + // static const double navRailWidth = 80.0; + static const double navRailWidth = 72.0; + // Pangea# static bool isColumnModeByWidth(double width) => width > columnWidth * 2 + navRailWidth; diff --git a/lib/pages/chat/chat.dart b/lib/pages/chat/chat.dart index dab1050b4..8a6b44580 100644 --- a/lib/pages/chat/chat.dart +++ b/lib/pages/chat/chat.dart @@ -3,6 +3,7 @@ import 'dart:async'; import 'dart:developer'; import 'dart:io'; +import 'dart:math'; import 'package:collection/collection.dart'; import 'package:device_info_plus/device_info_plus.dart'; @@ -26,6 +27,7 @@ import 'package:fluffychat/pangea/choreographer/enums/edit_type.dart'; import 'package:fluffychat/pangea/choreographer/models/choreo_record.dart'; import 'package:fluffychat/pangea/choreographer/widgets/igc/message_analytics_feedback.dart'; import 'package:fluffychat/pangea/choreographer/widgets/igc/pangea_text_controller.dart'; +import 'package:fluffychat/pangea/common/constants/model_keys.dart'; import 'package:fluffychat/pangea/common/controllers/pangea_controller.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/common/utils/firebase_analytics.dart'; @@ -514,7 +516,7 @@ class ChatController extends State // #Pangea // If fake event was sent, don't animate in the next event. // It makes the replacement of the fake event jumpy. - if (_fakeEventID != null) { + if (_fakeEventIDs.isNotEmpty) { animateInEventIndex = null; return; } @@ -686,7 +688,6 @@ class ChatController extends State MatrixState.pAnyState.closeAllOverlays(force: true); showToolbarStream.close(); stopMediaStream.close(); - hideTextController.dispose(); _levelSubscription?.cancel(); _analyticsSubscription?.cancel(); _router.routeInformationProvider.removeListener(_onRouteChanged); @@ -719,10 +720,6 @@ class ChatController extends State // TextEditingController sendController = TextEditingController(); PangeaTextController get sendController => choreographer.textController; - - /// used to obscure text in text field after sending fake message without - /// changing the actual text in the sendController - final TextEditingController hideTextController = TextEditingController(); // #Pangea void setSendingClient(Client c) { @@ -757,26 +754,47 @@ class ChatController extends State pangeaEditingEvent = null; } - String? _fakeEventID; - bool get obscureText => _fakeEventID != null; + final List _fakeEventIDs = []; + bool get obscureText => _fakeEventIDs.isNotEmpty; /// Add a fake event to the timeline to visually indicate that a message is being sent. /// Used when tokenizing after message send, specifically because tokenization for some /// languages takes some time. - void sendFakeMessage() { + String? sendFakeMessage() { + if (sendController.text.trim().isEmpty) return null; + final eventID = room.sendFakeMessage( text: sendController.text, inReplyTo: replyEvent, editEventId: editEvent?.eventId, ); - setState(() => _fakeEventID = eventID); + sendController.setSystemText("", EditType.other); + setState(() => _fakeEventIDs.add(eventID)); + + // wait for the next event to come through before clearing any fake event, + // to make the replacement look smooth + room.client.onTimelineEvent.stream + .firstWhere((event) => event.content[ModelKey.tempEventId] == eventID) + .then( + (_) => clearFakeEvent(eventID), + ); + + return eventID; } - void clearFakeEvent() { - if (_fakeEventID == null) return; - timeline?.events.removeWhere((e) => e.eventId == _fakeEventID); + void clearFakeEvent(String? eventId) { + if (eventId == null) return; + + final inTimeline = timeline != null && + timeline!.events.any( + (e) => e.eventId == eventId, + ); + + if (!inTimeline) return; + timeline?.events.removeWhere((e) => e.eventId == eventId); + setState(() { - _fakeEventID = null; + _fakeEventIDs.remove(eventId); }); } @@ -785,20 +803,26 @@ class ChatController extends State // but for choero, the tx id is generated before the message send. // Also, adding PangeaMessageData Future send({ + required String message, PangeaRepresentation? originalSent, PangeaRepresentation? originalWritten, PangeaMessageTokens? tokensSent, PangeaMessageTokens? tokensWritten, ChoreoRecord? choreo, + String? tempEventId, }) async { + if (message.trim().isEmpty) return; + // if (sendController.text.trim().isEmpty) return; // Pangea# - if (sendController.text.trim().isEmpty) return; _storeInputTimeoutTimer?.cancel(); final prefs = await SharedPreferences.getInstance(); prefs.remove('draft_$roomId'); var parseCommands = true; - final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text); + // #Pangea + // final commandMatch = RegExp(r'^\/(\w+)').firstMatch(sendController.text); + final commandMatch = RegExp(r'^\/(\w+)').firstMatch(message); + // Pangea# if (commandMatch != null && !sendingClient.commands.keys.contains(commandMatch[1]!.toLowerCase())) { final l10n = L10n.of(context); @@ -809,7 +833,13 @@ class ChatController extends State okLabel: l10n.sendAsText, cancelLabel: l10n.cancel, ); - if (dialogResult == OkCancelResult.cancel) return; + // #Pangea + // if (dialogResult == OkCancelResult.cancel) return; + if (dialogResult == OkCancelResult.cancel) { + clearFakeEvent(tempEventId); + return; + } + // Pangea# parseCommands = false; } @@ -821,15 +851,20 @@ class ChatController extends State // editEventId: editEvent?.eventId, // parseCommands: parseCommands, // ); - final previousEdit = editEvent; - // wait for the next event to come through before clearing any fake event, - // to make the replacement look smooth - room.client.onTimelineEvent.stream.first.then((_) => clearFakeEvent()); + // If the message and the sendController text don't match, it's possible + // that there was a delay in tokenization before send, and the user started + // typing a new message. We don't want to erase that, so only reset the input + // bar text if the message is the same as the sendController text. + if (message == sendController.text) { + sendController.setSystemText("", EditType.other); + } + + final previousEdit = editEvent; room .pangeaSendTextEvent( - sendController.text, + message, inReplyTo: replyEvent, editEventId: editEvent?.eventId, parseCommands: parseCommands, @@ -838,6 +873,7 @@ class ChatController extends State tokensSent: tokensSent, tokensWritten: tokensWritten, choreo: choreo, + tempEventId: tempEventId, ) .then( (String? msgEventId) async { @@ -914,7 +950,7 @@ class ChatController extends State s: StackTrace.current, data: { 'roomId': roomId, - 'text': sendController.text, + 'text': message, 'inReplyTo': replyEvent?.eventId, 'editEventId': editEvent?.eventId, }, @@ -923,7 +959,7 @@ class ChatController extends State } }, ).catchError((err, s) { - clearFakeEvent(); + clearFakeEvent(tempEventId); if (err is EventTooLarge) { showAdaptiveDialog( context: context, @@ -936,22 +972,21 @@ class ChatController extends State s: s, data: { 'roomId': roomId, - 'text': sendController.text, + 'text': message, 'inReplyTo': replyEvent?.eventId, 'editEventId': editEvent?.eventId, }, ); }); + // sendController.value = TextEditingValue( + // text: pendingText, + // selection: const TextSelection.collapsed(offset: 0), + // ); // Pangea# - sendController.value = TextEditingValue( - text: pendingText, - selection: const TextSelection.collapsed(offset: 0), - ); setState(() { // #Pangea // sendController.text = pendingText; - sendController.setSystemText(pendingText, EditType.other); // Pangea# _inputTextIsEmpty = pendingText.isEmpty; replyEvent = null; @@ -1898,7 +1933,10 @@ class ChatController extends State return; } // Close emoji picker, if open - showEmojiPicker = false; + if (showEmojiPicker) { + hideEmojiPicker(); + return; + } // Check if the user has set their languages. If not, prompt them to do so. if (!MatrixState.pangeaController.languageController.languagesSet) { diff --git a/lib/pages/chat/chat_input_row.dart b/lib/pages/chat/chat_input_row.dart index 0f6d253ab..c500f251f 100644 --- a/lib/pages/chat/chat_input_row.dart +++ b/lib/pages/chat/chat_input_row.dart @@ -320,7 +320,12 @@ class ChatInputRow extends StatelessWidget { ) : FloatingActionButton.small( tooltip: L10n.of(context).send, - onPressed: controller.send, + // #Pangea + // onPressed: controller.send, + onPressed: () => controller.send( + message: controller.sendController.text, + ), + // Pangea# elevation: 0, heroTag: null, shape: RoundedRectangleBorder( diff --git a/lib/pages/chat/chat_view.dart b/lib/pages/chat/chat_view.dart index 7a5f79220..97af603f2 100644 --- a/lib/pages/chat/chat_view.dart +++ b/lib/pages/chat/chat_view.dart @@ -109,44 +109,43 @@ class ChatView extends StatelessWidget { ], ), ]; + } else if (!controller.room.isArchived) { // #Pangea + return [ + IconButton( + icon: const Icon(Icons.search_outlined), + tooltip: L10n.of(context).search, + onPressed: () { + context.go('/rooms/${controller.room.id}/search'); + }, + ), + IconButton( + icon: const Icon(Icons.settings_outlined), + tooltip: L10n.of(context).chatDetails, + onPressed: () { + if (GoRouterState.of(context).uri.path.endsWith('/details')) { + context.go('/rooms/${controller.room.id}'); + } else { + context.go('/rooms/${controller.room.id}/details'); + } + }, + ), + ]; + // return [ + // if (AppConfig.experimentalVoip && + // Matrix.of(context).voipPlugin != null && + // controller.room.isDirectChat) + // IconButton( + // onPressed: controller.onPhoneButtonTap, + // icon: const Icon(Icons.call_outlined), + // tooltip: L10n.of(context).placeCall, + // ), + // EncryptionButton(controller.room), + // ChatSettingsPopupMenu(controller.room, true), + // ]; + // Pangea# } - // } else if (!controller.room.isArchived) { - // return [ - // if (AppConfig.experimentalVoip && - // Matrix.of(context).voipPlugin != null && - // controller.room.isDirectChat) - // IconButton( - // onPressed: controller.onPhoneButtonTap, - // icon: const Icon(Icons.call_outlined), - // tooltip: L10n.of(context).placeCall, - // ), - // EncryptionButton(controller.room), - // ChatSettingsPopupMenu(controller.room, true), - // ]; - // } - // return []; - return [ - IconButton( - icon: const Icon(Icons.search_outlined), - tooltip: L10n.of(context).search, - onPressed: () { - context.go('/rooms/${controller.room.id}/search'); - }, - ), - IconButton( - icon: const Icon(Icons.settings_outlined), - tooltip: L10n.of(context).chatDetails, - onPressed: () { - if (GoRouterState.of(context).uri.path.endsWith('/details')) { - context.go('/rooms/${controller.room.id}'); - } else { - context.go('/rooms/${controller.room.id}/details'); - } - }, - ), - ]; - // Pangea# + return []; } @override diff --git a/lib/pages/chat/events/html_message.dart b/lib/pages/chat/events/html_message.dart index 5f4c91434..f83b04689 100644 --- a/lib/pages/chat/events/html_message.dart +++ b/lib/pages/chat/events/html_message.dart @@ -389,6 +389,9 @@ class HtmlMessage extends StatelessWidget { if (matrixId.sigil == '@') { final user = room.unsafeGetUserFromMemoryOrFallback(matrixId); return WidgetSpan( + // #Pangea + alignment: PlaceholderAlignment.middle, + // Pangea# child: MatrixPill( key: Key('user_pill_$matrixId'), name: user.calcDisplayname(), @@ -397,6 +400,9 @@ class HtmlMessage extends StatelessWidget { outerContext: context, fontSize: fontSize, color: linkStyle.color, + // #Pangea + userId: user.id, + // Pangea# ), ); } @@ -405,6 +411,9 @@ class HtmlMessage extends StatelessWidget { ? this.room.client.getRoomById(matrixId) : this.room.client.getRoomByAlias(matrixId); return WidgetSpan( + // #Pangea + alignment: PlaceholderAlignment.middle, + // Pangea# child: MatrixPill( name: room?.getLocalizedDisplayname() ?? matrixId, avatar: room?.avatar, @@ -802,6 +811,9 @@ class MatrixPill extends StatelessWidget { final String uri; final double? fontSize; final Color? color; + // #Pangea + final String? userId; + // Pangea# const MatrixPill({ super.key, @@ -811,6 +823,9 @@ class MatrixPill extends StatelessWidget { required this.uri, required this.fontSize, required this.color, + // #Pangea + this.userId, + // Pangea# }); @override @@ -825,6 +840,9 @@ class MatrixPill extends StatelessWidget { mxContent: avatar, name: name, size: 16, + // #Pangea + userId: userId, + // Pangea# ), const SizedBox(width: 6), Text( diff --git a/lib/pages/chat/events/image_bubble.dart b/lib/pages/chat/events/image_bubble.dart index ea0439bec..f056366b1 100644 --- a/lib/pages/chat/events/image_bubble.dart +++ b/lib/pages/chat/events/image_bubble.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; +import 'package:cached_network_image/cached_network_image.dart'; import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:matrix/matrix.dart'; @@ -112,17 +113,39 @@ class ImageBubble extends StatelessWidget { borderRadius: borderRadius, child: Hero( tag: event.eventId, - child: MxcImage( - event: event, - width: width, - height: height, - fit: fit, - animated: animated, - isThumbnail: thumbnailOnly, - placeholder: event.messageType == MessageTypes.Sticker - ? null - : _buildPlaceholder, - ), + // #Pangea + child: event.content['url'] is String && + !(event.content['url'] as String).startsWith('mxc') + ? CachedNetworkImage( + imageUrl: event.content['url'] as String, + width: width, + height: height, + fit: fit, + placeholder: (context, url) => _buildPlaceholder(context), + ) + : MxcImage( + event: event, + width: width, + height: height, + fit: fit, + animated: animated, + isThumbnail: thumbnailOnly, + placeholder: event.messageType == MessageTypes.Sticker + ? null + : _buildPlaceholder, + ), + // child: MxcImage( + // event: event, + // width: width, + // height: height, + // fit: fit, + // animated: animated, + // isThumbnail: thumbnailOnly, + // placeholder: event.messageType == MessageTypes.Sticker + // ? null + // : _buildPlaceholder, + // ), + // Pangea# ), ), ), diff --git a/lib/pages/chat/events/message.dart b/lib/pages/chat/events/message.dart index 366b87f12..940c80b16 100644 --- a/lib/pages/chat/events/message.dart +++ b/lib/pages/chat/events/message.dart @@ -450,8 +450,12 @@ class Message extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ - if (event.relationshipType == - RelationshipTypes.reply) + if ({ + RelationshipTypes.reply, + RelationshipTypes.thread, + }.contains( + event.relationshipType, + )) FutureBuilder( future: event.getReplyEvent( timeline, diff --git a/lib/pages/chat/events/video_player.dart b/lib/pages/chat/events/video_player.dart index 882d2c950..b3b5bfc05 100644 --- a/lib/pages/chat/events/video_player.dart +++ b/lib/pages/chat/events/video_player.dart @@ -101,7 +101,6 @@ class EventVideoPlayerState extends State { autoPlay: true, autoInitialize: true, ); - // #Pangea _stopVideoSubscription?.cancel(); _stopVideoSubscription = diff --git a/lib/pages/chat/input_bar.dart b/lib/pages/chat/input_bar.dart index a0acda3da..d1c4f51fb 100644 --- a/lib/pages/chat/input_bar.dart +++ b/lib/pages/chat/input_bar.dart @@ -429,16 +429,7 @@ class InputBar extends StatelessWidget { direction: VerticalDirection.up, hideOnEmpty: true, hideOnLoading: true, - // #Pangea - // if should obscure text (to make it looks that a message has been sent after sending fake message), - // use hideTextController - - // controller: controller, - controller: - (controller?.choreographer.chatController.obscureText) ?? false - ? controller?.choreographer.chatController.hideTextController - : controller, - // Pangea# + controller: controller, focusNode: focusNode, hideOnSelect: false, debounceDuration: const Duration(milliseconds: 50), @@ -447,14 +438,10 @@ class InputBar extends StatelessWidget { builder: (context, _, focusNode) { final textField = TextField( enableSuggestions: enableAutocorrect, - readOnly: controller != null && - (controller!.choreographer.isRunningIT || - controller!.choreographer.chatController.obscureText), + readOnly: + controller != null && (controller!.choreographer.isRunningIT), autocorrect: enableAutocorrect, - controller: - (controller?.choreographer.chatController.obscureText) ?? false - ? controller?.choreographer.chatController.hideTextController - : controller, + controller: controller, focusNode: focusNode, contextMenuBuilder: (c, e) => markdownContextBuilder( c, diff --git a/lib/pages/chat_details/chat_details.dart b/lib/pages/chat_details/chat_details.dart index fa178d409..044289785 100644 --- a/lib/pages/chat_details/chat_details.dart +++ b/lib/pages/chat_details/chat_details.dart @@ -3,6 +3,7 @@ import 'dart:developer'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; +import 'package:collection/collection.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:go_router/go_router.dart'; import 'package:image_picker/image_picker.dart'; diff --git a/lib/pages/chat_details/participant_list_item.dart b/lib/pages/chat_details/participant_list_item.dart index 43743e868..8e24d3165 100644 --- a/lib/pages/chat_details/participant_list_item.dart +++ b/lib/pages/chat_details/participant_list_item.dart @@ -85,14 +85,12 @@ class ParticipantListItem extends StatelessWidget { ), ], ), - subtitle: - // #Pangea - LevelDisplayName(userId: user.id), - // Text( + // #Pangea + subtitle: LevelDisplayName(userId: user.id), + // subtitle: Text( // user.id, // maxLines: 1, // overflow: TextOverflow.ellipsis, - // ), // Pangea# leading: Opacity( opacity: user.membership == Membership.join ? 1 : 0.5, diff --git a/lib/pages/chat_list/chat_list_body.dart b/lib/pages/chat_list/chat_list_body.dart index 4fa105675..7dad30408 100644 --- a/lib/pages/chat_list/chat_list_body.dart +++ b/lib/pages/chat_list/chat_list_body.dart @@ -11,6 +11,7 @@ import 'package:fluffychat/pages/chat_list/dummy_chat_list_item.dart'; import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pages/chat_list/space_view.dart'; import 'package:fluffychat/pangea/chat_list/widgets/pangea_chat_list_header.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding.dart'; import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/user_dialog.dart'; @@ -287,6 +288,16 @@ class ChatListViewBody extends StatelessWidget { ); }, ), + // #Pangea + const SliverPadding(padding: EdgeInsets.all(12.0)), + if (!FluffyThemes.isColumnMode(context)) + SliverList.builder( + itemCount: 1, + itemBuilder: (context, _) { + return const Onboarding(); + }, + ), + // Pangea# ], ), ); @@ -387,6 +398,7 @@ class _SearchItem extends StatelessWidget { final void Function() onPressed; // #Pangea final BorderRadius? radius; + final String? userId; // Pangea# const _SearchItem({ @@ -395,6 +407,7 @@ class _SearchItem extends StatelessWidget { required this.onPressed, // #Pangea this.radius, + this.userId, // Pangea# }); @@ -412,6 +425,7 @@ class _SearchItem extends StatelessWidget { name: title, // #Pangea borderRadius: radius, + userId: userId, // Pangea# ), Padding( @@ -467,6 +481,7 @@ class UserSearchResultsListState extends State { widget.userSearchResult.results[i].userId.localpart ?? L10n.of(context).unknownDevice, avatar: widget.userSearchResult.results[i].avatarUrl, + userId: widget.userSearchResult.results[i].userId, onPressed: () => UserDialog.show( context: context, profile: widget.userSearchResult.results[i], diff --git a/lib/pages/chat_list/chat_list_view.dart b/lib/pages/chat_list/chat_list_view.dart index f2ed0c1c1..3351a67fe 100644 --- a/lib/pages/chat_list/chat_list_view.dart +++ b/lib/pages/chat_list/chat_list_view.dart @@ -7,6 +7,8 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pangea/chat_list/widgets/chat_list_view_body_wrapper.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart'; import 'package:fluffychat/widgets/navigation_rail.dart'; class ChatListView extends StatelessWidget { @@ -41,6 +43,9 @@ class ChatListView extends StatelessWidget { activeSpaceId: controller.activeSpaceId, onGoToChats: controller.clearActiveSpace, onGoToSpaceId: controller.setActiveSpace, + // #Pangea + clearActiveSpace: controller.clearActiveSpace, + // Pangea# ), Container( color: Theme.of(context).dividerColor, @@ -56,14 +61,25 @@ class ChatListView extends StatelessWidget { // #Pangea // body: ChatListViewBody(controller), body: ChatListViewBodyWrapper(controller: controller), - // Pangea# + // floatingActionButton: !controller.isSearchMode && + // controller.activeSpaceId == null floatingActionButton: !controller.isSearchMode && - controller.activeSpaceId == null + controller.activeSpaceId == null && + OnboardingController.complete( + OnboardingStepsEnum.chatWithBot, + ) + // Pangea# ? FloatingActionButton.extended( onPressed: () => context.go('/rooms/newprivatechat'), - icon: const Icon(Icons.add_outlined), + // #Pangea + icon: const Icon(Icons.chat_bubble_outline), + // icon: const Icon(Icons.add_outlined), + // Pangea# label: Text( - L10n.of(context).chat, + // #Pangea + L10n.of(context).directMessage, + // L10n.of(context).chat, + // Pangea# overflow: TextOverflow.fade, ), ) diff --git a/lib/pages/chat_list/client_chooser_button.dart b/lib/pages/chat_list/client_chooser_button.dart index 252ec83e5..68acc13cb 100644 --- a/lib/pages/chat_list/client_chooser_button.dart +++ b/lib/pages/chat_list/client_chooser_button.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -56,28 +57,27 @@ class ClientChooserButton extends StatelessWidget { ], ), ), - // Currently disabled because of: - // https://github.com/matrix-org/matrix-react-sdk/pull/12286 - /*PopupMenuItem( + PopupMenuItem( value: SettingsAction.archive, child: Row( children: [ const Icon(Icons.archive_outlined), const SizedBox(width: 18), - Text(L10n.of(context)!.archive), - ], - ), - ),*/ - PopupMenuItem( - value: SettingsAction.settings, - child: Row( - children: [ - const Icon(Icons.settings_outlined), - const SizedBox(width: 18), - Text(L10n.of(context).settings), + Text(L10n.of(context).archive), ], ), ), + if (!FluffyThemes.isColumnMode(context)) + PopupMenuItem( + value: SettingsAction.settings, + child: Row( + children: [ + const Icon(Icons.settings_outlined), + const SizedBox(width: 18), + Text(L10n.of(context).settings), + ], + ), + ), const PopupMenuDivider(), for (final bundle in bundles) ...[ if (matrix.accountBundles[bundle]!.length != 1 || @@ -158,30 +158,22 @@ class ClientChooserButton extends StatelessWidget { matrix.accountBundles.forEach((key, value) => clientCount += value.length); return FutureBuilder( future: matrix.client.fetchOwnProfile(), - builder: (context, snapshot) => Stack( - alignment: Alignment.center, - children: [ - ...List.generate( - clientCount, - (index) => const SizedBox.shrink(), - ), - const SizedBox.shrink(), - const SizedBox.shrink(), - PopupMenuButton( - onSelected: (o) => _clientSelected(o, context), - itemBuilder: _bundleMenuItems, - child: Material( - color: Colors.transparent, - borderRadius: BorderRadius.circular(99), - child: Avatar( - mxContent: snapshot.data?.avatarUrl, - name: snapshot.data?.displayName ?? - matrix.client.userID!.localpart, - size: 32, - ), + builder: (context, snapshot) => Material( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(99), + color: Colors.transparent, + child: PopupMenuButton( + onSelected: (o) => _clientSelected(o, context), + itemBuilder: _bundleMenuItems, + child: Center( + child: Avatar( + mxContent: snapshot.data?.avatarUrl, + name: + snapshot.data?.displayName ?? matrix.client.userID!.localpart, + size: 32, ), ), - ], + ), ), ); } diff --git a/lib/pages/chat_list/navi_rail_item.dart b/lib/pages/chat_list/navi_rail_item.dart index 5b4677dd6..f221955e5 100644 --- a/lib/pages/chat_list/navi_rail_item.dart +++ b/lib/pages/chat_list/navi_rail_item.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart'; import 'package:badges/badges.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/widgets/hover_builder.dart'; import 'package:fluffychat/widgets/unread_rooms_badge.dart'; import '../../config/themes.dart'; @@ -35,14 +34,27 @@ class NaviRailItem extends StatelessWidget { Widget build(BuildContext context) { final theme = Theme.of(context); - final borderRadius = BorderRadius.circular(AppConfig.borderRadius); + // #Pangea + // final borderRadius = BorderRadius.circular(AppConfig.borderRadius); + final borderRadius = BorderRadius.circular(10.0); + + final isColumnMode = FluffyThemes.isColumnMode(context); + final width = isColumnMode + ? FluffyThemes.navRailWidth + : FluffyThemes.navRailWidth - 8.0; + // Pangea# final icon = isSelected ? selectedIcon ?? this.icon : this.icon; final unreadBadgeFilter = this.unreadBadgeFilter; return HoverBuilder( builder: (context, hovered) { + // #Pangea + // return SizedBox( + // height: 72, return SizedBox( - height: 72, - width: FluffyThemes.navRailWidth, + height: width - (isColumnMode ? 16.0 : 12.0), + width: width, + // width: FluffyThemes.navRailWidth, + // Pangea# child: Stack( children: [ Positioned( @@ -53,7 +65,7 @@ class NaviRailItem extends StatelessWidget { // #Pangea // width: isSelected ? 8 : 0, width: isSelected - ? FluffyThemes.isColumnMode(context) + ? isColumnMode ? 8 : 4 : 0, @@ -74,16 +86,25 @@ class NaviRailItem extends StatelessWidget { scale: hovered ? 1.1 : 1.0, duration: FluffyThemes.animationDuration, curve: FluffyThemes.animationCurve, - child: Material( - borderRadius: borderRadius, - // #Pangea - // color: isSelected - // ? theme.colorScheme.primaryContainer - // : theme.colorScheme.surfaceContainerHigh, - color: backgroundColor ?? - (isSelected - ? theme.colorScheme.primaryContainer - : theme.colorScheme.surfaceContainerHigh), + // #Pangea + // child: Material( + // borderRadius: borderRadius, + // color: isSelected + // ? theme.colorScheme.primaryContainer + // : theme.colorScheme.surfaceContainerHigh, + child: Container( + alignment: Alignment.center, + decoration: BoxDecoration( + color: backgroundColor ?? + (isSelected + ? theme.colorScheme.primaryContainer + : theme.colorScheme.surfaceContainerHigh), + borderRadius: borderRadius, + ), + margin: EdgeInsets.symmetric( + horizontal: isColumnMode ? 16.0 : 12.0, + vertical: isColumnMode ? 8.0 : 6.0, + ), // Pangea# child: Tooltip( message: toolTip, @@ -95,8 +116,12 @@ class NaviRailItem extends StatelessWidget { : UnreadRoomsBadge( filter: unreadBadgeFilter, badgePosition: BadgePosition.topEnd( - top: -12, - end: -8, + // #Pangea + // top: -12, + // end: -8, + top: -20, + end: -16, + // Pangea# ), child: icon, ), diff --git a/lib/pages/chat_list/space_view.dart b/lib/pages/chat_list/space_view.dart index 1bde3fb21..6f60cef4b 100644 --- a/lib/pages/chat_list/space_view.dart +++ b/lib/pages/chat_list/space_view.dart @@ -17,9 +17,11 @@ import 'package:fluffychat/pages/chat_list/search_title.dart'; import 'package:fluffychat/pangea/chat_settings/constants/pangea_room_types.dart'; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding.dart'; import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart'; import 'package:fluffychat/pangea/spaces/constants/space_constants.dart'; import 'package:fluffychat/pangea/spaces/widgets/knocking_users_indicator.dart'; +import 'package:fluffychat/pangea/spaces/widgets/leaderboard_participant_list.dart'; import 'package:fluffychat/utils/localized_exception_extension.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; @@ -176,13 +178,17 @@ class _SpaceViewState extends State { await _joinDefaultChats(); } catch (e, s) { Logs().w('Unable to load hierarchy', e, s); - if (!mounted) return; - ScaffoldMessenger.of(context) - .showSnackBar(SnackBar(content: Text(e.toLocalizedString(context)))); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(e.toLocalizedString(context))), + ); + } } finally { - setState(() { - _isLoading = false; - }); + if (mounted) { + setState(() { + _isLoading = false; + }); + } } } @@ -680,7 +686,7 @@ class _SpaceViewState extends State { // label: Text(L10n.of(context).group), onPressed: () => context.go("/rooms/newgroup?space=${widget.spaceId}"), - label: Text(L10n.of(context).chat), + label: Text(L10n.of(context).groupChat), // Pangea# icon: const Icon(Icons.group_add_outlined), ) @@ -802,6 +808,14 @@ class _SpaceViewState extends State { // }, // ), KnockingUsersIndicator(room: room), + SliverList.builder( + itemCount: 1, + itemBuilder: (context, i) { + return LeaderboardParticipantList( + space: room, + ); + }, + ), // Pangea# SliverList.builder( itemCount: joinedRooms.length, @@ -943,6 +957,16 @@ class _SpaceViewState extends State { ); }, ), + // #Pangea + const SliverPadding(padding: EdgeInsets.all(12.0)), + if (!FluffyThemes.isColumnMode(context)) + SliverList.builder( + itemCount: 1, + itemBuilder: (context, _) { + return const Onboarding(); + }, + ), + // Pangea# const SliverPadding(padding: EdgeInsets.only(top: 32)), ], ); diff --git a/lib/pages/chat_list/status_msg_list.dart b/lib/pages/chat_list/status_msg_list.dart index 9a7972f32..15402c99a 100644 --- a/lib/pages/chat_list/status_msg_list.dart +++ b/lib/pages/chat_list/status_msg_list.dart @@ -98,11 +98,21 @@ class PresenceAvatar extends StatelessWidget { final CachedPresence presence; final double height; final void Function(Profile) onTap; + // #Pangea + final LinearGradient? gradient; + final Widget? floatingIndicator; + final bool showPresence; + // Pangea# const PresenceAvatar({ required this.presence, required this.height, required this.onTap, + // #Pangea + this.gradient, + this.showPresence = true, + this.floatingIndicator, + // Pangea# super.key, }); @@ -146,7 +156,11 @@ class PresenceAvatar extends StatelessWidget { Container( padding: const EdgeInsets.all(3), decoration: BoxDecoration( - gradient: presence.gradient, + // #Pangea + // gradient: presence.gradient, + gradient: gradient ?? + (showPresence ? presence.gradient : null), + // Pangea# borderRadius: BorderRadius.circular(avatarSize), ), @@ -159,7 +173,11 @@ class PresenceAvatar extends StatelessWidget { size: avatarSize - 6, ), ), - if (presence.userid == client.userID) + // #Pangea + // if (presence.userid == client.userID) + if (floatingIndicator == null && + presence.userid == client.userID) + // Pangea# Positioned( right: 0, bottom: 0, @@ -182,6 +200,9 @@ class PresenceAvatar extends StatelessWidget { ), ), ), + // #Pangea + if (floatingIndicator != null) floatingIndicator!, + // Pangea# if (statusMsg != null) ...[ Positioned( left: 0, diff --git a/lib/pages/chat_members/chat_members.dart b/lib/pages/chat_members/chat_members.dart index 6d1cf22ee..6376a9b2c 100644 --- a/lib/pages/chat_members/chat_members.dart +++ b/lib/pages/chat_members/chat_members.dart @@ -34,11 +34,7 @@ class ChatMembersController extends State { final members = this .members - ?.where( - (member) => - membershipFilter == Membership.join || - member.membership == membershipFilter, - ) + ?.where((member) => member.membership == membershipFilter) .toList(); if (filter.isEmpty) { diff --git a/lib/pages/chat_members/chat_members_view.dart b/lib/pages/chat_members/chat_members_view.dart index e1598344c..67bf67cd2 100644 --- a/lib/pages/chat_members/chat_members_view.dart +++ b/lib/pages/chat_members/chat_members_view.dart @@ -144,9 +144,30 @@ class ChatMembersView extends StatelessWidget { Membership.ban => L10n.of(context).banned, Membership.invite => - L10n.of(context).invited, + L10n.of(context).countInvited( + room.summary + .mInvitedMemberCount ?? + controller.members + ?.where( + (member) => + member.membership == + Membership.invite, + ) + .length ?? + 0, + ), Membership.join => - L10n.of(context).all, + L10n.of(context).countParticipants( + room.summary.mJoinedMemberCount ?? + controller.members + ?.where( + (member) => + member.membership == + Membership.join, + ) + .length ?? + 0, + ), Membership.knock => L10n.of(context).knocking, Membership.leave => diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart index e42917b94..e001ba6ca 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings.dart @@ -7,6 +7,7 @@ import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/pages/chat_permissions_settings/chat_permissions_settings_view.dart'; +import 'package:fluffychat/pangea/chat/constants/default_power_level.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/matrix.dart'; import 'package:fluffychat/widgets/permission_slider_dialog.dart'; @@ -71,6 +72,59 @@ class ChatPermissionsSettingsController extends State { false), ); + // #Pangea + Map get defaultPowerLevels { + final chatPowerLevels = RoomDefaults.defaultPowerLevels( + Matrix.of(context).client.userID!, + ).content; + + final spacePowerLevels = RoomDefaults.defaultSpacePowerLevels( + Matrix.of(context).client.userID!, + ).content; + + if (roomId == null) return chatPowerLevels; + final room = Matrix.of(context).client.getRoomById(roomId!); + if (room == null) return chatPowerLevels; + + return room.isSpace ? spacePowerLevels : chatPowerLevels; + } + + int getDefaultValue( + String permissionKey, { + String? category, + }) { + final room = Matrix.of(context).client.getRoomById(roomId!); + if (room == null) return 0; + final powerLevelsContent = Map.from( + room.getState(EventTypes.RoomPowerLevels)?.content ?? {}, + ); + + final powerLevels = Map.from(powerLevelsContent) + ..removeWhere((k, v) => v is! int); + + if (category == null) { + switch (permissionKey) { + case 'users_default': + case 'events_default': + return powerLevels[permissionKey] ?? 0; + case 'state_default': + return powerLevels[permissionKey] ?? 50; + case 'ban': + case 'kick': + case 'invite': + return powerLevels[permissionKey] ?? 0; + case 'redact': + return powerLevels[permissionKey] ?? + powerLevels['events_default'] ?? + 0; + } + } else if (category == 'events') { + return room.powerForChangingStateEvent(permissionKey); + } + return 0; + } + // Pangea# + @override Widget build(BuildContext context) => ChatPermissionsSettingsView(this); } diff --git a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart index f4e25d9f9..b2c57597e 100644 --- a/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart +++ b/lib/pages/chat_permissions_settings/chat_permissions_settings_view.dart @@ -44,6 +44,35 @@ class ChatPermissionsSettingsView extends StatelessWidget { final eventsPowerLevels = Map.from( powerLevelsContent.tryGetMap('events') ?? {}, )..removeWhere((k, v) => v is! int); + // #Pangea + final defaults = Map.from( + controller.defaultPowerLevels, + ); + + Map missingPowerLevels = Map.from( + defaults, + )..removeWhere((k, v) => v is! int || powerLevels.containsKey(k)); + + missingPowerLevels = missingPowerLevels.map( + (key, value) => MapEntry(key, controller.getDefaultValue(key)), + ); + + Map missingEventsPowerLevels = Map.from( + defaults.tryGetMap('events') ?? {}, + )..removeWhere( + (k, v) => v is! int || eventsPowerLevels.containsKey(k), + ); + + missingEventsPowerLevels = missingEventsPowerLevels.map( + (key, value) => MapEntry( + key, + controller.getDefaultValue(key, category: 'events'), + ), + ); + + powerLevels.addAll(missingPowerLevels); + eventsPowerLevels.addAll(missingEventsPowerLevels); + // Pangea# return Column( children: [ ListTile( diff --git a/lib/pages/chat_permissions_settings/permission_list_tile.dart b/lib/pages/chat_permissions_settings/permission_list_tile.dart index a8f14e002..bdee6543c 100644 --- a/lib/pages/chat_permissions_settings/permission_list_tile.dart +++ b/lib/pages/chat_permissions_settings/permission_list_tile.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; class PermissionsListTile extends StatelessWidget { final String permissionKey; @@ -100,6 +101,8 @@ class PermissionsListTile extends StatelessWidget { return L10n.of(context).pinMessages; case EventTypes.RoomJoinRules: return L10n.of(context).setJoinRules; + case PangeaEventTypes.activityPlan: + return L10n.of(context).sendActivities; // Pangea# } } diff --git a/lib/pages/new_private_chat/new_private_chat_view.dart b/lib/pages/new_private_chat/new_private_chat_view.dart index f5274e59f..171691af5 100644 --- a/lib/pages/new_private_chat/new_private_chat_view.dart +++ b/lib/pages/new_private_chat/new_private_chat_view.dart @@ -30,7 +30,10 @@ class NewPrivateChatView extends StatelessWidget { appBar: AppBar( scrolledUnderElevation: 0, leading: const Center(child: BackButton()), - title: Text(L10n.of(context).newChat), + // #Pangea + title: Text(L10n.of(context).newDirectMessage), + // title: Text(L10n.of(context).newChat), + // Pangea# backgroundColor: theme.scaffoldBackgroundColor, // #Pangea // actions: [ @@ -125,7 +128,7 @@ class NewPrivateChatView extends StatelessWidget { ), style: TextStyle( color: theme.colorScheme.onSurface, - fontSize: 13, + fontSize: 12, ), ), ), @@ -167,9 +170,15 @@ class NewPrivateChatView extends StatelessWidget { vertical: 24.0, ), child: Material( - borderRadius: - BorderRadius.circular(AppConfig.borderRadius), - color: theme.colorScheme.primaryContainer, + shape: RoundedRectangleBorder( + borderRadius: + BorderRadius.circular(AppConfig.borderRadius), + side: BorderSide( + width: 3, + color: theme.colorScheme.primary, + ), + ), + color: Colors.transparent, clipBehavior: Clip.hardEdge, child: InkWell( borderRadius: @@ -179,10 +188,10 @@ class NewPrivateChatView extends StatelessWidget { userId, ), child: Padding( - padding: const EdgeInsets.all(32.0), + padding: const EdgeInsets.all(16.0), child: ConstrainedBox( constraints: - const BoxConstraints(maxWidth: 256), + const BoxConstraints(maxWidth: 200), child: PrettyQrView.data( // #Pangea // data: 'https://matrix.to/#/$userId', @@ -191,8 +200,7 @@ class NewPrivateChatView extends StatelessWidget { decoration: PrettyQrDecoration( shape: PrettyQrSmoothSymbol( roundFactor: 1, - color: - theme.colorScheme.onPrimaryContainer, + color: theme.colorScheme.primary, ), ), ), diff --git a/lib/pages/settings/settings.dart b/lib/pages/settings/settings.dart index 00acd3f38..6ea6f417d 100644 --- a/lib/pages/settings/settings.dart +++ b/lib/pages/settings/settings.dart @@ -207,6 +207,36 @@ class SettingsController extends State { // Pangea# } + // #Pangea + void setStatus() async { + final client = Matrix.of(context).client; + final currentPresence = await client.fetchCurrentPresence(client.userID!); + final input = await showTextInputDialog( + useRootNavigator: false, + context: context, + title: L10n.of(context).setStatus, + message: L10n.of(context).leaveEmptyToClearStatus, + okLabel: L10n.of(context).ok, + cancelLabel: L10n.of(context).cancel, + hintText: L10n.of(context).statusExampleMessage, + maxLines: 6, + minLines: 1, + maxLength: 255, + initialText: currentPresence.statusMsg, + ); + if (input == null) return; + if (!mounted) return; + await showFutureLoadingDialog( + context: context, + future: () => client.setPresence( + client.userID!, + PresenceType.online, + statusMsg: input, + ), + ); + } + // Pangea# + @override Widget build(BuildContext context) { final client = Matrix.of(context).client; diff --git a/lib/pages/settings/settings_view.dart b/lib/pages/settings/settings_view.dart index 4c51ea86d..ee6a74e51 100644 --- a/lib/pages/settings/settings_view.dart +++ b/lib/pages/settings/settings_view.dart @@ -165,6 +165,25 @@ class SettingsView extends StatelessWidget { // style: const TextStyle(fontSize: 12), ), ), + // #Pangea + TextButton.icon( + onPressed: controller.setStatus, + icon: const Icon( + Icons.add, + size: 14, + ), + style: TextButton.styleFrom( + foregroundColor: + theme.colorScheme.secondary, + iconColor: theme.colorScheme.secondary, + ), + label: Text( + L10n.of(context).setStatus, + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + // Pangea# ], ), ), diff --git a/lib/pangea/activity_planner/activity_planner_builder.dart b/lib/pangea/activity_planner/activity_planner_builder.dart index cb0bf261b..48948cff0 100644 --- a/lib/pangea/activity_planner/activity_planner_builder.dart +++ b/lib/pangea/activity_planner/activity_planner_builder.dart @@ -1,5 +1,3 @@ -import 'dart:typed_data'; - import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -25,8 +23,6 @@ class ActivityPlannerBuilder extends StatefulWidget { final Future Function( String, ActivityPlanModel, - Uint8List?, - String?, )? onEdit; const ActivityPlannerBuilder({ @@ -204,8 +200,6 @@ class ActivityPlannerBuilderState extends State { await widget.onEdit!( widget.initialActivity.bookmarkId, updatedActivity, - avatar, - filename, ); } } @@ -225,7 +219,6 @@ class ActivityPlannerBuilderState extends State { updatedActivity, avatar: avatar, filename: filename, - avatarURL: imageURL, ); } diff --git a/lib/pangea/activity_planner/activity_planner_page_appbar.dart b/lib/pangea/activity_planner/activity_planner_page_appbar.dart index cdccf5950..8322d9ca3 100644 --- a/lib/pangea/activity_planner/activity_planner_page_appbar.dart +++ b/lib/pangea/activity_planner/activity_planner_page_appbar.dart @@ -4,6 +4,7 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:go_router/go_router.dart'; import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart'; import 'package:fluffychat/pangea/activity_suggestions/activity_suggestions_constants.dart'; import 'package:fluffychat/pangea/common/widgets/customized_svg.dart'; @@ -28,16 +29,6 @@ class ActivityPlannerPageAppBar extends StatelessWidget final theme = Theme.of(context); return AppBar( - leadingWidth: 150.0, - leading: Row( - children: [ - const SizedBox(width: 8.0), - IconButton( - icon: const Icon(Icons.arrow_back), - onPressed: () => Navigator.of(context).pop(), - ), - ], - ), title: pageMode == PageMode.savedActivities ? Row( mainAxisAlignment: MainAxisAlignment.center, @@ -63,52 +54,59 @@ class ActivityPlannerPageAppBar extends StatelessWidget ], ), actions: [ - Container( - width: 150.0, - alignment: Alignment.center, - child: InkWell( - customBorder: const CircleBorder(), - onTap: () => roomID != null - ? context.go('/rooms/$roomID/details/planner/generator') - : context.go("/rooms/homepage/planner/generator"), - child: Container( - decoration: BoxDecoration( - color: theme.colorScheme.surfaceContainerHighest, - borderRadius: BorderRadius.circular(36.0), - ), - padding: const EdgeInsets.symmetric( - vertical: 6.0, - horizontal: 10.0, - ), - child: Row( - spacing: 8.0, - mainAxisSize: MainAxisSize.min, - children: [ - CustomizedSvg( - svgUrl: - "${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.crayonIconPath}", - colorReplacements: { - "#CDBEF9": colorToHex( - theme.colorScheme.secondary, - ), - }, - height: 16.0, - width: 16.0, - ), - Flexible( - child: Text( - L10n.of(context).createActivity, - style: theme.textTheme.titleSmall?.copyWith( - fontWeight: FontWeight.bold, - ), - overflow: TextOverflow.ellipsis, + FluffyThemes.isColumnMode(context) + ? Container( + width: 150.0, + alignment: Alignment.center, + child: InkWell( + customBorder: const CircleBorder(), + onTap: () => roomID != null + ? context.go('/rooms/$roomID/details/planner/generator') + : context.go("/rooms/homepage/planner/generator"), + child: Container( + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest, + borderRadius: BorderRadius.circular(36.0), + ), + padding: const EdgeInsets.symmetric( + vertical: 6.0, + horizontal: 10.0, + ), + child: Row( + spacing: 8.0, + mainAxisSize: MainAxisSize.min, + children: [ + CustomizedSvg( + svgUrl: + "${AppConfig.assetsBaseURL}/${ActivitySuggestionsConstants.crayonIconPath}", + colorReplacements: { + "#CDBEF9": colorToHex( + theme.colorScheme.secondary, + ), + }, + height: 16.0, + width: 16.0, + ), + Flexible( + child: Text( + L10n.of(context).createActivity, + style: theme.textTheme.titleSmall?.copyWith( + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, + ), + ), + ], ), ), - ], + ), + ) + : IconButton( + icon: const Icon(Icons.add), + onPressed: () => roomID != null + ? context.go('/rooms/$roomID/details/planner/generator') + : context.go("/rooms/homepage/planner/generator"), ), - ), - ), - ), ], ); } diff --git a/lib/pangea/activity_planner/bookmarked_activity_list.dart b/lib/pangea/activity_planner/bookmarked_activity_list.dart index 168f5d149..8793bd6b3 100644 --- a/lib/pangea/activity_planner/bookmarked_activity_list.dart +++ b/lib/pangea/activity_planner/bookmarked_activity_list.dart @@ -1,5 +1,3 @@ -import 'dart:typed_data'; - import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; @@ -12,7 +10,6 @@ import 'package:fluffychat/pangea/activity_planner/activity_planner_page.dart'; import 'package:fluffychat/pangea/activity_planner/bookmarked_activities_repo.dart'; import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_card.dart'; import 'package:fluffychat/pangea/activity_suggestions/activity_suggestion_dialog.dart'; -import 'package:fluffychat/widgets/matrix.dart'; class BookmarkedActivitiesList extends StatefulWidget { final Room? room; @@ -42,27 +39,7 @@ class BookmarkedActivitiesListState extends State { Future _onEdit( String activityId, ActivityPlanModel activity, - Uint8List? avatar, - String? filename, ) async { - if (avatar != null) { - final url = await Matrix.of(context).client.uploadContent( - avatar, - filename: filename, - ); - if (!mounted) return; - setState(() { - activity = ActivityPlanModel( - req: activity.req, - title: activity.title, - learningObjective: activity.learningObjective, - instructions: activity.instructions, - vocab: activity.vocab, - imageURL: url.toString(), - ); - }); - } - await BookmarkedActivitiesRepo.remove(activityId); await BookmarkedActivitiesRepo.save(activity); if (mounted) setState(() {}); diff --git a/lib/pangea/activity_suggestions/activity_room_selection.dart b/lib/pangea/activity_suggestions/activity_room_selection.dart index cc1ed8ad4..45dc9b066 100644 --- a/lib/pangea/activity_suggestions/activity_room_selection.dart +++ b/lib/pangea/activity_suggestions/activity_room_selection.dart @@ -128,7 +128,6 @@ class ActivityRoomSelectionState extends State { widget.controller.updatedActivity, avatar: widget.controller.avatar, filename: widget.controller.filename, - avatarURL: widget.controller.imageURL, ); _launchStatus[room.id] = 1; } catch (e, s) { @@ -205,7 +204,6 @@ class ActivityRoomSelectionState extends State { widget.controller.updatedActivity, avatar: widget.controller.avatar, filename: widget.controller.filename, - avatarURL: widget.controller.imageURL, ); } _launchStatus["placeholder"] = 1; diff --git a/lib/pangea/activity_suggestions/activity_suggestions_area.dart b/lib/pangea/activity_suggestions/activity_suggestions_area.dart index ba2bd3e5e..0e4121969 100644 --- a/lib/pangea/activity_suggestions/activity_suggestions_area.dart +++ b/lib/pangea/activity_suggestions/activity_suggestions_area.dart @@ -114,13 +114,15 @@ class ActivitySuggestionsAreaState extends State { final resp = await ActivitySearchRepo.get(request).timeout( const Duration(seconds: 5), onTimeout: () { - setState(() { - _timeout = true; - _loading = false; - }); + if (mounted) { + setState(() { + _timeout = true; + _loading = false; + }); + } Future.delayed(const Duration(seconds: 5), () { - _setActivityItems(retries: retries + 1); + if (mounted) _setActivityItems(retries: retries + 1); }); return ActivityPlanResponse(activityPlans: []); }, @@ -206,7 +208,7 @@ class ActivitySuggestionsAreaState extends State { ), ), IconButton( - icon: const Icon(Icons.menu_outlined), + icon: const Icon(Icons.event_note_outlined), onPressed: () => context.go('/rooms/homepage/planner'), tooltip: L10n.of(context).activityPlannerTitle, ), diff --git a/lib/pangea/activity_suggestions/suggestions_page.dart b/lib/pangea/activity_suggestions/suggestions_page.dart index e46be8e91..8512db450 100644 --- a/lib/pangea/activity_suggestions/suggestions_page.dart +++ b/lib/pangea/activity_suggestions/suggestions_page.dart @@ -15,8 +15,9 @@ class SuggestionsPage extends StatelessWidget { @override Widget build(BuildContext context) { final isColumnMode = FluffyThemes.isColumnMode(context); - return Material( - child: SafeArea( + return Scaffold( + resizeToAvoidBottomInset: true, + body: SafeArea( child: Row( crossAxisAlignment: CrossAxisAlignment.start, children: [ diff --git a/lib/pangea/analytics_misc/client_analytics_extension.dart b/lib/pangea/analytics_misc/client_analytics_extension.dart index 2fa509be7..9eb1ca142 100644 --- a/lib/pangea/analytics_misc/client_analytics_extension.dart +++ b/lib/pangea/analytics_misc/client_analytics_extension.dart @@ -132,7 +132,11 @@ extension AnalyticsClientExtension on Client { /// so other members of the space need to add their analytics rooms to the space. Future addAnalyticsRoomsToSpaces() async { if (userID == null || userID == BotName.byEnvironment) return; - final spaces = rooms.where((room) => room.isSpace).toList(); + final spaces = rooms + .where( + (room) => room.isSpace && room.membership == Membership.join, + ) + .toList(); final Random random = Random(); for (final space in spaces) { diff --git a/lib/pangea/analytics_summary/learning_progress_indicator_button.dart b/lib/pangea/analytics_summary/learning_progress_indicator_button.dart index 542293f1d..30ea42199 100644 --- a/lib/pangea/analytics_summary/learning_progress_indicator_button.dart +++ b/lib/pangea/analytics_summary/learning_progress_indicator_button.dart @@ -2,14 +2,18 @@ import 'package:flutter/material.dart'; import 'package:fluffychat/widgets/hover_builder.dart'; -class LearningProgressIndicatorButton extends StatelessWidget { +class HoverButton extends StatelessWidget { final VoidCallback? onPressed; final Widget child; + final BorderRadius? borderRadius; + final double hoverOpacity; - const LearningProgressIndicatorButton({ + const HoverButton({ super.key, required this.onPressed, required this.child, + this.borderRadius, + this.hoverOpacity = 0.2, }); @override @@ -23,9 +27,12 @@ class LearningProgressIndicatorButton extends StatelessWidget { child: Container( decoration: BoxDecoration( color: hovered - ? Theme.of(context).colorScheme.primary.withAlpha(50) + ? Theme.of(context) + .colorScheme + .primary + .withAlpha((hoverOpacity * 255).round()) : Colors.transparent, - borderRadius: BorderRadius.circular(36.0), + borderRadius: borderRadius ?? BorderRadius.circular(36.0), ), padding: const EdgeInsets.symmetric( vertical: 2.0, diff --git a/lib/pangea/analytics_summary/learning_progress_indicators.dart b/lib/pangea/analytics_summary/learning_progress_indicators.dart index c25d378fb..d7a6d1c98 100644 --- a/lib/pangea/analytics_summary/learning_progress_indicators.dart +++ b/lib/pangea/analytics_summary/learning_progress_indicators.dart @@ -105,7 +105,7 @@ class LearningProgressIndicatorsState spacing: 16.0, children: ConstructTypeEnum.values .map( - (c) => LearningProgressIndicatorButton( + (c) => HoverButton( onPressed: () { showDialog( context: context, @@ -124,7 +124,7 @@ class LearningProgressIndicatorsState .toList(), ), ), - LearningProgressIndicatorButton( + HoverButton( onPressed: () => showDialog( context: context, builder: (c) => const SettingsLearning(), diff --git a/lib/pangea/chat/constants/default_power_level.dart b/lib/pangea/chat/constants/default_power_level.dart index c52c65b53..cc354b346 100644 --- a/lib/pangea/chat/constants/default_power_level.dart +++ b/lib/pangea/chat/constants/default_power_level.dart @@ -1,5 +1,7 @@ import 'package:matrix/matrix.dart'; +import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; + class RoomDefaults { static StateEvent defaultPowerLevels(String userID) => StateEvent( type: EventTypes.RoomPowerLevels, @@ -10,6 +12,7 @@ class RoomDefaults { "invite": 50, "redact": 50, "events": { + PangeaEventTypes.activityPlan: 0, "m.room.power_levels": 100, "m.room.pinned_events": 50, }, @@ -34,6 +37,7 @@ class RoomDefaults { "invite": 50, "redact": 50, "events": { + PangeaEventTypes.activityPlan: 50, "m.room.power_levels": 100, "m.room.pinned_events": 50, }, diff --git a/lib/pangea/chat/widgets/chat_input_bar.dart b/lib/pangea/chat/widgets/chat_input_bar.dart index bad1545b1..f824549dd 100644 --- a/lib/pangea/chat/widgets/chat_input_bar.dart +++ b/lib/pangea/chat/widgets/chat_input_bar.dart @@ -73,7 +73,10 @@ class ChatInputBarState extends State { ), child: Column( children: [ - ReplyDisplay(widget.controller), + // #Pangea + if (!widget.controller.obscureText) + // Pangea# + ReplyDisplay(widget.controller), PangeaChatInputRow( controller: widget.controller, ), diff --git a/lib/pangea/chat/widgets/pangea_chat_input_row.dart b/lib/pangea/chat/widgets/pangea_chat_input_row.dart index b556ec38b..cb6df5877 100644 --- a/lib/pangea/chat/widgets/pangea_chat_input_row.dart +++ b/lib/pangea/chat/widgets/pangea_chat_input_row.dart @@ -213,6 +213,7 @@ class PangeaChatInputRowState extends State { clipBehavior: Clip.hardEdge, decoration: const BoxDecoration(), child: PopupMenuButton( + useRootNavigator: true, icon: const Icon(Icons.add_outlined), onSelected: _controller.onAddPopupMenuButtonSelected, itemBuilder: (BuildContext context) => diff --git a/lib/pangea/chat_list/widgets/pangea_chat_list_header.dart b/lib/pangea/chat_list/widgets/pangea_chat_list_header.dart index 51dff54fe..00c3b6e41 100644 --- a/lib/pangea/chat_list/widgets/pangea_chat_list_header.dart +++ b/lib/pangea/chat_list/widgets/pangea_chat_list_header.dart @@ -2,8 +2,11 @@ import 'package:flutter/material.dart'; import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/chat_list.dart'; import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicators.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart'; class PangeaChatListHeader extends StatelessWidget implements PreferredSizeWidget { @@ -33,43 +36,51 @@ class PangeaChatListHeader extends StatelessWidget child: Column( children: [ const LearningProgressIndicators(), - TextField( - controller: controller.searchController, - focusNode: controller.searchFocusNode, - textInputAction: TextInputAction.search, - onChanged: (text) => controller.onSearchEnter( - text, - globalSearch: globalSearch, - ), - decoration: InputDecoration( - filled: true, - fillColor: theme.colorScheme.secondaryContainer, - border: OutlineInputBorder( - borderSide: BorderSide.none, - borderRadius: BorderRadius.circular(99), - ), - contentPadding: EdgeInsets.zero, - hintText: L10n.of(context).search, - hintStyle: TextStyle( - color: theme.colorScheme.onPrimaryContainer, - fontWeight: FontWeight.normal, - ), - floatingLabelBehavior: FloatingLabelBehavior.never, - prefixIcon: controller.isSearchMode - ? IconButton( - tooltip: L10n.of(context).cancel, - icon: const Icon(Icons.close_outlined), - onPressed: controller.cancelSearch, - color: theme.colorScheme.onPrimaryContainer, - ) - : IconButton( - onPressed: controller.startSearch, - icon: Icon( - Icons.search_outlined, - color: theme.colorScheme.onPrimaryContainer, - ), + AnimatedSize( + duration: FluffyThemes.animationDuration, + child: OnboardingController.complete( + OnboardingStepsEnum.joinSpace, + ) + ? TextField( + controller: controller.searchController, + focusNode: controller.searchFocusNode, + textInputAction: TextInputAction.search, + onChanged: (text) => controller.onSearchEnter( + text, + globalSearch: globalSearch, ), - ), + decoration: InputDecoration( + filled: true, + fillColor: theme.colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: BorderSide.none, + borderRadius: BorderRadius.circular(99), + ), + contentPadding: EdgeInsets.zero, + hintText: L10n.of(context).search, + hintStyle: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontWeight: FontWeight.normal, + ), + floatingLabelBehavior: FloatingLabelBehavior.never, + prefixIcon: controller.isSearchMode + ? IconButton( + tooltip: L10n.of(context).cancel, + icon: const Icon(Icons.close_outlined), + onPressed: controller.cancelSearch, + color: theme.colorScheme.onPrimaryContainer, + ) + : IconButton( + onPressed: controller.startSearch, + icon: Icon( + Icons.search_outlined, + color: + theme.colorScheme.onPrimaryContainer, + ), + ), + ), + ) + : const SizedBox.shrink(), ), ], ), diff --git a/lib/pangea/chat_settings/pages/pangea_chat_access_settings.dart b/lib/pangea/chat_settings/pages/pangea_chat_access_settings.dart index def795a42..af51d9c3c 100644 --- a/lib/pangea/chat_settings/pages/pangea_chat_access_settings.dart +++ b/lib/pangea/chat_settings/pages/pangea_chat_access_settings.dart @@ -3,7 +3,6 @@ import 'package:flutter/material.dart' hide Visibility; import 'package:flutter_gen/gen_l10n/l10n.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_access_settings/chat_access_settings_controller.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; @@ -168,20 +167,20 @@ class ChatAccessTile extends StatelessWidget { return InkWell( onTap: onTap, - borderRadius: BorderRadius.circular(AppConfig.borderRadius), + borderRadius: BorderRadius.circular(10), child: Opacity( opacity: selected ? 1.0 : 0.5, child: Container( decoration: BoxDecoration( - borderRadius: BorderRadius.circular(AppConfig.borderRadius), + borderRadius: BorderRadius.circular(10), border: Border.all( color: selected - ? theme.colorScheme.primaryContainer - : theme.colorScheme.outline, + ? theme.colorScheme.primaryFixedDim + : theme.colorScheme.surfaceContainerHigh, width: 2, ), color: selected - ? theme.colorScheme.primaryContainer.withAlpha(50) + ? theme.colorScheme.primaryFixedDim.withAlpha(50) : null, ), padding: const EdgeInsets.all(16.0), diff --git a/lib/pangea/chat_settings/pages/pangea_chat_details.dart b/lib/pangea/chat_settings/pages/pangea_chat_details.dart index 8bd24277c..20631b837 100644 --- a/lib/pangea/chat_settings/pages/pangea_chat_details.dart +++ b/lib/pangea/chat_settings/pages/pangea_chat_details.dart @@ -8,7 +8,6 @@ import 'package:flutter_linkify/flutter_linkify.dart'; import 'package:go_router/go_router.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_details/chat_details.dart'; import 'package:fluffychat/pages/chat_details/participant_list_item.dart'; @@ -19,19 +18,21 @@ import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; import 'package:fluffychat/pangea/chat_settings/utils/delete_room.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/conversation_bot/conversation_bot_settings.dart'; import 'package:fluffychat/pangea/chat_settings/widgets/delete_space_dialog.dart'; +import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart'; import 'package:fluffychat/pangea/spaces/widgets/download_space_analytics_dialog.dart'; +import 'package:fluffychat/pangea/spaces/widgets/leaderboard_participant_list.dart'; import 'package:fluffychat/utils/fluffy_share.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/url_launcher.dart'; import 'package:fluffychat/widgets/adaptive_dialogs/show_ok_cancel_alert_dialog.dart'; -import 'package:fluffychat/widgets/adaptive_dialogs/user_dialog.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/future_loading_dialog.dart'; import 'package:fluffychat/widgets/hover_builder.dart'; import 'package:fluffychat/widgets/layouts/max_width_body.dart'; import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/widgets/member_actions_popup_menu_button.dart'; class PangeaChatDetailsView extends StatelessWidget { final ChatDetailsController controller; @@ -79,7 +80,7 @@ class PangeaChatDetailsView extends StatelessWidget { : const Center(child: BackButton())), ), body: MaxWidthBody( - maxWidth: 800, + maxWidth: 900, showBorder: false, child: ListView.builder( physics: const NeverScrollableScrollPhysics(), @@ -303,8 +304,8 @@ class RoomDetailsButtonRowState extends State { super.dispose(); } - final double _buttonWidth = 120.0; - final double _buttonHeight = 70.0; + final double _buttonWidth = 125.0; + final double _buttonHeight = 84.0; final double _miniButtonWidth = 50.0; Room get room => widget.room; @@ -314,21 +315,22 @@ class RoomDetailsButtonRowState extends State { return [ ButtonDetails( title: l10n.activities, - icon: const Icon(Icons.event_note_outlined), + icon: const Icon(Icons.event_note_outlined, size: 30.0), onPressed: () => context.go("/rooms/${room.id}/details/planner"), - visible: room.canSendDefaultStates || room.isSpace, - enabled: room.canSendDefaultStates, + visible: room.canChangeStateEvent(PangeaEventTypes.activityPlan) || + room.isSpace, + enabled: room.canChangeStateEvent(PangeaEventTypes.activityPlan), ), ButtonDetails( title: l10n.permissions, - icon: const Icon(Icons.edit_attributes_outlined), + icon: const Icon(Icons.edit_attributes_outlined, size: 30.0), onPressed: () => context.go('/rooms/${room.id}/details/permissions'), visible: (room.isRoomAdmin && !room.isDirectChat) || room.isSpace, enabled: room.isRoomAdmin && !room.isDirectChat, ), ButtonDetails( title: l10n.access, - icon: const Icon(Icons.shield_outlined), + icon: const Icon(Icons.shield_outlined, size: 30.0), onPressed: () => context.go('/rooms/${room.id}/details/access'), visible: room.isSpace && room.spaceParents.isEmpty, enabled: room.isSpace && room.isRoomAdmin, @@ -341,6 +343,7 @@ class RoomDetailsButtonRowState extends State { room.pushRuleState == PushRuleState.notify ? Icons.notifications_on_outlined : Icons.notifications_off_outlined, + size: 30.0, ), onPressed: () => showFutureLoadingDialog( context: context, @@ -354,14 +357,14 @@ class RoomDetailsButtonRowState extends State { ), ButtonDetails( title: l10n.invite, - icon: const Icon(Icons.person_add_outlined), + icon: const Icon(Icons.person_add_outlined, size: 30.0), onPressed: () => context.go('/rooms/${room.id}/details/invite'), visible: (room.canInvite && !room.isDirectChat) || room.isSpace, enabled: room.canInvite && !room.isDirectChat, ), ButtonDetails( title: l10n.addSubspace, - icon: const Icon(Icons.add_outlined), + icon: const Icon(Icons.add_outlined, size: 30.0), onPressed: widget.controller.addSubspace, visible: room.isSpace && room.canChangeStateEvent( @@ -371,7 +374,7 @@ class RoomDetailsButtonRowState extends State { ), ButtonDetails( title: l10n.downloadSpaceAnalytics, - icon: const Icon(Icons.download_outlined), + icon: const Icon(Icons.download_outlined, size: 30.0), onPressed: () { showDialog( context: context, @@ -383,7 +386,7 @@ class RoomDetailsButtonRowState extends State { ), ButtonDetails( title: l10n.download, - icon: const Icon(Icons.download_outlined), + icon: const Icon(Icons.download_outlined, size: 30.0), onPressed: widget.controller.downloadChatAction, visible: room.ownPowerLevel >= 50 && !room.isSpace, ), @@ -404,14 +407,14 @@ class RoomDetailsButtonRowState extends State { ), ButtonDetails( title: l10n.chatCapacity, - icon: const Icon(Icons.reduce_capacity), + icon: const Icon(Icons.reduce_capacity, size: 30.0), onPressed: widget.controller.setRoomCapacity, visible: !room.isSpace && !room.isDirectChat && room.canSendDefaultStates, ), ButtonDetails( title: l10n.leave, - icon: const Icon(Icons.logout_outlined), + icon: const Icon(Icons.logout_outlined, size: 30.0), onPressed: () async { final confirmed = await showOkCancelAlertDialog( useRootNavigator: false, @@ -438,7 +441,7 @@ class RoomDetailsButtonRowState extends State { ), ButtonDetails( title: l10n.delete, - icon: const Icon(Icons.delete_outline), + icon: const Icon(Icons.delete_outline, size: 30.0), onPressed: () async { if (room.isSpace) { final resp = await showDialog( @@ -516,6 +519,7 @@ class RoomDetailsButtonRowState extends State { } return PopupMenuButton( + useRootNavigator: true, onSelected: (button) => button.onPressed?.call(), itemBuilder: (context) { return otherButtons @@ -594,43 +598,53 @@ class RoomDetailsButton extends StatelessWidget { return const SizedBox(); } - return AbsorbPointer( - absorbing: !buttonDetails.enabled, - child: MouseRegion( - cursor: SystemMouseCursors.click, - child: HoverBuilder( - builder: (context, hovered) { - return GestureDetector( - onTap: buttonDetails.onPressed, - child: Opacity( - opacity: buttonDetails.enabled ? 1.0 : 0.5, - child: Container( - width: width, - height: height, - decoration: BoxDecoration( - color: hovered - ? Theme.of(context).colorScheme.primary.withAlpha(50) - : Colors.transparent, - borderRadius: BorderRadius.circular(8), - ), - padding: const EdgeInsets.all(8.0), - child: mini - ? buttonDetails.icon - : Column( - spacing: 8.0, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - buttonDetails.icon, - Text( - buttonDetails.title, - textAlign: TextAlign.center, + return TooltipVisibility( + visible: mini, + child: Tooltip( + message: buttonDetails.title, + child: AbsorbPointer( + absorbing: !buttonDetails.enabled, + child: MouseRegion( + cursor: SystemMouseCursors.click, + child: HoverBuilder( + builder: (context, hovered) { + return GestureDetector( + onTap: buttonDetails.onPressed, + child: Opacity( + opacity: buttonDetails.enabled ? 1.0 : 0.5, + child: Container( + width: width, + height: height, + decoration: BoxDecoration( + color: hovered + ? Theme.of(context) + .colorScheme + .primary + .withAlpha(50) + : Colors.transparent, + borderRadius: BorderRadius.circular(8), + ), + padding: EdgeInsets.all(mini ? 6 : 12.0), + child: mini + ? buttonDetails.icon + : Column( + spacing: 12.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + buttonDetails.icon, + Text( + buttonDetails.title, + textAlign: TextAlign.center, + style: const TextStyle(fontSize: 12.0), + ), + ], ), - ], - ), - ), - ), - ); - }, + ), + ), + ); + }, + ), + ), ), ), ); @@ -663,7 +677,7 @@ class RoomParticipantsSection extends StatelessWidget { super.key, }); - final double _width = 80.0; + final double _width = 100.0; final double _padding = 12.0; double get _fullWidth => _width + (_padding * 2); @@ -680,60 +694,54 @@ class RoomParticipantsSection extends StatelessWidget { builder: (context, constraints) { final availableWidth = constraints.maxWidth; final capacity = (availableWidth / _fullWidth).floor(); - - if (capacity < 4) { - return Column( - children: [ - ...members.map((member) => ParticipantListItem(member)), - if (actualMembersCount - members.length > 0) - ListTile( - title: Text( - L10n.of(context).loadCountMoreParticipants( - (actualMembersCount - members.length), - ), - ), - leading: CircleAvatar( - backgroundColor: Theme.of(context).scaffoldBackgroundColor, - child: const Icon( - Icons.group_outlined, - color: Colors.grey, - ), - ), - onTap: () => context.push( - '/rooms/${room.id}/details/members', - ), - trailing: const Icon(Icons.chevron_right_outlined), - ), - ], - ); - } - return LoadParticipantsUtil( space: room, builder: (participantsLoader) { + if (capacity < 4) { + return Column( + children: [ + ...members.map((member) => ParticipantListItem(member)), + if (actualMembersCount - members.length > 0) + ListTile( + title: Text( + L10n.of(context).loadCountMoreParticipants( + (actualMembersCount - members.length), + ), + ), + leading: CircleAvatar( + backgroundColor: + Theme.of(context).scaffoldBackgroundColor, + child: const Icon( + Icons.group_outlined, + color: Colors.grey, + ), + ), + onTap: () => context.push( + '/rooms/${room.id}/details/members', + ), + trailing: const Icon(Icons.chevron_right_outlined), + ), + ], + ); + } + final filteredParticipants = participantsLoader.filteredParticipants(""); + return Wrap( alignment: WrapAlignment.center, runAlignment: WrapAlignment.center, children: [ ...filteredParticipants.mapIndexed((index, user) { - Color? color = index == 0 - ? AppConfig.gold - : index == 1 - ? Colors.grey[400]! - : index == 2 - ? Colors.brown[400]! - : null; - final publicProfile = participantsLoader.getPublicProfile( user.id, ); + LinearGradient? gradient = index.leaderboardGradient; if (user.id == BotName.byEnvironment || publicProfile == null || publicProfile.level == null) { - color = null; + gradient = null; } return Padding( @@ -745,21 +753,13 @@ class RoomParticipantsSection extends StatelessWidget { Stack( alignment: Alignment.center, children: [ - if (color != null) + if (gradient != null) CircleAvatar( radius: _width / 2, child: Container( decoration: BoxDecoration( shape: BoxShape.circle, - gradient: LinearGradient( - begin: const Alignment(0.5, -0.5), - end: const Alignment(-0.5, 0.5), - colors: [ - color, - Colors.white, - color, - ], - ), + gradient: gradient, ), ), ) @@ -768,27 +768,27 @@ class RoomParticipantsSection extends StatelessWidget { height: _width, width: _width, ), - MouseRegion( - cursor: SystemMouseCursors.click, - child: GestureDetector( - onTap: () => UserDialog.show( - context: context, - profile: Profile( - userId: user.id, - displayName: user.displayName, - avatarUrl: user.avatarUrl, + Builder( + builder: (context) { + return MouseRegion( + cursor: SystemMouseCursors.click, + child: GestureDetector( + onTap: () => showMemberActionsPopupMenu( + context: context, + user: user, + ), + child: Center( + child: Avatar( + mxContent: user.avatarUrl, + name: user.calcDisplayname(), + size: _width - 6.0, + presenceUserId: user.id, + showPresence: false, + ), + ), ), - ), - child: Center( - child: Avatar( - mxContent: user.avatarUrl, - name: user.calcDisplayname(), - size: _width - 6.0, - presenceUserId: user.id, - showPresence: false, - ), - ), - ), + ); + }, ), ], ), diff --git a/lib/pangea/choreographer/controllers/choreographer.dart b/lib/pangea/choreographer/controllers/choreographer.dart index ef5364270..cadd4c3d0 100644 --- a/lib/pangea/choreographer/controllers/choreographer.dart +++ b/lib/pangea/choreographer/controllers/choreographer.dart @@ -114,7 +114,9 @@ class Choreographer { maxWidth: 325, transformTargetId: inputTransformTargetKey, ) - : chatController.send(); + : chatController.send( + message: chatController.sendController.text, + ); return; } @@ -135,7 +137,12 @@ class Choreographer { return; } - chatController.sendFakeMessage(); + if (chatController.sendController.text.trim().isEmpty) { + return; + } + + final message = chatController.sendController.text; + final fakeEventId = chatController.sendFakeMessage(); final PangeaRepresentation? originalWritten = choreoRecord.includedIT && itController.sourceText != null ? PangeaRepresentation( @@ -156,7 +163,7 @@ class Choreographer { repEventId: null, room: chatController.room, req: TokensRequestModel( - fullText: currentText, + fullText: message, senderL1: l1LangCode!, senderL2: l2LangCode!, ), @@ -167,7 +174,7 @@ class Choreographer { originalSent = PangeaRepresentation( langCode: res?.detections.firstOrNull?.langCode ?? LanguageKeys.unknownLanguage, - text: currentText, + text: message, originalSent: true, originalWritten: originalWritten == null, ); @@ -183,7 +190,7 @@ class Choreographer { e: e, s: s, data: { - "currentText": currentText, + "currentText": message, "l1LangCode": l1LangCode, "l2LangCode": l2LangCode, "choreoRecord": choreoRecord.toJson(), @@ -191,9 +198,11 @@ class Choreographer { ); } finally { chatController.send( + message: message, originalSent: originalSent, tokensSent: tokensSent, choreo: choreoRecord, + tempEventId: fakeEventId, ); clear(); } @@ -558,8 +567,6 @@ class Choreographer { choreoRecord = ChoreoRecord.newRecord; itController.clear(); igc.dispose(); - //@ggurdin - why is this commented out? - // errorService.clear(); _resetDebounceTimer(); } diff --git a/lib/pangea/common/config/environment.dart b/lib/pangea/common/config/environment.dart index 9e984784b..26b0ce898 100644 --- a/lib/pangea/common/config/environment.dart +++ b/lib/pangea/common/config/environment.dart @@ -121,12 +121,7 @@ class Environment { try { final String jsonString = await rootBundle.loadString('envs.json'); data = jsonDecode(jsonString); - } catch (e, s) { - ErrorHandler.logError( - e: e, - s: s, - data: {}, - ); + } catch (e) { return []; } diff --git a/lib/pangea/common/constants/model_keys.dart b/lib/pangea/common/constants/model_keys.dart index cb565eb06..781667c34 100644 --- a/lib/pangea/common/constants/model_keys.dart +++ b/lib/pangea/common/constants/model_keys.dart @@ -90,6 +90,7 @@ class ModelKey { static const String messageTagMorphEdit = "morph_edit"; static const String messageTagLemmaEdit = "lemma_edit"; static const String messageTagActivityPlan = "activity_plan"; + static const String tempEventId = "temporary_event_id"; static const String baseDefinition = "base_definition"; static const String targetDefinition = "target_definition"; diff --git a/lib/pangea/common/controllers/pangea_controller.dart b/lib/pangea/common/controllers/pangea_controller.dart index c1c609697..546655323 100644 --- a/lib/pangea/common/controllers/pangea_controller.dart +++ b/lib/pangea/common/controllers/pangea_controller.dart @@ -1,5 +1,4 @@ import 'dart:async'; -import 'dart:developer'; import 'dart:math'; import 'package:flutter/foundation.dart'; @@ -10,13 +9,8 @@ import 'package:sentry_flutter/sentry_flutter.dart'; import 'package:fluffychat/pangea/analytics_misc/get_analytics_controller.dart'; import 'package:fluffychat/pangea/analytics_misc/put_analytics_controller.dart'; -import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; -import 'package:fluffychat/pangea/chat/constants/default_power_level.dart'; -import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart'; -import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; import 'package:fluffychat/pangea/choreographer/controllers/contextual_definition_controller.dart'; import 'package:fluffychat/pangea/choreographer/controllers/word_net_controller.dart'; -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; import 'package:fluffychat/pangea/events/constants/pangea_event_types.dart'; import 'package:fluffychat/pangea/events/controllers/message_data_controller.dart'; import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; @@ -81,10 +75,7 @@ class PangeaController { putAnalytics.initialize(); getAnalytics.initialize(); subscriptionController.initialize(); - - startChatWithBotIfNotPresent(); setPangeaPushRules(); - // joinSupportSpace(); } /// Initialize controllers @@ -203,158 +194,6 @@ class PangeaController { await getAnalytics.initialize(); } - void startChatWithBotIfNotPresent() { - Future.delayed(const Duration(milliseconds: 10000), () async { - // check if user is logged in - if (!matrixState.client.isLogged() || - matrixState.client.userID == null || - matrixState.client.userID == BotName.byEnvironment) { - return; - } - - final List botDMs = []; - for (final room in matrixState.client.rooms) { - if (await room.isBotDM) { - botDMs.add(room); - } - } - - if (botDMs.isEmpty) { - try { - // Copied from client.dart.startDirectChat - final directChatRoomId = - matrixState.client.getDirectChatFromUserId(BotName.byEnvironment); - if (directChatRoomId != null) { - final room = matrixState.client.getRoomById(directChatRoomId); - if (room != null) { - if (room.membership == Membership.join) { - return null; - } else if (room.membership == Membership.invite) { - // we might already have an invite into a DM room. If that is the case, we should try to join. If the room is - // unjoinable, that will automatically leave the room, so in that case we need to continue creating a new - // room. (This implicitly also prevents the room from being returned as a DM room by getDirectChatFromUserId, - // because it only returns joined or invited rooms atm.) - await room.join(); - if (room.membership != Membership.leave) { - if (room.membership != Membership.join) { - // Wait for room actually appears in sync with the right membership - await matrixState.client - .waitForRoomInSync(directChatRoomId, join: true); - } - return null; - } - } - } - } - // enableEncryption ??= - // encryptionEnabled && await userOwnsEncryptionKeys(mxid); - // if (enableEncryption) { - // initialState ??= []; - // if (!initialState.any((s) => s.type == EventTypes.Encryption)) { - // initialState.add( - // StateEvent( - // content: { - // 'algorithm': supportedGroupEncryptionAlgorithms.first, - // }, - // type: EventTypes.Encryption, - // ), - // ); - // } - // } - - // Start a new direct chat - final roomId = await matrixState.client.createRoom( - invite: [], // intentionally not invite bot yet - isDirect: true, - preset: CreateRoomPreset.trustedPrivateChat, - initialState: [ - BotOptionsModel(mode: BotMode.directChat).toStateEvent, - RoomDefaults.defaultPowerLevels( - matrixState.client.userID!, - ), - ], - ); - - Room? room = matrixState.client.getRoomById(roomId); - if (room == null || room.membership != Membership.join) { - // Wait for room actually appears in sync - await matrixState.client.waitForRoomInSync(roomId, join: true); - room = matrixState.client.getRoomById(roomId); - if (room == null) { - ErrorHandler.logError( - e: "Bot chat null after waiting for room in sync", - data: { - "roomId": roomId, - }, - ); - return null; - } - } - - final botOptions = room.getState(PangeaEventTypes.botOptions); - if (botOptions == null) { - await matrixState.client.setRoomStateWithKey( - roomId, - PangeaEventTypes.botOptions, - "", - BotOptionsModel(mode: BotMode.directChat).toJson(), - ); - await matrixState.client - .getRoomStateWithKey(roomId, PangeaEventTypes.botOptions, ""); - } - - // invite bot to direct chat - await matrixState.client.setRoomStateWithKey( - roomId, EventTypes.RoomMember, BotName.byEnvironment, { - "membership": Membership.invite.name, - "is_direct": true, - }); - await room.addToDirectChat(BotName.byEnvironment); - - return null; - } catch (err, stack) { - debugger(when: kDebugMode); - ErrorHandler.logError( - e: err, - s: stack, - data: { - "directChatRoomId": matrixState.client - .getDirectChatFromUserId(BotName.byEnvironment), - }, - ); - } - } - - final Room botDMWithLatestActivity = botDMs.reduce((a, b) { - if (a.timeline == null || - b.timeline == null || - a.timeline!.events.isEmpty || - b.timeline!.events.isEmpty) { - return a; - } - final aLastEvent = a.timeline!.events.last; - final bLastEvent = b.timeline!.events.last; - return aLastEvent.originServerTs.isAfter(bLastEvent.originServerTs) - ? a - : b; - }); - - for (final room in botDMs) { - if (room.id != botDMWithLatestActivity.id) { - await room.leave(); - continue; - } - } - - final participants = await botDMWithLatestActivity.requestParticipants(); - final joinedParticipants = - participants.where((e) => e.membership == Membership.join).toList(); - if (joinedParticipants.length < 2) { - await botDMWithLatestActivity.invite(BotName.byEnvironment); - } - }); - } - void _subscribeToStreams() { matrixState.client.onLoginStateChanged.stream .listen(_handleLoginStateChange); diff --git a/lib/pangea/events/controllers/message_data_controller.dart b/lib/pangea/events/controllers/message_data_controller.dart index bfdee9313..bae9f2512 100644 --- a/lib/pangea/events/controllers/message_data_controller.dart +++ b/lib/pangea/events/controllers/message_data_controller.dart @@ -97,7 +97,10 @@ class MessageDataController extends BaseController { repEventId: repEventId, req: req, room: room, - ); + ).catchError((e, s) { + _tokensCache.remove(req.hashCode); + return Future.error(e, s); + }); /////// translation //////// diff --git a/lib/pangea/extensions/room_events_extension.dart b/lib/pangea/extensions/room_events_extension.dart index d587ce849..3da8c1272 100644 --- a/lib/pangea/extensions/room_events_extension.dart +++ b/lib/pangea/extensions/room_events_extension.dart @@ -202,6 +202,7 @@ extension EventsRoomExtension on Room { PangeaMessageTokens? tokensWritten, ChoreoRecord? choreo, String? messageTag, + String? tempEventId, }) { // if (parseCommands) { // return client.parseAndRunCommand(this, message, @@ -233,6 +234,9 @@ extension EventsRoomExtension on Room { if (messageTag != null) { event[ModelKey.messageTags] = messageTag; } + if (tempEventId != null) { + event[ModelKey.tempEventId] = tempEventId; + } if (parseMarkdown) { final html = markdown( @@ -269,15 +273,21 @@ extension EventsRoomExtension on Room { Future sendActivityPlan( ActivityPlanModel activity, { Uint8List? avatar, - String? avatarURL, String? filename, }) async { BookmarkedActivitiesRepo.save(activity); + + String? imageURL = activity.imageURL; + final eventId = await pangeaSendTextEvent( + activity.markdown, + messageTag: ModelKey.messageTagActivityPlan, + ); + Uint8List? bytes = avatar; - if (avatarURL != null && bytes == null) { + if (imageURL != null && bytes == null) { try { final resp = await http - .get(Uri.parse(avatarURL)) + .get(Uri.parse(imageURL)) .timeout(const Duration(seconds: 5)); bytes = resp.bodyBytes; } catch (e, s) { @@ -285,12 +295,20 @@ extension EventsRoomExtension on Room { e: e, s: s, data: { - "avatarURL": avatarURL, + "avatarURL": imageURL, }, ); } } + if (bytes != null && imageURL == null) { + final url = await client.uploadContent( + bytes, + filename: filename, + ); + imageURL = url.toString(); + } + MatrixFile? file; if (filename != null && bytes != null) { file = MatrixFile( @@ -298,19 +316,16 @@ extension EventsRoomExtension on Room { name: filename, ); } - final eventId = await pangeaSendTextEvent( - activity.markdown, - messageTag: ModelKey.messageTagActivityPlan, - ); if (file != null) { - await sendFileEvent( - file, - shrinkImageMaxDimension: 1600, - extraContent: { - ModelKey.messageTags: ModelKey.messageTagActivityPlan, - }, - ); + final content = { + 'msgtype': file.msgType, + 'body': file.name, + 'filename': file.name, + 'url': imageURL, + ModelKey.messageTags: ModelKey.messageTagActivityPlan, + }; + await sendEvent(content); } if (canSendDefaultStates) { diff --git a/lib/pangea/find_your_people/find_your_people.dart b/lib/pangea/find_your_people/find_your_people.dart new file mode 100644 index 000000000..24fe4d78a --- /dev/null +++ b/lib/pangea/find_your_people/find_your_people.dart @@ -0,0 +1,102 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pangea/common/utils/error_handler.dart'; +import 'package:fluffychat/pangea/find_your_people/find_your_people_view.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class FindYourPeople extends StatefulWidget { + const FindYourPeople({super.key}); + + @override + State createState() => FindYourPeopleState(); +} + +class FindYourPeopleState extends State { + final TextEditingController searchController = TextEditingController(); + + String? error; + bool loading = true; + + Timer? _coolDown; + + final List spaceItems = []; + + @override + void initState() { + super.initState(); + setSpaceItems(); + } + + @override + void dispose() { + searchController.dispose(); + _coolDown?.cancel(); + super.dispose(); + } + + void onSearchEnter(String text, {bool globalSearch = true}) { + if (text.isEmpty) { + setSpaceItems(); + return; + } + + _coolDown?.cancel(); + _coolDown = Timer(const Duration(milliseconds: 500), setSpaceItems); + } + + Future setSpaceItems() async { + setState(() { + loading = true; + error = null; + spaceItems.clear(); + }); + + try { + final resp = await Matrix.of(context).client.queryPublicRooms( + filter: PublicRoomQueryFilter( + roomTypes: ['m.space'], + genericSearchTerm: searchController.text, + ), + limit: 100, + ); + spaceItems.addAll(resp.chunk); + spaceItems.sort((a, b) { + int getPriority(item) { + final bool hasTopic = item.topic != null && item.topic!.isNotEmpty; + final bool hasAvatar = item.avatarUrl != null; + + if (hasTopic && hasAvatar) return 0; // Highest priority + if (hasAvatar) return 1; // Second priority + if (hasTopic) return 2; // Third priority + return 3; // Lowest priority + } + + return getPriority(a).compareTo(getPriority(b)); + }); + } catch (e, s) { + ErrorHandler.logError( + e: e, + s: s, + data: { + 'searchText': searchController.text, + }, + ); + error = e.toString(); + } finally { + if (mounted) { + setState(() { + loading = false; + }); + } + } + } + + @override + Widget build(BuildContext context) { + return FindYourPeopleView(controller: this); + } +} diff --git a/lib/pangea/find_your_people/find_your_people_constants.dart b/lib/pangea/find_your_people/find_your_people_constants.dart new file mode 100644 index 000000000..04cf879ff --- /dev/null +++ b/lib/pangea/find_your_people/find_your_people_constants.dart @@ -0,0 +1,3 @@ +class FindYourPeopleConstants { + static const String sideBearFileName = "Bear_Find_your_people.png"; +} diff --git a/lib/pangea/find_your_people/find_your_people_side_view.dart b/lib/pangea/find_your_people/find_your_people_side_view.dart new file mode 100644 index 000000000..0514455e7 --- /dev/null +++ b/lib/pangea/find_your_people/find_your_people_side_view.dart @@ -0,0 +1,48 @@ +import 'package:flutter/material.dart'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:go_router/go_router.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/find_your_people/find_your_people_constants.dart'; +import 'package:fluffychat/widgets/navigation_rail.dart'; + +class FindYourPeopleSideView extends StatelessWidget { + const FindYourPeopleSideView({super.key}); + + @override + Widget build(BuildContext context) { + return Row( + children: [ + if (FluffyThemes.isColumnMode(context) || + AppConfig.displayNavigationRail) ...[ + SpacesNavigationRail( + activeSpaceId: null, + onGoToChats: () => context.go('/rooms'), + onGoToSpaceId: (spaceId) => context.go('/rooms?spaceId=$spaceId'), + ), + Container( + color: Colors.transparent, + width: 1, + ), + ], + Expanded( + child: Center( + child: SizedBox( + width: 250.0, + child: CachedNetworkImage( + imageUrl: + "${AppConfig.assetsBaseURL}/${FindYourPeopleConstants.sideBearFileName}", + errorWidget: (context, url, error) => const SizedBox(), + placeholder: (context, url) => const Center( + child: CircularProgressIndicator.adaptive(), + ), + ), + ), + ), + ), + ], + ); + } +} diff --git a/lib/pangea/find_your_people/find_your_people_view.dart b/lib/pangea/find_your_people/find_your_people_view.dart new file mode 100644 index 000000000..03ed03547 --- /dev/null +++ b/lib/pangea/find_your_people/find_your_people_view.dart @@ -0,0 +1,243 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:go_router/go_router.dart'; + +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/find_your_people/find_your_people.dart'; +import 'package:fluffychat/pangea/find_your_people/public_space_tile.dart'; +import 'package:fluffychat/pangea/spaces/utils/space_code.dart'; + +class FindYourPeopleView extends StatelessWidget { + final FindYourPeopleState controller; + + const FindYourPeopleView({ + super.key, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final isColumnMode = FluffyThemes.isColumnMode(context); + + return Scaffold( + appBar: isColumnMode + ? null + : AppBar( + leading: IconButton( + icon: Icon( + Icons.chevron_left, + color: theme.colorScheme.primary, + ), + onPressed: () => Navigator.of(context).pop(), + ), + title: Icon( + Icons.groups_outlined, + size: 24.0, + color: theme.colorScheme.primary, + ), + centerTitle: false, + leadingWidth: 48.0, + actions: [ + TextButton( + child: Row( + children: [ + Icon( + Icons.join_full, + color: theme.colorScheme.primary, + size: 24.0, + ), + const SizedBox(width: 8.0), + Text( + L10n.of(context).joinWithCode, + style: TextStyle( + color: theme.colorScheme.primary, + fontSize: 14.0, + ), + ), + ], + ), + onPressed: () => + SpaceCodeUtil.joinWithSpaceCodeDialog(context), + ), + ], + ), + floatingActionButton: isColumnMode + ? null + : FloatingActionButton.extended( + onPressed: () => context.push('/rooms/newspace'), + icon: const Icon(Icons.add_box_outlined), + label: Text( + L10n.of(context).space, + overflow: TextOverflow.fade, + ), + ), + body: Padding( + padding: isColumnMode + ? const EdgeInsets.symmetric( + horizontal: 24.0, + vertical: 20.0, + ) + : const EdgeInsets.all(12.0), + child: Column( + spacing: 16.0, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (isColumnMode) + Padding( + padding: const EdgeInsets.symmetric( + vertical: 16.0, + horizontal: 12.0, + ), + child: Text( + L10n.of(context).findYourPeople, + style: const TextStyle(fontSize: 32.0), + ), + ), + Expanded( + child: Column( + spacing: isColumnMode ? 32.0 : 16.0, + children: [ + Container( + height: 48.0, + padding: isColumnMode + ? const EdgeInsets.symmetric(horizontal: 12) + : null, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + spacing: 10, + children: [ + Expanded( + child: SizedBox( + height: 40.0, + child: TextField( + controller: controller.searchController, + onChanged: controller.onSearchEnter, + textInputAction: TextInputAction.search, + decoration: InputDecoration( + filled: !isColumnMode, + fillColor: isColumnMode + ? null + : theme.colorScheme.secondaryContainer, + border: OutlineInputBorder( + borderSide: isColumnMode + ? const BorderSide() + : BorderSide.none, + borderRadius: BorderRadius.circular(100), + ), + contentPadding: + const EdgeInsets.fromLTRB(0, 0, 20.0, 0), + hintText: L10n.of(context).findYourPeople, + hintStyle: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontWeight: FontWeight.normal, + fontSize: 16.0, + ), + floatingLabelBehavior: + FloatingLabelBehavior.never, + prefixIcon: IconButton( + onPressed: () {}, + icon: Icon( + Icons.search_outlined, + color: theme.colorScheme.onPrimaryContainer, + ), + ), + ), + ), + ), + ), + if (isColumnMode) + TextButton( + child: Row( + children: [ + Icon( + Icons.join_full, + color: theme.colorScheme.onPrimaryContainer, + size: 24.0, + ), + const SizedBox(width: 8.0), + Text( + L10n.of(context).joinWithCode, + style: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontSize: 16.0, + ), + ), + ], + ), + onPressed: () => + SpaceCodeUtil.joinWithSpaceCodeDialog(context), + ), + if (isColumnMode) + TextButton( + child: Row( + children: [ + Icon( + Icons.add_box_outlined, + color: theme.colorScheme.onPrimaryContainer, + size: 24.0, + ), + const SizedBox(width: 8.0), + Text( + L10n.of(context).createYourSpace, + style: TextStyle( + color: theme.colorScheme.onPrimaryContainer, + fontSize: 16.0, + ), + ), + ], + ), + onPressed: () => context.push('/rooms/newspace'), + ), + ], + ), + ), + controller.error != null + ? Row( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + L10n.of(context).oopsSomethingWentWrong, + ), + IconButton( + onPressed: controller.setSpaceItems, + icon: const Icon(Icons.refresh), + ), + ], + ) + : controller.loading + ? const CircularProgressIndicator.adaptive() + : controller.spaceItems.isEmpty + ? Text( + L10n.of(context).nothingFound, + ) + : Expanded( + child: ListView.builder( + itemCount: controller.spaceItems.length, + itemBuilder: (context, index) { + final space = + controller.spaceItems[index]; + return Padding( + padding: isColumnMode + ? const EdgeInsets.only( + bottom: 32.0, + ) + : const EdgeInsets.only( + bottom: 16.0, + ), + child: PublicSpaceTile(space: space), + ); + }, + ), + ), + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pangea/find_your_people/public_space_tile.dart b/lib/pangea/find_your_people/public_space_tile.dart new file mode 100644 index 000000000..a66b1a77e --- /dev/null +++ b/lib/pangea/find_your_people/public_space_tile.dart @@ -0,0 +1,105 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/analytics_summary/learning_progress_indicator_button.dart'; +import 'package:fluffychat/pangea/public_spaces/public_room_bottom_sheet.dart'; +import 'package:fluffychat/widgets/avatar.dart'; + +class PublicSpaceTile extends StatelessWidget { + final PublicRoomsChunk space; + const PublicSpaceTile({super.key, required this.space}); + + @override + Widget build(BuildContext context) { + final bool isColumnMode = FluffyThemes.isColumnMode(context); + + return HoverButton( + onPressed: () => PublicRoomBottomSheet.show( + context: context, + chunk: space, + ), + borderRadius: BorderRadius.circular(10.0), + hoverOpacity: 0.1, + child: Padding( + padding: isColumnMode + ? const EdgeInsets.all(12.0) + : const EdgeInsets.all(0.0), + child: Column( + spacing: 12, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: isColumnMode ? 80.0 : 58.0, + child: Row( + children: [ + Avatar( + mxContent: space.avatarUrl, + name: space.name, + size: isColumnMode ? 80.0 : 58.0, + borderRadius: BorderRadius.circular( + 10, + ), + ), + Flexible( + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + spacing: 8.0, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + space.name ?? '', + style: TextStyle( + fontSize: isColumnMode ? 20.0 : 14.0, + height: 1.2, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Row( + spacing: 10, + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.group, + size: isColumnMode ? 20.0 : 16.0, + ), + Text( + L10n.of(context).countParticipants( + space.numJoinedMembers, + ), + style: TextStyle( + fontSize: isColumnMode ? 16.0 : 12.0, + height: 1.2, + ), + ), + ], + ), + ], + ), + ), + ), + ], + ), + ), + if (isColumnMode && space.topic != null && space.topic!.isNotEmpty) + Text( + space.topic!, + style: const TextStyle( + fontSize: 16.0, + height: 1.2, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pangea/message_token_text/message_token_button.dart b/lib/pangea/message_token_text/message_token_button.dart index 7e855e223..2b2624948 100644 --- a/lib/pangea/message_token_text/message_token_button.dart +++ b/lib/pangea/message_token_text/message_token_button.dart @@ -296,13 +296,14 @@ class MessageTokenButtonContent extends StatelessWidget { BorderRadius.circular(AppConfig.borderRadius - 4); Color _color(BuildContext context) { - if (activity == null) { - return Theme.of(context).colorScheme.primary; - } - if (isActivityCompleteOrNullForToken) { - return AppConfig.gold; - } - return Theme.of(context).colorScheme.primary; + final bool isLight = Theme.of(context).brightness == Brightness.light; + final defaultColor = isLight + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.primaryContainer; + + return activity != null && isActivityCompleteOrNullForToken + ? AppConfig.gold + : defaultColor; } @override @@ -377,6 +378,8 @@ class MessageTokenButtonContent extends StatelessWidget { (selectedChoice != null ? 0.4 : 0.0) + (accepted.isNotEmpty ? 0.3 : 0.0); + final theme = Theme.of(context); + return InkWell( onTap: selectedChoice != null ? () => onMatch?.call(selectedChoice!) @@ -384,10 +387,11 @@ class MessageTokenButtonContent extends StatelessWidget { borderRadius: _borderRadius, child: CustomPaint( painter: DottedBorderPainter( - color: Theme.of(context) - .colorScheme - .primary - .withAlpha((colorAlpha * 255).toInt()), + color: theme.brightness == Brightness.light + ? theme.colorScheme.primary + .withAlpha((colorAlpha * 255).toInt()) + : theme.colorScheme.primaryContainer + .withAlpha((colorAlpha * 255).toInt()), borderRadius: _borderRadius, ), child: Container( @@ -396,9 +400,7 @@ class MessageTokenButtonContent extends StatelessWidget { width: max(width, 24.0), alignment: Alignment.center, decoration: BoxDecoration( - color: Theme.of(context) - .colorScheme - .primary + color: theme.colorScheme.primary .withAlpha((max(0, colorAlpha - 0.7) * 255).toInt()), borderRadius: _borderRadius, ), diff --git a/lib/pangea/onboarding/onboarding.dart b/lib/pangea/onboarding/onboarding.dart new file mode 100644 index 000000000..1ef67ed57 --- /dev/null +++ b/lib/pangea/onboarding/onboarding.dart @@ -0,0 +1,109 @@ +import 'package:flutter/material.dart'; + +import 'package:get_storage/get_storage.dart'; +import 'package:go_router/go_router.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; +import 'package:fluffychat/pangea/chat/constants/default_power_level.dart'; +import 'package:fluffychat/pangea/chat_settings/constants/bot_mode.dart'; +import 'package:fluffychat/pangea/chat_settings/models/bot_options_model.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_view.dart'; +import 'package:fluffychat/utils/fluffy_share.dart'; +import 'package:fluffychat/widgets/future_loading_dialog.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class Onboarding extends StatefulWidget { + const Onboarding({super.key}); + + @override + OnboardingController createState() => OnboardingController(); +} + +class OnboardingController extends State { + static final GetStorage _onboardingStorage = GetStorage('onboarding_storage'); + + static bool get isClosed => _onboardingStorage.read('closed') ?? false; + + static bool get isComplete => OnboardingStepsEnum.values.every( + (step) => complete(step), + ); + + static bool complete(OnboardingStepsEnum step) { + switch (step) { + case OnboardingStepsEnum.chatWithBot: + return hasBotDM; + case OnboardingStepsEnum.joinSpace: + return MatrixState.pangeaController.matrixState.client.rooms.any( + (r) => r.isSpace, + ); + case OnboardingStepsEnum.inviteFriends: + return hasInvitedFriends; + } + } + + static bool get hasInvitedFriends => + _onboardingStorage.read('invite_friends') ?? false; + + static bool get hasBotDM => + MatrixState.pangeaController.matrixState.client.rooms.any((room) { + if (room.isDirectChat && + room.directChatMatrixID == BotName.byEnvironment) { + return true; + } + if (room.botOptions?.mode == BotMode.directChat) { + return true; + } + return false; + }); + + Future closeCompletedMessage() async { + await _onboardingStorage.write('closed', true); + if (mounted) setState(() {}); + } + + Future inviteFriends() async { + FluffyShare.shareInviteLink(context); + await _onboardingStorage.write('invite_friends', true); + if (mounted) setState(() {}); + } + + Future startChatWithBot() async { + final resp = await showFutureLoadingDialog( + context: context, + future: () => Matrix.of(context).client.createRoom( + invite: [BotName.byEnvironment], + isDirect: true, + preset: CreateRoomPreset.trustedPrivateChat, + initialState: [ + BotOptionsModel(mode: BotMode.directChat).toStateEvent, + RoomDefaults.defaultPowerLevels( + Matrix.of(context).client.userID!, + ), + ], + ), + ); + if (resp.isError) return; + context.go("/rooms/${resp.result}"); + } + + void joinCommunities() { + context.go('/rooms/communities'); + } + + Future onPressed(OnboardingStepsEnum step) async { + switch (step) { + case OnboardingStepsEnum.chatWithBot: + return startChatWithBot(); + case OnboardingStepsEnum.joinSpace: + return joinCommunities(); + case OnboardingStepsEnum.inviteFriends: + return inviteFriends(); + } + } + + @override + Widget build(BuildContext context) => OnboardingView(controller: this); +} diff --git a/lib/pangea/onboarding/onboarding_complete.dart b/lib/pangea/onboarding/onboarding_complete.dart new file mode 100644 index 000000000..ef1301325 --- /dev/null +++ b/lib/pangea/onboarding/onboarding_complete.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_constants.dart'; + +class OnboardingComplete extends StatelessWidget { + final OnboardingController controller; + const OnboardingComplete({super.key, required this.controller}); + + @override + Widget build(BuildContext context) { + return FluffyThemes.isColumnMode(context) + ? Text( + L10n.of(context).getStartedComplete, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 32.0, + ), + ) + : Stack( + children: [ + Container( + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onSurface.withAlpha(20), + borderRadius: BorderRadius.circular( + 10.0, + ), + ), + margin: const EdgeInsets.all(12.0), + padding: const EdgeInsets.fromLTRB( + 48.0, + 8.0, + 48.0, + 0.0, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + spacing: 24.0, + children: [ + Text( + L10n.of(context).getStartedComplete, + textAlign: TextAlign.center, + style: const TextStyle( + fontSize: 14.0, + ), + ), + CachedNetworkImage( + imageUrl: + "${AppConfig.assetsBaseURL}/${OnboardingConstants.onboardingImageFileName}", + fit: BoxFit.cover, + ), + ], + ), + ), + Positioned( + right: 16.0, + top: 16.0, + child: IconButton( + icon: const Icon(Icons.close), + onPressed: controller.closeCompletedMessage, + ), + ), + ], + ); + } +} diff --git a/lib/pangea/onboarding/onboarding_constants.dart b/lib/pangea/onboarding/onboarding_constants.dart new file mode 100644 index 000000000..744acec18 --- /dev/null +++ b/lib/pangea/onboarding/onboarding_constants.dart @@ -0,0 +1,3 @@ +class OnboardingConstants { + static String onboardingImageFileName = "Getting+Started.png"; +} diff --git a/lib/pangea/onboarding/onboarding_step.dart b/lib/pangea/onboarding/onboarding_step.dart new file mode 100644 index 000000000..0b78884f0 --- /dev/null +++ b/lib/pangea/onboarding/onboarding_step.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart'; + +class OnboardingStep extends StatelessWidget { + final OnboardingStepsEnum step; + + final bool isComplete; + final VoidCallback onPressed; + + const OnboardingStep({ + super.key, + required this.step, + this.isComplete = false, + required this.onPressed, + }); + + @override + Widget build(BuildContext context) { + final isColumnMode = FluffyThemes.isColumnMode(context); + + return Container( + padding: EdgeInsets.symmetric( + horizontal: isColumnMode ? 20.0 : 8.0, + vertical: isColumnMode ? 24.0 : 8.0, + ), + margin: isColumnMode + ? const EdgeInsets.only( + bottom: 10.0, + ) + : const EdgeInsets.all(0.0), + decoration: isColumnMode && isComplete + ? ShapeDecoration( + shape: RoundedRectangleBorder( + side: const BorderSide( + width: 1, + color: AppConfig.success, + ), + borderRadius: BorderRadius.circular( + 24, + ), + ), + ) + : null, + child: Row( + spacing: isColumnMode ? 24.0 : 12.0, + children: [ + Icon( + Icons.task_alt, + size: isColumnMode ? 30.0 : 18.0, + color: isComplete + ? AppConfig.success + : Theme.of(context).colorScheme.primary, + ), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: isColumnMode ? 16.0 : 8.0, + children: [ + Text( + isComplete + ? step.completeMessage( + L10n.of(context), + ) + : step.description( + L10n.of(context), + ), + style: TextStyle( + fontSize: isColumnMode ? 20.0 : 12.0, + ), + ), + if (!isComplete) + ElevatedButton( + onPressed: onPressed, + child: Row( + spacing: 8.0, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + step.icon(18.0), + Text( + step.buttonText( + L10n.of( + context, + ), + ), + style: const TextStyle( + fontSize: 14.0, + ), + ), + ], + ), + ), + ], + ), + ), + ], + ), + ); + } +} diff --git a/lib/pangea/onboarding/onboarding_steps_enum.dart b/lib/pangea/onboarding/onboarding_steps_enum.dart new file mode 100644 index 000000000..cfe3c7d93 --- /dev/null +++ b/lib/pangea/onboarding/onboarding_steps_enum.dart @@ -0,0 +1,55 @@ +import 'package:flutter/material.dart'; + +import 'package:flutter_gen/gen_l10n/l10n.dart'; + +import 'package:fluffychat/pangea/bot/widgets/bot_face_svg.dart'; + +enum OnboardingStepsEnum { + chatWithBot, + joinSpace, + inviteFriends; + + String description(L10n l10n) { + switch (this) { + case OnboardingStepsEnum.chatWithBot: + return l10n.getStartedBotChatDesc; + case OnboardingStepsEnum.joinSpace: + return l10n.getStartedCommunitiesDesc; + case OnboardingStepsEnum.inviteFriends: + return l10n.getStartedFriendsDesc; + } + } + + String completeMessage(L10n l10n) { + switch (this) { + case OnboardingStepsEnum.chatWithBot: + return l10n.getStartedBotChatComplete; + case OnboardingStepsEnum.joinSpace: + return l10n.getStartedCommunitiesComplete; + case OnboardingStepsEnum.inviteFriends: + return l10n.getStartedFriendsComplete; + } + } + + Widget icon(double size) { + switch (this) { + case OnboardingStepsEnum.chatWithBot: + return BotFace(expression: BotExpression.gold, width: size); + case OnboardingStepsEnum.joinSpace: + return Icon(Icons.groups_outlined, size: size); + case OnboardingStepsEnum.inviteFriends: + return Icon(Icons.share, size: size); + } + } + + String buttonText(L10n l10n) { + switch (this) { + case OnboardingStepsEnum.chatWithBot: + return l10n.getStartedBotChatButton; + case OnboardingStepsEnum.joinSpace: + return l10n.findYourPeople; + case OnboardingStepsEnum.inviteFriends: + return l10n.getStartedFriendsButton; + } + } +} diff --git a/lib/pangea/onboarding/onboarding_view.dart b/lib/pangea/onboarding/onboarding_view.dart new file mode 100644 index 000000000..7f383c75c --- /dev/null +++ b/lib/pangea/onboarding/onboarding_view.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; + +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_complete.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_constants.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_step.dart'; +import 'package:fluffychat/pangea/onboarding/onboarding_steps_enum.dart'; +import 'package:fluffychat/utils/stream_extension.dart'; +import 'package:fluffychat/widgets/layouts/max_width_body.dart'; +import 'package:fluffychat/widgets/matrix.dart'; + +class OnboardingView extends StatelessWidget { + final OnboardingController controller; + + const OnboardingView({ + super.key, + required this.controller, + }); + + @override + Widget build(BuildContext context) { + final client = Matrix.of(context).client; + final isColumnMode = FluffyThemes.isColumnMode(context); + + final screenheight = MediaQuery.of(context).size.height; + + return Material( + child: StreamBuilder( + key: ValueKey( + client.userID.toString(), + ), + stream: client.onSync.stream + .where((s) => s.hasRoomUpdate) + .rateLimit(const Duration(seconds: 1)), + builder: (context, _) { + return Stack( + alignment: Alignment.topCenter, + children: [ + if (isColumnMode && !OnboardingController.isClosed) + Positioned( + bottom: 0.0, + child: AnimatedOpacity( + duration: FluffyThemes.animationDuration, + opacity: OnboardingController.isComplete ? 1.0 : 0.3, + child: CachedNetworkImage( + imageUrl: + "${AppConfig.assetsBaseURL}/${OnboardingConstants.onboardingImageFileName}", + fit: BoxFit.cover, + ), + ), + ), + AnimatedContainer( + duration: FluffyThemes.animationDuration, + height: OnboardingController.isClosed ? 0 : screenheight, + child: Padding( + padding: EdgeInsets.symmetric( + vertical: 12.0, + horizontal: isColumnMode ? 20.0 : 8.0, + ), + child: MaxWidthBody( + showBorder: false, + maxWidth: 850.0, + child: Column( + children: [ + Text( + L10n.of(context).getStarted, + style: TextStyle( + fontSize: isColumnMode ? 32.0 : 16.0, + height: isColumnMode ? 1.2 : 1.5, + ), + ), + Padding( + padding: EdgeInsets.all( + isColumnMode ? 40.0 : 12.0, + ), + child: Row( + spacing: 8.0, + mainAxisSize: MainAxisSize.min, + children: OnboardingStepsEnum.values.map((step) { + final complete = + OnboardingController.complete(step); + return CircleAvatar( + radius: 6.0, + backgroundColor: complete + ? AppConfig.success + : Theme.of(context).colorScheme.primary, + child: CircleAvatar( + radius: 3.0, + backgroundColor: + Theme.of(context).colorScheme.surface, + ), + ); + }).toList(), + ), + ), + OnboardingController.isComplete + ? OnboardingComplete( + controller: controller, + ) + : Column( + spacing: 12.0, + children: [ + for (final step in OnboardingStepsEnum.values) + OnboardingStep( + step: step, + isComplete: + OnboardingController.complete(step), + onPressed: () => + controller.onPressed(step), + ), + ], + ), + ], + ), + ), + ), + ), + ], + ); + }, + ), + ); + } +} diff --git a/lib/pangea/public_spaces/public_room_bottom_sheet.dart b/lib/pangea/public_spaces/public_room_bottom_sheet.dart index 2a5381fc4..07b29095d 100644 --- a/lib/pangea/public_spaces/public_room_bottom_sheet.dart +++ b/lib/pangea/public_spaces/public_room_bottom_sheet.dart @@ -142,7 +142,7 @@ class PublicRoomBottomSheetState extends State { context: context, future: () async => client.knockRoom( roomAlias ?? chunk!.roomId, - serverName: via, + via: via, ), onSuccess: () => L10n.of(context).knockSpaceSuccess, delay: false, diff --git a/lib/pangea/spaces/widgets/leaderboard_participant_list.dart b/lib/pangea/spaces/widgets/leaderboard_participant_list.dart new file mode 100644 index 000000000..347d12cd2 --- /dev/null +++ b/lib/pangea/spaces/widgets/leaderboard_participant_list.dart @@ -0,0 +1,174 @@ +import 'package:flutter/material.dart'; + +import 'package:matrix/matrix.dart'; + +import 'package:fluffychat/config/app_config.dart'; +import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pages/chat_list/status_msg_list.dart'; +import 'package:fluffychat/pangea/bot/utils/bot_name.dart'; +import 'package:fluffychat/pangea/spaces/utils/load_participants_util.dart'; +import 'package:fluffychat/utils/stream_extension.dart'; +import 'package:fluffychat/widgets/adaptive_dialogs/user_dialog.dart'; +import 'package:fluffychat/widgets/matrix.dart'; +import 'package:fluffychat/widgets/presence_builder.dart'; + +class LeaderboardParticipantList extends StatefulWidget { + final Room space; + + const LeaderboardParticipantList({ + required this.space, + super.key, + }); + + static const double height = 116; + + @override + State createState() => + LeaderboardParticipantListState(); +} + +class LeaderboardParticipantListState + extends State { + final _scrollController = ScrollController(); + + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + final client = Matrix.of(context).client; + final theme = Theme.of(context); + + return StreamBuilder( + stream: client.onSync.stream.rateLimit(const Duration(seconds: 3)), + builder: (context, snapshot) { + return LoadParticipantsUtil( + space: widget.space, + builder: (participantsLoader) { + final participants = participantsLoader.filteredParticipants(""); + + return AnimatedSize( + duration: FluffyThemes.animationDuration, + curve: Curves.easeInOut, + child: SizedBox( + height: 130.0, + child: Scrollbar( + controller: _scrollController, + child: ListView.builder( + controller: _scrollController, + padding: const EdgeInsets.fromLTRB( + 8.0, + 8.0, + 8.0, + 16.0, + ), + scrollDirection: Axis.horizontal, + itemCount: participants.length, + itemBuilder: (context, i) { + final user = participants[i]; + final publicProfile = participantsLoader.getPublicProfile( + user.id, + ); + + LinearGradient? gradient = i.leaderboardGradient; + + if (user.id == BotName.byEnvironment || + publicProfile == null || + publicProfile.level == null) { + gradient = null; + } + + return PresenceBuilder( + userId: user.id, + builder: (context, presence) { + Color? dotColor; + if (presence != null) { + dotColor = presence.presence.isOnline + ? Colors.green + : presence.presence.isUnavailable + ? Colors.orange + : Colors.grey; + } + + return PresenceAvatar( + presence: presence ?? + CachedPresence( + PresenceType.unavailable, + null, + null, + null, + user.id, + ), + height: StatusMessageList.height, + onTap: (profile) => UserDialog.show( + context: context, + profile: profile, + ), + gradient: gradient, + showPresence: false, + floatingIndicator: Positioned( + bottom: 0, + right: 0, + child: Container( + width: 16, + height: 16, + decoration: BoxDecoration( + color: theme.colorScheme.surface, + borderRadius: BorderRadius.circular(32), + ), + alignment: Alignment.center, + child: Container( + width: 10, + height: 10, + decoration: BoxDecoration( + color: dotColor, + borderRadius: BorderRadius.circular(16), + border: Border.all( + width: 1, + color: theme.colorScheme.surface, + ), + ), + ), + ), + ), + ); + }, + ); + }, + ), + ), + ), + ); + }, + ); + }, + ); + } +} + +extension LeaderboardGradient on int { + LinearGradient? get leaderboardGradient { + final Color? color = this == 0 + ? AppConfig.gold + : this == 1 + ? Colors.grey[400]! + : this == 2 + ? Colors.brown[400]! + : null; + + if (color == null) return null; + + return LinearGradient( + colors: [ + color, + Colors.white, + color, + ], + begin: Alignment.topLeft, + end: Alignment.bottomRight, + ); + } +} diff --git a/lib/pangea/toolbar/widgets/message_selection_positioner.dart b/lib/pangea/toolbar/widgets/message_selection_positioner.dart index 76d7b908c..b60f07756 100644 --- a/lib/pangea/toolbar/widgets/message_selection_positioner.dart +++ b/lib/pangea/toolbar/widgets/message_selection_positioner.dart @@ -282,7 +282,7 @@ class MessageSelectionPositionerState extends State ); double get _columnWidth => FluffyThemes.isColumnMode(context) - ? (FluffyThemes.columnWidth + FluffyThemes.navRailWidth) + ? (FluffyThemes.columnWidth + FluffyThemes.navRailWidth + 1.0) : 0; /// Available vertical space not taken up by the header and footer diff --git a/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart b/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart index 37ce838d8..605053ae2 100644 --- a/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart +++ b/lib/pangea/toolbar/widgets/practice_activity/word_audio_button.dart @@ -91,8 +91,16 @@ class WordAudioButtonState extends State { context: context, targetID: 'word-audio-button-${widget.uniqueID}', langCode: widget.langCode, - onStart: () => setState(() => _isPlaying = true), - onStop: () => setState(() => _isPlaying = false), + onStart: () { + if (mounted) { + setState(() => _isPlaying = true); + } + }, + onStop: () { + if (mounted) { + setState(() => _isPlaying = false); + } + }, ); } }, diff --git a/lib/pangea/toolbar/widgets/select_mode_buttons.dart b/lib/pangea/toolbar/widgets/select_mode_buttons.dart index 5b1396c51..ec3ef75d1 100644 --- a/lib/pangea/toolbar/widgets/select_mode_buttons.dart +++ b/lib/pangea/toolbar/widgets/select_mode_buttons.dart @@ -80,8 +80,7 @@ class SelectModeButtonsState extends State { super.initState(); _onPlayerStateChanged = _audioPlayer.playerStateStream.listen((state) { if (state.processingState == ProcessingState.completed) { - _audioPlayer.stop(); - _audioPlayer.seek(null); + _updateMode(null); } setState(() {}); }); @@ -121,8 +120,18 @@ class SelectModeButtonsState extends State { } } - Future _updateMode(SelectMode mode) async { + Future _updateMode(SelectMode? mode) async { _clear(); + + if (mode == null) { + setState(() { + _audioPlayer.stop(); + _audioPlayer.seek(null); + _selectedMode = null; + }); + return; + } + setState( () => _selectedMode = _selectedMode == mode && mode != SelectMode.audio ? null : mode, diff --git a/lib/pangea/user/constants/age_limits.dart b/lib/pangea/user/constants/age_limits.dart deleted file mode 100644 index 9a07840ef..000000000 --- a/lib/pangea/user/constants/age_limits.dart +++ /dev/null @@ -1,4 +0,0 @@ -class AgeLimits { - static const int toAccessFeatures = 18; - static const int toUseTheApp = 13; -} diff --git a/lib/pangea/user/models/user_profile_search_model.dart b/lib/pangea/user/models/user_profile_search_model.dart deleted file mode 100644 index 212e8e392..000000000 --- a/lib/pangea/user/models/user_profile_search_model.dart +++ /dev/null @@ -1,27 +0,0 @@ -import 'user_model.dart'; - -class UserProfileSearchResponse { - int count; - String? next; - String? previous; - List results; - - UserProfileSearchResponse({ - required this.count, - required this.next, - required this.previous, - required this.results, - }); - - factory UserProfileSearchResponse.fromJson(Map json) { - return UserProfileSearchResponse( - count: json["count"], - next: json["next"], - previous: json["previous"], - results: json["results"] - .map((p) => PangeaProfile.fromJson(p)) - .toList() - .cast(), - ); - } -} diff --git a/lib/pangea/user/pages/find_partner.dart b/lib/pangea/user/pages/find_partner.dart deleted file mode 100644 index 5b3b1d52c..000000000 --- a/lib/pangea/user/pages/find_partner.dart +++ /dev/null @@ -1,179 +0,0 @@ -import 'dart:async'; - -import 'package:flutter/material.dart'; - -import 'package:country_picker/country_picker.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; - -import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/pangea/learning_settings/models/language_model.dart'; -import 'package:fluffychat/pangea/user/models/user_model.dart'; -import '../../../widgets/matrix.dart'; -import '../../common/controllers/pangea_controller.dart'; -import '../models/user_profile_search_model.dart'; -import '../repo/user_repo.dart'; -import 'find_partner_view.dart'; - -class FindPartner extends StatefulWidget { - const FindPartner({super.key}); - - @override - State createState() => FindPartnerController(); -} - -class FindPartnerController extends State { - PangeaController pangeaController = MatrixState.pangeaController; - - bool initialLoad = true; - bool loading = false; - String currentSearchTerm = ""; - late LanguageModel targetLanguageSearch; - late LanguageModel sourceLanguageSearch; - String? countrySearch; - String? flagEmoji; - - //PTODO - implement pagination - String? nextUrl = ""; - int nextPage = 1; - - Timer? coolDown; - - final List _userProfilesCache = []; - - final scrollController = ScrollController(); - String? error; - - @override - void initState() { - targetLanguageSearch = pangeaController.languageController.userL1 ?? - pangeaController.pLanguageStore.targetOptions[1]; - sourceLanguageSearch = pangeaController.languageController.userL2 ?? - pangeaController.pLanguageStore.targetOptions[0]; - - scrollController.addListener(() { - if (scrollController.position.pixels == - scrollController.position.maxScrollExtent) { - searchUserProfiles(); - } - }); - - searchUserProfiles().then((_) => setState(() => initialLoad = false)); - - super.initState(); - } - - @override - void dispose() { - scrollController.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - if (error != null && error!.isNotEmpty) { - return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text(L10n.of(context).oopsSomethingWentWrong), - Text(L10n.of(context).errorPleaseRefresh), - ], - ), - ); - } - return FindPartnerView(this); - } - - List get userProfiles => _userProfilesCache.where((p) { - return (p.targetLanguage != null && - targetLanguageSearch.langCode == p.targetLanguage) && - (p.sourceLanguage != null && - sourceLanguageSearch.langCode == p.sourceLanguage) && - (countrySearch == null || - (p.country != null && countrySearch == p.country)); - }).toList(); - - void searchUserProfilesWithCoolDown(String text) { - coolDown?.cancel(); - coolDown = Timer( - const Duration(milliseconds: 0), - () => searchUserProfiles(), - ); - } - - Future searchUserProfiles() async { - coolDown?.cancel(); - if (loading || nextUrl == null) return; - setState(() => loading = true); - - UserProfileSearchResponse response; - try { - final String accessToken = pangeaController.userController.accessToken; - response = await PUserRepo.searchUserProfiles( - accessToken: accessToken, - targetLanguage: targetLanguageSearch.langCode, - sourceLanguage: sourceLanguageSearch.langCode, - country: countrySearch, - limit: 15, - pageNumber: nextPage.toString(), - ); - } catch (err, s) { - error = err.toString(); - setState(() => loading = false); - ErrorHandler.logError( - e: err, - s: s, - data: { - "accessToken": pangeaController.userController.accessToken, - "targetLanguage": targetLanguageSearch.langCode, - "sourceLanguage": sourceLanguageSearch.langCode, - "country": countrySearch, - "pageNumber": nextPage.toString(), - }, - ); - return; - } - - nextUrl = response.next; - nextPage++; - - final String? currentUserId = pangeaController.matrixState.client.userID; - _userProfilesCache.addAll( - response.results.where( - (p) => - !_userProfilesCache.any( - (element) => p.pangeaUserId == element.pangeaUserId, - ) && - p.pangeaUserId != currentUserId, - ), - ); - - setState(() => loading = false); - } - - Future filterUserProfiles({ - LanguageModel? targetLanguage, - LanguageModel? sourceLanguage, - Country? country, - }) async { - if (country != null) { - if (country.name != "World Wide") { - countrySearch = country.displayNameNoCountryCode; - flagEmoji = country.flagEmoji; - } else { - countrySearch = null; - flagEmoji = null; - } - } - if (targetLanguage != null) { - targetLanguageSearch = targetLanguage; - } - if (sourceLanguage != null) { - sourceLanguageSearch = sourceLanguage; - } - nextPage = 1; - nextUrl = ""; - await searchUserProfiles(); - setState(() {}); - } -} diff --git a/lib/pangea/user/pages/find_partner_view.dart b/lib/pangea/user/pages/find_partner_view.dart deleted file mode 100644 index 5ba019bbe..000000000 --- a/lib/pangea/user/pages/find_partner_view.dart +++ /dev/null @@ -1,315 +0,0 @@ -import 'package:flutter/material.dart'; - -import 'package:country_picker/country_picker.dart'; -import 'package:flutter_gen/gen_l10n/l10n.dart'; -import 'package:go_router/go_router.dart'; -import 'package:matrix/matrix.dart' as matrix; - -import 'package:fluffychat/config/app_config.dart'; -import 'package:fluffychat/config/themes.dart'; -import 'package:fluffychat/pangea/common/widgets/pangea_logo_svg.dart'; -import 'package:fluffychat/pangea/learning_settings/utils/country_display.dart'; -import 'package:fluffychat/pangea/learning_settings/widgets/p_language_dropdown.dart'; -import 'package:fluffychat/pangea/user/models/user_model.dart'; -import 'package:fluffychat/pangea/user/widgets/list_placeholder.dart'; -import 'package:fluffychat/widgets/avatar.dart'; -import 'package:fluffychat/widgets/matrix.dart'; -import '../../../widgets/profile_bottom_sheet.dart'; -import 'find_partner.dart'; - -class FindPartnerView extends StatelessWidget { - final FindPartnerController controller; - const FindPartnerView(this.controller, {super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - leading: IconButton( - icon: const Icon(Icons.close_outlined), - onPressed: () => context.pop(), - ), - centerTitle: true, - title: const PageTitleText(), - ), - body: Center( - child: Container( - constraints: const BoxConstraints( - maxWidth: FluffyThemes.columnWidth * 2, - minWidth: FluffyThemes.columnWidth * 2, - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - LanguageSelectionRow( - controller: controller, - isSource: true, - ), - LanguageSelectionRow( - controller: controller, - isSource: false, - ), - Padding( - padding: const EdgeInsets.all(18), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - L10n.of(context).iWantALanguagePartnerFrom, - style: const TextStyle(fontSize: 16), - ), - Row( - children: [ - Text( - controller.countrySearch ?? - L10n.of(context).worldWide, - ), - Padding( - padding: const EdgeInsets.fromLTRB(15, 0, 15, 0), - child: controller.flagEmoji != null - ? RichText( - text: TextSpan( - text: controller.flagEmoji, - style: const TextStyle(fontSize: 30), - ), - ) - : const PangeaLogoSvg(width: 30), - ), - IconButton( - icon: const Icon(Icons.expand_more), - onPressed: () => showCountryPicker( - showWorldWide: true, - context: context, - showPhoneCode: false, - onSelect: (Country country) { - controller.filterUserProfiles( - country: country, - ); - }, - ), - ), - ], - ), - ], - ), - ), - controller.initialLoad - ? const ExpandedContainer(body: ListPlaceholder()) - : controller.userProfiles.isNotEmpty - ? ExpandedContainer( - body: ListView.builder( - controller: controller.scrollController, - itemCount: controller.userProfiles.length + 1, - itemBuilder: (context, i) => i != - controller.userProfiles.length - ? UserProfileEntry( - pangeaProfile: controller.userProfiles[i], - controller: controller, - ) - : controller.loading - ? const Center( - child: CircularProgressIndicator - .adaptive(), - ) - : const SizedBox.shrink(), - ), - ) - : ExpandedContainer( - body: Center( - child: Text(L10n.of(context).noResults), - ), - ), - ], - ), - ), - ), - ); - } -} - -class ExpandedContainer extends StatelessWidget { - const ExpandedContainer({ - super.key, - required this.body, - }); - - final Widget body; - - @override - Widget build(BuildContext context) { - return Expanded( - child: Container( - margin: const EdgeInsets.fromLTRB(0, 20, 0, 20), - child: body, - ), - ); - } -} - -class ProfileSearchTextField extends StatelessWidget { - const ProfileSearchTextField({ - super.key, - required this.controller, - }); - - final FindPartnerController controller; - - @override - Widget build(BuildContext context) { - return TextField( - autofocus: true, - decoration: InputDecoration( - hintText: L10n.of(context).searchBy, - suffixIconConstraints: const BoxConstraints( - maxWidth: 48, - maxHeight: 48, - minWidth: 48, - ), - suffixIcon: controller.initialLoad - ? const CircularProgressIndicator.adaptive() - : const Icon(Icons.search_outlined), - contentPadding: const EdgeInsets.symmetric(horizontal: 16), - ), - onChanged: controller.searchUserProfilesWithCoolDown, - ); - } -} - -class PageTitleText extends StatelessWidget { - const PageTitleText({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return FittedBox( - child: Text( - L10n.of(context).iWantAConversationPartner, - style: TextStyle( - color: Theme.of(context).textTheme.bodyLarge!.color, - fontSize: 18, - fontWeight: FontWeight.w700, - ), - overflow: TextOverflow.clip, - textAlign: TextAlign.center, - ), - ); - } -} - -class LanguageSelectionRow extends StatelessWidget { - const LanguageSelectionRow({ - super.key, - required this.controller, - required this.isSource, - }); - - final FindPartnerController controller; - final bool isSource; - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Flexible( - child: ListTile( - title: isSource - ? Text( - L10n.of(context).iWantALanguagePartnerWhoSpeaks, - style: const TextStyle(fontSize: 16), - ) - : Text( - L10n.of(context).iWantALanguagePartnerWhoIsLearning, - style: const TextStyle(fontSize: 16), - ), - ), - ), - Flexible( - child: PLanguageDropdown( - languages: isSource - ? controller.pangeaController.pLanguageStore.baseOptions - : controller.pangeaController.pLanguageStore.targetOptions, - onChange: (language) { - controller.filterUserProfiles( - sourceLanguage: isSource ? language : null, - targetLanguage: isSource ? null : language, - ); - }, - isL2List: !isSource, - initialLanguage: isSource - ? controller.sourceLanguageSearch - : controller.targetLanguageSearch, - decorationText: isSource - ? L10n.of(context).myBaseLanguage - : L10n.of(context).iWantToLearn, - ), - ), - ], - ); - } -} - -class UserProfileEntry extends StatelessWidget { - final PangeaProfile pangeaProfile; - final FindPartnerController controller; - - const UserProfileEntry({ - super.key, - required this.pangeaProfile, - required this.controller, - }); - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - FutureBuilder( - future: Matrix.of(context) - .client - .getProfileFromUserId(pangeaProfile.pangeaUserId), - builder: ((context, snapshot) { - final matrixProfile = snapshot.data; - return ListTile( - leading: Avatar( - name: matrixProfile == null || matrixProfile.avatarUrl == null - ? pangeaProfile.pangeaUserId - : null, - mxContent: matrixProfile?.avatarUrl, - ), - title: Row( - children: [ - Flexible( - child: Text( - //PTODO - get matrix u and show displayName - matrixProfile?.displayName ?? - pangeaProfile.pangeaUserId.replaceAll( - ":${AppConfig.defaultHomeserver.replaceAll("matrix.", "")}", - "", - ), - overflow: TextOverflow.ellipsis, - ), - ), - const SizedBox(width: 20), - RichText( - text: TextSpan( - text: CountryDisplayUtil.flagEmoji(pangeaProfile.country), - style: const TextStyle(fontSize: 15), - ), - ), - ], - ), - onTap: () => showModalBottomSheet( - context: context, - builder: (c) => ProfileBottomSheet( - userId: pangeaProfile.pangeaUserId, - outerContext: context, - ), - ), - ); - }), - ), - ], - ); - } -} diff --git a/lib/pangea/user/repo/user_repo.dart b/lib/pangea/user/repo/user_repo.dart deleted file mode 100644 index 9f6320f32..000000000 --- a/lib/pangea/user/repo/user_repo.dart +++ /dev/null @@ -1,48 +0,0 @@ -import 'dart:convert'; - -import 'package:http/http.dart'; - -import 'package:fluffychat/pangea/common/config/environment.dart'; -import 'package:fluffychat/pangea/common/constants/model_keys.dart'; -import '../../common/network/requests.dart'; -import '../../common/network/urls.dart'; -import '../models/user_profile_search_model.dart'; - -class PUserRepo { - static Future searchUserProfiles({ - // List? interests, - String? targetLanguage, - String? sourceLanguage, - String? country, - // String? speaks, - String? pageNumber, - required String accessToken, - required int limit, - }) async { - final Requests req = Requests( - accessToken: accessToken, - choreoApiKey: Environment.choreoApiKey, - ); - final Map body = {}; - // if (interests != null) body[ModelKey.userInterests] = interests.toString(); - if (targetLanguage != null) { - body[ModelKey.userTargetLanguage] = targetLanguage; - } - if (sourceLanguage != null) { - body[ModelKey.userSourceLanguage] = sourceLanguage; - } - if (country != null) body[ModelKey.userCountry] = country; - - final String searchUrl = - "${PApiUrls.searchUserProfiles}?limit=$limit${pageNumber != null ? '&page=$pageNumber' : ''}"; - - final Response res = await req.post( - url: searchUrl, - body: body, - ); - - //PTODO - implement paginiation - make another call with next url - - return UserProfileSearchResponse.fromJson(jsonDecode(res.body)); - } -} diff --git a/lib/pangea/user/widgets/list_placeholder.dart b/lib/pangea/user/widgets/list_placeholder.dart deleted file mode 100644 index 4b37c08d6..000000000 --- a/lib/pangea/user/widgets/list_placeholder.dart +++ /dev/null @@ -1,72 +0,0 @@ -import 'package:flutter/material.dart'; - -class ListPlaceholder extends StatelessWidget { - static const dummyChatCount = 5; - - const ListPlaceholder({super.key}); - - @override - Widget build(BuildContext context) { - final titleColor = - Theme.of(context).textTheme.bodyLarge!.color!.withAlpha(100); - final subtitleColor = - Theme.of(context).textTheme.bodyLarge!.color!.withAlpha(50); - - return ListView.builder( - itemCount: dummyChatCount, - itemBuilder: (context, i) => Opacity( - opacity: (dummyChatCount - i) / dummyChatCount, - child: Material( - child: ListTile( - leading: CircleAvatar( - backgroundColor: titleColor, - child: CircularProgressIndicator( - strokeWidth: 1, - color: Theme.of(context).textTheme.bodyLarge!.color, - ), - ), - title: Row( - children: [ - Expanded( - child: Container( - height: 14, - decoration: BoxDecoration( - color: titleColor, - borderRadius: BorderRadius.circular(3), - ), - ), - ), - const SizedBox(width: 36), - Container( - height: 14, - width: 14, - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(14), - ), - ), - const SizedBox(width: 12), - Container( - height: 14, - width: 14, - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(14), - ), - ), - ], - ), - subtitle: Container( - decoration: BoxDecoration( - color: subtitleColor, - borderRadius: BorderRadius.circular(3), - ), - height: 12, - margin: const EdgeInsets.only(right: 22), - ), - ), - ), - ), - ); - } -} diff --git a/lib/pangea/writing_assistance/writing_assistance_input_row.dart b/lib/pangea/writing_assistance/writing_assistance_input_row.dart deleted file mode 100644 index 462c8b5d3..000000000 --- a/lib/pangea/writing_assistance/writing_assistance_input_row.dart +++ /dev/null @@ -1,113 +0,0 @@ -// // presents choices from vocab_bank_repo -// // displays them as emoji choices -// // once selection, these words are inserted into the input bar - -// import 'dart:async'; - -// import 'package:fluffychat/config/themes.dart'; -// import 'package:fluffychat/pages/chat/chat.dart'; -// import 'package:fluffychat/pangea/choreographer/controllers/choreographer.dart'; -// import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; -// import 'package:fluffychat/pangea/emojis/emoji_stack.dart'; -// import 'package:fluffychat/pangea/learning_settings/constants/language_constants.dart'; -// import 'package:fluffychat/pangea/toolbar/reading_assistance_input_row/message_emoji_choice_item.dart'; -// import 'package:fluffychat/pangea/word_bank/vocab_bank_repo.dart'; -// import 'package:fluffychat/pangea/word_bank/vocab_request.dart'; -// import 'package:fluffychat/pangea/word_bank/vocab_response.dart'; -// import 'package:fluffychat/widgets/matrix.dart'; -// import 'package:flutter/material.dart'; - -// class WritingAssistanceInputRow extends StatefulWidget { -// final ChatController controller; - -// const WritingAssistanceInputRow( -// this.controller, { -// super.key, -// }); - -// @override -// WritingAssistanceInputRowState createState() => -// WritingAssistanceInputRowState(); -// } - -// class WritingAssistanceInputRowState extends State { -// List suggestions = []; - -// StreamSubscription? _choreoSub; - -// Choreographer get choreographer => widget.controller.choreographer; - -// @override -// void initState() { -// // Rebuild the widget each time there's an update from choreo -// _choreoSub = choreographer.stateListener.stream.listen((_) { -// setSuggestions(); -// }); -// setSuggestions(); -// super.initState(); -// } - -// @override -// void dispose() { -// _choreoSub?.cancel(); -// super.dispose(); -// } - -// Future setSuggestions() async { -// final String currentText = choreographer.currentText; - -// final VocabRequest request = VocabRequest( -// langCode: MatrixState -// .pangeaController.languageController.userL2?.langCodeShort ?? -// LanguageKeys.defaultLanguage, -// level: MatrixState -// .pangeaController.userController.profile.userSettings.cefrLevel, -// prefix: currentText, -// ); - -// final VocabResponse response = await VocabRepo.get(request); - -// setState(() { -// suggestions = response.vocab; -// }); -// } - -// @override -// Widget build(BuildContext context) { -// return AnimatedContainer( -// duration: FluffyThemes.animationDuration, -// curve: FluffyThemes.animationCurve, -// child: SingleChildScrollView( -// scrollDirection: Axis.horizontal, -// child: Row( -// children: suggestions -// .map( -// (suggestion) => MessageEmojiChoiceItem( -// topContent: EmojiStack( -// emoji: suggestion.userSetEmoji, -// // suggestion.userSetEmoji ?? -// // MatrixState -// // .pangeaController.getAnalytics.constructListModel -// // .getConstructUses(suggestion) -// // ?.xpEmoji ?? -// // AnalyticsConstants.emojiForSeed, -// style: const TextStyle(fontSize: 24), -// ), -// content: suggestion.lemma, -// onTap: () { -// choreographer.onPredictorSelect(suggestion.lemma); -// // setState(() { -// // suggestions = []; -// // }); -// }, -// isSelected: false, -// textSize: 16, -// greenHighlight: false, -// ), -// ) -// .toList(), -// ), -// ), -// ); -// } -// } diff --git a/lib/utils/client_manager.dart b/lib/utils/client_manager.dart index 523f9c416..8362d4a64 100644 --- a/lib/utils/client_manager.dart +++ b/lib/utils/client_manager.dart @@ -132,9 +132,6 @@ abstract class ClientManager { }, logLevel: kReleaseMode ? Level.warning : Level.verbose, databaseBuilder: flutterMatrixSdkDatabaseBuilder, - // #Pangea - // legacyDatabaseBuilder: FlutterHiveCollectionsDatabase.databaseBuilder, - // Pangea# supportedLoginTypes: { AuthenticationTypes.password, AuthenticationTypes.sso, diff --git a/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart b/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart deleted file mode 100644 index 9ad5ce5c0..000000000 --- a/lib/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart +++ /dev/null @@ -1,149 +0,0 @@ -import 'dart:convert'; -import 'dart:io'; - -import 'package:flutter/foundation.dart' hide Key; -import 'package:flutter/services.dart'; - -import 'package:flutter_secure_storage/flutter_secure_storage.dart'; -import 'package:hive/hive.dart'; -import 'package:matrix/matrix.dart'; -import 'package:path_provider/path_provider.dart'; -import 'package:universal_html/html.dart' as html; - -// ignore: deprecated_member_use -class FlutterHiveCollectionsDatabase extends HiveCollectionsDatabase { - FlutterHiveCollectionsDatabase( - super.name, - String super.path, { - super.key, - }); - - static const String _cipherStorageKey = 'hive_encryption_key'; - - static Future databaseBuilder( - Client client, - ) async { - Logs().d('Open Hive...'); - HiveAesCipher? hiverCipher; - try { - // Workaround for secure storage is calling Platform.operatingSystem on web - if (kIsWeb) { - // ignore: unawaited_futures - html.window.navigator.storage?.persist(); - throw MissingPluginException(); - } - - const secureStorage = FlutterSecureStorage(); - final containsEncryptionKey = - await secureStorage.read(key: _cipherStorageKey) != null; - if (!containsEncryptionKey) { - // do not try to create a buggy secure storage for new Linux users - if (Platform.isLinux) throw MissingPluginException(); - final key = Hive.generateSecureKey(); - await secureStorage.write( - key: _cipherStorageKey, - value: base64UrlEncode(key), - ); - } - - // workaround for if we just wrote to the key and it still doesn't exist - final rawEncryptionKey = await secureStorage.read(key: _cipherStorageKey); - if (rawEncryptionKey == null) throw MissingPluginException(); - - hiverCipher = HiveAesCipher(base64Url.decode(rawEncryptionKey)); - } on MissingPluginException catch (_) { - const FlutterSecureStorage() - .delete(key: _cipherStorageKey) - .catchError((_) {}); - Logs().i('Hive encryption is not supported on this platform'); - } catch (e, s) { - const FlutterSecureStorage() - .delete(key: _cipherStorageKey) - .catchError((_) {}); - Logs().w('Unable to init Hive encryption', e, s); - } - - final db = FlutterHiveCollectionsDatabase( - 'hive_collections_${client.clientName.replaceAll(' ', '_').toLowerCase()}', - await findDatabasePath(client), - key: hiverCipher, - ); - try { - await db.open(); - } catch (e, s) { - Logs().w('Unable to open Hive. Delete database and storage key...', e, s); - const FlutterSecureStorage().delete(key: _cipherStorageKey); - await db.clear().catchError((_) {}); - await Hive.deleteFromDisk(); - rethrow; - } - Logs().d('Hive is ready'); - return db; - } - - static Future findDatabasePath(Client client) async { - var path = client.clientName; - if (!kIsWeb) { - Directory directory; - try { - if (Platform.isLinux) { - directory = await getApplicationSupportDirectory(); - } else { - directory = await getApplicationDocumentsDirectory(); - } - } catch (_) { - try { - directory = await getLibraryDirectory(); - } catch (_) { - directory = Directory.current; - } - } - // do not destroy your stable FluffyChat in debug mode - directory = Directory( - directory.uri.resolve(kDebugMode ? 'hive_debug' : 'hive').toFilePath(), - ); - directory.create(recursive: true); - path = directory.path; - } - return path; - } - - @override - int get maxFileSize => supportsFileStoring ? 100 * 1000 * 1000 : 0; - @override - bool get supportsFileStoring => !kIsWeb; - - Future _getFileStoreDirectory() async { - try { - try { - return (await getTemporaryDirectory()).path; - } catch (_) { - return (await getApplicationDocumentsDirectory()).path; - } - } catch (_) { - return (await getDownloadsDirectory())!.path; - } - } - - @override - Future getFile(Uri mxcUri) async { - if (!supportsFileStoring) return null; - final tempDirectory = await _getFileStoreDirectory(); - final file = - File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}'); - if (await file.exists() == false) return null; - final bytes = await file.readAsBytes(); - return bytes; - } - - @override - Future storeFile(Uri mxcUri, Uint8List bytes, int time) async { - if (!supportsFileStoring) return null; - final tempDirectory = await _getFileStoreDirectory(); - final file = - File('$tempDirectory/${Uri.encodeComponent(mxcUri.toString())}'); - if (await file.exists()) return; - await file.writeAsBytes(bytes); - return; - } -} diff --git a/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart b/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart index 85bc517bc..b07f6e613 100644 --- a/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart +++ b/lib/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart @@ -10,7 +10,6 @@ import 'package:sqflite_common_ffi/sqflite_ffi.dart'; import 'package:universal_html/html.dart' as html; import 'package:fluffychat/pangea/common/utils/error_handler.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart'; import 'package:fluffychat/utils/platform_infos.dart'; import 'cipher.dart'; @@ -74,7 +73,7 @@ Future flutterMatrixSdkDatabaseBuilder(Client client) async { Logs().e('Unable to send error notification', e, s); } - return FlutterHiveCollectionsDatabase.databaseBuilder(client); + rethrow; } } diff --git a/lib/utils/matrix_sdk_extensions/matrix_locals.dart b/lib/utils/matrix_sdk_extensions/matrix_locals.dart index 83875cb43..0db2251d7 100644 --- a/lib/utils/matrix_sdk_extensions/matrix_locals.dart +++ b/lib/utils/matrix_sdk_extensions/matrix_locals.dart @@ -1,4 +1,5 @@ import 'package:flutter_gen/gen_l10n/l10n.dart'; +import 'package:intl/intl.dart'; import 'package:matrix/matrix.dart'; /// This is a temporary helper class until there is a proper solution to this with the new system @@ -353,4 +354,21 @@ class MatrixLocals extends MatrixLocalizations { @override String get cancelledSend => l10n.sendCanceled; + + @override + String voiceMessage(String senderName, Duration? duration) { + final dateTime = duration == null + ? null + : DateTime.fromMillisecondsSinceEpoch( + duration.inSeconds * 1000, + ); + final formattedDuration = dateTime == null + ? '' + : DateFormat( + DateFormat.MINUTE_SECOND, + l10n.localeName, + ).format(dateTime); + + return l10n.sentVoiceMessage(senderName, formattedDuration); + } } diff --git a/lib/utils/push_helper.dart b/lib/utils/push_helper.dart index 33e69b925..d4f15ccec 100644 --- a/lib/utils/push_helper.dart +++ b/lib/utils/push_helper.dart @@ -87,7 +87,7 @@ Future _tryPushHelper( .first; final event = await client.getEventByPushNotification( notification, - storeInDatabase: isBackgroundMessage, + storeInDatabase: false, ); if (event == null) { diff --git a/lib/utils/url_launcher.dart b/lib/utils/url_launcher.dart index 5dff82be7..6ae98926b 100644 --- a/lib/utils/url_launcher.dart +++ b/lib/utils/url_launcher.dart @@ -191,7 +191,6 @@ class UrlLauncher { await PublicRoomBottomSheet.show( context: context, roomAlias: identityParts.primaryIdentifier, - // Pangea# ); // Pangea# } diff --git a/lib/widgets/avatar.dart b/lib/widgets/avatar.dart index b850b40e9..7368d80e9 100644 --- a/lib/widgets/avatar.dart +++ b/lib/widgets/avatar.dart @@ -71,50 +71,48 @@ class Avatar extends StatelessWidget { borderRadius: borderRadius, side: border ?? BorderSide.none, ), - clipBehavior: Clip.hardEdge, - child: - // #Pangea - (userId ?? presenceUserId) == BotName.byEnvironment - ? BotFace( - width: size, - expression: BotExpression.idle, - useRive: useRive, - ) - : + clipBehavior: Clip.antiAlias, + // #Pangea + // child: noPic + child: (userId ?? presenceUserId) == BotName.byEnvironment + ? BotFace( + width: size, + expression: BotExpression.idle, + useRive: useRive, + ) + : noPic // Pangea# - - noPic - ? Container( - decoration: - BoxDecoration(color: name?.lightColorAvatar), - alignment: Alignment.center, - child: Text( - fallbackLetters, - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'RobotoMono', - color: Colors.white, - fontWeight: FontWeight.bold, - fontSize: (size / 2.5).roundToDouble(), - ), - ), - ) - : MxcImage( - client: client, - key: ValueKey(mxContent.toString()), - cacheKey: '${mxContent}_$size', - uri: mxContent, - fit: BoxFit.cover, - width: size, - height: size, - placeholder: (_) => Center( - child: Icon( - Icons.person_2, - color: theme.colorScheme.tertiary, - size: size / 1.5, - ), - ), + ? Container( + decoration: + BoxDecoration(color: name?.lightColorAvatar), + alignment: Alignment.center, + child: Text( + fallbackLetters, + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'RobotoMono', + color: Colors.white, + fontWeight: FontWeight.bold, + fontSize: (size / 2.5).roundToDouble(), ), + ), + ) + : MxcImage( + client: client, + key: ValueKey(mxContent.toString()), + cacheKey: '${mxContent}_$size', + uri: mxContent, + fit: BoxFit.cover, + width: size, + height: size, + placeholder: (_) => Center( + child: Icon( + Icons.person_2, + color: theme.colorScheme.tertiary, + size: size / 1.5, + ), + ), + ), ), ), // #Pangea diff --git a/lib/widgets/layouts/two_column_layout.dart b/lib/widgets/layouts/two_column_layout.dart index 86a6f56c5..f440cfcfd 100644 --- a/lib/widgets/layouts/two_column_layout.dart +++ b/lib/widgets/layouts/two_column_layout.dart @@ -5,11 +5,17 @@ import 'package:fluffychat/config/themes.dart'; class TwoColumnLayout extends StatelessWidget { final Widget mainView; final Widget sideView; + // #Pangea + final Color? dividerColor; + // Pangea# const TwoColumnLayout({ super.key, required this.mainView, required this.sideView, + // #Pangea + this.dividerColor, + // Pangea# }); @override Widget build(BuildContext context) { @@ -27,7 +33,10 @@ class TwoColumnLayout extends StatelessWidget { ), Container( width: 1.0, - color: theme.dividerColor, + // #Pangea + // color: theme.dividerColor, + color: dividerColor ?? theme.dividerColor, + // Pangea# ), Expanded( child: ClipRRect( diff --git a/lib/widgets/navigation_rail.dart b/lib/widgets/navigation_rail.dart index 802fe5c8b..6fd3c1e3b 100644 --- a/lib/widgets/navigation_rail.dart +++ b/lib/widgets/navigation_rail.dart @@ -7,7 +7,6 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pages/chat_list/navi_rail_item.dart'; -import 'package:fluffychat/pangea/spaces/utils/space_code.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/utils/stream_extension.dart'; import 'package:fluffychat/widgets/avatar.dart'; @@ -17,11 +16,17 @@ class SpacesNavigationRail extends StatelessWidget { final String? activeSpaceId; final void Function() onGoToChats; final void Function(String) onGoToSpaceId; + // #Pangea + final void Function()? clearActiveSpace; + // Pangea# const SpacesNavigationRail({ required this.activeSpaceId, required this.onGoToChats, required this.onGoToSpaceId, + // #Pangea + this.clearActiveSpace, + // Pangea# super.key, }); @@ -35,13 +40,14 @@ class SpacesNavigationRail extends StatelessWidget { .path .startsWith('/rooms/settings'); // #Pangea - final isHomepage = GoRouter.of(context) - .routeInformationProvider - .value - .uri - .path - .contains('homepage'); + final path = GoRouter.of(context).routeInformationProvider.value.uri.path; + final isHomepage = path.contains('homepage'); + final isCommunities = path.contains('communities'); final isColumnMode = FluffyThemes.isColumnMode(context); + + final width = isColumnMode + ? FluffyThemes.navRailWidth + : FluffyThemes.navRailWidth - 8.0; // return StreamBuilder( return Material( child: SafeArea( @@ -67,9 +73,7 @@ class SpacesNavigationRail extends StatelessWidget { return SizedBox( // #Pangea // width: FluffyThemes.navRailWidth, - width: isColumnMode - ? FluffyThemes.navRailWidth - : FluffyThemes.navRailWidth * 0.75, + width: width, // Pangea# child: Column( children: [ @@ -78,18 +82,17 @@ class SpacesNavigationRail extends StatelessWidget { scrollDirection: Axis.vertical, // #Pangea // itemCount: rootSpaces.length + 2, - itemCount: rootSpaces.length + 4, + itemCount: rootSpaces.length + 3, // Pangea# itemBuilder: (context, i) { // #Pangea if (i == 0) { return NaviRailItem( - isSelected: isColumnMode - ? activeSpaceId == null && !isSettings - : isHomepage, - onTap: () => isColumnMode - ? onGoToChats() - : context.go("/rooms/homepage"), + isSelected: isHomepage, + onTap: () { + clearActiveSpace?.call(); + context.go("/rooms/homepage"); + }, backgroundColor: Colors.transparent, icon: FutureBuilder( future: client.fetchOwnProfile(), @@ -103,7 +106,8 @@ class SpacesNavigationRail extends StatelessWidget { mxContent: snapshot.data?.avatarUrl, name: snapshot.data?.displayName ?? client.userID!.localpart, - size: 45, + size: + width - (isColumnMode ? 32.0 : 24.0), ), ), ], @@ -115,52 +119,50 @@ class SpacesNavigationRail extends StatelessWidget { i--; // Pangea# if (i == 0) { - return isColumnMode - ? const SizedBox() - : NaviRailItem( - // #Pangea - // isSelected: activeSpaceId == null && !isSettings, - isSelected: activeSpaceId == null && - !isSettings && - !isHomepage, - // Pangea# - onTap: onGoToChats, - icon: const Padding( - padding: EdgeInsets.all(10.0), - child: Icon(Icons.forum_outlined), - ), - selectedIcon: const Padding( - padding: EdgeInsets.all(10.0), - child: Icon(Icons.forum), - ), - toolTip: L10n.of(context).chats, - unreadBadgeFilter: (room) => true, - ); + return NaviRailItem( + // #Pangea + // isSelected: activeSpaceId == null && !isSettings, + isSelected: activeSpaceId == null && + !isSettings && + !isHomepage && + !isCommunities, + // Pangea# + onTap: onGoToChats, + // #Pangea + // icon: const Padding( + // padding: EdgeInsets.all(10.0), + // child: Icon(Icons.forum_outlined), + // ), + // selectedIcon: const Padding( + // padding: EdgeInsets.all(10.0), + // child: Icon(Icons.forum), + // ), + icon: const Icon(Icons.forum_outlined), + selectedIcon: const Icon(Icons.forum), + // Pangea# + toolTip: L10n.of(context).chats, + unreadBadgeFilter: (room) => true, + ); } i--; if (i == rootSpaces.length) { - // #Pangea return NaviRailItem( - isSelected: false, - onTap: () => - SpaceCodeUtil.joinWithSpaceCodeDialog(context), - icon: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.join_right_outlined), - ), - toolTip: L10n.of(context).joinByCode, - ); - } - if (i == rootSpaces.length + 1) { - // Pangea# - return NaviRailItem( - isSelected: false, - onTap: () => context.go('/rooms/newspace'), - icon: const Padding( - padding: EdgeInsets.all(8.0), - child: Icon(Icons.add), - ), - toolTip: L10n.of(context).createNewSpace, + // #Pangea + // isSelected: false, + // onTap: () => context.go('/rooms/newspace'), + // icon: const Padding( + // padding: EdgeInsets.all(8.0), + // child: Icon(Icons.add), + // ), + // toolTip: L10n.of(context).createNewSpace, + isSelected: isCommunities, + onTap: () { + clearActiveSpace?.call(); + context.go('/rooms/communities'); + }, + icon: const Icon(Icons.groups), + toolTip: L10n.of(context).findYourPeople, + // Pangea# ); } final space = rootSpaces[i]; @@ -186,6 +188,9 @@ class SpacesNavigationRail extends StatelessWidget { borderRadius: BorderRadius.circular( AppConfig.borderRadius / 2, ), + // #Pangea + size: width - (isColumnMode ? 32.0 : 24.0), + // Pangea# ), ); }, @@ -194,14 +199,18 @@ class SpacesNavigationRail extends StatelessWidget { NaviRailItem( isSelected: isSettings, onTap: () => context.go('/rooms/settings'), - icon: const Padding( - padding: EdgeInsets.all(10.0), - child: Icon(Icons.settings_outlined), - ), - selectedIcon: const Padding( - padding: EdgeInsets.all(10.0), - child: Icon(Icons.settings), - ), + // #Pangea + // icon: const Padding( + // padding: EdgeInsets.all(10.0), + // child: Icon(Icons.settings_outlined), + // ), + // selectedIcon: const Padding( + // padding: EdgeInsets.all(10.0), + // child: Icon(Icons.settings), + // ), + icon: const Icon(Icons.settings_outlined), + selectedIcon: const Icon(Icons.settings), + // Pangea# toolTip: L10n.of(context).settings, ), ], diff --git a/lib/widgets/permission_slider_dialog.dart b/lib/widgets/permission_slider_dialog.dart index fff6685e9..306c3b428 100644 --- a/lib/widgets/permission_slider_dialog.dart +++ b/lib/widgets/permission_slider_dialog.dart @@ -72,6 +72,13 @@ Future showPermissionChooser( onPressed: () => Navigator.of(context).pop(0), child: Text(L10n.of(context).normalUser), ), + // #Pangea + AdaptiveDialogAction( + bigButtons: true, + onPressed: () => Navigator.of(context).pop(), + child: Text(L10n.of(context).close), + ), + // Pangea# ], ), ); diff --git a/lib/widgets/share_scaffold_dialog.dart b/lib/widgets/share_scaffold_dialog.dart index 21d6b4020..1123bc7a7 100644 --- a/lib/widgets/share_scaffold_dialog.dart +++ b/lib/widgets/share_scaffold_dialog.dart @@ -7,6 +7,7 @@ import 'package:matrix/matrix.dart'; import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; +import 'package:fluffychat/pangea/extensions/pangea_room_extension.dart'; import 'package:fluffychat/utils/matrix_sdk_extensions/matrix_locals.dart'; import 'package:fluffychat/widgets/avatar.dart'; import 'package:fluffychat/widgets/matrix.dart'; @@ -71,6 +72,9 @@ class _ShareScaffoldDialogState extends State { (room) => room.canSendDefaultMessages && !room.isSpace && + // #Pangea + !room.isAnalyticsRoom && + // Pangea# room.membership == Membership.join, ) .toList(); diff --git a/pangea_packages/fcm_shared_isolate/example/ios/Flutter/ephemeral/flutter_lldb_helper.py b/pangea_packages/fcm_shared_isolate/example/ios/Flutter/ephemeral/flutter_lldb_helper.py new file mode 100644 index 000000000..a88caf99d --- /dev/null +++ b/pangea_packages/fcm_shared_isolate/example/ios/Flutter/ephemeral/flutter_lldb_helper.py @@ -0,0 +1,32 @@ +# +# Generated file, do not edit. +# + +import lldb + +def handle_new_rx_page(frame: lldb.SBFrame, bp_loc, extra_args, intern_dict): + """Intercept NOTIFY_DEBUGGER_ABOUT_RX_PAGES and touch the pages.""" + base = frame.register["x0"].GetValueAsAddress() + page_len = frame.register["x1"].GetValueAsUnsigned() + + # Note: NOTIFY_DEBUGGER_ABOUT_RX_PAGES will check contents of the + # first page to see if handled it correctly. This makes diagnosing + # misconfiguration (e.g. missing breakpoint) easier. + data = bytearray(page_len) + data[0:8] = b'IHELPED!' + + error = lldb.SBError() + frame.GetThread().GetProcess().WriteMemory(base, data, error) + if not error.Success(): + print(f'Failed to write into {base}[+{page_len}]', error) + return + +def __lldb_init_module(debugger: lldb.SBDebugger, _): + target = debugger.GetDummyTarget() + # Caveat: must use BreakpointCreateByRegEx here and not + # BreakpointCreateByName. For some reasons callback function does not + # get carried over from dummy target for the later. + bp = target.BreakpointCreateByRegex("^NOTIFY_DEBUGGER_ABOUT_RX_PAGES$") + bp.SetScriptCallbackFunction('{}.handle_new_rx_page'.format(__name__)) + bp.SetAutoContinue(True) + print("-- LLDB integration loaded --") diff --git a/pangea_packages/fcm_shared_isolate/example/ios/Flutter/ephemeral/flutter_lldbinit b/pangea_packages/fcm_shared_isolate/example/ios/Flutter/ephemeral/flutter_lldbinit new file mode 100644 index 000000000..e3ba6fbed --- /dev/null +++ b/pangea_packages/fcm_shared_isolate/example/ios/Flutter/ephemeral/flutter_lldbinit @@ -0,0 +1,5 @@ +# +# Generated file, do not edit. +# + +command script import --relative-to-command-file flutter_lldb_helper.py diff --git a/pubspec.lock b/pubspec.lock index f3d6b7c90..968c0f8ae 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -13,10 +13,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "401dd18096f5eaa140404ccbbbf346f83c850e6f27049698a7ee75a3488ddb32" + sha256: "214e6f07e2a44f45972e0365c7b537eaeaddb4598db0778dd4ac64b4acd3f5b1" url: "https://pub.dev" source: hosted - version: "1.3.52" + version: "1.3.55" _macros: dependency: transitive description: dart @@ -106,10 +106,10 @@ packages: dependency: transitive description: name: args - sha256: bf9f5caeea8d8fe6721a9c358dd8a5c1947b27f1cfaa18b39c301273594919e6 + sha256: d0481093c50b1da8910eb0bb301626d4d8eb7284aa739614d2b394ee09e3ea04 url: "https://pub.dev" source: hosted - version: "2.6.0" + version: "2.7.0" async: dependency: "direct main" description: @@ -130,58 +130,58 @@ packages: dependency: "direct main" description: name: audioplayers - sha256: b3d02a23918b980073d4a76c4e5659eaf5127251ca91a6a804869ba88ef1c8bf + sha256: a5341380a4f1d3a10a4edde5bb75de5127fe31e0faa8c4d860e64d2f91ad84c7 url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.0" audioplayers_android: dependency: transitive description: name: audioplayers_android - sha256: "56830454da1a943f33c686cdbfca52a8d24f4c6fd6ce88c6b3501020dbaaf48b" + sha256: f8c90823a45b475d2c129f85bbda9c029c8d4450b172f62e066564c6e170f69a url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "5.2.0" audioplayers_darwin: dependency: transitive description: name: audioplayers_darwin - sha256: "34e1fb3a88ba02566615c6ca7173aa93b39cf61a38a05a729b60ba14c4899169" + sha256: "405cdbd53ebdb4623f1c5af69f275dad4f930ce895512d5261c07cd95d23e778" url: "https://pub.dev" source: hosted - version: "6.1.0" + version: "6.2.0" audioplayers_linux: dependency: transitive description: name: audioplayers_linux - sha256: ce8383c1ff0db1ba93b5b0b8ef5b260ec2d0ce2598ad37dc8b946b773dd2c5fa + sha256: "7e0d081a6a527c53aef9539691258a08ff69a7dc15ef6335fbea1b4b03ebbef0" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" audioplayers_platform_interface: dependency: transitive description: name: audioplayers_platform_interface - sha256: "6834dd48dfb7bc6c2404998ebdd161f79cd3774a7e6779e1348d54a3bfdcfaa5" + sha256: "77e5fa20fb4a64709158391c75c1cca69a481d35dc879b519e350a05ff520373" url: "https://pub.dev" source: hosted - version: "7.0.0" + version: "7.1.0" audioplayers_web: dependency: transitive description: name: audioplayers_web - sha256: "3609bdf0e05e66a3d9750ee40b1e37f2a622c4edb796cc600b53a90a30a2ace4" + sha256: bd99d8821114747682a2be0adcdb70233d4697af989b549d3a20a0f49f6c9b13 url: "https://pub.dev" source: hosted - version: "5.0.1" + version: "5.1.0" audioplayers_windows: dependency: transitive description: name: audioplayers_windows - sha256: "52b57c706a20fc85daa911be67381b3a5c9d03f8e27cb9fdb226de5bddf293b1" + sha256: "871d3831c25cd2408ddc552600fd4b32fba675943e319a41284704ee038ad563" url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" badges: dependency: "direct main" description: @@ -258,10 +258,10 @@ packages: dependency: transitive description: name: chalkdart - sha256: "08c910ee341fcdd1e46f20ddce59b13c1d85f5d97f2fd2f12014c46ede670e40" + sha256: "7ffc6bd39c81453fb9ba8dbce042a9c960219b75ea1c07196a7fa41c2fab9e86" url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "3.0.5" characters: dependency: "direct main" description: @@ -290,10 +290,18 @@ packages: dependency: "direct main" description: name: chewie - sha256: df6711bc3ba165ad19cb496e350250be5673327f79c61c9cc8a15088ed8007ed + sha256: "4d9554a8f87cc2dc6575dfd5ad20a4375015a29edd567fd6733febe6365e2566" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.11.3" + cli_config: + dependency: transitive + description: + name: cli_config + sha256: ac20a183a07002b700f0c25e61b7ee46b23c309d76ab7b7640a028f18e4d99ec + url: "https://pub.dev" + source: hosted + version: "0.2.0" cli_util: dependency: transitive description: @@ -354,10 +362,10 @@ packages: dependency: transitive description: name: coverage - sha256: e3493833ea012784c740e341952298f1cc77f1f01b1bbc3eb4eecf6984fb7f43 + sha256: "4b8701e48a58f7712492c9b1f7ba0bb9d525644dd66d023b62e1fc8cdb560c8a" url: "https://pub.dev" source: hosted - version: "1.11.1" + version: "1.14.0" cross_file: dependency: "direct main" description: @@ -402,10 +410,10 @@ packages: dependency: transitive description: name: dart_webrtc - sha256: "03df5b41b23bc185ebcf4b0ffc92d002e295bf56287fb5f9d2c321ddaf7760cc" + sha256: "5b76fd85ac95d6f5dee3e7d7de8d4b51bfbec1dc73804647c6aebb52d6297116" url: "https://pub.dev" source: hosted - version: "1.5.1" + version: "1.5.3+hotfix.2" dbus: dependency: transitive description: @@ -521,10 +529,10 @@ packages: dependency: transitive description: name: ffi - sha256: "16ed7b077ef01ad6170a3d0c57caa4a112a38d7a2ed5602e0aca9ca6f3d98da6" + sha256: "289279317b4b16eb2bb7e271abccd4bf84ec9bdcbe999e278a94b804f5630418" url: "https://pub.dev" source: hosted - version: "2.1.3" + version: "2.1.4" file: dependency: transitive description: @@ -553,10 +561,10 @@ packages: dependency: transitive description: name: file_selector_android - sha256: "98ac58e878b05ea2fdb204e7f4fc4978d90406c9881874f901428e01d3b18fbc" + sha256: "6bba3d590ee9462758879741abc132a19133600dd31832f55627442f1ebd7b54" url: "https://pub.dev" source: hosted - version: "0.5.1+12" + version: "0.5.1+14" file_selector_ios: dependency: transitive description: @@ -577,10 +585,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: "271ab9986df0c135d45c3cdb6bd0faa5db6f4976d3e4b437cf7d0f258d941bfc" + sha256: "8c9250b2bd2d8d4268e39c82543bacbaca0fda7d29e0728c3c4bbb7c820fd711" url: "https://pub.dev" source: hosted - version: "0.9.4+2" + version: "0.9.4+3" file_selector_platform_interface: dependency: transitive description: @@ -609,34 +617,34 @@ packages: dependency: "direct main" description: name: firebase_analytics - sha256: "6abce50b79729d8a13c3d4ae05ac612d5ef2f57394330bc5e581ca0e762325f4" + sha256: b5b31727ad7c7f41e139637d1d582e435fd14054abcbd162c64476f90737105c url: "https://pub.dev" source: hosted - version: "11.4.3" + version: "11.4.6" firebase_analytics_platform_interface: dependency: transitive description: name: firebase_analytics_platform_interface - sha256: cd9ae65870bf23ab7e63a04fe9c1b38522fd3556a8c32288afd3f5cb10d4b8f4 + sha256: "86817ee7db6377e09830467932c31644713d8e8915cefff64610ad735612bc7d" url: "https://pub.dev" source: hosted - version: "4.3.3" + version: "4.3.6" firebase_analytics_web: dependency: transitive description: name: firebase_analytics_web - sha256: "5654ed7e39d7a8099e60748924327159785512d78d913e965f9ca93c533af910" + sha256: "4914e184a6aa37811d976db59f95fa8fda1d58db49ab7f73d4d8aac9dd9b19b7" url: "https://pub.dev" source: hosted - version: "0.5.10+9" + version: "0.5.10+12" firebase_core: dependency: "direct main" description: name: firebase_core - sha256: "6a4ea0f1d533443c8afc3d809cd36a4e2b8f2e2e711f697974f55bb31d71d1b8" + sha256: "8cfe3c900512399ce8d50fcc817e5758ff8615eeb6fa5c846a4cc47bbf6353b6" url: "https://pub.dev" source: hosted - version: "3.12.0" + version: "3.13.1" firebase_core_platform_interface: dependency: transitive description: @@ -649,42 +657,42 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: e47f5c2776de018fa19bc9f6f723df136bc75cdb164d64b65305babd715c8e41 + sha256: ddd72baa6f727e5b23f32d9af23d7d453d67946f380bd9c21daf474ee0f7326e url: "https://pub.dev" source: hosted - version: "2.21.0" + version: "2.23.0" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: "8755a083a20bac4485e8b46d223f6f2eab34e659a76a75f8cf3cded53bc98a15" + sha256: "38111089e511f03daa2c66b4c3614c16421b7d78c84ee04331a0a65b47df4542" url: "https://pub.dev" source: hosted - version: "15.2.3" + version: "15.2.6" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: "8cc771079677460de53ad8fcca5bc3074d58c5fc4f9d89b19585e5bfd9c64292" + sha256: ba254769982e5f439e534eed68856181c74e2b3f417c8188afad2bb440807cc7 url: "https://pub.dev" source: hosted - version: "4.6.3" + version: "4.6.6" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: caa73059b0396c97f691683c4cfc3f897c8543801579b7dd4851c431d8e4e091 + sha256: dba89137272aac39e95f71408ba25c33fb8ed903cd5fa8d1e49b5cd0d96069e0 url: "https://pub.dev" source: hosted - version: "3.10.3" + version: "3.10.6" fixnum: dependency: transitive description: name: fixnum - sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + sha256: b6dc7065e46c974bc7c5f143080a6764ec7a4be6da1285ececdc37be96de53be url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" flutter: dependency: "direct main" description: flutter @@ -864,10 +872,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: "615a505aef59b151b46bbeef55b36ce2b6ed299d160c51d84281946f0aa0ce0e" + sha256: f948e346c12f8d5480d2825e03de228d0eb8c3a737e4cdaa122267b89c022b5e url: "https://pub.dev" source: hosted - version: "2.0.24" + version: "2.0.28" flutter_secure_storage: dependency: "direct main" description: @@ -880,10 +888,10 @@ packages: dependency: transitive description: name: flutter_secure_storage_linux - sha256: bf7404619d7ab5c0a1151d7c4e802edad8f33535abfbeff2f9e1fe1274e2d705 + sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.3" flutter_secure_storage_macos: dependency: transitive description: @@ -928,10 +936,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: c200fd79c918a40c5cd50ea0877fa13f81bdaf6f0a5d3dbcc2a13e3285d6aa1b + sha256: d44bf546b13025ec7353091516f6881f1d4c633993cb109c3916c3a0159dadf1 url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.1.0" flutter_test: dependency: "direct dev" description: flutter @@ -941,10 +949,10 @@ packages: dependency: "direct main" description: name: flutter_tts - sha256: baa3cb6b4990318460fe28bfa8c7869399e97223971532c02bd97c5e876aa3c5 + sha256: bdf2fc4483e74450dc9fc6fe6a9b6a5663e108d4d0dad3324a22c8e26bf48af4 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.2.3" flutter_typeahead: dependency: "direct main" description: @@ -980,10 +988,10 @@ packages: dependency: "direct main" description: name: flutter_webrtc - sha256: "9c4ca34ced1d1b780baf3776557f9edd0af18ce030969346f752e8df455faaab" + sha256: b832dc76c0d1577f14aaf35e9c38d4ed7667cbc89c492b7bf4505d8d5f62e08b url: "https://pub.dev" source: hosted - version: "0.12.10" + version: "0.12.12+hotfix.1" freezed_annotation: dependency: transitive description: @@ -1009,50 +1017,50 @@ packages: dependency: "direct main" description: name: geolocator - sha256: d2ec66329cab29cb297d51d96c067d457ca519dca8589665fa0b82ebacb7dbe4 + sha256: f62bcd90459e63210bbf9c35deb6a51c521f992a78de19a1fe5c11704f9530e2 url: "https://pub.dev" source: hosted - version: "13.0.2" + version: "13.0.4" geolocator_android: dependency: transitive description: name: geolocator_android - sha256: "7aefc530db47d90d0580b552df3242440a10fe60814496a979aa67aa98b1fd47" + sha256: fcb1760a50d7500deca37c9a666785c047139b5f9ee15aa5469fae7dbbe3170d url: "https://pub.dev" source: hosted - version: "4.6.1" + version: "4.6.2" geolocator_apple: dependency: transitive description: name: geolocator_apple - sha256: c4ecead17985ede9634f21500072edfcb3dba0ef7b97f8d7bc556d2d722b3ba3 + sha256: dbdd8789d5aaf14cf69f74d4925ad1336b4433a6efdf2fce91e8955dc921bf22 url: "https://pub.dev" source: hosted - version: "2.3.9" + version: "2.3.13" geolocator_platform_interface: dependency: transitive description: name: geolocator_platform_interface - sha256: "386ce3d9cce47838355000070b1d0b13efb5bc430f8ecda7e9238c8409ace012" + sha256: "30cb64f0b9adcc0fb36f628b4ebf4f731a2961a0ebd849f4b56200205056fe67" url: "https://pub.dev" source: hosted - version: "4.2.4" + version: "4.2.6" geolocator_web: dependency: transitive description: name: geolocator_web - sha256: "2ed69328e05cd94e7eb48bb0535f5fc0c0c44d1c4fa1e9737267484d05c29b5e" + sha256: b1ae9bdfd90f861fde8fd4f209c37b953d65e92823cb73c7dee1fa021b06f172 url: "https://pub.dev" source: hosted - version: "4.1.1" + version: "4.1.3" geolocator_windows: dependency: transitive description: name: geolocator_windows - sha256: "53da08937d07c24b0d9952eb57a3b474e29aae2abf9dd717f7e1230995f13f0e" + sha256: "175435404d20278ffd220de83c2ca293b73db95eafbdc8131fe8609be1421eb6" url: "https://pub.dev" source: hosted - version: "0.2.3" + version: "0.2.5" get: dependency: transitive description: @@ -1097,10 +1105,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: f02fd7d2a4dc512fec615529824fdd217fecb3a3d3de68360293a551f21634b3 + sha256: "0b1e06223bee260dee31a171fb1153e306907563a0b0225e8c1733211911429a" url: "https://pub.dev" source: hosted - version: "14.8.1" + version: "15.1.2" graphs: dependency: transitive description: @@ -1153,10 +1161,10 @@ packages: dependency: "direct main" description: name: html - sha256: "1fc58edeaec4307368c60d59b7e15b9d658b57d7f3125098b6294153c75337ec" + sha256: "6d1264f2dffa1b1101c25a91dff0dc2daee4c18e87cd8538729773c073dbf602" url: "https://pub.dev" source: hosted - version: "0.15.5" + version: "0.15.6" html_unescape: dependency: transitive description: @@ -1169,10 +1177,10 @@ packages: dependency: "direct main" description: name: http - sha256: fe7ab022b76f3034adc518fb6ea04a82387620e19977665ea18d30a1cf43442f + sha256: "2c11f3f94c687ee9bad77c171151672986360b2b001d109814ee7140b2cf261b" url: "https://pub.dev" source: hosted - version: "1.3.0" + version: "1.4.0" http_multi_server: dependency: transitive description: @@ -1209,10 +1217,10 @@ packages: dependency: transitive description: name: image_picker_android - sha256: "82652a75e3dd667a91187769a6a2cc81bd8c111bbead698d8e938d2b63e5e89a" + sha256: "317a5d961cec5b34e777b9252393f2afbd23084aa6e60fcf601dcf6341b9ebeb" url: "https://pub.dev" source: hosted - version: "0.8.12+21" + version: "0.8.12+23" image_picker_for_web: dependency: transitive description: @@ -1233,10 +1241,10 @@ packages: dependency: transitive description: name: image_picker_linux - sha256: "4ed1d9bb36f7cd60aa6e6cd479779cc56a4cb4e4de8f49d487b1aaad831300fa" + sha256: "34a65f6740df08bbbeb0a1abd8e6d32107941fd4868f67a507b25601651022c9" url: "https://pub.dev" source: hosted - version: "0.2.1+1" + version: "0.2.1+2" image_picker_macos: dependency: transitive description: @@ -1273,18 +1281,18 @@ packages: dependency: "direct main" description: name: in_app_purchase - sha256: "11a40f148eeb4f681a0572003e2b33432e110c90c1bbb4f9ef83b81ec0c4f737" + sha256: "5cddd7f463f3bddb1d37a72b95066e840d5822d66291331d7f8f05ce32c24b6c" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.2.3" in_app_purchase_android: dependency: transitive description: name: in_app_purchase_android - sha256: "45ae4fe253f85b4fcc58b421fe137f6e48aca16bf8a618cd760cb0542e7f854e" + sha256: fd76e5612da6facadcfe8a3477da092908227260a9f6ec7db9a66dd989c69b02 url: "https://pub.dev" source: hosted - version: "0.4.0" + version: "0.4.0+2" in_app_purchase_platform_interface: dependency: transitive description: @@ -1297,10 +1305,10 @@ packages: dependency: transitive description: name: in_app_purchase_storekit - sha256: "276831961023055b55a2156c1fc043f50f6215ff49fb0f5f2273da6eeb510ecf" + sha256: "9c2b438aa8ef95ac1c1f5ab1aaace8d6edd0ba3745b8b8df832f7baa2e7492f7" url: "https://pub.dev" source: hosted - version: "0.3.21" + version: "0.4.1" injector: dependency: transitive description: @@ -1358,18 +1366,18 @@ packages: dependency: transitive description: name: just_audio_platform_interface - sha256: "271b93b484c6f494ecd72a107fffbdb26b425f170c665b9777a0a24a726f2f24" + sha256: "4cd94536af0219fa306205a58e78d67e02b0555283c1c094ee41e402a14a5c4a" url: "https://pub.dev" source: hosted - version: "4.4.0" + version: "4.5.0" just_audio_web: dependency: transitive description: name: just_audio_web - sha256: "58915be64509a7683c44bf11cd1a23c15a48de104927bee116e3c63c8eeea0d4" + sha256: "6ba8a2a7e87d57d32f0f7b42856ade3d6a9fbe0f1a11fabae0a4f00bb73f0663" url: "https://pub.dev" source: hosted - version: "0.4.14" + version: "0.4.16" jwt_decode: dependency: "direct main" description: @@ -1494,19 +1502,19 @@ packages: dependency: "direct main" description: name: material_symbols_icons - sha256: "1403944c2a68dbdf934fca2b2115a372e70a4a185c136ab71a9d16e2108d0b14" + sha256: "7c50901b39d1ad645ee25d920aed008061e1fd541a897b4ebf2c01d966dbf16b" url: "https://pub.dev" source: hosted - version: "4.2805.1" + version: "4.2815.1" matrix: dependency: "direct main" description: path: "." - ref: disable-space-hierarchy-cache - resolved-ref: "8f62b78df9cea6490ee8b23802e46d7b320aeb83" + ref: main + resolved-ref: "3fb6836b3f8b37091b5874b194aa25b17bb12b32" url: "https://github.com/pangeachat/matrix-dart-sdk.git" source: git - version: "0.38.0" + version: "0.40.0" meta: dependency: transitive description: @@ -1535,10 +1543,10 @@ packages: dependency: "direct dev" description: name: msix - sha256: c50d6bd1aafe0d071a3c1e5a5ccb056404502935cb0a549e3178c4aae16caf33 + sha256: edde648a8133bf301883c869d19d127049683037c65ff64173ba526ac7a8af2f url: "https://pub.dev" source: hosted - version: "3.16.8" + version: "3.16.9" native_imaging: dependency: "direct main" description: @@ -1575,10 +1583,10 @@ packages: dependency: transitive description: name: olm - sha256: "3306bf534ceb914fd148b3b4a3d603fb5e067b2e6da8304025b47c24cfdf6b46" + sha256: "6a3fe1e5170b954dd9e4ba3b27513e6aa9b7591eb7bb0d7f6f32140b7f140c6f" url: "https://pub.dev" source: hosted - version: "2.0.4" + version: "3.1.0" open_file: dependency: "direct main" description: @@ -1655,26 +1663,26 @@ packages: dependency: transitive description: name: package_config - sha256: "92d4488434b520a62570293fbd33bb556c7d49230791c1b4bbd973baf6d2dc67" + sha256: f096c55ebb7deb7e384101542bfba8c52696c1b56fca2eb62827989ef2353bbc url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.2.0" package_info_plus: dependency: "direct main" description: name: package_info_plus - sha256: "67eae327b1b0faf761964a1d2e5d323c797f3799db0e85aa232db8d9e922bc35" + sha256: "7976bfe4c583170d6cdc7077e3237560b364149fcd268b5f53d95a991963b191" url: "https://pub.dev" source: hosted - version: "8.2.1" + version: "8.3.0" package_info_plus_platform_interface: dependency: transitive description: name: package_info_plus_platform_interface - sha256: "205ec83335c2ab9107bbba3f8997f9356d72ca3c715d2f038fc773d0366b4c76" + sha256: "6c935fb612dff8e3cc9632c2b301720c77450a126114126ffaafe28d2e87956c" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" pana: dependency: transitive description: @@ -1719,10 +1727,10 @@ packages: dependency: transitive description: name: path_provider_android - sha256: "4adf4fd5423ec60a29506c76581bc05854c55e3a0b72d35bb28d661c9686edf2" + sha256: d0d310befe2c8ab9e7f393288ccbb11b60c019c6b5afc21973eeee4dda2b35e9 url: "https://pub.dev" source: hosted - version: "2.2.15" + version: "2.2.17" path_provider_foundation: dependency: transitive description: @@ -1783,10 +1791,10 @@ packages: dependency: transitive description: name: permission_handler_apple - sha256: f84a188e79a35c687c132a0a0556c254747a08561e99ab933f12f6ca71ef3c98 + sha256: f000131e755c54cf4d84a5d8bd6e4149e262cc31c5a8b1d698de1ac85fa41023 url: "https://pub.dev" source: hosted - version: "9.4.6" + version: "9.4.7" permission_handler_html: dependency: transitive description: @@ -1815,10 +1823,10 @@ packages: dependency: transitive description: name: petitparser - sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27 + sha256: "07c8f0b1913bcde1ff0d26e57ace2f3012ccbf2b204e070290dad3bb22797646" url: "https://pub.dev" source: hosted - version: "6.0.2" + version: "6.1.0" platform: dependency: transitive description: @@ -1827,14 +1835,6 @@ packages: url: "https://pub.dev" source: hosted version: "3.1.6" - platform_detect: - dependency: transitive - description: - name: platform_detect - sha256: a62f99417fc4fa2d099ce0ccdbb1bd3977920f2a64292c326271f049d4bc3a4f - url: "https://pub.dev" - source: hosted - version: "2.1.0" plugin_platform_interface: dependency: transitive description: @@ -1871,10 +1871,10 @@ packages: dependency: transitive description: name: pointer_interceptor_web - sha256: "7a7087782110f8c1827170660b09f8aa893e0e9a61431dbbe2ac3fc482e8c044" + sha256: "460b600e71de6fcea2b3d5f662c92293c049c4319e27f0829310e5a953b3ee2a" url: "https://pub.dev" source: hosted - version: "0.10.2+1" + version: "0.10.3" polylabel: dependency: transitive description: @@ -1895,10 +1895,10 @@ packages: dependency: "direct main" description: name: pretty_qr_code - sha256: cbdb4af29da1c1fa21dd76f809646c591320ab9e435d3b0eab867492d43607d5 + sha256: b078bd5d51956dea4342378af1b092ad962b81bdbb55b10fffce03461da8db74 url: "https://pub.dev" source: hosted - version: "3.3.0" + version: "3.4.0" process: dependency: transitive description: @@ -1927,18 +1927,18 @@ packages: dependency: "direct main" description: name: provider - sha256: c8a055ee5ce3fd98d6fc872478b03823ffdb448699c6ebdbbc71d59b596fd48c + sha256: "4abbd070a04e9ddc287673bf5a030c7ca8b685ff70218720abab8b092f53dd84" url: "https://pub.dev" source: hosted - version: "6.1.2" + version: "6.1.5" pub_semver: dependency: transitive description: name: pub_semver - sha256: "7b3cfbf654f3edd0c6298ecd5be782ce997ddf0e00531b9464b55245185bbbbd" + sha256: "5bfcf68ca79ef689f8990d1160781b4bad40a3bd5e5218ad4076ddb7f4081585" url: "https://pub.dev" source: hosted - version: "2.1.5" + version: "2.2.0" pubspec_parse: dependency: transitive description: @@ -1959,10 +1959,10 @@ packages: dependency: "direct main" description: name: purchases_flutter - sha256: "88099e7b2a9f140d565d6ded919b9b3b2a0187b59542e46abee1b16b319c2025" + sha256: "0bbb149e5a875f6f39ca913ea70f9007a2ba1c2556d0c3a99e76ef6d169dac4d" url: "https://pub.dev" source: hosted - version: "8.5.1" + version: "8.10.0" qr: dependency: transitive description: @@ -2015,10 +2015,10 @@ packages: dependency: transitive description: name: record_android - sha256: "36e009c3b83e034321a44a7683d95dd055162a231f95600f7da579dcc79701f9" + sha256: "97d7122455f30de89a01c6c244c839085be6b12abca251fc0e78f67fed73628b" url: "https://pub.dev" source: hosted - version: "1.3.1" + version: "1.3.3" record_darwin: dependency: transitive description: @@ -2047,18 +2047,18 @@ packages: dependency: transitive description: name: record_web - sha256: ef6f5c7760f22d6785ee8d97a2133ff14cb839c65e525ad831eb7f891d83f592 + sha256: "024c81eb7f51468b1833a3eca8b461c7ca25c04899dba37abe580bb57afd32e4" url: "https://pub.dev" source: hosted - version: "1.1.5" + version: "1.1.8" record_windows: dependency: transitive description: name: record_windows - sha256: "26bfebc8899f4fa5b6b044089887dc42115820cd6a907bdf40c16e909e87de0a" + sha256: "85a22fc97f6d73ecd67c8ba5f2f472b74ef1d906f795b7970f771a0914167e99" url: "https://pub.dev" source: hosted - version: "1.0.5" + version: "1.0.6" retry: dependency: transitive description: @@ -2119,10 +2119,10 @@ packages: dependency: transitive description: name: sentry - sha256: "90c2f956c146bcc9c4843406dd4a65d08b25575828dc2ad51de0ca5cd713209f" + sha256: "599701ca0693a74da361bc780b0752e1abc98226cf5095f6b069648116c896bb" url: "https://pub.dev" source: hosted - version: "8.13.2" + version: "8.14.2" sentry_dart_plugin: dependency: "direct dev" description: @@ -2135,10 +2135,10 @@ packages: dependency: "direct main" description: name: sentry_flutter - sha256: ee6b41956ad570706bf5c2489915d71d75522d154200c0df24be2c4e5654ca21 + sha256: "5ba2cf40646a77d113b37a07bd69f61bb3ec8a73cbabe5537b05a7c89d2656f8" url: "https://pub.dev" source: hosted - version: "8.13.2" + version: "8.14.2" share_plus: dependency: "direct main" description: @@ -2159,18 +2159,18 @@ packages: dependency: "direct main" description: name: shared_preferences - sha256: "846849e3e9b68f3ef4b60c60cf4b3e02e9321bc7f4d8c4692cf87ffa82fc8a3a" + sha256: "6e8bf70b7fef813df4e9a36f658ac46d107db4b4cfe1048b477d4e453a8159f5" url: "https://pub.dev" source: hosted - version: "2.5.2" + version: "2.5.3" shared_preferences_android: dependency: transitive description: name: shared_preferences_android - sha256: a768fc8ede5f0c8e6150476e14f38e2417c0864ca36bb4582be8e21925a03c22 + sha256: "20cbd561f743a342c76c151d6ddb93a9ce6005751e7aa458baad3858bfbfb6ac" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.10" shared_preferences_foundation: dependency: transitive description: @@ -2239,10 +2239,10 @@ packages: dependency: transitive description: name: shelf_web_socket - sha256: cc36c297b52866d203dbf9332263c94becc2fe0ceaa9681d07b6ef9807023b67 + sha256: "3632775c8e90d6c9712f883e633716432a27758216dfb61bd86a8321c0580925" url: "https://pub.dev" source: hosted - version: "2.0.1" + version: "3.0.0" shimmer: dependency: "direct main" description: @@ -2300,42 +2300,42 @@ packages: dependency: transitive description: name: sqflite - sha256: "2d7299468485dca85efeeadf5d38986909c5eb0cd71fd3db2c2f000e6c9454bb" + sha256: e2297b1da52f127bc7a3da11439985d9b536f75070f3325e62ada69a5c585d03 url: "https://pub.dev" source: hosted - version: "2.4.1" + version: "2.4.2" sqflite_android: dependency: transitive description: name: sqflite_android - sha256: "78f489aab276260cdd26676d2169446c7ecd3484bbd5fead4ca14f3ed4dd9ee3" + sha256: "2b3070c5fa881839f8b402ee4a39c1b4d561704d4ebbbcfb808a119bc2a1701b" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "761b9740ecbd4d3e66b8916d784e581861fd3c3553eda85e167bc49fdb68f709" + sha256: "84731e8bfd8303a3389903e01fb2141b6e59b5973cacbb0929021df08dddbe8b" url: "https://pub.dev" source: hosted - version: "2.5.4+6" + version: "2.5.5" sqflite_common_ffi: dependency: "direct main" description: name: sqflite_common_ffi - sha256: "883dd810b2b49e6e8c3b980df1829ef550a94e3f87deab5d864917d27ca6bf36" + sha256: "1f3ef3888d3bfbb47785cc1dda0dc7dd7ebd8c1955d32a9e8e9dae1e38d1c4c1" url: "https://pub.dev" source: hosted - version: "2.3.4+4" + version: "2.3.5" sqflite_darwin: dependency: transitive description: name: sqflite_darwin - sha256: "22adfd9a2c7d634041e96d6241e6e1c8138ca6817018afc5d443fef91dcefa9c" + sha256: "279832e5cde3fe99e8571879498c9211f3ca6391b0d818df4e17d9fff5c6ccb3" url: "https://pub.dev" source: hosted - version: "2.4.1+1" + version: "2.4.2" sqflite_platform_interface: dependency: transitive description: @@ -2348,18 +2348,18 @@ packages: dependency: "direct main" description: name: sqlcipher_flutter_libs - sha256: a6a08d3082c1deaacc8f6670c78a9c2384991102db5b234d5293aa2c65e87f61 + sha256: "777c3469ada8fe6b808bd50f1c752cdd2ca1b1f3cf751d434502ead15334f3a5" url: "https://pub.dev" source: hosted - version: "0.6.4" + version: "0.6.6" sqlite3: dependency: transitive description: name: sqlite3 - sha256: "32b632dda27d664f85520093ed6f735ae5c49b5b75345afb8b19411bc59bb53d" + sha256: c0503c69b44d5714e6abbf4c1f51a3c3cc42b75ce785f44404765e4635481d38 url: "https://pub.dev" source: hosted - version: "2.7.4" + version: "2.7.6" stack_trace: dependency: transitive description: @@ -2412,10 +2412,10 @@ packages: dependency: transitive description: name: synchronized - sha256: "69fe30f3a8b04a0be0c15ae6490fc859a78ef4c43ae2dd5e8a623d45bfcf9225" + sha256: "0669c70faae6270521ee4f05bffd2919892d42d1276e6c495be80174b6bc0ef6" url: "https://pub.dev" source: hosted - version: "3.3.0+3" + version: "3.3.1" system_info2: dependency: transitive description: @@ -2613,18 +2613,18 @@ packages: dependency: transitive description: name: url_launcher_android - sha256: "6fc2f56536ee873eeb867ad176ae15f304ccccc357848b351f6f0d8d4a40d193" + sha256: "8582d7f6fe14d2652b4c45c9b6c14c0b678c2af2d083a11b604caeba51930d79" url: "https://pub.dev" source: hosted - version: "6.3.14" + version: "6.3.16" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "16a513b6c12bb419304e72ea0ae2ab4fed569920d1c7cb850263fe3acc824626" + sha256: "7f2022359d4c099eea7df3fdf739f7d3d3b9faf3166fb1dd390775176e0b76cb" url: "https://pub.dev" source: hosted - version: "6.3.2" + version: "6.3.3" url_launcher_linux: dependency: transitive description: @@ -2653,10 +2653,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: "3ba963161bd0fe395917ba881d320b9c4f6dd3c4a233da62ab18a5025c85f1e9" + sha256: "4bd2b7b4dc4d4d0b94e5babfffbca8eac1a126c7f3d6ecbc1a11013faa3abba2" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.1" url_launcher_windows: dependency: transitive description: @@ -2693,10 +2693,10 @@ packages: dependency: transitive description: name: vector_graphics_compiler - sha256: "1b4b9e706a10294258727674a340ae0d6e64a7231980f9f9a3d12e4b42407aad" + sha256: "557a315b7d2a6dbb0aaaff84d857967ce6bdc96a63dc6ee2a57ce5a6ee5d3331" url: "https://pub.dev" source: hosted - version: "1.1.16" + version: "1.1.17" vector_math: dependency: transitive description: @@ -2717,26 +2717,26 @@ packages: dependency: "direct main" description: name: video_player - sha256: "48941c8b05732f9582116b1c01850b74dbee1d8520cd7e34ad4609d6df666845" + sha256: "0d55b1f1a31e5ad4c4967bfaa8ade0240b07d20ee4af1dfef5f531056512961a" url: "https://pub.dev" source: hosted - version: "2.9.3" + version: "2.10.0" video_player_android: dependency: transitive description: name: video_player_android - sha256: "7018dbcb395e2bca0b9a898e73989e67c0c4a5db269528e1b036ca38bcca0d0b" + sha256: "4a5135754a62dbc827a64a42ef1f8ed72c962e191c97e2d48744225c2b9ebb73" url: "https://pub.dev" source: hosted - version: "2.7.17" + version: "2.8.7" video_player_avfoundation: dependency: transitive description: name: video_player_avfoundation - sha256: "84b4752745eeccb6e75865c9aab39b3d28eb27ba5726d352d45db8297fbd75bc" + sha256: "9ee764e5cd2fc1e10911ae8ad588e1a19db3b6aa9a6eb53c127c42d3a3c3f22f" url: "https://pub.dev" source: hosted - version: "2.7.0" + version: "2.7.1" video_player_platform_interface: dependency: transitive description: @@ -2749,10 +2749,10 @@ packages: dependency: transitive description: name: video_player_web - sha256: "3ef40ea6d72434edbfdba4624b90fd3a80a0740d260667d91e7ecd2d79e13476" + sha256: e8bba2e5d1e159d5048c9a491bb2a7b29c535c612bb7d10c1e21107f5bd365ba url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.5" vm_service: dependency: transitive description: @@ -2765,18 +2765,18 @@ packages: dependency: "direct main" description: name: wakelock_plus - sha256: "36c88af0b930121941345306d259ec4cc4ecca3b151c02e3a9e71aede83c615e" + sha256: a474e314c3e8fb5adef1f9ae2d247e57467ad557fa7483a2b895bc1b421c5678 url: "https://pub.dev" source: hosted - version: "1.2.10" + version: "1.3.2" wakelock_plus_platform_interface: dependency: transitive description: name: wakelock_plus_platform_interface - sha256: "70e780bc99796e1db82fe764b1e7dcb89a86f1e5b3afb1db354de50f2e41eb7a" + sha256: e10444072e50dbc4999d7316fd303f7ea53d31c824aa5eb05d7ccbdd98985207 url: "https://pub.dev" source: hosted - version: "1.2.2" + version: "1.2.3" watcher: dependency: transitive description: @@ -2789,26 +2789,26 @@ packages: dependency: transitive description: name: web - sha256: cd3543bd5798f6ad290ea73d210f423502e71900302dde696f8bff84bf89a1cb + sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a" url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.1" web_socket: dependency: transitive description: name: web_socket - sha256: "3c12d96c0c9a4eec095246debcea7b86c0324f22df69893d538fcc6f1b8cce83" + sha256: "34d64019aa8e36bf9842ac014bb5d2f5586ca73df5e4d9bf5c936975cae6982c" url: "https://pub.dev" source: hosted - version: "0.1.6" + version: "1.0.1" web_socket_channel: dependency: transitive description: name: web_socket_channel - sha256: "0b8e2457400d8a859b7b2030786835a28a8e80836ef64402abef392ff4f1d0e5" + sha256: d645757fb0f4773d602444000a8131ff5d48c9e47adfe9772652dd1a4f2d45c8 url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.3" webdriver: dependency: transitive description: @@ -2829,10 +2829,10 @@ packages: dependency: "direct main" description: name: webrtc_interface - sha256: e92afec11152a9ccb5c9f35482754edd99696e886ab6acaf90c06dd2d09f09eb + sha256: "86fe3afc81a08481dfb25cf14a5a94e27062ecef25544783f352c914e0bbc1ca" url: "https://pub.dev" source: hosted - version: "1.2.2+hotfix.1" + version: "1.2.2+hotfix.2" win32: dependency: "direct overridden" description: @@ -2890,5 +2890,5 @@ packages: source: hosted version: "3.1.3" sdks: - dart: ">=3.7.0-0 <4.0.0" - flutter: ">=3.27.0" + dart: ">=3.7.0 <4.0.0" + flutter: ">=3.29.0" diff --git a/pubspec.yaml b/pubspec.yaml index b8b738d3d..441d6a5bf 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -18,7 +18,7 @@ dependencies: async: ^2.11.0 badges: ^3.1.2 blurhash_dart: ^1.2.1 - chewie: ^1.8.1 + chewie: ^1.11.0 collection: ^1.18.0 cross_file: ^0.3.4+2 cupertino_icons: any @@ -56,7 +56,7 @@ dependencies: flutter_web_auth_2: ^3.1.1 # Version 4 blocked by https://github.com/MixinNetwork/flutter-plugins/issues/379 flutter_webrtc: ^0.12.9 geolocator: ^13.0.1 - go_router: ^14.8.1 + go_router: ^15.1.2 handy_window: ^0.4.0 hive: ^2.2.3 hive_flutter: ^1.1.0 @@ -72,8 +72,8 @@ dependencies: matrix: git: url: https://github.com/pangeachat/matrix-dart-sdk.git # repo - ref: disable-space-hierarchy-cache # branch - # matrix: ^0.39.1 + ref: main + # matrix: ^0.40.0 # Pangea# mime: ^1.0.6 native_imaging: ^0.2.0 diff --git a/test/utils/test_client.dart b/test/utils/test_client.dart index 7cbe72f71..c27faa8f6 100644 --- a/test/utils/test_client.dart +++ b/test/utils/test_client.dart @@ -3,7 +3,7 @@ import 'package:matrix/encryption/utils/key_verification.dart'; import 'package:matrix/matrix.dart'; -import 'package:fluffychat/utils/matrix_sdk_extensions/flutter_hive_collections_database.dart'; +import 'package:fluffychat/utils/matrix_sdk_extensions/flutter_matrix_dart_sdk_database/builder.dart'; Future prepareTestClient({ bool loggedIn = false, @@ -22,7 +22,7 @@ Future prepareTestClient({ importantStateEvents: { 'im.ponies.room_emotes', // we want emotes to work properly }, - databaseBuilder: FlutterHiveCollectionsDatabase.databaseBuilder, + databaseBuilder: flutterMatrixSdkDatabaseBuilder, supportedLoginTypes: { AuthenticationTypes.password, AuthenticationTypes.sso,