From a810086aefc9568d9dbfa3f1f829e708e856d73b Mon Sep 17 00:00:00 2001 From: Sikander Saleem Date: Wed, 24 Mar 2021 16:59:52 +0300 Subject: [PATCH] download aya to device added, share aya added & play aya as audio added. --- android/app/src/main/AndroidManifest.xml | 4 + lib/api/tangheem_user_api_client.dart | 21 ++ lib/api/user_api_client.dart | 10 +- lib/models/country_model.dart | 65 ++++++ lib/models/tangheem_type_model.dart | 54 +++++ .../country_selection_bottom_sheet.dart | 104 ++++++++++ lib/ui/screens/login_screen.dart | 4 +- lib/ui/screens/registration_screen.dart | 113 ++++++----- lib/ui/screens/surah_screen.dart | 57 ++++-- lib/ui/screens/tangheem_screen.dart | 3 +- lib/widgets/aya_player_widget.dart | 190 +++++++++++++++--- lib/widgets/common_textfield_widget.dart | 17 +- pubspec.yaml | 6 + 13 files changed, 534 insertions(+), 114 deletions(-) create mode 100644 lib/models/country_model.dart create mode 100644 lib/models/tangheem_type_model.dart create mode 100644 lib/ui/bottom_sheets/country_selection_bottom_sheet.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index cdbf5e5..b6c1eb1 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -6,11 +6,15 @@ additional functionality it is fine to subclass or reimplement FlutterApplication and put your custom class here. --> + + + AyaModel.fromJson(json), url, postParams); } + + Future getTangheemType() async { + String url = "${ApiConsts.tangheemUsers}TangheemType_Get"; + var postParams = {}; + return await ApiClient().postJsonForObject((json) => TangheemType.fromJson(json), url, postParams); + } + + Future contactUs(String firstName, String lastName, String email, String phone, String description) async { + String url = "${ApiConsts.tangheemUsers}ContactUs_Add"; + var postParams = {"firstName": firstName, "lastName": lastName, "email": email, "phone": phone, "description": description}; + return await ApiClient().postJsonForObject((json) => GeneralResponseModel.fromJson(json), url, postParams); + } + + Future getCountry() async { + String url = "${ApiConsts.tangheemUsers}Country_Get"; + var postParams = {}; + return await ApiClient().postJsonForObject((json) => CountryModel.fromJson(json), url, postParams); + } } diff --git a/lib/api/user_api_client.dart b/lib/api/user_api_client.dart index 3a3e3ef..1696b14 100644 --- a/lib/api/user_api_client.dart +++ b/lib/api/user_api_client.dart @@ -1,4 +1,5 @@ import 'package:tangheem/classes/consts.dart'; +import 'package:tangheem/models/country_model.dart'; import 'package:tangheem/models/general_response_model.dart'; import 'api_client.dart'; @@ -12,6 +13,7 @@ class UserApiClient { String _lastName, String _email, String _password, + String _countryCode, String _phone, ) async { String url = "${ApiConsts.user}UserRegistration_Add"; @@ -22,7 +24,7 @@ class UserApiClient { "secondName": "", "thirdName": "", "lastName": _lastName, - "countryCode": "string", + "countryCode": _countryCode, "mobileNumber": _phone, "isUserLock": false, "gender": 0, @@ -52,4 +54,10 @@ class UserApiClient { var postParams = {"email": _email, "opt": _otp, "newPassword": _password, "confirmPassword": _password}; return await ApiClient().postJsonForObject((json) => GeneralResponseModel.fromJson(json), url, postParams); } + + Future getCountry() async { + String url = "${ApiConsts.user}Country_Get"; + var postParams = {}; + return await ApiClient().postJsonForObject((json) => CountryModel.fromJson(json), url, postParams); + } } diff --git a/lib/models/country_model.dart b/lib/models/country_model.dart new file mode 100644 index 0000000..4d97064 --- /dev/null +++ b/lib/models/country_model.dart @@ -0,0 +1,65 @@ +class CountryModel { + int totalItemsCount; + int statusCode; + String message; + List data; + + CountryModel( + {this.totalItemsCount, this.statusCode, this.message, this.data}); + + CountryModel.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 Data.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 Data { + int countryId; + String countryCode; + String countryNameEn; + String countryNameAr; + String countryFlag; + + Data( + {this.countryId, + this.countryCode, + this.countryNameEn, + this.countryNameAr, + this.countryFlag}); + + Data.fromJson(Map json) { + countryId = json['countryId']; + countryCode = json['countryCode']; + countryNameEn = json['countryNameEn']; + countryNameAr = json['countryNameAr']; + countryFlag = json['countryFlag']; + } + + Map toJson() { + final Map data = new Map(); + data['countryId'] = this.countryId; + data['countryCode'] = this.countryCode; + data['countryNameEn'] = this.countryNameEn; + data['countryNameAr'] = this.countryNameAr; + data['countryFlag'] = this.countryFlag; + return data; + } +} diff --git a/lib/models/tangheem_type_model.dart b/lib/models/tangheem_type_model.dart new file mode 100644 index 0000000..ea9d9ce --- /dev/null +++ b/lib/models/tangheem_type_model.dart @@ -0,0 +1,54 @@ +class TangheemType { + int totalItemsCount; + int statusCode; + String message; + List data; + + TangheemType( + {this.totalItemsCount, this.statusCode, this.message, this.data}); + + TangheemType.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 Data.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 Data { + String tangheemTypeId; + String tangheemTypeName; + bool isActive; + + Data({this.tangheemTypeId, this.tangheemTypeName, this.isActive}); + + Data.fromJson(Map json) { + tangheemTypeId = json['tangheemTypeId']; + tangheemTypeName = json['tangheemTypeName']; + isActive = json['isActive']; + } + + Map toJson() { + final Map data = new Map(); + data['tangheemTypeId'] = this.tangheemTypeId; + data['tangheemTypeName'] = this.tangheemTypeName; + data['isActive'] = this.isActive; + return data; + } +} diff --git a/lib/ui/bottom_sheets/country_selection_bottom_sheet.dart b/lib/ui/bottom_sheets/country_selection_bottom_sheet.dart new file mode 100644 index 0000000..ca8355d --- /dev/null +++ b/lib/ui/bottom_sheets/country_selection_bottom_sheet.dart @@ -0,0 +1,104 @@ +import 'package:flutter/material.dart'; +import 'package:tangheem/classes/colors.dart'; +import 'package:tangheem/models/country_model.dart'; +import 'package:tangheem/widgets/common_textfield_widget.dart'; + +class CountrySelectionBottomSheet extends StatefulWidget { + final List countryList; + final Function(Data) onSelectCountry; + CountrySelectionBottomSheet({Key key, this.countryList, this.onSelectCountry}) : super(key: key); + + @override + _CountrySelectionBottomSheetState createState() { + return _CountrySelectionBottomSheetState(); + } +} + +class _CountrySelectionBottomSheetState extends State { + TextEditingController _searchCountryController = TextEditingController(); + List _filteredCountryList = []; + + @override + void initState() { + super.initState(); + _searchCountryController.addListener(_onTextChange); + _filterList(""); + } + + void _filterList(String _query) { + _filteredCountryList = []; + if (_query.isEmpty) { + _filteredCountryList = widget.countryList; + } else { + _filteredCountryList = widget.countryList.where((element) => element.countryNameAr.contains(_query) || element.countryNameEn.toLowerCase().contains(_query.toLowerCase()))?.toList() ?? []; + } + setState(() {}); + } + + void _onTextChange() { + var _searchText = _searchCountryController.text; + print("_searchText:$_searchText"); + _filterList(_searchText); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Directionality( + textDirection: TextDirection.rtl, + child: Container( + height: MediaQuery.of(context).size.height * 0.75, + padding: EdgeInsets.all(16), + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + ), + ), + child: Column( + children: [ + Container( + padding: EdgeInsets.all(8), + height: 54, + decoration: BoxDecoration( + color: ColorConsts.primaryBlue, + borderRadius: BorderRadius.only( + topLeft: Radius.circular(12), + topRight: Radius.circular(12), + bottomRight: Radius.circular(12), + bottomLeft: Radius.circular(12), + ), + ), + // color: Const.primaryBlue, + child: CommonTextFieldWidget(hint: "البحث في البلد", controller: _searchCountryController), + ), + Expanded( + child: ListView.separated( + padding: EdgeInsets.only(left: 8, right: 8), + itemCount: _filteredCountryList.length, + physics: BouncingScrollPhysics(), + separatorBuilder: (context, index) => Divider( + height: 1, + color: Colors.black87.withOpacity(0.3), + ), + itemBuilder: (context, index) => ListTile( + title: Text(_filteredCountryList[index].countryNameAr + " (" + _filteredCountryList[index].countryCode + ")"), + dense: true, + onTap: () { + Navigator.pop(context); + widget.onSelectCountry(_filteredCountryList[index]); + }, + ), + ), + ) + ], + ), + ), + ); + } +} diff --git a/lib/ui/screens/login_screen.dart b/lib/ui/screens/login_screen.dart index 18846fa..45da454 100644 --- a/lib/ui/screens/login_screen.dart +++ b/lib/ui/screens/login_screen.dart @@ -37,10 +37,10 @@ class _LoginScreenState extends State { super.dispose(); } - void performLogin(String email, String password) async { + void performLogin(String _email, String _password) async { Utils.showLoading(context); try { - _authenticationUser = await AuthenticationApiClient().authenticateUser(email, password); + _authenticationUser = await AuthenticationApiClient().authenticateUser(_email, _password); Utils.showToast("Login successfully"); } catch (ex, tr) { Utils.handleException(ex, null); diff --git a/lib/ui/screens/registration_screen.dart b/lib/ui/screens/registration_screen.dart index 2a0fd94..e6cc260 100644 --- a/lib/ui/screens/registration_screen.dart +++ b/lib/ui/screens/registration_screen.dart @@ -4,6 +4,8 @@ import 'package:flutter_svg/svg.dart'; import 'package:tangheem/api/user_api_client.dart'; import 'package:tangheem/classes/colors.dart'; 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'; @@ -19,24 +21,32 @@ class RegistrationScreen extends StatefulWidget { } class _RegistrationScreenState extends State { - TextEditingController _usernameController = TextEditingController(); - TextEditingController _fullNameController = TextEditingController(); + TextEditingController _firstNameController = TextEditingController(); + TextEditingController _lastNameController = TextEditingController(); TextEditingController _emailController = TextEditingController(); TextEditingController _mobileNumberController = TextEditingController(); TextEditingController _passwordController = TextEditingController(); TextEditingController _confirmPasswordController = TextEditingController(); + bool _isAccept = false; + Data _selectedCountry; + CountryModel _countryModel; + @override void initState() { super.initState(); + fetchCountryList(); } - void registerUser(String email, String password) async { + void fetchCountryList() async { Utils.showLoading(context); try { - await UserApiClient().registerUser("", "", email, password, _mobileNumberController.text); - Utils.showToast("Register successfully"); + _countryModel = await UserApiClient().getCountry(); + if ((_countryModel?.data?.length ?? 0) > 0) { + _selectedCountry = _countryModel.data.first; + } + setState(() {}); } catch (ex, tr) { Utils.handleException(ex, null); } finally { @@ -44,6 +54,19 @@ class _RegistrationScreenState extends State { } } + void registerUser(String _firstName, String _lastName, String _email, String _password, String _countryCode, String _phone) async { + Utils.showLoading(context); + try { + await UserApiClient().registerUser(_firstName, _lastName, _email, _password, _countryCode, _phone); + Utils.showToast("Register successfully"); + Utils.hideLoading(context); + Navigator.pop(context); + } catch (ex, tr) { + Utils.handleException(ex, null); + Utils.hideLoading(context); + } + } + @override void dispose() { super.dispose(); @@ -93,22 +116,26 @@ class _RegistrationScreenState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - CommonTextFieldWidget(hint: "اسم المستخدم", controller: _usernameController), + CommonTextFieldWidget(hint: "الاسم الاول", controller: _firstNameController), SizedBox(height: 8), - CommonTextFieldWidget(hint: "الايميل المسجل", controller: _fullNameController), + CommonTextFieldWidget(hint: "اسم النهاية", controller: _lastNameController), SizedBox(height: 8), CommonTextFieldWidget(hint: "الايميل", controller: _emailController), SizedBox(height: 8), CommonTextFieldWidget( - hint: "البلد", - controller: _mobileNumberController, - showSuffix: true, + hint: _selectedCountry?.countryNameAr ?? "البلد", + controller: null, + suffixWidget: Icon(Icons.keyboard_arrow_down, size: 18, color: ColorConsts.secondaryOrange), onTap: () { _selectCountry(context); }, ), SizedBox(height: 8), - CommonTextFieldWidget(hint: " رقم الجوال (9xx xxxxxxxxx+)", controller: _mobileNumberController), + CommonTextFieldWidget( + hint: " رقم الجوال${" (" + (_selectedCountry?.countryCode ?? "+9xx") + " xxxxxxxxx)"}", + controller: _mobileNumberController, + suffixWidget: Text(_selectedCountry?.countryCode ?? "", textDirection: TextDirection.ltr), + ), SizedBox(height: 8), CommonTextFieldWidget(hint: "تعيين كلمة المرور", controller: _passwordController), SizedBox(height: 8), @@ -141,11 +168,11 @@ class _RegistrationScreenState extends State { height: 50, child: TextButton( onPressed: () { - if (_usernameController.text.length < 1) { + if (_firstNameController.text.length < 1) { Utils.showToast("Username is empty."); return; } - if (_fullNameController.text.length < 1) { + if (_lastNameController.text.length < 1) { Utils.showToast("Name is empty."); return; } @@ -153,6 +180,10 @@ class _RegistrationScreenState extends State { Utils.showToast("Email is empty."); return; } + if (_selectedCountry?.countryCode == null) { + Utils.showToast("you must select country."); + return; + } if (_mobileNumberController.text.length < 1) { Utils.showToast("Phone number is empty."); return; @@ -180,7 +211,8 @@ class _RegistrationScreenState extends State { Utils.showToast("You must accept statement to proceed."); return; } - Utils.showToast("اعادة تعيين كلمة المرور"); + registerUser( + _firstNameController.text, _lastNameController.text, _emailController.text, _passwordController.text, _selectedCountry?.countryCode, _mobileNumberController.text); }, style: TextButton.styleFrom( primary: Colors.white, @@ -205,42 +237,23 @@ class _RegistrationScreenState extends State { void _selectCountry(context) { showModalBottomSheet( - context: context, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), + context: context, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), ), - backgroundColor: Colors.transparent, - builder: (BuildContext bc) { - return Container( - height: MediaQuery.of(context).size.height * 0.75, - // padding: EdgeInsets.all(16), - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - ), - child: Column( - children: [ - Container( - padding: EdgeInsets.all(16), - decoration: BoxDecoration( - color: ColorConsts.primaryBlue, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(16), - topRight: Radius.circular(16), - ), - ), - // color: Const.primaryBlue, - child: CommonTextFieldWidget(hint: "تعيين كلمة المرور", controller: null), - ), - Expanded(child: ListView()) - ], - )); - }); + ), + backgroundColor: Colors.transparent, + builder: (BuildContext bc) => CountrySelectionBottomSheet( + countryList: _countryModel?.data ?? [], + onSelectCountry: (country) { + WidgetsBinding.instance.addPostFrameCallback((_) { + setState(() { + _selectedCountry = country; + }); + }); + }), + ); } } diff --git a/lib/ui/screens/surah_screen.dart b/lib/ui/screens/surah_screen.dart index 78a4414..109125e 100644 --- a/lib/ui/screens/surah_screen.dart +++ b/lib/ui/screens/surah_screen.dart @@ -25,6 +25,7 @@ class SurahScreen extends StatefulWidget { } class _SurahScreenState extends State { + GlobalKey _globalKey = GlobalKey(); int _selectedSurah = 0; int _selectedFromAya = 0; int _selectedToAya = 0; @@ -100,6 +101,8 @@ class _SurahScreenState extends State { @override Widget build(BuildContext context) { + String _surahAya = _ayaModel?.data?.map((e) => e.ayahText)?.toList()?.fold("", (value, element) => value + element) ?? ""; + return Container( padding: EdgeInsets.fromLTRB(16, 24, 16, 0), width: double.infinity, @@ -181,30 +184,40 @@ class _SurahScreenState extends State { ], ), Expanded( - child: ListView( + child: SingleChildScrollView( physics: BouncingScrollPhysics(), - padding: EdgeInsets.only(top: 16, bottom: 8), - children: [ - Text( - "بسم الله الرحمن الرحيم", - textAlign: TextAlign.center, - style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: ColorConsts.primaryBlue, height: 1), - ), - SizedBox(height: 8), - Container( - padding: EdgeInsets.only(left: 4, right: 4), - child: Text( - _ayaModel?.data?.map((e) => e.ayahText)?.toList()?.fold("", (value, element) => value + element) ?? "", - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: "UthmanicHafs", - color: ColorConsts.primaryBlue, - fontSize: 18, - fontWeight: FontWeight.bold, - ), + child: RepaintBoundary( + key: _globalKey, + child: Material( + color: Colors.white, + child: ListView( + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: EdgeInsets.only(top: 16, bottom: 8), + children: [ + Text( + "بسم الله الرحمن الرحيم", + textAlign: TextAlign.center, + style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20, color: ColorConsts.primaryBlue, height: 1), + ), + SizedBox(height: 8), + Container( + padding: EdgeInsets.only(left: 4, right: 4), + child: Text( + _surahAya, + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: "UthmanicHafs", + color: ColorConsts.primaryBlue, + fontSize: 18, + fontWeight: FontWeight.bold, + ), + ), + ), + ], ), ), - ], + ), ), ), SizedBox(height: 8), @@ -254,7 +267,7 @@ class _SurahScreenState extends State { ), ), ), - AyaPlayerWidget(surahName: _surahList.isNotEmpty ? _surahList[_selectedSurah] : null) + AyaPlayerWidget(surahName: _surahList.isNotEmpty ? _surahList[_selectedSurah] : null, globalKey: _globalKey) ], ), ); diff --git a/lib/ui/screens/tangheem_screen.dart b/lib/ui/screens/tangheem_screen.dart index 76952a5..e7f0f5c 100644 --- a/lib/ui/screens/tangheem_screen.dart +++ b/lib/ui/screens/tangheem_screen.dart @@ -16,6 +16,7 @@ class TangheemScreen extends StatefulWidget { } class _TangheemScreenState extends State { + GlobalKey _globalKey = GlobalKey(); List temp1 = List(); List temp2 = List(); @@ -154,7 +155,7 @@ class _TangheemScreenState extends State { ), ), ), - AyaPlayerWidget() + AyaPlayerWidget(globalKey: null) ], ), ); diff --git a/lib/widgets/aya_player_widget.dart b/lib/widgets/aya_player_widget.dart index 3a8f246..f1207be 100644 --- a/lib/widgets/aya_player_widget.dart +++ b/lib/widgets/aya_player_widget.dart @@ -1,12 +1,24 @@ +import 'dart:io'; import 'dart:math' as math; - +import 'dart:typed_data'; +import 'dart:ui' as ui; import 'package:flutter/material.dart'; +import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:image_gallery_saver/image_gallery_saver.dart'; +import 'package:just_audio/just_audio.dart'; +import 'package:path_provider/path_provider.dart'; +import 'package:permission_handler/permission_handler.dart'; +import 'package:share/share.dart'; import 'package:tangheem/classes/colors.dart'; +import 'package:tangheem/classes/utils.dart'; +import 'package:volume_controller/volume_controller.dart'; class AyaPlayerWidget extends StatefulWidget { final String surahName; - AyaPlayerWidget({Key key, this.surahName}) : super(key: key); + final GlobalKey globalKey; + AyaPlayerWidget({Key key, this.surahName, @required this.globalKey}) : super(key: key); @override _AyaPlayerWidgetState createState() { @@ -17,14 +29,28 @@ class AyaPlayerWidget extends StatefulWidget { class _AyaPlayerWidgetState extends State { double sliderValue = 0.4; bool isPlaying = false; + AudioPlayer _player; + bool _isAudioHaveError = false; @override void initState() { super.initState(); + _player = AudioPlayer(); + try { + final _playlist = ConcatenatingAudioSource(children: [ + AudioSource.uri( + Uri.parse("https://file-examples-com.github.io/uploads/2017/11/file_example_MP3_1MG.mp3"), + ), + ]); + _player.setAudioSource(_playlist, initialIndex: 0, initialPosition: Duration.zero).then((value) => () {}); + } catch (e) { + _isAudioHaveError = true; + } } @override void dispose() { + _player.dispose(); super.dispose(); } @@ -82,10 +108,25 @@ class _AyaPlayerWidgetState extends State { ], ), ), - commonIconButton("assets/icons/download_aya.svg", () {}), - commonIconButton("assets/icons/share_aya.svg", () {}), + commonIconButton("assets/icons/download_aya.svg", () async { + if (await _requestPermission()) { + if (await _saveAya()) { + Utils.showToast("Aya saved successfully"); + } else { + Utils.showToast("Failed to save aya"); + } + } else { + Utils.showToast("you must granted permission to download aya."); + } + }), + commonIconButton("assets/icons/share_aya.svg", () { + _shareAya(); + }), commonIconButton("assets/icons/bookmark.svg", () {}), - commonIconButton("assets/icons/audio_level.svg", () {}), + commonIconButton("assets/icons/audio_level.svg", () async { + // var vol = await VolumeController.getVolume(); + VolumeController.maxVolume(); + }), ], ), SizedBox(height: 8), @@ -94,39 +135,65 @@ class _AyaPlayerWidgetState extends State { children: [ commonIconButton("assets/icons/next_aya.svg", () {}), SizedBox(width: 4), - commonIconButton(isPlaying ? "assets/icons/pause.svg" : "assets/icons/play_aya.svg", () { - setState(() { - isPlaying = !isPlaying; - }); - }), + 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() : _player.play(); + }); + }, + ), SizedBox(width: 4), commonIconButton("assets/icons/previous_aya.svg", () {}), SizedBox(width: 16), Expanded( - child: SliderTheme( - data: SliderTheme.of(context).copyWith( - activeTrackColor: ColorConsts.sliderBackground, - inactiveTrackColor: ColorConsts.secondaryOrange, - // trackShape: RoundedRectRangeSliderTrackShape(), - trackHeight: 8.0, - thumbColor: ColorConsts.primaryBlack, - thumbShape: RoundSliderThumbShape(enabledThumbRadius: 10.0), - overlayColor: Colors.red.withAlpha(32), - overlayShape: RoundSliderOverlayShape(overlayRadius: 12.0), - ), - child: Slider( - value: sliderValue, - onChanged: (value) { - setState(() { - sliderValue = value; - }); - }, - ), + 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())); + }, + ), + ), + ); + }, ), ), - Text( - "06:00", - 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"), + ); + }, ), ], ) @@ -135,7 +202,64 @@ class _AyaPlayerWidgetState extends State { ); } + String _durationTime(Duration duration) { + if (duration == null) { + return "00:00"; + } + String twoDigits(int n) => n.toString().padLeft(2, "0"); + String twoDigitMinutes = twoDigits(duration.inMinutes.remainder(60)); + String twoDigitSeconds = twoDigits(duration.inSeconds.remainder(60)); + return "$twoDigitMinutes:$twoDigitSeconds"; + } + Widget commonIconButton(String icon, VoidCallback onPressed) { - return IconButton(constraints: BoxConstraints(), padding: EdgeInsets.only(right: 2), icon: SvgPicture.asset(icon, height: 16, width: 16), onPressed: onPressed); + return IconButton( + highlightColor: Colors.transparent, + splashColor: Colors.transparent, + constraints: BoxConstraints(), + padding: EdgeInsets.only(right: 2), + icon: SvgPicture.asset(icon, height: 16, width: 16), + onPressed: onPressed); + } + + Future _requestPermission() async { + Map statuses = await [ + Permission.storage, + ].request(); + + return statuses[Permission.storage].isGranted; + } + + Future _saveAya() async { + Utils.showLoading(context); + try { + RenderRepaintBoundary boundary = widget.globalKey.currentContext.findRenderObject(); + ui.Image image = await boundary.toImage(pixelRatio: 3.0); + ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png); + final result = await ImageGallerySaver.saveImage(byteData.buffer.asUint8List(), quality: 100); + Utils.hideLoading(context); + return result["isSuccess"]; + } catch (ex) { + Utils.hideLoading(context); + return false; + } + } + + void _shareAya() async { + Utils.showLoading(context); + try { + RenderRepaintBoundary boundary = widget.globalKey.currentContext.findRenderObject(); + ui.Image image = await boundary.toImage(pixelRatio: 3.0); + ByteData byteData = await image.toByteData(format: ui.ImageByteFormat.png); + Uint8List pngBytes = byteData.buffer.asUint8List(); + + final tempDir = await getTemporaryDirectory(); + final file = await File('${tempDir.path}/${DateTime.now().toString()}.png').create(); + await file.writeAsBytes(pngBytes); + Utils.hideLoading(context); + Share.shareFiles(['${file.path}']); + } catch (ex) { + Utils.hideLoading(context); + } } } diff --git a/lib/widgets/common_textfield_widget.dart b/lib/widgets/common_textfield_widget.dart index 08d6c8e..562db4a 100644 --- a/lib/widgets/common_textfield_widget.dart +++ b/lib/widgets/common_textfield_widget.dart @@ -8,10 +8,10 @@ class CommonTextFieldWidget extends StatelessWidget { final TextEditingController controller; final bool isPassword; final String prefixIcon; - final bool showSuffix; + final Widget suffixWidget; final Function onTap; - CommonTextFieldWidget({Key key, @required this.hint, @required this.controller, this.isPassword = false, this.prefixIcon, this.showSuffix = false, this.onTap}) : super(key: key); + CommonTextFieldWidget({Key key, @required this.hint, @required this.controller, this.isPassword = false, this.prefixIcon, this.suffixWidget, this.onTap}) : super(key: key); @override Widget build(BuildContext context) { @@ -33,13 +33,20 @@ class CommonTextFieldWidget extends StatelessWidget { filled: true, hintStyle: TextStyle(color: ColorConsts.textGrey2, fontSize: 14), hintText: hint, - suffixIcon: !showSuffix + suffixIcon: suffixWidget == null ? null : Padding( padding: EdgeInsets.only(left: 8, right: 0), - child: Icon(Icons.keyboard_arrow_down, size: 18, color: ColorConsts.secondaryOrange), + child: suffixWidget, ), - suffixIconConstraints: !showSuffix ? null : BoxConstraints(maxHeight: 30), + + // !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 ? prefixIcon diff --git a/pubspec.yaml b/pubspec.yaml index af21aaf..129a415 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -30,6 +30,12 @@ dependencies: flutter_svg: ^0.19.3 fluttertoast: ^7.1.8 http: ^0.13.0 + image_gallery_saver: ^1.6.8 + path_provider: ^2.0.1 + permission_handler: ^6.1.1 + share: ^2.0.1 + just_audio: ^0.7.2 + volume_controller: ^1.0.1+1 dev_dependencies: flutter_test: