created first prototype of timeline screen

This commit is contained in:
Myzel394 2022-08-13 20:32:03 +02:00
parent fbcc2982c6
commit b0d0d709dd
7 changed files with 350 additions and 65 deletions

View File

@ -4,6 +4,7 @@ import 'package:flutter/material.dart';
import 'package:share_location/constants/apis.dart';
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:supabase_flutter/supabase_flutter.dart';
@ -58,6 +59,7 @@ class MyApp extends StatelessWidget {
WelcomeScreen.ID: (context) => const WelcomeScreen(),
MainScreen.ID: (context) => const MainScreen(),
LoginScreen.ID: (context) => const LoginScreen(),
TimelineScreen.ID: (context) => const TimelineScreen(),
},
initialRoute: initialPage,
);

View File

@ -1,4 +1,5 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:share_location/enums.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
@ -9,6 +10,8 @@ const uuid = Uuid();
final supabase = Supabase.instance.client;
class FileManager {
static Map<String, Uint8List> fileCache = {};
static Future<User> getUser(final String userID) async {
final response = await supabase
.from('users')
@ -68,4 +71,27 @@ class FileManager {
return [file.data!, memoryType];
}
static Future<Uint8List> downloadFile(
final String table,
final String path,
) async {
final key = '$table:$path';
if (fileCache.containsKey(key)) {
return fileCache[key]!;
}
final response = await supabase.storage.from(table).download(path);
if (response.error != null) {
throw Exception('Error downloading file: ${response.error!.message}');
}
final data = response.data!;
fileCache[key] = data;
return data;
}
}

View File

@ -0,0 +1,13 @@
import 'package:flutter/material.dart';
import 'package:share_location/widgets/timeline_scroll.dart';
class TimelineScreen extends StatelessWidget {
static const ID = 'timeline';
const TimelineScreen({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return TimelineScroll();
}
}

139
lib/widgets/memory.dart Normal file
View File

@ -0,0 +1,139 @@
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:share_location/constants/spacing.dart';
import 'package:share_location/enums.dart';
import 'package:share_location/managers/file_manager.dart';
import 'package:share_location/utils/auth_required.dart';
import 'package:share_location/widgets/raw_memory_display.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
enum MemoryFetchStatus {
preparing,
loadingMetadata,
downloading,
error,
done,
}
class Memory extends StatefulWidget {
final String location;
final DateTime creationDate;
const Memory({
Key? key,
required this.location,
required this.creationDate,
}) : super(key: key);
@override
State<Memory> createState() => _MemoryState();
}
class _MemoryState extends AuthRequiredState<Memory> {
late final User _user;
MemoryFetchStatus status = MemoryFetchStatus.preparing;
Uint8List? data;
MemoryType? type;
@override
void initState() {
super.initState();
loadMemoryFile();
}
@override
void onAuthenticated(Session session) {
final user = session.user;
if (user != null) {
_user = user;
}
}
Future<void> loadMemoryFile() async {
final filename = widget.location.split('/').last;
setState(() {
status = MemoryFetchStatus.loadingMetadata;
});
final response = await supabase
.from('memories')
.select()
.eq('location', '${_user.id}/$filename')
.limit(1)
.single()
.execute();
if (response.data == null) {
setState(() {
status = MemoryFetchStatus.error;
});
return;
}
setState(() {
status = MemoryFetchStatus.downloading;
});
final memory = response.data;
final location = memory['location'];
final memoryType =
location.split('.').last == 'jpg' ? MemoryType.photo : MemoryType.video;
try {
final fileData = await FileManager.downloadFile('memories', location);
setState(() {
status = MemoryFetchStatus.done;
data = fileData;
type = memoryType;
});
} catch (error) {
setState(() {
status = MemoryFetchStatus.error;
});
return;
}
}
@override
Widget build(BuildContext context) {
if (status == MemoryFetchStatus.error) {
return const Center(
child: Text('Memory could not be loaded.'),
);
}
if (status == MemoryFetchStatus.done) {
return RawMemoryDisplay(
data: data!,
type: type!,
);
}
return Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
const CircularProgressIndicator(),
const SizedBox(height: SMALL_SPACE),
() {
switch (status) {
// ADD dot loading text
case MemoryFetchStatus.preparing:
return const Text('Preparing to download memory');
case MemoryFetchStatus.loadingMetadata:
return const Text('Loading memory metadata');
case MemoryFetchStatus.downloading:
return const Text('Downloading memory');
default:
return const SizedBox();
}
}(),
],
);
}
}

View File

@ -0,0 +1,92 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_location/enums.dart';
import 'package:video_player/video_player.dart';
class RawMemoryDisplay extends StatefulWidget {
final Uint8List data;
final MemoryType type;
final String? filename;
const RawMemoryDisplay({
Key? key,
required this.data,
required this.type,
this.filename,
}) : super(key: key);
@override
State<RawMemoryDisplay> createState() => _RawMemoryDisplayState();
}
class _RawMemoryDisplayState extends State<RawMemoryDisplay> {
VideoPlayerController? videoController;
@override
void initState() {
super.initState();
if (widget.type == MemoryType.video) {
initializeVideo();
}
}
Future<File> createTempVideo() async {
final tempDirectory = await getTemporaryDirectory();
final path = '${tempDirectory.path}/${widget.filename ?? 'video.mp4'}';
if (widget.filename != null) {
// File already exists, so just return the path
return File(path);
}
// File needs to be created
final file = await File(path).create();
await file.writeAsBytes(widget.data);
return file;
}
Future<void> initializeVideo() async {
final file = await createTempVideo();
videoController = VideoPlayerController.file(file);
videoController!.initialize().then((value) {
setState(() {});
videoController!.setLooping(true);
videoController!.play();
});
}
@override
void dispose() {
videoController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
switch (widget.type) {
case MemoryType.photo:
return Image.memory(
widget.data,
fit: BoxFit.cover,
);
case MemoryType.video:
if (videoController == null) {
return const SizedBox();
}
return AspectRatio(
aspectRatio: videoController!.value.aspectRatio,
child: VideoPlayer(videoController!),
);
default:
throw Exception('Unknown memory type: ${widget.type}');
}
}
}

View File

@ -0,0 +1,63 @@
import 'package:flutter/material.dart';
import 'package:share_location/utils/loadable.dart';
import 'package:share_location/widgets/memory.dart';
import 'package:supabase_flutter/supabase_flutter.dart';
final supabase = Supabase.instance.client;
class TimelineScroll extends StatefulWidget {
TimelineScroll({
Key? key,
}) : super(key: key);
@override
State<TimelineScroll> createState() => _TimelineScrollState();
}
class _TimelineScrollState extends State<TimelineScroll> with Loadable {
final pageController = PageController();
dynamic timeline;
@override
initState() {
super.initState();
loadTimeline();
}
Future<void> loadTimeline() async {
final response = await supabase
.from('memories')
.select()
.order('created_at', ascending: false)
.execute();
setState(() {
timeline = response.data;
});
}
@override
Widget build(BuildContext context) {
if (timeline == null) {
return const Center(
child: CircularProgressIndicator(),
);
}
return Scaffold(
body: PageView.builder(
controller: pageController,
scrollDirection: Axis.vertical,
itemCount: timeline.length,
itemBuilder: (_, index) => Container(
width: MediaQuery.of(context).size.width,
height: MediaQuery.of(context).size.height,
child: Memory(
location: timeline[index]['location'],
creationDate: DateTime.parse(timeline[index]['created_at']),
),
),
),
);
}
}

View File

@ -1,13 +1,13 @@
import 'dart:io';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
import 'package:share_location/constants/spacing.dart';
import 'package:share_location/enums.dart';
import 'package:video_player/video_player.dart';
import 'package:share_location/screens/timeline_screen.dart';
class TodayPhotoButton extends StatefulWidget {
import 'raw_memory_display.dart';
class TodayPhotoButton extends StatelessWidget {
final Uint8List? data;
final MemoryType? type;
@ -17,45 +17,12 @@ class TodayPhotoButton extends StatefulWidget {
this.type,
}) : super(key: key);
@override
State<TodayPhotoButton> createState() => _TodayPhotoButtonState();
}
class _TodayPhotoButtonState extends State<TodayPhotoButton> {
VideoPlayerController? videoController;
@override
void initState() {
super.initState();
if (widget.type == MemoryType.video) {
initializeVideo();
}
}
Future<void> initializeVideo() async {
final tempDir = await getTemporaryDirectory();
final file = await File('${tempDir.path}/video.mp4').create();
file.writeAsBytesSync(widget.data!);
videoController = VideoPlayerController.file(file);
videoController!.initialize().then((value) {
setState(() {});
videoController!.setLooping(true);
videoController!.play();
});
}
@override
void dispose() {
videoController?.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return InkWell(
onTap: () {
Navigator.pushNamed(context, TimelineScreen.ID);
},
child: Container(
width: 45,
height: 45,
@ -69,30 +36,13 @@ class _TodayPhotoButtonState extends State<TodayPhotoButton> {
),
child: ClipRRect(
borderRadius: BorderRadius.circular(SMALL_SPACE),
child: () {
if (widget.data == null) {
return SizedBox();
}
switch (widget.type) {
case MemoryType.photo:
return Image.memory(
widget.data as Uint8List,
fit: BoxFit.cover,
);
case MemoryType.video:
if (videoController == null) {
return const SizedBox();
}
return AspectRatio(
aspectRatio: videoController!.value.aspectRatio,
child: VideoPlayer(videoController!),
);
default:
return const SizedBox();
}
}()),
child: (data == null || type == null)
? const SizedBox()
: RawMemoryDisplay(
data: data!,
type: type!,
),
),
),
);
}