diff --git a/assets/icons/chat_user.svg b/assets/icons/chat_user.svg new file mode 100644 index 0000000..ad14f39 --- /dev/null +++ b/assets/icons/chat_user.svg @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/lib/api/admin_configuration_api_client.dart b/lib/api/admin_configuration_api_client.dart new file mode 100644 index 0000000..c3ea326 --- /dev/null +++ b/lib/api/admin_configuration_api_client.dart @@ -0,0 +1,18 @@ +import 'package:tangheem/app_state/app_state.dart'; +import 'package:tangheem/classes/consts.dart'; +import 'package:tangheem/models/general_response_model.dart'; + +import 'api_client.dart'; + +class AdminConfigurationApiClient { + static final AdminConfigurationApiClient _instance = AdminConfigurationApiClient._internal(); + AdminConfigurationApiClient._internal(); + factory AdminConfigurationApiClient() => _instance; + + Future addDiscussion(String discussionText, String ayaTangheemTypeId) async { + String url = "${ApiConsts.adminConfiguration}Discussion_Add"; + var postParams = {"discussionText": discussionText, "ayaTangheemTypeId": ayaTangheemTypeId}; + var _headers = {"Authorization": "Bearer ${AppState().token}"}; + return await ApiClient().postJsonForObject((json) => GeneralResponseModel.fromJson(json), url, postParams, headers: _headers); + } +} diff --git a/lib/api/tangheem_user_api_client.dart b/lib/api/tangheem_user_api_client.dart index 05a0bcd..b636fdd 100644 --- a/lib/api/tangheem_user_api_client.dart +++ b/lib/api/tangheem_user_api_client.dart @@ -4,6 +4,7 @@ import 'package:tangheem/models/aya_tangheem_property.dart'; import 'package:tangheem/models/aya_tangheem_type.dart'; import 'package:tangheem/models/aya_tangheem_type_mapped.dart'; import 'package:tangheem/models/country_model.dart'; +import 'package:tangheem/models/discussion_model.dart'; import 'package:tangheem/models/general_response_model.dart'; import 'package:tangheem/models/quick_links_model.dart'; import 'package:tangheem/models/tangheem_type_model.dart'; @@ -97,4 +98,12 @@ class TangheemUserApiClient { var postParams = {}; return await ApiClient().postJsonForObject((json) => CountryModel.fromJson(json), url, postParams); } + + Future getDiscussionByTangheemID(int pageNumber, String tangheemID) async { + String url = "${ApiConsts.tangheemUsers}Discussion_Get"; + var postParams = {"itemsPerPage": 5, "currentPageNo": pageNumber, "ayaTangheemTypeId": tangheemID}; + postParams["itemsPerPage"] = null; + postParams["currentPageNo"] = null; + return await ApiClient().postJsonForObject((json) => DiscussionModel.fromJson(json), url, postParams); + } } diff --git a/lib/app_state/app_state.dart b/lib/app_state/app_state.dart new file mode 100644 index 0000000..2b55228 --- /dev/null +++ b/lib/app_state/app_state.dart @@ -0,0 +1,21 @@ +import 'package:tangheem/models/surah_model.dart'; +import 'package:tangheem/models/authentication_user_model.dart'; + +class AppState { + static final AppState _instance = AppState._internal(); + AppState._internal(); + factory AppState() => _instance; + + SurahModel _surahModel; + SurahModel get getSurahModel => _surahModel; + void setSurahModel(SurahModel _surahModel) { + this._surahModel = _surahModel; + } + + AuthenticationUserModel _authenticationUser; + bool get isUserLogin => _authenticationUser != null; + String get token => _authenticationUser?.result?.data?.token; + void setAuthenticationModel(AuthenticationUserModel _authenticationUser) { + this._authenticationUser = _authenticationUser; + } +} diff --git a/lib/app_state/surah_app_state.dart b/lib/app_state/surah_app_state.dart deleted file mode 100644 index 913c93e..0000000 --- a/lib/app_state/surah_app_state.dart +++ /dev/null @@ -1,13 +0,0 @@ -import 'package:tangheem/models/surah_model.dart'; - -class SurahAppState { - static final SurahAppState _instance = SurahAppState._internal(); - SurahAppState._internal(); - factory SurahAppState() => _instance; - - SurahModel _surahModel; - SurahModel get getSurahModel => _surahModel; - void setSurahModel(SurahModel _surahModel) { - this._surahModel = _surahModel; - } -} diff --git a/lib/classes/consts.dart b/lib/classes/consts.dart index c61e47b..362228a 100644 --- a/lib/classes/consts.dart +++ b/lib/classes/consts.dart @@ -3,5 +3,12 @@ class ApiConsts { static String baseUrl = "https://api.cssynapses.com/tangheem/"; // Live server static String authentication = baseUrl + "api/Authentication/"; static String tangheemUsers = baseUrl + "api/TangheemUsers/"; + static String adminConfiguration = baseUrl + "api/AdminConfiguration/"; static String user = baseUrl + "api/User/"; } + +class GlobalConsts { + static String isRememberMe = "remember_me"; + static String email = "email"; + static String password = "password"; +} diff --git a/lib/extensions/email_validator.dart b/lib/extensions/email_validator.dart deleted file mode 100644 index 97ee38a..0000000 --- a/lib/extensions/email_validator.dart +++ /dev/null @@ -1,5 +0,0 @@ -extension EmailValidator on String { - bool isValidEmail() { - return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); - } -} diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart new file mode 100644 index 0000000..ddd1c6f --- /dev/null +++ b/lib/extensions/string_extensions.dart @@ -0,0 +1,14 @@ +import 'package:intl/intl.dart'; + +extension EmailValidator on String { + bool isValidEmail() { + return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); + } + + String toFormattedDate() { + DateFormat inputFormat = DateFormat('yyyy-mm-ddThh:mm:ss'); + DateTime inputDate = inputFormat.parse(this); + DateFormat outputFormat = DateFormat('dd/MM/yyyy hh:mm:a'); + return outputFormat.format(inputDate); + } +} diff --git a/lib/models/aya_tangheem_type_mapped.dart b/lib/models/aya_tangheem_type_mapped.dart index 162095f..81ebed8 100644 --- a/lib/models/aya_tangheem_type_mapped.dart +++ b/lib/models/aya_tangheem_type_mapped.dart @@ -42,6 +42,7 @@ class AyatTangheemTypeMappedData { int ayatNumberInSurah; String highlightText; String userId; + String ayahTextBase; List property; List voiceNote; @@ -57,6 +58,7 @@ class AyatTangheemTypeMappedData { this.ayatNumberInSurah, this.highlightText, this.userId, + this.ayahTextBase, this.property, this.voiceNote}); @@ -72,6 +74,7 @@ class AyatTangheemTypeMappedData { ayatNumberInSurah = json['ayatNumberInSurah']; highlightText = json['highlightText']; userId = json['userId']; + ayahTextBase = json['ayahTextBase']; if (json['property'] != null) { property = new List(); json['property'].forEach((v) { @@ -99,6 +102,7 @@ class AyatTangheemTypeMappedData { data['ayatNumberInSurah'] = this.ayatNumberInSurah; data['highlightText'] = this.highlightText; data['userId'] = this.userId; + data['ayahTextBase'] = this.ayahTextBase; if (this.property != null) { data['property'] = this.property.map((v) => v.toJson()).toList(); } @@ -151,23 +155,28 @@ class VoiceNote { String voiceNoteId; String voiceNotePath; String exposeFilePath; - String userId; + String filePath; + String voiceNoteAuthorId; String userName; String fileName; String fileType; String ayaTangheemTypeId; + String profilePicture; - VoiceNote({this.voiceNoteId, this.voiceNotePath, this.exposeFilePath, this.userId, this.userName, this.fileName, this.fileType, this.ayaTangheemTypeId}); + VoiceNote( + {this.voiceNoteId, this.voiceNotePath, this.exposeFilePath, this.filePath, this.voiceNoteAuthorId, this.userName, this.fileName, this.fileType, this.ayaTangheemTypeId, this.profilePicture}); VoiceNote.fromJson(Map json) { voiceNoteId = json['voiceNoteId']; voiceNotePath = json['voiceNotePath']; exposeFilePath = json['exposeFilePath']; - userId = json['userId']; + filePath = json['filePath']; + voiceNoteAuthorId = json['voiceNoteAuthorId']; userName = json['userName']; fileName = json['fileName']; fileType = json['fileType']; ayaTangheemTypeId = json['ayaTangheemTypeId']; + profilePicture = json['profilePicture']; } Map toJson() { @@ -175,11 +184,13 @@ class VoiceNote { data['voiceNoteId'] = this.voiceNoteId; data['voiceNotePath'] = this.voiceNotePath; data['exposeFilePath'] = this.exposeFilePath; - data['userId'] = this.userId; + data['filePath'] = this.filePath; + data['voiceNoteAuthorId'] = this.voiceNoteAuthorId; data['userName'] = this.userName; data['fileName'] = this.fileName; data['fileType'] = this.fileType; data['ayaTangheemTypeId'] = this.ayaTangheemTypeId; + data['profilePicture'] = this.profilePicture; return data; } } diff --git a/lib/models/discussion_model.dart b/lib/models/discussion_model.dart new file mode 100644 index 0000000..a9a9b9c --- /dev/null +++ b/lib/models/discussion_model.dart @@ -0,0 +1,97 @@ +class DiscussionModel { + int totalItemsCount; + int statusCode; + String message; + List data; + + DiscussionModel( + {this.totalItemsCount, this.statusCode, this.message, this.data}); + + DiscussionModel.fromJson(Map json) { + totalItemsCount = json['totalItemsCount']; + statusCode = json['statusCode']; + message = json['message']; + if (json['data'] != null) { + data = new List(); + json['data'].forEach((v) { + data.add(new DiscussionModelData.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['totalItemsCount'] = this.totalItemsCount; + data['statusCode'] = this.statusCode; + data['message'] = this.message; + if (this.data != null) { + data['data'] = this.data.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class DiscussionModelData { + String discussionId; + String discussionText; + String ayaTangheemTypeId; + int statusId; + String status; + bool isResponse; + String responseId; + String userName; + String ayaText; + String highlightText; + String adminResponse; + String adminDiscussionId; + String date; + + DiscussionModelData( + {this.discussionId, + this.discussionText, + this.ayaTangheemTypeId, + this.statusId, + this.status, + this.isResponse, + this.responseId, + this.userName, + this.ayaText, + this.highlightText, + this.adminResponse, + this.adminDiscussionId, + this.date}); + + DiscussionModelData.fromJson(Map json) { + discussionId = json['discussionId']; + discussionText = json['discussionText']; + ayaTangheemTypeId = json['ayaTangheemTypeId']; + statusId = json['statusId']; + status = json['status']; + isResponse = json['isResponse']; + responseId = json['responseId']; + userName = json['userName']; + ayaText = json['ayaText']; + highlightText = json['highlightText']; + adminResponse = json['adminResponse']; + adminDiscussionId = json['adminDiscussionId']; + date = json['date']; + } + + Map toJson() { + final Map data = new Map(); + data['discussionId'] = this.discussionId; + data['discussionText'] = this.discussionText; + data['ayaTangheemTypeId'] = this.ayaTangheemTypeId; + data['statusId'] = this.statusId; + data['status'] = this.status; + data['isResponse'] = this.isResponse; + data['responseId'] = this.responseId; + data['userName'] = this.userName; + data['ayaText'] = this.ayaText; + data['highlightText'] = this.highlightText; + data['adminResponse'] = this.adminResponse; + data['adminDiscussionId'] = this.adminDiscussionId; + data['date'] = this.date; + return data; + } +} diff --git a/lib/ui/common_appbar.dart b/lib/ui/common_appbar.dart index ff9c081..09d3d66 100644 --- a/lib/ui/common_appbar.dart +++ b/lib/ui/common_appbar.dart @@ -1,9 +1,14 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.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/utils.dart'; +import 'package:tangheem/models/quick_links_model.dart'; import 'package:tangheem/ui/screens/contact_us_screen.dart'; import 'package:tangheem/ui/screens/login_screen.dart'; import 'package:tangheem/ui/screens/quran_screen.dart'; +import 'package:url_launcher/url_launcher.dart'; class CommonAppbar extends StatefulWidget { final bool showDrawer; @@ -20,10 +25,21 @@ class CommonAppbar extends StatefulWidget { class _CommonAppbarState extends State { final GlobalKey _scaffoldKey = new GlobalKey(); - + List quickLinks = []; @override void initState() { super.initState(); + getQuickLinks(); + } + + getQuickLinks() async { + if (widget.showDrawer) { + try { + quickLinks = (await TangheemUserApiClient().quickLinks())?.data ?? []; + quickLinks.sort((a, b) => a.orderNo.compareTo(b.orderNo)); + } catch (ex) {} + setState(() {}); + } } @override @@ -115,6 +131,10 @@ class _CommonAppbarState extends State { commonIconButton("assets/icons/reduce_size.svg", () {}), commonIconButton("assets/icons/notification.svg", () {}), commonIconButton("assets/icons/user_logged.svg", () { + if (AppState().isUserLogin) { + Utils.showToast("أنت بالفعل تسجيل الدخول"); + return; + } Navigator.pushNamed(context, LoginScreen.routeName); }), ], @@ -229,12 +249,10 @@ class _CommonAppbarState extends State { padding: EdgeInsets.only(left: 32, right: 32), child: Row( children: [ - commonIconButton("assets/icons/linkedin.svg", () {}, size: 35), - commonIconButton("assets/icons/pinterest.svg", () {}, size: 35), - commonIconButton("assets/icons/whatsapp.svg", () {}, size: 35), - commonIconButton("assets/icons/facebook.svg", () {}, size: 35), - commonIconButton("assets/icons/instgram.svg", () {}, size: 35), - commonIconButton("assets/icons/twitter.svg", () {}, size: 35), + for (QuickLinksData _quickLink in quickLinks) + commonIconButton(_quickLink.exposeFilePath, () { + _launchURL(_quickLink.imageUrl); + }, size: 35, isAsset: false), ], ), ), @@ -261,11 +279,13 @@ class _CommonAppbarState extends State { ); } - Widget commonIconButton(String icon, VoidCallback onPressed, {double size}) { + void _launchURL(String _url) async => await canLaunch(_url) ? await launch(_url) : throw 'Could not launch $_url'; + + Widget commonIconButton(String icon, VoidCallback onPressed, {double size, bool isAsset = true}) { return Expanded( child: IconButton( padding: EdgeInsets.zero, - icon: SvgPicture.asset(icon, height: size ?? 25, width: size ?? 30), + icon: isAsset ? SvgPicture.asset(icon, height: size ?? 25, width: size ?? 30) : Image.network(icon, height: size ?? 25, width: size ?? 30), onPressed: () { Navigator.pop(context); Future.delayed(Duration(milliseconds: 200), () => onPressed()); diff --git a/lib/ui/dialogs/discussion_input_dialog.dart b/lib/ui/dialogs/discussion_input_dialog.dart new file mode 100644 index 0000000..03ceb49 --- /dev/null +++ b/lib/ui/dialogs/discussion_input_dialog.dart @@ -0,0 +1,98 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:tangheem/api/user_api_client.dart'; +import 'package:tangheem/classes/colors.dart'; +import 'package:tangheem/classes/utils.dart'; +import 'package:tangheem/widgets/common_textfield_widget.dart'; +import 'package:tangheem/widgets/otp_widget.dart'; + +class DiscussionInputDialog extends StatefulWidget { + final Function(String) onCommentPress; + DiscussionInputDialog({Key key, this.onCommentPress}) : super(key: key); + + @override + _DiscussionInputDialogState createState() { + return _DiscussionInputDialogState(); + } +} + +class _DiscussionInputDialogState extends State { + final TextEditingController _commentController = TextEditingController(); + final FocusNode _focusNode = FocusNode(); + + bool hasError = false; + String errorMessage; + String otpMessage = ""; + + @override + void initState() { + super.initState(); + _focusNode.requestFocus(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Dialog( + insetPadding: EdgeInsets.symmetric(horizontal: 60.0, vertical: 24.0), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + elevation: 0, + backgroundColor: Colors.transparent, + child: Directionality( + textDirection: TextDirection.rtl, + child: Container( + width: double.infinity, + decoration: BoxDecoration( + color: ColorConsts.primaryBlue, + borderRadius: BorderRadius.circular(16), + ), + padding: EdgeInsets.symmetric(vertical: 16, horizontal: 16), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + "اضاف تعليق", + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white, fontSize: 16), + ), + SizedBox(height: 12), + CommonTextFieldWidget(hint: "فحوى التعليق", controller: _commentController, maxLines: 5, focusNode: _focusNode), + SizedBox(height: 12), + SizedBox( + width: double.infinity, + height: 40, + child: TextButton( + onPressed: () { + if (_commentController.text.length < 1) { + Utils.showToast("يجب أن تكتب كلمات قليلة"); + return; + } + _focusNode.unfocus(); + widget.onCommentPress(_commentController.text); + }, + 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("أضف تعليقك"), + ), + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/ui/screens/contact_us_screen.dart b/lib/ui/screens/contact_us_screen.dart index 75ee31c..cc796da 100644 --- a/lib/ui/screens/contact_us_screen.dart +++ b/lib/ui/screens/contact_us_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter_svg/svg.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/extensions/email_validator.dart'; +import 'package:tangheem/extensions/string_extensions.dart'; import 'package:tangheem/widgets/common_textfield_widget.dart'; class ContactUsScreen extends StatefulWidget { diff --git a/lib/ui/screens/forgot_password_screen.dart b/lib/ui/screens/forgot_password_screen.dart index a370e62..ed78e77 100644 --- a/lib/ui/screens/forgot_password_screen.dart +++ b/lib/ui/screens/forgot_password_screen.dart @@ -7,7 +7,7 @@ import 'package:tangheem/models/general_response_model.dart'; import 'package:tangheem/ui/dialogs/change_password_dialog.dart'; import 'package:tangheem/ui/dialogs/otp_dialog.dart'; import 'package:tangheem/widgets/common_textfield_widget.dart'; -import 'package:tangheem/extensions/email_validator.dart'; +import 'package:tangheem/extensions/string_extensions.dart'; class ForgotPasswordScreen extends StatefulWidget { static const String routeName = "/forgot_password"; diff --git a/lib/ui/screens/home_screen.dart b/lib/ui/screens/home_screen.dart index 8fb6567..43b02e8 100644 --- a/lib/ui/screens/home_screen.dart +++ b/lib/ui/screens/home_screen.dart @@ -4,12 +4,11 @@ import 'package:flutter/painting.dart'; import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:tangheem/api/tangheem_user_api_client.dart'; -import 'package:tangheem/app_state/surah_app_state.dart'; +import 'package:tangheem/app_state/app_state.dart'; import 'package:tangheem/classes/colors.dart'; import 'package:tangheem/classes/utils.dart'; import 'package:tangheem/models/surah_model.dart'; import 'package:tangheem/models/tangheem_type_model.dart'; -import 'package:tangheem/ui/screens/quran_screen.dart'; import 'package:tangheem/ui/screens/tangheem_screen.dart'; import 'package:tangheem/widgets/common_dropdown_button.dart'; @@ -48,12 +47,17 @@ class _HomeScreenState extends State { Utils.showLoading(context); try { _surahModel = await TangheemUserApiClient().getSurahs(); - SurahAppState().setSurahModel(_surahModel); + AppState().setSurahModel(_surahModel); _tangheemType = await TangheemUserApiClient().getTangheemType(); _surahList = _surahModel.data.map((element) => element.nameAR).toList(); _tangheemList = _tangheemType?.data?.where((element) => element.isActive)?.toList()?.map((element) => element.tangheemTypeName)?.toList() ?? []; setState(() {}); + + // QuickLinkProvider().setQuickLinksModel(await TangheemUserApiClient().quickLinks()); + // AppState().setQuickLinksModel(await TangheemUserApiClient().quickLinks()); + } catch (ex, tr) { + print(ex); Utils.handleException(ex, null); } finally { Utils.hideLoading(context); @@ -157,7 +161,7 @@ class _HomeScreenState extends State { ), ), SizedBox(height: 16), - Container( + Container( height: 50, padding: EdgeInsets.only(top: 4, bottom: 6), child: TextField( diff --git a/lib/ui/screens/login_screen.dart b/lib/ui/screens/login_screen.dart index 33f5a42..b50e2d7 100644 --- a/lib/ui/screens/login_screen.dart +++ b/lib/ui/screens/login_screen.dart @@ -1,13 +1,16 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:shared_preferences/shared_preferences.dart'; import 'package:tangheem/api/authentication_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/authentication_user_model.dart'; import 'package:tangheem/ui/screens/forgot_password_screen.dart'; import 'package:tangheem/ui/screens/registration_screen.dart'; import 'package:tangheem/widgets/common_textfield_widget.dart'; -import 'package:tangheem/extensions/email_validator.dart'; +import 'package:tangheem/extensions/string_extensions.dart'; class LoginScreen extends StatefulWidget { static const String routeName = "/login"; @@ -30,6 +33,19 @@ class _LoginScreenState extends State { @override void initState() { super.initState(); + checkPrefs(); + } + + checkPrefs() { + SharedPreferences.getInstance().then((value) { + if ((value.getBool(GlobalConsts.isRememberMe) ?? false) == true) { + _isRemember = true; + _emailController.text = value.getString(GlobalConsts.email); + _passwordController.text = value.getString(GlobalConsts.password); + } else { + _isRemember = false; + } + }); } @override @@ -42,9 +58,19 @@ class _LoginScreenState extends State { try { _authenticationUser = await AuthenticationApiClient().authenticateUser(_email, _password); Utils.showToast("تسجيل الدخول بنجاح"); + AppState().setAuthenticationModel(_authenticationUser); + SharedPreferences prefs = await SharedPreferences.getInstance(); + if (!_isRemember) { + _email = ""; + _password = ""; + } + await prefs.setBool(GlobalConsts.isRememberMe, _isRemember); + await prefs.setString(GlobalConsts.email, _email); + await prefs.setString(GlobalConsts.password, _password); + Utils.hideLoading(context); + Navigator.pop(context); } catch (ex, tr) { Utils.handleException(ex, null); - } finally { Utils.hideLoading(context); } } diff --git a/lib/ui/screens/quran_screen.dart b/lib/ui/screens/quran_screen.dart index 33f4b7a..7a16b92 100644 --- a/lib/ui/screens/quran_screen.dart +++ b/lib/ui/screens/quran_screen.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:tangheem/api/tangheem_user_api_client.dart'; -import 'package:tangheem/app_state/surah_app_state.dart'; +import 'package:tangheem/app_state/app_state.dart'; import 'package:tangheem/classes/colors.dart'; import 'package:tangheem/classes/utils.dart'; import 'package:tangheem/models/aya_model.dart'; @@ -57,12 +57,12 @@ class _QuranScreenState extends State { void getSurah() async { try { - if (SurahAppState().getSurahModel == null) { + if (AppState().getSurahModel == null) { Utils.showLoading(context); _surahModel = await TangheemUserApiClient().getSurahs(); - SurahAppState().setSurahModel(_surahModel); + AppState().setSurahModel(_surahModel); } else { - _surahModel = SurahAppState().getSurahModel; + _surahModel = AppState().getSurahModel; } _surahList = _surahModel.data.map((element) => element.nameAR).toList(); _currentPage = 1; @@ -145,11 +145,15 @@ class _QuranScreenState extends State { final ScrollController scrollController = ScrollController(); ScrollToId scrollToId; - String getBismillahWithSurahName(String _surahName, bool isShowBismillah, bool isFirstIsAya) { + String getBismillahWithSurahName(int _surahID, String _surahName, bool isShowBismillah, bool isFirstIsAya) { + String _bismillah = "\n بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيم"; + if (_surahID == 9) { + _bismillah = ""; + } if (isFirstIsAya && isShowBismillah) { - return "" + _surahName + "\n بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ \n"; + return "" + _surahName + "$_bismillah \n"; } else if (isShowBismillah) { - return "\n" + _surahName + "\n بِسۡمِ ٱللَّهِ ٱلرَّحۡمَٰنِ ٱلرَّحِيمِ \n"; + return "\n" + _surahName + "$_bismillah \n"; } return "" + _surahName + "\n"; } @@ -181,7 +185,9 @@ class _QuranScreenState extends State { String _surahAya = ""; _ayaModel?.data?.forEach((element) { - var temp = element.numberInSurah == 1 ? getBismillahWithSurahName(element.surahNameAR, element.surahID != 1, _surahAya.length <= 1) + element.reverseAyatNumber() : element.reverseAyatNumber(); + var temp = element.numberInSurah == 1 + ? getBismillahWithSurahName(element.surahID, element.surahNameAR, element.surahID != 1, _surahAya.length <= 1) + element.reverseAyatNumber() + : element.reverseAyatNumber(); _surahAya = _surahAya + temp; }); diff --git a/lib/ui/screens/registration_screen.dart b/lib/ui/screens/registration_screen.dart index f22f127..86ad5ed 100644 --- a/lib/ui/screens/registration_screen.dart +++ b/lib/ui/screens/registration_screen.dart @@ -7,7 +7,7 @@ import 'package:tangheem/classes/utils.dart'; import 'package:tangheem/models/country_model.dart'; import 'package:tangheem/ui/bottom_sheets/country_selection_bottom_sheet.dart'; import 'package:tangheem/widgets/common_textfield_widget.dart'; -import 'package:tangheem/extensions/email_validator.dart'; +import 'package:tangheem/extensions/string_extensions.dart'; class RegistrationScreen extends StatefulWidget { static const String routeName = "/registration"; diff --git a/lib/ui/screens/tangheem_detail_screen.dart b/lib/ui/screens/tangheem_detail_screen.dart index 359643d..81cb1e0 100644 --- a/lib/ui/screens/tangheem_detail_screen.dart +++ b/lib/ui/screens/tangheem_detail_screen.dart @@ -1,10 +1,18 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:tangheem/api/admin_configuration_api_client.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/utils.dart'; import 'package:tangheem/models/aya_tangheem_type_mapped.dart'; +import 'package:tangheem/models/discussion_model.dart'; +import 'package:tangheem/ui/dialogs/discussion_input_dialog.dart'; import 'package:tangheem/widgets/aya_player_widget.dart'; import 'package:tangheem/widgets/text_highlight_widget.dart'; +import 'package:tangheem/extensions/string_extensions.dart'; +import 'login_screen.dart'; class TangheemDetailScreen extends StatefulWidget { static const String routeName = "/tangheem_detail"; @@ -24,9 +32,10 @@ class _TangheemDetailScreenState extends State { List _tangheemInsideTableEmptyList = []; List _tangheemWords = []; - int _currentTangheemPage = -1; + int _discussionPage = -1; AyatTangheemTypeMapped _ayatTangheemTypeMapped; AyatTangheemTypeMappedData _ayatTangheemTypeMappedData; + DiscussionModel _discussionModel; @override void initState() { @@ -34,20 +43,34 @@ class _TangheemDetailScreenState extends State { _tangheemInsideTableValueList = []; _tangheemInsideTableEmptyList = []; filterData(); + getTangheemDiscussion(); } - // void getTangheemData() async { - // Utils.showLoading(context); - // try { - // _ayatTangheemTypeMapped = await TangheemUserApiClient().getAyaTangheemTypeMapped(widget.surah.surahID, widget.tangheemTypeName); - // if (_ayatTangheemTypeMapped.data.length > 0) _currentTangheemPage = 0; - // } catch (ex, tr) { - // Utils.handleException(ex, null); - // } finally { - // Utils.hideLoading(context); - // } - // filterData(); - // } + void getTangheemDiscussion() async { + Utils.showLoading(context); + try { + _discussionModel = await TangheemUserApiClient().getDiscussionByTangheemID(_discussionPage, widget.ayatTangheemTypeMappedData.ayaTangheemTypeId); + Utils.hideLoading(context); + setState(() {}); + } catch (ex, tr) { + Utils.handleException(ex, null); + Utils.hideLoading(context); + } + } + + void sendComment(String discussionText) async { + Utils.showLoading(context); + try { + await AdminConfigurationApiClient().addDiscussion(discussionText, widget.ayatTangheemTypeMappedData.ayaTangheemTypeId); + Utils.showToast("تم إرسال التعليق ، سيكون مرئيًا بمجرد موافقة المسؤول عليه"); + Utils.hideLoading(context); + Navigator.pop(context); + } catch (ex, tr) { + print(ex); + Utils.handleException(ex, null); + Utils.hideLoading(context); + } + } void filterData() { _ayatTangheemTypeMappedData = widget.ayatTangheemTypeMappedData; @@ -83,135 +106,110 @@ class _TangheemDetailScreenState extends State { ), SizedBox(height: 8), Expanded( - child: Container( - margin: EdgeInsets.only(top: 4, bottom: 4), - padding: EdgeInsets.only(top: 8, bottom: 8, right: 4, left: 4), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(8), - ), - child: Column( - children: [ - Expanded( - child: SingleChildScrollView( - physics: BouncingScrollPhysics(), - child: RepaintBoundary( - key: _globalKey, - child: Material( - color: Colors.white, - child: ListView( - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - padding: EdgeInsets.all(4), - children: [ - TextHighLightWidget( - text: _ayatTangheemTypeMappedData.reverseAyatNumber() ?? "", - valueColor: ColorConsts.primaryBlue, - highlights: _tangheemWords, - style: TextStyle( - fontFamily: "UthmanicHafs", - fontSize: 18, - fontWeight: FontWeight.bold, - ), + child: ListView( + physics: BouncingScrollPhysics(), + padding: EdgeInsets.only(bottom: 16), + children: [ + Container( + margin: EdgeInsets.only(top: 4, bottom: 4), + padding: EdgeInsets.only(top: 8, bottom: 8, right: 4, left: 4), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + child: RepaintBoundary( + key: _globalKey, + child: Material( + color: Colors.white, + child: ListView( + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsets.all(4), + children: [ + TextHighLightWidget( + text: _ayatTangheemTypeMappedData.reverseAyatNumber() ?? "", + valueColor: ColorConsts.primaryBlue, + highlights: _tangheemWords, + style: TextStyle( + fontFamily: "UthmanicHafs", + fontSize: 18, + fontWeight: FontWeight.bold, ), - SizedBox(height: 16), - ListView.separated( - itemCount: _tangheemInsideTableValueList.length, - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - separatorBuilder: (context, index) { - return Divider( - color: Colors.white, - height: 1, - thickness: 0, - ); - }, - itemBuilder: (context, index) { - return Row( - children: [ - Expanded( - child: Container( - height: 40, - padding: EdgeInsets.only(left: 4, right: 8), - alignment: Alignment.centerRight, - child: Text( - _tangheemInsideTableValueList[index].propertyText, - style: TextStyle(fontWeight: FontWeight.bold, color: ColorConsts.secondaryOrange), - ), - color: ColorConsts.secondaryWhite, + ), + SizedBox(height: 16), + ListView.separated( + itemCount: _tangheemInsideTableValueList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + separatorBuilder: (context, index) { + return Divider( + color: Colors.white, + height: 1, + thickness: 0, + ); + }, + itemBuilder: (context, index) { + return Row( + children: [ + Expanded( + child: Container( + height: 40, + padding: EdgeInsets.only(left: 4, right: 8), + alignment: Alignment.centerRight, + child: Text( + _tangheemInsideTableValueList[index].propertyText, + style: TextStyle(fontWeight: FontWeight.bold, color: ColorConsts.secondaryOrange), ), + color: ColorConsts.secondaryWhite, ), - SizedBox(width: 8), - Expanded( - child: Container( - height: 40, - padding: EdgeInsets.only(left: 4, right: 8), - alignment: Alignment.centerRight, - child: Text( - _tangheemInsideTableValueList[index].propertyValue, - style: TextStyle(color: ColorConsts.primaryBlack), - ), - color: ColorConsts.secondaryWhite, - ), - ) - ], - ); - }), - if (_tangheemInsideTableTrueList.isNotEmpty) - Container( - color: ColorConsts.primaryBlue, - margin: EdgeInsets.only(top: 8, bottom: 8), - padding: EdgeInsets.all(8), - child: Column( - children: [ - Text( - _ayatTangheemTypeMappedData.tangheemTypeName ?? "", - style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white), ), - SizedBox(height: 8), - tangheemPropertyView(_tangheemInsideTableTrueList) + SizedBox(width: 8), + Expanded( + child: Container( + height: 40, + padding: EdgeInsets.only(left: 4, right: 8), + alignment: Alignment.centerRight, + child: Text( + _tangheemInsideTableValueList[index].propertyValue, + style: TextStyle(color: ColorConsts.primaryBlack), + ), + color: ColorConsts.secondaryWhite, + ), + ) ], - ), + ); + }), + if (_tangheemInsideTableTrueList.isNotEmpty) + Container( + color: ColorConsts.primaryBlue, + margin: EdgeInsets.only(top: 8, bottom: 8), + padding: EdgeInsets.all(8), + child: Column( + children: [ + Text( + _ayatTangheemTypeMappedData.tangheemTypeName ?? "", + style: TextStyle(fontWeight: FontWeight.bold, color: Colors.white), + ), + SizedBox(height: 8), + tangheemPropertyView(_tangheemInsideTableTrueList) + ], ), - tangheemPropertyView(_tangheemInsideTableEmptyList) - ], - ), + ), + tangheemPropertyView(_tangheemInsideTableEmptyList) + ], ), ), ), ), - // SizedBox(height: 4), - // Padding( - // padding: EdgeInsets.only(left: 4, right: 4), - // child: Row( - // mainAxisAlignment: MainAxisAlignment.spaceBetween, - // children: [ - // nextOptionButton( - // "assets/icons/prev_single.svg", - // "الآيات السابقة", - // (_currentTangheemPage == 0 || (_ayatTangheemTypeMapped.data.length < 1)) - // ? null - // : () { - // _currentTangheemPage = _currentTangheemPage - 1; - // filterData(); - // }),سكندر - // previousOptionButton( - // "assets/icons/next_single.svg", - // "الآيات التالية", - // (_currentTangheemPage == (_ayatTangheemTypeMapped.data.length - 1) || (_ayatTangheemTypeMapped.data.length < 1)) - // ? null - // : () { - // _currentTangheemPage = _currentTangheemPage + 1; - // filterData(); - // }), - // ], - // ), - // ), - ], - ), + ), + SizedBox(height: 8), + discussionView(_discussionModel?.data ?? []), + ], ), ), - AyaPlayerWidget(surahName: _ayatTangheemTypeMappedData.surahNameAr ?? "", globalKey: _globalKey, voiceNoteList: _ayatTangheemTypeMappedData.voiceNote ?? []) + AyaPlayerWidget(surahName: _ayatTangheemTypeMappedData.surahNameAr ?? "", globalKey: _globalKey, voiceNoteList: _ayatTangheemTypeMappedData?.voiceNote ?? []) ], ), ); @@ -297,4 +295,116 @@ class _TangheemDetailScreenState extends State { ), ); } + + Widget discussionView(List _discussionList) { + return Stack( + alignment: Alignment.bottomCenter, + children: [ + Container( + margin: EdgeInsets.only(top: 4, bottom: 25), + padding: EdgeInsets.all(8), + width: double.infinity, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(8), + ), + child: ListView.separated( + padding: EdgeInsets.only(top: 4, bottom: 24), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: _discussionList.length, + separatorBuilder: (context, index) => SizedBox(height: 16), + itemBuilder: (context, index) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + children: [ + SvgPicture.asset( + "assets/icons/chat_user.svg", + width: 60, + height: 60, + ), + SizedBox(width: 8), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + "تعليق على الآية ${widget.ayatTangheemTypeMappedData.ayatNumberInSurah}", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16, color: ColorConsts.primaryBlue, height: 1.5), + ), + SizedBox(height: 4), + Text( + _discussionList[index].date.toFormattedDate(), + style: TextStyle(fontSize: 12, color: ColorConsts.textGrey, height: 1), + ), + ], + ) + ], + ), + SizedBox(height: 4), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Text( + // "عنوان التعليق", + // style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: ColorConsts.primaryBlue, height: 1.5), + // ), + Text( + _discussionList[index].discussionText, + style: TextStyle(fontSize: 14, color: ColorConsts.textGrey, height: 1.4), + ), + ], + ) + ], + ); + }, + ), + ), + Positioned( + bottom: 0, + child: InkWell( + borderRadius: BorderRadius.circular(30), + onTap: () async { + if (!AppState().isUserLogin) { + await Navigator.pushNamed(context, LoginScreen.routeName); + if (!AppState().isUserLogin) { + Utils.showToast("يجب عليك تسجيل الدخول للتعليق"); + return; + } + } + showDialog( + context: context, + barrierColor: ColorConsts.secondaryWhite.withOpacity(0.8), + builder: (BuildContext context) => DiscussionInputDialog(onCommentPress: (comment) { + sendComment(comment); + }), + ); + }, + child: Container( + height: 40, + padding: EdgeInsets.only(left: 24, right: 24), + alignment: Alignment.centerRight, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(30), + color: ColorConsts.gradientPink, + gradient: LinearGradient( + stops: [0.0, 0.5], + begin: Alignment.topCenter, + end: Alignment.bottomCenter, + colors: [ColorConsts.gradientPink, ColorConsts.gradientOrange], + ), + ), + child: Text( + "أضافة تعليق", + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14, color: Colors.white, height: 1.5), + ), + ), + ), + ), + ], + ); + } } diff --git a/lib/widgets/aya_player_widget.dart b/lib/widgets/aya_player_widget.dart index 5455f6e..2323abd 100644 --- a/lib/widgets/aya_player_widget.dart +++ b/lib/widgets/aya_player_widget.dart @@ -1,3 +1,4 @@ +import 'dart:convert'; import 'dart:io'; import 'dart:math' as math; import 'dart:typed_data'; @@ -45,18 +46,19 @@ class _AyaPlayerWidgetState extends State { setAudioSource() { try { - final _playlist = ConcatenatingAudioSource(children: [ - AudioSource.uri( - Uri.parse("https://server6.mp3quran.net/thubti/109.mp3"), - ), - ]); - var voiceList = [AudioSource.uri(Uri.parse("https://server6.mp3quran.net/thubti/109.mp3"))]; + // final _playlist = ConcatenatingAudioSource(children: [ + // AudioSource.uri( + // Uri.parse("https://server6.mp3quran.net/thubti/109.mp3"), + // ), + // ]); + List voiceList = []; - // if ((widget.voiceNoteList?.length ?? 0) > 0) { - // voiceList = widget.voiceNoteList.map((e) => AudioSource.uri(Uri.parse(e.exposeFilePath))).toList(); - // _currentVoiceNote = 0; - // } - // final _playlist = ConcatenatingAudioSource(children: voiceList); + if ((widget.voiceNoteList?.length ?? 0) > 0) { + voiceList = widget.voiceNoteList.map((e) => AudioSource.uri(Uri.parse(e.exposeFilePath))).toList(); + // _currentVoiceNote = 0; + } + // voiceList = [AudioSource.uri(Uri.parse(widget.voiceNoteList.first.exposeFilePath))]; + final _playlist = ConcatenatingAudioSource(children: voiceList); _player.setAudioSource(_playlist, initialIndex: 0, initialPosition: Duration.zero).then((value) => () {}); } catch (e) { _isAudioHaveError = true; @@ -66,7 +68,7 @@ class _AyaPlayerWidgetState extends State { @override void didUpdateWidget(covariant AyaPlayerWidget oldWidget) { if (widget.voiceNoteList != oldWidget.voiceNoteList) { - // setAudioSource(); + setAudioSource(); } super.didUpdateWidget(oldWidget); } @@ -90,27 +92,65 @@ class _AyaPlayerWidgetState extends State { children: [ Row( children: [ - Transform.rotate( - angle: 180 * math.pi / 180, - child: SvgPicture.asset( - "assets/icons/drop_menu.svg", - width: 16, - ), - ), - Container( - width: 50.0, - margin: EdgeInsets.only(left: 8, right: 8), - height: 50.0, - decoration: BoxDecoration( - image: DecorationImage( - fit: BoxFit.cover, - image: NetworkImage("https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSgswb98Ga7aJjIaNvqUWBsjMkdR18xzp3pyg&usqp=CAU"), - ), - borderRadius: BorderRadius.all( - Radius.circular(30.0), + PopupMenuButton( + padding: EdgeInsets.fromLTRB(4, 4, 0, 4), + onSelected: (int index) { + _player.seek(Duration.zero, index: index); + }, + child: Transform.rotate( + angle: 180 * math.pi / 180, + child: SvgPicture.asset( + "assets/icons/drop_menu.svg", + width: 16, ), ), + itemBuilder: (context) { + var _voiceList = widget.voiceNoteList; + return List.generate(_voiceList.length, (index) { + return PopupMenuItem( + value: index, + child: Text( + '${_voiceList[index].userName}', + style: TextStyle(color: ColorConsts.primaryBlack), + ), + ); + }); + }, ), + StreamBuilder( + stream: _player.currentIndexStream, + builder: (context, snapshot) { + final state = snapshot.data; + return Container( + width: 50.0, + margin: EdgeInsets.only(left: 8, right: 8), + height: 50.0, + decoration: BoxDecoration( + image: widget.voiceNoteList.length < 1 + ? null + : DecorationImage( + fit: BoxFit.cover, + image: MemoryImage(base64Decode(widget.voiceNoteList?.elementAt(state)?.profilePicture)), + ), + 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, + ); + }, + ), + Expanded( child: Column( mainAxisSize: MainAxisSize.min, diff --git a/lib/widgets/common_textfield_widget.dart b/lib/widgets/common_textfield_widget.dart index 916534f..eecd221 100644 --- a/lib/widgets/common_textfield_widget.dart +++ b/lib/widgets/common_textfield_widget.dart @@ -11,8 +11,10 @@ class CommonTextFieldWidget extends StatelessWidget { final Widget suffixWidget; final Function onTap; final int maxLines; + final FocusNode focusNode; - CommonTextFieldWidget({Key key, @required this.hint, @required this.controller, this.maxLines = 1, this.isPassword = false, this.prefixIcon, this.suffixWidget, this.onTap}) : super(key: key); + CommonTextFieldWidget({Key key, @required this.hint, @required this.controller, this.maxLines = 1, this.focusNode, this.isPassword = false, this.prefixIcon, this.suffixWidget, this.onTap}) + : super(key: key); @override Widget build(BuildContext context) { @@ -28,6 +30,7 @@ class CommonTextFieldWidget extends StatelessWidget { readOnly: onTap != null, maxLines: maxLines, onTap: onTap, + focusNode: focusNode, scrollPhysics: BouncingScrollPhysics(), decoration: InputDecoration( contentPadding: EdgeInsets.fromLTRB(4, 4, 8, 4), @@ -42,13 +45,6 @@ class CommonTextFieldWidget extends StatelessWidget { padding: EdgeInsets.only(left: 8, right: 0), child: suffixWidget, ), - - // !showSuffix - // ? null - // : Padding( - // padding: EdgeInsets.only(left: 8, right: 0), - // child: Icon(Icons.keyboard_arrow_down, size: 18, color: ColorConsts.secondaryOrange), - // ), suffixIconConstraints: suffixWidget == null ? null : BoxConstraints(maxHeight: 30), prefixIconConstraints: prefixIcon == null ? prefixIcon : BoxConstraints(maxHeight: 18), prefixIcon: prefixIcon == null diff --git a/pubspec.yaml b/pubspec.yaml index 129a415..b78842c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -36,6 +36,9 @@ dependencies: share: ^2.0.1 just_audio: ^0.7.2 volume_controller: ^1.0.1+1 + intl: ^0.17.0 + shared_preferences: ^2.0.5 + url_launcher: ^6.0.3 dev_dependencies: flutter_test: