mirror of
https://github.com/Iconica-Development/flutter_timeline.git
synced 2025-05-19 18:43:45 +02:00
feat: Added search bar with filter
This commit is contained in:
parent
14dced5ef4
commit
e5e2eb5c22
13 changed files with 237 additions and 101 deletions
|
@ -22,7 +22,7 @@ class _PostScreenState extends State<PostScreen> {
|
|||
body: TimelinePostScreen(
|
||||
userId: 'test_user',
|
||||
service: widget.service,
|
||||
options: const TimelineOptions(),
|
||||
options: TimelineOptions(),
|
||||
post: widget.post,
|
||||
onPostDelete: () {
|
||||
print('delete post');
|
||||
|
|
|
@ -11,11 +11,12 @@ import 'package:flutter_timeline/flutter_timeline.dart';
|
|||
import 'package:uuid/uuid.dart';
|
||||
|
||||
class TestTimelineService with ChangeNotifier implements TimelineService {
|
||||
List<TimelinePost> _posts = [];
|
||||
@override
|
||||
List<TimelinePost> posts = [];
|
||||
|
||||
@override
|
||||
Future<TimelinePost> createPost(TimelinePost post) async {
|
||||
_posts.add(
|
||||
posts.add(
|
||||
post.copyWith(
|
||||
creator: const TimelinePosterUserModel(userId: 'test_user'),
|
||||
),
|
||||
|
@ -26,7 +27,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
|
|||
|
||||
@override
|
||||
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();
|
||||
}
|
||||
|
@ -43,7 +44,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
|
|||
reaction: post.reaction - 1,
|
||||
reactions: (post.reactions ?? [])..remove(reaction),
|
||||
);
|
||||
_posts = _posts
|
||||
posts = posts
|
||||
.map(
|
||||
(p) => p.id == post.id ? updatedPost : p,
|
||||
)
|
||||
|
@ -64,7 +65,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
|
|||
creator: const TimelinePosterUserModel(userId: 'test_user')));
|
||||
}
|
||||
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();
|
||||
return updatedPost;
|
||||
}
|
||||
|
@ -72,7 +73,6 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
|
|||
@override
|
||||
Future<List<TimelinePost>> fetchPosts(String? category) async {
|
||||
var posts = getMockedPosts();
|
||||
_posts = posts;
|
||||
notifyListeners();
|
||||
return posts;
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
|
|||
int limit,
|
||||
) async {
|
||||
notifyListeners();
|
||||
return _posts;
|
||||
return posts;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -94,32 +94,31 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
|
|||
|
||||
@override
|
||||
Future<List<TimelinePost>> refreshPosts(String? category) async {
|
||||
var posts = <TimelinePost>[];
|
||||
var newPosts = <TimelinePost>[];
|
||||
|
||||
_posts = [...posts, ..._posts];
|
||||
posts = [...posts, ...newPosts];
|
||||
notifyListeners();
|
||||
return posts;
|
||||
}
|
||||
|
||||
@override
|
||||
TimelinePost? getPost(String postId) =>
|
||||
(_posts.any((element) => element.id == postId))
|
||||
? _posts.firstWhere((element) => element.id == postId)
|
||||
(posts.any((element) => element.id == postId))
|
||||
? posts.firstWhere((element) => element.id == postId)
|
||||
: null;
|
||||
|
||||
@override
|
||||
List<TimelinePost> getPosts(String? category) => _posts
|
||||
List<TimelinePost> getPosts(String? category) => posts
|
||||
.where((element) => category == null || element.category == category)
|
||||
.toList();
|
||||
|
||||
@override
|
||||
Future<TimelinePost> likePost(String userId, TimelinePost post) async {
|
||||
print(userId);
|
||||
var updatedPost = post.copyWith(
|
||||
likes: post.likes + 1,
|
||||
likedBy: (post.likedBy ?? [])..add(userId),
|
||||
);
|
||||
_posts = _posts
|
||||
posts = posts
|
||||
.map(
|
||||
(p) => p.id == post.id ? updatedPost : p,
|
||||
)
|
||||
|
@ -135,7 +134,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
|
|||
likes: post.likes - 1,
|
||||
likedBy: post.likedBy?..remove(userId),
|
||||
);
|
||||
_posts = _posts
|
||||
posts = posts
|
||||
.map(
|
||||
(p) => p.id == post.id ? updatedPost : p,
|
||||
)
|
||||
|
@ -161,7 +160,7 @@ class TestTimelineService with ChangeNotifier implements TimelineService {
|
|||
reactions: post.reactions?..add(updatedReaction),
|
||||
);
|
||||
|
||||
_posts = _posts
|
||||
posts = posts
|
||||
.map(
|
||||
(p) => p.id == post.id ? updatedPost : p,
|
||||
)
|
||||
|
|
|
@ -25,7 +25,6 @@ List<GoRoute> getTimelineStoryRoutes(
|
|||
options: configuration.optionsBuilder(context),
|
||||
onPostTap: (post) async =>
|
||||
TimelineUserStoryRoutes.timelineViewPath(post.id),
|
||||
timelineCategoryFilter: null,
|
||||
);
|
||||
return buildScreenWithoutTransition(
|
||||
context: context,
|
||||
|
|
|
@ -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:uuid/uuid.dart';
|
||||
|
||||
class FirebaseTimelineService
|
||||
with ChangeNotifier
|
||||
implements TimelineService, TimelineUserService {
|
||||
class FirebaseTimelineService extends TimelineService with TimelineUserService {
|
||||
FirebaseTimelineService({
|
||||
required TimelineUserService userService,
|
||||
FirebaseApp? app,
|
||||
|
@ -35,8 +33,6 @@ class FirebaseTimelineService
|
|||
|
||||
final Map<String, TimelinePosterUserModel> _users = {};
|
||||
|
||||
List<TimelinePost> _posts = [];
|
||||
|
||||
@override
|
||||
Future<TimelinePost> createPost(TimelinePost post) async {
|
||||
var postId = const Uuid().v4();
|
||||
|
@ -52,14 +48,14 @@ class FirebaseTimelineService
|
|||
var postRef =
|
||||
_db.collection(_options.timelineCollectionName).doc(updatedPost.id);
|
||||
await postRef.set(updatedPost.toJson());
|
||||
_posts.add(updatedPost);
|
||||
posts.add(updatedPost);
|
||||
notifyListeners();
|
||||
return updatedPost;
|
||||
}
|
||||
|
||||
@override
|
||||
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);
|
||||
await postRef.delete();
|
||||
notifyListeners();
|
||||
|
@ -77,7 +73,7 @@ class FirebaseTimelineService
|
|||
reaction: post.reaction - 1,
|
||||
reactions: (post.reactions ?? [])..remove(reaction),
|
||||
);
|
||||
_posts = _posts
|
||||
posts = posts
|
||||
.map(
|
||||
(p) => p.id == post.id ? updatedPost : p,
|
||||
)
|
||||
|
@ -107,7 +103,7 @@ class FirebaseTimelineService
|
|||
}
|
||||
}
|
||||
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();
|
||||
return updatedPost;
|
||||
}
|
||||
|
@ -129,7 +125,7 @@ class FirebaseTimelineService
|
|||
var post = TimelinePost.fromJson(doc.id, data).copyWith(creator: user);
|
||||
posts.add(post);
|
||||
}
|
||||
_posts = posts;
|
||||
|
||||
notifyListeners();
|
||||
return posts;
|
||||
}
|
||||
|
@ -140,12 +136,12 @@ class FirebaseTimelineService
|
|||
int limit,
|
||||
) async {
|
||||
// only take posts that are in our category
|
||||
var oldestPost = _posts
|
||||
var oldestPost = posts
|
||||
.where(
|
||||
(element) => category == null || element.category == category,
|
||||
)
|
||||
.fold(
|
||||
_posts.first,
|
||||
posts.first,
|
||||
(previousValue, element) =>
|
||||
(previousValue.createdAt.isBefore(element.createdAt))
|
||||
? previousValue
|
||||
|
@ -166,16 +162,16 @@ class FirebaseTimelineService
|
|||
.limit(limit)
|
||||
.get();
|
||||
// add the new posts to the list
|
||||
var posts = <TimelinePost>[];
|
||||
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);
|
||||
posts.add(post);
|
||||
newPosts.add(post);
|
||||
}
|
||||
_posts = [..._posts, ...posts];
|
||||
posts = [...posts, ...newPosts];
|
||||
notifyListeners();
|
||||
return posts;
|
||||
return newPosts;
|
||||
}
|
||||
|
||||
@override
|
||||
|
@ -190,7 +186,7 @@ class FirebaseTimelineService
|
|||
var updatedPost = TimelinePost.fromJson(doc.id, data).copyWith(
|
||||
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();
|
||||
return updatedPost;
|
||||
}
|
||||
|
@ -198,12 +194,12 @@ class FirebaseTimelineService
|
|||
@override
|
||||
Future<List<TimelinePost>> refreshPosts(String? category) async {
|
||||
// fetch all posts between now and the newest posts we have
|
||||
var newestPostWeHave = _posts
|
||||
var newestPostWeHave = posts
|
||||
.where(
|
||||
(element) => category == null || element.category == category,
|
||||
)
|
||||
.fold(
|
||||
_posts.first,
|
||||
posts.first,
|
||||
(previousValue, element) =>
|
||||
(previousValue.createdAt.isAfter(element.createdAt))
|
||||
? previousValue
|
||||
|
@ -220,26 +216,26 @@ class FirebaseTimelineService
|
|||
.orderBy('created_at', descending: true)
|
||||
.endBefore([newestPostWeHave.createdAt]).get();
|
||||
// add the new posts to the list
|
||||
var posts = <TimelinePost>[];
|
||||
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);
|
||||
posts.add(post);
|
||||
newPosts.add(post);
|
||||
}
|
||||
_posts = [...posts, ..._posts];
|
||||
posts = [...posts, ...newPosts];
|
||||
notifyListeners();
|
||||
return posts;
|
||||
return newPosts;
|
||||
}
|
||||
|
||||
@override
|
||||
TimelinePost? getPost(String postId) =>
|
||||
(_posts.any((element) => element.id == postId))
|
||||
? _posts.firstWhere((element) => element.id == postId)
|
||||
(posts.any((element) => element.id == postId))
|
||||
? posts.firstWhere((element) => element.id == postId)
|
||||
: null;
|
||||
|
||||
@override
|
||||
List<TimelinePost> getPosts(String? category) => _posts
|
||||
List<TimelinePost> getPosts(String? category) => posts
|
||||
.where((element) => category == null || element.category == category)
|
||||
.toList();
|
||||
|
||||
|
@ -250,7 +246,7 @@ class FirebaseTimelineService
|
|||
likes: post.likes + 1,
|
||||
likedBy: post.likedBy?..add(userId),
|
||||
);
|
||||
_posts = _posts
|
||||
posts = posts
|
||||
.map(
|
||||
(p) => p.id == post.id ? updatedPost : p,
|
||||
)
|
||||
|
@ -271,7 +267,7 @@ class FirebaseTimelineService
|
|||
likes: post.likes - 1,
|
||||
likedBy: post.likedBy?..remove(userId),
|
||||
);
|
||||
_posts = _posts
|
||||
posts = posts
|
||||
.map(
|
||||
(p) => p.id == post.id ? updatedPost : p,
|
||||
)
|
||||
|
@ -314,7 +310,7 @@ class FirebaseTimelineService
|
|||
'reaction': FieldValue.increment(1),
|
||||
'reactions': FieldValue.arrayUnion([updatedReaction.toJson()]),
|
||||
});
|
||||
_posts = _posts
|
||||
posts = posts
|
||||
.map(
|
||||
(p) => p.id == post.id ? updatedPost : p,
|
||||
)
|
||||
|
|
|
@ -8,5 +8,6 @@ export 'src/model/timeline_category.dart';
|
|||
export 'src/model/timeline_post.dart';
|
||||
export 'src/model/timeline_poster.dart';
|
||||
export 'src/model/timeline_reaction.dart';
|
||||
export 'src/services/filter_service.dart';
|
||||
export 'src/services/timeline_service.dart';
|
||||
export 'src/services/user_service.dart';
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -9,6 +9,8 @@ import 'package:flutter_timeline_interface/src/model/timeline_post.dart';
|
|||
import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart';
|
||||
|
||||
abstract class TimelineService with ChangeNotifier {
|
||||
List<TimelinePost> posts = [];
|
||||
|
||||
Future<void> deletePost(TimelinePost post);
|
||||
Future<TimelinePost> deletePostReaction(TimelinePost post, String reactionId);
|
||||
Future<TimelinePost> createPost(TimelinePost post);
|
||||
|
|
|
@ -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:intl/intl.dart';
|
||||
|
||||
@immutable
|
||||
class TimelineOptions {
|
||||
const TimelineOptions({
|
||||
TimelineOptions({
|
||||
this.theme = const TimelineTheme(),
|
||||
this.translations = const TimelineTranslations.empty(),
|
||||
this.imagePickerConfig = const ImagePickerConfig(),
|
||||
|
@ -18,7 +17,7 @@ class TimelineOptions {
|
|||
this.timelinePostHeight,
|
||||
this.allowAllDeletion = false,
|
||||
this.sortCommentsAscending = true,
|
||||
this.sortPostsAscending = false,
|
||||
this.sortPostsAscending,
|
||||
this.doubleTapTolike = false,
|
||||
this.iconsWithValues = false,
|
||||
this.likeAndDislikeIconsForDoubleTap = const (
|
||||
|
@ -44,6 +43,10 @@ class TimelineOptions {
|
|||
this.categories,
|
||||
this.categoryButtonBuilder,
|
||||
this.catergoryLabelBuilder,
|
||||
this.categorySelectorHorizontalPadding,
|
||||
this.filterEnabled = false,
|
||||
this.initialFilterWord,
|
||||
this.searchBarBuilder,
|
||||
});
|
||||
|
||||
/// Theming options for the timeline
|
||||
|
@ -59,7 +62,7 @@ class TimelineOptions {
|
|||
final bool sortCommentsAscending;
|
||||
|
||||
/// Whether to sort posts ascending or descending
|
||||
final bool sortPostsAscending;
|
||||
final bool? sortPostsAscending;
|
||||
|
||||
/// Allow all posts to be deleted instead of
|
||||
/// only the posts of the current user
|
||||
|
@ -132,6 +135,23 @@ class TimelineOptions {
|
|||
/// Ability to set an proper label for the category selectors.
|
||||
/// Default to category key.
|
||||
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(
|
||||
|
|
|
@ -28,6 +28,7 @@ class TimelineTranslations {
|
|||
required this.postAt,
|
||||
required this.postLoadingError,
|
||||
required this.timelineSelectionDescription,
|
||||
required this.searchHint,
|
||||
});
|
||||
|
||||
const TimelineTranslations.empty()
|
||||
|
@ -52,7 +53,8 @@ class TimelineTranslations {
|
|||
writeComment = 'Write your comment here...',
|
||||
postAt = 'at',
|
||||
postLoadingError = 'Something went wrong while loading the post',
|
||||
timelineSelectionDescription = 'Choose a category';
|
||||
timelineSelectionDescription = 'Choose a category',
|
||||
searchHint = 'Search...';
|
||||
|
||||
final String noPosts;
|
||||
final String noPostsWithFilter;
|
||||
|
@ -79,6 +81,8 @@ class TimelineTranslations {
|
|||
|
||||
final String timelineSelectionDescription;
|
||||
|
||||
final String searchHint;
|
||||
|
||||
TimelineTranslations copyWith({
|
||||
String? noPosts,
|
||||
String? noPostsWithFilter,
|
||||
|
@ -101,6 +105,7 @@ class TimelineTranslations {
|
|||
String? firstComment,
|
||||
String? postLoadingError,
|
||||
String? timelineSelectionDescription,
|
||||
String? searchHint,
|
||||
}) =>
|
||||
TimelineTranslations(
|
||||
noPosts: noPosts ?? this.noPosts,
|
||||
|
@ -127,5 +132,6 @@ class TimelineTranslations {
|
|||
postLoadingError: postLoadingError ?? this.postLoadingError,
|
||||
timelineSelectionDescription:
|
||||
timelineSelectionDescription ?? this.timelineSelectionDescription,
|
||||
searchHint: searchHint ?? this.searchHint,
|
||||
);
|
||||
}
|
||||
|
|
|
@ -18,7 +18,7 @@ class TimelineScreen extends StatefulWidget {
|
|||
this.onPostTap,
|
||||
this.onUserTap,
|
||||
this.posts,
|
||||
this.timelineCategoryFilter,
|
||||
this.timelineCategory,
|
||||
this.postWidget,
|
||||
super.key,
|
||||
});
|
||||
|
@ -36,7 +36,7 @@ class TimelineScreen extends StatefulWidget {
|
|||
final ScrollController? scrollController;
|
||||
|
||||
/// 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
|
||||
/// of fetching them from the service
|
||||
|
@ -57,11 +57,15 @@ class TimelineScreen extends StatefulWidget {
|
|||
|
||||
class _TimelineScreenState extends State<TimelineScreen> {
|
||||
late ScrollController controller;
|
||||
late var textFieldController =
|
||||
TextEditingController(text: widget.options.initialFilterWord);
|
||||
late var service = widget.service;
|
||||
|
||||
bool isLoading = true;
|
||||
|
||||
late var filter = widget.timelineCategoryFilter;
|
||||
late var category = widget.timelineCategory;
|
||||
|
||||
late var filterWord = widget.options.initialFilterWord;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
|
@ -80,32 +84,109 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
|||
return ListenableBuilder(
|
||||
listenable: service,
|
||||
builder: (context, _) {
|
||||
var posts = widget.posts ?? service.getPosts(filter);
|
||||
var posts = widget.posts ?? service.getPosts(category);
|
||||
|
||||
posts = posts
|
||||
.where(
|
||||
(p) => filter == null || p.category == filter,
|
||||
(p) => category == null || p.category == category,
|
||||
)
|
||||
.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
|
||||
posts.sort(
|
||||
(a, b) => widget.options.sortPostsAscending
|
||||
? a.createdAt.compareTo(b.createdAt)
|
||||
: b.createdAt.compareTo(a.createdAt),
|
||||
);
|
||||
if (widget.options.sortPostsAscending != null) {
|
||||
posts.sort(
|
||||
(a, b) => widget.options.sortPostsAscending!
|
||||
? a.createdAt.compareTo(b.createdAt)
|
||||
: b.createdAt.compareTo(a.createdAt),
|
||||
);
|
||||
}
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
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(
|
||||
filter: filter,
|
||||
filter: category,
|
||||
options: widget.options,
|
||||
onTapCategory: (categoryKey) {
|
||||
setState(() {
|
||||
filter = categoryKey;
|
||||
category = categoryKey;
|
||||
});
|
||||
},
|
||||
),
|
||||
const SizedBox(
|
||||
height: 12,
|
||||
),
|
||||
Expanded(
|
||||
child: SingleChildScrollView(
|
||||
controller: controller,
|
||||
|
@ -159,7 +240,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
|||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(
|
||||
filter == null
|
||||
category == null
|
||||
? widget.options.translations.noPosts
|
||||
: widget.options.translations.noPostsWithFilter,
|
||||
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 {
|
||||
if (widget.posts != null) return;
|
||||
try {
|
||||
await service.fetchPosts(filter);
|
||||
await service.fetchPosts(category);
|
||||
setState(() {
|
||||
isLoading = false;
|
||||
});
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_timeline_view/flutter_timeline_view.dart';
|
||||
import 'package:flutter_timeline_view/src/widgets/category_selector_button.dart';
|
||||
|
@ -22,49 +24,51 @@ class CategorySelector extends StatelessWidget {
|
|||
|
||||
return SingleChildScrollView(
|
||||
scrollDirection: Axis.horizontal,
|
||||
child: Padding(
|
||||
padding: EdgeInsets.symmetric(
|
||||
horizontal: options.padding.horizontal,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
options.categoryButtonBuilder?.call(
|
||||
categoryKey: null,
|
||||
categoryName:
|
||||
options.catergoryLabelBuilder?.call(null) ?? 'All',
|
||||
onTap: () => onTapCategory(null),
|
||||
child: Row(
|
||||
children: [
|
||||
SizedBox(
|
||||
width: options.categorySelectorHorizontalPadding ??
|
||||
max(options.padding.horizontal - 4, 0),
|
||||
),
|
||||
options.categoryButtonBuilder?.call(
|
||||
categoryKey: null,
|
||||
categoryName:
|
||||
options.catergoryLabelBuilder?.call(null) ?? 'All',
|
||||
onTap: () => onTapCategory(null),
|
||||
selected: filter == null,
|
||||
) ??
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: CategorySelectorButton(
|
||||
category: 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: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: CategorySelectorButton(
|
||||
category: null,
|
||||
selected: filter == null,
|
||||
onTap: () => onTapCategory(null),
|
||||
category: category,
|
||||
selected: filter == category,
|
||||
onTap: () => onTapCategory(category),
|
||||
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),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ class CategorySelectorButton extends StatelessWidget {
|
|||
return TextButton(
|
||||
onPressed: onTap,
|
||||
style: ButtonStyle(
|
||||
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
padding: const MaterialStatePropertyAll(
|
||||
EdgeInsets.symmetric(
|
||||
vertical: 5,
|
||||
|
|
|
@ -49,7 +49,9 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
|
|||
return InkWell(
|
||||
onTap: widget.onTap,
|
||||
child: SizedBox(
|
||||
height: widget.post.imageUrl != null ? widget.options.postWidgetheight : null,
|
||||
height: widget.post.imageUrl != null
|
||||
? widget.options.postWidgetheight
|
||||
: null,
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
|
|
Loading…
Reference in a new issue