Compare commits
12 Commits
dev_v3.13.
...
dev_3.24.3
| Author | SHA1 | Date |
|---|---|---|
|
|
4be0fc1cc9 | 1 year ago |
|
|
7984aedcd8 | 1 year ago |
|
|
a76358a871 | 1 year ago |
|
|
3826b854fc | 1 year ago |
|
|
74aafad2ec | 1 year ago |
|
|
86cd70d881 | 1 year ago |
|
|
0427d0025f | 1 year ago |
|
|
1d7963c98a | 1 year ago |
|
|
e19f318585 | 1 year ago |
|
|
a8265516d4 | 1 year ago |
|
|
a568c76de1 | 1 year ago |
|
|
da3e266989 | 1 year ago |
Binary file not shown.
|
After Width: | Height: | Size: 113 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 651 KiB |
@ -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,
|
||||||
|
};
|
||||||
|
}
|
||||||
@ -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',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
|
||||||
|
],
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -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";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue