Compare commits

...

12 Commits

Binary file not shown.

After

Width:  |  Height:  |  Size: 113 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 651 KiB

@ -22,8 +22,8 @@ var PACKAGES_ORDER_HISTORY = '/api/orders/items';
var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara'; var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara';
// var BASE_URL = 'http://10.50.100.198:2018/'; // var BASE_URL = 'http://10.50.100.198:2018/';
// var BASE_URL = 'http://10.50.100.198:4422/'; // var BASE_URL = 'http://10.50.100.198:4422/';
// var BASE_URL = 'https://uat.hmgwebservices.com/'; var BASE_URL = 'https://uat.hmgwebservices.com/';
var BASE_URL = 'https://hmgwebservices.com/'; // var BASE_URL = 'https://hmgwebservices.com/';
// var BASE_URL = 'http://10.20.200.111:1010/'; // var BASE_URL = 'http://10.20.200.111:1010/';
// var BASE_URL = 'https://orash.cloudsolutions.com.sa/'; // var BASE_URL = 'https://orash.cloudsolutions.com.sa/';
// var BASE_URL = 'https://vidauat.cloudsolutions.com.sa/'; // var BASE_URL = 'https://vidauat.cloudsolutions.com.sa/';
@ -685,6 +685,11 @@ var applePayMerchantId = "merchant.com.hmgwebservices";
// var payFortEnvironment = FortEnvironment.test; // var payFortEnvironment = FortEnvironment.test;
// var applePayMerchantId = "merchant.com.hmgwebservices.uat"; // var applePayMerchantId = "merchant.com.hmgwebservices.uat";
// Patient Courses
var GET_PATIENT_COURSES_LIST = "Services/Nabed.svc/REST/PatientEducationalJourney";
var GET_PATIENT_COURSE_BY_ID = "Services/Nabed.svc/REST/PatientEducationalJourneyByID";
var INSERT_PATIENT_COURSE_VIEW_STATS = "Services/Nabed.svc/REST/InsertJourneyStatistics";
class AppGlobal { class AppGlobal {
static var context; static var context;

@ -1,5 +1,6 @@
import 'dart:convert'; import 'dart:convert';
import 'package:crypto/crypto.dart'; import 'package:crypto/crypto.dart';
import 'package:flutter/material.dart';
extension CapExtension on String { extension CapExtension on String {
String get toCamelCase => "${this[0].toUpperCase()}${this.substring(1)}"; String get toCamelCase => "${this[0].toUpperCase()}${this.substring(1)}";
@ -17,3 +18,13 @@ extension HashSha on String {
return sha256.convert(bytes).toString(); return sha256.convert(bytes).toString();
} }
} }
extension OnTapWidget on Widget {
Widget onTap(VoidCallback onTap, {Key? key}) {
return GestureDetector(
key: key,
onTap: onTap,
child: this, // This refers to the widget on which the extension is called
);
}
}

@ -23,6 +23,7 @@ import 'core/viewModels/pharmacyModule/OrderPreviewViewModel.dart';
import 'core/viewModels/project_view_model.dart'; import 'core/viewModels/project_view_model.dart';
import 'locator.dart'; import 'locator.dart';
import 'pages/pharmacies/compare-list.dart'; import 'pages/pharmacies/compare-list.dart';
import 'services/course_service/course_service.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
@ -55,8 +56,6 @@ class _MyApp extends State<MyApp> {
//0567184134 mobile //0567184134 mobile
//246305493 //246305493
// checkForUpdate() { // checkForUpdate() {
// // todo need to verify 'imp' // // todo need to verify 'imp'
// InAppUpdate.checkForUpdate().then((info) { // InAppUpdate.checkForUpdate().then((info) {
@ -118,6 +117,9 @@ class _MyApp extends State<MyApp> {
ChangeNotifierProvider<SearchProvider>( ChangeNotifierProvider<SearchProvider>(
create: (context) => SearchProvider(), create: (context) => SearchProvider(),
), ),
ChangeNotifierProvider<CourseServiceProvider>(
create: (context) => CourseServiceProvider(),
),
ChangeNotifierProvider.value( ChangeNotifierProvider.value(
value: SearchProvider(), value: SearchProvider(),
), ),
@ -140,14 +142,12 @@ class _MyApp extends State<MyApp> {
], ],
child: Consumer<ProjectViewModel>( child: Consumer<ProjectViewModel>(
builder: (context, projectProvider, child) => MaterialApp( builder: (context, projectProvider, child) => MaterialApp(
builder: (_, mchild) { builder: (_, mchild) {
return MediaQuery( return MediaQuery(
data: MediaQuery.of(context).copyWith( data: MediaQuery.of(context).copyWith(
textScaler: TextScaler.linear(1.0), textScaler: TextScaler.linear(1.0),
), //set desired text scale factor here ), //set desired text scale factor here
child: mchild! child: mchild!);
);
// Container( // Container(
// color: Colors.blue, // color: Colors.blue,
// )); // ));

@ -0,0 +1,85 @@
import 'dart:convert';
class PatientEducationJourneyInsert {
String? tokenId;
int? patientId;
int? languageId;
List<Data>? data;
PatientEducationJourneyInsert({
this.tokenId,
this.patientId,
this.languageId,
this.data,
});
factory PatientEducationJourneyInsert.fromRawJson(String str) => PatientEducationJourneyInsert.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory PatientEducationJourneyInsert.fromJson(Map<String, dynamic> json) => PatientEducationJourneyInsert(
tokenId: json["TokenID"],
patientId: json["PatientID"],
languageId: json["LanguageID"],
data: json["data"] == null ? [] : List<Data>.from(json["data"]!.map((x) => Data.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"TokenID": tokenId,
"PatientID": patientId,
"LanguageID": languageId,
"data": data == null ? [] : List<dynamic>.from(data!.map((x) => x.toJson())),
};
}
class Data {
String? type;
int? consultationId;
int? contentClassId;
int? topicId;
int? contentId;
int? percentage;
int? flavorId;
String? srcType;
String? screenType;
Data({
this.type,
this.consultationId,
this.contentClassId,
this.topicId,
this.contentId,
this.percentage,
this.flavorId,
this.srcType,
this.screenType,
});
factory Data.fromRawJson(String str) => Data.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Data.fromJson(Map<String, dynamic> json) => Data(
type: json["Type"],
consultationId: json["ConsultationID"],
contentClassId: json["ContentClassId"],
topicId: json["TopicID"],
contentId: json["ContentID"],
percentage: json["Percentage"],
flavorId: json["FlavorId"],
srcType: json["SrcType"],
screenType: json["ScreenType"],
);
Map<String, dynamic> toJson() => {
"Type": type,
"ConsultationID": consultationId,
"ContentClassId": contentClassId,
"TopicID": topicId,
"ContentID": contentId,
"Percentage": percentage,
"FlavorId": flavorId,
"SrcType": srcType,
"ScreenType": screenType,
};
}

@ -0,0 +1,333 @@
// To parse this JSON data, do
//
// final patientEducationJourneyListModel = patientEducationJourneyListModelFromJson(jsonString);
import 'dart:convert';
PatientEducationJourneyListModel patientEducationJourneyListModelFromJson(String str) => PatientEducationJourneyListModel.fromJson(json.decode(str));
String patientEducationJourneyListModelToJson(PatientEducationJourneyListModel data) => json.encode(data.toJson());
class PatientEducationJourneyListModel {
NabedJourneyResponseResult? nabedJourneyResponseResult;
PatientEducationJourneyListModel({
this.nabedJourneyResponseResult,
});
factory PatientEducationJourneyListModel.fromJson(Map<String, dynamic> json) => PatientEducationJourneyListModel(
nabedJourneyResponseResult: json["NabedJourneyResponseResult"] == null ? null : NabedJourneyResponseResult.fromJson(json["NabedJourneyResponseResult"]),
);
Map<String, dynamic> toJson() => {
"NabedJourneyResponseResult": nabedJourneyResponseResult?.toJson(),
};
}
class NabedJourneyResponseResult {
List<Datum>? data;
Links? links;
Meta? meta;
NabedJourneyResponseResult({
this.data,
this.links,
this.meta,
});
factory NabedJourneyResponseResult.fromJson(Map<String, dynamic> json) => NabedJourneyResponseResult(
data: json["data"] == null ? [] : List<Datum>.from(json["data"]!.map((x) => Datum.fromJson(x))),
links: json["links"] == null ? null : Links.fromJson(json["links"]),
meta: json["meta"] == null ? null : Meta.fromJson(json["meta"]),
);
Map<String, dynamic> toJson() => {
"data": data == null ? [] : List<dynamic>.from(data!.map((x) => x.toJson())),
"links": links?.toJson(),
"meta": meta?.toJson(),
};
}
class Datum {
List<ContentClass>? contentClasses;
DateTime? date;
DefaultPocData? defaultPocData;
int? id;
String? integrationApproach;
bool? isNew;
DateTime? openedAt;
String? sourceType;
Stats? stats;
TagValues? tagValues;
Datum({
this.contentClasses,
this.date,
this.defaultPocData,
this.id,
this.integrationApproach,
this.isNew,
this.openedAt,
this.sourceType,
this.stats,
this.tagValues,
});
factory Datum.fromJson(Map<String, dynamic> json) => Datum(
contentClasses: json["content_classes"] == null ? [] : List<ContentClass>.from(json["content_classes"]!.map((x) => ContentClass.fromJson(x))),
date: json["date"] == null ? null : DateTime.parse(json["date"]),
defaultPocData: json["default_poc_data"] == null ? null : DefaultPocData.fromJson(json["default_poc_data"]),
id: json["id"],
integrationApproach: json["integration_approach"],
isNew: json["is_new"],
openedAt: json["opened_at"] == null ? null : DateTime.parse(json["opened_at"]),
sourceType: json["source_type"],
stats: json["stats"] == null ? null : Stats.fromJson(json["stats"]),
tagValues: json["tag_values"] == null ? null : TagValues.fromJson(json["tag_values"]),
);
Map<String, dynamic> toJson() => {
"content_classes": contentClasses == null ? [] : List<dynamic>.from(contentClasses!.map((x) => x.toJson())),
"date": "${date!.year.toString().padLeft(4, '0')}-${date!.month.toString().padLeft(2, '0')}-${date!.day.toString().padLeft(2, '0')}",
"default_poc_data": defaultPocData?.toJson(),
"id": id,
"integration_approach": integrationApproach,
"is_new": isNew,
"opened_at": openedAt?.toIso8601String(),
"source_type": sourceType,
"stats": stats?.toJson(),
"tag_values": tagValues?.toJson(),
};
}
class ContentClass {
int? id;
Image? image;
int? readPercentage;
String? title;
dynamic topics;
String? type;
ContentClass({
this.id,
this.image,
this.readPercentage,
this.title,
this.topics,
this.type,
});
factory ContentClass.fromJson(Map<String, dynamic> json) => ContentClass(
id: json["id"],
image: json["image"] == null ? null : Image.fromJson(json["image"]),
readPercentage: json["read_percentage"],
title: json["title"],
topics: json["topics"],
type: json["type"],
);
Map<String, dynamic> toJson() => {
"id": id,
"image": image?.toJson(),
"read_percentage": readPercentage,
"title": title,
"topics": topics,
"type": type,
};
}
class Image {
String? thumbUrl;
String? url;
Image({
this.thumbUrl,
this.url,
});
factory Image.fromJson(Map<String, dynamic> json) => Image(
thumbUrl: json["thumb_url"],
url: json["url"],
);
Map<String, dynamic> toJson() => {
"thumb_url": thumbUrl,
"url": url,
};
}
class DefaultPocData {
Image? image;
String? title;
DefaultPocData({
this.image,
this.title,
});
factory DefaultPocData.fromJson(Map<String, dynamic> json) => DefaultPocData(
image: json["image"] == null ? null : Image.fromJson(json["image"]),
title: json["title"],
);
Map<String, dynamic> toJson() => {
"image": image?.toJson(),
"title": title,
};
}
class Stats {
int? attachmentsCount;
int? contentCount;
int? contentReadCount;
int? educationalContentCount;
int? educationalContentReadCount;
int? educationalReadPercentage;
bool? isCompleted;
int? readPercentage;
int? readRemainingPercentage;
int? screenshotsCount;
int? topicCount;
int? videosCount;
int? videosReadCount;
Stats({
this.attachmentsCount,
this.contentCount,
this.contentReadCount,
this.educationalContentCount,
this.educationalContentReadCount,
this.educationalReadPercentage,
this.isCompleted,
this.readPercentage,
this.readRemainingPercentage,
this.screenshotsCount,
this.topicCount,
this.videosCount,
this.videosReadCount,
});
factory Stats.fromJson(Map<String, dynamic> json) => Stats(
attachmentsCount: json["attachments_count"],
contentCount: json["content_count"],
contentReadCount: json["content_read_count"],
educationalContentCount: json["educational_content_count"],
educationalContentReadCount: json["educational_content_read_count"],
educationalReadPercentage: json["educational_read_percentage"],
isCompleted: json["is_completed"],
readPercentage: json["read_percentage"],
readRemainingPercentage: json["read_remaining_percentage"],
screenshotsCount: json["screenshots_count"],
topicCount: json["topic_count"],
videosCount: json["videos_count"],
videosReadCount: json["videos_read_count"],
);
Map<String, dynamic> toJson() => {
"attachments_count": attachmentsCount,
"content_count": contentCount,
"content_read_count": contentReadCount,
"educational_content_count": educationalContentCount,
"educational_content_read_count": educationalContentReadCount,
"educational_read_percentage": educationalReadPercentage,
"is_completed": isCompleted,
"read_percentage": readPercentage,
"read_remaining_percentage": readRemainingPercentage,
"screenshots_count": screenshotsCount,
"topic_count": topicCount,
"videos_count": videosCount,
"videos_read_count": videosReadCount,
};
}
class TagValues {
String? consultationCode;
String? title;
String? titleAr;
TagValues({
this.consultationCode,
this.title,
this.titleAr,
});
factory TagValues.fromJson(Map<String, dynamic> json) => TagValues(
consultationCode: json["consultation_code"],
title: json["title"],
titleAr: json["title_ar"],
);
Map<String, dynamic> toJson() => {
"consultation_code": consultationCode,
"title": title,
"title_ar": titleAr,
};
}
class Links {
String? first;
String? last;
dynamic next;
dynamic prev;
Links({
this.first,
this.last,
this.next,
this.prev,
});
factory Links.fromJson(Map<String, dynamic> json) => Links(
first: json["first"],
last: json["last"],
next: json["next"],
prev: json["prev"],
);
Map<String, dynamic> toJson() => {
"first": first,
"last": last,
"next": next,
"prev": prev,
};
}
class Meta {
int? currentPage;
int? from;
int? lastPage;
String? path;
int? perPage;
int? to;
int? total;
Meta({
this.currentPage,
this.from,
this.lastPage,
this.path,
this.perPage,
this.to,
this.total,
});
factory Meta.fromJson(Map<String, dynamic> json) => Meta(
currentPage: json["current_page"],
from: json["from"],
lastPage: json["last_page"],
path: json["path"],
perPage: json["per_page"],
to: json["to"],
total: json["total"],
);
Map<String, dynamic> toJson() => {
"current_page": currentPage,
"from": from,
"last_page": lastPage,
"path": path,
"per_page": perPage,
"to": to,
"total": total,
};
}

@ -0,0 +1,624 @@
import 'dart:async';
import 'dart:convert';
import 'package:video_player/video_player.dart';
class PatientEducationJourneyModel {
dynamic date;
int? languageId;
int? serviceName;
dynamic time;
dynamic androidLink;
dynamic authenticationTokenId;
dynamic data;
bool? dataw;
int? dietType;
int? dietTypeId;
dynamic errorCode;
dynamic errorEndUserMessage;
dynamic errorEndUserMessageN;
dynamic errorMessage;
int? errorStatusCode;
int? errorType;
int? foodCategory;
dynamic iosLink;
bool? isAuthenticated;
int? mealOrderStatus;
int? mealType;
int? messageStatus;
int? numberOfResultRecords;
dynamic patientBlodType;
dynamic successMsg;
dynamic successMsgN;
dynamic vidaUpdatedResponse;
NabedJourneyByIdResponseResult? nabedJourneyByIdResponseResult;
dynamic nabedJourneyResponseResult;
dynamic nabedPatientList;
dynamic nabedResponse;
PatientEducationJourneyModel({
this.date,
this.languageId,
this.serviceName,
this.time,
this.androidLink,
this.authenticationTokenId,
this.data,
this.dataw,
this.dietType,
this.dietTypeId,
this.errorCode,
this.errorEndUserMessage,
this.errorEndUserMessageN,
this.errorMessage,
this.errorStatusCode,
this.errorType,
this.foodCategory,
this.iosLink,
this.isAuthenticated,
this.mealOrderStatus,
this.mealType,
this.messageStatus,
this.numberOfResultRecords,
this.patientBlodType,
this.successMsg,
this.successMsgN,
this.vidaUpdatedResponse,
this.nabedJourneyByIdResponseResult,
this.nabedJourneyResponseResult,
this.nabedPatientList,
this.nabedResponse,
});
factory PatientEducationJourneyModel.fromRawJson(String str) => PatientEducationJourneyModel.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory PatientEducationJourneyModel.fromJson(Map<String, dynamic> json) => PatientEducationJourneyModel(
date: json["Date"],
languageId: json["LanguageID"],
serviceName: json["ServiceName"],
time: json["Time"],
androidLink: json["AndroidLink"],
authenticationTokenId: json["AuthenticationTokenID"],
data: json["Data"],
dataw: json["Dataw"],
dietType: json["DietType"],
dietTypeId: json["DietTypeID"],
errorCode: json["ErrorCode"],
errorEndUserMessage: json["ErrorEndUserMessage"],
errorEndUserMessageN: json["ErrorEndUserMessageN"],
errorMessage: json["ErrorMessage"],
errorStatusCode: json["ErrorStatusCode"],
errorType: json["ErrorType"],
foodCategory: json["FoodCategory"],
iosLink: json["IOSLink"],
isAuthenticated: json["IsAuthenticated"],
mealOrderStatus: json["MealOrderStatus"],
mealType: json["MealType"],
messageStatus: json["MessageStatus"],
numberOfResultRecords: json["NumberOfResultRecords"],
patientBlodType: json["PatientBlodType"],
successMsg: json["SuccessMsg"],
successMsgN: json["SuccessMsgN"],
vidaUpdatedResponse: json["VidaUpdatedResponse"],
nabedJourneyByIdResponseResult: json["NabedJourneyByIdResponseResult"] == null ? null : NabedJourneyByIdResponseResult.fromJson(json["NabedJourneyByIdResponseResult"]),
nabedJourneyResponseResult: json["NabedJourneyResponseResult"],
nabedPatientList: json["NabedPatientList"],
nabedResponse: json["NabedResponse"],
);
Map<String, dynamic> toJson() => {
"Date": date,
"LanguageID": languageId,
"ServiceName": serviceName,
"Time": time,
"AndroidLink": androidLink,
"AuthenticationTokenID": authenticationTokenId,
"Data": data,
"Dataw": dataw,
"DietType": dietType,
"DietTypeID": dietTypeId,
"ErrorCode": errorCode,
"ErrorEndUserMessage": errorEndUserMessage,
"ErrorEndUserMessageN": errorEndUserMessageN,
"ErrorMessage": errorMessage,
"ErrorStatusCode": errorStatusCode,
"ErrorType": errorType,
"FoodCategory": foodCategory,
"IOSLink": iosLink,
"IsAuthenticated": isAuthenticated,
"MealOrderStatus": mealOrderStatus,
"MealType": mealType,
"MessageStatus": messageStatus,
"NumberOfResultRecords": numberOfResultRecords,
"PatientBlodType": patientBlodType,
"SuccessMsg": successMsg,
"SuccessMsgN": successMsgN,
"VidaUpdatedResponse": vidaUpdatedResponse,
"NabedJourneyByIdResponseResult": nabedJourneyByIdResponseResult?.toJson(),
"NabedJourneyResponseResult": nabedJourneyResponseResult,
"NabedPatientList": nabedPatientList,
"NabedResponse": nabedResponse,
};
}
class NabedJourneyByIdResponseResult {
Consultation? consultation;
List<ContentClass>? contentClasses;
NabedJourneyByIdResponseResult({
this.consultation,
this.contentClasses,
});
factory NabedJourneyByIdResponseResult.fromRawJson(String str) => NabedJourneyByIdResponseResult.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory NabedJourneyByIdResponseResult.fromJson(Map<String, dynamic> json) => NabedJourneyByIdResponseResult(
consultation: json["consultation"] == null ? null : Consultation.fromJson(json["consultation"]),
contentClasses: json["content_classes"] == null ? [] : List<ContentClass>.from(json["content_classes"]!.map((x) => ContentClass.fromJson(x))),
);
Map<String, dynamic> toJson() => {
"consultation": consultation?.toJson(),
"content_classes": contentClasses == null ? [] : List<dynamic>.from(contentClasses!.map((x) => x.toJson())),
};
}
class Consultation {
DateTime? date;
DefaultPocData? defaultPocData;
int? id;
String? integrationApproach;
bool? isNew;
String? openedAt;
String? sourceType;
Stats? stats;
TagValues? tagValues;
Consultation({
this.date,
this.defaultPocData,
this.id,
this.integrationApproach,
this.isNew,
this.openedAt,
this.sourceType,
this.stats,
this.tagValues,
});
factory Consultation.fromRawJson(String str) => Consultation.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Consultation.fromJson(Map<String, dynamic> json) => Consultation(
date: json["date"] == null ? null : DateTime.parse(json["date"]),
defaultPocData: json["default_poc_data"] == null ? null : DefaultPocData.fromJson(json["default_poc_data"]),
id: json["id"],
integrationApproach: json["integration_approach"],
isNew: json["is_new"],
openedAt: json["opened_at"],
sourceType: json["source_type"],
stats: json["stats"] == null ? null : Stats.fromJson(json["stats"]),
tagValues: json["tag_values"] == null ? null : TagValues.fromJson(json["tag_values"]),
);
Map<String, dynamic> toJson() => {
"date": "${date!.year.toString().padLeft(4, '0')}-${date!.month.toString().padLeft(2, '0')}-${date!.day.toString().padLeft(2, '0')}",
"default_poc_data": defaultPocData?.toJson(),
"id": id,
"integration_approach": integrationApproach,
"is_new": isNew,
"opened_at": openedAt,
"source_type": sourceType,
"stats": stats?.toJson(),
"tag_values": tagValues?.toJson(),
};
}
class DefaultPocData {
Image? image;
String? title;
DefaultPocData({
this.image,
this.title,
});
factory DefaultPocData.fromRawJson(String str) => DefaultPocData.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory DefaultPocData.fromJson(Map<String, dynamic> json) => DefaultPocData(
image: json["image"] == null ? null : Image.fromJson(json["image"]),
title: json["title"],
);
Map<String, dynamic> toJson() => {
"image": image?.toJson(),
"title": title,
};
}
class Image {
String? thumbUrl;
String? url;
Image({
this.thumbUrl,
this.url,
});
factory Image.fromRawJson(String str) => Image.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Image.fromJson(Map<String, dynamic> json) => Image(
thumbUrl: json["thumb_url"],
url: json["url"],
);
Map<String, dynamic> toJson() => {
"thumb_url": thumbUrl,
"url": url,
};
}
class Stats {
int? attachmentsCount;
int? contentCount;
int? contentReadCount;
int? educationalContentCount;
int? educationalContentReadCount;
int? educationalReadPercentage;
bool? isCompleted;
int? readPercentage;
int? readRemainingPercentage;
int? screenshotsCount;
int? topicCount;
int? videosCount;
int? videosReadCount;
Stats({
this.attachmentsCount,
this.contentCount,
this.contentReadCount,
this.educationalContentCount,
this.educationalContentReadCount,
this.educationalReadPercentage,
this.isCompleted,
this.readPercentage,
this.readRemainingPercentage,
this.screenshotsCount,
this.topicCount,
this.videosCount,
this.videosReadCount,
});
factory Stats.fromRawJson(String str) => Stats.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Stats.fromJson(Map<String, dynamic> json) => Stats(
attachmentsCount: json["attachments_count"],
contentCount: json["content_count"],
contentReadCount: json["content_read_count"],
educationalContentCount: json["educational_content_count"],
educationalContentReadCount: json["educational_content_read_count"],
educationalReadPercentage: json["educational_read_percentage"],
isCompleted: json["is_completed"],
readPercentage: json["read_percentage"],
readRemainingPercentage: json["read_remaining_percentage"],
screenshotsCount: json["screenshots_count"],
topicCount: json["topic_count"],
videosCount: json["videos_count"],
videosReadCount: json["videos_read_count"],
);
Map<String, dynamic> toJson() => {
"attachments_count": attachmentsCount,
"content_count": contentCount,
"content_read_count": contentReadCount,
"educational_content_count": educationalContentCount,
"educational_content_read_count": educationalContentReadCount,
"educational_read_percentage": educationalReadPercentage,
"is_completed": isCompleted,
"read_percentage": readPercentage,
"read_remaining_percentage": readRemainingPercentage,
"screenshots_count": screenshotsCount,
"topic_count": topicCount,
"videos_count": videosCount,
"videos_read_count": videosReadCount,
};
}
class TagValues {
String? consultationCode;
String? title;
String? titleAr;
TagValues({
this.consultationCode,
this.title,
this.titleAr,
});
factory TagValues.fromRawJson(String str) => TagValues.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory TagValues.fromJson(Map<String, dynamic> json) => TagValues(
consultationCode: json["consultation_code"],
title: json["title"],
titleAr: json["title_ar"],
);
Map<String, dynamic> toJson() => {
"consultation_code": consultationCode,
"title": title,
"title_ar": titleAr,
};
}
class ContentClass {
int? id;
Image? image;
int? readPercentage;
String? title;
List<Topic>? topics;
String? type;
ContentClass({
this.id,
this.image,
this.readPercentage,
this.title,
this.topics,
this.type,
});
factory ContentClass.fromRawJson(String str) => ContentClass.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory ContentClass.fromJson(Map<String, dynamic> json) => ContentClass(
id: json["id"],
image: json["image"] == null ? null : Image.fromJson(json["image"]),
readPercentage: json["read_percentage"],
title: json["title"],
topics: json["topics"] == null ? [] : List<Topic>.from(json["topics"]!.map((x) => Topic.fromJson(x))),
type: json["type"],
);
Map<String, dynamic> toJson() => {
"id": id,
"image": image?.toJson(),
"read_percentage": readPercentage,
"title": title,
"topics": topics == null ? [] : List<dynamic>.from(topics!.map((x) => x.toJson())),
"type": type,
};
}
class Topic {
List<Content>? contents;
int? contentsCount;
int? id;
Image? image;
int? readContentsCount;
int? readPercentage;
String? subjectId;
String? title;
Topic({
this.contents,
this.contentsCount,
this.id,
this.image,
this.readContentsCount,
this.readPercentage,
this.subjectId,
this.title,
});
factory Topic.fromRawJson(String str) => Topic.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Topic.fromJson(Map<String, dynamic> json) => Topic(
contents: json["contents"] == null ? [] : List<Content>.from(json["contents"]!.map((x) => Content.fromJson(x))),
contentsCount: json["contents_count"],
id: json["id"],
image: json["image"] == null ? null : Image.fromJson(json["image"]),
readContentsCount: json["read_contents_count"],
readPercentage: json["read_percentage"],
subjectId: json["subject_id"],
title: json["title"],
);
Map<String, dynamic> toJson() => {
"contents": contents == null ? [] : List<dynamic>.from(contents!.map((x) => x.toJson())),
"contents_count": contentsCount,
"id": id,
"image": image?.toJson(),
"read_contents_count": readContentsCount,
"read_percentage": readPercentage,
"subject_id": subjectId,
"title": title,
};
}
enum VideoPlayerState { playing, paused, completed, loading }
class Content {
String? body;
int? id;
Question? question;
dynamic read;
String? subjectId;
String? title;
Video? video;
VideoPlayerController? controller;
VideoPlayerState? videoState;
double? viewedPercentage;
Content({this.body, this.id, this.question, this.read, this.subjectId, this.title, this.video, this.controller, this.videoState, this.viewedPercentage});
factory Content.fromRawJson(String str) => Content.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Content.fromJson(Map<String, dynamic> json) => Content(
body: json["body"],
id: json["id"],
question: json["question"] == null ? null : Question.fromJson(json["question"]),
read: json["read"],
subjectId: json["subject_id"],
title: json["title"],
video: json["video"] == null ? null : Video.fromJson(json["video"]),
controller: null,
videoState: VideoPlayerState.paused,
viewedPercentage: 0.0,
);
Map<String, dynamic> toJson() => {
"body": body,
"id": id,
"question": question?.toJson(),
"read": read,
"subject_id": subjectId,
"title": title,
"video": video?.toJson(),
};
}
class Question {
List<Answer>? answers;
int? id;
bool? shouldAnswer;
String? subjectId;
String? text;
int? triggerAt;
Question({
this.answers,
this.id,
this.shouldAnswer,
this.subjectId,
this.text,
this.triggerAt,
});
factory Question.fromRawJson(String str) => Question.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Question.fromJson(Map<String, dynamic> json) => Question(
answers: json["answers"] == null ? [] : List<Answer>.from(json["answers"]!.map((x) => Answer.fromJson(x))),
id: json["id"],
shouldAnswer: json["should_answer"],
subjectId: json["subject_id"],
text: json["text"],
triggerAt: json["trigger_at"],
);
Map<String, dynamic> toJson() => {
"answers": answers == null ? [] : List<dynamic>.from(answers!.map((x) => x.toJson())),
"id": id,
"should_answer": shouldAnswer,
"subject_id": subjectId,
"text": text,
"trigger_at": triggerAt,
};
}
class Answer {
String? description;
int? id;
bool? isCorrect;
String? text;
Answer({
this.description,
this.id,
this.isCorrect,
this.text,
});
factory Answer.fromRawJson(String str) => Answer.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Answer.fromJson(Map<String, dynamic> json) => Answer(
description: json["description"],
id: json["id"],
isCorrect: json["is_correct"],
text: json["text"],
);
Map<String, dynamic> toJson() => {
"description": description,
"id": id,
"is_correct": isCorrect,
"text": text,
};
}
class Video {
Flavor? flavor;
Video({
this.flavor,
});
factory Video.fromRawJson(String str) => Video.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Video.fromJson(Map<String, dynamic> json) => Video(
flavor: json["flavor"] == null ? null : Flavor.fromJson(json["flavor"]),
);
Map<String, dynamic> toJson() => {
"flavor": flavor?.toJson(),
};
}
class Flavor {
String? downloadable;
int? duration;
int? flavorId;
String? hls;
String? picture;
Flavor({
this.downloadable,
this.duration,
this.flavorId,
this.hls,
this.picture,
});
factory Flavor.fromRawJson(String str) => Flavor.fromJson(json.decode(str));
String toRawJson() => json.encode(toJson());
factory Flavor.fromJson(Map<String, dynamic> json) => Flavor(
downloadable: json["downloadable"],
duration: json["duration"],
flavorId: json["flavor_id"],
hls: json["hls"],
picture: json["picture"],
);
Map<String, dynamic> toJson() => {
"downloadable": downloadable,
"duration": duration,
"flavor_id": flavorId,
"hls": hls,
"picture": picture,
};
}

@ -576,7 +576,7 @@ class _ToDoState extends State<ToDo> with SingleTickerProviderStateMixin {
Permission.bluetoothScan, Permission.bluetoothScan,
Permission.activityRecognition, Permission.activityRecognition,
].request().whenComplete(() { ].request().whenComplete(() {
PenguinMethodChannel.launch("penguin", projectViewModel.isArabic ? "ar" : "en", projectViewModel.authenticatedUserObject.user.patientID.toString(), details: data); PenguinMethodChannel().launch("penguin", projectViewModel.isArabic ? "ar" : "en", projectViewModel.authenticatedUserObject.user.patientID.toString(), details: data);
}); });
} }
} }

@ -22,8 +22,7 @@ import 'package:diplomaticquarterapp/pages/rateAppointment/rate_appointment_doct
import 'package:diplomaticquarterapp/pages/videocall-webrtc-rnd/webrtc/start_video_call.dart'; import 'package:diplomaticquarterapp/pages/videocall-webrtc-rnd/webrtc/start_video_call.dart';
import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart'; import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart';
import 'package:diplomaticquarterapp/services/clinic_services/get_clinic_service.dart'; import 'package:diplomaticquarterapp/services/clinic_services/get_clinic_service.dart';
import 'package:diplomaticquarterapp/services/family_files/family_files_provider.dart' import 'package:diplomaticquarterapp/services/family_files/family_files_provider.dart' as family;
as family;
import 'package:diplomaticquarterapp/services/livecare_services/livecare_provider.dart'; import 'package:diplomaticquarterapp/services/livecare_services/livecare_provider.dart';
import 'package:diplomaticquarterapp/services/payfort_services/payfort_view_model.dart'; import 'package:diplomaticquarterapp/services/payfort_services/payfort_view_model.dart';
import 'package:diplomaticquarterapp/services/robo_search/event_provider.dart'; import 'package:diplomaticquarterapp/services/robo_search/event_provider.dart';
@ -44,6 +43,7 @@ import 'package:diplomaticquarterapp/widgets/transitions/fade_page.dart';
import 'package:firebase_messaging/firebase_messaging.dart'; import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
// import 'package:flutter_app_icon_badge/flutter_app_icon_badge.dart'; // import 'package:flutter_app_icon_badge/flutter_app_icon_badge.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_local_notifications/flutter_local_notifications.dart';
import 'package:flutter_svg/flutter_svg.dart'; import 'package:flutter_svg/flutter_svg.dart';
@ -548,12 +548,19 @@ class _LandingPageState extends State<LandingPage> with WidgetsBindingObserver {
}, },
), ),
actions: [ actions: [
// IconButton( IconButton(
// //iconSize: 70, //iconSize: 70,
// icon: Icon( icon: Icon(
// projectViewModel.isLogin ? Icons.settings : Icons.login, Icons.video_call,
// color: Theme.of(context).textTheme.headline1.color, size: 30,
// ), color: Colors.red,
),
onPressed: () {
Navigator.of(context).pushNamed(
COURSES_LIST_PAGE,
);
},
),
// onPressed: () { // onPressed: () {
// if (projectViewModel.isLogin) // if (projectViewModel.isLogin)
// Navigator.of(context).pushNamed( // Navigator.of(context).pushNamed(

@ -0,0 +1,52 @@
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
class ContentWidget extends StatefulWidget {
final String body;
ContentWidget({required this.body});
@override
_ContentWidgetState createState() => _ContentWidgetState();
}
class _ContentWidgetState extends State<ContentWidget> {
bool isExpanded = false;
@override
Widget build(BuildContext context) {
final truncatedText = widget.body.length > 70 ? '${widget.body.substring(0, 70)}... ' : widget.body;
return RichText(
text: TextSpan(
children: [
TextSpan(
text: isExpanded ? widget.body : truncatedText,
style: TextStyle(
color: Color(0xFFA2A2A2),
fontSize: 13,
fontWeight: FontWeight.w400,
height: 13 / 10,
),
),
if (widget.body.length > 100)
TextSpan(
text: isExpanded ? 'See Less' : 'See More',
style: TextStyle(
color: Color(0xFF2B353E),
fontSize: 13,
fontWeight: FontWeight.w500,
height: 13 / 10,
),
recognizer: TapGestureRecognizer()
..onTap = () {
setState(() {
isExpanded = !isExpanded;
});
},
),
],
),
);
}
}

@ -0,0 +1,317 @@
import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:diplomaticquarterapp/core/viewModels/project_view_model.dart';
import 'package:diplomaticquarterapp/extensions/string_extensions.dart';
import 'package:diplomaticquarterapp/models/course/education_journey_model.dart';
import 'package:diplomaticquarterapp/pages/learning/content_widget.dart';
import 'package:diplomaticquarterapp/pages/learning/measureWidget.dart';
import 'package:diplomaticquarterapp/pages/learning/progress_bar_widget.dart';
import 'package:diplomaticquarterapp/pages/learning/question_sheet.dart';
import 'package:diplomaticquarterapp/pages/learning/scroll_widget.dart';
import 'package:diplomaticquarterapp/services/course_service/course_service.dart';
import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:video_player/video_player.dart';
import 'package:visibility_detector/visibility_detector.dart';
import 'widgets/player_controlls.dart';
class CourseDetailedPage extends StatefulWidget {
@override
State<CourseDetailedPage> createState() => _CourseDetailedPageState();
}
class _CourseDetailedPageState extends State<CourseDetailedPage> {
@override
void initState() {
super.initState();
context.read<CourseServiceProvider>().getCourseById(context);
}
@override
void dispose() {
super.dispose();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
isShowAppBar: false,
isShowDecPage: false,
showNewAppBarTitle: true,
showNewAppBar: true,
appBarTitle: context.read<CourseServiceProvider>().getPageTitle ?? "",
backgroundColor: Color(0xFFF7F7F7),
onTap: () {},
body: Consumer<CourseServiceProvider>(
builder: (context, provider, child) {
if (provider.courseData != null) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (provider.controller != null && provider.controller!.value.isInitialized) ...[
Column(
children: [
AspectRatio(
aspectRatio: provider.controller!.value.aspectRatio,
child: Stack(
alignment: Alignment.bottomCenter,
children: [
VideoPlayer(provider.controller!),
PlayerControlsOverlay(),
VideoProgressIndicator(
provider.controller!,
padding: EdgeInsets.only(bottom: 0),
colors: VideoProgressColors(
backgroundColor: Color(0xFFD9D9D9),
bufferedColor: Colors.black12,
playedColor: Color(0xFFD02127),
),
allowScrubbing: true,
),
],
),
).onTap(() {
setState(() {
if (provider.controller!.value.isPlaying) {
provider.controller!.pause();
provider.playedContent!.videoState = VideoPlayerState.paused;
} else {
provider.controller!.play();
provider.playedContent!.videoState = VideoPlayerState.playing;
}
});
}),
],
)
] else ...[
CachedNetworkImage(
width: double.infinity,
height: MediaQuery.of(context).size.height * .265,
imageUrl: provider.courseData!.nabedJourneyByIdResponseResult!.consultation!.defaultPocData!.image!.thumbUrl!,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
),
),
child: provider.controller != null && !provider.controller!.value.isLooping
? Center(
child: CircularProgressIndicator(
color: Color(0xFFD02127),
),
)
: provider.controller != null
? Center(
child: Icon(
Icons.play_circle,
size: 35,
color: Colors.white,
),
)
: SizedBox(),
),
placeholder: (context, url) => Center(
child: CircularProgressIndicator(),
),
errorWidget: (context, url, error) => Icon(Icons.error),
).onTap(() {
setState(() {
if (provider.controller != null) {
provider.controller!.value.isPlaying ? provider.controller!.pause() : provider.controller!.play();
}
});
}),
],
if (provider.consultation != null) ...[
Padding(
padding: const EdgeInsets.symmetric(horizontal: 20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 20.0),
Text(
context.read<ProjectViewModel>().isArabic ? provider.consultation!.tagValues!.titleAr! : provider.consultation!.tagValues!.title ?? "Basics of Heart",
style: TextStyle(
fontSize: 16.0,
height: 24 / 16,
color: Color(0xFF2E303A),
fontWeight: FontWeight.w600,
),
),
// SizedBox(height: 2.0),
// Text(
// "Heart diseases include conditions like coronary and heart failure, often caused by factors ",
// style: TextStyle(fontSize: 10.0, height: 15 / 10, color: Color(0xFF575757)),
// ),
// SizedBox(height: 6.0),
Text(
provider.consultation!.stats!.videosCount.toString() + " Videos",
style: TextStyle(
fontSize: 10.0,
height: 15 / 10,
color: Color(0xFF2E303A),
fontWeight: FontWeight.w600,
),
),
],
),
),
],
SizedBox(height: 20.0),
if (provider.courseTopics != null) ...[
Expanded(
child: ListViewSeparatedExtension.separatedWithScrollListener(
itemCount: provider.courseTopics!.length,
shrinkWrap: true,
physics: AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.symmetric(horizontal: 20),
itemBuilder: (context, ind) {
Topic topic = provider.courseTopics![ind];
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.max,
children: [
// Topic title
Text(
(topic.title.toString() + "(${topic.contentsCount.toString()})") ?? "Topic",
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.bold,
color: Color(0xFF2B353E),
),
),
SizedBox(height: 8),
ListViewSeparatedExtension.separatedWithScrollListener(
itemCount: topic.contents!.length,
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
final content = topic.contents![index];
var percentage = content.viewedPercentage != 0.0 ? content.viewedPercentage : provider.convertToDoublePercentage(content.read ?? 0);
return VisibilityDetector(
key: Key(content.id.toString()),
onVisibilityChanged: (visibilityInfo) => provider.onVisibilityChange(visibilityInfo, content, topic.id!),
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(index.toString()),
SizedBox(
width: 9,
),
Expanded(
flex: 10,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
content.title ?? "Course overview",
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w600,
color: Color(0xFF2B353E),
),
),
Text(
"Video - ${provider.getDurationOfVideo(content.video!.flavor!.duration!)}",
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
color: Color(0xFF575757),
),
),
ContentWidget(
body: content.body!,
)
],
),
),
Expanded(
flex: 4,
child: Column(
children: [
SizedBox(
height: 10,
),
Row(
crossAxisAlignment: CrossAxisAlignment.center,
mainAxisAlignment: MainAxisAlignment.end,
children: [
CustomProgressBar(
playedPercentage: percentage!,
bufferedPercentage: 0.0,
backgroundColor: Color(0xFFD9D9D9),
bufferedColor: Colors.black12,
playedColor: Color(0xFF359846),
),
SizedBox(
width: 10,
),
Container(
width: 20,
height: 20,
decoration: BoxDecoration(
color: Color(0xFFDFDFDF),
borderRadius: BorderRadius.all(
Radius.circular(30),
),
),
child: Icon(
content.videoState == VideoPlayerState.loading
? Icons.hourglass_top
: content.videoState == VideoPlayerState.playing
? Icons.pause
: content.videoState == VideoPlayerState.paused
? Icons.play_arrow
: content.videoState == VideoPlayerState.completed
? Icons.replay
: Icons.play_arrow,
// Use specific content's state
size: 18,
).onTap(() {
setState(() {
provider.onVisibilityChange(null, content, topic.id!, isClicked: true);
provider.play(context, content: content);
});
}),
)
],
)
],
),
),
],
),
),
);
},
separatorBuilder: (BuildContext context, int index) {
return SizedBox();
},
// onScroll: onScroll,
),
],
);
},
separatorBuilder: (BuildContext context, int index) {
return SizedBox();
},
),
),
]
],
);
} else {
return SizedBox();
}
},
),
);
}
}

@ -0,0 +1,206 @@
import 'dart:async';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:diplomaticquarterapp/core/enum/viewstate.dart';
import 'package:diplomaticquarterapp/extensions/string_extensions.dart';
import 'package:diplomaticquarterapp/models/course/education_journey_list_model.dart' as model;
import 'package:diplomaticquarterapp/pages/learning/scroll_widget.dart';
import 'package:diplomaticquarterapp/routes.dart';
import 'package:diplomaticquarterapp/services/course_service/course_service.dart';
import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart';
import 'package:diplomaticquarterapp/widgets/others/app_scaffold_widget.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class CourseList extends StatefulWidget {
@override
State<CourseList> createState() => _CourseListState();
}
class _CourseListState extends State<CourseList> {
CourseServiceProvider? _courseServiceProvider;
GlobalKey _cardKey = GlobalKey();
@override
void initState() {
super.initState();
_courseServiceProvider = context.read<CourseServiceProvider>();
_courseServiceProvider!.getCourses(context);
}
@override
void dispose() {
_clearData();
super.dispose();
}
Future<void> _clearData() async {
await _courseServiceProvider!.postNabedJourneyData();
await _courseServiceProvider!.clearData();
}
@override
Widget build(BuildContext context) {
return AppScaffold(
isShowAppBar: false,
isShowDecPage: false,
showNewAppBarTitle: true,
showNewAppBar: true,
appBarTitle: "Learning",
backgroundColor: Color(0xFFF7F7F7),
onTap: () {},
body: Consumer<CourseServiceProvider>(builder: (context, provider, child) {
if (provider.nabedJourneyResponse != null) {
return ListViewSeparatedExtension.separatedWithScrollListener(
itemCount: provider.data!.length,
itemBuilder: (context, index) {
List<model.ContentClass> conClass = provider.data![index].contentClasses!;
model.Datum data = provider.data![index];
return Card(
key: index == 0 ? _cardKey : null,
margin: EdgeInsets.only(left: 20, right: 20, top: 20),
shape: RoundedRectangleBorder(
side: BorderSide(width: 1, color: Color(0xFFEFEFEF)),
borderRadius: BorderRadius.circular(10.0), // Set your desired radius here
),
child: Padding(
padding: EdgeInsets.all(12.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(10),
child: CachedNetworkImage(
imageUrl: conClass.first.image!.thumbUrl!,
width: double.infinity,
height: MediaQuery.of(context).size.height * .25,
imageBuilder: (context, imageProvider) => Container(
decoration: BoxDecoration(
image: DecorationImage(
image: imageProvider,
fit: BoxFit.cover,
//colorFilter: ColorFilter.mode(Colors.red, BlendMode.colorBurn),
),
),
),
placeholder: (context, url) => Center(child: CircularProgressIndicator()),
errorWidget: (context, url, error) => Icon(Icons.error),
),
),
SizedBox(height: 20.0),
Text(
conClass.first.title ?? "",
// "Basics of Heart",
style: TextStyle(
fontSize: 16.0,
height: 24 / 16,
color: Color(0xFF2E303A),
fontWeight: FontWeight.w600,
),
),
SizedBox(height: 2.0),
Text(
//data.date!.toIso8601String(),
"Heart diseases include conditions like coronary and heart failure, often caused by factors ",
style: TextStyle(fontSize: 10.0, height: 15 / 10, color: Color(0xFF575757)),
),
SizedBox(height: 6.0),
Text(
"${data.stats!.videosReadCount} of ${data.stats!.videosCount} Watched",
// conClass.first.readPercentage.toString() + "% Watched",
// "2 Hours",
style: TextStyle(
fontSize: 10.0,
height: 15 / 10,
color: Color(0xFF2E303A),
fontWeight: FontWeight.w600,
),
),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
Icon(
Icons.play_arrow,
),
Text(
"${data.stats!.videosCount} Videos",
// conClass.first.readPercentage.toString() + "% Watched",
// "2 Hours",
style: TextStyle(
fontSize: 10.0,
height: 15 / 10,
color: Color(0xFF2E303A),
fontWeight: FontWeight.w600,
),
),
],
),
SizedBox(
width: 8,
),
Icon(
Icons.arrow_right_alt,
size: 18.0,
color: Color(0xFF2B353E),
),
],
),
],
),
),
).onTap(() {
provider.insertJourneyListData(index, true);
provider.navigate(context, data.id!, conClass.first.title!, onBack: (val) {
// provider.uploadStats();
provider.clear();
});
});
},
separatorBuilder: (context, index) {
return SizedBox();
},
onScroll: onScroll,
);
} else {
return SizedBox();
}
}),
);
}
final Set<int> _visibleCardIds = {};
void onScroll(ScrollNotification notification, ScrollController controller, int itemCount) {
double _cardHeight = _getCardSize();
if (_cardHeight > 0) {
final double offset = controller.offset;
final double screenHeight = MediaQuery.of(context).size.height;
int firstVisibleIndex = (offset / _cardHeight).floor();
int lastVisibleIndex = ((offset + screenHeight) / _cardHeight).ceil();
Set<int> newlyVisibleCards = {};
for (int index = firstVisibleIndex; index <= lastVisibleIndex && index < itemCount; index++) {
final double cardStartOffset = index * _cardHeight;
final double cardEndOffset = cardStartOffset + _cardHeight;
if (cardStartOffset >= offset && cardEndOffset <= offset + screenHeight) {
if (!_visibleCardIds.contains(index)) {
newlyVisibleCards.add(index);
_courseServiceProvider!.insertJourneyListData(index, false);
}
}
}
_visibleCardIds.addAll(newlyVisibleCards);
}
}
double _getCardSize() {
final RenderBox renderBox = _cardKey.currentContext?.findRenderObject() as RenderBox;
final cardHeight = renderBox.size.height;
return cardHeight;
}
}

@ -0,0 +1,28 @@
import 'package:flutter/material.dart';
typedef OnWidgetSizeChange = void Function(Size size);
class MeasureSize extends StatefulWidget {
final Widget child;
final OnWidgetSizeChange onChange;
const MeasureSize({
Key? key,
required this.onChange,
required this.child,
}) : super(key: key);
@override
_MeasureSizeState createState() => _MeasureSizeState();
}
class _MeasureSizeState extends State<MeasureSize> {
@override
Widget build(BuildContext context) {
WidgetsBinding.instance.addPostFrameCallback((_) {
final size = context.size;
if (size != null) widget.onChange(size);
});
return widget.child;
}
}

@ -0,0 +1,50 @@
import 'package:flutter/material.dart';
class CustomProgressBar extends StatelessWidget {
final double playedPercentage; // value between 0.0 and 1.0
final double bufferedPercentage; // value between 0.0 and 1.0
final Color backgroundColor;
final Color bufferedColor;
final Color playedColor;
const CustomProgressBar({
Key? key,
required this.playedPercentage,
required this.bufferedPercentage,
this.backgroundColor = const Color(0xFFD9D9D9),
this.bufferedColor = Colors.black12,
this.playedColor = const Color(0xFF359846),
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
width: 55,
height: 4,
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(2),
),
child: Stack(
children: [
// Buffered bar
Container(
width: 55 * bufferedPercentage.clamp(0.0, 1.0), // Buffered width as a percentage of 55
decoration: BoxDecoration(
color: bufferedColor,
borderRadius: BorderRadius.circular(2),
),
),
// Played bar
Container(
width: 55 * playedPercentage.clamp(0.0, 1.0), // Played width as a percentage of 55
decoration: BoxDecoration(
color: playedColor,
borderRadius: BorderRadius.circular(2),
),
),
],
),
);
}
}

@ -0,0 +1,222 @@
import 'package:diplomaticquarterapp/models/course/education_journey_model.dart';
import 'package:diplomaticquarterapp/services/course_service/course_service.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
enum QuestionSheetAction { skip, rewatch }
class QuestionSheet extends StatefulWidget {
final Content content;
final Function(QuestionSheetAction) onActionSelected;
const QuestionSheet({Key? key, required this.content, required this.onActionSelected}) : super(key: key);
@override
_QuestionSheetState createState() => _QuestionSheetState();
}
class _QuestionSheetState extends State<QuestionSheet> {
int? selectedAnswerId;
Answer? selectedAnswer;
@override
Widget build(BuildContext context) {
return AnimatedPadding(
duration: Duration(milliseconds: 300),
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(height: 10),
_QuestionText(questionText: widget.content.question!.text!),
SizedBox(height: 20),
...widget.content.question!.answers!.map((answer) => _AnswerOption(
answer: answer,
isSelected: selectedAnswerId == answer.id,
onSelect: () {
if (selectedAnswer == null) {
setState(() {
selectedAnswerId = answer.id;
selectedAnswer = answer;
});
}
},
)),
SizedBox(height: 20),
if (selectedAnswer != null)
_AnswerDescription(
description: selectedAnswer!.description!,
isCorrect: selectedAnswer!.isCorrect!,
),
SizedBox(height: 20),
if (selectedAnswer != null)
_ActionButtons(
onSkip: () => widget.onActionSelected(QuestionSheetAction.skip),
onRewatch: () => widget.onActionSelected(QuestionSheetAction.rewatch),
),
SizedBox(height: 10),
],
),
),
);
}
}
class _QuestionText extends StatelessWidget {
final String questionText;
const _QuestionText({Key? key, required this.questionText}) : super(key: key);
@override
Widget build(BuildContext context) {
return Text(
questionText,
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
);
}
}
class _AnswerOption extends StatelessWidget {
final Answer answer;
final bool isSelected;
final VoidCallback onSelect;
const _AnswerOption({
Key? key,
required this.answer,
required this.isSelected,
required this.onSelect,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AnimatedContainer(
width: double.infinity,
duration: Duration(milliseconds: 300),
margin: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
border: Border.all(
color: isSelected ? (answer.isCorrect! ? Colors.green : Color(0xFFD1272D)) : Colors.grey,
width: 2,
),
borderRadius: BorderRadius.circular(10),
),
child: TextButton(
onPressed: onSelect,
child: Text(
answer.text!,
style: TextStyle(fontSize: 16),
),
),
);
}
}
class _AnswerDescription extends StatelessWidget {
final String description;
final bool isCorrect;
const _AnswerDescription({
Key? key,
required this.description,
required this.isCorrect,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: Text(
description,
style: TextStyle(
color: isCorrect ? Colors.green : Color(0xFFD1272D),
),
),
);
}
}
class _ActionButtons extends StatelessWidget {
final VoidCallback onSkip;
final VoidCallback onRewatch;
const _ActionButtons({
Key? key,
required this.onSkip,
required this.onRewatch,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
Expanded(
child: _ActionButton(
label: "SKIP",
onPressed: onSkip,
),
),
SizedBox(width: 20),
Expanded(
child: _ActionButton(
label: "RE-WATCH",
onPressed: onRewatch,
),
),
],
);
}
}
class _ActionButton extends StatelessWidget {
final String label;
final VoidCallback onPressed;
const _ActionButton({
Key? key,
required this.label,
required this.onPressed,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return AnimatedContainer(
width: double.infinity,
duration: Duration(milliseconds: 300),
margin: EdgeInsets.symmetric(vertical: 5),
decoration: BoxDecoration(
border: Border.all(color: Color(0xFFD1272D), width: 2),
borderRadius: BorderRadius.circular(10),
),
child: TextButton(
onPressed: onPressed,
child: Text(
label,
style: TextStyle(fontSize: 16),
),
),
);
}
}
// Modified showQuestionSheet function
Future<QuestionSheetAction?> showQuestionSheet(BuildContext con, {required Content content}) {
return showModalBottomSheet<QuestionSheetAction>(
context: con,
isDismissible: false,
isScrollControlled: true,
shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20))),
builder: (context) => QuestionSheet(
content: content,
onActionSelected: (action) {
Navigator.of(context).pop(action);
},
),
);
}

@ -0,0 +1,36 @@
import 'package:flutter/material.dart';
typedef ScrollCallback = void Function(ScrollNotification notification, ScrollController controller, int itemCount);
extension ListViewSeparatedExtension on ListView {
static Widget separatedWithScrollListener({
required IndexedWidgetBuilder itemBuilder,
required IndexedWidgetBuilder separatorBuilder,
required int itemCount,
bool shrinkWrap = false,
EdgeInsetsGeometry padding = EdgeInsets.zero,
ScrollCallback? onScroll,
ScrollController? controller,
ScrollPhysics? physics,
}) {
final ScrollController _scrollController = controller ?? ScrollController();
return NotificationListener<ScrollNotification>(
onNotification: (ScrollNotification notification) {
if (onScroll != null) {
onScroll(notification, _scrollController, itemCount);
}
return false;
},
child: ListView.separated(
controller: _scrollController,
itemBuilder: itemBuilder,
padding: padding,
separatorBuilder: separatorBuilder,
physics: physics,
shrinkWrap: shrinkWrap,
itemCount: itemCount,
),
);
}
}

@ -0,0 +1,39 @@
import 'package:diplomaticquarterapp/models/course/education_journey_model.dart';
import 'package:diplomaticquarterapp/services/course_service/course_service.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class PlayerControlsOverlay extends StatefulWidget {
@override
State<PlayerControlsOverlay> createState() => _PlayerControlsOverlayState();
}
class _PlayerControlsOverlayState extends State<PlayerControlsOverlay> {
@override
Widget build(BuildContext context) {
return Consumer<CourseServiceProvider>(builder: (context, provider, child) {
return Stack(
children: <Widget>[
AnimatedSwitcher(
duration: const Duration(milliseconds: 50),
reverseDuration: const Duration(milliseconds: 200),
child: provider.controller!.value.isPlaying
? const SizedBox.shrink()
: const ColoredBox(
color: Colors.black26,
child: Center(
child: Icon(
Icons.play_arrow,
color: Colors.white,
size: 50.0,
semanticLabel: 'Play',
),
),
),
),
],
);
});
}
}

@ -7,6 +7,8 @@ import 'package:diplomaticquarterapp/pages/TestPage.dart';
import 'package:diplomaticquarterapp/pages/appUpdatePage/app_update_page.dart'; import 'package:diplomaticquarterapp/pages/appUpdatePage/app_update_page.dart';
import 'package:diplomaticquarterapp/pages/conference/zoom/call_screen.dart'; import 'package:diplomaticquarterapp/pages/conference/zoom/call_screen.dart';
import 'package:diplomaticquarterapp/pages/landing/landing_page.dart'; import 'package:diplomaticquarterapp/pages/landing/landing_page.dart';
import 'package:diplomaticquarterapp/pages/learning/course_detailed.dart';
import 'package:diplomaticquarterapp/pages/learning/course_list.dart';
import 'package:diplomaticquarterapp/pages/livecare/incoming_call.dart'; import 'package:diplomaticquarterapp/pages/livecare/incoming_call.dart';
import 'package:diplomaticquarterapp/pages/livecare/livecare_home.dart'; import 'package:diplomaticquarterapp/pages/livecare/livecare_home.dart';
import 'package:diplomaticquarterapp/pages/login/confirm-login.dart'; import 'package:diplomaticquarterapp/pages/login/confirm-login.dart';
@ -60,6 +62,8 @@ const String HEALTH_WEATHER = 'health-weather';
const APP_UPDATE = 'app-update'; const APP_UPDATE = 'app-update';
const ZOOM_CALL_PAGE = 'zoom_call_page'; const ZOOM_CALL_PAGE = 'zoom_call_page';
const COURSES_LIST_PAGE = 'courses_list_page';
const COURSES_DETAILED_PAGE = 'courses_detailed_page';
var routes = { var routes = {
SPLASH: (_) => SplashScreen(), SPLASH: (_) => SplashScreen(),
@ -91,6 +95,9 @@ var routes = {
ZOOM_CALL_PAGE: (_) => CallScreen(), ZOOM_CALL_PAGE: (_) => CallScreen(),
COURSES_LIST_PAGE: (_) => CourseList(),
COURSES_DETAILED_PAGE: (_) => CourseDetailedPage(),
OPENTOK_CALL_PAGE: (_) => OpenTokConnectCallPage( OPENTOK_CALL_PAGE: (_) => OpenTokConnectCallPage(
apiKey: OPENTOK_API_KEY, apiKey: OPENTOK_API_KEY,
sessionId: '1_MX40NjIwOTk2Mn5-MTY0NzE3MzcwNjA0MH55RUJoZnd0ZGh2U3BPc01ENVZBelQvT1Z-fg', sessionId: '1_MX40NjIwOTk2Mn5-MTY0NzE3MzcwNjA0MH55RUJoZnd0ZGh2U3BPc01ENVZBelQvT1Z-fg',

@ -0,0 +1,431 @@
import 'dart:async';
import 'dart:convert';
import 'package:diplomaticquarterapp/config/config.dart';
import 'package:diplomaticquarterapp/config/shared_pref_kay.dart';
import 'package:diplomaticquarterapp/core/service/client/base_app_client.dart';
import 'package:diplomaticquarterapp/models/Authentication/authenticated_user.dart';
import 'package:diplomaticquarterapp/models/course/education_journey_insert_model.dart';
import 'package:diplomaticquarterapp/models/course/education_journey_list_model.dart';
import 'package:diplomaticquarterapp/models/course/education_journey_model.dart' as ejm;
import 'package:diplomaticquarterapp/pages/learning/question_sheet.dart';
import 'package:diplomaticquarterapp/routes.dart';
import 'package:diplomaticquarterapp/services/authentication/auth_provider.dart';
import 'package:diplomaticquarterapp/uitl/app_shared_preferences.dart';
import 'package:diplomaticquarterapp/uitl/gif_loader_dialog_utils.dart';
import 'package:diplomaticquarterapp/uitl/utils.dart';
import 'package:flutter/material.dart';
import 'package:video_player/video_player.dart';
import 'package:visibility_detector/visibility_detector.dart';
class CourseServiceProvider with ChangeNotifier {
AppSharedPreferences sharedPref = AppSharedPreferences();
AppGlobal appGlobal = AppGlobal();
AuthenticatedUser authUser = AuthenticatedUser();
AuthProvider authProvider = AuthProvider();
bool isDataLoaded = false;
int? _selectedJourney;
int get getSelectedJourney => _selectedJourney!;
set setSelectedJourney(int value) {
_selectedJourney = value;
}
String? _pageTitle;
String? get getPageTitle => _pageTitle;
set setPageTitle(String? value) {
_pageTitle = value;
}
List<Datum>? data;
PatientEducationJourneyListModel? nabedJourneyResponse;
ejm.PatientEducationJourneyModel? courseData;
List<ejm.Topic>? courseTopics;
ejm.Consultation? consultation;
List<ejm.ContentClass>? contentClasses;
List<Data> nabedInsertDataPayload = [];
ejm.Content? playedContent;
VideoPlayerController? controller;
Timer? timer;
// Learning Page
void getCourses(BuildContext context) async {
GifLoaderDialogUtils.showMyDialog(context);
Map<String, dynamic> request;
if (await this.sharedPref.getObject(USER_PROFILE) != null) {
var data = AuthenticatedUser.fromJson(await this.sharedPref.getObject(USER_PROFILE));
authUser = data;
}
dynamic response;
//{"Channel": 3, "TokenID": "@dm!n", "PatientID": 22335};
request = {"Channel": 3};
await BaseAppClient().post(GET_PATIENT_COURSES_LIST, onSuccess: (res, statusCode) async {
response = PatientEducationJourneyListModel.fromJson(res);
}, onFailure: (String error, int statusCode) {
GifLoaderDialogUtils.hideDialog(context);
Utils.showErrorToast(error);
throw error;
}, body: request);
if (response.nabedJourneyResponseResult != null) {
GifLoaderDialogUtils.hideDialog(context);
nabedJourneyResponse = response;
isDataLoaded = true;
data = nabedJourneyResponse!.nabedJourneyResponseResult!.data;
}
notifyListeners();
}
// Detailed Page
void getCourseById(BuildContext context) async {
GifLoaderDialogUtils.showMyDialog(context);
Map<String, dynamic> request;
if (await this.sharedPref.getObject(USER_PROFILE) != null) {
var data = AuthenticatedUser.fromJson(await this.sharedPref.getObject(USER_PROFILE));
authUser = data;
}
dynamic response;
// "TokenID": "@dm!n",
request = {"Channel": 3, "JourneyID": getSelectedJourney};
await BaseAppClient().post(GET_PATIENT_COURSE_BY_ID, onSuccess: (res, statusCode) async {
response = res;
}, onFailure: (String error, int statusCode) {
GifLoaderDialogUtils.hideDialog(context);
Utils.showErrorToast(error);
throw error;
}, body: request);
GifLoaderDialogUtils.hideDialog(context);
if (response != null) {
courseData = ejm.PatientEducationJourneyModel.fromRawJson(jsonEncode(response));
courseTopics = courseData!.nabedJourneyByIdResponseResult!.contentClasses!.first.topics;
contentClasses = courseData!.nabedJourneyByIdResponseResult!.contentClasses;
consultation = courseData!.nabedJourneyByIdResponseResult!.consultation;
// Future.delayed(Duration(seconds: 1), () {
// insertDetailedJourneyListData();
// });
}
notifyListeners();
}
// Learning Page
Future<void> postNabedJourneyData() async {
// GifLoaderDialogUtils.showMyDialog(context);
Map<String, dynamic> request;
if (await this.sharedPref.getObject(USER_PROFILE) != null) {
var data = AuthenticatedUser.fromJson(await this.sharedPref.getObject(USER_PROFILE));
authUser = data;
}
dynamic response;
print(jsonEncode(nabedInsertDataPayload));
//"PatientID": 22335,
//"TokenID": "@dm!n",
request = {"LanguageID": 1, "data": nabedInsertDataPayload};
await BaseAppClient().post(INSERT_PATIENT_COURSE_VIEW_STATS, onSuccess: (res, statusCode) async {
print(res);
//response = PatientEducationJourneyListModel.fromJson(res);
}, onFailure: (String error, int statusCode) {
// GifLoaderDialogUtils.hideDialog(context);
Utils.showErrorToast(error);
throw error;
}, body: request);
notifyListeners();
}
Future navigate(BuildContext context, int journey, String title, {required Function(Object? val) onBack}) async {
setSelectedJourney = journey;
setPageTitle = title;
Navigator.of(context).pushNamed(COURSES_DETAILED_PAGE).then((val) {
onBack(val);
});
}
String getDurationOfVideo(int duration) {
final durationInSeconds = duration;
final minutes = durationInSeconds ~/ 60;
final seconds = durationInSeconds % 60;
return "$minutes:${seconds.toString().padLeft(2, '0')} mins";
}
void play(BuildContext context, {required ejm.Content content}) async {
switch (content.videoState) {
case ejm.VideoPlayerState.loading:
notifyListeners();
break;
case ejm.VideoPlayerState.playing:
controller?.pause();
content.videoState = ejm.VideoPlayerState.paused;
notifyListeners();
break;
case ejm.VideoPlayerState.paused:
if (controller != null && content.id == playedContent!.id) {
controller!.play();
playedContent!.videoState = ejm.VideoPlayerState.playing;
notifyListeners();
} else {
playVideo(context, content: content);
}
default:
playVideo(context, content: content);
break;
}
}
void playVideo(BuildContext context, {required ejm.Content content}) async {
if (controller != null && controller!.value.isPlaying) {
controller!.pause();
if (playedContent != null) {
playedContent!.videoState = ejm.VideoPlayerState.paused;
}
removeVideoListener(context);
controller = null;
}
content.videoState = ejm.VideoPlayerState.loading;
notifyListeners();
final videoUrl = content.video?.flavor?.downloadable ?? "";
if (videoUrl.isEmpty) {
Utils.showErrorToast("Something went wrong.");
content.videoState = ejm.VideoPlayerState.paused;
notifyListeners();
return;
}
controller = await VideoPlayerController.networkUrl(Uri.parse(videoUrl));
controller!.initialize()..then((value) => notifyListeners());
print("Controller Value = "+controller!.value.toString());
controller!.play();
addVideoListener(context);
content.videoState = ejm.VideoPlayerState.playing;
playedContent = content;
notifyListeners();
}
void addVideoListener(BuildContext context) {
controller?.removeListener(() => videoListener(context));
controller?.addListener(() => videoListener(context));
}
void removeVideoListener(BuildContext context) {
controller?.removeListener(() => videoListener(context));
}
int? lastProcessedSecond;
void videoListener(BuildContext context) async {
final currentSecond = controller!.value.position.inSeconds;
if (lastProcessedSecond != currentSecond) {
lastProcessedSecond = currentSecond;
Duration currentWatchedTime = controller!.value.position;
Duration totalVideoTime = controller!.value.duration;
if (totalVideoTime != null) {
playedContent!.viewedPercentage = await calculateWatchedPercentage(currentWatchedTime, totalVideoTime);
notifyListeners();
}
print(playedContent!.viewedPercentage);
if (currentSecond == playedContent!.question!.triggerAt) {
controller!.pause();
playedContent!.videoState = ejm.VideoPlayerState.paused;
notifyListeners();
QuestionSheetAction? action = await showQuestionSheet(context, content: playedContent!);
if (action == QuestionSheetAction.skip) {
controller!.play();
playedContent!.videoState = ejm.VideoPlayerState.playing;
} else if (action == QuestionSheetAction.rewatch) {
playVideo(context, content: playedContent!);
}
notifyListeners();
}
}
}
double calculateWatchedPercentage(Duration currentWatchedTime, Duration totalVideoTime) {
if (totalVideoTime.inSeconds == 0) {
return 0.0;
}
double percentage = (currentWatchedTime.inSeconds / totalVideoTime.inSeconds);
return percentage.clamp(0.0, 1.0); // Clamp the value between 0.0 and 1.0
}
int convertToIntegerPercentage(double percentage) {
return (percentage.clamp(0.0, 1.0) * 99 + 1).toInt();
}
double convertToDoublePercentage(int value) {
int clampedValue = value.clamp(1, 100);
return (clampedValue - 1) / 99.0;
}
void stopTimer() {
timer?.cancel();
timer = null;
}
Future<void> uploadStats() async {
if (courseTopics != null) {
nabedInsertDataPayload = [];
}
print("===============");
print(nabedInsertDataPayload.toString());
print("===============");
}
void insertJourneyListData(int index, bool isClicked) {
if (index >= 0 && index < data!.length) {
var datum = data![index];
bool journeyDisplayed = nabedInsertDataPayload.any((dataItem) => dataItem.type == UserAction.journeyDisplayed.toJson() && dataItem.consultationId == datum.id);
if (!journeyDisplayed) {
nabedInsertDataPayload.add(
Data(
type: UserAction.journeyDisplayed.toJson(),
consultationId: datum.id,
srcType: "educate-external",
screenType: "journey_listing_screen",
),
);
}
if (isClicked) {
bool journeyClick = nabedInsertDataPayload.any((dataItem) => dataItem.type == UserAction.journeyClick.toJson() && dataItem.consultationId == datum.id);
if (!journeyClick) {
nabedInsertDataPayload.add(
Data(
type: UserAction.journeyClick.toJson(),
consultationId: datum.id,
srcType: "educate-external",
screenType: "journey_listing_screen",
),
);
}
}
print(jsonEncode(nabedInsertDataPayload));
} else {
print("Index $index is out of bounds. Please provide a valid index.");
}
}
void onVisibilityChange(VisibilityInfo? visibilityInfo, ejm.Content content, int topicId, {bool isClicked = false}) {
int? contentClassID;
for (var data in contentClasses!) {
for (var topic in data.topics!) {
for (var con in topic.contents!) {
if (con.id == content.id) {
contentClassID = data.id;
}
}
}
}
if (!isClicked) {
var visiblePercentage = visibilityInfo!.visibleFraction * 100;
if (visiblePercentage == 100) {
bool isAddedBefore = nabedInsertDataPayload.any((dataItem) => dataItem.type == UserAction.contentDisplayed.toJson() && dataItem.contentId == content.id);
if (!isAddedBefore) {
nabedInsertDataPayload.add(
Data(
type: UserAction.contentDisplayed.toJson(),
consultationId: consultation!.id,
srcType: "educate-external",
screenType: "journey_details_screen",
flavorId: content.video!.flavor!.flavorId,
topicId: topicId,
contentId: content.id,
contentClassId: contentClassID ?? null,
),
);
}
}
}
if (isClicked) {
bool journeyClick = nabedInsertDataPayload.any((item) => item.type == UserAction.contentClick.toJson() && item.contentId == content.id);
if (!journeyClick) {
double percentage = content.viewedPercentage ?? 0.0;
nabedInsertDataPayload.add(
Data(
type: UserAction.contentClick.toJson(),
consultationId: consultation!.id,
srcType: "educate-external",
screenType: "journey_details_screen",
flavorId: content.video!.flavor!.flavorId,
topicId: topicId,
contentId: content.id,
percentage: convertToIntegerPercentage(percentage),
contentClassId: contentClassID ?? null,
),
);
}
bool isWatchBefore = nabedInsertDataPayload.any((item) => item.type == UserAction.contentWatch.toJson() && item.contentId == content.id);
if (!isWatchBefore) {
nabedInsertDataPayload.add(
Data(
type: UserAction.contentWatch.toJson(),
consultationId: consultation!.id,
srcType: "educate-external",
screenType: "journey_details_screen",
flavorId: content.video!.flavor!.flavorId,
topicId: topicId,
contentId: content.id,
contentClassId: contentClassID ?? null,
percentage: convertToIntegerPercentage(content.viewedPercentage!),
),
);
} else {
for (var dataItem in nabedInsertDataPayload) {
if (dataItem.contentId == content.id && dataItem.type == UserAction.contentWatch.toJson()) {
dataItem.percentage = convertToIntegerPercentage(content.viewedPercentage!);
break;
}
}
}
}
print("======= Updated Data =============");
print(jsonEncode(nabedInsertDataPayload));
}
void getContentClassID() {}
Future<void> clearData() async {
print("======== Clear Data ======");
data = courseData = nabedJourneyResponse = courseTopics = consultation = timer = controller = null;
setSelectedJourney = 0;
nabedInsertDataPayload = [];
}
Future<void> clear() async {
print("==== Clear ======");
if (controller != null) controller!.dispose();
courseData = courseTopics = timer = controller = null;
}
}
enum UserAction {
contentDisplayed,
contentClick,
contentWatch,
journeyClick,
journeyDisplayed,
}
extension UserActionExtension on UserAction {
String toJson() {
switch (this) {
case UserAction.contentDisplayed:
return "content_displayed";
case UserAction.contentClick:
return "content_click";
case UserAction.contentWatch:
return "content_watch";
case UserAction.journeyClick:
return "journey_click";
case UserAction.journeyDisplayed:
return "journey_displayed";
}
}
}

@ -31,7 +31,7 @@ class LocalNotification {
_initialize() async { _initialize() async {
try { try {
var initializationSettingsAndroid = new AndroidInitializationSettings('app_icon'); var initializationSettingsAndroid = new AndroidInitializationSettings('app_icon');
var initializationSettingsIOS = DarwinInitializationSettings(onDidReceiveLocalNotification: null); var initializationSettingsIOS = DarwinInitializationSettings();
var initializationSettings = InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS); var initializationSettings = InitializationSettings(android: initializationSettingsAndroid, iOS: initializationSettingsIOS);
await flutterLocalNotificationsPlugin.initialize( await flutterLocalNotificationsPlugin.initialize(
initializationSettings, initializationSettings,

@ -39,11 +39,9 @@ class PenguinMethodChannel {
void setMethodCallHandler() { void setMethodCallHandler() {
_channel.setMethodCallHandler((MethodCall call) async { _channel.setMethodCallHandler((MethodCall call) async {
try { try {
print(call.method); print(call.method);
switch (call.method) { switch (call.method) {
case PenguinMethodNames.onPenNavInitializationError: case PenguinMethodNames.onPenNavInitializationError:
_handleInitializationError(call.arguments); // Handle onPenNavInitializationError errors. _handleInitializationError(call.arguments); // Handle onPenNavInitializationError errors.
break; break;
@ -64,20 +62,19 @@ class PenguinMethodChannel {
} }
}); });
} }
static void _handleUnknownMethod(String method) { static void _handleUnknownMethod(String method) {
print("Unknown method: $method"); print("Unknown method: $method");
// Optionally, handle this unknown method case, such as reporting or ignoring it // Optionally, handle this unknown method case, such as reporting or ignoring it
} }
static void _handleInitializationError(Map<dynamic, dynamic> error) { static void _handleInitializationError(Map<dynamic, dynamic> error) {
final type = error['type'] as String?; final type = error['type'] as String?;
final description = error['description'] as String?; final description = error['description'] as String?;
print("Initialization Error: ${type ?? 'Unknown Type'}, ${description ?? 'No Description'}"); print("Initialization Error: ${type ?? 'Unknown Type'}, ${description ?? 'No Description'}");
} }
} }
// Define constants for method names // Define constants for method names
class PenguinMethodNames { class PenguinMethodNames {
static const String showPenguinUI = 'showPenguinUI'; static const String showPenguinUI = 'showPenguinUI';

@ -155,11 +155,9 @@ dependencies:
badges: ^3.1.2 badges: ^3.1.2
# flutter_app_icon_badge: ^2.0.0 # flutter_app_icon_badge: ^2.0.0
# dropdown_search: 5.0.6
youtube_player_flutter: ^9.1.0 youtube_player_flutter: ^9.1.0
video_player: ^2.9.2
# shimmer: ^3.0.0
# carousel_slider: ^4.0.0
# flutter_staggered_grid_view: ^0.7.0 # flutter_staggered_grid_view: ^0.7.0
huawei_hmsavailability: ^6.11.0+301 huawei_hmsavailability: ^6.11.0+301
huawei_location: ^6.11.0+301 huawei_location: ^6.11.0+301
@ -182,6 +180,8 @@ dependencies:
win32: ^5.5.4 win32: ^5.5.4
cloudflare_turnstile: ^2.0.1 cloudflare_turnstile: ^2.0.1
visibility_detector: ^0.4.0+2
dependency_overrides: dependency_overrides:

Loading…
Cancel
Save