mirror of
https://github.com/Iconica-Development/flutter_timeline.git
synced 2025-05-19 10:33:44 +02:00
fix: Made parameters nullable
This commit is contained in:
parent
d0b4db1eb0
commit
179841f930
9 changed files with 537 additions and 446 deletions
|
@ -40,7 +40,7 @@ List<GoRoute> getTimelineStoryRoutes() => getTimelineStoryRoutes(
|
||||||
service: FirebaseTimelineService(),
|
service: FirebaseTimelineService(),
|
||||||
userService: FirebaseUserService(),
|
userService: FirebaseUserService(),
|
||||||
userId: currentUserId,
|
userId: currentUserId,
|
||||||
optionsBuilder: (context) {},
|
optionsBuilder: (context) => FirebaseOptions(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
```
|
```
|
||||||
|
@ -79,13 +79,12 @@ TimelineScreen(
|
||||||
userId: currentUserId,
|
userId: currentUserId,
|
||||||
service: timelineService,
|
service: timelineService,
|
||||||
options: timelineOptions,
|
options: timelineOptions,
|
||||||
onPostTap: (post) {}
|
|
||||||
),
|
),
|
||||||
````
|
````
|
||||||
|
|
||||||
`TimelineScreen` is supplied with a standard `TimelinePostScreen` which opens the detail page of the selected post. Needed parameter like `TimelineService` and `TimelineOptions` will be the same as the ones supplied to the `TimelineScreen`.
|
`TimelineScreen` is supplied with a standard `TimelinePostScreen` which opens the detail page of the selected post. Needed parameter like `TimelineService` and `TimelineOptions` will be the same as the ones supplied to the `TimelineScreen`.
|
||||||
|
|
||||||
The standard `TimelinePostScreen` can be overridden by supplying `onPostTap` as shown below.
|
The standard `TimelinePostScreen` can be overridden by defining `onPostTap` as shown below.
|
||||||
|
|
||||||
```
|
```
|
||||||
TimelineScreen(
|
TimelineScreen(
|
||||||
|
|
|
@ -4,7 +4,7 @@ import 'package:flutter_timeline/flutter_timeline.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
List<GoRoute> getTimelineRoutes() => getTimelineStoryRoutes(
|
List<GoRoute> getTimelineRoutes() => getTimelineStoryRoutes(
|
||||||
getConfig(TimelineService(
|
configuration: getConfig(TimelineService(
|
||||||
postService: LocalTimelinePostService(),
|
postService: LocalTimelinePostService(),
|
||||||
)),
|
)),
|
||||||
);
|
);
|
||||||
|
|
|
@ -31,8 +31,7 @@ class MyHomePage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
var timelineService =
|
var timelineService = TimelineService(postService: LocalTimelinePostService());
|
||||||
TimelineService(postService: LocalTimelinePostService());
|
|
||||||
var timelineOptions = options;
|
var timelineOptions = options;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -32,8 +32,7 @@ class MyHomePage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _MyHomePageState extends State<MyHomePage> {
|
class _MyHomePageState extends State<MyHomePage> {
|
||||||
var timelineService =
|
var timelineService = TimelineService(postService: LocalTimelinePostService());
|
||||||
TimelineService(postService: LocalTimelinePostService());
|
|
||||||
var timelineOptions = options;
|
var timelineOptions = options;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -5,47 +5,80 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_timeline/flutter_timeline.dart';
|
import 'package:flutter_timeline/flutter_timeline.dart';
|
||||||
|
|
||||||
Widget timeLineNavigatorUserStory(
|
Widget timeLineNavigatorUserStory({
|
||||||
TimelineUserStoryConfiguration configuration,
|
required BuildContext context,
|
||||||
BuildContext context,
|
TimelineUserStoryConfiguration? configuration,
|
||||||
) =>
|
}) {
|
||||||
_timelineScreenRoute(configuration, context);
|
var config = configuration ??
|
||||||
|
TimelineUserStoryConfiguration(
|
||||||
|
userId: 'test_user',
|
||||||
|
service: TimelineService(
|
||||||
|
postService: LocalTimelinePostService(),
|
||||||
|
),
|
||||||
|
optionsBuilder: (context) => const TimelineOptions(),
|
||||||
|
);
|
||||||
|
|
||||||
Widget _timelineScreenRoute(
|
return _timelineScreenRoute(configuration: config, context: context);
|
||||||
TimelineUserStoryConfiguration configuration,
|
}
|
||||||
BuildContext context,
|
|
||||||
) =>
|
Widget _timelineScreenRoute({
|
||||||
TimelineScreen(
|
required BuildContext context,
|
||||||
service: configuration.service,
|
TimelineUserStoryConfiguration? configuration,
|
||||||
options: configuration.optionsBuilder(context),
|
}) {
|
||||||
userId: configuration.userId,
|
var config = configuration ??
|
||||||
|
TimelineUserStoryConfiguration(
|
||||||
|
userId: 'test_user',
|
||||||
|
service: TimelineService(
|
||||||
|
postService: LocalTimelinePostService(),
|
||||||
|
),
|
||||||
|
optionsBuilder: (context) => const TimelineOptions(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return TimelineScreen(
|
||||||
|
service: config.service,
|
||||||
|
options: config.optionsBuilder(context),
|
||||||
|
userId: config.userId,
|
||||||
onPostTap: (post) async =>
|
onPostTap: (post) async =>
|
||||||
configuration.onPostTap?.call(context, post) ??
|
config.onPostTap?.call(context, post) ??
|
||||||
Navigator.of(context).push(
|
Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) =>
|
builder: (context) => _postDetailScreenRoute(
|
||||||
_postDetailScreenRoute(configuration, context, post),
|
configuration: config,
|
||||||
|
context: context,
|
||||||
|
post: post,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
onUserTap: (userId) {
|
onUserTap: (userId) {
|
||||||
configuration.onUserTap?.call(context, userId);
|
config.onUserTap?.call(context, userId);
|
||||||
},
|
},
|
||||||
filterEnabled: configuration.filterEnabled,
|
filterEnabled: config.filterEnabled,
|
||||||
postWidgetBuilder: configuration.postWidgetBuilder,
|
postWidgetBuilder: config.postWidgetBuilder,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _postDetailScreenRoute({
|
||||||
|
required BuildContext context,
|
||||||
|
required TimelinePost post,
|
||||||
|
TimelineUserStoryConfiguration? configuration,
|
||||||
|
}) {
|
||||||
|
var config = configuration ??
|
||||||
|
TimelineUserStoryConfiguration(
|
||||||
|
userId: 'test_user',
|
||||||
|
service: TimelineService(
|
||||||
|
postService: LocalTimelinePostService(),
|
||||||
|
),
|
||||||
|
optionsBuilder: (context) => const TimelineOptions(),
|
||||||
);
|
);
|
||||||
|
|
||||||
Widget _postDetailScreenRoute(
|
return TimelinePostScreen(
|
||||||
TimelineUserStoryConfiguration configuration,
|
userId: config.userId,
|
||||||
BuildContext context,
|
service: config.service,
|
||||||
TimelinePost post,
|
options: config.optionsBuilder(context),
|
||||||
) =>
|
|
||||||
TimelinePostScreen(
|
|
||||||
userId: configuration.userId,
|
|
||||||
service: configuration.service,
|
|
||||||
options: configuration.optionsBuilder(context),
|
|
||||||
post: post,
|
post: post,
|
||||||
onPostDelete: () async {
|
onPostDelete: () async {
|
||||||
configuration.onPostDelete?.call(context, post) ??
|
config.onPostDelete?.call(context, post) ??
|
||||||
await configuration.service.postService.deletePost(post);
|
await config.service.postService.deletePost(post);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -3,37 +3,43 @@
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_timeline/flutter_timeline.dart';
|
||||||
import 'package:flutter_timeline/src/go_router.dart';
|
import 'package:flutter_timeline/src/go_router.dart';
|
||||||
import 'package:flutter_timeline/src/models/timeline_configuration.dart';
|
|
||||||
import 'package:flutter_timeline/src/routes.dart';
|
|
||||||
import 'package:flutter_timeline_view/flutter_timeline_view.dart';
|
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
List<GoRoute> getTimelineStoryRoutes(
|
List<GoRoute> getTimelineStoryRoutes({
|
||||||
TimelineUserStoryConfiguration configuration,
|
TimelineUserStoryConfiguration? configuration,
|
||||||
) =>
|
}) {
|
||||||
<GoRoute>[
|
var config = configuration ??= TimelineUserStoryConfiguration(
|
||||||
|
userId: 'test_user',
|
||||||
|
service: TimelineService(
|
||||||
|
postService: LocalTimelinePostService(),
|
||||||
|
),
|
||||||
|
optionsBuilder: (context) => const TimelineOptions(),
|
||||||
|
);
|
||||||
|
|
||||||
|
return <GoRoute>[
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: TimelineUserStoryRoutes.timelineHome,
|
path: TimelineUserStoryRoutes.timelineHome,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
var timelineScreen = TimelineScreen(
|
var timelineScreen = TimelineScreen(
|
||||||
userId: configuration.userId,
|
userId: config.userId,
|
||||||
onUserTap: (user) => configuration.onUserTap?.call(context, user),
|
onUserTap: (user) => config.onUserTap?.call(context, user),
|
||||||
service: configuration.service,
|
service: config.service,
|
||||||
options: configuration.optionsBuilder(context),
|
options: config.optionsBuilder(context),
|
||||||
onPostTap: (post) async =>
|
onPostTap: (post) async =>
|
||||||
configuration.onPostTap?.call(context, post) ??
|
config.onPostTap?.call(context, post) ??
|
||||||
await context.push(
|
await context.push(
|
||||||
TimelineUserStoryRoutes.timelineViewPath(post.id),
|
TimelineUserStoryRoutes.timelineViewPath(post.id),
|
||||||
),
|
),
|
||||||
filterEnabled: configuration.filterEnabled,
|
filterEnabled: config.filterEnabled,
|
||||||
postWidgetBuilder: configuration.postWidgetBuilder,
|
postWidgetBuilder: config.postWidgetBuilder,
|
||||||
);
|
);
|
||||||
|
|
||||||
return buildScreenWithoutTransition(
|
return buildScreenWithoutTransition(
|
||||||
context: context,
|
context: context,
|
||||||
state: state,
|
state: state,
|
||||||
child: configuration.openPageBuilder?.call(
|
child: config.openPageBuilder?.call(
|
||||||
context,
|
context,
|
||||||
timelineScreen,
|
timelineScreen,
|
||||||
) ??
|
) ??
|
||||||
|
@ -46,22 +52,22 @@ List<GoRoute> getTimelineStoryRoutes(
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: TimelineUserStoryRoutes.timelineView,
|
path: TimelineUserStoryRoutes.timelineView,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
var post = configuration.service.postService
|
var post =
|
||||||
.getPost(state.pathParameters['post']!)!;
|
config.service.postService.getPost(state.pathParameters['post']!)!;
|
||||||
|
|
||||||
var timelinePostWidget = TimelinePostScreen(
|
var timelinePostWidget = TimelinePostScreen(
|
||||||
userId: configuration.userId,
|
userId: config.userId,
|
||||||
options: configuration.optionsBuilder(context),
|
options: config.optionsBuilder(context),
|
||||||
service: configuration.service,
|
service: config.service,
|
||||||
post: post,
|
post: post,
|
||||||
onPostDelete: () => configuration.onPostDelete?.call(context, post),
|
onPostDelete: () => config.onPostDelete?.call(context, post),
|
||||||
onUserTap: (user) => configuration.onUserTap?.call(context, user),
|
onUserTap: (user) => config.onUserTap?.call(context, user),
|
||||||
);
|
);
|
||||||
|
|
||||||
return buildScreenWithoutTransition(
|
return buildScreenWithoutTransition(
|
||||||
context: context,
|
context: context,
|
||||||
state: state,
|
state: state,
|
||||||
child: configuration.openPageBuilder?.call(
|
child: config.openPageBuilder?.call(
|
||||||
context,
|
context,
|
||||||
timelinePostWidget,
|
timelinePostWidget,
|
||||||
) ??
|
) ??
|
||||||
|
@ -72,3 +78,4 @@ List<GoRoute> getTimelineStoryRoutes(
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
}
|
||||||
|
|
|
@ -6,5 +6,5 @@
|
||||||
library flutter_timeline_firebase;
|
library flutter_timeline_firebase;
|
||||||
|
|
||||||
export 'src/config/firebase_timeline_options.dart';
|
export 'src/config/firebase_timeline_options.dart';
|
||||||
export 'src/service/firebase_timeline_service.dart';
|
export 'src/service/firebase_post_service.dart';
|
||||||
export 'src/service/firebase_user_service.dart';
|
export 'src/service/firebase_user_service.dart';
|
||||||
|
|
|
@ -0,0 +1,352 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Iconica
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
import 'dart:typed_data';
|
||||||
|
|
||||||
|
import 'package:cloud_firestore/cloud_firestore.dart';
|
||||||
|
import 'package:firebase_core/firebase_core.dart';
|
||||||
|
import 'package:firebase_storage/firebase_storage.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_timeline_firebase/src/config/firebase_timeline_options.dart';
|
||||||
|
import 'package:flutter_timeline_firebase/src/models/firebase_user_document.dart';
|
||||||
|
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
|
||||||
|
import 'package:uuid/uuid.dart';
|
||||||
|
|
||||||
|
class FirebaseTimelinePostService extends TimelinePostService
|
||||||
|
with TimelineUserService {
|
||||||
|
FirebaseTimelinePostService({
|
||||||
|
required TimelineUserService userService,
|
||||||
|
FirebaseApp? app,
|
||||||
|
options = const FirebaseTimelineOptions(),
|
||||||
|
}) {
|
||||||
|
var appInstance = app ?? Firebase.app();
|
||||||
|
_db = FirebaseFirestore.instanceFor(app: appInstance);
|
||||||
|
_storage = FirebaseStorage.instanceFor(app: appInstance);
|
||||||
|
_userService = userService;
|
||||||
|
_options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
late FirebaseFirestore _db;
|
||||||
|
late FirebaseStorage _storage;
|
||||||
|
late TimelineUserService _userService;
|
||||||
|
late FirebaseTimelineOptions _options;
|
||||||
|
|
||||||
|
final Map<String, TimelinePosterUserModel> _users = {};
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TimelinePost> createPost(TimelinePost post) async {
|
||||||
|
var postId = const Uuid().v4();
|
||||||
|
var user = await _userService.getUser(post.creatorId);
|
||||||
|
var updatedPost = post.copyWith(id: postId, creator: user);
|
||||||
|
if (post.image != null) {
|
||||||
|
var imageRef =
|
||||||
|
_storage.ref().child('${_options.timelineCollectionName}/$postId');
|
||||||
|
var result = await imageRef.putData(post.image!);
|
||||||
|
var imageUrl = await result.ref.getDownloadURL();
|
||||||
|
updatedPost = updatedPost.copyWith(imageUrl: imageUrl);
|
||||||
|
}
|
||||||
|
var postRef =
|
||||||
|
_db.collection(_options.timelineCollectionName).doc(updatedPost.id);
|
||||||
|
await postRef.set(updatedPost.toJson());
|
||||||
|
posts.add(updatedPost);
|
||||||
|
notifyListeners();
|
||||||
|
return updatedPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<void> deletePost(TimelinePost post) async {
|
||||||
|
posts = posts.where((element) => element.id != post.id).toList();
|
||||||
|
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id);
|
||||||
|
await postRef.delete();
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TimelinePost> deletePostReaction(
|
||||||
|
TimelinePost post,
|
||||||
|
String reactionId,
|
||||||
|
) async {
|
||||||
|
if (post.reactions != null && post.reactions!.isNotEmpty) {
|
||||||
|
var reaction =
|
||||||
|
post.reactions!.firstWhere((element) => element.id == reactionId);
|
||||||
|
var updatedPost = post.copyWith(
|
||||||
|
reaction: post.reaction - 1,
|
||||||
|
reactions: (post.reactions ?? [])..remove(reaction),
|
||||||
|
);
|
||||||
|
posts = posts
|
||||||
|
.map(
|
||||||
|
(p) => p.id == post.id ? updatedPost : p,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
var postRef =
|
||||||
|
_db.collection(_options.timelineCollectionName).doc(post.id);
|
||||||
|
await postRef.update({
|
||||||
|
'reaction': FieldValue.increment(-1),
|
||||||
|
'reactions': FieldValue.arrayRemove(
|
||||||
|
[reaction.toJsonWithMicroseconds()],
|
||||||
|
),
|
||||||
|
});
|
||||||
|
notifyListeners();
|
||||||
|
return updatedPost;
|
||||||
|
}
|
||||||
|
return post;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TimelinePost> fetchPostDetails(TimelinePost post) async {
|
||||||
|
var reactions = post.reactions ?? [];
|
||||||
|
var updatedReactions = <TimelinePostReaction>[];
|
||||||
|
for (var reaction in reactions) {
|
||||||
|
var user = await _userService.getUser(reaction.creatorId);
|
||||||
|
if (user != null) {
|
||||||
|
updatedReactions.add(reaction.copyWith(creator: user));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var updatedPost = post.copyWith(reactions: updatedReactions);
|
||||||
|
posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList();
|
||||||
|
notifyListeners();
|
||||||
|
return updatedPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<TimelinePost>> fetchPosts(String? category) async {
|
||||||
|
debugPrint('fetching posts from firebase with category: $category');
|
||||||
|
var snapshot = (category != null)
|
||||||
|
? await _db
|
||||||
|
.collection(_options.timelineCollectionName)
|
||||||
|
.where('category', isEqualTo: category)
|
||||||
|
.get()
|
||||||
|
: await _db.collection(_options.timelineCollectionName).get();
|
||||||
|
|
||||||
|
var posts = <TimelinePost>[];
|
||||||
|
for (var doc in snapshot.docs) {
|
||||||
|
var data = doc.data();
|
||||||
|
var user = await _userService.getUser(data['creator_id']);
|
||||||
|
var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user);
|
||||||
|
posts.add(post);
|
||||||
|
}
|
||||||
|
|
||||||
|
notifyListeners();
|
||||||
|
return posts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<TimelinePost>> fetchPostsPaginated(
|
||||||
|
String? category,
|
||||||
|
int limit,
|
||||||
|
) async {
|
||||||
|
// only take posts that are in our category
|
||||||
|
var oldestPost = posts
|
||||||
|
.where(
|
||||||
|
(element) => category == null || element.category == category,
|
||||||
|
)
|
||||||
|
.fold(
|
||||||
|
posts.first,
|
||||||
|
(previousValue, element) =>
|
||||||
|
(previousValue.createdAt.isBefore(element.createdAt))
|
||||||
|
? previousValue
|
||||||
|
: element,
|
||||||
|
);
|
||||||
|
var snapshot = (category != null)
|
||||||
|
? await _db
|
||||||
|
.collection(_options.timelineCollectionName)
|
||||||
|
.where('category', isEqualTo: category)
|
||||||
|
.orderBy('created_at', descending: true)
|
||||||
|
.startAfter([oldestPost])
|
||||||
|
.limit(limit)
|
||||||
|
.get()
|
||||||
|
: await _db
|
||||||
|
.collection(_options.timelineCollectionName)
|
||||||
|
.orderBy('created_at', descending: true)
|
||||||
|
.startAfter([oldestPost.createdAt])
|
||||||
|
.limit(limit)
|
||||||
|
.get();
|
||||||
|
// add the new posts to the list
|
||||||
|
var newPosts = <TimelinePost>[];
|
||||||
|
for (var doc in snapshot.docs) {
|
||||||
|
var data = doc.data();
|
||||||
|
var user = await _userService.getUser(data['creator_id']);
|
||||||
|
var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user);
|
||||||
|
newPosts.add(post);
|
||||||
|
}
|
||||||
|
posts = [...posts, ...newPosts];
|
||||||
|
notifyListeners();
|
||||||
|
return newPosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TimelinePost> fetchPost(TimelinePost post) async {
|
||||||
|
var doc = await _db
|
||||||
|
.collection(_options.timelineCollectionName)
|
||||||
|
.doc(post.id)
|
||||||
|
.get();
|
||||||
|
var data = doc.data();
|
||||||
|
if (data == null) return post;
|
||||||
|
var user = await _userService.getUser(data['creator_id']);
|
||||||
|
var updatedPost = TimelinePost.fromJson(doc.id, data).copyWith(
|
||||||
|
creator: user,
|
||||||
|
);
|
||||||
|
posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList();
|
||||||
|
notifyListeners();
|
||||||
|
return updatedPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<List<TimelinePost>> refreshPosts(String? category) async {
|
||||||
|
// fetch all posts between now and the newest posts we have
|
||||||
|
var newestPostWeHave = posts
|
||||||
|
.where(
|
||||||
|
(element) => category == null || element.category == category,
|
||||||
|
)
|
||||||
|
.fold(
|
||||||
|
posts.first,
|
||||||
|
(previousValue, element) =>
|
||||||
|
(previousValue.createdAt.isAfter(element.createdAt))
|
||||||
|
? previousValue
|
||||||
|
: element,
|
||||||
|
);
|
||||||
|
var snapshot = (category != null)
|
||||||
|
? await _db
|
||||||
|
.collection(_options.timelineCollectionName)
|
||||||
|
.where('category', isEqualTo: category)
|
||||||
|
.orderBy('created_at', descending: true)
|
||||||
|
.endBefore([newestPostWeHave.createdAt]).get()
|
||||||
|
: await _db
|
||||||
|
.collection(_options.timelineCollectionName)
|
||||||
|
.orderBy('created_at', descending: true)
|
||||||
|
.endBefore([newestPostWeHave.createdAt]).get();
|
||||||
|
// add the new posts to the list
|
||||||
|
var newPosts = <TimelinePost>[];
|
||||||
|
for (var doc in snapshot.docs) {
|
||||||
|
var data = doc.data();
|
||||||
|
var user = await _userService.getUser(data['creator_id']);
|
||||||
|
var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user);
|
||||||
|
newPosts.add(post);
|
||||||
|
}
|
||||||
|
posts = [...posts, ...newPosts];
|
||||||
|
notifyListeners();
|
||||||
|
return newPosts;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
TimelinePost? getPost(String postId) =>
|
||||||
|
(posts.any((element) => element.id == postId))
|
||||||
|
? posts.firstWhere((element) => element.id == postId)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
List<TimelinePost> getPosts(String? category) => posts
|
||||||
|
.where((element) => category == null || element.category == category)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TimelinePost> likePost(String userId, TimelinePost post) async {
|
||||||
|
// update the post with the new like
|
||||||
|
var updatedPost = post.copyWith(
|
||||||
|
likes: post.likes + 1,
|
||||||
|
likedBy: post.likedBy?..add(userId),
|
||||||
|
);
|
||||||
|
posts = posts
|
||||||
|
.map(
|
||||||
|
(p) => p.id == post.id ? updatedPost : p,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id);
|
||||||
|
await postRef.update({
|
||||||
|
'likes': FieldValue.increment(1),
|
||||||
|
'liked_by': FieldValue.arrayUnion([userId]),
|
||||||
|
});
|
||||||
|
notifyListeners();
|
||||||
|
return updatedPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TimelinePost> unlikePost(String userId, TimelinePost post) async {
|
||||||
|
// update the post with the new like
|
||||||
|
var updatedPost = post.copyWith(
|
||||||
|
likes: post.likes - 1,
|
||||||
|
likedBy: post.likedBy?..remove(userId),
|
||||||
|
);
|
||||||
|
posts = posts
|
||||||
|
.map(
|
||||||
|
(p) => p.id == post.id ? updatedPost : p,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id);
|
||||||
|
await postRef.update({
|
||||||
|
'likes': FieldValue.increment(-1),
|
||||||
|
'liked_by': FieldValue.arrayRemove([userId]),
|
||||||
|
});
|
||||||
|
notifyListeners();
|
||||||
|
return updatedPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<TimelinePost> reactToPost(
|
||||||
|
TimelinePost post,
|
||||||
|
TimelinePostReaction reaction, {
|
||||||
|
Uint8List? image,
|
||||||
|
}) async {
|
||||||
|
var reactionId = const Uuid().v4();
|
||||||
|
// also fetch the user information and add it to the reaction
|
||||||
|
var user = await _userService.getUser(reaction.creatorId);
|
||||||
|
var updatedReaction = reaction.copyWith(id: reactionId, creator: user);
|
||||||
|
if (image != null) {
|
||||||
|
var imageRef = _storage
|
||||||
|
.ref()
|
||||||
|
.child('${_options.timelineCollectionName}/${post.id}/$reactionId}');
|
||||||
|
var result = await imageRef.putData(image);
|
||||||
|
var imageUrl = await result.ref.getDownloadURL();
|
||||||
|
updatedReaction = updatedReaction.copyWith(imageUrl: imageUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
var updatedPost = post.copyWith(
|
||||||
|
reaction: post.reaction + 1,
|
||||||
|
reactions: post.reactions?..add(updatedReaction),
|
||||||
|
);
|
||||||
|
|
||||||
|
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id);
|
||||||
|
await postRef.update({
|
||||||
|
'reaction': FieldValue.increment(1),
|
||||||
|
'reactions': FieldValue.arrayUnion([updatedReaction.toJson()]),
|
||||||
|
});
|
||||||
|
posts = posts
|
||||||
|
.map(
|
||||||
|
(p) => p.id == post.id ? updatedPost : p,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
notifyListeners();
|
||||||
|
return updatedPost;
|
||||||
|
}
|
||||||
|
|
||||||
|
CollectionReference<FirebaseUserDocument> get _userCollection => _db
|
||||||
|
.collection(_options.usersCollectionName)
|
||||||
|
.withConverter<FirebaseUserDocument>(
|
||||||
|
fromFirestore: (snapshot, _) => FirebaseUserDocument.fromJson(
|
||||||
|
snapshot.data()!,
|
||||||
|
snapshot.id,
|
||||||
|
),
|
||||||
|
toFirestore: (user, _) => user.toJson(),
|
||||||
|
);
|
||||||
|
@override
|
||||||
|
Future<TimelinePosterUserModel?> getUser(String userId) async {
|
||||||
|
if (_users.containsKey(userId)) {
|
||||||
|
return _users[userId]!;
|
||||||
|
}
|
||||||
|
var data = (await _userCollection.doc(userId).get()).data();
|
||||||
|
|
||||||
|
var user = data == null
|
||||||
|
? TimelinePosterUserModel(userId: userId)
|
||||||
|
: TimelinePosterUserModel(
|
||||||
|
userId: userId,
|
||||||
|
firstName: data.firstName,
|
||||||
|
lastName: data.lastName,
|
||||||
|
imageUrl: data.imageUrl,
|
||||||
|
);
|
||||||
|
|
||||||
|
_users[userId] = user;
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,352 +1,54 @@
|
||||||
// SPDX-FileCopyrightText: 2023 Iconica
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
|
||||||
|
|
||||||
import 'dart:typed_data';
|
|
||||||
|
|
||||||
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:flutter_timeline_firebase/flutter_timeline_firebase.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_timeline_firebase/src/config/firebase_timeline_options.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';
|
||||||
import 'package:uuid/uuid.dart';
|
|
||||||
|
|
||||||
class FirebaseTimelineService extends TimelinePostService
|
class FirebaseTimelineService implements TimelineService {
|
||||||
with TimelineUserService {
|
|
||||||
FirebaseTimelineService({
|
FirebaseTimelineService({
|
||||||
required TimelineUserService userService,
|
this.options,
|
||||||
FirebaseApp? app,
|
this.app,
|
||||||
options = const FirebaseTimelineOptions(),
|
this.firebasePostService,
|
||||||
|
this.firebaseUserService,
|
||||||
}) {
|
}) {
|
||||||
var appInstance = app ?? Firebase.app();
|
firebaseUserService ??= FirebaseTimelinePostService(
|
||||||
_db = FirebaseFirestore.instanceFor(app: appInstance);
|
userService: userService,
|
||||||
_storage = FirebaseStorage.instanceFor(app: appInstance);
|
options: options,
|
||||||
_userService = userService;
|
app: app,
|
||||||
_options = options;
|
|
||||||
}
|
|
||||||
|
|
||||||
late FirebaseFirestore _db;
|
|
||||||
late FirebaseStorage _storage;
|
|
||||||
late TimelineUserService _userService;
|
|
||||||
late FirebaseTimelineOptions _options;
|
|
||||||
|
|
||||||
final Map<String, TimelinePosterUserModel> _users = {};
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TimelinePost> createPost(TimelinePost post) async {
|
|
||||||
var postId = const Uuid().v4();
|
|
||||||
var user = await _userService.getUser(post.creatorId);
|
|
||||||
var updatedPost = post.copyWith(id: postId, creator: user);
|
|
||||||
if (post.image != null) {
|
|
||||||
var imageRef =
|
|
||||||
_storage.ref().child('${_options.timelineCollectionName}/$postId');
|
|
||||||
var result = await imageRef.putData(post.image!);
|
|
||||||
var imageUrl = await result.ref.getDownloadURL();
|
|
||||||
updatedPost = updatedPost.copyWith(imageUrl: imageUrl);
|
|
||||||
}
|
|
||||||
var postRef =
|
|
||||||
_db.collection(_options.timelineCollectionName).doc(updatedPost.id);
|
|
||||||
await postRef.set(updatedPost.toJson());
|
|
||||||
posts.add(updatedPost);
|
|
||||||
notifyListeners();
|
|
||||||
return updatedPost;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<void> deletePost(TimelinePost post) async {
|
|
||||||
posts = posts.where((element) => element.id != post.id).toList();
|
|
||||||
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id);
|
|
||||||
await postRef.delete();
|
|
||||||
notifyListeners();
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TimelinePost> deletePostReaction(
|
|
||||||
TimelinePost post,
|
|
||||||
String reactionId,
|
|
||||||
) async {
|
|
||||||
if (post.reactions != null && post.reactions!.isNotEmpty) {
|
|
||||||
var reaction =
|
|
||||||
post.reactions!.firstWhere((element) => element.id == reactionId);
|
|
||||||
var updatedPost = post.copyWith(
|
|
||||||
reaction: post.reaction - 1,
|
|
||||||
reactions: (post.reactions ?? [])..remove(reaction),
|
|
||||||
);
|
|
||||||
posts = posts
|
|
||||||
.map(
|
|
||||||
(p) => p.id == post.id ? updatedPost : p,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
var postRef =
|
|
||||||
_db.collection(_options.timelineCollectionName).doc(post.id);
|
|
||||||
await postRef.update({
|
|
||||||
'reaction': FieldValue.increment(-1),
|
|
||||||
'reactions': FieldValue.arrayRemove(
|
|
||||||
[reaction.toJsonWithMicroseconds()],
|
|
||||||
),
|
|
||||||
});
|
|
||||||
notifyListeners();
|
|
||||||
return updatedPost;
|
|
||||||
}
|
|
||||||
return post;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TimelinePost> fetchPostDetails(TimelinePost post) async {
|
|
||||||
var reactions = post.reactions ?? [];
|
|
||||||
var updatedReactions = <TimelinePostReaction>[];
|
|
||||||
for (var reaction in reactions) {
|
|
||||||
var user = await _userService.getUser(reaction.creatorId);
|
|
||||||
if (user != null) {
|
|
||||||
updatedReactions.add(reaction.copyWith(creator: user));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var updatedPost = post.copyWith(reactions: updatedReactions);
|
|
||||||
posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList();
|
|
||||||
notifyListeners();
|
|
||||||
return updatedPost;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<TimelinePost>> fetchPosts(String? category) async {
|
|
||||||
debugPrint('fetching posts from firebase with category: $category');
|
|
||||||
var snapshot = (category != null)
|
|
||||||
? await _db
|
|
||||||
.collection(_options.timelineCollectionName)
|
|
||||||
.where('category', isEqualTo: category)
|
|
||||||
.get()
|
|
||||||
: await _db.collection(_options.timelineCollectionName).get();
|
|
||||||
|
|
||||||
var posts = <TimelinePost>[];
|
|
||||||
for (var doc in snapshot.docs) {
|
|
||||||
var data = doc.data();
|
|
||||||
var user = await _userService.getUser(data['creator_id']);
|
|
||||||
var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user);
|
|
||||||
posts.add(post);
|
|
||||||
}
|
|
||||||
|
|
||||||
notifyListeners();
|
|
||||||
return posts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<TimelinePost>> fetchPostsPaginated(
|
|
||||||
String? category,
|
|
||||||
int limit,
|
|
||||||
) async {
|
|
||||||
// only take posts that are in our category
|
|
||||||
var oldestPost = posts
|
|
||||||
.where(
|
|
||||||
(element) => category == null || element.category == category,
|
|
||||||
)
|
|
||||||
.fold(
|
|
||||||
posts.first,
|
|
||||||
(previousValue, element) =>
|
|
||||||
(previousValue.createdAt.isBefore(element.createdAt))
|
|
||||||
? previousValue
|
|
||||||
: element,
|
|
||||||
);
|
|
||||||
var snapshot = (category != null)
|
|
||||||
? await _db
|
|
||||||
.collection(_options.timelineCollectionName)
|
|
||||||
.where('category', isEqualTo: category)
|
|
||||||
.orderBy('created_at', descending: true)
|
|
||||||
.startAfter([oldestPost])
|
|
||||||
.limit(limit)
|
|
||||||
.get()
|
|
||||||
: await _db
|
|
||||||
.collection(_options.timelineCollectionName)
|
|
||||||
.orderBy('created_at', descending: true)
|
|
||||||
.startAfter([oldestPost.createdAt])
|
|
||||||
.limit(limit)
|
|
||||||
.get();
|
|
||||||
// add the new posts to the list
|
|
||||||
var newPosts = <TimelinePost>[];
|
|
||||||
for (var doc in snapshot.docs) {
|
|
||||||
var data = doc.data();
|
|
||||||
var user = await _userService.getUser(data['creator_id']);
|
|
||||||
var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user);
|
|
||||||
newPosts.add(post);
|
|
||||||
}
|
|
||||||
posts = [...posts, ...newPosts];
|
|
||||||
notifyListeners();
|
|
||||||
return newPosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TimelinePost> fetchPost(TimelinePost post) async {
|
|
||||||
var doc = await _db
|
|
||||||
.collection(_options.timelineCollectionName)
|
|
||||||
.doc(post.id)
|
|
||||||
.get();
|
|
||||||
var data = doc.data();
|
|
||||||
if (data == null) return post;
|
|
||||||
var user = await _userService.getUser(data['creator_id']);
|
|
||||||
var updatedPost = TimelinePost.fromJson(doc.id, data).copyWith(
|
|
||||||
creator: user,
|
|
||||||
);
|
|
||||||
posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList();
|
|
||||||
notifyListeners();
|
|
||||||
return updatedPost;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<List<TimelinePost>> refreshPosts(String? category) async {
|
|
||||||
// fetch all posts between now and the newest posts we have
|
|
||||||
var newestPostWeHave = posts
|
|
||||||
.where(
|
|
||||||
(element) => category == null || element.category == category,
|
|
||||||
)
|
|
||||||
.fold(
|
|
||||||
posts.first,
|
|
||||||
(previousValue, element) =>
|
|
||||||
(previousValue.createdAt.isAfter(element.createdAt))
|
|
||||||
? previousValue
|
|
||||||
: element,
|
|
||||||
);
|
|
||||||
var snapshot = (category != null)
|
|
||||||
? await _db
|
|
||||||
.collection(_options.timelineCollectionName)
|
|
||||||
.where('category', isEqualTo: category)
|
|
||||||
.orderBy('created_at', descending: true)
|
|
||||||
.endBefore([newestPostWeHave.createdAt]).get()
|
|
||||||
: await _db
|
|
||||||
.collection(_options.timelineCollectionName)
|
|
||||||
.orderBy('created_at', descending: true)
|
|
||||||
.endBefore([newestPostWeHave.createdAt]).get();
|
|
||||||
// add the new posts to the list
|
|
||||||
var newPosts = <TimelinePost>[];
|
|
||||||
for (var doc in snapshot.docs) {
|
|
||||||
var data = doc.data();
|
|
||||||
var user = await _userService.getUser(data['creator_id']);
|
|
||||||
var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user);
|
|
||||||
newPosts.add(post);
|
|
||||||
}
|
|
||||||
posts = [...posts, ...newPosts];
|
|
||||||
notifyListeners();
|
|
||||||
return newPosts;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
TimelinePost? getPost(String postId) =>
|
|
||||||
(posts.any((element) => element.id == postId))
|
|
||||||
? posts.firstWhere((element) => element.id == postId)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
List<TimelinePost> getPosts(String? category) => posts
|
|
||||||
.where((element) => category == null || element.category == category)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TimelinePost> likePost(String userId, TimelinePost post) async {
|
|
||||||
// update the post with the new like
|
|
||||||
var updatedPost = post.copyWith(
|
|
||||||
likes: post.likes + 1,
|
|
||||||
likedBy: post.likedBy?..add(userId),
|
|
||||||
);
|
|
||||||
posts = posts
|
|
||||||
.map(
|
|
||||||
(p) => p.id == post.id ? updatedPost : p,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id);
|
|
||||||
await postRef.update({
|
|
||||||
'likes': FieldValue.increment(1),
|
|
||||||
'liked_by': FieldValue.arrayUnion([userId]),
|
|
||||||
});
|
|
||||||
notifyListeners();
|
|
||||||
return updatedPost;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TimelinePost> unlikePost(String userId, TimelinePost post) async {
|
|
||||||
// update the post with the new like
|
|
||||||
var updatedPost = post.copyWith(
|
|
||||||
likes: post.likes - 1,
|
|
||||||
likedBy: post.likedBy?..remove(userId),
|
|
||||||
);
|
|
||||||
posts = posts
|
|
||||||
.map(
|
|
||||||
(p) => p.id == post.id ? updatedPost : p,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id);
|
|
||||||
await postRef.update({
|
|
||||||
'likes': FieldValue.increment(-1),
|
|
||||||
'liked_by': FieldValue.arrayRemove([userId]),
|
|
||||||
});
|
|
||||||
notifyListeners();
|
|
||||||
return updatedPost;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Future<TimelinePost> reactToPost(
|
|
||||||
TimelinePost post,
|
|
||||||
TimelinePostReaction reaction, {
|
|
||||||
Uint8List? image,
|
|
||||||
}) async {
|
|
||||||
var reactionId = const Uuid().v4();
|
|
||||||
// also fetch the user information and add it to the reaction
|
|
||||||
var user = await _userService.getUser(reaction.creatorId);
|
|
||||||
var updatedReaction = reaction.copyWith(id: reactionId, creator: user);
|
|
||||||
if (image != null) {
|
|
||||||
var imageRef = _storage
|
|
||||||
.ref()
|
|
||||||
.child('${_options.timelineCollectionName}/${post.id}/$reactionId}');
|
|
||||||
var result = await imageRef.putData(image);
|
|
||||||
var imageUrl = await result.ref.getDownloadURL();
|
|
||||||
updatedReaction = updatedReaction.copyWith(imageUrl: imageUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
var updatedPost = post.copyWith(
|
|
||||||
reaction: post.reaction + 1,
|
|
||||||
reactions: post.reactions?..add(updatedReaction),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id);
|
firebasePostService ??= FirebaseTimelinePostService(
|
||||||
await postRef.update({
|
userService: userService,
|
||||||
'reaction': FieldValue.increment(1),
|
options: options,
|
||||||
'reactions': FieldValue.arrayUnion([updatedReaction.toJson()]),
|
app: app,
|
||||||
});
|
);
|
||||||
posts = posts
|
|
||||||
.map(
|
|
||||||
(p) => p.id == post.id ? updatedPost : p,
|
|
||||||
)
|
|
||||||
.toList();
|
|
||||||
notifyListeners();
|
|
||||||
return updatedPost;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CollectionReference<FirebaseUserDocument> get _userCollection => _db
|
final FirebaseTimelineOptions? options;
|
||||||
.collection(_options.usersCollectionName)
|
final FirebaseApp? app;
|
||||||
.withConverter<FirebaseUserDocument>(
|
TimelinePostService? firebasePostService;
|
||||||
fromFirestore: (snapshot, _) => FirebaseUserDocument.fromJson(
|
TimelineUserService? firebaseUserService;
|
||||||
snapshot.data()!,
|
|
||||||
snapshot.id,
|
|
||||||
),
|
|
||||||
toFirestore: (user, _) => user.toJson(),
|
|
||||||
);
|
|
||||||
@override
|
@override
|
||||||
Future<TimelinePosterUserModel?> getUser(String userId) async {
|
TimelinePostService get postService {
|
||||||
if (_users.containsKey(userId)) {
|
if (firebasePostService != null) {
|
||||||
return _users[userId]!;
|
return firebasePostService!;
|
||||||
}
|
} else {
|
||||||
var data = (await _userCollection.doc(userId).get()).data();
|
return FirebaseTimelinePostService(
|
||||||
|
userService: userService,
|
||||||
var user = data == null
|
options: options,
|
||||||
? TimelinePosterUserModel(userId: userId)
|
app: app,
|
||||||
: TimelinePosterUserModel(
|
|
||||||
userId: userId,
|
|
||||||
firstName: data.firstName,
|
|
||||||
lastName: data.lastName,
|
|
||||||
imageUrl: data.imageUrl,
|
|
||||||
);
|
);
|
||||||
|
}
|
||||||
_users[userId] = user;
|
}
|
||||||
|
|
||||||
return user;
|
@override
|
||||||
|
TimelineUserService get userService {
|
||||||
|
if (firebaseUserService != null) {
|
||||||
|
return firebaseUserService!;
|
||||||
|
} else {
|
||||||
|
return FirebaseUserService(
|
||||||
|
options: options,
|
||||||
|
app: app,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue