mirror of
https://github.com/Iconica-Development/flutter_timeline.git
synced 2025-05-19 10:33:44 +02:00
feat: add timelineoverview cards
This commit is contained in:
parent
55e4e50798
commit
c33d8cb893
8 changed files with 199 additions and 3 deletions
|
@ -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';
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
);
|
||||||
|
|
|
@ -11,7 +11,6 @@ class TimelinePostScreen extends StatelessWidget {
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
final TimelinePost post;
|
final TimelinePost post;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
|
@ -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),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue