feat: Added search bar with filter

This commit is contained in:
Jacques 2024-01-23 11:22:51 +01:00
parent 14dced5ef4
commit e5e2eb5c22
13 changed files with 237 additions and 101 deletions

View file

@ -22,7 +22,7 @@ class _PostScreenState extends State<PostScreen> {
body: TimelinePostScreen( body: TimelinePostScreen(
userId: 'test_user', userId: 'test_user',
service: widget.service, service: widget.service,
options: const TimelineOptions(), options: TimelineOptions(),
post: widget.post, post: widget.post,
onPostDelete: () { onPostDelete: () {
print('delete post'); print('delete post');

View file

@ -11,11 +11,12 @@ import 'package:flutter_timeline/flutter_timeline.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
class TestTimelineService with ChangeNotifier implements TimelineService { class TestTimelineService with ChangeNotifier implements TimelineService {
List<TimelinePost> _posts = []; @override
List<TimelinePost> posts = [];
@override @override
Future<TimelinePost> createPost(TimelinePost post) async { Future<TimelinePost> createPost(TimelinePost post) async {
_posts.add( posts.add(
post.copyWith( post.copyWith(
creator: const TimelinePosterUserModel(userId: 'test_user'), creator: const TimelinePosterUserModel(userId: 'test_user'),
), ),
@ -26,7 +27,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
@override @override
Future<void> deletePost(TimelinePost post) async { Future<void> deletePost(TimelinePost post) async {
_posts = _posts.where((element) => element.id != post.id).toList(); posts = posts.where((element) => element.id != post.id).toList();
notifyListeners(); notifyListeners();
} }
@ -43,7 +44,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
reaction: post.reaction - 1, reaction: post.reaction - 1,
reactions: (post.reactions ?? [])..remove(reaction), reactions: (post.reactions ?? [])..remove(reaction),
); );
_posts = _posts posts = posts
.map( .map(
(p) => p.id == post.id ? updatedPost : p, (p) => p.id == post.id ? updatedPost : p,
) )
@ -64,7 +65,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
creator: const TimelinePosterUserModel(userId: 'test_user'))); creator: const TimelinePosterUserModel(userId: 'test_user')));
} }
var updatedPost = post.copyWith(reactions: updatedReactions); var updatedPost = post.copyWith(reactions: updatedReactions);
_posts = _posts.map((p) => (p.id == post.id) ? updatedPost : p).toList(); posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList();
notifyListeners(); notifyListeners();
return updatedPost; return updatedPost;
} }
@ -72,7 +73,6 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
@override @override
Future<List<TimelinePost>> fetchPosts(String? category) async { Future<List<TimelinePost>> fetchPosts(String? category) async {
var posts = getMockedPosts(); var posts = getMockedPosts();
_posts = posts;
notifyListeners(); notifyListeners();
return posts; return posts;
} }
@ -83,7 +83,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
int limit, int limit,
) async { ) async {
notifyListeners(); notifyListeners();
return _posts; return posts;
} }
@override @override
@ -94,32 +94,31 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
@override @override
Future<List<TimelinePost>> refreshPosts(String? category) async { Future<List<TimelinePost>> refreshPosts(String? category) async {
var posts = <TimelinePost>[]; var newPosts = <TimelinePost>[];
_posts = [...posts, ..._posts]; posts = [...posts, ...newPosts];
notifyListeners(); notifyListeners();
return posts; return posts;
} }
@override @override
TimelinePost? getPost(String postId) => TimelinePost? getPost(String postId) =>
(_posts.any((element) => element.id == postId)) (posts.any((element) => element.id == postId))
? _posts.firstWhere((element) => element.id == postId) ? posts.firstWhere((element) => element.id == postId)
: null; : null;
@override @override
List<TimelinePost> getPosts(String? category) => _posts List<TimelinePost> getPosts(String? category) => posts
.where((element) => category == null || element.category == category) .where((element) => category == null || element.category == category)
.toList(); .toList();
@override @override
Future<TimelinePost> likePost(String userId, TimelinePost post) async { Future<TimelinePost> likePost(String userId, TimelinePost post) async {
print(userId);
var updatedPost = post.copyWith( var updatedPost = post.copyWith(
likes: post.likes + 1, likes: post.likes + 1,
likedBy: (post.likedBy ?? [])..add(userId), likedBy: (post.likedBy ?? [])..add(userId),
); );
_posts = _posts posts = posts
.map( .map(
(p) => p.id == post.id ? updatedPost : p, (p) => p.id == post.id ? updatedPost : p,
) )
@ -135,7 +134,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
likes: post.likes - 1, likes: post.likes - 1,
likedBy: post.likedBy?..remove(userId), likedBy: post.likedBy?..remove(userId),
); );
_posts = _posts posts = posts
.map( .map(
(p) => p.id == post.id ? updatedPost : p, (p) => p.id == post.id ? updatedPost : p,
) )
@ -161,7 +160,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
reactions: post.reactions?..add(updatedReaction), reactions: post.reactions?..add(updatedReaction),
); );
_posts = _posts posts = posts
.map( .map(
(p) => p.id == post.id ? updatedPost : p, (p) => p.id == post.id ? updatedPost : p,
) )

View file

@ -25,7 +25,6 @@ List<GoRoute> getTimelineStoryRoutes(
options: configuration.optionsBuilder(context), options: configuration.optionsBuilder(context),
onPostTap: (post) async => onPostTap: (post) async =>
TimelineUserStoryRoutes.timelineViewPath(post.id), TimelineUserStoryRoutes.timelineViewPath(post.id),
timelineCategoryFilter: null,
); );
return buildScreenWithoutTransition( return buildScreenWithoutTransition(
context: context, context: context,

View file

@ -13,9 +13,7 @@ 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'; import 'package:uuid/uuid.dart';
class FirebaseTimelineService class FirebaseTimelineService extends TimelineService with TimelineUserService {
with ChangeNotifier
implements TimelineService, TimelineUserService {
FirebaseTimelineService({ FirebaseTimelineService({
required TimelineUserService userService, required TimelineUserService userService,
FirebaseApp? app, FirebaseApp? app,
@ -35,8 +33,6 @@ class FirebaseTimelineService
final Map<String, TimelinePosterUserModel> _users = {}; final Map<String, TimelinePosterUserModel> _users = {};
List<TimelinePost> _posts = [];
@override @override
Future<TimelinePost> createPost(TimelinePost post) async { Future<TimelinePost> createPost(TimelinePost post) async {
var postId = const Uuid().v4(); var postId = const Uuid().v4();
@ -52,14 +48,14 @@ class FirebaseTimelineService
var postRef = var postRef =
_db.collection(_options.timelineCollectionName).doc(updatedPost.id); _db.collection(_options.timelineCollectionName).doc(updatedPost.id);
await postRef.set(updatedPost.toJson()); await postRef.set(updatedPost.toJson());
_posts.add(updatedPost); posts.add(updatedPost);
notifyListeners(); notifyListeners();
return updatedPost; return updatedPost;
} }
@override @override
Future<void> deletePost(TimelinePost post) async { Future<void> deletePost(TimelinePost post) async {
_posts = _posts.where((element) => element.id != post.id).toList(); posts = posts.where((element) => element.id != post.id).toList();
var postRef = _db.collection(_options.timelineCollectionName).doc(post.id); var postRef = _db.collection(_options.timelineCollectionName).doc(post.id);
await postRef.delete(); await postRef.delete();
notifyListeners(); notifyListeners();
@ -77,7 +73,7 @@ class FirebaseTimelineService
reaction: post.reaction - 1, reaction: post.reaction - 1,
reactions: (post.reactions ?? [])..remove(reaction), reactions: (post.reactions ?? [])..remove(reaction),
); );
_posts = _posts posts = posts
.map( .map(
(p) => p.id == post.id ? updatedPost : p, (p) => p.id == post.id ? updatedPost : p,
) )
@ -107,7 +103,7 @@ class FirebaseTimelineService
} }
} }
var updatedPost = post.copyWith(reactions: updatedReactions); var updatedPost = post.copyWith(reactions: updatedReactions);
_posts = _posts.map((p) => (p.id == post.id) ? updatedPost : p).toList(); posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList();
notifyListeners(); notifyListeners();
return updatedPost; return updatedPost;
} }
@ -129,7 +125,7 @@ class FirebaseTimelineService
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);
} }
_posts = posts;
notifyListeners(); notifyListeners();
return posts; return posts;
} }
@ -140,12 +136,12 @@ class FirebaseTimelineService
int limit, int limit,
) async { ) async {
// only take posts that are in our category // only take posts that are in our category
var oldestPost = _posts var oldestPost = posts
.where( .where(
(element) => category == null || element.category == category, (element) => category == null || element.category == category,
) )
.fold( .fold(
_posts.first, posts.first,
(previousValue, element) => (previousValue, element) =>
(previousValue.createdAt.isBefore(element.createdAt)) (previousValue.createdAt.isBefore(element.createdAt))
? previousValue ? previousValue
@ -166,16 +162,16 @@ class FirebaseTimelineService
.limit(limit) .limit(limit)
.get(); .get();
// add the new posts to the list // add the new posts to the list
var posts = <TimelinePost>[]; var newPosts = <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['creator_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); newPosts.add(post);
} }
_posts = [..._posts, ...posts]; posts = [...posts, ...newPosts];
notifyListeners(); notifyListeners();
return posts; return newPosts;
} }
@override @override
@ -190,7 +186,7 @@ class FirebaseTimelineService
var updatedPost = TimelinePost.fromJson(doc.id, data).copyWith( var updatedPost = TimelinePost.fromJson(doc.id, data).copyWith(
creator: user, creator: user,
); );
_posts = _posts.map((p) => (p.id == post.id) ? updatedPost : p).toList(); posts = posts.map((p) => (p.id == post.id) ? updatedPost : p).toList();
notifyListeners(); notifyListeners();
return updatedPost; return updatedPost;
} }
@ -198,12 +194,12 @@ class FirebaseTimelineService
@override @override
Future<List<TimelinePost>> refreshPosts(String? category) async { Future<List<TimelinePost>> refreshPosts(String? category) async {
// fetch all posts between now and the newest posts we have // fetch all posts between now and the newest posts we have
var newestPostWeHave = _posts var newestPostWeHave = posts
.where( .where(
(element) => category == null || element.category == category, (element) => category == null || element.category == category,
) )
.fold( .fold(
_posts.first, posts.first,
(previousValue, element) => (previousValue, element) =>
(previousValue.createdAt.isAfter(element.createdAt)) (previousValue.createdAt.isAfter(element.createdAt))
? previousValue ? previousValue
@ -220,26 +216,26 @@ class FirebaseTimelineService
.orderBy('created_at', descending: true) .orderBy('created_at', descending: true)
.endBefore([newestPostWeHave.createdAt]).get(); .endBefore([newestPostWeHave.createdAt]).get();
// add the new posts to the list // add the new posts to the list
var posts = <TimelinePost>[]; var newPosts = <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['creator_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); newPosts.add(post);
} }
_posts = [...posts, ..._posts]; posts = [...posts, ...newPosts];
notifyListeners(); notifyListeners();
return posts; return newPosts;
} }
@override @override
TimelinePost? getPost(String postId) => TimelinePost? getPost(String postId) =>
(_posts.any((element) => element.id == postId)) (posts.any((element) => element.id == postId))
? _posts.firstWhere((element) => element.id == postId) ? posts.firstWhere((element) => element.id == postId)
: null; : null;
@override @override
List<TimelinePost> getPosts(String? category) => _posts List<TimelinePost> getPosts(String? category) => posts
.where((element) => category == null || element.category == category) .where((element) => category == null || element.category == category)
.toList(); .toList();
@ -250,7 +246,7 @@ class FirebaseTimelineService
likes: post.likes + 1, likes: post.likes + 1,
likedBy: post.likedBy?..add(userId), likedBy: post.likedBy?..add(userId),
); );
_posts = _posts posts = posts
.map( .map(
(p) => p.id == post.id ? updatedPost : p, (p) => p.id == post.id ? updatedPost : p,
) )
@ -271,7 +267,7 @@ class FirebaseTimelineService
likes: post.likes - 1, likes: post.likes - 1,
likedBy: post.likedBy?..remove(userId), likedBy: post.likedBy?..remove(userId),
); );
_posts = _posts posts = posts
.map( .map(
(p) => p.id == post.id ? updatedPost : p, (p) => p.id == post.id ? updatedPost : p,
) )
@ -314,7 +310,7 @@ class FirebaseTimelineService
'reaction': FieldValue.increment(1), 'reaction': FieldValue.increment(1),
'reactions': FieldValue.arrayUnion([updatedReaction.toJson()]), 'reactions': FieldValue.arrayUnion([updatedReaction.toJson()]),
}); });
_posts = _posts posts = posts
.map( .map(
(p) => p.id == post.id ? updatedPost : p, (p) => p.id == post.id ? updatedPost : p,
) )

View file

@ -8,5 +8,6 @@ export 'src/model/timeline_category.dart';
export 'src/model/timeline_post.dart'; export 'src/model/timeline_post.dart';
export 'src/model/timeline_poster.dart'; export 'src/model/timeline_poster.dart';
export 'src/model/timeline_reaction.dart'; export 'src/model/timeline_reaction.dart';
export 'src/services/filter_service.dart';
export 'src/services/timeline_service.dart'; export 'src/services/timeline_service.dart';
export 'src/services/user_service.dart'; export 'src/services/user_service.dart';

View file

@ -0,0 +1,22 @@
// SPDX-FileCopyrightText: 2024 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
mixin TimelineFilterService on TimelineService {
List<TimelinePost> filterPosts(
String filterWord,
Map<String, dynamic> options,
) {
var filteredPosts = posts
.where(
(post) => post.title.toLowerCase().contains(
filterWord.toLowerCase(),
),
)
.toList();
return filteredPosts;
}
}

View file

@ -9,6 +9,8 @@ import 'package:flutter_timeline_interface/src/model/timeline_post.dart';
import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart'; import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart';
abstract class TimelineService with ChangeNotifier { abstract class TimelineService with ChangeNotifier {
List<TimelinePost> posts = [];
Future<void> deletePost(TimelinePost post); Future<void> deletePost(TimelinePost post);
Future<TimelinePost> deletePostReaction(TimelinePost post, String reactionId); Future<TimelinePost> deletePostReaction(TimelinePost post, String reactionId);
Future<TimelinePost> createPost(TimelinePost post); Future<TimelinePost> createPost(TimelinePost post);

View file

@ -8,9 +8,8 @@ import 'package:flutter_timeline_view/src/config/timeline_theme.dart';
import 'package:flutter_timeline_view/src/config/timeline_translations.dart'; import 'package:flutter_timeline_view/src/config/timeline_translations.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@immutable
class TimelineOptions { class TimelineOptions {
const TimelineOptions({ TimelineOptions({
this.theme = const TimelineTheme(), this.theme = const TimelineTheme(),
this.translations = const TimelineTranslations.empty(), this.translations = const TimelineTranslations.empty(),
this.imagePickerConfig = const ImagePickerConfig(), this.imagePickerConfig = const ImagePickerConfig(),
@ -18,7 +17,7 @@ class TimelineOptions {
this.timelinePostHeight, this.timelinePostHeight,
this.allowAllDeletion = false, this.allowAllDeletion = false,
this.sortCommentsAscending = true, this.sortCommentsAscending = true,
this.sortPostsAscending = false, this.sortPostsAscending,
this.doubleTapTolike = false, this.doubleTapTolike = false,
this.iconsWithValues = false, this.iconsWithValues = false,
this.likeAndDislikeIconsForDoubleTap = const ( this.likeAndDislikeIconsForDoubleTap = const (
@ -44,6 +43,10 @@ class TimelineOptions {
this.categories, this.categories,
this.categoryButtonBuilder, this.categoryButtonBuilder,
this.catergoryLabelBuilder, this.catergoryLabelBuilder,
this.categorySelectorHorizontalPadding,
this.filterEnabled = false,
this.initialFilterWord,
this.searchBarBuilder,
}); });
/// Theming options for the timeline /// Theming options for the timeline
@ -59,7 +62,7 @@ class TimelineOptions {
final bool sortCommentsAscending; final bool sortCommentsAscending;
/// Whether to sort posts ascending or descending /// Whether to sort posts ascending or descending
final bool sortPostsAscending; final bool? sortPostsAscending;
/// Allow all posts to be deleted instead of /// Allow all posts to be deleted instead of
/// only the posts of the current user /// only the posts of the current user
@ -132,6 +135,23 @@ class TimelineOptions {
/// Ability to set an proper label for the category selectors. /// Ability to set an proper label for the category selectors.
/// Default to category key. /// Default to category key.
final String Function(String? categoryKey)? catergoryLabelBuilder; final String Function(String? categoryKey)? catergoryLabelBuilder;
/// Overides the standard horizontal padding of the whole category selector.
final double? categorySelectorHorizontalPadding;
/// if true the filter textfield is enabled.
bool filterEnabled;
/// Set a value to search through posts. When set the searchbar is shown.
/// If null no searchbar is shown.
final String? initialFilterWord;
final Widget Function(
Future<List<TimelinePost>> Function(
String filterWord,
Map<String, dynamic> options,
) search,
)? searchBarBuilder;
} }
typedef ButtonBuilder = Widget Function( typedef ButtonBuilder = Widget Function(

View file

@ -28,6 +28,7 @@ class TimelineTranslations {
required this.postAt, required this.postAt,
required this.postLoadingError, required this.postLoadingError,
required this.timelineSelectionDescription, required this.timelineSelectionDescription,
required this.searchHint,
}); });
const TimelineTranslations.empty() const TimelineTranslations.empty()
@ -52,7 +53,8 @@ class TimelineTranslations {
writeComment = 'Write your comment here...', writeComment = 'Write your comment here...',
postAt = 'at', postAt = 'at',
postLoadingError = 'Something went wrong while loading the post', postLoadingError = 'Something went wrong while loading the post',
timelineSelectionDescription = 'Choose a category'; timelineSelectionDescription = 'Choose a category',
searchHint = 'Search...';
final String noPosts; final String noPosts;
final String noPostsWithFilter; final String noPostsWithFilter;
@ -79,6 +81,8 @@ class TimelineTranslations {
final String timelineSelectionDescription; final String timelineSelectionDescription;
final String searchHint;
TimelineTranslations copyWith({ TimelineTranslations copyWith({
String? noPosts, String? noPosts,
String? noPostsWithFilter, String? noPostsWithFilter,
@ -101,6 +105,7 @@ class TimelineTranslations {
String? firstComment, String? firstComment,
String? postLoadingError, String? postLoadingError,
String? timelineSelectionDescription, String? timelineSelectionDescription,
String? searchHint,
}) => }) =>
TimelineTranslations( TimelineTranslations(
noPosts: noPosts ?? this.noPosts, noPosts: noPosts ?? this.noPosts,
@ -127,5 +132,6 @@ class TimelineTranslations {
postLoadingError: postLoadingError ?? this.postLoadingError, postLoadingError: postLoadingError ?? this.postLoadingError,
timelineSelectionDescription: timelineSelectionDescription:
timelineSelectionDescription ?? this.timelineSelectionDescription, timelineSelectionDescription ?? this.timelineSelectionDescription,
searchHint: searchHint ?? this.searchHint,
); );
} }

View file

@ -18,7 +18,7 @@ class TimelineScreen extends StatefulWidget {
this.onPostTap, this.onPostTap,
this.onUserTap, this.onUserTap,
this.posts, this.posts,
this.timelineCategoryFilter, this.timelineCategory,
this.postWidget, this.postWidget,
super.key, super.key,
}); });
@ -36,7 +36,7 @@ class TimelineScreen extends StatefulWidget {
final ScrollController? scrollController; final ScrollController? scrollController;
/// The string to filter the timeline by category /// The string to filter the timeline by category
final String? timelineCategoryFilter; final String? timelineCategory;
/// This is used if you want to pass in a list of posts instead /// This is used if you want to pass in a list of posts instead
/// of fetching them from the service /// of fetching them from the service
@ -57,11 +57,15 @@ class TimelineScreen extends StatefulWidget {
class _TimelineScreenState extends State<TimelineScreen> { class _TimelineScreenState extends State<TimelineScreen> {
late ScrollController controller; late ScrollController controller;
late var textFieldController =
TextEditingController(text: widget.options.initialFilterWord);
late var service = widget.service; late var service = widget.service;
bool isLoading = true; bool isLoading = true;
late var filter = widget.timelineCategoryFilter; late var category = widget.timelineCategory;
late var filterWord = widget.options.initialFilterWord;
@override @override
void initState() { void initState() {
@ -80,32 +84,109 @@ class _TimelineScreenState extends State<TimelineScreen> {
return ListenableBuilder( return ListenableBuilder(
listenable: service, listenable: service,
builder: (context, _) { builder: (context, _) {
var posts = widget.posts ?? service.getPosts(filter); var posts = widget.posts ?? service.getPosts(category);
posts = posts posts = posts
.where( .where(
(p) => filter == null || p.category == filter, (p) => category == null || p.category == category,
) )
.toList(); .toList();
if (widget.options.filterEnabled && filterWord != null) {
if (service is TimelineFilterService?) {
posts =
(service as TimelineFilterService).filterPosts(filterWord!, {});
} else {
debugPrint('Timeline service needs to mixin'
' with TimelineFilterService');
}
}
// sort posts by date // sort posts by date
posts.sort( if (widget.options.sortPostsAscending != null) {
(a, b) => widget.options.sortPostsAscending posts.sort(
? a.createdAt.compareTo(b.createdAt) (a, b) => widget.options.sortPostsAscending!
: b.createdAt.compareTo(a.createdAt), ? a.createdAt.compareTo(b.createdAt)
); : b.createdAt.compareTo(a.createdAt),
);
}
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
SizedBox(
height: widget.options.padding.top,
),
if (widget.options.filterEnabled) ...[
Padding(
padding: EdgeInsets.symmetric(
horizontal: widget.options.padding.horizontal,
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Expanded(
child: TextField(
controller: textFieldController,
onChanged: (value) {
setState(() {
filterWord = value;
});
},
decoration: InputDecoration(
hintText: widget.options.translations.searchHint,
suffixIconConstraints:
const BoxConstraints(maxHeight: 14),
contentPadding: const EdgeInsets.only(
left: 12,
right: 12,
bottom: -10,
),
suffixIcon: const Padding(
padding: EdgeInsets.only(right: 12),
child: Icon(Icons.search),
),
),
),
),
const SizedBox(
width: 8,
),
InkWell(
onTap: () {
setState(() {
textFieldController.clear();
widget.options.filterEnabled = false;
filterWord = null;
});
},
child: const Padding(
padding: EdgeInsets.all(8),
child: Icon(
Icons.close,
color: Color(0xFF000000),
),
),
),
],
),
),
const SizedBox(
height: 24,
),
],
CategorySelector( CategorySelector(
filter: filter, filter: category,
options: widget.options, options: widget.options,
onTapCategory: (categoryKey) { onTapCategory: (categoryKey) {
setState(() { setState(() {
filter = categoryKey; category = categoryKey;
}); });
}, },
), ),
const SizedBox(
height: 12,
),
Expanded( Expanded(
child: SingleChildScrollView( child: SingleChildScrollView(
controller: controller, controller: controller,
@ -159,7 +240,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Text( child: Text(
filter == null category == null
? widget.options.translations.noPosts ? widget.options.translations.noPosts
: widget.options.translations.noPostsWithFilter, : widget.options.translations.noPostsWithFilter,
style: widget.options.theme.textStyles.noPostsStyle, style: widget.options.theme.textStyles.noPostsStyle,
@ -170,6 +251,9 @@ class _TimelineScreenState extends State<TimelineScreen> {
), ),
), ),
), ),
SizedBox(
height: widget.options.padding.bottom,
),
], ],
); );
}, },
@ -179,7 +263,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
Future<void> loadPosts() async { Future<void> loadPosts() async {
if (widget.posts != null) return; if (widget.posts != null) return;
try { try {
await service.fetchPosts(filter); await service.fetchPosts(category);
setState(() { setState(() {
isLoading = false; isLoading = false;
}); });

View file

@ -1,3 +1,5 @@
import 'dart:math';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_timeline_view/flutter_timeline_view.dart'; import 'package:flutter_timeline_view/flutter_timeline_view.dart';
import 'package:flutter_timeline_view/src/widgets/category_selector_button.dart'; import 'package:flutter_timeline_view/src/widgets/category_selector_button.dart';
@ -22,49 +24,51 @@ class CategorySelector extends StatelessWidget {
return SingleChildScrollView( return SingleChildScrollView(
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
child: Padding( child: Row(
padding: EdgeInsets.symmetric( children: [
horizontal: options.padding.horizontal, SizedBox(
), width: options.categorySelectorHorizontalPadding ??
child: Row( max(options.padding.horizontal - 4, 0),
children: [ ),
options.categoryButtonBuilder?.call( options.categoryButtonBuilder?.call(
categoryKey: null, categoryKey: null,
categoryName: categoryName:
options.catergoryLabelBuilder?.call(null) ?? 'All', options.catergoryLabelBuilder?.call(null) ?? 'All',
onTap: () => onTapCategory(null), onTap: () => onTapCategory(null),
selected: filter == null,
) ??
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: CategorySelectorButton(
category: null,
selected: filter == null, selected: filter == null,
onTap: () => onTapCategory(null),
labelBuilder: options.catergoryLabelBuilder,
),
),
for (var category in options.categories!) ...[
options.categoryButtonBuilder?.call(
categoryKey: category,
categoryName:
options.catergoryLabelBuilder?.call(category) ?? category,
onTap: () => onTapCategory(category),
selected: filter == category,
) ?? ) ??
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(horizontal: 4),
child: CategorySelectorButton( child: CategorySelectorButton(
category: null, category: category,
selected: filter == null, selected: filter == category,
onTap: () => onTapCategory(null), onTap: () => onTapCategory(category),
labelBuilder: options.catergoryLabelBuilder, labelBuilder: options.catergoryLabelBuilder,
), ),
), ),
for (var category in options.categories!) ...[
options.categoryButtonBuilder?.call(
categoryKey: category,
categoryName:
options.catergoryLabelBuilder?.call(category) ??
category,
onTap: () => onTapCategory(category),
selected: filter == category,
) ??
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: CategorySelectorButton(
category: category,
selected: filter == category,
onTap: () => onTapCategory(category),
labelBuilder: options.catergoryLabelBuilder,
),
),
],
], ],
), SizedBox(
width: options.categorySelectorHorizontalPadding ??
max(options.padding.horizontal - 4, 0),
),
],
), ),
); );
} }

View file

@ -21,6 +21,7 @@ class CategorySelectorButton extends StatelessWidget {
return TextButton( return TextButton(
onPressed: onTap, onPressed: onTap,
style: ButtonStyle( style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: const MaterialStatePropertyAll( padding: const MaterialStatePropertyAll(
EdgeInsets.symmetric( EdgeInsets.symmetric(
vertical: 5, vertical: 5,

View file

@ -49,7 +49,9 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
return InkWell( return InkWell(
onTap: widget.onTap, onTap: widget.onTap,
child: SizedBox( child: SizedBox(
height: widget.post.imageUrl != null ? widget.options.postWidgetheight : null, height: widget.post.imageUrl != null
? widget.options.postWidgetheight
: null,
width: double.infinity, width: double.infinity,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,