feat: add timelineoverview cards

This commit is contained in:
Freek van de Ven 2023-11-19 18:47:05 +01:00
parent 55e4e50798
commit c33d8cb893
8 changed files with 199 additions and 3 deletions

View file

@ -5,4 +5,5 @@
library flutter_timeline_interface; library flutter_timeline_interface;
export 'src/model/timeline_post.dart'; export 'src/model/timeline_post.dart';
export 'src/model/timeline_poster.dart';
export 'src/model/timeline_reaction.dart'; export 'src/model/timeline_reaction.dart';

View file

@ -1,4 +1,9 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_timeline_interface/src/model/timeline_poster.dart';
import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart'; import 'package:flutter_timeline_interface/src/model/timeline_reaction.dart';
/// A post of the timeline. /// A post of the timeline.
@ -14,6 +19,7 @@ class TimelinePost {
required this.reaction, required this.reaction,
required this.createdAt, required this.createdAt,
required this.reactionEnabled, required this.reactionEnabled,
this.creator,
this.likedBy, this.likedBy,
this.reactions, this.reactions,
this.imageUrl, this.imageUrl,
@ -25,6 +31,9 @@ class TimelinePost {
/// The unique identifier of the creator of the post. /// The unique identifier of the creator of the post.
final String creatorId; final String creatorId;
/// The creator of the post. If null it isn't loaded yet.
final TimelinePosterUserModel? creator;
/// The title of the post. /// The title of the post.
final String title; final String title;

View file

@ -0,0 +1,34 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
@immutable
class TimelinePosterUserModel {
const TimelinePosterUserModel({
required this.id,
this.firstName,
this.lastName,
this.imageUrl,
});
final String id;
final String? firstName;
final String? lastName;
final String? imageUrl;
String? get fullName {
var fullName = '';
if (firstName != null && lastName != null) {
fullName += '$firstName $lastName';
} else if (firstName != null) {
fullName += firstName!;
} else if (lastName != null) {
fullName += lastName!;
}
return fullName == '' ? null : fullName;
}
}

View file

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@immutable @immutable

View file

@ -3,6 +3,7 @@
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_image_picker/flutter_image_picker.dart'; import 'package:flutter_image_picker/flutter_image_picker.dart';
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
import 'package:flutter_timeline_view/src/config/timeline_translations.dart'; import 'package:flutter_timeline_view/src/config/timeline_translations.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
@ -15,6 +16,7 @@ class TimelineOptions {
this.dateformat, this.dateformat,
this.buttonBuilder, this.buttonBuilder,
this.textInputBuilder, this.textInputBuilder,
this.userAvatarBuilder,
}); });
/// The format to display the post time in /// The format to display the post time in
@ -26,6 +28,8 @@ class TimelineOptions {
final TextInputBuilder? textInputBuilder; final TextInputBuilder? textInputBuilder;
final UserAvatarBuilder? userAvatarBuilder;
/// ImagePickerTheme can be used to change the UI of the /// ImagePickerTheme can be used to change the UI of the
/// Image Picker Widget to change the text/icons to your liking. /// Image Picker Widget to change the text/icons to your liking.
final ImagePickerTheme imagePickerTheme; final ImagePickerTheme imagePickerTheme;
@ -42,9 +46,13 @@ typedef ButtonBuilder = Widget Function(
bool enabled, bool enabled,
}); });
typedef TextInputBuilder = Widget Function( typedef TextInputBuilder = Widget Function(
TextEditingController controller, TextEditingController controller,
Widget? suffixIcon, Widget? suffixIcon,
String hintText, String hintText,
); );
typedef UserAvatarBuilder = Widget Function(
TimelinePosterUserModel user,
double size,
);

View file

@ -11,7 +11,6 @@ class TimelinePostScreen extends StatelessWidget {
super.key, super.key,
}); });
final TimelinePost post; final TimelinePost post;
@override @override

View file

