mirror of
https://github.com/Myzel394/quid_faciam_hodie.git
synced 2025-06-19 15:45:26 +02:00
created basic timeline
This commit is contained in:
parent
3904c4dbef
commit
7558d1b86b
50
lib/controllers/memory_slide_controller.dart
Normal file
50
lib/controllers/memory_slide_controller.dart
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import 'package:property_change_notifier/property_change_notifier.dart';
|
||||||
|
|
||||||
|
class MemorySlideController extends PropertyChangeNotifier<String> {
|
||||||
|
final int memoryLength;
|
||||||
|
|
||||||
|
MemorySlideController({
|
||||||
|
required this.memoryLength,
|
||||||
|
});
|
||||||
|
|
||||||
|
int _index = 0;
|
||||||
|
bool _paused = false;
|
||||||
|
bool _done = false;
|
||||||
|
bool _completed = false;
|
||||||
|
|
||||||
|
bool get paused => _paused;
|
||||||
|
bool get done => _done;
|
||||||
|
int get index => _index;
|
||||||
|
bool get completed => _completed;
|
||||||
|
|
||||||
|
void setPaused(bool paused) {
|
||||||
|
_paused = paused;
|
||||||
|
notifyListeners('paused');
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDone() {
|
||||||
|
_done = true;
|
||||||
|
notifyListeners('done');
|
||||||
|
}
|
||||||
|
|
||||||
|
void next() {
|
||||||
|
if (_index == memoryLength - 1) {
|
||||||
|
_completed = true;
|
||||||
|
notifyListeners('completed');
|
||||||
|
} else {
|
||||||
|
_paused = false;
|
||||||
|
_done = false;
|
||||||
|
_index++;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void reset() {
|
||||||
|
_completed = false;
|
||||||
|
_paused = false;
|
||||||
|
_done = false;
|
||||||
|
_index = 0;
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
}
|
29
lib/controllers/status_controller.dart
Normal file
29
lib/controllers/status_controller.dart
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import 'package:property_change_notifier/property_change_notifier.dart';
|
||||||
|
|
||||||
|
class StatusController extends PropertyChangeNotifier<String> {
|
||||||
|
final Duration duration;
|
||||||
|
bool _isForwarding = false;
|
||||||
|
bool _done = false;
|
||||||
|
|
||||||
|
bool get isForwarding => _isForwarding;
|
||||||
|
bool get done => _done;
|
||||||
|
|
||||||
|
StatusController({
|
||||||
|
this.duration = const Duration(seconds: 4),
|
||||||
|
});
|
||||||
|
|
||||||
|
void start() {
|
||||||
|
_isForwarding = true;
|
||||||
|
notifyListeners('isForwarding');
|
||||||
|
}
|
||||||
|
|
||||||
|
void stop() {
|
||||||
|
_isForwarding = false;
|
||||||
|
notifyListeners('isForwarding');
|
||||||
|
}
|
||||||
|
|
||||||
|
void setDone() {
|
||||||
|
_done = true;
|
||||||
|
notifyListeners('done');
|
||||||
|
}
|
||||||
|
}
|
30
lib/foreign_types/memory.dart
Normal file
30
lib/foreign_types/memory.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:share_location/enums.dart';
|
||||||
|
|
||||||
|
class Memory {
|
||||||
|
final String id;
|
||||||
|
final DateTime creationDate;
|
||||||
|
final String location;
|
||||||
|
final bool isPublic;
|
||||||
|
final String userID;
|
||||||
|
|
||||||
|
const Memory({
|
||||||
|
required this.id,
|
||||||
|
required this.creationDate,
|
||||||
|
required this.location,
|
||||||
|
required this.isPublic,
|
||||||
|
required this.userID,
|
||||||
|
});
|
||||||
|
|
||||||
|
static parse(Map<String, dynamic> jsonData) {
|
||||||
|
return Memory(
|
||||||
|
id: jsonData['id'],
|
||||||
|
creationDate: DateTime.parse(jsonData['created_at']),
|
||||||
|
location: jsonData['location'],
|
||||||
|
isPublic: jsonData['is_public'],
|
||||||
|
userID: jsonData['user'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
MemoryType get type =>
|
||||||
|
location.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video;
|
||||||
|
}
|
@ -29,49 +29,51 @@ class _LoginScreenState extends AuthState<LoginScreen> with Loadable {
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<bool> doesEmailExist() async {
|
Future<void> _signUp() async {
|
||||||
// TODO: SECURE PROFILE READ ACCESS TO ONLY ALLOW ACCESS TO EMAIL ADDRESSES
|
final response = await supabase.auth.signUp(
|
||||||
final response = await supabase
|
emailController.text.trim(),
|
||||||
.from('profiles')
|
passwordController.text,
|
||||||
.select()
|
);
|
||||||
.match({'username': emailController.text.trim()}).execute();
|
|
||||||
|
|
||||||
return response.data.isNotEmpty;
|
final error = response.error;
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
throw Exception(error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _signIn() async {
|
||||||
|
final response = await supabase.auth.signIn(
|
||||||
|
email: emailController.text.trim(),
|
||||||
|
password: passwordController.text,
|
||||||
|
);
|
||||||
|
|
||||||
|
final error = response.error;
|
||||||
|
|
||||||
|
if (error != null) {
|
||||||
|
throw Exception(error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> signIn() async {
|
Future<void> signIn() async {
|
||||||
if (await doesEmailExist()) {
|
try {
|
||||||
// Login User
|
await _signUp();
|
||||||
final response = await supabase.auth.signIn(
|
} catch (error) {
|
||||||
email: emailController.text.trim(),
|
try {
|
||||||
password: passwordController.text,
|
await _signIn();
|
||||||
);
|
} catch (error) {
|
||||||
final error = response.error;
|
if (mounted) {
|
||||||
|
context.showErrorSnackBar(message: error.toString());
|
||||||
|
|
||||||
if (mounted) {
|
|
||||||
if (error != null) {
|
|
||||||
context.showErrorSnackBar(message: error.message);
|
|
||||||
} else {
|
|
||||||
emailController.clear();
|
emailController.clear();
|
||||||
passwordController.clear();
|
passwordController.clear();
|
||||||
}
|
}
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// Sign up User
|
|
||||||
final response = await supabase.auth.signUp(
|
|
||||||
emailController.text.trim(),
|
|
||||||
passwordController.text,
|
|
||||||
);
|
|
||||||
|
|
||||||
final error = response.error;
|
if (mounted) {
|
||||||
|
Navigator.pushReplacementNamed(context, MainScreen.ID);
|
||||||
if (mounted) {
|
|
||||||
if (error != null) {
|
|
||||||
context.showErrorSnackBar(message: error.message);
|
|
||||||
} else {
|
|
||||||
Navigator.pushReplacementNamed(context, MainScreen.ID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,7 +62,7 @@ class _PermissionsRequiredPageState extends State<PermissionsRequiredPage> {
|
|||||||
),
|
),
|
||||||
const SizedBox(height: MEDIUM_SPACE),
|
const SizedBox(height: MEDIUM_SPACE),
|
||||||
const Text(
|
const Text(
|
||||||
'Please grant permissions to use this app',
|
'Please grant the following permissions to use this app',
|
||||||
),
|
),
|
||||||
const SizedBox(height: LARGE_SPACE),
|
const SizedBox(height: LARGE_SPACE),
|
||||||
if (hasDeniedForever) ...[
|
if (hasDeniedForever) ...[
|
||||||
|
@ -7,6 +7,7 @@ import 'package:share_location/managers/file_manager.dart';
|
|||||||
import 'package:share_location/utils/auth_required.dart';
|
import 'package:share_location/utils/auth_required.dart';
|
||||||
import 'package:share_location/widgets/raw_memory_display.dart';
|
import 'package:share_location/widgets/raw_memory_display.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
import 'package:video_player/video_player.dart';
|
||||||
|
|
||||||
enum MemoryFetchStatus {
|
enum MemoryFetchStatus {
|
||||||
preparing,
|
preparing,
|
||||||
@ -16,21 +17,27 @@ enum MemoryFetchStatus {
|
|||||||
done,
|
done,
|
||||||
}
|
}
|
||||||
|
|
||||||
class Memory extends StatefulWidget {
|
class MemoryView extends StatefulWidget {
|
||||||
final String location;
|
final String location;
|
||||||
final DateTime creationDate;
|
final DateTime creationDate;
|
||||||
|
final bool loopVideo;
|
||||||
|
final void Function(VideoPlayerController)? onVideoControllerInitialized;
|
||||||
|
final VoidCallback? onFileDownloaded;
|
||||||
|
|
||||||
const Memory({
|
const MemoryView({
|
||||||
Key? key,
|
Key? key,
|
||||||
required this.location,
|
required this.location,
|
||||||
required this.creationDate,
|
required this.creationDate,
|
||||||
|
this.loopVideo = false,
|
||||||
|
this.onVideoControllerInitialized,
|
||||||
|
this.onFileDownloaded,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<Memory> createState() => _MemoryState();
|
State<MemoryView> createState() => _MemoryViewState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MemoryState extends AuthRequiredState<Memory> {
|
class _MemoryViewState extends AuthRequiredState<MemoryView> {
|
||||||
late final User _user;
|
late final User _user;
|
||||||
MemoryFetchStatus status = MemoryFetchStatus.preparing;
|
MemoryFetchStatus status = MemoryFetchStatus.preparing;
|
||||||
Uint8List? data;
|
Uint8List? data;
|
||||||
@ -67,6 +74,10 @@ class _MemoryState extends AuthRequiredState<Memory> {
|
|||||||
.single()
|
.single()
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (response.data == null) {
|
if (response.data == null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
status = MemoryFetchStatus.error;
|
status = MemoryFetchStatus.error;
|
||||||
@ -86,12 +97,24 @@ class _MemoryState extends AuthRequiredState<Memory> {
|
|||||||
try {
|
try {
|
||||||
final fileData = await FileManager.downloadFile('memories', location);
|
final fileData = await FileManager.downloadFile('memories', location);
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
status = MemoryFetchStatus.done;
|
status = MemoryFetchStatus.done;
|
||||||
data = fileData;
|
data = fileData;
|
||||||
type = memoryType;
|
type = memoryType;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
if (widget.onFileDownloaded != null) {
|
||||||
|
widget.onFileDownloaded!();
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
status = MemoryFetchStatus.error;
|
status = MemoryFetchStatus.error;
|
||||||
});
|
});
|
||||||
@ -111,6 +134,8 @@ class _MemoryState extends AuthRequiredState<Memory> {
|
|||||||
return RawMemoryDisplay(
|
return RawMemoryDisplay(
|
||||||
data: data!,
|
data: data!,
|
||||||
type: type!,
|
type: type!,
|
||||||
|
loopVideo: widget.loopVideo,
|
||||||
|
onVideoControllerInitialized: widget.onVideoControllerInitialized,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
85
lib/widgets/memory_page.dart
Normal file
85
lib/widgets/memory_page.dart
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.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/widgets/memory_slide.dart';
|
||||||
|
|
||||||
|
class MemoryPage extends StatefulWidget {
|
||||||
|
final DateTime date;
|
||||||
|
final List<Memory> memories;
|
||||||
|
final VoidCallback onPreviousTimeline;
|
||||||
|
final VoidCallback onNextTimeline;
|
||||||
|
|
||||||
|
const MemoryPage({
|
||||||
|
Key? key,
|
||||||
|
required this.date,
|
||||||
|
required this.memories,
|
||||||
|
required this.onPreviousTimeline,
|
||||||
|
required this.onNextTimeline,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MemoryPage> createState() => _MemoryPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemoryPageState extends State<MemoryPage> {
|
||||||
|
late final MemorySlideController controller;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
controller = MemorySlideController(memoryLength: widget.memories.length);
|
||||||
|
controller.addListener(() {
|
||||||
|
if (controller.done) {
|
||||||
|
controller.next();
|
||||||
|
// Force UI update
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
}, ['done']);
|
||||||
|
controller.addListener(() {
|
||||||
|
if (controller.completed) {
|
||||||
|
widget.onNextTimeline();
|
||||||
|
}
|
||||||
|
}, ['completed']);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return GestureDetector(
|
||||||
|
onTapDown: (_) {
|
||||||
|
controller.setPaused(true);
|
||||||
|
},
|
||||||
|
onTapUp: (_) {
|
||||||
|
controller.setPaused(false);
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: <Widget>[
|
||||||
|
MemorySlide(
|
||||||
|
key: Key(controller.index.toString()),
|
||||||
|
controller: controller,
|
||||||
|
memory: widget.memories[controller.index],
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(
|
||||||
|
top: LARGE_SPACE, left: MEDIUM_SPACE, right: MEDIUM_SPACE),
|
||||||
|
child: Text(
|
||||||
|
DateFormat('dd. MMMM yyyy').format(widget.date),
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
style: Theme.of(context).textTheme.headline1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
100
lib/widgets/memory_slide.dart
Normal file
100
lib/widgets/memory_slide.dart
Normal file
@ -0,0 +1,100 @@
|
|||||||
|
import 'package:flutter/material.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';
|
||||||
|
import 'package:share_location/widgets/status.dart';
|
||||||
|
|
||||||
|
import 'memory.dart';
|
||||||
|
|
||||||
|
const BAR_HEIGHT = 4.0;
|
||||||
|
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
|
||||||
|
State<MemorySlide> createState() => _MemorySlideState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MemorySlideState extends State<MemorySlide>
|
||||||
|
with TickerProviderStateMixin {
|
||||||
|
StatusController? controller;
|
||||||
|
|
||||||
|
Duration? duration;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
widget.controller.addListener(() {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.controller.paused) {
|
||||||
|
controller?.stop();
|
||||||
|
} else {
|
||||||
|
controller?.start();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
controller?.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeAnimation(final Duration duration) {
|
||||||
|
this.duration = duration;
|
||||||
|
|
||||||
|
controller = StatusController(
|
||||||
|
duration: duration,
|
||||||
|
)..addListener(widget.controller.setDone, ['done']);
|
||||||
|
|
||||||
|
setState(() {});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Status(
|
||||||
|
controller: controller,
|
||||||
|
child: MemoryView(
|
||||||
|
creationDate: widget.memory.creationDate,
|
||||||
|
location: widget.memory.location,
|
||||||
|
loopVideo: false,
|
||||||
|
onFileDownloaded: () {
|
||||||
|
if (widget.memory.type == MemoryType.photo) {
|
||||||
|
initializeAnimation(DEFAULT_IMAGE_DURATION);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onVideoControllerInitialized: (controller) {
|
||||||
|
if (mounted) {
|
||||||
|
initializeAnimation(controller.value.duration);
|
||||||
|
|
||||||
|
widget.controller.addListener(() {
|
||||||
|
if (!mounted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (widget.controller.paused) {
|
||||||
|
controller.pause();
|
||||||
|
} else {
|
||||||
|
controller.play();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -11,6 +11,7 @@ class RawMemoryDisplay extends StatefulWidget {
|
|||||||
final MemoryType type;
|
final MemoryType type;
|
||||||
final bool loopVideo;
|
final bool loopVideo;
|
||||||
final String? filename;
|
final String? filename;
|
||||||
|
final void Function(VideoPlayerController)? onVideoControllerInitialized;
|
||||||
|
|
||||||
const RawMemoryDisplay({
|
const RawMemoryDisplay({
|
||||||
Key? key,
|
Key? key,
|
||||||
@ -18,6 +19,7 @@ class RawMemoryDisplay extends StatefulWidget {
|
|||||||
required this.type,
|
required this.type,
|
||||||
this.loopVideo = false,
|
this.loopVideo = false,
|
||||||
this.filename,
|
this.filename,
|
||||||
|
this.onVideoControllerInitialized,
|
||||||
}) : super(key: key);
|
}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -60,6 +62,10 @@ class _RawMemoryDisplayState extends State<RawMemoryDisplay> {
|
|||||||
setState(() {});
|
setState(() {});
|
||||||
videoController!.setLooping(widget.loopVideo);
|
videoController!.setLooping(widget.loopVideo);
|
||||||
videoController!.play();
|
videoController!.play();
|
||||||
|
|
||||||
|
if (widget.onVideoControllerInitialized != null) {
|
||||||
|
widget.onVideoControllerInitialized!(videoController!);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
105
lib/widgets/status.dart
Normal file
105
lib/widgets/status.dart
Normal file
@ -0,0 +1,105 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:share_location/constants/spacing.dart';
|
||||||
|
import 'package:share_location/controllers/status_controller.dart';
|
||||||
|
|
||||||
|
const BAR_HEIGHT = 4.0;
|
||||||
|
|
||||||
|
class Status extends StatefulWidget {
|
||||||
|
final StatusController? controller;
|
||||||
|
final Widget child;
|
||||||
|
|
||||||
|
const Status({
|
||||||
|
Key? key,
|
||||||
|
required this.child,
|
||||||
|
this.controller,
|
||||||
|
}) : super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<Status> createState() => _StatusState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _StatusState extends State<Status> with TickerProviderStateMixin {
|
||||||
|
late final Animation<double> animation;
|
||||||
|
AnimationController? animationController;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant Status oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
|
||||||
|
if (widget.controller != null && animationController == null) {
|
||||||
|
initializeAnimation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
animationController?.dispose();
|
||||||
|
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
void initializeAnimation() {
|
||||||
|
animationController = AnimationController(
|
||||||
|
duration: widget.controller!.duration,
|
||||||
|
vsync: this,
|
||||||
|
);
|
||||||
|
animation =
|
||||||
|
Tween<double>(begin: 0.0, end: 1.0).animate(animationController!)
|
||||||
|
..addStatusListener((status) {
|
||||||
|
if (status == AnimationStatus.completed) {
|
||||||
|
widget.controller!.setDone();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
animationController!.forward();
|
||||||
|
|
||||||
|
widget.controller!.addListener(() {
|
||||||
|
if (widget.controller!.isForwarding) {
|
||||||
|
animationController!.forward();
|
||||||
|
} else {
|
||||||
|
animationController!.stop();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Stack(
|
||||||
|
fit: StackFit.expand,
|
||||||
|
children: <Widget>[
|
||||||
|
widget.child,
|
||||||
|
Positioned(
|
||||||
|
left: 0,
|
||||||
|
bottom: SMALL_SPACE,
|
||||||
|
width: MediaQuery.of(context).size.width,
|
||||||
|
height: BAR_HEIGHT,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: SMALL_SPACE),
|
||||||
|
child: (widget.controller == null)
|
||||||
|
? ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(HUGE_SPACE),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: null,
|
||||||
|
valueColor: AlwaysStoppedAnimation<Color>(
|
||||||
|
Colors.white.withOpacity(.3)),
|
||||||
|
backgroundColor: Colors.white.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: AnimatedBuilder(
|
||||||
|
animation: animation,
|
||||||
|
builder: (_, __) => ClipRRect(
|
||||||
|
borderRadius: BorderRadius.circular(HUGE_SPACE),
|
||||||
|
child: LinearProgressIndicator(
|
||||||
|
value: animation.value,
|
||||||
|
valueColor:
|
||||||
|
const AlwaysStoppedAnimation<Color>(Colors.white),
|
||||||
|
backgroundColor: Colors.white.withOpacity(0.1),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,8 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:intl/intl.dart';
|
||||||
|
import 'package:share_location/foreign_types/memory.dart';
|
||||||
import 'package:share_location/utils/loadable.dart';
|
import 'package:share_location/utils/loadable.dart';
|
||||||
import 'package:share_location/widgets/memory.dart';
|
import 'package:share_location/widgets/memory_page.dart';
|
||||||
import 'package:supabase_flutter/supabase_flutter.dart';
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
||||||
|
|
||||||
final supabase = Supabase.instance.client;
|
final supabase = Supabase.instance.client;
|
||||||
@ -30,12 +32,32 @@ class _TimelineScrollState extends State<TimelineScroll> with Loadable {
|
|||||||
.select()
|
.select()
|
||||||
.order('created_at', ascending: false)
|
.order('created_at', ascending: false)
|
||||||
.execute();
|
.execute();
|
||||||
|
final memories = List<Memory>.from(
|
||||||
|
List<Map<String, dynamic>>.from(response.data).map(Memory.parse));
|
||||||
|
final timelineMapped = convertMemoriesToTimeline(memories);
|
||||||
|
|
||||||
setState(() {
|
setState(() {
|
||||||
timeline = response.data;
|
timeline = timelineMapped;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Map<String, List<Memory>> convertMemoriesToTimeline(
|
||||||
|
final List<Memory> memories,
|
||||||
|
) {
|
||||||
|
final map = <String, List<Memory>>{};
|
||||||
|
|
||||||
|
for (final memory in memories) {
|
||||||
|
final date = DateFormat('yyyy-MM-dd').format(memory.creationDate);
|
||||||
|
if (map.containsKey(date)) {
|
||||||
|
map[date]!.add(memory);
|
||||||
|
} else {
|
||||||
|
map[date] = [memory];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (timeline == null) {
|
if (timeline == null) {
|
||||||
@ -49,13 +71,17 @@ class _TimelineScrollState extends State<TimelineScroll> with Loadable {
|
|||||||
controller: pageController,
|
controller: pageController,
|
||||||
scrollDirection: Axis.vertical,
|
scrollDirection: Axis.vertical,
|
||||||
itemCount: timeline.length,
|
itemCount: timeline.length,
|
||||||
itemBuilder: (_, index) => Container(
|
itemBuilder: (_, index) => MemoryPage(
|
||||||
width: MediaQuery.of(context).size.width,
|
date: DateTime.parse(timeline.keys.toList()[index]),
|
||||||
height: MediaQuery.of(context).size.height,
|
memories: timeline.values.toList()[index],
|
||||||
child: Memory(
|
onNextTimeline: () {
|
||||||
location: timeline[index]['location'],
|
pageController.nextPage(
|
||||||
creationDate: DateTime.parse(timeline[index]['created_at']),
|
duration: Duration(milliseconds: 500), curve: Curves.ease);
|
||||||
),
|
},
|
||||||
|
onPreviousTimeline: () {
|
||||||
|
pageController.previousPage(
|
||||||
|
duration: Duration(milliseconds: 500), curve: Curves.ease);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
14
pubspec.lock
14
pubspec.lock
@ -254,6 +254,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.1"
|
version: "4.0.1"
|
||||||
|
intl:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: intl
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.17.0"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
@ -443,6 +450,13 @@ packages:
|
|||||||
url: "https://pub.dartlang.org"
|
url: "https://pub.dartlang.org"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.2.4"
|
version: "4.2.4"
|
||||||
|
property_change_notifier:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: property_change_notifier
|
||||||
|
url: "https://pub.dartlang.org"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.0"
|
||||||
quiver:
|
quiver:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
@ -42,6 +42,8 @@ dependencies:
|
|||||||
uuid: ^3.0.6
|
uuid: ^3.0.6
|
||||||
video_player: ^2.4.6
|
video_player: ^2.4.6
|
||||||
path_provider: ^2.0.11
|
path_provider: ^2.0.11
|
||||||
|
intl: ^0.17.0
|
||||||
|
property_change_notifier: ^0.3.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user