mirror of
https://github.com/Iconica-Development/flutter_timeline.git
synced 2025-05-19 18:43:45 +02:00
feat: small improvements to the screens
This commit is contained in:
parent
8792079fa4
commit
ca4ec03002
7 changed files with 183 additions and 89 deletions
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Iconica
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
|
|
|
@ -5,7 +5,9 @@
|
||||||
library flutter_timeline_view;
|
library flutter_timeline_view;
|
||||||
|
|
||||||
export 'src/config/timeline_options.dart';
|
export 'src/config/timeline_options.dart';
|
||||||
|
export 'src/config/timeline_theme.dart';
|
||||||
export 'src/config/timeline_translations.dart';
|
export 'src/config/timeline_translations.dart';
|
||||||
export 'src/screens/timeline_post_creation_screen.dart';
|
export 'src/screens/timeline_post_creation_screen.dart';
|
||||||
export 'src/screens/timeline_post_screen.dart';
|
export 'src/screens/timeline_post_screen.dart';
|
||||||
export 'src/screens/timeline_screen.dart';
|
export 'src/screens/timeline_screen.dart';
|
||||||
|
export 'src/widgets/timeline_post_widget.dart';
|
||||||
|
|
|
@ -16,6 +16,7 @@ class TimelineOptions {
|
||||||
this.imagePickerConfig = const ImagePickerConfig(),
|
this.imagePickerConfig = const ImagePickerConfig(),
|
||||||
this.imagePickerTheme = const ImagePickerTheme(),
|
this.imagePickerTheme = const ImagePickerTheme(),
|
||||||
this.sortCommentsAscending = false,
|
this.sortCommentsAscending = false,
|
||||||
|
this.sortPostsAscending = false,
|
||||||
this.dateformat,
|
this.dateformat,
|
||||||
this.timeFormat,
|
this.timeFormat,
|
||||||
this.buttonBuilder,
|
this.buttonBuilder,
|
||||||
|
@ -35,6 +36,9 @@ class TimelineOptions {
|
||||||
/// Whether to sort comments ascending or descending
|
/// Whether to sort comments ascending or descending
|
||||||
final bool sortCommentsAscending;
|
final bool sortCommentsAscending;
|
||||||
|
|
||||||
|
/// Whether to sort posts ascending or descending
|
||||||
|
final bool sortPostsAscending;
|
||||||
|
|
||||||
final TimelineTranslations translations;
|
final TimelineTranslations translations;
|
||||||
|
|
||||||
final ButtonBuilder? buttonBuilder;
|
final ButtonBuilder? buttonBuilder;
|
||||||
|
|
|
@ -12,6 +12,8 @@ class TimelineTheme {
|
||||||
this.commentIcon,
|
this.commentIcon,
|
||||||
this.likedIcon,
|
this.likedIcon,
|
||||||
this.sendIcon,
|
this.sendIcon,
|
||||||
|
this.moreIcon,
|
||||||
|
this.deleteIcon,
|
||||||
});
|
});
|
||||||
|
|
||||||
final Color? iconColor;
|
final Color? iconColor;
|
||||||
|
@ -27,4 +29,10 @@ class TimelineTheme {
|
||||||
|
|
||||||
/// The icon to display to submit a comment
|
/// The icon to display to submit a comment
|
||||||
final Widget? sendIcon;
|
final Widget? sendIcon;
|
||||||
|
|
||||||
|
/// The icon for more actions (open delete menu)
|
||||||
|
final Widget? moreIcon;
|
||||||
|
|
||||||
|
/// The icon for delete action (delete post)
|
||||||
|
final Widget? deleteIcon;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@ class TimelinePostScreen extends StatefulWidget {
|
||||||
required this.userService,
|
required this.userService,
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.post,
|
required this.post,
|
||||||
|
this.onUserTap,
|
||||||
this.padding = const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
|
this.padding = const EdgeInsets.symmetric(vertical: 24, horizontal: 16),
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
@ -42,6 +43,9 @@ class TimelinePostScreen extends StatefulWidget {
|
||||||
/// The padding around the screen
|
/// The padding around the screen
|
||||||
final EdgeInsets padding;
|
final EdgeInsets padding;
|
||||||
|
|
||||||
|
/// If this is not null, the user can tap on the user avatar or name
|
||||||
|
final Function(String userId)? onUserTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TimelinePostScreen> createState() => _TimelinePostScreenState();
|
State<TimelinePostScreen> createState() => _TimelinePostScreenState();
|
||||||
}
|
}
|
||||||
|
@ -110,8 +114,14 @@ class _TimelinePostScreenState extends State<TimelinePostScreen> {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (post.creator != null)
|
|
||||||
Row(
|
Row(
|
||||||
|
children: [
|
||||||
|
if (post.creator != null)
|
||||||
|
InkWell(
|
||||||
|
onTap: widget.onUserTap != null
|
||||||
|
? () => widget.onUserTap?.call(post.creator!.userId)
|
||||||
|
: null,
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (post.creator!.imageUrl != null) ...[
|
if (post.creator!.imageUrl != null) ...[
|
||||||
widget.options.userAvatarBuilder?.call(
|
widget.options.userAvatarBuilder?.call(
|
||||||
|
@ -132,10 +142,14 @@ class _TimelinePostScreenState extends State<TimelinePostScreen> {
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
],
|
||||||
// three small dots at the end
|
),
|
||||||
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
const Icon(Icons.more_horiz),
|
widget.options.theme.moreIcon ??
|
||||||
|
const Icon(
|
||||||
|
Icons.more_horiz_rounded,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
|
@ -13,32 +13,47 @@ class TimelineScreen extends StatefulWidget {
|
||||||
const TimelineScreen({
|
const TimelineScreen({
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.posts,
|
|
||||||
required this.onPostTap,
|
required this.onPostTap,
|
||||||
required this.service,
|
required this.service,
|
||||||
|
this.onUserTap,
|
||||||
|
this.posts,
|
||||||
this.controller,
|
this.controller,
|
||||||
this.timelineCategoryFilter,
|
this.timelineCategoryFilter,
|
||||||
this.timelinePostHeight = 100.0,
|
this.timelinePostHeight = 100.0,
|
||||||
|
this.padding = const EdgeInsets.symmetric(vertical: 12.0),
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The user id of the current user
|
/// The user id of the current user
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// The service to use for fetching and manipulating posts
|
||||||
final TimelineService service;
|
final TimelineService service;
|
||||||
|
|
||||||
|
/// All the configuration options for the timelinescreens and widgets
|
||||||
final TimelineOptions options;
|
final TimelineOptions options;
|
||||||
|
|
||||||
|
/// The controller for the scroll view
|
||||||
final ScrollController? controller;
|
final ScrollController? controller;
|
||||||
|
|
||||||
final String? timelineCategoryFilter;
|
final String? timelineCategoryFilter;
|
||||||
|
|
||||||
|
/// The height of a post in the timeline
|
||||||
final double timelinePostHeight;
|
final double timelinePostHeight;
|
||||||
|
|
||||||
final List<TimelinePost> posts;
|
/// This is used if you want to pass in a list of posts instead
|
||||||
|
/// of fetching them from the service
|
||||||
|
final List<TimelinePost>? posts;
|
||||||
|
|
||||||
|
/// Called when a post is tapped
|
||||||
final Function(TimelinePost) onPostTap;
|
final Function(TimelinePost) onPostTap;
|
||||||
|
|
||||||
|
/// If this is not null, the user can tap on the user avatar or name
|
||||||
|
final Function(String userId)? onUserTap;
|
||||||
|
|
||||||
|
/// The padding between posts in the timeline
|
||||||
|
final EdgeInsets padding;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<TimelineScreen> createState() => _TimelineScreenState();
|
State<TimelineScreen> createState() => _TimelineScreenState();
|
||||||
}
|
}
|
||||||
|
@ -55,7 +70,71 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
||||||
unawaited(loadPosts());
|
unawaited(loadPosts());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
if (isLoading && widget.posts == null) {
|
||||||
|
// Show loading indicator while data is being fetched
|
||||||
|
return const Center(child: CircularProgressIndicator());
|
||||||
|
}
|
||||||
|
|
||||||
|
var posts = widget.posts ?? this.posts;
|
||||||
|
posts = posts
|
||||||
|
.where(
|
||||||
|
(p) =>
|
||||||
|
widget.timelineCategoryFilter == null ||
|
||||||
|
p.category == widget.timelineCategoryFilter,
|
||||||
|
)
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
// sort posts by date
|
||||||
|
posts.sort(
|
||||||
|
(a, b) => widget.options.sortPostsAscending
|
||||||
|
? b.createdAt.compareTo(a.createdAt)
|
||||||
|
: a.createdAt.compareTo(b.createdAt),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Build the list of posts
|
||||||
|
return SingleChildScrollView(
|
||||||
|
controller: controller,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
...posts.map(
|
||||||
|
(post) => Padding(
|
||||||
|
padding: widget.padding,
|
||||||
|
child: TimelinePostWidget(
|
||||||
|
userId: widget.userId,
|
||||||
|
options: widget.options,
|
||||||
|
post: post,
|
||||||
|
height: widget.timelinePostHeight,
|
||||||
|
onTap: () => widget.onPostTap.call(post),
|
||||||
|
onTapLike: () async => updatePostInList(
|
||||||
|
await widget.service.likePost(widget.userId, post),
|
||||||
|
),
|
||||||
|
onTapUnlike: () async => updatePostInList(
|
||||||
|
await widget.service.unlikePost(widget.userId, post),
|
||||||
|
),
|
||||||
|
onUserTap: widget.onUserTap,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (posts.isEmpty)
|
||||||
|
Center(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(8.0),
|
||||||
|
child: Text(
|
||||||
|
widget.timelineCategoryFilter == null
|
||||||
|
? widget.options.translations.noPosts
|
||||||
|
: widget.options.translations.noPostsWithFilter,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> loadPosts() async {
|
Future<void> loadPosts() async {
|
||||||
|
if (widget.posts != null) return;
|
||||||
try {
|
try {
|
||||||
// Fetching posts from the service
|
// Fetching posts from the service
|
||||||
var fetchedPosts =
|
var fetchedPosts =
|
||||||
|
@ -86,39 +165,4 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
if (isLoading) {
|
|
||||||
// Show loading indicator while data is being fetched
|
|
||||||
return const Center(child: CircularProgressIndicator());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build the list of posts
|
|
||||||
return SingleChildScrollView(
|
|
||||||
controller: controller,
|
|
||||||
child: Column(
|
|
||||||
children: posts
|
|
||||||
.map(
|
|
||||||
(post) => Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
child: TimelinePostWidget(
|
|
||||||
userId: widget.userId,
|
|
||||||
options: widget.options,
|
|
||||||
post: post,
|
|
||||||
height: widget.timelinePostHeight,
|
|
||||||
onTap: () => widget.onPostTap.call(post),
|
|
||||||
onTapLike: () async => updatePostInList(
|
|
||||||
await widget.service.likePost(widget.userId, post),
|
|
||||||
),
|
|
||||||
onTapUnlike: () async => updatePostInList(
|
|
||||||
await widget.service.unlikePost(widget.userId, post),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
.toList(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,7 @@
|
||||||
|
// SPDX-FileCopyrightText: 2023 Iconica
|
||||||
|
//
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
|
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
|
||||||
|
@ -12,6 +16,7 @@ class TimelinePostWidget extends StatelessWidget {
|
||||||
required this.onTapLike,
|
required this.onTapLike,
|
||||||
required this.onTapUnlike,
|
required this.onTapUnlike,
|
||||||
required this.onTap,
|
required this.onTap,
|
||||||
|
this.onUserTap,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -25,6 +30,9 @@ class TimelinePostWidget extends StatelessWidget {
|
||||||
final VoidCallback onTapLike;
|
final VoidCallback onTapLike;
|
||||||
final VoidCallback onTapUnlike;
|
final VoidCallback onTapUnlike;
|
||||||
|
|
||||||
|
/// If this is not null, the user can tap on the user avatar or name
|
||||||
|
final Function(String userId)? onUserTap;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
var theme = Theme.of(context);
|
var theme = Theme.of(context);
|
||||||
|
@ -36,8 +44,14 @@ class TimelinePostWidget extends StatelessWidget {
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (post.creator != null)
|
|
||||||
Row(
|
Row(
|
||||||
|
children: [
|
||||||
|
if (post.creator != null)
|
||||||
|
InkWell(
|
||||||
|
onTap: onUserTap != null
|
||||||
|
? () => onUserTap?.call(post.creator!.userId)
|
||||||
|
: null,
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
if (post.creator!.imageUrl != null) ...[
|
if (post.creator!.imageUrl != null) ...[
|
||||||
options.userAvatarBuilder?.call(
|
options.userAvatarBuilder?.call(
|
||||||
|
@ -58,10 +72,14 @@ class TimelinePostWidget extends StatelessWidget {
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
],
|
||||||
// three small dots at the end
|
),
|
||||||
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
const Icon(Icons.more_horiz),
|
options.theme.moreIcon ??
|
||||||
|
const Icon(
|
||||||
|
Icons.more_horiz_rounded,
|
||||||
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
Loading…
Reference in a new issue