@ -4,16 +4,22 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
import 'package:flutter_timeline_view/src/config/timeline_options.dart';
import 'package:flutter_timeline_view/src/widgets/timeline_post_widget.dart';
class TimelineScreen extends StatefulWidget { class TimelineScreen extends StatefulWidget {
const TimelineScreen({ const TimelineScreen({
required this.options,
required this.posts, required this.posts,
required this.onPostTap,
this.controller, this.controller,
this.timelineCategoryFilter, this.timelineCategoryFilter,
this.timelinePostHeight = 100.0, this.timelinePostHeight = 100.0,
super.key, super.key,
}); });
final TimelineOptions options;
final ScrollController? controller; final ScrollController? controller;
final String? timelineCategoryFilter; final String? timelineCategoryFilter;
@ -22,6 +28,8 @@ class TimelineScreen extends StatefulWidget {
final List<TimelinePost> posts; final List<TimelinePost> posts;
final Function(TimelinePost) onPostTap;
@override @override
State<TimelineScreen> createState() => _TimelineScreenState(); State<TimelineScreen> createState() => _TimelineScreenState();
} }
@ -36,5 +44,22 @@ class _TimelineScreenState extends State<TimelineScreen> {
} }
@override @override
Widget build(BuildContext context) => const Placeholder(); Widget build(BuildContext context) => SingleChildScrollView(
child: Column(
children: [
for (var post in widget.posts)
if (widget.timelineCategoryFilter == null ||
post.category == widget.timelineCategoryFilter)
Padding(
padding: const EdgeInsets.symmetric(vertical: 8.0),
child: TimelinePostWidget(
options: widget.options,
post: post,
height: widget.timelinePostHeight,
onTap: () => widget.onPostTap.call(post),
),
),
],
),
);
} }

View file

@ -0,0 +1,116 @@
import 'package:cached_network_image/cached_network_image.dart';
import 'package:flutter/material.dart';
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
import 'package:flutter_timeline_view/src/config/timeline_options.dart';
class TimelinePostWidget extends StatelessWidget {
const TimelinePostWidget({
required this.options,
required this.post,
required this.height,
this.onTap,
super.key,
});
final TimelineOptions options;
final TimelinePost post;
final double height;
final VoidCallback? onTap;
@override
Widget build(BuildContext context) {
var theme = Theme.of(context);
return InkWell(
onTap: onTap,
child: SizedBox(
height: height,
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (post.creator != null)
Row(
children: [
if (post.creator!.imageUrl != null) ...[
options.userAvatarBuilder?.call(
post.creator!,
40,
) ??
CircleAvatar(
radius: 20,
backgroundImage: CachedNetworkImageProvider(
post.creator!.imageUrl!,
),
),
],
const SizedBox(width: 10),
if (post.creator!.fullName != null) ...[
Text(
post.creator!.fullName!,
style: theme.textTheme.titleMedium,
),
],
// three small dots at the end
const Spacer(),
const Icon(Icons.more_horiz),
],
),
const SizedBox(height: 8),
// image of the post
if (post.imageUrl != null) ...[
Flexible(
child: CachedNetworkImage(
imageUrl: post.imageUrl!,
width: double.infinity,
fit: BoxFit.fitWidth,
),
),
],
// post information
Row(
children: [
// like icon
IconButton(
onPressed: () {},
icon: const Icon(Icons.thumb_up_rounded),
),
// comment icon
IconButton(
onPressed: () {},
icon: const Icon(
Icons.chat_bubble_outline_rounded,
),
),
],
),
Text(
'${post.likes} ${options.translations.likesTitle}',
style: theme.textTheme.titleSmall,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text(
post.creator?.fullName ?? '',
style: theme.textTheme.titleSmall,
),
const SizedBox(width: 8),
Text(
post.content,
style: theme.textTheme.bodyMedium,
overflow: TextOverflow.fade,
),
],
),
Text(
options.translations.viewPost,
style: theme.textTheme.bodySmall,
),
],
),
),
);
}
}