Merge pull request #42 from Iconica-Development/3.0.0

feat: default styling and flow
This commit is contained in:
Gorter-dev 2024-04-25 15:03:51 +02:00 committed by GitHub
commit 423f4ce03a
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 791 additions and 414 deletions

View file

@ -1,3 +1,6 @@
## 3.0.0
- Add default styling and default flow
## 2.3.1 ## 2.3.1
- Updated readme. - Updated readme.

View file

@ -43,9 +43,9 @@ List<GoRoute> getTimelineStoryRoutes({
); );
var button = FloatingActionButton( var button = FloatingActionButton(
backgroundColor: Theme.of(context).primaryColor, backgroundColor: const Color(0xff71C6D1),
onPressed: () async => context.go( onPressed: () async => context.push(
TimelineUserStoryRoutes.timelinePostCreation, TimelineUserStoryRoutes.timelineCategorySelection,
), ),
shape: const CircleBorder(), shape: const CircleBorder(),
child: const Icon( child: const Icon(
@ -62,10 +62,17 @@ List<GoRoute> getTimelineStoryRoutes({
?.call(context, timelineScreen, button) ?? ?.call(context, timelineScreen, button) ??
Scaffold( Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.black, backgroundColor: const Color(0xff212121),
title: Text( title: Text(
'Iconinstagram', config
style: Theme.of(context).textTheme.titleLarge, .optionsBuilder(context)
.translations
.timeLineScreenTitle!,
style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
), ),
), ),
body: timelineScreen, body: timelineScreen,
@ -74,6 +81,51 @@ List<GoRoute> getTimelineStoryRoutes({
); );
}, },
), ),
GoRoute(
path: TimelineUserStoryRoutes.timelineCategorySelection,
pageBuilder: (context, state) {
var timelineSelectionScreen = TimelineSelectionScreen(
options: config.optionsBuilder(context),
categories: config
.optionsBuilder(context)
.categoriesOptions
.categoriesBuilder!(context),
onCategorySelected: (category) async {
await context.push(
TimelineUserStoryRoutes.timelinepostCreation(category.title),
);
},
);
var backButton = IconButton(
color: Colors.white,
icon: const Icon(Icons.arrow_back_ios),
onPressed: () => context.go(TimelineUserStoryRoutes.timelineHome),
);
return buildScreenWithoutTransition(
context: context,
state: state,
child: config.categorySelectionOpenPageBuilder
?.call(context, timelineSelectionScreen) ??
Scaffold(
appBar: AppBar(
leading: backButton,
backgroundColor: const Color(0xff212121),
title: Text(
config.optionsBuilder(context).translations.postCreation!,
style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
),
),
body: timelineSelectionScreen,
),
);
},
),
GoRoute( GoRoute(
path: TimelineUserStoryRoutes.timelineView, path: TimelineUserStoryRoutes.timelineView,
pageBuilder: (context, state) { pageBuilder: (context, state) {
@ -103,10 +155,14 @@ List<GoRoute> getTimelineStoryRoutes({
Scaffold( Scaffold(
appBar: AppBar( appBar: AppBar(
leading: backButton, leading: backButton,
backgroundColor: Colors.black, backgroundColor: const Color(0xff212121),
title: Text( title: Text(
'Category', post.category ?? 'Category',
style: Theme.of(context).textTheme.titleLarge, style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
), ),
), ),
body: timelinePostWidget, body: timelinePostWidget,
@ -117,6 +173,7 @@ List<GoRoute> getTimelineStoryRoutes({
GoRoute( GoRoute(
path: TimelineUserStoryRoutes.timelinePostCreation, path: TimelineUserStoryRoutes.timelinePostCreation,
pageBuilder: (context, state) { pageBuilder: (context, state) {
var category = state.pathParameters['category'];
var timelinePostCreationWidget = TimelinePostCreationScreen( var timelinePostCreationWidget = TimelinePostCreationScreen(
userId: config.userId, userId: config.userId,
options: config.optionsBuilder(context), options: config.optionsBuilder(context),
@ -137,11 +194,16 @@ List<GoRoute> getTimelineStoryRoutes({
extra: post, extra: post,
), ),
enablePostOverviewScreen: config.enablePostOverviewScreen, enablePostOverviewScreen: config.enablePostOverviewScreen,
postCategory: category,
); );
var backButton = IconButton( var backButton = IconButton(
icon: const Icon(Icons.arrow_back_ios), icon: const Icon(
onPressed: () => context.go(TimelineUserStoryRoutes.timelineHome), Icons.arrow_back_ios,
color: Colors.white,
),
onPressed: () =>
context.go(TimelineUserStoryRoutes.timelineCategorySelection),
); );
return buildScreenWithoutTransition( return buildScreenWithoutTransition(
@ -151,12 +213,16 @@ List<GoRoute> getTimelineStoryRoutes({
?.call(context, timelinePostCreationWidget, backButton) ?? ?.call(context, timelinePostCreationWidget, backButton) ??
Scaffold( Scaffold(
appBar: AppBar( appBar: AppBar(
backgroundColor: Colors.black, backgroundColor: const Color(0xff212121),
title: Text(
config.optionsBuilder(context).translations.postCreation,
style: Theme.of(context).textTheme.titleLarge,
),
leading: backButton, leading: backButton,
title: Text(
config.optionsBuilder(context).translations.postCreation!,
style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
),
), ),
body: timelinePostCreationWidget, body: timelinePostCreationWidget,
), ),
@ -179,6 +245,13 @@ List<GoRoute> getTimelineStoryRoutes({
} }
}, },
); );
var backButton = IconButton(
icon: const Icon(
Icons.arrow_back_ios,
color: Colors.white,
),
onPressed: () async => context.pop(),
);
return buildScreenWithoutTransition( return buildScreenWithoutTransition(
context: context, context: context,
@ -187,7 +260,21 @@ List<GoRoute> getTimelineStoryRoutes({
context, context,
timelinePostOverviewWidget, timelinePostOverviewWidget,
) ?? ) ??
timelinePostOverviewWidget, Scaffold(
appBar: AppBar(
leading: backButton,
backgroundColor: const Color(0xff212121),
title: Text(
config.optionsBuilder(context).translations.postOverview!,
style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
),
),
body: timelinePostOverviewWidget,
),
); );
}, },
), ),

View file

@ -44,23 +44,11 @@ Widget _timelineScreenRoute({
optionsBuilder: (context) => const TimelineOptions(), optionsBuilder: (context) => const TimelineOptions(),
); );
return Scaffold( var timelineScreen = TimelineScreen(
appBar: AppBar(), userId: config.userId,
floatingActionButton: FloatingActionButton( onUserTap: (user) => config.onUserTap?.call(context, user),
onPressed: () async => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postCreationScreenRoute(
configuration: config,
context: context,
),
),
),
child: const Icon(Icons.add),
),
body: TimelineScreen(
service: config.service, service: config.service,
options: config.optionsBuilder(context), options: config.optionsBuilder(context),
userId: config.userId,
onPostTap: (post) async => onPostTap: (post) async =>
config.onPostTap?.call(context, post) ?? config.onPostTap?.call(context, post) ??
Navigator.of(context).push( Navigator.of(context).push(
@ -72,12 +60,43 @@ Widget _timelineScreenRoute({
), ),
), ),
), ),
onUserTap: (userId) {
config.onUserTap?.call(context, userId);
},
filterEnabled: config.filterEnabled, filterEnabled: config.filterEnabled,
postWidgetBuilder: config.postWidgetBuilder, postWidgetBuilder: config.postWidgetBuilder,
);
var button = FloatingActionButton(
backgroundColor: const Color(0xff71C6D1),
onPressed: () async => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postCategorySelectionScreen(
configuration: config,
context: context,
), ),
),
),
shape: const CircleBorder(),
child: const Icon(
Icons.add,
color: Colors.white,
size: 30,
),
);
return config.homeOpenPageBuilder?.call(context, timelineScreen, button) ??
Scaffold(
appBar: AppBar(
backgroundColor: const Color(0xff212121),
title: Text(
config.optionsBuilder(context).translations.timeLineScreenTitle!,
style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
),
),
body: timelineScreen,
floatingActionButton: button,
); );
} }
@ -101,15 +120,39 @@ Widget _postDetailScreenRoute({
optionsBuilder: (context) => const TimelineOptions(), optionsBuilder: (context) => const TimelineOptions(),
); );
return TimelinePostScreen( var timelinePostScreen = TimelinePostScreen(
userId: config.userId, userId: config.userId,
service: config.service,
options: config.optionsBuilder(context), options: config.optionsBuilder(context),
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); await config.service.postService.deletePost(post),
}, onUserTap: (user) => config.onUserTap?.call(context, user),
);
var backButton = IconButton(
color: Colors.white,
icon: const Icon(Icons.arrow_back_ios),
onPressed: () => Navigator.of(context).pop(),
);
return config.postViewOpenPageBuilder
?.call(context, timelinePostScreen, backButton) ??
Scaffold(
appBar: AppBar(
leading: backButton,
backgroundColor: const Color(0xff212121),
title: Text(
post.category ?? 'Category',
style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
),
),
body: timelinePostScreen,
); );
} }
@ -120,6 +163,7 @@ Widget _postDetailScreenRoute({
/// as parameters. If no configuration is provided, default values will be used. /// as parameters. If no configuration is provided, default values will be used.
Widget _postCreationScreenRoute({ Widget _postCreationScreenRoute({
required BuildContext context, required BuildContext context,
required TimelineCategory category,
TimelineUserStoryConfiguration? configuration, TimelineUserStoryConfiguration? configuration,
}) { }) {
var config = configuration ?? var config = configuration ??
@ -131,19 +175,12 @@ Widget _postCreationScreenRoute({
optionsBuilder: (context) => const TimelineOptions(), optionsBuilder: (context) => const TimelineOptions(),
); );
return Scaffold( var timelinePostCreationScreen = TimelinePostCreationScreen(
appBar: AppBar(
title: Text(
style: Theme.of(context).textTheme.titleLarge,
config.optionsBuilder(context).translations.postCreation,
),
),
body: TimelinePostCreationScreen(
userId: config.userId, userId: config.userId,
service: config.service,
options: config.optionsBuilder(context), options: config.optionsBuilder(context),
service: config.service,
onPostCreated: (post) async { onPostCreated: (post) async {
await config.service.postService.createPost(post); var newPost = await config.service.postService.createPost(post);
if (context.mounted) { if (context.mounted) {
if (config.afterPostCreationGoHome) { if (config.afterPostCreationGoHome) {
await Navigator.pushReplacement( await Navigator.pushReplacement(
@ -156,21 +193,20 @@ Widget _postCreationScreenRoute({
), ),
); );
} else { } else {
await Navigator.pushReplacement( await Navigator.push(
context, context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => _postDetailScreenRoute( builder: (context) => _postOverviewScreenRoute(
configuration: config, configuration: config,
context: context, context: context,
post: post, post: newPost,
), ),
), ),
); );
} }
} }
}, },
onPostOverview: (post) async { onPostOverview: (post) async => Navigator.of(context).push(
await Navigator.of(context).push(
MaterialPageRoute( MaterialPageRoute(
builder: (context) => _postOverviewScreenRoute( builder: (context) => _postOverviewScreenRoute(
configuration: config, configuration: config,
@ -178,10 +214,35 @@ Widget _postCreationScreenRoute({
post: post, post: post,
), ),
), ),
);
},
enablePostOverviewScreen: config.enablePostOverviewScreen,
), ),
enablePostOverviewScreen: config.enablePostOverviewScreen,
postCategory: category.title,
);
var backButton = IconButton(
icon: const Icon(
Icons.arrow_back_ios,
color: Colors.white,
),
onPressed: () => Navigator.of(context).pop(),
);
return config.postCreationOpenPageBuilder
?.call(context, timelinePostCreationScreen, backButton) ??
Scaffold(
appBar: AppBar(
backgroundColor: const Color(0xff212121),
leading: backButton,
title: Text(
config.optionsBuilder(context).translations.postCreation!,
style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
),
),
body: timelinePostCreationScreen,
); );
} }
@ -205,21 +266,109 @@ Widget _postOverviewScreenRoute({
optionsBuilder: (context) => const TimelineOptions(), optionsBuilder: (context) => const TimelineOptions(),
); );
return TimelinePostOverviewScreen( var timelinePostOverviewWidget = TimelinePostOverviewScreen(
timelinePost: post,
options: config.optionsBuilder(context), options: config.optionsBuilder(context),
service: config.service, service: config.service,
timelinePost: post,
onPostSubmit: (post) async { onPostSubmit: (post) async {
await config.service.postService.createPost(post); await config.service.postService.createPost(post);
if (context.mounted) { if (context.mounted) {
await Navigator.pushReplacement( await Navigator.of(context).pushAndRemoveUntil(
context,
MaterialPageRoute( MaterialPageRoute(
builder: (context) => builder: (context) =>
_timelineScreenRoute(configuration: config, context: context), _timelineScreenRoute(configuration: config, context: context),
), ),
(route) => false,
); );
} }
}, },
isOverviewScreen: true,
);
var backButton = IconButton(
icon: const Icon(
Icons.arrow_back_ios,
color: Colors.white,
),
onPressed: () async => Navigator.of(context).pop(),
);
return config.postOverviewOpenPageBuilder?.call(
context,
timelinePostOverviewWidget,
) ??
Scaffold(
appBar: AppBar(
leading: backButton,
backgroundColor: const Color(0xff212121),
title: Text(
config.optionsBuilder(context).translations.postOverview!,
style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
),
),
body: timelinePostOverviewWidget,
);
}
Widget _postCategorySelectionScreen({
required BuildContext context,
TimelineUserStoryConfiguration? configuration,
}) {
var config = configuration ??
TimelineUserStoryConfiguration(
userId: 'test_user',
service: TimelineService(
postService: LocalTimelinePostService(),
),
optionsBuilder: (context) => const TimelineOptions(),
);
var timelineSelectionScreen = TimelineSelectionScreen(
options: config.optionsBuilder(context),
categories: config
.optionsBuilder(context)
.categoriesOptions
.categoriesBuilder!(context),
onCategorySelected: (category) async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postCreationScreenRoute(
configuration: config,
context: context,
category: category,
),
),
);
},
);
var backButton = IconButton(
color: Colors.white,
icon: const Icon(Icons.arrow_back_ios),
onPressed: () async {
Navigator.of(context).pop();
},
);
return config.categorySelectionOpenPageBuilder
?.call(context, timelineSelectionScreen) ??
Scaffold(
appBar: AppBar(
leading: backButton,
backgroundColor: const Color(0xff212121),
title: Text(
config.optionsBuilder(context).translations.postCreation!,
style: const TextStyle(
color: Color(0xff71C6D1),
fontSize: 24,
fontWeight: FontWeight.w800,
),
),
),
body: timelineSelectionScreen,
); );
} }

View file

@ -59,7 +59,8 @@ class TimelineUserStoryConfiguration {
this.filterEnabled = false, this.filterEnabled = false,
this.postWidgetBuilder, this.postWidgetBuilder,
this.afterPostCreationGoHome = false, this.afterPostCreationGoHome = false,
this.enablePostOverviewScreen = false, this.enablePostOverviewScreen = true,
this.categorySelectionOpenPageBuilder,
}); });
/// The ID of the user associated with this user story configuration. /// The ID of the user associated with this user story configuration.
@ -132,4 +133,11 @@ class TimelineUserStoryConfiguration {
/// Boolean to enable redirect to home after post creation. /// Boolean to enable redirect to home after post creation.
/// If false, it will redirect to created post screen /// If false, it will redirect to created post screen
final bool afterPostCreationGoHome; final bool afterPostCreationGoHome;
/// Open page builder function for the category selection page. This function
/// accepts a [BuildContext] and a child widget.
final Function(
BuildContext context,
Widget child,
)? categorySelectionOpenPageBuilder;
} }

View file

@ -6,6 +6,11 @@ mixin TimelineUserStoryRoutes {
static const String timelineHome = '/timeline'; static const String timelineHome = '/timeline';
static const String timelineView = '/timeline-view/:post'; static const String timelineView = '/timeline-view/:post';
static String timelineViewPath(String postId) => '/timeline-view/$postId'; static String timelineViewPath(String postId) => '/timeline-view/$postId';
static const String timelinePostCreation = '/timeline-post-creation'; static String timelinepostCreation(String category) =>
'/timeline-post-creation/$category';
static const String timelinePostCreation =
'/timeline-post-creation/:category';
static String timelinePostOverview = '/timeline-post-overview'; static String timelinePostOverview = '/timeline-post-overview';
static String timelineCategorySelection = '/timeline-category-selection';
} }

