diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml
index f48099f..6bccb1c 100644
--- a/.idea/libraries/Dart_Packages.xml
+++ b/.idea/libraries/Dart_Packages.xml
@@ -1014,7 +1014,7 @@
-
+
@@ -1252,7 +1252,7 @@
-
+
diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml
index 8f6f0be..5371787 100644
--- a/.idea/libraries/Flutter_Plugins.xml
+++ b/.idea/libraries/Flutter_Plugins.xml
@@ -22,13 +22,13 @@
-
+
diff --git a/app/lib/components/app_page.dart b/app/lib/components/app_page.dart
index 4839f8f..e91d72f 100644
--- a/app/lib/components/app_page.dart
+++ b/app/lib/components/app_page.dart
@@ -9,8 +9,11 @@ class AppPage extends StatelessWidget {
final Widget? leading;
final Widget? floatingActionButton;
final Widget? body;
+ final Widget Function(BuildContext context)? bodyBuilder;
final bool automaticallyImplyLeading;
final bool? resizeToAvoidBottomInset;
+ final Color? backgroundColor;
+ final Color? barBackgroundColor;
const AppPage({
super.key,
@@ -21,7 +24,10 @@ class AppPage extends StatelessWidget {
this.body,
this.floatingActionButton,
this.resizeToAvoidBottomInset,
+ this.bodyBuilder,
this.automaticallyImplyLeading = true,
+ this.barBackgroundColor,
+ this.backgroundColor,
});
@override
@@ -39,6 +45,7 @@ class AppPage extends StatelessWidget {
leading == null
? null
: CupertinoNavigationBar(
+ backgroundColor: barBackgroundColor,
leading: leading,
middle: titleWidget ?? _title(context),
border: null,
@@ -56,10 +63,15 @@ class AppPage extends StatelessWidget {
: null,
),
resizeToAvoidBottomInset: resizeToAvoidBottomInset ?? true,
+ backgroundColor: backgroundColor,
child: Stack(
alignment: Alignment.bottomRight,
children: [
- body ?? const SizedBox(),
+ body ??
+ Builder(
+ builder: (context) =>
+ bodyBuilder?.call(context) ?? const SizedBox(),
+ ),
SafeArea(
child: Padding(
padding: const EdgeInsets.only(right: 16, bottom: 16),
@@ -76,12 +88,18 @@ class AppPage extends StatelessWidget {
leading == null
? null
: AppBar(
+ backgroundColor: barBackgroundColor,
title: titleWidget ?? _title(context),
actions: actions,
leading: leading,
automaticallyImplyLeading: automaticallyImplyLeading,
),
- body: body,
+ body: body ??
+ Builder(
+ builder: (context) =>
+ bodyBuilder?.call(context) ?? const SizedBox(),
+ ),
+ backgroundColor: backgroundColor,
floatingActionButton: floatingActionButton,
resizeToAvoidBottomInset: resizeToAvoidBottomInset,
);
@@ -92,3 +110,46 @@ class AppPage extends StatelessWidget {
overflow: TextOverflow.ellipsis,
);
}
+
+class AdaptiveAppBar extends StatelessWidget {
+ final String text;
+ final Widget? leading;
+ final List? actions;
+ final bool iosTransitionBetweenRoutes;
+ final bool automaticallyImplyLeading;
+
+ const AdaptiveAppBar(
+ {super.key,
+ required this.text,
+ this.leading,
+ this.actions,
+ this.iosTransitionBetweenRoutes = true,
+ this.automaticallyImplyLeading = true});
+
+ @override
+ Widget build(BuildContext context) {
+ return Platform.isIOS || Platform.isMacOS
+ ? CupertinoNavigationBar(
+ transitionBetweenRoutes: iosTransitionBetweenRoutes,
+ middle: Text(text),
+ previousPageTitle:
+ MaterialLocalizations.of(context).backButtonTooltip,
+ automaticallyImplyLeading: automaticallyImplyLeading,
+ leading: leading,
+ trailing: actions == null
+ ? null
+ : actions!.length == 1
+ ? actions!.first
+ : Row(
+ mainAxisSize: MainAxisSize.min,
+ children: actions!,
+ ),
+ )
+ : AppBar(
+ leading: leading,
+ actions: actions,
+ automaticallyImplyLeading: automaticallyImplyLeading,
+ title: Text(text),
+ );
+ }
+}
diff --git a/app/lib/domain/extensions/map_extensions.dart b/app/lib/domain/extensions/map_extensions.dart
new file mode 100644
index 0000000..0dd21dc
--- /dev/null
+++ b/app/lib/domain/extensions/map_extensions.dart
@@ -0,0 +1,4 @@
+extension MapExtension on Map> {
+ List valuesWhere(bool Function(E element) test) =>
+ values.expand((element) => element).toList().where(test).toList();
+}
diff --git a/app/lib/ui/flow/accounts/accounts_screen.dart b/app/lib/ui/flow/accounts/accounts_screen.dart
index 5cc6cb4..3fdf06c 100644
--- a/app/lib/ui/flow/accounts/accounts_screen.dart
+++ b/app/lib/ui/flow/accounts/accounts_screen.dart
@@ -39,56 +39,58 @@ class _AccountsScreenState extends ConsumerState {
return AppPage(
title: context.l10n.common_accounts,
- body: ListView(
- padding: const EdgeInsets.all(16),
- children: [
- if (googleAccount != null)
- AccountsTab(
- name: googleAccount.displayName ?? googleAccount.email,
- serviceDescription: context.l10n.common_google_drive,
- profileImage: googleAccount.photoUrl,
- actionList: ActionList(buttons: [
- ActionListButton(
- title: context.l10n.common_auto_back_up,
- trailing: Consumer(
- builder: (context, ref, child) {
- final googleDriveAutoBackUp = ref
- .watch(AppPreferences.canTakeAutoBackUpInGoogleDrive);
- return AppSwitch(
- value: googleDriveAutoBackUp,
- onChanged: (bool value) {
- ref
- .read(AppPreferences
- .canTakeAutoBackUpInGoogleDrive.notifier)
- .state = value;
- },
- );
- },
+ bodyBuilder: (context) {
+ return ListView(
+ padding: context.systemPadding + const EdgeInsets.all(16),
+ children: [
+ if (googleAccount != null)
+ AccountsTab(
+ name: googleAccount.displayName ?? googleAccount.email,
+ serviceDescription: context.l10n.common_google_drive,
+ profileImage: googleAccount.photoUrl,
+ actionList: ActionList(buttons: [
+ ActionListButton(
+ title: context.l10n.common_auto_back_up,
+ trailing: Consumer(
+ builder: (context, ref, child) {
+ final googleDriveAutoBackUp = ref.watch(
+ AppPreferences.canTakeAutoBackUpInGoogleDrive);
+ return AppSwitch(
+ value: googleDriveAutoBackUp,
+ onChanged: (bool value) {
+ ref
+ .read(AppPreferences
+ .canTakeAutoBackUpInGoogleDrive.notifier)
+ .state = value;
+ },
+ );
+ },
+ ),
),
+ ActionListButton(
+ title: context.l10n.common_sign_out,
+ onPressed: notifier.signOutWithGoogle,
+ ),
+ ]),
+ backgroundColor: AppColors.googleDriveColor.withAlpha(50),
+ ),
+ if (googleAccount == null)
+ OnTapScale(
+ onTap: () {
+ notifier.signInWithGoogle();
+ },
+ child: AccountsTab(
+ name: context.l10n.add_account_title,
+ backgroundColor: context.colorScheme.containerNormal,
),
- ActionListButton(
- title: context.l10n.common_sign_out,
- onPressed: notifier.signOutWithGoogle,
- ),
- ]),
- backgroundColor: AppColors.googleDriveColor.withAlpha(50),
- ),
- if (googleAccount == null)
- OnTapScale(
- onTap: () {
- notifier.signInWithGoogle();
- },
- child: AccountsTab(
- name: context.l10n.add_account_title,
- backgroundColor: context.colorScheme.containerNormal,
),
- ),
- const SizedBox(height: 16),
- const SettingsActionList(),
- const SizedBox(height: 16),
- _buildVersion(context: context),
- ],
- ),
+ const SizedBox(height: 16),
+ const SettingsActionList(),
+ const SizedBox(height: 16),
+ _buildVersion(context: context),
+ ],
+ );
+ },
);
}
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 39a1880..5666751 100644
--- a/app/lib/ui/flow/home/components/app_media_item.dart
+++ b/app/lib/ui/flow/home/components/app_media_item.dart
@@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter_svg/svg.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,47 +31,47 @@ class AppMediaItem extends StatefulWidget {
class _AppMediaItemState extends State
with AutomaticKeepAliveClientMixin {
- //VideoPlayerController? _videoPlayerController;
+ VideoPlayerController? _videoPlayerController;
@override
void initState() {
///TODO: Video view
- // if (widget.media.type.isVideo &&
- // widget.media.sources.contains(AppMediaSource.local)) {
- //
- // _videoPlayerController =
- // VideoPlayerController.file(File(widget.media.path))
- // ..initialize().then((_) {
- // setState(() {});
- // });
- //
- // }
+ if (widget.media.type.isVideo &&
+ widget.media.sources.contains(AppMediaSource.local)) {
+ _videoPlayerController =
+ VideoPlayerController.file(File(widget.media.path))
+ ..initialize().then((_) {
+ setState(() {});
+ });
+ }
super.initState();
}
@override
void dispose() {
- // _videoPlayerController?.dispose();
+ _videoPlayerController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
super.build(context);
- return ItemSelector(
- onTap: widget.onTap,
- onLongTap: widget.onLongTap,
- isSelected: widget.isSelected,
- child: ClipRRect(
- borderRadius: BorderRadius.circular(4),
- child: Stack(
- alignment: Alignment.bottomLeft,
- children: [
- widget.media.type.isVideo && widget.media.thumbnailLink == null
- ? _buildVideoView(context: context)
- : _buildImageView(context: context),
- _sourceIndicators(context: context),
- ],
+ return LayoutBuilder(
+ builder: (context, constraints) => ItemSelector(
+ onTap: widget.onTap,
+ onLongTap: widget.onLongTap,
+ isSelected: widget.isSelected,
+ child: ClipRRect(
+ borderRadius: BorderRadius.circular(4),
+ child: Stack(
+ alignment: Alignment.bottomLeft,
+ children: [
+ widget.media.type.isVideo && widget.media.thumbnailLink == null
+ ? _buildVideoView(context: context)
+ : _buildImageView(context: context, constraints: constraints),
+ _sourceIndicators(context: context),
+ ],
+ ),
),
),
);
@@ -114,9 +115,11 @@ class _AppMediaItemState extends State
);
}
- Widget _buildImageView({required BuildContext context}) {
- return LayoutBuilder(builder: (context, constraints) {
- return Image(
+ Widget _buildImageView(
+ {required BuildContext context, required BoxConstraints constraints}) {
+ return Hero(
+ tag: widget.media,
+ child: Image(
image: widget.media.sources.contains(AppMediaSource.local)
? ResizeImage(
FileImage(File(widget.media.path)),
@@ -162,8 +165,8 @@ class _AppMediaItemState extends State
},
width: double.maxFinite,
height: double.maxFinite,
- );
- });
+ ),
+ );
}
Widget _buildVideoView({required BuildContext context}) {
@@ -174,7 +177,7 @@ class _AppMediaItemState extends State
decoration: BoxDecoration(
color: context.colorScheme.containerNormalOnSurface,
),
- // child: VideoPlayer(_videoPlayerController!),
+ child: VideoPlayer(_videoPlayerController!),
),
Icon(CupertinoIcons.play_arrow_solid,
color: context.colorScheme.onPrimary),
diff --git a/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart b/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart
index d3ebd2f..10b9ebe 100644
--- a/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart
+++ b/app/lib/ui/flow/home/components/no_local_medias_access_screen.dart
@@ -42,7 +42,7 @@ class NoLocalMediasAccessScreen extends ConsumerWidget {
PrimaryButton(
onPressed: () async {
await openAppSettings();
- await notifier.loadMedias();
+ await notifier.loadLocalMedia();
},
text: context.l10n.load_local_media_button_text,
),
diff --git a/app/lib/ui/flow/home/home_screen.dart b/app/lib/ui/flow/home/home_screen.dart
index aa72718..b0c6b11 100644
--- a/app/lib/ui/flow/home/home_screen.dart
+++ b/app/lib/ui/flow/home/home_screen.dart
@@ -1,4 +1,5 @@
import 'package:cloud_gallery/components/app_page.dart';
+import 'package:cloud_gallery/domain/extensions/widget_extensions.dart';
import 'package:cloud_gallery/ui/flow/media_preview/media_preview.dart';
import 'package:cloud_gallery/domain/extensions/context_extensions.dart';
import 'package:cloud_gallery/ui/flow/home/components/no_local_medias_access_screen.dart';
@@ -18,7 +19,7 @@ import '../../navigation/app_router.dart';
import 'components/app_media_item.dart';
import 'components/hints.dart';
import 'components/multi_selection_done_button.dart';
-import 'package:style/slivers/sticky_header_delegate.dart';
+import 'package:style/buttons/action_button.dart';
class HomeScreen extends ConsumerStatefulWidget {
const HomeScreen({super.key});
@@ -55,15 +56,11 @@ class _HomeScreenState extends ConsumerState {
@override
Widget build(BuildContext context) {
_errorObserver();
-
return AppPage(
+ //barBackgroundColor: context.colorScheme.surface,
titleWidget: _titleWidget(context: context),
actions: [
- IconButton(
- style: IconButton.styleFrom(
- backgroundColor: context.colorScheme.containerNormalOnSurface,
- minimumSize: const Size(28, 28),
- ),
+ ActionButton(
onPressed: () {
AppRouter.accounts.push(context);
},
@@ -79,47 +76,45 @@ class _HomeScreenState extends ConsumerState {
}
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 State
+ final ({
+ Map> medias,
+ List uploadingMedias,
+ List selectedMedias,
+ bool isLoading,
+ bool hasLocalMediaAccess,
+ String? lastLocalMediaId
+ }) state = ref.watch(homeViewStateNotifier.select((value) => (
+ medias: value.medias,
+ uploadingMedias: value.uploadingMedias,
+ selectedMedias: value.selectedMedias,
+ isLoading: value.loading,
+ hasLocalMediaAccess: value.hasLocalMediaAccess,
+ lastLocalMediaId: value.lastLocalMediaId,
+ )));
//View
- if (isLoading) {
+ if (state.isLoading) {
return const Center(child: AppCircularProgressIndicator());
- } else if (medias.isEmpty && !hasLocalMediaAccess) {
+ } else if (state.medias.isEmpty && !state.hasLocalMediaAccess) {
return const NoLocalMediasAccessScreen();
}
- return RefreshIndicator.adaptive(
- onRefresh: () async {
- await notifier.loadMedias();
- },
- child: Stack(
- alignment: Alignment.bottomRight,
- children: [
- _buildMediaList(
- context: context,
- medias: medias,
- uploadingMedias: uploadingMedias,
- selectedMedias: selectedMedias,
+ return Stack(
+ alignment: Alignment.bottomRight,
+ children: [
+ _buildMediaList(
+ context: context,
+ medias: state.medias,
+ uploadingMedias: state.uploadingMedias,
+ selectedMedias: state.selectedMedias,
+ lastLocalMediaId: state.lastLocalMediaId,
+ ),
+ if (state.selectedMedias.isNotEmpty)
+ Padding(
+ padding: context.systemPadding + const EdgeInsets.all(16),
+ child: const MultiSelectionDoneButton(),
),
- if (selectedMedias.isNotEmpty)
- Padding(
- padding: context.systemPadding + const EdgeInsets.all(16),
- child: const MultiSelectionDoneButton(),
- ),
- ],
- ),
+ ],
);
}
@@ -127,86 +122,82 @@ class _HomeScreenState extends ConsumerState {
{required BuildContext context,
required Map> medias,
required List uploadingMedias,
+ required String? lastLocalMediaId,
required List selectedMedias}) {
return Scrollbar(
controller: _scrollController,
interactive: true,
- child: CustomScrollView(controller: _scrollController, slivers: [
- const SliverToBoxAdapter(
- child: HomeScreenHints(),
- ),
- ...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,
- ),
- ),
- ),
+ child: ListView.builder(
+ controller: _scrollController,
+ itemCount: medias.length + 1,
+ itemBuilder: (context, index) {
+ if (index == 0) {
+ return const HomeScreenHints();
+ } else {
+ final gridEntry = medias.entries.elementAt(index - 1);
+ return Column(
+ children: [
+ Container(
+ height: 45,
+ padding: const EdgeInsets.only(left: 16, top: 5),
+ margin: EdgeInsets.zero,
+ alignment: Alignment.centerLeft,
+ decoration: BoxDecoration(
+ color: context.colorScheme.surface,
+ ),
+ child: Text(
+ DateFormat("d MMMM, y").format(gridEntry.key),
+ style: AppTextStyles.subtitle1.copyWith(
+ color: context.colorScheme.textPrimary,
),
- pinned: true,
),
- SliverPadding(
- padding:
- const EdgeInsets.symmetric(horizontal: 4, vertical: 8),
- sliver: SliverGrid.builder(
- itemBuilder: (context, index) {
- final media = e.value[index];
- return Hero(
- tag: "${AppRoutePath.home}/${media.id}",
- child: AppMediaItem(
- key: ValueKey(media.id),
- onTap: () {
- if (selectedMedias.isNotEmpty) {
- notifier.toggleMediaSelection(media);
- } else {
- AppMediaView.showPreview(
- context: context,
- media: media,
- heroTag: "${AppRoutePath.home}/${media.id}",
- );
- }
- },
- onLongTap: () {
- notifier.toggleMediaSelection(media);
- },
- isSelected: selectedMedias.contains(media),
- status: uploadingMedias
- .firstWhereOrNull(
- (element) => element.mediaId == media.id)
- ?.status,
+ ),
+ GridView.builder(
+ padding: const EdgeInsets.all(4),
+ physics: const NeverScrollableScrollPhysics(),
+ shrinkWrap: true,
+ gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
+ crossAxisCount: 3,
+ crossAxisSpacing: 4,
+ mainAxisSpacing: 4,
+ ),
+ itemCount: gridEntry.value.length,
+ itemBuilder: (context, index) {
+ final media = gridEntry.value[index];
+ if (media.id == lastLocalMediaId) {
+ runPostFrame(() {
+ notifier.loadLocalMedia(append: true);
+ });
+ }
+ return AppMediaItem(
+ key: ValueKey(media.id),
+ onTap: () {
+ if (selectedMedias.isNotEmpty) {
+ notifier.toggleMediaSelection(media);
+ } else {
+ AppMediaView.showPreview(
+ context: context,
media: media,
- ),
- );
+ );
+ }
},
- itemCount: e.value.length,
- gridDelegate:
- const SliverGridDelegateWithFixedCrossAxisCount(
- crossAxisCount: 3,
- crossAxisSpacing: 4,
- mainAxisSpacing: 4,
- ),
- ),
- ),
- ],
- ),
- )
- .toList(),
- SliverToBoxAdapter(
- child: SizedBox(height: context.systemPadding.bottom),
- ),
- ]),
+ onLongTap: () {
+ notifier.toggleMediaSelection(media);
+ },
+ isSelected: selectedMedias.contains(media),
+ status: uploadingMedias
+ .firstWhereOrNull(
+ (element) => element.mediaId == media.id)
+ ?.status,
+ media: media,
+ );
+ },
+ ),
+ ],
+ );
+ }
+ },
+ ),
);
}
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 2e1cad8..93291d2 100644
--- a/app/lib/ui/flow/home/home_screen_view_model.dart
+++ b/app/lib/ui/flow/home/home_screen_view_model.dart
@@ -1,12 +1,12 @@
import 'dart:async';
import 'package:cloud_gallery/domain/extensions/date_extensions.dart';
+import 'package:cloud_gallery/domain/extensions/map_extensions.dart';
import 'package:collection/collection.dart';
import 'package:data/errors/app_error.dart';
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:data/storage/app_preferences.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:google_sign_in/google_sign_in.dart';
@@ -17,20 +17,11 @@ part 'home_screen_view_model.freezed.dart';
final homeViewStateNotifier =
StateNotifierProvider.autoDispose(
(ref) {
- final homeViewStateNotifier = HomeViewStateNotifier(
+ return HomeViewStateNotifier(
ref.read(localMediaServiceProvider),
ref.read(googleDriveServiceProvider),
ref.read(authServiceProvider),
- ref.read(AppPreferences.canTakeAutoBackUpInGoogleDrive),
);
- final subscription = ref.listen(AppPreferences.canTakeAutoBackUpInGoogleDrive,
- (previous, next) {
- homeViewStateNotifier.updateAutoBackUpStatus(next);
- });
- ref.onDispose(() {
- subscription.close();
- });
- return homeViewStateNotifier;
});
class HomeViewStateNotifier extends StateNotifier {
@@ -38,140 +29,39 @@ class HomeViewStateNotifier extends StateNotifier {
final AuthService _authService;
final LocalMediaService _localMediaService;
StreamSubscription? _googleAccountSubscription;
- bool _isAutoBackUpEnabled = false;
- bool _isAutoBackUpWorking = false;
- bool _loading = false;
+
+ List _uploadedMedia = [];
String? _backUpFolderId;
- int? _localMediaCount;
- List _localMedias = [];
- List _googleDriveMedias = [];
+ bool _isGoogleDriveLoading = false;
+ bool _isLocalMediaLoading = false;
+ bool _isMaxLocalMediaLoaded = false;
- HomeViewStateNotifier(this._localMediaService, this._googleDriveService,
- this._authService, this._isAutoBackUpEnabled)
+ HomeViewStateNotifier(
+ this._localMediaService, this._googleDriveService, this._authService)
: super(const HomeViewState()) {
_googleAccountSubscription =
- _authService.onGoogleAccountChange.listen((event) {
+ _authService.onGoogleAccountChange.listen((event) async {
state = state.copyWith(googleAccount: event);
- loadMedias();
- if (_isAutoBackUpEnabled && !_isAutoBackUpWorking && event != null) {
- _autoBackUpMedias();
- }
- });
- if (_isAutoBackUpEnabled &&
- !_isAutoBackUpWorking &&
- state.googleAccount != null) {
- _autoBackUpMedias();
- }
- loadMedias();
- }
-
- @override
- Future dispose() async {
- await _googleAccountSubscription?.cancel();
- super.dispose();
- }
-
- Future _autoBackUpMedias() async {
- _backUpFolderId ??= await _googleDriveService.getBackupFolderId();
-
- final backUpQueue = state.medias.values
- .expand((element) => element)
- .where(
- (element) => !element.sources.contains(AppMediaSource.googleDrive))
- .toList();
-
- state = state.copyWith(
- uploadingMedias: backUpQueue
- .map((e) =>
- UploadProgress(mediaId: e.id, status: UploadStatus.waiting))
- .toList(),
- error: null,
- );
-
- _isAutoBackUpWorking = true;
-
- for (final media in backUpQueue) {
- try {
- if (!_isAutoBackUpEnabled) {
- _isAutoBackUpWorking = false;
- state = state.copyWith(
- uploadingMedias: [],
- );
- return;
- }
-
+ await loadGoogleDriveMedia();
+ if (event == null) {
+ _uploadedMedia.clear();
state = state.copyWith(
- uploadingMedias: state.uploadingMedias.toList()
- ..updateElement(
- newElement: UploadProgress(
- mediaId: media.id, status: UploadStatus.uploading),
- oldElement: UploadProgress(
- mediaId: media.id, status: UploadStatus.waiting)),
- );
-
- await _googleDriveService.uploadInGoogleDrive(
- media: media,
- folderID: _backUpFolderId!,
- );
- state = state.copyWith(
- uploadingMedias: state.uploadingMedias.toList()
- ..removeWhere((element) => element.mediaId == media.id),
- medias: state.medias.map((key, value) {
- value.updateElement(
- newElement: media.copyWith(
- sources: media.sources.toList()
- ..add(AppMediaSource.googleDrive)),
- oldElement: media);
- return MapEntry(key, value);
- }),
- );
- } catch (error) {
- if (error is BackUpFolderNotFound) {
- _backUpFolderId = await _googleDriveService.getBackupFolderId();
- _autoBackUpMedias();
- }
- state = state.copyWith(
- error: error,
- uploadingMedias: state.uploadingMedias.toList()
- ..removeWhere((element) => element.mediaId == media.id),
+ medias: _sortMedias(
+ medias: _removeGoogleDriveRefFromMedias(state.medias)),
);
}
- }
- _isAutoBackUpWorking = false;
- }
-
- Future updateAutoBackUpStatus(bool status) async {
- _isAutoBackUpEnabled = status;
- if (_isAutoBackUpEnabled && !_isAutoBackUpWorking) {
- _autoBackUpMedias();
- }
+ });
+ _loadInitialMedia();
}
- Future loadMedias() async {
- if (_loading == true) return;
- _loading = true;
- _googleDriveMedias = [];
- _localMedias = [];
- state = state.copyWith(loading: state.medias.isEmpty, error: null);
- try {
- _localMediaCount ??= await _getLocalMediaCount();
- if (_localMediaCount != null) {
- await Future.wait([
- _getGoogleDriveMedias(),
- _getLocalMedias(),
- ]);
- } else {
- await _getGoogleDriveMedias();
- }
- state = state.copyWith(
- loading: false,
- medias: _sortMedias(medias: _getAllMedias()),
- hasLocalMediaAccess: _localMediaCount != null,
- );
- } catch (error) {
- state = state.copyWith(loading: false, error: error);
- } finally {
- _loading = false;
+ void _loadInitialMedia() async {
+ state = state.copyWith(loading: true, error: null);
+ final hasAccess = await _localMediaService.requestPermission();
+ state = state.copyWith(hasLocalMediaAccess: hasAccess, loading: false);
+ if (hasAccess) {
+ await Future.wait([loadLocalMedia(), loadGoogleDriveMedia()]);
+ } else {
+ await loadGoogleDriveMedia();
}
}
@@ -184,60 +74,96 @@ class HomeViewStateNotifier extends StateNotifier {
}
}
- List _getAllMedias() {
- 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],
- thumbnailLink: googleDriveMedia.thumbnailLink,
- webContentLink: googleDriveMedia.webContentLink,
- ));
- });
+ Future loadLocalMedia({bool append = false}) async {
+ if (_isLocalMediaLoading || (_isMaxLocalMediaLoaded && append)) return;
+ if (_isMaxLocalMediaLoaded && !append) {
+ _isMaxLocalMediaLoaded = false;
}
+ _isLocalMediaLoading = true;
+ try {
+ state = state.copyWith(loading: state.medias.isEmpty, error: null);
- return [..._localMedias, ..._googleDriveMedias, ...commonMedias];
- }
+ final loadedLocalMediaCount = state.medias
+ .valuesWhere((e) => e.sources.contains(AppMediaSource.local))
+ .length;
- Map> _sortMedias({required List medias}) {
- medias.sort((a, b) => (b.createdTime ?? DateTime.now())
- .compareTo(a.createdTime ?? DateTime.now()));
- return groupBy(
- medias,
- (AppMedia media) =>
- media.createdTime?.dateOnly ?? DateTime.now().dateOnly,
- );
- }
+ final localMedia = await _localMediaService.getLocalMedia(
+ start: append ? loadedLocalMediaCount : 0,
+ end: append
+ ? loadedLocalMediaCount + 30
+ : loadedLocalMediaCount < 30
+ ? 30
+ : loadedLocalMediaCount,
+ );
+ final mergedMedia = _mergeCommonMedia(
+ localMedias: localMedia,
+ googleDriveMedias: _uploadedMedia,
+ );
- Future _getGoogleDriveMedias() async {
- if (_authService.signedInWithGoogle) {
- _backUpFolderId ??= await _googleDriveService.getBackupFolderId();
- _googleDriveMedias = await _googleDriveService.getDriveMedias(
- backUpFolderId: _backUpFolderId!);
- }
- }
+ final lastLocalMedia = mergedMedia.last;
- Future _getLocalMediaCount() async {
- final hasAccess = await _localMediaService.requestPermission();
- if (hasAccess) {
- return await _localMediaService.getMediaCount();
+ state = state.copyWith(
+ medias: _sortMedias(
+ medias: append
+ ? [
+ ...state.medias.values.expand((element) => element).toList(),
+ ...mergedMedia
+ ]
+ : mergedMedia,
+ ),
+ loading: false,
+ lastLocalMediaId: lastLocalMedia.id,
+ );
+ } catch (e) {
+ if (e is NoElementError) {
+ _isMaxLocalMediaLoaded = true;
+ state = state.copyWith(loading: false);
+ } else {
+ state = state.copyWith(loading: false, error: e);
+ }
+ } finally {
+ _isLocalMediaLoading = false;
}
- return null;
}
- Future _getLocalMedias() async {
- if (_localMediaCount != null) {
- _localMedias = await _localMediaService.getLocalMedia(
- start: 0,
- end: _localMediaCount!,
+ Future loadGoogleDriveMedia() async {
+ if (state.googleAccount == null || _isGoogleDriveLoading) return;
+ _isGoogleDriveLoading = true;
+ try {
+ _backUpFolderId ??= await _googleDriveService.getBackupFolderId();
+
+ state = state.copyWith(loading: state.medias.isEmpty, error: null);
+ final driveMedias = await _googleDriveService.getDriveMedias(
+ backUpFolderId: _backUpFolderId!,
+ );
+
+ // Separate media by its local existence
+ List googleDriveMedia = [];
+ List uploadedMedia = [];
+ for (var media in driveMedias) {
+ if (await media.isExist) {
+ uploadedMedia.add(media);
+ } else {
+ googleDriveMedia.add(media);
+ }
+ }
+ _uploadedMedia = uploadedMedia;
+
+ //override google drive media if exist.
+ state = state.copyWith(
+ medias: _sortMedias(medias: [
+ ..._mergeCommonMedia(
+ localMedias: _removeGoogleDriveRefFromMedias(state.medias),
+ googleDriveMedias: uploadedMedia,
+ ),
+ ...googleDriveMedia
+ ]),
+ loading: false,
);
+ } catch (e) {
+ state = state.copyWith(loading: false, error: e);
+ } finally {
+ _isGoogleDriveLoading = false;
}
}
@@ -251,8 +177,9 @@ class HomeViewStateNotifier extends StateNotifier {
try {
if (!_authService.signedInWithGoogle) {
await _authService.signInWithGoogle();
- loadMedias();
+ await loadGoogleDriveMedia();
}
+
List uploadingMedias = state.selectedMedias
.where((element) =>
!element.sources.contains(AppMediaSource.googleDrive))
@@ -265,6 +192,7 @@ class HomeViewStateNotifier extends StateNotifier {
.toList(),
error: null,
);
+
_backUpFolderId ??= await _googleDriveService.getBackupFolderId();
for (final media in uploadingMedias) {
@@ -276,6 +204,7 @@ class HomeViewStateNotifier extends StateNotifier {
oldElement: UploadProgress(
mediaId: media.id, status: UploadStatus.waiting)),
);
+
await _googleDriveService.uploadInGoogleDrive(
media: media,
folderID: _backUpFolderId!,
@@ -305,6 +234,71 @@ class HomeViewStateNotifier extends StateNotifier {
state = state.copyWith(error: error, uploadingMedias: []);
}
}
+
+ //Helper functions
+ List _mergeCommonMedia({
+ required List localMedias,
+ required List googleDriveMedias,
+ }) {
+ // If one of the lists is empty, return the other list.
+ if (googleDriveMedias.isEmpty) return localMedias;
+
+ // Convert the lists to mutable lists.
+ localMedias = localMedias.toList();
+ googleDriveMedias = googleDriveMedias.toList();
+
+ final mergedMedias = [];
+
+ // Add common media to mergedMedias and remove them from the lists.
+ for (AppMedia localMedia in localMedias.toList()) {
+ googleDriveMedias
+ .toList()
+ .where((googleDriveMedia) => googleDriveMedia.path == localMedia.path)
+ .forEach((googleDriveMedia) {
+ localMedias.removeWhere((media) => media.id == localMedia.id);
+
+ mergedMedias.add(localMedia.copyWith(
+ sources: [AppMediaSource.local, AppMediaSource.googleDrive],
+ thumbnailLink: googleDriveMedia.thumbnailLink,
+ ));
+ });
+ }
+
+ return [...mergedMedias, ...localMedias];
+ }
+
+ Map> _sortMedias({required List medias}) {
+ medias.sort((a, b) => (b.createdTime ?? DateTime.now())
+ .compareTo(a.createdTime ?? DateTime.now()));
+ return groupBy(
+ medias,
+ (AppMedia media) =>
+ media.createdTime?.dateOnly ?? DateTime.now().dateOnly,
+ );
+ }
+
+ List _removeGoogleDriveRefFromMedias(
+ Map> medias) {
+ final allMedias = medias.values.expand((element) => element).toList();
+ for (int index = 0; index < allMedias.length; index++) {
+ if (allMedias[index].sources.length > 1) {
+ allMedias[index] = allMedias[index].copyWith(
+ sources: allMedias[index].sources.toList()
+ ..remove(AppMediaSource.googleDrive),
+ thumbnailLink: null,
+ );
+ } else if (allMedias.contains(AppMediaSource.googleDrive)) {
+ allMedias.removeAt(index);
+ }
+ }
+ return allMedias;
+ }
+
+ @override
+ Future dispose() async {
+ await _googleAccountSubscription?.cancel();
+ super.dispose();
+ }
}
@freezed
@@ -314,6 +308,7 @@ class HomeViewState with _$HomeViewState {
@Default(false) bool hasLocalMediaAccess,
@Default(false) bool loading,
GoogleSignInAccount? googleAccount,
+ String? lastLocalMediaId,
@Default({}) Map> medias,
@Default([]) List selectedMedias,
@Default([]) List uploadingMedias,
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 c6b9bf1..74356fb 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
@@ -20,6 +20,7 @@ mixin _$HomeViewState {
bool get hasLocalMediaAccess => throw _privateConstructorUsedError;
bool get loading => throw _privateConstructorUsedError;
GoogleSignInAccount? get googleAccount => throw _privateConstructorUsedError;
+ String? get lastLocalMediaId => throw _privateConstructorUsedError;
Map> get medias =>
throw _privateConstructorUsedError;
List get selectedMedias => throw _privateConstructorUsedError;
@@ -42,6 +43,7 @@ abstract class $HomeViewStateCopyWith<$Res> {
bool hasLocalMediaAccess,
bool loading,
GoogleSignInAccount? googleAccount,
+ String? lastLocalMediaId,
Map> medias,
List selectedMedias,
List uploadingMedias});
@@ -64,6 +66,7 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState>
Object? hasLocalMediaAccess = null,
Object? loading = null,
Object? googleAccount = freezed,
+ Object? lastLocalMediaId = freezed,
Object? medias = null,
Object? selectedMedias = null,
Object? uploadingMedias = null,
@@ -82,6 +85,10 @@ class _$HomeViewStateCopyWithImpl<$Res, $Val extends HomeViewState>
? _value.googleAccount
: googleAccount // ignore: cast_nullable_to_non_nullable
as GoogleSignInAccount?,
+ lastLocalMediaId: freezed == lastLocalMediaId
+ ? _value.lastLocalMediaId
+ : lastLocalMediaId // ignore: cast_nullable_to_non_nullable
+ as String?,
medias: null == medias
? _value.medias
: medias // ignore: cast_nullable_to_non_nullable
@@ -111,6 +118,7 @@ abstract class _$$HomeViewStateImplCopyWith<$Res>
bool hasLocalMediaAccess,
bool loading,
GoogleSignInAccount? googleAccount,
+ String? lastLocalMediaId,
Map> medias,
List selectedMedias,
List uploadingMedias});
@@ -131,6 +139,7 @@ class __$$HomeViewStateImplCopyWithImpl<$Res>
Object? hasLocalMediaAccess = null,
Object? loading = null,
Object? googleAccount = freezed,
+ Object? lastLocalMediaId = freezed,
Object? medias = null,
Object? selectedMedias = null,
Object? uploadingMedias = null,
@@ -149,6 +158,10 @@ class __$$HomeViewStateImplCopyWithImpl<$Res>
? _value.googleAccount
: googleAccount // ignore: cast_nullable_to_non_nullable
as GoogleSignInAccount?,
+ lastLocalMediaId: freezed == lastLocalMediaId
+ ? _value.lastLocalMediaId
+ : lastLocalMediaId // ignore: cast_nullable_to_non_nullable
+ as String?,
medias: null == medias
? _value._medias
: medias // ignore: cast_nullable_to_non_nullable
@@ -173,6 +186,7 @@ class _$HomeViewStateImpl implements _HomeViewState {
this.hasLocalMediaAccess = false,
this.loading = false,
this.googleAccount,
+ this.lastLocalMediaId,
final Map> medias = const {},
final List selectedMedias = const [],
final List uploadingMedias = const []})
@@ -190,6 +204,8 @@ class _$HomeViewStateImpl implements _HomeViewState {
final bool loading;
@override
final GoogleSignInAccount? googleAccount;
+ @override
+ final String? lastLocalMediaId;
final Map> _medias;
@override
@JsonKey()
@@ -219,7 +235,7 @@ class _$HomeViewStateImpl implements _HomeViewState {
@override
String toString() {
- return 'HomeViewState(error: $error, hasLocalMediaAccess: $hasLocalMediaAccess, loading: $loading, googleAccount: $googleAccount, medias: $medias, selectedMedias: $selectedMedias, uploadingMedias: $uploadingMedias)';
+ return 'HomeViewState(error: $error, hasLocalMediaAccess: $hasLocalMediaAccess, loading: $loading, googleAccount: $googleAccount, lastLocalMediaId: $lastLocalMediaId, medias: $medias, selectedMedias: $selectedMedias, uploadingMedias: $uploadingMedias)';
}
@override
@@ -233,6 +249,8 @@ class _$HomeViewStateImpl implements _HomeViewState {
(identical(other.loading, loading) || other.loading == loading) &&
(identical(other.googleAccount, googleAccount) ||
other.googleAccount == googleAccount) &&
+ (identical(other.lastLocalMediaId, lastLocalMediaId) ||
+ other.lastLocalMediaId == lastLocalMediaId) &&
const DeepCollectionEquality().equals(other._medias, _medias) &&
const DeepCollectionEquality()
.equals(other._selectedMedias, _selectedMedias) &&
@@ -247,6 +265,7 @@ class _$HomeViewStateImpl implements _HomeViewState {
hasLocalMediaAccess,
loading,
googleAccount,
+ lastLocalMediaId,
const DeepCollectionEquality().hash(_medias),
const DeepCollectionEquality().hash(_selectedMedias),
const DeepCollectionEquality().hash(_uploadingMedias));
@@ -264,6 +283,7 @@ abstract class _HomeViewState implements HomeViewState {
final bool hasLocalMediaAccess,
final bool loading,
final GoogleSignInAccount? googleAccount,
+ final String? lastLocalMediaId,
final Map> medias,
final List selectedMedias,
final List uploadingMedias}) = _$HomeViewStateImpl;
@@ -277,6 +297,8 @@ abstract class _HomeViewState implements HomeViewState {
@override
GoogleSignInAccount? get googleAccount;
@override
+ String? get lastLocalMediaId;
+ @override
Map> get medias;
@override
List get selectedMedias;
diff --git a/app/lib/ui/flow/media_preview/image_preview/components /network_image_preview/network_image_preview.dart b/app/lib/ui/flow/media_preview/image_preview/components /network_image_preview/network_image_preview.dart
new file mode 100644
index 0000000..8543203
--- /dev/null
+++ b/app/lib/ui/flow/media_preview/image_preview/components /network_image_preview/network_image_preview.dart
@@ -0,0 +1,30 @@
+import 'dart:typed_data';
+import 'package:cloud_gallery/ui/flow/media_preview/image_preview/components%20/network_image_preview/network_image_preview_view_model.dart';
+import 'package:data/models/media/media.dart';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:style/indicators/circular_progress_indicator.dart';
+
+class NetworkImagePreview extends ConsumerWidget {
+ final AppMedia media;
+
+ const NetworkImagePreview({super.key, required this.media});
+
+ @override
+ Widget build(BuildContext context, WidgetRef ref) {
+ final state = ref.watch(networkImagePreviewStateNotifierProvider);
+
+ if (state.loading) {
+ return const Center(child: AppCircularProgressIndicator());
+ } else if (state.mediaBytes != null) {
+ return Hero(
+ tag: media,
+ child: Image.memory(Uint8List.fromList(state.mediaBytes!),
+ fit: BoxFit.fitWidth),
+ );
+ } else if (state.error != null) {
+ return const Center(child: Text('Error'));
+ }
+ return const Placeholder();
+ }
+}
diff --git a/app/lib/ui/flow/media_preview/image_preview/components /network_image_preview/network_image_preview_view_model.dart b/app/lib/ui/flow/media_preview/image_preview/components /network_image_preview/network_image_preview_view_model.dart
new file mode 100644
index 0000000..571b17f
--- /dev/null
+++ b/app/lib/ui/flow/media_preview/image_preview/components /network_image_preview/network_image_preview_view_model.dart
@@ -0,0 +1,44 @@
+import 'package:data/models/media_content/media_content.dart';
+import 'package:data/services/google_drive_service.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+
+part 'network_image_preview_view_model.freezed.dart';
+
+final networkImagePreviewStateNotifierProvider = StateNotifierProvider<
+ NetworkImagePreviewStateNotifier, NetworkImagePreviewState>((ref) {
+ return NetworkImagePreviewStateNotifier(ref.read(googleDriveServiceProvider));
+});
+
+class NetworkImagePreviewStateNotifier
+ extends StateNotifier {
+ final GoogleDriveService _googleDriveServices;
+
+ NetworkImagePreviewStateNotifier(this._googleDriveServices)
+ : super(const NetworkImagePreviewState());
+
+ Future loadImage(String mediaId) async {
+ try {
+ state = state.copyWith(loading: true, error: null);
+ final mediaContent = await _googleDriveServices.fetchMediaBytes(mediaId);
+ final mediaByte = [];
+ await for (var mediaBytes in mediaContent.stream) {
+ mediaByte.addAll(mediaBytes);
+ }
+ state = state.copyWith(
+ mediaContent: mediaContent, mediaBytes: mediaByte, loading: false);
+ } catch (error) {
+ state = state.copyWith(error: error, loading: false);
+ }
+ }
+}
+
+@freezed
+class NetworkImagePreviewState with _$NetworkImagePreviewState {
+ const factory NetworkImagePreviewState({
+ @Default(false) bool loading,
+ AppMediaContent? mediaContent,
+ List? mediaBytes,
+ Object? error,
+ }) = _NetworkImagePreviewState;
+}
diff --git a/app/lib/ui/flow/media_preview/image_preview/components /network_image_preview/network_image_preview_view_model.freezed.dart b/app/lib/ui/flow/media_preview/image_preview/components /network_image_preview/network_image_preview_view_model.freezed.dart
new file mode 100644
index 0000000..b953966
--- /dev/null
+++ b/app/lib/ui/flow/media_preview/image_preview/components /network_image_preview/network_image_preview_view_model.freezed.dart
@@ -0,0 +1,229 @@
+// 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 'network_image_preview_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#adding-getters-and-methods-to-our-models');
+
+/// @nodoc
+mixin _$NetworkImagePreviewState {
+ bool get loading => throw _privateConstructorUsedError;
+ AppMediaContent? get mediaContent => throw _privateConstructorUsedError;
+ List? get mediaBytes => throw _privateConstructorUsedError;
+ Object? get error => throw _privateConstructorUsedError;
+
+ @JsonKey(ignore: true)
+ $NetworkImagePreviewStateCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $NetworkImagePreviewStateCopyWith<$Res> {
+ factory $NetworkImagePreviewStateCopyWith(NetworkImagePreviewState value,
+ $Res Function(NetworkImagePreviewState) then) =
+ _$NetworkImagePreviewStateCopyWithImpl<$Res, NetworkImagePreviewState>;
+ @useResult
+ $Res call(
+ {bool loading,
+ AppMediaContent? mediaContent,
+ List? mediaBytes,
+ Object? error});
+
+ $AppMediaContentCopyWith<$Res>? get mediaContent;
+}
+
+/// @nodoc
+class _$NetworkImagePreviewStateCopyWithImpl<$Res,
+ $Val extends NetworkImagePreviewState>
+ implements $NetworkImagePreviewStateCopyWith<$Res> {
+ _$NetworkImagePreviewStateCopyWithImpl(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? mediaContent = freezed,
+ Object? mediaBytes = freezed,
+ Object? error = freezed,
+ }) {
+ return _then(_value.copyWith(
+ loading: null == loading
+ ? _value.loading
+ : loading // ignore: cast_nullable_to_non_nullable
+ as bool,
+ mediaContent: freezed == mediaContent
+ ? _value.mediaContent
+ : mediaContent // ignore: cast_nullable_to_non_nullable
+ as AppMediaContent?,
+ mediaBytes: freezed == mediaBytes
+ ? _value.mediaBytes
+ : mediaBytes // ignore: cast_nullable_to_non_nullable
+ as List?,
+ error: freezed == error ? _value.error : error,
+ ) as $Val);
+ }
+
+ @override
+ @pragma('vm:prefer-inline')
+ $AppMediaContentCopyWith<$Res>? get mediaContent {
+ if (_value.mediaContent == null) {
+ return null;
+ }
+
+ return $AppMediaContentCopyWith<$Res>(_value.mediaContent!, (value) {
+ return _then(_value.copyWith(mediaContent: value) as $Val);
+ });
+ }
+}
+
+/// @nodoc
+abstract class _$$NetworkImagePreviewStateImplCopyWith<$Res>
+ implements $NetworkImagePreviewStateCopyWith<$Res> {
+ factory _$$NetworkImagePreviewStateImplCopyWith(
+ _$NetworkImagePreviewStateImpl value,
+ $Res Function(_$NetworkImagePreviewStateImpl) then) =
+ __$$NetworkImagePreviewStateImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call(
+ {bool loading,
+ AppMediaContent? mediaContent,
+ List? mediaBytes,
+ Object? error});
+
+ @override
+ $AppMediaContentCopyWith<$Res>? get mediaContent;
+}
+
+/// @nodoc
+class __$$NetworkImagePreviewStateImplCopyWithImpl<$Res>
+ extends _$NetworkImagePreviewStateCopyWithImpl<$Res,
+ _$NetworkImagePreviewStateImpl>
+ implements _$$NetworkImagePreviewStateImplCopyWith<$Res> {
+ __$$NetworkImagePreviewStateImplCopyWithImpl(
+ _$NetworkImagePreviewStateImpl _value,
+ $Res Function(_$NetworkImagePreviewStateImpl) _then)
+ : super(_value, _then);
+
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? loading = null,
+ Object? mediaContent = freezed,
+ Object? mediaBytes = freezed,
+ Object? error = freezed,
+ }) {
+ return _then(_$NetworkImagePreviewStateImpl(
+ loading: null == loading
+ ? _value.loading
+ : loading // ignore: cast_nullable_to_non_nullable
+ as bool,
+ mediaContent: freezed == mediaContent
+ ? _value.mediaContent
+ : mediaContent // ignore: cast_nullable_to_non_nullable
+ as AppMediaContent?,
+ mediaBytes: freezed == mediaBytes
+ ? _value._mediaBytes
+ : mediaBytes // ignore: cast_nullable_to_non_nullable
+ as List?,
+ error: freezed == error ? _value.error : error,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$NetworkImagePreviewStateImpl implements _NetworkImagePreviewState {
+ const _$NetworkImagePreviewStateImpl(
+ {this.loading = false,
+ this.mediaContent,
+ final List? mediaBytes,
+ this.error})
+ : _mediaBytes = mediaBytes;
+
+ @override
+ @JsonKey()
+ final bool loading;
+ @override
+ final AppMediaContent? mediaContent;
+ final List? _mediaBytes;
+ @override
+ List? get mediaBytes {
+ final value = _mediaBytes;
+ if (value == null) return null;
+ if (_mediaBytes is EqualUnmodifiableListView) return _mediaBytes;
+ // ignore: implicit_dynamic_type
+ return EqualUnmodifiableListView(value);
+ }
+
+ @override
+ final Object? error;
+
+ @override
+ String toString() {
+ return 'NetworkImagePreviewState(loading: $loading, mediaContent: $mediaContent, mediaBytes: $mediaBytes, error: $error)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$NetworkImagePreviewStateImpl &&
+ (identical(other.loading, loading) || other.loading == loading) &&
+ (identical(other.mediaContent, mediaContent) ||
+ other.mediaContent == mediaContent) &&
+ const DeepCollectionEquality()
+ .equals(other._mediaBytes, _mediaBytes) &&
+ const DeepCollectionEquality().equals(other.error, error));
+ }
+
+ @override
+ int get hashCode => Object.hash(
+ runtimeType,
+ loading,
+ mediaContent,
+ const DeepCollectionEquality().hash(_mediaBytes),
+ const DeepCollectionEquality().hash(error));
+
+ @JsonKey(ignore: true)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$NetworkImagePreviewStateImplCopyWith<_$NetworkImagePreviewStateImpl>
+ get copyWith => __$$NetworkImagePreviewStateImplCopyWithImpl<
+ _$NetworkImagePreviewStateImpl>(this, _$identity);
+}
+
+abstract class _NetworkImagePreviewState implements NetworkImagePreviewState {
+ const factory _NetworkImagePreviewState(
+ {final bool loading,
+ final AppMediaContent? mediaContent,
+ final List? mediaBytes,
+ final Object? error}) = _$NetworkImagePreviewStateImpl;
+
+ @override
+ bool get loading;
+ @override
+ AppMediaContent? get mediaContent;
+ @override
+ List? get mediaBytes;
+ @override
+ Object? get error;
+ @override
+ @JsonKey(ignore: true)
+ _$$NetworkImagePreviewStateImplCopyWith<_$NetworkImagePreviewStateImpl>
+ get copyWith => throw _privateConstructorUsedError;
+}
diff --git a/app/lib/ui/flow/media_preview/image_preview/image_preview_screen.dart b/app/lib/ui/flow/media_preview/image_preview/image_preview_screen.dart
new file mode 100644
index 0000000..efe0721
--- /dev/null
+++ b/app/lib/ui/flow/media_preview/image_preview/image_preview_screen.dart
@@ -0,0 +1,128 @@
+import 'dart:io';
+import 'dart:math';
+import 'package:cloud_gallery/ui/flow/media_preview/image_preview/components%20/network_image_preview/network_image_preview.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/app_page.dart';
+import '../../../../domain/extensions/widget_extensions.dart';
+import 'components /network_image_preview/network_image_preview_view_model.dart';
+
+class ImagePreviewScreen extends ConsumerStatefulWidget {
+ final AppMedia media;
+
+ const ImagePreviewScreen({
+ super.key,
+ required this.media,
+ });
+
+ @override
+ ConsumerState createState() => _ImagePreviewScreenState();
+}
+
+class _ImagePreviewScreenState extends ConsumerState {
+ final _transformationController = TransformationController();
+ double _translateY = 0;
+ double _scale = 1;
+
+ late NetworkImagePreviewStateNotifier notifier;
+
+ @override
+ void initState() {
+ if (!widget.media.sources.contains(AppMediaSource.local)) {
+ notifier = ref.read(networkImagePreviewStateNotifierProvider.notifier);
+ runPostFrame(() {
+ notifier.loadImage(widget.media.id);
+ });
+ }
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onVerticalDragStart: (details) {
+ _translateY = 0;
+ _scale = 1;
+ },
+ onVerticalDragUpdate: (details) {
+ if (_transformationController.value.getMaxScaleOnAxis() > 1) {
+ return;
+ }
+
+ setState(() {
+ _translateY = max(0, _translateY + (details.primaryDelta ?? 0));
+ _scale = 1 - (_translateY / 1000);
+ });
+ },
+ onVerticalDragEnd: (details) {
+ if (_transformationController.value.getMaxScaleOnAxis() > 1 ||
+ _translateY == 0) {
+ return;
+ }
+
+ final velocity = details.primaryVelocity ?? 0;
+ if (velocity > 1000) {
+ Navigator.of(context).pop();
+ } else if (_translateY > 100) {
+ Navigator.of(context).pop();
+ } else {
+ setState(() {
+ _translateY = 0;
+ _scale = 1;
+ });
+ }
+ },
+ child: AppPage(
+ backgroundColor: _scale == 1
+ ? context.colorScheme.surface
+ : context.colorScheme.surface.withOpacity(_scale / 2),
+ body: Stack(
+ children: [
+ Transform.translate(
+ offset: Offset(0, _translateY),
+ child: Transform.scale(
+ scale: _scale,
+ child: InteractiveViewer(
+ transformationController: _transformationController,
+ maxScale: 100,
+ child: Center(
+ child: SizedBox(
+ width: double.infinity,
+ child: widget.media.sources.contains(AppMediaSource.local)
+ ? _displayLocalImage(context: context)
+ : NetworkImagePreview(
+ media: widget.media,
+ ),
+ ),
+ ),
+ ),
+ ),
+ ),
+ if (_scale == 1)
+ Column(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ AdaptiveAppBar(
+ iosTransitionBetweenRoutes: false,
+ text: widget.media.name ?? '',
+ ),
+ ],
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _displayLocalImage({required BuildContext context}) {
+ return Hero(
+ tag: widget.media,
+ child: Image.file(
+ File(widget.media.path),
+ fit: BoxFit.contain,
+ ),
+ );
+ }
+}
diff --git a/app/lib/ui/flow/media_preview/image_preview_screen.dart b/app/lib/ui/flow/media_preview/image_preview_screen.dart
deleted file mode 100644
index 509be70..0000000
--- a/app/lib/ui/flow/media_preview/image_preview_screen.dart
+++ /dev/null
@@ -1,63 +0,0 @@
-import 'dart:io';
-import 'package:cached_network_image/cached_network_image.dart';
-import 'package:flutter/material.dart';
-import 'package:style/extensions/context_extensions.dart';
-import 'package:style/indicators/circular_progress_indicator.dart';
-import '../../../components/app_page.dart';
-
-class ImagePreviewScreen extends StatefulWidget {
- final String url;
- final bool isLocal;
- final String heroTag;
- final bool showCloseBtn;
-
- const ImagePreviewScreen({
- super.key,
- required this.url,
- required this.isLocal,
- required this.heroTag,
- this.showCloseBtn = true,
- });
-
- @override
- State createState() => _ImagePreviewScreenState();
-}
-
-class _ImagePreviewScreenState extends State {
- @override
- Widget build(BuildContext context) {
- return AppPage(
- title: '',
- leading: Visibility(
- visible: widget.showCloseBtn,
- child: IconButton(
- onPressed: () => Navigator.of(context).pop(),
- icon: Icon(
- Icons.close_rounded,
- color: context.colorScheme.textSecondary,
- ),
- ),
- ),
- body: InteractiveViewer(
- child: Center(
- child: Hero(
- tag: widget.heroTag,
- child: SizedBox(
- width: double.infinity,
- child: widget.isLocal
- ? Image.file(File.fromUri(Uri.parse(widget.url)),
- fit: BoxFit.fitWidth)
- : CachedNetworkImage(
- imageUrl: widget.url,
- fit: BoxFit.fitWidth,
- progressIndicatorBuilder:
- (context, child, loadingProgress) =>
- const AppCircularProgressIndicator(),
- ),
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/app/lib/ui/flow/media_preview/media_preview.dart b/app/lib/ui/flow/media_preview/media_preview.dart
index 63a67de..ec95377 100644
--- a/app/lib/ui/flow/media_preview/media_preview.dart
+++ b/app/lib/ui/flow/media_preview/media_preview.dart
@@ -6,21 +6,12 @@ import 'package:flutter/cupertino.dart';
class AppMediaView {
static void showPreview(
{required BuildContext context,
- required AppMedia media,
- required String heroTag}) {
+ required AppMedia media}) {
if (media.type.isImage) {
- final path = media.sources.contains(AppMediaSource.local)
- ? media.path
- : media.webContentLink;
- AppRouter.imagePreview(
- path: path!,
- heroTag: heroTag,
- isLocal: media.sources.contains(AppMediaSource.local))
- .push(context);
+ AppRouter.imagePreview(media: media).push(context);
} else if (media.type.isVideo) {
AppRouter.videoPreview(
path: media.path,
- heroTag: heroTag,
isLocal: media.sources.contains(AppMediaSource.local))
.push(context);
} else {
diff --git a/app/lib/ui/navigation/app_router.dart b/app/lib/ui/navigation/app_router.dart
index a5f87e5..dcc76b7 100644
--- a/app/lib/ui/navigation/app_router.dart
+++ b/app/lib/ui/navigation/app_router.dart
@@ -1,7 +1,9 @@
import 'package:cloud_gallery/ui/flow/accounts/accounts_screen.dart';
-import 'package:cloud_gallery/ui/flow/media_preview/image_preview_screen.dart';
+import 'package:cloud_gallery/ui/flow/media_preview/image_preview/image_preview_screen.dart';
import 'package:cloud_gallery/ui/flow/media_preview/video_preview_screen.dart';
import 'package:cloud_gallery/ui/flow/onboard/onboard_screen.dart';
+import 'package:data/models/media/media.dart';
+import 'package:flutter/cupertino.dart';
import 'package:go_router/go_router.dart';
import '../flow/home/home_screen.dart';
import 'app_route.dart';
@@ -22,20 +24,12 @@ class AppRouter {
builder: (context) => const AccountsScreen(),
);
- static AppRoute imagePreview(
- {required String path,
- required String heroTag,
- required bool isLocal}) =>
- AppRoute(
+ static AppRoute imagePreview({required AppMedia media}) => AppRoute(
AppRoutePath.imagePreview,
- builder: (context) =>
- ImagePreviewScreen(url: path, isLocal: isLocal, heroTag: heroTag),
+ builder: (context) => ImagePreviewScreen(media: media),
);
- static AppRoute videoPreview(
- {required String path,
- required String heroTag,
- required bool isLocal}) =>
+ static AppRoute videoPreview({required String path, required bool isLocal}) =>
AppRoute(
AppRoutePath.videoPreview,
builder: (context) => const VideoPreviewScreen(),
@@ -47,7 +41,16 @@ class AppRouter {
accounts.goRoute,
GoRoute(
path: AppRoutePath.imagePreview,
- builder: (context, state) => state.widget(context),
+ pageBuilder: (context, state) {
+ return CustomTransitionPage(
+ opaque: false,
+ key: state.pageKey,
+ child: state.widget(context),
+ transitionsBuilder: (context, animation, secondaryAnimation, child) {
+ return FadeTransition(opacity: animation, child: child);
+ },
+ );
+ },
),
GoRoute(
path: AppRoutePath.videoPreview,
diff --git a/app/pubspec.lock b/app/pubspec.lock
index f256aad..6ddf9e0 100644
--- a/app/pubspec.lock
+++ b/app/pubspec.lock
@@ -1156,10 +1156,10 @@ packages:
dependency: "direct main"
description:
name: video_player
- sha256: fbf28ce8bcfe709ad91b5789166c832cb7a684d14f571a81891858fefb5bb1c2
+ sha256: afc65f4b8bcb2c188f64a591f84fb471f4f2e19fc607c65fd8d2f8fedb3dec23
url: "https://pub.dev"
source: hosted
- version: "2.8.2"
+ version: "2.8.3"
video_player_android:
dependency: transitive
description:
diff --git a/app/pubspec.yaml b/app/pubspec.yaml
index dd9198a..3989ba8 100644
--- a/app/pubspec.yaml
+++ b/app/pubspec.yaml
@@ -28,7 +28,7 @@ dependencies:
flutter_svg: ^2.0.9
fluttertoast: ^8.2.4
cached_network_image: ^3.3.1
- video_player: ^2.8.2
+ video_player: ^2.8.3
visibility_detector: ^0.4.0+2
flutter_displaymode: ^0.6.0
diff --git a/data/.flutter-plugins-dependencies b/data/.flutter-plugins-dependencies
index 24d471e..1ebfe74 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.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
+{"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-26 16:38:34.855593","version":"3.19.3"}
\ No newline at end of file
diff --git a/data/lib/errors/app_error.dart b/data/lib/errors/app_error.dart
index 46f196e..752d94c 100644
--- a/data/lib/errors/app_error.dart
+++ b/data/lib/errors/app_error.dart
@@ -48,6 +48,11 @@ class BackUpFolderNotFound extends AppError {
message: "Back up folder not found");
}
+class NoElementError extends AppError {
+ const NoElementError()
+ : super(message: "No element found!");
+}
+
class SomethingWentWrongError extends AppError {
const SomethingWentWrongError({String? message, String? statusCode})
: super(
diff --git a/data/lib/models/media/media.dart b/data/lib/models/media/media.dart
index aa34a32..8b642eb 100644
--- a/data/lib/models/media/media.dart
+++ b/data/lib/models/media/media.dart
@@ -1,3 +1,4 @@
+import 'dart:io';
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;
@@ -13,6 +14,16 @@ class UploadProgress {
final UploadStatus status;
UploadProgress({required this.mediaId, required this.status});
+
+ @override
+ bool operator ==(Object other) {
+ return other is UploadProgress &&
+ other.mediaId == mediaId &&
+ other.status == status;
+ }
+
+ @override
+ int get hashCode => mediaId.hashCode ^ status.hashCode;
}
enum AppMediaType {
@@ -85,7 +96,6 @@ class AppMedia with _$AppMedia {
const factory AppMedia({
required String id,
String? name,
- String? webContentLink,
required String path,
String? thumbnailLink,
double? displayHeight,
@@ -108,7 +118,7 @@ class AppMedia with _$AppMedia {
factory AppMedia.fromGoogleDriveFile(drive.File file) {
final type = AppMediaType.getType(
mimeType: file.mimeType,
- location: file.webContentLink ?? file.description ?? '');
+ location: file.description ?? '');
final height = type.isImage
? file.imageMediaMetadata?.height?.toDouble()
@@ -132,7 +142,6 @@ class AppMedia with _$AppMedia {
: null;
return AppMedia(
id: file.id!,
- webContentLink: file.webContentLink,
path: file.description ?? file.thumbnailLink ?? '',
thumbnailLink: file.thumbnailLink,
name: file.name,
@@ -179,3 +188,9 @@ class AppMedia with _$AppMedia {
);
}
}
+
+extension AppMediaExtension on AppMedia {
+ Future get isExist async {
+ return await File(path).exists();
+ }
+}
diff --git a/data/lib/models/media/media.freezed.dart b/data/lib/models/media/media.freezed.dart
index 59eb019..a5ee01b 100644
--- a/data/lib/models/media/media.freezed.dart
+++ b/data/lib/models/media/media.freezed.dart
@@ -22,7 +22,6 @@ AppMedia _$AppMediaFromJson(Map json) {
mixin _$AppMedia {
String get id => throw _privateConstructorUsedError;
String? get name => throw _privateConstructorUsedError;
- String? get webContentLink => throw _privateConstructorUsedError;
String get path => throw _privateConstructorUsedError;
String? get thumbnailLink => throw _privateConstructorUsedError;
double? get displayHeight => throw _privateConstructorUsedError;
@@ -52,7 +51,6 @@ abstract class $AppMediaCopyWith<$Res> {
$Res call(
{String id,
String? name,
- String? webContentLink,
String path,
String? thumbnailLink,
double? displayHeight,
@@ -84,7 +82,6 @@ class _$AppMediaCopyWithImpl<$Res, $Val extends AppMedia>
$Res call({
Object? id = null,
Object? name = freezed,
- Object? webContentLink = freezed,
Object? path = null,
Object? thumbnailLink = freezed,
Object? displayHeight = freezed,
@@ -109,10 +106,6 @@ class _$AppMediaCopyWithImpl<$Res, $Val extends AppMedia>
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
- webContentLink: freezed == webContentLink
- ? _value.webContentLink
- : webContentLink // ignore: cast_nullable_to_non_nullable
- as String?,
path: null == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
@@ -184,7 +177,6 @@ abstract class _$$AppMediaImplCopyWith<$Res>
$Res call(
{String id,
String? name,
- String? webContentLink,
String path,
String? thumbnailLink,
double? displayHeight,
@@ -214,7 +206,6 @@ class __$$AppMediaImplCopyWithImpl<$Res>
$Res call({
Object? id = null,
Object? name = freezed,
- Object? webContentLink = freezed,
Object? path = null,
Object? thumbnailLink = freezed,
Object? displayHeight = freezed,
@@ -239,10 +230,6 @@ class __$$AppMediaImplCopyWithImpl<$Res>
? _value.name
: name // ignore: cast_nullable_to_non_nullable
as String?,
- webContentLink: freezed == webContentLink
- ? _value.webContentLink
- : webContentLink // ignore: cast_nullable_to_non_nullable
- as String?,
path: null == path
? _value.path
: path // ignore: cast_nullable_to_non_nullable
@@ -309,7 +296,6 @@ class _$AppMediaImpl implements _AppMedia {
const _$AppMediaImpl(
{required this.id,
this.name,
- this.webContentLink,
required this.path,
this.thumbnailLink,
this.displayHeight,
@@ -334,8 +320,6 @@ class _$AppMediaImpl implements _AppMedia {
@override
final String? name;
@override
- final String? webContentLink;
- @override
final String path;
@override
final String? thumbnailLink;
@@ -372,7 +356,7 @@ class _$AppMediaImpl implements _AppMedia {
@override
String toString() {
- return 'AppMedia(id: $id, name: $name, webContentLink: $webContentLink, path: $path, thumbnailLink: $thumbnailLink, displayHeight: $displayHeight, displayWidth: $displayWidth, type: $type, mimeType: $mimeType, createdTime: $createdTime, modifiedTime: $modifiedTime, orientation: $orientation, size: $size, videoDuration: $videoDuration, latitude: $latitude, longitude: $longitude, sources: $sources)';
+ return 'AppMedia(id: $id, name: $name, path: $path, thumbnailLink: $thumbnailLink, 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
@@ -382,8 +366,6 @@ class _$AppMediaImpl implements _AppMedia {
other is _$AppMediaImpl &&
(identical(other.id, id) || other.id == id) &&
(identical(other.name, name) || other.name == name) &&
- (identical(other.webContentLink, webContentLink) ||
- other.webContentLink == webContentLink) &&
(identical(other.path, path) || other.path == path) &&
(identical(other.thumbnailLink, thumbnailLink) ||
other.thumbnailLink == thumbnailLink) &&
@@ -416,7 +398,6 @@ class _$AppMediaImpl implements _AppMedia {
runtimeType,
id,
name,
- webContentLink,
path,
thumbnailLink,
displayHeight,
@@ -450,7 +431,6 @@ abstract class _AppMedia implements AppMedia {
const factory _AppMedia(
{required final String id,
final String? name,
- final String? webContentLink,
required final String path,
final String? thumbnailLink,
final double? displayHeight,
@@ -474,8 +454,6 @@ abstract class _AppMedia implements AppMedia {
@override
String? get name;
@override
- String? get webContentLink;
- @override
String get path;
@override
String? get thumbnailLink;
diff --git a/data/lib/models/media/media.g.dart b/data/lib/models/media/media.g.dart
index 739b78d..d99a234 100644
--- a/data/lib/models/media/media.g.dart
+++ b/data/lib/models/media/media.g.dart
@@ -10,7 +10,6 @@ _$AppMediaImpl _$$AppMediaImplFromJson(Map json) =>
_$AppMediaImpl(
id: json['id'] as String,
name: json['name'] as String?,
- webContentLink: json['webContentLink'] as String?,
path: json['path'] as String,
thumbnailLink: json['thumbnailLink'] as String?,
displayHeight: (json['displayHeight'] as num?)?.toDouble(),
@@ -41,7 +40,6 @@ Map _$$AppMediaImplToJson(_$AppMediaImpl instance) =>
{
'id': instance.id,
'name': instance.name,
- 'webContentLink': instance.webContentLink,
'path': instance.path,
'thumbnailLink': instance.thumbnailLink,
'displayHeight': instance.displayHeight,
diff --git a/data/lib/models/media_content/media_content.dart b/data/lib/models/media_content/media_content.dart
new file mode 100644
index 0000000..882786d
--- /dev/null
+++ b/data/lib/models/media_content/media_content.dart
@@ -0,0 +1,18 @@
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:googleapis/drive/v3.dart' as drive show Media;
+
+part 'media_content.freezed.dart';
+
+@freezed
+class AppMediaContent with _$AppMediaContent {
+ const factory AppMediaContent(
+ {required Stream> stream,
+ required int? length,
+ required String contentType}) = _AppMediaContent;
+
+ factory AppMediaContent.fromGoogleDrive(drive.Media media) => AppMediaContent(
+ stream: media.stream,
+ length: media.length,
+ contentType: media.contentType,
+ );
+}
diff --git a/data/lib/models/media_content/media_content.freezed.dart b/data/lib/models/media_content/media_content.freezed.dart
new file mode 100644
index 0000000..7bd4880
--- /dev/null
+++ b/data/lib/models/media_content/media_content.freezed.dart
@@ -0,0 +1,170 @@
+// 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 'media_content.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#adding-getters-and-methods-to-our-models');
+
+/// @nodoc
+mixin _$AppMediaContent {
+ Stream> get stream => throw _privateConstructorUsedError;
+ int? get length => throw _privateConstructorUsedError;
+ String get contentType => throw _privateConstructorUsedError;
+
+ @JsonKey(ignore: true)
+ $AppMediaContentCopyWith get copyWith =>
+ throw _privateConstructorUsedError;
+}
+
+/// @nodoc
+abstract class $AppMediaContentCopyWith<$Res> {
+ factory $AppMediaContentCopyWith(
+ AppMediaContent value, $Res Function(AppMediaContent) then) =
+ _$AppMediaContentCopyWithImpl<$Res, AppMediaContent>;
+ @useResult
+ $Res call({Stream> stream, int? length, String contentType});
+}
+
+/// @nodoc
+class _$AppMediaContentCopyWithImpl<$Res, $Val extends AppMediaContent>
+ implements $AppMediaContentCopyWith<$Res> {
+ _$AppMediaContentCopyWithImpl(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? stream = null,
+ Object? length = freezed,
+ Object? contentType = null,
+ }) {
+ return _then(_value.copyWith(
+ stream: null == stream
+ ? _value.stream
+ : stream // ignore: cast_nullable_to_non_nullable
+ as Stream>,
+ length: freezed == length
+ ? _value.length
+ : length // ignore: cast_nullable_to_non_nullable
+ as int?,
+ contentType: null == contentType
+ ? _value.contentType
+ : contentType // ignore: cast_nullable_to_non_nullable
+ as String,
+ ) as $Val);
+ }
+}
+
+/// @nodoc
+abstract class _$$AppMediaContentImplCopyWith<$Res>
+ implements $AppMediaContentCopyWith<$Res> {
+ factory _$$AppMediaContentImplCopyWith(_$AppMediaContentImpl value,
+ $Res Function(_$AppMediaContentImpl) then) =
+ __$$AppMediaContentImplCopyWithImpl<$Res>;
+ @override
+ @useResult
+ $Res call({Stream> stream, int? length, String contentType});
+}
+
+/// @nodoc
+class __$$AppMediaContentImplCopyWithImpl<$Res>
+ extends _$AppMediaContentCopyWithImpl<$Res, _$AppMediaContentImpl>
+ implements _$$AppMediaContentImplCopyWith<$Res> {
+ __$$AppMediaContentImplCopyWithImpl(
+ _$AppMediaContentImpl _value, $Res Function(_$AppMediaContentImpl) _then)
+ : super(_value, _then);
+
+ @pragma('vm:prefer-inline')
+ @override
+ $Res call({
+ Object? stream = null,
+ Object? length = freezed,
+ Object? contentType = null,
+ }) {
+ return _then(_$AppMediaContentImpl(
+ stream: null == stream
+ ? _value.stream
+ : stream // ignore: cast_nullable_to_non_nullable
+ as Stream>,
+ length: freezed == length
+ ? _value.length
+ : length // ignore: cast_nullable_to_non_nullable
+ as int?,
+ contentType: null == contentType
+ ? _value.contentType
+ : contentType // ignore: cast_nullable_to_non_nullable
+ as String,
+ ));
+ }
+}
+
+/// @nodoc
+
+class _$AppMediaContentImpl implements _AppMediaContent {
+ const _$AppMediaContentImpl(
+ {required this.stream, required this.length, required this.contentType});
+
+ @override
+ final Stream> stream;
+ @override
+ final int? length;
+ @override
+ final String contentType;
+
+ @override
+ String toString() {
+ return 'AppMediaContent(stream: $stream, length: $length, contentType: $contentType)';
+ }
+
+ @override
+ bool operator ==(Object other) {
+ return identical(this, other) ||
+ (other.runtimeType == runtimeType &&
+ other is _$AppMediaContentImpl &&
+ (identical(other.stream, stream) || other.stream == stream) &&
+ (identical(other.length, length) || other.length == length) &&
+ (identical(other.contentType, contentType) ||
+ other.contentType == contentType));
+ }
+
+ @override
+ int get hashCode => Object.hash(runtimeType, stream, length, contentType);
+
+ @JsonKey(ignore: true)
+ @override
+ @pragma('vm:prefer-inline')
+ _$$AppMediaContentImplCopyWith<_$AppMediaContentImpl> get copyWith =>
+ __$$AppMediaContentImplCopyWithImpl<_$AppMediaContentImpl>(
+ this, _$identity);
+}
+
+abstract class _AppMediaContent implements AppMediaContent {
+ const factory _AppMediaContent(
+ {required final Stream> stream,
+ required final int? length,
+ required final String contentType}) = _$AppMediaContentImpl;
+
+ @override
+ Stream> get stream;
+ @override
+ int? get length;
+ @override
+ String get contentType;
+ @override
+ @JsonKey(ignore: true)
+ _$$AppMediaContentImplCopyWith<_$AppMediaContentImpl> get copyWith =>
+ throw _privateConstructorUsedError;
+}
diff --git a/data/lib/services/google_drive_service.dart b/data/lib/services/google_drive_service.dart
index 3acd9c1..db8ebcb 100644
--- a/data/lib/services/google_drive_service.dart
+++ b/data/lib/services/google_drive_service.dart
@@ -1,5 +1,6 @@
import 'dart:io';
import 'package:data/models/media/media.dart';
+import 'package:data/models/media_content/media_content.dart';
import 'package:extension_google_sign_in_as_googleapis_auth/extension_google_sign_in_as_googleapis_auth.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_sign_in/google_sign_in.dart';
@@ -59,7 +60,7 @@ 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, size, imageMediaMetadata, videoMediaMetadata)",
+ "files(id, name, description, mimeType, thumbnailLink, createdTime, modifiedTime, size, imageMediaMetadata, videoMediaMetadata)",
);
return (response.files ?? [])
@@ -94,4 +95,11 @@ class GoogleDriveService {
throw AppError.fromError(error);
}
}
+
+ Future fetchMediaBytes(String mediaId) async {
+ final api = await _getGoogleDriveAPI();
+ final media = await api.files.get(mediaId,
+ downloadOptions: drive.DownloadOptions.fullMedia) as drive.Media;
+ return AppMediaContent.fromGoogleDrive(media);
+ }
}
diff --git a/data/lib/services/local_media_service.dart b/data/lib/services/local_media_service.dart
index 5b02374..e4c977e 100644
--- a/data/lib/services/local_media_service.dart
+++ b/data/lib/services/local_media_service.dart
@@ -3,6 +3,8 @@ import 'package:data/models/media/media.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:photo_manager/photo_manager.dart';
+import '../errors/app_error.dart';
+
final localMediaServiceProvider = Provider(
(ref) => const LocalMediaService(),
);
@@ -25,18 +27,25 @@ class LocalMediaService {
Future> getLocalMedia(
{required int start, required int end}) async {
- 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) => AppMedia.fromAssetEntity(asset),
- ),
- );
- return files.whereNotNull().toList();
+ try {
+ 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) => AppMedia.fromAssetEntity(asset),
+ ),
+ );
+ return files.whereNotNull().toList();
+ } catch (e) {
+ if (e is StateError && e.message.contains('No element')) {
+ throw const NoElementError();
+ }
+ throw AppError.fromError(e);
+ }
}
}
diff --git a/style/lib/buttons/action_button.dart b/style/lib/buttons/action_button.dart
new file mode 100644
index 0000000..cdef9d5
--- /dev/null
+++ b/style/lib/buttons/action_button.dart
@@ -0,0 +1,48 @@
+import 'dart:io';
+import 'package:flutter/cupertino.dart';
+import 'package:flutter/material.dart';
+import 'package:style/extensions/context_extensions.dart';
+import 'package:style/indicators/circular_progress_indicator.dart';
+
+class ActionButton extends StatelessWidget {
+ final void Function() onPressed;
+ final Widget icon;
+ final bool progress;
+ final Color? backgroundColor;
+ final double size;
+ final EdgeInsets padding;
+
+ const ActionButton(
+ {super.key,
+ required this.onPressed,
+ required this.icon,
+ this.size = 30,
+ this.backgroundColor,
+ this.progress = false,
+ this.padding = const EdgeInsets.all(0)});
+
+ @override
+ Widget build(BuildContext context) {
+ if (Platform.isIOS) {
+ return CupertinoButton(
+ minSize: size,
+ borderRadius: BorderRadius.circular(size),
+ color: backgroundColor ?? context.colorScheme.containerNormal,
+ onPressed: onPressed,
+ padding: padding,
+ child: progress ? const AppCircularProgressIndicator() : icon,
+ );
+ } else {
+ return IconButton(
+ style: IconButton.styleFrom(
+ backgroundColor:
+ backgroundColor ?? context.colorScheme.containerNormal,
+ minimumSize: Size(size, size),
+ ),
+ onPressed: onPressed,
+ icon: progress ? const AppCircularProgressIndicator() : icon,
+ padding: padding,
+ );
+ }
+ }
+}
diff --git a/style/lib/theme/app_theme_builder.dart b/style/lib/theme/app_theme_builder.dart
index 6dd6ded..e4e8ea5 100644
--- a/style/lib/theme/app_theme_builder.dart
+++ b/style/lib/theme/app_theme_builder.dart
@@ -32,7 +32,6 @@ class AppThemeBuilder {
surfaceTintColor: colorScheme.surface,
foregroundColor: colorScheme.textPrimary,
scrolledUnderElevation: 3,
-
),
);
}
@@ -43,7 +42,7 @@ class AppThemeBuilder {
brightness: colorScheme.brightness,
primaryColor: colorScheme.primary,
primaryContrastingColor: colorScheme.onPrimary,
- barBackgroundColor: colorScheme.surface,
+ barBackgroundColor: colorScheme.barColor,
scaffoldBackgroundColor: colorScheme.surface,
textTheme: CupertinoTextThemeData(
primaryColor: colorScheme.textPrimary,
diff --git a/style/lib/theme/colors.dart b/style/lib/theme/colors.dart
index 605718a..6c4d212 100644
--- a/style/lib/theme/colors.dart
+++ b/style/lib/theme/colors.dart
@@ -20,6 +20,9 @@ class AppColors {
static const surfaceLightColor = Color(0xFFFFFFFF);
static const surfaceDarkColor = Color(0xFF000000);
+ static const barLightColor = Color(0xA6FFFFFF);
+ static const barDarkColor = Color(0xA6000000);
+
static const textPrimaryLightColor = Color(0xDE000000);
static const textSecondaryLightColor = Color(0x99000000);
static const textDisabledLightColor = Color(0x66000000);
diff --git a/style/lib/theme/theme.dart b/style/lib/theme/theme.dart
index 81d5b62..71531a2 100644
--- a/style/lib/theme/theme.dart
+++ b/style/lib/theme/theme.dart
@@ -32,6 +32,7 @@ class AppColorScheme {
final Color outline;
final Color textPrimary;
final Color textSecondary;
+ final Color barColor;
final Color textDisabled;
final Color outlineInverse;
final Color textInversePrimary;
@@ -65,6 +66,7 @@ class AppColorScheme {
required this.textDisabled,
required this.outlineInverse,
required this.textInversePrimary,
+ required this.barColor,
required this.textInverseSecondary,
required this.textInverseDisabled,
required this.containerNormalInverse,
@@ -90,8 +92,6 @@ class AppColorScheme {
Color get containerLowOnSurface => Color.alphaBlend(containerLow, surface);
}
-
-
final appColorSchemeLight = AppColorScheme(
primary: AppColors.primaryColor,
secondary: AppColors.secondaryLightColor,
@@ -120,6 +120,7 @@ final appColorSchemeLight = AppColorScheme(
onPrimary: AppColors.textPrimaryDarkColor,
onSecondary: AppColors.textSecondaryDarkColor,
onDisabled: AppColors.textDisabledLightColor,
+ barColor: AppColors.barLightColor,
brightness: Brightness.light,
);
@@ -151,5 +152,6 @@ final appColorSchemeDark = AppColorScheme(
onPrimary: AppColors.textPrimaryDarkColor,
onSecondary: AppColors.textSecondaryDarkColor,
onDisabled: AppColors.textDisabledDarkColor,
+ barColor: AppColors.barDarkColor,
brightness: Brightness.dark,
);