-
Notifications
You must be signed in to change notification settings - Fork 7
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b1522f1
commit 8385719
Showing
17 changed files
with
984 additions
and
9 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,122 @@ | ||
import 'package:flutter/material.dart'; | ||
import 'package:flutter/services.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
import 'package:style/animations/fade_in_switcher.dart'; | ||
import 'package:style/buttons/primary_button.dart'; | ||
import 'package:style/extensions/context_extensions.dart'; | ||
import 'package:style/indicators/circular_progress_indicator.dart'; | ||
import '../../../components/app_page.dart'; | ||
import '../../../components/error_screen.dart'; | ||
import '../../../components/place_holder_screen.dart'; | ||
import '../../../components/snack_bar.dart'; | ||
import '../../../domain/extensions/context_extensions.dart'; | ||
import '../home/components/app_media_item.dart'; | ||
import 'clean_up_state_notifier.dart'; | ||
|
||
class CleanUpScreen extends ConsumerStatefulWidget { | ||
const CleanUpScreen({super.key}); | ||
|
||
@override | ||
ConsumerState<CleanUpScreen> createState() => _BinScreenState(); | ||
} | ||
|
||
class _BinScreenState extends ConsumerState<CleanUpScreen> { | ||
late CleanUpStateNotifier _notifier; | ||
|
||
@override | ||
void initState() { | ||
_notifier = ref.read(cleanUpStateNotifierProvider.notifier); | ||
super.initState(); | ||
} | ||
|
||
void _observeError() { | ||
ref.listen( | ||
cleanUpStateNotifierProvider.select((value) => value.actionError), | ||
(previous, next) { | ||
if (next != null) { | ||
showErrorSnackBar(context: context, error: next); | ||
} | ||
}, | ||
); | ||
} | ||
|
||
@override | ||
Widget build(BuildContext context) { | ||
_observeError(); | ||
return AppPage( | ||
title: context.l10n.clean_up_screen_title, | ||
body: FadeInSwitcher(child: _body(context)), | ||
); | ||
} | ||
|
||
Widget _body(BuildContext context) { | ||
final state = ref.watch(cleanUpStateNotifierProvider); | ||
|
||
if (state.loading) { | ||
return const Center(child: AppCircularProgressIndicator()); | ||
} else if (state.error != null) { | ||
return ErrorScreen( | ||
error: state.error!, | ||
onRetryTap: _notifier.loadCleanUpMedias, | ||
); | ||
} else if (state.medias.isEmpty) { | ||
return PlaceHolderScreen( | ||
icon: Icon( | ||
Icons.cleaning_services, | ||
size: 100, | ||
color: context.colorScheme.containerHighOnSurface, | ||
), | ||
title: context.l10n.empty_clean_up_title, | ||
message: context.l10n.empty_clean_up_message, | ||
); | ||
} | ||
|
||
return Column( | ||
children: [ | ||
Expanded( | ||
child: GridView.builder( | ||
padding: const EdgeInsets.all(4), | ||
physics: const NeverScrollableScrollPhysics(), | ||
shrinkWrap: true, | ||
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( | ||
crossAxisCount: (context.mediaQuerySize.width > 600 | ||
? context.mediaQuerySize.width ~/ 180 | ||
: context.mediaQuerySize.width ~/ 100) | ||
.clamp(1, 6), | ||
crossAxisSpacing: 4, | ||
mainAxisSpacing: 4, | ||
), | ||
itemCount: state.medias.length, | ||
itemBuilder: (context, index) { | ||
return AppMediaItem( | ||
media: state.medias[index], | ||
heroTag: "clean_up${state.medias[index].toString()}", | ||
onTap: () async { | ||
_notifier.toggleSelection(state.medias[index].id); | ||
HapticFeedback.lightImpact(); | ||
}, | ||
isSelected: state.selected.contains(state.medias[index].id), | ||
); | ||
}, | ||
), | ||
), | ||
Padding( | ||
padding: const EdgeInsets.all(16) | ||
.copyWith(bottom: 16 + context.systemPadding.bottom), | ||
child: SizedBox( | ||
width: double.infinity, | ||
child: PrimaryButton( | ||
onPressed: _notifier.deleteAll, | ||
text: context.l10n.clean_up_title, | ||
child: state.deleteAllLoading | ||
? AppCircularProgressIndicator( | ||
color: context.colorScheme.onPrimary, | ||
) | ||
: Text(context.l10n.clean_up_title), | ||
), | ||
), | ||
), | ||
], | ||
); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
import 'package:data/log/logger.dart'; | ||
import 'package:data/models/media/media.dart'; | ||
import 'package:data/services/local_media_service.dart'; | ||
import 'package:flutter_riverpod/flutter_riverpod.dart'; | ||
import 'package:freezed_annotation/freezed_annotation.dart'; | ||
import 'package:logger/logger.dart'; | ||
|
||
part 'clean_up_state_notifier.freezed.dart'; | ||
|
||
final cleanUpStateNotifierProvider = | ||
StateNotifierProvider.autoDispose<CleanUpStateNotifier, CleanUpState>( | ||
(ref) { | ||
return CleanUpStateNotifier( | ||
ref.read(localMediaServiceProvider), | ||
ref.read(loggerProvider), | ||
); | ||
}); | ||
|
||
class CleanUpStateNotifier extends StateNotifier<CleanUpState> { | ||
final LocalMediaService _localMediaService; | ||
final Logger _logger; | ||
|
||
CleanUpStateNotifier( | ||
this._localMediaService, | ||
this._logger, | ||
) : super(const CleanUpState()) { | ||
loadCleanUpMedias(); | ||
} | ||
|
||
Future<void> loadCleanUpMedias() async { | ||
try { | ||
state = state.copyWith(loading: true, error: null); | ||
final cleanUpMedias = await _localMediaService.getCleanUpMedias(); | ||
|
||
final medias = await Future.wait( | ||
cleanUpMedias.map( | ||
(e) => _localMediaService.getMedia(id: e.id), | ||
), | ||
).then( | ||
(value) => value.nonNulls.toList(), | ||
); | ||
|
||
state = state.copyWith(loading: false, medias: medias); | ||
} catch (e, s) { | ||
state = state.copyWith(loading: false, error: e); | ||
_logger.e( | ||
"BinStateNotifier: Error occur while loading bin items", | ||
error: e, | ||
stackTrace: s, | ||
); | ||
} | ||
} | ||
|
||
void toggleSelection(String id) { | ||
final selected = state.selected.toList(); | ||
if (selected.contains(id)) { | ||
state = state.copyWith(selected: selected..remove(id)); | ||
} else { | ||
state = state.copyWith(selected: [...selected, id]); | ||
} | ||
} | ||
|
||
Future<void> deleteSelected() async { | ||
try { | ||
final deleteMedias = state.selected; | ||
state = state.copyWith( | ||
deleteSelectedLoading: deleteMedias, | ||
selected: [], | ||
actionError: null, | ||
); | ||
final res = await _localMediaService.deleteMedias(deleteMedias); | ||
if (res.isNotEmpty) { | ||
await _localMediaService.removeFromCleanUpMediaDatabase(res); | ||
} | ||
state = state.copyWith( | ||
deleteSelectedLoading: [], | ||
medias: | ||
state.medias.where((e) => !deleteMedias.contains(e.id)).toList(), | ||
); | ||
} catch (e, s) { | ||
state = state.copyWith(deleteSelectedLoading: [], actionError: e); | ||
_logger.e( | ||
"BinStateNotifier: Error occur while deleting selected bin items", | ||
error: e, | ||
stackTrace: s, | ||
); | ||
} | ||
} | ||
|
||
Future<void> deleteAll() async { | ||
try { | ||
state = state.copyWith(deleteAllLoading: true, actionError: null); | ||
final res = await _localMediaService | ||
.deleteMedias(state.medias.map((e) => e.id).toList()); | ||
|
||
if (res.isNotEmpty) { | ||
await _localMediaService.clearCleanUpMediaDatabase(); | ||
} | ||
state = state.copyWith( | ||
deleteAllLoading: false, | ||
selected: [], | ||
medias: [], | ||
); | ||
} catch (e, s) { | ||
state = state.copyWith(deleteAllLoading: false, actionError: e); | ||
_logger.e( | ||
"BinStateNotifier: Error occur while deleting all bin items", | ||
error: e, | ||
stackTrace: s, | ||
); | ||
} | ||
} | ||
} | ||
|
||
@freezed | ||
class CleanUpState with _$CleanUpState { | ||
const factory CleanUpState({ | ||
@Default(false) bool deleteAllLoading, | ||
@Default([]) List<String> deleteSelectedLoading, | ||
@Default([]) List<AppMedia> medias, | ||
@Default([]) List<String> selected, | ||
@Default(false) bool loading, | ||
Object? error, | ||
Object? actionError, | ||
}) = _CleanUpState; | ||
} |
Oops, something went wrong.