View file

@ -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: 2.3.1 version: 3.0.0
publish_to: none publish_to: none
@ -19,13 +19,13 @@ dependencies:
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: 2.3.1 ref: 3.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: 2.3.1 ref: 3.0.0
dev_dependencies: dev_dependencies:
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0

View file

@ -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: 2.3.1 version: 3.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: 2.3.1 ref: 3.0.0
dev_dependencies: dev_dependencies:
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0

View file

@ -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: 2.3.1 version: 3.0.0
publish_to: none publish_to: none

View file

@ -12,7 +12,7 @@ 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.empty(), this.translations = const TimelineTranslations(),
this.imagePickerConfig = const ImagePickerConfig(), this.imagePickerConfig = const ImagePickerConfig(),
this.imagePickerTheme = const ImagePickerTheme(), this.imagePickerTheme = const ImagePickerTheme(),
this.timelinePostHeight, this.timelinePostHeight,
@ -37,7 +37,8 @@ class TimelineOptions {
this.userAvatarBuilder, this.userAvatarBuilder,
this.anonymousAvatarBuilder, this.anonymousAvatarBuilder,
this.nameBuilder, this.nameBuilder,
this.padding = const EdgeInsets.symmetric(vertical: 12.0), 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 = this.postPadding =
@ -49,6 +50,10 @@ class TimelineOptions {
this.maxTitleLength, this.maxTitleLength,
this.minContentLength, this.minContentLength,
this.maxContentLength, this.maxContentLength,
this.categorySelectorButtonBuilder,
this.postOverviewButtonBuilder,
this.titleInputDecoration,
this.contentInputDecoration,
}); });
/// Theming options for the timeline /// Theming options for the timeline
@ -142,11 +147,69 @@ class TimelineOptions {
/// Maximum length of the post content /// Maximum length of the post content
final int? maxContentLength; final int? maxContentLength;
/// Builder for the category selector button
/// on the timeline category selection screen
final Widget Function(
BuildContext context,
Function() onPressed,
String text,
)? categorySelectorButtonBuilder;
/// Builder for the post overview button
/// on the timeline post overview screen
final Widget Function(
BuildContext context,
Function() onPressed,
String text,
)? postOverviewButtonBuilder;
/// inputdecoration for the title textfield
final InputDecoration? titleInputDecoration;
/// inputdecoration for the content textfield
final InputDecoration? contentInputDecoration;
} }
List<TimelineCategory> _getDefaultCategories(context) => [
const TimelineCategory(
key: null,
title: 'All',
icon: Padding(
padding: EdgeInsets.only(right: 8.0),
child: Icon(
Icons.apps,
color: Colors.black,
),
),
),
const TimelineCategory(
key: 'Category',
title: 'Category',
icon: Padding(
padding: EdgeInsets.only(right: 8.0),
child: Icon(
Icons.category,
color: Colors.black,
),
),
),
const TimelineCategory(
key: 'Category with two lines',
title: 'Category with two lines',
icon: Padding(
padding: EdgeInsets.only(right: 8.0),
child: Icon(
Icons.category,
color: Colors.black,
),
),
),
];
class CategoriesOptions { class CategoriesOptions {
const CategoriesOptions({ const CategoriesOptions({
this.categoriesBuilder, this.categoriesBuilder = _getDefaultCategories,
this.categoryButtonBuilder, this.categoryButtonBuilder,
this.categorySelectorHorizontalPadding, this.categorySelectorHorizontalPadding,
}); });
@ -157,12 +220,14 @@ class CategoriesOptions {
categoriesBuilder; categoriesBuilder;
/// Abilty to override the standard category selector /// Abilty to override the standard category selector
final Widget Function({ final Widget Function(
required String? categoryKey, String? categoryKey,
required String categoryName, String categoryName,
required Function onTap, Function() onTap,
required bool selected, // ignore: avoid_positional_boolean_parameters
})? categoryButtonBuilder; bool selected,
bool isOnTop,
)? categoryButtonBuilder;
/// Overides the standard horizontal padding of the whole category selector. /// Overides the standard horizontal padding of the whole category selector.
final double? categorySelectorHorizontalPadding; final double? categorySelectorHorizontalPadding;

View file

@ -7,108 +7,78 @@ import 'package:flutter/material.dart';
@immutable @immutable
class TimelineTranslations { class TimelineTranslations {
const TimelineTranslations({ const TimelineTranslations({
required this.anonymousUser, this.anonymousUser = 'Anonymous user',
required this.noPosts, this.noPosts = 'No posts yet',
required this.noPostsWithFilter, this.noPostsWithFilter = 'No posts with this filter',
required this.title, this.title = 'Title',
required this.titleHintText, this.titleHintText = 'Title...',
required this.content, this.content = 'Content',
required this.contentHintText, this.contentHintText = 'Content...',
required this.contentDescription, this.contentDescription = 'What do you want to share?',
required this.uploadImage, this.uploadImage = 'Upload image',
required this.uploadImageDescription, this.uploadImageDescription = 'Upload an image to your message (optional)',
required this.allowComments, this.allowComments = 'Are people allowed to comment?',
required this.allowCommentsDescription, this.allowCommentsDescription =
required this.commentsTitleOnPost, 'Indicate whether people are allowed to respond',
required this.checkPost, this.commentsTitleOnPost = 'Comments',
required this.deletePost, this.checkPost = 'Check post overview',
required this.deleteReaction, this.deletePost = 'Delete post',
required this.viewPost, this.deleteReaction = 'Delete Reaction',
required this.likesTitle, this.viewPost = 'View post',
required this.commentsTitle, this.likesTitle = 'Likes',
required this.firstComment, this.commentsTitle = 'Are people allowed to comment?',
required this.writeComment, this.firstComment = 'Be the first to comment',
required this.postAt, this.writeComment = 'Write your comment here...',
required this.postLoadingError, this.postAt = 'at',
required this.timelineSelectionDescription, this.postLoadingError = 'Something went wrong while loading the post',
required this.searchHint, this.timelineSelectionDescription = 'Choose a category',
required this.postOverview, this.searchHint = 'Search...',
required this.postIn, this.postOverview = 'Post Overview',
required this.postCreation, this.postIn = 'Post in',
required this.yes, this.postCreation = 'add post',
required this.no, this.yes = 'Yes',
this.no = 'No',
this.timeLineScreenTitle = 'iconinstagram',
}); });
const TimelineTranslations.empty() final String? noPosts;
: anonymousUser = 'Anonymous user', final String? noPostsWithFilter;
noPosts = 'No posts yet', final String? anonymousUser;
noPostsWithFilter = 'No posts with this filter',
title = 'Title',
content = 'Content',
contentDescription = 'What do you want to share?',
uploadImage = 'Upload image',
uploadImageDescription = 'Upload an image to your message (optional)',
allowComments = 'Are people allowed to comment?',
allowCommentsDescription =
'Indicate whether people are allowed to respond',
commentsTitleOnPost = 'Comments',
checkPost = 'Check post overview',
deletePost = 'Delete post',
deleteReaction = 'Delete Reaction',
viewPost = 'View post',
likesTitle = 'Likes',
commentsTitle = 'Are people allowed to comment?',
firstComment = 'Be the first to comment',
writeComment = 'Write your comment here...',
postAt = 'at',
postLoadingError = 'Something went wrong while loading the post',
timelineSelectionDescription = 'Choose a category',
searchHint = 'Search...',
postOverview = 'Post Overview',
postIn = 'Post in',
postCreation = 'Create Post',
titleHintText = 'Title...',
contentHintText = 'Context...',
yes = 'Yes',
no = 'No';
final String noPosts; final String? title;
final String noPostsWithFilter; final String? content;
final String anonymousUser; final String? contentDescription;
final String? uploadImage;
final String? uploadImageDescription;
final String? allowComments;
final String? allowCommentsDescription;
final String? checkPost;
final String? postAt;
final String title; final String? titleHintText;
final String content; final String? contentHintText;
final String contentDescription;
final String uploadImage;
final String uploadImageDescription;
final String allowComments;
final String allowCommentsDescription;
final String checkPost;
final String postAt;
final String titleHintText; final String? deletePost;
final String contentHintText; 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 deletePost; 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 timelineSelectionDescription; final String? searchHint;
final String searchHint; final String? postOverview;
final String? postIn;
final String? postCreation;
final String postOverview; final String? yes;
final String postIn; final String? no;
final String postCreation; final String? timeLineScreenTitle;
final String yes;
final String no;
TimelineTranslations copyWith({ TimelineTranslations copyWith({
String? noPosts, String? noPosts,
@ -141,6 +111,7 @@ class TimelineTranslations {
String? contentHintText, String? contentHintText,
String? yes, String? yes,
String? no, String? no,
String? timeLineScreenTitle,
}) => }) =>
TimelineTranslations( TimelineTranslations(
noPosts: noPosts ?? this.noPosts, noPosts: noPosts ?? this.noPosts,
@ -176,5 +147,6 @@ class TimelineTranslations {
contentHintText: contentHintText ?? this.contentHintText, contentHintText: contentHintText ?? this.contentHintText,
yes: yes ?? this.yes, yes: yes ?? this.yes,
no: no ?? this.no, no: no ?? this.no,
timeLineScreenTitle: timeLineScreenTitle ?? this.timeLineScreenTitle,
); );
} }

View file

@ -124,12 +124,15 @@ class _TimelinePostCreationScreenState
padding: widget.options.padding, padding: widget.options.padding,
child: SingleChildScrollView( child: SingleChildScrollView(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.max,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
widget.options.translations.title, widget.options.translations.title!,
style: theme.textTheme.titleMedium, style: const TextStyle(
fontWeight: FontWeight.w800,
fontSize: 20,
),
), ),
widget.options.textInputBuilder?.call( widget.options.textInputBuilder?.call(
titleController, titleController,
@ -138,44 +141,49 @@ class _TimelinePostCreationScreenState
) ?? ) ??
TextField( TextField(
controller: titleController, controller: titleController,
decoration: InputDecoration( decoration: widget.options.contentInputDecoration ??
InputDecoration(
hintText: widget.options.translations.titleHintText, hintText: widget.options.translations.titleHintText,
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
widget.options.translations.content, widget.options.translations.content!,
style: theme.textTheme.titleMedium, style: const TextStyle(
fontWeight: FontWeight.w800,
fontSize: 20,
),
), ),
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
SizedBox( TextField(
height: 100,
child: TextField(
controller: contentController, controller: contentController,
textCapitalization: TextCapitalization.sentences, textCapitalization: TextCapitalization.sentences,
expands: true, expands: false,
maxLines: null, maxLines: null,
minLines: null, minLines: null,
decoration: InputDecoration( decoration: widget.options.contentInputDecoration ??
InputDecoration(
hintText: widget.options.translations.contentHintText, hintText: widget.options.translations.contentHintText,
), ),
), ),
),
const SizedBox( const SizedBox(
height: 16, height: 16,
), ),
// input field for the content // input field for the content
Text( Text(
widget.options.translations.uploadImage, widget.options.translations.uploadImage!,
style: theme.textTheme.titleMedium, style: const TextStyle(
fontWeight: FontWeight.w800,
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
@ -262,17 +270,21 @@ class _TimelinePostCreationScreenState
const SizedBox(height: 16), const SizedBox(height: 16),
Text( Text(
widget.options.translations.commentsTitle, widget.options.translations.commentsTitle!,
style: theme.textTheme.titleMedium, style: const TextStyle(
fontWeight: FontWeight.w800,
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),
value: allowComments, value: allowComments,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -280,8 +292,9 @@ class _TimelinePostCreationScreenState
}); });
}, },
), ),
Text(widget.options.translations.yes), Text(widget.options.translations.yes!),
Checkbox( Checkbox(
activeColor: const Color(0xff71C6D1),
value: !allowComments, value: !allowComments,
onChanged: (value) { onChanged: (value) {
setState(() { setState(() {
@ -289,9 +302,10 @@ class _TimelinePostCreationScreenState
}); });
}, },
), ),
Text(widget.options.translations.no), Text(widget.options.translations.no!),
], ],
), ),
const SizedBox(height: 120),
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
@ -299,10 +313,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(
backgroundColor:
MaterialStatePropertyAll(Color(0xff71C6D1)),
),
onPressed: editingDone onPressed: editingDone
? () async { ? () async {
await onPostCreated(); await onPostCreated();
@ -310,11 +328,18 @@ class _TimelinePostCreationScreenState
.fetchPosts(null); .fetchPosts(null);
} }
: null, : null,
child: Padding(
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: theme.textTheme.bodyMedium, style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w800,
),
),
), ),
), ),
), ),

View file

@ -10,24 +10,18 @@ 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) {
return Scaffold( return Column(
appBar: AppBar(
backgroundColor: Colors.black,
title: Text(
options.translations.postOverview,
style: TextStyle(color: Theme.of(context).primaryColor),
),
),
body: Column(
children: [ children: [
Flexible( Flexible(
child: TimelinePostScreen( child: TimelinePostScreen(
@ -36,21 +30,39 @@ class TimelinePostOverviewScreen extends StatelessWidget {
post: timelinePost, post: timelinePost,
onPostDelete: () async {}, onPostDelete: () async {},
service: service, service: service,
isOverviewScreen: isOverviewScreen,
), ),
), ),
options.postOverviewButtonBuilder?.call(
context,
() {
onPostSubmit(timelinePost);
},
'${options.translations.postIn} ${timelinePost.category}',
) ??
Padding( Padding(
padding: const EdgeInsets.only(bottom: 30.0), padding: const EdgeInsets.only(bottom: 30.0),
child: ElevatedButton( child: ElevatedButton(
style: const ButtonStyle(
backgroundColor: MaterialStatePropertyAll(Color(0xff71C6D1)),
),
onPressed: () { onPressed: () {
onPostSubmit(timelinePost); onPostSubmit(timelinePost);
}, },
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Text( child: Text(
'${options.translations.postIn} ${timelinePost.category}', '${options.translations.postIn} ${timelinePost.category}',
style: const TextStyle(
color: Colors.white,
fontSize: 20,
fontWeight: FontWeight.w800,
),
),
), ),
), ),
), ),
], ],
),
); );
} }
} }

