mirror of
https://github.com/Myzel394/quid_faciam_hodie.git
synced 2025-06-19 15:45:26 +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: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,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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'),
|
||||||
|
@ -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();
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user