diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 0e7557a..47515db 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -4,20 +4,26 @@ + + diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 02e5f58..aeaff6f 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip diff --git a/ios/Podfile b/ios/Podfile index adfd349..2c0add7 100644 --- a/ios/Podfile +++ b/ios/Podfile @@ -38,6 +38,18 @@ post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) end + ################ Awesome Notifications pod modification 1 ################### + awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks') + require awesome_pod_file + update_awesome_pod_build_settings(installer) + ################ Awesome Notifications pod modification 1 ################### + end + + ################ Awesome Notifications pod modification 2 ################### + awesome_pod_file = File.expand_path(File.join('plugins', 'awesome_notifications', 'ios', 'Scripts', 'AwesomePodFile'), '.symlinks') + require awesome_pod_file + update_awesome_main_target_settings('Runner', File.dirname(File.realpath(__FILE__)), flutter_root) + ################ Awesome Notifications pod modification 2 ################### installer.generated_projects.each do |project| project.targets.each do |target| target.build_configurations.each do |config| diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 42a0532..4a094b3 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -50,5 +50,9 @@ UIViewControllerBasedStatusBarAppearance + UNNotificationServiceExtension + + $(PRODUCT_BUNDLE_IDENTIFIER).MyNotificationServiceExtension + diff --git a/lib/main.dart b/lib/main.dart index 07afaae..8ccfa8d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -7,8 +7,12 @@ import 'package:didvan/providers/theme.dart'; import 'package:didvan/providers/user.dart'; import 'package:didvan/routes/route_generator.dart'; import 'package:didvan/services/app_initalizer.dart'; +import 'package:didvan/services/notification/awsome/awsome_notification_handler.dart'; +import 'package:didvan/services/notification/fcm/firebase_notification_handler.dart'; import 'package:didvan/views/podcasts/podcasts_state.dart'; import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart'; +import 'package:didvan/services/notification/lc/local_notification_service.dart'; +import 'package:didvan/services/notification/lc/show_notification_handler.dart'; import 'package:firebase_core/firebase_core.dart'; import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:flutter/foundation.dart'; @@ -16,36 +20,45 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:provider/provider.dart'; +@pragma('vm:entry-point') +Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { + // If you're going to use other Firebase services in the background, such as Firestore, + // make sure you call `initializeApp` before using other Firebase services. + WidgetsFlutterBinding.ensureInitialized(); + await Firebase.initializeApp( + options: kIsWeb + ? const FirebaseOptions( + apiKey: "AIzaSyA0HZjKpRuPOi1SC3f_EZTvlS3mcj9UVo0", + authDomain: "didvan-9b7da.firebaseapp.com", + projectId: "didvan-9b7da", + storageBucket: "didvan-9b7da.appspot.com", + messagingSenderId: "935017686266", + appId: "1:935017686266:web:a93f7a19bed23c51d2d543", + measurementId: "G-80B4H9E8Y0") + : const FirebaseOptions( + apiKey: 'AIzaSyBp-UHjWeM0H0UHtX5yguFKG-riMzvvCzw', + appId: '1:935017686266:android:f9cbc9aba8e3d65ed2d543', + messagingSenderId: '935017686266', + projectId: 'didvan-9b7da', + ), + ); + + // LocalNotificationService.initialize(); + // LocalNotificationService.display(message); + // LocalNotificationService.showBigPictureNotification(); + AwsomeNotificationHandler().main(); + AwsomeNotificationHandler().show(message); + + print("Handling a background message: ${message.messageId}"); +} + void main() async { try { WidgetsFlutterBinding.ensureInitialized(); - - await Firebase.initializeApp( - options: kIsWeb - ? const FirebaseOptions( - apiKey: "AIzaSyA0HZjKpRuPOi1SC3f_EZTvlS3mcj9UVo0", - authDomain: "didvan-9b7da.firebaseapp.com", - projectId: "didvan-9b7da", - storageBucket: "didvan-9b7da.appspot.com", - messagingSenderId: "935017686266", - appId: "1:935017686266:web:a93f7a19bed23c51d2d543", - measurementId: "G-80B4H9E8Y0") - : const FirebaseOptions( - apiKey: 'AIzaSyBp-UHjWeM0H0UHtX5yguFKG-riMzvvCzw', - appId: '1:935017686266:android:f9cbc9aba8e3d65ed2d543', - messagingSenderId: '935017686266', - projectId: 'didvan-9b7da', - ), - ); - final initMsg = await FirebaseMessaging.instance.getInitialMessage(); - if (initMsg != null) { - AppInitializer.clickAction = initMsg.data['click_action'].replaceAll( - 'navigate-', - '', - ); - } + FirebaseMessaging.onBackgroundMessage(_firebaseMessagingBackgroundHandler); + FirebaseNotificationHandler().initial(); } catch (e) { - log(e.toString()); + print(e.toString()); } runApp(const Didvan()); } @@ -54,6 +67,7 @@ final GlobalKey navigatorKey = GlobalKey(); class Didvan extends StatelessWidget { const Didvan({Key? key}) : super(key: key); + @override Widget build(BuildContext context) { return MultiProvider( @@ -94,7 +108,8 @@ class Didvan extends StatelessWidget { themeMode: themeProvider.themeMode, onGenerateRoute: (settings) => RouteGenerator.generateRoute(settings), - builder: BotToastInit(), //1. call BotToastInit + builder: BotToastInit(), + //1. call BotToastInit navigatorObservers: [BotToastNavigatorObserver()], initialRoute: '/', localizationsDelegates: const [ diff --git a/lib/models/comment/comment.dart b/lib/models/comment/comment.dart index 1fbf8d3..89c4d1e 100644 --- a/lib/models/comment/comment.dart +++ b/lib/models/comment/comment.dart @@ -6,9 +6,12 @@ class CommentData { int id; final String text; final String createdAt; + String? type; bool liked; bool disliked; + bool private; int status; + dynamic? mention; final FeedbackData feedback; final UserOverview user; final List replies; @@ -19,17 +22,21 @@ class CommentData { required this.createdAt, required this.liked, required this.disliked, + required this.private, required this.feedback, required this.user, required this.replies, required this.status, + this.type, + required this.mention, }); - factory CommentData.fromJson(Map json) => CommentData( + factory CommentData.fromJson(Map json, bool private) => CommentData( id: json['id'], text: json['text'], createdAt: json['createdAt'], liked: json['liked'], + private: private, disliked: json['disliked'], feedback: FeedbackData.fromJson(json['feedback']), user: UserOverview.fromJson(json['user']), @@ -39,14 +46,19 @@ class CommentData { ), ), status: json['status'], + mention: json['mention'], ); + Map toJson() => { 'id': id, 'text': text, 'createdAt': createdAt, 'liked': liked, 'disliked': disliked, + 'private': private, + 'mention': mention, + 'type': type, 'feedback': feedback.toJson(), 'user': user.toJson(), 'replies': replies.map((e) => e.toJson()).toList(), diff --git a/lib/models/comment/reply.dart b/lib/models/comment/reply.dart index a5804f9..7358bf5 100644 --- a/lib/models/comment/reply.dart +++ b/lib/models/comment/reply.dart @@ -11,6 +11,7 @@ class Reply { final FeedbackData feedback; final UserOverview user; final UserOverview toUser; + final String? mention; Reply({ required this.id, @@ -22,6 +23,7 @@ class Reply { required this.user, required this.toUser, required this.status, + this.mention, }); factory Reply.fromJson(Map json) => Reply( @@ -34,6 +36,7 @@ class Reply { user: UserOverview.fromJson(json['user']), toUser: UserOverview.fromJson(json['toUser']), status: json['status'], + mention: json['mention'], ); Map toJson() => { @@ -45,5 +48,6 @@ class Reply { 'feedback': feedback.toJson(), 'user': user.toJson(), 'toUser': toUser.toJson(), + 'mention': mention, }; } diff --git a/lib/models/notification_message.dart b/lib/models/notification_message.dart new file mode 100644 index 0000000..901f1c1 --- /dev/null +++ b/lib/models/notification_message.dart @@ -0,0 +1,36 @@ +class NotificationMessage { + String? title; + String? body; + String? type; + String? clickAction; + String? url; + String? image; + + NotificationMessage( + {this.title, + this.body, + this.type, + this.clickAction, + this.url, + this.image}); + + NotificationMessage.fromJson(Map json) { + title = json['title']; + body = json['body']; + type = json['type']; + clickAction = json['click_action']; + url = json['url']; + image = json['image']; + } + + Map toJson() { + final Map data = new Map(); + data['title'] = this.title; + data['body'] = this.body; + data['type'] = this.type; + data['click_action'] = this.clickAction; + data['url'] = this.url; + data['image'] = this.image; + return data; + } +} \ No newline at end of file diff --git a/lib/models/users_mention.dart b/lib/models/users_mention.dart new file mode 100644 index 0000000..0a75aa1 --- /dev/null +++ b/lib/models/users_mention.dart @@ -0,0 +1,24 @@ +class UsersMention { + int? id; + String? name; + String? type; + String? photo; + + UsersMention({this.id, this.name, this.type, this.photo}); + + UsersMention.fromJson(Map json) { + id = json['id']; + name = json['name']; + type = json['type']; + photo = json['photo']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['name'] = this.name; + data['type'] = this.type; + data['photo'] = this.photo; + return data; + } +} \ No newline at end of file diff --git a/lib/services/network/request.dart b/lib/services/network/request.dart index db54e3a..411ae74 100644 --- a/lib/services/network/request.dart +++ b/lib/services/network/request.dart @@ -11,6 +11,9 @@ class RequestService { static late String token; int? statusCode; + dynamic data(String s){ + return _body?[s] ?? const {}; + } Map get result => _body?['result'] ?? const {}; Map get errors => _body?['errors'] ?? const {}; diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index 1693f9d..cb0b9ca 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -186,12 +186,13 @@ class RequestHelper { static String mark(int id, String type) => '$baseUrl/$type/$id/mark'; static String tracking(int id, String type) => '$baseUrl/$type/$id/tracking'; - static String comments(int id, String type) => '$baseUrl/$type/$id/comments'; + static String comments(int id, String type) => '$baseUrl/$type/$id/comments/v2'; + static String usersMentions(String search) => '$baseUrl/comment/user?search=$search'; static String feedback(int id, int commentId, String type) => - '$baseUrl/$type/$id/comments/$commentId/feedback'; + '$baseUrl/$type/$id/comments/$commentId/feedback/v2'; static String addComment(int id, String type) => '$baseUrl/$type/$id/comments/add'; - static String deleteComment(int id) => '$baseUrl/comment/$id'; + static String deleteComment(int id) => '$baseUrl/comment/$id/v2'; static String reportComment(int id) => '$baseUrl/comment/$id/report'; static String _urlConcatGenerator(List> additions) { diff --git a/lib/services/notification/awsome/awsome_notification_controller.dart b/lib/services/notification/awsome/awsome_notification_controller.dart new file mode 100644 index 0000000..4781752 --- /dev/null +++ b/lib/services/notification/awsome/awsome_notification_controller.dart @@ -0,0 +1,41 @@ +import 'package:awesome_notifications/awesome_notifications.dart'; + +class NotificationController { + + /// Use this method to detect when a new notification or a schedule is created + @pragma("vm:entry-point") + static Future onNotificationCreatedMethod(ReceivedNotification receivedNotification) async { + // Your code goes here + print("onNotificationCreatedMethod--------------------------------------------------------------"); + + } + + /// Use this method to detect every time that a new notification is displayed + @pragma("vm:entry-point") + static Future onNotificationDisplayedMethod(ReceivedNotification receivedNotification) async { + // Your code goes here + print("onNotificationDisplayedMethod--------------------------------------------------------------"); + + } + + /// Use this method to detect if the user dismissed a notification + @pragma("vm:entry-point") + static Future onDismissActionReceivedMethod(ReceivedAction receivedAction) async { + // Your code goes here + print("onDismissActionReceivedMethod--------------------------------------------------------------"); + + } + + /// Use this method to detect when the user taps on a notification or action button + @pragma("vm:entry-point") + static Future onActionReceivedMethod(ReceivedAction receivedAction) async { + // Your code goes here + + // Navigate into pages, avoiding to open the notification details page over another details page already opened + + // MyApp.navigatorKey.currentState?.pushNamedAndRemoveUntil('/notification-page', + // (route) => (route.settings.name != '/notification-page') || route.isFirst, + // arguments: receivedAction); + print("onActionReceivedMethod--------------------------------------------------------------"); + } +} \ No newline at end of file diff --git a/lib/services/notification/awsome/awsome_notification_handler.dart b/lib/services/notification/awsome/awsome_notification_handler.dart new file mode 100644 index 0000000..e7091e4 --- /dev/null +++ b/lib/services/notification/awsome/awsome_notification_handler.dart @@ -0,0 +1,154 @@ +import 'dart:ui'; + +import 'package:awesome_notifications/awesome_notifications.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; + +import '../../../models/notification_message.dart'; +import 'awsome_notification_controller.dart'; + +class AwsomeNotificationHandler { + main() async { + late ReceivedAction? initialAction; + + AwesomeNotifications().initialize( + // set the icon to null if you want to use the default app icon + null, + [ + NotificationChannel( + channelKey: 'alerts', + channelName: 'Alerts', + channelDescription: 'Notification tests as alerts', + playSound: true, + onlyAlertOnce: true, + groupAlertBehavior: GroupAlertBehavior.Children, + importance: NotificationImportance.High, + defaultPrivacy: NotificationPrivacy.Public, + defaultColor: const Color(0xFF007EA7), + criticalAlerts: true, + ledColor: Colors.white) + ], + + // Channel groups are only visual and are not required + // channelGroups: [ + // NotificationChannelGroup( + // channelGroupKey: 'basic_channel_group', + // channelGroupName: 'Basic group') + // ], + debug: true); + + initialAction = await AwesomeNotifications() + .getInitialNotificationAction(removeFromActionEvents: false); + + AwesomeNotifications().setListeners( + onActionReceivedMethod: NotificationController.onActionReceivedMethod, + onNotificationCreatedMethod: + NotificationController.onNotificationCreatedMethod, + onNotificationDisplayedMethod: + NotificationController.onNotificationDisplayedMethod, + onDismissActionReceivedMethod: + NotificationController.onDismissActionReceivedMethod); + + AwesomeNotifications().isNotificationAllowed().then((isAllowed) { + //It would be more appropriate if you can show your own dialog + //to the user before requesting the notifications permissons. + if (!isAllowed) { + AwesomeNotifications().requestPermissionToSendNotifications( + permissions: [ + NotificationPermission.Alert, + NotificationPermission.Sound, + NotificationPermission.Badge, + NotificationPermission.Vibration, + NotificationPermission.Light, + NotificationPermission.FullScreenIntent, + ], + ); + } + }); + } + + show(RemoteMessage message) async { + NotificationMessage notificationMessage = + NotificationMessage.fromJson(message.data); + + switch (notificationMessage.type) { + case "1": + await showNotificationTypeNews(notificationMessage); + break; + + case "2": + await showNotificationTypeMessage(notificationMessage); + + break; + + case "3": + await showNotificationTypeEmoji(notificationMessage); + + break; + } + try {} catch (ex) {} + } + + showNotificationTypeNews(NotificationMessage message) async { + AwesomeNotifications().createNotification( + content: NotificationContent( + id: DateTime.now().millisecondsSinceEpoch ~/ 1000, + channelKey: 'alerts', + actionType: ActionType.Default, + title: "\u200f ${message.title} \u200f", + body: "${message.body.toString()}", + notificationLayout: NotificationLayout.BigText, + largeIcon: message.image.toString(), + color: const Color(0xFF007EA7)), + ); + } + + showNotificationTypeMessage(NotificationMessage message) async { + AwesomeNotifications().createNotification( + content: NotificationContent( + id: DateTime.now().millisecondsSinceEpoch ~/ 1000, + channelKey: 'alerts', + actionType: ActionType.Default, + title: message.title.toString(), + body: message.body.toString(), + largeIcon: message.image.toString(), + roundedLargeIcon: true, + notificationLayout: NotificationLayout.Inbox, + color: const Color(0xFF007EA7)), + actionButtons: [ + NotificationActionButton( + key: 'REPLY', + label: 'جواب دادن', + requireInputText: true, + actionType: ActionType.SilentAction), + NotificationActionButton( + key: 'DISMISS', + label: 'خوانده شده', + actionType: ActionType.DismissAction, + isDangerousOption: true) + ]); + } + + showNotificationTypeEmoji(NotificationMessage message) async { + AwesomeNotifications().createNotification( + content: NotificationContent( + id: DateTime.now().millisecondsSinceEpoch ~/ 1000, + channelKey: 'alerts', + title: 'Emojis are awesome too! ' + + Emojis.animals_lady_beetle + + Emojis.activites_balloon + + Emojis.emotion_red_heart, + body: + 'Simple body with a bunch of Emojis! ${Emojis.transport_police_car} ${Emojis.animals_dog} ${Emojis.flag_UnitedStates} ${Emojis.person_baby}', + largeIcon: + 'https://cdn.britannica.com/72/232772-050-4E3D86CC/mind-blown-emoji-head-exploding-emoticon.jpg', + notificationLayout: NotificationLayout.BigPicture, + payload: { + 'title': 'Notification Title', + 'body': 'Notification Body', + 'image': 'path/to/smallImage.png', // Path to small image + 'largeImage': 'path/to/largeImage.png', // Path to large image + })); + } +} diff --git a/lib/services/notification/fcm/firebase_notification_handler.dart b/lib/services/notification/fcm/firebase_notification_handler.dart new file mode 100644 index 0000000..c1f905e --- /dev/null +++ b/lib/services/notification/fcm/firebase_notification_handler.dart @@ -0,0 +1,161 @@ +import 'package:didvan/services/notification/awsome/awsome_notification_handler.dart'; +import 'package:firebase_core/firebase_core.dart'; +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/foundation.dart'; + +import '../lc/local_notification_service.dart'; +import '../../app_initalizer.dart'; + +class FirebaseNotificationHandler { + Future initial() async { + try { + WidgetsFlutterBinding.ensureInitialized(); + + await Firebase.initializeApp( + options: kIsWeb + ? const FirebaseOptions( + apiKey: "AIzaSyA0HZjKpRuPOi1SC3f_EZTvlS3mcj9UVo0", + authDomain: "didvan-9b7da.firebaseapp.com", + projectId: "didvan-9b7da", + storageBucket: "didvan-9b7da.appspot.com", + messagingSenderId: "935017686266", + appId: "1:935017686266:web:a93f7a19bed23c51d2d543", + measurementId: "G-80B4H9E8Y0") + : const FirebaseOptions( + apiKey: 'AIzaSyBp-UHjWeM0H0UHtX5yguFKG-riMzvvCzw', + appId: '1:935017686266:android:f9cbc9aba8e3d65ed2d543', + messagingSenderId: '935017686266', + projectId: 'didvan-9b7da', + ), + ); + final initMsg = await FirebaseMessaging.instance.getInitialMessage(); + if (initMsg != null) { + AppInitializer.clickAction = initMsg.data['click_action'].replaceAll( + 'navigate-', + '', + ); + } + + // LocalNotificationService.initialize(); + + NotificationSettings settings = + await FirebaseMessaging.instance.requestPermission( + alert: true, + announcement: false, + badge: true, + carPlay: false, + criticalAlert: false, + provisional: false, + sound: true, + ); + + debugPrint( + 'User granted notifications permission: ${settings.authorizationStatus}'); + + // Handling background messages using the specified handler + // FirebaseMessaging.onBackgroundMessage( + // _firebaseMessagingBackgroundHandler); + + // Listening for incoming messages while the app is in the foreground + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + if (message.data != null) { +/* if (message.notification!.title != null && + message.notification!.body != null) { + final notificationData = message.data; + final screen = notificationData['screen']; + + // Showing an alert dialog when a notification is received (Foreground state) + // showDialog( + // context: context, + // barrierDismissible: false, + // builder: (BuildContext context) { + // return WillPopScope( + // onWillPop: () async => false, + // child: AlertDialog( + // title: Text(message.notification!.title!), + // content: Text(message.notification!.body!), + // actions: [ + // if (notificationData.containsKey('screen')) + // TextButton( + // onPressed: () { + // Navigator.pop(context); + // Navigator.of(context).pushNamed(screen); + // }, + // child: const Text('Open Screen'), + // ), + // TextButton( + // onPressed: () => Navigator.of(context).pop(), + // child: const Text('Dismiss'), + // ), + // ], + // ), + // ); + // }, + // ); + }*/ + // LocalNotificationService.initialize(); + // LocalNotificationService.showBigPictureNotification(); + // LocalNotificationService.display(message); + AwsomeNotificationHandler().main(); + AwsomeNotificationHandler().show(message); + } + }); + + // Handling the initial message received when the app is launched from dead (killed state) + // When the app is killed and a new notification arrives when user clicks on it + // It gets the data to which screen to open + FirebaseMessaging.instance.getInitialMessage().then((message) { + if (message != null) { + _handleNotificationClick(message); + } + }); + + // Handling a notification click event when the app is in the background + FirebaseMessaging.onMessageOpenedApp.listen((RemoteMessage message) { + _handleNotificationClick(message); + }); + } catch (e) { + print(e.toString()); + } + } + + // Handling a notification click event by navigating to the specified screen + void _handleNotificationClick(RemoteMessage message) { + final notificationData = message.data; + + if (notificationData.containsKey('screen')) { + final screen = notificationData['screen']; + // Navigator.of(context).pushNamed(screen); + } + } +} + +// Handler for background messages +// @pragma('vm:entry-point') +// Future _firebaseMessagingBackgroundHandler(RemoteMessage message) async { +// await Firebase.initializeApp( +// options: kIsWeb +// ? const FirebaseOptions( +// apiKey: "AIzaSyA0HZjKpRuPOi1SC3f_EZTvlS3mcj9UVo0", +// authDomain: "didvan-9b7da.firebaseapp.com", +// projectId: "didvan-9b7da", +// storageBucket: "didvan-9b7da.appspot.com", +// messagingSenderId: "935017686266", +// appId: "1:935017686266:web:a93f7a19bed23c51d2d543", +// measurementId: "G-80B4H9E8Y0") +// : const FirebaseOptions( +// apiKey: 'AIzaSyBp-UHjWeM0H0UHtX5yguFKG-riMzvvCzw', +// appId: '1:935017686266:android:f9cbc9aba8e3d65ed2d543', +// messagingSenderId: '935017686266', +// projectId: 'didvan-9b7da', +// ), +// ); +// +// LocalNotificationService.initialize(); +// LocalNotificationService.display(message); +// // AwsomeNotificationHandler().main(); +// // AwsomeNotificationHandler().show(message); +// +// return Future.value(); +// } diff --git a/lib/services/notification/lc/local_notification_service.dart b/lib/services/notification/lc/local_notification_service.dart new file mode 100644 index 0000000..63d9b70 --- /dev/null +++ b/lib/services/notification/lc/local_notification_service.dart @@ -0,0 +1,133 @@ +import 'dart:io'; + +import 'package:firebase_messaging/firebase_messaging.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; +import 'package:http/http.dart'; +import 'package:path_provider/path_provider.dart'; + +import '../../../constants/assets.dart'; +import '../../../models/notification_message.dart'; +import '../../../views/widgets/notification/notification_dynamic_dialog.dart'; + +class LocalNotificationService { +// Instance of Flutternotification plugin + static final FlutterLocalNotificationsPlugin _notificationsPlugin = + FlutterLocalNotificationsPlugin(); + + static void initialize() { + // Initialization setting for android + const InitializationSettings initializationSettingsAndroid = + InitializationSettings( + android: AndroidInitializationSettings("@mipmap/ic_launcher")); + _notificationsPlugin.initialize( + initializationSettingsAndroid, + // to handle event when we receive notification + onDidReceiveNotificationResponse: (details) { + if (details.input != null) {} + }, + ); + } + + static Future display(RemoteMessage message) async { + // To display the notification in device + try { + final id = DateTime.now().millisecondsSinceEpoch ~/ 1000; + final BigPictureStyleInformation bigPictureStyleInformation = + BigPictureStyleInformation( + FilePathAndroidBitmap( + "lib/assets/aaaa.jpg"), + contentTitle: NotificationMessage.fromJson(message.data).title, + htmlFormatContentTitle: true, + summaryText: NotificationMessage.fromJson(message.data).body, + htmlFormatSummaryText: true, + htmlFormatContent: true, + largeIcon: const FilePathAndroidBitmap( + "lib/assets/aaaa.jpg")); + + NotificationDetails notificationDetails = NotificationDetails( + android: AndroidNotificationDetails("Channel Id", "Main Channel", + groupKey: "gfg", + color: Colors.white, + importance: Importance.max, + playSound: true, + priority: Priority.high), + ); + if (message.notification == null) { + final NotificationMessage notificationModel = + NotificationMessage.fromJson(message.data); + await _notificationsPlugin.show(id, notificationModel.title, + notificationModel.body, notificationDetails, + payload: message.data['route']); + } else { + await _notificationsPlugin.show(id, message.notification?.title, + message.notification?.body, notificationDetails, + payload: message.data['route']); + } + } catch (e) { + debugPrint(e.toString()); + } + } + + void messageListener(BuildContext context) { + // Either you can pass buildcontext or you + // can take a context from navigator key + FirebaseMessaging.onMessage.listen((RemoteMessage message) { + print('Got a message in the foreground!'); + print('Message data: ${message.data}'); + + if (message.data != null) { + print( + 'Message also contained a notification: so it will pop up ${message.notification!.body}'); + showDialog( + context: context, + // context: navigatorKey!.currentContext!, + builder: ((BuildContext context) { + return NotificationDynamicDialog( + title: message.notification!.title, + body: message.notification!.body); + })); + } + }); + } + + static Future _downloadAndSaveFile(String url, String fileName) async { + final Directory directory = await getApplicationDocumentsDirectory(); + final String filePath = '${directory.path}/$fileName'; + final Response response = await get(Uri.parse(url)); + final File file = File(filePath); + await file.writeAsBytes(response.bodyBytes); + return filePath; + } + + static Future showBigPictureNotification() async { + final String largeIconPath = await _downloadAndSaveFile( + 'https://via.placeholder.com/48x48', 'largeIcon'); + final String bigPicturePath = await _downloadAndSaveFile( + 'https://via.placeholder.com/400x800', 'bigPicture'); + final BigPictureStyleInformation bigPictureStyleInformation = + BigPictureStyleInformation(FilePathAndroidBitmap(bigPicturePath), + largeIcon: FilePathAndroidBitmap(largeIconPath), + contentTitle: 'overridden big content title', + htmlFormatContentTitle: true, + summaryText: 'summary text', + htmlFormatSummaryText: true); + final AndroidNotificationDetails androidPlatformChannelSpecifics = + AndroidNotificationDetails( + 'big text channel id', 'big text channel name', + channelDescription: 'big text channel description', + largeIcon: FilePathAndroidBitmap(largeIconPath), + channelShowBadge: false, + + + styleInformation: bigPictureStyleInformation); + final NotificationDetails platformChannelSpecifics = + NotificationDetails(android: androidPlatformChannelSpecifics); + await _notificationsPlugin.show( + 0, 'big text title', 'silent body', platformChannelSpecifics); + } + +} + + + diff --git a/lib/services/notification/lc/show_notification_handler.dart b/lib/services/notification/lc/show_notification_handler.dart new file mode 100644 index 0000000..c085647 --- /dev/null +++ b/lib/services/notification/lc/show_notification_handler.dart @@ -0,0 +1,40 @@ +import 'package:flutter_local_notifications/flutter_local_notifications.dart'; + +void showNotificationAndroid(String title, String value) async { + const AndroidNotificationDetails androidNotificationDetails = + AndroidNotificationDetails('channel_id', 'Channel Name', + channelDescription: 'Channel Description', + importance: Importance.max, + priority: Priority.high, + ticker: 'ticker'); + + int notification_id = 1; + const NotificationDetails notificationDetails = + NotificationDetails(android: androidNotificationDetails); + + await FlutterLocalNotificationsPlugin().show( + notification_id, title, value, notificationDetails, + payload: 'Not present'); +} + +void showNotificationIos(String title, String value) async { + DarwinNotificationDetails iOSPlatformChannelSpecifics = DarwinNotificationDetails( + presentAlert: true, + // Present an alert when the notification is displayed and the application is in the foreground (only from iOS 10 onwards) + presentBadge: true, + // Present the badge number when the notification is displayed and the application is in the foreground (only from iOS 10 onwards) + presentSound: true, + // Play a sound when the notification is displayed and the application is in the foreground (only from iOS 10 onwards) + subtitle: title, + //Secondary description (only from iOS 10 onwards) + threadIdentifier: value); + + int notification_id = 1; + + NotificationDetails platformChannelSpecifics = + NotificationDetails(iOS: iOSPlatformChannelSpecifics); + + await FlutterLocalNotificationsPlugin().show( + notification_id, title, value, platformChannelSpecifics, + payload: 'Not present'); +} diff --git a/lib/views/comments/comments.dart b/lib/views/comments/comments.dart index 1488eff..e70175e 100644 --- a/lib/views/comments/comments.dart +++ b/lib/views/comments/comments.dart @@ -1,22 +1,33 @@ +import 'dart:ui'; + import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/assets.dart'; +import 'package:didvan/models/users_mention.dart'; import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/views/comments/comments_state.dart'; import 'package:didvan/views/comments/widgets/comment.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; +import 'package:didvan/views/widgets/didvan/checkbox.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter/widgets.dart'; import 'package:provider/provider.dart'; +import '../widgets/skeleton_image.dart'; +import '../widgets/user_mention.dart'; + class Comments extends StatefulWidget { final Map pageData; + const Comments({ Key? key, required this.pageData, @@ -47,6 +58,8 @@ class _CommentsState extends State { @override Widget build(BuildContext context) { + final commentsState = context.watch(); + final bottomViewInset = MediaQuery.of(context).viewInsets.bottom; if (bottomViewInset == 0) { if (_bottomPadding != 0) { @@ -97,6 +110,48 @@ class _CommentsState extends State { ), ], ), + AnimatedVisibility( + duration: DesignConfig.lowAnimationDuration, + isVisible: commentsState.showUsersForMentionsLayout, + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0), + child: Container( + decoration: BoxDecoration( + color: + Theme.of(context).colorScheme.focused.withOpacity(0.5)), + child: DidvanScaffold( + appBarData: null, + padding: const EdgeInsets.only( + left: 16, right: 16, top: 16, bottom: 92), + backgroundColor: Colors.white.withOpacity(0.0), + slivers: [ + Consumer( + builder: (context, state, child) => + SliverStateHandler( + onRetry: state.getUsersMention, + state: state, + itemPadding: const EdgeInsets.symmetric(vertical: 8), + childCount: state.usersMention.length, + placeholder: const _UsersPlaceholder(), + centerEmptyState: _isPage, + enableEmptyState: state.usersMention.isEmpty, + emptyState: EmptyState( + asset: Assets.emptyBookmark, + title: 'لیست خالی است', + ), + builder: (context, state, index) { + return UserMention( + user: state.usersMention[index], + index: index, + ); + }, + ), + ), + ], + ), + ), + ), + ), Positioned( left: 0, right: 0, @@ -111,6 +166,7 @@ class _CommentsState extends State { class _MessageBox extends StatefulWidget { final FocusNode focusNode; + const _MessageBox({Key? key, required this.focusNode}) : super(key: key); @override @@ -118,59 +174,190 @@ class _MessageBox extends StatefulWidget { } class _MessageBoxState extends State<_MessageBox> { - final _controller = TextEditingController(); - @override Widget build(BuildContext context) { final state = context.watch(); + + void _onCheckBoxChange(bool b) { + state.hideMentionedUser = b; + state.update(); + } + return Column( children: [ AnimatedVisibility( duration: DesignConfig.lowAnimationDuration, isVisible: state.showReplyBox, - child: Container( - padding: const EdgeInsets.all(8), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - border: Border( - top: BorderSide( - color: Theme.of(context).colorScheme.border, + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondCTA, + borderRadius: const BorderRadius.only( + topRight: Radius.circular(16), + topLeft: Radius.circular(16)), + boxShadow: [ + BoxShadow( + color: Theme.of(context) + .colorScheme + .title + .withOpacity(0.2), + offset: const Offset( + 5.0, + 5.0, + ), + blurRadius: 10.0, + spreadRadius: 2.0, + ) + ] + + // border: Border( + // top: BorderSide( + // color: Theme.of(context).colorScheme.border, + // ), + // ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + if (state.replyingTo != null) + DidvanText( + 'پاسخ به ${state.replyingTo!.fullName}:', + color: Theme.of(context).colorScheme.primary, + style: Theme.of(context).textTheme.bodySmall, + ), + const Spacer(), + DidvanIconButton( + gestureSize: 24, + color: Theme.of(context).colorScheme.primary, + icon: DidvanIcons.close_regular, + onPressed: () { + state.commentId = null; + state.replyingTo = null; + state.showReplyBox = false; + state.update(); + }, + ), + ], ), ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - if (state.replyingTo != null) - DidvanText( - 'پاسخ به ${state.replyingTo!.fullName}:', - color: Theme.of(context).colorScheme.caption, - style: Theme.of(context).textTheme.bodySmall, - ), - const Spacer(), - DidvanIconButton( - gestureSize: 24, - color: Theme.of(context).colorScheme.caption, - icon: DidvanIcons.close_regular, - onPressed: () { - state.commentId = null; - state.replyingTo = null; - state.showReplyBox = false; - state.update(); - }, + Container( + color: Theme.of(context).colorScheme.secondCTA, + padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), + child: Divider( + height: 2, + color: Theme.of(context).colorScheme.border, ), - ], - ), + ) + ], + ), + ), + AnimatedVisibility( + duration: DesignConfig.lowAnimationDuration, + isVisible: state.usersMentioned.name != null, + child: Column( + children: [ + Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondCTA, + borderRadius: !state.showReplyBox + ? BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16)) + : null + // border: Border( + // top: BorderSide( + // color: Theme.of(context).colorScheme.border, + // ), + // ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Container( + color: Theme.of(context).colorScheme.primary, + width: 2, + height: 40, + ), + SizedBox( + width: 8, + ), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DidvanText( + "اشاره به", + color: Theme.of(context).colorScheme.inputText, + style: Theme.of(context).textTheme.labelSmall, + ), + DidvanText( + state.usersMentioned.name.toString(), + color: Theme.of(context).colorScheme.navigation, + style: Theme.of(context).textTheme.bodySmall, + ), + ], + ) + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + DidvanIconButton( + gestureSize: 24, + color: Theme.of(context).colorScheme.primary, + icon: DidvanIcons.close_regular, + onPressed: () { + state.usersMentioned = UsersMention(); + state.update(); + }, + ), + !state.showReplyBox + ? DidvanCheckbox( + title: "پنهان کردن فراخوانی", + value: state.hideMentionedUser, + color: Theme.of(context).colorScheme.primary, + onChanged: _onCheckBoxChange, + size: 12, + ) + : SizedBox(), + ], + ) + ], + ), + ), + ], ), ), Container( decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - border: Border( - top: BorderSide( - color: Theme.of(context).colorScheme.border, - ), - ), + boxShadow: state.showReplyBox && state.usersMentioned.name == null + ? null + : [ + BoxShadow( + color: + Theme.of(context).colorScheme.title.withOpacity(0.2), + offset: const Offset( + 5.0, + 5.0, + ), + blurRadius: 10.0, + spreadRadius: 2.0, + ) + ], + color: state.showReplyBox && state.usersMentioned.name == null + ? Theme.of(context).colorScheme.secondCTA + : Theme.of(context).colorScheme.surface, + // border: Border( + // top: BorderSide( + // color: Theme.of(context).colorScheme.border, + // ), + // ), ), child: Row( children: [ @@ -183,7 +370,7 @@ class _MessageBoxState extends State<_MessageBox> { Expanded( child: TextField( focusNode: widget.focusNode, - controller: _controller, + controller: state.commentTextFieldController, keyboardType: TextInputType.multiline, textInputAction: TextInputAction.send, style: Theme.of(context).textTheme.bodyMedium, @@ -195,7 +382,7 @@ class _MessageBoxState extends State<_MessageBox> { hintStyle: Theme.of(context).textTheme.bodySmall!.copyWith( color: Theme.of(context).colorScheme.disabledText), ), - onChanged: (value) => state.text = value, + onChanged: (value) => _onChange(state, value), ), ), ], @@ -206,9 +393,33 @@ class _MessageBoxState extends State<_MessageBox> { } void _onSend(CommentsState state) { - if (state.text.replaceAll(' ', '').isNotEmpty) { + if (state.commentTextFieldController.text.replaceAll(' ', '').isNotEmpty) { state.addComment(); - _controller.text = ''; + state.commentTextFieldController.text = ''; + } + } + + void _onChange(CommentsState state, value) { + state.commentTextFieldController.text = value; + if (state.usersMentioned.name == null) { + if (state.commentTextFieldController.text.contains("@")) { + int index = state.commentTextFieldController.text.indexOf("@"); + if (state.commentTextFieldController.text.length > index + 1) { + if (state.commentTextFieldController.text + .substring(index) + .contains(" ")) { + state.showUsersForMentionsLayout = false; + } else { + state.mentionedText = + state.commentTextFieldController.text.substring(index); + state.getUsersMention(); + state.showUsersForMentionsLayout = true; + } + } + } else { + state.showUsersForMentionsLayout = false; + } + state.update(); } } } @@ -276,3 +487,28 @@ class _CommentPlaceholder extends StatelessWidget { ); } } + +class _UsersPlaceholder extends StatelessWidget { + const _UsersPlaceholder({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return const Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ShimmerPlaceholder( + height: 40, + width: 40, + borderRadius: DesignConfig.highBorderRadius, + ), + SizedBox(width: 16), + Expanded( + child: ShimmerPlaceholder( + borderRadius: DesignConfig.highBorderRadius, + height: 40, + ), + ), + ], + ); + } +} diff --git a/lib/views/comments/comments_state.dart b/lib/views/comments/comments_state.dart index 4a0594c..1344860 100644 --- a/lib/views/comments/comments_state.dart +++ b/lib/views/comments/comments_state.dart @@ -4,24 +4,32 @@ import 'package:didvan/models/comment/feedback.dart'; import 'package:didvan/models/comment/reply.dart'; import 'package:didvan/models/comment/user.dart'; import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/users_mention.dart'; import 'package:didvan/models/view/alert_data.dart'; import 'package:didvan/providers/core.dart'; import 'package:didvan/providers/user.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/utils/action_sheet.dart'; +import 'package:didvan/views/widgets/user_mention.dart'; +import 'package:flutter/cupertino.dart'; import 'package:provider/provider.dart'; class CommentsState extends CoreProvier { - String text = ''; + TextEditingController commentTextFieldController = TextEditingController(); + UsersMention usersMentioned = UsersMention(); + String mentionedText = ''; int? commentId; UserOverview? replyingTo; bool showReplyBox = false; + bool showUsersForMentionsLayout = false; + bool hideMentionedUser = false; late void Function(int count) onCommentsChanged; int _count = 0; late String type; final List comments = []; + final List usersMention = []; final Map> _feedbackQueue = {}; int itemId = 0; @@ -33,9 +41,9 @@ class CommentsState extends CoreProvier { await service.httpGet(); if (service.isSuccess) { comments.clear(); - final messages = service.result['comments']; + final messages = service.result['comments']['public']; for (var i = 0; i < messages.length; i++) { - comments.add(CommentData.fromJson(messages[i])); + comments.add(CommentData.fromJson(messages[i], false)); _count++; for (var j = 0; j < messages[i]['replies'].length; j++) { _count++; @@ -47,6 +55,23 @@ class CommentsState extends CoreProvier { appState = AppState.failed; } + Future getUsersMention() async { + final service = RequestService( + RequestHelper.usersMentions(mentionedText.replaceAll("@", "")), + ); + await service.httpGet(); + if (service.isSuccess) { + usersMention.clear(); + final List users = service.data('users'); + usersMention + .addAll(users.map((users) => UsersMention.fromJson(users)).toList()); + + appState = AppState.idle; + return; + } + appState = AppState.failed; + } + Future feedback({ required int id, required bool like, @@ -92,7 +117,7 @@ class CommentsState extends CoreProvier { comments.firstWhere((comment) => comment.id == commentId).replies.add( Reply( id: 0, - text: text, + text: commentTextFieldController.text, createdAt: DateTime.now().toString(), liked: false, disliked: false, @@ -104,26 +129,29 @@ class CommentsState extends CoreProvier { photo: user.photo, ), status: 2, + mention: usersMentioned.name, ), ); } else { comments.insert( 0, CommentData( - id: 0, - text: text, - createdAt: DateTime.now().toString(), - liked: false, - disliked: false, - feedback: FeedbackData(like: 0, dislike: 0), - user: UserOverview( - id: user.id, - fullName: user.fullName, - photo: user.photo, - ), - replies: [], - status: 2, - ), + id: 0, + text: commentTextFieldController.text, + createdAt: DateTime.now().toString(), + liked: false, + disliked: false, + feedback: FeedbackData(like: 0, dislike: 0), + user: UserOverview( + id: user.id, + fullName: user.fullName, + photo: user.photo, + ), + replies: [], + status: 2, + private: hideMentionedUser, + mention: usersMentioned.name, + ), ); } @@ -138,8 +166,12 @@ class CommentsState extends CoreProvier { body.addAll({'status': 2}); showReplyBox = false; - update(); - body.addAll({'text': text}); + // update(); + body.addAll({ + 'text': commentTextFieldController.text, + "mention": usersMentioned.id, + "private": hideMentionedUser + }); final service = RequestService( RequestHelper.addComment(itemId, type), body: body, @@ -159,6 +191,9 @@ class CommentsState extends CoreProvier { } commentId = null; replyingTo = null; + usersMentioned = UsersMention(); + mentionedText = ''; + update(); } } diff --git a/lib/views/comments/widgets/comment.dart b/lib/views/comments/widgets/comment.dart index 124d8bc..f070cc1 100644 --- a/lib/views/comments/widgets/comment.dart +++ b/lib/views/comments/widgets/comment.dart @@ -20,6 +20,7 @@ import 'package:provider/provider.dart'; class Comment extends StatefulWidget { final FocusNode focusNode; final CommentData comment; + const Comment({ Key? key, required this.focusNode, @@ -109,6 +110,13 @@ class CommentState extends State { ), ], ), + comment.mention != null + ? DidvanText( + comment.mention.toString(), + color: Theme.of(context).colorScheme.primary, + style: Theme.of(context).textTheme.titleSmall, + ) + : SizedBox(), const SizedBox(height: 8), if (isReply) DidvanText( @@ -254,6 +262,7 @@ class _FeedbackButtons extends StatefulWidget { final bool dislikeValue; final void Function(bool like, bool dislike, int likeCount, int dislikeCount) onFeedback; + const _FeedbackButtons({ Key? key, required this.onFeedback, diff --git a/lib/views/widgets/didvan/checkbox.dart b/lib/views/widgets/didvan/checkbox.dart index e0df308..9200422 100644 --- a/lib/views/widgets/didvan/checkbox.dart +++ b/lib/views/widgets/didvan/checkbox.dart @@ -1,15 +1,19 @@ +import 'package:didvan/config/theme_data.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class DidvanCheckbox extends StatefulWidget { final String title; final bool? value; + final double size; + final Color? color; final void Function(bool value) onChanged; const DidvanCheckbox({ Key? key, required this.title, required this.value, - required this.onChanged, + required this.onChanged, this.size = 15, this.color, }) : super(key: key); @override @@ -29,6 +33,9 @@ class _DidvanCheckboxState extends State { @override Widget build(BuildContext context) { + Color? _color = widget.color; + _color ??= Theme.of(context).colorScheme.text; + return GestureDetector( onTap: () { setState(() { @@ -40,16 +47,22 @@ class _DidvanCheckboxState extends State { color: Colors.transparent, child: Row( children: [ - Checkbox( - value: _value, - onChanged: (value) { - setState(() { - _value = value ?? false; - }); - widget.onChanged(_value); - }, - ), - DidvanText(widget.title), + Transform.scale( + scale: widget.size ==15 ? 1 : 0.8, + child: Checkbox( + activeColor: widget.color, + + visualDensity: const VisualDensity(horizontal: -4, vertical: -4), + side: BorderSide(color: _color, width: 1.5), + value: _value, + onChanged: (value) { + setState(() { + _value = value ?? false; + }); + widget.onChanged(_value); + }, + ), + ), DidvanText(widget.title,fontSize: widget.size,color: _color, ), ], ), ), diff --git a/lib/views/widgets/notification/notification_dynamic_dialog.dart b/lib/views/widgets/notification/notification_dynamic_dialog.dart new file mode 100644 index 0000000..4256ae5 --- /dev/null +++ b/lib/views/widgets/notification/notification_dynamic_dialog.dart @@ -0,0 +1,31 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +class NotificationDynamicDialog extends StatefulWidget { + final title; + final body; + const NotificationDynamicDialog({this.title, this.body}); + @override + _NotificationDynamicDialogState createState() => _NotificationDynamicDialogState(); +} + +class _NotificationDynamicDialogState extends State { + @override + Widget build(BuildContext context) { + // You can change the UI as per + // your requirement or choice + return AlertDialog( + title: Text(widget.title), + actions: [ + OutlinedButton.icon( + label: const Text('Close'), + onPressed: () { + Navigator.pop(context); + }, + icon: const Icon(Icons.close)) + ], + content: Text(widget.body), + ); + } +} + diff --git a/lib/views/widgets/user_mention.dart b/lib/views/widgets/user_mention.dart new file mode 100644 index 0000000..201ff39 --- /dev/null +++ b/lib/views/widgets/user_mention.dart @@ -0,0 +1,77 @@ +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +import '../../config/design_config.dart'; +import '../../constants/app_icons.dart'; +import '../../models/users_mention.dart'; +import '../comments/comments_state.dart'; +import 'didvan/text.dart'; + +class UserMention extends StatefulWidget { + final UsersMention user; + final int index; + + const UserMention({ + super.key, + required this.user, + required this.index, + }); + + @override + State createState() => _UserMentionState(); +} + +class _UserMentionState extends State { + @override + Widget build(BuildContext context) { + final state = context.watch(); + + return InkWrapper( + onPressed: () { + state.usersMentioned = widget.user; + state.commentTextFieldController.text = state + .commentTextFieldController.text + .replaceAll(state.mentionedText.toString(), "") + .replaceAll("@", ""); + state.showUsersForMentionsLayout = false; + state.update(); + }, + child: Column( + children: [ + Row( + children: [ + widget.user.photo == null + ? const Icon(DidvanIcons.avatar_light,size: 40,) + : SkeletonImage( + imageUrl: widget.user.photo.toString(), + height: 40, + width: 40, + borderRadius: BorderRadius.circular(360), + ), + const SizedBox( + width: 8, + ), + DidvanText( + widget.user.name.toString(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + const SizedBox( + height: 8, + ), + widget.index == state.usersMention.length - 1 + ? const SizedBox() + : Divider( + height: 2, + color: Theme.of(context).colorScheme.splash, + ) + ], + ), + ); + } +} diff --git a/pubspec.lock b/pubspec.lock index dbedf97..2654060 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,10 +5,10 @@ packages: dependency: transitive description: name: _flutterfire_internals - sha256: "4eec93681221723a686ad580c2e7d960e1017cf1a4e0a263c2573c2c6b0bf5cd" + sha256: "2350805d7afefb0efe7acd325cb19d3ae8ba4039b906eade3807ffb69938a01f" url: "https://pub.dev" source: hosted - version: "1.3.25" + version: "1.3.33" animated_custom_dropdown: dependency: "direct main" description: @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: args - sha256: eef6c46b622e0494a36c5a12d10d77fb4e855501a91c1b9ef9339326e58f0596 + sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a" url: "https://pub.dev" source: hosted - version: "2.4.2" + version: "2.5.0" assets_audio_player: dependency: "direct main" description: @@ -65,6 +65,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.2" + awesome_notifications: + dependency: "direct main" + description: + name: awesome_notifications + sha256: "65f730f9c0e73a346039ef746384bcff1773f9f03821b859705a7ab8db977b23" + url: "https://pub.dev" + source: hosted + version: "0.8.2" + awesome_notifications_core: + dependency: "direct main" + description: + name: awesome_notifications_core + sha256: "38c9f9d2618c0c2abe0cea267dc161aa8c4a0a239ca2263400900b9610f33e6f" + url: "https://pub.dev" + source: hosted + version: "0.9.3" better_player: dependency: "direct main" description: @@ -110,10 +126,10 @@ packages: dependency: transitive description: name: cached_network_image_web - sha256: "42a835caa27c220d1294311ac409a43361088625a4f23c820b006dd9bffb3316" + sha256: "205d6a9f1862de34b93184f22b9d2d94586b2f05c581d546695e3d8f6a805cd7" url: "https://pub.dev" source: hosted - version: "1.1.1" + version: "1.2.0" carousel_slider: dependency: "direct main" description: @@ -158,10 +174,10 @@ packages: dependency: transitive description: name: cross_file - sha256: fedaadfa3a6996f75211d835aaeb8fede285dae94262485698afd832371b9a5e + sha256: "55d7b444feb71301ef6b8838dbc1ae02e63dd48c8773f3810ff53bb1e2945b32" url: "https://pub.dev" source: hosted - version: "0.3.3+8" + version: "0.3.4+1" crypto: dependency: transitive description: @@ -182,10 +198,10 @@ packages: dependency: "direct main" description: name: cupertino_icons - sha256: d57953e10f9f8327ce64a508a355f0b1ec902193f66288e8cb5070e7c47eeb2d + sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6 url: "https://pub.dev" source: hosted - version: "1.0.6" + version: "1.0.8" day_night_time_picker: dependency: "direct main" description: @@ -194,6 +210,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.3.1" + dbus: + dependency: transitive + description: + name: dbus + sha256: "365c771ac3b0e58845f39ec6deebc76e3276aa9922b0cc60840712094d9047ac" + url: "https://pub.dev" + source: hosted + version: "0.7.10" equatable: dependency: transitive description: @@ -222,10 +246,10 @@ packages: dependency: transitive description: name: ffi - sha256: "7bf0adc28a23d395f19f3f1eb21dd7cfd1dd9f8e1c50051c069122e6853bc878" + sha256: "493f37e7df1804778ff3a53bd691d8692ddf69702cf4c1c1096a2e41b4779e21" url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" file: dependency: transitive description: @@ -246,10 +270,10 @@ packages: dependency: transitive description: name: file_selector_macos - sha256: b15c3da8bd4908b9918111fa486903f5808e388b8d1c559949f584725a6594d6 + sha256: f42eacb83b318e183b1ae24eead1373ab1334084404c8c16e0354f9a3e55d385 url: "https://pub.dev" source: hosted - version: "0.9.3+3" + version: "0.9.4" file_selector_platform_interface: dependency: transitive description: @@ -270,10 +294,10 @@ packages: dependency: "direct main" description: name: firebase_core - sha256: "53316975310c8af75a96e365f9fccb67d1c544ef0acdbf0d88bbe30eedd1c4f9" + sha256: "372d94ced114b9c40cb85e18c50ac94a7e998c8eec630c50d7aec047847d27bf" url: "https://pub.dev" source: hosted - version: "2.27.0" + version: "2.31.0" firebase_core_platform_interface: dependency: transitive description: @@ -286,34 +310,34 @@ packages: dependency: transitive description: name: firebase_core_web - sha256: c8e1d59385eee98de63c92f961d2a7062c5d9a65e7f45bdc7f1b0b205aab2492 + sha256: "43d9e951ac52b87ae9cc38ecdcca1e8fa7b52a1dd26a96085ba41ce5108db8e9" url: "https://pub.dev" source: hosted - version: "2.11.5" + version: "2.17.0" firebase_messaging: dependency: "direct main" description: name: firebase_messaging - sha256: e41586e0fd04fe9a40424f8b0053d0832e6d04f49e020cdaf9919209a28497e9 + sha256: e0882a7426821f7caccaabfc15a535155cd15b4daa73a5a7b3af701a552d73ab url: "https://pub.dev" source: hosted - version: "14.7.19" + version: "14.9.2" firebase_messaging_platform_interface: dependency: transitive description: name: firebase_messaging_platform_interface - sha256: f7a9d74ff7fc588a924f6b2eaeaa148b0db521b13a9db55f6ad45864fa98c06e + sha256: "52e12cc50e1395ad7ea3552dcbe9958fb1994b5afcf58ee4c0db053932a6fce5" url: "https://pub.dev" source: hosted - version: "4.5.27" + version: "4.5.35" firebase_messaging_web: dependency: transitive description: name: firebase_messaging_web - sha256: fc21e771166860c55b103701c5ac7cdb2eec28897b97c42e6e5703cbedf9e02e + sha256: "8812cc5929380b783f92290d934bf32e2fea06701583f47cdccd5f13f4f24522" url: "https://pub.dev" source: hosted - version: "3.6.8" + version: "3.8.5" fl_chart: dependency: "direct main" description: @@ -351,6 +375,30 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + flutter_local_notifications: + dependency: "direct main" + description: + name: flutter_local_notifications + sha256: "40e6fbd2da7dcc7ed78432c5cdab1559674b4af035fddbfb2f9a8f9c2112fcef" + url: "https://pub.dev" + source: hosted + version: "17.1.2" + flutter_local_notifications_linux: + dependency: transitive + description: + name: flutter_local_notifications_linux + sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" + url: "https://pub.dev" + source: hosted + version: "4.0.0+1" + flutter_local_notifications_platform_interface: + dependency: transitive + description: + name: flutter_local_notifications_platform_interface + sha256: "340abf67df238f7f0ef58f4a26d2a83e1ab74c77ab03cd2b2d5018ac64db30b7" + url: "https://pub.dev" + source: hosted + version: "7.1.0" flutter_localizations: dependency: "direct main" description: flutter @@ -360,10 +408,10 @@ packages: dependency: transitive description: name: flutter_plugin_android_lifecycle - sha256: b068ffc46f82a55844acfa4fdbb61fad72fa2aef0905548419d97f0f95c456da + sha256: "8cf40eebf5dec866a6d1956ad7b4f7016e6c0cc69847ab946833b7d43743809f" url: "https://pub.dev" source: hosted - version: "2.0.17" + version: "2.0.19" flutter_secure_storage: dependency: "direct main" description: @@ -376,34 +424,34 @@ packages: dependency: transitive description: name: flutter_secure_storage_linux - sha256: "3d5032e314774ee0e1a7d0a9f5e2793486f0dff2dd9ef5a23f4e3fb2a0ae6a9e" + sha256: "4d91bfc23047422cbcd73ac684bc169859ee766482517c22172c86596bf1464b" url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.1" flutter_secure_storage_macos: dependency: transitive description: name: flutter_secure_storage_macos - sha256: bd33935b4b628abd0b86c8ca20655c5b36275c3a3f5194769a7b3f37c905369c + sha256: "8cfa53010a294ff095d7be8fa5bb15f2252c50018d69c5104851303f3ff92510" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "3.1.0" flutter_secure_storage_platform_interface: dependency: transitive description: name: flutter_secure_storage_platform_interface - sha256: "0d4d3a5dd4db28c96ae414d7ba3b8422fd735a8255642774803b2532c9a61d7e" + sha256: "301f67ee9b87f04aef227f57f13f126fa7b13543c8e7a93f25c5d2d534c28a4a" url: "https://pub.dev" source: hosted - version: "1.0.2" + version: "1.1.1" flutter_secure_storage_web: dependency: transitive description: name: flutter_secure_storage_web - sha256: "30f84f102df9dcdaa2241866a958c2ec976902ebdaa8883fbfe525f1f2f3cf20" + sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9 url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.1" flutter_secure_storage_windows: dependency: transitive description: @@ -424,10 +472,10 @@ packages: dependency: "direct main" description: name: flutter_svg - sha256: d39e7f95621fc84376bc0f7d504f05c3a41488c562f4a8ad410569127507402c + sha256: "7b4ca6cf3304575fe9c8ec64813c8d02ee41d2afe60bcfe0678bcb5375d596a2" url: "https://pub.dev" source: hosted - version: "2.0.9" + version: "2.0.10+1" flutter_test: dependency: "direct dev" description: flutter @@ -482,10 +530,10 @@ packages: dependency: "direct main" description: name: http - sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" + sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" url: "https://pub.dev" source: hosted - version: "0.13.6" + version: "1.2.1" http_parser: dependency: "direct main" description: @@ -506,34 +554,34 @@ packages: dependency: "direct main" description: name: image_picker - sha256: "26222b01a0c9a2c8fe02fc90b8208bd3325da5ed1f4a2acabf75939031ac0bdd" + sha256: "33974eca2e87e8b4e3727f1b94fa3abcb25afe80b6bc2c4d449a0e150aedf720" url: "https://pub.dev" source: hosted - version: "1.0.7" + version: "1.1.1" image_picker_android: dependency: transitive description: name: image_picker_android - sha256: "42c098e7fb6334746be37cdc30369ade356ed4f14d48b7a0313f95a9159f4321" + sha256: "79455f6cff4cbef583b2b524bbf0d4ec424e5959f4d464e36ef5323715b98370" url: "https://pub.dev" source: hosted - version: "0.8.9+5" + version: "0.8.12" image_picker_for_web: dependency: transitive description: name: image_picker_for_web - sha256: e2423c53a68b579a7c37a1eda967b8ae536c3d98518e5db95ca1fe5719a730a3 + sha256: "5d6eb13048cd47b60dbf1a5495424dea226c5faf3950e20bf8120a58efb5b5f3" url: "https://pub.dev" source: hosted - version: "3.0.2" + version: "3.0.4" image_picker_ios: dependency: transitive description: name: image_picker_ios - sha256: "917a5cadd67d052554cfb258595e54217de53fac5b52939426e26319a02e6297" + sha256: cb0db0ec0d3e2cd49674f2e6053be25ccdb959832607c1cbd215dd6cf10fb0dd url: "https://pub.dev" source: hosted - version: "0.8.9+2" + version: "0.8.11" image_picker_linux: dependency: transitive description: @@ -554,10 +602,10 @@ packages: dependency: transitive description: name: image_picker_platform_interface - sha256: fa4e815e6fcada50e35718727d83ba1c92f1edf95c0b4436554cec301b56233b + sha256: "9ec26d410ff46f483c5519c29c02ef0e02e13a543f882b152d4bfd2f06802f80" url: "https://pub.dev" source: hosted - version: "2.9.3" + version: "2.10.0" image_picker_windows: dependency: transitive description: @@ -690,26 +738,26 @@ packages: dependency: "direct main" description: name: path_provider - sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b + sha256: c9e7d3a4cd1410877472158bee69963a4579f78b68c65a2b7d40d1a7a88bb161 url: "https://pub.dev" source: hosted - version: "2.1.2" + version: "2.1.3" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "477184d672607c0a3bf68fbbf601805f92ef79c82b64b4d6eb318cbca4c48668" + sha256: a248d8146ee5983446bf03ed5ea8f6533129a12b11f12057ad1b4a67a2b3b41d url: "https://pub.dev" source: hosted - version: "2.2.2" + version: "2.2.4" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "5a7999be66e000916500be4f15a3633ebceb8302719b47b9cc49ce924125350f" + sha256: f234384a3fdd67f989b4d54a5d73ca2a6c422fa55ae694381ae0f4375cd1ea16 url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.4.0" path_provider_linux: dependency: transitive description: @@ -746,10 +794,10 @@ packages: dependency: transitive description: name: permission_handler_android - sha256: "1acac6bae58144b442f11e66621c062aead9c99841093c38f5bcdcc24c1c3474" + sha256: "8bb852cd759488893805c3161d0b2b5db55db52f773dbb014420b304055ba2c5" url: "https://pub.dev" source: hosted - version: "12.0.5" + version: "12.0.6" permission_handler_apple: dependency: transitive description: @@ -935,18 +983,18 @@ packages: dependency: transitive description: name: sqflite - sha256: a9016f495c927cb90557c909ff26a6d92d9bd54fc42ba92e19d4e79d61e798c6 + sha256: a43e5a27235518c03ca238e7b4732cf35eabe863a369ceba6cbefa537a66f16d url: "https://pub.dev" source: hosted - version: "2.3.2" + version: "2.3.3+1" sqflite_common: dependency: transitive description: name: sqflite_common - sha256: "28d8c66baee4968519fb8bd6cdbedad982d6e53359091f0b74544a9f32ec72d5" + sha256: "3da423ce7baf868be70e2c0976c28a1bb2f73644268b7ffa7d2e08eab71f16a4" url: "https://pub.dev" source: hosted - version: "2.5.3" + version: "2.5.4" stack_trace: dependency: transitive description: @@ -995,6 +1043,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.6.1" + timezone: + dependency: transitive + description: + name: timezone + sha256: a6ccda4a69a442098b602c44e61a1e2b4bf6f5516e875bbf0f427d5df14745d5 + url: "https://pub.dev" + source: hosted + version: "0.9.3" toggle_switch: dependency: "direct main" description: @@ -1031,26 +1087,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: "0ecc004c62fd3ed36a2ffcbe0dd9700aee63bd7532d0b642a488b1ec310f492e" + sha256: "6ce1e04375be4eed30548f10a315826fd933c1e493206eab82eed01f438c8d2e" url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.2.6" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: d4ed0711849dd8e33eb2dd69c25db0d0d3fdc37e0a62e629fe32f57a22db2745 + sha256: "360a6ed2027f18b73c8d98e159dda67a61b7f2e0f6ec26e86c3ada33b0621775" url: "https://pub.dev" source: hosted - version: "6.3.0" + version: "6.3.1" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "9149d493b075ed740901f3ee844a38a00b33116c7c5c10d7fb27df8987fb51d5" + sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89" url: "https://pub.dev" source: hosted - version: "6.2.5" + version: "6.3.0" url_launcher_linux: dependency: transitive description: @@ -1063,10 +1119,10 @@ packages: dependency: transitive description: name: url_launcher_macos - sha256: b7244901ea3cf489c5335bdacda07264a6e960b1c1b1a9f91e4bc371d9e68234 + sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de" url: "https://pub.dev" source: hosted - version: "3.1.0" + version: "3.2.0" url_launcher_platform_interface: dependency: transitive description: @@ -1079,10 +1135,10 @@ packages: dependency: transitive description: name: url_launcher_web - sha256: fff0932192afeedf63cdd50ecbb1bc825d31aed259f02bb8dba0f3b729a5e88b + sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.1" url_launcher_windows: dependency: transitive description: @@ -1103,26 +1159,26 @@ packages: dependency: transitive description: name: vector_graphics - sha256: "4ac59808bbfca6da38c99f415ff2d3a5d7ca0a6b4809c71d9cf30fba5daf9752" + sha256: "32c3c684e02f9bc0afb0ae0aa653337a2fe022e8ab064bcd7ffda27a74e288e3" url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.11+1" vector_graphics_codec: dependency: transitive description: name: vector_graphics_codec - sha256: f3247e7ab0ec77dc759263e68394990edc608fb2b480b80db8aa86ed09279e33 + sha256: c86987475f162fadff579e7320c7ddda04cd2fdeffbe1129227a85d9ac9e03da url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.11+1" vector_graphics_compiler: dependency: transitive description: name: vector_graphics_compiler - sha256: "18489bdd8850de3dd7ca8a34e0c446f719ec63e2bab2e7a8cc66a9028dd76c5a" + sha256: "12faff3f73b1741a36ca7e31b292ddeb629af819ca9efe9953b70bd63fc8cd81" url: "https://pub.dev" source: hosted - version: "1.1.10+1" + version: "1.1.11+1" vector_math: dependency: transitive description: @@ -1191,10 +1247,10 @@ packages: dependency: transitive description: name: web - sha256: "4188706108906f002b3a293509234588823c8c979dc83304e229ff400c996b05" + sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" url: "https://pub.dev" source: hosted - version: "0.4.2" + version: "0.5.1" webview_flutter: dependency: "direct main" description: @@ -1207,10 +1263,10 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: f038ee2fae73b509dde1bc9d2c5a50ca92054282de17631a9a3d515883740934 + sha256: dad3313c9ead95517bb1cae5e1c9d20ba83729d5a59e5e83c0a2d66203f27f91 url: "https://pub.dev" source: hosted - version: "3.16.0" + version: "3.16.1" webview_flutter_platform_interface: dependency: transitive description: @@ -1252,5 +1308,5 @@ packages: source: hosted version: "6.5.0" sdks: - dart: ">=3.2.3 <4.0.0" - flutter: ">=3.16.6" + dart: ">=3.3.0 <4.0.0" + flutter: ">=3.19.0" diff --git a/pubspec.yaml b/pubspec.yaml index 3fdf8a1..f11cb7f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -78,6 +78,10 @@ dependencies: http: any http_parser: any cached_network_image_platform_interface: any + flutter_local_notifications: ^17.1.2 + awesome_notifications_core: ^0.9.0 + awesome_notifications: any + dev_dependencies: flutter_test: sdk: flutter