diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 8ff0ea3..7ac0e9f 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -542,5 +542,6 @@ "uploadedDocuments": "المستندات التي تم تحميلها", "addAtLeastOneAttachment": "الرجاء إضافة مرفق واحد على الأقل.", "pleaseClickButtonToJoinMarathon": "الرجاء الضغط على الزر أدناه للانضمام إلى الماراثون", - "youCannotJoinTheMarathon": "لا يمكنك الانضمام إلى الماراثون لأنك تجاوزت الحد الزمني" + "youCannotJoinTheMarathon": "لا يمكنك الانضمام إلى الماراثون لأنك تجاوزت الحد الزمني", + "open": "يفتح" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 2449802..b679368 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -559,6 +559,7 @@ "userSearch": "User Search", "userName": "User Name", "userId": "UserID", + "open": "open", "addAtLeastOneAttachment": "Please add at least one attachment.", "pleaseClickButtonToJoinMarathon": "Press the button below to join the Marathon.", "youCannotJoinTheMarathon": "You cannot join the Marathon because you have exceeded the time limit." diff --git a/lib/api/marathon/marathon_api_client.dart b/lib/api/marathon/marathon_api_client.dart index 428e586..d9021c6 100644 --- a/lib/api/marathon/marathon_api_client.dart +++ b/lib/api/marathon/marathon_api_client.dart @@ -11,6 +11,7 @@ import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/models/marathon/marathon_generic_model.dart'; import 'package:mohem_flutter_app/models/marathon/marathon_model.dart'; import 'package:mohem_flutter_app/models/marathon/question_model.dart'; +import 'package:mohem_flutter_app/models/marathon/tutorial_notification_model.dart'; import 'package:mohem_flutter_app/models/marathon/winner_model.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; import 'package:provider/provider.dart'; @@ -195,6 +196,24 @@ class MarathonApiClient { return null; } + Future getMarathonTutorial() async { + Response response = await ApiClient().getJsonForResponse( + // ApiConsts.marathonGetTutorial, + "https://marathoon.com/uatservice/api/tutorial/GetTutorialNotification", + token: AppState().getMarathonToken == null || AppState().getMarathonToken == "" ? await getMarathonToken() : AppState().getMarathonToken, + ); + try { + MarathonGenericModel marathonModel = MarathonGenericModel.fromJson(jsonDecode(response.body)); + if (marathonModel.data is List) { + return TutorialNotificationModel.fromJson(marathonModel.data[0]); + } + } catch (ex) { + print(ex); + } + + return null; + } + // late HubConnection hubConnection; // Future buildHubConnection(BuildContext context, String prizeId) async { // HttpConnectionOptions httpOptions = HttpConnectionOptions(skipNegotiation: false, logMessageContent: true); diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index d345415..4d64e9c 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -3,8 +3,8 @@ import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; class ApiConsts { //static String baseUrl = "http://10.200.204.20:2801/"; // Local server // static String baseUrl = "https://erptstapp.srca.org.sa"; // SRCA server - // static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver - static String baseUrl = "https://hmgwebservices.com"; // Live server + static String baseUrl = "https://uat.hmgwebservices.com"; // UAT ser343622ver + // static String baseUrl = "https://hmgwebservices.com"; // Live server static String baseUrlServices = baseUrl + "/Services/"; // server // static String baseUrlServices = "https://api.cssynapses.com/tangheem/"; // Live server static String utilitiesRest = baseUrlServices + "Utilities.svc/REST/"; @@ -51,6 +51,7 @@ class ApiConsts { static String marathonQualifiersUrl = marathonBaseUrl + "winner/getWinner/"; static String marathonSelectedWinner = marathonBaseUrl + "winner/getSelectedWinner/"; static String marathonGetMarathonersCount = marathonBaseUrl + "Participant/GetRemainingParticipants"; + static String marathonGetTutorial = marathonBaseUrl + "tutorial/GetTutorialNotification"; //DummyCards for the UI static CardContent dummyQuestion = const CardContent(); diff --git a/lib/classes/file_process.dart b/lib/classes/file_process.dart index 6974702..83ea15b 100644 --- a/lib/classes/file_process.dart +++ b/lib/classes/file_process.dart @@ -4,6 +4,7 @@ import 'dart:typed_data'; import 'package:open_file/open_file.dart'; import 'package:path_provider/path_provider.dart'; +import 'package:http/http.dart' as http; class FileProcess { static bool isFolderCreated = false; @@ -23,9 +24,9 @@ class FileProcess { } } - static void openFile(String fileName) { + static void openFile(String fileName, {bool isFullPath = false}) { String dir = directory!.path + "/$fileName.pdf"; - OpenFile.open(dir); + OpenFile.open(isFullPath ? fileName : dir); } static Future downloadFile(String base64Content, String fileName) async { @@ -37,4 +38,24 @@ class FileProcess { await file.writeAsBytes(bytes); return file; } + + static Future downloadFileFromUrl(String url,String fileName) async { + await checkDocumentFolder(); + String filePath = '${directory!.path}/$fileName'; + + if (await File(filePath).exists()) { + await Future.delayed(const Duration(seconds: 1)); + return filePath; + } else { + var response = await http.get(Uri.parse(url)); + if (response.statusCode == 200) { + var bytes = response.bodyBytes; + File file = File(filePath); + await file.writeAsBytes(bytes); + return filePath; + } else { + throw Exception('Failed to download file'); + } + } + } } diff --git a/lib/config/routes.dart b/lib/config/routes.dart index 07a927d..33559e2 100644 --- a/lib/config/routes.dart +++ b/lib/config/routes.dart @@ -27,6 +27,7 @@ import 'package:mohem_flutter_app/ui/login/verify_login_screen.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_intro_screen.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_screen.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_sponsor_video_screen.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_tutorial_viewer_screen.dart'; import 'package:mohem_flutter_app/ui/marathon/marathon_waiting_screen.dart'; import 'package:mohem_flutter_app/ui/misc/request_submit_screen.dart'; import 'package:mohem_flutter_app/ui/my_attendance/dynamic_screens/dynamic_input_screen.dart'; @@ -203,6 +204,7 @@ class AppRoutes { static const String marathonWinnerScreen = "/marathonWinnerScreen"; static const String marathonSponsorVideoScreen = "/marathonSponsorVideoScreen"; static const String marathonWaitingScreen = "/marathonWaitingScreen"; + static const String marathonTutorialScreen = "/marathonTutorialScreen"; static const String unsafeDeviceScreen = "/unsafeDeviceScreen"; static const String appUpdateScreen = "/appUpdateScreen"; @@ -319,6 +321,7 @@ class AppRoutes { marathonScreen: (BuildContext context) => MarathonScreen(), marathonSponsorVideoScreen: (BuildContext context) => const SponsorVideoScreen(), marathonWaitingScreen: (BuildContext context) => const MarathonWaitingScreen(), + marathonTutorialScreen: (BuildContext context) => const MarathonTutorialViewerScreen(), unsafeDeviceScreen: (BuildContext context) => const UnsafeDeviceScreen(), appUpdateScreen: (BuildContext context) => const AppUpdateScreen(), diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 7a3501f..a6fd43d 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -549,4 +549,5 @@ abstract class LocaleKeys { static const addAtLeastOneAttachment ='addAtLeastOneAttachment'; static const pleaseClickButtonToJoinMarathon ='pleaseClickButtonToJoinMarathon'; static const youCannotJoinTheMarathon ='youCannotJoinTheMarathon'; + static const open ='open'; } diff --git a/lib/models/itg/advertisement.dart b/lib/models/itg/advertisement.dart index ebcf704..fb172ad 100644 --- a/lib/models/itg/advertisement.dart +++ b/lib/models/itg/advertisement.dart @@ -94,8 +94,9 @@ class ViewAttachFileColl { dynamic referenceItemId; dynamic content; dynamic filePath; + dynamic languageId; - ViewAttachFileColl({this.attachmentId, this.fileName, this.contentType, this.attachFileStream, this.base64String, this.isActive, this.referenceItemId, this.content, this.filePath}); + ViewAttachFileColl({this.attachmentId, this.fileName, this.contentType, this.attachFileStream, this.base64String, this.isActive, this.referenceItemId, this.content, this.filePath, this.languageId}); ViewAttachFileColl.fromJson(Map json) { attachmentId = json['attachmentId']; @@ -107,6 +108,7 @@ class ViewAttachFileColl { referenceItemId = json['referenceItemId']; content = json['content']; filePath = json['filePath']; + languageId = json['languageId']; } Map toJson() { @@ -120,6 +122,7 @@ class ViewAttachFileColl { data['referenceItemId'] = this.referenceItemId; data['content'] = this.content; data['filePath'] = this.filePath; + data['languageId'] = this.languageId; return data; } } diff --git a/lib/models/itg/itg_response_model.dart b/lib/models/itg/itg_response_model.dart index dff7869..e8d8acf 100644 --- a/lib/models/itg/itg_response_model.dart +++ b/lib/models/itg/itg_response_model.dart @@ -46,19 +46,31 @@ class ItgResponseResult { this.errormsg, }); - final dynamic totalItemsCount; - final ItgResponseData? data; - final dynamic errormsg; - - factory ItgResponseResult.fromJson(Map json) => ItgResponseResult( - totalItemsCount: json["totalItemsCount"], - data: json["data"] == null ? null : ItgResponseData.fromJson(json["data"]), - errormsg: json["errormsg"], - ); + dynamic totalItemsCount; + List? data; + dynamic errormsg; + + ItgResponseResult.fromJson(Map json) { + totalItemsCount = json['totalItemsCount']; + if (json['data'] != null) { + data = []; + json['data'].forEach((v) { + data!.add(ItgResponseData.fromJson(v)); + }); + } + errormsg = json['errormsg']; + } + + // + // factory ItgResponseResult.fromJson(Map json) => ItgResponseResult( + // totalItemsCount: json["totalItemsCount"], + // data: json["data"] == null ? null : ItgResponseData.fromJson(json["data"]), + // errormsg: json["errormsg"], + // ); Map toJson() => { "totalItemsCount": totalItemsCount, - "data": data == null ? null : data!.toJson(), + "data": data == null ? null : data!.map((v) => v.toJson()).toList(), "errormsg": errormsg, }; } diff --git a/lib/models/marathon/tutorial_notification_model.dart b/lib/models/marathon/tutorial_notification_model.dart new file mode 100644 index 0000000..ca1dd52 --- /dev/null +++ b/lib/models/marathon/tutorial_notification_model.dart @@ -0,0 +1,104 @@ +// class TutorialNotificationModel { +// List? data; +// bool? isSuccessful; +// String? message; +// int? statusCode; +// +// TutorialNotificationModel({this.data, this.isSuccessful, this.message, this.statusCode}); +// +// TutorialNotificationModel.fromJson(Map json) { +// if (json['data'] != null) { +// data = []; +// json['data'].forEach((v) { +// data!.add(new Data.fromJson(v)); +// }); +// } +// isSuccessful = json['isSuccessful']; +// message = json['message']; +// statusCode = json['statusCode']; +// } +// +// Map toJson() { +// final Map data = new Map(); +// if (this.data != null) { +// data['data'] = this.data!.map((v) => v.toJson()).toList(); +// } +// data['isSuccessful'] = this.isSuccessful; +// data['message'] = this.message; +// data['statusCode'] = this.statusCode; +// +// return data; +// } +// } + +class TutorialNotificationModel { + String? tutorialNotificationId; + String? tutorialName; + String? tutorialDescription; + String? startDate; + String? endDate; + String? fileName; + String? contentType; + String? filePath; + int? orderNo; + bool? isActive; + int? isStatus; + String? created; + String? createdBy; + String? modified; + String? modifiedBy; + + TutorialNotificationModel({this.tutorialNotificationId, + this.tutorialName, + this.tutorialDescription, + this.startDate, + this.endDate, + this.fileName, + this.contentType, + this.filePath, + this.orderNo, + this.isActive, + this.isStatus, + this.created, + this.createdBy, + this.modified, + this.modifiedBy}); + + TutorialNotificationModel.fromJson(Map json) { + tutorialNotificationId = json['tutorialNotificationId']; + tutorialName = json['tutorialName']; + tutorialDescription = json['tutorialDescription']; + startDate = json['startDate']; + endDate = json['endDate']; + fileName = json['fileName']; + contentType = json['contentType']; + filePath = json['filePath']; + orderNo = json['orderNo']; + isActive = json['isActive']; + isStatus = json['isStatus']; + created = json['created']; + createdBy = json['createdBy']; + modified = json['modified']; + modifiedBy = json['modifiedBy']; + } + + Map toJson() { + Map data = new Map(); + data['tutorialNotificationId'] = this.tutorialNotificationId; + data['tutorialName'] = this.tutorialName; + data['tutorialDescription'] = this.tutorialDescription; + data['startDate'] = this.startDate; + data['endDate'] = this.endDate; + data['fileName'] = this.fileName; + data['contentType'] = this.contentType; + data['filePath'] = this.filePath; + data['orderNo'] = this.orderNo; + data['isActive'] = this.isActive; + data['isStatus'] = this.isStatus; + data['created'] = this.created; + data['createdBy'] = this.createdBy; + data['modified'] = this.modified; + data['modifiedBy'] = this.modifiedBy; + return data; + } +} diff --git a/lib/ui/landing/dashboard_screen.dart b/lib/ui/landing/dashboard_screen.dart index 4116594..ea3b2cb 100644 --- a/lib/ui/landing/dashboard_screen.dart +++ b/lib/ui/landing/dashboard_screen.dart @@ -150,6 +150,7 @@ class _DashboardScreenState extends State with WidgetsBindingOb data.fetchMenuEntries(); data.getCategoryOffersListAPI(context); marathonProvider.getMarathonDetailsFromApi(); + marathonProvider.getMarathonTutorial(); if (isFromInit) { checkERMChannel(); } @@ -161,8 +162,8 @@ class _DashboardScreenState extends State with WidgetsBindingOb data.getITGNotification().then((val) { if (val!.result!.data != null) { print("-------------------- Survey ----------------------------"); - if (val.result!.data!.notificationType == "Survey") { - DashboardApiClient().getAdvertisementDetail(val.result!.data!.notificationMasterId ?? "").then( + if (val.result!.data!.first.notificationType == "Survey") { + DashboardApiClient().getAdvertisementDetail(val.result!.data!.first.notificationMasterId ?? "").then( (value) { if (value!.mohemmItgResponseItem!.statusCode == 200) { if (value.mohemmItgResponseItem!.result!.data != null) { @@ -178,13 +179,13 @@ class _DashboardScreenState extends State with WidgetsBindingOb ); } else { print("------------------------------------------- Ads --------------------"); - DashboardApiClient().getAdvertisementDetail(val.result!.data!.notificationMasterId ?? "").then( + DashboardApiClient().getAdvertisementDetail(val.result!.data!.first.notificationMasterId ?? "").then( (value) { if (value!.mohemmItgResponseItem!.statusCode == 200) { if (value.mohemmItgResponseItem!.result!.data != null) { Navigator.pushNamed(context, AppRoutes.advertisement, arguments: { - "masterId": val.result!.data!.notificationMasterId, - "advertisement": value.mohemmItgResponseItem!.result!.data!.advertisement, + "masterId": val.result!.data!.first.notificationMasterId, + "advertisement": value.mohemmItgResponseItem!.result!.data!.first.advertisement, }); } } @@ -493,7 +494,49 @@ class _DashboardScreenState extends State with WidgetsBindingOb mainAxisSize: MainAxisSize.min, children: [ ServicesWidget(), - context.watch().isLoading ? const MarathonBannerShimmer().paddingAll(20) : const MarathonBanner().paddingOnly(left: 21, right: 21, bottom: 21, top: 8), + context.watch().isLoading ? const MarathonBannerShimmer().paddingAll(20) : const MarathonBanner().paddingOnly(left: 21, right: 21, bottom: 8, top: 8), + context.watch().isTutorialLoading + ? const MarathonBannerShimmer().paddingAll(20) + : Container( + padding: EdgeInsets.only(bottom: 12, top: 12), + margin: EdgeInsets.only(left: 21, right: 21, bottom: 21, top: 8), + width: double.infinity, + alignment: Alignment.center, + decoration: BoxDecoration( + color: MyColors.backgroundBlackColor, + borderRadius: BorderRadius.circular(20), + border: Border.all(color: MyColors.lightGreyEDColor, width: 1), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "Tutorial:", + style: TextStyle( + fontSize: 11, + fontStyle: FontStyle.italic, + fontWeight: FontWeight.w600, + color: MyColors.white.withOpacity(0.83), + letterSpacing: -0.4, + ), + ), + Text( + context.read().tutorial?.tutorialName ?? "", + overflow: TextOverflow.ellipsis, + style: TextStyle( + fontStyle: FontStyle.italic, + fontSize: 19, + fontWeight: FontWeight.bold, + color: MyColors.white, + height: 32 / 22, + ), + ), + ], + ), + ).onPress(() { + Navigator.pushNamed(context, AppRoutes.marathonTutorialScreen); + }), ], ), ), diff --git a/lib/ui/marathon/marathon_provider.dart b/lib/ui/marathon/marathon_provider.dart index 544aad3..ecffd96 100644 --- a/lib/ui/marathon/marathon_provider.dart +++ b/lib/ui/marathon/marathon_provider.dart @@ -13,6 +13,7 @@ import 'package:mohem_flutter_app/config/routes.dart'; import 'package:mohem_flutter_app/models/marathon/marathon_generic_model.dart'; import 'package:mohem_flutter_app/models/marathon/marathon_model.dart'; import 'package:mohem_flutter_app/models/marathon/question_model.dart'; +import 'package:mohem_flutter_app/models/marathon/tutorial_notification_model.dart'; import 'package:mohem_flutter_app/models/marathon/winner_model.dart'; import 'package:mohem_flutter_app/models/privilege_list_model.dart'; import 'package:mohem_flutter_app/ui/marathon/widgets/question_card.dart'; @@ -594,4 +595,15 @@ class MarathonProvider extends ChangeNotifier { AppState().setIsDemoMarathon = true; await callNextQuestionApi(); } + + bool isTutorialLoading = false; + TutorialNotificationModel? tutorial; + + Future getMarathonTutorial() async { + isTutorialLoading = true; + notifyListeners(); + tutorial = await MarathonApiClient().getMarathonTutorial(); + isTutorialLoading = false; + notifyListeners(); + } } diff --git a/lib/ui/marathon/marathon_tutorial_viewer_screen.dart b/lib/ui/marathon/marathon_tutorial_viewer_screen.dart new file mode 100644 index 0000000..2e40ecf --- /dev/null +++ b/lib/ui/marathon/marathon_tutorial_viewer_screen.dart @@ -0,0 +1,239 @@ +import 'dart:convert'; +import 'dart:io' as Io; +import 'dart:io'; +import 'dart:typed_data'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_countdown_timer/index.dart'; +import 'package:lottie/lottie.dart'; +import 'package:mohem_flutter_app/api/dashboard_api_client.dart'; +import 'package:mohem_flutter_app/app_state/app_state.dart'; +import 'package:mohem_flutter_app/classes/colors.dart'; +import 'package:mohem_flutter_app/classes/file_process.dart'; +import 'package:mohem_flutter_app/classes/lottie_consts.dart'; +import 'package:mohem_flutter_app/classes/utils.dart'; +import 'package:mohem_flutter_app/extensions/int_extensions.dart'; +import 'package:mohem_flutter_app/extensions/string_extensions.dart'; +import 'package:mohem_flutter_app/extensions/widget_extensions.dart'; +import 'package:mohem_flutter_app/generated/locale_keys.g.dart'; +import 'package:mohem_flutter_app/main.dart'; +import 'package:mohem_flutter_app/models/itg/advertisement.dart' as ads; +import 'package:mohem_flutter_app/models/marathon/tutorial_notification_model.dart'; +import 'package:mohem_flutter_app/ui/chat/common.dart'; +import 'package:mohem_flutter_app/ui/marathon/marathon_provider.dart'; +import 'package:mohem_flutter_app/widgets/app_bar_widget.dart'; +import 'package:mohem_flutter_app/widgets/button/default_button.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:provider/provider.dart'; +import 'package:video_player/video_player.dart'; + +class MarathonTutorialViewerScreen extends StatefulWidget { + const MarathonTutorialViewerScreen({Key? key}) : super(key: key); + + @override + _MarathonTutorialViewerScreenState createState() => _MarathonTutorialViewerScreenState(); +} + +class _MarathonTutorialViewerScreenState extends State { + late Future _futureController; + VideoPlayerController? _controller; + bool skip = false; + + bool isVideo = false; + bool isImage = false; + bool isAudio = false; + bool isPdf = false; + + String link = ""; + + String ext = ''; + late File imageFile; + ads.Advertisement? advertisementData; + dynamic data; + String? masterID; + int videoDuration = 0; + + ValueNotifier videoLength = ValueNotifier(0); + + void checkFileTypes(String link) { + ext = "." + link.split(".").last.toLowerCase(); + if (ext == ".png" || ext == ".jpg" || ext == ".jpeg" || ext == ".gif") { + isImage = true; + } else if (ext == ".pdf") { + isPdf = true; + } else { + if (ext == ".aac") { + isAudio = true; + } + isVideo = true; + _futureController = createVideoPlayer(link); + } + + setState(() {}); + } + + Future processImage(String encodedBytes) async { + try { + Uint8List decodedBytes = base64Decode(encodedBytes.split("base64,").last); + Directory appDocumentsDirectory = await getApplicationDocumentsDirectory(); // 1 + imageFile = Io.File("${appDocumentsDirectory.path}/addImage$ext"); + imageFile.writeAsBytesSync(decodedBytes); + } catch (e) { + logger.d(e); + } + } + + Future createVideoPlayer(String link) async { + try { + VideoPlayerController controller = VideoPlayerController.networkUrl(Uri.parse(link)); + await controller.initialize(); + await controller.play(); + await controller.setVolume(1.0); + await controller.setLooping(false); + controller.addListener(() { + controller.position.then((value) { + videoLength.value = value!.inMilliseconds; + // if(controller.value.isCompleted) { + // videoLength.value = 0; + // } + }); + }); + return controller; + } catch (e) { + print(e); + return VideoPlayerController.network("https://apimohemmweb.cloudsolutions.com.sa/ErmAttachment/compressedvideo.mp4"); + } + } + + bool showControls = false; + + void showVideoControls() { + showControls = !showControls; + setState(() {}); + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + _controller?.dispose(); + super.dispose(); + } + + TutorialNotificationModel? tutorial; + + @override + Widget build(BuildContext context) { + if (tutorial == null) { + tutorial ??= context.read().tutorial; + link = tutorial!.filePath!; + checkFileTypes(link); + } + + return Scaffold( + backgroundColor: Colors.black, + appBar: AppBarWidget(context, title: tutorial!.fileName!, showHomeButton: false), + body: Stack( + children: [ + if (isVideo) + FutureBuilder( + future: _futureController, + builder: (BuildContext context, AsyncSnapshot snapshot) { + if (snapshot.connectionState == ConnectionState.done && snapshot.data != null) { + _controller = snapshot.data as VideoPlayerController; + return Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Center( + child: isAudio + ? Lottie.asset(MyLottieConsts.audioPlaybackLottie) + : AspectRatio( + aspectRatio: _controller!.value.aspectRatio, + child: Stack( + alignment: Alignment.bottomCenter, + children: [ + VideoPlayer(_controller!), + AnimatedContainer( + duration: const Duration(milliseconds: 250), + color: Colors.black.withOpacity(showControls ? .4 : .0), + child: AnimatedOpacity( + opacity: showControls ? 1 : 0, + duration: const Duration(milliseconds: 250), + child: Column( + children: [ + Icon(_controller!.value.isPlaying ? Icons.pause : Icons.play_arrow, color: Colors.white) + .onPress(() { + if (!showControls) { + showVideoControls(); + return; + } + + if (_controller!.value.isPlaying) { + _controller!.pause(); + } else if (_controller!.value.isCompleted) { + videoLength.value = 0; + _controller!.play(); + } else { + _controller!.play(); + } + setState(() {}); + }) + .center + .expanded, + ValueListenableBuilder( + valueListenable: videoLength, + builder: (context, val, child) { + return SeekBar( + duration: _controller!.value.duration, + position: Duration(milliseconds: val), + bufferedPosition: Duration(), + onChanged: (duration) { + _controller!.seekTo(duration); + }, + ); + }), + ], + ), + ), + ).onPress(() { + showVideoControls(); + }), + ], + ), + ), + ), + 30.height, + ], + ); + } else { + return const Center( + child: CircularProgressIndicator( + color: Colors.white, + ), + ); + } + }, + ), + if (isImage) Image.network(link).center, + if (isPdf) + DefaultButton(LocaleKeys.open.tr(), () async { + try { + Utils.showLoading(context); + await FileProcess.downloadFileFromUrl(link, tutorial!.fileName!).then((path) { + Utils.hideLoading(context); + FileProcess.openFile(path, isFullPath: true); + }); + } catch (ex) { + Utils.hideLoading(context); + Utils.handleException(ex, context, null); + } + }).paddingAll(21).center + ], + ), + ); + } +} diff --git a/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart b/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart index b4cb242..30a9fa2 100644 --- a/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart +++ b/lib/ui/screens/offers_and_discounts/offers_and_discounts_details.dart @@ -76,7 +76,7 @@ class _OffersAndDiscountsDetailsState extends State { : getOffersList[0].titleEn!.toText22(isBold: true, color: const Color(0xff2B353E)).center, Html( data: AppState().isArabic(context) ? getOffersList[0].descriptionAr! : getOffersList[0].descriptionEn ?? "", - onLinkTap: (String? url, RenderContext context, Map attributes, _) { + onLinkTap: (String? url, Map attributes, _) { launchUrl(Uri.parse(url!)); }, ),