mirror of
https://github.com/Myzel394/quid_faciam_hodie.git
synced 2025-06-18 23:35:25 +02:00
improved update handling
This commit is contained in:
parent
a2c5dd8237
commit
ffcaec244a
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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'),
|
||||
|
@ -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();
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
|
@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
Loading…
x
Reference in New Issue
Block a user