Merge pull request #39 from Iconica-Development/feat/implement-figma-design

feat: implement figma designs
This commit is contained in:
Gorter-dev 2024-04-19 15:55:05 +02:00 committed by GitHub
commit 7b76a8d956
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
20 changed files with 365 additions and 144 deletions

View file

@ -25,9 +25,14 @@ class GoRouterApp extends StatelessWidget {
routerConfig: _router,
title: 'Flutter Timeline',
theme: ThemeData(
colorScheme:
ColorScheme.fromSeed(seedColor: Colors.deepPurple).copyWith(
background: const Color(0xFFB8E2E8),
textTheme: const TextTheme(
titleLarge: TextStyle(
color: Color(0xffb71c6d), fontFamily: 'Playfair Display')),
colorScheme: ColorScheme.fromSeed(
seedColor: const Color(0xFFB8E2E8),
primary: const Color(0xffb71c6d),
).copyWith(
background: const Color(0XFFFAF9F6),
),
useMaterial3: true,
),

View file

@ -34,7 +34,13 @@ class _PostScreenState extends State<PostScreen> {
class TestUserService implements TimelineUserService {
final Map<String, TimelinePosterUserModel> _users = {
'test_user': const TimelinePosterUserModel(userId: 'test_user')
'test_user': 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',
)
};
@override

View file

@ -101,6 +101,13 @@ void generatePost(TimelineService service) {
content: "Post $amountOfPosts content",
likes: 0,
reaction: 0,
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',
),
createdAt: DateTime.now(),
reactionEnabled: amountOfPosts % 2 == 0 ? false : true,
imageUrl: amountOfPosts % 3 != 0

View file

@ -1,6 +1,6 @@
// import 'package:example/apps/go_router/app.dart';
// import 'package:example/apps/navigator/app.dart';
import 'package:example/apps/widgets/app.dart';
import 'package:example/apps/go_router/app.dart';
import 'package:flutter/material.dart';
import 'package:intl/date_symbol_data_local.dart';
@ -9,7 +9,7 @@ void main() {
// Uncomment any, but only one, of these lines to run the example with specific navigation.
runApp(const WidgetApp());
// runApp(const WidgetApp());
// runApp(const NavigatorApp());
// runApp(const GoRouterApp());
runApp(const GoRouterApp());
}

View file

@ -0,0 +1,29 @@
// This is a basic Flutter widget test.
//
// To perform an interaction with a widget in your test, use the WidgetTester
// utility in the flutter_test package. For example, you can send tap and scroll
// gestures. You can also use WidgetTester to find child widgets in the widget
// tree, read text, and verify that the values of widget properties are correct.
import 'package:example/apps/widgets/app.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
// Build our app and trigger a frame.
await tester.pumpWidget(const WidgetApp());
// Verify that our counter starts at 0.
expect(find.text('0'), findsOneWidget);
expect(find.text('1'), findsNothing);
// Tap the '+' icon and trigger a frame.
await tester.tap(find.byIcon(Icons.add));
await tester.pump();
// Verify that our counter has incremented.
expect(find.text('0'), findsNothing);
expect(find.text('1'), findsOneWidget);
});
}

View file

