22-02-1403 / Rhmn / changes: notification Fix & Mentions Create no.1

This commit is contained in:
OkaykOrhmn 2024-05-11 17:41:44 +03:30
parent 562c006eb6
commit 2d00d239be
24 changed files with 1306 additions and 188 deletions

View File

@ -4,20 +4,26 @@
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.USE_FULL_SCREEN_INTENT" />
<application
android:label="Didvan"
android:icon="@mipmap/ic_launcher"
android:usesCleartextTraffic="true"
android:requestLegacyExternalStorage="true">
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:showOnLockScreen="true"
android:showWhenLocked="true"
android:turnScreenOn="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
@ -30,6 +36,7 @@
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
@ -44,6 +51,16 @@
android:name="com.yalantis.ucrop.UCropActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
<service
android:name=".MyFirebaseMessagingService"
android:exported="true">
<intent-filter>
<action android:name="com.google.firebase.messaging.RECEIVE" />
</intent-filter>
</service>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
</application>

View File

@ -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

View File

@ -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|

View File

@ -50,5 +50,9 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UNNotificationServiceExtension</key>
<array>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).MyNotificationServiceExtension</string>
</array>
</dict>
</plist>

View File

@ -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<void> _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<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
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 [

View File

@ -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<Reply> 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<String, dynamic> json) => CommentData(
factory CommentData.fromJson(Map<String, dynamic> 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<String, dynamic> 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(),

View File

@ -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<String, dynamic> 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<String, dynamic> toJson() => {
@ -45,5 +48,6 @@ class Reply {
'feedback': feedback.toJson(),
'user': user.toJson(),
'toUser': toUser.toJson(),
'mention': mention,
};
}

View File

@ -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<String, dynamic> json) {
title = json['title'];
body = json['body'];
type = json['type'];
clickAction = json['click_action'];
url = json['url'];
image = json['image'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
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;
}
}

View File

@ -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<String, dynamic> json) {
id = json['id'];
name = json['name'];
type = json['type'];
photo = json['photo'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['id'] = this.id;
data['name'] = this.name;
data['type'] = this.type;
data['photo'] = this.photo;
return data;
}
}

View File

@ -11,6 +11,9 @@ class RequestService {
static late String token;
int? statusCode;
dynamic data(String s){
return _body?[s] ?? const {};
}
Map<String, dynamic> get result => _body?['result'] ?? const {};
Map<String, dynamic> get errors => _body?['errors'] ?? const {};

View File

@ -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<MapEntry<String, dynamic>> additions) {

View File

@ -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 <void> 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 <void> 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 <void> 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 <void> 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--------------------------------------------------------------");
}
}

View File

@ -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
}));
}
}

View File

@ -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<void> 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<void> _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<void>.value();
// }

View File

@ -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<void> 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<String> _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<void> 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 <b>big</b> content title',
htmlFormatContentTitle: true,
summaryText: 'summary <i>text</i>',
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);
}
}

View File

@ -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');
}

View File

@ -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<String, dynamic> pageData;
const Comments({
Key? key,
required this.pageData,
@ -47,6 +58,8 @@ class _CommentsState extends State<Comments> {
@override
Widget build(BuildContext context) {
final commentsState = context.watch<CommentsState>();
final bottomViewInset = MediaQuery.of(context).viewInsets.bottom;
if (bottomViewInset == 0) {
if (_bottomPadding != 0) {
@ -97,6 +110,48 @@ class _CommentsState extends State<Comments> {
),
],
),
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<CommentsState>(
builder: (context, state, child) =>
SliverStateHandler<CommentsState>(
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<Comments> {
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<CommentsState>();
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,
),
),
],
);
}
}

View File

@ -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<CommentData> comments = [];
final List<UsersMention> usersMention = [];
final Map<int, MapEntry<bool, bool>> _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<void> getUsersMention() async {
final service = RequestService(
RequestHelper.usersMentions(mentionedText.replaceAll("@", "")),
);
await service.httpGet();
if (service.isSuccess) {
usersMention.clear();
final List<dynamic> users = service.data('users');
usersMention
.addAll(users.map((users) => UsersMention.fromJson(users)).toList());
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> 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();
}
}

View File

@ -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> {
),
],
),
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,

View File

@ -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<DidvanCheckbox> {
@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<DidvanCheckbox> {
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, ),
],
),
),

View File

@ -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<NotificationDynamicDialog> {
@override
Widget build(BuildContext context) {
// You can change the UI as per
// your requirement or choice
return AlertDialog(
title: Text(widget.title),
actions: <Widget>[
OutlinedButton.icon(
label: const Text('Close'),
onPressed: () {
Navigator.pop(context);
},
icon: const Icon(Icons.close))
],
content: Text(widget.body),
);
}
}

View File

@ -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<UserMention> createState() => _UserMentionState();
}
class _UserMentionState extends State<UserMention> {
@override
Widget build(BuildContext context) {
final state = context.watch<CommentsState>();
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,
)
],
),
);
}
}

View File

@ -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"

View File

@ -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