improved update handling

This commit is contained in:
Myzel394 2022-08-15 20:43:37 +02:00
parent a2c5dd8237
commit ffcaec244a
5 changed files with 159 additions and 70 deletions

View File

@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:share_location/constants/values.dart'; import 'package:share_location/constants/values.dart';
import 'package:supabase_flutter/supabase_flutter.dart'; import 'package:supabase_flutter/supabase_flutter.dart';
@ -26,30 +27,42 @@ extension ShowSnackBar on BuildContext {
); );
} }
showToast({
required final String message,
final Toast toastLength = Toast.LENGTH_SHORT,
final Color backgroundColor = Colors.white,
final Color textColor = Colors.black,
}) {
Fluttertoast.showToast(
msg: message,
toastLength: toastLength,
backgroundColor: backgroundColor,
textColor: textColor,
);
}
void showErrorSnackBar({ void showErrorSnackBar({
required final String message, required final String message,
final BuildContext? context,
}) { }) {
showSnackBar( showSnackBar(
message: message, message: message,
backgroundColor: Colors.red, backgroundColor: Colors.red,
duration: const Duration(milliseconds: 550),
); );
} }
void showSuccessSnackBar({ void showSuccessSnackBar({
required final String message, required final String message,
final BuildContext? context,
}) { }) {
showSnackBar( showSnackBar(
message: message, message: message,
duration: const Duration(milliseconds: 550),
backgroundColor: Colors.green, backgroundColor: Colors.green,
duration: const Duration(milliseconds: 550),
); );
} }
void showPendingSnackBar({ void showPendingSnackBar({
required final String message, required final String message,
final BuildContext? context,
}) { }) {
pendingSnackBar = showSnackBar( pendingSnackBar = showSnackBar(
message: message, message: message,
@ -57,4 +70,26 @@ extension ShowSnackBar on BuildContext {
duration: DURATION_INFINITY, duration: DURATION_INFINITY,
); );
} }
void showSuccessToast({
required final String message,
}) {
showToast(
message: message,
backgroundColor: Colors.green,
textColor: Colors.white,
toastLength: Toast.LENGTH_SHORT,
);
}
void showErrorToast({
required final String message,
}) {
showToast(
message: message,
backgroundColor: Colors.red,
textColor: Colors.white,
toastLength: Toast.LENGTH_SHORT,
);
}
} }

View File

@ -4,6 +4,9 @@ import 'package:intl/intl.dart';
import 'package:property_change_notifier/property_change_notifier.dart'; import 'package:property_change_notifier/property_change_notifier.dart';
import 'package:share_location/foreign_types/memory.dart'; import 'package:share_location/foreign_types/memory.dart';
import 'package:share_location/models/memory_pack.dart'; import 'package:share_location/models/memory_pack.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
final supabase = Supabase.instance.client;
class TimelineModel extends PropertyChangeNotifier<String> { class TimelineModel extends PropertyChangeNotifier<String> {
final Map<String, MemoryPack> _timeline; final Map<String, MemoryPack> _timeline;
@ -15,14 +18,16 @@ class TimelineModel extends PropertyChangeNotifier<String> {
int _currentIndex = 0; int _currentIndex = 0;
int _memoryIndex = 0; int _memoryIndex = 0;
bool _paused = false; bool _paused = false;
bool _isInitializing = true;
Map<String, MemoryPack> get values => _timeline; Map<String, MemoryPack> get values => _timeline;
int get length => _timeline.length; int get length => _timeline.length;
int get currentIndex => _currentIndex; int get currentIndex => _currentIndex;
int get memoryIndex => _memoryIndex; int get memoryIndex => _memoryIndex;
bool get paused => _paused; bool get paused => _paused;
bool get isInitializing => _isInitializing;
static TimelineModel fromMemoriesList( static Map<String, MemoryPack> mapFromMemoriesList(
final List<Memory> memories, final List<Memory> memories,
) { ) {
final map = <String, List<Memory>>{}; final map = <String, List<Memory>>{};
@ -36,7 +41,7 @@ class TimelineModel extends PropertyChangeNotifier<String> {
} }
} }
final data = Map.fromEntries( return Map.fromEntries(
map.entries.map( map.entries.map(
(entry) => MapEntry<String, MemoryPack>( (entry) => MapEntry<String, MemoryPack>(
entry.key, entry.key,
@ -44,10 +49,6 @@ class TimelineModel extends PropertyChangeNotifier<String> {
), ),
), ),
); );
return TimelineModel(
timeline: data,
);
} }
DateTime dateAtIndex(final int index) => DateTime dateAtIndex(final int index) =>
@ -62,16 +63,8 @@ class TimelineModel extends PropertyChangeNotifier<String> {
Memory get currentMemory => Memory get currentMemory =>
currentMemoryPack.memories.elementAt(_memoryIndex); currentMemoryPack.memories.elementAt(_memoryIndex);
void removeEmptyDates() { void _removeEmptyDates() {
final previousLength = _timeline.length;
_timeline.removeWhere((key, value) => value.memories.isEmpty); _timeline.removeWhere((key, value) => value.memories.isEmpty);
final newLength = _timeline.length;
if (previousLength != newLength) {
notifyListeners();
}
} }
void setCurrentIndex(final int index) { void setCurrentIndex(final int index) {
@ -92,14 +85,22 @@ class TimelineModel extends PropertyChangeNotifier<String> {
notifyListeners('paused'); notifyListeners('paused');
} }
void setIsInitializing(bool isInitializing) {
_isInitializing = isInitializing;
notifyListeners('isInitializing');
}
void removeMemory( void removeMemory(
final int timelineIndex, final int timelineIndex,
final int memoryIndex, final int memoryIndex,
) { ) {
_timeline.values.elementAt(timelineIndex).memories.removeAt(memoryIndex); _timeline.values.elementAt(timelineIndex).memories.removeAt(memoryIndex);
_removeEmptyDates();
notifyListeners(); notifyListeners();
} }
void removeCurrentMemory() => removeMemory(_currentIndex, _memoryIndex);
void pause() => setPaused(true); void pause() => setPaused(true);
void resume() => setPaused(false); void resume() => setPaused(false);
@ -135,4 +136,29 @@ class TimelineModel extends PropertyChangeNotifier<String> {
setMemoryIndex(memoryIndex - 1); setMemoryIndex(memoryIndex - 1);
} }
} }
Future<void> initialize() async {
setIsInitializing(true);
await refreshFromServer();
setIsInitializing(false);
}
Future<void> refreshFromServer() async {
final response = await supabase
.from('memories')
.select()
.order('created_at', ascending: false)
.execute();
final memories = List<Memory>.from(
List<Map<String, dynamic>>.from(response.data).map(Memory.parse),
);
values
..clear()
..addAll(mapFromMemoriesList(memories));
notifyListeners();
}
} }

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:gallery_saver/gallery_saver.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/enums.dart';
@ -8,31 +7,37 @@ 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';
import 'package:share_location/widgets/modal_sheet.dart'; import 'package:share_location/widgets/modal_sheet.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class MemorySheet extends StatefulWidget { class MemorySheet extends StatefulWidget {
final Memory memory; final Memory memory;
final VoidCallback onMemoryDeleted;
final BuildContext sheetContext; final BuildContext sheetContext;
final VoidCallback onDelete;
final VoidCallback onVisibilityChanged;
const MemorySheet({ const MemorySheet({
Key? key, Key? key,
required this.memory, required this.memory,
required this.onMemoryDeleted,
required this.sheetContext, required this.sheetContext,
required this.onDelete,
required this.onVisibilityChanged,
}) : super(key: key); }) : super(key: key);
@override @override
State<MemorySheet> createState() => _MemorySheetState(); State<MemorySheet> createState() => _MemorySheetState();
} }
final supabase = Supabase.instance.client;
class _MemorySheetState extends State<MemorySheet> with Loadable { 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();
if (mounted) { if (mounted) {
Navigator.pop(context); Navigator.pop(context);
} }
widget.onDelete();
} }
Future<void> downloadFile() async { Future<void> downloadFile() async {
@ -57,13 +62,31 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
context.showSuccessSnackBar(message: 'File saved to Gallery!'); context.showSuccessSnackBar(message: 'File saved to Gallery!');
} catch (error) { } catch (error) {
context.showErrorSnackBar(message: 'There was an error'); context.showErrorSnackBar(message: 'There was an error.');
Fluttertoast.showToast( }
msg: 'There was an error', }
toastLength: Toast.LENGTH_SHORT,
backgroundColor: Colors.red, Future<void> changeVisibility() async {
textColor: Colors.white, final isNowPublic = !widget.memory.isPublic == true;
);
try {
await supabase.from('memories').update({
'is_public': !widget.memory.isPublic,
}).match({
'id': widget.memory.id,
}).execute();
Navigator.pop(context);
if (isNowPublic) {
context.showSuccessSnackBar(message: 'Your Memory is public now!');
} else {
context.showSuccessSnackBar(message: 'Your Memory is private now.');
}
widget.onVisibilityChanged();
} catch (error) {
context.showErrorSnackBar(message: 'There was an error.');
} }
} }
@ -103,6 +126,20 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
? buildLoadingIndicator() ? buildLoadingIndicator()
: null, : null,
), ),
ListTile(
leading: Icon(widget.memory.isPublic
? Icons.public_off_rounded
: Icons.public_rounded),
title:
Text(widget.memory.isPublic ? 'Make private' : 'Make public'),
enabled: !getIsLoadingSpecificID('public'),
onTap: getIsLoadingSpecificID('public')
? null
: () => callWithLoading(changeVisibility, 'public'),
trailing: getIsLoadingSpecificID('public')
? buildLoadingIndicator()
: null,
),
ListTile( ListTile(
leading: const Icon(Icons.delete_forever_sharp), leading: const Icon(Icons.delete_forever_sharp),
title: const Text('Delete Memory'), title: const Text('Delete Memory'),

View File

@ -122,7 +122,12 @@ class _TimelinePageState extends State<TimelinePage> {
builder: (sheetContext) => MemorySheet( builder: (sheetContext) => MemorySheet(
memory: timeline.currentMemory, memory: timeline.currentMemory,
sheetContext: sheetContext, sheetContext: sheetContext,
onMemoryDeleted: timeline.removeEmptyDates, onDelete: () async {
timeline.removeCurrentMemory();
},
onVisibilityChanged: () async {
timeline.refreshFromServer();
},
), ),
); );

View File

@ -1,6 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:share_location/foreign_types/memory.dart';
import 'package:share_location/models/timeline.dart'; import 'package:share_location/models/timeline.dart';
import 'package:share_location/utils/loadable.dart'; import 'package:share_location/utils/loadable.dart';
import 'package:share_location/widgets/timeline_page.dart'; import 'package:share_location/widgets/timeline_page.dart';
@ -19,54 +18,41 @@ class TimelineScroll extends StatefulWidget {
class _TimelineScrollState extends State<TimelineScroll> with Loadable { class _TimelineScrollState extends State<TimelineScroll> with Loadable {
final pageController = PageController(); final pageController = PageController();
TimelineModel? timeline; final timeline = TimelineModel();
@override @override
initState() { initState() {
super.initState(); super.initState();
loadTimeline();
timeline.initialize();
// Update page view
timeline.addListener(() {
if (timeline.currentIndex != pageController.page) {
pageController.animateToPage(
timeline.currentIndex,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutQuad,
);
}
}, ['currentIndex']);
// Update page when initializing is done
timeline.addListener(() {
setState(() {});
}, ['isInitializing']);
} }
@override @override
dispose() { dispose() {
pageController.dispose(); pageController.dispose();
timeline?.dispose();
super.dispose(); super.dispose();
} }
Future<void> loadTimeline() async {
timeline?.dispose();
final response = await supabase
.from('memories')
.select()
.order('created_at', ascending: false)
.execute();
final memories = List<Memory>.from(
List<Map<String, dynamic>>.from(response.data).map(Memory.parse));
final newTimeline = TimelineModel.fromMemoriesList(memories);
setState(() {
timeline = newTimeline;
});
// Update page
newTimeline.addListener(() {
if (newTimeline.currentIndex != pageController.page) {
pageController.animateToPage(
newTimeline.currentIndex,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutQuad,
);
}
}, ['currentIndex']);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (timeline == null) { if (timeline.isInitializing) {
return const Center( return const Center(
child: CircularProgressIndicator(), child: CircularProgressIndicator(),
); );
@ -78,17 +64,17 @@ class _TimelineScrollState extends State<TimelineScroll> with Loadable {
child: PageView.builder( child: PageView.builder(
controller: pageController, controller: pageController,
scrollDirection: Axis.vertical, scrollDirection: Axis.vertical,
itemCount: timeline!.values.length, itemCount: timeline.values.length,
onPageChanged: (newPage) { onPageChanged: (newPage) {
if (timeline!.currentIndex != newPage) { if (timeline.currentIndex != newPage) {
// User manually changed page // User manually changed page
timeline!.setCurrentIndex(newPage); timeline.setCurrentIndex(newPage);
timeline!.setMemoryIndex(0); timeline.setMemoryIndex(0);
} }
}, },
itemBuilder: (_, index) => TimelinePage( itemBuilder: (_, index) => TimelinePage(
date: timeline!.dateAtIndex(index), date: timeline.dateAtIndex(index),
), ),
), ),
), ),