-
Notifications
You must be signed in to change notification settings - Fork 14
Patch create group #717
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Patch create group #717
Changes from all commits
Commits
Show all changes
32 commits
Select commit
Hold shift + click to select a range
a2b934b
refactor: replace relay connection check with delayed relay error sta…
codeswot 44644b4
feat: implement delayed relay error handling and optimize connection …
codeswot 17631fe
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot 476f0d9
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot e69e0f0
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot 68b926c
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot c7e2a70
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot b6fa9fb
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot 4299063
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot be040c1
Refactor group chat creation to use createGroupProvider
codeswot 47f2520
Add create group state and provider
codeswot 0571e5f
Add discardChanges to group creation flow
codeswot 01dc865
Refactor image utils usage in CreateGroupNotifier
codeswot 3c87763
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot 6a0041b
Merge branch 'master' into 535-implement-group-image-upload-on-group-…
codeswot 38b53fe
Refactor group image upload to handle result and update group data
codeswot a38954e
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot 59a3c76
Merge branch '535-implement-group-image-upload-on-group-creation' int…
codeswot 9665166
feat: add group description handling and improve group creation flow
codeswot bc87da4
Refactor generated code for state management classes to improve reada…
codeswot a7b076f
Merge branch 'master' into patch-create-group
codeswot 70981f9
feat: reset group creation state in discardChanges method
codeswot 41aebf7
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot 12d32f1
Merge branch 'master' into patch-create-group
codeswot 84e2055
Merge branch 'patch-create-group' of github.com:parres-hq/whitenoise_…
codeswot 2954d11
refactor: remove stackTrace from CreateGroupState and related classes
codeswot 3af61a6
refactor: streamline code formatting in state classes for consistency
codeswot a9f9390
Merge branch 'master' of github.com:parres-hq/whitenoise_flutter
codeswot 49a4f2a
resolve conficts
codeswot b6527fb
feat: update localization and improve group chat UI elements
codeswot e25c831
feat: restore group chat UI elements and improve invite message local…
codeswot 7a1dfec
feat: add error message for retrying after an error occurs in multipl…
codeswot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
Binary file modified
BIN
-317 KB
(99%)
android/app/src/main/jniLibs/arm64-v8a/librust_lib_whitenoise.so
Binary file not shown.
Binary file modified
BIN
+82.7 KB
(100%)
android/app/src/main/jniLibs/armeabi-v7a/librust_lib_whitenoise.so
Binary file not shown.
Binary file modified
BIN
-410 KB
(99%)
android/app/src/main/jniLibs/x86_64/librust_lib_whitenoise.so
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| import 'package:flutter/material.dart'; | ||
| import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
| import 'package:logging/logging.dart'; | ||
| import 'package:whitenoise/config/providers/active_account_provider.dart'; | ||
| import 'package:whitenoise/config/providers/active_pubkey_provider.dart'; | ||
| import 'package:whitenoise/config/providers/group_provider.dart'; | ||
| import 'package:whitenoise/config/states/create_group_state.dart'; | ||
| import 'package:whitenoise/domain/models/contact_model.dart'; | ||
| import 'package:whitenoise/domain/services/image_picker_service.dart'; | ||
| import 'package:whitenoise/src/rust/api/groups.dart'; | ||
| import 'package:whitenoise/src/rust/api/users.dart'; | ||
| import 'package:whitenoise/src/rust/api/utils.dart' as rust_utils; | ||
| import 'package:whitenoise/utils/localization_extensions.dart'; | ||
|
|
||
| class CreateGroupNotifier extends StateNotifier<CreateGroupState> { | ||
| final _logger = Logger('CreateGroupNotifier'); | ||
| static final _imagePickerService = ImagePickerService(); | ||
| final Ref ref; | ||
|
|
||
| CreateGroupNotifier(this.ref) : super(const CreateGroupState()); | ||
|
|
||
| void updateGroupName(String groupName) { | ||
| final isValid = groupName.trim().isNotEmpty; | ||
| state = state.copyWith( | ||
| groupName: groupName, | ||
| isGroupNameValid: isValid, | ||
| error: null, | ||
| ); | ||
| } | ||
|
|
||
| void updateGroupDescription(String groupDescription) { | ||
| state = state.copyWith( | ||
| groupDescription: groupDescription, | ||
| error: null, | ||
| ); | ||
| } | ||
|
|
||
| Future<void> pickGroupImage() async { | ||
| try { | ||
| final imagePath = await _imagePickerService.pickProfileImage(); | ||
| if (imagePath != null) { | ||
| state = state.copyWith( | ||
| selectedImagePath: imagePath, | ||
| error: null, | ||
| ); | ||
| } | ||
| } catch (e, st) { | ||
| _logger.severe('pickGroupImage', e, st); | ||
| state = state.copyWith( | ||
| error: 'Failed to pick group image', | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Future<void> filterContactsWithKeyPackage( | ||
| List<ContactModel> selectedContacts, | ||
| ) async { | ||
| try { | ||
| final filteredContacts = await _filterContactsByKeyPackage(selectedContacts); | ||
| final contactsWithKeyPackage = filteredContacts['withKeyPackage']!; | ||
| final contactsWithoutKeyPackage = filteredContacts['withoutKeyPackage']!; | ||
|
|
||
| state = state.copyWith( | ||
| contactsWithKeyPackage: contactsWithKeyPackage, | ||
| contactsWithoutKeyPackage: contactsWithoutKeyPackage, | ||
| shouldShowInviteSheet: contactsWithoutKeyPackage.isNotEmpty, | ||
| ); | ||
|
|
||
| if (contactsWithKeyPackage.isEmpty) { | ||
| state = state.copyWith(isCreatingGroup: false); | ||
| return; | ||
| } | ||
| } catch (e, st) { | ||
| _logger.severe('filterContactsWithKeyPackage', e, st); | ||
| state = state.copyWith( | ||
| error: 'Error filtering contacts: ${e.toString()}', | ||
| isCreatingGroup: false, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Future<void> createGroup({ | ||
| ValueChanged<Group?>? onGroupCreated, | ||
| }) async { | ||
| if (!state.isGroupNameValid) return; | ||
| if (state.contactsWithKeyPackage.isEmpty) return; | ||
|
|
||
| state = state.copyWith(isCreatingGroup: true, error: null); | ||
|
|
||
| try { | ||
| final createdGroup = await _createGroupWithContacts(state.contactsWithKeyPackage); | ||
|
|
||
| if (createdGroup != null) { | ||
| if (state.selectedImagePath != null && state.selectedImagePath!.isNotEmpty) { | ||
| final activePubkey = ref.read(activePubkeyProvider) ?? ''; | ||
| if (activePubkey.isEmpty) { | ||
| throw Exception('No active pubkey available'); | ||
| } | ||
|
|
||
| final uploadResult = await _uploadGroupImage( | ||
| createdGroup.mlsGroupId, | ||
| activePubkey, | ||
| ); | ||
|
|
||
| if (uploadResult != null) { | ||
| await createdGroup.updateGroupData( | ||
| accountPubkey: activePubkey, | ||
| groupData: FlutterGroupDataUpdate( | ||
| imageKey: uploadResult.imageKey, | ||
| imageHash: uploadResult.encryptedHash, | ||
| imageNonce: uploadResult.imageNonce, | ||
| ), | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| onGroupCreated?.call(createdGroup); | ||
|
|
||
| state = state.copyWith( | ||
| isCreatingGroup: false, | ||
| shouldShowInviteSheet: false, | ||
| contactsWithoutKeyPackage: [], | ||
| ); | ||
| } else { | ||
| state = state.copyWith( | ||
| error: 'ui.failedToCreateGroup'.tr(), | ||
| isCreatingGroup: false, | ||
| ); | ||
| } | ||
| } catch (e, st) { | ||
| _logger.severe('createGroup', e, st); | ||
| state = state.copyWith( | ||
| error: 'ui.errorCreatingGroup'.tr(), | ||
| isCreatingGroup: false, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| Future<Group?> _createGroupWithContacts(List<ContactModel> contactsWithKeyPackage) async { | ||
| final groupName = state.groupName.trim(); | ||
| final groupDescription = state.groupDescription.trim(); | ||
| final notifier = ref.read(groupsProvider.notifier); | ||
|
|
||
| return await notifier.createNewGroup( | ||
| groupName: groupName, | ||
| groupDescription: groupDescription, | ||
| memberPublicKeyHexs: contactsWithKeyPackage.map((c) => c.publicKey).toList(), | ||
| adminPublicKeyHexs: [], | ||
| ); | ||
| } | ||
|
|
||
| Future<UploadGroupImageResult?> _uploadGroupImage(String groupId, String accountPubkey) async { | ||
| if (state.selectedImagePath == null || state.selectedImagePath!.isEmpty) return null; | ||
|
|
||
| state = state.copyWith(isUploadingImage: true, error: null); | ||
|
|
||
| try { | ||
| final imageUtils = ref.read(wnImageUtilsProvider); | ||
| final imageType = await imageUtils.getMimeTypeFromPath(state.selectedImagePath!); | ||
| if (imageType == null) { | ||
| throw Exception( | ||
| 'Could not determine image type from file path: ${state.selectedImagePath}', | ||
| ); | ||
| } | ||
|
|
||
| final serverUrl = await rust_utils.getDefaultBlossomServerUrl(); | ||
|
|
||
| final result = await uploadGroupImage( | ||
| accountPubkey: accountPubkey, | ||
| groupId: groupId, | ||
| filePath: state.selectedImagePath!, | ||
| imageType: imageType, | ||
| serverUrl: serverUrl, | ||
| ); | ||
|
|
||
| state = state.copyWith( | ||
| isUploadingImage: false, | ||
| error: null, | ||
| ); | ||
| return result; | ||
| } catch (e, st) { | ||
| _logger.severe('_uploadGroupImage', e, st); | ||
| state = state.copyWith( | ||
| error: 'Failed to upload group image: ${e.toString()}', | ||
| isUploadingImage: false, | ||
| ); | ||
| } | ||
| return null; | ||
| } | ||
|
|
||
| Future<Map<String, List<ContactModel>>> _filterContactsByKeyPackage( | ||
| List<ContactModel> contacts, | ||
| ) async { | ||
| final contactsWithKeyPackage = <ContactModel>[]; | ||
| final contactsWithoutKeyPackage = <ContactModel>[]; | ||
|
|
||
| for (final contact in contacts) { | ||
| try { | ||
| final hasKeyPackage = await userHasKeyPackage(pubkey: contact.publicKey); | ||
|
|
||
| if (hasKeyPackage) { | ||
| contactsWithKeyPackage.add(contact); | ||
| } else { | ||
| contactsWithoutKeyPackage.add(contact); | ||
| } | ||
| } catch (e) { | ||
| contactsWithoutKeyPackage.add(contact); | ||
| } | ||
| } | ||
|
|
||
| return { | ||
| 'withKeyPackage': contactsWithKeyPackage, | ||
| 'withoutKeyPackage': contactsWithoutKeyPackage, | ||
| }; | ||
| } | ||
|
|
||
| void clearError() { | ||
| state = state.copyWith( | ||
| error: null, | ||
| ); | ||
| } | ||
|
|
||
| void dismissInviteSheet() { | ||
| state = state.copyWith( | ||
| shouldShowInviteSheet: false, | ||
| contactsWithoutKeyPackage: [], | ||
| ); | ||
| } | ||
|
|
||
| void discardChanges() { | ||
| state = state.copyWith( | ||
| groupName: '', | ||
| groupDescription: '', | ||
| isGroupNameValid: false, | ||
| isCreatingGroup: false, | ||
| isUploadingImage: false, | ||
| selectedImagePath: null, | ||
| error: null, | ||
| contactsWithKeyPackage: [], | ||
| contactsWithoutKeyPackage: [], | ||
| shouldShowInviteSheet: false, | ||
| ); | ||
| } | ||
| } | ||
|
|
||
| final createGroupProvider = StateNotifierProvider<CreateGroupNotifier, CreateGroupState>( | ||
| CreateGroupNotifier.new, | ||
| ); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| import 'package:freezed_annotation/freezed_annotation.dart'; | ||
| import 'package:whitenoise/domain/models/contact_model.dart'; | ||
|
|
||
| part 'create_group_state.freezed.dart'; | ||
|
|
||
| @freezed | ||
| sealed class CreateGroupState with _$CreateGroupState { | ||
| const factory CreateGroupState({ | ||
| @Default('') String groupName, | ||
| @Default('') String groupDescription, | ||
| @Default(false) bool isGroupNameValid, | ||
| @Default(false) bool isCreatingGroup, | ||
| @Default(false) bool isUploadingImage, | ||
| String? selectedImagePath, | ||
| String? error, | ||
| @Default([]) List<ContactModel> contactsWithoutKeyPackage, | ||
| @Default([]) List<ContactModel> contactsWithKeyPackage, | ||
| @Default(false) bool shouldShowInviteSheet, | ||
| }) = _CreateGroupState; | ||
|
|
||
| const CreateGroupState._(); | ||
|
|
||
| bool get canCreateGroup => | ||
| isGroupNameValid && | ||
| !isCreatingGroup && | ||
| !isUploadingImage && | ||
| contactsWithKeyPackage.isNotEmpty; | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.