Merge branch 'development' of https://gitlab.com/Cloud_Solution/doctor_app_flutter into episode_fixes

merge-requests/773/head^2
Mohammad Aljammal 3 years ago
commit 1429a59138

@ -13,6 +13,7 @@ public interface VideoCallContract {
void onCallChangeCallStatusSuccessful(SessionStatusModel sessionStatusModel);
void onFailure();
}
interface VideoCallPresenter {

@ -59,7 +59,6 @@ public class VideoCallPresenterImpl implements VideoCallContract.VideoCallPresen
public void onResponse(@NotNull Call<SessionStatusModel> call, @NotNull Response<SessionStatusModel> response) {
if (!response.isSuccessful())
view.onFailure();
}
@Override

@ -416,6 +416,20 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session
mSession = Session.Builder(context, apiKey, sessionId).build()
mSession!!.setSessionListener(this)
mSession!!.connect(token)
mSession!!.setReconnectionListener(object :Session.ReconnectionListener{
override fun onReconnecting(p0: Session?) {
}
override fun onReconnected(session: Session?) {
session?.connect(token)
}
})
} catch (e: Exception) {
e.printStackTrace()
}
@ -453,10 +467,10 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session
override fun onDisconnected(session: Session) {
Log.d(TAG, "onDisconnected: disconnected from session " + session.sessionId)
mSession = null
cmTimer.stop()
disconnectSession()
videoCallResponseListener?.minimizeVideoEvent(false)
// mSession = null
// cmTimer.stop()
// disconnectSession()
// videoCallResponseListener?.minimizeVideoEvent(false)
}
override fun onError(session: Session, opentokError: OpentokError) {
@ -503,7 +517,7 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session
mSubscriber!!.destroy()
mSubscriber = null
}
disconnectSession()
//disconnectSession()
}
override fun onStreamCreated(publisherKit: PublisherKit?, stream: Stream) {
@ -628,6 +642,16 @@ class VideoCallFragment : DialogFragment(), PermissionCallbacks, Session.Session
sessionStatusModel!!.vcid
)
)
// if(isDismiss)
// dialog?.dismiss()
// else
onCallChangeCallStatusSuccessful()
}
fun onCallChangeCallStatusSuccessful(){
val returnIntent = Intent()
returnIntent.putExtra("CallEnd", sessionStatusModel)
videoCallResponseListener?.onCallFinished(Activity.RESULT_CANCELED, returnIntent)
dialog?.dismiss()
}

@ -1,33 +1,33 @@
{
"project_info": {
"project_number": "157373154094",
"project_id": "hmg-doctor-app-1553688619744",
"storage_bucket": "hmg-doctor-app-1553688619744.appspot.com"
"project_number": "1095694324958",
"project_id": "mobapp-bb272",
"storage_bucket": "mobapp-bb272.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:157373154094:android:daeea3a4e1f4462a1bf0bf",
"mobilesdk_app_id": "1:1095694324958:android:70a42e30fcc98ea33fde4f",
"android_client_info": {
"package_name": "com.hmg.hmgDr"
}
},
"oauth_client": [
{
"client_id": "157373154094-egrhbfr861l7k722g3v2gd4a0opi3r1u.apps.googleusercontent.com",
"client_id": "1095694324958-5psabq8tpbti0uqo8bt46atjtbas9uhr.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIzaSyDX8RPwu00MyrpqC-T2zXtrUQvTQGRv1mM"
"current_key": "AIzaSyCuPtf1hTikWXrp5OQIVfqO-TJWubI-Vd8"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "157373154094-egrhbfr861l7k722g3v2gd4a0opi3r1u.apps.googleusercontent.com",
"client_id": "1095694324958-5psabq8tpbti0uqo8bt46atjtbas9uhr.apps.googleusercontent.com",
"client_type": 3
}
]
@ -36,4 +36,4 @@
}
],
"configuration_version": "1"
}
}

@ -232,6 +232,8 @@ const INSERT_MEDICAL_REPORT = "Services/Patients.svc/REST/DAPP_InsertMedicalRepo
const UPDATE_MEDICAL_REPORT = "Services/Patients.svc/REST/DAPP_UpdateMedicalReport";
const GET_SICK_LEAVE_DOCTOR_APP = "Services/DoctorApplication.svc/REST/GetAllSickLeaves";
const ADD_PATIENT_TO_DOCTOR = "LiveCareApi/DoctorApp/AssignPatientToDoctor";
const REMOVE_PATIENT_FROM_DOCTOR = "LiveCareApi/DoctorApp/BackPatientToQueue";
var selectedPatientType = 1;

@ -0,0 +1,27 @@
class AddPatientToDoctorListRequestModel {
int vCID;
String tokenID;
String generalid;
int doctorId;
bool isOutKsa;
AddPatientToDoctorListRequestModel({this.vCID, this.tokenID, this.generalid, this.doctorId, this.isOutKsa});
AddPatientToDoctorListRequestModel.fromJson(Map<String, dynamic> json) {
vCID = json['VC_ID'];
tokenID = json['TokenID'];
generalid = json['generalid'];
doctorId = json['DoctorId'];
isOutKsa = json['IsOutKsa'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['VC_ID'] = this.vCID;
data['TokenID'] = this.tokenID;
data['generalid'] = this.generalid;
data['DoctorId'] = this.doctorId;
data['IsOutKsa'] = this.isOutKsa;
return data;
}
}

@ -0,0 +1,22 @@
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
class AnalyticsService {
final FirebaseAnalytics _analytics = FirebaseAnalytics();
FirebaseAnalyticsObserver getAnalyticsObserver() =>
FirebaseAnalyticsObserver(analytics: _analytics);
Future logEvent(
{String eventCategory,
String eventLabel,
String eventAction,
String eventValue}) async {
await _analytics.logEvent(name: 'event', parameters: {
"eventCategory": eventCategory,
"eventLabel": eventLabel,
"eventAction": eventAction,
"eventValue": eventValue
});
}
}

@ -32,8 +32,8 @@ class VideoCallService extends BaseService {
kToken: startCallRes.openTokenID,
kSessionId: startCallRes.openSessionID,
kApiKey:'46209962',
// kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGIyZDljOTY3YjFiNWU1YzUzNzFmMjIyNjJmNmEzY2Y5NzZjOTdlYzpzZXNzaW9uX2lkPTFfTVg0ME56STBOemsxTkg1LU1UWXlNekEyTlRRMU9EVXhObjVrVFRoMFlVdFJXaXRYTWpadFZGZHFhSGxZVGpOdE1UVi1mZyZjcmVhdGVfdGltZT0xNjIzMDY1NDk1Jm5vbmNlPTAuMjM2Mjk0NTIwMTkyOTA4OTcmcm9sZT1wdWJsaXNoZXImZXhwaXJlX3RpbWU9MTYyNTY1NzQ5NCZpbml0aWFsX2xheW91dF9jbGFzc19saXN0PQ==",
// kSessionId: "1_MX40NzI0Nzk1NH5-MTYyMzA2NTQ1ODUxNn5kTTh0YUtRWitXMjZtVFdqaHlYTjNtMTV-fg",
// kToken: "T1==cGFydG5lcl9pZD00NzI0Nzk1NCZzaWc9NGUyZjgxMjFlYTFkNzU5NjcxNDY2ZTM2ZjM3YTVhNTI2NGY0NTI2NzpzZXNzaW9uX2lkPTJfTVg0ME56STBOemsxTkg1LU1UWXlOVGN5TmpnMk5qZzNOMzQ1YUhCcGRtcDFXbVpDTDFkNE1qbDRkWFY2TTA4cmIySi1mZyZjcmVhdGVfdGltZT0xNjI1NzI2ODg5Jm5vbmNlPTAuNjc2Nzc4OTQxNjA1MTMxNSZyb2xlPXB1Ymxpc2hlciZleHBpcmVfdGltZT0xNjI4MzE4ODg4JmluaXRpYWxfbGF5b3V0X2NsYXNzX2xpc3Q9",
// kSessionId: "2_MX40NzI0Nzk1NH5-MTYyNTcyNjg2Njg3N345aHBpdmp1WmZCL1d4Mjl4dXV6M08rb2J-fg",
// kApiKey:'47247954',
vcId: patient.vcId,
isRecording: isRecording,

@ -1,7 +1,7 @@
import 'package:doctor_app_flutter/config/config.dart';
import 'package:doctor_app_flutter/core/model/live_care/AlternativeServicesList.dart';
import 'package:doctor_app_flutter/core/model/live_care/PendingPatientERForDoctorAppRequestModel.dart';
import 'package:doctor_app_flutter/core/model/live_care/add_patient_to_doctor_list_request_model.dart';
import 'package:doctor_app_flutter/core/model/live_care/live_care_login_reguest_model.dart';
import 'package:doctor_app_flutter/core/service/base/base_service.dart';
import 'package:doctor_app_flutter/models/livecare/end_call_req.dart';
@ -35,14 +35,12 @@ class LiveCarePatientServices extends BaseService {
StartCallRes get startCallRes => _startCallRes;
Future getPendingPatientERForDoctorApp(
PendingPatientERForDoctorAppRequestModel
pendingPatientERForDoctorAppRequestModel) async {
PendingPatientERForDoctorAppRequestModel pendingPatientERForDoctorAppRequestModel) async {
hasError = false;
await baseAppClient.post(
GET_PENDING_PATIENT_ER_FOR_DOCTOR_APP,
onSuccess: (dynamic response, int statusCode) {
List<PatiantInformtion> localPatientList= [];
List<PatiantInformtion> localPatientList = [];
response['List_PendingPatientList'].forEach((v) {
localPatientList.add(PatiantInformtion.fromJson(v));
@ -50,22 +48,19 @@ class LiveCarePatientServices extends BaseService {
/// add new items.
localPatientList.forEach((element) {
if ((_patientList.singleWhere((it) => it.patientId == element.patientId,
orElse: () => null)) == null) {
if ((_patientList.singleWhere((it) => it.patientId == element.patientId, orElse: () => null)) == null) {
_patientList.add(element);
}
});
/// remove items.
List<PatiantInformtion> removedPatientList= [];
List<PatiantInformtion> removedPatientList = [];
_patientList.forEach((element) {
if ((localPatientList.singleWhere((it) => it.patientId == element.patientId,
orElse: () => null)) == null) {
if ((localPatientList.singleWhere((it) => it.patientId == element.patientId, orElse: () => null)) == null) {
removedPatientList.add(element);
}
});
removedPatientList.forEach((element) {
_patientList.remove(element);
});
@ -105,11 +100,7 @@ class LiveCarePatientServices extends BaseService {
}, onFailure: (String error, int statusCode) {
hasError = true;
super.error = error;
}, body: {
"VC_ID": vcID,
"AltServiceList": altServiceList,
"generalid":GENERAL_ID
}, isLiveCare: _isLive);
}, body: {"VC_ID": vcID, "AltServiceList": altServiceList, "generalid": GENERAL_ID}, isLiveCare: _isLive);
}
Future transferToAdmin(int vcID, String notes) async {
@ -128,20 +119,17 @@ class LiveCarePatientServices extends BaseService {
Future sendSMSInstruction(int vcID) async {
hasError = false;
await baseAppClient.post(SEND_SMS_INSTRUCTIONS,
onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(SEND_SMS_INSTRUCTIONS, onSuccess: (dynamic response, int statusCode) {
transferToAdminResponse = response;
}, onFailure: (String error, int statusCode) {
hasError = true;
super.error = error;
}, body: {
"VC_ID": vcID, "generalid": GENERAL_ID
}, isLiveCare: _isLive);
}, body: {"VC_ID": vcID, "generalid": GENERAL_ID}, isLiveCare: _isLive);
}
Future isLogin({LiveCareUserLoginRequestModel isLoginRequestModel, int loginStatus}) async {
hasError = false;
await getDoctorProfile( );
await getDoctorProfile();
isLoginRequestModel.doctorId = super.doctorProfile.doctorID;
await baseAppClient.post(LIVE_CARE_IS_LOGIN, onSuccess: (response, statusCode) async {
isLoginResponse = response;
@ -155,17 +143,48 @@ class LiveCarePatientServices extends BaseService {
hasError = false;
alternativeServicesList.clear();
await baseAppClient.post(GET_ALTERNATIVE_SERVICE,
onSuccess: (dynamic response, int statusCode) {
await baseAppClient.post(GET_ALTERNATIVE_SERVICE, onSuccess: (dynamic response, int statusCode) {
response['AlternativeServicesList'].forEach((v) {
alternativeServicesList.add(AlternativeService.fromJson(v));
});
}, onFailure: (String error, int statusCode) {
hasError = true;
super.error = error;
}, body: {
"VC_ID": vcID,
"generalid": GENERAL_ID
}, isLiveCare: _isLive);
}, body: {"VC_ID": vcID, "generalid": GENERAL_ID}, isLiveCare: _isLive);
}
Future addPatientToDoctorList({int vcID}) async {
hasError = false;
await getDoctorProfile();
AddPatientToDoctorListRequestModel addPatientToDoctorListRequestModel = AddPatientToDoctorListRequestModel();
addPatientToDoctorListRequestModel.doctorId = super.doctorProfile.doctorID;
addPatientToDoctorListRequestModel.vCID = vcID;
addPatientToDoctorListRequestModel.isOutKsa = false;
addPatientToDoctorListRequestModel.generalid = GENERAL_ID;
await baseAppClient.post(ADD_PATIENT_TO_DOCTOR, onSuccess: (response, statusCode) async {
isLoginResponse = response;
}, onFailure: (String error, int statusCode) {
hasError = true;
super.error = error;
}, body: addPatientToDoctorListRequestModel.toJson(), isLiveCare: _isLive);
}
Future removePatientFromDoctorList({int vcID}) async {
hasError = false;
AddPatientToDoctorListRequestModel addPatientToDoctorListRequestModel = AddPatientToDoctorListRequestModel();
await getDoctorProfile();
addPatientToDoctorListRequestModel.doctorId = super.doctorProfile.doctorID;
addPatientToDoctorListRequestModel.vCID = vcID;
addPatientToDoctorListRequestModel.isOutKsa = false;
addPatientToDoctorListRequestModel.generalid = GENERAL_ID;
await baseAppClient.post(REMOVE_PATIENT_FROM_DOCTOR, onSuccess: (response, statusCode) async {
isLoginResponse = response;
}, onFailure: (String error, int statusCode) {
hasError = true;
super.error = error;
}, body: addPatientToDoctorListRequestModel.toJson(), isLiveCare: _isLive);
}
}

@ -20,8 +20,7 @@ class LiveCarePatientViewModel extends BaseViewModel {
StartCallRes get startCallRes => _liveCarePatientServices.startCallRes;
List<AlternativeService> get alternativeServicesList =>
_liveCarePatientServices.alternativeServicesList;
List<AlternativeService> get alternativeServicesList => _liveCarePatientServices.alternativeServicesList;
DashboardService _dashboardService = locator<DashboardService>();
@ -106,8 +105,7 @@ class LiveCarePatientViewModel extends BaseViewModel {
selectedServices = getSelectedAlternativeServices();
}
await _liveCarePatientServices.endCallWithCharge(
vcID, selectedServices);
await _liveCarePatientServices.endCallWithCharge(vcID, selectedServices);
if (_liveCarePatientServices.hasError) {
error = _liveCarePatientServices.error;
setState(ViewState.ErrorLocal);
@ -209,8 +207,7 @@ class LiveCarePatientViewModel extends BaseViewModel {
AlternativeService(serviceID: 2, serviceName: "LABORATORY"),
);
alternativeServicesList.add(
AlternativeService(
serviceID: 3, serviceName: "RADIOLOGY(ULTRASOUND) For pregnant only"),
AlternativeService(serviceID: 3, serviceName: "RADIOLOGY(ULTRASOUND) For pregnant only"),
);
alternativeServicesList.add(
AlternativeService(serviceID: 4, serviceName: "VACCINATIONS"),
@ -231,25 +228,46 @@ class LiveCarePatientViewModel extends BaseViewModel {
AlternativeService(serviceID: 9, serviceName: "FAMILY MEDICIN DR"),
);
alternativeServicesList.add(
AlternativeService(
serviceID: 10, serviceName: "FOLYS CATHETER INSERTION"),
AlternativeService(serviceID: 10, serviceName: "FOLYS CATHETER INSERTION"),
);
alternativeServicesList.add(
AlternativeService(serviceID: 11, serviceName: "GASTRIC TUBE CHANGE"),
);
}
updateInCallPatient({PatiantInformtion patient, appointmentNo}){
_liveCarePatientServices.patientList.forEach((e) {
if(e.patientId == patient.patientId) {
e.episodeNo = 0 ;
updateInCallPatient({PatiantInformtion patient, appointmentNo}) {
_liveCarePatientServices.patientList.forEach((e) {
if (e.patientId == patient.patientId) {
e.episodeNo = 0;
e.appointmentNo = appointmentNo;
return;
}
});
setState(ViewState.Idle);
}
Future addPatientToDoctorList(int vcID) async {
await getDoctorProfile(isGetProfile: true);
setState(ViewState.BusyLocal);
await _liveCarePatientServices.addPatientToDoctorList(vcID: vcID);
if (_liveCarePatientServices.hasError) {
error = _liveCarePatientServices.error;
setState(ViewState.ErrorLocal);
} else {
setState(ViewState.Idle);
}
}
Future removePatientFromDoctorList(int vcID) async {
await getDoctorProfile(isGetProfile: true);
setState(ViewState.BusyLocal);
await _liveCarePatientServices.removePatientFromDoctorList(vcID: vcID);
if (_liveCarePatientServices.hasError) {
error = _liveCarePatientServices.error;
setState(ViewState.ErrorLocal);
} else {
setState(ViewState.Idle);
}
}
}

@ -10,6 +10,7 @@ import 'package:doctor_app_flutter/core/viewModel/scan_qr_view_model.dart';
import 'package:doctor_app_flutter/core/viewModel/sick_leave_view_model.dart';
import 'package:get_it/get_it.dart';
import 'core/service/AnalyticsService.dart';
import 'core/service/NavigationService.dart';
import 'core/service/VideoCallService.dart';
import 'core/service/home/dasboard_service.dart';
@ -98,6 +99,7 @@ void setupLocator() {
locator.registerLazySingleton(() => ScanQrService());
locator.registerLazySingleton(() => SpecialClinicsService());
locator.registerLazySingleton(() => VideoCallService());
locator.registerLazySingleton(() => AnalyticsService());
/// View Model
locator.registerFactory(() => DoctorReplayViewModel());

@ -2,6 +2,8 @@ import 'package:doctor_app_flutter/core/provider/robot_provider.dart';
import 'package:doctor_app_flutter/core/viewModel/livecare_view_model.dart';
import 'package:doctor_app_flutter/core/viewModel/project_view_model.dart';
import 'package:doctor_app_flutter/util/translations_delegate_base.dart';
import 'package:firebase_analytics/firebase_analytics.dart';
import 'package:firebase_analytics/observer.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
@ -11,6 +13,7 @@ import 'package:provider/provider.dart';
import './config/size_config.dart';
import './routes.dart';
import 'config/config.dart';
import 'core/service/AnalyticsService.dart';
import 'core/service/NavigationService.dart';
import 'core/viewModel/authentication_view_model.dart';
import 'locator.dart';
@ -67,7 +70,10 @@ class MyApp extends StatelessWidget {
dividerColor: Colors.grey[350],
backgroundColor: Color.fromRGBO(255, 255, 255, 1),
),
navigatorKey: locator<NavigationService>().navigatorKey,
navigatorKey: locator<NavigationService>().navigatorKey,
navigatorObservers:[
locator<AnalyticsService>().getAnalyticsObserver(),
],
initialRoute: INIT_ROUTE,
routes: routes,
debugShowCheckedModeBanner: false,

@ -194,10 +194,7 @@ class _LiveCarePatientScreenState extends State<LiveCarePatientScreen> {
);
}
callConnected(){
callConnected() {}
}
callDisconnected(){
}
callDisconnected() {}
}

@ -32,6 +32,7 @@ class PatientProfileScreen extends StatefulWidget {
class _PatientProfileScreenState extends State<PatientProfileScreen> with SingleTickerProviderStateMixin {
PatiantInformtion patient;
LiveCarePatientViewModel _liveCareViewModel = LiveCarePatientViewModel();
bool isFromSearch = false;
bool isFromLiveCare = false;
@ -63,6 +64,9 @@ class _PatientProfileScreenState extends State<PatientProfileScreen> with Single
void dispose() {
_tabController.dispose();
super.dispose();
if (isFromLiveCare) {
_liveCareViewModel.removePatientFromDoctorList(patient.vcId);
}
}
@override
@ -123,6 +127,9 @@ class _PatientProfileScreenState extends State<PatientProfileScreen> with Single
Widget build(BuildContext context) {
final screenSize = MediaQuery.of(context).size;
return BaseView<LiveCarePatientViewModel>(
onModelReady: (model) async {
if (isFromLiveCare && patient.patientStatus == 1) await model.addPatientToDoctorList(patient.vcId);
},
builder: (_, model, w) => AppScaffold(
baseViewModel: model,
appBarTitle: TranslationBase.of(context).patientProfile,
@ -316,43 +323,35 @@ class _PatientProfileScreenState extends State<PatientProfileScreen> with Single
Navigator.push(
context,
MaterialPageRoute(
builder: (BuildContext context) =>
EndCallScreen(patient: patient)));
builder: (BuildContext context) => EndCallScreen(patient: patient)));
} else {
GifLoaderDialogUtils.showMyDialog(context);
await model.startCall(
isReCall: false, vCID: patient.vcId);
await model.startCall(isReCall: false, vCID: patient.vcId);
if (model.state == ViewState.ErrorLocal) {
GifLoaderDialogUtils.hideDialog(context);
Helpers.showErrorToast(model.error);
} else {
await model.getDoctorProfile();
patient.appointmentNo = int.parse(model
.startCallRes.appointmentNo
.toString());
patient.appointmentNo = int.parse(model.startCallRes.appointmentNo.toString());
patient.episodeNo = 0;
model.updateInCallPatient(
patient: patient,
appointmentNo: int.parse(model
.startCallRes.appointmentNo
.toString()));
appointmentNo: int.parse(model.startCallRes.appointmentNo.toString()));
setState(() {
isCallStarted = true;
});
GifLoaderDialogUtils.hideDialog(context);
AppPermissionsUtils
.requestVideoCallPermission(
context: context,
onTapGrant: () {
locator<VideoCallService>()
.openVideo(
model.startCallRes,
patient,
model.startCallRes != null ? model.startCallRes.isRecording : true
, callConnected,
callDisconnected);
});
AppPermissionsUtils.requestVideoCallPermission(
context: context,
onTapGrant: () {
locator<VideoCallService>().openVideo(
model.startCallRes,
patient,
model.startCallRes != null ? model.startCallRes.isRecording : true,
callConnected,
callDisconnected);
});
}
}
},

@ -351,6 +351,34 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "5.2.1"
firebase:
dependency: transitive
description:
name: firebase
url: "https://pub.dartlang.org"
source: hosted
version: "7.3.3"
firebase_analytics:
dependency: "direct main"
description:
name: firebase_analytics
url: "https://pub.dartlang.org"
source: hosted
version: "6.3.0"
firebase_analytics_platform_interface:
dependency: transitive
description:
name: firebase_analytics_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.0.3"
firebase_analytics_web:
dependency: transitive
description:
name: firebase_analytics_web
url: "https://pub.dartlang.org"
source: hosted
version: "0.1.1"
firebase_core:
dependency: transitive
description:

@ -71,6 +71,7 @@ dependencies:
# Firebase
firebase_messaging: ^7.0.3
firebase_analytics: 6.3.0
#GIF image
flutter_gifimage: ^1.0.1

Loading…
Cancel
Save