22-02-1403 / Rhmn / changes: notification Fix & Mentions Create no.1
This commit is contained in:
parent
562c006eb6
commit
2d00d239be
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
12
ios/Podfile
12
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|
|
||||
|
|
|
|||
|
|
@ -50,5 +50,9 @@
|
|||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UNNotificationServiceExtension</key>
|
||||
<array>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).MyNotificationServiceExtension</string>
|
||||
</array>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
|||
|
|
@ -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 [
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {};
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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--------------------------------------------------------------");
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
@ -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();
|
||||
// }
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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, ),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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,
|
||||
)
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
220
pubspec.lock
220
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"
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Reference in New Issue