diff --git a/.DS_Store b/.DS_Store
index dd49705..6d56fc5 100644
Binary files a/.DS_Store and b/.DS_Store differ
diff --git a/.idea/libraries/Dart_Packages.xml b/.idea/libraries/Dart_Packages.xml
index 6bccb1c..10e385a 100644
--- a/.idea/libraries/Dart_Packages.xml
+++ b/.idea/libraries/Dart_Packages.xml
@@ -233,6 +233,13 @@
+
+
+
+
+
+
+
@@ -1145,6 +1152,7 @@
+
diff --git a/.idea/libraries/Flutter_Plugins.xml b/.idea/libraries/Flutter_Plugins.xml
index da28e2f..36f37f9 100644
--- a/.idea/libraries/Flutter_Plugins.xml
+++ b/.idea/libraries/Flutter_Plugins.xml
@@ -15,20 +15,20 @@
+
+
+
-
-
-
diff --git a/app/assets/locales/app_en.arb b/app/assets/locales/app_en.arb
index 205015b..c251037 100644
--- a/app/assets/locales/app_en.arb
+++ b/app/assets/locales/app_en.arb
@@ -40,6 +40,7 @@
"load_local_media_button_text": "Load local media",
"theme_text": "Theme",
+ "notification_text": "Notification",
"light_theme_text": "Light",
"dark_theme_text": "Dark",
"system_theme_text": "System",
diff --git a/app/ios/Podfile.lock b/app/ios/Podfile.lock
index 4be63c7..c87fc7a 100644
--- a/app/ios/Podfile.lock
+++ b/app/ios/Podfile.lock
@@ -138,4 +138,4 @@ SPEC CHECKSUMS:
PODFILE CHECKSUM: 819463e6a0290f5a72f145ba7cde16e8b6ef0796
-COCOAPODS: 1.14.3
+COCOAPODS: 1.15.2
diff --git a/app/ios/Runner/Base.lproj/Main.storyboard b/app/ios/Runner/Base.lproj/Main.storyboard
index f3c2851..04bdd8d 100644
--- a/app/ios/Runner/Base.lproj/Main.storyboard
+++ b/app/ios/Runner/Base.lproj/Main.storyboard
@@ -1,8 +1,10 @@
-
-
+
+
+
-
+
+
@@ -14,13 +16,14 @@
-
+
-
+
+
diff --git a/app/lib/ui/flow/accounts/accounts_screen_view_model.dart b/app/lib/ui/flow/accounts/accounts_screen_view_model.dart
index 0227ad7..2b4b013 100644
--- a/app/lib/ui/flow/accounts/accounts_screen_view_model.dart
+++ b/app/lib/ui/flow/accounts/accounts_screen_view_model.dart
@@ -1,6 +1,7 @@
import 'dart:async';
import 'package:data/services/auth_service.dart';
import 'package:data/services/device_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';
@@ -12,6 +13,7 @@ final accountsStateNotifierProvider =
(ref) => AccountsStateNotifier(
ref.read(deviceServiceProvider),
ref.read(authServiceProvider),
+ ref.read(AppPreferences.canTakeAutoBackUpInGoogleDrive.notifier),
),
);
@@ -19,11 +21,11 @@ class AccountsStateNotifier extends StateNotifier {
final DeviceService _deviceService;
final AuthService _authService;
StreamSubscription? _googleAccountSubscription;
+ StateController canTakeAutoBackUpInGoogleDrive;
- AccountsStateNotifier(
- this._deviceService,
- this._authService,
- ) : super(AccountsState(googleAccount: _authService.googleAccount));
+ AccountsStateNotifier(this._deviceService, this._authService,
+ this.canTakeAutoBackUpInGoogleDrive)
+ : super(AccountsState(googleAccount: _authService.googleAccount));
Future init() async {
_getAppVersion();
@@ -54,6 +56,7 @@ class AccountsStateNotifier extends StateNotifier {
Future signOutWithGoogle() async {
try {
await _authService.signOutWithGoogle();
+ canTakeAutoBackUpInGoogleDrive.state = false;
} catch (e) {
state = state.copyWith(error: e);
}
diff --git a/app/lib/ui/flow/accounts/components/settings_action_list.dart b/app/lib/ui/flow/accounts/components/settings_action_list.dart
index a914518..eaca3b6 100644
--- a/app/lib/ui/flow/accounts/components/settings_action_list.dart
+++ b/app/lib/ui/flow/accounts/components/settings_action_list.dart
@@ -4,6 +4,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:style/buttons/buttons_list.dart';
import 'package:style/buttons/segmented_button.dart';
+import 'package:style/buttons/switch.dart';
class SettingsActionList extends ConsumerWidget {
const SettingsActionList({super.key});
@@ -11,7 +12,17 @@ class SettingsActionList extends ConsumerWidget {
@override
Widget build(BuildContext context, WidgetRef ref) {
final isDarkMode = ref.watch(AppPreferences.isDarkMode);
+ final notifications = ref.watch(AppPreferences.notifications);
return ActionList(buttons: [
+ ActionListButton(
+ title: context.l10n.notification_text,
+ trailing: AppSwitch(
+ value: notifications,
+ onChanged: (value) {
+ ref.read(AppPreferences.notifications.notifier).state = value;
+ },
+ ),
+ ),
ActionListButton(
title: context.l10n.theme_text,
trailing: AppSegmentedButton(
diff --git a/app/lib/ui/flow/home/components/hint_view.dart b/app/lib/ui/flow/home/components/hint_view.dart
index 45b45c7..74bbed5 100644
--- a/app/lib/ui/flow/home/components/hint_view.dart
+++ b/app/lib/ui/flow/home/components/hint_view.dart
@@ -30,11 +30,11 @@ class HintView extends StatelessWidget {
child: Column(
children: [
Row(
- crossAxisAlignment: CrossAxisAlignment.start,
+ crossAxisAlignment: CrossAxisAlignment.center,
children: [
Expanded(
child: Padding(
- padding: const EdgeInsets.only(top: 16, left: 16),
+ padding: const EdgeInsets.only(top: 4, left: 16),
child: Text(
title,
style: AppTextStyles.subtitle2.copyWith(
@@ -43,14 +43,18 @@ class HintView extends StatelessWidget {
),
),
),
- ActionButton(
- backgroundColor: context.colorScheme.containerNormal,
- size: 28,
- onPressed: onClose,
- icon: Icon(
- CupertinoIcons.xmark,
- color: context.colorScheme.textSecondary,
- size: 18,
+ Padding(
+ padding: const EdgeInsets.all(8.0).copyWith(bottom: 4),
+ child: ActionButton(
+ backgroundColor: context.colorScheme.containerNormal,
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
+ size: 28,
+ onPressed: onClose,
+ icon: Icon(
+ CupertinoIcons.xmark,
+ color: context.colorScheme.textSecondary,
+ size: 18,
+ ),
),
),
],
diff --git a/app/lib/ui/flow/home/home_screen.dart b/app/lib/ui/flow/home/home_screen.dart
index fdf9d6b..a2af761 100644
--- a/app/lib/ui/flow/home/home_screen.dart
+++ b/app/lib/ui/flow/home/home_screen.dart
@@ -69,6 +69,7 @@ class _HomeScreenState extends ConsumerState {
child: Padding(
padding: const EdgeInsets.only(right: 8.0),
child: ActionButton(
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
size: 36,
backgroundColor: context.colorScheme.containerNormal,
onPressed: () {
@@ -87,6 +88,7 @@ class _HomeScreenState extends ConsumerState {
ActionButton(
size: 36,
backgroundColor: context.colorScheme.containerNormal,
+ tapTargetSize: MaterialTapTargetSize.shrinkWrap,
onPressed: () {
AppRouter.accounts.push(context);
},
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 8daa41f..9ba17aa 100644
--- a/app/lib/ui/flow/home/home_screen_view_model.dart
+++ b/app/lib/ui/flow/home/home_screen_view_model.dart
@@ -8,6 +8,7 @@ import 'package:data/repositories/google_drive_process_repo.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';
@@ -19,16 +20,23 @@ part 'home_screen_view_model.freezed.dart';
final homeViewStateNotifier =
StateNotifierProvider.autoDispose(
(ref) {
- return HomeViewStateNotifier(
+ final homeView = HomeViewStateNotifier(
ref.read(localMediaServiceProvider),
ref.read(googleDriveServiceProvider),
ref.read(authServiceProvider),
ref.read(googleDriveProcessRepoProvider),
+ ref.read(AppPreferences.canTakeAutoBackUpInGoogleDrive),
);
+
+ ref.listen(AppPreferences.canTakeAutoBackUpInGoogleDrive, (previous, next) {
+ homeView.updateAutoBackUpStatus(next);
+ });
+ return homeView;
});
class HomeViewStateNotifier extends StateNotifier
with HomeViewModelHelperMixin {
+ bool _autoBackUpStatus;
final AuthService _authService;
final GoogleDriveService _googleDriveService;
final GoogleDriveProcessRepo _googleDriveProcessRepo;
@@ -43,12 +51,26 @@ class HomeViewStateNotifier extends StateNotifier
bool _isMaxLocalMediaLoaded = false;
HomeViewStateNotifier(this._localMediaService, this._googleDriveService,
- this._authService, this._googleDriveProcessRepo)
+ this._authService, this._googleDriveProcessRepo, this._autoBackUpStatus)
: super(const HomeViewState()) {
_listenUserGoogleAccount();
_googleDriveProcessRepo.setBackUpFolderId(_backUpFolderId);
_googleDriveProcessRepo.addListener(_listenGoogleDriveProcess);
_loadInitialMedia();
+ _checkAutoBackUp();
+ }
+
+ void updateAutoBackUpStatus(bool status) {
+ _autoBackUpStatus = status;
+ _checkAutoBackUp();
+ }
+
+ void _checkAutoBackUp() {
+ if (_autoBackUpStatus) {
+ _googleDriveProcessRepo.uploadMediasInGoogleDrive(
+ medias: state.medias.valuesWhere((element) => element.isLocalStored),
+ );
+ }
}
void _listenUserGoogleAccount() {
@@ -115,7 +137,7 @@ class HomeViewStateNotifier extends StateNotifier
_googleDriveProcessRepo.downloadQueue.isNotEmpty);
}
- void _loadInitialMedia() async {
+ Future _loadInitialMedia() async {
state = state.copyWith(loading: true, error: null);
final hasAccess = await _localMediaService.requestPermission();
state = state.copyWith(hasLocalMediaAccess: hasAccess, loading: false);
diff --git a/app/lib/ui/flow/media_preview/components/image_preview_screen.dart b/app/lib/ui/flow/media_preview/components/image_preview_screen.dart
index c66b552..9fef3a4 100644
--- a/app/lib/ui/flow/media_preview/components/image_preview_screen.dart
+++ b/app/lib/ui/flow/media_preview/components/image_preview_screen.dart
@@ -3,6 +3,7 @@ import 'package:cloud_gallery/components/app_page.dart';
import 'package:cloud_gallery/components/error_view.dart';
import 'package:cloud_gallery/domain/extensions/context_extensions.dart';
import 'package:data/models/media/media.dart';
+import 'package:data/models/media/media_extension.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../../../domain/extensions/widget_extensions.dart';
@@ -28,8 +29,9 @@ class _ImagePreviewScreenState extends ConsumerState {
void initState() {
if (!widget.media.sources.contains(AppMediaSource.local)) {
notifier = ref.read(networkImagePreviewStateNotifierProvider.notifier);
- runPostFrame(() {
- notifier.loadImage(widget.media.id);
+ runPostFrame(() async {
+ await notifier.loadImageFromGoogleDrive(
+ id: widget.media.id, extension: widget.media.extension);
});
}
super.initState();
diff --git a/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart b/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart
index b794206..53389e5 100644
--- a/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart
+++ b/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview.dart
@@ -1,9 +1,10 @@
-import 'dart:typed_data';
+import 'dart:io';
import 'package:cloud_gallery/domain/extensions/context_extensions.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';
+import '../../../../../components/app_page.dart';
import '../../../../../components/error_view.dart';
import 'network_image_preview_view_model.dart';
@@ -18,11 +19,17 @@ class NetworkImagePreview extends ConsumerWidget {
if (state.loading) {
return Center(child: AppCircularProgressIndicator(value: state.progress));
- } else if (state.mediaBytes != null) {
+ } else if (state.filePath != null) {
return Hero(
tag: media,
- child: Image.memory(Uint8List.fromList(state.mediaBytes!),
- fit: BoxFit.fitWidth),
+ child: Image.file(File(state.filePath!), fit: BoxFit.fitWidth,
+ errorBuilder: (context, error, stackTrace) {
+ return AppPage(
+ body: ErrorView(
+ title: context.l10n.unable_to_load_media_error,
+ message: context.l10n.unable_to_load_media_message,
+ ));
+ }),
);
} else if (state.error != null) {
return ErrorView(
@@ -30,6 +37,6 @@ class NetworkImagePreview extends ConsumerWidget {
message: context.l10n.unable_to_load_media_message,
);
}
- return const Placeholder();
+ return const SizedBox();
}
}
diff --git a/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview_view_model.dart b/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview_view_model.dart
index dbff95e..d22f556 100644
--- a/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview_view_model.dart
+++ b/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview_view_model.dart
@@ -1,13 +1,14 @@
import 'dart:async';
-
-import 'package:data/models/media_content/media_content.dart';
+import 'dart:io';
import 'package:data/services/google_drive_service.dart';
+import 'package:dio/dio.dart' show CancelToken;
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
+import 'package:path_provider/path_provider.dart';
part 'network_image_preview_view_model.freezed.dart';
-final networkImagePreviewStateNotifierProvider = StateNotifierProvider<
+final networkImagePreviewStateNotifierProvider = StateNotifierProvider.autoDispose<
NetworkImagePreviewStateNotifier, NetworkImagePreviewState>((ref) {
return NetworkImagePreviewStateNotifier(ref.read(googleDriveServiceProvider));
});
@@ -15,40 +16,32 @@ final networkImagePreviewStateNotifierProvider = StateNotifierProvider<
class NetworkImagePreviewStateNotifier
extends StateNotifier {
final GoogleDriveService _googleDriveServices;
- late StreamSubscription _subscription;
NetworkImagePreviewStateNotifier(this._googleDriveServices)
: super(const NetworkImagePreviewState());
- Future loadImage(String mediaId) async {
+ File? tempFile;
+ CancelToken? cancelToken;
+
+ Future loadImageFromGoogleDrive(
+ {required String id, required String extension}) async {
try {
state = state.copyWith(loading: true, error: null);
- final mediaContent = await _googleDriveServices.fetchMediaBytes(mediaId);
- final mediaByte = [];
- final length = mediaContent.length ?? 0;
-
- _subscription = mediaContent.stream.listen(
- (byteChunk) {
- mediaByte.addAll(byteChunk);
- state = state.copyWith(
- progress: length <= 0 ? 0 : mediaByte.length / length);
- },
- onDone: () {
- state = state.copyWith(
- mediaContent: mediaContent,
- mediaBytes: mediaByte,
- loading: false,
- );
- _subscription.cancel();
- },
- onError: (error) {
- state = state.copyWith(
- error: error,
- loading: false,
- );
- _subscription.cancel();
+ cancelToken = CancelToken();
+ final dir = await getTemporaryDirectory();
+ tempFile = File('${dir.path}/$id.$extension');
+ await _googleDriveServices.downloadFromGoogleDrive(
+ id: id,
+ saveLocation: tempFile!.path,
+ cancelToken: cancelToken,
+ onProgress: (progress, total) {
+ state = state.copyWith(progress: total <= 0 ? 0 : progress / total);
},
);
+ state = state.copyWith(
+ loading: false,
+ filePath: tempFile?.path,
+ );
} catch (error) {
state = state.copyWith(
error: error,
@@ -59,7 +52,8 @@ class NetworkImagePreviewStateNotifier
@override
void dispose() {
- _subscription.cancel();
+ tempFile?.deleteSync();
+ cancelToken?.cancel();
super.dispose();
}
}
@@ -68,9 +62,8 @@ class NetworkImagePreviewStateNotifier
class NetworkImagePreviewState with _$NetworkImagePreviewState {
const factory NetworkImagePreviewState({
@Default(false) bool loading,
- AppMediaContent? mediaContent,
- List? mediaBytes,
- @Default(0.0) double progress,
+ double? progress,
+ String? filePath,
Object? error,
}) = _NetworkImagePreviewState;
}
diff --git a/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview_view_model.freezed.dart b/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview_view_model.freezed.dart
index a1a47a9..c8479e5 100644
--- a/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview_view_model.freezed.dart
+++ b/app/lib/ui/flow/media_preview/components/network_image_preview/network_image_preview_view_model.freezed.dart
@@ -17,9 +17,8 @@ final _privateConstructorUsedError = UnsupportedError(
/// @nodoc
mixin _$NetworkImagePreviewState {
bool get loading => throw _privateConstructorUsedError;
- AppMediaContent? get mediaContent => throw _privateConstructorUsedError;
- List? get mediaBytes => throw _privateConstructorUsedError;
- double get progress => throw _privateConstructorUsedError;
+ double? get progress => throw _privateConstructorUsedError;
+ String? get filePath => throw _privateConstructorUsedError;
Object? get error => throw _privateConstructorUsedError;
@JsonKey(ignore: true)
@@ -33,14 +32,7 @@ abstract class $NetworkImagePreviewStateCopyWith<$Res> {
$Res Function(NetworkImagePreviewState) then) =
_$NetworkImagePreviewStateCopyWithImpl<$Res, NetworkImagePreviewState>;
@useResult
- $Res call(
- {bool loading,
- AppMediaContent? mediaContent,
- List? mediaBytes,
- double progress,
- Object? error});
-
- $AppMediaContentCopyWith<$Res>? get mediaContent;
+ $Res call({bool loading, double? progress, String? filePath, Object? error});
}
/// @nodoc
@@ -58,9 +50,8 @@ class _$NetworkImagePreviewStateCopyWithImpl<$Res,
@override
$Res call({
Object? loading = null,
- Object? mediaContent = freezed,
- Object? mediaBytes = freezed,
- Object? progress = null,
+ Object? progress = freezed,
+ Object? filePath = freezed,
Object? error = freezed,
}) {
return _then(_value.copyWith(
@@ -68,33 +59,17 @@ class _$NetworkImagePreviewStateCopyWithImpl<$Res,
? _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?,
- progress: null == progress
+ progress: freezed == progress
? _value.progress
: progress // ignore: cast_nullable_to_non_nullable
- as double,
+ as double?,
+ filePath: freezed == filePath
+ ? _value.filePath
+ : filePath // ignore: cast_nullable_to_non_nullable
+ as String?,
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
@@ -106,15 +81,7 @@ abstract class _$$NetworkImagePreviewStateImplCopyWith<$Res>
__$$NetworkImagePreviewStateImplCopyWithImpl<$Res>;
@override
@useResult
- $Res call(
- {bool loading,
- AppMediaContent? mediaContent,
- List? mediaBytes,
- double progress,
- Object? error});
-
- @override
- $AppMediaContentCopyWith<$Res>? get mediaContent;
+ $Res call({bool loading, double? progress, String? filePath, Object? error});
}
/// @nodoc
@@ -131,9 +98,8 @@ class __$$NetworkImagePreviewStateImplCopyWithImpl<$Res>
@override
$Res call({
Object? loading = null,
- Object? mediaContent = freezed,
- Object? mediaBytes = freezed,
- Object? progress = null,
+ Object? progress = freezed,
+ Object? filePath = freezed,
Object? error = freezed,
}) {
return _then(_$NetworkImagePreviewStateImpl(
@@ -141,18 +107,14 @@ class __$$NetworkImagePreviewStateImplCopyWithImpl<$Res>
? _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?,
- progress: null == progress
+ progress: freezed == progress
? _value.progress
: progress // ignore: cast_nullable_to_non_nullable
- as double,
+ as double?,
+ filePath: freezed == filePath
+ ? _value.filePath
+ : filePath // ignore: cast_nullable_to_non_nullable
+ as String?,
error: freezed == error ? _value.error : error,
));
}
@@ -162,37 +124,21 @@ class __$$NetworkImagePreviewStateImplCopyWithImpl<$Res>
class _$NetworkImagePreviewStateImpl implements _NetworkImagePreviewState {
const _$NetworkImagePreviewStateImpl(
- {this.loading = false,
- this.mediaContent,
- final List? mediaBytes,
- this.progress = 0.0,
- this.error})
- : _mediaBytes = mediaBytes;
+ {this.loading = false, this.progress, this.filePath, this.error});
@override
@JsonKey()
final bool loading;
@override
- final AppMediaContent? mediaContent;
- final List? _mediaBytes;
+ final double? progress;
@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
- @JsonKey()
- final double progress;
+ final String? filePath;
@override
final Object? error;
@override
String toString() {
- return 'NetworkImagePreviewState(loading: $loading, mediaContent: $mediaContent, mediaBytes: $mediaBytes, progress: $progress, error: $error)';
+ return 'NetworkImagePreviewState(loading: $loading, progress: $progress, filePath: $filePath, error: $error)';
}
@override
@@ -201,22 +147,15 @@ class _$NetworkImagePreviewStateImpl implements _NetworkImagePreviewState {
(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) &&
(identical(other.progress, progress) ||
other.progress == progress) &&
+ (identical(other.filePath, filePath) ||
+ other.filePath == filePath) &&
const DeepCollectionEquality().equals(other.error, error));
}
@override
- int get hashCode => Object.hash(
- runtimeType,
- loading,
- mediaContent,
- const DeepCollectionEquality().hash(_mediaBytes),
- progress,
+ int get hashCode => Object.hash(runtimeType, loading, progress, filePath,
const DeepCollectionEquality().hash(error));
@JsonKey(ignore: true)
@@ -230,19 +169,16 @@ class _$NetworkImagePreviewStateImpl implements _NetworkImagePreviewState {
abstract class _NetworkImagePreviewState implements NetworkImagePreviewState {
const factory _NetworkImagePreviewState(
{final bool loading,
- final AppMediaContent? mediaContent,
- final List? mediaBytes,
- final double progress,
+ final double? progress,
+ final String? filePath,
final Object? error}) = _$NetworkImagePreviewStateImpl;
@override
bool get loading;
@override
- AppMediaContent? get mediaContent;
- @override
- List? get mediaBytes;
+ double? get progress;
@override
- double get progress;
+ String? get filePath;
@override
Object? get error;
@override
diff --git a/app/lib/ui/flow/media_preview/components/top_bar.dart b/app/lib/ui/flow/media_preview/components/top_bar.dart
index 908fe39..c4ddf76 100644
--- a/app/lib/ui/flow/media_preview/components/top_bar.dart
+++ b/app/lib/ui/flow/media_preview/components/top_bar.dart
@@ -50,18 +50,28 @@ class PreviewTopBar extends StatelessWidget {
),
if(media.isGoogleDriveStored)
ActionButton(
+ padding: const EdgeInsets.all(4),
onPressed: () {
notifier.downloadMediaFromGoogleDrive(media: media);
},
- icon: Padding(
- padding: const EdgeInsets.all(4.0),
- child: Icon(
- CupertinoIcons.cloud_download,
+ icon: Icon(
+ CupertinoIcons.cloud_download,
+ color: context.colorScheme.textSecondary,
+ size: 22,
+ ),
+ ),
+ if(media.isLocalStored)
+ ActionButton(
+ padding: const EdgeInsets.all(4),
+ onPressed: () {
+ notifier.uploadMediaInGoogleDrive(media: media);
+ },
+ icon: Icon(
+ CupertinoIcons.cloud_upload,
color: context.colorScheme.textSecondary,
size: 22,
),
),
- ),
ActionButton(
onPressed: () async {
if (media.isCommonStored && media.driveMediaRefId != null) {
diff --git a/app/lib/ui/flow/media_preview/media_preview_screen.dart b/app/lib/ui/flow/media_preview/media_preview_screen.dart
index 5e70c34..a34973b 100644
--- a/app/lib/ui/flow/media_preview/media_preview_screen.dart
+++ b/app/lib/ui/flow/media_preview/media_preview_screen.dart
@@ -121,14 +121,16 @@ class _MediaPreviewState extends ConsumerState {
Widget build(BuildContext context) {
_observeError();
_updateVideoControllerOnMediaChange();
- final medias = ref.watch(_provider.select((state) => state.medias));
- final showActions =
- ref.watch(_provider.select((state) => state.showActions));
+ final ({List medias, bool showActions}) state =
+ ref.watch(_provider.select((state) => (
+ medias: state.medias,
+ showActions: state.showActions,
+ )));
return DismissiblePage(
backgroundColor: context.colorScheme.surface,
onProgress: (progress) {
- if (progress > 0 && showActions) {
+ if (progress > 0 && state.showActions) {
notifier.toggleActionVisibility();
}
},
@@ -146,9 +148,9 @@ class _MediaPreviewState extends ConsumerState {
child: PageView.builder(
onPageChanged: notifier.changeVisibleMediaIndex,
controller: _pageController,
- itemCount: medias.length,
+ itemCount: state.medias.length,
itemBuilder: (context, index) =>
- _preview(context: context, media: medias[index]),
+ _preview(context: context, media: state.medias[index]),
),
),
PreviewTopBar(provider: _provider),
diff --git a/app/lib/ui/flow/media_preview/media_preview_view_model.dart b/app/lib/ui/flow/media_preview/media_preview_view_model.dart
index efef08f..584b236 100644
--- a/app/lib/ui/flow/media_preview/media_preview_view_model.dart
+++ b/app/lib/ui/flow/media_preview/media_preview_view_model.dart
@@ -103,6 +103,10 @@ class MediaPreviewStateNotifier extends StateNotifier {
_googleDriveProcessRepo.downloadMediasFromGoogleDrive(medias: [media]);
}
+ Future uploadMediaInGoogleDrive({required AppMedia media})async {
+ _googleDriveProcessRepo.uploadMediasInGoogleDrive(medias: [media]);
+ }
+
void updateVideoPosition(Duration position) {
if (state.videoPosition == position) return;
state = state.copyWith(videoPosition: position);
diff --git a/app/lib/ui/flow/media_transfer/components/transfer_item.dart b/app/lib/ui/flow/media_transfer/components/transfer_item.dart
index 017336f..f6c45e9 100644
--- a/app/lib/ui/flow/media_transfer/components/transfer_item.dart
+++ b/app/lib/ui/flow/media_transfer/components/transfer_item.dart
@@ -96,7 +96,6 @@ class _ProcessItemState extends State {
],
),
),
- if (widget.process.status.isWaiting)
ActionButton(
onPressed: widget.onCancelTap,
icon: const Icon(CupertinoIcons.xmark),
diff --git a/app/pubspec.lock b/app/pubspec.lock
index 6ddf9e0..b29b141 100644
--- a/app/pubspec.lock
+++ b/app/pubspec.lock
@@ -272,6 +272,14 @@ packages:
relative: true
source: path
version: "0.0.1"
+ dio:
+ dependency: "direct main"
+ description:
+ name: dio
+ sha256: "11e40df547d418cc0c4900a9318b26304e665da6fa4755399a9ff9efd09034b5"
+ url: "https://pub.dev"
+ source: hosted
+ version: "5.4.3+1"
extension_google_sign_in_as_googleapis_auth:
dependency: transitive
description:
@@ -717,7 +725,7 @@ packages:
source: hosted
version: "1.0.1"
path_provider:
- dependency: transitive
+ dependency: "direct main"
description:
name: path_provider
sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b
diff --git a/app/pubspec.yaml b/app/pubspec.yaml
index 3989ba8..bacd4c1 100644
--- a/app/pubspec.yaml
+++ b/app/pubspec.yaml
@@ -47,6 +47,8 @@ dependencies:
# core
firebase_core: ^2.24.2
collection: ^1.18.0
+ path_provider: ^2.1.2
+ dio: ^5.4.3+1
# storage
shared_preferences: ^2.2.2
diff --git a/data/.flutter-plugins-dependencies b/data/.flutter-plugins-dependencies
index 52c68b3..04e3d87 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":"path_provider_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"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":"path_provider_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_android-2.2.2/","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":"path_provider_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"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","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","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-04-04 11:45:07.647168","version":"3.19.3"}
\ 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":"path_provider_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"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":"path_provider_android","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_android-2.2.2/","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":"path_provider_foundation","path":"/Users/pratikcanopas/.pub-cache/hosted/pub.dev/path_provider_foundation-2.3.2/","shared_darwin_source":true,"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","dependencies":["path_provider_android","path_provider_foundation","path_provider_linux","path_provider_windows"]},{"name":"path_provider_android","dependencies":[]},{"name":"path_provider_foundation","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-04-18 14:12:20.264184","version":"3.19.3"}
\ No newline at end of file
diff --git a/data/lib/apis/google_drive/google_drive_endpoint.dart b/data/lib/apis/google_drive/google_drive_endpoint.dart
new file mode 100644
index 0000000..56f9f71
--- /dev/null
+++ b/data/lib/apis/google_drive/google_drive_endpoint.dart
@@ -0,0 +1,98 @@
+import 'dart:convert';
+import 'package:data/apis/network/base_url.dart';
+import 'package:data/apis/network/endpoint.dart';
+import 'package:data/models/media_content/media_content.dart';
+import 'package:dio/dio.dart';
+import 'package:googleapis/drive/v3.dart' as drive;
+import 'package:http_parser/http_parser.dart';
+
+class UploadGoogleDriveFile extends Endpoint {
+ final drive.File request;
+ final AppMediaContent content;
+ final CancelToken? cancellationToken;
+ final void Function(int chunk, int length)? onProgress;
+
+ const UploadGoogleDriveFile({
+ required this.request,
+ required this.content,
+ this.cancellationToken,
+ this.onProgress,
+ });
+
+ @override
+ String get baseUrl => BaseURL.googleDriveUpload;
+
+ @override
+ CancelToken? get cancelToken => cancellationToken;
+
+ @override
+ HttpMethod get method => HttpMethod.post;
+
+ @override
+ Map get headers => {
+ 'Content-Type': 'multipart/related',
+ 'Content-Length': content.length.toString(),
+ };
+
+ @override
+ Object? get data => FormData.fromMap(
+ {
+ 'metadata': MultipartFile.fromString(
+ json.encode(request.toJson()),
+ contentType: MediaType.parse("application/json; charset=UTF-8"),
+ ),
+ 'media': MultipartFile.fromStream(
+ () => content.stream,
+ content.length ?? 0,
+ ),
+ },
+ );
+
+ @override
+ String get path => '/files';
+
+ @override
+ Map? get queryParameters => {
+ 'uploadType': 'multipart',
+ };
+
+ @override
+ void Function(int p1, int p2)? get onSendProgress => onProgress;
+}
+
+class DownloadGoogleDriveFileContent extends DownloadEndpoint {
+ final String id;
+
+ final void Function(int received, int total)? onProgress;
+
+ final String saveLocation;
+
+ final CancelToken? cancellationToken;
+
+ const DownloadGoogleDriveFileContent({
+ required this.id,
+ this.cancellationToken,
+ this.onProgress,
+ required this.saveLocation,
+ });
+
+ @override
+ String get baseUrl => BaseURL.googleDrive;
+
+ @override
+ String get path => '/files/$id';
+
+ @override
+ Map? get queryParameters => {
+ 'alt': 'media',
+ };
+
+ @override
+ CancelToken? get cancelToken => cancellationToken;
+
+ @override
+ void Function(int p1, int p2)? get onReceiveProgress => onProgress;
+
+ @override
+ String? get storePath => saveLocation;
+}
diff --git a/data/lib/apis/network/base_url.dart b/data/lib/apis/network/base_url.dart
new file mode 100644
index 0000000..72aafd4
--- /dev/null
+++ b/data/lib/apis/network/base_url.dart
@@ -0,0 +1,4 @@
+class BaseURL {
+ static const googleDriveUpload = 'https://www.googleapis.com/upload/drive/v3';
+ static const googleDrive = 'https://www.googleapis.com/drive/v3';
+}
\ No newline at end of file
diff --git a/data/lib/apis/network/client.dart b/data/lib/apis/network/client.dart
new file mode 100644
index 0000000..68f187b
--- /dev/null
+++ b/data/lib/apis/network/client.dart
@@ -0,0 +1,73 @@
+import 'package:data/apis/network/interceptors/auth_interceptor.dart';
+import 'package:data/errors/app_error.dart';
+import 'package:data/services/auth_service.dart';
+import 'package:dio/dio.dart';
+import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'endpoint.dart';
+
+final googleAuthenticatedDioProvider = Provider((ref) {
+ return Dio()
+ ..options.connectTimeout = const Duration(seconds: 60)
+ ..options.sendTimeout = const Duration(seconds: 60)
+ ..options.receiveTimeout = const Duration(seconds: 60)
+ ..interceptors.add(
+ GoogleDriveAuthInterceptor(
+ googleSignIn: ref.read(googleSignInProvider),
+ ),
+ );
+});
+
+final rawDioProvider = Provider((ref) {
+ return Dio()
+ ..options.connectTimeout = const Duration(seconds: 60)
+ ..options.sendTimeout = const Duration(seconds: 60)
+ ..options.receiveTimeout = const Duration(seconds: 60);
+});
+
+extension DioExtensions on Dio {
+ Future> req(Endpoint endpoint) async {
+ try {
+ return await request(
+ endpoint.baseUrl + endpoint.path,
+ queryParameters: endpoint.queryParameters,
+ options: Options(
+ method: endpoint.method.name,
+ headers: endpoint.headers,
+ responseType: endpoint.responseType,
+ contentType: endpoint.contentType,
+ validateStatus: (status) =>
+ status != null && status >= 200 && status < 300,
+ ),
+ data: endpoint.data,
+ cancelToken: endpoint.cancelToken,
+ onReceiveProgress: endpoint.onReceiveProgress,
+ onSendProgress: endpoint.onSendProgress,
+ );
+ } catch (error) {
+ throw AppError.fromError(error);
+ }
+ }
+
+ Future downloadReq(DownloadEndpoint endpoint) async {
+ try {
+ return await download(
+ endpoint.baseUrl + endpoint.path,
+ endpoint.storePath,
+ queryParameters: endpoint.queryParameters,
+ options: Options(
+ method: endpoint.method.name,
+ headers: endpoint.headers,
+ responseType: endpoint.responseType,
+ contentType: endpoint.contentType,
+ validateStatus: (status) =>
+ status != null && status >= 200 && status < 300,
+ ),
+ data: endpoint.data,
+ cancelToken: endpoint.cancelToken,
+ onReceiveProgress: endpoint.onReceiveProgress,
+ );
+ } catch (e) {
+ throw AppError.fromError(e);
+ }
+ }
+}
diff --git a/data/lib/apis/network/endpoint.dart b/data/lib/apis/network/endpoint.dart
new file mode 100644
index 0000000..4748731
--- /dev/null
+++ b/data/lib/apis/network/endpoint.dart
@@ -0,0 +1,37 @@
+import 'package:dio/dio.dart';
+
+enum HttpMethod { get, post, put, delete, patch }
+
+abstract class Endpoint {
+ const Endpoint();
+
+ HttpMethod get method => HttpMethod.get;
+
+ String get path;
+
+ String get baseUrl;
+
+ Map? get queryParameters => null;
+
+ Map get headers => const {};
+
+ Object? get data => null;
+
+ String? get contentType => null;
+
+ ResponseType get responseType => ResponseType.json;
+
+ CancelToken? get cancelToken => null;
+
+ void Function(int, int)? get onReceiveProgress => null;
+
+ void Function(int, int)? get onSendProgress => null;
+}
+
+abstract class DownloadEndpoint extends Endpoint {
+ const DownloadEndpoint();
+ @override
+ ResponseType get responseType => ResponseType.stream;
+
+ String? get storePath;
+}
diff --git a/data/lib/apis/network/interceptors/auth_interceptor.dart b/data/lib/apis/network/interceptors/auth_interceptor.dart
new file mode 100644
index 0000000..b4501c6
--- /dev/null
+++ b/data/lib/apis/network/interceptors/auth_interceptor.dart
@@ -0,0 +1,22 @@
+import 'package:dio/dio.dart';
+import 'package:google_sign_in/google_sign_in.dart';
+
+class GoogleDriveAuthInterceptor extends Interceptor {
+ final GoogleSignIn googleSignIn;
+
+ GoogleDriveAuthInterceptor({
+ required this.googleSignIn,
+ });
+
+ @override
+ Future onRequest(
+ RequestOptions options,
+ RequestInterceptorHandler handler,
+ ) async {
+ final authHeaders = await googleSignIn.currentUser?.authHeaders;
+ if (authHeaders != null) {
+ options.headers.addAll(authHeaders);
+ }
+ handler.next(options);
+ }
+}
diff --git a/data/lib/errors/app_error.dart b/data/lib/errors/app_error.dart
index 46f196e..e418569 100644
--- a/data/lib/errors/app_error.dart
+++ b/data/lib/errors/app_error.dart
@@ -1,11 +1,11 @@
import 'dart:io';
-
import 'package:data/errors/l10n_error_codes.dart';
+import 'package:dio/dio.dart' show DioException, DioExceptionType;
class AppError implements Exception {
final String? message;
final String? l10nCode;
- final String? statusCode;
+ final int? statusCode;
const AppError({this.message, this.statusCode, this.l10nCode});
@@ -19,6 +19,14 @@ class AppError implements Exception {
return error;
} else if (error is SocketException) {
return const NoConnectionError();
+ } else if (error is DioException) {
+ if (error.type == DioExceptionType.cancel) {
+ return const RequestCancelledByUser();
+ }
+ return SomethingWentWrongError(
+ message: error.message,
+ statusCode: error.response?.statusCode,
+ );
} else {
return const SomethingWentWrongError();
}
@@ -41,6 +49,13 @@ class UserGoogleSignInAccountNotFound extends AppError {
"User google signed in account not found. Please sign in again");
}
+class RequestCancelledByUser extends AppError {
+ const RequestCancelledByUser()
+ : super(
+ message: "Request cancelled.",
+ );
+}
+
class BackUpFolderNotFound extends AppError {
const BackUpFolderNotFound()
: super(
@@ -48,8 +63,13 @@ class BackUpFolderNotFound extends AppError {
message: "Back up folder not found");
}
+class UnableToSaveFileInGallery extends AppError {
+ const UnableToSaveFileInGallery()
+ : super(message: "Unable to save file in gallery");
+}
+
class SomethingWentWrongError extends AppError {
- const SomethingWentWrongError({String? message, String? statusCode})
+ const SomethingWentWrongError({String? message, int? statusCode})
: super(
l10nCode: AppErrorL10nCodes.somethingWentWrongError,
message: message,
diff --git a/data/lib/models/app_process/app_process.dart b/data/lib/models/app_process/app_process.dart
index 8ae5c4f..c819bc4 100644
--- a/data/lib/models/app_process/app_process.dart
+++ b/data/lib/models/app_process/app_process.dart
@@ -10,6 +10,7 @@ enum AppProcessStatus {
deleting,
downloading,
success,
+ terminated,
failed;
bool get isProcessing =>
@@ -22,6 +23,8 @@ enum AppProcessStatus {
bool get isSuccess => this == AppProcessStatus.success;
bool get isFailed => this == AppProcessStatus.failed;
+
+ bool get isTerminated => this == AppProcessStatus.terminated;
}
@freezed
diff --git a/data/lib/models/media/media_extension.dart b/data/lib/models/media/media_extension.dart
index 8707149..1877b2b 100644
--- a/data/lib/models/media/media_extension.dart
+++ b/data/lib/models/media/media_extension.dart
@@ -31,7 +31,7 @@ extension AppMediaExtension on AppMedia {
);
}
- AppMedia mergeGoogleDriveMedia(AppMedia media){
+ AppMedia mergeGoogleDriveMedia(AppMedia media) {
return copyWith(
thumbnailLink: media.thumbnailLink,
driveMediaRefId: media.driveMediaRefId,
@@ -46,6 +46,13 @@ extension AppMediaExtension on AppMedia {
sources.contains(AppMediaSource.local) && sources.length == 1;
bool get isCommonStored => sources.length > 1;
+
+ String get extension {
+ if (mimeType?.trim().isNotEmpty ?? false) return mimeType!.split('/').last;
+ if (type.isVideo) return 'mp4';
+ if (type.isImage) return 'jpg';
+ return '';
+ }
}
class ThumbNailParameter {
diff --git a/data/lib/repositories/google_drive_process_repo.dart b/data/lib/repositories/google_drive_process_repo.dart
index 3431d77..6ae9345 100644
--- a/data/lib/repositories/google_drive_process_repo.dart
+++ b/data/lib/repositories/google_drive_process_repo.dart
@@ -1,12 +1,15 @@
import 'dart:async';
+import 'dart:io';
import 'package:collection/collection.dart';
import 'package:data/extensions/iterable_extension.dart';
import 'package:data/models/app_process/app_process.dart';
import 'package:data/models/media/media_extension.dart';
import 'package:data/services/google_drive_service.dart';
import 'package:data/services/local_media_service.dart';
+import 'package:dio/dio.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
+import 'package:path_provider/path_provider.dart';
import '../errors/app_error.dart';
import '../models/media/media.dart';
@@ -71,10 +74,19 @@ class GoogleDriveProcessRepo extends ChangeNotifier {
_backUpFolderID ??= await _googleDriveService.getBackupFolderId();
+ final cancelToken = CancelToken();
+
final res = await _googleDriveService.uploadInGoogleDrive(
folderID: _backUpFolderID!,
media: process.media,
- onProgress: (total, chunk) {
+ onProgress: (chunk, total) {
+ if (_uploadQueue
+ .firstWhereOrNull((element) => element.id == process.id)
+ ?.status
+ .isTerminated ??
+ true) {
+ cancelToken.cancel();
+ }
_uploadQueue.updateWhere(
where: (element) => element.id == process.id,
update: (element) => element.copyWith(
@@ -82,6 +94,7 @@ class GoogleDriveProcessRepo extends ChangeNotifier {
);
notifyListeners();
},
+ cancelToken: cancelToken,
);
_uploadQueue.updateWhere(
where: (element) => element.id == process.id,
@@ -91,7 +104,9 @@ class GoogleDriveProcessRepo extends ChangeNotifier {
),
);
} catch (error) {
- if (error is BackUpFolderNotFound) {
+ if(error is RequestCancelledByUser){
+ return;
+ } else if (error is BackUpFolderNotFound) {
_backUpFolderID = await _googleDriveService.getBackupFolderId();
_uploadInGoogleDrive(process);
return;
@@ -163,6 +178,7 @@ class GoogleDriveProcessRepo extends ChangeNotifier {
}
Future _downloadFromGoogleDrive(AppProcess process) async {
+ String? tempFileLocation;
try {
_downloadQueue.updateWhere(
where: (element) => element.id == process.id,
@@ -171,39 +187,66 @@ class GoogleDriveProcessRepo extends ChangeNotifier {
);
notifyListeners();
- final mediaContent = await _googleDriveService
- .fetchMediaBytes(process.media.driveMediaRefId!);
+ final tempDir = await getTemporaryDirectory();
+ tempFileLocation =
+ "${tempDir.path}/${process.media.id}.${process.media.extension}";
- final localMedia = await _localMediaService.saveMedia(
- content: mediaContent,
- onProgress: (total, chunk) {
+ final cancelToken = CancelToken();
+
+ await _googleDriveService.downloadFromGoogleDrive(
+ id: process.media.driveMediaRefId!,
+ saveLocation: tempFileLocation,
+ onProgress: (received, total) {
+ if (_downloadQueue
+ .firstWhereOrNull((element) => element.id == process.id)
+ ?.status
+ .isTerminated ??
+ true) {
+ cancelToken.cancel();
+ }
_downloadQueue.updateWhere(
where: (element) => element.id == process.id,
update: (element) => element.copyWith(
- progress: AppProcessProgress(total: total, chunk: chunk)),
+ progress: AppProcessProgress(total: total, chunk: received)),
);
notifyListeners();
},
- mimeType: process.media.mimeType,
+ cancelToken: cancelToken,
+ );
+
+ final localMedia = await _localMediaService.saveInGallery(
+ saveFromLocation: tempFileLocation,
type: process.media.type,
);
+ if (localMedia == null) {
+ throw const UnableToSaveFileInGallery();
+ }
+
final updatedMedia = await _googleDriveService.updateMediaDescription(
- process.media.id, localMedia?.path ?? "");
+ process.media.id,
+ localMedia.id,
+ );
_downloadQueue.updateWhere(
where: (element) => element.id == process.id,
update: (element) => element.copyWith(
status: AppProcessStatus.success,
- response: localMedia?.mergeGoogleDriveMedia(updatedMedia)),
+ response: localMedia.mergeGoogleDriveMedia(updatedMedia)),
);
} catch (error) {
+ if(error is RequestCancelledByUser){
+ return;
+ }
_downloadQueue.updateWhere(
where: (element) => element.id == process.id,
update: (element) => element.copyWith(status: AppProcessStatus.failed),
);
} finally {
notifyListeners();
+ if (tempFileLocation != null) {
+ await File(tempFileLocation).delete();
+ }
}
}
@@ -215,7 +258,10 @@ class GoogleDriveProcessRepo extends ChangeNotifier {
}
void terminateUploadProcess(String id) {
- _uploadQueue.removeWhere((element) => element.id == id);
+ _uploadQueue.updateWhere(
+ where: (element) => element.id == id,
+ update: (element) =>
+ element.copyWith(status: AppProcessStatus.terminated));
notifyListeners();
}
@@ -225,7 +271,10 @@ class GoogleDriveProcessRepo extends ChangeNotifier {
}
void terminateDownloadProcess(String id) {
- _downloadQueue.removeWhere((element) => element.id == id);
+ _uploadQueue.updateWhere(
+ where: (element) => element.id == id,
+ update: (element) =>
+ element.copyWith(status: AppProcessStatus.terminated));
notifyListeners();
}
}
diff --git a/data/lib/services/google_drive_service.dart b/data/lib/services/google_drive_service.dart
index d4fc2f7..fed602b 100644
--- a/data/lib/services/google_drive_service.dart
+++ b/data/lib/services/google_drive_service.dart
@@ -1,6 +1,10 @@
+import 'dart:async';
import 'dart:io';
+import 'package:data/apis/google_drive/google_drive_endpoint.dart';
+import 'package:data/apis/network/client.dart';
import 'package:data/models/media/media.dart';
import 'package:data/models/media_content/media_content.dart';
+import 'package:dio/dio.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';
@@ -9,15 +13,18 @@ import '../errors/app_error.dart';
import 'auth_service.dart';
final googleDriveServiceProvider = Provider(
- (ref) => GoogleDriveService(ref.read(googleSignInProvider)),
+ (ref) => GoogleDriveService(
+ ref.read(googleSignInProvider),
+ ref.read(googleAuthenticatedDioProvider),
+ ),
);
class GoogleDriveService {
final String _backUpFolderName = "Cloud Gallery Backup";
-
+ final Dio _client;
final GoogleSignIn _googleSignIn;
- const GoogleDriveService(this._googleSignIn);
+ GoogleDriveService(this._googleSignIn, this._client);
Future _getGoogleDriveAPI() async {
if (_googleSignIn.currentUser == null) {
@@ -96,41 +103,55 @@ class GoogleDriveService {
Future uploadInGoogleDrive(
{required String folderID,
required AppMedia media,
- void Function(int total, int chunk)? onProgress}) async {
+ CancelToken? cancelToken,
+ void Function(int chunk, int total)? onProgress}) async {
final localFile = File(media.path);
try {
- final driveApi = await _getGoogleDriveAPI();
-
final file = drive.File(
name: media.name ?? localFile.path.split('/').last,
+ mimeType: media.mimeType,
description: media.id,
parents: [folderID],
);
- final fileLength = localFile.lengthSync();
- int chunk = 0;
- final googleDriveFile = await driveApi.files.create(
- file,
- uploadMedia: drive.Media(
- localFile.openRead().map((event) {
- chunk += event.length;
- onProgress?.call(fileLength, chunk);
- return event;
- }),
- fileLength),
+
+ final res = await _client.req(
+ UploadGoogleDriveFile(
+ request: file,
+ content: AppMediaContent(
+ stream: localFile.openRead(),
+ length: localFile.lengthSync(),
+ contentType: 'application/octet-stream',
+ ),
+ onProgress: onProgress,
+ cancellationToken: cancelToken,
+ ),
);
- return AppMedia.fromGoogleDriveFile(googleDriveFile);
+
+ return AppMedia.fromGoogleDriveFile(drive.File.fromJson(res.data));
} catch (error) {
- if (error is drive.DetailedApiRequestError && error.status == 404) {
+ if (error is AppError && error.statusCode == 404) {
throw const BackUpFolderNotFound();
}
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);
+ Future downloadFromGoogleDrive(
+ {required String id,
+ required String saveLocation,
+ void Function(int chunk, int total)? onProgress,
+ CancelToken? cancelToken}) async {
+ try {
+ await _client.downloadReq(
+ DownloadGoogleDriveFileContent(
+ id: id,
+ cancellationToken: cancelToken,
+ saveLocation: saveLocation,
+ onProgress: onProgress,
+ ),
+ );
+ } catch (e) {
+ throw AppError.fromError(e);
+ }
}
}
diff --git a/data/lib/services/local_media_service.dart b/data/lib/services/local_media_service.dart
index 7a1dac0..d4d6a4e 100644
--- a/data/lib/services/local_media_service.dart
+++ b/data/lib/services/local_media_service.dart
@@ -2,9 +2,7 @@ import 'dart:async';
import 'dart:io';
import 'package:collection/collection.dart';
import 'package:data/models/media/media.dart';
-import 'package:data/models/media_content/media_content.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
-import 'package:path_provider/path_provider.dart';
import 'package:photo_manager/photo_manager.dart';
import '../errors/app_error.dart';
@@ -15,9 +13,10 @@ final localMediaServiceProvider = Provider(
class LocalMediaService {
const LocalMediaService();
-
- Future isLocalFileExist({required AppMediaType type, required String id}) async {
- return await AssetEntity(id: id, typeInt: type.index, width: 0, height: 0).isLocallyAvailable();
+ Future isLocalFileExist(
+ {required AppMediaType type, required String id}) async {
+ return await AssetEntity(id: id, typeInt: type.index, width: 0, height: 0)
+ .isLocallyAvailable();
}
Future requestPermission() async {
@@ -62,52 +61,28 @@ class LocalMediaService {
}
}
- Future saveMedia({
+ Future saveInGallery({
+ required String saveFromLocation,
required AppMediaType type,
- required String? mimeType,
- required AppMediaContent content,
- required void Function(int total, int chunk) onProgress,
}) async {
+ AssetEntity? asset;
try {
- final extension = mimeType?.trim().isNotEmpty ?? false
- ? mimeType!.split('/').last
- : type.isVideo
- ? 'mp4'
- : 'jpg';
-
- AssetEntity? asset;
-
- final tempDir = await getTemporaryDirectory();
- final tempFile = File(
- '${tempDir.path}${DateTime.now()}_gd_cloud_gallery_temp.$extension');
- await tempFile.create();
-
- int chunkLength = 0;
-
- StreamSubscription> subscription =
- content.stream.listen((chunk) {
- chunkLength += chunk.length;
- onProgress(content.length ?? 0, chunkLength);
- tempFile.writeAsBytesSync(chunk, mode: FileMode.append);
- });
- await subscription.asFuture();
- subscription.cancel();
-
if (type.isVideo) {
asset = await PhotoManager.editor.saveVideo(
- tempFile,
- title: "${DateTime.now()}_gd_cloud_gallery.$extension",
+ File(saveFromLocation),
+ title: saveFromLocation.split('/').last,
);
} else if (type.isImage) {
asset = await PhotoManager.editor.saveImageWithPath(
- tempFile.path,
- title: "${DateTime.now()}_gd_cloud_gallery.$extension",
+ saveFromLocation,
+ title: saveFromLocation.split('/').last,
);
}
- await tempFile.delete();
return asset != null ? AppMedia.fromAssetEntity(asset) : null;
} catch (e) {
throw AppError.fromError(e);
}
}
}
+
+
diff --git a/data/lib/storage/app_preferences.dart b/data/lib/storage/app_preferences.dart
index 35c6b4d..0bb6818 100644
--- a/data/lib/storage/app_preferences.dart
+++ b/data/lib/storage/app_preferences.dart
@@ -12,6 +12,11 @@ class AppPreferences {
defaultValue: null,
);
+ static StateProvider notifications = createPrefProvider(
+ prefKey: "show_notifications",
+ defaultValue: true,
+ );
+
static StateProvider canTakeAutoBackUpInGoogleDrive =
createPrefProvider(
prefKey: "google_drive_auto_backup",
diff --git a/data/pubspec.yaml b/data/pubspec.yaml
index b429eb3..c7f0d99 100644
--- a/data/pubspec.yaml
+++ b/data/pubspec.yaml
@@ -12,7 +12,8 @@ dependencies:
# services
googleapis: ^12.0.0
- http: ^1.2.0
+ http_parser: ^4.0.2
+ dio: ^5.4.3+1
photo_manager: ^3.0.0-dev.5
package_info_plus: ^5.0.1
path_provider: ^2.1.2