diff --git a/packages/flutter_timeline_view/example/.gitignore b/packages/flutter_timeline/example/.gitignore similarity index 98% rename from packages/flutter_timeline_view/example/.gitignore rename to packages/flutter_timeline/example/.gitignore index 24476c5..29a3a50 100644 --- a/packages/flutter_timeline_view/example/.gitignore +++ b/packages/flutter_timeline/example/.gitignore @@ -27,7 +27,6 @@ migrate_working_dir/ .dart_tool/ .flutter-plugins .flutter-plugins-dependencies -.packages .pub-cache/ .pub/ /build/ diff --git a/packages/flutter_timeline_view/example/analysis_options.yaml b/packages/flutter_timeline/example/analysis_options.yaml similarity index 100% rename from packages/flutter_timeline_view/example/analysis_options.yaml rename to packages/flutter_timeline/example/analysis_options.yaml diff --git a/packages/flutter_timeline/example/lib/main.dart b/packages/flutter_timeline/example/lib/main.dart new file mode 100644 index 0000000..f3d1e3c --- /dev/null +++ b/packages/flutter_timeline/example/lib/main.dart @@ -0,0 +1,93 @@ +import 'package:example/post_screen.dart'; +import 'package:example/timeline_service.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_timeline/flutter_timeline.dart'; +import 'package:intl/date_symbol_data_local.dart'; + +void main() { + initializeDateFormatting(); + + runApp(const MyApp()); +} + +class MyApp extends StatelessWidget { + const MyApp({super.key}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Timeline', + theme: ThemeData( + colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), + useMaterial3: true, + ), + home: const MyHomePage(), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({ + super.key, + }); + + @override + State createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + var timelineService = TestTimelineService(); + + @override + Widget build(BuildContext context) { + print('test'); + return Scaffold( + floatingActionButton: FloatingActionButton( + onPressed: () { + createPost(); + }, + child: const Icon( + Icons.add, + color: Colors.white, + ), + ), + body: SafeArea( + child: TimelineScreen( + userId: 'test_id', + options: const TimelineOptions(), + onPostTap: (post) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => PostScreen( + service: timelineService, + post: post, + ), + ), + ); + }, + service: timelineService, + ), + ), + ); + } + + void createPost() { + print('creating post'); + var amountOfPosts = timelineService.getPosts('text').length; + + timelineService.createPost( + TimelinePost( + id: 'Post$amountOfPosts', + creatorId: 'test_user', + title: 'Post $amountOfPosts', + category: 'text', + content: "Post $amountOfPosts content", + likes: 0, + reaction: 0, + createdAt: DateTime.now(), + reactionEnabled: false, + ), + ); + } +} diff --git a/packages/flutter_timeline/example/lib/post_screen.dart b/packages/flutter_timeline/example/lib/post_screen.dart new file mode 100644 index 0000000..21d9b77 --- /dev/null +++ b/packages/flutter_timeline/example/lib/post_screen.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_timeline/flutter_timeline.dart'; + +class PostScreen extends StatefulWidget { + const PostScreen({ + required this.service, + required this.post, + super.key, + }); + + final TimelineService service; + final TimelinePost post; + + @override + State createState() => _PostScreenState(); +} + +class _PostScreenState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + body: TimelinePostScreen( + userId: 'test_user', + service: widget.service, + userService: TestUserService(), + options: const TimelineOptions(), + post: widget.post, + onPostDelete: () { + print('delete post'); + }, + ), + ); + } +} + +class TestUserService implements TimelineUserService { + final Map _users = { + 'test_user': const TimelinePosterUserModel(userId: 'test_user') + }; + + @override + Future getUser(String userId) async { + if (_users.containsKey(userId)) { + return _users[userId]!; + } + + _users[userId] = TimelinePosterUserModel(userId: userId); + + return TimelinePosterUserModel(userId: userId); + } +} diff --git a/packages/flutter_timeline/example/lib/timeline_service.dart b/packages/flutter_timeline/example/lib/timeline_service.dart new file mode 100644 index 0000000..9e3e539 --- /dev/null +++ b/packages/flutter_timeline/example/lib/timeline_service.dart @@ -0,0 +1,186 @@ +// SPDX-FileCopyrightText: 2023 Iconica +// +// SPDX-License-Identifier: BSD-3-Clause + +import 'dart:typed_data'; + +import 'package:flutter/material.dart'; +import 'package:flutter_timeline/flutter_timeline.dart'; + +// ignore: depend_on_referenced_packages +import 'package:uuid/uuid.dart'; + +class TestTimelineService with ChangeNotifier implements TimelineService { + List _posts = []; + + @override + Future createPost(TimelinePost post) async { + _posts.add(post); + notifyListeners(); + return post; + } + + @override + Future deletePost(TimelinePost post) async { + _posts = _posts.where((element) => element.id != post.id).toList(); + + notifyListeners(); + } + + @override + Future deletePostReaction( + TimelinePost post, + String reactionId, + ) async { + if (post.reactions != null && post.reactions!.isNotEmpty) { + var reaction = + post.reactions!.firstWhere((element) => element.id == reactionId); + var updatedPost = post.copyWith( + reaction: post.reaction - 1, + reactions: (post.reactions ?? [])..remove(reaction), + ); + _posts = _posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + + notifyListeners(); + return updatedPost; + } + return post; + } + + @override + Future fetchPostDetails(TimelinePost post) async { + var reactions = post.reactions ?? []; + var updatedReactions = []; + for (var reaction in reactions) { + updatedReactions.add(reaction.copyWith( + creator: const TimelinePosterUserModel(userId: 'test_user'))); + } + var updatedPost = post.copyWith(reactions: updatedReactions); + _posts = _posts.map((p) => (p.id == post.id) ? updatedPost : p).toList(); + notifyListeners(); + return updatedPost; + } + + @override + Future> fetchPosts(String? category) async { + print('fetch posts'); + var posts = getMockedPosts(); + _posts = posts; + notifyListeners(); + return posts; + } + + @override + Future> fetchPostsPaginated( + String? category, + int limit, + ) async { + notifyListeners(); + return _posts; + } + + @override + Future fetchPost(TimelinePost post) async { + notifyListeners(); + return post; + } + + @override + Future> refreshPosts(String? category) async { + var posts = []; + + _posts = [...posts, ..._posts]; + notifyListeners(); + return posts; + } + + @override + TimelinePost? getPost(String postId) => + (_posts.any((element) => element.id == postId)) + ? _posts.firstWhere((element) => element.id == postId) + : null; + + @override + List getPosts(String? category) => _posts + .where((element) => category == null || element.category == category) + .toList(); + + @override + Future likePost(String userId, TimelinePost post) async { + var updatedPost = post.copyWith( + likes: post.likes + 1, + likedBy: post.likedBy?..add(userId), + ); + _posts = _posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + + notifyListeners(); + return updatedPost; + } + + @override + Future unlikePost(String userId, TimelinePost post) async { + var updatedPost = post.copyWith( + likes: post.likes - 1, + likedBy: post.likedBy?..remove(userId), + ); + _posts = _posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + + notifyListeners(); + return updatedPost; + } + + @override + Future reactToPost( + TimelinePost post, + TimelinePostReaction reaction, { + Uint8List? image, + }) async { + var reactionId = const Uuid().v4(); + var updatedReaction = reaction.copyWith( + id: reactionId, + creator: const TimelinePosterUserModel(userId: 'test_user')); + + + var updatedPost = post.copyWith( + reaction: post.reaction + 1, + reactions: post.reactions?..add(updatedReaction), + ); + + + _posts = _posts + .map( + (p) => p.id == post.id ? updatedPost : p, + ) + .toList(); + notifyListeners(); + return updatedPost; + } + + List getMockedPosts() { + return [ + TimelinePost( + id: 'Post0', + creatorId: 'test_user', + title: 'Post 0', + category: 'text', + content: "Post 0 content", + likes: 0, + reaction: 0, + createdAt: DateTime.now(), + reactionEnabled: false, + ) + ]; + } +} diff --git a/packages/flutter_timeline/example/pubspec.yaml b/packages/flutter_timeline/example/pubspec.yaml new file mode 100644 index 0000000..9a87739 --- /dev/null +++ b/packages/flutter_timeline/example/pubspec.yaml @@ -0,0 +1,95 @@ +name: example +description: "A new Flutter project." +# The following line prevents the package from being accidentally published to +# pub.dev using `flutter pub publish`. This is preferred for private packages. +publish_to: 'none' # Remove this line if you wish to publish to pub.dev + +# The following defines the version and build number for your application. +# A version number is three numbers separated by dots, like 1.2.43 +# followed by an optional build number separated by a +. +# Both the version and the builder number may be overridden in flutter +# build by specifying --build-name and --build-number, respectively. +# In Android, build-name is used as versionName while build-number used as versionCode. +# Read more about Android versioning at https://developer.android.com/studio/publish/versioning +# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. +# Read more about iOS versioning at +# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +# In Windows, build-name is used as the major, minor, and patch parts +# of the product and file versions while build-number is used as the build suffix. +version: 1.0.0+1 + +environment: + sdk: '>=3.2.3 <4.0.0' + +# Dependencies specify other packages that your package needs in order to work. +# To automatically upgrade your package dependencies to the latest versions +# consider running `flutter pub upgrade --major-versions`. Alternatively, +# dependencies can be manually updated by changing the version numbers below to +# the latest version available on pub.dev. To see which dependencies have newer +# versions available, run `flutter pub outdated`. +dependencies: + flutter: + sdk: flutter + + + # The following adds the Cupertino Icons font to your application. + # Use with the CupertinoIcons class for iOS style icons. + cupertino_icons: ^1.0.2 + flutter_timeline: + path: ../ + flutter_timeline_firebase: + path: ../../flutter_timeline_firebase + intl: ^0.19.0 + +dev_dependencies: + flutter_test: + sdk: flutter + + # The "flutter_lints" package below contains a set of recommended lints to + # encourage good coding practices. The lint set provided by the package is + # activated in the `analysis_options.yaml` file located at the root of your + # package. See that file for information about deactivating specific lint + # rules and activating additional ones. + flutter_lints: ^2.0.0 + +# For information on the generic Dart part of this file, see the +# following page: https://dart.dev/tools/pub/pubspec + +# The following section is specific to Flutter packages. +flutter: + + # The following line ensures that the Material Icons font is + # included with your application, so that you can use the icons in + # the material Icons class. + uses-material-design: true + + # To add assets to your application, add an assets section, like this: + # assets: + # - images/a_dot_burr.jpeg + # - images/a_dot_ham.jpeg + + # An image asset can refer to one or more resolution-specific "variants", see + # https://flutter.dev/assets-and-images/#resolution-aware + + # For details regarding adding assets from package dependencies, see + # https://flutter.dev/assets-and-images/#from-packages + + # To add custom fonts to your application, add a fonts section here, + # in this "flutter" section. Each entry in this list should have a + # "family" key with the font family name, and a "fonts" key with a + # list giving the asset and other descriptors for the font. For + # example: + # fonts: + # - family: Schyler + # fonts: + # - asset: fonts/Schyler-Regular.ttf + # - asset: fonts/Schyler-Italic.ttf + # style: italic + # - family: Trajan Pro + # fonts: + # - asset: fonts/TrajanPro.ttf + # - asset: fonts/TrajanPro_Bold.ttf + # weight: 700 + # + # For details regarding fonts from package dependencies, + # see https://flutter.dev/custom-fonts/#from-packages diff --git a/packages/flutter_timeline/example/test/widget_test.dart b/packages/flutter_timeline/example/test/widget_test.dart new file mode 100644 index 0000000..092d222 --- /dev/null +++ b/packages/flutter_timeline/example/test/widget_test.dart @@ -0,0 +1,30 @@ +// 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:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:example/main.dart'; + +void main() { + testWidgets('Counter increments smoke test', (WidgetTester tester) async { + // Build our app and trigger a frame. + await tester.pumpWidget(const MyApp()); + + // 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); + }); +} diff --git a/packages/flutter_timeline_view/example/lib/main.dart b/packages/flutter_timeline_view/example/lib/main.dart deleted file mode 100644 index 568731b..0000000 --- a/packages/flutter_timeline_view/example/lib/main.dart +++ /dev/null @@ -1,49 +0,0 @@ -// SPDX-FileCopyrightText: 2023 Iconica -// -// SPDX-License-Identifier: BSD-3-Clause - -import 'package:flutter/material.dart'; - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - @override - Widget build(BuildContext context) { - return MaterialApp( - title: 'Flutter Timeline Example', - theme: ThemeData( - colorScheme: ColorScheme.fromSeed(seedColor: Colors.deepPurple), - useMaterial3: true, - ), - home: const MyHomePage(), - ); - } -} - -class MyHomePage extends StatelessWidget { - const MyHomePage({ - super.key, - }); - - @override - Widget build(BuildContext context) { - return Scaffold( - appBar: AppBar( - backgroundColor: Theme.of(context).colorScheme.inversePrimary, - ), - body: const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - 'You have pushed the button this many times:', - ), - ], - ), - ), - ); - } -} diff --git a/packages/flutter_timeline_view/example/pubspec.yaml b/packages/flutter_timeline_view/example/pubspec.yaml deleted file mode 100644 index b8097d1..0000000 --- a/packages/flutter_timeline_view/example/pubspec.yaml +++ /dev/null @@ -1,21 +0,0 @@ -name: example -description: Flutter timeline example -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -version: 1.0.0+1 - -environment: - sdk: '>=3.1.3 <4.0.0' - -dependencies: - flutter: - sdk: flutter - cupertino_icons: ^1.0.2 - -dev_dependencies: - flutter_test: - sdk: flutter - flutter_lints: ^2.0.0 - -flutter: - uses-material-design: true diff --git a/packages/flutter_timeline_view/example/test/widget_test.dart b/packages/flutter_timeline_view/example/test/widget_test.dart deleted file mode 100644 index 73b773e..0000000 --- a/packages/flutter_timeline_view/example/test/widget_test.dart +++ /dev/null @@ -1,14 +0,0 @@ -// 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:flutter_test/flutter_test.dart'; - -void main() { - test('blank test', () { - expect(true, isTrue); - }); -}