mirror of
https://github.com/Iconica-Development/flutter_timeline.git
synced 2025-05-19 02:23:46 +02:00
Merge pull request #54 from Iconica-Development/4.0.0
Improve flutter_timeline for safino usage
This commit is contained in:
commit
9d476129fd
26 changed files with 672 additions and 368 deletions
2
.github/workflows/melos-component-ci.yml
vendored
2
.github/workflows/melos-component-ci.yml
vendored
|
@ -10,3 +10,5 @@ jobs:
|
||||||
uses: Iconica-Development/.github/.github/workflows/melos-ci.yml@master
|
uses: Iconica-Development/.github/.github/workflows/melos-ci.yml@master
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
permissions: write-all
|
permissions: write-all
|
||||||
|
with:
|
||||||
|
flutter_version: 3.19.6
|
25
CHANGELOG.md
25
CHANGELOG.md
|
@ -1,3 +1,28 @@
|
||||||
|
## 4.0.0
|
||||||
|
|
||||||
|
- Add a serviceBuilder to the userstory configuration
|
||||||
|
- Add a listHeaderBuilder for showing a header at the top of the list of posts in the timeline
|
||||||
|
- Add a getUserId function to retrieve the userId when needed in the userstory configuration
|
||||||
|
- Fix the timelinecategory selection by removing the categories with key null
|
||||||
|
- Set an optional max length on the default post title input field
|
||||||
|
- Add a postCreationFloatingActionButtonColor to the timeline theme to set the color of the floating action button
|
||||||
|
- Add a post and a category to the postViewOpenPageBuilder function
|
||||||
|
- Add a refresh functionality to the timeline with a pull to refresh callback to allow additional functionality when refreshing the timeline
|
||||||
|
- Use the adaptive variants of the material elements in the timeline
|
||||||
|
- Change the default blue color to the primary color of the Theme.of(context) in the timeline
|
||||||
|
- Change the TimelineTranslations constructor to require all translations or use the TimelineTranslations.empty constructor if you don't want to specify all translations
|
||||||
|
- Add a TimelinePaddingOptions class to store the padding options for the timeline
|
||||||
|
- fix the avatar size to match the new design
|
||||||
|
- Add the iconbutton for image uploading back to the ReactionBottom
|
||||||
|
- Fix category key is correctly used for saving timeline posts and category title is shown everywhere
|
||||||
|
- Fix when clicking on post delete in the post screen of the userstory it will now navigate back to the timeline and delete the post
|
||||||
|
- Fix like icon being used for both like and unliked posts
|
||||||
|
- Fix post creator can only like the post once and after it is actually created
|
||||||
|
- Change the CategorySelectorButton to use more styling options and allow for an icon to be shown
|
||||||
|
- Fix incorrect timeline reaction name
|
||||||
|
- Add a dialog for post deletion confirmation
|
||||||
|
- Add a callback method to determine if a user can delete posts that gets called when needed
|
||||||
|
|
||||||
## 3.0.1
|
## 3.0.1
|
||||||
|
|
||||||
- Fixed postOverviewScreen not displaying the creators name.
|
- Fixed postOverviewScreen not displaying the creators name.
|
||||||
|
|
|
@ -7,13 +7,15 @@ TimelineUserStoryConfiguration getConfig(TimelineService service) {
|
||||||
userId: 'test_user',
|
userId: 'test_user',
|
||||||
optionsBuilder: (context) => options,
|
optionsBuilder: (context) => options,
|
||||||
enablePostOverviewScreen: false,
|
enablePostOverviewScreen: false,
|
||||||
|
canDeleteAllPosts: (_) => true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
var options = TimelineOptions(
|
var options = TimelineOptions(
|
||||||
textInputBuilder: null,
|
textInputBuilder: null,
|
||||||
padding: const EdgeInsets.all(20).copyWith(top: 28),
|
paddings: TimelinePaddingOptions(
|
||||||
allowAllDeletion: true,
|
mainPadding: const EdgeInsets.all(20).copyWith(top: 28),
|
||||||
|
),
|
||||||
categoriesOptions: CategoriesOptions(
|
categoriesOptions: CategoriesOptions(
|
||||||
categoriesBuilder: (context) => [
|
categoriesBuilder: (context) => [
|
||||||
const TimelineCategory(
|
const TimelineCategory(
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
//
|
//
|
||||||
// SPDX-License-Identifier: BSD-3-Clause
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_timeline/flutter_timeline.dart';
|
import 'package:flutter_timeline/flutter_timeline.dart';
|
||||||
import 'package:flutter_timeline/src/go_router.dart';
|
import 'package:flutter_timeline/src/go_router.dart';
|
||||||
|
@ -28,10 +29,13 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: TimelineUserStoryRoutes.timelineHome,
|
path: TimelineUserStoryRoutes.timelineHome,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
|
var service = config.serviceBuilder?.call(context) ?? config.service;
|
||||||
var timelineScreen = TimelineScreen(
|
var timelineScreen = TimelineScreen(
|
||||||
userId: config.userId,
|
userId: config.getUserId?.call(context) ?? config.userId,
|
||||||
onUserTap: (user) => config.onUserTap?.call(context, user),
|
onUserTap: (user) => config.onUserTap?.call(context, user),
|
||||||
service: config.service,
|
allowAllDeletion: config.canDeleteAllPosts?.call(context) ?? false,
|
||||||
|
onRefresh: config.onRefresh,
|
||||||
|
service: service,
|
||||||
options: config.optionsBuilder(context),
|
options: config.optionsBuilder(context),
|
||||||
onPostTap: (post) async =>
|
onPostTap: (post) async =>
|
||||||
config.onPostTap?.call(context, post) ??
|
config.onPostTap?.call(context, post) ??
|
||||||
|
@ -43,7 +47,11 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
);
|
);
|
||||||
|
|
||||||
var button = FloatingActionButton(
|
var button = FloatingActionButton(
|
||||||
backgroundColor: const Color(0xff71C6D1),
|
backgroundColor: config
|
||||||
|
.optionsBuilder(context)
|
||||||
|
.theme
|
||||||
|
.postCreationFloatingActionButtonColor ??
|
||||||
|
Theme.of(context).primaryColor,
|
||||||
onPressed: () async => context.push(
|
onPressed: () async => context.push(
|
||||||
TimelineUserStoryRoutes.timelineCategorySelection,
|
TimelineUserStoryRoutes.timelineCategorySelection,
|
||||||
),
|
),
|
||||||
|
@ -67,9 +75,9 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
config
|
config
|
||||||
.optionsBuilder(context)
|
.optionsBuilder(context)
|
||||||
.translations
|
.translations
|
||||||
.timeLineScreenTitle!,
|
.timeLineScreenTitle,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
@ -89,10 +97,12 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
categories: config
|
categories: config
|
||||||
.optionsBuilder(context)
|
.optionsBuilder(context)
|
||||||
.categoriesOptions
|
.categoriesOptions
|
||||||
.categoriesBuilder!(context),
|
.categoriesBuilder
|
||||||
|
?.call(context) ??
|
||||||
|
[],
|
||||||
onCategorySelected: (category) async {
|
onCategorySelected: (category) async {
|
||||||
await context.push(
|
await context.push(
|
||||||
TimelineUserStoryRoutes.timelinepostCreation(category.title),
|
TimelineUserStoryRoutes.timelinepostCreation(category.key ?? ''),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -113,9 +123,9 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
leading: backButton,
|
leading: backButton,
|
||||||
backgroundColor: const Color(0xff212121),
|
backgroundColor: const Color(0xff212121),
|
||||||
title: Text(
|
title: Text(
|
||||||
config.optionsBuilder(context).translations.postCreation!,
|
config.optionsBuilder(context).translations.postCreation,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
@ -129,15 +139,30 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
GoRoute(
|
GoRoute(
|
||||||
path: TimelineUserStoryRoutes.timelineView,
|
path: TimelineUserStoryRoutes.timelineView,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
var post =
|
var service = config.serviceBuilder?.call(context) ?? config.service;
|
||||||
config.service.postService.getPost(state.pathParameters['post']!);
|
var post = service.postService.getPost(state.pathParameters['post']!);
|
||||||
|
var category = config.optionsBuilder
|
||||||
|
.call(context)
|
||||||
|
.categoriesOptions
|
||||||
|
.categoriesBuilder
|
||||||
|
?.call(context)
|
||||||
|
.firstWhereOrNull(
|
||||||
|
(element) => element.key == post?.category,
|
||||||
|
);
|
||||||
|
|
||||||
var timelinePostWidget = TimelinePostScreen(
|
var timelinePostWidget = TimelinePostScreen(
|
||||||
userId: config.userId,
|
userId: config.getUserId?.call(context) ?? config.userId,
|
||||||
|
allowAllDeletion: config.canDeleteAllPosts?.call(context) ?? false,
|
||||||
options: config.optionsBuilder(context),
|
options: config.optionsBuilder(context),
|
||||||
service: config.service,
|
service: service,
|
||||||
post: post!,
|
post: post!,
|
||||||
onPostDelete: () => config.onPostDelete?.call(context, post),
|
onPostDelete: () async =>
|
||||||
|
config.onPostDelete?.call(context, post) ??
|
||||||
|
() async {
|
||||||
|
await service.postService.deletePost(post);
|
||||||
|
if (!context.mounted) return;
|
||||||
|
context.go(TimelineUserStoryRoutes.timelineHome);
|
||||||
|
}.call(),
|
||||||
onUserTap: (user) => config.onUserTap?.call(context, user),
|
onUserTap: (user) => config.onUserTap?.call(context, user),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -150,16 +175,21 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
return buildScreenWithoutTransition(
|
return buildScreenWithoutTransition(
|
||||||
context: context,
|
context: context,
|
||||||
state: state,
|
state: state,
|
||||||
child: config.postViewOpenPageBuilder
|
child: config.postViewOpenPageBuilder?.call(
|
||||||
?.call(context, timelinePostWidget, backButton) ??
|
context,
|
||||||
|
timelinePostWidget,
|
||||||
|
backButton,
|
||||||
|
post,
|
||||||
|
category,
|
||||||
|
) ??
|
||||||
Scaffold(
|
Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: backButton,
|
leading: backButton,
|
||||||
backgroundColor: const Color(0xff212121),
|
backgroundColor: const Color(0xff212121),
|
||||||
title: Text(
|
title: Text(
|
||||||
post.category ?? 'Category',
|
category?.title ?? post.category ?? 'Category',
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
@ -174,20 +204,20 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
path: TimelineUserStoryRoutes.timelinePostCreation,
|
path: TimelineUserStoryRoutes.timelinePostCreation,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
var category = state.pathParameters['category'];
|
var category = state.pathParameters['category'];
|
||||||
|
var service = config.serviceBuilder?.call(context) ?? config.service;
|
||||||
var timelinePostCreationWidget = TimelinePostCreationScreen(
|
var timelinePostCreationWidget = TimelinePostCreationScreen(
|
||||||
userId: config.userId,
|
userId: config.getUserId?.call(context) ?? config.userId,
|
||||||
options: config.optionsBuilder(context),
|
options: config.optionsBuilder(context),
|
||||||
service: config.service,
|
service: service,
|
||||||
onPostCreated: (post) async {
|
onPostCreated: (post) async {
|
||||||
var newPost = await config.service.postService.createPost(post);
|
var newPost = await service.postService.createPost(post);
|
||||||
if (context.mounted) {
|
if (!context.mounted) return;
|
||||||
if (config.afterPostCreationGoHome) {
|
if (config.afterPostCreationGoHome) {
|
||||||
context.go(TimelineUserStoryRoutes.timelineHome);
|
context.go(TimelineUserStoryRoutes.timelineHome);
|
||||||
} else {
|
} else {
|
||||||
await context
|
await context
|
||||||
.push(TimelineUserStoryRoutes.timelineViewPath(newPost.id));
|
.push(TimelineUserStoryRoutes.timelineViewPath(newPost.id));
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onPostOverview: (post) async => context.push(
|
onPostOverview: (post) async => context.push(
|
||||||
TimelineUserStoryRoutes.timelinePostOverview,
|
TimelineUserStoryRoutes.timelinePostOverview,
|
||||||
|
@ -216,9 +246,9 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
backgroundColor: const Color(0xff212121),
|
backgroundColor: const Color(0xff212121),
|
||||||
leading: backButton,
|
leading: backButton,
|
||||||
title: Text(
|
title: Text(
|
||||||
config.optionsBuilder(context).translations.postCreation!,
|
config.optionsBuilder(context).translations.postCreation,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
@ -233,16 +263,15 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
path: TimelineUserStoryRoutes.timelinePostOverview,
|
path: TimelineUserStoryRoutes.timelinePostOverview,
|
||||||
pageBuilder: (context, state) {
|
pageBuilder: (context, state) {
|
||||||
var post = state.extra! as TimelinePost;
|
var post = state.extra! as TimelinePost;
|
||||||
|
var service = config.serviceBuilder?.call(context) ?? config.service;
|
||||||
var timelinePostOverviewWidget = TimelinePostOverviewScreen(
|
var timelinePostOverviewWidget = TimelinePostOverviewScreen(
|
||||||
options: config.optionsBuilder(context),
|
options: config.optionsBuilder(context),
|
||||||
service: config.service,
|
service: service,
|
||||||
timelinePost: post,
|
timelinePost: post,
|
||||||
onPostSubmit: (post) async {
|
onPostSubmit: (post) async {
|
||||||
await config.service.postService.createPost(post);
|
await service.postService.createPost(post);
|
||||||
if (context.mounted) {
|
if (!context.mounted) return;
|
||||||
context.go(TimelineUserStoryRoutes.timelineHome);
|
context.go(TimelineUserStoryRoutes.timelineHome);
|
||||||
}
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
var backButton = IconButton(
|
var backButton = IconButton(
|
||||||
|
@ -265,9 +294,9 @@ List<GoRoute> getTimelineStoryRoutes({
|
||||||
leading: backButton,
|
leading: backButton,
|
||||||
backgroundColor: const Color(0xff212121),
|
backgroundColor: const Color(0xff212121),
|
||||||
title: Text(
|
title: Text(
|
||||||
config.optionsBuilder(context).translations.postOverview!,
|
config.optionsBuilder(context).translations.postOverview,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
|
|
@ -45,7 +45,8 @@ Widget _timelineScreenRoute({
|
||||||
);
|
);
|
||||||
|
|
||||||
var timelineScreen = TimelineScreen(
|
var timelineScreen = TimelineScreen(
|
||||||
userId: config.userId,
|
userId: config.getUserId?.call(context) ?? config.userId,
|
||||||
|
allowAllDeletion: config.canDeleteAllPosts?.call(context) ?? false,
|
||||||
onUserTap: (user) => config.onUserTap?.call(context, user),
|
onUserTap: (user) => config.onUserTap?.call(context, user),
|
||||||
service: config.service,
|
service: config.service,
|
||||||
options: config.optionsBuilder(context),
|
options: config.optionsBuilder(context),
|
||||||
|
@ -60,12 +61,17 @@ Widget _timelineScreenRoute({
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
onRefresh: config.onRefresh,
|
||||||
filterEnabled: config.filterEnabled,
|
filterEnabled: config.filterEnabled,
|
||||||
postWidgetBuilder: config.postWidgetBuilder,
|
postWidgetBuilder: config.postWidgetBuilder,
|
||||||
);
|
);
|
||||||
|
|
||||||
var button = FloatingActionButton(
|
var button = FloatingActionButton(
|
||||||
backgroundColor: const Color(0xff71C6D1),
|
backgroundColor: config
|
||||||
|
.optionsBuilder(context)
|
||||||
|
.theme
|
||||||
|
.postCreationFloatingActionButtonColor ??
|
||||||
|
Theme.of(context).primaryColor,
|
||||||
onPressed: () async => Navigator.of(context).push(
|
onPressed: () async => Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
builder: (context) => _postCategorySelectionScreen(
|
builder: (context) => _postCategorySelectionScreen(
|
||||||
|
@ -87,9 +93,9 @@ Widget _timelineScreenRoute({
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
backgroundColor: const Color(0xff212121),
|
backgroundColor: const Color(0xff212121),
|
||||||
title: Text(
|
title: Text(
|
||||||
config.optionsBuilder(context).translations.timeLineScreenTitle!,
|
config.optionsBuilder(context).translations.timeLineScreenTitle,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
@ -121,16 +127,29 @@ Widget _postDetailScreenRoute({
|
||||||
);
|
);
|
||||||
|
|
||||||
var timelinePostScreen = TimelinePostScreen(
|
var timelinePostScreen = TimelinePostScreen(
|
||||||
userId: config.userId,
|
userId: config.getUserId?.call(context) ?? config.userId,
|
||||||
|
allowAllDeletion: config.canDeleteAllPosts?.call(context) ?? false,
|
||||||
options: config.optionsBuilder(context),
|
options: config.optionsBuilder(context),
|
||||||
service: config.service,
|
service: config.service,
|
||||||
post: post,
|
post: post,
|
||||||
onPostDelete: () async =>
|
onPostDelete: () async =>
|
||||||
config.onPostDelete?.call(context, post) ??
|
config.onPostDelete?.call(context, post) ??
|
||||||
await config.service.postService.deletePost(post),
|
() async {
|
||||||
|
await config.service.postService.deletePost(post);
|
||||||
|
if (context.mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
|
}.call(),
|
||||||
onUserTap: (user) => config.onUserTap?.call(context, user),
|
onUserTap: (user) => config.onUserTap?.call(context, user),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
var category = config
|
||||||
|
.optionsBuilder(context)
|
||||||
|
.categoriesOptions
|
||||||
|
.categoriesBuilder
|
||||||
|
?.call(context)
|
||||||
|
.firstWhere((element) => element.key == post.category);
|
||||||
|
|
||||||
var backButton = IconButton(
|
var backButton = IconButton(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
icon: const Icon(Icons.arrow_back_ios),
|
icon: const Icon(Icons.arrow_back_ios),
|
||||||
|
@ -138,15 +157,15 @@ Widget _postDetailScreenRoute({
|
||||||
);
|
);
|
||||||
|
|
||||||
return config.postViewOpenPageBuilder
|
return config.postViewOpenPageBuilder
|
||||||
?.call(context, timelinePostScreen, backButton) ??
|
?.call(context, timelinePostScreen, backButton, post, category) ??
|
||||||
Scaffold(
|
Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
leading: backButton,
|
leading: backButton,
|
||||||
backgroundColor: const Color(0xff212121),
|
backgroundColor: const Color(0xff212121),
|
||||||
title: Text(
|
title: Text(
|
||||||
post.category ?? 'Category',
|
category?.title ?? post.category ?? 'Category',
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
@ -176,12 +195,12 @@ Widget _postCreationScreenRoute({
|
||||||
);
|
);
|
||||||
|
|
||||||
var timelinePostCreationScreen = TimelinePostCreationScreen(
|
var timelinePostCreationScreen = TimelinePostCreationScreen(
|
||||||
userId: config.userId,
|
userId: config.getUserId?.call(context) ?? config.userId,
|
||||||
options: config.optionsBuilder(context),
|
options: config.optionsBuilder(context),
|
||||||
service: config.service,
|
service: config.service,
|
||||||
onPostCreated: (post) async {
|
onPostCreated: (post) async {
|
||||||
var newPost = await config.service.postService.createPost(post);
|
var newPost = await config.service.postService.createPost(post);
|
||||||
if (context.mounted) {
|
if (!context.mounted) return;
|
||||||
if (config.afterPostCreationGoHome) {
|
if (config.afterPostCreationGoHome) {
|
||||||
await Navigator.pushReplacement(
|
await Navigator.pushReplacement(
|
||||||
context,
|
context,
|
||||||
|
@ -204,7 +223,6 @@ Widget _postCreationScreenRoute({
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
},
|
},
|
||||||
onPostOverview: (post) async => Navigator.of(context).push(
|
onPostOverview: (post) async => Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
@ -216,7 +234,7 @@ Widget _postCreationScreenRoute({
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
enablePostOverviewScreen: config.enablePostOverviewScreen,
|
enablePostOverviewScreen: config.enablePostOverviewScreen,
|
||||||
postCategory: category.title,
|
postCategory: category.key,
|
||||||
);
|
);
|
||||||
|
|
||||||
var backButton = IconButton(
|
var backButton = IconButton(
|
||||||
|
@ -234,9 +252,9 @@ Widget _postCreationScreenRoute({
|
||||||
backgroundColor: const Color(0xff212121),
|
backgroundColor: const Color(0xff212121),
|
||||||
leading: backButton,
|
leading: backButton,
|
||||||
title: Text(
|
title: Text(
|
||||||
config.optionsBuilder(context).translations.postCreation!,
|
config.optionsBuilder(context).translations.postCreation,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
@ -282,7 +300,6 @@ Widget _postOverviewScreenRoute({
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
isOverviewScreen: true,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var backButton = IconButton(
|
var backButton = IconButton(
|
||||||
|
@ -302,9 +319,9 @@ Widget _postOverviewScreenRoute({
|
||||||
leading: backButton,
|
leading: backButton,
|
||||||
backgroundColor: const Color(0xff212121),
|
backgroundColor: const Color(0xff212121),
|
||||||
title: Text(
|
title: Text(
|
||||||
config.optionsBuilder(context).translations.postOverview!,
|
config.optionsBuilder(context).translations.postOverview,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
@ -332,7 +349,9 @@ Widget _postCategorySelectionScreen({
|
||||||
categories: config
|
categories: config
|
||||||
.optionsBuilder(context)
|
.optionsBuilder(context)
|
||||||
.categoriesOptions
|
.categoriesOptions
|
||||||
.categoriesBuilder!(context),
|
.categoriesBuilder
|
||||||
|
?.call(context) ??
|
||||||
|
[],
|
||||||
onCategorySelected: (category) async {
|
onCategorySelected: (category) async {
|
||||||
await Navigator.of(context).push(
|
await Navigator.of(context).push(
|
||||||
MaterialPageRoute(
|
MaterialPageRoute(
|
||||||
|
@ -361,9 +380,9 @@ Widget _postCategorySelectionScreen({
|
||||||
leading: backButton,
|
leading: backButton,
|
||||||
backgroundColor: const Color(0xff212121),
|
backgroundColor: const Color(0xff212121),
|
||||||
title: Text(
|
title: Text(
|
||||||
config.optionsBuilder(context).translations.postCreation!,
|
config.optionsBuilder(context).translations.postCreation,
|
||||||
style: const TextStyle(
|
style: TextStyle(
|
||||||
color: Color(0xff71C6D1),
|
color: Theme.of(context).primaryColor,
|
||||||
fontSize: 24,
|
fontSize: 24,
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
),
|
),
|
||||||
|
|
|
@ -48,6 +48,9 @@ class TimelineUserStoryConfiguration {
|
||||||
const TimelineUserStoryConfiguration({
|
const TimelineUserStoryConfiguration({
|
||||||
required this.service,
|
required this.service,
|
||||||
required this.optionsBuilder,
|
required this.optionsBuilder,
|
||||||
|
this.getUserId,
|
||||||
|
this.serviceBuilder,
|
||||||
|
this.canDeleteAllPosts,
|
||||||
this.userId = 'test_user',
|
this.userId = 'test_user',
|
||||||
this.homeOpenPageBuilder,
|
this.homeOpenPageBuilder,
|
||||||
this.postCreationOpenPageBuilder,
|
this.postCreationOpenPageBuilder,
|
||||||
|
@ -55,6 +58,7 @@ class TimelineUserStoryConfiguration {
|
||||||
this.postOverviewOpenPageBuilder,
|
this.postOverviewOpenPageBuilder,
|
||||||
this.onPostTap,
|
this.onPostTap,
|
||||||
this.onUserTap,
|
this.onUserTap,
|
||||||
|
this.onRefresh,
|
||||||
this.onPostDelete,
|
this.onPostDelete,
|
||||||
this.filterEnabled = false,
|
this.filterEnabled = false,
|
||||||
this.postWidgetBuilder,
|
this.postWidgetBuilder,
|
||||||
|
@ -66,9 +70,19 @@ class TimelineUserStoryConfiguration {
|
||||||
/// The ID of the user associated with this user story configuration.
|
/// The ID of the user associated with this user story configuration.
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// A function to get the userId only when needed and with a context
|
||||||
|
final String Function(BuildContext context)? getUserId;
|
||||||
|
|
||||||
|
/// A function to determine if a user can delete posts that is called
|
||||||
|
/// when needed
|
||||||
|
final bool Function(BuildContext context)? canDeleteAllPosts;
|
||||||
|
|
||||||
/// The TimelineService responsible for fetching user story data.
|
/// The TimelineService responsible for fetching user story data.
|
||||||
final TimelineService service;
|
final TimelineService service;
|
||||||
|
|
||||||
|
/// A function to get the timeline service only when needed and with a context
|
||||||
|
final TimelineService Function(BuildContext context)? serviceBuilder;
|
||||||
|
|
||||||
/// A function that builds TimelineOptions based on the given BuildContext.
|
/// A function that builds TimelineOptions based on the given BuildContext.
|
||||||
final TimelineOptions Function(BuildContext context) optionsBuilder;
|
final TimelineOptions Function(BuildContext context) optionsBuilder;
|
||||||
|
|
||||||
|
@ -100,6 +114,8 @@ class TimelineUserStoryConfiguration {
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
Widget child,
|
Widget child,
|
||||||
IconButton? button,
|
IconButton? button,
|
||||||
|
TimelinePost post,
|
||||||
|
TimelineCategory? category,
|
||||||
)? postViewOpenPageBuilder;
|
)? postViewOpenPageBuilder;
|
||||||
|
|
||||||
/// Open page builder function for the post overview page. This function
|
/// Open page builder function for the post overview page. This function
|
||||||
|
@ -117,6 +133,9 @@ class TimelineUserStoryConfiguration {
|
||||||
/// A callback function invoked when the user's profile is tapped.
|
/// A callback function invoked when the user's profile is tapped.
|
||||||
final Function(BuildContext context, String userId)? onUserTap;
|
final Function(BuildContext context, String userId)? onUserTap;
|
||||||
|
|
||||||
|
/// A callback function invoked when the timeline is refreshed by pulling down
|
||||||
|
final Function(BuildContext context, String? category)? onRefresh;
|
||||||
|
|
||||||
/// A callback function invoked when a post deletion is requested.
|
/// A callback function invoked when a post deletion is requested.
|
||||||
final Widget Function(BuildContext context, TimelinePost post)? onPostDelete;
|
final Widget Function(BuildContext context, TimelinePost post)? onPostDelete;
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
name: flutter_timeline
|
name: flutter_timeline
|
||||||
description: Visual elements and interface combined into one package
|
description: Visual elements and interface combined into one package
|
||||||
version: 3.0.1
|
version: 4.0.0
|
||||||
|
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
|
@ -15,17 +15,19 @@ dependencies:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
go_router: any
|
go_router: any
|
||||||
|
|
||||||
|
collection: any
|
||||||
|
|
||||||
flutter_timeline_view:
|
flutter_timeline_view:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_timeline
|
url: https://github.com/Iconica-Development/flutter_timeline
|
||||||
path: packages/flutter_timeline_view
|
path: packages/flutter_timeline_view
|
||||||
ref: 3.0.1
|
ref: 4.0.0
|
||||||
|
|
||||||
flutter_timeline_interface:
|
flutter_timeline_interface:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_timeline
|
url: https://github.com/Iconica-Development/flutter_timeline
|
||||||
path: packages/flutter_timeline_interface
|
path: packages/flutter_timeline_interface
|
||||||
ref: 3.0.1
|
ref: 4.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^2.0.0
|
flutter_lints: ^2.0.0
|
||||||
|
|
|
@ -9,10 +9,8 @@ class FirebaseTimelineOptions {
|
||||||
const FirebaseTimelineOptions({
|
const FirebaseTimelineOptions({
|
||||||
this.usersCollectionName = 'users',
|
this.usersCollectionName = 'users',
|
||||||
this.timelineCollectionName = 'timeline',
|
this.timelineCollectionName = 'timeline',
|
||||||
this.allTimelineCategories = const [],
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final String usersCollectionName;
|
final String usersCollectionName;
|
||||||
final String timelineCollectionName;
|
final String timelineCollectionName;
|
||||||
final List<String> allTimelineCategories;
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -254,7 +254,7 @@ class FirebaseTimelinePostService
|
||||||
// update the post with the new like
|
// update the post with the new like
|
||||||
var updatedPost = post.copyWith(
|
var updatedPost = post.copyWith(
|
||||||
likes: post.likes + 1,
|
likes: post.likes + 1,
|
||||||
likedBy: post.likedBy?..add(userId),
|
likedBy: [...post.likedBy ?? [], userId],
|
||||||
);
|
);
|
||||||
posts = posts
|
posts = posts
|
||||||
.map(
|
.map(
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_timeline_firebase
|
name: flutter_timeline_firebase
|
||||||
description: Implementation of the Flutter Timeline interface for Firebase.
|
description: Implementation of the Flutter Timeline interface for Firebase.
|
||||||
version: 3.0.1
|
version: 4.0.0
|
||||||
|
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_timeline
|
url: https://github.com/Iconica-Development/flutter_timeline
|
||||||
path: packages/flutter_timeline_interface
|
path: packages/flutter_timeline_interface
|
||||||
ref: 3.0.1
|
ref: 4.0.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_lints: ^2.0.0
|
flutter_lints: ^2.0.0
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_timeline_interface
|
name: flutter_timeline_interface
|
||||||
description: Interface for the service of the Flutter Timeline component
|
description: Interface for the service of the Flutter Timeline component
|
||||||
version: 3.0.1
|
version: 4.0.0
|
||||||
|
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
library flutter_timeline_view;
|
library flutter_timeline_view;
|
||||||
|
|
||||||
export 'src/config/timeline_options.dart';
|
export 'src/config/timeline_options.dart';
|
||||||
|
export 'src/config/timeline_paddings.dart';
|
||||||
export 'src/config/timeline_styles.dart';
|
export 'src/config/timeline_styles.dart';
|
||||||
export 'src/config/timeline_theme.dart';
|
export 'src/config/timeline_theme.dart';
|
||||||
export 'src/config/timeline_translations.dart';
|
export 'src/config/timeline_translations.dart';
|
||||||
|
|
|
@ -5,6 +5,7 @@ import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_image_picker/flutter_image_picker.dart';
|
import 'package:flutter_image_picker/flutter_image_picker.dart';
|
||||||
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
|
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
|
||||||
|
import 'package:flutter_timeline_view/src/config/timeline_paddings.dart';
|
||||||
import 'package:flutter_timeline_view/src/config/timeline_theme.dart';
|
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';
|
||||||
|
@ -12,11 +13,11 @@ import 'package:intl/intl.dart';
|
||||||
class TimelineOptions {
|
class TimelineOptions {
|
||||||
const TimelineOptions({
|
const TimelineOptions({
|
||||||
this.theme = const TimelineTheme(),
|
this.theme = const TimelineTheme(),
|
||||||
this.translations = const TimelineTranslations(),
|
this.translations = const TimelineTranslations.empty(),
|
||||||
|
this.paddings = const TimelinePaddingOptions(),
|
||||||
this.imagePickerConfig = const ImagePickerConfig(),
|
this.imagePickerConfig = const ImagePickerConfig(),
|
||||||
this.imagePickerTheme = const ImagePickerTheme(),
|
this.imagePickerTheme = const ImagePickerTheme(),
|
||||||
this.timelinePostHeight,
|
this.timelinePostHeight,
|
||||||
this.allowAllDeletion = false,
|
|
||||||
this.sortCommentsAscending = true,
|
this.sortCommentsAscending = true,
|
||||||
this.sortPostsAscending,
|
this.sortPostsAscending,
|
||||||
this.doubleTapTolike = false,
|
this.doubleTapTolike = false,
|
||||||
|
@ -37,12 +38,8 @@ class TimelineOptions {
|
||||||
this.userAvatarBuilder,
|
this.userAvatarBuilder,
|
||||||
this.anonymousAvatarBuilder,
|
this.anonymousAvatarBuilder,
|
||||||
this.nameBuilder,
|
this.nameBuilder,
|
||||||
this.padding =
|
|
||||||
const EdgeInsets.only(left: 12.0, top: 24.0, right: 12.0, bottom: 12.0),
|
|
||||||
this.iconSize = 26,
|
this.iconSize = 26,
|
||||||
this.postWidgetHeight,
|
this.postWidgetHeight,
|
||||||
this.postPadding =
|
|
||||||
const EdgeInsets.symmetric(vertical: 12.0, horizontal: 12.0),
|
|
||||||
this.filterOptions = const FilterOptions(),
|
this.filterOptions = const FilterOptions(),
|
||||||
this.categoriesOptions = const CategoriesOptions(),
|
this.categoriesOptions = const CategoriesOptions(),
|
||||||
this.requireImageForPost = false,
|
this.requireImageForPost = false,
|
||||||
|
@ -52,6 +49,8 @@ class TimelineOptions {
|
||||||
this.maxContentLength,
|
this.maxContentLength,
|
||||||
this.categorySelectorButtonBuilder,
|
this.categorySelectorButtonBuilder,
|
||||||
this.postOverviewButtonBuilder,
|
this.postOverviewButtonBuilder,
|
||||||
|
this.deletionDialogBuilder,
|
||||||
|
this.listHeaderBuilder,
|
||||||
this.titleInputDecoration,
|
this.titleInputDecoration,
|
||||||
this.contentInputDecoration,
|
this.contentInputDecoration,
|
||||||
});
|
});
|
||||||
|
@ -71,15 +70,15 @@ class TimelineOptions {
|
||||||
/// 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
|
|
||||||
/// only the posts of the current user
|
|
||||||
final bool allowAllDeletion;
|
|
||||||
|
|
||||||
/// The height of a post in the timeline
|
/// The height of a post in the timeline
|
||||||
final double? timelinePostHeight;
|
final double? timelinePostHeight;
|
||||||
|
|
||||||
|
/// Class that contains all the translations used in the timeline
|
||||||
final TimelineTranslations translations;
|
final TimelineTranslations translations;
|
||||||
|
|
||||||
|
/// Class that contains all the paddings used in the timeline
|
||||||
|
final TimelinePaddingOptions paddings;
|
||||||
|
|
||||||
final ButtonBuilder? buttonBuilder;
|
final ButtonBuilder? buttonBuilder;
|
||||||
|
|
||||||
final TextInputBuilder? textInputBuilder;
|
final TextInputBuilder? textInputBuilder;
|
||||||
|
@ -115,18 +114,12 @@ class TimelineOptions {
|
||||||
/// The builder for the divider
|
/// The builder for the divider
|
||||||
final Widget Function()? dividerBuilder;
|
final Widget Function()? dividerBuilder;
|
||||||
|
|
||||||
/// The padding between posts in the timeline
|
|
||||||
final EdgeInsets padding;
|
|
||||||
|
|
||||||
/// Size of icons like the comment and like icons. Dafualts to 26
|
/// Size of icons like the comment and like icons. Dafualts to 26
|
||||||
final double iconSize;
|
final double iconSize;
|
||||||
|
|
||||||
/// Sets a predefined height for the postWidget.
|
/// Sets a predefined height for the postWidget.
|
||||||
final double? postWidgetHeight;
|
final double? postWidgetHeight;
|
||||||
|
|
||||||
/// Padding of each post
|
|
||||||
final EdgeInsets postPadding;
|
|
||||||
|
|
||||||
/// Options for filtering
|
/// Options for filtering
|
||||||
final FilterOptions filterOptions;
|
final FilterOptions filterOptions;
|
||||||
|
|
||||||
|
@ -156,6 +149,11 @@ class TimelineOptions {
|
||||||
String text,
|
String text,
|
||||||
)? categorySelectorButtonBuilder;
|
)? categorySelectorButtonBuilder;
|
||||||
|
|
||||||
|
/// This widgetbuilder is placed at the top of the list of posts and can be
|
||||||
|
/// used to add custom elements
|
||||||
|
final Widget Function(BuildContext context, String? category)?
|
||||||
|
listHeaderBuilder;
|
||||||
|
|
||||||
/// Builder for the post overview button
|
/// Builder for the post overview button
|
||||||
/// on the timeline post overview screen
|
/// on the timeline post overview screen
|
||||||
final Widget Function(
|
final Widget Function(
|
||||||
|
@ -164,6 +162,11 @@ class TimelineOptions {
|
||||||
String text,
|
String text,
|
||||||
)? postOverviewButtonBuilder;
|
)? postOverviewButtonBuilder;
|
||||||
|
|
||||||
|
/// Optional builder to override the default alertdialog for post deletion
|
||||||
|
/// It should pop the navigator with true to delete the post and
|
||||||
|
/// false to cancel deletion
|
||||||
|
final WidgetBuilder? deletionDialogBuilder;
|
||||||
|
|
||||||
/// inputdecoration for the title textfield
|
/// inputdecoration for the title textfield
|
||||||
final InputDecoration? titleInputDecoration;
|
final InputDecoration? titleInputDecoration;
|
||||||
|
|
||||||
|
@ -221,8 +224,7 @@ class CategoriesOptions {
|
||||||
|
|
||||||
/// Abilty to override the standard category selector
|
/// Abilty to override the standard category selector
|
||||||
final Widget Function(
|
final Widget Function(
|
||||||
String? categoryKey,
|
TimelineCategory category,
|
||||||
String categoryName,
|
|
||||||
Function() onTap,
|
Function() onTap,
|
||||||
// ignore: avoid_positional_boolean_parameters
|
// ignore: avoid_positional_boolean_parameters
|
||||||
bool selected,
|
bool selected,
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
/// This class contains the paddings used in the timeline options
|
||||||
|
class TimelinePaddingOptions {
|
||||||
|
const TimelinePaddingOptions({
|
||||||
|
this.mainPadding =
|
||||||
|
const EdgeInsets.only(left: 12.0, top: 24.0, right: 12.0, bottom: 12.0),
|
||||||
|
this.postPadding =
|
||||||
|
const EdgeInsets.symmetric(vertical: 12.0, horizontal: 12.0),
|
||||||
|
this.postOverviewButtonBottomPadding = 30.0,
|
||||||
|
this.categoryButtonTextPadding,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// The padding between posts in the timeline
|
||||||
|
final EdgeInsets mainPadding;
|
||||||
|
|
||||||
|
/// The padding of each post
|
||||||
|
final EdgeInsets postPadding;
|
||||||
|
|
||||||
|
/// The bottom padding of the button on the post overview screen
|
||||||
|
final double postOverviewButtonBottomPadding;
|
||||||
|
|
||||||
|
/// The padding between the icon and the text in the category button
|
||||||
|
final double? categoryButtonTextPadding;
|
||||||
|
}
|
|
@ -15,6 +15,9 @@ class TimelineTheme {
|
||||||
this.sendIcon,
|
this.sendIcon,
|
||||||
this.moreIcon,
|
this.moreIcon,
|
||||||
this.deleteIcon,
|
this.deleteIcon,
|
||||||
|
this.categorySelectionButtonBorderColor,
|
||||||
|
this.categorySelectionButtonBackgroundColor,
|
||||||
|
this.postCreationFloatingActionButtonColor,
|
||||||
this.textStyles = const TimelineTextStyles(),
|
this.textStyles = const TimelineTextStyles(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -38,5 +41,16 @@ class TimelineTheme {
|
||||||
/// The icon for delete action (delete post)
|
/// The icon for delete action (delete post)
|
||||||
final Widget? deleteIcon;
|
final Widget? deleteIcon;
|
||||||
|
|
||||||
|
/// The text style overrides for all the texts in the timeline
|
||||||
final TimelineTextStyles textStyles;
|
final TimelineTextStyles textStyles;
|
||||||
|
|
||||||
|
/// The color of the border around the category in the selection screen
|
||||||
|
final Color? categorySelectionButtonBorderColor;
|
||||||
|
|
||||||
|
/// The color of the background of the category selection button in the
|
||||||
|
/// selection screen
|
||||||
|
final Color? categorySelectionButtonBackgroundColor;
|
||||||
|
|
||||||
|
/// The color of the floating action button on the overview screen
|
||||||
|
final Color? postCreationFloatingActionButtonColor;
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,8 +5,54 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
@immutable
|
@immutable
|
||||||
|
|
||||||
|
/// Class that holds all the translations for the timeline component view and
|
||||||
|
/// the corresponding userstory
|
||||||
class TimelineTranslations {
|
class TimelineTranslations {
|
||||||
|
/// TimelineTranslations constructor where everything is required use this
|
||||||
|
/// if you want to be sure to have all translations specified
|
||||||
|
/// If you just want the default values use the empty constructor
|
||||||
|
/// and optionally override the values with the copyWith method
|
||||||
const TimelineTranslations({
|
const TimelineTranslations({
|
||||||
|
required this.anonymousUser,
|
||||||
|
required this.noPosts,
|
||||||
|
required this.noPostsWithFilter,
|
||||||
|
required this.title,
|
||||||
|
required this.titleHintText,
|
||||||
|
required this.content,
|
||||||
|
required this.contentHintText,
|
||||||
|
required this.contentDescription,
|
||||||
|
required this.uploadImage,
|
||||||
|
required this.uploadImageDescription,
|
||||||
|
required this.allowComments,
|
||||||
|
required this.allowCommentsDescription,
|
||||||
|
required this.commentsTitleOnPost,
|
||||||
|
required this.checkPost,
|
||||||
|
required this.postAt,
|
||||||
|
required this.deletePost,
|
||||||
|
required this.deleteReaction,
|
||||||
|
required this.deleteConfirmationMessage,
|
||||||
|
required this.deleteConfirmationTitle,
|
||||||
|
required this.deleteCancelButton,
|
||||||
|
required this.deleteButton,
|
||||||
|
required this.viewPost,
|
||||||
|
required this.likesTitle,
|
||||||
|
required this.commentsTitle,
|
||||||
|
required this.firstComment,
|
||||||
|
required this.writeComment,
|
||||||
|
required this.postLoadingError,
|
||||||
|
required this.timelineSelectionDescription,
|
||||||
|
required this.searchHint,
|
||||||
|
required this.postOverview,
|
||||||
|
required this.postIn,
|
||||||
|
required this.postCreation,
|
||||||
|
required this.yes,
|
||||||
|
required this.no,
|
||||||
|
required this.timeLineScreenTitle,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Default translations for the timeline component view
|
||||||
|
const TimelineTranslations.empty({
|
||||||
this.anonymousUser = 'Anonymous user',
|
this.anonymousUser = 'Anonymous user',
|
||||||
this.noPosts = 'No posts yet',
|
this.noPosts = 'No posts yet',
|
||||||
this.noPostsWithFilter = 'No posts with this filter',
|
this.noPostsWithFilter = 'No posts with this filter',
|
||||||
|
@ -23,6 +69,11 @@ class TimelineTranslations {
|
||||||
this.commentsTitleOnPost = 'Comments',
|
this.commentsTitleOnPost = 'Comments',
|
||||||
this.checkPost = 'Check post overview',
|
this.checkPost = 'Check post overview',
|
||||||
this.deletePost = 'Delete post',
|
this.deletePost = 'Delete post',
|
||||||
|
this.deleteConfirmationTitle = 'Delete Post',
|
||||||
|
this.deleteConfirmationMessage =
|
||||||
|
'Are you sure you want to delete this post?',
|
||||||
|
this.deleteButton = 'Delete',
|
||||||
|
this.deleteCancelButton = 'Cancel',
|
||||||
this.deleteReaction = 'Delete Reaction',
|
this.deleteReaction = 'Delete Reaction',
|
||||||
this.viewPost = 'View post',
|
this.viewPost = 'View post',
|
||||||
this.likesTitle = 'Likes',
|
this.likesTitle = 'Likes',
|
||||||
|
@ -41,45 +92,51 @@ class TimelineTranslations {
|
||||||
this.timeLineScreenTitle = 'iconinstagram',
|
this.timeLineScreenTitle = 'iconinstagram',
|
||||||
});
|
});
|
||||||
|
|
||||||
final String? noPosts;
|
final String noPosts;
|
||||||
final String? noPostsWithFilter;
|
final String noPostsWithFilter;
|
||||||
final String? anonymousUser;
|
final String anonymousUser;
|
||||||
|
|
||||||
final String? title;
|
final String title;
|
||||||
final String? content;
|
final String content;
|
||||||
final String? contentDescription;
|
final String contentDescription;
|
||||||
final String? uploadImage;
|
final String uploadImage;
|
||||||
final String? uploadImageDescription;
|
final String uploadImageDescription;
|
||||||
final String? allowComments;
|
final String allowComments;
|
||||||
final String? allowCommentsDescription;
|
final String allowCommentsDescription;
|
||||||
final String? checkPost;
|
final String checkPost;
|
||||||
final String? postAt;
|
final String postAt;
|
||||||
|
|
||||||
final String? titleHintText;
|
final String titleHintText;
|
||||||
final String? contentHintText;
|
final String contentHintText;
|
||||||
|
|
||||||
final String? deletePost;
|
final String deletePost;
|
||||||
final String? deleteReaction;
|
final String deleteConfirmationTitle;
|
||||||
final String? viewPost;
|
final String deleteConfirmationMessage;
|
||||||
final String? likesTitle;
|
final String deleteButton;
|
||||||
final String? commentsTitle;
|
final String deleteCancelButton;
|
||||||
final String? commentsTitleOnPost;
|
|
||||||
final String? writeComment;
|
|
||||||
final String? firstComment;
|
|
||||||
final String? postLoadingError;
|
|
||||||
|
|
||||||
final String? timelineSelectionDescription;
|
final String deleteReaction;
|
||||||
|
final String viewPost;
|
||||||
|
final String likesTitle;
|
||||||
|
final String commentsTitle;
|
||||||
|
final String commentsTitleOnPost;
|
||||||
|
final String writeComment;
|
||||||
|
final String firstComment;
|
||||||
|
final String postLoadingError;
|
||||||
|
|
||||||
final String? searchHint;
|
final String timelineSelectionDescription;
|
||||||
|
|
||||||
final String? postOverview;
|
final String searchHint;
|
||||||
final String? postIn;
|
|
||||||
final String? postCreation;
|
|
||||||
|
|
||||||
final String? yes;
|
final String postOverview;
|
||||||
final String? no;
|
final String postIn;
|
||||||
final String? timeLineScreenTitle;
|
final String postCreation;
|
||||||
|
|
||||||
|
final String yes;
|
||||||
|
final String no;
|
||||||
|
final String timeLineScreenTitle;
|
||||||
|
|
||||||
|
/// Method to override the default values of the translations
|
||||||
TimelineTranslations copyWith({
|
TimelineTranslations copyWith({
|
||||||
String? noPosts,
|
String? noPosts,
|
||||||
String? noPostsWithFilter,
|
String? noPostsWithFilter,
|
||||||
|
@ -95,6 +152,10 @@ class TimelineTranslations {
|
||||||
String? checkPost,
|
String? checkPost,
|
||||||
String? postAt,
|
String? postAt,
|
||||||
String? deletePost,
|
String? deletePost,
|
||||||
|
String? deleteConfirmationTitle,
|
||||||
|
String? deleteConfirmationMessage,
|
||||||
|
String? deleteButton,
|
||||||
|
String? deleteCancelButton,
|
||||||
String? deleteReaction,
|
String? deleteReaction,
|
||||||
String? viewPost,
|
String? viewPost,
|
||||||
String? likesTitle,
|
String? likesTitle,
|
||||||
|
@ -130,6 +191,12 @@ class TimelineTranslations {
|
||||||
checkPost: checkPost ?? this.checkPost,
|
checkPost: checkPost ?? this.checkPost,
|
||||||
postAt: postAt ?? this.postAt,
|
postAt: postAt ?? this.postAt,
|
||||||
deletePost: deletePost ?? this.deletePost,
|
deletePost: deletePost ?? this.deletePost,
|
||||||
|
deleteConfirmationTitle:
|
||||||
|
deleteConfirmationTitle ?? this.deleteConfirmationTitle,
|
||||||
|
deleteConfirmationMessage:
|
||||||
|
deleteConfirmationMessage ?? this.deleteConfirmationMessage,
|
||||||
|
deleteButton: deleteButton ?? this.deleteButton,
|
||||||
|
deleteCancelButton: deleteCancelButton ?? this.deleteCancelButton,
|
||||||
deleteReaction: deleteReaction ?? this.deleteReaction,
|
deleteReaction: deleteReaction ?? this.deleteReaction,
|
||||||
viewPost: viewPost ?? this.viewPost,
|
viewPost: viewPost ?? this.viewPost,
|
||||||
likesTitle: likesTitle ?? this.likesTitle,
|
likesTitle: likesTitle ?? this.likesTitle,
|
||||||
|
|
|
@ -104,6 +104,7 @@ class _TimelinePostCreationScreenState
|
||||||
category: widget.postCategory,
|
category: widget.postCategory,
|
||||||
content: contentController.text,
|
content: contentController.text,
|
||||||
likes: 0,
|
likes: 0,
|
||||||
|
likedBy: const [],
|
||||||
reaction: 0,
|
reaction: 0,
|
||||||
createdAt: DateTime.now(),
|
createdAt: DateTime.now(),
|
||||||
reactionEnabled: allowComments,
|
reactionEnabled: allowComments,
|
||||||
|
@ -121,14 +122,14 @@ class _TimelinePostCreationScreenState
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: () => FocusScope.of(context).unfocus(),
|
onTap: () => FocusScope.of(context).unfocus(),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: widget.options.padding,
|
padding: widget.options.paddings.mainPadding,
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.max,
|
mainAxisSize: MainAxisSize.max,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.title!,
|
widget.options.translations.title,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
@ -140,6 +141,7 @@ class _TimelinePostCreationScreenState
|
||||||
'',
|
'',
|
||||||
) ??
|
) ??
|
||||||
TextField(
|
TextField(
|
||||||
|
maxLength: widget.options.maxTitleLength,
|
||||||
controller: titleController,
|
controller: titleController,
|
||||||
decoration: widget.options.contentInputDecoration ??
|
decoration: widget.options.contentInputDecoration ??
|
||||||
InputDecoration(
|
InputDecoration(
|
||||||
|
@ -148,7 +150,7 @@ class _TimelinePostCreationScreenState
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.content!,
|
widget.options.translations.content,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
@ -156,7 +158,7 @@ class _TimelinePostCreationScreenState
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.contentDescription!,
|
widget.options.translations.contentDescription,
|
||||||
style: theme.textTheme.bodyMedium,
|
style: theme.textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
// input field for the content
|
// input field for the content
|
||||||
|
@ -176,14 +178,14 @@ class _TimelinePostCreationScreenState
|
||||||
),
|
),
|
||||||
// input field for the content
|
// input field for the content
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.uploadImage!,
|
widget.options.translations.uploadImage,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.uploadImageDescription!,
|
widget.options.translations.uploadImageDescription,
|
||||||
style: theme.textTheme.bodyMedium,
|
style: theme.textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
// image picker field
|
// image picker field
|
||||||
|
@ -270,21 +272,21 @@ class _TimelinePostCreationScreenState
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.commentsTitle!,
|
widget.options.translations.commentsTitle,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.allowCommentsDescription!,
|
widget.options.translations.allowCommentsDescription,
|
||||||
style: theme.textTheme.bodyMedium,
|
style: theme.textTheme.bodyMedium,
|
||||||
),
|
),
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.start,
|
mainAxisAlignment: MainAxisAlignment.start,
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
Checkbox(
|
Checkbox(
|
||||||
activeColor: const Color(0xff71C6D1),
|
activeColor: theme.colorScheme.primary,
|
||||||
value: allowComments,
|
value: allowComments,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -292,9 +294,9 @@ class _TimelinePostCreationScreenState
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Text(widget.options.translations.yes!),
|
Text(widget.options.translations.yes),
|
||||||
Checkbox(
|
Checkbox(
|
||||||
activeColor: const Color(0xff71C6D1),
|
activeColor: theme.colorScheme.primary,
|
||||||
value: !allowComments,
|
value: !allowComments,
|
||||||
onChanged: (value) {
|
onChanged: (value) {
|
||||||
setState(() {
|
setState(() {
|
||||||
|
@ -302,7 +304,7 @@ class _TimelinePostCreationScreenState
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
Text(widget.options.translations.no!),
|
Text(widget.options.translations.no),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 120),
|
const SizedBox(height: 120),
|
||||||
|
@ -313,13 +315,14 @@ class _TimelinePostCreationScreenState
|
||||||
? widget.options.buttonBuilder!(
|
? widget.options.buttonBuilder!(
|
||||||
context,
|
context,
|
||||||
onPostCreated,
|
onPostCreated,
|
||||||
widget.options.translations.checkPost!,
|
widget.options.translations.checkPost,
|
||||||
enabled: editingDone,
|
enabled: editingDone,
|
||||||
)
|
)
|
||||||
: ElevatedButton(
|
: ElevatedButton(
|
||||||
style: const ButtonStyle(
|
style: ButtonStyle(
|
||||||
backgroundColor:
|
backgroundColor: MaterialStatePropertyAll(
|
||||||
MaterialStatePropertyAll(Color(0xff71C6D1)),
|
theme.colorScheme.primary,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
onPressed: editingDone
|
onPressed: editingDone
|
||||||
? () async {
|
? () async {
|
||||||
|
@ -332,8 +335,8 @@ class _TimelinePostCreationScreenState
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.enablePostOverviewScreen
|
widget.enablePostOverviewScreen
|
||||||
? widget.options.translations.checkPost!
|
? widget.options.translations.checkPost
|
||||||
: widget.options.translations.postCreation!,
|
: widget.options.translations.postCreation,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
// ignore_for_file: prefer_expression_function_bodies
|
// ignore_for_file: prefer_expression_function_bodies
|
||||||
|
|
||||||
|
import 'package:collection/collection.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';
|
||||||
import 'package:flutter_timeline_view/flutter_timeline_view.dart';
|
import 'package:flutter_timeline_view/flutter_timeline_view.dart';
|
||||||
|
@ -10,27 +11,33 @@ class TimelinePostOverviewScreen extends StatelessWidget {
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.service,
|
required this.service,
|
||||||
required this.onPostSubmit,
|
required this.onPostSubmit,
|
||||||
this.isOverviewScreen,
|
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
final TimelinePost timelinePost;
|
final TimelinePost timelinePost;
|
||||||
final TimelineOptions options;
|
final TimelineOptions options;
|
||||||
final TimelineService service;
|
final TimelineService service;
|
||||||
final void Function(TimelinePost) onPostSubmit;
|
final void Function(TimelinePost) onPostSubmit;
|
||||||
final bool? isOverviewScreen;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
// the timelinePost.category is a key so we need to get the category object
|
||||||
|
var timelineCategoryName = options.categoriesOptions.categoriesBuilder
|
||||||
|
?.call(context)
|
||||||
|
.firstWhereOrNull((element) => element.key == timelinePost.category)
|
||||||
|
?.title ??
|
||||||
|
timelinePost.category;
|
||||||
|
var buttonText = '${options.translations.postIn} $timelineCategoryName';
|
||||||
return Column(
|
return Column(
|
||||||
|
mainAxisSize: MainAxisSize.max,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Expanded(
|
||||||
child: TimelinePostScreen(
|
child: TimelinePostScreen(
|
||||||
userId: timelinePost.creatorId,
|
userId: timelinePost.creatorId,
|
||||||
options: options,
|
options: options,
|
||||||
post: timelinePost,
|
post: timelinePost,
|
||||||
onPostDelete: () async {},
|
onPostDelete: () async {},
|
||||||
service: service,
|
service: service,
|
||||||
isOverviewScreen: isOverviewScreen,
|
isOverviewScreen: true,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
options.postOverviewButtonBuilder?.call(
|
options.postOverviewButtonBuilder?.call(
|
||||||
|
@ -38,13 +45,20 @@ class TimelinePostOverviewScreen extends StatelessWidget {
|
||||||
() {
|
() {
|
||||||
onPostSubmit(timelinePost);
|
onPostSubmit(timelinePost);
|
||||||
},
|
},
|
||||||
'${options.translations.postIn} ${timelinePost.category}',
|
buttonText,
|
||||||
) ??
|
) ??
|
||||||
Padding(
|
options.buttonBuilder?.call(
|
||||||
padding: const EdgeInsets.only(bottom: 30.0),
|
context,
|
||||||
child: ElevatedButton(
|
() {
|
||||||
style: const ButtonStyle(
|
onPostSubmit(timelinePost);
|
||||||
backgroundColor: MaterialStatePropertyAll(Color(0xff71C6D1)),
|
},
|
||||||
|
buttonText,
|
||||||
|
enabled: true,
|
||||||
|
) ??
|
||||||
|
ElevatedButton(
|
||||||
|
style: ButtonStyle(
|
||||||
|
backgroundColor:
|
||||||
|
MaterialStatePropertyAll(Theme.of(context).primaryColor),
|
||||||
),
|
),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
onPostSubmit(timelinePost);
|
onPostSubmit(timelinePost);
|
||||||
|
@ -52,7 +66,7 @@ class TimelinePostOverviewScreen extends StatelessWidget {
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(12.0),
|
padding: const EdgeInsets.all(12.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
'${options.translations.postIn} ${timelinePost.category}',
|
buttonText,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
@ -61,7 +75,7 @@ class TimelinePostOverviewScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
SizedBox(height: options.paddings.postOverviewButtonBottomPadding),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,15 +13,17 @@ import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
|
||||||
import 'package:flutter_timeline_view/src/config/timeline_options.dart';
|
import 'package:flutter_timeline_view/src/config/timeline_options.dart';
|
||||||
import 'package:flutter_timeline_view/src/widgets/reaction_bottom.dart';
|
import 'package:flutter_timeline_view/src/widgets/reaction_bottom.dart';
|
||||||
import 'package:flutter_timeline_view/src/widgets/tappable_image.dart';
|
import 'package:flutter_timeline_view/src/widgets/tappable_image.dart';
|
||||||
|
import 'package:flutter_timeline_view/src/widgets/timeline_post_widget.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
class TimelinePostScreen extends StatelessWidget {
|
class TimelinePostScreen extends StatefulWidget {
|
||||||
const TimelinePostScreen({
|
const TimelinePostScreen({
|
||||||
required this.userId,
|
required this.userId,
|
||||||
required this.service,
|
required this.service,
|
||||||
required this.options,
|
required this.options,
|
||||||
required this.post,
|
required this.post,
|
||||||
required this.onPostDelete,
|
required this.onPostDelete,
|
||||||
|
this.allowAllDeletion = false,
|
||||||
this.isOverviewScreen = false,
|
this.isOverviewScreen = false,
|
||||||
this.onUserTap,
|
this.onUserTap,
|
||||||
super.key,
|
super.key,
|
||||||
|
@ -30,6 +32,10 @@ class TimelinePostScreen extends StatelessWidget {
|
||||||
/// The user id of the current user
|
/// The user id of the current user
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// Allow all posts to be deleted instead of
|
||||||
|
/// only the posts of the current user
|
||||||
|
final bool allowAllDeletion;
|
||||||
|
|
||||||
/// The timeline service to fetch the post details
|
/// The timeline service to fetch the post details
|
||||||
final TimelineService service;
|
final TimelineService service;
|
||||||
|
|
||||||
|
@ -47,49 +53,10 @@ class TimelinePostScreen extends StatelessWidget {
|
||||||
final bool? isOverviewScreen;
|
final bool? isOverviewScreen;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) => Scaffold(
|
State<TimelinePostScreen> createState() => _TimelinePostScreenState();
|
||||||
body: _TimelinePostScreen(
|
|
||||||
userId: userId,
|
|
||||||
service: service,
|
|
||||||
options: options,
|
|
||||||
post: post,
|
|
||||||
onPostDelete: onPostDelete,
|
|
||||||
onUserTap: onUserTap,
|
|
||||||
isOverviewScreen: isOverviewScreen,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class _TimelinePostScreen extends StatefulWidget {
|
class _TimelinePostScreenState extends State<TimelinePostScreen> {
|
||||||
const _TimelinePostScreen({
|
|
||||||
required this.userId,
|
|
||||||
required this.service,
|
|
||||||
required this.options,
|
|
||||||
required this.post,
|
|
||||||
required this.onPostDelete,
|
|
||||||
this.onUserTap,
|
|
||||||
this.isOverviewScreen,
|
|
||||||
});
|
|
||||||
|
|
||||||
final String userId;
|
|
||||||
|
|
||||||
final TimelineService service;
|
|
||||||
|
|
||||||
final TimelineOptions options;
|
|
||||||
|
|
||||||
final TimelinePost post;
|
|
||||||
|
|
||||||
final Function(String userId)? onUserTap;
|
|
||||||
|
|
||||||
final VoidCallback onPostDelete;
|
|
||||||
|
|
||||||
final bool? isOverviewScreen;
|
|
||||||
|
|
||||||
@override
|
|
||||||
State<_TimelinePostScreen> createState() => _TimelinePostScreenState();
|
|
||||||
}
|
|
||||||
|
|
||||||
class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
|
||||||
TimelinePost? post;
|
TimelinePost? post;
|
||||||
bool isLoading = true;
|
bool isLoading = true;
|
||||||
|
|
||||||
|
@ -146,13 +113,13 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
|
|
||||||
if (isLoading) {
|
if (isLoading) {
|
||||||
const Center(
|
const Center(
|
||||||
child: CircularProgressIndicator(),
|
child: CircularProgressIndicator.adaptive(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (this.post == null) {
|
if (this.post == null) {
|
||||||
return Center(
|
return Center(
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.options.translations.postLoadingError!,
|
widget.options.translations.postLoadingError,
|
||||||
style: widget.options.theme.textStyles.errorTextStyle,
|
style: widget.options.theme.textStyles.errorTextStyle,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
@ -166,7 +133,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
|
|
||||||
return Stack(
|
return Stack(
|
||||||
children: [
|
children: [
|
||||||
RefreshIndicator(
|
RefreshIndicator.adaptive(
|
||||||
onRefresh: () async {
|
onRefresh: () async {
|
||||||
updatePost(
|
updatePost(
|
||||||
await widget.service.postService.fetchPostDetails(
|
await widget.service.postService.fetchPostDetails(
|
||||||
|
@ -178,7 +145,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
},
|
},
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: widget.options.padding,
|
padding: widget.options.paddings.mainPadding,
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@ -198,7 +165,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
28,
|
28,
|
||||||
) ??
|
) ??
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 20,
|
radius: 14,
|
||||||
backgroundImage:
|
backgroundImage:
|
||||||
CachedNetworkImageProvider(
|
CachedNetworkImageProvider(
|
||||||
post.creator!.imageUrl!,
|
post.creator!.imageUrl!,
|
||||||
|
@ -210,7 +177,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
28,
|
28,
|
||||||
) ??
|
) ??
|
||||||
const CircleAvatar(
|
const CircleAvatar(
|
||||||
radius: 20,
|
radius: 14,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.person,
|
Icons.person,
|
||||||
),
|
),
|
||||||
|
@ -221,7 +188,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
widget.options.nameBuilder
|
widget.options.nameBuilder
|
||||||
?.call(post.creator) ??
|
?.call(post.creator) ??
|
||||||
post.creator?.fullName ??
|
post.creator?.fullName ??
|
||||||
widget.options.translations.anonymousUser!,
|
widget.options.translations.anonymousUser,
|
||||||
style: widget.options.theme.textStyles
|
style: widget.options.theme.textStyles
|
||||||
.postCreatorTitleStyle ??
|
.postCreatorTitleStyle ??
|
||||||
theme.textTheme.titleMedium,
|
theme.textTheme.titleMedium,
|
||||||
|
@ -230,10 +197,19 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (widget.options.allowAllDeletion ||
|
if (!(widget.isOverviewScreen ?? false) &&
|
||||||
post.creator?.userId == widget.userId)
|
(widget.allowAllDeletion ||
|
||||||
|
post.creator?.userId == widget.userId))
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
onSelected: (value) => widget.onPostDelete(),
|
onSelected: (value) async {
|
||||||
|
if (value == 'delete') {
|
||||||
|
await showPostDeletionConfirmationDialog(
|
||||||
|
widget.options,
|
||||||
|
context,
|
||||||
|
widget.onPostDelete,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) =>
|
||||||
<PopupMenuEntry<String>>[
|
<PopupMenuEntry<String>>[
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
|
@ -241,7 +217,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.deletePost!,
|
widget.options.translations.deletePost,
|
||||||
style: widget.options.theme.textStyles
|
style: widget.options.theme.textStyles
|
||||||
.deletePostStyle ??
|
.deletePostStyle ??
|
||||||
theme.textTheme.bodyMedium,
|
theme.textTheme.bodyMedium,
|
||||||
|
@ -344,6 +320,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
] else ...[
|
] else ...[
|
||||||
InkWell(
|
InkWell(
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
|
if (widget.isOverviewScreen ?? false) return;
|
||||||
updatePost(
|
updatePost(
|
||||||
await widget.service.postService.likePost(
|
await widget.service.postService.likePost(
|
||||||
widget.userId,
|
widget.userId,
|
||||||
|
@ -441,7 +418,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
if (post.reactionEnabled) ...[
|
if (post.reactionEnabled) ...[
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.commentsTitleOnPost!,
|
widget.options.translations.commentsTitleOnPost,
|
||||||
style: theme.textTheme.titleMedium,
|
style: theme.textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
for (var reaction
|
for (var reaction
|
||||||
|
@ -450,7 +427,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onLongPressStart: (details) async {
|
onLongPressStart: (details) async {
|
||||||
if (reaction.creatorId == widget.userId ||
|
if (reaction.creatorId == widget.userId ||
|
||||||
widget.options.allowAllDeletion) {
|
widget.allowAllDeletion) {
|
||||||
var overlay = Overlay.of(context)
|
var overlay = Overlay.of(context)
|
||||||
.context
|
.context
|
||||||
.findRenderObject()! as RenderBox;
|
.findRenderObject()! as RenderBox;
|
||||||
|
@ -469,7 +446,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
PopupMenuItem<String>(
|
PopupMenuItem<String>(
|
||||||
value: 'delete',
|
value: 'delete',
|
||||||
child: Text(
|
child: Text(
|
||||||
widget.options.translations.deleteReaction!,
|
widget.options.translations.deleteReaction,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
@ -495,7 +472,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
28,
|
28,
|
||||||
) ??
|
) ??
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 20,
|
radius: 14,
|
||||||
backgroundImage: CachedNetworkImageProvider(
|
backgroundImage: CachedNetworkImageProvider(
|
||||||
reaction.creator!.imageUrl!,
|
reaction.creator!.imageUrl!,
|
||||||
),
|
),
|
||||||
|
@ -506,7 +483,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
28,
|
28,
|
||||||
) ??
|
) ??
|
||||||
const CircleAvatar(
|
const CircleAvatar(
|
||||||
radius: 20,
|
radius: 14,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.person,
|
Icons.person,
|
||||||
),
|
),
|
||||||
|
@ -520,10 +497,10 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.options.nameBuilder
|
widget.options.nameBuilder
|
||||||
?.call(post.creator) ??
|
?.call(reaction.creator) ??
|
||||||
reaction.creator?.fullName ??
|
reaction.creator?.fullName ??
|
||||||
widget.options.translations
|
widget.options.translations
|
||||||
.anonymousUser!,
|
.anonymousUser,
|
||||||
style: theme.textTheme.titleSmall,
|
style: theme.textTheme.titleSmall,
|
||||||
),
|
),
|
||||||
Padding(
|
Padding(
|
||||||
|
@ -541,7 +518,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
child: Text.rich(
|
child: Text.rich(
|
||||||
TextSpan(
|
TextSpan(
|
||||||
text: widget.options.nameBuilder
|
text: widget.options.nameBuilder
|
||||||
?.call(post.creator) ??
|
?.call(reaction.creator) ??
|
||||||
reaction.creator?.fullName ??
|
reaction.creator?.fullName ??
|
||||||
widget
|
widget
|
||||||
.options.translations.anonymousUser,
|
.options.translations.anonymousUser,
|
||||||
|
@ -565,7 +542,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
if (post.reactions?.isEmpty ?? true) ...[
|
if (post.reactions?.isEmpty ?? true) ...[
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.firstComment!,
|
widget.options.translations.firstComment,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
const SizedBox(height: 120),
|
const SizedBox(height: 120),
|
||||||
|
@ -575,7 +552,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (post.reactionEnabled && !widget.isOverviewScreen!)
|
if (post.reactionEnabled && !(widget.isOverviewScreen ?? false))
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.bottomCenter,
|
alignment: Alignment.bottomCenter,
|
||||||
child: ReactionBottom(
|
child: ReactionBottom(
|
||||||
|
|
|
@ -16,16 +16,22 @@ class TimelineScreen extends StatefulWidget {
|
||||||
this.onPostTap,
|
this.onPostTap,
|
||||||
this.scrollController,
|
this.scrollController,
|
||||||
this.onUserTap,
|
this.onUserTap,
|
||||||
|
this.onRefresh,
|
||||||
this.posts,
|
this.posts,
|
||||||
this.timelineCategory,
|
this.timelineCategory,
|
||||||
this.postWidgetBuilder,
|
this.postWidgetBuilder,
|
||||||
this.filterEnabled = false,
|
this.filterEnabled = false,
|
||||||
|
this.allowAllDeletion = false,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The user id of the current user
|
/// The user id of the current user
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// Allow all posts to be deleted instead of
|
||||||
|
/// only the posts of the current user
|
||||||
|
final bool allowAllDeletion;
|
||||||
|
|
||||||
/// The service to use for fetching and manipulating posts
|
/// The service to use for fetching and manipulating posts
|
||||||
final TimelineService? service;
|
final TimelineService? service;
|
||||||
|
|
||||||
|
@ -45,6 +51,9 @@ class TimelineScreen extends StatefulWidget {
|
||||||
/// Called when a post is tapped
|
/// Called when a post is tapped
|
||||||
final Function(TimelinePost)? onPostTap;
|
final Function(TimelinePost)? onPostTap;
|
||||||
|
|
||||||
|
/// Called when the timeline is refreshed by pulling down
|
||||||
|
final Function(BuildContext context, String? category)? onRefresh;
|
||||||
|
|
||||||
/// If this is not null, the user can tap on the user avatar or name
|
/// If this is not null, the user can tap on the user avatar or name
|
||||||
final Function(String userId)? onUserTap;
|
final Function(String userId)? onUserTap;
|
||||||
|
|
||||||
|
@ -104,7 +113,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
if (isLoading && widget.posts == null) {
|
if (isLoading && widget.posts == null) {
|
||||||
return const Center(child: CircularProgressIndicator());
|
return const Center(child: CircularProgressIndicator.adaptive());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build the list of posts
|
// Build the list of posts
|
||||||
|
@ -143,12 +152,13 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: widget.options.padding.top,
|
height: widget.options.paddings.mainPadding.top,
|
||||||
),
|
),
|
||||||
if (widget.filterEnabled) ...[
|
if (widget.filterEnabled) ...[
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.only(
|
||||||
horizontal: widget.options.padding.horizontal,
|
left: widget.options.paddings.mainPadding.left,
|
||||||
|
right: widget.options.paddings.mainPadding.right,
|
||||||
),
|
),
|
||||||
child: Row(
|
child: Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
@ -218,19 +228,29 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
||||||
height: 12,
|
height: 12,
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
|
child: RefreshIndicator.adaptive(
|
||||||
|
onRefresh: () async {
|
||||||
|
await widget.onRefresh?.call(context, category);
|
||||||
|
await loadPosts();
|
||||||
|
},
|
||||||
child: SingleChildScrollView(
|
child: SingleChildScrollView(
|
||||||
controller: controller,
|
controller: controller,
|
||||||
child: Column(
|
child: Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
/// Add a optional custom header to the list of posts
|
||||||
|
widget.options.listHeaderBuilder
|
||||||
|
?.call(context, category) ??
|
||||||
|
const SizedBox.shrink(),
|
||||||
...posts.map(
|
...posts.map(
|
||||||
(post) => Padding(
|
(post) => Padding(
|
||||||
padding: widget.options.postPadding,
|
padding: widget.options.paddings.postPadding,
|
||||||
child: widget.postWidgetBuilder?.call(post) ??
|
child: widget.postWidgetBuilder?.call(post) ??
|
||||||
TimelinePostWidget(
|
TimelinePostWidget(
|
||||||
service: service,
|
service: service,
|
||||||
userId: widget.userId,
|
userId: widget.userId,
|
||||||
options: widget.options,
|
options: widget.options,
|
||||||
|
allowAllDeletion: widget.allowAllDeletion,
|
||||||
post: post,
|
post: post,
|
||||||
onTap: () async {
|
onTap: () async {
|
||||||
if (widget.onPostTap != null) {
|
if (widget.onPostTap != null) {
|
||||||
|
@ -249,7 +269,8 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
||||||
options: widget.options,
|
options: widget.options,
|
||||||
post: post,
|
post: post,
|
||||||
onPostDelete: () {
|
onPostDelete: () {
|
||||||
service.postService.deletePost(post);
|
service.postService
|
||||||
|
.deletePost(post);
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
@ -273,10 +294,11 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Text(
|
child: Text(
|
||||||
category == null
|
category == null
|
||||||
? widget.options.translations.noPosts!
|
? widget.options.translations.noPosts
|
||||||
: widget
|
: widget
|
||||||
.options.translations.noPostsWithFilter!,
|
.options.translations.noPostsWithFilter,
|
||||||
style: widget.options.theme.textStyles.noPostsStyle,
|
style:
|
||||||
|
widget.options.theme.textStyles.noPostsStyle,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -284,8 +306,9 @@ class _TimelineScreenState extends State<TimelineScreen> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: widget.options.padding.bottom,
|
height: widget.options.paddings.mainPadding.bottom,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
|
@ -29,7 +29,7 @@ class TimelineSelectionScreen extends StatelessWidget {
|
||||||
Padding(
|
Padding(
|
||||||
padding: EdgeInsets.only(top: size.height * 0.05, bottom: 8),
|
padding: EdgeInsets.only(top: size.height * 0.05, bottom: 8),
|
||||||
child: Text(
|
child: Text(
|
||||||
options.translations.timelineSelectionDescription!,
|
options.translations.timelineSelectionDescription,
|
||||||
style: const TextStyle(
|
style: const TextStyle(
|
||||||
fontWeight: FontWeight.w800,
|
fontWeight: FontWeight.w800,
|
||||||
fontSize: 20,
|
fontSize: 20,
|
||||||
|
@ -38,7 +38,7 @@ class TimelineSelectionScreen extends StatelessWidget {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
for (var category in categories.where(
|
for (var category in categories.where(
|
||||||
(element) => element.canCreate,
|
(element) => element.canCreate && element.key != null,
|
||||||
)) ...[
|
)) ...[
|
||||||
options.categorySelectorButtonBuilder?.call(
|
options.categorySelectorButtonBuilder?.call(
|
||||||
context,
|
context,
|
||||||
|
@ -55,9 +55,13 @@ class TimelineSelectionScreen extends StatelessWidget {
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
border: Border.all(
|
border: Border.all(
|
||||||
color: const Color(0xff71C6D1),
|
color:
|
||||||
|
options.theme.categorySelectionButtonBorderColor ??
|
||||||
|
Theme.of(context).primaryColor,
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
|
color:
|
||||||
|
options.theme.categorySelectionButtonBackgroundColor,
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.symmetric(vertical: 4),
|
margin: const EdgeInsets.symmetric(vertical: 4),
|
||||||
child: Column(
|
child: Column(
|
||||||
|
|
|
@ -37,12 +37,11 @@ class _CategorySelectorState extends State<CategorySelector> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: widget.options.categoriesOptions
|
width: widget.options.categoriesOptions
|
||||||
.categorySelectorHorizontalPadding ??
|
.categorySelectorHorizontalPadding ??
|
||||||
max(widget.options.padding.horizontal - 20, 0),
|
max(widget.options.paddings.mainPadding.left - 20, 0),
|
||||||
),
|
),
|
||||||
for (var category in categories) ...[
|
for (var category in categories) ...[
|
||||||
widget.options.categoriesOptions.categoryButtonBuilder?.call(
|
widget.options.categoriesOptions.categoryButtonBuilder?.call(
|
||||||
category.key,
|
category,
|
||||||
category.title,
|
|
||||||
() => widget.onTapCategory(category.key),
|
() => widget.onTapCategory(category.key),
|
||||||
widget.filter == category.key,
|
widget.filter == category.key,
|
||||||
widget.isOnTop,
|
widget.isOnTop,
|
||||||
|
@ -61,7 +60,7 @@ class _CategorySelectorState extends State<CategorySelector> {
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: widget.options.categoriesOptions
|
width: widget.options.categoriesOptions
|
||||||
.categorySelectorHorizontalPadding ??
|
.categorySelectorHorizontalPadding ??
|
||||||
max(widget.options.padding.horizontal - 4, 0),
|
max(widget.options.paddings.mainPadding.right - 4, 0),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
@ -14,7 +14,7 @@ class CategorySelectorButton extends StatelessWidget {
|
||||||
|
|
||||||
final TimelineCategory category;
|
final TimelineCategory category;
|
||||||
final bool selected;
|
final bool selected;
|
||||||
final void Function() onTap;
|
final VoidCallback onTap;
|
||||||
final TimelineOptions options;
|
final TimelineOptions options;
|
||||||
final bool isOnTop;
|
final bool isOnTop;
|
||||||
|
|
||||||
|
@ -36,15 +36,19 @@ class CategorySelectorButton extends StatelessWidget {
|
||||||
),
|
),
|
||||||
fixedSize: MaterialStatePropertyAll(Size(140, isOnTop ? 140 : 20)),
|
fixedSize: MaterialStatePropertyAll(Size(140, isOnTop ? 140 : 20)),
|
||||||
backgroundColor: MaterialStatePropertyAll(
|
backgroundColor: MaterialStatePropertyAll(
|
||||||
selected ? const Color(0xff71C6D1) : Colors.transparent,
|
selected
|
||||||
|
? theme.colorScheme.primary
|
||||||
|
: options.theme.categorySelectionButtonBackgroundColor ??
|
||||||
|
Colors.transparent,
|
||||||
),
|
),
|
||||||
shape: const MaterialStatePropertyAll(
|
shape: MaterialStatePropertyAll(
|
||||||
RoundedRectangleBorder(
|
RoundedRectangleBorder(
|
||||||
borderRadius: BorderRadius.all(
|
borderRadius: const BorderRadius.all(
|
||||||
Radius.circular(8),
|
Radius.circular(8),
|
||||||
),
|
),
|
||||||
side: BorderSide(
|
side: BorderSide(
|
||||||
color: Color(0xff71C6D1),
|
color: options.theme.categorySelectionButtonBorderColor ??
|
||||||
|
theme.colorScheme.primary,
|
||||||
width: 2,
|
width: 2,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -53,7 +57,9 @@ class CategorySelectorButton extends StatelessWidget {
|
||||||
child: isOnTop
|
child: isOnTop
|
||||||
? SizedBox(
|
? SizedBox(
|
||||||
width: MediaQuery.of(context).size.width,
|
width: MediaQuery.of(context).size.width,
|
||||||
child: Column(
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
|
@ -70,13 +76,25 @@ class CategorySelectorButton extends StatelessWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
Center(child: category.icon),
|
||||||
|
],
|
||||||
|
),
|
||||||
)
|
)
|
||||||
: Row(
|
: Row(
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
category.icon,
|
||||||
|
SizedBox(
|
||||||
|
width:
|
||||||
|
options.paddings.categoryButtonTextPadding ?? 8,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
child: Text(
|
child: Text(
|
||||||
category.title,
|
category.title,
|
||||||
style: (options.theme.textStyles.categoryTitleStyle ??
|
style:
|
||||||
|
(options.theme.textStyles.categoryTitleStyle ??
|
||||||
theme.textTheme.labelLarge)
|
theme.textTheme.labelLarge)
|
||||||
?.copyWith(
|
?.copyWith(
|
||||||
color: selected
|
color: selected
|
||||||
|
@ -90,6 +108,9 @@ class CategorySelectorButton extends StatelessWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,18 @@ class _ReactionBottomState extends State<ReactionBottom> {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
|
if (widget.onPressSelectImage != null) ...[
|
||||||
|
IconButton(
|
||||||
|
onPressed: () async {
|
||||||
|
_textEditingController.clear();
|
||||||
|
widget.onPressSelectImage?.call();
|
||||||
|
},
|
||||||
|
icon: Icon(
|
||||||
|
Icons.image,
|
||||||
|
color: widget.iconColor,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () async {
|
onPressed: () async {
|
||||||
var value = _textEditingController.text;
|
var value = _textEditingController.text;
|
||||||
|
@ -65,7 +77,7 @@ class _ReactionBottomState extends State<ReactionBottom> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
widget.translations.writeComment!,
|
widget.translations.writeComment,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
@ -18,12 +18,18 @@ class TimelinePostWidget extends StatefulWidget {
|
||||||
required this.onTapUnlike,
|
required this.onTapUnlike,
|
||||||
required this.onPostDelete,
|
required this.onPostDelete,
|
||||||
required this.service,
|
required this.service,
|
||||||
|
required this.allowAllDeletion,
|
||||||
this.onUserTap,
|
this.onUserTap,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
/// The user id of the current user
|
/// The user id of the current user
|
||||||
final String userId;
|
final String userId;
|
||||||
|
|
||||||
|
/// Allow all posts to be deleted instead of
|
||||||
|
/// only the posts of the current user
|
||||||
|
final bool allowAllDeletion;
|
||||||
|
|
||||||
final TimelineOptions options;
|
final TimelineOptions options;
|
||||||
|
|
||||||
final TimelinePost post;
|
final TimelinePost post;
|
||||||
|
@ -72,7 +78,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
|
||||||
28,
|
28,
|
||||||
) ??
|
) ??
|
||||||
CircleAvatar(
|
CircleAvatar(
|
||||||
radius: 20,
|
radius: 14,
|
||||||
backgroundImage: CachedNetworkImageProvider(
|
backgroundImage: CachedNetworkImageProvider(
|
||||||
widget.post.creator!.imageUrl!,
|
widget.post.creator!.imageUrl!,
|
||||||
),
|
),
|
||||||
|
@ -80,10 +86,10 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
|
||||||
] else ...[
|
] else ...[
|
||||||
widget.options.anonymousAvatarBuilder?.call(
|
widget.options.anonymousAvatarBuilder?.call(
|
||||||
widget.post.creator!,
|
widget.post.creator!,
|
||||||
40,
|
28,
|
||||||
) ??
|
) ??
|
||||||
const CircleAvatar(
|
const CircleAvatar(
|
||||||
radius: 20,
|
radius: 14,
|
||||||
child: Icon(
|
child: Icon(
|
||||||
Icons.person,
|
Icons.person,
|
||||||
),
|
),
|
||||||
|
@ -94,7 +100,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
|
||||||
widget.options.nameBuilder
|
widget.options.nameBuilder
|
||||||
?.call(widget.post.creator) ??
|
?.call(widget.post.creator) ??
|
||||||
widget.post.creator?.fullName ??
|
widget.post.creator?.fullName ??
|
||||||
widget.options.translations.anonymousUser!,
|
widget.options.translations.anonymousUser,
|
||||||
style: widget.options.theme.textStyles
|
style: widget.options.theme.textStyles
|
||||||
.postCreatorTitleStyle ??
|
.postCreatorTitleStyle ??
|
||||||
theme.textTheme.titleMedium,
|
theme.textTheme.titleMedium,
|
||||||
|
@ -103,12 +109,16 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
const Spacer(),
|
const Spacer(),
|
||||||
if (widget.options.allowAllDeletion ||
|
if (widget.allowAllDeletion ||
|
||||||
widget.post.creator?.userId == widget.userId)
|
widget.post.creator?.userId == widget.userId)
|
||||||
PopupMenuButton(
|
PopupMenuButton(
|
||||||
onSelected: (value) {
|
onSelected: (value) async {
|
||||||
if (value == 'delete') {
|
if (value == 'delete') {
|
||||||
widget.onPostDelete();
|
await showPostDeletionConfirmationDialog(
|
||||||
|
widget.options,
|
||||||
|
context,
|
||||||
|
widget.onPostDelete,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
itemBuilder: (BuildContext context) =>
|
itemBuilder: (BuildContext context) =>
|
||||||
|
@ -118,7 +128,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
|
||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.deletePost!,
|
widget.options.translations.deletePost,
|
||||||
style: widget.options.theme.textStyles
|
style: widget.options.theme.textStyles
|
||||||
.deletePostStyle ??
|
.deletePostStyle ??
|
||||||
theme.textTheme.bodyMedium,
|
theme.textTheme.bodyMedium,
|
||||||
|
@ -257,7 +267,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
|
||||||
onTap: widget.onTapLike,
|
onTap: widget.onTapLike,
|
||||||
child: Container(
|
child: Container(
|
||||||
color: Colors.transparent,
|
color: Colors.transparent,
|
||||||
child: widget.options.theme.likedIcon ??
|
child: widget.options.theme.likeIcon ??
|
||||||
Icon(
|
Icon(
|
||||||
Icons.favorite_outline,
|
Icons.favorite_outline,
|
||||||
color: widget.options.theme.iconColor,
|
color: widget.options.theme.iconColor,
|
||||||
|
@ -318,7 +328,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
|
||||||
),
|
),
|
||||||
const SizedBox(height: 4),
|
const SizedBox(height: 4),
|
||||||
Text(
|
Text(
|
||||||
widget.options.translations.viewPost!,
|
widget.options.translations.viewPost,
|
||||||
style: widget.options.theme.textStyles.viewPostStyle ??
|
style: widget.options.theme.textStyles.viewPostStyle ??
|
||||||
theme.textTheme.bodySmall,
|
theme.textTheme.bodySmall,
|
||||||
),
|
),
|
||||||
|
@ -331,3 +341,39 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> showPostDeletionConfirmationDialog(
|
||||||
|
TimelineOptions options,
|
||||||
|
BuildContext context,
|
||||||
|
Function() onPostDelete,
|
||||||
|
) async {
|
||||||
|
var result = await showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) =>
|
||||||
|
options.deletionDialogBuilder?.call(context) ??
|
||||||
|
AlertDialog(
|
||||||
|
title: Text(options.translations.deleteConfirmationTitle),
|
||||||
|
content: Text(options.translations.deleteConfirmationMessage),
|
||||||
|
actions: <Widget>[
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(false);
|
||||||
|
},
|
||||||
|
child: Text(options.translations.deleteCancelButton),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () {
|
||||||
|
Navigator.of(context).pop(true);
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
options.translations.deleteButton,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (result == true) {
|
||||||
|
onPostDelete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
|
|
||||||
name: flutter_timeline_view
|
name: flutter_timeline_view
|
||||||
description: Visual elements of the Flutter Timeline Component
|
description: Visual elements of the Flutter Timeline Component
|
||||||
version: 3.0.1
|
version: 4.0.0
|
||||||
|
|
||||||
publish_to: none
|
publish_to: none
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ dependencies:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_timeline
|
url: https://github.com/Iconica-Development/flutter_timeline
|
||||||
path: packages/flutter_timeline_interface
|
path: packages/flutter_timeline_interface
|
||||||
ref: 3.0.1
|
ref: 4.0.0
|
||||||
flutter_image_picker:
|
flutter_image_picker:
|
||||||
git:
|
git:
|
||||||
url: https://github.com/Iconica-Development/flutter_image_picker
|
url: https://github.com/Iconica-Development/flutter_image_picker
|
||||||
|
|
Loading…
Reference in a new issue