From 5d4dfc529e21c72d3efdc2251c1eb714889f75d9 Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Thu, 25 Nov 2021 10:36:01 +0300 Subject: [PATCH] welcome video player added & improvements. --- android/app/build.gradle | 3 +- assets/fonts/B-Arabics.ttf | Bin 0 -> 52972 bytes lib/api/authentication_api_client.dart | 2 +- lib/api/tangheem_user_api_client.dart | 18 +- lib/api/user_api_client.dart | 4 +- lib/classes/consts.dart | 7 +- lib/extensions/string_extensions.dart | 37 +++- lib/main.dart | 16 +- lib/models/aya_tangheem_type_mapped.dart | 10 +- lib/models/tangheem_type_model.dart | 9 + lib/ui/common_appbar.dart | 8 +- lib/ui/screens/bookmark_screen.dart | 2 +- lib/ui/screens/home_screen.dart | 224 +++++++++++++++------ lib/ui/screens/pdf_viewer_screen.dart | 86 +++++++- lib/ui/screens/quran_screen.dart | 63 +++--- lib/ui/screens/tangheem_detail_screen.dart | 192 ++++++++++++++---- lib/ui/screens/tangheem_screen.dart | 13 +- lib/widgets/aya_player_widget.dart | 219 ++++++++++---------- lib/widgets/aya_record_widget.dart | 144 ++++++------- lib/widgets/video_player_widget.dart | 122 +++++++++++ pubspec.yaml | 11 +- 21 files changed, 850 insertions(+), 340 deletions(-) create mode 100644 assets/fonts/B-Arabics.ttf create mode 100644 lib/widgets/video_player_widget.dart diff --git a/android/app/build.gradle b/android/app/build.gradle index 669d9d6..7cf5766 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -39,10 +39,11 @@ android { defaultConfig { // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). applicationId "com.cloudsolutions.tangheem" - minSdkVersion 16 + minSdkVersion 19 targetSdkVersion 29 versionCode flutterVersionCode.toInteger() versionName flutterVersionName + multiDexEnabled true } buildTypes { diff --git a/assets/fonts/B-Arabics.ttf b/assets/fonts/B-Arabics.ttf new file mode 100644 index 0000000000000000000000000000000000000000..c44adaa83ef40e59a7f59601e11140cec11b2307 GIT binary patch literal 52972 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 _instance; Future authenticateUser(String email, String password) async { - String url = "${ApiConsts.authentication}Login"; + String url = "${ApiConsts.authentication}MobileLogin"; var postParams = {"email": email, "password": password}; return await ApiClient().postJsonForObject((json) => AuthenticationUserModel.fromJson(json), url, postParams); } diff --git a/lib/api/tangheem_user_api_client.dart b/lib/api/tangheem_user_api_client.dart index b3252f0..02e2e6c 100644 --- a/lib/api/tangheem_user_api_client.dart +++ b/lib/api/tangheem_user_api_client.dart @@ -60,6 +60,12 @@ class TangheemUserApiClient { return await ApiClient().postJsonForObject((json) => TangheemType.fromJson(json), url, postParams); } + Future getAyaTangheemTypeMappedRelated(int surahNo, String ayatNumberInSurahs) async { + String url = "${ApiConsts.tangheemUsers}AyatTangheemTypeMapped_Get"; + var postParams = {"surahNo": surahNo, "numberInSurahs": ayatNumberInSurahs}; + return await ApiClient().postJsonForObject((json) => AyatTangheemTypeMapped.fromJson(json), url, postParams); + } + Future getAyaTangheemTypeMapped(int surahNo, String tangheemTypeName, String ayaText, int itemsPerPage, int currentPageNo) async { String url = "${ApiConsts.tangheemUsers}AyatTangheemTypeMapped_Get"; var postParams = {}; @@ -78,9 +84,17 @@ class TangheemUserApiClient { return await ApiClient().postJsonForObject((json) => AyatTangheemTypeMapped.fromJson(json), url, postParams); } - Future getAyaTangheemType(int currentPage, int surahNo, String tangheemTypeName) async { + Future getAyaTangheemType(int surahNo, String tangheemTypeName) async { String url = "${ApiConsts.tangheemUsers}AyaTangheemType_Get"; - var postParams = {"itemsPerPage": 5, "currentPageNo": currentPage, "surahNo": surahNo, "tangheemTypeName": tangheemTypeName}; + + var postParams = {}; + if (surahNo != null) { + postParams["surahNo"] = surahNo; + } + if (tangheemTypeName != null) { + postParams["tangheemTypeName"] = tangheemTypeName; + } + return await ApiClient().postJsonForObject((json) => AyaTangheemType.fromJson(json), url, postParams); } diff --git a/lib/api/user_api_client.dart b/lib/api/user_api_client.dart index 858cb3f..80c638b 100644 --- a/lib/api/user_api_client.dart +++ b/lib/api/user_api_client.dart @@ -19,7 +19,7 @@ class UserApiClient { String _countryCode, String _phone, ) async { - String url = "${ApiConsts.user}UserRegistration_Add"; + String url = "${ApiConsts.user}MobileUserRegistration_Add"; var postParams = { "password": _password, "email": _email, @@ -53,7 +53,7 @@ class UserApiClient { } Future updatePassword(String _email, int _otp, String _password) async { - String url = "${ApiConsts.user}UpdatePassword"; + String url = "${ApiConsts.user}MobileUpdatePassword"; var postParams = {"email": _email, "opt": _otp, "newPassword": _password, "confirmPassword": _password}; return await ApiClient().postJsonForObject((json) => GeneralResponseModel.fromJson(json), url, postParams); } diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index e1f1e94..479ff60 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -1,7 +1,8 @@ class ApiConsts { //static String baseUrl = "http://10.200.204.20:2801/"; // Local server - static String baseUrl = "http://20.203.25.82"; // Live server - static String baseUrlServices = baseUrl + "/services/"; // Live server + static String baseUrl = "http://20.203.25.82"; // production server + // static String baseUrlServices = baseUrl + "/services/"; // production server + static String baseUrlServices = "https://api.cssynapses.com/tangheem/"; // Live server static String authentication = baseUrlServices + "api/Authentication/"; static String tangheemUsers = baseUrlServices + "api/TangheemUsers/"; static String adminConfiguration = baseUrlServices + "api/AdminConfiguration/"; @@ -14,4 +15,6 @@ class GlobalConsts { static String password = "password"; static String bookmark = "bookmark"; static String fontZoomSize = "font_zoom_size"; + static String welcomeVideoUrl = "welcomeVideoUrl"; + static String doNotShowWelcomeVideo = "doNotShowWelcomeVideo"; } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index b56d98b..a565faa 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -6,9 +6,38 @@ extension EmailValidator on String { } String toFormattedDate() { - DateFormat inputFormat = DateFormat('yyyy-mm-ddThh:mm:ss'); - DateTime inputDate = inputFormat.parse(this); - DateFormat outputFormat = DateFormat('DD MMMM yyyy hh:mm a'); - return outputFormat.format(inputDate); + String date = this.split("T")[0]; + String time = this.split("T")[1]; + var dates = date.split("-"); + return "${dates[2]} ${getMonth(int.parse(dates[1]))} ${dates[0]} ${DateFormat('hh:mm a').format(DateFormat('hh:mm:ss').parse(time))}"; + } + + getMonth(int month) { + switch (month) { + case 1: + return "January"; + case 2: + return "February"; + case 3: + return "March"; + case 4: + return "April"; + case 5: + return "May"; + case 6: + return "June"; + case 7: + return "July"; + case 8: + return "August"; + case 9: + return "September"; + case 10: + return "October"; + case 11: + return "November"; + case 12: + return "December"; + } } } diff --git a/lib/main.dart b/lib/main.dart index a515c78..1ba33e8 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -63,8 +63,11 @@ class Application extends StatelessWidget { case MemberScreen.routeName: className = CommonAppbar(child: MemberScreen()); break; + case PdfListScreen.routeName: + className = CommonAppbar(child: PdfListScreen()); + break; case PdfViewerScreen.routeName: - className = CommonAppbar(child: PdfViewerScreen()); + className = CommonAppbar(child: PdfViewerScreen(settings.arguments)); break; case ContentInfoScreen.routeName: var index = settings.arguments; @@ -97,11 +100,12 @@ class Application extends StatelessWidget { eventName = eventName.replaceAll("/", ""); if (eventName.isEmpty) eventName = "home"; try { - analytics.setCurrentScreen( - screenName: eventName, - screenClassOverride: eventName, - ).then((value) => print("EventSent:$eventName")); - + analytics + .setCurrentScreen( + screenName: eventName, + screenClassOverride: eventName, + ) + .then((value) => print("EventSent:$eventName")); } catch (ex) { print("Analytics:$ex"); } diff --git a/lib/models/aya_tangheem_type_mapped.dart b/lib/models/aya_tangheem_type_mapped.dart index 21e86f6..3e7691c 100644 --- a/lib/models/aya_tangheem_type_mapped.dart +++ b/lib/models/aya_tangheem_type_mapped.dart @@ -44,6 +44,8 @@ class AyatTangheemTypeMappedData { String highlightText; String userId; String ayahTextBase; + String ayahNos; + String ayatNumberInSurahs; List property; List voiceNote; @@ -61,6 +63,8 @@ class AyatTangheemTypeMappedData { this.highlightText, this.userId, this.ayahTextBase, + this.ayahNos, + this.ayatNumberInSurahs, this.property, this.voiceNote}); @@ -78,6 +82,8 @@ class AyatTangheemTypeMappedData { highlightText = json['highlightText']; userId = json['userId']; ayahTextBase = json['ayahTextBase']; + ayahNos = json['ayahNos']; + ayatNumberInSurahs = json['ayatNumberInSurahs']; if (json['property'] != null) { property = []; json['property'].forEach((v) { @@ -107,6 +113,8 @@ class AyatTangheemTypeMappedData { data['highlightText'] = this.highlightText; data['userId'] = this.userId; data['ayahTextBase'] = this.ayahTextBase; + data['ayahNos'] = this.ayahNos; + data['ayatNumberInSurahs'] = this.ayatNumberInSurahs; if (this.property != null) { data['property'] = this.property.map((v) => v.toJson()).toList(); } @@ -133,7 +141,7 @@ class TangheemProperty { String ayaTangheemTypePropertyId; String propertyValue; - TangheemProperty({this.tangheemTypePropertyId, this.propertyText, this.isInsideTable, this.textColor,this.orderNo, this.ayaTangheemTypePropertyId, this.propertyValue}); + TangheemProperty({this.tangheemTypePropertyId, this.propertyText, this.isInsideTable, this.textColor, this.orderNo, this.ayaTangheemTypePropertyId, this.propertyValue}); TangheemProperty.fromJson(Map json) { tangheemTypePropertyId = json['tangheemTypePropertyId']; diff --git a/lib/models/tangheem_type_model.dart b/lib/models/tangheem_type_model.dart index 8b4cb72..26f4ade 100644 --- a/lib/models/tangheem_type_model.dart +++ b/lib/models/tangheem_type_model.dart @@ -34,6 +34,9 @@ class TangheemTypeData { String tangheemTypeId; String tangheemTypeName; bool isActive; + int orderNo; + String tangheemTypeDescription; + int numberOfAyaTangheemType; TangheemTypeData({this.tangheemTypeId, this.tangheemTypeName, this.isActive}); @@ -41,6 +44,9 @@ class TangheemTypeData { tangheemTypeId = json['tangheemTypeId']; tangheemTypeName = json['tangheemTypeName']; isActive = json['isActive']; + orderNo = json['orderNo']; + tangheemTypeDescription = json['tangheemTypeDescription']; + numberOfAyaTangheemType = json['numberOfAyaTangheemType']; } Map toJson() { @@ -48,6 +54,9 @@ class TangheemTypeData { data['tangheemTypeId'] = this.tangheemTypeId; data['tangheemTypeName'] = this.tangheemTypeName; data['isActive'] = this.isActive; + data['orderNo'] = this.orderNo; + data['tangheemTypeDescription'] = this.tangheemTypeDescription; + data['numberOfAyaTangheemType'] = this.numberOfAyaTangheemType; return data; } } diff --git a/lib/ui/common_appbar.dart b/lib/ui/common_appbar.dart index 74867ef..93343d1 100644 --- a/lib/ui/common_appbar.dart +++ b/lib/ui/common_appbar.dart @@ -6,12 +6,12 @@ import 'package:tangheem/app_state/app_state.dart'; import 'package:tangheem/classes/colors.dart'; import 'package:tangheem/classes/consts.dart'; import 'package:tangheem/classes/utils.dart'; -import 'package:tangheem/models/content_info_model.dart'; import 'package:tangheem/models/navigation_model.dart'; import 'package:tangheem/models/quick_links_model.dart'; import 'package:tangheem/ui/screens/bookmark_screen.dart'; import 'package:tangheem/ui/screens/content_info_screen.dart'; import 'package:tangheem/ui/screens/login_screen.dart'; +import 'package:tangheem/ui/screens/pdf_viewer_screen.dart'; import 'package:url_launcher/url_launcher.dart'; class CommonAppbar extends StatefulWidget { @@ -198,6 +198,9 @@ class _CommonAppbarState extends State { } else if (subItem.mobileNavigationUrl == "/encyclopedia") { url = ContentInfoScreen.routeName; contentId = 1; + } else if (subItem.mobileNavigationUrl == "/tangheempdf") { + url = PdfListScreen.routeName; + contentId = 8; } Navigator.pushNamed(context, url, arguments: contentId); }), @@ -274,7 +277,8 @@ class _CommonAppbarState extends State { child: Row( children: [ for (QuickLinksData _quickLink in quickLinks) - commonIconButton(ApiConsts.baseUrl + _quickLink.exposeFilePath, () { + // commonIconButton(ApiConsts.baseUrl + _quickLink.exposeFilePath, () { for live produciton server + commonIconButton( _quickLink.exposeFilePath, () { _launchURL(_quickLink.imageUrl); }, size: 35, isAsset: false), ], diff --git a/lib/ui/screens/bookmark_screen.dart b/lib/ui/screens/bookmark_screen.dart index 3d5379c..b449898 100644 --- a/lib/ui/screens/bookmark_screen.dart +++ b/lib/ui/screens/bookmark_screen.dart @@ -99,7 +99,7 @@ class _BookmarkScreenState extends State { ), Text( " ${_bookMarkList[index].numberInSurah}", - style: TextStyle(fontSize: 14, color: ColorConsts.secondaryOrange), + style: TextStyle(fontSize: 14, fontFamily: "BArabics", color: ColorConsts.secondaryOrange), ), ], ), diff --git a/lib/ui/screens/home_screen.dart b/lib/ui/screens/home_screen.dart index a4b456f..73abdc8 100644 --- a/lib/ui/screens/home_screen.dart +++ b/lib/ui/screens/home_screen.dart @@ -4,20 +4,25 @@ import 'package:flutter/material.dart'; import 'package:flutter/painting.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:tangheem/api/tangheem_user_api_client.dart'; import 'package:tangheem/app_state/app_state.dart'; import 'package:tangheem/classes/colors.dart'; +import 'package:tangheem/classes/consts.dart'; import 'package:tangheem/classes/utils.dart'; +import 'package:tangheem/models/aya_tangheem_type.dart'; import 'package:tangheem/models/content_info_model.dart'; import 'package:tangheem/models/surah_model.dart'; import 'package:tangheem/models/tangheem_type_model.dart'; import 'package:tangheem/ui/dialogs/general_dialog.dart'; import 'package:tangheem/ui/screens/tangheem_screen.dart'; import 'package:tangheem/widgets/common_dropdown_button.dart'; +import 'package:tangheem/widgets/video_player_widget.dart'; class HomeScreen extends StatefulWidget { static const String routeName = "/"; final FirebaseAnalytics analytics; + HomeScreen(this.analytics, {Key key}) : super(key: key); @override @@ -68,22 +73,94 @@ class _HomeScreenState extends State { checkScreenMode(); } + void filterTangheemTypesListBySurah(int surahNo, {bool showLoading = true}) async { + if (showLoading) Utils.showLoading(context); + try { + AyaTangheemType ayaTangheemType = await TangheemUserApiClient().getAyaTangheemType(surahNo, null); + + var tangheemType = _tangheemType?.data?.where((element) => element.isActive)?.toList() ?? []; + var ayaTangheem = ayaTangheemType?.data?.where((element) => element.isActive)?.toList() ?? []; + var result = tangheemType.where((tangheem) => ayaTangheem.any((element) => tangheem.tangheemTypeId == element.tangheemTypeId)).toList() ?? []; + _tangheemListNotifier.value = result?.map((element) => element.tangheemTypeName)?.toList() ?? []; + if (showLoading) Utils.hideLoading(context); + } catch (ex) { + if (showLoading) Utils.hideLoading(context); + Utils.handleException(ex, null); + } + } + + void filterSurahListByTangheemType(String tangheemName) async { + Utils.showLoading(context); + try { + AyaTangheemType ayaTangheemType = await TangheemUserApiClient().getAyaTangheemType(null, tangheemName); + _surahList = _surahModel.data.where((surah) => ayaTangheemType.data.any((element) => surah.id == element.surahNo)).toList().map((element) => element.nameAR).toList(); + setState(() {}); + Utils.hideLoading(context); + } catch (ex) { + Utils.hideLoading(context); + Utils.handleException(ex, null); + } + } + void checkScreenMode() { - WidgetsBinding.instance.addPostFrameCallback((_) { + WidgetsBinding.instance.addPostFrameCallback((_) async { if (MediaQuery.of(context).orientation == Orientation.portrait) { - showDialog( + await showDialog( context: context, barrierColor: ColorConsts.secondaryWhite.withOpacity(0.8), builder: (BuildContext context) => GeneralDialog(), ); } + showWelcomeVideoDialog(); }); } + SharedPreferences prefs; + + void showWelcomeVideoDialog() async { + prefs = await SharedPreferences.getInstance(); + String permLink = "aAmP-WcI6dg"; + String link = prefs.getString(GlobalConsts.welcomeVideoUrl) ?? permLink; + if (permLink == link) { + await prefs.setString(GlobalConsts.welcomeVideoUrl, permLink); + bool showDialog = prefs.getBool(GlobalConsts.doNotShowWelcomeVideo) ?? false; + if (showDialog) { + return; + } + } else { + await prefs.setString(GlobalConsts.welcomeVideoUrl, permLink); + } + await prefs.setBool(GlobalConsts.doNotShowWelcomeVideo, false); + showDialog( + context: context, + barrierColor: ColorConsts.secondaryWhite.withOpacity(0.8), + builder: (BuildContext context) => VideoPlayerWidget(permLink), + ); + } + Future getTangheemTypes() async { try { _tangheemType = await TangheemUserApiClient().getTangheemType(); + if ((_tangheemType?.data?.length ?? 0) > 0) { + _tangheemType.data.sort((a, b) => a.orderNo.compareTo(b.orderNo)); + } _tangheemListNotifier.value = _tangheemType?.data?.where((element) => element.isActive)?.toList()?.map((element) => element.tangheemTypeName)?.toList() ?? []; + return; + + // enable these lines if in future need to filter types too + if (_surahModel.data.map((element) => element.nameAR).toList().length == _surahList.length && _selectedSurah >= 0) { + setState(() { + _selectedTangheemType = -1; + }); + // filterTangheemTypesListBySurah(_surahModel.data[_selectedSurah].id, showLoading: false); + } else { + if (_surahModel.data.map((element) => element.nameAR).toList().length != _surahList.length && _selectedSurah >= 0) { + setState(() { + _selectedSurah = -1; + }); + } + _tangheemListNotifier.value = _tangheemType?.data?.where((element) => element.isActive)?.toList()?.map((element) => element.tangheemTypeName)?.toList() ?? []; + } } catch (ex) {} } @@ -126,9 +203,14 @@ class _HomeScreenState extends State { builder: (context, value, child) { return CommonDropDownButton(_selectedTangheemType, hintText: "اختر الأسلوب اللغوي", list: value, onSelect: (index) { if (_selectedTangheemType != index) { - setState(() { + setState((){ + _selectedSurah = -1; _selectedTangheemType = index; }); + if (_selectedSurah >= 0) { + return; + } + filterSurahListByTangheemType(_tangheemListNotifier.value[_selectedTangheemType]); } }); }, @@ -141,64 +223,64 @@ class _HomeScreenState extends State { setState(() { _selectedSurah = index; }); + if (_selectedTangheemType >= 0) { + return; + } + // filterTangheemTypesListBySurah(_surahModel.data[_selectedSurah].id); } }), ) ], ), SizedBox(height: 16), - InkWell( - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - onTap: () async { - if (_selectedTangheemType < 0) { - Utils.showToast("الرجائ تعبئة جميع القوائم"); - return; - } - if (_selectedSurah < 0) { - Utils.showToast("الرجائ تعبئة جميع القوائم"); - return; - } - _searchFocusNode.unfocus(); - _searchFocusNode.canRequestFocus = false; - var data = {"tangheemTypeName": _tangheemListNotifier.value[_selectedTangheemType], "surahData": _surahModel.data[_selectedSurah]}; - try { - await widget.analytics.logEvent( - name: 'tangheem_by_selection', - parameters: { - "tangheemTypeName": _tangheemListNotifier.value[_selectedTangheemType], - "surahName": _surahModel.data[_selectedSurah].nameAR, - }, - ); - } catch (ex) { - print("tangheemTypeName:$ex"); - } - await Navigator.pushNamed(context, TangheemScreen.routeName, arguments: data); - _searchFocusNode.canRequestFocus = true; - getTangheemTypes(); - }, - child: Container( - height: 36, - decoration: BoxDecoration( - color: ColorConsts.secondaryPink, - borderRadius: BorderRadius.circular(6), - ), - padding: EdgeInsets.fromLTRB(8, 2, 8, 2), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - mainAxisSize: MainAxisSize.min, - children: [ - Text( - "بحث", - maxLines: 1, - style: TextStyle(fontSize: 12, color: Colors.white), - ), - SizedBox(width: 12), - SvgPicture.asset("assets/icons/go_forward.svg", width: 20, height: 20, color: Colors.white), - ], - ), - ), + Row( + children: [ + iconButton("بحث", "assets/icons/go_forward.svg", () async { + // if (_selectedTangheemType < 0) { + // Utils.showToast("الرجائ تعبئة جميع القوائم"); + // return; + // } + if (_selectedSurah < 0) { + Utils.showToast("يرجى اختيار السورة"); + return; + } + _searchFocusNode.unfocus(); + _searchFocusNode.canRequestFocus = false; + var surah = _surahModel.data.firstWhere((surah) => surah.nameAR == _surahList[_selectedSurah], orElse: null); + Map data = {}; + data["surahData"] = surah; + if (_selectedTangheemType >= 0) { + data["tangheemTypeName"] = _tangheemListNotifier.value[_selectedTangheemType].toString(); + } + try { + await widget.analytics.logEvent( + name: 'tangheem_by_selection', + parameters: { + "tangheemTypeName": _tangheemListNotifier.value[_selectedTangheemType], + "surahName": _surahModel.data[_selectedSurah].nameAR, + }, + ); + } catch (ex) { + print("tangheemTypeName:$ex"); + } + await Navigator.pushNamed(context, TangheemScreen.routeName, arguments: data); + _searchFocusNode.canRequestFocus = true; + await getTangheemTypes(); + }), + SizedBox(width: 8), + iconButton( + "إلغاء", + "assets/icons/cancel.svg", + (_selectedSurah == -1 && _selectedTangheemType == -1) + ? null + : () { + _selectedSurah = -1; + _selectedTangheemType = -1; + _surahList = _surahModel.data.map((element) => element.nameAR).toList(); + _tangheemListNotifier.value = _tangheemType?.data?.where((element) => element.isActive)?.toList()?.map((element) => element.tangheemTypeName)?.toList() ?? []; + setState(() {}); + }), + ], ), SizedBox(height: 16), Container( @@ -236,7 +318,7 @@ class _HomeScreenState extends State { ); await Navigator.pushNamed(context, TangheemScreen.routeName, arguments: data); _searchFocusNode.canRequestFocus = true; - getTangheemTypes(); + await getTangheemTypes(); }, splashColor: Colors.transparent, highlightColor: Colors.transparent, @@ -265,4 +347,34 @@ class _HomeScreenState extends State { ), ); } + + Widget iconButton(String title, String icon, VoidCallback callback) { + return InkWell( + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + onTap: callback, + child: Container( + height: 36, + decoration: BoxDecoration( + color: callback == null ? ColorConsts.textHintGrey : ColorConsts.secondaryPink, + borderRadius: BorderRadius.circular(6), + ), + padding: EdgeInsets.fromLTRB(8, 2, 8, 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisSize: MainAxisSize.min, + children: [ + Text( + title, + maxLines: 1, + style: TextStyle(fontSize: 12, color: Colors.white), + ), + SizedBox(width: 12), + SvgPicture.asset(icon, width: 20, height: 20, color: Colors.white), + ], + ), + ), + ); + } } diff --git a/lib/ui/screens/pdf_viewer_screen.dart b/lib/ui/screens/pdf_viewer_screen.dart index 537021a..4f32035 100644 --- a/lib/ui/screens/pdf_viewer_screen.dart +++ b/lib/ui/screens/pdf_viewer_screen.dart @@ -1,10 +1,88 @@ import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_pdfviewer/pdfviewer.dart'; +import 'package:tangheem/api/tangheem_user_api_client.dart'; +import 'package:tangheem/classes/colors.dart'; +import 'package:tangheem/classes/utils.dart'; +import 'package:tangheem/models/content_info_model.dart'; +import 'package:tangheem/ui/misc/no_data_ui.dart'; -class PdfViewerScreen extends StatefulWidget { +class PdfListScreen extends StatefulWidget { static const String routeName = "/tangheem_pdf"; - PdfViewerScreen({Key key}) : super(key: key); + PdfListScreen({Key key}) : super(key: key); + + @override + _PdfListScreenState createState() { + return _PdfListScreenState(); + } +} + +class _PdfListScreenState extends State { + List contentList; + + @override + void initState() { + super.initState(); + getPdfs(); + } + + void getPdfs() async { + Utils.showLoading(context); + try { + var membersData = await TangheemUserApiClient().getContentInfo(8); + contentList = membersData?.data ?? []; + } catch (ex) { + contentList = []; + Utils.handleException(ex, null); + } finally { + Utils.hideLoading(context); + } + setState(() {}); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return contentList == null + ? SizedBox() + : contentList.isEmpty + ? NoDataUI() + : ListView.separated( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.all(16), + itemCount: contentList.length, + separatorBuilder: (context, index) { + return SizedBox(height: 8); + }, + itemBuilder: (context, index) { + return ListTile( + tileColor: Colors.white, + onTap: () { + Navigator.pushNamed(context, PdfViewerScreen.routeName, arguments: contentList[index]); + }, + title: Text( + contentList[index].fileName?.trim() ?? "", + style: TextStyle(fontSize: 14, color: ColorConsts.primaryBlue), + ), + subtitle: Text( + contentList[index].contentTypeNameAr?.trim() ?? "", + style: TextStyle(fontSize: 12, color: ColorConsts.primaryBlue), + ), + ); + }, + ); + } +} + +class PdfViewerScreen extends StatefulWidget { + static const String routeName = "/tangheem_pdf_view"; + final ContentInfoDataModel pdfDetail; + + PdfViewerScreen(this.pdfDetail, {Key key}) : super(key: key); @override _PdfViewerScreenState createState() { @@ -27,8 +105,8 @@ class _PdfViewerScreenState extends State { @override Widget build(BuildContext context) { - return SfPdfViewer.asset( - 'assets/files/tangheem.pdf', + return SfPdfViewer.network( + widget.pdfDetail.exposeFilePath, key: _pdfViewerKey, canShowScrollHead: false, enableTextSelection: false, diff --git a/lib/ui/screens/quran_screen.dart b/lib/ui/screens/quran_screen.dart index 2d1efca..0089768 100644 --- a/lib/ui/screens/quran_screen.dart +++ b/lib/ui/screens/quran_screen.dart @@ -85,7 +85,7 @@ class _QuranScreenState extends State { } setState(() {}); getQuranByPageNo(); - getTangheemBySurahId(); + // getTangheemBySurahId(); } List _bookMark = []; @@ -111,7 +111,7 @@ class _QuranScreenState extends State { setState(() {}); } - void getTangheemBySurahId() async { + Future getTangheemBySurahId() async { try { _ayatTangheemTypeMapped = await TangheemUserApiClient().getTangheemBySurah(_selectedSurah + 1); _tangheemWords = _ayatTangheemTypeMapped?.data?.map((e) => e.highlightText)?.toList() ?? []; @@ -125,20 +125,22 @@ class _QuranScreenState extends State { Utils.showLoading(context); try { _ayaModel = await TangheemUserApiClient().getAyaByFilter(_selectedSurah + 1, _fromAyaList[_selectedFromAya], _toAyaList[_selectedToAya]); + await getTangheemBySurahId(); } catch (ex) { Utils.handleException(ex, null); } finally { Utils.hideLoading(context); } - getTangheemBySurahId(); + // getTangheemBySurahId(); } void getQuranByPageNo() async { Utils.showLoading(context); try { _ayaModel = await TangheemUserApiClient().getQuranByPageNo(_currentPage); + await getTangheemBySurahId(); Utils.hideLoading(context); - setState(() {}); + // setState(() {}); } catch (ex) { Utils.handleException(ex, null); Utils.hideLoading(context); @@ -322,29 +324,30 @@ class _QuranScreenState extends State { ), quranTextView, SizedBox(height: 4), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - nextOptionButton( - "assets/icons/prev_single.svg", - "الصفحة السابقة", - _currentPage <= 1 - ? null - : (value) { - _currentPage = _currentPage - 1; - _clearFilterAndRefreshData(); - }), - previousOptionButton( - "assets/icons/next_single.svg", - "الصفحة التالية", - _currentPage == 604 - ? null - : (value) { - _currentPage = _currentPage + 1; - _clearFilterAndRefreshData(); - }), - ], - ), + if (_selectedFromAya == -1 && _selectedToAya == -1) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + nextOptionButton( + "assets/icons/prev_single.svg", + "الصفحة السابقة", + _currentPage <= 1 + ? null + : (value) { + _currentPage = _currentPage - 1; + _clearFilterAndRefreshData(); + }), + previousOptionButton( + "assets/icons/next_single.svg", + "الصفحة التالية", + _currentPage == 604 + ? null + : (value) { + _currentPage = _currentPage + 1; + _clearFilterAndRefreshData(); + }), + ], + ), ], ), ); @@ -516,7 +519,7 @@ class _QuranScreenState extends State { TextSpan( text: "\n" + "$_currentPage", style: TextStyle( - fontFamily: "UthmanicHafs", + fontFamily: "BArabics", fontSize: fontSize, color: ColorConsts.primaryBlue, fontWeight: FontWeight.bold, @@ -559,8 +562,7 @@ class _QuranScreenState extends State { onTap: () async { Navigator.pop(context); List list = []; - - list = _ayatTangheemTypeMapped?.data?.where((element) => element.ayahNo == _selectedAyaForBookmark.ayahID)?.toList() ?? []; + list = _ayatTangheemTypeMapped?.data?.where((element) => int.parse(element.ayahNos.split(",")[0]) == _selectedAyaForBookmark.ayahID)?.toList() ?? []; if (list.isEmpty) { Utils.showToast("لا توجد أساليب تنغيم في هذه الآية"); return; @@ -610,5 +612,6 @@ class _QuranScreenState extends State { class TangheemTemp { final String tangheemName; final String ayaTangheemId; + TangheemTemp(this.tangheemName, this.ayaTangheemId); } diff --git a/lib/ui/screens/tangheem_detail_screen.dart b/lib/ui/screens/tangheem_detail_screen.dart index bbc4a9d..9d87935 100644 --- a/lib/ui/screens/tangheem_detail_screen.dart +++ b/lib/ui/screens/tangheem_detail_screen.dart @@ -1,5 +1,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:tangheem/api/admin_configuration_api_client.dart'; @@ -21,6 +22,7 @@ import 'login_screen.dart'; class TangheemDetailParams { final String selectedTangheemTypeId; final List ayatTangheemTypeMappedDataList; + TangheemDetailParams({@required this.selectedTangheemTypeId, @required this.ayatTangheemTypeMappedDataList}); } @@ -43,6 +45,8 @@ class _TangheemDetailScreenState extends State { List ayatTangheemTypeMappedDataList = []; + List _dataList = []; + int _discussionPage = -1; AyatTangheemTypeMappedData _ayatTangheemTypeMappedFirstData; DiscussionModel _discussionModel; @@ -52,12 +56,11 @@ class _TangheemDetailScreenState extends State { @override void initState() { super.initState(); - ayatTangheemTypeMappedDataList = widget.tangheemDetailParams.ayatTangheemTypeMappedDataList; _ayatTangheemTypeMappedFirstData = ayatTangheemTypeMappedDataList.first; filterVoiceListData(); getPrefs(); - getTangheemDiscussion(); + getTangheemDiscussionAndRelatedData(); } double fontSize = 18; @@ -85,18 +88,42 @@ class _TangheemDetailScreenState extends State { return ""; } - void getTangheemDiscussion() async { + void getTangheemDiscussionAndRelatedData() async { Utils.showLoading(context); try { _discussionModel = await TangheemUserApiClient().getDiscussionByTangheemID(_discussionPage, widget.tangheemDetailParams.selectedTangheemTypeId); + if (!_ayatTangheemTypeMappedFirstData.ayatNumberInSurahs.contains(",")) { + _dataList = await getTangheemRelatedData(); + } Utils.hideLoading(context); setState(() {}); } catch (ex) { + print(ex); Utils.handleException(ex, null); Utils.hideLoading(context); } } + Future> getTangheemRelatedData() async { + _dataList = []; + AyatTangheemTypeMapped _ayatTangheemTypeMapped = + await TangheemUserApiClient().getAyaTangheemTypeMappedRelated(_ayatTangheemTypeMappedFirstData.surahNo, _ayatTangheemTypeMappedFirstData.ayatNumberInSurahs); + _dataList = _ayatTangheemTypeMapped?.data ?? []; + if (_dataList.isNotEmpty) { + _dataList = _dataList.where((element) => element.tangheemTypeId != _ayatTangheemTypeMappedFirstData.tangheemTypeId)?.toList() ?? []; + var _tempList = _dataList.map((e) => e.tangheemTypeId).toList().toSet().toList(); + var _dataTempList = []; + _tempList.forEach((_tempElement) { + _dataTempList.add(_dataList.firstWhere((element) { + return !element.ayatNumberInSurahs.contains(",") && (element.tangheemTypeId == _tempElement); + }, orElse: null)); + }); + _dataList = _dataTempList; + } + + return _dataList; + } + void sendComment(String discussionText) async { Utils.showLoading(context); try { @@ -264,19 +291,29 @@ class _TangheemDetailScreenState extends State { SizedBox(width: 4), Expanded( child: Container( - height: 40, - padding: EdgeInsets.only(left: 4, right: 8), - alignment: Alignment.centerRight, - child: Text( - _tangheemAboveTableList[index].propertyValue, - maxLines: 1, - style: TextStyle( - color: Color( - Utils.stringToHex(_tangheemAboveTableList[index].textColor), - ), + color: ColorConsts.secondaryWhite, + padding: EdgeInsets.all(4), + child: Container( + color: Colors.white, + padding: EdgeInsets.only(left: 4, right: 8), + // alignment: Alignment.centerRight, + child: Html( + data: _tangheemAboveTableList[index]?.propertyValue ?? "", + style: { + 'html': Style(textAlign: TextAlign.left), + }, ), + + // Text( + // _tangheemAboveTableList[index].propertyValue, + // maxLines: 1, + // style: TextStyle( + // color: Color( + // Utils.stringToHex(_tangheemAboveTableList[index].textColor), + // ), + // ), + // ), ), - color: ColorConsts.secondaryWhite, ), ) ], @@ -308,6 +345,60 @@ class _TangheemDetailScreenState extends State { ), SizedBox(height: 8), discussionView(_discussionModel?.data ?? []), + if (_dataList.isNotEmpty) + Container( + margin: EdgeInsets.only(top: 8), + padding: EdgeInsets.only(bottom: 20), + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: Column( + children: [ + Container( + height: 60, + width: double.infinity, + margin: EdgeInsets.only(bottom: 8), + alignment: Alignment.center, + decoration: BoxDecoration( + color: ColorConsts.primaryBlue, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(8), + topRight: Radius.circular(8), + ), + ), + child: Text( + "قائمة الأساليب اللغوية في هذه الآية", + style: TextStyle(fontSize: 16, color: Colors.white), + ), + ), + ListView.separated( + padding: EdgeInsets.fromLTRB(4, 8, 4, 4), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: _dataList.length, + separatorBuilder: (context, index) => SizedBox(height: 16), + itemBuilder: (context, index) { + return InkWell( + onTap: () { + List list = _dataList; + var removedData = list[index]; + list.remove(removedData); + list.insert(0, removedData); + TangheemDetailParams tangheem = TangheemDetailParams(selectedTangheemTypeId: _dataList[index].ayaTangheemTypeId, ayatTangheemTypeMappedDataList: list); + Navigator.pushNamed(context, TangheemDetailScreen.routeName, arguments: tangheem); + }, + child: Text( + _dataList[index].tangheemTypeName, + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: ColorConsts.secondaryOrange, height: 1.5), + ), + ); + }, + ), + ], + ), + ), SizedBox(height: 16), AyaRecordWidget() ], @@ -434,19 +525,28 @@ class _TangheemDetailScreenState extends State { SizedBox(width: 4), Expanded( child: Container( - height: 40, - padding: EdgeInsets.only(left: 4, right: 8), - alignment: Alignment.centerRight, - child: Text( - tangheemPropertyList[index].propertyValue, - maxLines: 1, - style: TextStyle( - color: Color( - Utils.stringToHex(tangheemPropertyList[index].textColor), - ), + color: ColorConsts.secondaryWhite, + padding: EdgeInsets.all(4), + child: Container( + color: Colors.white, + padding: EdgeInsets.only(left: 4, right: 8), + // alignment: Alignment.centerRight, + child: Html( + data: tangheemPropertyList[index]?.propertyValue ?? "", + style: { + 'html': Style(textAlign: TextAlign.left), + }, ), + // Text( + // tangheemPropertyList[index].propertyValue, + // maxLines: 1, + // style: TextStyle( + // color: Color( + // Utils.stringToHex(tangheemPropertyList[index].textColor), + // ), + // ), + // ), ), - color: ColorConsts.secondaryWhite, ), ) ], @@ -473,32 +573,41 @@ class _TangheemDetailScreenState extends State { children: [ Container( color: ColorConsts.secondaryWhite, - height: 30, - alignment: Alignment.centerRight, + //height: 30, + alignment: Alignment.center, padding: EdgeInsets.only(left: 2, right: 4), width: double.infinity, child: Text( property.propertyText ?? "", - maxLines: 1, + // maxLines: 1, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12, color: ColorConsts.secondaryOrange), ), ), Container(width: double.infinity, height: 4, color: Colors.white), Container( color: ColorConsts.secondaryWhite, - height: 30, - padding: EdgeInsets.only(left: 2, right: 4), - alignment: Alignment.centerRight, - width: double.infinity, - child: Text( - property.propertyValue ?? "", - maxLines: 1, - style: TextStyle( - fontSize: 12, - color: Color( - Utils.stringToHex(property.textColor), - ), + padding: EdgeInsets.all(4), + child: Container( + color: Colors.white, + padding: EdgeInsets.only(left: 2, right: 4), + width: double.infinity, + child: Html( + data: property.propertyValue ?? "", + style: { + 'html': Style(textAlign: TextAlign.left), + }, ), + + // Text( + // property.propertyValue ?? "", + // maxLines: 1, + // style: TextStyle( + // fontSize: 12, + // color: Color( + // Utils.stringToHex(property.textColor), + // ), + // ), + // ), ), ), ], @@ -553,6 +662,7 @@ class _TangheemDetailScreenState extends State { } Widget discussionView(List _discussionList) { + _discussionList = _discussionList.where((element) => element.status.toLowerCase() == "Accept".toLowerCase()).toList(); return Stack( alignment: Alignment.bottomCenter, children: [ @@ -589,7 +699,7 @@ class _TangheemDetailScreenState extends State { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "تعليق على الآية ${_ayatTangheemTypeMappedFirstData.ayatNumberInSurah}", + "تعليق على الآية ${_ayatTangheemTypeMappedFirstData.ayatNumberInSurahs}", style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: ColorConsts.primaryBlue, height: 1.5), ), SizedBox(height: 4), diff --git a/lib/ui/screens/tangheem_screen.dart b/lib/ui/screens/tangheem_screen.dart index 194b4d8..bd3d5fd 100644 --- a/lib/ui/screens/tangheem_screen.dart +++ b/lib/ui/screens/tangheem_screen.dart @@ -85,9 +85,12 @@ class _TangheemScreenState extends State { itemBuilder: (context, index) { return InkWell( onTap: () { - List list = []; - list = _dataList?.where((element) => (element.ayahNo == _dataList[index].ayahNo) && (element.tangheemTypeId == _dataList[index].tangheemTypeId))?.toList() ?? []; - TangheemDetailParams tangheem = TangheemDetailParams(selectedTangheemTypeId: _dataList[index].ayaTangheemTypeId, ayatTangheemTypeMappedDataList: list); + List list = [] + _dataList; + var removedData = _dataList[index]; + list.remove(removedData); + list.insert(0, removedData); + list = list?.where((element) => (element.ayahNos.contains(removedData.ayahNos)) && (element.tangheemTypeId == removedData.tangheemTypeId))?.toList() ?? []; + TangheemDetailParams tangheem = TangheemDetailParams(selectedTangheemTypeId: removedData.ayaTangheemTypeId, ayatTangheemTypeMappedDataList: list); Navigator.pushNamed(context, TangheemDetailScreen.routeName, arguments: tangheem); }, borderRadius: BorderRadius.circular(4), @@ -110,8 +113,8 @@ class _TangheemScreenState extends State { style: TextStyle(fontSize: 12, color: ColorConsts.primaryBlue), ), Text( - " ${_dataList[index].ayatNumberInSurah}", - style: TextStyle(fontSize: 14, color: ColorConsts.secondaryOrange), + " ${_dataList[index].ayatNumberInSurahs.split(",").toList().reversed.join(",")}", + style: TextStyle(fontSize: 14, fontFamily: "BArabics", color: ColorConsts.secondaryOrange), ), ], ), diff --git a/lib/widgets/aya_player_widget.dart b/lib/widgets/aya_player_widget.dart index c9f248c..05c8638 100644 --- a/lib/widgets/aya_player_widget.dart +++ b/lib/widgets/aya_player_widget.dart @@ -85,12 +85,12 @@ class _AyaPlayerWidgetState extends State { if (_tempIndex == null || _tempIndex != index) { _tempIndex = index; String encodedImage = widget.voiceNoteList.elementAt(index).profilePicture; - if (encodedImage.contains("data:image/png;base64,")) { + if (encodedImage?.contains("data:image/png;base64,") ?? false) { encodedImage = encodedImage.replaceAll("data:image/png;base64,", ""); - } + } if (encodedImage == null) return null; temp = base64Decode(encodedImage); } - + if (temp == null) return null; return DecorationImage( fit: BoxFit.cover, image: MemoryImage(temp), @@ -139,35 +139,36 @@ class _AyaPlayerWidgetState extends State { }); }, ), - StreamBuilder( - stream: _player.currentIndexStream, - builder: (context, snapshot) { - int state = snapshot.data; - if (state == null) return SizedBox(); - return Container( - width: 50.0, - margin: EdgeInsets.only(left: 8, right: 8), - height: 50.0, - decoration: BoxDecoration( - image: widget.voiceNoteList.length < 1 ? null : getDecodedImage(state), - borderRadius: BorderRadius.all( - Radius.circular(30.0), + if ((widget.voiceNoteList?.length ?? 0) > 0) + StreamBuilder( + stream: _player.currentIndexStream, + builder: (context, snapshot) { + int state = snapshot.data; + if (state == null) return SizedBox(); + return Container( + width: 50.0, + margin: EdgeInsets.only(left: 8, right: 8), + height: 50.0, + decoration: BoxDecoration( + image: widget.voiceNoteList.length < 1 ? null : getDecodedImage(state), + borderRadius: BorderRadius.all( + Radius.circular(30.0), + ), ), - ), - child: widget.voiceNoteList.length < 1 - ? ClipRRect( - borderRadius: BorderRadius.all( - Radius.circular(30.0), - ), - child: SvgPicture.asset( - "assets/icons/chat_user.svg", - clipBehavior: Clip.antiAlias, - ), - ) - : null, - ); - }, - ), + child: widget.voiceNoteList.length < 1 + ? ClipRRect( + borderRadius: BorderRadius.all( + Radius.circular(30.0), + ), + child: SvgPicture.asset( + "assets/icons/chat_user.svg", + clipBehavior: Clip.antiAlias, + ), + ) + : null, + ); + }, + ), Expanded( child: Column( mainAxisSize: MainAxisSize.min, @@ -180,16 +181,17 @@ class _AyaPlayerWidgetState extends State { style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: ColorConsts.primaryBlack, height: 1), ), SizedBox(height: 4), - StreamBuilder( - stream: _player.currentIndexStream, - builder: (context, snapshot) { - final state = snapshot.data; - return Text( - (state == null || widget.voiceNoteList.isEmpty) ? "" : widget.voiceNoteList?.elementAt(state)?.userName ?? "", - style: TextStyle(fontSize: 10, color: ColorConsts.textGrey1, height: 1), - ); - }, - ), + if ((widget.voiceNoteList?.length ?? 0) > 0) + StreamBuilder( + stream: _player.currentIndexStream, + builder: (context, snapshot) { + final state = snapshot.data; + return Text( + (state == null || widget.voiceNoteList.isEmpty) ? "" : widget.voiceNoteList?.elementAt(state)?.userName ?? "", + style: TextStyle(fontSize: 10, color: ColorConsts.textGrey1, height: 1), + ); + }, + ), ], ), ), @@ -234,80 +236,81 @@ class _AyaPlayerWidgetState extends State { ], ), SizedBox(height: 8), - Row( - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - commonIconButton("assets/icons/next_aya.svg", () { - _player.seekToNext(); - }), - SizedBox(width: 4), - StreamBuilder( - stream: _player.playerStateStream, - builder: (context, snapshot) { - final state = snapshot.data?.playing ?? false; - if (state) { - if (_player?.duration?.inSeconds == _player?.position?.inSeconds) { - _player.pause(); - _player.seek(Duration.zero); + if ((widget.voiceNoteList?.length ?? 0) > 0) + Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + commonIconButton("assets/icons/next_aya.svg", () { + _player.seekToNext(); + }), + SizedBox(width: 4), + StreamBuilder( + stream: _player.playerStateStream, + builder: (context, snapshot) { + final state = snapshot.data?.playing ?? false; + if (state) { + if (_player?.duration?.inSeconds == _player?.position?.inSeconds) { + _player.pause(); + _player.seek(Duration.zero); + } } - } - return commonIconButton(state ? "assets/icons/pause.svg" : "assets/icons/play_aya.svg", () { - state - ? _player.pause() - : _isAudioHaveError - ? Utils.showToast("خطأ في تحميل ملف الصوت") - : _player.play(); - }); - }, - ), - SizedBox(width: 4), - commonIconButton("assets/icons/previous_aya.svg", () { - _player.seekToPrevious(); - }), - SizedBox(width: 16), - Expanded( - child: StreamBuilder( + return commonIconButton(state ? "assets/icons/pause.svg" : "assets/icons/play_aya.svg", () { + state + ? _player.pause() + : _isAudioHaveError + ? Utils.showToast("خطأ في تحميل ملف الصوت") + : _player.play(); + }); + }, + ), + SizedBox(width: 4), + commonIconButton("assets/icons/previous_aya.svg", () { + _player.seekToPrevious(); + }), + SizedBox(width: 16), + Expanded( + child: StreamBuilder( + stream: _player.positionStream, + builder: (context, snapshot) { + final state = snapshot.data; + return SliderTheme( + data: SliderTheme.of(context).copyWith( + inactiveTrackColor: ColorConsts.sliderBackground, + activeTrackColor: ColorConsts.secondaryOrange, + trackHeight: 8.0, + thumbColor: ColorConsts.primaryBlack, + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 10.0), + overlayColor: ColorConsts.primaryBlack.withAlpha(32), + overlayShape: RoundSliderOverlayShape(overlayRadius: 12.0), + ), + child: Directionality( + textDirection: TextDirection.ltr, + child: Slider( + value: (state?.inSeconds ?? 0) + 0.0, + min: 0, + max: (_player?.duration?.inSeconds ?? 0) + 0.0, + onChanged: (value) { + _player.seek(Duration(seconds: value.round())); + }, + ), + ), + ); + }, + ), + ), + SizedBox(width: 8), + StreamBuilder( stream: _player.positionStream, builder: (context, snapshot) { final state = snapshot.data; - return SliderTheme( - data: SliderTheme.of(context).copyWith( - inactiveTrackColor: ColorConsts.sliderBackground, - activeTrackColor: ColorConsts.secondaryOrange, - trackHeight: 8.0, - thumbColor: ColorConsts.primaryBlack, - thumbShape: RoundSliderThumbShape(enabledThumbRadius: 10.0), - overlayColor: ColorConsts.primaryBlack.withAlpha(32), - overlayShape: RoundSliderOverlayShape(overlayRadius: 12.0), - ), - child: Directionality( - textDirection: TextDirection.ltr, - child: Slider( - value: (state?.inSeconds ?? 0) + 0.0, - min: 0, - max: (_player?.duration?.inSeconds ?? 0) + 0.0, - onChanged: (value) { - _player.seek(Duration(seconds: value.round())); - }, - ), - ), + return Text( + _durationTime(state) ?? "", + style: TextStyle(color: ColorConsts.textGrey1, height: 1.1, fontFamily: "Roboto"), ); }, ), - ), - SizedBox(width: 8), - StreamBuilder( - stream: _player.positionStream, - builder: (context, snapshot) { - final state = snapshot.data; - return Text( - _durationTime(state) ?? "", - style: TextStyle(color: ColorConsts.textGrey1, height: 1.1, fontFamily: "Roboto"), - ); - }, - ), - ], - ) + ], + ) ], ), ); diff --git a/lib/widgets/aya_record_widget.dart b/lib/widgets/aya_record_widget.dart index 3dd7ab7..9bdf9d2 100644 --- a/lib/widgets/aya_record_widget.dart +++ b/lib/widgets/aya_record_widget.dart @@ -155,84 +155,86 @@ class _AyaRecordWidgetState extends State { height: 60, ), ), - Container( - height: 50, - margin: EdgeInsets.all(16), - padding: EdgeInsets.only(left: 12, right: 12), - decoration: BoxDecoration( - color: ColorConsts.secondaryWhite, - borderRadius: BorderRadius.circular(30), - ), - child: Row( - children: [ - Expanded( - child: StreamBuilder( + if (!(recordFilePath != null && File(recordFilePath).existsSync())) SizedBox(height: 16), + if (recordFilePath != null && File(recordFilePath).existsSync()) + Container( + height: 50, + margin: EdgeInsets.all(16), + padding: EdgeInsets.only(left: 12, right: 12), + decoration: BoxDecoration( + color: ColorConsts.secondaryWhite, + borderRadius: BorderRadius.circular(30), + ), + child: Row( + children: [ + Expanded( + child: StreamBuilder( + stream: _audioPlayer.positionStream, + builder: (context, snapshot) { + final state = snapshot.data; + return SliderTheme( + data: SliderTheme.of(context).copyWith( + inactiveTrackColor: ColorConsts.sliderBackground, + activeTrackColor: ColorConsts.secondaryOrange, + trackHeight: 3.0, + thumbColor: ColorConsts.primaryBlack, + thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6.0), + overlayColor: ColorConsts.primaryBlack.withAlpha(32), + overlayShape: RoundSliderOverlayShape(overlayRadius: 6.0), + ), + child: Directionality( + textDirection: TextDirection.ltr, + child: Slider( + value: (state?.inSeconds ?? 0) + 0.0, + min: 0, + max: (_audioPlayer?.duration?.inSeconds ?? 0) + 0.0, + onChanged: (value) {}, + ), + ), + ); + }, + ), + ), + SizedBox(width: 8), + StreamBuilder( stream: _audioPlayer.positionStream, builder: (context, snapshot) { final state = snapshot.data; - return SliderTheme( - data: SliderTheme.of(context).copyWith( - inactiveTrackColor: ColorConsts.sliderBackground, - activeTrackColor: ColorConsts.secondaryOrange, - trackHeight: 3.0, - thumbColor: ColorConsts.primaryBlack, - thumbShape: RoundSliderThumbShape(enabledThumbRadius: 6.0), - overlayColor: ColorConsts.primaryBlack.withAlpha(32), - overlayShape: RoundSliderOverlayShape(overlayRadius: 6.0), - ), - child: Directionality( - textDirection: TextDirection.ltr, - child: Slider( - value: (state?.inSeconds ?? 0) + 0.0, - min: 0, - max: (_audioPlayer?.duration?.inSeconds ?? 0) + 0.0, - onChanged: (value) {}, - ), - ), + return Text( + _durationTime(state) ?? "", + style: TextStyle(color: ColorConsts.textGrey1, height: 1.1, fontFamily: "Roboto", fontSize: 16), ); }, ), - ), - SizedBox(width: 8), - StreamBuilder( - stream: _audioPlayer.positionStream, - builder: (context, snapshot) { - final state = snapshot.data; - return Text( - _durationTime(state) ?? "", - style: TextStyle(color: ColorConsts.textGrey1, height: 1.1, fontFamily: "Roboto", fontSize: 16), - ); - }, - ), - SizedBox(width: 8), - StreamBuilder( - stream: _audioPlayer.playerStateStream, - builder: (context, snapshot) { - final state = snapshot.data?.playing ?? false; - if (state) { - if (_audioPlayer?.duration?.inSeconds == _audioPlayer?.position?.inSeconds) { - _audioPlayer.pause(); - _audioPlayer.seek(Duration.zero); + SizedBox(width: 8), + StreamBuilder( + stream: _audioPlayer.playerStateStream, + builder: (context, snapshot) { + final state = snapshot.data?.playing ?? false; + if (state) { + if (_audioPlayer?.duration?.inSeconds == _audioPlayer?.position?.inSeconds) { + _audioPlayer.pause(); + _audioPlayer.seek(Duration.zero); + } } - } - return IconButton( - highlightColor: Colors.transparent, - splashColor: Colors.transparent, - constraints: BoxConstraints(), - padding: EdgeInsets.only(right: 0), - icon: SvgPicture.asset(state ? "assets/icons/pause.svg" : "assets/icons/play_aya.svg", height: 16, width: 16), - onPressed: () { - state - ? _audioPlayer.pause() - : _isAudioHaveError - ? Utils.showToast("خطأ في تحميل ملف الصوت") - : play(); - }); - }, - ), - ], - ), - ) + return IconButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + constraints: BoxConstraints(), + padding: EdgeInsets.only(right: 0), + icon: SvgPicture.asset(state ? "assets/icons/pause.svg" : "assets/icons/play_aya.svg", height: 16, width: 16), + onPressed: () { + state + ? _audioPlayer.pause() + : _isAudioHaveError + ? Utils.showToast("خطأ في تحميل ملف الصوت") + : play(); + }); + }, + ), + ], + ), + ) ], ), ), diff --git a/lib/widgets/video_player_widget.dart b/lib/widgets/video_player_widget.dart new file mode 100644 index 0000000..e9636de --- /dev/null +++ b/lib/widgets/video_player_widget.dart @@ -0,0 +1,122 @@ +import 'package:flutter/material.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:tangheem/classes/colors.dart'; +import 'package:tangheem/classes/consts.dart'; +import 'package:youtube_player_flutter/youtube_player_flutter.dart'; + +class VideoPlayerWidget extends StatefulWidget { + final String link; + + VideoPlayerWidget(this.link, {Key key}) : super(key: key); + + @override + _VideoPlayerWidgetState createState() { + return _VideoPlayerWidgetState(); + } +} + +class _VideoPlayerWidgetState extends State { + bool doNotShowAgain = false; + YoutubePlayerController _controller; + + @override + void initState() { + super.initState(); + _controller = YoutubePlayerController( + initialVideoId: widget.link, + flags: YoutubePlayerFlags( + autoPlay: true, + mute: true, + ), + ); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Dialog( + insetPadding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 24.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + backgroundColor: Colors.transparent, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: ColorConsts.primaryBlue, + borderRadius: BorderRadius.circular(16), + ), + padding: EdgeInsets.symmetric(vertical: MediaQuery.of(context).orientation == Orientation.portrait ? 32 : 16, horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + MediaQuery.of(context).orientation == Orientation.portrait ? videoPlayer() : Expanded(child: videoPlayer()), + SizedBox(height: 8), + Row( + children: [ + Expanded( + child: Text( + "لا تظهر مرة أخرى", + textAlign: TextAlign.right, + style: TextStyle(color: Colors.white), + ), + ), + SizedBox(width: 16), + SizedBox( + width: 12, + height: 12, + child: Checkbox( + value: doNotShowAgain, + side: BorderSide(color: Colors.white), + activeColor: ColorConsts.secondaryPink, + onChanged: (value) { + setState(() { + doNotShowAgain = value; + }); + }, + ), + ), + ], + ), + SizedBox(height: 16), + SizedBox( + width: double.infinity, + height: 40, + child: TextButton( + onPressed: () async { + if (doNotShowAgain) { + SharedPreferences prefs = await SharedPreferences.getInstance(); + await prefs.setBool(GlobalConsts.doNotShowWelcomeVideo, doNotShowAgain); + } + Navigator.pop(context); + }, + style: TextButton.styleFrom( + primary: Colors.white, + padding: EdgeInsets.all(2), + backgroundColor: ColorConsts.secondaryPink, + textStyle: TextStyle(fontSize: 14, fontFamily: "DroidKufi"), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6.0), + ), + ), + child: Text("أغلق"), + ), + ), + ], + ), + ), + ); + } + + Widget videoPlayer() { + return YoutubePlayer( + controller: _controller, + showVideoProgressIndicator: true, + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 3e6c39b..743e483 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,13 +27,13 @@ dependencies: # The following adds the Cupertino Icons font to your application. # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.2 - flutter_svg: ^0.19.3 + flutter_svg: ^0.23.0+1 fluttertoast: ^7.1.8 http: ^0.13.0 - image_gallery_saver: ^1.6.8 + image_gallery_saver: ^1.7.1 path_provider: ^2.0.1 permission_handler: ^6.1.1 - share: ^2.0.1 + share: ^2.0.4 just_audio: ^0.7.2 intl: ^0.17.0 shared_preferences: ^2.0.5 @@ -43,6 +43,8 @@ dependencies: syncfusion_flutter_pdfviewer: ^19.1.59-beta firebase_core: ^1.3.0 firebase_analytics: ^8.1.2 + youtube_player_flutter: ^8.0.0 + flutter_html: ^2.1.5 dev_dependencies: flutter_test: @@ -87,6 +89,9 @@ flutter: - asset: assets/fonts/UthmanicHafs-Regular.ttf - asset: assets/fonts/UthmanicHafs-Bold.ttf weight: 700 + - family: BArabics + fonts: + - asset: assets/fonts/B-Arabics.ttf # # For details regarding fonts from package dependencies, # see https://flutter.dev/custom-fonts/#from-packages