feat: combine UI screens with service layer

This commit is contained in:
Freek van de Ven 2023-11-20 11:54:00 +01:00
parent 4113e9fea2
commit e8822d92a3
8 changed files with 97 additions and 33 deletions

View file

@ -4,3 +4,7 @@
/// ///
library flutter_timeline_firebase; library flutter_timeline_firebase;
export 'src/config/firebase_timeline_options.dart';
export 'src/service/firebase_timeline_service.dart';
export 'src/service/firebase_user_service.dart';

View file

@ -8,8 +8,9 @@ import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_storage/firebase_storage.dart'; import 'package:firebase_storage/firebase_storage.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_timeline_firebase/config/firebase_timeline_options.dart'; import 'package:flutter_timeline_firebase/src/config/firebase_timeline_options.dart';
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
import 'package:uuid/uuid.dart';
class FirebaseTimelineService implements TimelineService { class FirebaseTimelineService implements TimelineService {
FirebaseTimelineService({ FirebaseTimelineService({
@ -32,14 +33,17 @@ class FirebaseTimelineService implements TimelineService {
List<TimelinePost> _posts = []; List<TimelinePost> _posts = [];
@override @override
Future<void> createPost(TimelinePost post) async { Future<TimelinePost> createPost(TimelinePost post) async {
var imageRef = _storage.ref().child('timeline/${post.id}'); var postId = const Uuid().v4();
var imageRef = _storage.ref().child('timeline/$postId');
var result = await imageRef.putData(post.image!); var result = await imageRef.putData(post.image!);
var imageUrl = await result.ref.getDownloadURL(); var imageUrl = await result.ref.getDownloadURL();
var updatedPost = post.copyWith(imageUrl: imageUrl); var updatedPost = post.copyWith(imageUrl: imageUrl, id: postId);
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); var postRef =
_db.collection(_options.timelineCollectionName).doc(updatedPost.id);
_posts.add(updatedPost); _posts.add(updatedPost);
return postRef.set(updatedPost.toJson()); await postRef.set(updatedPost.toJson());
return updatedPost;
} }
@override @override
@ -56,15 +60,17 @@ class FirebaseTimelineService implements TimelineService {
@override @override
Future<List<TimelinePost>> fetchPosts(String? category) async { Future<List<TimelinePost>> fetchPosts(String? category) async {
var snapshot = await _db var snapshot = (category != null)
.collection(_options.timelineCollectionName) ? await _db
.where('category', isEqualTo: category) .collection(_options.timelineCollectionName)
.get(); .where('category', isEqualTo: category)
.get()
: await _db.collection(_options.timelineCollectionName).get();
var posts = <TimelinePost>[]; var posts = <TimelinePost>[];
for (var doc in snapshot.docs) { for (var doc in snapshot.docs) {
var data = doc.data(); var data = doc.data();
var user = await _userService.getUser(data['user_id']); var user = await _userService.getUser(data['creator_id']);
var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user); var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user);
posts.add(post); posts.add(post);
} }
@ -72,6 +78,11 @@ class FirebaseTimelineService implements TimelineService {
return posts; return posts;
} }
@override
List<TimelinePost> getPosts(String? category) => _posts
.where((element) => category == null || element.category == category)
.toList();
@override @override
Future<void> likePost(String userId, TimelinePost post) { Future<void> likePost(String userId, TimelinePost post) {
// update the post with the new like // update the post with the new like

View file

@ -4,8 +4,8 @@
import 'package:cloud_firestore/cloud_firestore.dart'; import 'package:cloud_firestore/cloud_firestore.dart';
import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_core/firebase_core.dart';
import 'package:flutter_timeline_firebase/config/firebase_timeline_options.dart'; import 'package:flutter_timeline_firebase/src/config/firebase_timeline_options.dart';
import 'package:flutter_timeline_firebase/models/firebase_user_document.dart'; import 'package:flutter_timeline_firebase/src/models/firebase_user_document.dart';
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
class FirebaseUserService implements TimelineUserService { class FirebaseUserService implements TimelineUserService {

View file

@ -9,8 +9,9 @@ import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart';
abstract class TimelineService { abstract class TimelineService {
Future<void> deletePost(TimelinePost post); Future<void> deletePost(TimelinePost post);
Future<void> createPost(TimelinePost post); Future<TimelinePost> createPost(TimelinePost post);
Future<List<TimelinePost>> fetchPosts(String? category); Future<List<TimelinePost>> fetchPosts(String? category);
List<TimelinePost> getPosts(String? category);
Future<TimelinePost> fetchPostDetails(TimelinePost post); Future<TimelinePost> fetchPostDetails(TimelinePost post);
Future<void> reactToPost( Future<void> reactToPost(
TimelinePost post, TimelinePost post,

View file

@ -7,15 +7,30 @@ import 'dart:typed_data';
import 'package:dotted_border/dotted_border.dart'; import 'package:dotted_border/dotted_border.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_image_picker/flutter_image_picker.dart'; import 'package:flutter_image_picker/flutter_image_picker.dart';
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
import 'package:flutter_timeline_view/src/config/timeline_options.dart'; import 'package:flutter_timeline_view/src/config/timeline_options.dart';
class TimelinePostCreationScreen extends StatefulWidget { class TimelinePostCreationScreen extends StatefulWidget {
const TimelinePostCreationScreen({ const TimelinePostCreationScreen({
required this.userId,
required this.postCategory,
required this.onPostCreated,
required this.service,
required this.options, required this.options,
this.padding = const EdgeInsets.symmetric(vertical: 24, horizontal: 16), this.padding = const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
super.key, super.key,
}); });
final String userId;
final String postCategory;
/// called when the post is created
final Function(TimelinePost) onPostCreated;
/// The service to use for creating the post
final TimelineService service;
/// The options for the timeline /// The options for the timeline
final TimelineOptions options; final TimelineOptions options;
@ -59,6 +74,23 @@ class _TimelinePostCreationScreenState
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Future<void> onPostCreated() async {
var post = TimelinePost(
id: '',
creatorId: widget.userId,
title: titleController.text,
category: widget.postCategory,
content: contentController.text,
likes: 0,
reaction: 0,
createdAt: DateTime.now(),
reactionEnabled: allowComments,
image: image,
);
var newPost = await widget.service.createPost(post);
widget.onPostCreated.call(newPost);
}
var theme = Theme.of(context); var theme = Theme.of(context);
return Padding( return Padding(
padding: widget.padding, padding: widget.padding,
@ -214,12 +246,12 @@ class _TimelinePostCreationScreenState
child: (widget.options.buttonBuilder != null) child: (widget.options.buttonBuilder != null)
? widget.options.buttonBuilder!( ? widget.options.buttonBuilder!(
context, context,
() {}, onPostCreated,
widget.options.translations.checkPost, widget.options.translations.checkPost,
enabled: editingDone, enabled: editingDone,
) )
: ElevatedButton( : ElevatedButton(
onPressed: editingDone ? () {} : null, onPressed: editingDone ? onPostCreated : null,
child: Text( child: Text(
widget.options.translations.checkPost, widget.options.translations.checkPost,
style: theme.textTheme.bodyMedium, style: theme.textTheme.bodyMedium,

View file

@ -9,15 +9,21 @@ import 'package:flutter_timeline_view/src/widgets/timeline_post_widget.dart';
class TimelineScreen extends StatefulWidget { class TimelineScreen extends StatefulWidget {
const TimelineScreen({ const TimelineScreen({
required this.userId,
required this.options, required this.options,
required this.posts, required this.posts,
required this.onPostTap, required this.onPostTap,
required this.service,
this.controller, this.controller,
this.timelineCategoryFilter, this.timelineCategoryFilter,
this.timelinePostHeight = 100.0, this.timelinePostHeight = 100.0,
super.key, super.key,
}); });
final String userId;
final TimelineService service;
final TimelineOptions options; final TimelineOptions options;
final ScrollController? controller; final ScrollController? controller;
@ -44,22 +50,32 @@ class _TimelineScreenState extends State<TimelineScreen> {
} }
@override @override
Widget build(BuildContext context) => SingleChildScrollView( Widget build(BuildContext context) => FutureBuilder(
child: Column( // ignore: discarded_futures
children: [ future: widget.service.fetchPosts(widget.timelineCategoryFilter),
for (var post in widget.posts) builder: (context, snapshot) {
if (widget.timelineCategoryFilter == null || if (snapshot.hasData && snapshot.data != null) {
post.category == widget.timelineCategoryFilter) return SingleChildScrollView(
Padding( child: Column(
padding: const EdgeInsets.symmetric(vertical: 8.0), children: [
child: TimelinePostWidget( for (var post in snapshot.data!)
options: widget.options, if (widget.timelineCategoryFilter == null ||
post: post, post.category == widget.timelineCategoryFilter)
height: widget.timelinePostHeight, Padding(
onTap: () => widget.onPostTap.call(post), padding: const EdgeInsets.symmetric(vertical: 8.0),
), child: TimelinePostWidget(
), options: widget.options,
], post: post,
), height: widget.timelinePostHeight,
onTap: () => widget.onPostTap.call(post),
),
),
],
),
);
} else {
return const Center(child: CircularProgressIndicator());
}
},
); );
} }