View file

@ -22,6 +22,7 @@ class TimelinePostScreen extends StatelessWidget {
required this.options, required this.options,
required this.post, required this.post,
required this.onPostDelete, required this.onPostDelete,
this.isOverviewScreen = false,
this.onUserTap, this.onUserTap,
super.key, super.key,
}); });
@ -43,6 +44,8 @@ class TimelinePostScreen extends StatelessWidget {
final VoidCallback onPostDelete; final VoidCallback onPostDelete;
final bool? isOverviewScreen;
@override @override
Widget build(BuildContext context) => Scaffold( Widget build(BuildContext context) => Scaffold(
body: _TimelinePostScreen( body: _TimelinePostScreen(
@ -52,6 +55,7 @@ class TimelinePostScreen extends StatelessWidget {
post: post, post: post,
onPostDelete: onPostDelete, onPostDelete: onPostDelete,
onUserTap: onUserTap, onUserTap: onUserTap,
isOverviewScreen: isOverviewScreen,
), ),
); );
} }
@ -64,6 +68,7 @@ class _TimelinePostScreen extends StatefulWidget {
required this.post, required this.post,
required this.onPostDelete, required this.onPostDelete,
this.onUserTap, this.onUserTap,
this.isOverviewScreen,
}); });
final String userId; final String userId;
@ -78,6 +83,8 @@ class _TimelinePostScreen extends StatefulWidget {
final VoidCallback onPostDelete; final VoidCallback onPostDelete;
final bool? isOverviewScreen;
@override @override
State<_TimelinePostScreen> createState() => _TimelinePostScreenState(); State<_TimelinePostScreen> createState() => _TimelinePostScreenState();
} }
@ -145,7 +152,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
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,
), ),
); );
@ -214,7 +221,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,
@ -234,7 +241,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,
@ -257,8 +264,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
), ),
], ],
), ),
// image of the post // image of the posts
if (post.imageUrl != null) ...[ if (post.imageUrl != null || post.image != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
ClipRRect( ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)), borderRadius: const BorderRadius.all(Radius.circular(8)),
@ -293,6 +300,12 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
false; false;
}, },
) )
: post.image != null
? Image.memory(
width: double.infinity,
post.image!,
fit: BoxFit.fitHeight,
)
: CachedNetworkImage( : CachedNetworkImage(
width: double.infinity, width: double.infinity,
imageUrl: post.imageUrl!, imageUrl: post.imageUrl!,
@ -320,9 +333,11 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
color: Colors.transparent, color: Colors.transparent,
child: widget.options.theme.likedIcon ?? child: widget.options.theme.likedIcon ??
Icon( Icon(
Icons.thumb_up_rounded, widget.post.likedBy
color: widget.options.theme.iconColor, ?.contains(widget.userId) ??
size: widget.options.iconSize, false
? Icons.favorite_rounded
: Icons.favorite_outline_outlined,
), ),
), ),
), ),
@ -340,8 +355,11 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
color: Colors.transparent, color: Colors.transparent,
child: widget.options.theme.likeIcon ?? child: widget.options.theme.likeIcon ??
Icon( Icon(
Icons.thumb_up_alt_outlined, widget.post.likedBy
color: widget.options.theme.iconColor, ?.contains(widget.userId) ??
false
? Icons.favorite_rounded
: Icons.favorite_outline_outlined,
size: widget.options.iconSize, size: widget.options.iconSize,
), ),
), ),
@ -423,7 +441,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
@ -451,7 +469,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!,
), ),
), ),
], ],
@ -505,7 +523,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
?.call(post.creator) ?? ?.call(post.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(
@ -547,7 +565,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),
@ -557,7 +575,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
), ),
), ),
), ),
if (post.reactionEnabled) if (post.reactionEnabled && !widget.isOverviewScreen!)
Align( Align(
alignment: Alignment.bottomCenter, alignment: Alignment.bottomCenter,
child: ReactionBottom( child: ReactionBottom(

View file

@ -273,8 +273,9 @@ 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.options.translations.noPostsWithFilter, : widget
.options.translations.noPostsWithFilter!,
style: widget.options.theme.textStyles.noPostsStyle, style: widget.options.theme.textStyles.noPostsStyle,
), ),
), ),

View file

@ -19,7 +19,6 @@ class TimelineSelectionScreen extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
var size = MediaQuery.of(context).size; var size = MediaQuery.of(context).size;
var theme = Theme.of(context);
return Padding( return Padding(
padding: EdgeInsets.symmetric( padding: EdgeInsets.symmetric(
horizontal: size.width * 0.05, horizontal: size.width * 0.05,
@ -30,33 +29,52 @@ 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: style: const TextStyle(
options.theme.textStyles.categorySelectionDescriptionStyle ?? fontWeight: FontWeight.w800,
theme.textTheme.displayMedium, fontSize: 20,
),
), ),
), ),
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,
)) ...[ )) ...[
options.categorySelectorButtonBuilder?.call(
context,
() {
onCategorySelected.call(category);
},
category.title,
) ??
InkWell( InkWell(
onTap: () => onCategorySelected.call(category), onTap: () => onCategorySelected.call(category),
child: Container( child: Container(
height: 60,
width: double.infinity, width: double.infinity,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
border: Border.all(
color: const Color(0xff71C6D1),
width: 2,
), ),
padding: const EdgeInsets.symmetric(
vertical: 26,
horizontal: 16,
), ),
margin: const EdgeInsets.symmetric(vertical: 8), margin: const EdgeInsets.symmetric(vertical: 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.center,
children: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 12.0),
child: Text( child: Text(
category.title, category.title,
style: options.theme.textStyles.categorySelectionTitleStyle ?? style: const TextStyle(
theme.textTheme.displaySmall, fontSize: 18,
fontWeight: FontWeight.w800,
),
),
),
],
), ),
), ),
), ),

View file

@ -21,8 +21,8 @@ class LocalTimelinePostService
userId: 'test_user', userId: 'test_user',
imageUrl: imageUrl:
'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop', 'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop',
firstName: 'Dirk', firstName: 'Ico',
lastName: 'lukassen', lastName: 'Nica',
), ),
), ),
); );
@ -174,8 +174,8 @@ class LocalTimelinePostService
userId: 'test_user', userId: 'test_user',
imageUrl: imageUrl:
'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop', 'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop',
firstName: 'Dirk', firstName: 'Ico',
lastName: 'lukassen', lastName: 'Nica',
), ),
); );
@ -197,63 +197,47 @@ class LocalTimelinePostService
TimelinePost( TimelinePost(
id: 'Post0', id: 'Post0',
creatorId: 'test_user', creatorId: 'test_user',
title: 'Post 0', title: 'De topper van de maand september',
category: null, category: 'Category',
imageUrl: imageUrl:
'https://t4.ftcdn.net/jpg/02/77/71/45/240_F_277714513_fQ0akmI3TQxa0wkPCLeO12Rx3cL2AuIf.jpg', 'https://firebasestorage.googleapis.com/v0/b/appshell-demo.appspot.com/o/do_not_delete_1.png?alt=media&token=e4b2f9f3-c81f-4ac7-a938-e846691399f7',
content: 'Standard post without image made by the current user', content: 'Dit is onze topper van de maand september! Gefeliciteerd!',
likes: 0, likes: 72,
reaction: 0, reaction: 0,
createdAt: DateTime.now(), createdAt: DateTime.now(),
reactionEnabled: false, reactionEnabled: true,
creator: const TimelinePosterUserModel( creator: const TimelinePosterUserModel(
userId: 'test_user', userId: 'test_user',
imageUrl: imageUrl:
'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop', 'https://firebasestorage.googleapis.com/v0/b/appshell-demo.appspot.com/o/do_not_delete_3.png?alt=media&token=cd7c156d-0dda-43be-9199-f7d31c30132e',
firstName: 'Dirk', firstName: 'Robin',
lastName: 'lukassen', lastName: 'De Vries',
), ),
), ),
TimelinePost( TimelinePost(
id: 'Post1', id: 'Post1',
creatorId: 'test_user2', creatorId: 'test_user2',
title: 'Post 1', title: 'De soep van de week is: Aspergesoep',
category: null, category: 'Category with two lines',
content: 'Standard post with image made by a different user and ' content:
'reactions enabled', 'Aspergesoep is echt een heerlijke delicatesse. Deze soep wordt'
likes: 0, ' vaak gemaakt met verse asperges, bouillon en wat kruiden voor'
reaction: 0, ' smaak. Het is een perfecte keuze voor een lichte en smaakvolle'
createdAt: DateTime.now(), ' maaltijd, vooral in het voorjaar wanneer asperges in seizoen'
reactionEnabled: false, ' zijn. We serveren het met een vleugje room en wat knapperige'
imageUrl: ' croutons voor die extra touch.',
'https://t4.ftcdn.net/jpg/02/77/71/45/240_F_277714513_fQ0akmI3TQxa0wkPCLeO12Rx3cL2AuIf.jpg', likes: 72,
creator: const TimelinePosterUserModel(
userId: 'test_user',
imageUrl:
'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop',
firstName: 'Dirk',
lastName: 'lukassen',
),
),
TimelinePost(
id: 'Post2',
creatorId: 'test_user',
title: 'Post 2',
category: null,
content: 'Standard post with image made by the current user and'
' reactions enabled',
likes: 0,
reaction: 0, reaction: 0,
createdAt: DateTime.now(), createdAt: DateTime.now(),
reactionEnabled: true, reactionEnabled: true,
imageUrl: imageUrl:
'https://t4.ftcdn.net/jpg/02/77/71/45/240_F_277714513_fQ0akmI3TQxa0wkPCLeO12Rx3cL2AuIf.jpg', 'https://firebasestorage.googleapis.com/v0/b/appshell-demo.appspot.com/o/do_not_delete_2.png?alt=media&token=ee4a8771-531f-4d1d-8613-a2366771e775',
creator: const TimelinePosterUserModel( creator: const TimelinePosterUserModel(
userId: 'test_user', userId: 'test_user',
imageUrl: imageUrl:
'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop', 'https://firebasestorage.googleapis.com/v0/b/appshell-demo.appspot.com/o/do_not_delete_4.png?alt=media&token=775d4d10-6d2b-4aef-a51b-ba746b7b137f',
firstName: 'Dirk', firstName: 'Elise',
lastName: 'lukassen', lastName: 'Welling',
), ),
), ),
]; ];

View file

@ -41,10 +41,11 @@ class _CategorySelectorState extends State<CategorySelector> {
), ),
for (var category in categories) ...[ for (var category in categories) ...[
widget.options.categoriesOptions.categoryButtonBuilder?.call( widget.options.categoriesOptions.categoryButtonBuilder?.call(
categoryKey: category.key, category.key,
categoryName: category.title, category.title,
onTap: () => widget.onTapCategory(category.key), () => widget.onTapCategory(category.key),
selected: widget.filter == category.key, widget.filter == category.key,
widget.isOnTop,
) ?? ) ??
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(horizontal: 4),

View file

@ -22,9 +22,8 @@ class CategorySelectorButton extends StatelessWidget {
Widget build(BuildContext context) { Widget build(BuildContext context) {
var theme = Theme.of(context); var theme = Theme.of(context);
return AnimatedContainer( return SizedBox(
height: isOnTop ? 140 : 40, height: isOnTop ? 140 : 40,
duration: const Duration(milliseconds: 100),
child: TextButton( child: TextButton(
onPressed: onTap, onPressed: onTap,
style: ButtonStyle( style: ButtonStyle(
@ -37,26 +36,26 @@ class CategorySelectorButton extends StatelessWidget {
), ),
fixedSize: MaterialStatePropertyAll(Size(140, isOnTop ? 140 : 20)), fixedSize: MaterialStatePropertyAll(Size(140, isOnTop ? 140 : 20)),
backgroundColor: MaterialStatePropertyAll( backgroundColor: MaterialStatePropertyAll(
selected ? theme.colorScheme.primary : Colors.transparent, selected ? const Color(0xff71C6D1) : Colors.transparent,
), ),
shape: MaterialStatePropertyAll( shape: const MaterialStatePropertyAll(
RoundedRectangleBorder( RoundedRectangleBorder(
borderRadius: const BorderRadius.all( borderRadius: BorderRadius.all(
Radius.circular(8), Radius.circular(8),
), ),
side: BorderSide( side: BorderSide(
color: theme.colorScheme.primary, color: Color(0xff71C6D1),
width: 2, width: 2,
), ),
), ),
), ),
), ),
child: Row( child: isOnTop
mainAxisAlignment: MainAxisAlignment.start, ? SizedBox(
children: [ width: MediaQuery.of(context).size.width,
Column( child: Column(
mainAxisAlignment: mainAxisAlignment: MainAxisAlignment.end,
isOnTop ? MainAxisAlignment.end : MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Text( Text(
category.title, category.title,
@ -67,9 +66,27 @@ class CategorySelectorButton extends StatelessWidget {
? theme.colorScheme.onPrimary ? theme.colorScheme.onPrimary
: theme.colorScheme.onSurface, : theme.colorScheme.onSurface,
), ),
textAlign: TextAlign.start,
), ),
], ],
), ),
)
: Row(
children: [
Flexible(
child: Text(
category.title,
style: (options.theme.textStyles.categoryTitleStyle ??
theme.textTheme.labelLarge)
?.copyWith(
color: selected
? theme.colorScheme.onPrimary
: theme.colorScheme.onSurface,
),
textAlign: TextAlign.start,
overflow: TextOverflow.ellipsis,
),
),
], ],
), ),
), ),

View file

@ -65,7 +65,7 @@ class _ReactionBottomState extends State<ReactionBottom> {
], ],
), ),
), ),
widget.translations.writeComment, widget.translations.writeComment!,
), ),
), ),
), ),

View file

@ -99,10 +99,16 @@ class _TappableImageState extends State<TappableImage>
offset: Offset(0, animation.value * -32), offset: Offset(0, animation.value * -32),
child: Transform.scale( child: Transform.scale(
scale: 1 + animation.value * 0.1, scale: 1 + animation.value * 0.1,
child: CachedNetworkImage( child: widget.post.imageUrl != null
? CachedNetworkImage(
imageUrl: widget.post.imageUrl ?? '', imageUrl: widget.post.imageUrl ?? '',
width: double.infinity, width: double.infinity,
fit: BoxFit.fitHeight, fit: BoxFit.fitHeight,
)
: Image.memory(
width: double.infinity,
widget.post.image!,
fit: BoxFit.fitHeight,
), ),
), ),
), ),

View file

@ -49,7 +49,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
return InkWell( return InkWell(
onTap: widget.onTap, onTap: widget.onTap,
child: SizedBox( child: SizedBox(
height: widget.post.imageUrl != null height: widget.post.imageUrl != null || widget.post.image != null
? widget.options.postWidgetHeight ? widget.options.postWidgetHeight
: null, : null,
width: double.infinity, width: double.infinity,
@ -94,7 +94,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,
@ -118,7 +118,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,
@ -142,7 +142,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
], ],
), ),
// image of the post // image of the post
if (widget.post.imageUrl != null) ...[ if (widget.post.imageUrl != null || widget.post.image != null) ...[
const SizedBox(height: 8), const SizedBox(height: 8),
Flexible( Flexible(
flex: widget.options.postWidgetHeight != null ? 1 : 0, flex: widget.options.postWidgetHeight != null ? 1 : 0,
@ -176,10 +176,16 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
return result.likedBy?.contains(userId) ?? false; return result.likedBy?.contains(userId) ?? false;
}, },
) )
: CachedNetworkImage( : widget.post.imageUrl != null
? CachedNetworkImage(
width: double.infinity, width: double.infinity,
imageUrl: widget.post.imageUrl!, imageUrl: widget.post.imageUrl!,
fit: BoxFit.fitWidth, fit: BoxFit.fitWidth,
)
: Image.memory(
width: double.infinity,
widget.post.image!,
fit: BoxFit.fitWidth,
), ),
), ),
), ),
@ -312,7 +318,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,
), ),

View file

@ -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: 2.3.1 version: 3.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: 2.3.1 ref: 3.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