added download functionality

This commit is contained in:
Myzel394 2022-08-15 20:07:15 +02:00
parent 351bd6fec9
commit a2c5dd8237
9 changed files with 163 additions and 40 deletions

View File

@ -12,11 +12,12 @@ extension ShowSnackBar on BuildContext {
required final String message, required final String message,
final Color backgroundColor = Colors.white, final Color backgroundColor = Colors.white,
final Duration duration = const Duration(seconds: 4), final Duration duration = const Duration(seconds: 4),
final BuildContext? context,
}) { }) {
pendingSnackBar?.close(); pendingSnackBar?.close();
pendingSnackBar = null; pendingSnackBar = null;
return ScaffoldMessenger.of(this).showSnackBar( return ScaffoldMessenger.of(context ?? this).showSnackBar(
SnackBar( SnackBar(
content: Text(message), content: Text(message),
backgroundColor: backgroundColor, backgroundColor: backgroundColor,
@ -25,14 +26,20 @@ extension ShowSnackBar on BuildContext {
); );
} }
void showErrorSnackBar({required final String message}) { void showErrorSnackBar({
required final String message,
final BuildContext? context,
}) {
showSnackBar( showSnackBar(
message: message, message: message,
backgroundColor: Colors.red, backgroundColor: Colors.red,
); );
} }
void showSuccessSnackBar({required final String message}) { void showSuccessSnackBar({
required final String message,
final BuildContext? context,
}) {
showSnackBar( showSnackBar(
message: message, message: message,
duration: const Duration(milliseconds: 550), duration: const Duration(milliseconds: 550),
@ -40,7 +47,10 @@ extension ShowSnackBar on BuildContext {
); );
} }
void showPendingSnackBar({required final String message}) { void showPendingSnackBar({
required final String message,
final BuildContext? context,
}) {
pendingSnackBar = showSnackBar( pendingSnackBar = showSnackBar(
message: message, message: message,
backgroundColor: Colors.yellow, backgroundColor: Colors.yellow,

View File

@ -1,6 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:path_provider/path_provider.dart';
import 'package:share_location/enums.dart'; import 'package:share_location/enums.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
@ -65,7 +66,7 @@ class FileManager {
location.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video; location.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video;
try { try {
final file = await downloadFile('memories', location); final file = await getFileData('memories', location);
return [file, memoryType]; return [file, memoryType];
} catch (error) { } catch (error) {
@ -73,13 +74,11 @@ class FileManager {
} }
} }
static Future<Uint8List> downloadFile( static Future<Uint8List> getFileData(final String table, final String path,
final String table, {final bool disableCache = false}) async {
final String path,
) async {
final key = '$table:$path'; final key = '$table:$path';
if (fileCache.containsKey(key)) { if (!disableCache && fileCache.containsKey(key)) {
return fileCache[key]!; return fileCache[key]!;
} }
@ -96,6 +95,30 @@ class FileManager {
return data; return data;
} }
static Future<File> downloadFile(
final String table,
final String path, {
final bool disableDownloadCache = false,
final bool disableFileCache = false,
}) async {
final tempDirectory = await getTemporaryDirectory();
final filename = '${tempDirectory.path}/$path';
final file = File(filename);
if (!disableFileCache && (await file.exists())) {
return file;
}
final data =
await getFileData(table, path, disableCache: disableDownloadCache);
// Create file
await file.create(recursive: true);
await file.writeAsBytes(data);
return file;
}
static Future<void> deleteFile(final String path) async { static Future<void> deleteFile(final String path) async {
final response = final response =
await supabase.from('memories').delete().eq('location', path).execute(); await supabase.from('memories').delete().eq('location', path).execute();

View File

@ -1,20 +1,31 @@
mixin Loadable { import 'package:uuid/uuid.dart';
bool _isLoading = false;
bool get isLoading => _isLoading; const uuid = Uuid();
mixin Loadable {
static final String _generalLoadingID = '#_loadable-${uuid.v4()}';
final Set<String> _IDs = <String>{};
bool get isLoading => _IDs.contains(_generalLoadingID);
bool getIsLoadingSpecificID(final String id) => _IDs.contains(id);
bool getIsLoading(final String id) => isLoading || getIsLoadingSpecificID(id);
void setState(void Function() callback); void setState(void Function() callback);
Future<void> callWithLoading(Future<void> Function() callback) async { Future<void> callWithLoading(
Future<void> Function() callback, [
final String? id,
]) async {
setState(() { setState(() {
_isLoading = true; _IDs.add(id ?? _generalLoadingID);
}); });
try { try {
await callback(); await callback();
} finally { } finally {
setState(() { setState(() {
_isLoading = false; _IDs.remove(id ?? _generalLoadingID);
}); });
} }
} }

View File

@ -98,7 +98,7 @@ class _MemoryViewState extends AuthRequiredState<MemoryView> {
location.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video; location.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video;
try { try {
final fileData = await FileManager.downloadFile('memories', location); final fileData = await FileManager.getFileData('memories', location);
if (!mounted) { if (!mounted) {
return; return;

View File

@ -1,5 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:gallery_saver/gallery_saver.dart';
import 'package:share_location/constants/spacing.dart'; import 'package:share_location/constants/spacing.dart';
import 'package:share_location/enums.dart';
import 'package:share_location/extensions/snackbar.dart';
import 'package:share_location/foreign_types/memory.dart'; import 'package:share_location/foreign_types/memory.dart';
import 'package:share_location/managers/file_manager.dart'; import 'package:share_location/managers/file_manager.dart';
import 'package:share_location/utils/loadable.dart'; import 'package:share_location/utils/loadable.dart';
@ -8,11 +12,13 @@ import 'package:share_location/widgets/modal_sheet.dart';
class MemorySheet extends StatefulWidget { class MemorySheet extends StatefulWidget {
final Memory memory; final Memory memory;
final VoidCallback onMemoryDeleted; final VoidCallback onMemoryDeleted;
final BuildContext sheetContext;
const MemorySheet({ const MemorySheet({
Key? key, Key? key,
required this.memory, required this.memory,
required this.onMemoryDeleted, required this.onMemoryDeleted,
required this.sheetContext,
}) : super(key: key); }) : super(key: key);
@override @override
@ -23,6 +29,55 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
Future<void> deleteFile() async { Future<void> deleteFile() async {
await FileManager.deleteFile(widget.memory.location); await FileManager.deleteFile(widget.memory.location);
widget.onMemoryDeleted(); widget.onMemoryDeleted();
if (mounted) {
Navigator.pop(context);
}
}
Future<void> downloadFile() async {
try {
final file =
await FileManager.downloadFile('memories', widget.memory.location);
if (!mounted) {
return;
}
switch (widget.memory.type) {
case MemoryType.photo:
await GallerySaver.saveImage(file.path);
break;
case MemoryType.video:
await GallerySaver.saveVideo(file.path);
break;
}
Navigator.pop(context);
context.showSuccessSnackBar(message: 'File saved to Gallery!');
} catch (error) {
context.showErrorSnackBar(message: 'There was an error');
Fluttertoast.showToast(
msg: 'There was an error',
toastLength: Toast.LENGTH_SHORT,
backgroundColor: Colors.red,
textColor: Colors.white,
);
}
}
Widget buildLoadingIndicator() {
final theme = Theme.of(context);
return SizedBox(
width: theme.textTheme.titleLarge!.fontSize,
height: theme.textTheme.titleLarge!.fontSize,
child: CircularProgressIndicator(
strokeWidth: 2,
color: theme.textTheme.bodyText1!.color,
),
);
} }
@override @override
@ -38,18 +93,26 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
), ),
const SizedBox(height: MEDIUM_SPACE), const SizedBox(height: MEDIUM_SPACE),
ListTile( ListTile(
leading: Icon(Icons.delete_forever_sharp), leading: const Icon(Icons.download),
title: Text('Delete Memory'), title: const Text('Download to Gallery'),
onTap: isLoading enabled: !getIsLoadingSpecificID('download'),
onTap: getIsLoadingSpecificID('download')
? null ? null
: () async { : () => callWithLoading(downloadFile, 'download'),
await callWithLoading(deleteFile); trailing: getIsLoadingSpecificID('download')
? buildLoadingIndicator()
if (mounted) { : null,
Navigator.pop(context); ),
} ListTile(
}, leading: const Icon(Icons.delete_forever_sharp),
trailing: isLoading ? const CircularProgressIndicator() : null, title: const Text('Delete Memory'),
enabled: !getIsLoadingSpecificID('delete'),
onTap: getIsLoadingSpecificID('delete')
? null
: () => callWithLoading(deleteFile, 'delete'),
trailing: getIsLoadingSpecificID('delete')
? buildLoadingIndicator()
: null,
), ),
], ],
), ),

View File

@ -19,20 +19,19 @@ class ModalSheet extends StatelessWidget {
Padding( Padding(
padding: padding:
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom), EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
child: Container( child: Material(
width: double.infinity,
padding: const EdgeInsets.all(MEDIUM_SPACE),
decoration: BoxDecoration(
color: theme.bottomSheetTheme.modalBackgroundColor ??
theme.bottomAppBarColor,
borderRadius: const BorderRadius.only( borderRadius: const BorderRadius.only(
topLeft: Radius.circular(LARGE_SPACE), topLeft: Radius.circular(LARGE_SPACE),
topRight: Radius.circular(LARGE_SPACE), topRight: Radius.circular(LARGE_SPACE),
), ),
), color: theme.bottomSheetTheme.modalBackgroundColor ??
theme.bottomAppBarColor,
child: Container(
padding: const EdgeInsets.all(MEDIUM_SPACE),
child: child, child: child,
), ),
), ),
),
], ],
); );
} }

View File

@ -119,8 +119,9 @@ class _TimelinePageState extends State<TimelinePage> {
context: context, context: context,
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
isScrollControlled: true, isScrollControlled: true,
builder: (_) => MemorySheet( builder: (sheetContext) => MemorySheet(
memory: timeline.currentMemory, memory: timeline.currentMemory,
sheetContext: sheetContext,
onMemoryDeleted: timeline.removeEmptyDates, onMemoryDeleted: timeline.removeEmptyDates,
), ),
); );

View File

@ -205,6 +205,13 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
url: "https://pub.dartlang.org"
source: hosted
version: "8.0.9"
functions_client: functions_client:
dependency: transitive dependency: transitive
description: description:
@ -212,6 +219,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.0.1-dev.5" version: "0.0.1-dev.5"
gallery_saver:
dependency: "direct main"
description:
name: gallery_saver
url: "https://pub.dartlang.org"
source: hosted
version: "2.3.2"
gotrue: gotrue:
dependency: transitive dependency: transitive
description: description:

View File

@ -46,6 +46,8 @@ dependencies:
property_change_notifier: ^0.3.0 property_change_notifier: ^0.3.0
path: ^1.8.1 path: ^1.8.1
provider: ^6.0.3 provider: ^6.0.3
gallery_saver: ^2.3.2
fluttertoast: ^8.0.9
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: