mirror of
https://github.com/Myzel394/quid_faciam_hodie.git
synced 2025-06-18 23:35:25 +02:00
improvements
This commit is contained in:
parent
f300e52993
commit
3441938e11
@ -2,6 +2,8 @@ import 'dart:collection';
|
||||
|
||||
const DURATION_INFINITY = Duration(days: 999);
|
||||
const SECONDARY_BUTTONS_DURATION_MULTIPLIER = 1.8;
|
||||
final CALENDAR_DATE_IN_DURATION_MULTIPLIER = 1.1;
|
||||
const PHOTO_SHOW_AFTER_CREATION_DURATION = Duration(milliseconds: 500);
|
||||
final UnmodifiableSetView<double> DEFAULT_ZOOM_LEVELS =
|
||||
UnmodifiableSetView({0.6, 1, 2, 5, 10, 20, 50, 100});
|
||||
const CALENDAR_DATE_IN_MAX_DELAY = Duration(milliseconds: 500);
|
||||
|
@ -2,6 +2,7 @@ import 'package:camera/camera.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_location/constants/apis.dart';
|
||||
import 'package:share_location/screens/calendar_screen.dart';
|
||||
import 'package:share_location/screens/grant_permission_screen.dart';
|
||||
@ -9,10 +10,12 @@ import 'package:share_location/screens/login_screen.dart';
|
||||
import 'package:share_location/screens/main_screen.dart';
|
||||
import 'package:share_location/screens/timeline_screen.dart';
|
||||
import 'package:share_location/screens/welcome_screen.dart';
|
||||
import 'package:share_location/utils/auth_required.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
import 'managers/global_values_manager.dart';
|
||||
import 'managers/startup_page_manager.dart';
|
||||
import 'models/memories.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
@ -35,7 +38,7 @@ void main() async {
|
||||
runApp(MyApp(initialPage: initialPage));
|
||||
}
|
||||
|
||||
class MyApp extends StatelessWidget {
|
||||
class MyApp extends StatefulWidget {
|
||||
final String initialPage;
|
||||
|
||||
const MyApp({
|
||||
@ -43,35 +46,65 @@ class MyApp extends StatelessWidget {
|
||||
required this.initialPage,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<MyApp> createState() => _MyAppState();
|
||||
}
|
||||
|
||||
class _MyAppState extends AuthRequiredState<MyApp> {
|
||||
final memories = Memories();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
memories.addListener(() {
|
||||
setState(() {
|
||||
|
||||
});
|
||||
}, ['isInitializing']);
|
||||
}
|
||||
|
||||
@override
|
||||
void onAuthenticated(Session session) {
|
||||
memories.initialize();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData.dark().copyWith(
|
||||
textTheme: ThemeData.dark().textTheme.copyWith(
|
||||
headline1: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w500,
|
||||
if (memories.isInitializing) {
|
||||
return SizedBox();
|
||||
}
|
||||
|
||||
return ChangeNotifierProvider.value(
|
||||
value: memories,
|
||||
child: MaterialApp(
|
||||
title: 'Flutter Demo',
|
||||
theme: ThemeData.dark().copyWith(
|
||||
textTheme: ThemeData.dark().textTheme.copyWith(
|
||||
headline1: const TextStyle(
|
||||
fontSize: 32,
|
||||
fontWeight: FontWeight.w500,
|
||||
),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
helperMaxLines: 10,
|
||||
errorMaxLines: 10,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
helperMaxLines: 10,
|
||||
errorMaxLines: 10,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
),
|
||||
),
|
||||
routes: {
|
||||
WelcomeScreen.ID: (context) => const WelcomeScreen(),
|
||||
MainScreen.ID: (context) => const MainScreen(),
|
||||
LoginScreen.ID: (context) => const LoginScreen(),
|
||||
TimelineScreen.ID: (context) => const TimelineScreen(),
|
||||
GrantPermissionScreen.ID: (context) => const GrantPermissionScreen(),
|
||||
CalendarScreen.ID: (context) => const CalendarScreen(),
|
||||
},
|
||||
initialRoute: widget.initialPage,
|
||||
),
|
||||
routes: {
|
||||
WelcomeScreen.ID: (context) => const WelcomeScreen(),
|
||||
MainScreen.ID: (context) => const MainScreen(),
|
||||
LoginScreen.ID: (context) => const LoginScreen(),
|
||||
TimelineScreen.ID: (context) => const TimelineScreen(),
|
||||
GrantPermissionScreen.ID: (context) => const GrantPermissionScreen(),
|
||||
CalendarScreen.ID: (context) => const CalendarScreen(),
|
||||
},
|
||||
initialRoute: initialPage,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
75
lib/managers/calendar_manager.dart
Normal file
75
lib/managers/calendar_manager.dart
Normal file
@ -0,0 +1,75 @@
|
||||
import 'package:share_location/foreign_types/memory.dart';
|
||||
import 'package:share_location/helpers/iterate_months.dart';
|
||||
|
||||
class CalendarManager {
|
||||
final Map<DateTime, Set<String>> _values;
|
||||
|
||||
CalendarManager({
|
||||
required final List<Memory> memories,
|
||||
}) : _values = mapFromMemoriesList(memories);
|
||||
|
||||
static DateTime createDateKey(final DateTime date) =>
|
||||
DateTime(date.year, date.month, date.day);
|
||||
|
||||
static Map<DateTime, Set<String>> mapFromMemoriesList(
|
||||
final List<Memory> memories) {
|
||||
final map = <DateTime, Set<String>>{};
|
||||
|
||||
for (final memory in memories) {
|
||||
final key = createDateKey(memory.creationDate);
|
||||
|
||||
if (map.containsKey(key)) {
|
||||
map[key]!.add(memory.id);
|
||||
} else {
|
||||
map[key] = {
|
||||
memory.id,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
static Map<DateTime, Map<DateTime, int>> fillEmptyMonths(
|
||||
Map<DateTime, Map<DateTime, int>> monthMapping) {
|
||||
final earliestDate =
|
||||
monthMapping.keys.reduce((a, b) => a.isBefore(b) ? a : b);
|
||||
final latestDate = monthMapping.keys.reduce((a, b) => a.isAfter(b) ? a : b);
|
||||
|
||||
final filledMonthMapping = <DateTime, Map<DateTime, int>>{};
|
||||
|
||||
for (final date in iterateMonths(earliestDate, latestDate)) {
|
||||
filledMonthMapping[date] = monthMapping[date] ?? {};
|
||||
}
|
||||
|
||||
return filledMonthMapping;
|
||||
}
|
||||
|
||||
Map<DateTime, Map<DateTime, int>> getMonthDayAmountMapping() {
|
||||
final map = <DateTime, Map<DateTime, int>>{};
|
||||
|
||||
for (final entry in _values.entries) {
|
||||
final date = entry.key;
|
||||
final monthDate = DateTime(date.year, date.month, 1);
|
||||
final memoryIDs = entry.value;
|
||||
|
||||
if (map.containsKey(monthDate)) {
|
||||
map[monthDate]![date] = memoryIDs.length;
|
||||
} else {
|
||||
map[monthDate] = {
|
||||
date: memoryIDs.length,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return map;
|
||||
}
|
||||
|
||||
Map<DateTime, Map<DateTime, int>> getMappingForList() {
|
||||
final monthMapping = fillEmptyMonths(getMonthDayAmountMapping());
|
||||
|
||||
return Map.fromEntries(
|
||||
monthMapping.entries.toList()..sort((a, b) => b.key.compareTo(a.key)),
|
||||
);
|
||||
}
|
||||
}
|
114
lib/models/memories.dart
Normal file
114
lib/models/memories.dart
Normal file
@ -0,0 +1,114 @@
|
||||
import 'package:property_change_notifier/property_change_notifier.dart';
|
||||
import 'package:share_location/foreign_types/memory.dart';
|
||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||
|
||||
final supabase = Supabase.instance.client;
|
||||
|
||||
class Memories extends PropertyChangeNotifier<String> {
|
||||
final List<Memory> _memories = [];
|
||||
|
||||
Memories();
|
||||
|
||||
RealtimeSubscription? _serverSubscription;
|
||||
bool _isInitializing = true;
|
||||
|
||||
List<Memory> get memories => _memories;
|
||||
bool get isInitializing => _isInitializing;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_serverSubscription?.unsubscribe();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void addMemory(final Memory memory) {
|
||||
_memories.add(memory);
|
||||
notifyListeners('memories');
|
||||
}
|
||||
|
||||
void addAllMemories(final List<Memory> memories) {
|
||||
_memories.addAll(memories);
|
||||
notifyListeners('memories');
|
||||
}
|
||||
|
||||
void removeMemory(final Memory memory) {
|
||||
_memories.remove(memory);
|
||||
notifyListeners('memories');
|
||||
}
|
||||
|
||||
void removeMemoryByID(final String id) {
|
||||
_memories.removeWhere((memory) => memory.id == id);
|
||||
notifyListeners('memories');
|
||||
}
|
||||
|
||||
void setIsInitializing(final bool value) {
|
||||
_isInitializing = value;
|
||||
notifyListeners('isInitializing');
|
||||
}
|
||||
|
||||
void sortMemories() {
|
||||
_memories.sort((a, b) => b.creationDate.compareTo(a.creationDate));
|
||||
notifyListeners('memories');
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
setIsInitializing(true);
|
||||
|
||||
await _loadInitialData();
|
||||
|
||||
setIsInitializing(false);
|
||||
notifyListeners();
|
||||
|
||||
// Watch new updates
|
||||
_serverSubscription = supabase
|
||||
.from('memories')
|
||||
.on(SupabaseEventTypes.all, _onServerUpdate)
|
||||
.subscribe();
|
||||
}
|
||||
|
||||
Future<void> _onServerUpdate(
|
||||
final SupabaseRealtimePayload response,
|
||||
) async {
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (response.eventType) {
|
||||
case 'INSERT':
|
||||
final memory = Memory.parse(response.newRecord!);
|
||||
|
||||
addMemory(memory);
|
||||
|
||||
break;
|
||||
case 'DELETE':
|
||||
final id = response.oldRecord!['id'];
|
||||
|
||||
removeMemoryByID(id);
|
||||
break;
|
||||
// Used for easier debugging
|
||||
case 'UPDATE':
|
||||
final memory = Memory.parse(response.newRecord!);
|
||||
final id = response.oldRecord!['id'];
|
||||
|
||||
removeMemoryByID(id);
|
||||
addMemory(memory);
|
||||
break;
|
||||
}
|
||||
|
||||
sortMemories();
|
||||
}
|
||||
|
||||
Future<void> _loadInitialData() async {
|
||||
final response = await supabase
|
||||
.from('memories')
|
||||
.select()
|
||||
.order('created_at', ascending: false)
|
||||
.execute();
|
||||
final newMemories = List<Memory>.from(
|
||||
List<Map<String, dynamic>>.from(response.data).map(Memory.parse),
|
||||
);
|
||||
|
||||
addAllMemories(newMemories);
|
||||
}
|
||||
}
|
@ -1,19 +1,17 @@
|
||||
import 'dart:math';
|
||||
|
||||
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;
|
||||
final Map<DateTime, List<Memory>> _timeline;
|
||||
|
||||
TimelineModel({
|
||||
Map<String, MemoryPack>? timeline,
|
||||
}) : _timeline = timeline ?? {};
|
||||
required final List<Memory> memories,
|
||||
}) : _timeline = mapFromMemoriesList(memories);
|
||||
|
||||
RealtimeSubscription? _serverSubscription;
|
||||
|
||||
@ -22,38 +20,35 @@ class TimelineModel extends PropertyChangeNotifier<String> {
|
||||
bool _paused = false;
|
||||
bool _isInitializing = true;
|
||||
|
||||
Map<String, MemoryPack> get values => _timeline;
|
||||
Map<DateTime, List<Memory>> get values => _timeline;
|
||||
int get length => _timeline.length;
|
||||
int get currentIndex => _currentIndex;
|
||||
int get memoryIndex => _memoryIndex;
|
||||
bool get paused => _paused;
|
||||
bool get isInitializing => _isInitializing;
|
||||
|
||||
DateTime dateAtIndex(final int index) =>
|
||||
DateTime.parse(_timeline.keys.elementAt(index));
|
||||
DateTime dateAtIndex(final int index) => _timeline.keys.elementAt(index);
|
||||
|
||||
MemoryPack atIndex(final int index) => _timeline.values.elementAt(index);
|
||||
List<Memory> atIndex(final int index) => _timeline.values.elementAt(index);
|
||||
|
||||
MemoryPack get _currentMemoryPack => atIndex(currentIndex);
|
||||
bool get _isAtLastMemory =>
|
||||
_memoryIndex == _currentMemoryPack.memories.length - 1;
|
||||
Memory get currentMemory =>
|
||||
_currentMemoryPack.memories.elementAt(_memoryIndex);
|
||||
List<Memory> get _currentMemoryPack => atIndex(currentIndex);
|
||||
bool get _isAtLastMemory => _memoryIndex == _currentMemoryPack.length - 1;
|
||||
Memory get currentMemory => _currentMemoryPack.elementAt(_memoryIndex);
|
||||
|
||||
void _removeEmptyDates() {
|
||||
_timeline.removeWhere((key, value) => value.memories.isEmpty);
|
||||
_timeline.removeWhere((key, memories) => memories.isEmpty);
|
||||
}
|
||||
|
||||
static formatCreationDateKey(final DateTime date) =>
|
||||
DateFormat('yyyy-MM-dd').format(date);
|
||||
static DateTime createDateKey(final DateTime date) =>
|
||||
DateTime(date.year, date.month, date.day);
|
||||
|
||||
static Map<String, MemoryPack> mapFromMemoriesList(
|
||||
static Map<DateTime, List<Memory>> mapFromMemoriesList(
|
||||
final List<Memory> memories,
|
||||
) {
|
||||
final map = <String, List<Memory>>{};
|
||||
final map = <DateTime, List<Memory>>{};
|
||||
|
||||
for (final memory in memories) {
|
||||
final key = formatCreationDateKey(memory.creationDate);
|
||||
final key = createDateKey(memory.creationDate);
|
||||
|
||||
if (map.containsKey(key)) {
|
||||
map[key]!.add(memory);
|
||||
@ -62,14 +57,7 @@ class TimelineModel extends PropertyChangeNotifier<String> {
|
||||
}
|
||||
}
|
||||
|
||||
return Map.fromEntries(
|
||||
map.entries.map(
|
||||
(entry) => MapEntry<String, MemoryPack>(
|
||||
entry.key,
|
||||
MemoryPack(entry.value),
|
||||
),
|
||||
),
|
||||
);
|
||||
return map;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -85,7 +73,7 @@ class TimelineModel extends PropertyChangeNotifier<String> {
|
||||
|
||||
void setMemoryIndex(final int index) {
|
||||
_memoryIndex = min(
|
||||
_timeline.values.elementAt(_currentIndex).memories.length - 1,
|
||||
_timeline.values.elementAt(_currentIndex).length - 1,
|
||||
max(0, index),
|
||||
);
|
||||
notifyListeners('memoryIndex');
|
||||
@ -118,7 +106,7 @@ class TimelineModel extends PropertyChangeNotifier<String> {
|
||||
}
|
||||
|
||||
setCurrentIndex(currentIndex - 1);
|
||||
setMemoryIndex(_currentMemoryPack.memories.length - 1);
|
||||
setMemoryIndex(_currentMemoryPack.length - 1);
|
||||
}
|
||||
|
||||
void nextMemory() {
|
||||
@ -137,100 +125,13 @@ class TimelineModel extends PropertyChangeNotifier<String> {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initialize() async {
|
||||
void refresh(final List<Memory> memories) {
|
||||
setIsInitializing(true);
|
||||
|
||||
await _listenToServer();
|
||||
_timeline.clear();
|
||||
_timeline.addAll(mapFromMemoriesList(memories));
|
||||
_removeEmptyDates();
|
||||
|
||||
setIsInitializing(false);
|
||||
}
|
||||
|
||||
void _insertMemory(final Memory memory) {
|
||||
final key = formatCreationDateKey(memory.creationDate);
|
||||
|
||||
if (!_timeline.containsKey(key)) {
|
||||
_timeline[key] = MemoryPack([memory]);
|
||||
return;
|
||||
}
|
||||
|
||||
final memoryPack = _timeline[key]!;
|
||||
|
||||
memoryPack.addMemory(memory);
|
||||
}
|
||||
|
||||
void _updateMemory(final String id, final Memory memory) {
|
||||
final key = formatCreationDateKey(memory.creationDate);
|
||||
|
||||
if (!_timeline.containsKey(key)) {
|
||||
_timeline[key] = MemoryPack([memory]);
|
||||
return;
|
||||
}
|
||||
|
||||
final memoryPack = _timeline[key]!;
|
||||
|
||||
memoryPack.updateWithNewMemory(id, memory);
|
||||
}
|
||||
|
||||
void _deleteMemory(final String id) {
|
||||
// Search for correct `Memory` and remove it
|
||||
for (final memories in _timeline.values) {
|
||||
memories.memories.removeWhere((memory) => memory.id == id);
|
||||
}
|
||||
|
||||
_removeEmptyDates();
|
||||
}
|
||||
|
||||
Future<void> _fetchInitialData() 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));
|
||||
}
|
||||
|
||||
Future<void> _onServerUpdate(
|
||||
final SupabaseRealtimePayload response,
|
||||
) async {
|
||||
if (response == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (response.eventType) {
|
||||
case 'INSERT':
|
||||
final memory = Memory.parse(response.newRecord!);
|
||||
_insertMemory(memory);
|
||||
break;
|
||||
case 'UPDATE':
|
||||
final memoryID = response.oldRecord!['id'];
|
||||
final memory = Memory.parse(response.newRecord!);
|
||||
|
||||
_updateMemory(memoryID, memory);
|
||||
break;
|
||||
case 'DELETE':
|
||||
final id = response.oldRecord!['id'];
|
||||
|
||||
_deleteMemory(id);
|
||||
break;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _listenToServer() async {
|
||||
await _fetchInitialData();
|
||||
notifyListeners();
|
||||
|
||||
// Watch new updates
|
||||
_serverSubscription = supabase
|
||||
.from('memories')
|
||||
.on(SupabaseEventTypes.all, _onServerUpdate)
|
||||
.subscribe();
|
||||
}
|
||||
}
|
||||
|
@ -1,87 +1,59 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_sticky_header/flutter_sticky_header.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
import 'package:share_location/helpers/iterate_months.dart';
|
||||
import 'package:share_location/models/calendar.dart';
|
||||
import 'package:share_location/managers/calendar_manager.dart';
|
||||
import 'package:share_location/models/memories.dart';
|
||||
import 'package:share_location/widgets/calendar_month.dart';
|
||||
import 'package:share_location/widgets/days_of_week_strip.dart';
|
||||
|
||||
class CalendarScreen extends StatefulWidget {
|
||||
class CalendarScreen extends StatelessWidget {
|
||||
static const ID = 'calendar';
|
||||
|
||||
const CalendarScreen({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<CalendarScreen> createState() => _CalendarScreenState();
|
||||
}
|
||||
|
||||
class _CalendarScreenState extends State<CalendarScreen> {
|
||||
final calendar = CalendarModel();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
calendar.initialize();
|
||||
|
||||
calendar.addListener(() {
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
|
||||
static Map<DateTime, Map<DateTime, int>> fillEmptyMonths(
|
||||
Map<DateTime, Map<DateTime, int>> monthMapping) {
|
||||
final earliestDate =
|
||||
monthMapping.keys.reduce((a, b) => a.isBefore(b) ? a : b);
|
||||
final latestDate = monthMapping.keys.reduce((a, b) => a.isAfter(b) ? a : b);
|
||||
|
||||
final filledMonthMapping = <DateTime, Map<DateTime, int>>{};
|
||||
|
||||
for (final date in iterateMonths(earliestDate, latestDate)) {
|
||||
filledMonthMapping[date] = monthMapping[date] ?? {};
|
||||
}
|
||||
|
||||
return filledMonthMapping;
|
||||
}
|
||||
const CalendarScreen({
|
||||
Key? key,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (calendar.isInitializing) {
|
||||
return const Scaffold(
|
||||
body: Center(
|
||||
child: CircularProgressIndicator(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final memoriesManager = context.read<Memories>();
|
||||
final theme = Theme.of(context);
|
||||
final monthMapping = fillEmptyMonths(calendar.getMonthDayAmountMapping());
|
||||
final sortedMonthMapping = Map.fromEntries(
|
||||
monthMapping.entries.toList()..sort((a, b) => b.key.compareTo(a.key)),
|
||||
);
|
||||
|
||||
return Scaffold(
|
||||
body: CustomScrollView(
|
||||
reverse: true,
|
||||
slivers: [
|
||||
SliverStickyHeader(
|
||||
header: Container(
|
||||
color: theme.canvasColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: SMALL_SPACE),
|
||||
child: const DaysOfWeekStrip(),
|
||||
),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) => CalendarMonth(
|
||||
year: sortedMonthMapping.keys.elementAt(index).year,
|
||||
month: sortedMonthMapping.keys.elementAt(index).month,
|
||||
dayAmountMap: sortedMonthMapping.values.elementAt(index),
|
||||
final calendarManager = CalendarManager(memories: memoriesManager.memories);
|
||||
final monthMapping = calendarManager.getMappingForList();
|
||||
|
||||
return Consumer<Memories>(
|
||||
builder: (context, memories, _) => Scaffold(
|
||||
body: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: MEDIUM_SPACE),
|
||||
child: CustomScrollView(
|
||||
reverse: true,
|
||||
slivers: [
|
||||
SliverStickyHeader(
|
||||
header: Container(
|
||||
color: theme.canvasColor,
|
||||
padding: const EdgeInsets.symmetric(vertical: SMALL_SPACE),
|
||||
child: const DaysOfWeekStrip(),
|
||||
),
|
||||
sliver: SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
final date = monthMapping.keys.elementAt(index);
|
||||
final dayMapping = monthMapping.values.elementAt(index);
|
||||
|
||||
return CalendarMonth(
|
||||
year: date.year,
|
||||
month: date.month,
|
||||
dayAmountMap: dayMapping,
|
||||
);
|
||||
},
|
||||
childCount: monthMapping.length,
|
||||
),
|
||||
),
|
||||
childCount: sortedMonthMapping.length,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_location/extensions/date.dart';
|
||||
import 'package:share_location/models/memories.dart';
|
||||
import 'package:share_location/models/timeline.dart';
|
||||
import 'package:share_location/utils/loadable.dart';
|
||||
import 'package:share_location/widgets/timeline_page.dart';
|
||||
@ -26,7 +27,7 @@ class TimelineScreen extends StatefulWidget {
|
||||
|
||||
class _TimelineScreenState extends State<TimelineScreen> with Loadable {
|
||||
final pageController = PageController();
|
||||
final timeline = TimelineModel();
|
||||
late final TimelineModel timeline;
|
||||
bool _ignorePageChanges = false;
|
||||
|
||||
Future<void> _goToPage(final int page) async {
|
||||
@ -45,7 +46,13 @@ class _TimelineScreenState extends State<TimelineScreen> with Loadable {
|
||||
initState() {
|
||||
super.initState();
|
||||
|
||||
timeline.initialize();
|
||||
final memoriesModel = context.read<Memories>();
|
||||
|
||||
timeline = TimelineModel(memories: memoriesModel.memories);
|
||||
|
||||
memoriesModel.addListener(() {
|
||||
timeline.refresh(memoriesModel.memories);
|
||||
}, ['memories']);
|
||||
|
||||
// Update page view
|
||||
timeline.addListener(() async {
|
||||
@ -86,22 +93,16 @@ class _TimelineScreenState extends State<TimelineScreen> with Loadable {
|
||||
|
||||
return timeline.values.keys
|
||||
.toList()
|
||||
.indexWhere((date) => DateTime.parse(date).isSameDay(widget.date!));
|
||||
.indexWhere((date) => date.isSameDay(widget.date!));
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (timeline.isInitializing) {
|
||||
return const Center(
|
||||
child: CircularProgressIndicator(),
|
||||
);
|
||||
}
|
||||
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
await Navigator.pushNamed(context, CalendarScreen.ID);
|
||||
await Navigator.pushReplacementNamed(context, CalendarScreen.ID);
|
||||
|
||||
return true;
|
||||
return false;
|
||||
},
|
||||
child: Scaffold(
|
||||
body: ChangeNotifierProvider.value(
|
||||
@ -124,7 +125,7 @@ class _TimelineScreenState extends State<TimelineScreen> with Loadable {
|
||||
},
|
||||
itemBuilder: (_, index) => TimelinePage(
|
||||
date: timeline.dateAtIndex(index),
|
||||
memoryPack: timeline.atIndex(index),
|
||||
memories: timeline.atIndex(index),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter_calendar_widget/flutter_calendar_widget.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:share_location/constants/spacing.dart';
|
||||
import 'package:share_location/constants/values.dart';
|
||||
import 'package:share_location/screens/timeline_screen.dart';
|
||||
import 'package:share_location/widgets/delay_render.dart';
|
||||
import 'package:share_location/widgets/fade_and_move_in_animation.dart';
|
||||
@ -37,11 +38,18 @@ class MonthCalendarBuilder extends CalendarBuilder {
|
||||
return amount / highestAmountOfEvents;
|
||||
}();
|
||||
|
||||
final duration = Duration(milliseconds: Random().nextInt(800));
|
||||
final delay = Duration(
|
||||
microseconds:
|
||||
Random().nextInt(CALENDAR_DATE_IN_MAX_DELAY.inMicroseconds));
|
||||
|
||||
return DelayRender(
|
||||
delay: duration,
|
||||
delay: delay,
|
||||
child: FadeAndMoveInAnimation(
|
||||
opacityDuration:
|
||||
DEFAULT_OPACITY_DURATION * CALENDAR_DATE_IN_DURATION_MULTIPLIER,
|
||||
translationDuration:
|
||||
DEFAULT_TRANSLATION_DURATION * CALENDAR_DATE_IN_DURATION_MULTIPLIER,
|
||||
translationOffset: const Offset(0.0, -MEDIUM_SPACE),
|
||||
child: Opacity(
|
||||
opacity: () {
|
||||
if (type.isOutSide) {
|
||||
|
@ -2,7 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:share_location/models/memory_pack.dart';
|
||||
import 'package:share_location/foreign_types/memory.dart';
|
||||
import 'package:share_location/models/timeline.dart';
|
||||
import 'package:share_location/models/timeline_overlay.dart';
|
||||
import 'package:share_location/widgets/memory_sheet.dart';
|
||||
@ -11,12 +11,12 @@ import 'package:share_location/widgets/timeline_overlay.dart';
|
||||
|
||||
class TimelinePage extends StatefulWidget {
|
||||
final DateTime date;
|
||||
final MemoryPack memoryPack;
|
||||
final List<Memory> memories;
|
||||
|
||||
const TimelinePage({
|
||||
Key? key,
|
||||
required this.date,
|
||||
required this.memoryPack,
|
||||
required this.memories,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
@ -165,14 +165,14 @@ class _TimelinePageState extends State<TimelinePage> {
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
scrollDirection: Axis.horizontal,
|
||||
itemBuilder: (_, index) => MemorySlide(
|
||||
key: Key(widget.memoryPack.memories[index].filename),
|
||||
memory: widget.memoryPack.memories[index],
|
||||
key: Key(widget.memories[index].filename),
|
||||
memory: widget.memories[index],
|
||||
),
|
||||
itemCount: widget.memoryPack.memories.length,
|
||||
itemCount: widget.memories.length,
|
||||
),
|
||||
TimelineOverlay(
|
||||
date: widget.date,
|
||||
memoriesAmount: widget.memoryPack.memories.length,
|
||||
memoriesAmount: widget.memories.length,
|
||||
memoryIndex: timeline.memoryIndex + 1,
|
||||
),
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user