Skip to content

Commit

Permalink
feat: add allbum selection
Browse files Browse the repository at this point in the history
  • Loading branch information
ice-alcides committed Dec 26, 2024
1 parent f01e029 commit c8017c2
Show file tree
Hide file tree
Showing 15 changed files with 434 additions and 45 deletions.
11 changes: 0 additions & 11 deletions lib/app/features/core/model/media_type.dart
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:flutter/material.dart';
import 'package:ion/app/extensions/extensions.dart';

enum MediaType {
image,
video,
Expand Down Expand Up @@ -36,12 +33,4 @@ enum MediaType {
static bool isVideoUrl(String url) {
return RegExp(r'https?://\S+\.(?:mp4|avi|mov|wmv|flv|mkv|webm)').hasMatch(url);
}

String title(BuildContext context) {
return switch (this) {
MediaType.image => context.i18n.gallery_add_photo_title,
MediaType.video => context.i18n.common_add_video,
MediaType.unknown => '',
};
}
}
15 changes: 15 additions & 0 deletions lib/app/features/gallery/data/models/album_data.c.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// SPDX-License-Identifier: ice License 1.0
import 'package:freezed_annotation/freezed_annotation.dart';

part 'album_data.c.freezed.dart';

@freezed
class AlbumData with _$AlbumData {
const factory AlbumData({
required String id,
required String name,
required int assetCount,
@Default(false) bool isAll,
}) = _AlbumData;

}
2 changes: 2 additions & 0 deletions lib/app/features/gallery/data/models/gallery_state.c.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:ion/app/features/gallery/data/models/album_data.c.dart';
import 'package:ion/app/features/gallery/views/pages/media_picker_type.dart';
import 'package:ion/app/services/media_service/media_service.c.dart';

Expand All @@ -13,5 +14,6 @@ class GalleryState with _$GalleryState {
required int currentPage,
required bool hasMore,
required MediaPickerType type,
AlbumData? selectedAlbum,
}) = _GalleryState;
}
1 change: 1 addition & 0 deletions lib/app/features/gallery/data/models/models.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
// SPDX-License-Identifier: ice License 1.0

export 'album_data.c.dart';
export 'gallery_state.c.dart';
export 'media_selection_state.c.dart';
131 changes: 111 additions & 20 deletions lib/app/features/gallery/providers/gallery_provider.c.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:collection/collection.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/features/core/permissions/data/models/permissions_types.dart';
import 'package:ion/app/features/core/permissions/providers/permissions_provider.c.dart';
import 'package:ion/app/features/gallery/data/models/gallery_state.c.dart';
import 'package:ion/app/features/gallery/data/models/models.dart';
import 'package:ion/app/features/gallery/providers/providers.dart';
import 'package:ion/app/features/gallery/views/pages/media_picker_type.dart';
Expand All @@ -14,6 +14,12 @@ import 'package:riverpod_annotation/riverpod_annotation.dart';

part 'gallery_provider.c.g.dart';

@riverpod
Future<List<AlbumData>> albums(Ref ref, {required MediaPickerType type}) {
final mediaService = ref.watch(mediaServiceProvider);
return mediaService.fetchAlbums(type: type);
}

