From e2654dd5c237a92438102c049dcbd8806f4522c7 Mon Sep 17 00:00:00 2001 From: Pratik-canopas Date: Tue, 5 Mar 2024 17:19:05 +0530 Subject: [PATCH 1/2] Show media list from different sources --- .idea/libraries/Dart_Packages.xml | 154 +++++++++ .idea/libraries/Flutter_Plugins.xml | 8 + app/ios/Podfile.lock | 21 ++ app/lib/components/resume_detector.dart | 39 +++ .../flow/home/components/app_media_item.dart | 153 +++++++++ .../ui/flow/home/components/image_item.dart | 111 ------- .../multi_selection_done_button.dart | 16 +- .../no_local_medias_access_screen.dart | 7 +- .../screen_source_segment_control.dart | 58 ---- .../google_drive_medias_screen.dart | 97 ------ ...google_drive_medias_screen_view_model.dart | 70 ----- ...rive_medias_screen_view_model.freezed.dart | 206 ------------- app/lib/ui/flow/home/home_screen.dart | 146 ++++++--- .../ui/flow/home/home_screen_view_model.dart | 179 +++++++++-- .../home/home_screen_view_model.freezed.dart | 183 ++++++++--- .../local/local_media_screen_view_model.dart | 121 -------- ...local_media_screen_view_model.freezed.dart | 291 ------------------ .../flow/home/local/local_medias_screen.dart | 136 -------- .../Flutter/GeneratedPluginRegistrant.swift | 6 + app/pubspec.lock | 154 ++++++++- app/pubspec.yaml | 4 + data/.flutter-plugins-dependencies | 2 +- data/lib/models/media/media.dart | 143 ++++++++- data/lib/models/media/media.freezed.dart | 111 +++++-- data/lib/models/media/media.g.dart | 22 +- data/lib/services/google_drive_service.dart | 8 +- data/lib/services/local_media_service.dart | 32 +- style/lib/animations/item_selector.dart | 57 ++++ ...allex_effect.dart => parallax_effect.dart} | 0 style/lib/buttons/buttons_list.dart | 2 +- style/lib/extensions/list_extensions.dart | 26 ++ .../circular_progress_indicator.dart | 34 ++ style/lib/theme/app_theme_builder.dart | 5 +- 33 files changed, 1346 insertions(+), 1256 deletions(-) create mode 100644 app/lib/components/resume_detector.dart create mode 100644 app/lib/ui/flow/home/components/app_media_item.dart delete mode 100644 app/lib/ui/flow/home/components/image_item.dart rename app/lib/ui/flow/home/{local => }/components/multi_selection_done_button.dart (71%) rename app/lib/ui/flow/home/{local => }/components/no_local_medias_access_screen.dart (87%) delete mode 100644 app/lib/ui/flow/home/components/screen_source_segment_control.dart delete mode 100644 app/lib/ui/flow/home/google_drive/google_drive_medias_screen.dart delete mode 100644 app/lib/ui/flow/home/google_drive/google_drive_medias_screen_view_model.dart delete mode 100644 app/lib/ui/flow/home/google_drive/google_drive_medias_screen_view_model.freezed.dart delete mode 100644 app/lib/ui/flow/home/local/local_media_screen_view_model.dart delete mode 100644 app/lib/ui/flow/home/local/local_media_screen_view_model.freezed.dart delete mode 100644 app/lib/ui/flow/home/local/local_medias_screen.dart create mode 100644 style/lib/animations/item_selector.dart rename style/lib/animations/{parallex_effect.dart => parallax_effect.dart} (100%) create mode 100644 style/lib/extensions/list_extensions.dart create mode 100644 style/lib/indicators/circular_progress_indicator.dart diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index 5633c04..a692d80 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -107,6 +107,27 @@ + + + + + + + + + + + + + + + + + + @@ -170,6 +191,13 @@ + + + + + + @@ -268,6 +296,13 @@ + + + + + + @@ -405,6 +440,7 @@ @@ -422,6 +458,13 @@ + + + + + + @@ -541,6 +584,13 @@ + + + + + + @@ -576,6 +626,27 @@ + + + + + + + + + + + + + + + + + + @@ -814,6 +885,20 @@ + + + + + + + + + + + + @@ -849,6 +934,13 @@ + + + + + + @@ -912,6 +1004,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -986,6 +1120,9 @@ + + + @@ -995,6 +1132,7 @@ + @@ -1008,6 +1146,7 @@ + @@ -1025,8 +1164,10 @@ + + @@ -1044,11 +1185,15 @@ + + + + @@ -1082,11 +1227,14 @@ + + + @@ -1096,6 +1244,12 @@ + + + + + + diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index d624f0c..16ff21e 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -20,6 +20,14 @@ + + + + + + + + diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 959bd87..6369ffb 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -39,6 +39,9 @@ PODS: - GTMSessionFetcher/Core (3.3.1) - package_info_plus (0.4.5): - Flutter + - path_provider_foundation (0.0.1): + - Flutter + - FlutterMacOS - permission_handler_apple (9.3.0): - Flutter - photo_manager (2.0.0): @@ -48,7 +51,13 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - sqflite (0.0.3): + - Flutter + - FlutterMacOS - Toast (4.1.0) + - video_player_avfoundation (0.0.1): + - Flutter + - FlutterMacOS DEPENDENCIES: - firebase_core (from `.symlinks/plugins/firebase_core/ios`) @@ -56,9 +65,12 @@ DEPENDENCIES: - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - google_sign_in_ios (from `.symlinks/plugins/google_sign_in_ios/darwin`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) + - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) - photo_manager (from `.symlinks/plugins/photo_manager/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sqflite (from `.symlinks/plugins/sqflite/darwin`) + - video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/darwin`) SPEC REPOS: trunk: @@ -84,12 +96,18 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/google_sign_in_ios/darwin" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" + path_provider_foundation: + :path: ".symlinks/plugins/path_provider_foundation/darwin" permission_handler_apple: :path: ".symlinks/plugins/permission_handler_apple/ios" photo_manager: :path: ".symlinks/plugins/photo_manager/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sqflite: + :path: ".symlinks/plugins/sqflite/darwin" + video_player_avfoundation: + :path: ".symlinks/plugins/video_player_avfoundation/darwin" SPEC CHECKSUMS: AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 @@ -105,11 +123,14 @@ SPEC CHECKSUMS: GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae GTMSessionFetcher: 8a1b34ad97ebe6f909fb8b9b77fba99943007556 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 + path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c permission_handler_apple: 036b856153a2b1f61f21030ff725f3e6fece2b78 photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604 PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 + sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec Toast: ec33c32b8688982cecc6348adeae667c1b9938da + video_player_avfoundation: 02011213dab73ae3687df27ce441fbbcc82b5579 PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796 diff --git a/app/lib/components/resume_detector.dart b/app/lib/components/resume_detector.dart new file mode 100644 index 0000000..6d316db --- /dev/null +++ b/app/lib/components/resume_detector.dart @@ -0,0 +1,39 @@ +import 'package:flutter/cupertino.dart'; +import 'package:visibility_detector/visibility_detector.dart'; + +class ResumeDetector extends StatefulWidget { + final Function() onResume; + final Widget child; + + const ResumeDetector({ + super.key, + required this.onResume, + required this.child, + }); + + @override + State createState() => _ResumeDetectorState(); +} + +class _ResumeDetectorState extends State { + final Key _key = UniqueKey(); + + var _lastNotifiedTime = DateTime.now(); + + @override + Widget build(BuildContext context) { + return VisibilityDetector( + key: _key, + child: widget.child, + onVisibilityChanged: (info) { + if (info.visibleFraction == 1) { + if (DateTime.now().difference(_lastNotifiedTime).inMilliseconds > + 1000) { + widget.onResume(); + _lastNotifiedTime = DateTime.now(); + } + } + }, + ); + } +} diff --git a/app/lib/ui/flow/home/components/app_media_item.dart b/app/lib/ui/flow/home/components/app_media_item.dart new file mode 100644 index 0000000..ff2989e --- /dev/null +++ b/app/lib/ui/flow/home/components/app_media_item.dart @@ -0,0 +1,153 @@ +import 'dart:io'; +import 'package:cached_network_image/cached_network_image.dart'; +import 'package:data/models/media/media.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:style/animations/parallax_effect.dart'; +import 'package:style/extensions/context_extensions.dart'; +import 'package:style/indicators/circular_progress_indicator.dart'; +import 'package:video_player/video_player.dart'; +import '../../../../domain/assets/assets_paths.dart'; +import 'package:style/animations/item_selector.dart'; + +class AppMediaItem extends StatefulWidget { + final AppMedia media; + final void Function()? onTap; + final void Function()? onLongTap; + final bool isSelected; + final bool isUploading; + + const AppMediaItem({ + super.key, + required this.media, + this.onTap, + this.onLongTap, + this.isSelected = false, + this.isUploading = false, + }); + + @override + State createState() => _AppMediaItemState(); +} + +class _AppMediaItemState extends State with AutomaticKeepAliveClientMixin { + final _imageKey = GlobalKey(); + VideoPlayerController? _videoPlayerController; + + + @override + void initState() { + if (widget.media.type.isVideo && + widget.media.sources.contains(AppMediaSource.local)) { + if (widget.media.sources.contains(AppMediaSource.local)) { + _videoPlayerController = + VideoPlayerController.file(File(widget.media.path)) + ..initialize().then((_) { + setState(() {}); + }); + } + } + super.initState(); + } + + + + @override + Widget build(BuildContext context) { + super.build(context); + return ItemSelector( + onTap: widget.onTap, + onLongTap: widget.onLongTap, + isSelected: widget.isSelected, + child: Stack( + alignment: Alignment.bottomLeft, + children: [ + widget.media.type.isVideo && + widget.media.sources.contains(AppMediaSource.local) + ? _buildVideoView(context: context) + : _buildImageView(context: context), + if (widget.media.sources.contains(AppMediaSource.googleDrive) || + widget.isUploading) + _sourceIndicators(context: context), + ], + ), + ); + } + + Widget _sourceIndicators({required BuildContext context}) { + return Container( + margin: const EdgeInsets.all(4), + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + color: context.colorScheme.surface.withOpacity(0.6), + borderRadius: BorderRadius.circular(8), + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + if (widget.media.sources.contains(AppMediaSource.googleDrive)) + SvgPicture.asset( + Assets.images.icons.googlePhotos, + height: 12, + width: 12, + ), + if (widget.isUploading) const AppCircularProgressIndicator(size: 12), + ], + ), + ); + } + + Widget _buildImageView({required BuildContext context}) { + return LayoutBuilder( + builder: (context, constraints) { + return ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Flow( + delegate: ParallaxFlowDelegate( + scrollable: Scrollable.of(context), + listItemContext: context, + backgroundImageKey: _imageKey, + ), + children: [ + Image( + key: _imageKey, + image: widget.media.sources.contains(AppMediaSource.local) + ? FileImage(File(widget.media.path)) + : CachedNetworkImageProvider(widget.media.thumbnailPath!) + as ImageProvider, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return const Center(child: AppCircularProgressIndicator()); + }, + width: constraints.maxWidth, + height: constraints.maxHeight * 1.5, + ), + ], + ), + ); + }, + ); + } + + Widget _buildVideoView({required BuildContext context}) { + return Stack( + alignment: Alignment.center, + children: [ + Container( + decoration: BoxDecoration( + color: context.colorScheme.containerLowOnSurface, + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: VideoPlayer(_videoPlayerController!))), + Icon(CupertinoIcons.play_arrow_solid, color: context.colorScheme.onPrimary), + ], + ); + } + + @override + bool get wantKeepAlive => true; +} diff --git a/app/lib/ui/flow/home/components/image_item.dart b/app/lib/ui/flow/home/components/image_item.dart deleted file mode 100644 index d5de8ed..0000000 --- a/app/lib/ui/flow/home/components/image_item.dart +++ /dev/null @@ -1,111 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:style/animations/on_tap_scale.dart'; -import 'package:style/animations/parallex_effect.dart'; -import 'package:style/extensions/context_extensions.dart'; - -class ImageItem extends StatefulWidget { - final VoidCallback? onTap; - final VoidCallback? onLongTap; - final ImageProvider imageProvider; - final bool isSelected; - - const ImageItem({ - super.key, - required this.imageProvider, - this.onTap, - this.onLongTap, - this.isSelected = false, - }); - - @override - State createState() => _ImageItemState(); -} - -class _ImageItemState extends State { - final _backgroundImageKey = GlobalKey(); - - @override - Widget build(BuildContext context) { - return ItemSelector( - onTap: widget.onTap, - onLongTap: widget.onLongTap, - isSelected: widget.isSelected, - child: LayoutBuilder( - builder: (context, constraints) { - return ClipRRect( - borderRadius: BorderRadius.circular(12), - child: Flow( - delegate: ParallaxFlowDelegate( - scrollable: Scrollable.of(context), - listItemContext: context, - backgroundImageKey: _backgroundImageKey, - ), - children: [ - Image( - key: _backgroundImageKey, - image: widget.imageProvider, - fit: BoxFit.cover, - width: constraints.maxWidth, - height: constraints.maxHeight * 1.5, - ), - ], - ), - ); - }, - ), - ); - } -} - -class ItemSelector extends StatelessWidget { - final void Function()? onTap; - final void Function()? onLongTap; - final bool isSelected; - final Widget child; - - const ItemSelector( - {super.key, - this.onTap, - this.onLongTap, - required this.isSelected, - required this.child}); - - @override - Widget build(BuildContext context) { - return OnTapScale( - onTap: onTap, - onLongTap: onLongTap, - child: Stack( - children: [ - AnimatedScale( - scale: isSelected ? 0.9 : 1, - duration: const Duration(milliseconds: 100), - child: AnimatedOpacity( - duration: const Duration(milliseconds: 100), - opacity: isSelected ? 0.7 : 1, - child: child), - ), - if (isSelected) - Align( - alignment: Alignment.bottomRight, - child: Container( - padding: const EdgeInsets.all(2), - decoration: BoxDecoration( - shape: BoxShape.circle, - color: context.colorScheme.surface, - border: Border.all( - color: const Color(0xff808080), - ), - ), - child: const Icon( - CupertinoIcons.checkmark_alt, - color: Color(0xff808080), - size: 16, - ), - ), - ), - ], - ), - ); - } -} diff --git a/app/lib/ui/flow/home/local/components/multi_selection_done_button.dart b/app/lib/ui/flow/home/components/multi_selection_done_button.dart similarity index 71% rename from app/lib/ui/flow/home/local/components/multi_selection_done_button.dart rename to app/lib/ui/flow/home/components/multi_selection_done_button.dart index a7ad362..90aab74 100644 --- a/app/lib/ui/flow/home/local/components/multi_selection_done_button.dart +++ b/app/lib/ui/flow/home/components/multi_selection_done_button.dart @@ -1,20 +1,21 @@ import 'package:cloud_gallery/domain/extensions/context_extensions.dart'; -import 'package:cloud_gallery/ui/flow/home/local/local_media_screen_view_model.dart'; +import 'package:cloud_gallery/ui/flow/home/home_screen_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:go_router/go_router.dart'; import 'package:style/extensions/context_extensions.dart'; -import '../../../../../components/action_sheet.dart'; -import '../../../../../components/app_sheet.dart'; -import '../../../../../domain/assets/assets_paths.dart'; +import '../../../../components/action_sheet.dart'; +import '../../../../components/app_sheet.dart'; +import '../../../../domain/assets/assets_paths.dart'; class MultiSelectionDoneButton extends ConsumerWidget { const MultiSelectionDoneButton({super.key}); @override Widget build(BuildContext context, WidgetRef ref) { - final notifier = ref.read(localMediasViewStateNotifier.notifier); + final notifier = ref.read(homeViewStateNotifier.notifier); return FloatingActionButton( elevation: 3, backgroundColor: context.colorScheme.primary, @@ -31,7 +32,10 @@ class MultiSelectionDoneButton extends ConsumerWidget { width: 24, ), title: context.l10n.back_up_on_google_drive_text, - onPressed: notifier.uploadMediaOnGoogleDrive, + onPressed: () { + notifier.uploadMediaOnGoogleDrive(); + context.pop(); + }, ), ], ), diff --git a/app/lib/ui/flow/home/local/components/no_local_medias_access_screen.dart b/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart similarity index 87% rename from app/lib/ui/flow/home/local/components/no_local_medias_access_screen.dart rename to app/lib/ui/flow/home/components/no_local_medias_access_screen.dart index 42ae9f5..d3ebd2f 100644 --- a/app/lib/ui/flow/home/local/components/no_local_medias_access_screen.dart +++ b/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart @@ -1,5 +1,5 @@ import 'package:cloud_gallery/domain/extensions/context_extensions.dart'; -import 'package:cloud_gallery/ui/flow/home/local/local_media_screen_view_model.dart'; +import 'package:cloud_gallery/ui/flow/home/home_screen_view_model.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:permission_handler/permission_handler.dart'; @@ -12,7 +12,7 @@ class NoLocalMediasAccessScreen extends ConsumerWidget { @override Widget build(BuildContext context, WidgetRef ref) { - final notifier = ref.read(localMediasViewStateNotifier.notifier); + final notifier = ref.read(homeViewStateNotifier.notifier); return SafeArea( child: Padding( padding: const EdgeInsets.all(30), @@ -42,8 +42,7 @@ class NoLocalMediasAccessScreen extends ConsumerWidget { PrimaryButton( onPressed: () async { await openAppSettings(); - await notifier.loadMediaCount(); - await notifier.loadMedia(); + await notifier.loadMedias(); }, text: context.l10n.load_local_media_button_text, ), diff --git a/app/lib/ui/flow/home/components/screen_source_segment_control.dart b/app/lib/ui/flow/home/components/screen_source_segment_control.dart deleted file mode 100644 index bdde01e..0000000 --- a/app/lib/ui/flow/home/components/screen_source_segment_control.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:cloud_gallery/domain/extensions/context_extensions.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:style/extensions/context_extensions.dart'; -import 'package:style/text/app_text_style.dart'; -import '../home_screen_view_model.dart'; - -class ScreenSourceSegmentControl extends ConsumerWidget { - const ScreenSourceSegmentControl({super.key}); - - @override - Widget build(BuildContext context, WidgetRef ref) { - final sources = - ref.watch(homeViewStateNotifier.select((state) => state.sourcePage)); - final notifier = ref.read(homeViewStateNotifier.notifier); - return Padding( - padding: const EdgeInsets.only(top: 10, left: 16, right: 16, bottom: 10), - child: SizedBox( - width: double.infinity, - child: SegmentedButton( - segments: [ - ButtonSegment( - value: MediaSource.local, - label: - Text(context.l10n.common_local, style: AppTextStyles.body2), - ), - ButtonSegment( - value: MediaSource.googleDrive, - label: Text( - context.l10n.common_google_drive, - style: AppTextStyles.body2, - ), - ) - ], - selected: {sources.sourcePage}, - multiSelectionEnabled: false, - onSelectionChanged: (source) { - notifier.updateMediaSource( - source: source.first, isChangedByScroll: false); - }, - showSelectedIcon: false, - style: SegmentedButton.styleFrom( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12), - side: BorderSide.none, - ), - side: BorderSide.none, - foregroundColor: context.colorScheme.textPrimary, - selectedForegroundColor: context.colorScheme.onPrimary, - selectedBackgroundColor: context.colorScheme.primary, - backgroundColor: context.colorScheme.containerNormalOnSurface, - visualDensity: VisualDensity.compact, - ), - ), - ), - ); - } -} diff --git a/app/lib/ui/flow/home/google_drive/google_drive_medias_screen.dart b/app/lib/ui/flow/home/google_drive/google_drive_medias_screen.dart deleted file mode 100644 index cac79f7..0000000 --- a/app/lib/ui/flow/home/google_drive/google_drive_medias_screen.dart +++ /dev/null @@ -1,97 +0,0 @@ -import 'package:cloud_gallery/ui/flow/home/google_drive/google_drive_medias_screen_view_model.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:style/buttons/primary_button.dart'; -import 'package:style/extensions/context_extensions.dart'; -import '../../../../components/snack_bar.dart'; -import '../../../../domain/extensions/widget_extensions.dart'; - -class GoogleDriveMediasScreen extends ConsumerStatefulWidget { - const GoogleDriveMediasScreen({super.key}); - - @override - ConsumerState createState() => _LocalSourceViewState(); -} - -class _LocalSourceViewState extends ConsumerState { - late GoogleDriveMediasStateNotifier notifier; - final _scrollController = ScrollController(); - - @override - void initState() { - super.initState(); - notifier = ref.read(googleDriveMediasStateNotifierProvider.notifier); - runPostFrame(() async { - await notifier.init(); - }); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - void _observeError() { - ref.listen( - googleDriveMediasStateNotifierProvider.select((value) => value.error), - (previous, next) { - if (next != null) { - showErrorSnackBar(context: context, error: next); - } - }); - } - - @override - Widget build(BuildContext context) { - //Listeners - _observeError(); - - //States - final medias = ref.watch( - googleDriveMediasStateNotifierProvider.select((state) => state.medias)); - - final isLoading = ref.watch(googleDriveMediasStateNotifierProvider - .select((state) => state.loading)); - - final isSignedIn = ref.watch(googleDriveMediasStateNotifierProvider - .select((state) => state.isSignedIn)); - - //View - if (!isSignedIn) { - return Center( - child: PrimaryButton( - onPressed: notifier.signInWithGoogle, - child: const Text('Sign in with Google'), - ), - ); - } else if (isLoading && medias.isEmpty) { - return const Center(child: CircularProgressIndicator.adaptive()); - } - return Scrollbar( - controller: _scrollController, - child: GridView.builder( - controller: _scrollController, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - ), - itemCount: medias.length, - itemBuilder: (context, index) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: context.colorScheme.outline, - image: DecorationImage( - image: NetworkImage(medias[index].path), - fit: BoxFit.cover, - ), - ), - ); - }, - ), - ); - } -} diff --git a/app/lib/ui/flow/home/google_drive/google_drive_medias_screen_view_model.dart b/app/lib/ui/flow/home/google_drive/google_drive_medias_screen_view_model.dart deleted file mode 100644 index ee4bb7e..0000000 --- a/app/lib/ui/flow/home/google_drive/google_drive_medias_screen_view_model.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:data/models/media/media.dart'; -import 'package:data/services/auth_service.dart'; -import 'package:data/services/google_drive_service.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'google_drive_medias_screen_view_model.freezed.dart'; - -final googleDriveMediasStateNotifierProvider = StateNotifierProvider - .autoDispose( - (ref) => GoogleDriveMediasStateNotifier( - ref.read(googleDriveServiceProvider), - ref.read(authServiceProvider), - ), -); - -class GoogleDriveMediasStateNotifier - extends StateNotifier { - final GoogleDriveService _googleDriveService; - final AuthService _authService; - - String? _backupFolderId; - - GoogleDriveMediasStateNotifier(this._googleDriveService, this._authService) - : super(GoogleDriveMediasViewState( - isSignedIn: _authService.hasUserSigned, - )); - - Future init() async { - if (_authService.hasUserSigned) { - getGoogleDriveMedias(); - } - } - - - Future signInWithGoogle() async { - try { - state = state.copyWith(loading: true, error: null); - await _authService.signInWithGoogle(); - state = state.copyWith(loading: false, isSignedIn: true); - getGoogleDriveMedias(); - } catch (error) { - state = state.copyWith(loading: false, error: error); - } - } - - Future getGoogleDriveMedias() async { - try { - _backupFolderId ??= await _googleDriveService.getBackupFolderId(); - if (_backupFolderId != null) { - state = state.copyWith(loading: true, error: null); - final medias = await _googleDriveService.getDriveMedias( - backUpFolderId: _backupFolderId!); - state = state.copyWith(loading: false, medias: medias); - } - } catch (e) { - state = state.copyWith(error: e); - } - } -} - -@freezed -class GoogleDriveMediasViewState with _$GoogleDriveMediasViewState { - const factory GoogleDriveMediasViewState({ - @Default(false) bool loading, - @Default([]) List medias, - required bool isSignedIn, - Object? error, - }) = _GoogleDriveMediasViewState; -} diff --git a/app/lib/ui/flow/home/google_drive/google_drive_medias_screen_view_model.freezed.dart b/app/lib/ui/flow/home/google_drive/google_drive_medias_screen_view_model.freezed.dart deleted file mode 100644 index 8665e17..0000000 --- a/app/lib/ui/flow/home/google_drive/google_drive_medias_screen_view_model.freezed.dart +++ /dev/null @@ -1,206 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'google_drive_medias_screen_view_model.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -/// @nodoc -mixin _$GoogleDriveMediasViewState { - bool get loading => throw _privateConstructorUsedError; - List get medias => throw _privateConstructorUsedError; - bool get isSignedIn => throw _privateConstructorUsedError; - Object? get error => throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $GoogleDriveMediasViewStateCopyWith - get copyWith => throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $GoogleDriveMediasViewStateCopyWith<$Res> { - factory $GoogleDriveMediasViewStateCopyWith(GoogleDriveMediasViewState value, - $Res Function(GoogleDriveMediasViewState) then) = - _$GoogleDriveMediasViewStateCopyWithImpl<$Res, - GoogleDriveMediasViewState>; - @useResult - $Res call( - {bool loading, List medias, bool isSignedIn, Object? error}); -} - -/// @nodoc -class _$GoogleDriveMediasViewStateCopyWithImpl<$Res, - $Val extends GoogleDriveMediasViewState> - implements $GoogleDriveMediasViewStateCopyWith<$Res> { - _$GoogleDriveMediasViewStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? loading = null, - Object? medias = null, - Object? isSignedIn = null, - Object? error = freezed, - }) { - return _then(_value.copyWith( - loading: null == loading - ? _value.loading - : loading // ignore: cast_nullable_to_non_nullable - as bool, - medias: null == medias - ? _value.medias - : medias // ignore: cast_nullable_to_non_nullable - as List, - isSignedIn: null == isSignedIn - ? _value.isSignedIn - : isSignedIn // ignore: cast_nullable_to_non_nullable - as bool, - error: freezed == error ? _value.error : error, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$GoogleDriveMediasViewStateImplCopyWith<$Res> - implements $GoogleDriveMediasViewStateCopyWith<$Res> { - factory _$$GoogleDriveMediasViewStateImplCopyWith( - _$GoogleDriveMediasViewStateImpl value, - $Res Function(_$GoogleDriveMediasViewStateImpl) then) = - __$$GoogleDriveMediasViewStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {bool loading, List medias, bool isSignedIn, Object? error}); -} - -/// @nodoc -class __$$GoogleDriveMediasViewStateImplCopyWithImpl<$Res> - extends _$GoogleDriveMediasViewStateCopyWithImpl<$Res, - _$GoogleDriveMediasViewStateImpl> - implements _$$GoogleDriveMediasViewStateImplCopyWith<$Res> { - __$$GoogleDriveMediasViewStateImplCopyWithImpl( - _$GoogleDriveMediasViewStateImpl _value, - $Res Function(_$GoogleDriveMediasViewStateImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? loading = null, - Object? medias = null, - Object? isSignedIn = null, - Object? error = freezed, - }) { - return _then(_$GoogleDriveMediasViewStateImpl( - loading: null == loading - ? _value.loading - : loading // ignore: cast_nullable_to_non_nullable - as bool, - medias: null == medias - ? _value._medias - : medias // ignore: cast_nullable_to_non_nullable - as List, - isSignedIn: null == isSignedIn - ? _value.isSignedIn - : isSignedIn // ignore: cast_nullable_to_non_nullable - as bool, - error: freezed == error ? _value.error : error, - )); - } -} - -/// @nodoc - -class _$GoogleDriveMediasViewStateImpl implements _GoogleDriveMediasViewState { - const _$GoogleDriveMediasViewStateImpl( - {this.loading = false, - final List medias = const [], - required this.isSignedIn, - this.error}) - : _medias = medias; - - @override - @JsonKey() - final bool loading; - final List _medias; - @override - @JsonKey() - List get medias { - if (_medias is EqualUnmodifiableListView) return _medias; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_medias); - } - - @override - final bool isSignedIn; - @override - final Object? error; - - @override - String toString() { - return 'GoogleDriveMediasViewState(loading: $loading, medias: $medias, isSignedIn: $isSignedIn, error: $error)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$GoogleDriveMediasViewStateImpl && - (identical(other.loading, loading) || other.loading == loading) && - const DeepCollectionEquality().equals(other._medias, _medias) && - (identical(other.isSignedIn, isSignedIn) || - other.isSignedIn == isSignedIn) && - const DeepCollectionEquality().equals(other.error, error)); - } - - @override - int get hashCode => Object.hash( - runtimeType, - loading, - const DeepCollectionEquality().hash(_medias), - isSignedIn, - const DeepCollectionEquality().hash(error)); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$GoogleDriveMediasViewStateImplCopyWith<_$GoogleDriveMediasViewStateImpl> - get copyWith => __$$GoogleDriveMediasViewStateImplCopyWithImpl< - _$GoogleDriveMediasViewStateImpl>(this, _$identity); -} - -abstract class _GoogleDriveMediasViewState - implements GoogleDriveMediasViewState { - const factory _GoogleDriveMediasViewState( - {final bool loading, - final List medias, - required final bool isSignedIn, - final Object? error}) = _$GoogleDriveMediasViewStateImpl; - - @override - bool get loading; - @override - List get medias; - @override - bool get isSignedIn; - @override - Object? get error; - @override - @JsonKey(ignore: true) - _$$GoogleDriveMediasViewStateImplCopyWith<_$GoogleDriveMediasViewStateImpl> - get copyWith => throw _privateConstructorUsedError; -} diff --git a/app/lib/ui/flow/home/home_screen.dart b/app/lib/ui/flow/home/home_screen.dart index 5d84170..8f062c1 100644 --- a/app/lib/ui/flow/home/home_screen.dart +++ b/app/lib/ui/flow/home/home_screen.dart @@ -1,15 +1,18 @@ import 'package:cloud_gallery/components/app_page.dart'; import 'package:cloud_gallery/domain/extensions/context_extensions.dart'; -import 'package:cloud_gallery/ui/flow/home/components/screen_source_segment_control.dart'; +import 'package:cloud_gallery/ui/flow/home/components/no_local_medias_access_screen.dart'; import 'package:cloud_gallery/ui/flow/home/home_screen_view_model.dart'; -import 'package:cloud_gallery/ui/flow/home/local/local_medias_screen.dart'; +import 'package:data/models/media/media.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:style/extensions/context_extensions.dart'; +import 'package:style/indicators/circular_progress_indicator.dart'; +import '../../../components/snack_bar.dart'; import '../../../domain/assets/assets_paths.dart'; import '../../navigation/app_router.dart'; -import 'google_drive/google_drive_medias_screen.dart'; +import 'components/app_media_item.dart'; +import 'components/multi_selection_done_button.dart'; class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key}); @@ -20,7 +23,7 @@ class HomeScreen extends ConsumerStatefulWidget { class _HomeScreenState extends ConsumerState { late HomeViewStateNotifier notifier; - final _pageController = PageController(); + final _scrollController = ScrollController(); @override void initState() { @@ -30,23 +33,23 @@ class _HomeScreenState extends ConsumerState { @override void dispose() { - _pageController.dispose(); + _scrollController.dispose(); super.dispose(); } - void _updatePageOnChangeSource() { - ref.listen(homeViewStateNotifier.select((value) => value.sourcePage), + void _errorObserver() { + ref.listen(homeViewStateNotifier.select((value) => value.error), (previous, next) { - if (!next.viewChangedByScroll) { - _pageController.animateToPage(next.sourcePage.index, - duration: const Duration(milliseconds: 300), curve: Curves.easeInOut); + if (next != null) { + showErrorSnackBar(context: context, error: next); } }); } @override Widget build(BuildContext context) { - _updatePageOnChangeSource(); + _errorObserver(); + return AppPage( titleWidget: _titleWidget(context: context), actions: [ @@ -65,37 +68,102 @@ class _HomeScreenState extends ConsumerState { ), ), ], - body: Column( - children: [ - const ScreenSourceSegmentControl(), - Expanded( - child: PageView( - restorationId: 'home_page_view', - onPageChanged: (value) { - notifier.updateMediaSource( - isChangedByScroll: true, source: MediaSource.values[value]); - }, - controller: _pageController, - children: const [ - LocalMediasScreen(), - GoogleDriveMediasScreen(), - ], - ), + body: _body(context: context), + ); + } + + Widget _body({required BuildContext context}) { + + //States + final medias = + ref.watch(homeViewStateNotifier.select((state) => state.medias)); + final isLoading = + ref.watch(homeViewStateNotifier.select((state) => state.loading)); + + final selectedMedias = ref + .watch(homeViewStateNotifier.select((state) => state.selectedMedias)); + + final uploadingMedias = ref + .watch(homeViewStateNotifier.select((state) => state.uploadingMedias)); + + final hasLocalMediaAccess = ref.watch( + homeViewStateNotifier.select((state) => state.hasLocalMediaAccess)); + + //View + if (isLoading) { + return const Center(child: AppCircularProgressIndicator()); + } else if (medias.isEmpty && !hasLocalMediaAccess) { + return const NoLocalMediasAccessScreen(); + } + return Stack( + alignment: Alignment.bottomRight, + children: [ + _buildMediaList( + context: context, + medias: medias, + uploadingMedias: uploadingMedias, + selectedMedias: selectedMedias, + ), + if (selectedMedias.isNotEmpty) + Padding( + padding: context.systemPadding + const EdgeInsets.all(16), + child: const MultiSelectionDoneButton(), ), - ], + ], + ); + } + + Widget _buildMediaList( + {required BuildContext context, + required List medias, + required List uploadingMedias, + required List selectedMedias}) { + return Scrollbar( + interactive: true, + thickness: 4, + + controller: _scrollController, + child: GridView.builder( + controller: _scrollController, + padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 16), + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 4, + mainAxisSpacing: 4, + ), + itemCount: medias.length, + itemBuilder: (context, index) { + final media = medias[index]; + return AppMediaItem( + key: ValueKey(media.id), + onTap: () { + if (selectedMedias.isNotEmpty) { + notifier.mediaSelection(media); + } + }, + onLongTap: () { + notifier.mediaSelection(media); + }, + isSelected: selectedMedias.contains(media.id), + isUploading: uploadingMedias.contains(media.id), + media: media, + ); + }, ), ); } - Widget _titleWidget({required BuildContext context}) => Row( - children: [ - const SizedBox(width: 16), - Image.asset( - Assets.images.appIcon, - width: 28, - ), - const SizedBox(width: 10), - Text(context.l10n.app_name) - ], - ); + Widget _titleWidget({required BuildContext context}) { + return Row( + children: [ + const SizedBox(width: 16), + Image.asset( + Assets.images.appIcon, + width: 28, + ), + const SizedBox(width: 10), + Text(context.l10n.app_name) + ], + ); + } } diff --git a/app/lib/ui/flow/home/home_screen_view_model.dart b/app/lib/ui/flow/home/home_screen_view_model.dart index c564e33..f0ec385 100644 --- a/app/lib/ui/flow/home/home_screen_view_model.dart +++ b/app/lib/ui/flow/home/home_screen_view_model.dart @@ -1,40 +1,177 @@ +import 'dart:async'; + +import 'package:data/models/media/media.dart'; +import 'package:data/services/auth_service.dart'; +import 'package:data/services/google_drive_service.dart'; +import 'package:data/services/local_media_service.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:style/extensions/list_extensions.dart'; part 'home_screen_view_model.freezed.dart'; final homeViewStateNotifier = StateNotifierProvider.autoDispose( - (ref) => HomeViewStateNotifier(), + (ref) => HomeViewStateNotifier( + ref.read(localMediaServiceProvider), + ref.read(googleDriveServiceProvider), + ref.read(authServiceProvider), + ), ); -enum MediaSource { local, googleDrive } - class HomeViewStateNotifier extends StateNotifier { - HomeViewStateNotifier() : super(const HomeViewState()); + final GoogleDriveService _googleDriveService; + final AuthService _authService; + final LocalMediaService _localMediaService; + + String? _backUpFolderId; + List _localMedias = []; + int _localMediaCount = 0; + List _googleDriveMedias = []; + + HomeViewStateNotifier( + this._localMediaService, this._googleDriveService, this._authService) + : super(const HomeViewState()) { + loadMedias(); + } + + Future loadMedias() async { + state = state.copyWith(loading: state.medias.isEmpty, error: null); + try { + final counts = await _loadMediaCount(); + if (counts != null) { + await Future.wait([ + _getGoogleDriveMedias(), + _loadLocalMedias(), + ]); + } else { + await _getGoogleDriveMedias(); + } + state = state.copyWith( + loading: false, + medias: _sortMedia(), + hasLocalMediaAccess: counts != null, + ); + } catch (error) { + state = state.copyWith(loading: false, error: error); + } + } + + List _sortMedia() { + final commonMedias = []; + + for (AppMedia localMedia in _localMedias.toList()) { + _googleDriveMedias + .toList() + .where((element) => element.path == localMedia.path) + .forEach((googleDriveMedia) { + _googleDriveMedias + .removeWhere((media) => media.id == googleDriveMedia.id); + _localMedias.removeWhere((media) => media.id == localMedia.id); + commonMedias.add(localMedia.copyWith( + sources: [AppMediaSource.local, AppMediaSource.googleDrive], + thumbnailPath: googleDriveMedia.thumbnailPath)); + }); + } + + List shortedMedia = [ + ..._localMedias, + ..._googleDriveMedias, + ...commonMedias + ]; + + shortedMedia.sort((a, b) => (b.createdTime ?? DateTime.now()) + .compareTo(a.createdTime ?? DateTime.now())); + return shortedMedia; + } + + Future _getGoogleDriveMedias() async { + if(_authService.hasUserSigned){ + _backUpFolderId ??= await _googleDriveService.getBackupFolderId(); + _googleDriveMedias = await _googleDriveService.getDriveMedias( + backUpFolderId: _backUpFolderId!); + } + } + + Future _loadMediaCount() async { + final hasAccess = await _localMediaService.requestPermission(); + if (hasAccess) { + _localMediaCount = await _localMediaService.getMediaCount(); + return _localMediaCount; + } + return null; + } - Future updateMediaSource( - {required MediaSource source, required bool isChangedByScroll}) async { - state = state.copyWith( - sourcePage: SourcePage( - sourcePage: source, viewChangedByScroll: isChangedByScroll)); + Future _loadLocalMedias() async { + _localMedias = await _localMediaService.getLocalMedia( + start: 0, + end: _localMediaCount, + ); + } + + void mediaSelection(AppMedia media) { + final selectedMedias = state.selectedMedias.toList(); + if (selectedMedias.contains(media.id)) { + state = state.copyWith( + selectedMedias: selectedMedias.toList()..remove(media.id), + error: null, + ); + } else { + state = state.copyWith( + selectedMedias: [...selectedMedias, media.id], + error: null, + ); + } + } + + Future uploadMediaOnGoogleDrive() async { + try { + if (!_authService.hasUserSigned) { + await _authService.signInWithGoogle(); + } + List uploadingMedias = state.medias + .where((element) => + state.selectedMedias.contains(element.id) && + !element.sources.contains(AppMediaSource.googleDrive)) + .toList(); + + state = state.copyWith( + uploadingMedias: uploadingMedias.map((e) => e.id).toList(), + error: null); + final folderId = await _googleDriveService.getBackupFolderId(); + + for (final media in uploadingMedias) { + await _googleDriveService.uploadInGoogleDrive( + media: media, + folderID: folderId!, + ); + state = state.copyWith( + medias: state.medias.toList() + ..updateElement( + newElement: media.copyWith( + sources: media.sources.toList() + ..add(AppMediaSource.googleDrive)), + oldElement: media, + ), + uploadingMedias: state.uploadingMedias.toList()..remove(media.id), + ); + } + + state = state.copyWith(uploadingMedias: [], selectedMedias: []); + } catch (error) { + state = state.copyWith(error: error, uploadingMedias: []); + } } } @freezed class HomeViewState with _$HomeViewState { const factory HomeViewState({ - @Default(SourcePage()) SourcePage sourcePage, - @Default(false) bool isLastViewChangedByScroll, + Object? error, + @Default(false) bool hasLocalMediaAccess, + @Default(false) bool loading, + @Default([]) List medias, + @Default([]) List selectedMedias, + @Default([]) List uploadingMedias, }) = _HomeViewState; } - -class SourcePage { - final MediaSource sourcePage; - final bool viewChangedByScroll; - - const SourcePage({ - this.sourcePage = MediaSource.local, - this.viewChangedByScroll = false, - }); -} diff --git a/app/lib/ui/flow/home/home_screen_view_model.freezed.dart b/app/lib/ui/flow/home/home_screen_view_model.freezed.dart index 3076101..c3b30ed 100644 --- a/app/lib/ui/flow/home/home_screen_view_model.freezed.dart +++ b/app/lib/ui/flow/home/home_screen_view_model.freezed.dart @@ -16,8 +16,12 @@ final _privateConstructorUsedError = UnsupportedError( /// @nodoc mixin _$HomeViewState { - SourcePage get sourcePage => throw _privateConstructorUsedError; - bool get isLastViewChangedByScroll => throw _privateConstructorUsedError; + Object? get error => throw _privateConstructorUsedError; + bool get hasLocalMediaAccess => throw _privateConstructorUsedError; + bool get loading => throw _privateConstructorUsedError; + List get medias => throw _privateConstructorUsedError; + List get selectedMedias => throw _privateConstructorUsedError; + List get uploadingMedias => throw _privateConstructorUsedError; @JsonKey(ignore: true) $HomeViewStateCopyWith get copyWith => @@ -30,7 +34,13 @@ abstract class $HomeViewStateCopyWith<$Res> { HomeViewState value, $Res Function(HomeViewState) then) = _$HomeViewStateCopyWithImpl<$Res, HomeViewState>; @useResult - $Res call({SourcePage sourcePage, bool isLastViewChangedByScroll}); + $Res call( + {Object? error, + bool hasLocalMediaAccess, + bool loading, + List medias, + List selectedMedias, + List uploadingMedias}); } /// @nodoc @@ -46,18 +56,35 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> @pragma('vm:prefer-inline') @override $Res call({ - Object? sourcePage = null, - Object? isLastViewChangedByScroll = null, + Object? error = freezed, + Object? hasLocalMediaAccess = null, + Object? loading = null, + Object? medias = null, + Object? selectedMedias = null, + Object? uploadingMedias = null, }) { return _then(_value.copyWith( - sourcePage: null == sourcePage - ? _value.sourcePage - : sourcePage // ignore: cast_nullable_to_non_nullable - as SourcePage, - isLastViewChangedByScroll: null == isLastViewChangedByScroll - ? _value.isLastViewChangedByScroll - : isLastViewChangedByScroll // ignore: cast_nullable_to_non_nullable + error: freezed == error ? _value.error : error, + hasLocalMediaAccess: null == hasLocalMediaAccess + ? _value.hasLocalMediaAccess + : hasLocalMediaAccess // ignore: cast_nullable_to_non_nullable as bool, + loading: null == loading + ? _value.loading + : loading // ignore: cast_nullable_to_non_nullable + as bool, + medias: null == medias + ? _value.medias + : medias // ignore: cast_nullable_to_non_nullable + as List, + selectedMedias: null == selectedMedias + ? _value.selectedMedias + : selectedMedias // ignore: cast_nullable_to_non_nullable + as List, + uploadingMedias: null == uploadingMedias + ? _value.uploadingMedias + : uploadingMedias // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } } @@ -70,7 +97,13 @@ abstract class _$$HomeViewStateImplCopyWith<$Res> __$$HomeViewStateImplCopyWithImpl<$Res>; @override @useResult - $Res call({SourcePage sourcePage, bool isLastViewChangedByScroll}); + $Res call( + {Object? error, + bool hasLocalMediaAccess, + bool loading, + List medias, + List selectedMedias, + List uploadingMedias}); } /// @nodoc @@ -84,18 +117,35 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> @pragma('vm:prefer-inline') @override $Res call({ - Object? sourcePage = null, - Object? isLastViewChangedByScroll = null, + Object? error = freezed, + Object? hasLocalMediaAccess = null, + Object? loading = null, + Object? medias = null, + Object? selectedMedias = null, + Object? uploadingMedias = null, }) { return _then(_$HomeViewStateImpl( - sourcePage: null == sourcePage - ? _value.sourcePage - : sourcePage // ignore: cast_nullable_to_non_nullable - as SourcePage, - isLastViewChangedByScroll: null == isLastViewChangedByScroll - ? _value.isLastViewChangedByScroll - : isLastViewChangedByScroll // ignore: cast_nullable_to_non_nullable + error: freezed == error ? _value.error : error, + hasLocalMediaAccess: null == hasLocalMediaAccess + ? _value.hasLocalMediaAccess + : hasLocalMediaAccess // ignore: cast_nullable_to_non_nullable + as bool, + loading: null == loading + ? _value.loading + : loading // ignore: cast_nullable_to_non_nullable as bool, + medias: null == medias + ? _value._medias + : medias // ignore: cast_nullable_to_non_nullable + as List, + selectedMedias: null == selectedMedias + ? _value._selectedMedias + : selectedMedias // ignore: cast_nullable_to_non_nullable + as List, + uploadingMedias: null == uploadingMedias + ? _value._uploadingMedias + : uploadingMedias // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -104,19 +154,54 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> class _$HomeViewStateImpl implements _HomeViewState { const _$HomeViewStateImpl( - {this.sourcePage = const SourcePage(), - this.isLastViewChangedByScroll = false}); + {this.error, + this.hasLocalMediaAccess = false, + this.loading = false, + final List medias = const [], + final List selectedMedias = const [], + final List uploadingMedias = const []}) + : _medias = medias, + _selectedMedias = selectedMedias, + _uploadingMedias = uploadingMedias; + + @override + final Object? error; + @override + @JsonKey() + final bool hasLocalMediaAccess; + @override + @JsonKey() + final bool loading; + final List _medias; + @override + @JsonKey() + List get medias { + if (_medias is EqualUnmodifiableListView) return _medias; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_medias); + } + final List _selectedMedias; @override @JsonKey() - final SourcePage sourcePage; + List get selectedMedias { + if (_selectedMedias is EqualUnmodifiableListView) return _selectedMedias; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_selectedMedias); + } + + final List _uploadingMedias; @override @JsonKey() - final bool isLastViewChangedByScroll; + List get uploadingMedias { + if (_uploadingMedias is EqualUnmodifiableListView) return _uploadingMedias; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_uploadingMedias); + } @override String toString() { - return 'HomeViewState(sourcePage: $sourcePage, isLastViewChangedByScroll: $isLastViewChangedByScroll)'; + return 'HomeViewState(error: $error, hasLocalMediaAccess: $hasLocalMediaAccess, loading: $loading, medias: $medias, selectedMedias: $selectedMedias, uploadingMedias: $uploadingMedias)'; } @override @@ -124,16 +209,26 @@ class _$HomeViewStateImpl implements _HomeViewState { return identical(this, other) || (other.runtimeType == runtimeType && other is _$HomeViewStateImpl && - (identical(other.sourcePage, sourcePage) || - other.sourcePage == sourcePage) && - (identical(other.isLastViewChangedByScroll, - isLastViewChangedByScroll) || - other.isLastViewChangedByScroll == isLastViewChangedByScroll)); + const DeepCollectionEquality().equals(other.error, error) && + (identical(other.hasLocalMediaAccess, hasLocalMediaAccess) || + other.hasLocalMediaAccess == hasLocalMediaAccess) && + (identical(other.loading, loading) || other.loading == loading) && + const DeepCollectionEquality().equals(other._medias, _medias) && + const DeepCollectionEquality() + .equals(other._selectedMedias, _selectedMedias) && + const DeepCollectionEquality() + .equals(other._uploadingMedias, _uploadingMedias)); } @override - int get hashCode => - Object.hash(runtimeType, sourcePage, isLastViewChangedByScroll); + int get hashCode => Object.hash( + runtimeType, + const DeepCollectionEquality().hash(error), + hasLocalMediaAccess, + loading, + const DeepCollectionEquality().hash(_medias), + const DeepCollectionEquality().hash(_selectedMedias), + const DeepCollectionEquality().hash(_uploadingMedias)); @JsonKey(ignore: true) @override @@ -144,13 +239,25 @@ class _$HomeViewStateImpl implements _HomeViewState { abstract class _HomeViewState implements HomeViewState { const factory _HomeViewState( - {final SourcePage sourcePage, - final bool isLastViewChangedByScroll}) = _$HomeViewStateImpl; + {final Object? error, + final bool hasLocalMediaAccess, + final bool loading, + final List medias, + final List selectedMedias, + final List uploadingMedias}) = _$HomeViewStateImpl; @override - SourcePage get sourcePage; + Object? get error; + @override + bool get hasLocalMediaAccess; + @override + bool get loading; + @override + List get medias; + @override + List get selectedMedias; @override - bool get isLastViewChangedByScroll; + List get uploadingMedias; @override @JsonKey(ignore: true) _$$HomeViewStateImplCopyWith<_$HomeViewStateImpl> get copyWith => diff --git a/app/lib/ui/flow/home/local/local_media_screen_view_model.dart b/app/lib/ui/flow/home/local/local_media_screen_view_model.dart deleted file mode 100644 index 4c7f04e..0000000 --- a/app/lib/ui/flow/home/local/local_media_screen_view_model.dart +++ /dev/null @@ -1,121 +0,0 @@ -import 'package:data/models/media/media.dart'; -import 'package:data/services/auth_service.dart'; -import 'package:data/services/google_drive_service.dart'; -import 'package:data/services/local_media_service.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:freezed_annotation/freezed_annotation.dart'; - -part 'local_media_screen_view_model.freezed.dart'; - -final localMediasViewStateNotifier = StateNotifierProvider.autoDispose< - LocalMediasViewStateNotifier, LocalMediasViewState>( - (ref) => LocalMediasViewStateNotifier( - ref.read(localMediaServiceProvider), - ref.read(googleDriveServiceProvider), - ref.read(authServiceProvider), - ), -); - -class LocalMediasViewStateNotifier extends StateNotifier { - final LocalMediaService _localMediaService; - final GoogleDriveService _googleDriveService; - final AuthService _authService; - - bool _loading = false; - - LocalMediasViewStateNotifier( - this._localMediaService, this._googleDriveService, this._authService) - : super(const LocalMediasViewState()); - - Future loadMediaCount() async { - try { - state = state.copyWith( - error: null, - loading: state.medias.isEmpty, - ); - final hasAccess = await _localMediaService.requestPermission(); - if (hasAccess) { - final count = await _localMediaService.getMediaCount(); - state = state.copyWith( - mediaCount: count, - hasLocalMediaAccess: hasAccess, - loading: false, - ); - } else { - state = state.copyWith(hasLocalMediaAccess: hasAccess, loading: false); - } - } catch (error) { - state = state.copyWith(error: error, loading: false); - } - } - - Future loadMedia({bool append = false}) async { - if (_loading == true) return; - _loading = true; - try { - state = state.copyWith(loading: state.medias.isEmpty, error: null); - final medias = await _localMediaService.getMedia( - start: append ? state.medias.length : 0, - end: append - ? state.medias.length + 20 - : state.medias.length < 20 - ? 20 - : state.medias.length, - ); - state = state.copyWith( - medias: [...state.medias, ...medias], - loading: false, - ); - } catch (error) { - state = state.copyWith(error: error, loading: false); - } - _loading = false; - } - - void mediaSelection(AppMedia media) { - final selectedMedias = state.selectedMedias; - if (selectedMedias.contains(media)) { - state = state.copyWith( - selectedMedias: selectedMedias.toList()..remove(media), - error: null, - ); - } else { - state = state.copyWith( - selectedMedias: [...selectedMedias, media], - error: null, - ); - } - } - - Future uploadMediaOnGoogleDrive() async { - try { - if (!_authService.hasUserSigned) { - await _authService.signInWithGoogle(); - } - state = - state.copyWith(uploadingMedias: state.selectedMedias, error: null); - final folderId = await _googleDriveService.getBackupFolderId(); - - for (final media in state.selectedMedias) { - await _googleDriveService.uploadInGoogleDrive( - media: media, folderID: folderId!); - } - state = state.copyWith(uploadingMedias: [], selectedMedias: []); - } catch (error) { - state = state.copyWith(error: error, uploadingMedias: []); - } - } -} - -@freezed -class LocalMediasViewState with _$LocalMediasViewState { - const factory LocalMediasViewState({ - @Default(false) bool loading, - @Default([]) List uploadingMedias, - @Default([]) List medias, - @Default([]) List selectedMedias, - @Default(0) int mediaCount, - @Default(false) hasLocalMediaAccess, - Object? error, - }) = _LocalMediasViewState; -} diff --git a/app/lib/ui/flow/home/local/local_media_screen_view_model.freezed.dart b/app/lib/ui/flow/home/local/local_media_screen_view_model.freezed.dart deleted file mode 100644 index e139c63..0000000 --- a/app/lib/ui/flow/home/local/local_media_screen_view_model.freezed.dart +++ /dev/null @@ -1,291 +0,0 @@ -// coverage:ignore-file -// GENERATED CODE - DO NOT MODIFY BY HAND -// ignore_for_file: type=lint -// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark - -part of 'local_media_screen_view_model.dart'; - -// ************************************************************************** -// FreezedGenerator -// ************************************************************************** - -T _$identity(T value) => value; - -final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); - -/// @nodoc -mixin _$LocalMediasViewState { - bool get loading => throw _privateConstructorUsedError; - List get uploadingMedias => throw _privateConstructorUsedError; - List get medias => throw _privateConstructorUsedError; - List get selectedMedias => throw _privateConstructorUsedError; - int get mediaCount => throw _privateConstructorUsedError; - dynamic get hasLocalMediaAccess => throw _privateConstructorUsedError; - Object? get error => throw _privateConstructorUsedError; - - @JsonKey(ignore: true) - $LocalMediasViewStateCopyWith get copyWith => - throw _privateConstructorUsedError; -} - -/// @nodoc -abstract class $LocalMediasViewStateCopyWith<$Res> { - factory $LocalMediasViewStateCopyWith(LocalMediasViewState value, - $Res Function(LocalMediasViewState) then) = - _$LocalMediasViewStateCopyWithImpl<$Res, LocalMediasViewState>; - @useResult - $Res call( - {bool loading, - List uploadingMedias, - List medias, - List selectedMedias, - int mediaCount, - dynamic hasLocalMediaAccess, - Object? error}); -} - -/// @nodoc -class _$LocalMediasViewStateCopyWithImpl<$Res, - $Val extends LocalMediasViewState> - implements $LocalMediasViewStateCopyWith<$Res> { - _$LocalMediasViewStateCopyWithImpl(this._value, this._then); - - // ignore: unused_field - final $Val _value; - // ignore: unused_field - final $Res Function($Val) _then; - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? loading = null, - Object? uploadingMedias = null, - Object? medias = null, - Object? selectedMedias = null, - Object? mediaCount = null, - Object? hasLocalMediaAccess = freezed, - Object? error = freezed, - }) { - return _then(_value.copyWith( - loading: null == loading - ? _value.loading - : loading // ignore: cast_nullable_to_non_nullable - as bool, - uploadingMedias: null == uploadingMedias - ? _value.uploadingMedias - : uploadingMedias // ignore: cast_nullable_to_non_nullable - as List, - medias: null == medias - ? _value.medias - : medias // ignore: cast_nullable_to_non_nullable - as List, - selectedMedias: null == selectedMedias - ? _value.selectedMedias - : selectedMedias // ignore: cast_nullable_to_non_nullable - as List, - mediaCount: null == mediaCount - ? _value.mediaCount - : mediaCount // ignore: cast_nullable_to_non_nullable - as int, - hasLocalMediaAccess: freezed == hasLocalMediaAccess - ? _value.hasLocalMediaAccess - : hasLocalMediaAccess // ignore: cast_nullable_to_non_nullable - as dynamic, - error: freezed == error ? _value.error : error, - ) as $Val); - } -} - -/// @nodoc -abstract class _$$LocalMediasViewStateImplCopyWith<$Res> - implements $LocalMediasViewStateCopyWith<$Res> { - factory _$$LocalMediasViewStateImplCopyWith(_$LocalMediasViewStateImpl value, - $Res Function(_$LocalMediasViewStateImpl) then) = - __$$LocalMediasViewStateImplCopyWithImpl<$Res>; - @override - @useResult - $Res call( - {bool loading, - List uploadingMedias, - List medias, - List selectedMedias, - int mediaCount, - dynamic hasLocalMediaAccess, - Object? error}); -} - -/// @nodoc -class __$$LocalMediasViewStateImplCopyWithImpl<$Res> - extends _$LocalMediasViewStateCopyWithImpl<$Res, _$LocalMediasViewStateImpl> - implements _$$LocalMediasViewStateImplCopyWith<$Res> { - __$$LocalMediasViewStateImplCopyWithImpl(_$LocalMediasViewStateImpl _value, - $Res Function(_$LocalMediasViewStateImpl) _then) - : super(_value, _then); - - @pragma('vm:prefer-inline') - @override - $Res call({ - Object? loading = null, - Object? uploadingMedias = null, - Object? medias = null, - Object? selectedMedias = null, - Object? mediaCount = null, - Object? hasLocalMediaAccess = freezed, - Object? error = freezed, - }) { - return _then(_$LocalMediasViewStateImpl( - loading: null == loading - ? _value.loading - : loading // ignore: cast_nullable_to_non_nullable - as bool, - uploadingMedias: null == uploadingMedias - ? _value._uploadingMedias - : uploadingMedias // ignore: cast_nullable_to_non_nullable - as List, - medias: null == medias - ? _value._medias - : medias // ignore: cast_nullable_to_non_nullable - as List, - selectedMedias: null == selectedMedias - ? _value._selectedMedias - : selectedMedias // ignore: cast_nullable_to_non_nullable - as List, - mediaCount: null == mediaCount - ? _value.mediaCount - : mediaCount // ignore: cast_nullable_to_non_nullable - as int, - hasLocalMediaAccess: freezed == hasLocalMediaAccess - ? _value.hasLocalMediaAccess! - : hasLocalMediaAccess, - error: freezed == error ? _value.error : error, - )); - } -} - -/// @nodoc - -class _$LocalMediasViewStateImpl implements _LocalMediasViewState { - const _$LocalMediasViewStateImpl( - {this.loading = false, - final List uploadingMedias = const [], - final List medias = const [], - final List selectedMedias = const [], - this.mediaCount = 0, - this.hasLocalMediaAccess = false, - this.error}) - : _uploadingMedias = uploadingMedias, - _medias = medias, - _selectedMedias = selectedMedias; - - @override - @JsonKey() - final bool loading; - final List _uploadingMedias; - @override - @JsonKey() - List get uploadingMedias { - if (_uploadingMedias is EqualUnmodifiableListView) return _uploadingMedias; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_uploadingMedias); - } - - final List _medias; - @override - @JsonKey() - List get medias { - if (_medias is EqualUnmodifiableListView) return _medias; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_medias); - } - - final List _selectedMedias; - @override - @JsonKey() - List get selectedMedias { - if (_selectedMedias is EqualUnmodifiableListView) return _selectedMedias; - // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_selectedMedias); - } - - @override - @JsonKey() - final int mediaCount; - @override - @JsonKey() - final dynamic hasLocalMediaAccess; - @override - final Object? error; - - @override - String toString() { - return 'LocalMediasViewState(loading: $loading, uploadingMedias: $uploadingMedias, medias: $medias, selectedMedias: $selectedMedias, mediaCount: $mediaCount, hasLocalMediaAccess: $hasLocalMediaAccess, error: $error)'; - } - - @override - bool operator ==(Object other) { - return identical(this, other) || - (other.runtimeType == runtimeType && - other is _$LocalMediasViewStateImpl && - (identical(other.loading, loading) || other.loading == loading) && - const DeepCollectionEquality() - .equals(other._uploadingMedias, _uploadingMedias) && - const DeepCollectionEquality().equals(other._medias, _medias) && - const DeepCollectionEquality() - .equals(other._selectedMedias, _selectedMedias) && - (identical(other.mediaCount, mediaCount) || - other.mediaCount == mediaCount) && - const DeepCollectionEquality() - .equals(other.hasLocalMediaAccess, hasLocalMediaAccess) && - const DeepCollectionEquality().equals(other.error, error)); - } - - @override - int get hashCode => Object.hash( - runtimeType, - loading, - const DeepCollectionEquality().hash(_uploadingMedias), - const DeepCollectionEquality().hash(_medias), - const DeepCollectionEquality().hash(_selectedMedias), - mediaCount, - const DeepCollectionEquality().hash(hasLocalMediaAccess), - const DeepCollectionEquality().hash(error)); - - @JsonKey(ignore: true) - @override - @pragma('vm:prefer-inline') - _$$LocalMediasViewStateImplCopyWith<_$LocalMediasViewStateImpl> - get copyWith => - __$$LocalMediasViewStateImplCopyWithImpl<_$LocalMediasViewStateImpl>( - this, _$identity); -} - -abstract class _LocalMediasViewState implements LocalMediasViewState { - const factory _LocalMediasViewState( - {final bool loading, - final List uploadingMedias, - final List medias, - final List selectedMedias, - final int mediaCount, - final dynamic hasLocalMediaAccess, - final Object? error}) = _$LocalMediasViewStateImpl; - - @override - bool get loading; - @override - List get uploadingMedias; - @override - List get medias; - @override - List get selectedMedias; - @override - int get mediaCount; - @override - dynamic get hasLocalMediaAccess; - @override - Object? get error; - @override - @JsonKey(ignore: true) - _$$LocalMediasViewStateImplCopyWith<_$LocalMediasViewStateImpl> - get copyWith => throw _privateConstructorUsedError; -} diff --git a/app/lib/ui/flow/home/local/local_medias_screen.dart b/app/lib/ui/flow/home/local/local_medias_screen.dart deleted file mode 100644 index 9638564..0000000 --- a/app/lib/ui/flow/home/local/local_medias_screen.dart +++ /dev/null @@ -1,136 +0,0 @@ -import 'dart:io'; -import 'package:cloud_gallery/ui/flow/home/local/components/multi_selection_done_button.dart'; -import 'package:cloud_gallery/ui/flow/home/local/components/no_local_medias_access_screen.dart'; -import 'package:cloud_gallery/ui/flow/home/local/local_media_screen_view_model.dart'; -import 'package:data/models/media/media.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_riverpod/flutter_riverpod.dart'; -import 'package:style/extensions/context_extensions.dart'; -import '../../../../components/snack_bar.dart'; -import '../../../../domain/extensions/widget_extensions.dart'; -import '../components/image_item.dart'; - -class LocalMediasScreen extends ConsumerStatefulWidget { - const LocalMediasScreen({super.key}); - - @override - ConsumerState createState() => _LocalSourceViewState(); -} - -class _LocalSourceViewState extends ConsumerState { - late LocalMediasViewStateNotifier notifier; - final _scrollController = ScrollController(); - - @override - void initState() { - super.initState(); - notifier = ref.read(localMediasViewStateNotifier.notifier); - runPostFrame(() async { - await notifier.loadMediaCount(); - await notifier.loadMedia(); - }); - } - - @override - void dispose() { - _scrollController.dispose(); - super.dispose(); - } - - void _observeError() { - ref.listen(localMediasViewStateNotifier.select((value) => value.error), - (previous, next) { - if (next != null) { - showErrorSnackBar(context: context, error: next); - } - }); - } - - @override - Widget build(BuildContext context) { - //Listeners - _observeError(); - - //States - final medias = - ref.watch(localMediasViewStateNotifier.select((state) => state.medias)); - - final selectedMedia = ref.watch( - localMediasViewStateNotifier.select((state) => state.selectedMedias)); - - final isLoading = ref - .watch(localMediasViewStateNotifier.select((state) => state.loading)); - - final mediaCounts = ref.watch( - localMediasViewStateNotifier.select((state) => state.mediaCount)); - - final hasAccess = ref.watch(localMediasViewStateNotifier - .select((state) => state.hasLocalMediaAccess)); - - //View - if (!hasAccess) { - return const NoLocalMediasAccessScreen(); - } else if (isLoading && medias.isEmpty) { - return const Center(child: CircularProgressIndicator.adaptive()); - } - return Stack( - alignment: Alignment.bottomRight, - children: [ - Scrollbar( - controller: _scrollController, - child: GridView.builder( - controller: _scrollController, - padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 16), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 8, - mainAxisSpacing: 8, - ), - itemCount: mediaCounts, - itemBuilder: (context, index) { - if (index > medias.length - 6) { - runPostFrame(() { - notifier.loadMedia(append: true); - }); - } - if (index < medias.length) { - if (medias[index].type != AppMediaType.image) { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: context.colorScheme.outline, - ), - ); - } - return ImageItem( - onTap: () { - if (selectedMedia.isNotEmpty) { - notifier.mediaSelection(medias[index]); - } - }, - onLongTap: () { - notifier.mediaSelection(medias[index]); - }, - isSelected: selectedMedia.contains(medias[index]), - imageProvider: FileImage(File(medias[index].path)), - ); - } else { - return Container( - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(12), - color: context.colorScheme.primary, - ), - ); - } - }, - ), - ), - if (selectedMedia.isNotEmpty) - Padding( - padding: context.systemPadding + const EdgeInsets.all(16), - child: const MultiSelectionDoneButton(), - ), - ], - ); - } -} diff --git a/app/macos/Flutter/GeneratedPluginRegistrant.swift b/app/macos/Flutter/GeneratedPluginRegistrant.swift index 78f1229..0136f73 100644 --- a/app/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/app/macos/Flutter/GeneratedPluginRegistrant.swift @@ -8,13 +8,19 @@ import Foundation import firebase_core import google_sign_in_ios import package_info_plus +import path_provider_foundation import photo_manager import shared_preferences_foundation +import sqflite +import video_player_avfoundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { FLTFirebaseCorePlugin.register(with: registry.registrar(forPlugin: "FLTFirebaseCorePlugin")) FLTGoogleSignInPlugin.register(with: registry.registrar(forPlugin: "FLTGoogleSignInPlugin")) FPPPackageInfoPlusPlugin.register(with: registry.registrar(forPlugin: "FPPPackageInfoPlusPlugin")) + PathProviderPlugin.register(with: registry.registrar(forPlugin: "PathProviderPlugin")) PhotoManagerPlugin.register(with: registry.registrar(forPlugin: "PhotoManagerPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) + SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin")) + FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin")) } diff --git a/app/pubspec.lock b/app/pubspec.lock index c1c2682..4b50096 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -121,6 +121,30 @@ packages: url: "https://pub.dev" source: hosted version: "8.8.1" + cached_network_image: + dependency: "direct main" + description: + name: cached_network_image + sha256: "28ea9690a8207179c319965c13cd8df184d5ee721ae2ce60f398ced1219cea1f" + url: "https://pub.dev" + source: hosted + version: "3.3.1" + cached_network_image_platform_interface: + dependency: transitive + description: + name: cached_network_image_platform_interface + sha256: "9e90e78ae72caa874a323d78fa6301b3fb8fa7ea76a8f96dc5b5bf79f283bf2f" + url: "https://pub.dev" + source: hosted + version: "4.0.0" + cached_network_image_web: + dependency: transitive + description: + name: cached_network_image_web + sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" + url: "https://pub.dev" + source: hosted + version: "1.1.1" characters: dependency: transitive description: @@ -170,7 +194,7 @@ packages: source: hosted version: "4.10.0" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a @@ -193,6 +217,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.0.3" + csslib: + dependency: transitive + description: + name: csslib + sha256: "706b5707578e0c1b4b7550f64078f0a0f19dec3f50a178ffae7006b0a9ca58fb" + url: "https://pub.dev" + source: hosted + version: "1.0.0" cupertino_icons: dependency: "direct main" description: @@ -309,6 +341,14 @@ packages: description: flutter source: sdk version: "0.0.0" + flutter_cache_manager: + dependency: transitive + description: + name: flutter_cache_manager + sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" + url: "https://pub.dev" + source: hosted + version: "3.3.1" flutter_lints: dependency: "direct dev" description: @@ -476,6 +516,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.1.0" + html: + dependency: transitive + description: + name: html + sha256: "3a7812d5bcd2894edf53dfaf8cd640876cf6cef50a8f238745c8b8120ea74d3a" + url: "https://pub.dev" + source: hosted + version: "0.15.4" http: dependency: transitive description: @@ -612,6 +660,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.4" + octo_image: + dependency: transitive + description: + name: octo_image + sha256: "45b40f99622f11901238e18d48f5f12ea36426d8eced9f4cbf58479c7aa2430d" + url: "https://pub.dev" + source: hosted + version: "2.0.0" package_config: dependency: transitive description: @@ -652,6 +708,30 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.1" + path_provider: + dependency: transitive + description: + name: path_provider + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + url: "https://pub.dev" + source: hosted + version: "2.1.2" + path_provider_android: + dependency: transitive + description: + name: path_provider_android + sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + url: "https://pub.dev" + source: hosted + version: "2.2.2" + path_provider_foundation: + dependency: transitive + description: + name: path_provider_foundation + sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + url: "https://pub.dev" + source: hosted + version: "2.3.2" path_provider_linux: dependency: transitive description: @@ -921,6 +1001,22 @@ packages: url: "https://pub.dev" source: hosted version: "7.0.0" + sqflite: + dependency: transitive + description: + name: sqflite + sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 + url: "https://pub.dev" + source: hosted + version: "2.3.2" + sqflite_common: + dependency: transitive + description: + name: sqflite_common + sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" + url: "https://pub.dev" + source: hosted + version: "2.5.3" stack_trace: dependency: transitive description: @@ -968,6 +1064,14 @@ packages: relative: true source: path version: "0.0.1" + synchronized: + dependency: transitive + description: + name: synchronized + sha256: "539ef412b170d65ecdafd780f924e5be3f60032a1128df156adad6c5b373d558" + url: "https://pub.dev" + source: hosted + version: "3.1.0+1" term_glyph: dependency: transitive description: @@ -1040,6 +1144,54 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.4" + video_player: + dependency: "direct main" + description: + name: video_player + sha256: fbf28ce8bcfe709ad91b5789166c832cb7a684d14f571a81891858fefb5bb1c2 + url: "https://pub.dev" + source: hosted + version: "2.8.2" + video_player_android: + dependency: transitive + description: + name: video_player_android + sha256: "4dd9b8b86d70d65eecf3dcabfcdfbb9c9115d244d022654aba49a00336d540c2" + url: "https://pub.dev" + source: hosted + version: "2.4.12" + video_player_avfoundation: + dependency: transitive + description: + name: video_player_avfoundation + sha256: "309e3962795e761be010869bae65c0b0e45b5230c5cee1bec72197ca7db040ed" + url: "https://pub.dev" + source: hosted + version: "2.5.6" + video_player_platform_interface: + dependency: transitive + description: + name: video_player_platform_interface + sha256: "236454725fafcacf98f0f39af0d7c7ab2ce84762e3b63f2cbb3ef9a7e0550bc6" + url: "https://pub.dev" + source: hosted + version: "6.2.2" + video_player_web: + dependency: transitive + description: + name: video_player_web + sha256: "8e9cb7fe94e49490e67bbc15149691792b58a0ade31b32e3f3688d104a0e057b" + url: "https://pub.dev" + source: hosted + version: "2.2.0" + visibility_detector: + dependency: "direct main" + description: + name: visibility_detector + sha256: dd5cc11e13494f432d15939c3aa8ae76844c42b723398643ce9addb88a5ed420 + url: "https://pub.dev" + source: hosted + version: "0.4.0+2" vm_service: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index e1dda92..ef63034 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -27,6 +27,9 @@ dependencies: cupertino_icons: ^1.0.2 flutter_svg: ^2.0.9 fluttertoast: ^8.2.4 + cached_network_image: ^3.3.1 + video_player: ^2.8.2 + visibility_detector: ^0.4.0+2 # state management flutter_riverpod: ^2.4.9 @@ -42,6 +45,7 @@ dependencies: # core firebase_core: ^2.24.2 + collection: ^1.18.0 # storage shared_preferences: ^2.2.2 diff --git a/data/.flutter-plugins-dependencies b/data/.flutter-plugins-dependencies index ab7759e..1776ed2 100644 --- a/data/.flutter-plugins-dependencies +++ b/data/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.5/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"google_sign_in_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_android-6.1.21/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_android-2.2.1/","native_build":true,"dependencies":[]}],"macos":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.5/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.3.2/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.3.2/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"google_sign_in_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_web-0.12.3+3/","dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_web-2.2.2/","dependencies":[]}]},"dependencyGraph":[{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"photo_manager","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2024-02-29 16:13:09.551104","version":"3.19.1"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.5/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"google_sign_in_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_android-6.1.21/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_android-2.2.1/","native_build":true,"dependencies":[]}],"macos":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.5/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.3.2/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.3.2/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"google_sign_in_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_web-0.12.3+3/","dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_web-2.2.2/","dependencies":[]}]},"dependencyGraph":[{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"photo_manager","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2024-03-01 11:09:45.230961","version":"3.19.1"} \ No newline at end of file diff --git a/data/lib/models/media/media.dart b/data/lib/models/media/media.dart index ebea721..865f80d 100644 --- a/data/lib/models/media/media.dart +++ b/data/lib/models/media/media.dart @@ -1,12 +1,75 @@ import 'package:freezed_annotation/freezed_annotation.dart'; +import 'package:googleapis/drive/v3.dart' as drive show File; +import 'package:photo_manager/photo_manager.dart' show AssetEntity; part 'media.freezed.dart'; part 'media.g.dart'; -enum AppMediaType { image, video } +enum AppMediaType { + image, + video, + other; -enum AppMediaOrientation { landscape, portrait } + bool get isImage => this == AppMediaType.image; + + bool get isVideo => this == AppMediaType.video; + + factory AppMediaType.getType({String? mimeType, required String? location}) { + if (mimeType != null) { + return AppMediaType.fromMimeType(mimeType: mimeType); + } else if (location != null) { + return AppMediaType.fromLocation(location: location); + } else { + return AppMediaType.other; + } + } + + factory AppMediaType.fromLocation({required String location}) { + location = location.toLowerCase(); + if (location.endsWith('.jpg') || + location.endsWith('.jpeg') || + location.endsWith('.png') || + location.endsWith('.gif') || + location.endsWith('.heic') || + location.endsWith('.webp')) { + return AppMediaType.image; + } else if (location.endsWith('.mp4') || + location.endsWith('.3gp') || + location.endsWith('.mkv') || + location.endsWith('.avi') || + location.endsWith('.mov') || + location.endsWith('.wmv') || + location.endsWith('.flv') || + location.endsWith('.webm')) { + return AppMediaType.video; + } + return AppMediaType.other; + } + + factory AppMediaType.fromMimeType({required String mimeType}) { + if (mimeType.startsWith('image')) { + return AppMediaType.image; + } else if (mimeType.startsWith('video')) { + return AppMediaType.video; + } + return AppMediaType.other; + } +} + +enum AppMediaOrientation { + landscape, + portrait; + + bool get isLandscape => this == AppMediaOrientation.landscape; + + bool get isPortrait => this == AppMediaOrientation.portrait; +} + +enum AppMediaSource { + local, + googleDrive, +} @freezed class AppMedia with _$AppMedia { @@ -14,6 +77,7 @@ class AppMedia with _$AppMedia { required String id, String? name, required String path, + String? thumbnailPath, double? displayHeight, double? displayWidth, required AppMediaType type, @@ -21,11 +85,84 @@ class AppMedia with _$AppMedia { DateTime? createdTime, DateTime? modifiedTime, AppMediaOrientation? orientation, + String? size, + Duration? videoDuration, double? latitude, double? longitude, - @Default(false) bool isLocal, + @Default([AppMediaSource.local]) List sources, }) = _AppMedia; factory AppMedia.fromJson(Map json) => _$AppMediaFromJson(json); + + factory AppMedia.fromGoogleDriveFile(drive.File file) { + final type = AppMediaType.getType( + mimeType: file.mimeType, + location: file.thumbnailLink ?? file.description ?? ''); + + final height = type.isImage + ? file.imageMediaMetadata?.height?.toDouble() + : file.videoMediaMetadata?.height?.toDouble(); + + final width = type.isImage + ? file.imageMediaMetadata?.width?.toDouble() + : file.videoMediaMetadata?.width?.toDouble(); + + final orientation = height != null && width != null + ? height > width + ? AppMediaOrientation.portrait + : AppMediaOrientation.landscape + : null; + + final videoDuration = + type.isVideo && file.videoMediaMetadata?.durationMillis != null + ? Duration( + milliseconds: + int.parse(file.videoMediaMetadata?.durationMillis ?? '0')) + : null; + + return AppMedia( + id: file.id!, + path: file.description ?? file.thumbnailLink ?? '', + thumbnailPath: file.thumbnailLink, + name: file.name, + createdTime: file.createdTime, + modifiedTime: file.modifiedTime, + mimeType: file.mimeType, + size: file.size, + type: type, + displayHeight: height, + displayWidth: width, + videoDuration: videoDuration, + orientation: orientation, + sources: [AppMediaSource.googleDrive], + ); + } + + static Future fromAssetEntity(AssetEntity asset) async { + final file = await asset.originFile; + + if (file == null) return null; + final type = + AppMediaType.getType(mimeType: asset.mimeType, location: file.path); + final length = await file.length(); + return AppMedia( + id: asset.id, + path: file.path, + mimeType: asset.mimeType, + size: length.toString(), + type: type, + createdTime: asset.createDateTime, + latitude: asset.latitude, + longitude: asset.longitude, + videoDuration: type.isVideo ? asset.videoDuration : null, + sources: [AppMediaSource.local], + orientation: asset.orientation == 90 || asset.orientation == 270 + ? AppMediaOrientation.landscape + : AppMediaOrientation.portrait, + modifiedTime: asset.modifiedDateTime, + displayHeight: asset.size.height, + displayWidth: asset.size.width, + ); + } } diff --git a/data/lib/models/media/media.freezed.dart b/data/lib/models/media/media.freezed.dart index 23007de..3a21c04 100644 --- a/data/lib/models/media/media.freezed.dart +++ b/data/lib/models/media/media.freezed.dart @@ -23,6 +23,7 @@ mixin _$AppMedia { String get id => throw _privateConstructorUsedError; String? get name => throw _privateConstructorUsedError; String get path => throw _privateConstructorUsedError; + String? get thumbnailPath => throw _privateConstructorUsedError; double? get displayHeight => throw _privateConstructorUsedError; double? get displayWidth => throw _privateConstructorUsedError; AppMediaType get type => throw _privateConstructorUsedError; @@ -30,9 +31,11 @@ mixin _$AppMedia { DateTime? get createdTime => throw _privateConstructorUsedError; DateTime? get modifiedTime => throw _privateConstructorUsedError; AppMediaOrientation? get orientation => throw _privateConstructorUsedError; + String? get size => throw _privateConstructorUsedError; + Duration? get videoDuration => throw _privateConstructorUsedError; double? get latitude => throw _privateConstructorUsedError; double? get longitude => throw _privateConstructorUsedError; - bool get isLocal => throw _privateConstructorUsedError; + List get sources => throw _privateConstructorUsedError; Map toJson() => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -49,6 +52,7 @@ abstract class $AppMediaCopyWith<$Res> { {String id, String? name, String path, + String? thumbnailPath, double? displayHeight, double? displayWidth, AppMediaType type, @@ -56,9 +60,11 @@ abstract class $AppMediaCopyWith<$Res> { DateTime? createdTime, DateTime? modifiedTime, AppMediaOrientation? orientation, + String? size, + Duration? videoDuration, double? latitude, double? longitude, - bool isLocal}); + List sources}); } /// @nodoc @@ -77,6 +83,7 @@ class _$AppMediaCopyWithImpl<$Res, $Val extends AppMedia> Object? id = null, Object? name = freezed, Object? path = null, + Object? thumbnailPath = freezed, Object? displayHeight = freezed, Object? displayWidth = freezed, Object? type = null, @@ -84,9 +91,11 @@ class _$AppMediaCopyWithImpl<$Res, $Val extends AppMedia> Object? createdTime = freezed, Object? modifiedTime = freezed, Object? orientation = freezed, + Object? size = freezed, + Object? videoDuration = freezed, Object? latitude = freezed, Object? longitude = freezed, - Object? isLocal = null, + Object? sources = null, }) { return _then(_value.copyWith( id: null == id @@ -101,6 +110,10 @@ class _$AppMediaCopyWithImpl<$Res, $Val extends AppMedia> ? _value.path : path // ignore: cast_nullable_to_non_nullable as String, + thumbnailPath: freezed == thumbnailPath + ? _value.thumbnailPath + : thumbnailPath // ignore: cast_nullable_to_non_nullable + as String?, displayHeight: freezed == displayHeight ? _value.displayHeight : displayHeight // ignore: cast_nullable_to_non_nullable @@ -129,6 +142,14 @@ class _$AppMediaCopyWithImpl<$Res, $Val extends AppMedia> ? _value.orientation : orientation // ignore: cast_nullable_to_non_nullable as AppMediaOrientation?, + size: freezed == size + ? _value.size + : size // ignore: cast_nullable_to_non_nullable + as String?, + videoDuration: freezed == videoDuration + ? _value.videoDuration + : videoDuration // ignore: cast_nullable_to_non_nullable + as Duration?, latitude: freezed == latitude ? _value.latitude : latitude // ignore: cast_nullable_to_non_nullable @@ -137,10 +158,10 @@ class _$AppMediaCopyWithImpl<$Res, $Val extends AppMedia> ? _value.longitude : longitude // ignore: cast_nullable_to_non_nullable as double?, - isLocal: null == isLocal - ? _value.isLocal - : isLocal // ignore: cast_nullable_to_non_nullable - as bool, + sources: null == sources + ? _value.sources + : sources // ignore: cast_nullable_to_non_nullable + as List, ) as $Val); } } @@ -157,6 +178,7 @@ abstract class _$$AppMediaImplCopyWith<$Res> {String id, String? name, String path, + String? thumbnailPath, double? displayHeight, double? displayWidth, AppMediaType type, @@ -164,9 +186,11 @@ abstract class _$$AppMediaImplCopyWith<$Res> DateTime? createdTime, DateTime? modifiedTime, AppMediaOrientation? orientation, + String? size, + Duration? videoDuration, double? latitude, double? longitude, - bool isLocal}); + List sources}); } /// @nodoc @@ -183,6 +207,7 @@ class __$$AppMediaImplCopyWithImpl<$Res> Object? id = null, Object? name = freezed, Object? path = null, + Object? thumbnailPath = freezed, Object? displayHeight = freezed, Object? displayWidth = freezed, Object? type = null, @@ -190,9 +215,11 @@ class __$$AppMediaImplCopyWithImpl<$Res> Object? createdTime = freezed, Object? modifiedTime = freezed, Object? orientation = freezed, + Object? size = freezed, + Object? videoDuration = freezed, Object? latitude = freezed, Object? longitude = freezed, - Object? isLocal = null, + Object? sources = null, }) { return _then(_$AppMediaImpl( id: null == id @@ -207,6 +234,10 @@ class __$$AppMediaImplCopyWithImpl<$Res> ? _value.path : path // ignore: cast_nullable_to_non_nullable as String, + thumbnailPath: freezed == thumbnailPath + ? _value.thumbnailPath + : thumbnailPath // ignore: cast_nullable_to_non_nullable + as String?, displayHeight: freezed == displayHeight ? _value.displayHeight : displayHeight // ignore: cast_nullable_to_non_nullable @@ -235,6 +266,14 @@ class __$$AppMediaImplCopyWithImpl<$Res> ? _value.orientation : orientation // ignore: cast_nullable_to_non_nullable as AppMediaOrientation?, + size: freezed == size + ? _value.size + : size // ignore: cast_nullable_to_non_nullable + as String?, + videoDuration: freezed == videoDuration + ? _value.videoDuration + : videoDuration // ignore: cast_nullable_to_non_nullable + as Duration?, latitude: freezed == latitude ? _value.latitude : latitude // ignore: cast_nullable_to_non_nullable @@ -243,10 +282,10 @@ class __$$AppMediaImplCopyWithImpl<$Res> ? _value.longitude : longitude // ignore: cast_nullable_to_non_nullable as double?, - isLocal: null == isLocal - ? _value.isLocal - : isLocal // ignore: cast_nullable_to_non_nullable - as bool, + sources: null == sources + ? _value._sources + : sources // ignore: cast_nullable_to_non_nullable + as List, )); } } @@ -258,6 +297,7 @@ class _$AppMediaImpl implements _AppMedia { {required this.id, this.name, required this.path, + this.thumbnailPath, this.displayHeight, this.displayWidth, required this.type, @@ -265,9 +305,12 @@ class _$AppMediaImpl implements _AppMedia { this.createdTime, this.modifiedTime, this.orientation, + this.size, + this.videoDuration, this.latitude, this.longitude, - this.isLocal = false}); + final List sources = const [AppMediaSource.local]}) + : _sources = sources; factory _$AppMediaImpl.fromJson(Map json) => _$$AppMediaImplFromJson(json); @@ -279,6 +322,8 @@ class _$AppMediaImpl implements _AppMedia { @override final String path; @override + final String? thumbnailPath; + @override final double? displayHeight; @override final double? displayWidth; @@ -293,16 +338,25 @@ class _$AppMediaImpl implements _AppMedia { @override final AppMediaOrientation? orientation; @override + final String? size; + @override + final Duration? videoDuration; + @override final double? latitude; @override final double? longitude; + final List _sources; @override @JsonKey() - final bool isLocal; + List get sources { + if (_sources is EqualUnmodifiableListView) return _sources; + // ignore: implicit_dynamic_type + return EqualUnmodifiableListView(_sources); + } @override String toString() { - return 'AppMedia(id: $id, name: $name, path: $path, displayHeight: $displayHeight, displayWidth: $displayWidth, type: $type, mimeType: $mimeType, createdTime: $createdTime, modifiedTime: $modifiedTime, orientation: $orientation, latitude: $latitude, longitude: $longitude, isLocal: $isLocal)'; + return 'AppMedia(id: $id, name: $name, path: $path, thumbnailPath: $thumbnailPath, displayHeight: $displayHeight, displayWidth: $displayWidth, type: $type, mimeType: $mimeType, createdTime: $createdTime, modifiedTime: $modifiedTime, orientation: $orientation, size: $size, videoDuration: $videoDuration, latitude: $latitude, longitude: $longitude, sources: $sources)'; } @override @@ -313,6 +367,8 @@ class _$AppMediaImpl implements _AppMedia { (identical(other.id, id) || other.id == id) && (identical(other.name, name) || other.name == name) && (identical(other.path, path) || other.path == path) && + (identical(other.thumbnailPath, thumbnailPath) || + other.thumbnailPath == thumbnailPath) && (identical(other.displayHeight, displayHeight) || other.displayHeight == displayHeight) && (identical(other.displayWidth, displayWidth) || @@ -326,11 +382,14 @@ class _$AppMediaImpl implements _AppMedia { other.modifiedTime == modifiedTime) && (identical(other.orientation, orientation) || other.orientation == orientation) && + (identical(other.size, size) || other.size == size) && + (identical(other.videoDuration, videoDuration) || + other.videoDuration == videoDuration) && (identical(other.latitude, latitude) || other.latitude == latitude) && (identical(other.longitude, longitude) || other.longitude == longitude) && - (identical(other.isLocal, isLocal) || other.isLocal == isLocal)); + const DeepCollectionEquality().equals(other._sources, _sources)); } @JsonKey(ignore: true) @@ -340,6 +399,7 @@ class _$AppMediaImpl implements _AppMedia { id, name, path, + thumbnailPath, displayHeight, displayWidth, type, @@ -347,9 +407,11 @@ class _$AppMediaImpl implements _AppMedia { createdTime, modifiedTime, orientation, + size, + videoDuration, latitude, longitude, - isLocal); + const DeepCollectionEquality().hash(_sources)); @JsonKey(ignore: true) @override @@ -370,6 +432,7 @@ abstract class _AppMedia implements AppMedia { {required final String id, final String? name, required final String path, + final String? thumbnailPath, final double? displayHeight, final double? displayWidth, required final AppMediaType type, @@ -377,9 +440,11 @@ abstract class _AppMedia implements AppMedia { final DateTime? createdTime, final DateTime? modifiedTime, final AppMediaOrientation? orientation, + final String? size, + final Duration? videoDuration, final double? latitude, final double? longitude, - final bool isLocal}) = _$AppMediaImpl; + final List sources}) = _$AppMediaImpl; factory _AppMedia.fromJson(Map json) = _$AppMediaImpl.fromJson; @@ -391,6 +456,8 @@ abstract class _AppMedia implements AppMedia { @override String get path; @override + String? get thumbnailPath; + @override double? get displayHeight; @override double? get displayWidth; @@ -405,11 +472,15 @@ abstract class _AppMedia implements AppMedia { @override AppMediaOrientation? get orientation; @override + String? get size; + @override + Duration? get videoDuration; + @override double? get latitude; @override double? get longitude; @override - bool get isLocal; + List get sources; @override @JsonKey(ignore: true) _$$AppMediaImplCopyWith<_$AppMediaImpl> get copyWith => diff --git a/data/lib/models/media/media.g.dart b/data/lib/models/media/media.g.dart index e2d0648..8f0c93e 100644 --- a/data/lib/models/media/media.g.dart +++ b/data/lib/models/media/media.g.dart @@ -11,6 +11,7 @@ _$AppMediaImpl _$$AppMediaImplFromJson(Map json) => id: json['id'] as String, name: json['name'] as String?, path: json['path'] as String, + thumbnailPath: json['thumbnailPath'] as String?, displayHeight: (json['displayHeight'] as num?)?.toDouble(), displayWidth: (json['displayWidth'] as num?)?.toDouble(), type: $enumDecode(_$AppMediaTypeEnumMap, json['type']), @@ -23,9 +24,16 @@ _$AppMediaImpl _$$AppMediaImplFromJson(Map json) => : DateTime.parse(json['modifiedTime'] as String), orientation: $enumDecodeNullable( _$AppMediaOrientationEnumMap, json['orientation']), + size: json['size'] as String?, + videoDuration: json['videoDuration'] == null + ? null + : Duration(microseconds: json['videoDuration'] as int), latitude: (json['latitude'] as num?)?.toDouble(), longitude: (json['longitude'] as num?)?.toDouble(), - isLocal: json['isLocal'] as bool? ?? false, + sources: (json['sources'] as List?) + ?.map((e) => $enumDecode(_$AppMediaSourceEnumMap, e)) + .toList() ?? + const [AppMediaSource.local], ); Map _$$AppMediaImplToJson(_$AppMediaImpl instance) => @@ -33,6 +41,7 @@ Map _$$AppMediaImplToJson(_$AppMediaImpl instance) => 'id': instance.id, 'name': instance.name, 'path': instance.path, + 'thumbnailPath': instance.thumbnailPath, 'displayHeight': instance.displayHeight, 'displayWidth': instance.displayWidth, 'type': _$AppMediaTypeEnumMap[instance.type]!, @@ -40,17 +49,26 @@ Map _$$AppMediaImplToJson(_$AppMediaImpl instance) => 'createdTime': instance.createdTime?.toIso8601String(), 'modifiedTime': instance.modifiedTime?.toIso8601String(), 'orientation': _$AppMediaOrientationEnumMap[instance.orientation], + 'size': instance.size, + 'videoDuration': instance.videoDuration?.inMicroseconds, 'latitude': instance.latitude, 'longitude': instance.longitude, - 'isLocal': instance.isLocal, + 'sources': + instance.sources.map((e) => _$AppMediaSourceEnumMap[e]!).toList(), }; const _$AppMediaTypeEnumMap = { AppMediaType.image: 'image', AppMediaType.video: 'video', + AppMediaType.other: 'other', }; const _$AppMediaOrientationEnumMap = { AppMediaOrientation.landscape: 'landscape', AppMediaOrientation.portrait: 'portrait', }; + +const _$AppMediaSourceEnumMap = { + AppMediaSource.local: 'local', + AppMediaSource.googleDrive: 'googleDrive', +}; diff --git a/data/lib/services/google_drive_service.dart b/data/lib/services/google_drive_service.dart index df1391d..98893f5 100644 --- a/data/lib/services/google_drive_service.dart +++ b/data/lib/services/google_drive_service.dart @@ -61,16 +61,12 @@ class GoogleDriveService { final response = await driveApi.files.list( q: "'$backUpFolderId' in parents and trashed=false", $fields: - "files(id, name, description, mimeType, thumbnailLink, webContentLink, createdTime, modifiedTime)", + "files(id, name, description, mimeType, thumbnailLink, webContentLink, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata)", ); return (response.files ?? []) .map( - (e) => AppMedia( - id: e.id!, - path: e.thumbnailLink!, - type: AppMediaType.image, - ), + (e) => AppMedia.fromGoogleDriveFile(e), ) .toList(); } catch (e) { diff --git a/data/lib/services/local_media_service.dart b/data/lib/services/local_media_service.dart index 2ae278c..df213f4 100644 --- a/data/lib/services/local_media_service.dart +++ b/data/lib/services/local_media_service.dart @@ -19,32 +19,18 @@ class LocalMediaService { return await PhotoManager.getAssetCount(); } - Future> getMedia( + Future> getLocalMedia( {required int start, required int end}) async { - final assets = await PhotoManager.getAssetListRange(start: start, end: end); + final assets = await PhotoManager.getAssetListRange( + start: start, + end: end, + filterOption: FilterOptionGroup( + orders: [const OrderOption(type: OrderOptionType.createDate)], + ), + ); final files = await Future.wait( assets.map( - (asset) async { - final file = await asset.originFile; - if (file == null) return null; - return AppMedia( - id: asset.id, - path: file.path, - type: asset.type == AssetType.image - ? AppMediaType.image - : AppMediaType.video, - createdTime: asset.createDateTime, - latitude: asset.latitude, - longitude: asset.longitude, - isLocal: true, - orientation: asset.orientation == 90 || asset.orientation == 270 - ? AppMediaOrientation.landscape - : AppMediaOrientation.portrait, - modifiedTime: asset.modifiedDateTime, - displayHeight: asset.size.height, - displayWidth: asset.size.width, - ); - }, + (asset) => AppMedia.fromAssetEntity(asset), ), ); return files.whereNotNull().toList(); diff --git a/style/lib/animations/item_selector.dart b/style/lib/animations/item_selector.dart new file mode 100644 index 0000000..d9ff7ed --- /dev/null +++ b/style/lib/animations/item_selector.dart @@ -0,0 +1,57 @@ +import 'package:flutter/cupertino.dart'; +import 'package:style/extensions/context_extensions.dart'; + +class ItemSelector extends StatelessWidget { + final void Function()? onTap; + final void Function()? onLongTap; + final bool isSelected; + final Widget child; + + const ItemSelector( + {super.key, + this.onTap, + this.onLongTap, + required this.isSelected, + required this.child}); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + onLongPress: onLongTap, + child: Stack( + children: [ + AnimatedScale( + curve: Curves.easeInOut, + scale: isSelected ? 0.9 : 1, + duration: const Duration(milliseconds: 100), + child: AnimatedOpacity( + curve: Curves.easeInOut, + duration: const Duration(milliseconds: 100), + opacity: isSelected ? 0.7 : 1, + child: child), + ), + if (isSelected) + Align( + alignment: Alignment.bottomRight, + child: Container( + padding: const EdgeInsets.all(2), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: context.colorScheme.surface, + border: Border.all( + color: const Color(0xff808080), + ), + ), + child: const Icon( + CupertinoIcons.checkmark_alt, + color: Color(0xff808080), + size: 16, + ), + ), + ), + ], + ), + ); + } +} diff --git a/style/lib/animations/parallex_effect.dart b/style/lib/animations/parallax_effect.dart similarity index 100% rename from style/lib/animations/parallex_effect.dart rename to style/lib/animations/parallax_effect.dart diff --git a/style/lib/buttons/buttons_list.dart b/style/lib/buttons/buttons_list.dart index 716943e..e3a74d9 100644 --- a/style/lib/buttons/buttons_list.dart +++ b/style/lib/buttons/buttons_list.dart @@ -61,7 +61,7 @@ class ActionList extends StatelessWidget { enabled: button.onPressed != null, onTap: button.onPressed, child: SizedBox( - height: 45, + height: 50, child: Padding( padding: const EdgeInsets.only(left: 16, right: 8), child: Row( diff --git a/style/lib/extensions/list_extensions.dart b/style/lib/extensions/list_extensions.dart new file mode 100644 index 0000000..4123c0a --- /dev/null +++ b/style/lib/extensions/list_extensions.dart @@ -0,0 +1,26 @@ +extension ListExtension on List { + void addIfNotExist(List list) { + for (E element in list) { + if (!contains(element)) { + add(element); + } + } + } + + /// Replaces [oldElement] with [newElement] if found, else adds [newElement]. + void updateElement({required E newElement, required E oldElement}) { + if (contains(oldElement)) { + this[indexOf(oldElement)] = newElement; + } else { + add(newElement); + } + } + + void addOrRemove({required E element}) { + if (contains(element)) { + remove(element); + } else { + add(element); + } + } +} diff --git a/style/lib/indicators/circular_progress_indicator.dart b/style/lib/indicators/circular_progress_indicator.dart new file mode 100644 index 0000000..ef3ec5b --- /dev/null +++ b/style/lib/indicators/circular_progress_indicator.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:style/extensions/context_extensions.dart'; + +class AppCircularProgressIndicator extends StatefulWidget { + final Color? color; + final double size; + + const AppCircularProgressIndicator({ + super.key, + this.color, + this.size = 32, + }); + + @override + State createState() => + _AppCircularProgressIndicatorState(); +} + +class _AppCircularProgressIndicatorState + extends State { + @override + Widget build(BuildContext context) { + return SizedBox( + width: widget.size, + height: widget.size, + child: CircularProgressIndicator.adaptive( + valueColor: AlwaysStoppedAnimation( + widget.color ?? context.colorScheme.primary, + ), + strokeCap: StrokeCap.round, + ), + ); + } +} diff --git a/style/lib/theme/app_theme_builder.dart b/style/lib/theme/app_theme_builder.dart index 72fc808..6dd6ded 100644 --- a/style/lib/theme/app_theme_builder.dart +++ b/style/lib/theme/app_theme_builder.dart @@ -29,7 +29,10 @@ class AppThemeBuilder { scaffoldBackgroundColor: colorScheme.surface, appBarTheme: AppBarTheme( backgroundColor: colorScheme.surface, - surfaceTintColor: colorScheme.containerLow, + surfaceTintColor: colorScheme.surface, + foregroundColor: colorScheme.textPrimary, + scrolledUnderElevation: 3, + ), ); } From 94f88f934599b220efdb3a6b4d2fd144caf42d88 Mon Sep 17 00:00:00 2001 From: Pratik-canopas Date: Wed, 6 Mar 2024 16:37:34 +0530 Subject: [PATCH 2/2] Inhance home view --- .idea/libraries/Dart_Packages.xml | 116 +++++++------ .idea/libraries/Flutter_Plugins.xml | 9 +- app/ios/Podfile.lock | 38 +++-- app/lib/domain/assets/assets_paths.dart | 8 +- .../domain/extensions/date_extensions.dart | 3 + app/lib/main.dart | 8 +- .../accounts_screen_view_model.freezed.dart | 2 +- .../flow/home/components/app_media_item.dart | 157 ++++++++++-------- app/lib/ui/flow/home/home_screen.dart | 97 +++++++---- .../ui/flow/home/home_screen_view_model.dart | 99 ++++++----- .../home/home_screen_view_model.freezed.dart | 47 +++--- app/pubspec.lock | 116 +++++++------ app/pubspec.yaml | 1 + data/.flutter-plugins | 4 +- data/.flutter-plugins-dependencies | 2 +- data/lib/models/media/media.freezed.dart | 2 +- data/lib/services/local_media_service.dart | 6 +- style/lib/animations/item_selector.dart | 2 +- style/lib/extensions/list_extensions.dart | 4 +- .../circular_progress_indicator.dart | 18 +- style/lib/slivers/sticky_header_delegate.dart | 29 ++++ 21 files changed, 441 insertions(+), 327 deletions(-) create mode 100644 app/lib/domain/extensions/date_extensions.dart create mode 100644 style/lib/slivers/sticky_header_delegate.dart diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml index a692d80..f48099f 100644 --- a/.idea/libraries/Dart_Packages.xml +++ b/.idea/libraries/Dart_Packages.xml @@ -12,14 +12,14 @@ - - @@ -89,7 +89,7 @@ - @@ -103,7 +103,7 @@ - @@ -208,28 +208,28 @@ - - - - @@ -250,7 +250,7 @@ - @@ -264,7 +264,7 @@ - @@ -278,7 +278,7 @@ - @@ -303,6 +303,13 @@ + + + + + + @@ -320,14 +327,14 @@ - - @@ -355,7 +362,7 @@ - @@ -383,7 +390,7 @@ - @@ -411,7 +418,7 @@ - @@ -439,7 +446,6 @@ - @@ -454,7 +460,7 @@ - @@ -504,6 +510,7 @@ @@ -580,7 +587,7 @@ - @@ -720,7 +727,7 @@ - @@ -762,21 +769,21 @@ - - - @@ -979,21 +986,21 @@ - - - @@ -1070,7 +1077,7 @@ - @@ -1106,8 +1113,8 @@ - - + + @@ -1117,9 +1124,9 @@ - + - + @@ -1134,39 +1141,39 @@ - - - - + + + + - + - + - + + - - + + - + - + - + - - + @@ -1174,6 +1181,7 @@ + @@ -1184,7 +1192,7 @@ - + @@ -1204,15 +1212,15 @@ - + - - - + + + @@ -1240,9 +1248,9 @@ - - - + + + @@ -1253,7 +1261,7 @@ - + diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml index 16ff21e..8f6f0be 100644 --- a/.idea/libraries/Flutter_Plugins.xml +++ b/.idea/libraries/Flutter_Plugins.xml @@ -7,17 +7,13 @@ - - - - @@ -28,6 +24,11 @@ + + + + + diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock index 6369ffb..4be63c7 100644 --- a/app/ios/Podfile.lock +++ b/app/ios/Podfile.lock @@ -5,16 +5,16 @@ PODS: - AppAuth/Core (1.6.2) - AppAuth/ExternalUserAgent (1.6.2): - AppAuth/Core - - Firebase/CoreOnly (10.18.0): - - FirebaseCore (= 10.18.0) - - firebase_core (2.24.2): - - Firebase/CoreOnly (= 10.18.0) + - Firebase/CoreOnly (10.20.0): + - FirebaseCore (= 10.20.0) + - firebase_core (2.25.5): + - Firebase/CoreOnly (= 10.20.0) - Flutter - - FirebaseCore (10.18.0): + - FirebaseCore (10.20.0): - FirebaseCoreInternal (~> 10.0) - GoogleUtilities/Environment (~> 7.12) - GoogleUtilities/Logger (~> 7.12) - - FirebaseCoreInternal (10.20.0): + - FirebaseCoreInternal (10.22.0): - "GoogleUtilities/NSData+zlib (~> 7.8)" - Flutter (1.0.0) - fluttertoast (0.0.2): @@ -28,11 +28,15 @@ PODS: - AppAuth (~> 1.5) - GTMAppAuth (< 3.0, >= 1.3) - GTMSessionFetcher/Core (< 4.0, >= 1.1) - - GoogleUtilities/Environment (7.12.0): + - GoogleUtilities/Environment (7.13.0): + - GoogleUtilities/Privacy - PromisesObjC (< 3.0, >= 1.2) - - GoogleUtilities/Logger (7.12.0): + - GoogleUtilities/Logger (7.13.0): - GoogleUtilities/Environment - - "GoogleUtilities/NSData+zlib (7.12.0)" + - GoogleUtilities/Privacy + - "GoogleUtilities/NSData+zlib (7.13.0)": + - GoogleUtilities/Privacy + - GoogleUtilities/Privacy (7.13.0) - GTMAppAuth (2.0.0): - AppAuth/Core (~> 1.6) - GTMSessionFetcher/Core (< 4.0, >= 1.5) @@ -47,7 +51,7 @@ PODS: - photo_manager (2.0.0): - Flutter - FlutterMacOS - - PromisesObjC (2.3.1) + - PromisesObjC (2.4.0) - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS @@ -111,22 +115,22 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: AppAuth: 3bb1d1cd9340bd09f5ed189fb00b1cc28e1e8570 - Firebase: 414ad272f8d02dfbf12662a9d43f4bba9bec2a06 - firebase_core: 0af4a2b24f62071f9bf283691c0ee41556dcb3f5 - FirebaseCore: 2322423314d92f946219c8791674d2f3345b598f - FirebaseCoreInternal: efeeb171ac02d623bdaefe121539939821e10811 + Firebase: 10c8cb12fb7ad2ae0c09ffc86cd9c1ab392a0031 + firebase_core: c8628c7ce80f79439149549052bff22f6784fbf5 + FirebaseCore: 28045c1560a2600d284b9c45a904fe322dc890b6 + FirebaseCoreInternal: bca337352024b18424a61e478460547d46c4c753 Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 fluttertoast: 31b00dabfa7fb7bacd9e7dbee580d7a2ff4bf265 - google_sign_in_ios: 1bfaf6607b44cd1b24c4d4bc39719870440f9ce1 + google_sign_in_ios: 989eea5abe94af62050782714daf920be883d4a2 GoogleSignIn: b232380cf495a429b8095d3178a8d5855b42e842 - GoogleUtilities: 0759d1a57ebb953965c2dfe0ba4c82e95ccc2e34 + GoogleUtilities: d053d902a8edaa9904e1bd00c37535385b8ed152 GTMAppAuth: 99fb010047ba3973b7026e45393f51f27ab965ae GTMSessionFetcher: 8a1b34ad97ebe6f909fb8b9b77fba99943007556 package_info_plus: 115f4ad11e0698c8c1c5d8a689390df880f47e85 path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c permission_handler_apple: 036b856153a2b1f61f21030ff725f3e6fece2b78 photo_manager: 4f6810b7dfc4feb03b461ac1a70dacf91fba7604 - PromisesObjC: c50d2056b5253dadbd6c2bea79b0674bd5a52fa4 + PromisesObjC: f5707f49cb48b9636751c5b2e7d227e43fba9f47 shared_preferences_foundation: b4c3b4cddf1c21f02770737f147a3f5da9d39695 sqflite: 673a0e54cc04b7d6dba8d24fb8095b31c3a99eec Toast: ec33c32b8688982cecc6348adeae667c1b9938da diff --git a/app/lib/domain/assets/assets_paths.dart b/app/lib/domain/assets/assets_paths.dart index 395413a..c4bd56a 100644 --- a/app/lib/domain/assets/assets_paths.dart +++ b/app/lib/domain/assets/assets_paths.dart @@ -1,13 +1,13 @@ class Assets { - static Images images = Images(); + static PathImages images = PathImages(); } -class Images { +class PathImages { String get appIcon => 'assets/images/app_logo.png'; - Icons get icons => Icons(); + PathIcons get icons => PathIcons(); } -class Icons { +class PathIcons { String get googlePhotos => 'assets/images/icons/google_photos.svg'; } diff --git a/app/lib/domain/extensions/date_extensions.dart b/app/lib/domain/extensions/date_extensions.dart new file mode 100644 index 0000000..016df91 --- /dev/null +++ b/app/lib/domain/extensions/date_extensions.dart @@ -0,0 +1,3 @@ +extension DateTimeExtensions on DateTime { + DateTime get dateOnly => DateTime(year, month, day); +} \ No newline at end of file diff --git a/app/lib/main.dart b/app/lib/main.dart index 3712607..130e4ec 100644 --- a/app/lib/main.dart +++ b/app/lib/main.dart @@ -1,20 +1,26 @@ import 'dart:async'; +import 'dart:io'; import 'package:cloud_gallery/firebase_options.dart'; import 'package:data/storage/provider/preferences_provider.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_displaymode/flutter_displaymode.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'ui/app.dart'; Future main() async { - WidgetsFlutterBinding.ensureInitialized(); await Firebase.initializeApp( options: DefaultFirebaseOptions.currentPlatform, ); + if (Platform.isAndroid) { + // Workaround for https://github.com/flutter/flutter/issues/35162 + await FlutterDisplayMode.setHighRefreshRate(); + } + final container = await _configureContainerWithAsyncDependency(); runApp( diff --git a/app/lib/ui/flow/accounts/accounts_screen_view_model.freezed.dart b/app/lib/ui/flow/accounts/accounts_screen_view_model.freezed.dart index 9bf736e..dcea795 100644 --- a/app/lib/ui/flow/accounts/accounts_screen_view_model.freezed.dart +++ b/app/lib/ui/flow/accounts/accounts_screen_view_model.freezed.dart @@ -12,7 +12,7 @@ part of 'accounts_screen_view_model.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$AccountsState { diff --git a/app/lib/ui/flow/home/components/app_media_item.dart b/app/lib/ui/flow/home/components/app_media_item.dart index ff2989e..15f9a2f 100644 --- a/app/lib/ui/flow/home/components/app_media_item.dart +++ b/app/lib/ui/flow/home/components/app_media_item.dart @@ -3,10 +3,8 @@ import 'package:cached_network_image/cached_network_image.dart'; import 'package:data/models/media/media.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:style/animations/parallax_effect.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/indicators/circular_progress_indicator.dart'; -import 'package:video_player/video_player.dart'; import '../../../../domain/assets/assets_paths.dart'; import 'package:style/animations/item_selector.dart'; @@ -30,27 +28,30 @@ class AppMediaItem extends StatefulWidget { State createState() => _AppMediaItemState(); } -class _AppMediaItemState extends State with AutomaticKeepAliveClientMixin { - final _imageKey = GlobalKey(); - VideoPlayerController? _videoPlayerController; - +class _AppMediaItemState extends State + with AutomaticKeepAliveClientMixin { + //VideoPlayerController? _videoPlayerController; @override void initState() { if (widget.media.type.isVideo && widget.media.sources.contains(AppMediaSource.local)) { - if (widget.media.sources.contains(AppMediaSource.local)) { - _videoPlayerController = - VideoPlayerController.file(File(widget.media.path)) - ..initialize().then((_) { - setState(() {}); - }); - } + // if (widget.media.sources.contains(AppMediaSource.local)) { + // _videoPlayerController = + // VideoPlayerController.file(File(widget.media.path)) + // ..initialize().then((_) { + // setState(() {}); + // }); + // } } super.initState(); } - + @override + void dispose() { + // _videoPlayerController?.dispose(); + super.dispose(); + } @override Widget build(BuildContext context) { @@ -59,17 +60,20 @@ class _AppMediaItemState extends State with AutomaticKeepAliveClie onTap: widget.onTap, onLongTap: widget.onLongTap, isSelected: widget.isSelected, - child: Stack( - alignment: Alignment.bottomLeft, - children: [ - widget.media.type.isVideo && - widget.media.sources.contains(AppMediaSource.local) - ? _buildVideoView(context: context) - : _buildImageView(context: context), - if (widget.media.sources.contains(AppMediaSource.googleDrive) || - widget.isUploading) - _sourceIndicators(context: context), - ], + child: ClipRRect( + borderRadius: BorderRadius.circular(4), + child: Stack( + alignment: Alignment.bottomLeft, + children: [ + widget.media.type.isVideo && + widget.media.sources.contains(AppMediaSource.local) + ? _buildVideoView(context: context) + : _buildImageView(context: context), + if (widget.media.sources.contains(AppMediaSource.googleDrive) || + widget.isUploading) + _sourceIndicators(context: context), + ], + ), ), ); } @@ -79,18 +83,18 @@ class _AppMediaItemState extends State with AutomaticKeepAliveClie margin: const EdgeInsets.all(4), padding: const EdgeInsets.all(4), decoration: BoxDecoration( - color: context.colorScheme.surface.withOpacity(0.6), + color: context.colorScheme.surface, borderRadius: BorderRadius.circular(8), ), child: Row( mainAxisSize: MainAxisSize.min, children: [ if (widget.media.sources.contains(AppMediaSource.googleDrive)) - SvgPicture.asset( - Assets.images.icons.googlePhotos, - height: 12, - width: 12, - ), + SvgPicture.asset( + Assets.images.icons.googlePhotos, + height: 12, + width: 12, + ), if (widget.isUploading) const AppCircularProgressIndicator(size: 12), ], ), @@ -98,38 +102,55 @@ class _AppMediaItemState extends State with AutomaticKeepAliveClie } Widget _buildImageView({required BuildContext context}) { - return LayoutBuilder( - builder: (context, constraints) { - return ClipRRect( - borderRadius: BorderRadius.circular(4), - child: Flow( - delegate: ParallaxFlowDelegate( - scrollable: Scrollable.of(context), - listItemContext: context, - backgroundImageKey: _imageKey, + return LayoutBuilder(builder: (context, constraints) { + return Image( + image: ResizeImage( + widget.media.sources.contains(AppMediaSource.local) + ? FileImage(File(widget.media.path)) + : CachedNetworkImageProvider(widget.media.thumbnailPath!) + as ImageProvider, + height: constraints.maxHeight.toInt(), + width: constraints.maxWidth.toInt(), + policy: ResizeImagePolicy.fit, + ), + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) { + return child; + } + return Container( + width: double.maxFinite, + height: double.maxFinite, + color: context.colorScheme.containerNormalOnSurface, + child: Center( + child: AppCircularProgressIndicator( + value: (loadingProgress.expectedTotalBytes != null && + (loadingProgress.expectedTotalBytes ?? 0) > 0) + ? loadingProgress.cumulativeBytesLoaded / + loadingProgress.expectedTotalBytes! + : null, + ), ), - children: [ - Image( - key: _imageKey, - image: widget.media.sources.contains(AppMediaSource.local) - ? FileImage(File(widget.media.path)) - : CachedNetworkImageProvider(widget.media.thumbnailPath!) - as ImageProvider, - fit: BoxFit.cover, - loadingBuilder: (context, child, loadingProgress) { - if (loadingProgress == null) { - return child; - } - return const Center(child: AppCircularProgressIndicator()); - }, - width: constraints.maxWidth, - height: constraints.maxHeight * 1.5, + ); + }, + errorBuilder: (context, error, stackTrace) { + return Container( + width: double.maxFinite, + height: double.maxFinite, + color: context.colorScheme.containerNormalOnSurface, + child: Center( + child: Icon( + CupertinoIcons.photo, + color: context.colorScheme.onPrimary, + size: 32, ), - ], - ), - ); - }, - ); + ), + ); + }, + width: double.maxFinite, + height: double.maxFinite, + ); + }); } Widget _buildVideoView({required BuildContext context}) { @@ -137,13 +158,13 @@ class _AppMediaItemState extends State with AutomaticKeepAliveClie alignment: Alignment.center, children: [ Container( - decoration: BoxDecoration( - color: context.colorScheme.containerLowOnSurface, - ), - child: ClipRRect( - borderRadius: BorderRadius.circular(4), - child: VideoPlayer(_videoPlayerController!))), - Icon(CupertinoIcons.play_arrow_solid, color: context.colorScheme.onPrimary), + decoration: BoxDecoration( + color: context.colorScheme.containerNormalOnSurface, + ), + // child: VideoPlayer(_videoPlayerController!), + ), + Icon(CupertinoIcons.play_arrow_solid, + color: context.colorScheme.onPrimary), ], ); } diff --git a/app/lib/ui/flow/home/home_screen.dart b/app/lib/ui/flow/home/home_screen.dart index 8f062c1..72e8ec0 100644 --- a/app/lib/ui/flow/home/home_screen.dart +++ b/app/lib/ui/flow/home/home_screen.dart @@ -6,13 +6,16 @@ import 'package:data/models/media/media.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; +import 'package:intl/intl.dart'; import 'package:style/extensions/context_extensions.dart'; import 'package:style/indicators/circular_progress_indicator.dart'; +import 'package:style/text/app_text_style.dart'; import '../../../components/snack_bar.dart'; import '../../../domain/assets/assets_paths.dart'; import '../../navigation/app_router.dart'; import 'components/app_media_item.dart'; import 'components/multi_selection_done_button.dart'; +import 'package:style/slivers/sticky_header_delegate.dart'; class HomeScreen extends ConsumerStatefulWidget { const HomeScreen({super.key}); @@ -73,7 +76,6 @@ class _HomeScreenState extends ConsumerState { } Widget _body({required BuildContext context}) { - //States final medias = ref.watch(homeViewStateNotifier.select((state) => state.medias)); @@ -115,40 +117,73 @@ class _HomeScreenState extends ConsumerState { Widget _buildMediaList( {required BuildContext context, - required List medias, + required Map> medias, required List uploadingMedias, - required List selectedMedias}) { + required List selectedMedias}) { return Scrollbar( - interactive: true, - thickness: 4, - controller: _scrollController, - child: GridView.builder( + interactive: true, + child: CustomScrollView( controller: _scrollController, - padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 16), - gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: 3, - crossAxisSpacing: 4, - mainAxisSpacing: 4, - ), - itemCount: medias.length, - itemBuilder: (context, index) { - final media = medias[index]; - return AppMediaItem( - key: ValueKey(media.id), - onTap: () { - if (selectedMedias.isNotEmpty) { - notifier.mediaSelection(media); - } - }, - onLongTap: () { - notifier.mediaSelection(media); - }, - isSelected: selectedMedias.contains(media.id), - isUploading: uploadingMedias.contains(media.id), - media: media, - ); - }, + slivers: medias.entries + .map( + (e) => SliverMainAxisGroup( + slivers: [ + SliverPersistentHeader( + delegate: SliverStickyHeaderDelegate( + header: Container( + padding: const EdgeInsets.only(left: 16), + alignment: Alignment.centerLeft, + + decoration: BoxDecoration( + color: context.colorScheme.surface, + ), + child: Text( + DateFormat("d MMMM, y").format(e.key), + style: AppTextStyles.subtitle1.copyWith( + color: context.colorScheme.textPrimary, + ), + ), + ), + ), + pinned: true, + floating: true, + // floating: true, + ), + SliverPadding( + padding: + const EdgeInsets.symmetric(horizontal: 4, vertical: 8), + sliver: SliverGrid.builder( + itemBuilder: (context, index) { + final media = e.value[index]; + return AppMediaItem( + key: ValueKey(media.id), + onTap: () { + if (selectedMedias.isNotEmpty) { + notifier.toggleMediaSelection(media); + } + }, + onLongTap: () { + notifier.toggleMediaSelection(media); + }, + isSelected: selectedMedias.contains(media), + isUploading: uploadingMedias.contains(media.id), + media: media, + ); + }, + itemCount: e.value.length, + gridDelegate: + const SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 4, + mainAxisSpacing: 4, + ), + ), + ), + ], + ), + ) + .toList(), ), ); } diff --git a/app/lib/ui/flow/home/home_screen_view_model.dart b/app/lib/ui/flow/home/home_screen_view_model.dart index f0ec385..f4571d0 100644 --- a/app/lib/ui/flow/home/home_screen_view_model.dart +++ b/app/lib/ui/flow/home/home_screen_view_model.dart @@ -1,5 +1,6 @@ import 'dart:async'; - +import 'package:cloud_gallery/domain/extensions/date_extensions.dart'; +import 'package:collection/collection.dart'; import 'package:data/models/media/media.dart'; import 'package:data/services/auth_service.dart'; import 'package:data/services/google_drive_service.dart'; @@ -26,7 +27,7 @@ class HomeViewStateNotifier extends StateNotifier { String? _backUpFolderId; List _localMedias = []; - int _localMediaCount = 0; + int? _localMediaCount; List _googleDriveMedias = []; HomeViewStateNotifier( @@ -38,26 +39,26 @@ class HomeViewStateNotifier extends StateNotifier { Future loadMedias() async { state = state.copyWith(loading: state.medias.isEmpty, error: null); try { - final counts = await _loadMediaCount(); - if (counts != null) { + _localMediaCount ??= await _getLocalMediaCount(); + if (_localMediaCount != null) { await Future.wait([ _getGoogleDriveMedias(), - _loadLocalMedias(), + _getLocalMedias(), ]); } else { await _getGoogleDriveMedias(); } state = state.copyWith( loading: false, - medias: _sortMedia(), - hasLocalMediaAccess: counts != null, + medias: _sortMedias(medias: _getAllMedias()), + hasLocalMediaAccess: _localMediaCount != null, ); } catch (error) { state = state.copyWith(loading: false, error: error); } } - List _sortMedia() { + List _getAllMedias() { final commonMedias = []; for (AppMedia localMedia in _localMedias.toList()) { @@ -74,54 +75,48 @@ class HomeViewStateNotifier extends StateNotifier { }); } - List shortedMedia = [ - ..._localMedias, - ..._googleDriveMedias, - ...commonMedias - ]; + return [..._localMedias, ..._googleDriveMedias, ...commonMedias]; + } - shortedMedia.sort((a, b) => (b.createdTime ?? DateTime.now()) + Map> _sortMedias({required List medias}) { + medias.sort((a, b) => (b.createdTime ?? DateTime.now()) .compareTo(a.createdTime ?? DateTime.now())); - return shortedMedia; + return groupBy( + medias, + (AppMedia media) => + media.createdTime?.dateOnly ?? DateTime.now().dateOnly, + ); } Future _getGoogleDriveMedias() async { - if(_authService.hasUserSigned){ + if (_authService.hasUserSigned) { _backUpFolderId ??= await _googleDriveService.getBackupFolderId(); _googleDriveMedias = await _googleDriveService.getDriveMedias( backUpFolderId: _backUpFolderId!); } } - Future _loadMediaCount() async { + Future _getLocalMediaCount() async { final hasAccess = await _localMediaService.requestPermission(); if (hasAccess) { - _localMediaCount = await _localMediaService.getMediaCount(); - return _localMediaCount; + return await _localMediaService.getMediaCount(); } return null; } - Future _loadLocalMedias() async { - _localMedias = await _localMediaService.getLocalMedia( - start: 0, - end: _localMediaCount, - ); + Future _getLocalMedias() async { + if (_localMediaCount != null) { + _localMedias = await _localMediaService.getLocalMedia( + start: 0, + end: _localMediaCount!, + ); + } } - void mediaSelection(AppMedia media) { + void toggleMediaSelection(AppMedia media) { final selectedMedias = state.selectedMedias.toList(); - if (selectedMedias.contains(media.id)) { - state = state.copyWith( - selectedMedias: selectedMedias.toList()..remove(media.id), - error: null, - ); - } else { - state = state.copyWith( - selectedMedias: [...selectedMedias, media.id], - error: null, - ); - } + selectedMedias.addOrRemove(element: media); + state = state.copyWith(selectedMedias: selectedMedias); } Future uploadMediaOnGoogleDrive() async { @@ -129,30 +124,32 @@ class HomeViewStateNotifier extends StateNotifier { if (!_authService.hasUserSigned) { await _authService.signInWithGoogle(); } - List uploadingMedias = state.medias + List uploadingMedias = state.selectedMedias .where((element) => - state.selectedMedias.contains(element.id) && !element.sources.contains(AppMediaSource.googleDrive)) .toList(); state = state.copyWith( - uploadingMedias: uploadingMedias.map((e) => e.id).toList(), - error: null); - final folderId = await _googleDriveService.getBackupFolderId(); + uploadingMedias: uploadingMedias.map((e) => e.id).toList(), + error: null, + ); + _backUpFolderId ??= await _googleDriveService.getBackupFolderId(); for (final media in uploadingMedias) { await _googleDriveService.uploadInGoogleDrive( media: media, - folderID: folderId!, + folderID: _backUpFolderId!, ); + state = state.copyWith( - medias: state.medias.toList() - ..updateElement( - newElement: media.copyWith( - sources: media.sources.toList() - ..add(AppMediaSource.googleDrive)), - oldElement: media, - ), + medias: state.medias.map((key, value) { + value.updateElement( + newElement: media.copyWith( + sources: media.sources.toList() + ..add(AppMediaSource.googleDrive)), + oldElement: media); + return MapEntry(key, value); + }), uploadingMedias: state.uploadingMedias.toList()..remove(media.id), ); } @@ -170,8 +167,8 @@ class HomeViewState with _$HomeViewState { Object? error, @Default(false) bool hasLocalMediaAccess, @Default(false) bool loading, - @Default([]) List medias, - @Default([]) List selectedMedias, + @Default({}) Map> medias, + @Default([]) List selectedMedias, @Default([]) List uploadingMedias, }) = _HomeViewState; } diff --git a/app/lib/ui/flow/home/home_screen_view_model.freezed.dart b/app/lib/ui/flow/home/home_screen_view_model.freezed.dart index c3b30ed..ef7ba7a 100644 --- a/app/lib/ui/flow/home/home_screen_view_model.freezed.dart +++ b/app/lib/ui/flow/home/home_screen_view_model.freezed.dart @@ -12,15 +12,16 @@ part of 'home_screen_view_model.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); /// @nodoc mixin _$HomeViewState { Object? get error => throw _privateConstructorUsedError; bool get hasLocalMediaAccess => throw _privateConstructorUsedError; bool get loading => throw _privateConstructorUsedError; - List get medias => throw _privateConstructorUsedError; - List get selectedMedias => throw _privateConstructorUsedError; + Map> get medias => + throw _privateConstructorUsedError; + List get selectedMedias => throw _privateConstructorUsedError; List get uploadingMedias => throw _privateConstructorUsedError; @JsonKey(ignore: true) @@ -38,8 +39,8 @@ abstract class $HomeViewStateCopyWith<$Res> { {Object? error, bool hasLocalMediaAccess, bool loading, - List medias, - List selectedMedias, + Map> medias, + List selectedMedias, List uploadingMedias}); } @@ -76,11 +77,11 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState> medias: null == medias ? _value.medias : medias // ignore: cast_nullable_to_non_nullable - as List, + as Map>, selectedMedias: null == selectedMedias ? _value.selectedMedias : selectedMedias // ignore: cast_nullable_to_non_nullable - as List, + as List, uploadingMedias: null == uploadingMedias ? _value.uploadingMedias : uploadingMedias // ignore: cast_nullable_to_non_nullable @@ -101,8 +102,8 @@ abstract class _$$HomeViewStateImplCopyWith<$Res> {Object? error, bool hasLocalMediaAccess, bool loading, - List medias, - List selectedMedias, + Map> medias, + List selectedMedias, List uploadingMedias}); } @@ -137,11 +138,11 @@ class __$$HomeViewStateImplCopyWithImpl<$Res> medias: null == medias ? _value._medias : medias // ignore: cast_nullable_to_non_nullable - as List, + as Map>, selectedMedias: null == selectedMedias ? _value._selectedMedias : selectedMedias // ignore: cast_nullable_to_non_nullable - as List, + as List, uploadingMedias: null == uploadingMedias ? _value._uploadingMedias : uploadingMedias // ignore: cast_nullable_to_non_nullable @@ -157,8 +158,8 @@ class _$HomeViewStateImpl implements _HomeViewState { {this.error, this.hasLocalMediaAccess = false, this.loading = false, - final List medias = const [], - final List selectedMedias = const [], + final Map> medias = const {}, + final List selectedMedias = const [], final List uploadingMedias = const []}) : _medias = medias, _selectedMedias = selectedMedias, @@ -172,19 +173,19 @@ class _$HomeViewStateImpl implements _HomeViewState { @override @JsonKey() final bool loading; - final List _medias; + final Map> _medias; @override @JsonKey() - List get medias { - if (_medias is EqualUnmodifiableListView) return _medias; + Map> get medias { + if (_medias is EqualUnmodifiableMapView) return _medias; // ignore: implicit_dynamic_type - return EqualUnmodifiableListView(_medias); + return EqualUnmodifiableMapView(_medias); } - final List _selectedMedias; + final List _selectedMedias; @override @JsonKey() - List get selectedMedias { + List get selectedMedias { if (_selectedMedias is EqualUnmodifiableListView) return _selectedMedias; // ignore: implicit_dynamic_type return EqualUnmodifiableListView(_selectedMedias); @@ -242,8 +243,8 @@ abstract class _HomeViewState implements HomeViewState { {final Object? error, final bool hasLocalMediaAccess, final bool loading, - final List medias, - final List selectedMedias, + final Map> medias, + final List selectedMedias, final List uploadingMedias}) = _$HomeViewStateImpl; @override @@ -253,9 +254,9 @@ abstract class _HomeViewState implements HomeViewState { @override bool get loading; @override - List get medias; + Map> get medias; @override - List get selectedMedias; + List get selectedMedias; @override List get uploadingMedias; @override diff --git a/app/pubspec.lock b/app/pubspec.lock index 4b50096..f256aad 100644 --- a/app/pubspec.lock +++ b/app/pubspec.lock @@ -13,18 +13,18 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: eb376e9acf6938204f90eb3b1f00b578640d3188b4c8a8ec054f9f479af8d051 + sha256: "0b2f2bd91ba804e53a61d757b986f89f1f9eaed5b11e4b2f5a2468d86d6c9fc7" url: "https://pub.dev" source: hosted - version: "64.0.0" + version: "67.0.0" analyzer: dependency: transitive description: name: analyzer - sha256: "69f54f967773f6c26c7dcb13e93d7ccee8b17a641689da39e878d5cf13b06893" + sha256: "37577842a27e4338429a1cbc32679d508836510b056f1eedf0c8d20e39c1383d" url: "https://pub.dev" source: hosted - version: "6.2.0" + version: "6.4.1" analyzer_plugin: dependency: transitive description: @@ -101,10 +101,10 @@ packages: dependency: transitive description: name: build_runner_core - sha256: c9e32d21dd6626b5c163d48b037ce906bbe428bc23ab77bcd77bb21e593b6185 + sha256: "4ae8ffe5ac758da294ecf1802f2aff01558d8b1b00616aa7538ea9a8a5d50799" url: "https://pub.dev" source: hosted - version: "7.2.11" + version: "7.3.0" built_collection: dependency: transitive description: @@ -117,10 +117,10 @@ packages: dependency: transitive description: name: built_value - sha256: c9aabae0718ec394e5bc3c7272e6bb0dc0b32201a08fe185ec1d8401d3e39309 + sha256: fedde275e0a6b798c3296963c5cd224e3e1b55d0e478d5b7e65e6b540f363a0e url: "https://pub.dev" source: hosted - version: "8.8.1" + version: "8.9.1" cached_network_image: dependency: "direct main" description: @@ -237,34 +237,34 @@ packages: dependency: transitive description: name: custom_lint - sha256: dfb893ff17c83cf08676c6b64df11d3e53d80590978d7c1fb242afff3ba6dedb + sha256: "445242371d91d2e24bd7b82e3583a2c05610094ba2d0575262484ad889c8f981" url: "https://pub.dev" source: hosted - version: "0.5.8" + version: "0.6.2" custom_lint_builder: dependency: transitive description: name: custom_lint_builder - sha256: "8df6634b38a36a6c6cb74a9c0eb02e9ba0b0ab89b29e38e6daa86e8ed2c6288d" + sha256: "4c0aed2a3491096e91cf1281923ba1b6814993f16dde0fd60f697925225bbbd6" url: "https://pub.dev" source: hosted - version: "0.5.8" + version: "0.6.2" custom_lint_core: dependency: transitive description: name: custom_lint_core - sha256: "2b235be098d157e244f18ea905a15a18c16a205e30553888fac6544bbf52f03f" + sha256: ce5d6215f4e143f7780ce53f73dfa6fc503f39d2d30bef76c48be9ac1a09d9a6 url: "https://pub.dev" source: hosted - version: "0.5.8" + version: "0.6.2" dart_style: dependency: transitive description: name: dart_style - sha256: "40ae61a5d43feea6d24bd22c0537a6629db858963b99b4bc1c3db80676f32368" + sha256: "99e066ce75c89d6b29903d788a7bb9369cf754f7b24bf70bf4b6d6d6b26853b9" url: "https://pub.dev" source: hosted - version: "2.3.4" + version: "2.3.6" data: dependency: "direct main" description: @@ -292,10 +292,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: transitive description: @@ -308,10 +308,10 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: "96607c0e829a581c2a483c658f04e8b159964c3bae2730f73297070bc85d40bb" + sha256: "797379ea206eaeeb62499775de812761493d0692890fdc7f90b6183a3369176d" url: "https://pub.dev" source: hosted - version: "2.24.2" + version: "2.25.5" firebase_core_platform_interface: dependency: transitive description: @@ -324,10 +324,10 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: d585bdf3c656c3f7821ba1bd44da5f13365d22fcecaf5eb75c4295246aaa83c0 + sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492 url: "https://pub.dev" source: hosted - version: "2.10.0" + version: "2.11.5" fixnum: dependency: transitive description: @@ -349,6 +349,14 @@ packages: url: "https://pub.dev" source: hosted version: "3.3.1" + flutter_displaymode: + dependency: "direct main" + description: + name: flutter_displaymode + sha256: "42c5e9abd13d28ed74f701b60529d7f8416947e58256e6659c5550db719c57ef" + url: "https://pub.dev" + source: hosted + version: "0.6.0" flutter_lints: dependency: "direct dev" description: @@ -366,18 +374,18 @@ packages: dependency: "direct main" description: name: flutter_riverpod - sha256: da9591d1f8d5881628ccd5c25c40e74fc3eef50ba45e40c3905a06e1712412d5 + sha256: "4bce556b7ecbfea26109638d5237684538d4abc509d253e6c5c4c5733b360098" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.4.10" flutter_svg: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -400,10 +408,10 @@ packages: dependency: "direct main" description: name: freezed - sha256: "6c5031daae12c7072b3a87eff98983076434b4889ef2a44384d0cae3f82372ba" + sha256: "57247f692f35f068cae297549a46a9a097100685c6780fe67177503eea5ed4e5" url: "https://pub.dev" source: hosted - version: "2.4.6" + version: "2.4.7" freezed_annotation: dependency: "direct main" description: @@ -432,10 +440,10 @@ packages: dependency: "direct main" description: name: go_router - sha256: "3b40e751eaaa855179b416974d59d29669e750d2e50fcdb2b37f1cb0ca8c803a" + sha256: "170c46e237d6eb0e6e9f0e8b3f56101e14fb64f787016e42edd74c39cf8b176a" url: "https://pub.dev" source: hosted - version: "13.0.1" + version: "13.2.0" google_identity_services_web: dependency: transitive description: @@ -464,10 +472,10 @@ packages: dependency: transitive description: name: google_sign_in_ios - sha256: f3336d9e44d4d28063ac90271f6db5caf99f0480cb07281330e7a432edb95226 + sha256: a7d653803468d30b82ceb47ea00fe86d23c56e63eb2e5c2248bb68e9df203217 url: "https://pub.dev" source: hosted - version: "5.7.3" + version: "5.7.4" google_sign_in_platform_interface: dependency: transitive description: @@ -496,10 +504,10 @@ packages: dependency: transitive description: name: googleapis_auth - sha256: "772779fe28a8b70939eab9c390a5f8f46cbb59bda9f1f10ea60dd894eff59ff0" + sha256: cafc46446574fd42826aa4cd4d623c94482598fda0a5a5649bf2781bcbc09258 url: "https://pub.dev" source: hosted - version: "1.4.2" + version: "1.5.0" graphs: dependency: transitive description: @@ -512,10 +520,10 @@ packages: dependency: transitive description: name: hotreloader - sha256: "94ee21a60ea2836500799f3af035dc3212b1562027f1e0031c14e087f0231449" + sha256: ed56fdc1f3a8ac924e717257621d09e9ec20e308ab6352a73a50a1d7a4d9158e url: "https://pub.dev" source: hosted - version: "4.1.0" + version: "4.2.0" html: dependency: transitive description: @@ -656,10 +664,10 @@ packages: dependency: transitive description: name: mime - sha256: e4ff8e8564c03f255408decd16e7899da1733852a9110a58fe6d1b817684a63e + sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2" url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "1.0.5" octo_image: dependency: transitive description: @@ -816,10 +824,10 @@ packages: dependency: transitive description: name: photo_manager - sha256: "8cf79918f6de9843b394a1670fe1aec54ebcac852b4b4c9ef88211894547dc61" + sha256: df594f989f0c31cdb3ed48f3d49cb9ffadf11cc3700d2c3460b1912c93432621 url: "https://pub.dev" source: hosted - version: "3.0.0-dev.5" + version: "3.0.0" platform: dependency: transitive description: @@ -864,26 +872,26 @@ packages: dependency: transitive description: name: riverpod - sha256: "942999ee48b899f8a46a860f1e13cee36f2f77609eb54c5b7a669bb20d550b11" + sha256: "548e2192eb7aeb826eb89387f814edb76594f3363e2c0bb99dd733d795ba3589" url: "https://pub.dev" source: hosted - version: "2.4.9" + version: "2.5.0" riverpod_analyzer_utils: dependency: transitive description: name: riverpod_analyzer_utils - sha256: d4dabc35358413bf4611fcb6abb46308a67c4ef4cd5e69fd3367b11925c59f57 + sha256: "8b71f03fc47ae27d13769496a1746332df4cec43918aeba9aff1e232783a780f" url: "https://pub.dev" source: hosted - version: "0.5.0" + version: "0.5.1" riverpod_lint: dependency: "direct dev" description: name: riverpod_lint - sha256: "944929ef82c9bfeaa455ccab97920abcf847a0ffed5c9f6babc520a95db25176" + sha256: e9bbd02e9e89e18eecb183bbca556d7b523a0669024da9b8167c08903f442937 url: "https://pub.dev" source: hosted - version: "2.3.7" + version: "2.3.9" rxdart: dependency: transitive description: @@ -1116,26 +1124,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "18f6690295af52d081f6808f2f7c69f0eed6d7e23a71539d75f4aeb8f0062172" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: "531d20465c10dfac7f5cd90b60bbe4dd9921f1ec4ca54c83ebb176dbacb7bb2d" + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "03012b0a33775c5530576b70240308080e1d5050f0faf000118c20e6463bc0ad" + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.9+2" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1220,10 +1228,10 @@ packages: dependency: transitive description: name: web_socket_channel - sha256: d88238e5eac9a42bb43ca4e721edba3c08c6354d4a53063afaa568516217621b + sha256: "939ab60734a4f8fa95feacb55804fa278de28bdeef38e616dc08e44a84adea23" url: "https://pub.dev" source: hosted - version: "2.4.0" + version: "2.4.3" win32: dependency: transitive description: diff --git a/app/pubspec.yaml b/app/pubspec.yaml index ef63034..dd9198a 100644 --- a/app/pubspec.yaml +++ b/app/pubspec.yaml @@ -30,6 +30,7 @@ dependencies: cached_network_image: ^3.3.1 video_player: ^2.8.2 visibility_detector: ^0.4.0+2 + flutter_displaymode: ^0.6.0 # state management flutter_riverpod: ^2.4.9 diff --git a/data/.flutter-plugins b/data/.flutter-plugins index faf4ec1..5cde211 100644 --- a/data/.flutter-plugins +++ b/data/.flutter-plugins @@ -1,12 +1,12 @@ # This is a generated file; do not edit or check into version control. google_sign_in=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in-6.2.1/ google_sign_in_android=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_android-6.1.21/ -google_sign_in_ios=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.3/ +google_sign_in_ios=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.4/ google_sign_in_web=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_web-0.12.3+3/ package_info_plus=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/ path_provider_linux=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/ path_provider_windows=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/ -photo_manager=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/ +photo_manager=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0/ shared_preferences=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences-2.2.2/ shared_preferences_android=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_android-2.2.1/ shared_preferences_foundation=/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.5/ diff --git a/data/.flutter-plugins-dependencies b/data/.flutter-plugins-dependencies index 1776ed2..24d471e 100644 --- a/data/.flutter-plugins-dependencies +++ b/data/.flutter-plugins-dependencies @@ -1 +1 @@ -{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.5/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"google_sign_in_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_android-6.1.21/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_android-2.2.1/","native_build":true,"dependencies":[]}],"macos":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.3/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0-dev.5/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.5/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.3.2/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.3.2/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"google_sign_in_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_web-0.12.3+3/","dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_web-2.2.2/","dependencies":[]}]},"dependencyGraph":[{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"photo_manager","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2024-03-01 11:09:45.230961","version":"3.19.1"} \ No newline at end of file +{"info":"This is a generated file; do not edit or check into version control.","plugins":{"ios":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.4/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.5/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"android":[{"name":"google_sign_in_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_android-6.1.21/","native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_android-2.2.1/","native_build":true,"dependencies":[]}],"macos":[{"name":"google_sign_in_ios","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_ios-5.7.4/","shared_darwin_source":true,"native_build":true,"dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":true,"dependencies":[]},{"name":"photo_manager","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/photo_manager-3.0.0/","native_build":true,"dependencies":[]},{"name":"shared_preferences_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_foundation-2.3.5/","shared_darwin_source":true,"native_build":true,"dependencies":[]}],"linux":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"path_provider_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_linux-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_linux","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_linux-2.3.2/","native_build":false,"dependencies":["path_provider_linux"]}],"windows":[{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","native_build":false,"dependencies":[]},{"name":"path_provider_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_windows-2.2.1/","native_build":false,"dependencies":[]},{"name":"shared_preferences_windows","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_windows-2.3.2/","native_build":false,"dependencies":["path_provider_windows"]}],"web":[{"name":"google_sign_in_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/google_sign_in_web-0.12.3+3/","dependencies":[]},{"name":"package_info_plus","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/package_info_plus-5.0.1/","dependencies":[]},{"name":"shared_preferences_web","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/shared_preferences_web-2.2.2/","dependencies":[]}]},"dependencyGraph":[{"name":"google_sign_in","dependencies":["google_sign_in_android","google_sign_in_ios","google_sign_in_web"]},{"name":"google_sign_in_android","dependencies":[]},{"name":"google_sign_in_ios","dependencies":[]},{"name":"google_sign_in_web","dependencies":[]},{"name":"package_info_plus","dependencies":[]},{"name":"path_provider_linux","dependencies":[]},{"name":"path_provider_windows","dependencies":[]},{"name":"photo_manager","dependencies":[]},{"name":"shared_preferences","dependencies":["shared_preferences_android","shared_preferences_foundation","shared_preferences_linux","shared_preferences_web","shared_preferences_windows"]},{"name":"shared_preferences_android","dependencies":[]},{"name":"shared_preferences_foundation","dependencies":[]},{"name":"shared_preferences_linux","dependencies":["path_provider_linux"]},{"name":"shared_preferences_web","dependencies":[]},{"name":"shared_preferences_windows","dependencies":["path_provider_windows"]}],"date_created":"2024-03-05 17:32:12.442322","version":"3.19.1"} \ No newline at end of file diff --git a/data/lib/models/media/media.freezed.dart b/data/lib/models/media/media.freezed.dart index 3a21c04..7c6a696 100644 --- a/data/lib/models/media/media.freezed.dart +++ b/data/lib/models/media/media.freezed.dart @@ -12,7 +12,7 @@ part of 'media.dart'; T _$identity(T value) => value; final _privateConstructorUsedError = UnsupportedError( - 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#custom-getters-and-methods'); + 'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models'); AppMedia _$AppMediaFromJson(Map json) { return _AppMedia.fromJson(json); diff --git a/data/lib/services/local_media_service.dart b/data/lib/services/local_media_service.dart index df213f4..5b02374 100644 --- a/data/lib/services/local_media_service.dart +++ b/data/lib/services/local_media_service.dart @@ -16,7 +16,11 @@ class LocalMediaService { } Future getMediaCount() async { - return await PhotoManager.getAssetCount(); + return await PhotoManager.getAssetCount( + filterOption: FilterOptionGroup( + orders: [const OrderOption(type: OrderOptionType.createDate)], + ), + ); } Future> getLocalMedia( diff --git a/style/lib/animations/item_selector.dart b/style/lib/animations/item_selector.dart index d9ff7ed..65040fe 100644 --- a/style/lib/animations/item_selector.dart +++ b/style/lib/animations/item_selector.dart @@ -33,7 +33,7 @@ class ItemSelector extends StatelessWidget { ), if (isSelected) Align( - alignment: Alignment.bottomRight, + alignment: Alignment.topRight, child: Container( padding: const EdgeInsets.all(2), decoration: BoxDecoration( diff --git a/style/lib/extensions/list_extensions.dart b/style/lib/extensions/list_extensions.dart index 4123c0a..95dd0c9 100644 --- a/style/lib/extensions/list_extensions.dart +++ b/style/lib/extensions/list_extensions.dart @@ -8,10 +8,10 @@ extension ListExtension on List { } /// Replaces [oldElement] with [newElement] if found, else adds [newElement]. - void updateElement({required E newElement, required E oldElement}) { + void updateElement({required E newElement, required E oldElement,bool addIfNotContain = false}) { if (contains(oldElement)) { this[indexOf(oldElement)] = newElement; - } else { + } else if(addIfNotContain){ add(newElement); } } diff --git a/style/lib/indicators/circular_progress_indicator.dart b/style/lib/indicators/circular_progress_indicator.dart index ef3ec5b..54de2b6 100644 --- a/style/lib/indicators/circular_progress_indicator.dart +++ b/style/lib/indicators/circular_progress_indicator.dart @@ -1,31 +1,27 @@ import 'package:flutter/material.dart'; import 'package:style/extensions/context_extensions.dart'; -class AppCircularProgressIndicator extends StatefulWidget { +class AppCircularProgressIndicator extends StatelessWidget { final Color? color; + final double? value; final double size; const AppCircularProgressIndicator({ super.key, this.color, this.size = 32, + this.value, }); - @override - State createState() => - _AppCircularProgressIndicatorState(); -} - -class _AppCircularProgressIndicatorState - extends State { @override Widget build(BuildContext context) { return SizedBox( - width: widget.size, - height: widget.size, + width: size, + height: size, child: CircularProgressIndicator.adaptive( + value: value, valueColor: AlwaysStoppedAnimation( - widget.color ?? context.colorScheme.primary, + color ?? context.colorScheme.primary, ), strokeCap: StrokeCap.round, ), diff --git a/style/lib/slivers/sticky_header_delegate.dart b/style/lib/slivers/sticky_header_delegate.dart new file mode 100644 index 0000000..11c1eb2 --- /dev/null +++ b/style/lib/slivers/sticky_header_delegate.dart @@ -0,0 +1,29 @@ +import 'package:flutter/cupertino.dart'; + +class SliverStickyHeaderDelegate extends SliverPersistentHeaderDelegate { + final Widget header; + @override + final double minExtent; + @override + final double maxExtent; + + const SliverStickyHeaderDelegate({ + required this.header, + this.minExtent = 45, + this.maxExtent = 45, + }); + + @override + Widget build( + BuildContext context, + double shrinkOffset, + bool overlapsContent, + ) => + SizedBox.expand(child: header); + + @override + bool shouldRebuild(SliverStickyHeaderDelegate oldDelegate) => + oldDelegate.maxExtent != maxExtent || + oldDelegate.minExtent != minExtent || + oldDelegate.header != header; +}