Compare commits

..

3 Commits

Author SHA1 Message Date
Syed Taha Alam 75b898ce80 Wd: read phone state permission is removed 5 months ago
Syed Taha Alam ce4568cf70 Wd: a scenario is occcuring when app is getting a call over a next call then object is getting retained so removed the object 5 months ago
Syed Taha Alam 5c75a186ed Wd: Incoming page and notification added to the application.
* changed the firebase package.
    * added flutter_callkit_incoming
    * added android.permission.FOREGROUND_SERVICE_PHONE_CALL
    * saved the caller object inside the shared preference for the routing
    * changed the incoming call function.
5 months ago

@ -31,8 +31,7 @@
android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"
tools:node="remove" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL"
tools:node="remove" />
android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission
android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"
tools:node="remove" />
@ -84,6 +83,9 @@
<uses-permission android:name="android.permission.READ_CALENDAR" />
<uses-permission android:name="android.permission.WRITE_CALENDAR" />
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.VIBRATE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-feature
android:name="android.hardware.location.network"
@ -97,6 +99,8 @@
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" /> <!-- <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/> -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_PHONE_CALL" />
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<!-- Detect Reboot Permission -->
<!-- <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/> -->
<queries>

@ -21,8 +21,8 @@ var PACKAGES_ORDERS = '/api/orders';
var PACKAGES_ORDER_HISTORY = '/api/orders/items';
var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara';
// var BASE_URL = 'http://10.50.100.198:2018/';
// var BASE_URL = 'https://uat.hmgwebservices.com/';
var BASE_URL = 'https://hmgwebservices.com/';
var BASE_URL = 'https://uat.hmgwebservices.com/';
// var BASE_URL = 'https://hmgwebservices.com/';
// var BASE_URL = 'http://10.201.204.103/';
// var BASE_URL = 'https://orash.cloudsolutions.com.sa/';
// var BASE_URL = 'https://vidauat.cloudsolutions.com.sa/';
@ -701,10 +701,10 @@ var IS_DOCTOR_AVAILABLE_BY_CALENDAR_SCHEDULE = 'Services/OUTPs.svc/REST/HIS_IsDo
//PAYFORT
var getPayFortProjectDetails = "Services/PayFort_Serv.svc/REST/GetPayFortProjectDetails";
var addPayFortApplePayResponse = "Services/PayFort_Serv.svc/REST/AddResponse";
var payFortEnvironment = FortEnvironment.production;
var applePayMerchantId = "merchant.com.hmgwebservices";
// var payFortEnvironment = FortEnvironment.test;
// var applePayMerchantId = "merchant.com.hmgwebservices.uat";
// var payFortEnvironment = FortEnvironment.production;
// var applePayMerchantId = "merchant.com.hmgwebservices";
var payFortEnvironment = FortEnvironment.test;
var applePayMerchantId = "merchant.com.hmgwebservices.uat";
class AppGlobal {
static var context;

@ -44,7 +44,6 @@ class FinalRadiology {
bool? isCVI;
bool? isRadMedicalReport;
bool? isLiveCareAppointment;
String? exam_Id;
FinalRadiology(
{this.setupID,
@ -89,8 +88,7 @@ class FinalRadiology {
this.speciality,
this.isCVI,
this.isRadMedicalReport,
this.isLiveCareAppointment,
this.exam_Id});
this.isLiveCareAppointment});
FinalRadiology.fromJson(Map<String, dynamic> json) {
try {
@ -137,7 +135,6 @@ class FinalRadiology {
// speciality = json['Speciality'].cast<String>();
isCVI = json['isCVI'];
isRadMedicalReport = json['isRadMedicalReport'];
exam_Id = json['Exam_id'];
} catch (e) {
print(e);
}

@ -55,7 +55,7 @@ class MedicalService extends BaseService {
getSchedule(DoctorList doctorRequest) async {
Map<String, dynamic> request;
request = {'DoctorID': doctorRequest.doctorID, 'ProjectID': doctorRequest.projectID, 'ClinicID': doctorRequest.clinicID, 'DoctorWorkingHoursDays': 90};
request = {'DoctorID': doctorRequest.doctorID, 'ProjectID': doctorRequest.projectID, 'ClinicID': doctorRequest.clinicID, 'DoctorWorkingHoursDays': 7};
dynamic localRes;
await baseAppClient.post(DOCTOR_SCHEDULE_URL, onSuccess: (response, statusCode) async {
localRes = response;
@ -68,7 +68,7 @@ class MedicalService extends BaseService {
getFreeSlot(DoctorList doctorRequest) async {
Map<String, dynamic> request;
request = {'DoctorID': doctorRequest.doctorID, 'ProjectID': doctorRequest.projectID, 'ClinicID': doctorRequest.clinicID, 'DoctorWorkingHoursDays': 90};
request = {'DoctorID': doctorRequest.doctorID, 'ProjectID': doctorRequest.projectID, 'ClinicID': doctorRequest.clinicID, 'DoctorWorkingHoursDays': 7};
dynamic localRes;
await baseAppClient.post(GET_DOCTOR_FREE_SLOTS, onSuccess: (response, statusCode) async {
localRes = response;

@ -12,7 +12,7 @@ class RadiologyService extends BaseService {
String radReportPDF = "";
Future getRadImageURL({int? invoiceNo, String? invoiceType, int? lineItem, int? projectId, bool? isVidaPlus, String? examId}) async {
Future getRadImageURL({int? invoiceNo, String? invoiceType, int? lineItem, int? projectId, bool? isVidaPlus}) async {
hasError = false;
final Map<String, dynamic> body = new Map<String, dynamic>();
body['InvoiceNo'] = isVidaPlus! ? "0" : invoiceNo;
@ -20,7 +20,6 @@ class RadiologyService extends BaseService {
body['LineItemNo'] = lineItem;
body['ProjectID'] = projectId;
body['InvoiceType'] = invoiceType;
body['ExamId'] = examId;
await baseAppClient.post(GET_RAD_IMAGE_URL, isAllowAny: true, onSuccess: (dynamic response, int statusCode) {
url = response['Data'];

@ -56,9 +56,9 @@ class RadiologyViewModel extends BaseViewModel {
String get radImageURL => _radiologyService.url;
getRadImageURL({required int invoiceNo, String? invoiceType, required int lineItem, required int projectId, required bool isVidaPlus, required String examId}) async {
getRadImageURL({required int invoiceNo, String? invoiceType, required int lineItem, required int projectId, required bool isVidaPlus}) async {
setState(ViewState.Busy);
await _radiologyService.getRadImageURL(invoiceNo: invoiceNo, invoiceType: invoiceType, lineItem: lineItem, projectId: projectId, isVidaPlus: isVidaPlus, examId: examId);
await _radiologyService.getRadImageURL(invoiceNo: invoiceNo, invoiceType: invoiceType, lineItem: lineItem, projectId: projectId, isVidaPlus: isVidaPlus);
if (_radiologyService.hasError) {
error = _radiologyService.error!;
setState(ViewState.Error);

@ -151,8 +151,13 @@ import 'services/pharmacy_services/product_detail_service.dart';
GetIt locator = GetIt.instance;
void setupNavigator(){
locator.registerSingleton( NavigationService());
}
///di
void setupLocator() {
setupNavigator();
/// Others
locator.registerLazySingleton(() => FlutterLocalNotificationsPlugin());
@ -168,7 +173,6 @@ void setupLocator() {
locator.registerLazySingleton(() => InsuranceCardService());
locator.registerLazySingleton(() => VitalSignService());
locator.registerLazySingleton(() => MedicalService());
locator.registerLazySingleton(() => NavigationService());
locator.registerLazySingleton(() => ReportsService());
locator.registerLazySingleton(() => DashboardService());
locator.registerLazySingleton(() => AppointmentRateService());

@ -6,6 +6,10 @@ import 'package:diplomaticquarterapp/config/config.dart';
import 'package:diplomaticquarterapp/core/viewModels/PharmacyPagesViewModel.dart';
import 'package:diplomaticquarterapp/core/viewModels/dashboard_view_model.dart';
import 'package:diplomaticquarterapp/models/Appointments/toDoCountProviderModel.dart';
import 'package:diplomaticquarterapp/pages/conference/widgets/platform_exception_alert_dialog.dart';
import 'package:diplomaticquarterapp/pages/conference/zoom/call_screen.dart';
import 'package:diplomaticquarterapp/pages/landing/landing_page.dart';
import 'package:diplomaticquarterapp/pages/webRTC/OpenTok/OpenTok.dart';
import 'package:diplomaticquarterapp/routes.dart';
import 'package:diplomaticquarterapp/services/payfort_services/payfort_view_model.dart';
import 'package:diplomaticquarterapp/services/robo_search/event_provider.dart';
@ -14,6 +18,7 @@ import 'package:diplomaticquarterapp/theme/theme_notifier.dart';
import 'package:diplomaticquarterapp/theme/theme_value.dart';
import 'package:diplomaticquarterapp/uitl/DeepLinkHandler.dart';
import 'package:diplomaticquarterapp/uitl/PlatformBridge.dart';
import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart';
import 'package:diplomaticquarterapp/uitl/navigation_service.dart';
import 'package:diplomaticquarterapp/uitl/translations_delegate_base.dart';
import 'package:firebase_core/firebase_core.dart';
@ -25,6 +30,7 @@ import 'config/size_config.dart';
import 'core/viewModels/pharmacyModule/OrderPreviewViewModel.dart';
import 'core/viewModels/project_view_model.dart';
import 'locator.dart';
import 'models/LiveCare/IncomingCallData.dart';
import 'pages/pharmacies/compare-list.dart';
void main() async {
@ -102,13 +108,67 @@ class _MyApp extends State<MyApp> {
super.dispose();
}
late final AppLifecycleListener _listener;
Future<void> initDeepLinks() async {
// Handle links
_linkSubscription = AppLinks().uriLinkStream.listen((uri) {
debugPrint('onAppLink: $uri');
openAppLink(uri);
});
_listener = AppLifecycleListener(
onShow: () => {},
onResume: () =>{},
onHide: () => {},
onInactive: () => {},
onPause: () => {},
onDetach: () => {},
onRestart: () => {},
onStateChange: (state) async {
if(state == AppLifecycleState.resumed) {
await Future.delayed(Duration(seconds: 5));
_handleTransition();
}
},
);
_handleTransition(shouldApplyDelay: true);
}
void _handleTransition({bool shouldApplyDelay = false}) async {
var data = await AppSharedPreferences().getObject('call_data');
if(data == null) return;
await AppSharedPreferences().remove('call_data');
if(shouldApplyDelay)
await Future.delayed(Duration(seconds: 5));
var incomingcallData= IncomingCallData.fromJson(data);
openCallPage(incomingcallData);
}
Future<void> openCallPage(IncomingCallData incomingCallData) async {
try {
LandingPage.isOpenCallPage = false;
if (incomingCallData!.background == "0") {
// Zoom Call Page
// Navigator.of(context).pop();
Navigator.pushNamed(
locator<NavigationService>().navigatorKey.currentContext!,
"zoom_call_page",
arguments: CallArguments(incomingCallData!.sessionId!, "123", "Patient", "40", "0", false, int.parse(incomingCallData!.appointmentNo!)),
);
} else {
// OpenTok Call Page
await Navigator.of(locator<NavigationService>().navigatorKey.currentContext!).push(
MaterialPageRoute(
builder: (BuildContext context) {
return OpenTokConnectCallPage(apiKey: OPENTOK_API_KEY, sessionId: incomingCallData!.sessionId!, token: incomingCallData!.token!);
},
),
);
}
} catch (err) {
print(err);
await PlatformExceptionAlertDialog(
exception: Exception(err),
).show(context);
}
}
void openAppLink(Uri uri) {

@ -1,6 +1,7 @@
import 'dart:async';
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:diplomaticquarterapp/config/config.dart';
import 'package:diplomaticquarterapp/config/shared_pref_kay.dart';
import 'package:diplomaticquarterapp/core/model/ImagesInfo.dart';
@ -37,6 +38,7 @@ import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart';
import 'package:diplomaticquarterapp/widgets/others/not_auh_page.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
// import 'package:flutter_app_icon_badge/flutter_app_icon_badge.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_svg/flutter_svg.dart';
@ -647,6 +649,15 @@ class _LandingPageState extends State<LandingPage> with WidgetsBindingObserver {
void checkUserStatus(token) async {
await PushNotificationHandler.getInstance().requestPermissions();
if (Platform.isAndroid) {
final deviceInfo = DeviceInfoPlugin();
final androidInfo = await deviceInfo.androidInfo;
int sdkInt = androidInfo.version.sdkInt ?? 0;
if(sdkInt>= 33){
await FlutterCallkitIncoming.requestFullIntentPermission();
}
}
// authService.selectDeviceImei(token).then((SelectDeviceIMEIRES? value) => setUserValues(value));
if (authenticatedUserObject.isLogin) {
var data = AuthenticatedUser.fromJson(await sharedPref.getObject(USER_PROFILE));

@ -99,7 +99,7 @@ class _ConfirmLogin extends State<ConfirmLogin> {
late ToDoCountProviderModel toDoProvider;
var dob;
int isHijri = 0;
late int isHijri;
var healthId;
@override

@ -33,8 +33,7 @@ class RadiologyDetailsPage extends StatelessWidget {
invoiceType: finalRadiology!.invoiceType,
lineItem: finalRadiology!.invoiceLineItemNo!,
invoiceNo: Utils.isVidaPlusProject(projectViewModel, finalRadiology!.projectID!) ? finalRadiology!.invoiceNo_VP : finalRadiology!.invoiceNo,
isVidaPlus: Utils.isVidaPlusProject(projectViewModel, finalRadiology!.projectID!),
examId: finalRadiology!.exam_Id!),
isVidaPlus: Utils.isVidaPlusProject(projectViewModel, finalRadiology!.projectID!)),
builder: (_, model, widget) => AppScaffold(
appBarTitle: TranslationBase.of(context).report,
isShowAppBar: true,

@ -81,9 +81,11 @@ class _SplashScreenState extends State<SplashScreen> {
projectProvider.setProjectsDetailList(_privilegeService.projectDetailListModel);
double lat = await AppSharedPreferences().getDouble(USER_LAT) ?? 0.0;
double long = await AppSharedPreferences().getDouble(USER_LONG) ?? 0.0;
var callData = await AppSharedPreferences().getObject('call_data');
AppSharedPreferences().clear(); // Clearing Shared Preferences On App Launch
await AppSharedPreferences().setDouble(USER_LAT, lat);
await AppSharedPreferences().setDouble(USER_LONG, long);
await AppSharedPreferences().setObject('call_data', callData);
AppSharedPreferences().setString(APP_LANGUAGE, projectProvider.isArabic ? "ar" : "en");
var themeNotifier = Provider.of<ThemeNotifier>(context, listen: false);
themeNotifier.setTheme(defaultTheme(fontName: projectProvider.isArabic ? 'Cairo' : 'Poppins'));

@ -1,7 +1,8 @@
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:flutter_callkit_incoming/entities/call_event.dart';
import 'package:uuid/uuid.dart';
import 'package:diplomaticquarterapp/config/config.dart';
import 'package:diplomaticquarterapp/config/shared_pref_kay.dart';
import 'package:diplomaticquarterapp/core/model/notifications/get_notifications_response_model.dart';
@ -22,6 +23,11 @@ import 'package:firebase_messaging/firebase_messaging.dart' as fir;
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/entities/android_params.dart';
import 'package:flutter_callkit_incoming/entities/call_kit_params.dart';
import 'package:flutter_callkit_incoming/entities/ios_params.dart';
import 'package:flutter_callkit_incoming/entities/notification_params.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
// import 'package:flutter_ios_voip_kit/call_state_type.dart';
// import 'package:flutter_ios_voip_kit/flutter_ios_voip_kit.dart';
@ -31,6 +37,7 @@ import 'package:huawei_hmsavailability/huawei_hmsavailability.dart';
import 'package:huawei_push/huawei_push.dart' as h_push;
import 'package:permission_handler/permission_handler.dart';
import '../pages/conference/widgets/platform_exception_alert_dialog.dart';
import 'app_shared_preferences.dart';
import 'navigation_service.dart';
@ -44,7 +51,6 @@ Future<dynamic> backgroundMessageHandler(dynamic message) async {
message_ = toFirebaseRemoteMessage(message);
h_push.Push.localNotification({h_push.HMSLocalNotificationAttr.TITLE: 'Background Message', h_push.HMSLocalNotificationAttr.MESSAGE: "By: BackgroundMessageHandler"});
}
if (message.data != null && (message.data['is_call'] == 'true' || message.data['is_call'] == true)) {
// showCallkitIncoming(message);
_incomingCall(message.data);
@ -162,18 +168,162 @@ callPage(String sessionID, String token) async {
_incomingCall(Map<String, dynamic> data) async {
LandingPage.incomingCallData = IncomingCallData.fromJson(data);
var dataItem = await AppSharedPreferences().getObject('call_data');
if(dataItem != null ) return; // to stop repeated attempt to invoke the call
if (LandingPage.isOpenCallPage == false) {
LandingPage.isOpenCallPage = true;
final bool permited = await AppPermission.askVideoCallPermission(currentContext!);
if (permited)
await NavigationService.navigateToPage(
IncomingCall(incomingCallData: LandingPage.incomingCallData),
WidgetsFlutterBinding.ensureInitialized();
var _currentUuid = Uuid().v4();
CallKitParams callKitParams = CallKitParams(
id: _currentUuid,
nameCaller: "${LandingPage.incomingCallData.doctorname}"
"\n Dr Sulaiman Al Habib \n ${LandingPage.incomingCallData.clinicname}",
appName: 'Dr Sulaiman Al Habib',
avatar: '',
handle: LandingPage.incomingCallData.name,
type: 0,
textAccept: 'Accept',
textDecline: 'Decline',
missedCallNotification: NotificationParams(
showNotification: true,
isShowCallback: false,
subtitle: 'Missed call',
callbackText: '',
),
callingNotification: const NotificationParams(
showNotification: true,
isShowCallback: true,
subtitle: 'Calling...',
callbackText: 'Hang Up',
),
duration: 30000,
extra: <String, dynamic>{'userId': '1a2b3c4d'},
headers: <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'},
android: const AndroidParams(
isCustomNotification: true,
isShowLogo: false,
logoUrl: '',
ringtonePath: 'system_ringtone_default',
backgroundColor: '#D02127',
backgroundUrl: '',
actionColor: '#4CAF50',
textColor: '#ffffff',
incomingCallNotificationChannelName: "Incoming Call",
missedCallNotificationChannelName: "Missed Call",
isShowCallID: false
),
ios: IOSParams(
iconName: 'Dr Sulaiman Al Habib',
handleType: 'generic',
supportsVideo: true,
maximumCallGroups: 2,
maximumCallsPerCallGroup: 1,
audioSessionMode: 'default',
audioSessionActive: true,
audioSessionPreferredSampleRate: 44100.0,
audioSessionPreferredIOBufferDuration: 0.005,
supportsDTMF: true,
supportsHolding: true,
supportsGrouping: false,
supportsUngrouping: false,
ringtonePath: 'system_ringtone_default',
),
);
await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);
try {
FlutterCallkitIncoming.onEvent.listen((event) async {
switch (event!.event) {
case Event.actionCallIncoming:
LandingPage.isOpenCallPage = false;
break;
case Event.actionCallStart:
break;
case Event.actionCallAccept:
await FlutterCallkitIncoming.endAllCalls();
try {
print("going to open the screen");
openCallPage(LandingPage.incomingCallData,
locator<NavigationService>().navigatorKey
.currentContext!);
await AppSharedPreferences().remove('call_data');
} catch (e) {
print("there is a exception $e");
await AppSharedPreferences().setObject('call_data', data);
}
break;
case Event.actionCallDecline:
break;
case Event.actionCallEnded:
break;
case Event.actionCallTimeout:
break;
case Event.actionCallCallback:
break;
case Event.actionCallToggleHold:
break;
case Event.actionCallToggleMute:
break;
case Event.actionCallToggleDmtf:
break;
case Event.actionCallToggleGroup:
break;
case Event.actionCallToggleAudioSession:
break;
case Event.actionDidUpdateDevicePushTokenVoip:
break;
case Event.actionCallCustom:
break;
}
});
} on Exception catch (e) {
print(e);
}
}
LandingPage.isOpenCallPage = false;
await Future.delayed(Duration(milliseconds: 500));
await AppSharedPreferences().remove('call_data');
}
Future<void> openCallPage(IncomingCallData incomingCallData, BuildContext context) async {
try {
LandingPage.isOpenCallPage = false;
if (incomingCallData!.background == "0") {
// Zoom Call Page
// Navigator.of(context).pop();
Navigator.pushReplacementNamed(
context,
"zoom_call_page",
arguments: CallArguments(incomingCallData!.sessionId!, "123", "Patient", "40", "0", false, int.parse(incomingCallData!.appointmentNo!)),
);
} else {
// OpenTok Call Page
await Navigator.of(context).pushReplacement(
MaterialPageRoute(
// fullscreenDialog: true,
builder: (BuildContext context) {
// final caller = widget.incomingCallData.callerID;
// final receiver = widget.incomingCallData.receiverID;
// final host = widget.incomingCallData.server;
// if(widget.incomingCallData.isWebRTC == "true"){
// return StartVideoCall(caller: caller, receiver: receiver, iAmCaller: false, host: host);
// }else{
return OpenTokConnectCallPage(apiKey: OPENTOK_API_KEY, sessionId: incomingCallData!.sessionId!, token: incomingCallData!.token!);
},
),
);
}
} catch (err) {
print(err);
await PlatformExceptionAlertDialog(
exception: Exception(err),
).show(context);
}
}
class PushNotificationHandler {
@ -382,7 +532,6 @@ class PushNotificationHandler {
// });
// }
FirebaseMessaging.onBackgroundMessage(backgroundMessageHandler);
}
newMessage(RemoteMessage remoteMessage) async {
@ -440,10 +589,6 @@ class PushNotificationHandler {
}
onResume() async {
var call_data = await AppSharedPreferences().getObject('call_data');
if (call_data != null) {
_incomingCall(call_data);
}
}
Future<void> requestPermissions() async {

@ -66,7 +66,7 @@ dependencies:
shared_preferences: ^2.0.0
# flutter_flexible_toast: ^0.1.4
fluttertoast: ^8.0.8
firebase_messaging: 14.1.0
firebase_messaging: 14.7.7
firebase_analytics: ^10.0.5
# Progress bar
flutter_progress_hud: ^2.0.2
@ -75,6 +75,7 @@ dependencies:
# Icons
font_awesome_flutter: any
cupertino_icons: ^1.0.0
flutter_callkit_incoming: ^2.5.3-alpha
# Image Attachments
image_picker: ^1.0.4
@ -88,6 +89,7 @@ dependencies:
# UI Reqs
expandable: ^5.0.1
uuid: ^3.0.7
# charts
flutter_charts: ^0.5.2

Loading…
Cancel
Save