Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
55 changes: 54 additions & 1 deletion lib/config/providers/group_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,10 @@ import 'package:whitenoise/config/providers/user_profile_provider.dart';
import 'package:whitenoise/config/states/group_state.dart';
import 'package:whitenoise/domain/models/user_model.dart' as domain_user;
import 'package:whitenoise/domain/models/user_model.dart';
import 'package:whitenoise/domain/services/image_picker_service.dart';
import 'package:whitenoise/src/rust/api/error.dart' show ApiError;
import 'package:whitenoise/src/rust/api/groups.dart';
import 'package:whitenoise/src/rust/api/utils.dart';
import 'package:whitenoise/utils/error_handling.dart';
import 'package:whitenoise/utils/localization_extensions.dart';
import 'package:whitenoise/utils/pubkey_formatter.dart';
Expand All @@ -39,6 +41,7 @@ class GroupsNotifier extends Notifier<GroupsState> {
getGroupsInformationFn,
Future<List<Group>> Function(GroupType)? getGroupsByTypeFn,
List<User>? Function(String)? getGroupMembersFn,
ImagePickerService? imagePickerService,
}) : _createGroupFn = createGroupFn ?? createGroup,
_pubkeyFormatter = pubkeyFormatter ?? _defaultPubkeyFormatter,
_getGroupsInformationFn = getGroupsInformationFn ?? getGroupsInformations,
Expand Down Expand Up @@ -158,6 +161,7 @@ class GroupsNotifier extends Notifier<GroupsState> {
for (final group in groups) {
groupsMap[group.mlsGroupId] = group;
}

state = state.copyWith(groups: groups, groupsMap: groupsMap, groupCreatedAts: createdAtMap);

Future.microtask(() => _loadGroupsMetadataLazy(groups));
Expand Down Expand Up @@ -1387,7 +1391,6 @@ class GroupsNotifier extends Notifier<GroupsState> {
groupData: groupData,
);

// Update provider state optimistically after successful backend call
_updateGroupInfo(groupId, name: trimmedName, description: trimmedDescription);
} catch (e, st) {
_logger.severe(
Expand All @@ -1406,6 +1409,56 @@ class GroupsNotifier extends Notifier<GroupsState> {
rethrow;
}
}

Future<void> updateGroupImage({
required String groupId,
required String accountPubkey,
required String imagePath,
}) async {
final group = state.groupsMap?[groupId];
if (group == null) {
throw Exception('Group not found');
}

try {
final serverUrl = await getDefaultBlossomServerUrl();

final uploadResult = await uploadGroupImage(
accountPubkey: accountPubkey,
groupId: groupId,
filePath: imagePath,
serverUrl: serverUrl,
);

final groupData = FlutterGroupDataUpdate(
imageKey: uploadResult.imageKey,
imageHash: uploadResult.encryptedHash,
imageNonce: uploadResult.imageNonce,
);

await group.updateGroupData(
accountPubkey: accountPubkey,
groupData: groupData,
);

await reloadGroupImagePath(groupId);
} catch (e, st) {
_logger.severe(
'GroupsProvider.updateGroupImage - Exception: $e (Type: ${e.runtimeType})',
e,
st,
);

String errorMessage = 'Failed to update group image';
if (e is ApiError) {
errorMessage = await e.messageText();
} else {
errorMessage = e.toString();
}
state = state.copyWith(error: errorMessage);
rethrow;
}
}
}

final groupsProvider = NotifierProvider<GroupsNotifier, GroupsState>(
Expand Down
60 changes: 53 additions & 7 deletions lib/ui/chat/chat_info/edit_group_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import 'package:logging/logging.dart';
import 'package:whitenoise/config/extensions/toast_extension.dart';
import 'package:whitenoise/config/providers/active_pubkey_provider.dart';
import 'package:whitenoise/config/providers/group_provider.dart';
import 'package:whitenoise/domain/services/image_picker_service.dart';
import 'package:whitenoise/ui/core/themes/assets.dart';
import 'package:whitenoise/ui/core/themes/src/extensions.dart';
import 'package:whitenoise/ui/core/ui/wn_app_bar.dart';
import 'package:whitenoise/ui/core/ui/wn_avatar.dart';
import 'package:whitenoise/ui/core/ui/wn_button.dart';
import 'package:whitenoise/ui/core/ui/wn_image.dart';
import 'package:whitenoise/ui/core/ui/wn_text_form_field.dart';
import 'package:whitenoise/ui/settings/profile/widgets/edit_icon.dart';
import 'package:whitenoise/utils/localization_extensions.dart';

class EditGroupScreen extends ConsumerStatefulWidget {
Expand All @@ -33,9 +35,11 @@ class _EditGroupScreenState extends ConsumerState<EditGroupScreen> {
final _logger = Logger('EditGroupScreen');
final _nameController = TextEditingController();
final _descriptionController = TextEditingController();
final _imagePickerService = ImagePickerService();

bool _isLoading = false;
bool _hasChanges = false;
String? _selectedImagePath;

@override
void initState() {
Expand Down Expand Up @@ -67,7 +71,8 @@ class _EditGroupScreenState extends ConsumerState<EditGroupScreen> {
if (group != null) {
final hasChanges =
_nameController.text.trim() != group.name ||
_descriptionController.text.trim() != group.description;
_descriptionController.text.trim() != group.description ||
_selectedImagePath != null;

if (hasChanges != _hasChanges) {
setState(() {
Expand All @@ -77,6 +82,24 @@ class _EditGroupScreenState extends ConsumerState<EditGroupScreen> {
}
}

Future<void> _pickGroupImage() async {
try {
final imagePath = await _imagePickerService.pickProfileImage();
if (imagePath != null) {
if (!mounted) return;
setState(() {
_selectedImagePath = imagePath;
});
_onTextChanged();
}
} catch (e) {
_logger.severe('Error picking group image: $e');
if (mounted) {
ref.showErrorToast('chats.failedToPickImage'.tr());
}
}
}

Future<void> _saveChanges() async {
if (!_hasChanges || _isLoading) return;

Expand All @@ -98,6 +121,16 @@ class _EditGroupScreenState extends ConsumerState<EditGroupScreen> {
setState(() => _isLoading = false);
return;
}
final newImagePath = _selectedImagePath ?? '';
if (newImagePath.isNotEmpty) {
await ref
.read(groupsProvider.notifier)
.updateGroupImage(
groupId: widget.groupId,
accountPubkey: activeAccount,
imagePath: newImagePath,
);
}

await ref
.read(groupsProvider.notifier)
Expand All @@ -110,7 +143,6 @@ class _EditGroupScreenState extends ConsumerState<EditGroupScreen> {

if (mounted) {
ref.showSuccessToast('chats.groupUpdatedSuccessfully'.tr());
//? Small delay to allow toast to show before navigation
await Future.delayed(const Duration(milliseconds: 500));
if (mounted) {
context.pop();
Expand Down Expand Up @@ -144,6 +176,7 @@ class _EditGroupScreenState extends ConsumerState<EditGroupScreen> {
);
}

final currentGroupImage = ref.watch(groupsProvider).groupImagePaths?[widget.groupId];
return AnnotatedRegion<SystemUiOverlayStyle>(
value: const SystemUiOverlayStyle(
statusBarColor: Colors.transparent,
Expand Down Expand Up @@ -186,11 +219,24 @@ class _EditGroupScreenState extends ConsumerState<EditGroupScreen> {
children: [
Gap(65.h),
Center(
child: WnAvatar(
imageUrl: '',
displayName: group.name,
size: 96.w,
showBorder: true,
child: Stack(
alignment: Alignment.bottomCenter,
children: [
WnAvatar(
imageUrl: _selectedImagePath ?? currentGroupImage ?? '',
displayName: group.name,
size: 96.w,
showBorder: true,
),
Positioned(
right: 5.w,
bottom: 4.h,
width: 28.w,
child: WnEditIconWidget(
onTap: _pickGroupImage,
),
),
],
),
),
Gap(36.h),
Expand Down