mirror of
https://github.com/Myzel394/quid_faciam_hodie.git
synced 2025-06-19 07:35:26 +02:00
added download functionality
This commit is contained in:
parent
351bd6fec9
commit
a2c5dd8237
@ -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,
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
@ -19,18 +19,17 @@ 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,
|
borderRadius: const BorderRadius.only(
|
||||||
padding: const EdgeInsets.all(MEDIUM_SPACE),
|
topLeft: Radius.circular(LARGE_SPACE),
|
||||||
decoration: BoxDecoration(
|
topRight: Radius.circular(LARGE_SPACE),
|
||||||
color: theme.bottomSheetTheme.modalBackgroundColor ??
|
),
|
||||||
theme.bottomAppBarColor,
|
color: theme.bottomSheetTheme.modalBackgroundColor ??
|
||||||
borderRadius: const BorderRadius.only(
|
theme.bottomAppBarColor,
|
||||||
topLeft: Radius.circular(LARGE_SPACE),
|
child: Container(
|
||||||
topRight: Radius.circular(LARGE_SPACE),
|
padding: const EdgeInsets.all(MEDIUM_SPACE),
|
||||||
),
|
child: child,
|
||||||
),
|
),
|
||||||
child: child,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -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,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
14
pubspec.lock
14
pubspec.lock
@ -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:
|
||||||
|
@ -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:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user