@riverpod
Future<AssetEntity?> assetEntity(Ref ref, String id) {
return AssetEntity.fromId(id);
Expand All @@ -31,6 +37,12 @@ Future<AssetEntity?> latestGalleryPreview(Ref ref) async {
return await ref.watch(assetEntityProvider(mediaData.first.path).future);
}

@riverpod
Future<AssetEntity?> albumPreview(Ref ref, String albumId) async {
final mediaService = ref.watch(mediaServiceProvider);
return mediaService.fetchFirstAssetOfAlbum(albumId);
}

@riverpod
Future<String?> assetFilePath(Ref ref, String assetId) async {
final assetEntity = await ref.watch(assetEntityProvider(assetId).future);
Expand Down Expand Up @@ -78,14 +90,31 @@ class GalleryNotifier extends _$GalleryNotifier {
}

Future<void> fetchNextPage() async {
if (state.isLoading || state.value?.hasMore == false) return;

final currentState = state.value;
if (currentState == null) return;
final currentState = state.valueOrNull;

if (currentState == null || state.isLoading) return;
if (!currentState.hasMore) return;

if (currentState.selectedAlbum == null) {
state = await AsyncValue.guard(() async {
final newMedia = await ref.read(mediaServiceProvider).fetchGalleryMedia(
page: currentState.currentPage,
size: _pageSize,
type: currentState.type,
);
final hasMore = newMedia.length == _pageSize;
return currentState.copyWith(
mediaData: [...currentState.mediaData, ...newMedia],
currentPage: currentState.currentPage + 1,
hasMore: hasMore,
);
});
return;
}

state = await AsyncValue.guard(() async {
final mediaService = ref.read(mediaServiceProvider);
final newMedia = await mediaService.fetchGalleryMedia(
final newMedia = await _fetchMediaFromSelectedAlbum(
album: currentState.selectedAlbum!,
page: currentState.currentPage,
size: _pageSize,
type: currentState.type,
Expand All @@ -101,24 +130,86 @@ class GalleryNotifier extends _$GalleryNotifier {
});
}

Future<void> addCapturedMediaFileToGallery(MediaFile mediaFile) async {
state = await AsyncValue.guard(() async {
final currentState = state.value!;
final updatedMediaData = [mediaFile, ...currentState.mediaData];
Future<void> selectAlbum(AlbumData album) async {
final currentState = state.valueOrNull;
if (currentState == null) return;

return currentState.copyWith(
mediaData: updatedMediaData,
currentPage: currentState.currentPage,
hasMore: currentState.hasMore,
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final newState = await _loadCurrentAlbum(
oldState: currentState.copyWith(selectedAlbum: album, mediaData: []),
page: 0,
type: currentState.type,
);
return newState;
});
}

Future<GalleryState> _loadCurrentAlbum({
required GalleryState oldState,
required int page,
required MediaPickerType type,
}) async {
var album = oldState.selectedAlbum;
if (album == null) {
final albumList = await ref.read(albumsProvider(type: type).future);
if (albumList.isEmpty) {
return oldState.copyWith(
mediaData: [],
currentPage: 0,
hasMore: false,
selectedAlbum: null,
);
}

album = albumList.firstWhereOrNull((a) => a.isAll) ?? albumList.first;
}

final newMedia = await _fetchMediaFromSelectedAlbum(
album: album,
page: page,
size: _pageSize,
type: type,
);

return oldState.copyWith(
mediaData: newMedia,
currentPage: page + 1,
hasMore: newMedia.length == _pageSize,
selectedAlbum: album,
);
}

final currentState = state.value;
Future<List<MediaFile>> _fetchMediaFromSelectedAlbum({
required AlbumData album,
required int page,
required int size,
required MediaPickerType type,
}) async {
final mediaService = ref.read(mediaServiceProvider);
final mediaFiles = await mediaService.fetchMediaFromAlbum(
albumId: album.id,
page: page,
size: size,
);

return mediaFiles;
}

Future<void> addCapturedMediaFileToGallery(MediaFile mediaFile) async {
final currentState = state.valueOrNull;
if (currentState == null) return;

ref.read(mediaSelectionNotifierProvider.notifier).toggleSelection(
mediaFile.path,
type: currentState.type,
);
state = await AsyncValue.guard(() async {
final updated = [mediaFile, ...currentState.mediaData];
return currentState.copyWith(mediaData: updated);
});

final updatedState = state.valueOrNull;
if (updatedState != null) {
ref
.read(mediaSelectionNotifierProvider.notifier)
.toggleSelection(mediaFile.path, type: updatedState.type);
}
}
}
65 changes: 65 additions & 0 deletions lib/app/features/gallery/views/components/albums/album_item.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:flutter/material.dart';
import 'package:ion/app/components/screen_offset/screen_side_offset.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/gallery/views/components/albums/album_thumbnail.dart';
import 'package:ion/generated/assets.gen.dart';

class AlbumItem extends StatelessWidget {
const AlbumItem({
required this.albumId,
required this.name,
required this.assetCount,
required this.isAll,
required this.isSelected,
required this.onTap,
super.key,
});

final String albumId;
final String name;
final int assetCount;
final bool isAll;
final bool isSelected;
final VoidCallback onTap;

@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: onTap,
child: ScreenSideOffset.small(
child: Padding(
padding: EdgeInsets.only(bottom: 16.0.s),
child: Row(
children: [
AlbumThumbnail(albumId: albumId),
SizedBox(width: 12.0.s),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
isAll ? context.i18n.gallery_albums_all_title : name,
style: context.theme.appTextThemes.subtitle3,
),
Text(
'$assetCount',
style: context.theme.appTextThemes.caption2.copyWith(
color: context.theme.appColors.tertararyText,
),
),
],
),
),
if (isSelected)
Assets.svg.iconBlockCheckboxOn.icon()
else
Assets.svg.iconBlockCheckboxOff.icon(color: context.theme.appColors.onTerararyFill),
],
),
),
),
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// SPDX-License-Identifier: ice License 1.0

import 'package:flutter/material.dart';
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ion/app/extensions/extensions.dart';
import 'package:ion/app/features/gallery/providers/gallery_provider.c.dart';
import 'package:ion/app/features/gallery/views/components/shimmer_loading_cell.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:photo_manager_image_provider/photo_manager_image_provider.dart';

class AlbumThumbnail extends ConsumerWidget {
const AlbumThumbnail({required this.albumId, super.key});

final String albumId;

@override
Widget build(BuildContext context, WidgetRef ref) {
final previewAsync = ref.watch(albumPreviewProvider(albumId));

return previewAsync.maybeWhen(
data: (asset) {
if (asset == null) {
return ClipRRect(
borderRadius: BorderRadius.circular(12.0.s),
child: ShimmerLoadingCell(dimension: 50.0.s),
);
}

return SizedBox.square(
dimension: 50.0.s,
child: ClipRRect(
borderRadius: BorderRadius.circular(12.0.s),
child: Image(
image: AssetEntityImageProvider(
asset,
isOriginal: false,
thumbnailSize: const ThumbnailSize.square(300),
),
fit: BoxFit.cover,
),
),
);
},
orElse: () => ClipRRect(
borderRadius: BorderRadius.circular(12.0.s),
child: ShimmerLoadingCell(dimension: 50.0.s),
),
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@ import 'package:ion/app/components/skeleton/skeleton.dart';
import 'package:ion/app/extensions/extensions.dart';

class ShimmerLoadingCell extends StatelessWidget {
const ShimmerLoadingCell({super.key});
const ShimmerLoadingCell({super.key, this.dimension});

final double? dimension;

@override
Widget build(BuildContext context) {
return Skeleton(
child: SizedBox(
width: 122.0.s,
height: 120.0.s,
child: SizedBox.square(
dimension: dimension ?? 122.0.s,
child: const DecoratedBox(
decoration: BoxDecoration(
color: Colors.grey,
Expand Down
Loading

0 comments on commit c8017c2

Please sign in to comment.