feat: add timeline selection screen

This commit is contained in:
Freek van de Ven 2023-12-03 20:18:41 +01:00
parent 0035e1f4fb
commit e523f52118
14 changed files with 187 additions and 63 deletions

View file

@ -1,9 +1,12 @@
// SPDX-FileCopyrightText: 2023 Iconica // SPDX-FileCopyrightText: 2023 Iconica
// //
// SPDX-License-Identifier: BSD-3-Clause // SPDX-License-Identifier: BSD-3-Clause
/// Flutter Timeline library /// Flutter Timeline library
library flutter_timeline; library flutter_timeline;
export 'package:flutter_timeline/src/flutter_timeline_userstory.dart'; export 'package:flutter_timeline/src/flutter_timeline_userstory.dart';
export 'package:flutter_timeline/src/models/timeline_configuration.dart';
export 'package:flutter_timeline/src/routes.dart';
export 'package:flutter_timeline_interface/flutter_timeline_interface.dart'; export 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
export 'package:flutter_timeline_view/flutter_timeline_view.dart'; export 'package:flutter_timeline_view/flutter_timeline_view.dart';

View file

@ -1,50 +1,14 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_timeline/flutter_timeline.dart';
import 'package:flutter_timeline/src/go_router.dart'; import 'package:flutter_timeline/src/go_router.dart';
import 'package:flutter_timeline/src/models/timeline_configuration.dart';
import 'package:flutter_timeline/src/routes.dart';
import 'package:flutter_timeline_view/flutter_timeline_view.dart';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';
mixin TimelineUserStoryRoutes {
static const String timelineHome = '/timeline';
static const String timelineCreate = '/timeline-create/:category';
static String timelineCreatePath(String category) =>
'/timeline-create/$category';
static const String timelineSelect = '/timeline-select';
static const String timelineView = '/timeline-view/:post';
static String timelineViewPath(String postId) => '/timeline-view/$postId';
}
class TimelineUserStoryConfiguration {
const TimelineUserStoryConfiguration({
required this.optionsBuilder,
required this.userId,
required this.service,
required this.userService,
this.mainPageBuilder,
this.postScreenBuilder,
this.postCreationScreenBuilder,
this.postSelectionScreenBuilder,
this.onUserTap,
});
final String userId;
final Function(String userId)? onUserTap;
final Widget Function(Widget filterBar, Widget child)? mainPageBuilder;
final Widget Function(Widget child)? postScreenBuilder;
final Widget Function(Widget child)? postCreationScreenBuilder;
final Widget Function(Widget child)? postSelectionScreenBuilder;
final TimelineService service;
final TimelineUserService userService;
final TimelineOptions Function(BuildContext context) optionsBuilder;
}
List<GoRoute> getTimelineStoryRoutes( List<GoRoute> getTimelineStoryRoutes(
TimelineUserStoryConfiguration configuration, TimelineUserStoryConfiguration configuration,
) => ) =>
@ -52,20 +16,23 @@ List<GoRoute> getTimelineStoryRoutes(
GoRoute( GoRoute(
path: TimelineUserStoryRoutes.timelineHome, path: TimelineUserStoryRoutes.timelineHome,
pageBuilder: (context, state) { pageBuilder: (context, state) {
var timelineFilter =
Container(); // TODO(anyone): create a filter widget
var timelineScreen = TimelineScreen( var timelineScreen = TimelineScreen(
userId: configuration.userId, userId: configuration.userId,
onUserTap: configuration.onUserTap, onUserTap: (user) => configuration.onUserTap?.call(context, user),
service: configuration.service, service: configuration.service,
options: configuration.optionsBuilder(context), options: configuration.optionsBuilder(context),
onPostTap: (post) async => onPostTap: (post) async =>
TimelineUserStoryRoutes.timelineViewPath(post.id), TimelineUserStoryRoutes.timelineViewPath(post.id),
timelineCategoryFilter: 'news', timelineCategoryFilter: null,
); );
return buildScreenWithoutTransition( return buildScreenWithoutTransition(
context: context, context: context,
state: state, state: state,
child: configuration.mainPageBuilder?.call( child: configuration.mainPageBuilder?.call(
Container(), // TODO(anyone): create a selection widget context,
timelineFilter,
timelineScreen, timelineScreen,
) ?? ) ??
Scaffold( Scaffold(
@ -77,12 +44,18 @@ List<GoRoute> getTimelineStoryRoutes(
GoRoute( GoRoute(
path: TimelineUserStoryRoutes.timelineSelect, path: TimelineUserStoryRoutes.timelineSelect,
pageBuilder: (context, state) { pageBuilder: (context, state) {
var timelineSelectionWidget = var timelineSelectionWidget = TimelineSelectionScreen(
Container(); // TODO(anyone): create timeline selection screen options: configuration.optionsBuilder(context),
categories: configuration.categoriesBuilder(context),
onCategorySelected: (category) async => context.push(
TimelineUserStoryRoutes.timelineCreatePath(category.name),
),
);
return buildScreenWithoutTransition( return buildScreenWithoutTransition(
context: context, context: context,
state: state, state: state,
child: configuration.postSelectionScreenBuilder?.call( child: configuration.postSelectionScreenBuilder?.call(
context,
timelineSelectionWidget, timelineSelectionWidget,
) ?? ) ??
Scaffold( Scaffold(
@ -106,8 +79,10 @@ List<GoRoute> getTimelineStoryRoutes(
return buildScreenWithoutTransition( return buildScreenWithoutTransition(
context: context, context: context,
state: state, state: state,
child: configuration.postCreationScreenBuilder child: configuration.postCreationScreenBuilder?.call(
?.call(timelineCreateWidget) ?? context,
timelineCreateWidget,
) ??
Scaffold( Scaffold(
body: timelineCreateWidget, body: timelineCreateWidget,
), ),
@ -123,16 +98,17 @@ List<GoRoute> getTimelineStoryRoutes(
service: configuration.service, service: configuration.service,
userService: configuration.userService, userService: configuration.userService,
post: configuration.service.getPost(state.pathParameters['post']!)!, post: configuration.service.getPost(state.pathParameters['post']!)!,
onPostDelete: () => context.go( onPostDelete: () => context.pop(),
TimelineUserStoryRoutes.timelineHome, onUserTap: (user) => configuration.onUserTap?.call(context, user),
),
onUserTap: configuration.onUserTap,
); );
var category = configuration.categoriesBuilder(context).first;
return buildScreenWithoutTransition( return buildScreenWithoutTransition(
context: context, context: context,
state: state, state: state,
child: configuration.postScreenBuilder?.call( child: configuration.postScreenBuilder?.call(
context,
timelinePostWidget, timelinePostWidget,
category,
) ?? ) ??
Scaffold( Scaffold(
body: timelinePostWidget, body: timelinePostWidget,

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';
import 'package:go_router/go_router.dart'; import 'package:go_router/go_router.dart';

View file

@ -0,0 +1,46 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
import 'package:flutter/material.dart';
import 'package:flutter_timeline_interface/flutter_timeline_interface.dart';
import 'package:flutter_timeline_view/flutter_timeline_view.dart';
@immutable
class TimelineUserStoryConfiguration {
const TimelineUserStoryConfiguration({
required this.categoriesBuilder,
required this.optionsBuilder,
required this.userId,
required this.service,
required this.userService,
this.mainPageBuilder,
this.postScreenBuilder,
this.postCreationScreenBuilder,
this.postSelectionScreenBuilder,
this.onUserTap,
});
final String userId;
final Function(BuildContext context, String userId)? onUserTap;
final Widget Function(BuildContext context, Widget filterBar, Widget child)?
mainPageBuilder;
final Widget Function(BuildContext context, Widget child, TimelineCategory category)? postScreenBuilder;
final Widget Function(BuildContext context, Widget child)?
postCreationScreenBuilder;
final Widget Function(BuildContext context, Widget child)?
postSelectionScreenBuilder;
final TimelineService service;
final TimelineUserService userService;
final TimelineOptions Function(BuildContext context) optionsBuilder;
final List<TimelineCategory> Function(BuildContext context) categoriesBuilder;
}

View file

@ -0,0 +1,13 @@
// SPDX-FileCopyrightText: 2023 Iconica
//
// SPDX-License-Identifier: BSD-3-Clause
mixin TimelineUserStoryRoutes {
static const String timelineHome = '/timeline';
static const String timelineCreate = '/timeline-create/:category';
static String timelineCreatePath(String category) =>
'/timeline-create/$category';
static const String timelineSelect = '/timeline-select';
static const String timelineView = '/timeline-view/:post';
static String timelineViewPath(String postId) => '/timeline-view/$postId';
}

View file

@ -16,12 +16,12 @@ dependencies:
go_router: ^12.1.1 go_router: ^12.1.1
flutter_timeline_view: flutter_timeline_view:
git: git:
url: https://github.com/Iconica-Development/flutter_timeline.git url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_view path: packages/flutter_timeline_view
ref: 1.0.0 ref: 1.0.0
flutter_timeline_interface: flutter_timeline_interface:
git: git:
url: https://github.com/Iconica-Development/flutter_timeline.git url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface path: packages/flutter_timeline_interface
ref: 1.0.0 ref: 1.0.0

View file

@ -21,7 +21,7 @@ dependencies:
flutter_timeline_interface: flutter_timeline_interface:
git: git:
url: https://github.com/Iconica-Development/flutter_timeline.git url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface path: packages/flutter_timeline_interface
ref: 1.0.0 ref: 1.0.0

View file

@ -4,9 +4,9 @@
/// ///
library flutter_timeline_interface; library flutter_timeline_interface;
export 'src/model/timeline_category.dart';
export 'src/model/timeline_post.dart'; export 'src/model/timeline_post.dart';
export 'src/model/timeline_poster.dart'; export 'src/model/timeline_poster.dart';
export 'src/model/timeline_reaction.dart'; export 'src/model/timeline_reaction.dart';
export 'src/services/timeline_service.dart'; export 'src/services/timeline_service.dart';
export 'src/services/user_service.dart'; export 'src/services/user_service.dart';

View file

@ -0,0 +1,17 @@
import 'package:flutter/material.dart';
@immutable
class TimelineCategory {
const TimelineCategory({
required this.name,
required this.title,
required this.icon,
this.canCreate = true,
this.canView = true,
});
final String name;
final String title;
final Widget icon;
final bool canCreate;
final bool canView;
}

View file

@ -14,10 +14,6 @@ environment:
dependencies: dependencies:
flutter: flutter:
sdk: flutter sdk: flutter
flutter_data_interface:
git:
url: https://github.com/Iconica-Development/flutter_data_interface.git
ref: 1.0.0
dev_dependencies: dev_dependencies:
flutter_lints: ^2.0.0 flutter_lints: ^2.0.0

View file

@ -10,4 +10,5 @@ export 'src/config/timeline_translations.dart';
export 'src/screens/timeline_post_creation_screen.dart'; export 'src/screens/timeline_post_creation_screen.dart';
export 'src/screens/timeline_post_screen.dart'; export 'src/screens/timeline_post_screen.dart';
export 'src/screens/timeline_screen.dart'; export 'src/screens/timeline_screen.dart';
export 'src/screens/timeline_selection_screen.dart';
export 'src/widgets/timeline_post_widget.dart'; export 'src/widgets/timeline_post_widget.dart';

View file

@ -27,6 +27,7 @@ class TimelineTranslations {
this.writeComment = 'Write your comment here...', this.writeComment = 'Write your comment here...',
this.postAt = 'at', this.postAt = 'at',
this.postLoadingError = 'Something went wrong while loading the post', this.postLoadingError = 'Something went wrong while loading the post',
this.timelineSelectionDescription = 'Choose a category',
}); });
final String noPosts; final String noPosts;
@ -50,4 +51,6 @@ class TimelineTranslations {
final String writeComment; final String writeComment;
final String firstComment; final String firstComment;
final String postLoadingError; final String postLoadingError;
final String timelineSelectionDescription;
} }

View file

@ -0,0 +1,65 @@
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 TimelineSelectionScreen extends StatelessWidget {
const TimelineSelectionScreen({
required this.options,
required this.categories,
required this.onCategorySelected,
super.key,
});
final List<TimelineCategory> categories;
final TimelineOptions options;
final Function(TimelineCategory) onCategorySelected;
@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,
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: EdgeInsets.only(top: size.height * 0.05, bottom: 8),
child: Text(
options.translations.timelineSelectionDescription,
style: theme.textTheme.displayMedium,
),
),
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(
category.title,
style: theme.textTheme.displaySmall,
),
),
),
],
],
),
);
}
}

View file

@ -21,7 +21,7 @@ dependencies:
flutter_timeline_interface: flutter_timeline_interface:
git: git:
url: https://github.com/Iconica-Development/flutter_timeline.git url: https://github.com/Iconica-Development/flutter_timeline
path: packages/flutter_timeline_interface path: packages/flutter_timeline_interface
ref: 1.0.0 ref: 1.0.0
flutter_image_picker: flutter_image_picker: