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:fluttertoast/fluttertoast.dart';
import 'package:share_location/constants/values.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({
required final String message,
final BuildContext? context,
}) {
showSnackBar(
message: message,
backgroundColor: Colors.red,
duration: const Duration(milliseconds: 550),
);
}
void showSuccessSnackBar({
required final String message,
final BuildContext? context,
}) {
showSnackBar(
message: message,
duration: const Duration(milliseconds: 550),
backgroundColor: Colors.green,
duration: const Duration(milliseconds: 550),
);
}
void showPendingSnackBar({
required final String message,
final BuildContext? context,
}) {
pendingSnackBar = showSnackBar(
message: message,
@ -57,4 +70,26 @@ extension ShowSnackBar on BuildContext {
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:share_location/foreign_types/memory.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> {
final Map<String, MemoryPack> _timeline;
@ -15,14 +18,16 @@ class TimelineModel extends PropertyChangeNotifier<String> {
int _currentIndex = 0;
int _memoryIndex = 0;
bool _paused = false;
bool _isInitializing = true;
Map<String, MemoryPack> get values => _timeline;
int get length => _timeline.length;
int get currentIndex => _currentIndex;
int get memoryIndex => _memoryIndex;
bool get paused => _paused;
bool get isInitializing => _isInitializing;
static TimelineModel fromMemoriesList(
static Map<String, MemoryPack> mapFromMemoriesList(
final List<Memory> memories,
) {
final map = <String, List<Memory>>{};
@ -36,7 +41,7 @@ class TimelineModel extends PropertyChangeNotifier<String> {
}
}
final data = Map.fromEntries(
return Map.fromEntries(
map.entries.map(
(entry) => MapEntry<String, MemoryPack>(
entry.key,
@ -44,10 +49,6 @@ class TimelineModel extends PropertyChangeNotifier<String> {
),
),
);
return TimelineModel(
timeline: data,
);
}
DateTime dateAtIndex(final int index) =>
@ -62,16 +63,8 @@ class TimelineModel extends PropertyChangeNotifier<String> {
Memory get currentMemory =>
currentMemoryPack.memories.elementAt(_memoryIndex);
void removeEmptyDates() {
final previousLength = _timeline.length;
void _removeEmptyDates() {
_timeline.removeWhere((key, value) => value.memories.isEmpty);
final newLength = _timeline.length;
if (previousLength != newLength) {
notifyListeners();
}
}
void setCurrentIndex(final int index) {
@ -92,14 +85,22 @@ class TimelineModel extends PropertyChangeNotifier<String> {
notifyListeners('paused');
}
void setIsInitializing(bool isInitializing) {
_isInitializing = isInitializing;
notifyListeners('isInitializing');
}
void removeMemory(
final int timelineIndex,
final int memoryIndex,
) {
_timeline.values.elementAt(timelineIndex).memories.removeAt(memoryIndex);
_removeEmptyDates();
notifyListeners();
}
void removeCurrentMemory() => removeMemory(_currentIndex, _memoryIndex);
void pause() => setPaused(true);
void resume() => setPaused(false);
@ -135,4 +136,29 @@ class TimelineModel extends PropertyChangeNotifier<String> {
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:fluttertoast/fluttertoast.dart';
import 'package:gallery_saver/gallery_saver.dart';
import 'package:share_location/constants/spacing.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/utils/loadable.dart';
import 'package:share_location/widgets/modal_sheet.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
class MemorySheet extends StatefulWidget {
final Memory memory;
final VoidCallback onMemoryDeleted;
final BuildContext sheetContext;
final VoidCallback onDelete;
final VoidCallback onVisibilityChanged;
const MemorySheet({
Key? key,
required this.memory,
required this.onMemoryDeleted,
required this.sheetContext,
required this.onDelete,
required this.onVisibilityChanged,
}) : super(key: key);
@override
State<MemorySheet> createState() => _MemorySheetState();
}
final supabase = Supabase.instance.client;
class _MemorySheetState extends State<MemorySheet> with Loadable {
Future<void> deleteFile() async {
await FileManager.deleteFile(widget.memory.location);
widget.onMemoryDeleted();
if (mounted) {
Navigator.pop(context);
}
widget.onDelete();
}
Future<void> downloadFile() async {
@ -57,13 +62,31 @@ class _MemorySheetState extends State<MemorySheet> with Loadable {
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,
);
context.showErrorSnackBar(message: 'There was an error.');
}
}
Future<void> changeVisibility() async {
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()
: 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(
leading: const Icon(Icons.delete_forever_sharp),
title: const Text('Delete Memory'),

View File

@ -122,7 +122,12 @@ class _TimelinePageState extends State<TimelinePage> {
builder: (sheetContext) => MemorySheet(
memory: timeline.currentMemory,
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:provider/provider.dart';
import 'package:share_location/foreign_types/memory.dart';
import 'package:share_location/models/timeline.dart';
import 'package:share_location/utils/loadable.dart';
import 'package:share_location/widgets/timeline_page.dart';
@ -19,54 +18,41 @@ class TimelineScroll extends StatefulWidget {
class _TimelineScrollState extends State<TimelineScroll> with Loadable {
final pageController = PageController();
TimelineModel? timeline;
final timeline = TimelineModel();
@override
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
dispose() {
pageController.dispose();
timeline?.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
Widget build(BuildContext context) {
if (timeline == null) {
if (timeline.isInitializing) {
return const Center(
child: CircularProgressIndicator(),
);
@ -78,17 +64,17 @@ class _TimelineScrollState extends State<TimelineScroll> with Loadable {
child: PageView.builder(
controller: pageController,
scrollDirection: Axis.vertical,
itemCount: timeline!.values.length,
itemCount: timeline.values.length,
onPageChanged: (newPage) {
if (timeline!.currentIndex != newPage) {
if (timeline.currentIndex != newPage) {
// User manually changed page
timeline!.setCurrentIndex(newPage);
timeline.setCurrentIndex(newPage);
timeline!.setMemoryIndex(0);
timeline.setMemoryIndex(0);
}
},
itemBuilder: (_, index) => TimelinePage(
date: timeline!.dateAtIndex(index),
date: timeline.dateAtIndex(index),
),
),
),