@ -43,10 +43,16 @@ List<GoRoute> getTimelineStoryRoutes({
);
var button = FloatingActionButton(
backgroundColor: Theme.of(context).primaryColor,
onPressed: () async => context.go(
TimelineUserStoryRoutes.timelinePostCreation,
),
child: const Icon(Icons.add),
shape: const CircleBorder(),
child: const Icon(
Icons.add,
color: Colors.white,
size: 30,
),
);
return buildScreenWithoutTransition(
@ -55,7 +61,13 @@ List<GoRoute> getTimelineStoryRoutes({
child: config.homeOpenPageBuilder
?.call(context, timelineScreen, button) ??
Scaffold(
appBar: AppBar(),
appBar: AppBar(
backgroundColor: Colors.black,
title: Text(
'Iconinstagram',
style: Theme.of(context).textTheme.titleLarge,
),
),
body: timelineScreen,
floatingActionButton: button,
),
@ -66,18 +78,19 @@ List<GoRoute> getTimelineStoryRoutes({
path: TimelineUserStoryRoutes.timelineView,
pageBuilder: (context, state) {
var post =
config.service.postService.getPost(state.pathParameters['post']!)!;
config.service.postService.getPost(state.pathParameters['post']!);
var timelinePostWidget = TimelinePostScreen(
userId: config.userId,
options: config.optionsBuilder(context),
service: config.service,
post: post,
post: post!,
onPostDelete: () => config.onPostDelete?.call(context, post),
onUserTap: (user) => config.onUserTap?.call(context, user),
);
var backButton = IconButton(
color: Colors.white,
icon: const Icon(Icons.arrow_back_ios),
onPressed: () => context.go(TimelineUserStoryRoutes.timelineHome),
);
@ -90,6 +103,11 @@ List<GoRoute> getTimelineStoryRoutes({
Scaffold(
appBar: AppBar(
leading: backButton,
backgroundColor: Colors.black,
title: Text(
'Category',
style: Theme.of(context).textTheme.titleLarge,
),
),
body: timelinePostWidget,
),
@ -133,8 +151,10 @@ 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,
),
leading: backButton,
),

View file

@ -134,6 +134,7 @@ Widget _postCreationScreenRoute({
return Scaffold(
appBar: AppBar(
title: Text(
style: Theme.of(context).textTheme.titleLarge,
config.optionsBuilder(context).translations.postCreation,
),
),

View file

@ -8,13 +8,13 @@ version: 2.3.0
publish_to: none
environment:
sdk: '>=3.1.3 <4.0.0'
sdk: ">=3.1.3 <4.0.0"
dependencies:
flutter:
sdk: flutter
go_router: any
flutter_timeline_view:
git:
url: https://github.com/Iconica-Development/flutter_timeline
@ -22,10 +22,10 @@ dependencies:
ref: 2.3.0
flutter_timeline_interface:
git:
url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface
ref: 2.3.0
git:
url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface
ref: 2.3.0
dev_dependencies:
flutter_lints: ^2.0.0
@ -35,4 +35,3 @@ dev_dependencies:
ref: 6.0.0
flutter:

View file

@ -40,7 +40,8 @@ class TimelineOptions {
this.padding = const EdgeInsets.symmetric(vertical: 12.0),
this.iconSize = 26,
this.postWidgetHeight,
this.postPadding = const EdgeInsets.all(12.0),
this.postPadding =
const EdgeInsets.symmetric(vertical: 12.0, horizontal: 12.0),
this.filterOptions = const FilterOptions(),
this.categoriesOptions = const CategoriesOptions(),
this.requireImageForPost = false,

View file

@ -20,6 +20,7 @@ class TimelineTextStyles {
this.postTitleStyle,
this.postLikeTitleAndAmount,
this.postCreatedAtStyle,
this.categoryTitleStyle,
});
/// The TextStyle for the text indicating that you can view a post
@ -70,4 +71,6 @@ class TimelineTextStyles {
/// The TextStyle for the creation time of the post
final TextStyle? postCreatedAtStyle;
final TextStyle? categoryTitleStyle;
}

View file

@ -11,12 +11,15 @@ class TimelineTranslations {
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,
@ -32,6 +35,8 @@ class TimelineTranslations {
required this.postOverview,
required this.postIn,
required this.postCreation,
required this.yes,
required this.no,
});
const TimelineTranslations.empty()
@ -46,12 +51,13 @@ class TimelineTranslations {
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 = 'Comments',
commentsTitle = 'Are people allowed to comment?',
firstComment = 'Be the first to comment',
writeComment = 'Write your comment here...',
postAt = 'at',
@ -60,7 +66,11 @@ class TimelineTranslations {
searchHint = 'Search...',
postOverview = 'Post Overview',
postIn = 'Post in',
postCreation = 'Create Post';
postCreation = 'Create Post',
titleHintText = 'Title...',
contentHintText = 'Context...',
yes = 'Yes',
no = 'No';
final String noPosts;
final String noPostsWithFilter;
@ -76,11 +86,15 @@ class TimelineTranslations {
final String checkPost;
final String postAt;
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;
@ -93,6 +107,9 @@ class TimelineTranslations {
final String postIn;
final String postCreation;
final String yes;
final String no;
TimelineTranslations copyWith({
String? noPosts,
String? noPostsWithFilter,
@ -104,6 +121,7 @@ class TimelineTranslations {
String? uploadImageDescription,
String? allowComments,
String? allowCommentsDescription,
String? commentsTitleOnPost,
String? checkPost,
String? postAt,
String? deletePost,
@ -119,6 +137,10 @@ class TimelineTranslations {
String? postOverview,
String? postIn,
String? postCreation,
String? titleHintText,
String? contentHintText,
String? yes,
String? no,
}) =>
TimelineTranslations(
noPosts: noPosts ?? this.noPosts,
@ -133,6 +155,7 @@ class TimelineTranslations {
allowComments: allowComments ?? this.allowComments,
allowCommentsDescription:
allowCommentsDescription ?? this.allowCommentsDescription,
commentsTitleOnPost: commentsTitleOnPost ?? this.commentsTitleOnPost,
checkPost: checkPost ?? this.checkPost,
postAt: postAt ?? this.postAt,
deletePost: deletePost ?? this.deletePost,
@ -149,5 +172,9 @@ class TimelineTranslations {
postOverview: postOverview ?? this.postOverview,
postIn: postIn ?? this.postIn,
postCreation: postCreation ?? this.postCreation,
titleHintText: titleHintText ?? this.titleHintText,
contentHintText: contentHintText ?? this.contentHintText,
yes: yes ?? this.yes,
no: no ?? this.no,
);
}

View file

@ -2,6 +2,7 @@
//
// SPDX-License-Identifier: BSD-3-Clause
import 'dart:math';
import 'dart:typed_data';
import 'package:dotted_border/dotted_border.dart';
@ -97,7 +98,7 @@ class _TimelinePostCreationScreenState
Widget build(BuildContext context) {
Future<void> onPostCreated() async {
var post = TimelinePost(
id: '',
id: 'Post${Random().nextInt(1000)}',
creatorId: widget.userId,
title: titleController.text,
category: widget.postCategory,
@ -128,7 +129,7 @@ class _TimelinePostCreationScreenState
children: [
Text(
widget.options.translations.title,
style: theme.textTheme.displaySmall,
style: theme.textTheme.titleMedium,
),
widget.options.textInputBuilder?.call(
titleController,
@ -137,11 +138,14 @@ class _TimelinePostCreationScreenState
) ??
TextField(
controller: titleController,
decoration: InputDecoration(
hintText: widget.options.translations.titleHintText,
),
),
const SizedBox(height: 16),
Text(
widget.options.translations.content,
style: theme.textTheme.displaySmall,
style: theme.textTheme.titleMedium,
),
const SizedBox(height: 4),
Text(
@ -157,6 +161,9 @@ class _TimelinePostCreationScreenState
expands: true,
maxLines: null,
minLines: null,
decoration: InputDecoration(
hintText: widget.options.translations.contentHintText,
),
),
),
const SizedBox(
@ -165,7 +172,7 @@ class _TimelinePostCreationScreenState
// input field for the content
Text(
widget.options.translations.uploadImage,
style: theme.textTheme.displaySmall,
style: theme.textTheme.titleMedium,
),
Text(
widget.options.translations.uploadImageDescription,
@ -198,30 +205,31 @@ class _TimelinePostCreationScreenState
}
checkIfEditingDone();
},
child: image != null
? ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.memory(
child: ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: image != null
? Image.memory(
image!,
width: double.infinity,
height: 150.0,
fit: BoxFit.cover,
// give it a rounded border
),
)
: DottedBorder(
radius: const Radius.circular(8.0),
color: theme.textTheme.displayMedium?.color ??
Colors.white,
child: const SizedBox(
width: double.infinity,
height: 150.0,
child: Icon(
Icons.image,
size: 32,
)
: DottedBorder(
dashPattern: const [4, 4],
radius: const Radius.circular(8.0),
color: theme.textTheme.displayMedium?.color ??
Colors.white,
child: const SizedBox(
width: double.infinity,
height: 150.0,
child: Icon(
Icons.image,
size: 50,
),
),
),
),
),
),
// if an image is selected, show a delete button
if (image != null) ...[
@ -255,21 +263,36 @@ class _TimelinePostCreationScreenState
Text(
widget.options.translations.commentsTitle,
style: theme.textTheme.displaySmall,
style: theme.textTheme.titleMedium,
),
Text(
widget.options.translations.allowCommentsDescription,
style: theme.textTheme.bodyMedium,
),
// radio buttons for yes or no
Switch(
value: allowComments,
onChanged: (newValue) {
setState(() {
allowComments = newValue;
});
},
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: <Widget>[
Checkbox(
value: allowComments,
onChanged: (value) {
setState(() {
allowComments = true;
});
},
),
Text(widget.options.translations.yes),
Checkbox(
value: !allowComments,
onChanged: (value) {
setState(() {
allowComments = false;
});
},
),
Text(widget.options.translations.no),
],
),
Align(
alignment: Alignment.bottomCenter,
child: (widget.options.buttonBuilder != null)

View file

@ -21,7 +21,11 @@ class TimelinePostOverviewScreen extends StatelessWidget {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(options.translations.postOverview),
backgroundColor: Colors.black,
title: Text(
options.translations.postOverview,
style: TextStyle(color: Theme.of(context).primaryColor),
),
),
body: Column(
children: [

View file

@ -93,6 +93,10 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
decoration: InputDecoration(
hintText: hintText,
suffixIcon: suffixIcon,
border: OutlineInputBorder(
borderRadius:
BorderRadius.circular(20.0), // Adjust the value as needed
),
),
);
@ -184,7 +188,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
if (post.creator!.imageUrl != null) ...[
widget.options.userAvatarBuilder?.call(
post.creator!,
40,
28,
) ??
CircleAvatar(
radius: 20,
@ -196,7 +200,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
] else ...[
widget.options.anonymousAvatarBuilder?.call(
post.creator!,
40,
28,
) ??
const CircleAvatar(
radius: 20,
@ -318,6 +322,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
Icon(
Icons.thumb_up_rounded,
color: widget.options.theme.iconColor,
size: widget.options.iconSize,
),
),
),
@ -418,8 +423,8 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
const SizedBox(height: 20),
if (post.reactionEnabled) ...[
Text(
widget.options.translations.commentsTitle,
style: theme.textTheme.displaySmall,
widget.options.translations.commentsTitleOnPost,
style: theme.textTheme.titleMedium,
),
for (var reaction
in post.reactions ?? <TimelinePostReaction>[]) ...[
@ -469,7 +474,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
reaction.creator!.imageUrl!.isNotEmpty) ...[
widget.options.userAvatarBuilder?.call(
reaction.creator!,
25,
28,
) ??
CircleAvatar(
radius: 20,
@ -480,7 +485,7 @@ class _TimelinePostScreenState extends State<_TimelinePostScreen> {
] else ...[
widget.options.anonymousAvatarBuilder?.call(
reaction.creator!,
25,
28,
) ??
const CircleAvatar(
radius: 20,

View file

@ -74,10 +74,27 @@ class _TimelineScreenState extends State<TimelineScreen> {
late var filterWord = widget.options.filterOptions.initialFilterWord;
bool _isOnTop = true;
@override
void dispose() {
controller.removeListener(_updateIsOnTop);
controller.dispose();
super.dispose();
}
void _updateIsOnTop() {
setState(() {
_isOnTop = controller.position.pixels < 40;
});
}
@override
void initState() {
super.initState();
controller = widget.scrollController ?? ScrollController();
controller.addListener(_updateIsOnTop);
// only load the posts after the first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
unawaited(loadPosts());
@ -188,6 +205,7 @@ class _TimelineScreenState extends State<TimelineScreen> {
),
],
CategorySelector(
isOnTop: _isOnTop,
filter: category,
options: widget.options,
onTapCategory: (categoryKey) {

View file

@ -17,7 +17,13 @@ class LocalTimelinePostService
Future<TimelinePost> createPost(TimelinePost post) async {
posts.add(
post.copyWith(
creator: const TimelinePosterUserModel(userId: 'test_user'),
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',
),
),
);
notifyListeners();
@ -62,7 +68,13 @@ class LocalTimelinePostService
for (var reaction in reactions) {
updatedReactions.add(
reaction.copyWith(
creator: const TimelinePosterUserModel(userId: 'test_user'),
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',
),
),
);
}
@ -156,7 +168,13 @@ class LocalTimelinePostService
var updatedReaction = reaction.copyWith(
id: reactionId,
creator: const TimelinePosterUserModel(userId: 'test_user'),
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',
),
);
var updatedPost = post.copyWith(
@ -179,11 +197,20 @@ class LocalTimelinePostService
creatorId: 'test_user',
title: 'Post 0',
category: null,
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,
reaction: 0,
createdAt: DateTime.now(),
reactionEnabled: false,
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: 'Post1',
@ -197,7 +224,14 @@ class LocalTimelinePostService
createdAt: DateTime.now(),
reactionEnabled: false,
imageUrl:
'https://s3-eu-west-1.amazonaws.com/sortlist-core-api/6qpvvqjtmniirpkvp8eg83bicnc2',
'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',
@ -211,7 +245,14 @@ class LocalTimelinePostService
createdAt: DateTime.now(),
reactionEnabled: true,
imageUrl:
'https://s3-eu-west-1.amazonaws.com/sortlist-core-api/6qpvvqjtmniirpkvp8eg83bicnc2',
'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',
),
),
];
}

View file

@ -3,55 +3,64 @@ import 'dart:math';
import 'package:flutter/material.dart';
import 'package:flutter_timeline_view/flutter_timeline_view.dart';
class CategorySelector extends StatelessWidget {
class CategorySelector extends StatefulWidget {
const CategorySelector({
required this.filter,
required this.options,
required this.onTapCategory,
required this.isOnTop,
super.key,
});
final String? filter;
final TimelineOptions options;
final void Function(String? categoryKey) onTapCategory;
final bool isOnTop;
@override
State<CategorySelector> createState() => _CategorySelectorState();
}
class _CategorySelectorState extends State<CategorySelector> {
@override
Widget build(BuildContext context) {
if (options.categoriesOptions.categoriesBuilder == null) {
if (widget.options.categoriesOptions.categoriesBuilder == null) {
return const SizedBox.shrink();
}
var categories = options.categoriesOptions.categoriesBuilder!(context);
var categories =
widget.options.categoriesOptions.categoriesBuilder!(context);
return SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
SizedBox(
width:
options.categoriesOptions.categorySelectorHorizontalPadding ??
max(options.padding.horizontal - 4, 0),
width: widget.options.categoriesOptions
.categorySelectorHorizontalPadding ??
max(widget.options.padding.horizontal - 20, 0),
),
for (var category in categories) ...[
options.categoriesOptions.categoryButtonBuilder?.call(
widget.options.categoriesOptions.categoryButtonBuilder?.call(
categoryKey: category.key,
categoryName: category.title,
onTap: () => onTapCategory(category.key),
selected: filter == category.key,
onTap: () => widget.onTapCategory(category.key),
selected: widget.filter == category.key,
) ??
Padding(
padding: const EdgeInsets.symmetric(horizontal: 4),
child: CategorySelectorButton(
isOnTop: widget.isOnTop,
category: category,
selected: filter == category.key,
onTap: () => onTapCategory(category.key),
selected: widget.filter == category.key,
onTap: () => widget.onTapCategory(category.key),
options: widget.options,
),
),
],
SizedBox(
width:
options.categoriesOptions.categorySelectorHorizontalPadding ??
max(options.padding.horizontal - 4, 0),
width: widget.options.categoriesOptions
.categorySelectorHorizontalPadding ??
max(widget.options.padding.horizontal - 4, 0),
),
],
),

View file

@ -1,50 +1,76 @@
import 'package:flutter/material.dart';
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
import 'package:flutter_timeline_view/flutter_timeline_view.dart';
class CategorySelectorButton extends StatelessWidget {
const CategorySelectorButton({
required this.category,
required this.selected,
required this.onTap,
required this.options,
required this.isOnTop,
super.key,
});
final TimelineCategory category;
final bool selected;
final void Function() onTap;
final TimelineOptions options;
final bool isOnTop;
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
return TextButton(
onPressed: onTap,
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: const MaterialStatePropertyAll(
EdgeInsets.symmetric(
vertical: 5,
horizontal: 12,
return AnimatedContainer(
height: isOnTop ? 140 : 40,
duration: const Duration(milliseconds: 100),
child: TextButton(
onPressed: onTap,
style: ButtonStyle(
tapTargetSize: MaterialTapTargetSize.shrinkWrap,
padding: const MaterialStatePropertyAll(
EdgeInsets.symmetric(
vertical: 5,
horizontal: 12,
),
),
),
minimumSize: const MaterialStatePropertyAll(Size.zero),
backgroundColor: MaterialStatePropertyAll(
selected ? theme.colorScheme.primary : theme.colorScheme.surface,
),
shape: const MaterialStatePropertyAll(
RoundedRectangleBorder(
borderRadius: BorderRadius.all(
Radius.circular(45),
fixedSize: MaterialStatePropertyAll(Size(140, isOnTop ? 140 : 20)),
backgroundColor: MaterialStatePropertyAll(
selected ? theme.colorScheme.primary : Colors.transparent,
),
shape: MaterialStatePropertyAll(
RoundedRectangleBorder(
borderRadius: const BorderRadius.all(
Radius.circular(8),
),
side: BorderSide(
color: theme.colorScheme.primary,
width: 2,
),
),
),
),
),
child: Text(
category.title,
style: theme.textTheme.labelMedium?.copyWith(
color: selected
? theme.colorScheme.onPrimary
: theme.colorScheme.onSurface,
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,
),
),
],
),
],
),
),
);

View file

@ -30,46 +30,43 @@ class _ReactionBottomState extends State<ReactionBottom> {
final TextEditingController _textEditingController = TextEditingController();
@override
Widget build(BuildContext context) => Container(
color: Theme.of(context).colorScheme.background,
Widget build(BuildContext context) => SafeArea(
bottom: true,
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
height: 45,
child: widget.messageInputBuilder(
_textEditingController,
Padding(
padding: const EdgeInsets.only(right: 15.0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: widget.onPressSelectImage,
icon: Icon(
Icons.image,
color: widget.iconColor,
),
),
IconButton(
onPressed: () async {
var value = _textEditingController.text;
if (value.isNotEmpty) {
await widget.onReactionSubmit(value);
_textEditingController.clear();
}
},
icon: Icon(
Icons.send,
color: widget.iconColor,
),
),
],
),
color: Theme.of(context).colorScheme.background,
child: Container(
margin: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
height: 48,
child: widget.messageInputBuilder(
_textEditingController,
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 4,
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
onPressed: () async {
var value = _textEditingController.text;
if (value.isNotEmpty) {
await widget.onReactionSubmit(value);
_textEditingController.clear();
}
},
icon: Icon(
Icons.send,
color: widget.iconColor,
),
),
],
),
),
widget.translations.writeComment,
),
widget.translations.writeComment,
),
),
);

View file

@ -69,7 +69,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
if (widget.post.creator!.imageUrl != null) ...[
widget.options.userAvatarBuilder?.call(
widget.post.creator!,
40,
28,
) ??
CircleAvatar(
radius: 20,
@ -213,7 +213,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
icon: widget.options.theme.likeIcon ??
Icon(
widget.post.likedBy?.contains(widget.userId) ?? false
? Icons.favorite
? Icons.favorite_rounded
: Icons.favorite_outline_outlined,
),
label: Text('${widget.post.likes}'),
@ -240,7 +240,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
color: Colors.transparent,
child: widget.options.theme.likedIcon ??
Icon(
Icons.thumb_up_rounded,
Icons.favorite_rounded,
color: widget.options.theme.iconColor,
size: widget.options.iconSize,
),
@ -253,7 +253,7 @@ class _TimelinePostWidgetState extends State<TimelinePostWidget> {
color: Colors.transparent,
child: widget.options.theme.likedIcon ??
Icon(
Icons.thumb_up_rounded,
Icons.favorite_outline,
color: widget.options.theme.iconColor,
size: widget.options.iconSize,
),