From 84d388e7e21082ab81ca5087fbd78189da05d3b5 Mon Sep 17 00:00:00 2001 From: Muhammad Umar Manzoor Chaudhary <124767245+umareko@users.noreply.github.com> Date: Tue, 11 Jun 2024 11:15:00 +0700 Subject: [PATCH 01/10] Fix/um/asc 23056/duplicate the reply post once user added new reply (#230) * Fix: Added A comment Query Screen with the Pagination * Chore: Version Update --- lib/core/route/app_router.dart | 3 +- lib/core/widget/comment_widget.dart | 2 +- .../comment_query_pagination_screen.dart | 439 ++++++++++++++++++ .../screen/dashboard/dashboard_screen.dart | 6 + .../screen/login/login_screen.dart | 35 +- pubspec.yaml | 3 +- 6 files changed, 473 insertions(+), 15 deletions(-) create mode 100644 lib/presentation/screen/comment_query_pagination/comment_query_pagination_screen.dart diff --git a/lib/core/route/app_router.dart b/lib/core/route/app_router.dart index 55f4f20..ebb5279 100644 --- a/lib/core/route/app_router.dart +++ b/lib/core/route/app_router.dart @@ -9,6 +9,7 @@ import 'package:flutter_social_sample_app/presentation/screen/channel_profile/ch import 'package:flutter_social_sample_app/presentation/screen/channel_update/channel_update_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/chat/chat_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/comment_query/comment_query_screen.dart'; +import 'package:flutter_social_sample_app/presentation/screen/comment_query_pagination/comment_query_pagination_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/comment_query_reply/comment_query_reply_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/community_category/community_category_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/community_create/community_create_screen.dart'; @@ -404,7 +405,7 @@ class AppRouter { name: AppRoute.commentList, path: AppRoute.commentListRoute, builder: (context, state) { - return CommentQueryScreen( + return CommentQueryPaginationScreen( referenceType: state.queryParams['referenceType']!, referenceId: state.queryParams['referenceId']!, communityId: state.queryParams['communityId'], diff --git a/lib/core/widget/comment_widget.dart b/lib/core/widget/comment_widget.dart index decd32c..2502f8d 100644 --- a/lib/core/widget/comment_widget.dart +++ b/lib/core/widget/comment_widget.dart @@ -431,7 +431,7 @@ class _CommentWidgetState extends State { queryParams: { 'referenceType': widget.referenceType, 'referenceId': widget.referenceId, - 'parentId': value.commentId, + 'parentCommentId': value.commentId, 'communityId': widget.communityId, 'isPublic': widget.isPublic.toString() }, diff --git a/lib/presentation/screen/comment_query_pagination/comment_query_pagination_screen.dart b/lib/presentation/screen/comment_query_pagination/comment_query_pagination_screen.dart new file mode 100644 index 0000000..924e6fc --- /dev/null +++ b/lib/presentation/screen/comment_query_pagination/comment_query_pagination_screen.dart @@ -0,0 +1,439 @@ +import 'dart:async'; + +import 'package:amity_sdk/amity_sdk.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_social_sample_app/core/widget/add_comment_widget.dart'; +import 'package:flutter_social_sample_app/core/widget/comment_widget.dart'; +import 'package:flutter_social_sample_app/core/widget/common_snackbar.dart'; +import 'package:flutter_social_sample_app/core/widget/dialog/error_dialog.dart'; +import 'package:flutter_social_sample_app/core/widget/dialog/progress_dialog_widget.dart'; + +class CommentQueryPaginationScreen extends StatefulWidget { + final String? communityId; + final bool isPublic; + final String referenceType; + final String referenceId; + const CommentQueryPaginationScreen( + {super.key, + this.communityId, + required this.isPublic, + required this.referenceType, + required this.referenceId}); + + @override + State createState() => + _CommentQueryPaginationScreenState(); +} + +class _CommentQueryPaginationScreenState + extends State { + List amityComments = []; + late PagingController _controller; + + final scrollcontroller = ScrollController(); + bool loading = false; + + AmityComment? _replyToComment; + + AmityCommentSortOption _sortOption = AmityCommentSortOption.LAST_CREATED; + + final mentionUsers = []; + + AmityCommentDataTypeFilter? dataTypes; + + bool _includeDeleted = false; + + @override + void initState() { + + if (widget.referenceType == 'post') { + _controller = PagingController( + pageFuture: (token) => AmitySocialClient.newCommentRepository() + .getComments() + .post(widget.referenceId) + .sortBy(_sortOption) + .parentId(null) + .includeDeleted(true) + .getPagingData(token: token, limit: 20), + pageSize: 20, + )..addListener( + () async { + if (_controller.error == null) { + setState(() { + amityComments.clear(); + amityComments.addAll(_controller.loadedItems); + }); + } else { + //Error on pagination controller + setState(() {}); + print(_controller.stacktrace); + ErrorDialog.show(context, + title: 'Error', + message: '${_controller.error}\n${_controller.stacktrace}'); + } + }, + ); + } + if (widget.referenceType == 'story') { + _controller = PagingController( + pageFuture: (token) => AmitySocialClient.newCommentRepository() + .getComments() + .story(widget.referenceId) + .sortBy(_sortOption) + .parentId(null) + .includeDeleted(true) + .getPagingData(token: token, limit: 20), + pageSize: 20, + )..addListener( + () async { + if (_controller.error == null) { + // Instead of clearing and re-adding all items, directly append new items + // This assumes `amityComments` is a List that can be compared with _controller.loadedItems for duplicates + var newComments = _controller.loadedItems; + // Append only new comments + var currentIds = amityComments.map((e) => e.commentId).toSet(); + var newItems = newComments + .where((item) => !currentIds.contains(item.commentId)) + .toList(); + if (newItems.isNotEmpty) { + amityComments.addAll(newItems); + } + } else {} + }, + ); + } + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _controller.fetchNextPage(); + }); + + scrollcontroller.addListener(loadnextpage); + } + + void loadnextpage() { + if ((scrollcontroller.position.pixels == + scrollcontroller.position.maxScrollExtent) && + _controller.hasMoreItems) { + setState(() { + _controller.fetchNextPage(); + }); + } + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: const Text('Comment Feed'), + actions: [ + PopupMenuButton( + itemBuilder: (context) { + return [ + PopupMenuItem( + value: 1, + child: Text(AmityCommentSortOption.LAST_CREATED.apiKey), + ), + PopupMenuItem( + value: 8, + child: Text(AmityCommentSortOption.LAST_UPDATED.apiKey), + ), + PopupMenuItem( + value: 2, + child: Text(AmityCommentSortOption.FIRST_CREATED.apiKey), + ), + PopupMenuItem( + value: 9, + child: Text(AmityCommentSortOption.FIRST_UPDATED.apiKey), + ), + const PopupMenuItem( + value: 3, + child: Text('Excat TEXT'), + ), + const PopupMenuItem( + value: 4, + child: Text('Excat IMAGE'), + ), + const PopupMenuItem( + value: 11, + child: Text('Excat IMAGE & TEXT'), + ), + const PopupMenuItem( + value: 5, + child: Text('Any TEXT'), + ), + const PopupMenuItem( + value: 6, + child: Text('Any IMAGE'), + ), + const PopupMenuItem( + value: 12, + child: Text('Any IMAGE & TEXT'), + ), + const PopupMenuItem( + value: 7, + child: Text('Clear'), + ), + const PopupMenuItem( + value: 10, + child: Text('Include Deleted'), + ), + const PopupMenuItem( + value: 13, + child: Text('Exclude Deleted'), + ), + ]; + }, + child: const Icon( + Icons.sort_rounded, + size: 24, + ), + onSelected: (index1) { + if (index1 == 1) { + _sortOption = AmityCommentSortOption.LAST_CREATED; + } + if (index1 == 2) { + _sortOption = AmityCommentSortOption.FIRST_CREATED; + } + if (index1 == 8) { + _sortOption = AmityCommentSortOption.LAST_UPDATED; + } + if (index1 == 9) { + _sortOption = AmityCommentSortOption.FIRST_UPDATED; + } + + if (index1 == 3) { + dataTypes = AmityCommentDataTypeFilter.exact( + dataTypes: [AmityDataType.TEXT]); + } + + if (index1 == 4) { + dataTypes = AmityCommentDataTypeFilter.exact( + dataTypes: [AmityDataType.IMAGE]); + } + if (index1 == 11) { + dataTypes = AmityCommentDataTypeFilter.exact( + dataTypes: [AmityDataType.TEXT, AmityDataType.IMAGE]); + } + + if (index1 == 5) { + dataTypes = AmityCommentDataTypeFilter.any( + dataTypes: [AmityDataType.TEXT]); + } + + if (index1 == 6) { + dataTypes = AmityCommentDataTypeFilter.any( + dataTypes: [AmityDataType.IMAGE]); + } + + if (index1 == 12) { + dataTypes = AmityCommentDataTypeFilter.any( + dataTypes: [AmityDataType.IMAGE, AmityDataType.TEXT]); + } + + if (index1 == 7) { + dataTypes = null; + } + + if (index1 == 10) { + _includeDeleted = true; + } + + if (index1 == 13) { + _includeDeleted = false; + } + + setState(() {}); + _controller.reset(); + _controller.fetchNextPage(); + }, + ) + ], + ), + body: Column( + children: [ + Expanded( + child: amityComments.isNotEmpty + ? RefreshIndicator( + onRefresh: () async { + _controller.reset(); + _controller.fetchNextPage(); + }, + child: ListView.builder( + controller: scrollcontroller, + physics: const AlwaysScrollableScrollPhysics(), + itemCount: amityComments.length, + itemBuilder: (context, index) { + final amityComment = amityComments[index]; + return CommentWidget( + widget.referenceType, + widget.referenceId, + amityComment, + (value) { + setState(() { + _replyToComment = value; + }); + }, + key: UniqueKey(), + communityId: widget.communityId, + isPublic: widget.isPublic, + ); + }, + ), + ) + : Container( + alignment: Alignment.center, + child: _controller.isFetching + ? const CircularProgressIndicator() + : const Text('No Comment'), + ), + ), + if (_controller.isFetching && amityComments.isNotEmpty) + Container( + alignment: Alignment.center, + child: const CircularProgressIndicator(), + ), + if (_replyToComment != null) + Container( + padding: const EdgeInsets.symmetric(horizontal: 12), + decoration: BoxDecoration(color: Colors.grey.shade200), + child: Row( + children: [ + const Text('Reply to '), + Text('@${_replyToComment!.user!.userId}'), + const Spacer(), + IconButton( + onPressed: () { + setState(() { + _replyToComment = null; + }); + }, + icon: const Icon( + Icons.clear_rounded, + ), + ) + ], + ), + ), + Container( + margin: const EdgeInsets.all(12), + child: AddCommentWidget( + AmityCoreClient.getCurrentUser(), + showMediaButton: true, + (text, user, attachments) async { + final completer = Completer(); + try { + ProgressDialog.showCompleter(context, completer); + + mentionUsers.clear(); + mentionUsers.addAll(user); + + //Clean up mention user list, as user might have removed some tagged user + mentionUsers.removeWhere( + (element) => !text.contains(element.displayName!)); + + final amityMentioneesMetadata = mentionUsers + .map((e) => + AmityUserMentionMetadata( + userId: e.userId!, + index: text.indexOf('@${e.displayName!}'), + length: e.displayName!.length)) + .toList(); + + Map metadata = + AmityMentionMetadataCreator(amityMentioneesMetadata) + .create(); + + List amityImages = []; + if (attachments.isNotEmpty) { + for (var element in attachments) { + final image = await waitForUploadComplete( + AmityCoreClient.newFileRepository() + .uploadImage(element) + .stream); + amityImages + .add(CommentImageAttachment(fileId: image.fileId!)); + } + } + + if (_replyToComment != null) { + ///Add comment to [_replyToComment] comment + final _comment = await _replyToComment! + .comment() + .create() + .attachments(amityImages) + .text(text) + .mentionUsers( + mentionUsers.map((e) => e.userId!).toList()) + .metadata(metadata) + .send(); + + setState(() { + _replyToComment = null; + }); + + return; + } + + if (widget.referenceType == "post") { + final _comment = + await AmitySocialClient.newCommentRepository() + .createComment() + .post(widget.referenceId) + .create() + .attachments(amityImages) + .text(text) + .mentionUsers(mentionUsers + .map((e) => e.userId!) + .toList()) + .metadata(metadata) + .send(); + } else if (widget.referenceType == "story") { + final _comment = + await AmitySocialClient.newCommentRepository() + .createComment() + .story(widget.referenceId) + .create() + .attachments(amityImages) + .text(text) + .mentionUsers(mentionUsers + .map((e) => e.userId!) + .toList()) + .metadata(metadata) + .send(); + } + + return; + } catch (error, stackTrace) { + if (error is AmityException) { + CommonSnackbar.showNagativeSnackbar( + context, 'Error', '$error\n${error.data}'); + } else { + CommonSnackbar.showNagativeSnackbar( + context, 'Error', error.toString()); + } + } finally { + completer.complete(); + } + }, + communityId: widget.communityId, + isPublic: widget.isPublic, + ), + ), + ], + ), + ); + } + + Future waitForUploadComplete(Stream source) { + final completer = Completer(); + source.listen((event) { + event.when( + progress: (uploadInfo, cancelToken) {}, + complete: (file) => completer.complete(file), + error: (error) => completer.completeError(error), + cancel: () {}, + ); + }); + return completer.future; + } +} diff --git a/lib/presentation/screen/dashboard/dashboard_screen.dart b/lib/presentation/screen/dashboard/dashboard_screen.dart index bd87d66..400a6d6 100644 --- a/lib/presentation/screen/dashboard/dashboard_screen.dart +++ b/lib/presentation/screen/dashboard/dashboard_screen.dart @@ -20,6 +20,12 @@ class _DashboardScreenState extends State { @override void initState() { super.initState(); + + FirebaseMessaging.onMessage.listen((event) { + event.notification?.body; + print('onMessage: $event'); + }); + } @override diff --git a/lib/presentation/screen/login/login_screen.dart b/lib/presentation/screen/login/login_screen.dart index d8d309e..7a0f420 100644 --- a/lib/presentation/screen/login/login_screen.dart +++ b/lib/presentation/screen/login/login_screen.dart @@ -1,5 +1,8 @@ +import 'dart:io'; + import 'package:amity_sdk/amity_sdk.dart'; import 'package:amity_video_player/amity_video_player.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; import 'package:flutter_social_sample_app/core/preferences/preference_interface_impl.dart'; import 'package:flutter_social_sample_app/core/route/app_route.dart'; @@ -29,9 +32,11 @@ class _LoginScreenState extends State { // STAGING Server _userIdTextController.text = 'victimAndroid'; _displayNameTextController.text = 'Victim Android'; - _apiKeyTextController.text = 'b0efe90c3bdda2304d628918520c1688845889e4bc363d2c'; - _serverUrlTextController.text = AmityRegionalHttpEndpoint.custom('https://api.staging.amity.co/').endpoint; - + _apiKeyTextController.text = + 'b0efe90c3bdda2304d628918520c1688845889e4bc363d2c'; + _serverUrlTextController.text = + AmityRegionalHttpEndpoint.custom('https://api.staging.amity.co/') + .endpoint; // //STAGING Server // _userIdTextController.text = 'sara'; @@ -39,14 +44,12 @@ class _LoginScreenState extends State { // _apiKeyTextController.text = 'b0efe90c3bdda2304d628918520c1688845889e4bc363d2c'; // _serverUrlTextController.text = AmityRegionalHttpEndpoint.custom('https://api.staging.amity.co/').endpoint; - // DEV Server // _userIdTextController.text = 'sara'; // _displayNameTextController.text = 'sara Android'; // _apiKeyTextController.text = 'b0ecee0c39dca1651d628b1c535d15dbd30ad9b0eb3c3a2f'; // _serverUrlTextController.text = AmityRegionalHttpEndpoint.custom('https://api.dev.amity.co/').endpoint; - // // SG Server // // 1 // _userIdTextController.text = 'johnwick2'; @@ -121,27 +124,35 @@ class _LoginScreenState extends State { await AmityCoreClient.setup( option: AmityCoreClientOption( apiKey: apikey, - httpEndpoint: AmityRegionalHttpEndpoint(_serverUrlTextController.text), + httpEndpoint: AmityRegionalHttpEndpoint( + _serverUrlTextController.text), // mqttEndpoint: AmityRegionalMqttEndpoint.custom('ssq.dev.amity.co'), - mqttEndpoint: AmityRegionalMqttEndpoint.custom('ssq.staging.amity.co'), + mqttEndpoint: AmityRegionalMqttEndpoint.custom( + 'ssq.staging.amity.co'), // mqttEndpoint: AmityRegionalMqttEndpoint.SG, showLogs: true), sycInitialization: true, ); - AmityStreamPlayerClient.setup(AmityCoreClient.getConfiguration()); + AmityStreamPlayerClient.setup( + AmityCoreClient.getConfiguration()); //Login the user String userId = _userIdTextController.text.trim(); - String userDisplayName = _displayNameTextController.text.trim(); + String userDisplayName = + _displayNameTextController.text.trim(); // AmityCoreClient.isUserLoggedIn(); - await AmityCoreClient.login(userId).displayName(userDisplayName).submit(); + await AmityCoreClient.login(userId) + .displayName(userDisplayName) + .submit(); PreferenceInterfaceImpl().setLoggedIn(true); - PreferenceInterfaceImpl().setLoggedInUserDisplayName(userDisplayName); + PreferenceInterfaceImpl() + .setLoggedInUserDisplayName(userDisplayName); PreferenceInterfaceImpl().setLoggedInUserId(userId); GoRouter.of(context).go(AppRoute.homeRoute); } catch (error) { - CommonSnackbar.showNagativeSnackbar(context, 'Error', error.toString()); + CommonSnackbar.showNagativeSnackbar( + context, 'Error', error.toString()); } }, style: TextButton.styleFrom( diff --git a/pubspec.yaml b/pubspec.yaml index a67f398..f4b8c9e 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,7 @@ name: flutter_social_sample_app description: Demonstrates how to use the flutter_application_1 plugin. -version: 1.1.45+60 +version: 1.1.46+61 + environment: From 1715e89ccf8bd0b9bd50ebdce2ce139aaafd0b27 Mon Sep 17 00:00:00 2001 From: trustratch Date: Tue, 11 Jun 2024 20:34:42 +0200 Subject: [PATCH 02/10] feat: add custom post in feed widdget --- lib/core/widget/feed_widget.dart | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/core/widget/feed_widget.dart b/lib/core/widget/feed_widget.dart index 4abb212..6f635a2 100644 --- a/lib/core/widget/feed_widget.dart +++ b/lib/core/widget/feed_widget.dart @@ -482,6 +482,15 @@ class FeedContentWidget extends StatelessWidget { ); } + if (amityPostData is CustomData) { + final data = amityPostData as CustomData; + return Container( + // color: Colors.green, + child: + Text('Custom post content -->>>> ${data.rawData}'), + ); + } + return Container( color: Colors.red, child: Text('>>>>> $amityPostData <<<<<<'), From 827784a862c2557286848672752a4c8492c7f201 Mon Sep 17 00:00:00 2001 From: trustratch Date: Tue, 11 Jun 2024 20:48:11 +0200 Subject: [PATCH 03/10] feat: add custom post creation page --- lib/core/route/app_route.dart | 3 + lib/core/route/app_router.dart | 8 + .../create_custom_post_screen.dart | 140 ++++++++++++++++++ .../screen/dashboard/dashboard_screen.dart | 7 + 4 files changed, 158 insertions(+) create mode 100644 lib/presentation/screen/create_custom_post/create_custom_post_screen.dart diff --git a/lib/core/route/app_route.dart b/lib/core/route/app_route.dart index 707956b..2b352a6 100644 --- a/lib/core/route/app_route.dart +++ b/lib/core/route/app_route.dart @@ -103,6 +103,9 @@ class AppRoute { static const createPollPost = 'createPollPost'; static const createPollPostRoute = 'createPollPost'; + static const createCustomPost = 'createCustomPost'; + static const createCustomPostRoute = 'createCustomPost'; + static const chat = 'chat'; static const chatRoute = 'chatRoute/:channelId'; diff --git a/lib/core/route/app_router.dart b/lib/core/route/app_router.dart index ebb5279..b3fe5c0 100644 --- a/lib/core/route/app_router.dart +++ b/lib/core/route/app_router.dart @@ -23,6 +23,7 @@ import 'package:flutter_social_sample_app/presentation/screen/community_notifica import 'package:flutter_social_sample_app/presentation/screen/community_pending_post/community_pending_post_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/community_profile/community_profile_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/community_update/community_update_screen.dart'; +import 'package:flutter_social_sample_app/presentation/screen/create_custom_post/create_custom_post_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/create_livestream_post/create_livestream_post.dart'; import 'package:flutter_social_sample_app/presentation/screen/create_poll_post/create_poll_post_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/create_post/create_post_screen.dart'; @@ -317,6 +318,13 @@ class AppRouter { path: AppRoute.createPollPostRoute, builder: (context, state) => const CreatePollPostScreen(), ), + + GoRoute( + name: AppRoute.createCustomPost, + path: AppRoute.createCustomPostRoute, + builder: (context, state) => const CreateCustomPostScreen(), + ), + GoRoute( name: AppRoute.chat, path: AppRoute.chatRoute, diff --git a/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart b/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart new file mode 100644 index 0000000..4cfc0f7 --- /dev/null +++ b/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart @@ -0,0 +1,140 @@ +import 'package:amity_sdk/amity_sdk.dart'; +import 'package:flutter/material.dart'; + +class CreateCustomPostScreen extends StatefulWidget { + const CreateCustomPostScreen({Key? key, this.userId}) : super(key: key); + final String? userId; + + @override + State createState() => _CreateCustomPostScreenState(); +} + +class _CreateCustomPostScreenState extends State { + final _targetUserTextEditController = TextEditingController(); + final _keyTextController = TextEditingController(); + final _valueTextController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final themeData = Theme.of(context); + + if (widget.userId != null) { + _targetUserTextEditController.text = widget.userId!; + } + + return Scaffold( + appBar: AppBar( + title: const Text('Create Custom Post'), + actions: [ + LoadingButton( + onPressed: () async { + try { + FocusManager.instance.primaryFocus?.unfocus(); + final target = _targetUserTextEditController.text.trim(); + final key = _keyTextController.text.trim(); + final value = _valueTextController.text.trim(); + + if (key.isEmpty || value.isEmpty) { + CommonSnackbar.showNagativeSnackbar( + context, 'Error', 'Key and value cannot be empty'); + return; + } + + final customData = {key: value}; + + final amityPost = await AmitySocialClient.newPostRepository() + .createPost() + .targetMe() + .custom('amity.custom', customData) + .post(); + + CommonSnackbar.showPositiveSnackbar( + context, 'Success', 'Custom Post Created Successfully'); + } catch (error, stackTrace) { + print(stackTrace.toString()); + CommonSnackbar.showNagativeSnackbar( + context, 'Error', error.toString()); + } + }, + text: 'POST', + ), + ], + ), + body: SingleChildScrollView( + child: Container( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextFormField( + controller: _targetUserTextEditController, + decoration: InputDecoration( + label: Text('Target User'), + ), + ), + const SizedBox(height: 12), + Text( + 'Custom Data', + style: themeData.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.bold, + ), + ), + TextFormField( + controller: _keyTextController, + decoration: const InputDecoration( + hintText: 'Key', + ), + ), + const SizedBox(height: 12), + TextFormField( + controller: _valueTextController, + decoration: const InputDecoration( + hintText: 'Value', + ), + ), + ], + ), + ), + ), + ); + } +} + +class LoadingButton extends StatelessWidget { + final Future Function() onPressed; + final String text; + + const LoadingButton({ + Key? key, + required this.onPressed, + required this.text, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: () => onPressed(), + child: Text(text), + ); + } +} + +class CommonSnackbar { + static void showPositiveSnackbar( + BuildContext context, String title, String message) { + final snackBar = SnackBar( + content: Text('$title: $message'), + backgroundColor: Colors.green, + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } + + static void showNagativeSnackbar( + BuildContext context, String title, String message) { + final snackBar = SnackBar( + content: Text('$title: $message'), + backgroundColor: Colors.red, + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } +} diff --git a/lib/presentation/screen/dashboard/dashboard_screen.dart b/lib/presentation/screen/dashboard/dashboard_screen.dart index 400a6d6..7fad791 100644 --- a/lib/presentation/screen/dashboard/dashboard_screen.dart +++ b/lib/presentation/screen/dashboard/dashboard_screen.dart @@ -129,6 +129,13 @@ class _DashboardScreenState extends State { child: const Text('Create Poll Post'), ), const SizedBox(height: 20), + TextButton( + onPressed: () { + GoRouter.of(context).goNamed(AppRoute.createCustomPost); + }, + child: const Text('Create Custom Post'), + ), + const SizedBox(height: 20), TextButton( onPressed: () async { FirebaseMessaging messaging = FirebaseMessaging.instance; From d0693362320c4f76b93d774da7cb6eb021010c78 Mon Sep 17 00:00:00 2001 From: trustratch Date: Tue, 11 Jun 2024 21:30:27 +0200 Subject: [PATCH 04/10] feat: add update custom post screen --- lib/core/widget/feed_widget.dart | 31 +++-- .../update_custom_post_screen.dart | 128 ++++++++++++++++++ 2 files changed, 150 insertions(+), 9 deletions(-) create mode 100644 lib/presentation/screen/update_post/update_custom_post_screen.dart diff --git a/lib/core/widget/feed_widget.dart b/lib/core/widget/feed_widget.dart index 6f635a2..d392532 100644 --- a/lib/core/widget/feed_widget.dart +++ b/lib/core/widget/feed_widget.dart @@ -4,12 +4,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_social_sample_app/core/route/app_route.dart'; import 'package:flutter_social_sample_app/core/utils/extension/date_extension.dart'; import 'package:flutter_social_sample_app/core/widget/add_comment_widget.dart'; -import 'package:flutter_social_sample_app/core/widget/common_snackbar.dart'; import 'package:flutter_social_sample_app/core/widget/dynamic_text_highlighting.dart'; import 'package:flutter_social_sample_app/core/widget/poll_widget.dart'; import 'package:flutter_social_sample_app/core/widget/reaction_action_widget.dart'; import 'package:flutter_social_sample_app/core/widget/shadow_container_widget.dart'; import 'package:flutter_social_sample_app/core/widget/user_profile_info_row_widget.dart'; +import 'package:flutter_social_sample_app/presentation/screen/update_post/update_custom_post_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/update_post/update_post_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/video_player/full_screen_video_player.dart'; import 'package:go_router/go_router.dart'; @@ -94,15 +94,28 @@ class FeedWidget extends StatelessWidget { ), onSelected: (index) { if (index == 1) { - Navigator.of(context).push( - MaterialPageRoute( - builder: (context) => UpdatePostScreen( - amityPost: value, - communityId: communityId, - isPublic: isPublic, + if (value.data is CustomData) { + Navigator.push( + context, + MaterialPageRoute( + builder: (context) => + UpdateCustomPostScreen( + amityPost: value), ), - ), - ); + ); + + } else { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => + UpdatePostScreen( + amityPost: value, + communityId: communityId, + isPublic: isPublic, + ), + ), + ); + } } if (index == 2) { value.delete(); diff --git a/lib/presentation/screen/update_post/update_custom_post_screen.dart b/lib/presentation/screen/update_post/update_custom_post_screen.dart new file mode 100644 index 0000000..3f3fdbd --- /dev/null +++ b/lib/presentation/screen/update_post/update_custom_post_screen.dart @@ -0,0 +1,128 @@ +import 'package:amity_sdk/amity_sdk.dart'; +import 'package:flutter/material.dart'; + +class UpdateCustomPostScreen extends StatefulWidget { + const UpdateCustomPostScreen({Key? key, required this.amityPost}) + : super(key: key); + final AmityPost amityPost; + + @override + State createState() => _UpdateCustomPostScreenState(); +} + +class _UpdateCustomPostScreenState extends State { + final _keyTextController = TextEditingController(); + final _valueTextController = TextEditingController(); + + @override + Widget build(BuildContext context) { + final themeData = Theme.of(context); + + return Scaffold( + appBar: AppBar( + title: const Text('Update Custom Post'), + actions: [ + LoadingButton( + onPressed: () async { + try { + FocusManager.instance.primaryFocus?.unfocus(); + final key = _keyTextController.text.trim(); + final value = _valueTextController.text.trim(); + + if (key.isEmpty || value.isEmpty) { + CommonSnackbar.showNagativeSnackbar( + context, 'Error', 'Key and value cannot be empty'); + return; + } + + final customData = {key: value}; + + await widget.amityPost + .edit() + .custom(customData) + .build() + .update(); + + CommonSnackbar.showPositiveSnackbar( + context, 'Success', 'Custom Post Updated Successfully'); + } catch (error, stackTrace) { + print(stackTrace.toString()); + CommonSnackbar.showNagativeSnackbar( + context, 'Error', error.toString()); + } + }, + text: 'UPDATE', + ), + ], + ), + body: SingleChildScrollView( + child: Container( + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Custom Data', + style: themeData.textTheme.titleMedium!.copyWith( + fontWeight: FontWeight.bold, + ), + ), + TextFormField( + controller: _keyTextController, + decoration: const InputDecoration( + hintText: 'Key', + ), + ), + const SizedBox(height: 12), + TextFormField( + controller: _valueTextController, + decoration: const InputDecoration( + hintText: 'Value', + ), + ), + ], + ), + ), + ), + ); + } +} + +class LoadingButton extends StatelessWidget { + final Future Function() onPressed; + final String text; + + const LoadingButton({ + Key? key, + required this.onPressed, + required this.text, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return TextButton( + onPressed: () => onPressed(), + child: Text(text), + ); + } +} + +class CommonSnackbar { + static void showPositiveSnackbar( + BuildContext context, String title, String message) { + final snackBar = SnackBar( + content: Text('$title: $message'), + backgroundColor: Colors.green, + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } + + static void showNagativeSnackbar( + BuildContext context, String title, String message) { + final snackBar = SnackBar( + content: Text('$title: $message'), + backgroundColor: Colors.red, + ); + ScaffoldMessenger.of(context).showSnackBar(snackBar); + } +} From 193b193a79084fd1c2fab083b078207f3bf5c529 Mon Sep 17 00:00:00 2001 From: trustratch Date: Tue, 11 Jun 2024 21:47:08 +0200 Subject: [PATCH 05/10] fix: oops, wrong custom post target in sample app --- .../screen/create_custom_post/create_custom_post_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart b/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart index 4cfc0f7..c75b094 100644 --- a/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart +++ b/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart @@ -44,7 +44,7 @@ class _CreateCustomPostScreenState extends State { final amityPost = await AmitySocialClient.newPostRepository() .createPost() - .targetMe() + .targetUser(target) .custom('amity.custom', customData) .post(); From fc51f4f71b1304025abab2f71df1055b095fc960 Mon Sep 17 00:00:00 2001 From: trustratch Date: Thu, 13 Jun 2024 09:42:18 +0200 Subject: [PATCH 06/10] fix: remove trim from target id text field for custom post --- .../screen/create_custom_post/create_custom_post_screen.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart b/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart index c75b094..f6d7810 100644 --- a/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart +++ b/lib/presentation/screen/create_custom_post/create_custom_post_screen.dart @@ -30,7 +30,7 @@ class _CreateCustomPostScreenState extends State { onPressed: () async { try { FocusManager.instance.primaryFocus?.unfocus(); - final target = _targetUserTextEditController.text.trim(); + final target = _targetUserTextEditController.text; final key = _keyTextController.text.trim(); final value = _valueTextController.text.trim(); From df4a75b8f802e140857b7633330727b992b5ef54 Mon Sep 17 00:00:00 2001 From: Zryte Date: Tue, 18 Jun 2024 15:54:24 +0700 Subject: [PATCH 07/10] Update UI for community list (#232) Co-authored-by: Zryte --- .../community_list/community_list_screen.dart | 258 +++++++++--------- 1 file changed, 130 insertions(+), 128 deletions(-) diff --git a/lib/presentation/screen/community_list/community_list_screen.dart b/lib/presentation/screen/community_list/community_list_screen.dart index 1ec37bc..a49aae4 100644 --- a/lib/presentation/screen/community_list/community_list_screen.dart +++ b/lib/presentation/screen/community_list/community_list_screen.dart @@ -33,13 +33,11 @@ class _CommunityListScreenState extends State { @override void initState() { - communityLiveCollectionInit(); super.initState(); } - void communityLiveCollectionInit() { - + void communityLiveCollectionInit() { communityLiveCollection = CommunityLiveCollection( request: () => AmitySocialClient.newCommunityRepository() .getCommunities() @@ -50,7 +48,6 @@ class _CommunityListScreenState extends State { .includeDeleted(_includeDelete) .build()); - communityLiveCollection.getStreamController().stream.listen((event) { if (mounted) { setState(() { @@ -59,19 +56,16 @@ class _CommunityListScreenState extends State { } }); - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - communityLiveCollection.loadNext(); - }); - scrollcontroller.addListener(pagination); } void pagination() { if ((scrollcontroller.position.pixels == - scrollcontroller.position.maxScrollExtent) && - communityLiveCollection.hasNextPage()) { - communityLiveCollection.loadNext(); - } + scrollcontroller.position.maxScrollExtent) && + communityLiveCollection.hasNextPage()) { + print("TriggerPagination"); + communityLiveCollection.loadNext(); + } } @override @@ -86,134 +80,141 @@ class _CommunityListScreenState extends State { onChanged: (value) { _debouncer.run(() { _keyboard = value; - communityLiveCollection.reset(); - communityLiveCollectionInit(); }); }, decoration: const InputDecoration(hintText: 'Enter Keybaord'), ), ), - Container( - child: Row( - children: [ - Container( - padding: const EdgeInsets.all(8), - child: PopupMenuButton( - itemBuilder: (context) { - return [ - PopupMenuItem( - value: 1, - child: Text(AmityCommunityFilter.ALL.name), - ), - PopupMenuItem( - value: 2, - child: Text(AmityCommunityFilter.MEMBER.name), - ), - PopupMenuItem( - value: 3, - child: Text(AmityCommunityFilter.NOT_MEMBER.name), - ) - ]; - }, - child: const Icon( - Icons.filter_alt_rounded, - size: 18, - ), - onSelected: (index) { - if (index == 1) { - _filter = AmityCommunityFilter.ALL; - } - if (index == 2) { - _filter = AmityCommunityFilter.MEMBER; - } - if (index == 3) { - _filter = AmityCommunityFilter.NOT_MEMBER; - } - - communityLiveCollection.reset(); - communityLiveCollectionInit(); - }, + Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + child: PopupMenuButton( + itemBuilder: (context) { + return [ + PopupMenuItem( + value: 1, + child: Text(AmityCommunityFilter.ALL.name), + ), + PopupMenuItem( + value: 2, + child: Text(AmityCommunityFilter.MEMBER.name), + ), + PopupMenuItem( + value: 3, + child: Text(AmityCommunityFilter.NOT_MEMBER.name), + ) + ]; + }, + child: const Icon( + Icons.filter_alt_rounded, + size: 18, ), + onSelected: (index) { + if (index == 1) { + _filter = AmityCommunityFilter.ALL; + } + if (index == 2) { + _filter = AmityCommunityFilter.MEMBER; + } + if (index == 3) { + _filter = AmityCommunityFilter.NOT_MEMBER; + } + }, ), - Container( - padding: const EdgeInsets.all(8), - child: PopupMenuButton( - itemBuilder: (context) { - return [ - PopupMenuItem( - - child: Text(AmityCommunitySortOption.DISPLAY_NAME.name), - value: 1, - ), - PopupMenuItem( - child: Text(AmityCommunitySortOption.FIRST_CREATED.name), - value: 2, - ), - PopupMenuItem( - child: Text(AmityCommunitySortOption.LAST_CREATED.name), - value: 3, - ) - ]; - }, - child: const Icon( - Icons.sort_rounded, - size: 18, - ), - onSelected: (index) { - if (index == 1) { - _sort = AmityCommunitySortOption.DISPLAY_NAME; - } - if (index == 2) { - _sort = AmityCommunitySortOption.FIRST_CREATED; - } - if (index == 3) { - _sort = AmityCommunitySortOption.LAST_CREATED; - } - communityLiveCollection.reset(); - communityLiveCollectionInit(); - }, + ), + Container( + padding: const EdgeInsets.all(8), + child: PopupMenuButton( + itemBuilder: (context) { + return [ + PopupMenuItem( + value: 1, + child: + Text(AmityCommunitySortOption.DISPLAY_NAME.name), + ), + PopupMenuItem( + value: 2, + child: + Text(AmityCommunitySortOption.FIRST_CREATED.name), + ), + PopupMenuItem( + value: 3, + child: + Text(AmityCommunitySortOption.LAST_CREATED.name), + ) + ]; + }, + child: const Icon( + Icons.sort_rounded, + size: 18, ), + onSelected: (index) { + if (index == 1) { + _sort = AmityCommunitySortOption.DISPLAY_NAME; + } + if (index == 2) { + _sort = AmityCommunitySortOption.FIRST_CREATED; + } + if (index == 3) { + _sort = AmityCommunitySortOption.LAST_CREATED; + } + }, + ), + ), + Container( + padding: const EdgeInsets.all(8), + child: InkWell( + child: const Icon(Icons.tag, size: 18), + onTap: () { + EditTextDialog.show(context, + title: 'Enter tags, separate by comma', + hintText: 'type tags here', onPress: (value) { + if (value.isNotEmpty) { + _tags = value.trim().split(','); + } else { + _tags = null; + } + }); + }, ), - Container( - padding: const EdgeInsets.all(8), - child: InkWell( - child: const Icon(Icons.tag, size: 18), - onTap: () { - EditTextDialog.show(context, title: 'Enter tags, separate by comma', hintText: 'type tags here', - onPress: (value) { - if (value.isNotEmpty) { - _tags = value.trim().split(','); - } else { - _tags = null; - } + ), + Container( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: _includeDelete, + onChanged: (value) { + setState(() { + _includeDelete = (value ?? false); + }); + }, + ), + const Text('Include Delete') + ], + ), + ), + Container( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + ElevatedButton( + onPressed: () { communityLiveCollection.reset(); communityLiveCollectionInit(); - }); - }, - ), - ), - Container( - padding: const EdgeInsets.all(8), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Checkbox( - value: _includeDelete, - onChanged: (value) { - setState(() { - _includeDelete = (value ?? false); - communityLiveCollection.reset(); - - communityLiveCollectionInit(); - }); - }, + communityLiveCollection.loadNext(); + }, + child: const Text( + "Query", ), - const Text('Include Delete') - ], - ), - ) - ], - ), + ) + ], + ), + ) + ], ), Expanded( child: amityCommunities.isNotEmpty @@ -221,6 +222,7 @@ class _CommunityListScreenState extends State { onRefresh: () async { communityLiveCollection.reset(); communityLiveCollectionInit(); + communityLiveCollection.loadNext(); }, child: ListView.builder( controller: scrollcontroller, From 329aa7fd51182260f1be42f28a8ea58e691d3ddf Mon Sep 17 00:00:00 2001 From: Muhammad Umar Manzoor Chaudhary Date: Thu, 27 Jun 2024 11:24:59 +0700 Subject: [PATCH 08/10] Fix: Null error in message in the when in synced state. --- lib/core/widget/message_widget.dart | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/core/widget/message_widget.dart b/lib/core/widget/message_widget.dart index 09ef087..e1d1949 100644 --- a/lib/core/widget/message_widget.dart +++ b/lib/core/widget/message_widget.dart @@ -766,7 +766,7 @@ class AmityMessageContentWidget extends StatelessWidget { onPressed: () {}, icon: const Icon(Icons.attach_file_rounded), label: Text( - data.file!.getFilePath!.split('/').last, + data.file?.getFilePath?.split('/').last ?? data.file?.getUrl ?? "", ), ) : Container( diff --git a/pubspec.yaml b/pubspec.yaml index f4b8c9e..8ab528c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_social_sample_app description: Demonstrates how to use the flutter_application_1 plugin. -version: 1.1.46+61 +version: 1.1.47+62 From 0e783588dbb045d0332b028632590d197ab5a897 Mon Sep 17 00:00:00 2001 From: Warakorn Sitthirit Date: Fri, 16 Aug 2024 10:21:54 +0700 Subject: [PATCH 09/10] Staging oddish (#235) * feat: Filter deleted user from user related api * chore: Increase sample app version number * chore: Update code snippets for channel/community members query and search api --- .../channel/amity_channel_member_query.dart | 1 + .../channel/amity_channel_member_search.dart | 1 + .../amity_community_member_query.dart | 1 + ...ty_community_member_search_by_keyword.dart | 1 + .../channel_member_info_row_widget.dart | 5 ++++ lib/core/widget/community_member_widget.dart | 5 ++++ .../widget/dialog/amity_user_info_widget.dart | 10 +++---- lib/core/widget/message_widget.dart | 10 +++---- .../channel_member/channel_member_screen.dart | 28 +++++++++++++++++++ .../community_member_screen.dart | 21 ++++++++++++++ pubspec.yaml | 2 +- 11 files changed, 74 insertions(+), 11 deletions(-) diff --git a/code_snippet/channel/amity_channel_member_query.dart b/code_snippet/channel/amity_channel_member_query.dart index 7bcc39f..1b48107 100644 --- a/code_snippet/channel/amity_channel_member_query.dart +++ b/code_snippet/channel/amity_channel_member_query.dart @@ -15,6 +15,7 @@ class AmityChannelMemberQuery { pageFuture: (token) => AmityChatClient.newChannelRepository() .membership(channelId) .getMembers() + .includeDeleted(false) // optional to filter deleted users from the result .getPagingData(token: token, limit: 20), pageSize: 20, )..addListener( diff --git a/code_snippet/channel/amity_channel_member_search.dart b/code_snippet/channel/amity_channel_member_search.dart index 06439de..653de60 100644 --- a/code_snippet/channel/amity_channel_member_search.dart +++ b/code_snippet/channel/amity_channel_member_search.dart @@ -15,6 +15,7 @@ class AmityChannelMemberSearch { pageFuture: (token) => AmityChatClient.newChannelRepository() .membership(channelId) .searchMembers(keyword) + .includeDeleted(false) // optional to filter deleted users from the result .getPagingData(token: token, limit: 20), pageSize: 20, )..addListener( diff --git a/code_snippet/community/amity_community_member_query.dart b/code_snippet/community/amity_community_member_query.dart index 9f45b50..850c697 100644 --- a/code_snippet/community/amity_community_member_query.dart +++ b/code_snippet/community/amity_community_member_query.dart @@ -29,6 +29,7 @@ class AmityCommunityMemberQuery { .getMembers() .filter(filter) .sortBy(sortOption) + .includeDeleted(false) // optional to filter deleted users from the result .roles([ 'community-moderator' ]) //optional to query specific members by roles diff --git a/code_snippet/community/amity_community_member_search_by_keyword.dart b/code_snippet/community/amity_community_member_search_by_keyword.dart index 7d17c61..ae5f9c1 100644 --- a/code_snippet/community/amity_community_member_search_by_keyword.dart +++ b/code_snippet/community/amity_community_member_search_by_keyword.dart @@ -30,6 +30,7 @@ class AmityCommunityMemberSearchByKeyword { .searchMembers(keyword) .filter(filter) .sortBy(sortOption) + .includeDeleted(false) // optional to filter deleted users from the result .roles([ 'community-moderator' ]) //optional to query specific members by roles diff --git a/lib/core/widget/channel_member_info_row_widget.dart b/lib/core/widget/channel_member_info_row_widget.dart index fd6992d..fbd09bc 100644 --- a/lib/core/widget/channel_member_info_row_widget.dart +++ b/lib/core/widget/channel_member_info_row_widget.dart @@ -86,6 +86,11 @@ class ChannelMemberInfoRowWidget extends StatelessWidget { style: themeData.textTheme.bodySmall, textAlign: TextAlign.start, ), + Text( + 'isDeleted - ${value.isDeleted ?? false}', + style: themeData.textTheme.bodySmall, + textAlign: TextAlign.start, + ), Text( 'Flag Count - ${value.user?.flagCount ?? 0}', style: themeData.textTheme.bodySmall, diff --git a/lib/core/widget/community_member_widget.dart b/lib/core/widget/community_member_widget.dart index dd81b14..0d6445c 100644 --- a/lib/core/widget/community_member_widget.dart +++ b/lib/core/widget/community_member_widget.dart @@ -88,6 +88,11 @@ class _CommunityMemberInfoRowWidget extends StatelessWidget { style: themeData.textTheme.bodySmall, textAlign: TextAlign.start, ), + Text( + 'isDeleted - ${value.isDeleted ?? false}', + style: themeData.textTheme.bodySmall, + textAlign: TextAlign.start, + ), ], ), ), diff --git a/lib/core/widget/dialog/amity_user_info_widget.dart b/lib/core/widget/dialog/amity_user_info_widget.dart index 0c200d2..69d29e8 100644 --- a/lib/core/widget/dialog/amity_user_info_widget.dart +++ b/lib/core/widget/dialog/amity_user_info_widget.dart @@ -67,27 +67,27 @@ class AmityUserInfoWidget extends StatelessWidget { ), // Text( // 'roles - $rolesText', - // style: _themeData.textTheme.caption, + // style: _themeData.textTheme.bodySmall, // textAlign: TextAlign.start, // ), // Text( // 'permissions - $permissionText', - // style: _themeData.textTheme.caption, + // style: _themeData.textTheme.bodySmall, // textAlign: TextAlign.start, // ), // Text( // 'isBanned - ${value.isBanned ?? false}', - // style: _themeData.textTheme.caption, + // style: _themeData.textTheme.bodySmall, // textAlign: TextAlign.start, // ), // Text( // 'isMuted - ${value.isMuted ?? false}', - // style: _themeData.textTheme.caption, + // style: _themeData.textTheme.bodySmall, // textAlign: TextAlign.start, // ), // Text( // 'Flag Count - ${value.flagCount ?? 0}', - // style: _themeData.textTheme.caption, + // style: _themeData.textTheme.bodySmall, // textAlign: TextAlign.start, // ), ], diff --git a/lib/core/widget/message_widget.dart b/lib/core/widget/message_widget.dart index e1d1949..eea94f4 100644 --- a/lib/core/widget/message_widget.dart +++ b/lib/core/widget/message_widget.dart @@ -134,7 +134,7 @@ class MessageWidget extends StatelessWidget { Text( value.createdAt?.toLocal().toIso8601String() ?? DateTime.now().toLocal().toIso8601String(), - style: themeData.textTheme.caption!.copyWith(), + style: themeData.textTheme.bodySmall!.copyWith(), ), const SizedBox(width: 12), @@ -394,7 +394,7 @@ class MessageWidget extends StatelessWidget { child: Text( value.reactions!.getCount('like').toString(), - style: themeData.textTheme.caption!.copyWith(fontSize: 14), + style: themeData.textTheme.bodySmall!.copyWith(fontSize: 14), ), ), @@ -442,7 +442,7 @@ class MessageWidget extends StatelessWidget { child: Text( value.reactions!.getCount('like').toString(), - style: themeData.textTheme.caption!.copyWith(fontSize: 14), + style: themeData.textTheme.bodySmall!.copyWith(fontSize: 14), ), ), @@ -488,7 +488,7 @@ class MessageWidget extends StatelessWidget { children: [ Text( value.reactions!.getCount('love').toString(), - style: themeData.textTheme.caption!.copyWith(fontSize: 14), + style: themeData.textTheme.bodySmall!.copyWith(fontSize: 14), ), const SizedBox(width: 2), Image.asset( @@ -532,7 +532,7 @@ class MessageWidget extends StatelessWidget { children: [ Text( value.reactions!.getCount('love').toString(), - style: themeData.textTheme.caption!.copyWith(fontSize: 14), + style: themeData.textTheme.bodySmall!.copyWith(fontSize: 14), ), const SizedBox(width: 2), Image.asset( diff --git a/lib/presentation/screen/channel_member/channel_member_screen.dart b/lib/presentation/screen/channel_member/channel_member_screen.dart index eeda84b..69cf8f6 100644 --- a/lib/presentation/screen/channel_member/channel_member_screen.dart +++ b/lib/presentation/screen/channel_member/channel_member_screen.dart @@ -27,6 +27,8 @@ class ChannelMemberScreenState extends State { String _keyboard = ''; + bool _includeDeleted = false; + final _debouncer = Debouncer(milliseconds: 500); @override @@ -35,6 +37,7 @@ class ChannelMemberScreenState extends State { pageFuture: (token) => AmityChatClient.newChannelRepository() .membership(widget.channelId) .searchMembers(_keyboard) + .includeDeleted(_includeDeleted) .getPagingData(token: token, limit: GlobalConstant.pageSize), pageSize: GlobalConstant.pageSize, )..addListener( @@ -108,6 +111,31 @@ class ChannelMemberScreenState extends State { decoration: const InputDecoration(hintText: 'Enter Keybaord'), ), ), + Container( + child: Row( + children: [ + Container( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: _includeDeleted, + onChanged: (value) { + setState(() { + _includeDeleted = (value ?? false); + _controller.reset(); + _controller.fetchNextPage(); + }); + }, + ), + const Text('Include Deleted') + ], + ), + ) + ], + ), + ), Expanded( child: amityChannelMembers.isNotEmpty ? RefreshIndicator( diff --git a/lib/presentation/screen/community_member/community_member_screen.dart b/lib/presentation/screen/community_member/community_member_screen.dart index dec658a..bb8a447 100644 --- a/lib/presentation/screen/community_member/community_member_screen.dart +++ b/lib/presentation/screen/community_member/community_member_screen.dart @@ -27,6 +27,7 @@ class CommunityMemberScreenState extends State { AmityCommunityMembershipFilter _filter = AmityCommunityMembershipFilter.MEMBER; AmityCommunityMembershipSortOption _sort = AmityCommunityMembershipSortOption.LAST_CREATED; + bool _includeDeleted = false; @override void initState() { _controller = PagingController( @@ -35,6 +36,7 @@ class CommunityMemberScreenState extends State { .searchMembers(_keyboard) .filter(_filter) .sortBy(_sort) + .includeDeleted(_includeDeleted) .getPagingData(token: token, limit: GlobalConstant.pageSize), pageSize: GlobalConstant.pageSize, )..addListener( @@ -181,6 +183,25 @@ class CommunityMemberScreenState extends State { }, ), ), + Container( + padding: const EdgeInsets.all(8), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Checkbox( + value: _includeDeleted, + onChanged: (value) { + setState(() { + _includeDeleted = (value ?? false); + _controller.reset(); + _controller.fetchNextPage(); + }); + }, + ), + const Text('Include Deleted') + ], + ), + ) ], ), ), diff --git a/pubspec.yaml b/pubspec.yaml index 8ab528c..71d7613 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: flutter_social_sample_app description: Demonstrates how to use the flutter_application_1 plugin. -version: 1.1.47+62 +version: 1.1.48+63 From b09258ffbed7d8f3f34ff57c238c5ee62c1b4c58 Mon Sep 17 00:00:00 2001 From: Warakorn Sitthirit Date: Fri, 16 Aug 2024 14:48:43 +0700 Subject: [PATCH 10/10] Staging Pikachu (#236) * feat: Filter deleted user from user related api * chore: Increase sample app version number * chore: Update code snippets for channel/community members query and search api * feat: Channel live collection * chore: Delaying auto-login on sample app to trial the session management process * chore: Show channel.isDeleted value * fix: Fix sample app UI --- lib/core/route/app_router.dart | 7 +- lib/core/widget/channel_widget.dart | 20 ++- lib/core/widget/community_widget.dart | 11 +- .../channel_list/channel_list_screen.dart | 114 ++++++++---------- .../channel_profile_screen.dart | 18 +-- .../screen/dashboard/dashboard_screen.dart | 11 +- 6 files changed, 74 insertions(+), 107 deletions(-) diff --git a/lib/core/route/app_router.dart b/lib/core/route/app_router.dart index b3fe5c0..d4590f3 100644 --- a/lib/core/route/app_router.dart +++ b/lib/core/route/app_router.dart @@ -8,7 +8,6 @@ import 'package:flutter_social_sample_app/presentation/screen/channel_list/chann import 'package:flutter_social_sample_app/presentation/screen/channel_profile/channel_profile_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/channel_update/channel_update_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/chat/chat_screen.dart'; -import 'package:flutter_social_sample_app/presentation/screen/comment_query/comment_query_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/comment_query_pagination/comment_query_pagination_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/comment_query_reply/comment_query_reply_screen.dart'; import 'package:flutter_social_sample_app/presentation/screen/community_category/community_category_screen.dart'; @@ -505,12 +504,14 @@ class AppRouter { log('redirecting to /login'); return AppRoute.loginRoute; } else { - // var user = await AmityCoreClient.getLoggedInUser(); var userId = await PreferenceInterfaceImpl().loggedInUserId(); var userName = await PreferenceInterfaceImpl().loggedInUserDisplayName(); - await AmityCoreClient.login(userId!) + // Delaying the login for 5 seconds to simulate the login process + Future.delayed(const Duration(seconds: 5), () { + AmityCoreClient.login(userId!) .displayName(userName!) .submit(); + }); return AppRoute.homeRoute; } } diff --git a/lib/core/widget/channel_widget.dart b/lib/core/widget/channel_widget.dart index 94c2181..e4ca79a 100644 --- a/lib/core/widget/channel_widget.dart +++ b/lib/core/widget/channel_widget.dart @@ -16,34 +16,24 @@ class ChannelWidget extends StatelessWidget { child: Text('Channel Deleted - ${amityChannel.channelId}'), ); } - return StreamBuilder( - stream: amityChannel.listen.stream, - initialData: amityChannel, - builder: (context, snapshot) { - if (snapshot.hasData) { - final value = snapshot.data!; - return Container( + return Container( decoration: BoxDecoration(color: Colors.grey.withOpacity(.2)), child: InkWell( onTap: () { GoRouter.of(context).pushNamed(AppRoute.channelProfile, - params: {'channelId': value.channelId!}); + params: {'channelId': amityChannel.channelId!}); // params: {'communityId': 'f5a99abc1f275df3f4259b6ca0e3cb15'}); }, child: Column( children: [ _ChannelInfoWidget( - amityChannel: value, + amityChannel: amityChannel, ), const Divider(), ], ), ), ); - } - return Container(); - }, - ); } } @@ -97,6 +87,10 @@ class _ChannelInfoWidget extends StatelessWidget { 'last Activity: ${amityChannel.lastActivity?.toIso8601String() ?? 'NaN'}', style: themeData.textTheme.bodySmall, ), + Text( + 'isDeleted: ${amityChannel.isDeleted ?? 'NaN'}', + style: themeData.textTheme.bodySmall, + ), SelectableText( 'Channel ID : ${amityChannel.channelId ?? 'NaN'}', style: themeData.textTheme.bodySmall, diff --git a/lib/core/widget/community_widget.dart b/lib/core/widget/community_widget.dart index 8a2a348..493554d 100644 --- a/lib/core/widget/community_widget.dart +++ b/lib/core/widget/community_widget.dart @@ -11,13 +11,6 @@ class CommunityWidget extends StatelessWidget { @override Widget build(BuildContext context) { - if (amityCommunity.isDeleted ?? false) { - return Container( - padding: const EdgeInsets.all(12), - decoration: BoxDecoration(color: Colors.grey.withOpacity(.05)), - child: Text('Community Deleted - ${amityCommunity.communityId}'), - ); - } return StreamBuilder( stream: amityCommunity.listen.stream, initialData: amityCommunity, @@ -114,6 +107,10 @@ class _CommunityInfoWidget extends StatelessWidget { 'Public: ${amityCommunity.isPublic}', style: themeData.textTheme.bodySmall, ), + Text( + 'isDeleted: ${amityCommunity.isDeleted}', + style: themeData.textTheme.bodySmall, + ), ], ), ), diff --git a/lib/presentation/screen/channel_list/channel_list_screen.dart b/lib/presentation/screen/channel_list/channel_list_screen.dart index d89b471..d78a717 100644 --- a/lib/presentation/screen/channel_list/channel_list_screen.dart +++ b/lib/presentation/screen/channel_list/channel_list_screen.dart @@ -1,11 +1,9 @@ import 'package:amity_sdk/amity_sdk.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_social_sample_app/core/constant/global_constant.dart'; import 'package:flutter_social_sample_app/core/route/app_route.dart'; import 'package:flutter_social_sample_app/core/utils/debouncer.dart'; import 'package:flutter_social_sample_app/core/widget/channel_widget.dart'; import 'package:flutter_social_sample_app/core/widget/dialog/edit_text_dialog.dart'; -import 'package:flutter_social_sample_app/core/widget/dialog/error_dialog.dart'; import 'package:go_router/go_router.dart'; class ChannelListScreen extends StatefulWidget { @@ -16,8 +14,8 @@ class ChannelListScreen extends StatefulWidget { } class _ChannelListScreenState extends State { - late PagingController _controller; - final amityCommunities = []; + late ChannelLiveCollection _channelLiveCollection; + List amityChannels = []; final scrollcontroller = ScrollController(); bool loading = false; @@ -34,53 +32,51 @@ class _ChannelListScreenState extends State { @override void initState() { - _controller = PagingController( - pageFuture: (token) => AmityChatClient.newChannelRepository() - .getChannels() - .withKeyword(_keyboard.isEmpty ? null : _keyboard) - .sortBy(_sort) - .filter(_filter) - .types(_type) - .includingTags(_tags ?? []) - .excludingTags(_excludingTags ?? []) - .includeDeleted(false) - .getPagingData(token: token, limit: GlobalConstant.pageSize), - pageSize: GlobalConstant.pageSize, - )..addListener( - () { - if (_controller.error == null) { - setState(() { - amityCommunities.clear(); - amityCommunities.addAll(_controller.loadedItems); - }); - } else { - //Error on pagination controller - setState(() {}); - print(_controller.error.toString()); - print(_controller.stacktrace.toString()); - ErrorDialog.show(context, - title: 'Error', message: _controller.error.toString()); - } - }, - ); - - WidgetsBinding.instance.addPostFrameCallback((timeStamp) { - _controller.fetchNextPage(); - }); - + resetLiveCollection(isReset: false); scrollcontroller.addListener(pagination); - super.initState(); } void pagination() { if ((scrollcontroller.position.pixels == scrollcontroller.position.maxScrollExtent) && - _controller.hasMoreItems) { + _channelLiveCollection.hasNextPage()) { + setState(() { + _channelLiveCollection.loadNext(); + }); + } + } + + void resetLiveCollection({ bool isReset = true }) async { + if (isReset) { + _channelLiveCollection.getStreamController().stream; setState(() { - _controller.fetchNextPage(); + amityChannels = []; }); } + + _channelLiveCollection = AmityChatClient.newChannelRepository() + .getChannels() + .withKeyword(_keyboard.isEmpty ? null : _keyboard) + .sortBy(_sort) + .filter(_filter) + .types(_type) + .includingTags(_tags ?? []) + .excludingTags(_excludingTags ?? []) + .includeDeleted(false) + .getLiveCollection(); + + _channelLiveCollection.getStreamController().stream.listen((event) { + if (mounted) { + setState(() { + amityChannels = event; + }); + } + }); + + WidgetsBinding.instance.addPostFrameCallback((timeStamp) { + _channelLiveCollection.loadNext(); + }); } @override @@ -95,8 +91,7 @@ class _ChannelListScreenState extends State { onChanged: (value) { _debouncer.run(() { _keyboard = value; - _controller.reset(); - _controller.fetchNextPage(); + resetLiveCollection(); }); }, decoration: const InputDecoration(hintText: 'Enter Keybaord'), @@ -128,7 +123,7 @@ class _ChannelListScreenState extends State { Icons.filter_alt_rounded, size: 18, ), - onSelected: (index) { + onSelected: (index) async { if (index == 1) { _filter = AmityChannelFilter.ALL; } @@ -138,9 +133,7 @@ class _ChannelListScreenState extends State { if (index == 3) { _filter = AmityChannelFilter.NOT_MEMBER; } - - _controller.reset(); - _controller.fetchNextPage(); + resetLiveCollection(); }, ), ), @@ -214,9 +207,7 @@ class _ChannelListScreenState extends State { _type.add(AmityChannelType.CONVERSATION); } } - - _controller.reset(); - _controller.fetchNextPage(); + resetLiveCollection(); }, ), ), @@ -240,9 +231,7 @@ class _ChannelListScreenState extends State { if (index == 1) { _sort = AmityChannelSortOption.LAST_ACTIVITY; } - - _controller.reset(); - _controller.fetchNextPage(); + resetLiveCollection(); }, ), ), @@ -260,8 +249,7 @@ class _ChannelListScreenState extends State { if (value.isEmpty) { _tags = []; } - _controller.reset(); - _controller.fetchNextPage(); + resetLiveCollection(); }); }, ), @@ -280,8 +268,7 @@ class _ChannelListScreenState extends State { if (value.isEmpty) { _excludingTags = []; } - _controller.reset(); - _controller.fetchNextPage(); + resetLiveCollection(); }); }, ), @@ -290,17 +277,16 @@ class _ChannelListScreenState extends State { ), ), Expanded( - child: amityCommunities.isNotEmpty + child: amityChannels.isNotEmpty ? RefreshIndicator( onRefresh: () async { - _controller.reset(); - _controller.fetchNextPage(); + resetLiveCollection(); }, child: ListView.builder( controller: scrollcontroller, - itemCount: amityCommunities.length, + itemCount: amityChannels.length, itemBuilder: (context, index) { - final amityChannel = amityCommunities[index]; + final amityChannel = amityChannels[index]; return Container( margin: const EdgeInsets.all(12), child: ChannelWidget( @@ -316,12 +302,12 @@ class _ChannelListScreenState extends State { ) : Container( alignment: Alignment.center, - child: _controller.isFetching + child: _channelLiveCollection.isFetching ? const CircularProgressIndicator() : const Text('No Post'), ), ), - if (_controller.isFetching && amityCommunities.isNotEmpty) + if (_channelLiveCollection.isFetching && amityChannels.isNotEmpty) Container( alignment: Alignment.center, child: const CircularProgressIndicator(), diff --git a/lib/presentation/screen/channel_profile/channel_profile_screen.dart b/lib/presentation/screen/channel_profile/channel_profile_screen.dart index 93d4414..fbe41fd 100644 --- a/lib/presentation/screen/channel_profile/channel_profile_screen.dart +++ b/lib/presentation/screen/channel_profile/channel_profile_screen.dart @@ -84,20 +84,11 @@ class _ChannelProfileScreenState extends State ), const PopupMenuItem( value: 2, - child: Text("Delete (Soft)"), - ), - const PopupMenuItem( - value: 3, - enabled: false, - child: Text("Delete (Hard)"), - ), - const PopupMenuItem( - value: 4, enabled: true, child: Text("Check my permission"), ), PopupMenuItem( - value: 5, + value: 3, child: Text((_amityChannel.isMuted ?? false) ? 'Unmute' : 'Mute'), @@ -116,11 +107,6 @@ class _ChannelProfileScreenState extends State queryParams: {'channelId': widget.channelId}); } if (index == 2) { - //Delete Channel - // AmitySocialClient.newChannelRepository() - // .deleteChannel(widget.channelId); - } - if (index == 4) { EditTextDialog.show(context, title: 'Check my permission in this community', @@ -145,7 +131,7 @@ class _ChannelProfileScreenState extends State } }); } - if (index == 5) { + if (index == 3) { ///Mute/Unmute Channel if (_amityChannel.isMuted ?? false) { AmityChatClient.newChannelRepository() diff --git a/lib/presentation/screen/dashboard/dashboard_screen.dart b/lib/presentation/screen/dashboard/dashboard_screen.dart index 7fad791..f1b1f63 100644 --- a/lib/presentation/screen/dashboard/dashboard_screen.dart +++ b/lib/presentation/screen/dashboard/dashboard_screen.dart @@ -1,4 +1,5 @@ import 'dart:io'; + import 'package:amity_sdk/amity_sdk.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/material.dart'; @@ -348,8 +349,9 @@ class _DashboardScreenState extends State { TextButton( onPressed: () { EditTextDialog.show(context, - hintText: 'Enter Channel Name', - buttonText: 'Join', onPress: (value) { + title: 'Go to Chat Screen', + hintText: 'Enter Channel ID', + buttonText: 'Submit', onPress: (value) { GoRouter.of(context) .goNamed(AppRoute.chat, params: {'channelId': value}); }); @@ -360,9 +362,10 @@ class _DashboardScreenState extends State { TextButton( onPressed: () { EditTextDialog.show(context, - hintText: 'Enter Channel Name', + title: 'Get Channel', + hintText: 'Enter Channel ID', // defString: 'live200', - buttonText: 'Join', onPress: (value) { + buttonText: 'Submit', onPress: (value) { GoRouter.of(context).pushNamed(AppRoute.channelProfile, params: {'channelId': value}); // AmityChatClient.newChannelRepository().getChannel(value);