feat: default styling and flow

This commit is contained in:
mike doornenbal 2024-04-25 14:47:16 +02:00
parent d4f7ec3768
commit 024f267ae5
22 changed files with 792 additions and 414 deletions

View file

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

View file

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

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_timeline/flutter_timeline.dart';
@ -44,41 +45,60 @@ Widget _timelineScreenRoute({
optionsBuilder: (context) => const TimelineOptions(),
);
return Scaffold(
appBar: AppBar(),
floatingActionButton: FloatingActionButton(
onPressed: () async => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postCreationScreenRoute(
configuration: config,
context: context,
),
),
),
child: const Icon(Icons.add),
),
body: TimelineScreen(
service: config.service,
options: config.optionsBuilder(context),
userId: config.userId,
onPostTap: (post) async =>
config.onPostTap?.call(context, post) ??
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postDetailScreenRoute(
configuration: config,
context: context,
post: post,
),
var timelineScreen = TimelineScreen(
userId: config.userId,
onUserTap: (user) => config.onUserTap?.call(context, user),
service: config.service,
options: config.optionsBuilder(context),
onPostTap: (post) async =>
config.onPostTap?.call(context, post) ??
Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postDetailScreenRoute(
configuration: config,
context: context,
post: post,
),
),
onUserTap: (userId) {
config.onUserTap?.call(context, userId);
},
filterEnabled: config.filterEnabled,
postWidgetBuilder: config.postWidgetBuilder,
),
filterEnabled: config.filterEnabled,
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,
);
}
/// A widget function that creates a post detail screen route.
@ -101,16 +121,40 @@ Widget _postDetailScreenRoute({
optionsBuilder: (context) => const TimelineOptions(),
);
return TimelinePostScreen(
var timelinePostScreen = TimelinePostScreen(
userId: config.userId,
service: config.service,
options: config.optionsBuilder(context),
service: config.service,
post: post,
onPostDelete: () async {
config.onPostDelete?.call(context, post) ??
await config.service.postService.deletePost(post);
},
onPostDelete: () async =>
config.onPostDelete?.call(context, 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,
);
}
/// A widget function that creates a post creation screen route.
@ -120,6 +164,7 @@ Widget _postDetailScreenRoute({
/// as parameters. If no configuration is provided, default values will be used.
Widget _postCreationScreenRoute({
required BuildContext context,
required TimelineCategory category,
TimelineUserStoryConfiguration? configuration,
}) {
var config = configuration ??
@ -131,58 +176,75 @@ Widget _postCreationScreenRoute({
optionsBuilder: (context) => const TimelineOptions(),
);
return Scaffold(
appBar: AppBar(
title: Text(
style: Theme.of(context).textTheme.titleLarge,
config.optionsBuilder(context).translations.postCreation,
var timelinePostCreationScreen = TimelinePostCreationScreen(
userId: config.userId,
options: config.optionsBuilder(context),
service: config.service,
onPostCreated: (post) async {
var newPost = await config.service.postService.createPost(post);
if (context.mounted) {
if (config.afterPostCreationGoHome) {
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => _timelineScreenRoute(
configuration: config,
context: context,
),
),
);
} else {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => _postOverviewScreenRoute(
configuration: config,
context: context,
post: newPost,
),
),
);
}
}
},
onPostOverview: (post) async => Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postOverviewScreenRoute(
configuration: config,
context: context,
post: post,
),
),
),
body: TimelinePostCreationScreen(
userId: config.userId,
service: config.service,
options: config.optionsBuilder(context),
onPostCreated: (post) async {
await config.service.postService.createPost(post);
if (context.mounted) {
if (config.afterPostCreationGoHome) {
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => _timelineScreenRoute(
configuration: config,
context: context,
),
),
);
} else {
await Navigator.pushReplacement(
context,
MaterialPageRoute(
builder: (context) => _postDetailScreenRoute(
configuration: config,
context: context,
post: post,
),
),
);
}
}
},
onPostOverview: (post) async {
await Navigator.of(context).push(
MaterialPageRoute(
builder: (context) => _postOverviewScreenRoute(
configuration: config,
context: context,
post: post,
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,
),
),
);
},
enablePostOverviewScreen: config.enablePostOverviewScreen,
),
);
),
body: timelinePostCreationScreen,
);
}
/// A widget function that creates a post overview screen route.
@ -205,21 +267,109 @@ Widget _postOverviewScreenRoute({
optionsBuilder: (context) => const TimelineOptions(),
);
return TimelinePostOverviewScreen(
timelinePost: post,
var timelinePostOverviewWidget = TimelinePostOverviewScreen(
options: config.optionsBuilder(context),
service: config.service,
timelinePost: post,
onPostSubmit: (post) async {
await config.service.postService.createPost(post);
if (context.mounted) {
await Navigator.pushReplacement(
context,
await Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(
builder: (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.postWidgetBuilder,
this.afterPostCreationGoHome = false,
this.enablePostOverviewScreen = false,
this.enablePostOverviewScreen = true,
this.categorySelectionOpenPageBuilder,
});
/// 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.
/// If false, it will redirect to created post screen
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 timelineView = '/timeline-view/:post';
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 timelineCategorySelection = '/timeline-category-selection';
}

View file

@ -3,7 +3,7 @@
# SPDX-License-Identifier: GPL-3.0-or-later
name: flutter_timeline
description: Visual elements and interface combined into one package
version: 2.3.1
version: 3.0.0
publish_to: none
@ -19,13 +19,13 @@ dependencies:
git:
url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_view
ref: 2.3.1
ref: 3.0.0
flutter_timeline_interface:
git:
url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface
ref: 2.3.1
ref: 3.0.0
dev_dependencies:
flutter_lints: ^2.0.0

View file

@ -4,7 +4,7 @@
name: flutter_timeline_firebase
description: Implementation of the Flutter Timeline interface for Firebase.
version: 2.3.1
version: 3.0.0
publish_to: none
@ -23,7 +23,7 @@ dependencies:
git:
url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface
ref: 2.3.1
ref: 3.0.0
dev_dependencies:
flutter_lints: ^2.0.0

View file

@ -4,7 +4,7 @@
name: flutter_timeline_interface
description: Interface for the service of the Flutter Timeline component
version: 2.3.1
version: 3.0.0
publish_to: none

View file

@ -12,7 +12,7 @@ import 'package:intl/intl.dart';
class TimelineOptions {
const TimelineOptions({
this.theme = const TimelineTheme(),
this.translations = const TimelineTranslations.empty(),
this.translations = const TimelineTranslations(),
this.imagePickerConfig = const ImagePickerConfig(),
this.imagePickerTheme = const ImagePickerTheme(),
this.timelinePostHeight,
@ -37,7 +37,8 @@ class TimelineOptions {
this.userAvatarBuilder,
this.anonymousAvatarBuilder,
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.postWidgetHeight,
this.postPadding =
@ -49,6 +50,10 @@ class TimelineOptions {
this.maxTitleLength,
this.minContentLength,
this.maxContentLength,
this.categorySelectorButtonBuilder,
this.postOverviewButtonBuilder,
this.titleInputDecoration,
this.contentInputDecoration,
});
/// Theming options for the timeline
@ -142,11 +147,69 @@ class TimelineOptions {
/// Maximum length of the post content
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 {
const CategoriesOptions({
this.categoriesBuilder,
this.categoriesBuilder = _getDefaultCategories,
this.categoryButtonBuilder,
this.categorySelectorHorizontalPadding,
});
@ -157,12 +220,14 @@ class CategoriesOptions {
categoriesBuilder;
/// Abilty to override the standard category selector
final Widget Function({
required String? categoryKey,
required String categoryName,
required Function onTap,
required bool selected,
})? categoryButtonBuilder;
final Widget Function(
String? categoryKey,
String categoryName,
Function() onTap,
// ignore: avoid_positional_boolean_parameters
bool selected,
bool isOnTop,
)? categoryButtonBuilder;
/// Overides the standard horizontal padding of the whole category selector.
final double? categorySelectorHorizontalPadding;

View file

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

View file

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

View file

@ -10,47 +10,59 @@ class TimelinePostOverviewScreen extends StatelessWidget {
required this.options,
required this.service,
required this.onPostSubmit,
this.isOverviewScreen,
super.key,
});
final TimelinePost timelinePost;
final TimelineOptions options;
final TimelineService service;
final void Function(TimelinePost) onPostSubmit;
final bool? isOverviewScreen;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Colors.black,
title: Text(
options.translations.postOverview,
style: TextStyle(color: Theme.of(context).primaryColor),
),
),
body: Column(
children: [
Flexible(
child: TimelinePostScreen(
userId: timelinePost.creatorId,
options: options,
post: timelinePost,
onPostDelete: () async {},
service: service,
),
return Column(
children: [
Flexible(
child: TimelinePostScreen(
userId: timelinePost.creatorId,
options: options,
post: timelinePost,
onPostDelete: () async {},
service: service,
isOverviewScreen: isOverviewScreen,
),
Padding(
padding: const EdgeInsets.only(bottom: 30.0),
child: ElevatedButton(
onPressed: () {
),
options.postOverviewButtonBuilder?.call(
context,
() {
onPostSubmit(timelinePost);
},
child: Text(
'${options.translations.postIn} ${timelinePost.category}',
'${options.translations.postIn} ${timelinePost.category}',
) ??
Padding(
padding: const EdgeInsets.only(bottom: 30.0),
child: ElevatedButton(
style: const ButtonStyle(
backgroundColor: MaterialStatePropertyAll(Color(0xff71C6D1)),
),
onPressed: () {
onPostSubmit(timelinePost);
},
child: Padding(
padding: const EdgeInsets.all(12.0),
child: Text(
'${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.post,
required this.onPostDelete,
this.isOverviewScreen = false,
this.onUserTap,
super.key,
});
@ -43,6 +44,8 @@ class TimelinePostScreen extends StatelessWidget {
final VoidCallback onPostDelete;
final bool? isOverviewScreen;
@override
Widget build(BuildContext context) => Scaffold(
body: _TimelinePostScreen(
@ -52,6 +55,7 @@ class TimelinePostScreen extends StatelessWidget {
post: post,
onPostDelete: onPostDelete,
onUserTap: onUserTap,
isOverviewScreen: isOverviewScreen,
),
);
}
@ -64,6 +68,7 @@ class _TimelinePostScreen extends StatefulWidget {
required this.post,
required this.onPostDelete,
this.onUserTap,
this.isOverviewScreen,
});
final String userId;
@ -78,6 +83,8 @@ class _TimelinePostScreen extends StatefulWidget {
final VoidCallback onPostDelete;
final bool? isOverviewScreen;
@override
State<_TimelinePostScreen> createState() => _TimelinePostScreenState();
}
@ -145,7 +152,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
if (this.post == null) {
return Center(
child: Text(
widget.options.translations.postLoadingError,
widget.options.translations.postLoadingError!,
style: widget.options.theme.textStyles.errorTextStyle,
),
);
@ -214,7 +221,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
widget.options.nameBuilder
?.call(post.creator) ??
post.creator?.fullName ??
widget.options.translations.anonymousUser,
widget.options.translations.anonymousUser!,
style: widget.options.theme.textStyles
.postCreatorTitleStyle ??
theme.textTheme.titleMedium,
@ -234,7 +241,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
child: Row(
children: [
Text(
widget.options.translations.deletePost,
widget.options.translations.deletePost!,
style: widget.options.theme.textStyles
.deletePostStyle ??
theme.textTheme.bodyMedium,
@ -257,8 +264,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
),
],
),
// image of the post
if (post.imageUrl != null) ...[
// image of the posts
if (post.imageUrl != null || post.image != null) ...[
const SizedBox(height: 8),
ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
@ -293,11 +300,17 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
false;
},
)
: CachedNetworkImage(
width: double.infinity,
imageUrl: post.imageUrl!,
fit: BoxFit.fitHeight,
),
: post.image != null
? Image.memory(
width: double.infinity,
post.image!,
fit: BoxFit.fitHeight,
)
: CachedNetworkImage(
width: double.infinity,
imageUrl: post.imageUrl!,
fit: BoxFit.fitHeight,
),
),
],
const SizedBox(
@ -320,9 +333,11 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
color: Colors.transparent,
child: widget.options.theme.likedIcon ??
Icon(
Icons.thumb_up_rounded,
color: widget.options.theme.iconColor,
size: widget.options.iconSize,
widget.post.likedBy
?.contains(widget.userId) ??
false
? Icons.favorite_rounded
: Icons.favorite_outline_outlined,
),
),
),
@ -340,8 +355,11 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
color: Colors.transparent,
child: widget.options.theme.likeIcon ??
Icon(
Icons.thumb_up_alt_outlined,
color: widget.options.theme.iconColor,
widget.post.likedBy
?.contains(widget.userId) ??
false
? Icons.favorite_rounded
: Icons.favorite_outline_outlined,
size: widget.options.iconSize,
),
),
@ -423,7 +441,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
const SizedBox(height: 20),
if (post.reactionEnabled) ...[
Text(
widget.options.translations.commentsTitleOnPost,
widget.options.translations.commentsTitleOnPost!,
style: theme.textTheme.titleMedium,
),
for (var reaction
@ -451,7 +469,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
PopupMenuItem<String>(
value: 'delete',
child: Text(
widget.options.translations.deleteReaction,
widget.options.translations.deleteReaction!,
),
),
],
@ -505,7 +523,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
?.call(post.creator) ??
reaction.creator?.fullName ??
widget.options.translations
.anonymousUser,
.anonymousUser!,
style: theme.textTheme.titleSmall,
),
Padding(
@ -547,7 +565,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
if (post.reactions?.isEmpty ?? true) ...[
const SizedBox(height: 16),
Text(
widget.options.translations.firstComment,
widget.options.translations.firstComment!,
),
],
const SizedBox(height: 120),
@ -557,7 +575,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
),
),
),
if (post.reactionEnabled)
if (post.reactionEnabled && !widget.isOverviewScreen!)
Align(
alignment: Alignment.bottomCenter,
child: ReactionBottom(

View file

@ -273,8 +273,9 @@ class _TimelineScreenState extends State<TimelineScreen> {
padding: const EdgeInsets.all(8.0),
child: Text(
category == null
? widget.options.translations.noPosts
: widget.options.translations.noPostsWithFilter,
? widget.options.translations.noPosts!
: widget
.options.translations.noPostsWithFilter!,
style: widget.options.theme.textStyles.noPostsStyle,
),
),

View file

@ -19,7 +19,6 @@ class TimelineSelectionScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
var size = MediaQuery.of(context).size;
var theme = Theme.of(context);
return Padding(
padding: EdgeInsets.symmetric(
horizontal: size.width * 0.05,
@ -30,36 +29,55 @@ class TimelineSelectionScreen extends StatelessWidget {
Padding(
padding: EdgeInsets.only(top: size.height * 0.05, bottom: 8),
child: Text(
options.translations.timelineSelectionDescription,
style:
options.theme.textStyles.categorySelectionDescriptionStyle ??
theme.textTheme.displayMedium,
options.translations.timelineSelectionDescription!,
style: const TextStyle(
fontWeight: FontWeight.w800,
fontSize: 20,
),
),
),
const SizedBox(height: 4),
for (var category in categories.where(
(element) => element.canCreate,
)) ...[
InkWell(
onTap: () => onCategorySelected.call(category),
child: Container(
width: double.infinity,
decoration: BoxDecoration(
color: Colors.grey,
borderRadius: BorderRadius.circular(10),
),
padding: const EdgeInsets.symmetric(
vertical: 26,
horizontal: 16,
),
margin: const EdgeInsets.symmetric(vertical: 8),
child: Text(
options.categorySelectorButtonBuilder?.call(
context,
() {
onCategorySelected.call(category);
},
category.title,
style: options.theme.textStyles.categorySelectionTitleStyle ??
theme.textTheme.displaySmall,
) ??
InkWell(
onTap: () => onCategorySelected.call(category),
child: Container(
height: 60,
width: double.infinity,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: const Color(0xff71C6D1),
width: 2,
),
),
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(
category.title,
style: const TextStyle(
fontSize: 18,
fontWeight: FontWeight.w800,
),
),
),
],
),
),
),
),
),
],
],
),

View file

@ -21,8 +21,8 @@ class LocalTimelinePostService
userId: 'test_user',
imageUrl:
'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop',
firstName: 'Dirk',
lastName: 'lukassen',
firstName: 'Ico',
lastName: 'Nica',
),
),
);
@ -174,8 +174,8 @@ class LocalTimelinePostService
userId: 'test_user',
imageUrl:
'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop',
firstName: 'Dirk',
lastName: 'lukassen',
firstName: 'Ico',
lastName: 'Nica',
),
);
@ -197,63 +197,47 @@ class LocalTimelinePostService
TimelinePost(
id: 'Post0',
creatorId: 'test_user',
title: 'Post 0',
category: null,
title: 'De topper van de maand september',
category: 'Category',
imageUrl:
'https://t4.ftcdn.net/jpg/02/77/71/45/240_F_277714513_fQ0akmI3TQxa0wkPCLeO12Rx3cL2AuIf.jpg',
content: 'Standard post without image made by the current user',
likes: 0,
'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: 'Dit is onze topper van de maand september! Gefeliciteerd!',
likes: 72,
reaction: 0,
createdAt: DateTime.now(),
reactionEnabled: false,
reactionEnabled: true,
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',
'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: 'Robin',
lastName: 'De Vries',
),
),
TimelinePost(
id: 'Post1',
creatorId: 'test_user2',
title: 'Post 1',
category: null,
content: 'Standard post with image made by a different user and '
'reactions enabled',
likes: 0,
reaction: 0,
createdAt: DateTime.now(),
reactionEnabled: false,
imageUrl:
'https://t4.ftcdn.net/jpg/02/77/71/45/240_F_277714513_fQ0akmI3TQxa0wkPCLeO12Rx3cL2AuIf.jpg',
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,
title: 'De soep van de week is: Aspergesoep',
category: 'Category with two lines',
content:
'Aspergesoep is echt een heerlijke delicatesse. Deze soep wordt'
' vaak gemaakt met verse asperges, bouillon en wat kruiden voor'
' smaak. Het is een perfecte keuze voor een lichte en smaakvolle'
' maaltijd, vooral in het voorjaar wanneer asperges in seizoen'
' zijn. We serveren het met een vleugje room en wat knapperige'
' croutons voor die extra touch.',
likes: 72,
reaction: 0,
createdAt: DateTime.now(),
reactionEnabled: true,
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(
userId: 'test_user',
imageUrl:
'https://cdn.britannica.com/68/143568-050-5246474F/Donkey.jpg?w=400&h=300&c=crop',
firstName: 'Dirk',
lastName: 'lukassen',
'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: 'Elise',
lastName: 'Welling',
),
),
];

View file

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

View file

@ -22,9 +22,8 @@ class CategorySelectorButton extends StatelessWidget {
Widget build(BuildContext context) {
var theme = Theme.of(context);
return AnimatedContainer(
return SizedBox(
height: isOnTop ? 140 : 40,
duration: const Duration(milliseconds: 100),
child: TextButton(
onPressed: onTap,
style: ButtonStyle(
@ -37,41 +36,59 @@ class CategorySelectorButton extends StatelessWidget {
),
fixedSize: MaterialStatePropertyAll(Size(140, isOnTop ? 140 : 20)),
backgroundColor: MaterialStatePropertyAll(
selected ? theme.colorScheme.primary : Colors.transparent,
selected ? const Color(0xff71C6D1) : Colors.transparent,
),
shape: MaterialStatePropertyAll(
shape: const MaterialStatePropertyAll(
RoundedRectangleBorder(
borderRadius: const BorderRadius.all(
borderRadius: BorderRadius.all(
Radius.circular(8),
),
side: BorderSide(
color: theme.colorScheme.primary,
color: Color(0xff71C6D1),
width: 2,
),
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Column(
mainAxisAlignment:
isOnTop ? MainAxisAlignment.end : MainAxisAlignment.center,
children: [
Text(
category.title,
style: (options.theme.textStyles.categoryTitleStyle ??
theme.textTheme.labelLarge)
?.copyWith(
color: selected
? theme.colorScheme.onPrimary
: theme.colorScheme.onSurface,
),
child: isOnTop
? SizedBox(
width: MediaQuery.of(context).size.width,
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
category.title,
style: (options.theme.textStyles.categoryTitleStyle ??
theme.textTheme.labelLarge)
?.copyWith(
color: selected
? theme.colorScheme.onPrimary
: 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,11 +99,17 @@ class _TappableImageState extends State<TappableImage>
offset: Offset(0, animation.value * -32),
child: Transform.scale(
scale: 1 + animation.value * 0.1,
child: CachedNetworkImage(
imageUrl: widget.post.imageUrl ?? '',
width: double.infinity,
fit: BoxFit.fitHeight,
),
child: widget.post.imageUrl != null
? CachedNetworkImage(
imageUrl: widget.post.imageUrl ?? '',
width: double.infinity,
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(
onTap: widget.onTap,
child: SizedBox(
height: widget.post.imageUrl != null
height: widget.post.imageUrl != null || widget.post.image != null
? widget.options.postWidgetHeight
: null,
width: double.infinity,
@ -94,7 +94,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
widget.options.nameBuilder
?.call(widget.post.creator) ??
widget.post.creator?.fullName ??
widget.options.translations.anonymousUser,
widget.options.translations.anonymousUser!,
style: widget.options.theme.textStyles
.postCreatorTitleStyle ??
theme.textTheme.titleMedium,
@ -118,7 +118,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
child: Row(
children: [
Text(
widget.options.translations.deletePost,
widget.options.translations.deletePost!,
style: widget.options.theme.textStyles
.deletePostStyle ??
theme.textTheme.bodyMedium,
@ -142,7 +142,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
],
),
// image of the post
if (widget.post.imageUrl != null) ...[
if (widget.post.imageUrl != null || widget.post.image != null) ...[
const SizedBox(height: 8),
Flexible(
flex: widget.options.postWidgetHeight != null ? 1 : 0,
@ -176,11 +176,17 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
return result.likedBy?.contains(userId) ?? false;
},
)
: CachedNetworkImage(
width: double.infinity,
imageUrl: widget.post.imageUrl!,
fit: BoxFit.fitWidth,
),
: widget.post.imageUrl != null
? CachedNetworkImage(
width: double.infinity,
imageUrl: widget.post.imageUrl!,
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),
Text(
widget.options.translations.viewPost,
widget.options.translations.viewPost!,
style: widget.options.theme.textStyles.viewPostStyle ??
theme.textTheme.bodySmall,
),

View file

@ -4,7 +4,7 @@
name: flutter_timeline_view
description: Visual elements of the Flutter Timeline Component
version: 2.3.1
version: 3.0.0
publish_to: none
@ -23,7 +23,7 @@ dependencies:
git:
url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface
ref: 2.3.1
ref: 3.0.0
flutter_image_picker:
git:
url: https://github.com/Iconica-Development/flutter_image_picker