mirror of
https://github.com/Myzel394/quid_faciam_hodie.git
synced 2025-06-18 23:35:25 +02:00
improved memory handling
This commit is contained in:
parent
2339fd27a0
commit
8409939825
@ -19,6 +19,8 @@ class MemorySlideController extends PropertyChangeNotifier<String> {
|
||||
int get index => _index;
|
||||
bool get completed => _completed;
|
||||
|
||||
bool get isLast => _index == memoryLength - 1;
|
||||
|
||||
void setPaused(bool paused) {
|
||||
_paused = paused;
|
||||
notifyListeners('paused');
|
||||
@ -30,7 +32,7 @@ class MemorySlideController extends PropertyChangeNotifier<String> {
|
||||
}
|
||||
|
||||
void next() {
|
||||
if (_index == memoryLength - 1) {
|
||||
if (isLast) {
|
||||
_completed = true;
|
||||
notifyListeners('completed');
|
||||
} else {
|
||||
|
@ -95,4 +95,20 @@ class FileManager {
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
static Future<void> deleteFile(final String path) async {
|
||||
final response =
|
||||
await supabase.from('memories').delete().eq('location', path).execute();
|
||||
|
||||
if (response.error != null) {
|
||||
throw Exception('Error deleting file: ${response.error!.message}');
|
||||
}
|
||||
|
||||
final storageResponse =
|
||||
await supabase.storage.from('memories').remove([path]);
|
||||
|
||||
if (storageResponse.error != null) {
|
||||
throw Exception('Error deleting file: ${storageResponse.error!.message}');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
61
lib/models/memory_pack.dart
Normal file
61
lib/models/memory_pack.dart
Normal file
@ -0,0 +1,61 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:property_change_notifier/property_change_notifier.dart';
|
||||
import 'package:share_location/foreign_types/memory.dart';
|
||||
|
||||
class MemoryPack extends PropertyChangeNotifier<String> {
|
||||
final List<Memory> _memories;
|
||||
int _currentMemoryIndex = 0;
|
||||
bool _paused = false;
|
||||
bool _completed = false;
|
||||
|
||||
MemoryPack(this._memories);
|
||||
|
||||
List<Memory> get memories => [..._memories];
|
||||
int get currentMemoryIndex => _currentMemoryIndex;
|
||||
Memory get currentMemory => _memories[_currentMemoryIndex];
|
||||
bool get paused => _paused;
|
||||
bool get completed => _completed;
|
||||
bool get isLast => _currentMemoryIndex == _memories.length - 1;
|
||||
|
||||
void setPaused(bool paused) {
|
||||
_paused = paused;
|
||||
notifyListeners('paused');
|
||||
}
|
||||
|
||||
void next() {
|
||||
if (isLast) {
|
||||
_completed = true;
|
||||
notifyListeners('completed');
|
||||
} else {
|
||||
_paused = false;
|
||||
_completed = false;
|
||||
_currentMemoryIndex++;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
void previous() {
|
||||
_currentMemoryIndex = max(_currentMemoryIndex - 1, 0);
|
||||
_paused = false;
|
||||
_completed = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void reset() {
|
||||
_completed = false;
|
||||
_paused = false;
|
||||
_currentMemoryIndex = 0;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void removeMemory(int index) {
|
||||
_memories.removeAt(index);
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void removeCurrentMemory() => removeMemory(_currentMemoryIndex);
|
||||
|
||||
void pause() => setPaused(true);
|
||||
void resume() => setPaused(false);
|
||||
}
|
@ -1,15 +1,35 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:property_change_notifier/property_change_notifier.dart';
|
||||
|
||||
class TimelineOverlay extends ChangeNotifier {
|
||||
enum TimelineState {
|
||||
loading,
|
||||
paused,
|
||||
playing,
|
||||
completed,
|
||||
}
|
||||
|
||||
class TimelineOverlay extends PropertyChangeNotifier<String> {
|
||||
bool _showOverlay = true;
|
||||
TimelineState _state = TimelineState.loading;
|
||||
|
||||
bool get showOverlay => _showOverlay;
|
||||
TimelineState get state => _state;
|
||||
|
||||
void hideOverlay() => setShowOverlay(false);
|
||||
void restoreOverlay() => setShowOverlay(true);
|
||||
|
||||
void setShowOverlay(bool showOverlay) {
|
||||
_showOverlay = showOverlay;
|
||||
notifyListeners('showOverlay');
|
||||
}
|
||||
|
||||
void setState(TimelineState state) {
|
||||
_state = state;
|
||||
notifyListeners('state');
|
||||
}
|
||||
|
||||
void reset() {
|
||||
_showOverlay = true;
|
||||
_state = TimelineState.loading;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
53
lib/widgets/memory_sheet.dart
Normal file
53
lib/widgets/memory_sheet.dart
Normal file
@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
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';
|
||||
|
||||
class MemorySheet extends StatefulWidget {
|
||||
final Memory memory;
|
||||
|
||||
const MemorySheet({
|
||||
Key? key,
|
||||
required this.memory,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MemorySheet> createState() => _MemorySheetState();
|
||||
}
|
||||
|
||||
class _MemorySheetState extends State<MemorySheet> with Loadable {
|
||||
Future<void> deleteFile() => FileManager.deleteFile(widget.memory.location);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return ModalSheet(
|
||||
child: Column(
|
||||
children: <Widget>[
|
||||
Text(
|
||||
'Edit Memory',
|
||||
style: theme.textTheme.headline1,
|
||||
),
|
||||
const SizedBox(height: MEDIUM_SPACE),
|
||||
ListTile(
|
||||
leading: Icon(Icons.delete_forever_sharp),
|
||||
title: Text('Delete Memory'),
|
||||
onTap: isLoading
|
||||
? null
|
||||
: () async {
|
||||
await callWithLoading(deleteFile);
|
||||
|
||||
if (mounted) {
|
||||
Navigator.pop(context);
|
||||
}
|
||||
},
|
||||
trailing: isLoading ? const CircularProgressIndicator() : null,
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_location/controllers/memory_slide_controller.dart';
|
||||
import 'package:share_location/controllers/status_controller.dart';
|
||||
import 'package:share_location/enums.dart';
|
||||
import 'package:share_location/foreign_types/memory.dart';
|
||||
@ -14,12 +13,10 @@ const DEFAULT_IMAGE_DURATION = Duration(seconds: 5);
|
||||
|
||||
class MemorySlide extends StatefulWidget {
|
||||
final Memory memory;
|
||||
final MemorySlideController controller;
|
||||
|
||||
const MemorySlide({
|
||||
Key? key,
|
||||
required this.memory,
|
||||
required this.controller,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -36,15 +33,19 @@ class _MemorySlideState extends State<MemorySlide>
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
widget.controller.addListener(() {
|
||||
final timelineOverlay = context.read<TimelineOverlay>();
|
||||
|
||||
timelineOverlay.addListener(() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (widget.controller.paused) {
|
||||
controller?.stop();
|
||||
} else {
|
||||
controller?.start();
|
||||
switch (timelineOverlay.state) {
|
||||
case TimelineState.playing:
|
||||
controller?.start();
|
||||
break;
|
||||
default:
|
||||
controller?.stop();
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -57,11 +58,23 @@ class _MemorySlideState extends State<MemorySlide>
|
||||
}
|
||||
|
||||
void initializeAnimation(final Duration duration) {
|
||||
final timelineOverlay = context.read<TimelineOverlay>();
|
||||
|
||||
this.duration = duration;
|
||||
|
||||
controller = StatusController(
|
||||
duration: duration,
|
||||
)..addListener(widget.controller.setDone, ['done']);
|
||||
);
|
||||
|
||||
controller!.addListener(() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (controller!.done) {
|
||||
timelineOverlay.setState(TimelineState.completed);
|
||||
}
|
||||
}, ['done']);
|
||||
|
||||
setState(() {});
|
||||
}
|
||||
@ -86,17 +99,17 @@ class _MemorySlideState extends State<MemorySlide>
|
||||
if (mounted) {
|
||||
initializeAnimation(controller.value.duration);
|
||||
|
||||
widget.controller.addListener(() {
|
||||
overlayController.addListener(() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (widget.controller.paused) {
|
||||
controller.pause();
|
||||
} else {
|
||||
if (overlayController.state == TimelineState.playing) {
|
||||
controller.play();
|
||||
} else {
|
||||
controller.pause();
|
||||
}
|
||||
});
|
||||
}, ['state']);
|
||||
}
|
||||
},
|
||||
),
|
||||
|
39
lib/widgets/modal_sheet.dart
Normal file
39
lib/widgets/modal_sheet.dart
Normal file
@ -0,0 +1,39 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
|
||||
class ModalSheet extends StatelessWidget {
|
||||
final Widget child;
|
||||
|
||||
const ModalSheet({
|
||||
Key? key,
|
||||
required this.child,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
|
||||
return Wrap(
|
||||
crossAxisAlignment: WrapCrossAlignment.center,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding:
|
||||
EdgeInsets.only(bottom: MediaQuery.of(context).viewInsets.bottom),
|
||||
child: Container(
|
||||
width: double.infinity,
|
||||
padding: const EdgeInsets.all(MEDIUM_SPACE),
|
||||
decoration: BoxDecoration(
|
||||
color: theme.bottomSheetTheme.modalBackgroundColor ??
|
||||
theme.bottomAppBarColor,
|
||||
borderRadius: const BorderRadius.only(
|
||||
topLeft: Radius.circular(LARGE_SPACE),
|
||||
topRight: Radius.circular(LARGE_SPACE),
|
||||
),
|
||||
),
|
||||
child: child,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
@ -4,21 +4,19 @@ import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
import 'package:share_location/controllers/memory_slide_controller.dart';
|
||||
import 'package:share_location/foreign_types/memory.dart';
|
||||
import 'package:share_location/models/memory_pack.dart';
|
||||
import 'package:share_location/models/timeline_overlay.dart';
|
||||
import 'package:share_location/widgets/memory_sheet.dart';
|
||||
import 'package:share_location/widgets/memory_slide.dart';
|
||||
|
||||
class TimelinePage extends StatefulWidget {
|
||||
final DateTime date;
|
||||
final List<Memory> memories;
|
||||
final VoidCallback onPreviousTimeline;
|
||||
final VoidCallback onNextTimeline;
|
||||
|
||||
const TimelinePage({
|
||||
Key? key,
|
||||
required this.date,
|
||||
required this.memories,
|
||||
required this.onPreviousTimeline,
|
||||
required this.onNextTimeline,
|
||||
}) : super(key: key);
|
||||
@ -30,7 +28,6 @@ class TimelinePage extends StatefulWidget {
|
||||
class _TimelinePageState extends State<TimelinePage> {
|
||||
final timelineOverlayController = TimelineOverlay();
|
||||
final pageController = PageController();
|
||||
late final MemorySlideController controller;
|
||||
|
||||
Timer? overlayRemover;
|
||||
|
||||
@ -38,37 +35,75 @@ class _TimelinePageState extends State<TimelinePage> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
controller = MemorySlideController(memoryLength: widget.memories.length);
|
||||
controller.addListener(() {
|
||||
if (controller.done) {
|
||||
controller.next();
|
||||
final memoryPack = context.read<MemoryPack>();
|
||||
|
||||
pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.linearToEaseOut,
|
||||
);
|
||||
timelineOverlayController.addListener(() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
}, ['done']);
|
||||
controller.addListener(() {
|
||||
if (controller.completed) {
|
||||
|
||||
if (timelineOverlayController.state == TimelineState.completed) {
|
||||
timelineOverlayController.reset();
|
||||
memoryPack.next();
|
||||
}
|
||||
}, ['state']);
|
||||
|
||||
memoryPack.addListener(() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (memoryPack.completed) {
|
||||
widget.onNextTimeline();
|
||||
memoryPack.reset();
|
||||
}
|
||||
}, ['completed']);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
controller.dispose();
|
||||
timelineOverlayController.dispose();
|
||||
memoryPack.addListener(() {
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
super.dispose();
|
||||
if (memoryPack.currentMemoryIndex != pageController.page) {
|
||||
pageController.animateToPage(
|
||||
memoryPack.currentMemoryIndex,
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeOutQuad,
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return GestureDetector(
|
||||
onDoubleTap: () async {
|
||||
final memoryPack = context.read<MemoryPack>();
|
||||
|
||||
memoryPack.pause();
|
||||
timelineOverlayController.hideOverlay();
|
||||
|
||||
await showModalBottomSheet(
|
||||
context: context,
|
||||
backgroundColor: Colors.transparent,
|
||||
isScrollControlled: true,
|
||||
builder: (_) => MemorySheet(
|
||||
memory: memoryPack.currentMemory,
|
||||
),
|
||||
);
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
memoryPack.removeCurrentMemory();
|
||||
memoryPack.resume();
|
||||
timelineOverlayController.restoreOverlay();
|
||||
},
|
||||
onTapDown: (_) {
|
||||
controller.setPaused(true);
|
||||
final memoryPack = context.read<MemoryPack>();
|
||||
|
||||
memoryPack.pause();
|
||||
|
||||
overlayRemover = Timer(
|
||||
const Duration(milliseconds: 200),
|
||||
@ -76,28 +111,32 @@ class _TimelinePageState extends State<TimelinePage> {
|
||||
);
|
||||
},
|
||||
onTapUp: (_) {
|
||||
final memoryPack = context.read<MemoryPack>();
|
||||
|
||||
overlayRemover?.cancel();
|
||||
|
||||
controller.setPaused(false);
|
||||
|
||||
memoryPack.resume();
|
||||
timelineOverlayController.restoreOverlay();
|
||||
},
|
||||
onTapCancel: () {
|
||||
final memoryPack = context.read<MemoryPack>();
|
||||
|
||||
overlayRemover?.cancel();
|
||||
|
||||
timelineOverlayController.restoreOverlay();
|
||||
controller.setPaused(false);
|
||||
memoryPack.resume();
|
||||
},
|
||||
onHorizontalDragEnd: (details) {
|
||||
final memoryPack = context.read<MemoryPack>();
|
||||
|
||||
if (details.primaryVelocity! < 0) {
|
||||
controller.next();
|
||||
memoryPack.next();
|
||||
|
||||
pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
curve: Curves.linearToEaseOut,
|
||||
);
|
||||
} else if (details.primaryVelocity! > 0) {
|
||||
controller.previous();
|
||||
memoryPack.previous();
|
||||
|
||||
pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 200),
|
||||
@ -110,16 +149,19 @@ class _TimelinePageState extends State<TimelinePage> {
|
||||
child: Stack(
|
||||
fit: StackFit.expand,
|
||||
children: <Widget>[
|
||||
PageView.builder(
|
||||
controller: pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (_, __) => MemorySlide(
|
||||
key: Key(controller.index.toString()),
|
||||
controller: controller,
|
||||
memory: widget.memories[controller.index],
|
||||
),
|
||||
itemCount: widget.memories.length,
|
||||
Consumer<MemoryPack>(
|
||||
builder: (_, memoryPack, __) {
|
||||
return PageView.builder(
|
||||
controller: pageController,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (_, index) => MemorySlide(
|
||||
key: Key(memoryPack.memories[index].filename),
|
||||
memory: memoryPack.memories[index],
|
||||
),
|
||||
itemCount: memoryPack.memories.length,
|
||||
);
|
||||
},
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
|
@ -1,6 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_location/foreign_types/memory.dart';
|
||||
import 'package:share_location/models/memory_pack.dart';
|
||||
import 'package:share_location/utils/loadable.dart';
|
||||
import 'package:share_location/widgets/timeline_page.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
@ -18,7 +20,7 @@ class TimelineScroll extends StatefulWidget {
|
||||
|
||||
class _TimelineScrollState extends State<TimelineScroll> with Loadable {
|
||||
final pageController = PageController();
|
||||
dynamic timeline;
|
||||
Map<String, MemoryPack>? timeline;
|
||||
|
||||
@override
|
||||
initState() {
|
||||
@ -41,7 +43,7 @@ class _TimelineScrollState extends State<TimelineScroll> with Loadable {
|
||||
});
|
||||
}
|
||||
|
||||
static Map<String, List<Memory>> convertMemoriesToTimeline(
|
||||
static Map<String, MemoryPack> convertMemoriesToTimeline(
|
||||
final List<Memory> memories,
|
||||
) {
|
||||
final map = <String, List<Memory>>{};
|
||||
@ -55,7 +57,14 @@ class _TimelineScrollState extends State<TimelineScroll> with Loadable {
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
return Map.fromEntries(
|
||||
map.entries.map(
|
||||
(entry) => MapEntry<String, MemoryPack>(
|
||||
entry.key,
|
||||
MemoryPack(entry.value),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -70,22 +79,24 @@ class _TimelineScrollState extends State<TimelineScroll> with Loadable {
|
||||
body: PageView.builder(
|
||||
controller: pageController,
|
||||
scrollDirection: Axis.vertical,
|
||||
itemCount: timeline.length,
|
||||
itemBuilder: (_, index) => TimelinePage(
|
||||
date: DateTime.parse(timeline.keys.toList()[index]),
|
||||
memories: timeline.values.toList()[index],
|
||||
onNextTimeline: () {
|
||||
pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
},
|
||||
onPreviousTimeline: () {
|
||||
pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
},
|
||||
itemCount: timeline!.length,
|
||||
itemBuilder: (_, index) => ChangeNotifierProvider<MemoryPack>(
|
||||
create: (_) => timeline!.values.elementAt(index),
|
||||
child: TimelinePage(
|
||||
date: DateTime.parse(timeline!.keys.toList()[index]),
|
||||
onNextTimeline: () {
|
||||
pageController.nextPage(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
},
|
||||
onPreviousTimeline: () {
|
||||
pageController.previousPage(
|
||||
duration: const Duration(milliseconds: 500),
|
||||
curve: Curves.ease,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
Loading…
x
Reference in New Issue
Block a user