From f43393f014db0f31e704b115e70ae1e752a55764 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 10 Mar 2022 11:51:14 +0330 Subject: [PATCH 01/23] D1APP-99 studio basics --- lib/models/requests/studio.dart | 13 ++++ lib/models/srudio_data.dart | 37 ++++++++++ lib/views/home/studio/studio.dart | 15 ++++ lib/views/home/studio/studio_state.dart | 68 ++++++++++++++++++- .../studio/video_details/video_details.dart | 1 + lib/views/home/studio/widgets/tab_bar.dart | 9 +-- 6 files changed, 138 insertions(+), 5 deletions(-) create mode 100644 lib/models/requests/studio.dart create mode 100644 lib/models/srudio_data.dart diff --git a/lib/models/requests/studio.dart b/lib/models/requests/studio.dart new file mode 100644 index 0000000..74feeaa --- /dev/null +++ b/lib/models/requests/studio.dart @@ -0,0 +1,13 @@ +class StudioRequestArgs { + final int page; + final String? search; + final String? order; + final String? type; + + const StudioRequestArgs({ + required this.page, + this.search, + this.order, + this.type, + }); +} diff --git a/lib/models/srudio_data.dart b/lib/models/srudio_data.dart new file mode 100644 index 0000000..1e7f4b6 --- /dev/null +++ b/lib/models/srudio_data.dart @@ -0,0 +1,37 @@ +class StudioData { + final int id; + final String title; + final String image; + final String duration; + final String createdAt; + bool marked; + + StudioData({ + required this.id, + required this.title, + required this.image, + required this.duration, + required this.createdAt, + required this.marked, + }); + + factory StudioData.fromJson(Map json) { + return StudioData( + id: json['id'], + title: json['title'], + image: json['image'], + duration: json['duration'], + createdAt: json['createdAt'], + marked: json['marked'], + ); + } + + Map toJson() => { + 'id': id, + 'title': title, + 'image': image, + 'duration': duration, + 'createdAt': createdAt, + 'marked': marked, + }; +} diff --git a/lib/views/home/studio/studio.dart b/lib/views/home/studio/studio.dart index 08c8e37..bf7a141 100644 --- a/lib/views/home/studio/studio.dart +++ b/lib/views/home/studio/studio.dart @@ -10,6 +10,7 @@ import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/radial_button.dart'; import 'package:didvan/views/widgets/item_title.dart'; +import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; @@ -23,6 +24,12 @@ class Studio extends StatefulWidget { class _StudioState extends State { final FocusNode _focusNode = FocusNode(); + @override + void initState() { + context.read().init(); + super.initState(); + } + @override Widget build(BuildContext context) { return CustomScrollView( @@ -82,6 +89,14 @@ class _StudioState extends State { ), ), ), + Consumer( + builder: (context, state, child) => SliverStateHandler( + state: state, + builder: (context, state, index) => Container(), + childCount: state.studios.length, + onRetry: () {}, + ), + ), ], ); } diff --git a/lib/views/home/studio/studio_state.dart b/lib/views/home/studio/studio_state.dart index 28ebd1e..0008b17 100644 --- a/lib/views/home/studio/studio_state.dart +++ b/lib/views/home/studio/studio_state.dart @@ -1,7 +1,73 @@ +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/requests/studio.dart'; +import 'package:didvan/models/srudio_data.dart'; import 'package:didvan/providers/core_provider.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; class StudioState extends CoreProvier { + final List studios = []; + + String? search; + String? lastSearch; + int page = 1; + int lastPage = 1; + int selectedSortTypeIndex = 0; - bool videosSelected = true; + bool _videosSelected = true; + + bool get videosSelected => _videosSelected; + + set videosSelected(bool value) { + if (_videosSelected == value) return; + _videosSelected = value; + getStudioOverviews(page: page); + } + + void init() { + search = ''; + lastSearch = ''; + videosSelected = true; + selectedSortTypeIndex = 0; + Future.delayed(Duration.zero, () { + getStudioOverviews(page: 1); + }); + } + + String get _order { + if (selectedSortTypeIndex == 0) return 'date'; + if (selectedSortTypeIndex == 1) return 'view'; + return 'comment'; + } + + Future getStudioOverviews({required int page}) async { + this.page = page; + if (page == 1) { + appState = AppState.busy; + } + + final service = RequestService( + RequestHelper.studioItemOverviews( + args: StudioRequestArgs( + page: page, + type: videosSelected ? 'video' : 'podcast', + search: search, + order: _order, + ), + ), + ); + + await service.httpGet(); + if (service.isSuccess) { + lastPage = service.result['lastPage']; + final studioItems = service.result['studios']; + for (var i = 0; i < studioItems.length; i++) { + studios.add(StudioData.fromJson(studioItems[i])); + } + appState = AppState.idle; + return; + } + appState = AppState.failed; + } } diff --git a/lib/views/home/studio/video_details/video_details.dart b/lib/views/home/studio/video_details/video_details.dart index e69de29..8b13789 100644 --- a/lib/views/home/studio/video_details/video_details.dart +++ b/lib/views/home/studio/video_details/video_details.dart @@ -0,0 +1 @@ + diff --git a/lib/views/home/studio/widgets/tab_bar.dart b/lib/views/home/studio/widgets/tab_bar.dart index 7e11744..3d915a8 100644 --- a/lib/views/home/studio/widgets/tab_bar.dart +++ b/lib/views/home/studio/widgets/tab_bar.dart @@ -14,14 +14,15 @@ class StudioTabBar extends StatelessWidget { @override Widget build(BuildContext context) { final state = context.watch(); - return Container( + return AnimatedContainer( + duration: DesignConfig.lowAnimationDuration, margin: const EdgeInsets.symmetric(horizontal: 16), padding: const EdgeInsets.all(4), decoration: BoxDecoration( border: Border.all( color: state.videosSelected ? Theme.of(context).colorScheme.secondary - : Theme.of(context).primaryColor, + : Theme.of(context).colorScheme.primary, ), borderRadius: DesignConfig.lowBorderRadius, ), @@ -32,7 +33,7 @@ class StudioTabBar extends StatelessWidget { icon: DidvanIcons.video_solid, selectedColor: Theme.of(context).colorScheme.secondary, title: 'ویدئو', - onTap: () {}, + onTap: () => state.videosSelected = true, isSelected: state.videosSelected, ), ), @@ -46,7 +47,7 @@ class StudioTabBar extends StatelessWidget { icon: DidvanIcons.podcast_solid, selectedColor: Theme.of(context).colorScheme.focusedBorder, title: 'پادکست', - onTap: () {}, + onTap: () => state.videosSelected = false, isSelected: !state.videosSelected, ), ), From cd5b17f60d9483bccaf4f373957e9383df34015e Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 10 Mar 2022 14:25:55 +0330 Subject: [PATCH 02/23] radar categories size changed --- lib/views/home/radar/widgets/category_item.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/home/radar/widgets/category_item.dart b/lib/views/home/radar/widgets/category_item.dart index 081b152..e223d00 100644 --- a/lib/views/home/radar/widgets/category_item.dart +++ b/lib/views/home/radar/widgets/category_item.dart @@ -66,10 +66,10 @@ class CategoryItem extends StatelessWidget { isVisible: !isColapsed, child: Container( width: !_useWebMobileLayout(context) - ? _width(context) / 1.5 + ? _width(context) / 1.75 : ds.width / 5, height: !_useWebMobileLayout(context) - ? _width(context) / 1.5 + ? _width(context) / 1.75 : ds.width / 5, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, From c47fbac5feaa821bb18c77be56b6702f88eed525 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 10 Mar 2022 14:34:29 +0330 Subject: [PATCH 03/23] web bugs fixed --- lib/routes/route_generator.dart | 6 +++++- lib/views/home/radar/widgets/category_item.dart | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 205d9b7..205dcb6 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -170,7 +170,11 @@ class RouteGenerator { final shortestSide = MediaQuery.of(context).size.shortestSide; final bool useMobileLayout = shortestSide < 600; if (kIsWeb && !useMobileLayout) { - return Center(child: AspectRatio(aspectRatio: 9 / 16, child: page)); + return Container( + color: Theme.of(context).colorScheme.background, + alignment: Alignment.center, + child: AspectRatio(aspectRatio: 9 / 16, child: page), + ); } return Container( color: Theme.of(context).colorScheme.surface, diff --git a/lib/views/home/radar/widgets/category_item.dart b/lib/views/home/radar/widgets/category_item.dart index e223d00..faee630 100644 --- a/lib/views/home/radar/widgets/category_item.dart +++ b/lib/views/home/radar/widgets/category_item.dart @@ -66,10 +66,10 @@ class CategoryItem extends StatelessWidget { isVisible: !isColapsed, child: Container( width: !_useWebMobileLayout(context) - ? _width(context) / 1.75 + ? _width(context) / 2 : ds.width / 5, height: !_useWebMobileLayout(context) - ? _width(context) / 1.75 + ? _width(context) / 2 : ds.width / 5, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, From 2815fc8ddc624b60e7aa66ab450dd7f864345b40 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Fri, 11 Mar 2022 01:12:57 +0330 Subject: [PATCH 04/23] work on paginations --- lib/services/network/request_helper.dart | 5 +++-- lib/views/home/hashtag/hashtag.dart | 16 ++++++++++++---- lib/views/home/hashtag/hashtag_state.dart | 1 + lib/views/home/news/news.dart | 2 +- lib/views/home/radar/radar.dart | 2 +- 5 files changed, 18 insertions(+), 8 deletions(-) diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index 37c1f14..ccb6391 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -3,7 +3,7 @@ import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/requests/studio.dart'; class RequestHelper { - static const String baseUrl = 'https://api.didvan.app'; + static const String baseUrl = 'https://test.api.didvan.app'; static const String _baseUserUrl = baseUrl + '/user'; static const String _baseRadarUrl = baseUrl + '/radar'; static const String _baseNewsUrl = baseUrl + '/news'; @@ -36,10 +36,11 @@ class RequestHelper { baseUrl + '/tag' + _urlConcatGenerator([ + MapEntry('page', page?.toString()), MapEntry('limit', limit?.toString() ?? '3'), MapEntry('type', type), MapEntry('id', itemId?.toString() ?? '1'), - MapEntry('tags', _urlListConcatGenerator(ids)) + MapEntry('tags', _urlListConcatGenerator(ids)), ]); static String markRadar(int id) => _baseRadarUrl + '/$id/mark'; diff --git a/lib/views/home/hashtag/hashtag.dart b/lib/views/home/hashtag/hashtag.dart index 2e94ce0..d833ad9 100644 --- a/lib/views/home/hashtag/hashtag.dart +++ b/lib/views/home/hashtag/hashtag.dart @@ -6,6 +6,7 @@ import 'package:didvan/views/home/widgets/radar_overview.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:provider/provider.dart'; class Hashtag extends StatefulWidget { @@ -37,10 +38,16 @@ class _HashtagState extends State { placeholder: RadarOverview.placeholder, builder: (context, state, index) { index++; - if (index % 15 == 0 && index / 15 >= state.page) { - state.getTagItems(page: index ~/ 15 + 1); + if (index % 15 == 0 && state.lastPage != state.page) { + state.getTagItems(page: state.page + 1); } index--; + if (index == state.items.length) { + return SpinKitThreeBounce( + color: Theme.of(context).colorScheme.primary, + size: 24, + ); + } final item = state.items[index]; final type = item.type; if (type == 'radar') { @@ -57,8 +64,9 @@ class _HashtagState extends State { } return Container(); }, - childCount: state.items.length, - onRetry: () => state.getTagItems(page: 1), + childCount: + state.items.length + (state.page != state.lastPage ? 1 : 0), + onRetry: () => state.getTagItems(page: state.page), ), ) ], diff --git a/lib/views/home/hashtag/hashtag_state.dart b/lib/views/home/hashtag/hashtag_state.dart index a94cdbe..fc1e5e7 100644 --- a/lib/views/home/hashtag/hashtag_state.dart +++ b/lib/views/home/hashtag/hashtag_state.dart @@ -9,6 +9,7 @@ class HashtagState extends CoreProvier { late final int id; int page = 1; + int lastPage = 1; Future getTagItems({required int page}) async { this.page = page; diff --git a/lib/views/home/news/news.dart b/lib/views/home/news/news.dart index 2911204..30df281 100644 --- a/lib/views/home/news/news.dart +++ b/lib/views/home/news/news.dart @@ -57,7 +57,7 @@ class _NewsState extends State { builder: (context, state, index) { index += 2; if (index % 15 == 0 && state.lastPage != state.page) { - state.getNews(page: index ~/ 15 + 1); + state.getNews(page: state.page + 1); } index -= 2; if (index >= state.news.length) { diff --git a/lib/views/home/radar/radar.dart b/lib/views/home/radar/radar.dart index 1a241b0..87c3e83 100644 --- a/lib/views/home/radar/radar.dart +++ b/lib/views/home/radar/radar.dart @@ -130,7 +130,7 @@ class _RadarState extends State { builder: (context, state, index) { index += 2; if (index % 15 == 0 && state.lastPage != state.page) { - state.getRadars(page: index ~/ 15 + 1); + state.getRadars(page: state.page + 1); } index -= 2; if (index >= state.radars.length) { From 643b499a95376b3551baddf83cca934ffb0ae7c2 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Fri, 11 Mar 2022 18:54:44 +0330 Subject: [PATCH 05/23] official version upgrade --- .fandogh/fandogh.yaml | 2 +- pubspec.yaml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.fandogh/fandogh.yaml b/.fandogh/fandogh.yaml index 1d5bac1..f3e17d1 100644 --- a/.fandogh/fandogh.yaml +++ b/.fandogh/fandogh.yaml @@ -3,7 +3,7 @@ name: app-test spec: allow_http: false disable_default_domains: true - image: app:1.1.1 + image: app:1.1.3 image_pull_policy: IfNotPresent path: / replicas: 1 diff --git a/pubspec.yaml b/pubspec.yaml index 8c847b0..44c80b2 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.0+1 +version: 1.1.3+3 environment: sdk: ">=2.12.0 <3.0.0" From 0a99267d609df715c625a09a5a9f199d25c553fd Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Sat, 12 Mar 2022 10:18:04 +0330 Subject: [PATCH 06/23] reset password dialog + bug fix --- .fandogh/fandogh.yaml | 2 +- lib/providers/user_provider.dart | 7 +++++-- lib/services/network/request.dart | 2 ++ .../authentication/screens/password.dart | 21 +++++++++++++++++++ lib/views/home/settings/settings.dart | 6 +++--- lib/views/splash/splash.dart | 10 ++++++++- pubspec.yaml | 2 +- 7 files changed, 42 insertions(+), 8 deletions(-) diff --git a/.fandogh/fandogh.yaml b/.fandogh/fandogh.yaml index f3e17d1..fa91faa 100644 --- a/.fandogh/fandogh.yaml +++ b/.fandogh/fandogh.yaml @@ -3,7 +3,7 @@ name: app-test spec: allow_http: false disable_default_domains: true - image: app:1.1.3 + image: app:1.1.4 image_pull_policy: IfNotPresent path: / replicas: 1 diff --git a/lib/providers/user_provider.dart b/lib/providers/user_provider.dart index 65aeacf..f53d0ac 100644 --- a/lib/providers/user_provider.dart +++ b/lib/providers/user_provider.dart @@ -24,13 +24,16 @@ class UserProvider extends CoreProvier { return null; } - Future getUserInfo() async { + Future getUserInfo() async { isAuthenticated = true; final RequestService service = RequestService(RequestHelper.userInfo); await service.httpGet(); if (service.isSuccess) { user = User.fromJson(service.result['user']); - return; + return true; + } + if (service.statusCode == 401) { + return false; } throw 'Getting user from API failed!'; } diff --git a/lib/services/network/request.dart b/lib/services/network/request.dart index 1614cbc..67ac373 100644 --- a/lib/services/network/request.dart +++ b/lib/services/network/request.dart @@ -5,6 +5,7 @@ import 'package:http_parser/http_parser.dart' as parser; class RequestService { static late String token; + int? statusCode; Map get result => _body?['result'] ?? const {}; Map get errors => _body?['errors'] ?? const {}; @@ -162,6 +163,7 @@ class RequestService { } void _handleResponse(http.Response? response) { + statusCode = response?.statusCode; if (_handleError(response)) { if (response!.body.isNotEmpty) { _body = json.decode(response.body); diff --git a/lib/views/authentication/screens/password.dart b/lib/views/authentication/screens/password.dart index 391556b..69cdc42 100644 --- a/lib/views/authentication/screens/password.dart +++ b/lib/views/authentication/screens/password.dart @@ -1,8 +1,10 @@ import 'dart:developer'; +import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/providers/server_data_provider.dart'; import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/routes/routes.dart'; +import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/authentication/authentication_state.dart'; import 'package:didvan/views/authentication/widgets/authentication_layout.dart'; import 'package:didvan/views/widgets/didvan/button.dart'; @@ -76,6 +78,25 @@ class _PasswordInputState extends State { log(token); await ServerDataProvider.getData(); Navigator.of(context).pushReplacementNamed(Routes.home); + _showResetPasswordDialog(); } } + + void _showResetPasswordDialog() { + ActionSheetUtils.openDialog( + data: ActionSheetData( + content: const DidvanText( + 'خوش آمدید!\nبرای امنیت بیشتر، رمز عبور خود را تغییر دهید.', + ), + title: 'تغییر رمز عبور', + onConfirmed: () => Navigator.of(ActionSheetUtils.context).pushNamed( + Routes.authenticaion, + arguments: true, + ), + confrimTitle: 'تغییر رمز عبور', + onDismissed: Navigator.of(ActionSheetUtils.context).pop, + dismissTitle: 'بعدا', + ), + ); + } } diff --git a/lib/views/home/settings/settings.dart b/lib/views/home/settings/settings.dart index 7753d26..63c4439 100644 --- a/lib/views/home/settings/settings.dart +++ b/lib/views/home/settings/settings.dart @@ -87,7 +87,7 @@ class Settings extends StatelessWidget { MenuItem( icon: DidvanIcons.didvan_solid, title: 'معرفی دیدوان', - onTap: () => Navigator.of(context).pushNamed(Routes.aboutUs), + onTap: () => launch('https://didvan.app/'), ), const DidvanDivider(), MenuItem( @@ -101,14 +101,14 @@ class Settings extends StatelessWidget { MenuItem( icon: DidvanIcons.alert_regular, title: 'حریم خصوصی', - onTap: () => {}, + onTap: () => launch('https://didvan.app/'), ), ], ), ), const SizedBox(height: 16), DidvanText( - 'نسخه نرم‌افزار: آزمایشی', + 'نسخه نرم‌افزار: 1.1.4', style: Theme.of(context).textTheme.caption, ), ], diff --git a/lib/views/splash/splash.dart b/lib/views/splash/splash.dart index c0d05cd..72e3b26 100644 --- a/lib/views/splash/splash.dart +++ b/lib/views/splash/splash.dart @@ -110,7 +110,15 @@ class _SplashState extends State { if (token != null) { log(token); RequestService.token = token; - await userProvider.getUserInfo(); + final result = await userProvider.getUserInfo(); + if (!result) { + StorageService.delete(key: 'token'); + Navigator.of(context).pushNamedAndRemoveUntil( + Routes.splash, + (_) => false, + ); + return; + } await ServerDataProvider.getData(); } Navigator.of(context).pushReplacementNamed( diff --git a/pubspec.yaml b/pubspec.yaml index 44c80b2..c685dfc 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.3+3 +version: 1.1.4+4 environment: sdk: ">=2.12.0 <3.0.0" From 6e3f8f27a06f9411192c52919d4620324245ba1b Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Sat, 12 Mar 2022 13:56:31 +0330 Subject: [PATCH 07/23] fandogh dev version latest config --- .fandogh/fandogh.yaml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.fandogh/fandogh.yaml b/.fandogh/fandogh.yaml index fa91faa..ebd7abc 100644 --- a/.fandogh/fandogh.yaml +++ b/.fandogh/fandogh.yaml @@ -1,13 +1,13 @@ kind: ExternalService -name: app-test +name: app-dev spec: allow_http: false disable_default_domains: true - image: app:1.1.4 + image: app-dev:1.1.4 image_pull_policy: IfNotPresent path: / replicas: 1 resources: - memory: 150Mi + memory: 100Mi domains: - - name: web.didvan.app \ No newline at end of file + - name: dev.didvan.app \ No newline at end of file From d0a4c6ea0a3c1eb027a8203b4ec5c83a6d5bc7fb Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Sat, 12 Mar 2022 19:29:48 +0330 Subject: [PATCH 08/23] bug fixes + code cleaning --- .../studio/podcast_details/podcast_details.dart | 17 ----------------- .../studio/video_details/video_details.dart | 1 - lib/views/home/widgets/bookmark_button.dart | 2 +- lib/views/home/widgets/radar_overview.dart | 1 + 4 files changed, 2 insertions(+), 19 deletions(-) delete mode 100644 lib/views/home/studio/podcast_details/podcast_details.dart delete mode 100644 lib/views/home/studio/video_details/video_details.dart diff --git a/lib/views/home/studio/podcast_details/podcast_details.dart b/lib/views/home/studio/podcast_details/podcast_details.dart deleted file mode 100644 index 140d8d0..0000000 --- a/lib/views/home/studio/podcast_details/podcast_details.dart +++ /dev/null @@ -1,17 +0,0 @@ -import 'package:flutter/material.dart'; - -class PodcastDetails extends StatelessWidget { - const PodcastDetails({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Material( - child: Container( - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - borderRadius: const BorderRadius.vertical(top: Radius.circular(8)), - ), - ), - ); - } -} diff --git a/lib/views/home/studio/video_details/video_details.dart b/lib/views/home/studio/video_details/video_details.dart deleted file mode 100644 index 8b13789..0000000 --- a/lib/views/home/studio/video_details/video_details.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/views/home/widgets/bookmark_button.dart b/lib/views/home/widgets/bookmark_button.dart index 4de2621..06d7604 100644 --- a/lib/views/home/widgets/bookmark_button.dart +++ b/lib/views/home/widgets/bookmark_button.dart @@ -40,7 +40,7 @@ class _BookmarkButtonState extends State { @override Widget build(BuildContext context) { return DidvanIconButton( - gestureSize: widget.bigGestureSize ? 32 : 24, + gestureSize: widget.bigGestureSize ? 32 : 28, icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular, onPressed: () async { bool confirm = false; diff --git a/lib/views/home/widgets/radar_overview.dart b/lib/views/home/widgets/radar_overview.dart index 5616994..f9988fd 100644 --- a/lib/views/home/widgets/radar_overview.dart +++ b/lib/views/home/widgets/radar_overview.dart @@ -97,6 +97,7 @@ class RadarOverview extends StatelessWidget { DidvanText( radar.description, maxLines: 3, + overflow: TextOverflow.ellipsis, ), const DidvanDivider(), Row( From aba3d69dabde052cebeb3ed50f2cf29c7829c4f0 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Mon, 14 Mar 2022 15:10:39 +0330 Subject: [PATCH 09/23] bookmarks search now works --- .../settings/bookmarks/bookmark_state.dart | 13 ++++++-- .../home/settings/bookmarks/bookmarks.dart | 31 ++++++++++++------- .../filtered_bookmark/filtered_bookmark.dart | 9 ++++-- .../filtered_bookmarks_state.dart | 26 +++++++++++++--- 4 files changed, 58 insertions(+), 21 deletions(-) diff --git a/lib/views/home/settings/bookmarks/bookmark_state.dart b/lib/views/home/settings/bookmarks/bookmark_state.dart index 1807179..063dc4b 100644 --- a/lib/views/home/settings/bookmarks/bookmark_state.dart +++ b/lib/views/home/settings/bookmarks/bookmark_state.dart @@ -9,20 +9,27 @@ class BookmarksState extends CoreProvier { final List bookmarks = []; String search = ''; String lastSearch = ''; + int page = 1; + int lastPage = 1; bool get searching => search != ''; - Future getBookmarks() async { + Future getBookmarks({required int page}) async { if (search != '') { lastSearch = search; } + if (page == 1) { + bookmarks.clear(); + } + this.page = page; appState = AppState.busy; - final service = RequestService(RequestHelper.bookmarks()); + final service = + RequestService(RequestHelper.bookmarks(page: page, search: search)); await service.httpGet(); if (service.isSuccess) { + lastPage = service.result['lastPage']; final marks = service.result['contents']; - bookmarks.clear(); for (var i = 0; i < marks.length; i++) { bookmarks.add(OverviewData.fromJson(marks[i])); } diff --git a/lib/views/home/settings/bookmarks/bookmarks.dart b/lib/views/home/settings/bookmarks/bookmarks.dart index 1cb736a..31746b8 100644 --- a/lib/views/home/settings/bookmarks/bookmarks.dart +++ b/lib/views/home/settings/bookmarks/bookmarks.dart @@ -33,7 +33,7 @@ class _BookmarksState extends State { @override void initState() { Future.delayed(Duration.zero, () { - context.read().getBookmarks(); + context.read().getBookmarks(page: 1); }); super.initState(); } @@ -74,14 +74,14 @@ class _BookmarksState extends State { ), const DidvanDivider(), MenuItem( - onTap: () => _onCategorySelected('videos'), + onTap: () => _onCategorySelected('video'), title: 'ویدئو‌ها', icon: DidvanIcons.video_regular, iconSize: 24, ), const DidvanDivider(), MenuItem( - onTap: () => _onCategorySelected('podcasts'), + onTap: () => _onCategorySelected('podcast'), title: 'پادکست‌ها', icon: DidvanIcons.podcast_regular, iconSize: 24, @@ -103,26 +103,33 @@ class _BookmarksState extends State { SliverStateHandler( state: state, centerEmptyState: state.searching, - builder: (context, state, index) => MultitypeOverview( - item: state.bookmarks[index], - onMarkChanged: state.onMarkChanged, - hasUnmarkConfirmation: true, - ), + builder: (context, state, index) { + index++; + if (index % 15 == 0 && state.lastPage != state.page) { + state.getBookmarks(page: state.page + 1); + } + index--; + return MultitypeOverview( + item: state.bookmarks[index], + onMarkChanged: state.onMarkChanged, + hasUnmarkConfirmation: true, + ); + }, placeholder: MultitypeOverview.placeholder, itemPadding: const EdgeInsets.only(bottom: 8), emptyState: state.searching ? EmptyResult(onNewSearch: _focuseNode.requestFocus) : const EmptyList(), enableEmptyState: state.bookmarks.isEmpty, - childCount: state.bookmarks.length, - onRetry: state.getBookmarks, + childCount: + state.bookmarks.length + (state.page != state.lastPage ? 1 : 0), + onRetry: () => state.getBookmarks(page: state.page), ), ], ); } void _onCategorySelected(String type) { - if (type != 'radar' && type != 'news') return; FocusScope.of(context).unfocus(); Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: type); } @@ -135,7 +142,7 @@ class _BookmarksState extends State { _timer?.cancel(); _timer = Timer(const Duration(seconds: 1), () { state.search = value; - state.getBookmarks(); + state.getBookmarks(page: 1); }); } } diff --git a/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart index f9a5454..106d718 100644 --- a/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart +++ b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart @@ -20,7 +20,7 @@ class _FilteredBookmarksState extends State { void initState() { Future.delayed( Duration.zero, - context.read().getBookmarks, + () => context.read().getBookmarks(page: 1), ); super.initState(); } @@ -54,6 +54,11 @@ class _FilteredBookmarksState extends State { placeholder: RadarOverview.placeholder, emptyState: const EmptyList(), builder: (context, state, index) { + index++; + if (index % 15 == 0 && state.lastPage != state.page) { + state.getBookmarks(page: state.page + 1); + } + index--; if (state.type == 'radar') { return RadarOverview( radar: state.bookmarks[index], @@ -69,7 +74,7 @@ class _FilteredBookmarksState extends State { ); }, childCount: state.bookmarks.length, - onRetry: () => state.getBookmarks(), + onRetry: () => state.getBookmarks(page: state.page), ), ), ], diff --git a/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart index 697ee3a..7aa0463 100644 --- a/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart +++ b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart @@ -10,24 +10,42 @@ class FilteredBookmarksState extends CoreProvier { String lastSearch = ''; final List bookmarks = []; final String type; + int page = 1; + int lastPage = 1; FilteredBookmarksState(this.type); bool get searching => search != ''; - Future getBookmarks() async { + Future getBookmarks({required int page}) async { if (search != '') { lastSearch = search; } + if (page == 1) { + bookmarks.clear(); + } + this.page = page; appState = AppState.busy; + String typeString = ''; + if (type == 'video' || type == 'podcast') { + typeString = 'studios'; + } else if (type == 'news') { + typeString = type; + } else { + typeString = type + 's'; + } final service = RequestService( - RequestHelper.bookmarks(type: type == 'news' ? type : type + 's'), + RequestHelper.bookmarks( + type: typeString, + page: page, + studioType: type == 'podcast' || type == 'video' ? type : null, + ), ); await service.httpGet(); if (service.isSuccess) { - final marks = service.result[type != 'news' ? type + 's' : type]; - bookmarks.clear(); + lastPage = service.result['lastPage']; + final marks = service.result[typeString]; for (var i = 0; i < marks.length; i++) { bookmarks.add(OverviewData.fromJson(marks[i])); } From 0ce465e3cb4a5c05e8d5937dac9246e1446f61a3 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Tue, 15 Mar 2022 12:42:51 +0330 Subject: [PATCH 10/23] comments bug fixed --- lib/views/home/news/news_details/news_details_state.dart | 2 +- lib/views/home/radar/radar_details/radar_details_state.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/views/home/news/news_details/news_details_state.dart b/lib/views/home/news/news_details/news_details_state.dart index 879a178..279bc78 100644 --- a/lib/views/home/news/news_details/news_details_state.dart +++ b/lib/views/home/news/news_details/news_details_state.dart @@ -84,7 +84,7 @@ class NewsDetailsState extends CoreProvier { news.any((n) => newsItem != null && n != null && n.id == newsItem.id); void onCommentsChanged(int count) { - news.firstWhere((item) => item!.id == currentNews.id)!.comments = count; + news.firstWhere((item) => item?.id == currentNews.id)!.comments = count; notifyListeners(); } diff --git a/lib/views/home/radar/radar_details/radar_details_state.dart b/lib/views/home/radar/radar_details/radar_details_state.dart index 2095cec..37ebb42 100644 --- a/lib/views/home/radar/radar_details/radar_details_state.dart +++ b/lib/views/home/radar/radar_details/radar_details_state.dart @@ -116,7 +116,7 @@ class RadarDetailsState extends CoreProvier { radars.any((r) => radar != null && r != null && r.id == radar.id); void onCommentsChanged(int count) { - radars.firstWhere((radar) => radar!.id == currentRadar.id)!.comments = + radars.firstWhere((radar) => radar?.id == currentRadar.id)!.comments = count; notifyListeners(); } From 9c096bcd0cf9eba1377844264d82d87a5a8d7ddb Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Wed, 16 Mar 2022 11:06:55 +0330 Subject: [PATCH 11/23] bug fixes --- lib/providers/server_data_provider.dart | 2 +- lib/views/home/direct/direct.dart | 10 ++++++++++ lib/views/home/direct/widgets/message_box.dart | 11 +++++++++-- .../home/widgets/floating_navigation_bar.dart | 14 +++++++++++++- lib/views/widgets/didvan/page_view.dart | 2 ++ lib/views/widgets/didvan/text.dart | 4 +++- 6 files changed, 38 insertions(+), 5 deletions(-) diff --git a/lib/providers/server_data_provider.dart b/lib/providers/server_data_provider.dart index 3b76c8f..532bb5e 100644 --- a/lib/providers/server_data_provider.dart +++ b/lib/providers/server_data_provider.dart @@ -8,7 +8,7 @@ class ServerDataProvider { await _getDirectTypes(); } - static int labelToTypeId(String? label) => label == null + static int labelToTypeId(String label) => label.contains('پشتیبانی') ? 7 : directTypes.firstWhere((element) => element.value.contains(label)).key; diff --git a/lib/views/home/direct/direct.dart b/lib/views/home/direct/direct.dart index 2af96ec..17ddee1 100644 --- a/lib/views/home/direct/direct.dart +++ b/lib/views/home/direct/direct.dart @@ -1,3 +1,4 @@ +import 'package:didvan/constants/assets.dart'; import 'package:didvan/models/enums.dart'; import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/providers/server_data_provider.dart'; @@ -6,6 +7,7 @@ import 'package:didvan/views/home/direct/direct_state.dart'; import 'package:didvan/views/home/direct/widgets/message.dart'; import 'package:didvan/views/home/direct/widgets/message_box.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart'; @@ -66,6 +68,14 @@ class _DirectState extends State { sliver: SliverStateHandler( itemPadding: const EdgeInsets.only(bottom: 12), state: state, + enableEmptyState: state.messages.isEmpty, + emptyState: Padding( + padding: const EdgeInsets.only(bottom: 160), + child: EmptyState( + asset: Assets.emptyChat, + title: 'اولین پیام را بنویسید...', + ), + ), builder: (context, state, index) => Message( message: state.messages[index], ), diff --git a/lib/views/home/direct/widgets/message_box.dart b/lib/views/home/direct/widgets/message_box.dart index 897cff5..c09d592 100644 --- a/lib/views/home/direct/widgets/message_box.dart +++ b/lib/views/home/direct/widgets/message_box.dart @@ -18,6 +18,7 @@ class MessageBox extends StatelessWidget { Consumer( builder: (context, state, child) => state.replyRadar != null ? _MessageBoxContainer( + isMessage: false, child: Padding( padding: const EdgeInsets.all(8.0), child: Row( @@ -53,6 +54,7 @@ class MessageBox extends StatelessWidget { : const SizedBox(), ), _MessageBoxContainer( + isMessage: true, child: Consumer( builder: (context, state, child) { if (state.isRecording) { @@ -71,12 +73,17 @@ class MessageBox extends StatelessWidget { class _MessageBoxContainer extends StatelessWidget { final Widget child; - const _MessageBoxContainer({Key? key, required this.child}) : super(key: key); + final bool isMessage; + const _MessageBoxContainer({ + Key? key, + required this.child, + required this.isMessage, + }) : super(key: key); @override Widget build(BuildContext context) { return Container( - height: 68, + height: isMessage ? 68 : null, decoration: BoxDecoration( border: Border( top: BorderSide( diff --git a/lib/views/home/widgets/floating_navigation_bar.dart b/lib/views/home/widgets/floating_navigation_bar.dart index 3cc0323..95727d1 100644 --- a/lib/views/home/widgets/floating_navigation_bar.dart +++ b/lib/views/home/widgets/floating_navigation_bar.dart @@ -233,7 +233,19 @@ class _FloatingNavigationBarState extends State { Navigator.of(context).pop(); Navigator.of(context).pushNamed( Routes.direct, - arguments: {}, + arguments: { + 'radarAttachment': RadarAttachment( + id: widget.item.id, + title: widget.item.title, + description: widget.item.contents.first.text, + timeToRead: widget.item.timeToRead, + image: widget.item.image, + forManagers: widget.item.forManagers, + categories: widget.item.categories, + createdAt: widget.item.createdAt, + ), + 'type': 'پشتیبانی' + }, ); }, icon: DidvanIcons.description_regular, diff --git a/lib/views/widgets/didvan/page_view.dart b/lib/views/widgets/didvan/page_view.dart index f0a83d3..7fc6b19 100644 --- a/lib/views/widgets/didvan/page_view.dart +++ b/lib/views/widgets/didvan/page_view.dart @@ -14,6 +14,7 @@ import 'package:didvan/views/widgets/item_title.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; import 'package:flutter_html/flutter_html.dart'; +import 'package:url_launcher/url_launcher.dart'; class DidvanPageView extends StatefulWidget { final List items; @@ -183,6 +184,7 @@ class _DidvanPageViewState extends State { if (content.text != null) { return Html( data: content.text, + onAnchorTap: (href, context, map, element) => launch(href!), style: { '*': Style( direction: TextDirection.rtl, diff --git a/lib/views/widgets/didvan/text.dart b/lib/views/widgets/didvan/text.dart index 1ea5ec8..b2e840b 100644 --- a/lib/views/widgets/didvan/text.dart +++ b/lib/views/widgets/didvan/text.dart @@ -34,7 +34,9 @@ class DidvanText extends StatelessWidget { fontWeight: fontWeight, fontSize: fontSize, )).copyWith( - fontFamily: isEnglishFont ? DesignConfig.fontFamily : null, + fontFamily: isEnglishFont + ? DesignConfig.fontFamily.replaceAll('-FA', '') + : null, height: 1.7, ), overflow: overflow, From ddc707bbe94cd141d7a42f2c784cbf33ad083ff0 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Wed, 16 Mar 2022 11:08:05 +0330 Subject: [PATCH 12/23] D1APP-99 studio updates --- lib/models/overview_data.dart | 13 +- lib/models/srudio_data.dart | 37 --- lib/models/studio_details_data.dart | 61 +++++ lib/models/view/app_bar_data.dart | 9 +- lib/providers/user_provider.dart | 17 ++ lib/routes/route_generator.dart | 11 + lib/routes/routes.dart | 1 + lib/services/network/request_helper.dart | 43 ++-- lib/views/home/studio/studio.dart | 47 +++- .../studio/studio_details/studio_details.dart | 210 ++++++++++++++++++ .../studio_details/studio_details_state.dart | 141 ++++++++++++ lib/views/home/studio/studio_state.dart | 36 ++- lib/views/home/widgets/audio_slider.dart | 14 +- lib/views/home/widgets/duration_widget.dart | 47 ++++ lib/views/home/widgets/podcast_overview.dart | 103 +++++---- lib/views/home/widgets/video_overview.dart | 157 +++++++++++++ lib/views/widgets/didvan/app_bar.dart | 3 +- lib/views/widgets/didvan/scaffold.dart | 12 +- pubspec.lock | 30 ++- pubspec.yaml | 1 + 20 files changed, 870 insertions(+), 123 deletions(-) delete mode 100644 lib/models/srudio_data.dart create mode 100644 lib/models/studio_details_data.dart create mode 100644 lib/views/home/studio/studio_details/studio_details.dart create mode 100644 lib/views/home/studio/studio_details/studio_details_state.dart create mode 100644 lib/views/home/widgets/duration_widget.dart create mode 100644 lib/views/home/widgets/video_overview.dart diff --git a/lib/models/overview_data.dart b/lib/models/overview_data.dart index 4d16449..a658839 100644 --- a/lib/models/overview_data.dart +++ b/lib/models/overview_data.dart @@ -6,6 +6,7 @@ class OverviewData { final String image; final String description; final int? timeToRead; + final int? duration; final String? reference; final bool forManagers; final String createdAt; @@ -24,6 +25,7 @@ class OverviewData { required this.marked, required this.comments, required this.forManagers, + this.duration, this.timeToRead, this.reference, this.categories, @@ -39,11 +41,16 @@ class OverviewData { forManagers: json['forManagers'] ?? false, comments: json['comments'] ?? 0, createdAt: json['createdAt'], + duration: json['duration'], type: json['type'] ?? '', marked: json['marked'] ?? false, - categories: (json['categories'] as List?) - ?.map((e) => CategoryData.fromJson(e as Map)) - .toList(), + categories: json['categories'] != null + ? List.from( + json['categories'].map( + (e) => CategoryData.fromJson(e), + ), + ) + : null, ); Map toJson() => { diff --git a/lib/models/srudio_data.dart b/lib/models/srudio_data.dart deleted file mode 100644 index 1e7f4b6..0000000 --- a/lib/models/srudio_data.dart +++ /dev/null @@ -1,37 +0,0 @@ -class StudioData { - final int id; - final String title; - final String image; - final String duration; - final String createdAt; - bool marked; - - StudioData({ - required this.id, - required this.title, - required this.image, - required this.duration, - required this.createdAt, - required this.marked, - }); - - factory StudioData.fromJson(Map json) { - return StudioData( - id: json['id'], - title: json['title'], - image: json['image'], - duration: json['duration'], - createdAt: json['createdAt'], - marked: json['marked'], - ); - } - - Map toJson() => { - 'id': id, - 'title': title, - 'image': image, - 'duration': duration, - 'createdAt': createdAt, - 'marked': marked, - }; -} diff --git a/lib/models/studio_details_data.dart b/lib/models/studio_details_data.dart new file mode 100644 index 0000000..b83722e --- /dev/null +++ b/lib/models/studio_details_data.dart @@ -0,0 +1,61 @@ +import 'package:didvan/models/overview_data.dart'; +import 'package:didvan/models/tag.dart'; + +class StudioDetailsData { + final int id; + final int duration; + final String title; + final String description; + final String image; + final String media; + final String createdAt; + final int order; + bool marked; + int comments; + final List tags; + final List relatedContents = []; + + StudioDetailsData({ + required this.id, + required this.duration, + required this.title, + required this.description, + required this.image, + required this.media, + required this.createdAt, + required this.order, + required this.marked, + required this.comments, + required this.tags, + }); + + factory StudioDetailsData.fromJson(Map json) { + return StudioDetailsData( + id: json['id'], + duration: json['duration'], + title: json['title'], + description: json['description'], + image: json['image'], + media: json['media'], + createdAt: json['createdAt'], + order: json['order'], + marked: json['marked'], + comments: json['comments'], + tags: List.from(json['tags'].map((e) => Tag.fromJson(e))), + ); + } + + Map toJson() => { + 'id': id, + 'duration': duration, + 'title': title, + 'description': description, + 'image': image, + 'media': media, + 'createdAt': createdAt, + 'order': order, + 'marked': marked, + 'comments': comments, + 'tags': tags.map((e) => e.toJson()).toList(), + }; +} diff --git a/lib/models/view/app_bar_data.dart b/lib/models/view/app_bar_data.dart index 9e00aa5..85b6e91 100644 --- a/lib/models/view/app_bar_data.dart +++ b/lib/models/view/app_bar_data.dart @@ -5,6 +5,13 @@ class AppBarData { final String? subtitle; final bool hasBack; final Widget? trailing; + final bool isSmall; - AppBarData({this.title, this.subtitle, this.hasBack = false, this.trailing}); + AppBarData({ + this.title, + this.subtitle, + this.hasBack = false, + this.trailing, + this.isSmall = false, + }); } diff --git a/lib/providers/user_provider.dart b/lib/providers/user_provider.dart index f53d0ac..19c5192 100644 --- a/lib/providers/user_provider.dart +++ b/lib/providers/user_provider.dart @@ -14,6 +14,7 @@ class UserProvider extends CoreProvier { static final List _radarMarkQueue = []; static final List _newsMarkQueue = []; + static final List _studioMarkQueue = []; Future setAndGetToken({String? newToken}) async { if (newToken == null) { @@ -141,6 +142,22 @@ class UserProvider extends CoreProvier { }); } + static Future changeStudioMark(int id, bool value) async { + _studioMarkQueue.add(MapEntry(id, value)); + Future.delayed(const Duration(milliseconds: 500), () async { + final MapEntry? lastChange = + _studioMarkQueue.lastWhereOrNull((item) => item.key == id); + if (lastChange == null) return; + final service = RequestService(RequestHelper.markStudio(id)); + if (lastChange.value) { + await service.post(); + } else { + await service.delete(); + } + _studioMarkQueue.removeWhere((element) => element.key == id); + }); + } + static Future changeNewsMark(int id, bool value) async { _newsMarkQueue.add(MapEntry(id, value)); Future.delayed(const Duration(milliseconds: 500), () async { diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 205dcb6..2989dee 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -25,6 +25,8 @@ import 'package:didvan/views/home/settings/direct_list/direct_list_state.dart'; import 'package:didvan/views/home/settings/general_settings/settings.dart'; import 'package:didvan/views/home/settings/general_settings/settings_state.dart'; import 'package:didvan/views/home/settings/profile/profile.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/studio/studio_state.dart'; import 'package:didvan/views/splash/splash.dart'; import 'package:didvan/routes/routes.dart'; @@ -99,6 +101,15 @@ class RouteGenerator { ), ), ); + case Routes.studioDetails: + return _createRoute( + ChangeNotifierProvider( + create: (context) => StudioDetailsState(), + child: StudioDetails( + pageData: settings.arguments as Map, + ), + ), + ); case Routes.directList: return _createRoute( ChangeNotifierProvider( diff --git a/lib/routes/routes.dart b/lib/routes/routes.dart index 7cbab35..fe94d78 100644 --- a/lib/routes/routes.dart +++ b/lib/routes/routes.dart @@ -8,6 +8,7 @@ class Routes { static const String generalSettings = '/general-settings'; static const String radarDetails = '/radar-details'; static const String newsDetails = '/news-details'; + static const String studioDetails = '/studio-details'; static const String directList = '/direct-list'; static const String direct = '/direct'; static const String comments = '/comments'; diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index ccb6391..e35f070 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -19,8 +19,19 @@ class RequestHelper { static const String checkUsername = _baseUserUrl + '/CheckUsername'; static const String updateProfile = _baseUserUrl + '/profile/edit'; static const String otp = _baseUserUrl + '/otp'; - static String bookmarks({String? type}) => - _baseUserUrl + '/marked/${type ?? ''}'; + static String bookmarks({ + required int page, + String? search, + String? type, + String? studioType, + }) => + _baseUserUrl + + '/marked/${type ?? ''}' + + _urlConcatGenerator([ + MapEntry('page', page), + MapEntry('type', studioType), + MapEntry('search', search), + ]); static const String directTypes = baseUrl + '/direct/types'; static String direct(int id) => _baseDirectUrl + '/$id'; @@ -36,10 +47,10 @@ class RequestHelper { baseUrl + '/tag' + _urlConcatGenerator([ - MapEntry('page', page?.toString()), - MapEntry('limit', limit?.toString() ?? '3'), + MapEntry('page', page), + MapEntry('limit', limit ?? '3'), MapEntry('type', type), - MapEntry('id', itemId?.toString() ?? '1'), + MapEntry('id', itemId ?? '1'), MapEntry('tags', _urlListConcatGenerator(ids)), ]); @@ -52,7 +63,7 @@ class RequestHelper { _baseRadarUrl + '/$id' + _urlConcatGenerator([ - MapEntry('page', args.page.toString()), + MapEntry('page', args.page), MapEntry('start', args.startDate), MapEntry('end', args.endDate), MapEntry('search', args.search), @@ -61,7 +72,7 @@ class RequestHelper { static String radarOverviews({required RadarRequestArgs args}) => _baseRadarUrl + _urlConcatGenerator([ - MapEntry('page', args.page.toString()), + MapEntry('page', args.page), MapEntry('start', args.startDate), MapEntry('end', args.endDate), MapEntry('search', args.search), @@ -77,7 +88,7 @@ class RequestHelper { _baseNewsUrl + '/$id' + _urlConcatGenerator([ - MapEntry('page', args.page.toString()), + MapEntry('page', args.page), MapEntry('start', args.startDate), MapEntry('end', args.endDate), MapEntry('search', args.search), @@ -85,7 +96,7 @@ class RequestHelper { static String newsOverviews({required NewsRequestArgs args}) => _baseNewsUrl + _urlConcatGenerator([ - MapEntry('page', args.page.toString()), + MapEntry('page', args.page), MapEntry('start', args.startDate), MapEntry('end', args.endDate), MapEntry('search', args.search), @@ -101,27 +112,29 @@ class RequestHelper { _baseStudioUrl + '/$id' + _urlConcatGenerator([ - MapEntry('page', args.page.toString()), + MapEntry('page', args.page), MapEntry('type', args.type), MapEntry('order', args.order), MapEntry('search', args.search), ]); static String studioOverviews({required StudioRequestArgs args}) => - _baseNewsUrl + + _baseStudioUrl + _urlConcatGenerator([ - MapEntry('page', args.page.toString()), + MapEntry('page', args.page), MapEntry('type', args.type), MapEntry('order', args.order), MapEntry('search', args.search), ]); - static String _urlConcatGenerator(List> additions) { + static String _urlConcatGenerator(List> additions) { String result = ''; - additions.removeWhere((element) => element.value == null); + additions.removeWhere( + (element) => element.value == null || element.value.toString().isEmpty, + ); if (additions.isNotEmpty) { result += '?'; for (var i = 0; i < additions.length; i++) { - result += (additions[i].key + '=' + additions[i].value!); + result += (additions[i].key + '=' + additions[i].value!.toString()); if (i != additions.length - 1) { result += '&'; } diff --git a/lib/views/home/studio/studio.dart b/lib/views/home/studio/studio.dart index a362ed8..d4cba73 100644 --- a/lib/views/home/studio/studio.dart +++ b/lib/views/home/studio/studio.dart @@ -1,11 +1,14 @@ import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/home/studio/studio_state.dart'; import 'package:didvan/views/home/studio/widgets/slider.dart'; import 'package:didvan/views/home/studio/widgets/tab_bar.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart'; +import 'package:didvan/views/home/widgets/podcast_overview.dart'; import 'package:didvan/views/home/widgets/search_field.dart'; +import 'package:didvan/views/home/widgets/video_overview.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/radial_button.dart'; @@ -24,6 +27,12 @@ class Studio extends StatefulWidget { class _StudioState extends State { final FocusNode _focusNode = FocusNode(); + @override + void initState() { + context.read().init(); + super.initState(); + } + @override Widget build(BuildContext context) { return CustomScrollView( @@ -86,9 +95,41 @@ class _StudioState extends State { Consumer( builder: (context, state, child) => SliverStateHandler( state: state, - builder: (context, state, index) => Container(), + itemPadding: const EdgeInsets.only( + bottom: 8, + left: 16, + right: 16, + ), + placeholder: state.videosSelected + ? VideoOverview.placeHolder + : PodcastOverview.placeholder, + builder: (context, state, index) => state.videosSelected + ? VideoOverview( + onMarkChanged: state.changeMark, + hasUnmarkConfirmation: false, + video: state.studios[index], + onCommentsChanged: state.onCommentsChanged, + studioRequestArgs: StudioRequestArgs( + page: state.page, + order: state.order, + search: state.search, + type: state.type, + ), + ) + : PodcastOverview( + podcast: state.studios[index], + onMarkChanged: state.changeMark, + hasUnmarkConfirmation: false, + onCommentsChanged: state.onCommentsChanged, + studioRequestArgs: StudioRequestArgs( + page: state.page, + order: state.order, + search: state.search, + type: state.type, + ), + ), childCount: state.studios.length, - onRetry: () {}, + onRetry: () => state.getStudioOverviews(page: 1), ), ), ], @@ -132,7 +173,7 @@ class _StudioState extends State { titleIcon: DidvanIcons.sort_regular, hasDismissButton: false, confrimTitle: 'مرتب سازی', - onConfirmed: () {}, + onConfirmed: () => state.getStudioOverviews(page: 1), ), ); } diff --git a/lib/views/home/studio/studio_details/studio_details.dart b/lib/views/home/studio/studio_details/studio_details.dart new file mode 100644 index 0000000..26491e2 --- /dev/null +++ b/lib/views/home/studio/studio_details/studio_details.dart @@ -0,0 +1,210 @@ +import 'dart:io'; + +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/models/studio_details_data.dart'; +import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; +import 'package:didvan/views/home/widgets/audio_slider.dart'; +import 'package:didvan/views/widgets/didvan/scaffold.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:provider/provider.dart'; +import 'package:webview_flutter/webview_flutter.dart'; + +class StudioDetails extends StatefulWidget { + final Map pageData; + + const StudioDetails({Key? key, required this.pageData}) : super(key: key); + + @override + State createState() => _StudioDetailsState(); +} + +class _StudioDetailsState extends State { + bool _isFullScreen = false; + bool _isInit = true; + + double _dwInPortrait = 0; + double _scaleInPortrait = 1; + + bool get _isVideo => widget.pageData['isVideo']; + + @override + void initState() { + final state = context.read(); + Future.delayed( + Duration.zero, + () => state.getStudioDetails(widget.pageData['id']), + ); + state.args = widget.pageData['args']; + if (Platform.isAndroid) WebView.platform = AndroidWebView(); + super.initState(); + } + + Future _changeFullSceen(bool value) async { + if (value) { + await SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: [], + ); + SystemChrome.setSystemUIOverlayStyle( + const SystemUiOverlayStyle( + systemNavigationBarColor: Colors.black, + ), + ); + await SystemChrome.setPreferredOrientations( + [DeviceOrientation.landscapeLeft], + ); + } else { + await SystemChrome.setEnabledSystemUIMode( + SystemUiMode.manual, + overlays: [SystemUiOverlay.bottom, SystemUiOverlay.top], + ); + await SystemChrome.setPreferredOrientations( + [DeviceOrientation.portraitUp], + ); + DesignConfig.updateSystemUiOverlayStyle(); + } + setState(() { + _isFullScreen = value; + }); + } + + @override + Widget build(BuildContext context) { + final ds = MediaQuery.of(context).size; + if (_isInit) { + _dwInPortrait = MediaQuery.of(context).size.width; + _scaleInPortrait = _dwInPortrait / 576; + _isInit = false; + } + return Consumer( + builder: (context, state, child) => StateHandler( + state: state, + onRetry: () => state.getStudioDetails(state.currentStudio.id), + builder: (context, state) => state.studios.isEmpty + ? const SizedBox() + : WillPopScope( + onWillPop: () async { + if (_isFullScreen) { + await _changeFullSceen(false); + return false; + } + return true; + }, + child: DidvanScaffold( + appBarData: _isFullScreen + ? null + : AppBarData( + isSmall: true, + title: state.currentStudio.title, + ), + children: [ + if (_isVideo) + SizedBox( + width: ds.width, + height: _isFullScreen ? ds.height : ds.width * 9 / 16, + child: Stack( + children: [ + WebView( + initialUrl: Uri.dataFromString( + ''' + + + + + + + ${state.currentStudio.media} + + + ''', + mimeType: 'text/html', + ).toString(), + javascriptMode: JavascriptMode.unrestricted, + ), + Positioned( + right: 42, + bottom: 8, + child: GestureDetector( + onTap: () => _changeFullSceen(!_isFullScreen), + child: Container( + color: Colors.transparent, + width: 24, + height: 30, + ), + ), + ), + ], + ), + ), + if (!_isVideo) + AudioPlayerWidget(podcast: state.currentStudio), + ], + ), + ), + ), + ); + } +} + +class AudioPlayerWidget extends StatelessWidget { + final StudioDetailsData podcast; + const AudioPlayerWidget({Key? key, required this.podcast}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: Hero( + tag: podcast.media, + child: SkeletonImage( + imageUrl: podcast.image, + aspectRatio: 1 / 1, + ), + ), + ), + const SizedBox(height: 16), + DidvanText( + podcast.title, + style: Theme.of(context).textTheme.bodyText1, + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: AudioSlider( + tag: podcast.media, + showTimer: true, + ), + ), + ], + ); + } +} diff --git a/lib/views/home/studio/studio_details/studio_details_state.dart b/lib/views/home/studio/studio_details/studio_details_state.dart new file mode 100644 index 0000000..4a4d9e2 --- /dev/null +++ b/lib/views/home/studio/studio_details/studio_details_state.dart @@ -0,0 +1,141 @@ +import 'dart:async'; +import 'dart:math'; + +import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/overview_data.dart'; +import 'package:didvan/models/requests/studio.dart'; +import 'package:didvan/models/studio_details_data.dart'; +import 'package:didvan/providers/core_provider.dart'; +import 'package:didvan/services/network/request.dart'; +import 'package:didvan/services/network/request_helper.dart'; + +class StudioDetailsState extends CoreProvier { + final List studios = []; + late Timer _trackingTimer; + int _trackingTimerCounter = 0; + late final int initialIndex; + late final StudioRequestArgs args; + bool isFetchingNewItem = false; + final List relatedQueue = []; + + int _currentIndex = 0; + int get currentIndex => _currentIndex; + + StudioDetailsData get currentStudio { + try { + return studios[_currentIndex]!; + } catch (e) { + return studios[_currentIndex + 1]!; + } + } + + Future getStudioDetails(int id, {bool? isForward}) async { + if (isForward == null) { + appState = AppState.busy; + } else { + isFetchingNewItem = true; + notifyListeners(); + } + final service = RequestService(RequestHelper.studioDetails(id, args)); + await service.httpGet(); + _handleTracking(sendRequest: isForward != null); + if (service.isSuccess) { + final result = service.result; + final studio = StudioDetailsData.fromJson(result['studio']); + if (args.page == 0) { + studios.add(studio); + initialIndex = 0; + appState = AppState.idle; + return; + } + + StudioDetailsData? prevStudio; + if (result['prevStudio'].isNotEmpty) { + prevStudio = StudioDetailsData.fromJson(result['prevStudio']); + } + + StudioDetailsData? nextStudio; + if (result['nextStudio'].isNotEmpty) { + nextStudio = StudioDetailsData.fromJson(result['nextStudio']); + } + + if (isForward == null) { + studios + .addAll(List.generate(max(studio.order - 2, 0), (index) => null)); + if (prevStudio != null) { + studios.add(prevStudio); + } + studios.add(studio); + if (nextStudio != null) { + studios.add(nextStudio); + } + _currentIndex = initialIndex = studio.order - 1; + } else if (isForward) { + if (!exists(nextStudio) && nextStudio != null) { + studios.add(nextStudio); + } + _currentIndex++; + } else if (!isForward) { + if (!exists(prevStudio) && prevStudio != null) { + studios[_currentIndex - 2] = prevStudio; + } + _currentIndex--; + } + isFetchingNewItem = false; + appState = AppState.idle; + return; + } + //why? total page state shouldn't die! + if (isForward == null) { + appState = AppState.failed; + } + } + + Future getRelatedContents() async { + if (currentStudio.relatedContents.isNotEmpty) return; + relatedQueue.add(currentStudio.id); + final service = RequestService(RequestHelper.tag( + ids: currentStudio.tags.map((tag) => tag.id).toList(), + itemId: currentStudio.id, + type: 'studio', + )); + await service.httpGet(); + if (service.isSuccess) { + final relateds = service.result['contents']; + for (var i = 0; i < relateds.length; i++) { + studios + .where((element) => element != null) + .firstWhere((element) => element!.id == currentStudio.id)! + .relatedContents + .add(OverviewData.fromJson(relateds[i])); + } + notifyListeners(); + } + } + + bool exists(StudioDetailsData? studio) => + studios.any((r) => studio != null && r != null && r.id == studio.id); + + void onCommentsChanged(int count) { + studios.firstWhere((studio) => studio?.id == currentStudio.id)!.comments = + count; + notifyListeners(); + } + + Future _handleTracking({bool sendRequest = true}) async { + if (!sendRequest) { + _trackingTimer = Timer.periodic(const Duration(seconds: 1), (timer) { + _trackingTimerCounter++; + }); + return; + } + //send request + _trackingTimerCounter = 0; + } + + @override + void dispose() { + _trackingTimer.cancel(); + super.dispose(); + } +} diff --git a/lib/views/home/studio/studio_state.dart b/lib/views/home/studio/studio_state.dart index f8e6944..18b7dc2 100644 --- a/lib/views/home/studio/studio_state.dart +++ b/lib/views/home/studio/studio_state.dart @@ -1,12 +1,13 @@ import 'package:didvan/models/enums.dart'; +import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; -import 'package:didvan/models/srudio_data.dart'; import 'package:didvan/providers/core_provider.dart'; +import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; class StudioState extends CoreProvier { - final List studios = []; + final List studios = []; String? search; String? lastSearch; @@ -22,52 +23,71 @@ class StudioState extends CoreProvier { set videosSelected(bool value) { if (_videosSelected == value) return; _videosSelected = value; + studios.clear(); getStudioOverviews(page: page); } void init() { search = ''; lastSearch = ''; - videosSelected = true; + _videosSelected = true; selectedSortTypeIndex = 0; Future.delayed(Duration.zero, () { getStudioOverviews(page: 1); }); } - String get _order { + String get order { if (selectedSortTypeIndex == 0) return 'date'; if (selectedSortTypeIndex == 1) return 'view'; return 'comment'; } + String get type { + if (videosSelected) return 'video'; + return 'podcast'; + } + Future getStudioOverviews({required int page}) async { this.page = page; if (page == 1) { appState = AppState.busy; } - final service = RequestService( RequestHelper.studioOverviews( args: StudioRequestArgs( page: page, - type: videosSelected ? 'video' : 'podcast', + type: type, search: search, - order: _order, + order: order, ), ), ); await service.httpGet(); if (service.isSuccess) { + if (page == 1) { + studios.clear(); + } lastPage = service.result['lastPage']; final studioItems = service.result['studios']; for (var i = 0; i < studioItems.length; i++) { - studios.add(StudioData.fromJson(studioItems[i])); + studios.add(OverviewData.fromJson(studioItems[i])); } appState = AppState.idle; return; } appState = AppState.failed; } + + Future changeMark(int id, bool value) async { + studios.firstWhere((element) => element.id == id).marked = value; + notifyListeners(); + UserProvider.changeStudioMark(id, value); + } + + void onCommentsChanged(int id, int count) { + studios.firstWhere((radar) => radar.id == id).comments = count; + notifyListeners(); + } } diff --git a/lib/views/home/widgets/audio_slider.dart b/lib/views/home/widgets/audio_slider.dart index b9edf70..dd7196f 100644 --- a/lib/views/home/widgets/audio_slider.dart +++ b/lib/views/home/widgets/audio_slider.dart @@ -1,10 +1,13 @@ import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; +import 'package:didvan/config/design_config.dart'; import 'package:didvan/services/media/media.dart'; import 'package:flutter/material.dart'; class AudioSlider extends StatelessWidget { final String tag; - const AudioSlider({Key? key, required this.tag}) : super(key: key); + final bool showTimer; + const AudioSlider({Key? key, required this.tag, this.showTimer = false}) + : super(key: key); bool get _isPlaying => MediaService.audioPlayerTag == tag; @@ -24,7 +27,14 @@ class AudioSlider extends StatelessWidget { _isPlaying ? MediaService.audioPlayer.bufferedPosition : null, thumbRadius: 6, barHeight: 3, - timeLabelTextStyle: const TextStyle(fontSize: 0), + timeLabelTextStyle: TextStyle( + fontSize: showTimer ? null : 0, + height: showTimer ? null : 0, + fontFamily: DesignConfig.fontFamily.replaceAll( + '-FA', + '', + ), + ), onSeek: (value) => _onSeek(value.inMilliseconds), ); }, diff --git a/lib/views/home/widgets/duration_widget.dart b/lib/views/home/widgets/duration_widget.dart new file mode 100644 index 0000000..025224d --- /dev/null +++ b/lib/views/home/widgets/duration_widget.dart @@ -0,0 +1,47 @@ +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:flutter/material.dart'; + +class DurationWidget extends StatelessWidget { + final int duration; + const DurationWidget({Key? key, required this.duration}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + padding: const EdgeInsets.all(4), + decoration: BoxDecoration( + border: Border.all( + color: Theme.of(context).colorScheme.focusedBorder, + ), + borderRadius: BorderRadius.circular(5), + ), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Icon( + DidvanIcons.timer_regular, + size: 16, + color: Theme.of(context).colorScheme.focusedBorder, + ), + const SizedBox(width: 4), + DidvanText( + DateTimeUtils.normalizeTimeDuration( + Duration(seconds: duration), + ), + isEnglishFont: true, + color: Theme.of(context).colorScheme.focusedBorder, + ), + const SizedBox(width: 4), + Icon( + DidvanIcons.play_circle_regular, + size: 16, + color: Theme.of(context).colorScheme.focusedBorder, + ), + ], + ), + ); + } +} diff --git a/lib/views/home/widgets/podcast_overview.dart b/lib/views/home/widgets/podcast_overview.dart index fa626fb..2e2265d 100644 --- a/lib/views/home/widgets/podcast_overview.dart +++ b/lib/views/home/widgets/podcast_overview.dart @@ -1,38 +1,46 @@ +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/overview_data.dart'; -import 'package:didvan/models/requests/news.dart'; +import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/home/widgets/bookmark_button.dart'; +import 'package:didvan/views/home/widgets/duration_widget.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; class PodcastOverview extends StatelessWidget { - final OverviewData news; - final NewsRequestArgs? newsRequestArgs; + final OverviewData podcast; + final void Function(int id, int count) onCommentsChanged; final void Function(int id, bool value) onMarkChanged; final bool hasUnmarkConfirmation; + final StudioRequestArgs? studioRequestArgs; const PodcastOverview({ Key? key, - required this.news, + required this.podcast, + required this.onCommentsChanged, required this.onMarkChanged, - this.newsRequestArgs, - this.hasUnmarkConfirmation = false, + required this.hasUnmarkConfirmation, + this.studioRequestArgs, }) : super(key: key); @override Widget build(BuildContext context) { return DidvanCard( onTap: () => Navigator.of(context).pushNamed( - Routes.newsDetails, + Routes.studioDetails, arguments: { 'onMarkChanged': onMarkChanged, - 'id': news.id, - 'args': newsRequestArgs, + 'onCommentsChanged': onCommentsChanged, + 'id': podcast.id, + 'args': studioRequestArgs, 'hasUnmarkConfirmation': hasUnmarkConfirmation, + 'isVideo': false, }, ), child: Column( @@ -41,47 +49,50 @@ class PodcastOverview extends StatelessWidget { Row( children: [ SkeletonImage( - imageUrl: news.image, + imageUrl: podcast.image, width: 64, height: 64, ), const SizedBox(width: 8), Expanded( - child: SizedBox( - height: 64, - child: DidvanText( - news.title, - style: Theme.of(context).textTheme.bodyText1, - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DidvanText( + podcast.title, + style: Theme.of(context).textTheme.bodyText1, + ), + const SizedBox(height: 4), + DidvanText( + DateTimeUtils.momentGenerator(podcast.createdAt), + style: Theme.of(context).textTheme.overline, + color: Theme.of(context).colorScheme.caption, + ), + ], ), ), ], ), const SizedBox(height: 8), DidvanText( - news.description, - maxLines: 3, + podcast.description, + maxLines: 2, + overflow: TextOverflow.ellipsis, ), const DidvanDivider(verticalPadding: 8), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - DidvanText( - news.reference!, - style: Theme.of(context).textTheme.caption, - ), - DidvanText( - ' - ' + DateTimeUtils.momentGenerator(news.createdAt), - style: Theme.of(context).textTheme.caption, - ), - ], + DurationWidget(duration: podcast.duration!), + const Spacer(), + DidvanIconButton( + gestureSize: 28, + icon: DidvanIcons.download_regular, + onPressed: () {}, ), + const SizedBox(width: 16), BookmarkButton( - value: news.marked, - onMarkChanged: (value) => onMarkChanged(news.id, value), - askForConfirmation: hasUnmarkConfirmation, + value: podcast.marked, + onMarkChanged: (value) => onMarkChanged(podcast.id, value), ), ], ), @@ -95,7 +106,7 @@ class PodcastOverview extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( - crossAxisAlignment: CrossAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, children: [ const ShimmerPlaceholder(height: 64, width: 64), const SizedBox(width: 8), @@ -109,7 +120,7 @@ class PodcastOverview extends StatelessWidget { ), ], ), - const SizedBox(height: 12), + const SizedBox(height: 16), const ShimmerPlaceholder( height: 16, width: double.infinity, @@ -117,19 +128,21 @@ class PodcastOverview extends StatelessWidget { const SizedBox(height: 8), const ShimmerPlaceholder( height: 16, - width: double.infinity, - ), - const SizedBox(height: 8), - const ShimmerPlaceholder( - height: 16, - width: 100, + width: 200, ), + const SizedBox(height: 4), const DidvanDivider(verticalPadding: 8), Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: const [ - ShimmerPlaceholder(height: 12, width: 150), - ShimmerPlaceholder(height: 24, width: 24), + children: [ + ShimmerPlaceholder( + height: 36, + width: 92, + borderRadius: BorderRadius.circular(5), + ), + const Spacer(), + const ShimmerPlaceholder(width: 24, height: 24), + const SizedBox(width: 16), + const ShimmerPlaceholder(width: 24, height: 24), ], ), ], diff --git a/lib/views/home/widgets/video_overview.dart b/lib/views/home/widgets/video_overview.dart new file mode 100644 index 0000000..f7e4975 --- /dev/null +++ b/lib/views/home/widgets/video_overview.dart @@ -0,0 +1,157 @@ +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/overview_data.dart'; +import 'package:didvan/models/requests/studio.dart'; +import 'package:didvan/routes/routes.dart'; +import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/home/widgets/bookmark_button.dart'; +import 'package:didvan/views/home/widgets/duration_widget.dart'; +import 'package:didvan/views/widgets/didvan/card.dart'; +import 'package:didvan/views/widgets/didvan/divider.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/shimmer_placeholder.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/material.dart'; + +class VideoOverview extends StatelessWidget { + final OverviewData video; + final void Function(int id, int count) onCommentsChanged; + final void Function(int id, bool value) onMarkChanged; + final bool hasUnmarkConfirmation; + final StudioRequestArgs? studioRequestArgs; + const VideoOverview({ + Key? key, + required this.video, + required this.onCommentsChanged, + required this.onMarkChanged, + required this.hasUnmarkConfirmation, + this.studioRequestArgs, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return DidvanCard( + onTap: () => Navigator.of(context).pushNamed( + Routes.studioDetails, + arguments: { + 'onMarkChanged': onMarkChanged, + 'onCommentsChanged': onCommentsChanged, + 'id': video.id, + 'args': studioRequestArgs, + 'hasUnmarkConfirmation': hasUnmarkConfirmation, + 'isVideo': true, + }, + ), + child: Row( + children: [ + Stack( + children: [ + SkeletonImage( + imageUrl: video.image, + height: 108, + width: 108, + ), + Positioned.fill( + child: Center( + child: Container( + height: 28, + width: 28, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: Theme.of(context) + .colorScheme + .secondary + .withOpacity(0.7), + ), + child: Icon( + DidvanIcons.play_solid, + color: Theme.of(context).colorScheme.white, + ), + ), + ), + ), + ], + ), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DidvanText( + video.title, + style: Theme.of(context).textTheme.bodyText1, + ), + const SizedBox(height: 4), + Row( + children: [ + const Icon( + DidvanIcons.calendar_day_regular, + size: 16, + ), + const SizedBox(width: 4), + DidvanText( + DateTimeUtils.momentGenerator(video.createdAt), + style: Theme.of(context).textTheme.overline, + color: Theme.of(context).colorScheme.caption, + ), + ], + ), + const DidvanDivider(verticalPadding: 8), + Row( + children: [ + DurationWidget(duration: video.duration!), + const Spacer(), + DidvanIconButton( + gestureSize: 28, + icon: DidvanIcons.download_regular, + onPressed: () {}, + ), + const SizedBox(width: 16), + BookmarkButton( + value: video.marked, + onMarkChanged: (value) => onMarkChanged(video.id, value), + ), + ], + ), + ], + ), + ), + ], + ), + ); + } + + static Widget get placeHolder => DidvanCard( + child: Row( + children: [ + const ShimmerPlaceholder(height: 108, width: 108), + const SizedBox(width: 8), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + const ShimmerPlaceholder(height: 20), + const SizedBox(height: 8), + const ShimmerPlaceholder(width: 100, height: 16), + const DidvanDivider(verticalPadding: 10), + Row( + children: [ + ShimmerPlaceholder( + height: 36, + width: 92, + borderRadius: BorderRadius.circular(5), + ), + const Spacer(), + const ShimmerPlaceholder(width: 24, height: 24), + const SizedBox(width: 16), + const ShimmerPlaceholder(width: 24, height: 24), + ], + ), + ], + ), + ), + ], + ), + ); +} diff --git a/lib/views/widgets/didvan/app_bar.dart b/lib/views/widgets/didvan/app_bar.dart index 1de49fe..7667dd8 100644 --- a/lib/views/widgets/didvan/app_bar.dart +++ b/lib/views/widgets/didvan/app_bar.dart @@ -18,11 +18,10 @@ class DidvanAppBar extends StatelessWidget { @override Widget build(BuildContext context) { return Container( - height: kToolbarHeight + MediaQuery.of(context).padding.top, + height: appBarData.isSmall ? 56 : 72, width: MediaQuery.of(context).size.width, padding: const EdgeInsets.only(right: 4, left: 20), decoration: BoxDecoration( - color: backgroundColor, border: hasBorder ? Border( bottom: BorderSide( diff --git a/lib/views/widgets/didvan/scaffold.dart b/lib/views/widgets/didvan/scaffold.dart index db75ac9..b1d8b28 100644 --- a/lib/views/widgets/didvan/scaffold.dart +++ b/lib/views/widgets/didvan/scaffold.dart @@ -5,7 +5,7 @@ import 'package:flutter/material.dart'; class DidvanScaffold extends StatefulWidget { final List? slivers; final List? children; - final AppBarData appBarData; + final AppBarData? appBarData; final EdgeInsets padding; final Color? backgroundColor; final bool reverse; @@ -40,14 +40,14 @@ class _DidvanScaffoldState extends State { controller: _scrollController, reverse: widget.reverse, slivers: [ - if (!widget.reverse) + if (!widget.reverse && widget.appBarData != null) SliverAppBar( - toolbarHeight: kToolbarHeight, + toolbarHeight: widget.appBarData!.isSmall ? 56 : 72, backgroundColor: widget.backgroundColor ?? Theme.of(context).colorScheme.background, automaticallyImplyLeading: false, pinned: true, - flexibleSpace: DidvanAppBar(appBarData: widget.appBarData), + flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!), ), if (!widget.reverse) const SliverToBoxAdapter( @@ -79,9 +79,9 @@ class _DidvanScaffoldState extends State { ), ], ), - if (widget.reverse) + if (widget.reverse && widget.appBarData != null) _AppBar( - appBarData: widget.appBarData, + appBarData: widget.appBarData!, scrollController: _scrollController, ), ], diff --git a/pubspec.lock b/pubspec.lock index 1ae7618..825fa09 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -566,7 +566,7 @@ packages: name: plugin_platform_interface url: "https://pub.dartlang.org" source: hosted - version: "2.0.2" + version: "2.1.2" process: dependency: transitive description: @@ -782,6 +782,34 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "2.1.1" + webview_flutter: + dependency: "direct main" + description: + name: webview_flutter + url: "https://pub.dartlang.org" + source: hosted + version: "3.0.1" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + url: "https://pub.dartlang.org" + source: hosted + version: "2.8.3" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + url: "https://pub.dartlang.org" + source: hosted + version: "1.8.1" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + url: "https://pub.dartlang.org" + source: hosted + version: "2.7.1" win32: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index c685dfc..41c210a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -62,6 +62,7 @@ dependencies: image_cropper: ^1.5.0 firebase_messaging: ^11.2.8 firebase_core: ^1.13.1 + webview_flutter: ^3.0.1 dev_dependencies: From ed6f16cd65e3ec7a288b082216c2594075b6e16d Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 17 Mar 2022 15:15:56 +0330 Subject: [PATCH 13/23] new overviews + restructuring --- lib/views/home/widgets/audio_slider.dart | 49 --- lib/views/home/widgets/audio_visualizer.dart | 314 ------------------ .../multitype.dart} | 0 .../news.dart} | 1 + .../podcast.dart} | 24 +- .../radar.dart} | 1 + .../video.dart} | 1 + 7 files changed, 12 insertions(+), 378 deletions(-) delete mode 100644 lib/views/home/widgets/audio_slider.dart delete mode 100644 lib/views/home/widgets/audio_visualizer.dart rename lib/views/home/widgets/{multitype_overview.dart => overview/multitype.dart} (100%) rename lib/views/home/widgets/{news_overview.dart => overview/news.dart} (99%) rename lib/views/home/widgets/{podcast_overview.dart => overview/podcast.dart} (89%) rename lib/views/home/widgets/{radar_overview.dart => overview/radar.dart} (99%) rename lib/views/home/widgets/{video_overview.dart => overview/video.dart} (99%) diff --git a/lib/views/home/widgets/audio_slider.dart b/lib/views/home/widgets/audio_slider.dart deleted file mode 100644 index dd7196f..0000000 --- a/lib/views/home/widgets/audio_slider.dart +++ /dev/null @@ -1,49 +0,0 @@ -import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; -import 'package:didvan/config/design_config.dart'; -import 'package:didvan/services/media/media.dart'; -import 'package:flutter/material.dart'; - -class AudioSlider extends StatelessWidget { - final String tag; - final bool showTimer; - const AudioSlider({Key? key, required this.tag, this.showTimer = false}) - : super(key: key); - - bool get _isPlaying => MediaService.audioPlayerTag == tag; - - @override - Widget build(BuildContext context) { - return IgnorePointer( - ignoring: MediaService.audioPlayerTag != tag, - child: Directionality( - textDirection: TextDirection.ltr, - child: StreamBuilder( - stream: _isPlaying ? MediaService.audioPlayer.positionStream : null, - builder: (context, snapshot) { - return ProgressBar( - total: MediaService.audioPlayer.duration ?? Duration.zero, - progress: snapshot.data ?? Duration.zero, - buffered: - _isPlaying ? MediaService.audioPlayer.bufferedPosition : null, - thumbRadius: 6, - barHeight: 3, - timeLabelTextStyle: TextStyle( - fontSize: showTimer ? null : 0, - height: showTimer ? null : 0, - fontFamily: DesignConfig.fontFamily.replaceAll( - '-FA', - '', - ), - ), - onSeek: (value) => _onSeek(value.inMilliseconds), - ); - }, - ), - ), - ); - } - - void _onSeek(int value) { - MediaService.audioPlayer.seek(Duration(milliseconds: value)); - } -} diff --git a/lib/views/home/widgets/audio_visualizer.dart b/lib/views/home/widgets/audio_visualizer.dart deleted file mode 100644 index c39ca3d..0000000 --- a/lib/views/home/widgets/audio_visualizer.dart +++ /dev/null @@ -1,314 +0,0 @@ -// import 'dart:io'; -// import 'dart:math'; - -// import 'package:didvan/config/design_config.dart'; -// import 'package:didvan/config/theme_data.dart'; -// import 'package:didvan/constants/app_icons.dart'; -// import 'package:didvan/constants/assets.dart'; -// import 'package:didvan/pages/home/direct/direct_state.dart'; -// import 'package:didvan/services/media/media.dart'; -// import 'package:didvan/services/storage/storage.dart'; -// import 'package:didvan/utils/date_time.dart'; -// import 'package:didvan/widgets/didvan/icon_button.dart'; -// import 'package:didvan/widgets/didvan/text.dart'; -// import 'package:flutter/foundation.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter_svg/flutter_svg.dart'; -// import 'package:just_waveform/just_waveform.dart'; -// import 'package:provider/provider.dart'; - -// class AudioVisualizer extends StatefulWidget { -// final File? audioFile; -// final Waveform? waveform; -// final String? audioUrl; -// final int? duration; -// final Color? backgroundColor; - -// const AudioVisualizer({ -// Key? key, -// this.audioFile, -// this.waveform, -// this.audioUrl, -// this.duration, -// this.backgroundColor, -// }) : super(key: key); - -// @override -// State createState() => _AudioVisualizerState(); -// } - -// class _AudioVisualizerState extends State { -// Stream? waveDataStream; - -// @override -// void initState() { -// if (!kIsWeb && widget.audioFile != null) { -// waveDataStream = JustWaveform.extract( -// audioInFile: widget.audioFile!, -// waveOutFile: File(StorageService.appTempsDir + '/rec-wave.wave'), -// zoom: const WaveformZoom.pixelsPerSecond(100), -// ); -// } -// super.initState(); -// } - -// bool get _nowPlaying => -// MediaService.lastAudioPath == widget.audioFile || -// MediaService.lastAudioPath == widget.audioUrl; - -// @override -// Widget build(BuildContext context) { -// return Container( -// decoration: BoxDecoration( -// color: widget.backgroundColor ?? -// (DesignConfig.isDark -// ? Theme.of(context).colorScheme.black -// : Theme.of(context).colorScheme.background), -// borderRadius: DesignConfig.mediumBorderRadius, -// ), -// child: Row( -// children: [ -// const SizedBox(width: 12), -// StreamBuilder( -// stream: -// _nowPlaying ? MediaService.audioPlayer.positionStream : null, -// builder: (context, snapshot) { -// String text = ''; -// if (MediaService.audioPlayer.duration == null) { -// Future.delayed(Duration.zero, () { -// if (mounted) { -// setState(() {}); -// } -// }); -// } -// if (snapshot.data == null || snapshot.data == Duration.zero) { -// text = DateTimeUtils.normalizeTimeDuration( -// MediaService.audioPlayer.duration ?? -// widget.waveform?.duration ?? -// Duration.zero); -// } else { -// text = DateTimeUtils.normalizeTimeDuration(snapshot.data!); -// } -// return DidvanText( -// text, -// color: Theme.of(context).colorScheme.focusedBorder, -// isEnglishFont: true, -// ); -// }, -// ), -// const SizedBox(width: 12), -// Expanded( -// child: Builder( -// builder: (context) { -// if (kIsWeb) { -// return SvgPicture.asset(Assets.record); -// } -// if (widget.audioFile != null) { -// return StreamBuilder( -// stream: waveDataStream, -// builder: (context, snapshot) { -// if (snapshot.data == null || -// snapshot.data!.waveform == null) { -// return const SizedBox(); -// } -// final waveform = snapshot.data!.waveform!; -// context.read().waveform = waveform; -// return _waveWidget(waveform); -// }, -// ); -// } -// if (widget.waveform == null && waveDataStream == null) { -// return SvgPicture.asset(Assets.record); -// } -// return _waveWidget(widget.waveform!); -// }, -// ), -// ), -// StreamBuilder( -// stream: _nowPlaying ? MediaService.audioPlayer.playingStream : null, -// builder: (context, snapshot) { -// return DidvanIconButton( -// icon: snapshot.data == true -// ? DidvanIcons.pause_circle_solid -// : DidvanIcons.play_circle_solid, -// color: Theme.of(context).colorScheme.focusedBorder, -// onPressed: () { -// MediaService.handleAudioPlayback( -// audioSource: widget.audioFile ?? widget.audioUrl, -// isNetworkAudio: widget.audioFile == null, -// ); -// setState(() {}); -// }, -// ); -// }, -// ), -// ], -// ), -// ); -// } - -// Widget _waveWidget(Waveform waveform) => IgnorePointer( -// ignoring: !_nowPlaying, -// child: GestureDetector( -// onHorizontalDragUpdate: _changePosition, -// onTapDown: _changePosition, -// child: SizedBox( -// height: double.infinity, -// width: double.infinity, -// child: _AudioWaveformWidget( -// waveform: waveform, -// start: Duration.zero, -// scale: 2, -// strokeWidth: 3, -// nowPlaying: _nowPlaying, -// duration: waveform.duration, -// waveColor: Theme.of(context).colorScheme.focusedBorder, -// ), -// ), -// ), -// ); - -// void _changePosition(details) { -// if (MediaService.audioPlayer.audioSource == null) return; -// double posper = -// details.localPosition.dx / (MediaQuery.of(context).size.width - 200); -// if (posper >= 1 || posper < 0) return; -// final position = MediaService.audioPlayer.duration!.inMilliseconds; -// MediaService.audioPlayer.seek( -// Duration(milliseconds: (posper * position).toInt()), -// ); -// } -// } - -// class _AudioWaveformWidget extends StatelessWidget { -// final Color waveColor; -// final double scale; -// final double strokeWidth; -// final double pixelsPerStep; -// final Waveform waveform; -// final Duration start; -// final bool nowPlaying; -// final Duration duration; - -// const _AudioWaveformWidget({ -// Key? key, -// required this.waveform, -// required this.start, -// required this.duration, -// required this.nowPlaying, -// this.waveColor = Colors.blue, -// this.scale = 1.0, -// this.strokeWidth = 5.0, -// this.pixelsPerStep = 8.0, -// }) : super(key: key); - -// @override -// Widget build(BuildContext context) { -// return ClipRect( -// child: StreamBuilder( -// stream: nowPlaying ? MediaService.audioPlayer.positionStream : null, -// builder: (context, snapshot) { -// double progress = 0; -// if (snapshot.data == null || -// MediaService.audioPlayer.duration == null) { -// progress = 0; -// } else { -// progress = snapshot.data!.inMilliseconds / -// MediaService.audioPlayer.duration!.inMilliseconds * -// 100; -// } -// if (progress >= 100) { -// progress = 0; -// MediaService.audioPlayer.stop(); -// MediaService.audioPlayer.seek(Duration.zero); -// } -// return CustomPaint( -// painter: _AudioWaveformPainter( -// waveColor: waveColor, -// waveform: waveform, -// start: start, -// duration: duration, -// scale: scale, -// strokeWidth: strokeWidth, -// pixelsPerStep: pixelsPerStep, -// progressPercentage: progress, -// progressColor: Theme.of(context).colorScheme.focusedBorder, -// color: Theme.of(context).colorScheme.border, -// ), -// ); -// }), -// ); -// } -// } - -// class _AudioWaveformPainter extends CustomPainter { -// final double scale; -// final double strokeWidth; -// final double pixelsPerStep; -// final Waveform waveform; -// final Duration start; -// final Duration duration; -// final double progressPercentage; -// final Color progressColor; -// final Color color; - -// _AudioWaveformPainter({ -// required this.waveform, -// required this.start, -// required this.duration, -// required this.progressPercentage, -// required this.color, -// required this.progressColor, -// Color waveColor = Colors.blue, -// this.scale = 1.0, -// this.strokeWidth = 5.0, -// this.pixelsPerStep = 8.0, -// }); - -// @override -// void paint(Canvas canvas, Size size) { -// if (duration == Duration.zero) return; -// double width = size.width; -// double height = size.height; - -// final waveformPixelsPerWindow = waveform.positionToPixel(duration).toInt(); -// final waveformPixelsPerDevicePixel = waveformPixelsPerWindow / width; -// final waveformPixelsPerStep = waveformPixelsPerDevicePixel * pixelsPerStep; -// final sampleOffset = waveform.positionToPixel(start); -// final sampleStart = -sampleOffset % waveformPixelsPerStep; -// final totalLength = waveformPixelsPerWindow; -// final wavePaintB = Paint() -// ..style = PaintingStyle.stroke -// ..strokeWidth = strokeWidth -// ..strokeCap = StrokeCap.round -// ..color = progressColor; -// final wavePaintA = Paint() -// ..style = PaintingStyle.stroke -// ..strokeWidth = strokeWidth -// ..strokeCap = StrokeCap.round -// ..color = color; -// for (var i = sampleStart.toDouble(); -// i <= waveformPixelsPerWindow + 1.0; -// i += waveformPixelsPerStep) { -// final sampleIdx = (sampleOffset + i).toInt(); -// final x = i / waveformPixelsPerDevicePixel; -// final minY = normalise(waveform.getPixelMin(sampleIdx), height); -// final maxY = normalise(waveform.getPixelMax(sampleIdx), height); -// canvas.drawLine( -// Offset(x + strokeWidth / 2, max(strokeWidth * 0.75, minY)), -// Offset(x + strokeWidth / 2, min(height - strokeWidth * 0.75, maxY)), -// i / totalLength < progressPercentage / 100 ? wavePaintB : wavePaintA, -// ); -// } -// } - -// @override -// bool shouldRepaint(covariant _AudioWaveformPainter oldDelegate) { -// return oldDelegate.progressPercentage != progressPercentage; -// } - -// double normalise(int s, double height) { -// final y = 32768 + (scale * s).clamp(-32768.0, 32767.0).toDouble(); -// return height - 1 - y * height / 65536; -// } -// } diff --git a/lib/views/home/widgets/multitype_overview.dart b/lib/views/home/widgets/overview/multitype.dart similarity index 100% rename from lib/views/home/widgets/multitype_overview.dart rename to lib/views/home/widgets/overview/multitype.dart diff --git a/lib/views/home/widgets/news_overview.dart b/lib/views/home/widgets/overview/news.dart similarity index 99% rename from lib/views/home/widgets/news_overview.dart rename to lib/views/home/widgets/overview/news.dart index e998acf..ac163c2 100644 --- a/lib/views/home/widgets/news_overview.dart +++ b/lib/views/home/widgets/overview/news.dart @@ -79,6 +79,7 @@ class NewsOverview extends StatelessWidget { ], ), BookmarkButton( + gestureSize: 24, value: news.marked, onMarkChanged: (value) => onMarkChanged(news.id, value), askForConfirmation: hasUnmarkConfirmation, diff --git a/lib/views/home/widgets/podcast_overview.dart b/lib/views/home/widgets/overview/podcast.dart similarity index 89% rename from lib/views/home/widgets/podcast_overview.dart rename to lib/views/home/widgets/overview/podcast.dart index 2e2265d..1669c0a 100644 --- a/lib/views/home/widgets/podcast_overview.dart +++ b/lib/views/home/widgets/overview/podcast.dart @@ -3,7 +3,9 @@ import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/routes/routes.dart'; +import 'package:didvan/services/media/media.dart'; import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/widgets/bookmark_button.dart'; import 'package:didvan/views/home/widgets/duration_widget.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; @@ -13,36 +15,27 @@ import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class PodcastOverview extends StatelessWidget { final OverviewData podcast; - final void Function(int id, int count) onCommentsChanged; final void Function(int id, bool value) onMarkChanged; - final bool hasUnmarkConfirmation; final StudioRequestArgs? studioRequestArgs; const PodcastOverview({ Key? key, required this.podcast, - required this.onCommentsChanged, required this.onMarkChanged, - required this.hasUnmarkConfirmation, this.studioRequestArgs, }) : super(key: key); @override Widget build(BuildContext context) { return DidvanCard( - onTap: () => Navigator.of(context).pushNamed( - Routes.studioDetails, - arguments: { - 'onMarkChanged': onMarkChanged, - 'onCommentsChanged': onCommentsChanged, - 'id': podcast.id, - 'args': studioRequestArgs, - 'hasUnmarkConfirmation': hasUnmarkConfirmation, - 'isVideo': false, - }, - ), + onTap: () { + context + .read() + .getStudioDetails(podcast.id, args: studioRequestArgs); + }, child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -91,6 +84,7 @@ class PodcastOverview extends StatelessWidget { ), const SizedBox(width: 16), BookmarkButton( + gestureSize: 24, value: podcast.marked, onMarkChanged: (value) => onMarkChanged(podcast.id, value), ), diff --git a/lib/views/home/widgets/radar_overview.dart b/lib/views/home/widgets/overview/radar.dart similarity index 99% rename from lib/views/home/widgets/radar_overview.dart rename to lib/views/home/widgets/overview/radar.dart index f9988fd..cd88ff3 100644 --- a/lib/views/home/widgets/radar_overview.dart +++ b/lib/views/home/widgets/overview/radar.dart @@ -103,6 +103,7 @@ class RadarOverview extends StatelessWidget { Row( children: [ BookmarkButton( + gestureSize: 24, value: radar.marked, onMarkChanged: (value) => onMarkChanged(radar.id, value), askForConfirmation: hasUnmarkConfirmation, diff --git a/lib/views/home/widgets/video_overview.dart b/lib/views/home/widgets/overview/video.dart similarity index 99% rename from lib/views/home/widgets/video_overview.dart rename to lib/views/home/widgets/overview/video.dart index f7e4975..1dbcdfa 100644 --- a/lib/views/home/widgets/video_overview.dart +++ b/lib/views/home/widgets/overview/video.dart @@ -109,6 +109,7 @@ class VideoOverview extends StatelessWidget { ), const SizedBox(width: 16), BookmarkButton( + gestureSize: 24, value: video.marked, onMarkChanged: (value) => onMarkChanged(video.id, value), ), From a17f6f5ba56321736715d50f2c43d429c5f34b83 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 17 Mar 2022 15:16:29 +0330 Subject: [PATCH 14/23] bug fixes --- lib/views/home/news/news.dart | 2 +- lib/views/home/radar/radar.dart | 2 +- lib/views/home/settings/bookmarks/bookmarks.dart | 2 +- .../bookmarks/filtered_bookmark/filtered_bookmark.dart | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/views/home/news/news.dart b/lib/views/home/news/news.dart index 30df281..421a045 100644 --- a/lib/views/home/news/news.dart +++ b/lib/views/home/news/news.dart @@ -8,7 +8,7 @@ import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/views/home/news/news_state.dart'; import 'package:didvan/views/home/widgets/date_picker_button.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart'; -import 'package:didvan/views/home/widgets/news_overview.dart'; +import 'package:didvan/views/home/widgets/overview/news.dart'; import 'package:didvan/views/home/widgets/search_field.dart'; import 'package:didvan/views/widgets/item_title.dart'; import 'package:didvan/views/widgets/state_handlers/empty_result.dart'; diff --git a/lib/views/home/radar/radar.dart b/lib/views/home/radar/radar.dart index 87c3e83..6058543 100644 --- a/lib/views/home/radar/radar.dart +++ b/lib/views/home/radar/radar.dart @@ -15,7 +15,7 @@ import 'package:didvan/views/home/radar/widgets/categories_list.dart'; import 'package:didvan/views/home/widgets/date_picker_button.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart'; import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/views/home/widgets/radar_overview.dart'; +import 'package:didvan/views/home/widgets/overview/radar.dart'; import 'package:didvan/views/home/widgets/search_field.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/checkbox.dart'; diff --git a/lib/views/home/settings/bookmarks/bookmarks.dart b/lib/views/home/settings/bookmarks/bookmarks.dart index 31746b8..520d880 100644 --- a/lib/views/home/settings/bookmarks/bookmarks.dart +++ b/lib/views/home/settings/bookmarks/bookmarks.dart @@ -6,7 +6,7 @@ import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/views/home/settings/bookmarks/bookmark_state.dart'; import 'package:didvan/views/home/widgets/menu_item.dart'; -import 'package:didvan/views/home/widgets/multitype_overview.dart'; +import 'package:didvan/views/home/widgets/overview/multitype.dart'; import 'package:didvan/views/home/widgets/search_field.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; diff --git a/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart index 106d718..2291e1a 100644 --- a/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart +++ b/lib/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmark.dart @@ -1,7 +1,7 @@ import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/views/home/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart'; -import 'package:didvan/views/home/widgets/news_overview.dart'; -import 'package:didvan/views/home/widgets/radar_overview.dart'; +import 'package:didvan/views/home/widgets/overview/news.dart'; +import 'package:didvan/views/home/widgets/overview/radar.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/state_handlers/empty_list.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; From 4ccd50b683444e65b8667b6243252c48f262ffb7 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 17 Mar 2022 15:17:11 +0330 Subject: [PATCH 15/23] component updates + restructuring --- .../widgets/audio/audio_player_widget.dart | 179 ++++++++++ .../home/widgets/audio/audio_slider.dart | 63 ++++ .../home/widgets/audio/audio_visualizer.dart | 314 ++++++++++++++++++ lib/views/home/widgets/bookmark_button.dart | 6 +- .../home/widgets/floating_navigation_bar.dart | 4 +- lib/views/widgets/didvan/app_bar.dart | 1 + lib/views/widgets/didvan/page_view.dart | 2 +- lib/views/widgets/didvan/scaffold.dart | 7 +- 8 files changed, 565 insertions(+), 11 deletions(-) create mode 100644 lib/views/home/widgets/audio/audio_player_widget.dart create mode 100644 lib/views/home/widgets/audio/audio_slider.dart create mode 100644 lib/views/home/widgets/audio/audio_visualizer.dart diff --git a/lib/views/home/widgets/audio/audio_player_widget.dart b/lib/views/home/widgets/audio/audio_player_widget.dart new file mode 100644 index 0000000..f8b0a69 --- /dev/null +++ b/lib/views/home/widgets/audio/audio_player_widget.dart @@ -0,0 +1,179 @@ +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/studio_details_data.dart'; +import 'package:didvan/services/media/media.dart'; +import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; +import 'package:didvan/views/home/widgets/bookmark_button.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; +import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:flutter/material.dart'; + +class AudioPlayerWidget extends StatelessWidget { + final StudioDetailsData podcast; + const AudioPlayerWidget({Key? key, required this.podcast}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Container( + decoration: BoxDecoration( + borderRadius: const BorderRadius.vertical(top: Radius.circular(8)), + color: Theme.of(context).colorScheme.surface, + ), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + margin: const EdgeInsets.symmetric(vertical: 20), + height: 3, + width: 50, + color: Theme.of(context).colorScheme.hint, + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 24), + child: SkeletonImage( + imageUrl: podcast.image, + aspectRatio: 1 / 1, + ), + ), + const SizedBox(height: 16), + DidvanText( + podcast.title, + style: Theme.of(context).textTheme.bodyText1, + ), + const SizedBox(height: 16), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: AudioSlider( + tag: podcast.media, + showTimer: true, + duration: podcast.duration, + ), + ), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + DidvanIconButton( + icon: DidvanIcons.sleep_timer_regular, + onPressed: () {}, + ), + Column( + children: [ + DidvanIconButton( + size: 32, + icon: DidvanIcons.media_forward_solid, + onPressed: () { + MediaService.audioPlayer.seek( + Duration( + seconds: + MediaService.audioPlayer.position.inSeconds + 30, + ), + ); + }, + ), + const DidvanText('30', isEnglishFont: true), + ], + ), + _PlayPouseAnimatedIcon( + audioSource: podcast.media, + ), + Column( + children: [ + DidvanIconButton( + size: 32, + icon: DidvanIcons.media_backward_solid, + onPressed: () { + MediaService.audioPlayer.seek( + Duration( + seconds: + MediaService.audioPlayer.position.inSeconds - 10, + ), + ); + }, + ), + const DidvanText('10', isEnglishFont: true), + ], + ), + BookmarkButton( + gestureSize: 48, + value: podcast.marked, + onMarkChanged: (value) {}, + ), + ], + ), + DidvanIconButton( + size: 32, + icon: DidvanIcons.angle_down_regular, + onPressed: Navigator.of(context).pop, + ), + ], + ), + ); + } +} + +class _PlayPouseAnimatedIcon extends StatefulWidget { + final String audioSource; + const _PlayPouseAnimatedIcon({Key? key, required this.audioSource}) + : super(key: key); + + @override + State<_PlayPouseAnimatedIcon> createState() => __PlayPouseAnimatedIconState(); +} + +class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon> + with SingleTickerProviderStateMixin { + late final AnimationController _animationController; + + @override + void initState() { + super.initState(); + _animationController = AnimationController( + vsync: this, + duration: DesignConfig.lowAnimationDuration, + ); + if (MediaService.audioPlayer.playing) { + _animationController.forward(); + } + } + + @override + Widget build(BuildContext context) { + return InkWrapper( + borderRadius: BorderRadius.circular(100), + onPressed: () { + MediaService.handleAudioPlayback( + audioSource: widget.audioSource, + isVoiceMessage: false, + ); + if (MediaService.audioPlayer.playing) { + _animationController.forward(); + } else { + _animationController.reverse(); + } + }, + child: Container( + padding: const EdgeInsets.all(8), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.title, + shape: BoxShape.circle, + ), + child: AnimatedIcon( + size: 40, + color: Theme.of(context).colorScheme.surface, + icon: AnimatedIcons.play_pause, + progress: _animationController, + ), + ), + ); + } + + @override + void dispose() { + _animationController.dispose(); + super.dispose(); + } +} diff --git a/lib/views/home/widgets/audio/audio_slider.dart b/lib/views/home/widgets/audio/audio_slider.dart new file mode 100644 index 0000000..9c80349 --- /dev/null +++ b/lib/views/home/widgets/audio/audio_slider.dart @@ -0,0 +1,63 @@ +import 'package:audio_video_progress_bar/audio_video_progress_bar.dart'; +import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/services/media/media.dart'; +import 'package:flutter/material.dart'; + +class AudioSlider extends StatelessWidget { + final String tag; + final bool showTimer; + final int? duration; + final bool disableThumb; + const AudioSlider({ + Key? key, + required this.tag, + this.showTimer = false, + this.duration, + this.disableThumb = false, + }) : super(key: key); + + bool get _isPlaying => MediaService.audioPlayerTag == tag; + + @override + Widget build(BuildContext context) { + return IgnorePointer( + ignoring: MediaService.audioPlayerTag != tag, + child: Directionality( + textDirection: TextDirection.ltr, + child: StreamBuilder( + stream: _isPlaying ? MediaService.audioPlayer.positionStream : null, + builder: (context, snapshot) => ProgressBar( + thumbColor: Theme.of(context).colorScheme.title, + progressBarColor: DesignConfig.isDark + ? Theme.of(context).colorScheme.title + : Theme.of(context).colorScheme.primary, + baseBarColor: Theme.of(context).colorScheme.border, + bufferedBarColor: Theme.of(context).colorScheme.splash, + total: MediaService.audioPlayer.duration ?? + Duration(seconds: duration ?? 0), + progress: snapshot.data ?? Duration.zero, + buffered: _isPlaying + ? MediaService.audioPlayer.bufferedPosition + : Duration.zero, + thumbRadius: disableThumb ? 0 : 6, + barHeight: 3, + timeLabelTextStyle: TextStyle( + fontSize: showTimer ? null : 0, + height: showTimer ? 3 : 0, + fontFamily: DesignConfig.fontFamily.replaceAll( + '-FA', + '', + ), + ), + onSeek: (value) => _onSeek(value.inMilliseconds), + ), + ), + ), + ); + } + + void _onSeek(int value) { + MediaService.audioPlayer.seek(Duration(milliseconds: value)); + } +} diff --git a/lib/views/home/widgets/audio/audio_visualizer.dart b/lib/views/home/widgets/audio/audio_visualizer.dart new file mode 100644 index 0000000..c39ca3d --- /dev/null +++ b/lib/views/home/widgets/audio/audio_visualizer.dart @@ -0,0 +1,314 @@ +// import 'dart:io'; +// import 'dart:math'; + +// import 'package:didvan/config/design_config.dart'; +// import 'package:didvan/config/theme_data.dart'; +// import 'package:didvan/constants/app_icons.dart'; +// import 'package:didvan/constants/assets.dart'; +// import 'package:didvan/pages/home/direct/direct_state.dart'; +// import 'package:didvan/services/media/media.dart'; +// import 'package:didvan/services/storage/storage.dart'; +// import 'package:didvan/utils/date_time.dart'; +// import 'package:didvan/widgets/didvan/icon_button.dart'; +// import 'package:didvan/widgets/didvan/text.dart'; +// import 'package:flutter/foundation.dart'; +// import 'package:flutter/material.dart'; +// import 'package:flutter_svg/flutter_svg.dart'; +// import 'package:just_waveform/just_waveform.dart'; +// import 'package:provider/provider.dart'; + +// class AudioVisualizer extends StatefulWidget { +// final File? audioFile; +// final Waveform? waveform; +// final String? audioUrl; +// final int? duration; +// final Color? backgroundColor; + +// const AudioVisualizer({ +// Key? key, +// this.audioFile, +// this.waveform, +// this.audioUrl, +// this.duration, +// this.backgroundColor, +// }) : super(key: key); + +// @override +// State createState() => _AudioVisualizerState(); +// } + +// class _AudioVisualizerState extends State { +// Stream? waveDataStream; + +// @override +// void initState() { +// if (!kIsWeb && widget.audioFile != null) { +// waveDataStream = JustWaveform.extract( +// audioInFile: widget.audioFile!, +// waveOutFile: File(StorageService.appTempsDir + '/rec-wave.wave'), +// zoom: const WaveformZoom.pixelsPerSecond(100), +// ); +// } +// super.initState(); +// } + +// bool get _nowPlaying => +// MediaService.lastAudioPath == widget.audioFile || +// MediaService.lastAudioPath == widget.audioUrl; + +// @override +// Widget build(BuildContext context) { +// return Container( +// decoration: BoxDecoration( +// color: widget.backgroundColor ?? +// (DesignConfig.isDark +// ? Theme.of(context).colorScheme.black +// : Theme.of(context).colorScheme.background), +// borderRadius: DesignConfig.mediumBorderRadius, +// ), +// child: Row( +// children: [ +// const SizedBox(width: 12), +// StreamBuilder( +// stream: +// _nowPlaying ? MediaService.audioPlayer.positionStream : null, +// builder: (context, snapshot) { +// String text = ''; +// if (MediaService.audioPlayer.duration == null) { +// Future.delayed(Duration.zero, () { +// if (mounted) { +// setState(() {}); +// } +// }); +// } +// if (snapshot.data == null || snapshot.data == Duration.zero) { +// text = DateTimeUtils.normalizeTimeDuration( +// MediaService.audioPlayer.duration ?? +// widget.waveform?.duration ?? +// Duration.zero); +// } else { +// text = DateTimeUtils.normalizeTimeDuration(snapshot.data!); +// } +// return DidvanText( +// text, +// color: Theme.of(context).colorScheme.focusedBorder, +// isEnglishFont: true, +// ); +// }, +// ), +// const SizedBox(width: 12), +// Expanded( +// child: Builder( +// builder: (context) { +// if (kIsWeb) { +// return SvgPicture.asset(Assets.record); +// } +// if (widget.audioFile != null) { +// return StreamBuilder( +// stream: waveDataStream, +// builder: (context, snapshot) { +// if (snapshot.data == null || +// snapshot.data!.waveform == null) { +// return const SizedBox(); +// } +// final waveform = snapshot.data!.waveform!; +// context.read().waveform = waveform; +// return _waveWidget(waveform); +// }, +// ); +// } +// if (widget.waveform == null && waveDataStream == null) { +// return SvgPicture.asset(Assets.record); +// } +// return _waveWidget(widget.waveform!); +// }, +// ), +// ), +// StreamBuilder( +// stream: _nowPlaying ? MediaService.audioPlayer.playingStream : null, +// builder: (context, snapshot) { +// return DidvanIconButton( +// icon: snapshot.data == true +// ? DidvanIcons.pause_circle_solid +// : DidvanIcons.play_circle_solid, +// color: Theme.of(context).colorScheme.focusedBorder, +// onPressed: () { +// MediaService.handleAudioPlayback( +// audioSource: widget.audioFile ?? widget.audioUrl, +// isNetworkAudio: widget.audioFile == null, +// ); +// setState(() {}); +// }, +// ); +// }, +// ), +// ], +// ), +// ); +// } + +// Widget _waveWidget(Waveform waveform) => IgnorePointer( +// ignoring: !_nowPlaying, +// child: GestureDetector( +// onHorizontalDragUpdate: _changePosition, +// onTapDown: _changePosition, +// child: SizedBox( +// height: double.infinity, +// width: double.infinity, +// child: _AudioWaveformWidget( +// waveform: waveform, +// start: Duration.zero, +// scale: 2, +// strokeWidth: 3, +// nowPlaying: _nowPlaying, +// duration: waveform.duration, +// waveColor: Theme.of(context).colorScheme.focusedBorder, +// ), +// ), +// ), +// ); + +// void _changePosition(details) { +// if (MediaService.audioPlayer.audioSource == null) return; +// double posper = +// details.localPosition.dx / (MediaQuery.of(context).size.width - 200); +// if (posper >= 1 || posper < 0) return; +// final position = MediaService.audioPlayer.duration!.inMilliseconds; +// MediaService.audioPlayer.seek( +// Duration(milliseconds: (posper * position).toInt()), +// ); +// } +// } + +// class _AudioWaveformWidget extends StatelessWidget { +// final Color waveColor; +// final double scale; +// final double strokeWidth; +// final double pixelsPerStep; +// final Waveform waveform; +// final Duration start; +// final bool nowPlaying; +// final Duration duration; + +// const _AudioWaveformWidget({ +// Key? key, +// required this.waveform, +// required this.start, +// required this.duration, +// required this.nowPlaying, +// this.waveColor = Colors.blue, +// this.scale = 1.0, +// this.strokeWidth = 5.0, +// this.pixelsPerStep = 8.0, +// }) : super(key: key); + +// @override +// Widget build(BuildContext context) { +// return ClipRect( +// child: StreamBuilder( +// stream: nowPlaying ? MediaService.audioPlayer.positionStream : null, +// builder: (context, snapshot) { +// double progress = 0; +// if (snapshot.data == null || +// MediaService.audioPlayer.duration == null) { +// progress = 0; +// } else { +// progress = snapshot.data!.inMilliseconds / +// MediaService.audioPlayer.duration!.inMilliseconds * +// 100; +// } +// if (progress >= 100) { +// progress = 0; +// MediaService.audioPlayer.stop(); +// MediaService.audioPlayer.seek(Duration.zero); +// } +// return CustomPaint( +// painter: _AudioWaveformPainter( +// waveColor: waveColor, +// waveform: waveform, +// start: start, +// duration: duration, +// scale: scale, +// strokeWidth: strokeWidth, +// pixelsPerStep: pixelsPerStep, +// progressPercentage: progress, +// progressColor: Theme.of(context).colorScheme.focusedBorder, +// color: Theme.of(context).colorScheme.border, +// ), +// ); +// }), +// ); +// } +// } + +// class _AudioWaveformPainter extends CustomPainter { +// final double scale; +// final double strokeWidth; +// final double pixelsPerStep; +// final Waveform waveform; +// final Duration start; +// final Duration duration; +// final double progressPercentage; +// final Color progressColor; +// final Color color; + +// _AudioWaveformPainter({ +// required this.waveform, +// required this.start, +// required this.duration, +// required this.progressPercentage, +// required this.color, +// required this.progressColor, +// Color waveColor = Colors.blue, +// this.scale = 1.0, +// this.strokeWidth = 5.0, +// this.pixelsPerStep = 8.0, +// }); + +// @override +// void paint(Canvas canvas, Size size) { +// if (duration == Duration.zero) return; +// double width = size.width; +// double height = size.height; + +// final waveformPixelsPerWindow = waveform.positionToPixel(duration).toInt(); +// final waveformPixelsPerDevicePixel = waveformPixelsPerWindow / width; +// final waveformPixelsPerStep = waveformPixelsPerDevicePixel * pixelsPerStep; +// final sampleOffset = waveform.positionToPixel(start); +// final sampleStart = -sampleOffset % waveformPixelsPerStep; +// final totalLength = waveformPixelsPerWindow; +// final wavePaintB = Paint() +// ..style = PaintingStyle.stroke +// ..strokeWidth = strokeWidth +// ..strokeCap = StrokeCap.round +// ..color = progressColor; +// final wavePaintA = Paint() +// ..style = PaintingStyle.stroke +// ..strokeWidth = strokeWidth +// ..strokeCap = StrokeCap.round +// ..color = color; +// for (var i = sampleStart.toDouble(); +// i <= waveformPixelsPerWindow + 1.0; +// i += waveformPixelsPerStep) { +// final sampleIdx = (sampleOffset + i).toInt(); +// final x = i / waveformPixelsPerDevicePixel; +// final minY = normalise(waveform.getPixelMin(sampleIdx), height); +// final maxY = normalise(waveform.getPixelMax(sampleIdx), height); +// canvas.drawLine( +// Offset(x + strokeWidth / 2, max(strokeWidth * 0.75, minY)), +// Offset(x + strokeWidth / 2, min(height - strokeWidth * 0.75, maxY)), +// i / totalLength < progressPercentage / 100 ? wavePaintB : wavePaintA, +// ); +// } +// } + +// @override +// bool shouldRepaint(covariant _AudioWaveformPainter oldDelegate) { +// return oldDelegate.progressPercentage != progressPercentage; +// } + +// double normalise(int s, double height) { +// final y = 32768 + (scale * s).clamp(-32768.0, 32767.0).toDouble(); +// return height - 1 - y * height / 65536; +// } +// } diff --git a/lib/views/home/widgets/bookmark_button.dart b/lib/views/home/widgets/bookmark_button.dart index 06d7604..ca98b06 100644 --- a/lib/views/home/widgets/bookmark_button.dart +++ b/lib/views/home/widgets/bookmark_button.dart @@ -8,14 +8,14 @@ import 'package:flutter/material.dart'; class BookmarkButton extends StatefulWidget { final bool value; final void Function(bool value) onMarkChanged; - final bool bigGestureSize; final bool askForConfirmation; + final double gestureSize; const BookmarkButton({ Key? key, required this.value, - this.bigGestureSize = false, required this.onMarkChanged, this.askForConfirmation = false, + required this.gestureSize, }) : super(key: key); @override @@ -40,7 +40,7 @@ class _BookmarkButtonState extends State { @override Widget build(BuildContext context) { return DidvanIconButton( - gestureSize: widget.bigGestureSize ? 32 : 28, + gestureSize: widget.gestureSize, icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular, onPressed: () async { bool confirm = false; diff --git a/lib/views/home/widgets/floating_navigation_bar.dart b/lib/views/home/widgets/floating_navigation_bar.dart index 95727d1..28a88e4 100644 --- a/lib/views/home/widgets/floating_navigation_bar.dart +++ b/lib/views/home/widgets/floating_navigation_bar.dart @@ -112,7 +112,7 @@ class _FloatingNavigationBarState extends State { Navigator.of(context).pop(); } }, - bigGestureSize: true, + gestureSize: 32, ), SizedBox( width: 60, @@ -151,7 +151,7 @@ class _FloatingNavigationBarState extends State { Navigator.of(context).pop(); } }, - bigGestureSize: true, + gestureSize: 32, ), if (widget.isRadar) DidvanIconButton( diff --git a/lib/views/widgets/didvan/app_bar.dart b/lib/views/widgets/didvan/app_bar.dart index 7667dd8..f7f00e3 100644 --- a/lib/views/widgets/didvan/app_bar.dart +++ b/lib/views/widgets/didvan/app_bar.dart @@ -52,6 +52,7 @@ class DidvanAppBar extends StatelessWidget { appBarData.title!, style: Theme.of(context).textTheme.headline3, color: Theme.of(context).colorScheme.title, + overflow: TextOverflow.ellipsis, ), if (appBarData.subtitle != null) DidvanText( diff --git a/lib/views/widgets/didvan/page_view.dart b/lib/views/widgets/didvan/page_view.dart index 7fc6b19..580a3e1 100644 --- a/lib/views/widgets/didvan/page_view.dart +++ b/lib/views/widgets/didvan/page_view.dart @@ -3,7 +3,7 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/utils/date_time.dart'; -import 'package:didvan/views/home/widgets/multitype_overview.dart'; +import 'package:didvan/views/home/widgets/overview/multitype.dart'; import 'package:didvan/views/home/widgets/tag_item.dart'; import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; diff --git a/lib/views/widgets/didvan/scaffold.dart b/lib/views/widgets/didvan/scaffold.dart index b1d8b28..0dabcf2 100644 --- a/lib/views/widgets/didvan/scaffold.dart +++ b/lib/views/widgets/didvan/scaffold.dart @@ -42,17 +42,14 @@ class _DidvanScaffoldState extends State { slivers: [ if (!widget.reverse && widget.appBarData != null) SliverAppBar( - toolbarHeight: widget.appBarData!.isSmall ? 56 : 72, + toolbarHeight: (widget.appBarData!.isSmall ? 56 : 72) - + statusBarHeight, backgroundColor: widget.backgroundColor ?? Theme.of(context).colorScheme.background, automaticallyImplyLeading: false, pinned: true, flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!), ), - if (!widget.reverse) - const SliverToBoxAdapter( - child: SizedBox(height: 16), - ), if (widget.children != null) SliverPadding( padding: widget.padding, From 8095795221e241bd253e5df607748cd5e239072c Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 17 Mar 2022 15:17:51 +0330 Subject: [PATCH 16/23] overview model support for studio --- lib/models/overview_data.dart | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/models/overview_data.dart b/lib/models/overview_data.dart index a658839..b72995b 100644 --- a/lib/models/overview_data.dart +++ b/lib/models/overview_data.dart @@ -8,6 +8,7 @@ class OverviewData { final int? timeToRead; final int? duration; final String? reference; + final String? media; final bool forManagers; final String createdAt; final String type; @@ -25,6 +26,7 @@ class OverviewData { required this.marked, required this.comments, required this.forManagers, + this.media, this.duration, this.timeToRead, this.reference, @@ -44,6 +46,7 @@ class OverviewData { duration: json['duration'], type: json['type'] ?? '', marked: json['marked'] ?? false, + media: json['media'], categories: json['categories'] != null ? List.from( json['categories'].map( From 108bddd6d02eae8b45a90d5cb35e956055a0dc02 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 17 Mar 2022 15:18:18 +0330 Subject: [PATCH 17/23] utils update --- lib/models/view/action_sheet_data.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/models/view/action_sheet_data.dart b/lib/models/view/action_sheet_data.dart index 6166bb6..d3e6b3c 100644 --- a/lib/models/view/action_sheet_data.dart +++ b/lib/models/view/action_sheet_data.dart @@ -6,7 +6,8 @@ class ActionSheetData { final String? dismissTitle; final VoidCallback? onConfirmed; final VoidCallback? onDismissed; - final String title; + final String? title; + final bool hasPadding; final IconData? titleIcon; final Color? titleColor; final bool hasDismissButton; @@ -16,10 +17,11 @@ class ActionSheetData { const ActionSheetData({ required this.content, - required this.title, + this.title, this.confrimTitle, this.onConfirmed, this.titleColor, + this.hasPadding = true, this.hasDismissButton = true, this.hasConfirmButton = true, this.titleIcon, From 5278238e19d72e72dccd1de847489c928821b738 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 17 Mar 2022 15:33:35 +0330 Subject: [PATCH 18/23] update statusbar color --- lib/config/design_config.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/config/design_config.dart b/lib/config/design_config.dart index 13cf836..bfb0fb8 100644 --- a/lib/config/design_config.dart +++ b/lib/config/design_config.dart @@ -43,8 +43,7 @@ class DesignConfig { static SystemUiOverlayStyle get systemUiOverlayStyle => SystemUiOverlayStyle( statusBarIconBrightness: brightness == Brightness.dark ? Brightness.light : Brightness.dark, - statusBarColor: - Theme.of(context!).colorScheme.background.withOpacity(0.5), + statusBarColor: Theme.of(context!).colorScheme.background, systemNavigationBarColor: Theme.of(context!).colorScheme.surface, systemNavigationBarDividerColor: Colors.transparent, systemNavigationBarIconBrightness: From 628ebbe271c9fbf811bcdaf70c97de07e5eddf8e Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Thu, 17 Mar 2022 15:35:15 +0330 Subject: [PATCH 19/23] D1APP-99 studio progress --- lib/routes/route_generator.dart | 7 +- lib/services/media/media.dart | 17 ++- lib/utils/action_sheet.dart | 79 +++++----- .../home/direct/widgets/audio_widget.dart | 2 +- lib/views/home/hashtag/hashtag.dart | 4 +- lib/views/home/studio/studio.dart | 8 +- .../studio/studio_details/studio_details.dart | 101 +++++-------- .../studio_details/studio_details_state.dart | 43 +++--- lib/views/home/widgets/bnb.dart | 142 +++++++++++++++--- lib/views/home/widgets/overview/video.dart | 3 + pubspec.lock | 7 + pubspec.yaml | 1 + 12 files changed, 248 insertions(+), 166 deletions(-) diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart index 2989dee..7514e7c 100644 --- a/lib/routes/route_generator.dart +++ b/lib/routes/route_generator.dart @@ -64,6 +64,9 @@ class RouteGenerator { ChangeNotifierProvider( create: (context) => StudioState(), ), + ChangeNotifierProvider( + create: (context) => StudioDetailsState(), + ), ], child: const Home(), ), @@ -103,8 +106,8 @@ class RouteGenerator { ); case Routes.studioDetails: return _createRoute( - ChangeNotifierProvider( - create: (context) => StudioDetailsState(), + ChangeNotifierProvider.value( + value: (settings.arguments as Map)['state'], child: StudioDetails( pageData: settings.arguments as Map, ), diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart index fb0fe74..353b309 100644 --- a/lib/services/media/media.dart +++ b/lib/services/media/media.dart @@ -1,3 +1,5 @@ +import 'package:didvan/models/requests/studio.dart'; +import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; import 'package:flutter/foundation.dart'; @@ -7,8 +9,8 @@ import 'package:just_audio/just_audio.dart'; class MediaService { static final AudioPlayer audioPlayer = AudioPlayer(); static String? audioPlayerTag; - static String? audioPlayerTitle; - static String? audioPlayerCover; + static StudioDetailsData? currentPodcast; + static StudioRequestArgs? podcastPlaylistArgs; static void init() { audioPlayer.positionStream.listen((event) { @@ -21,6 +23,7 @@ class MediaService { static Future handleAudioPlayback({ required dynamic audioSource, + bool isVoiceMessage = true, }) async { bool isNetworkAudio = audioSource.runtimeType == String; String tag; @@ -40,9 +43,11 @@ class MediaService { audioPlayerTag = tag; if (isNetworkAudio) { await audioPlayer.setUrl( - RequestHelper.baseUrl + - audioSource + - '?accessToken=${RequestService.token}', + isVoiceMessage + ? (RequestHelper.baseUrl + + audioSource + + '?accessToken=${RequestService.token}') + : audioSource, ); } else { if (kIsWeb) { @@ -58,6 +63,8 @@ class MediaService { static Future resetAudioPlayer() async { audioPlayerTag = null; + currentPodcast = null; + podcastPlaylistArgs = null; MediaService.audioPlayer.stop(); } diff --git a/lib/utils/action_sheet.dart b/lib/utils/action_sheet.dart index 0bdb571..15ea262 100644 --- a/lib/utils/action_sheet.dart +++ b/lib/utils/action_sheet.dart @@ -79,7 +79,7 @@ class ActionSheetUtils { isScrollControlled: true, context: context, builder: (context) => Container( - padding: const EdgeInsets.all(20), + padding: data.hasPadding ? const EdgeInsets.all(20) : EdgeInsets.zero, decoration: BoxDecoration( color: Theme.of(context).colorScheme.surface, borderRadius: const BorderRadius.vertical( @@ -91,6 +91,7 @@ class ActionSheetUtils { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ + const SizedBox(height: 20), Center( child: Container( height: 3, @@ -99,23 +100,24 @@ class ActionSheetUtils { ), ), const SizedBox(height: 8), - Row( - children: [ - if (data.titleIcon != null) - Icon( - data.titleIcon, + if (data.title != null) + Row( + children: [ + if (data.titleIcon != null) + Icon( + data.titleIcon, + color: data.titleColor ?? + Theme.of(context).colorScheme.title, + ), + if (data.titleIcon != null) const SizedBox(width: 8), + DidvanText( + data.title!, + style: Theme.of(context).textTheme.subtitle1, color: data.titleColor ?? Theme.of(context).colorScheme.title, - ), - if (data.titleIcon != null) const SizedBox(width: 8), - DidvanText( - data.title, - style: Theme.of(context).textTheme.subtitle1, - color: - data.titleColor ?? Theme.of(context).colorScheme.title, - ) - ], - ), + ) + ], + ), const SizedBox(height: 28), data.content, const SizedBox(height: 28), @@ -169,30 +171,31 @@ class ActionSheetUtils { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - GestureDetector( - onTap: () => Navigator.of(context).pop(), - child: Icon( - data.titleIcon, - size: 20, - color: data.titleColor, + if (data.title != null) + Row( + mainAxisSize: MainAxisSize.min, + children: [ + GestureDetector( + onTap: () => Navigator.of(context).pop(), + child: Icon( + data.titleIcon, + size: 20, + color: data.titleColor, + ), ), - ), - const SizedBox( - width: 8, - ), - Expanded( - child: DidvanText( - data.title, - style: Theme.of(context).textTheme.headline3, - color: data.titleColor, - fontWeight: FontWeight.bold, + const SizedBox( + width: 8, ), - ), - ], - ), + Expanded( + child: DidvanText( + data.title!, + style: Theme.of(context).textTheme.headline3, + color: data.titleColor, + fontWeight: FontWeight.bold, + ), + ), + ], + ), const SizedBox( height: 12, ), diff --git a/lib/views/home/direct/widgets/audio_widget.dart b/lib/views/home/direct/widgets/audio_widget.dart index 5bb7ba7..a33827c 100644 --- a/lib/views/home/direct/widgets/audio_widget.dart +++ b/lib/views/home/direct/widgets/audio_widget.dart @@ -1,7 +1,7 @@ import 'dart:io'; import 'package:didvan/services/media/media.dart'; -import 'package:didvan/views/home/widgets/audio_slider.dart'; +import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; import 'package:didvan/views/home/widgets/player_controller_button.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/home/hashtag/hashtag.dart b/lib/views/home/hashtag/hashtag.dart index d833ad9..24a9a7c 100644 --- a/lib/views/home/hashtag/hashtag.dart +++ b/lib/views/home/hashtag/hashtag.dart @@ -1,8 +1,8 @@ import 'package:didvan/models/tag.dart'; import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/views/home/hashtag/hashtag_state.dart'; -import 'package:didvan/views/home/widgets/news_overview.dart'; -import 'package:didvan/views/home/widgets/radar_overview.dart'; +import 'package:didvan/views/home/widgets/overview/news.dart'; +import 'package:didvan/views/home/widgets/overview/radar.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:flutter/material.dart'; diff --git a/lib/views/home/studio/studio.dart b/lib/views/home/studio/studio.dart index d4cba73..1ffca83 100644 --- a/lib/views/home/studio/studio.dart +++ b/lib/views/home/studio/studio.dart @@ -6,9 +6,9 @@ import 'package:didvan/views/home/studio/studio_state.dart'; import 'package:didvan/views/home/studio/widgets/slider.dart'; import 'package:didvan/views/home/studio/widgets/tab_bar.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart'; -import 'package:didvan/views/home/widgets/podcast_overview.dart'; +import 'package:didvan/views/home/widgets/overview/podcast.dart'; +import 'package:didvan/views/home/widgets/overview/video.dart'; import 'package:didvan/views/home/widgets/search_field.dart'; -import 'package:didvan/views/home/widgets/video_overview.dart'; import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/radial_button.dart'; @@ -25,7 +25,7 @@ class Studio extends StatefulWidget { } class _StudioState extends State { - final FocusNode _focusNode = FocusNode(); + final _focusNode = FocusNode(); @override void initState() { @@ -119,8 +119,6 @@ class _StudioState extends State { : PodcastOverview( podcast: state.studios[index], onMarkChanged: state.changeMark, - hasUnmarkConfirmation: false, - onCommentsChanged: state.onCommentsChanged, studioRequestArgs: StudioRequestArgs( page: state.page, order: state.order, diff --git a/lib/views/home/studio/studio_details/studio_details.dart b/lib/views/home/studio/studio_details/studio_details.dart index 26491e2..9d72d2b 100644 --- a/lib/views/home/studio/studio_details/studio_details.dart +++ b/lib/views/home/studio/studio_details/studio_details.dart @@ -1,12 +1,18 @@ import 'dart:io'; import 'package:didvan/config/design_config.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/view/app_bar_data.dart'; +import 'package:didvan/services/media/media.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; -import 'package:didvan/views/home/widgets/audio_slider.dart'; +import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; +import 'package:didvan/views/home/widgets/bookmark_button.dart'; +import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/ink_wrapper.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:flutter/material.dart'; @@ -30,15 +36,15 @@ class _StudioDetailsState extends State { double _dwInPortrait = 0; double _scaleInPortrait = 1; - bool get _isVideo => widget.pageData['isVideo']; - @override void initState() { final state = context.read(); + Future.delayed( Duration.zero, () => state.getStudioDetails(widget.pageData['id']), ); + state.args = widget.pageData['args']; if (Platform.isAndroid) WebView.platform = AndroidWebView(); super.initState(); @@ -96,6 +102,8 @@ class _StudioDetailsState extends State { return true; }, child: DidvanScaffold( + backgroundColor: Theme.of(context).colorScheme.surface, + padding: EdgeInsets.zero, appBarData: _isFullScreen ? null : AppBarData( @@ -103,15 +111,14 @@ class _StudioDetailsState extends State { title: state.currentStudio.title, ), children: [ - if (_isVideo) - SizedBox( - width: ds.width, - height: _isFullScreen ? ds.height : ds.width * 9 / 16, - child: Stack( - children: [ - WebView( - initialUrl: Uri.dataFromString( - ''' + SizedBox( + width: ds.width, + height: _isFullScreen ? ds.height : ds.width * 9 / 16, + child: Stack( + children: [ + WebView( + initialUrl: Uri.dataFromString( + ''' { ''', - mimeType: 'text/html', - ).toString(), - javascriptMode: JavascriptMode.unrestricted, - ), - Positioned( - right: 42, - bottom: 8, - child: GestureDetector( - onTap: () => _changeFullSceen(!_isFullScreen), - child: Container( - color: Colors.transparent, - width: 24, - height: 30, - ), + mimeType: 'text/html', + ).toString(), + javascriptMode: JavascriptMode.unrestricted, + ), + Positioned( + right: 42, + bottom: 8, + child: GestureDetector( + onTap: () => _changeFullSceen(!_isFullScreen), + child: Container( + color: Colors.transparent, + width: 24, + height: 30, ), ), - ], - ), + ), + ], ), - if (!_isVideo) - AudioPlayerWidget(podcast: state.currentStudio), + ), ], ), ), @@ -172,39 +177,3 @@ class _StudioDetailsState extends State { ); } } - -class AudioPlayerWidget extends StatelessWidget { - final StudioDetailsData podcast; - const AudioPlayerWidget({Key? key, required this.podcast}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Padding( - padding: const EdgeInsets.symmetric(horizontal: 24), - child: Hero( - tag: podcast.media, - child: SkeletonImage( - imageUrl: podcast.image, - aspectRatio: 1 / 1, - ), - ), - ), - const SizedBox(height: 16), - DidvanText( - podcast.title, - style: Theme.of(context).textTheme.bodyText1, - ), - const SizedBox(height: 16), - Padding( - padding: const EdgeInsets.symmetric(horizontal: 16), - child: AudioSlider( - tag: podcast.media, - showTimer: true, - ), - ), - ], - ); - } -} diff --git a/lib/views/home/studio/studio_details/studio_details_state.dart b/lib/views/home/studio/studio_details/studio_details_state.dart index 4a4d9e2..9f50e72 100644 --- a/lib/views/home/studio/studio_details/studio_details_state.dart +++ b/lib/views/home/studio/studio_details/studio_details_state.dart @@ -6,15 +6,14 @@ import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/providers/core_provider.dart'; +import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request_helper.dart'; class StudioDetailsState extends CoreProvier { final List studios = []; - late Timer _trackingTimer; - int _trackingTimerCounter = 0; - late final int initialIndex; - late final StudioRequestArgs args; + late int initialIndex; + late StudioRequestArgs args; bool isFetchingNewItem = false; final List relatedQueue = []; @@ -29,25 +28,36 @@ class StudioDetailsState extends CoreProvier { } } - Future getStudioDetails(int id, {bool? isForward}) async { + Future getStudioDetails(int id, + {bool? isForward, StudioRequestArgs? args}) async { + if (args != null) { + this.args = args; + } if (isForward == null) { appState = AppState.busy; } else { isFetchingNewItem = true; notifyListeners(); } - final service = RequestService(RequestHelper.studioDetails(id, args)); + final service = RequestService(RequestHelper.studioDetails(id, this.args)); await service.httpGet(); - _handleTracking(sendRequest: isForward != null); if (service.isSuccess) { final result = service.result; final studio = StudioDetailsData.fromJson(result['studio']); - if (args.page == 0) { + if (this.args.page == 0) { studios.add(studio); initialIndex = 0; appState = AppState.idle; return; } + if (this.args.type == 'podcast') { + MediaService.currentPodcast = studio; + MediaService.podcastPlaylistArgs = args; + await MediaService.handleAudioPlayback( + audioSource: studio.media, + isVoiceMessage: false, + ); + } StudioDetailsData? prevStudio; if (result['prevStudio'].isNotEmpty) { @@ -121,21 +131,4 @@ class StudioDetailsState extends CoreProvier { count; notifyListeners(); } - - Future _handleTracking({bool sendRequest = true}) async { - if (!sendRequest) { - _trackingTimer = Timer.periodic(const Duration(seconds: 1), (timer) { - _trackingTimerCounter++; - }); - return; - } - //send request - _trackingTimerCounter = 0; - } - - @override - void dispose() { - _trackingTimer.cancel(); - super.dispose(); - } } diff --git a/lib/views/home/widgets/bnb.dart b/lib/views/home/widgets/bnb.dart index 4c78de0..33bec5d 100644 --- a/lib/views/home/widgets/bnb.dart +++ b/lib/views/home/widgets/bnb.dart @@ -1,11 +1,21 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/models/view/action_sheet_data.dart'; +import 'package:didvan/routes/routes.dart'; import 'package:didvan/services/media/media.dart'; +import 'package:didvan/utils/action_sheet.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; +import 'package:didvan/views/home/studio/studio_state.dart'; +import 'package:didvan/views/home/widgets/audio/audio_player_widget.dart'; +import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; +import 'package:expandable_bottom_sheet/expandable_bottom_sheet.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class DidvanBNB extends StatelessWidget { final int currentTabIndex; @@ -15,6 +25,9 @@ class DidvanBNB extends StatelessWidget { {Key? key, required this.currentTabIndex, required this.onTabChanged}) : super(key: key); + bool get _enablePlayerController => + MediaService.currentPodcast != null || MediaService.audioPlayer.playing; + @override Widget build(BuildContext context) { return StreamBuilder( @@ -22,29 +35,89 @@ class DidvanBNB extends StatelessWidget { builder: (context, snapshot) { return Stack( children: [ - AnimatedContainer( - duration: DesignConfig.lowAnimationDuration, - height: snapshot.data == true ? 120 : 72, - decoration: BoxDecoration( - color: DesignConfig.isDark - ? Theme.of(context).colorScheme.focused - : Theme.of(context).colorScheme.navigation, - borderRadius: const BorderRadius.vertical( - top: Radius.circular(16), - ), - ), - child: Row( - children: [ - const DidvanIconButton( - icon: DidvanIcons.close_regular, - gestureSize: 24, - onPressed: MediaService.resetAudioPlayer, + GestureDetector( + onTap: () => _showPlayerBottomSheet(context), + child: AnimatedContainer( + padding: const EdgeInsets.only(top: 12), + duration: DesignConfig.lowAnimationDuration, + height: _enablePlayerController ? 120 : 72, + decoration: BoxDecoration( + color: DesignConfig.isDark + ? Theme.of(context).colorScheme.focused + : Theme.of(context).colorScheme.navigation, + borderRadius: const BorderRadius.vertical( + top: Radius.circular(16), ), - const SizedBox(width: 16), - if (MediaService.audioPlayerCover != null) - SkeletonImage(imageUrl: MediaService.audioPlayerCover!), - const SizedBox(width: 16), - ], + ), + child: !_enablePlayerController + ? const SizedBox() + : SizedBox( + height: 48, + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only( + right: 12, + left: 16, + ), + child: DidvanIconButton( + icon: DidvanIcons.close_regular, + color: DesignConfig.isDark + ? null + : Theme.of(context).colorScheme.secondCTA, + gestureSize: 28, + onPressed: MediaService.resetAudioPlayer, + ), + ), + SkeletonImage( + imageUrl: MediaService.currentPodcast!.image, + width: 32, + height: 32, + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + DidvanText( + MediaService.currentPodcast!.title, + color: DesignConfig.isDark + ? null + : Theme.of(context) + .colorScheme + .secondCTA, + ), + AudioSlider( + disableThumb: true, + tag: MediaService.audioPlayerTag!, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.only( + left: 12, + right: 16, + ), + child: DidvanIconButton( + gestureSize: 28, + color: DesignConfig.isDark + ? null + : Theme.of(context).colorScheme.secondCTA, + icon: snapshot.data! + ? DidvanIcons.pause_solid + : DidvanIcons.play_solid, + onPressed: () { + MediaService.handleAudioPlayback( + audioSource: MediaService.audioPlayerTag, + ); + }, + ), + ), + ], + ), + ), ), ), Positioned( @@ -105,6 +178,31 @@ class DidvanBNB extends StatelessWidget { ); }); } + + void _showPlayerBottomSheet(BuildContext context) { + final detailsState = context.read(); + showModalBottomSheet( + backgroundColor: Colors.transparent, + context: context, + isScrollControlled: true, + builder: (context) => ChangeNotifierProvider.value( + value: detailsState, + child: ExpandableBottomSheet( + background: Container(), + persistentHeader: AudioPlayerWidget( + podcast: MediaService.currentPodcast!, + ), + expandableContent: Container( + height: 300, + width: double.infinity, + color: Theme.of(context).colorScheme.surface, + alignment: Alignment.center, + child: const DidvanText('!Under Construction'), + ), + ), + ), + ); + } } class _NavBarItem extends StatelessWidget { diff --git a/lib/views/home/widgets/overview/video.dart b/lib/views/home/widgets/overview/video.dart index 1dbcdfa..3150b34 100644 --- a/lib/views/home/widgets/overview/video.dart +++ b/lib/views/home/widgets/overview/video.dart @@ -4,6 +4,7 @@ import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/routes/routes.dart'; import 'package:didvan/utils/date_time.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/widgets/bookmark_button.dart'; import 'package:didvan/views/home/widgets/duration_widget.dart'; import 'package:didvan/views/widgets/didvan/card.dart'; @@ -13,6 +14,7 @@ import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; class VideoOverview extends StatelessWidget { final OverviewData video; @@ -41,6 +43,7 @@ class VideoOverview extends StatelessWidget { 'args': studioRequestArgs, 'hasUnmarkConfirmation': hasUnmarkConfirmation, 'isVideo': true, + 'state': context.read(), }, ), child: Row( diff --git a/pubspec.lock b/pubspec.lock index 825fa09..ab49287 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -127,6 +127,13 @@ packages: url: "https://pub.dartlang.org" source: hosted version: "1.0.4+1" + expandable_bottom_sheet: + dependency: "direct main" + description: + name: expandable_bottom_sheet + url: "https://pub.dartlang.org" + source: hosted + version: "1.1.1+1" fake_async: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 41c210a..b3e664b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -63,6 +63,7 @@ dependencies: firebase_messaging: ^11.2.8 firebase_core: ^1.13.1 webview_flutter: ^3.0.1 + expandable_bottom_sheet: ^1.1.1+1 dev_dependencies: From e1ea7630f50bf760dd41e39028bfe73091ff58a7 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Sat, 19 Mar 2022 14:37:54 +0330 Subject: [PATCH 20/23] D1APP-99 studio progress --- ios/Podfile.lock | 6 ++ .../studio/studio_details/studio_details.dart | 11 +-- .../studio_details/studio_details_state.dart | 7 ++ .../widgets/studio_details.dart | 97 +++++++++++++++++++ .../widgets/audio/audio_player_widget.dart | 5 - lib/views/home/widgets/bnb.dart | 50 +++++++--- lib/views/home/widgets/overview/podcast.dart | 2 - 7 files changed, 146 insertions(+), 32 deletions(-) create mode 100644 lib/views/home/studio/studio_details/widgets/studio_details.dart diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 55fa4cf..89b36ba 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -88,6 +88,8 @@ PODS: - TOCropViewController (2.6.1) - url_launcher_ios (0.0.1): - Flutter + - webview_flutter_wkwebview (0.0.1): + - Flutter DEPENDENCIES: - audio_session (from `.symlinks/plugins/audio_session/ios`) @@ -103,6 +105,7 @@ DEPENDENCIES: - record (from `.symlinks/plugins/record/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) + - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) SPEC REPOS: trunk: @@ -145,6 +148,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/sqflite/ios" url_launcher_ios: :path: ".symlinks/plugins/url_launcher_ios/ios" + webview_flutter_wkwebview: + :path: ".symlinks/plugins/webview_flutter_wkwebview/ios" SPEC CHECKSUMS: audio_session: 4f3e461722055d21515cf3261b64c973c062f345 @@ -171,6 +176,7 @@ SPEC CHECKSUMS: sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af + webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162 PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea diff --git a/lib/views/home/studio/studio_details/studio_details.dart b/lib/views/home/studio/studio_details/studio_details.dart index 9d72d2b..20ce528 100644 --- a/lib/views/home/studio/studio_details/studio_details.dart +++ b/lib/views/home/studio/studio_details/studio_details.dart @@ -1,19 +1,9 @@ import 'dart:io'; import 'package:didvan/config/design_config.dart'; -import 'package:didvan/config/theme_data.dart'; -import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/view/app_bar_data.dart'; -import 'package:didvan/services/media/media.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; -import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; -import 'package:didvan/views/home/widgets/bookmark_button.dart'; -import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart'; -import 'package:didvan/views/widgets/didvan/text.dart'; -import 'package:didvan/views/widgets/ink_wrapper.dart'; -import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; @@ -117,6 +107,7 @@ class _StudioDetailsState extends State { child: Stack( children: [ WebView( + allowsInlineMediaPlayback: true, initialUrl: Uri.dataFromString( ''' diff --git a/lib/views/home/studio/studio_details/studio_details_state.dart b/lib/views/home/studio/studio_details/studio_details_state.dart index 9f50e72..0a6205b 100644 --- a/lib/views/home/studio/studio_details/studio_details_state.dart +++ b/lib/views/home/studio/studio_details/studio_details_state.dart @@ -14,12 +14,19 @@ class StudioDetailsState extends CoreProvier { final List studios = []; late int initialIndex; late StudioRequestArgs args; + int _selectedDetailsIndex = 0; bool isFetchingNewItem = false; final List relatedQueue = []; int _currentIndex = 0; int get currentIndex => _currentIndex; + int get selectedDetailsIndex => _selectedDetailsIndex; + set selectedDetailsIndex(int value) { + _selectedDetailsIndex = value; + notifyListeners(); + } + StudioDetailsData get currentStudio { try { return studios[_currentIndex]!; diff --git a/lib/views/home/studio/studio_details/widgets/studio_details.dart b/lib/views/home/studio/studio_details/widgets/studio_details.dart new file mode 100644 index 0000000..059b4cc --- /dev/null +++ b/lib/views/home/studio/studio_details/widgets/studio_details.dart @@ -0,0 +1,97 @@ +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/app_icons.dart'; +import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; +import 'package:didvan/views/widgets/didvan/text.dart'; +import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; +import 'package:flutter/material.dart'; +import 'package:provider/provider.dart'; + +class StudioDetailsWidget extends StatelessWidget { + const StudioDetailsWidget({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + return Consumer( + builder: (context, state, child) => StateHandler( + onRetry: () {}, + state: state, + builder: (context, state) => Container( + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + _TabItem( + icon: DidvanIcons.description_solid, + title: 'توضیحات', + onTap: () => state.selectedDetailsIndex = 0, + isSelected: state.selectedDetailsIndex == 0, + ), + _TabItem( + icon: DidvanIcons.chats_solid, + title: 'نظرات', + onTap: () => state.selectedDetailsIndex = 1, + isSelected: state.selectedDetailsIndex == 1, + ), + _TabItem( + icon: DidvanIcons.puzzle_solid, + title: 'مطالب مرتبط', + onTap: () => state.selectedDetailsIndex = 2, + isSelected: state.selectedDetailsIndex == 2, + ), + ], + ), + const SizedBox(height: 16), + ], + ), + ), + ), + ); + } +} + +class _TabItem extends StatelessWidget { + final IconData icon; + final String title; + final VoidCallback onTap; + final bool isSelected; + const _TabItem({ + Key? key, + required this.icon, + required this.title, + required this.onTap, + required this.isSelected, + }) : super(key: key); + + Color? _color(context) => + isSelected ? Theme.of(context).colorScheme.focusedBorder : null; + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + color: Colors.transparent, + child: Column( + children: [ + Icon( + icon, + color: _color(context), + ), + Container( + width: 64, + height: 1, + color: _color(context), + ), + DidvanText( + title, + color: _color(context), + style: Theme.of(context).textTheme.caption, + ) + ], + ), + ), + ); + } +} diff --git a/lib/views/home/widgets/audio/audio_player_widget.dart b/lib/views/home/widgets/audio/audio_player_widget.dart index f8b0a69..d369b10 100644 --- a/lib/views/home/widgets/audio/audio_player_widget.dart +++ b/lib/views/home/widgets/audio/audio_player_widget.dart @@ -104,11 +104,6 @@ class AudioPlayerWidget extends StatelessWidget { ), ], ), - DidvanIconButton( - size: 32, - icon: DidvanIcons.angle_down_regular, - onPressed: Navigator.of(context).pop, - ), ], ), ); diff --git a/lib/views/home/widgets/bnb.dart b/lib/views/home/widgets/bnb.dart index 33bec5d..a7651be 100644 --- a/lib/views/home/widgets/bnb.dart +++ b/lib/views/home/widgets/bnb.dart @@ -1,13 +1,9 @@ import 'package:didvan/config/design_config.dart'; import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/models/view/action_sheet_data.dart'; -import 'package:didvan/routes/routes.dart'; import 'package:didvan/services/media/media.dart'; -import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/views/home/studio/studio_details/studio_details.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; -import 'package:didvan/views/home/studio/studio_state.dart'; +import 'package:didvan/views/home/studio/studio_details/widgets/studio_details.dart'; import 'package:didvan/views/home/widgets/audio/audio_player_widget.dart'; import 'package:didvan/views/home/widgets/audio/audio_slider.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart'; @@ -180,6 +176,8 @@ class DidvanBNB extends StatelessWidget { } void _showPlayerBottomSheet(BuildContext context) { + final sheetKey = GlobalKey(); + bool isExpanded = false; final detailsState = context.read(); showModalBottomSheet( backgroundColor: Colors.transparent, @@ -188,17 +186,39 @@ class DidvanBNB extends StatelessWidget { builder: (context) => ChangeNotifierProvider.value( value: detailsState, child: ExpandableBottomSheet( - background: Container(), - persistentHeader: AudioPlayerWidget( - podcast: MediaService.currentPodcast!, - ), - expandableContent: Container( - height: 300, - width: double.infinity, - color: Theme.of(context).colorScheme.surface, - alignment: Alignment.center, - child: const DidvanText('!Under Construction'), + key: sheetKey, + background: const SizedBox(), + persistentHeader: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + AudioPlayerWidget( + podcast: MediaService.currentPodcast!, + ), + Container( + width: MediaQuery.of(context).size.width, + color: Theme.of(context).colorScheme.surface, + child: Column( + children: [ + DidvanIconButton( + size: 32, + icon: DidvanIcons.angle_down_regular, + onPressed: () { + if (!isExpanded) { + sheetKey.currentState?.expand(); + isExpanded = true; + return; + } + isExpanded = false; + sheetKey.currentState?.contract(); + }, + ), + const SizedBox(height: 16), + ], + ), + ), + ], ), + expandableContent: const StudioDetailsWidget(), ), ), ); diff --git a/lib/views/home/widgets/overview/podcast.dart b/lib/views/home/widgets/overview/podcast.dart index 1669c0a..1a243df 100644 --- a/lib/views/home/widgets/overview/podcast.dart +++ b/lib/views/home/widgets/overview/podcast.dart @@ -2,8 +2,6 @@ import 'package:didvan/config/theme_data.dart'; import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/requests/studio.dart'; -import 'package:didvan/routes/routes.dart'; -import 'package:didvan/services/media/media.dart'; import 'package:didvan/utils/date_time.dart'; import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart'; import 'package:didvan/views/home/widgets/bookmark_button.dart'; From 622434d5467cdd2b6af63f8ac9a1455b0c44f426 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Sat, 19 Mar 2022 17:17:02 +0330 Subject: [PATCH 21/23] contents text align fixed --- lib/views/widgets/didvan/page_view.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/views/widgets/didvan/page_view.dart b/lib/views/widgets/didvan/page_view.dart index 580a3e1..fd3a8e9 100644 --- a/lib/views/widgets/didvan/page_view.dart +++ b/lib/views/widgets/didvan/page_view.dart @@ -188,6 +188,7 @@ class _DidvanPageViewState extends State { style: { '*': Style( direction: TextDirection.rtl, + textAlign: TextAlign.right, lineHeight: LineHeight.percent(135), margin: EdgeInsets.zero, padding: EdgeInsets.zero, From 7aeb6f4026f7376781b73486ebf41179884935c9 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Sat, 19 Mar 2022 17:18:51 +0330 Subject: [PATCH 22/23] v1.2.0 buiild 1 --- lib/views/home/studio/studio.dart | 178 +++--------------------------- pubspec.yaml | 2 +- 2 files changed, 14 insertions(+), 166 deletions(-) diff --git a/lib/views/home/studio/studio.dart b/lib/views/home/studio/studio.dart index 1ffca83..5f5f4ca 100644 --- a/lib/views/home/studio/studio.dart +++ b/lib/views/home/studio/studio.dart @@ -1,178 +1,26 @@ -import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/models/requests/studio.dart'; -import 'package:didvan/models/view/action_sheet_data.dart'; -import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/views/home/studio/studio_state.dart'; -import 'package:didvan/views/home/studio/widgets/slider.dart'; -import 'package:didvan/views/home/studio/widgets/tab_bar.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/assets.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart'; -import 'package:didvan/views/home/widgets/overview/podcast.dart'; -import 'package:didvan/views/home/widgets/overview/video.dart'; -import 'package:didvan/views/home/widgets/search_field.dart'; -import 'package:didvan/views/widgets/didvan/divider.dart'; -import 'package:didvan/views/widgets/didvan/icon_button.dart'; -import 'package:didvan/views/widgets/didvan/radial_button.dart'; -import 'package:didvan/views/widgets/item_title.dart'; -import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -class Studio extends StatefulWidget { +class Studio extends StatelessWidget { const Studio({Key? key}) : super(key: key); - @override - State createState() => _StudioState(); -} - -class _StudioState extends State { - final _focusNode = FocusNode(); - - @override - void initState() { - context.read().init(); - super.initState(); - } - @override Widget build(BuildContext context) { - return CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: Row( - children: [ - const Expanded(child: LogoAppBar(type: 'studio')), - Padding( - padding: - EdgeInsets.only(top: MediaQuery.of(context).padding.top), - child: DidvanIconButton( - icon: DidvanIcons.bookmark_regular, - onPressed: () {}, - ), - ), - ], - ), - ), - const SliverToBoxAdapter( - child: StudioTabBar(), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: SearchField( - title: 'جستجو در استودیو', - onChanged: (value) {}, - focusNode: _focusNode, - ), - ), - ), - const SliverToBoxAdapter( - child: StudioSlider(), - ), - const SliverPadding( - padding: EdgeInsets.symmetric(horizontal: 16), - sliver: SliverToBoxAdapter( - child: DidvanDivider( - verticalPadding: 0, - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 16), - sliver: SliverToBoxAdapter( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const ItemTitle(title: 'تازه‌ترین‌ها'), - DidvanIconButton( - gestureSize: 36, - icon: DidvanIcons.sort_regular, - onPressed: _showSortDialog, - ), - ], - ), - ), - ), - Consumer( - builder: (context, state, child) => SliverStateHandler( - state: state, - itemPadding: const EdgeInsets.only( - bottom: 8, - left: 16, - right: 16, - ), - placeholder: state.videosSelected - ? VideoOverview.placeHolder - : PodcastOverview.placeholder, - builder: (context, state, index) => state.videosSelected - ? VideoOverview( - onMarkChanged: state.changeMark, - hasUnmarkConfirmation: false, - video: state.studios[index], - onCommentsChanged: state.onCommentsChanged, - studioRequestArgs: StudioRequestArgs( - page: state.page, - order: state.order, - search: state.search, - type: state.type, - ), - ) - : PodcastOverview( - podcast: state.studios[index], - onMarkChanged: state.changeMark, - studioRequestArgs: StudioRequestArgs( - page: state.page, - order: state.order, - search: state.search, - type: state.type, - ), - ), - childCount: state.studios.length, - onRetry: () => state.getStudioOverviews(page: 1), + return Column( + children: [ + const LogoAppBar(), + Expanded( + child: EmptyState( + asset: Assets.emptyStudio, + title: 'استودیو آینده', + subtitle: 'به زودی...', + titleColor: Theme.of(context).colorScheme.title, ), ), ], ); } - - void _showSortDialog() { - final state = context.read(); - ActionSheetUtils.showBottomSheet( - data: ActionSheetData( - content: StatefulBuilder( - builder: (context, setState) => Column( - children: [ - DidvanRadialButton( - title: 'جدیدترین‌ها', - onSelected: () => setState( - () => state.selectedSortTypeIndex = 0, - ), - value: state.selectedSortTypeIndex == 0, - ), - const SizedBox(height: 24), - DidvanRadialButton( - title: 'پربازدیدترین‌ها', - onSelected: () => setState( - () => state.selectedSortTypeIndex = 1, - ), - value: state.selectedSortTypeIndex == 1, - ), - const SizedBox(height: 24), - DidvanRadialButton( - title: 'پربحث‌ترین‌ها', - onSelected: () => setState( - () => state.selectedSortTypeIndex = 2, - ), - value: state.selectedSortTypeIndex == 2, - ), - ], - ), - ), - title: 'مرتب‌‌سازی', - titleIcon: DidvanIcons.sort_regular, - hasDismissButton: false, - confrimTitle: 'مرتب سازی', - onConfirmed: () => state.getStudioOverviews(page: 1), - ), - ); - } } diff --git a/pubspec.yaml b/pubspec.yaml index b3e664b..7bae68f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.4+4 +version: 1.2.0+1 environment: sdk: ">=2.12.0 <3.0.0" From 3d424f8766fc6e5728d942c07e7e5ee18393f612 Mon Sep 17 00:00:00 2001 From: MohammadTaha Basiri Date: Sat, 19 Mar 2022 17:20:31 +0330 Subject: [PATCH 23/23] v1.2.0 buiild 1 --- lib/services/network/request_helper.dart | 2 +- lib/views/home/studio/studio.dart | 178 ++--------------------- pubspec.yaml | 2 +- 3 files changed, 15 insertions(+), 167 deletions(-) diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart index e35f070..bfddb9a 100644 --- a/lib/services/network/request_helper.dart +++ b/lib/services/network/request_helper.dart @@ -3,7 +3,7 @@ import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/requests/studio.dart'; class RequestHelper { - static const String baseUrl = 'https://test.api.didvan.app'; + static const String baseUrl = 'https://api.didvan.app'; static const String _baseUserUrl = baseUrl + '/user'; static const String _baseRadarUrl = baseUrl + '/radar'; static const String _baseNewsUrl = baseUrl + '/news'; diff --git a/lib/views/home/studio/studio.dart b/lib/views/home/studio/studio.dart index 1ffca83..5f5f4ca 100644 --- a/lib/views/home/studio/studio.dart +++ b/lib/views/home/studio/studio.dart @@ -1,178 +1,26 @@ -import 'package:didvan/constants/app_icons.dart'; -import 'package:didvan/models/requests/studio.dart'; -import 'package:didvan/models/view/action_sheet_data.dart'; -import 'package:didvan/utils/action_sheet.dart'; -import 'package:didvan/views/home/studio/studio_state.dart'; -import 'package:didvan/views/home/studio/widgets/slider.dart'; -import 'package:didvan/views/home/studio/widgets/tab_bar.dart'; +import 'package:didvan/config/theme_data.dart'; +import 'package:didvan/constants/assets.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart'; -import 'package:didvan/views/home/widgets/overview/podcast.dart'; -import 'package:didvan/views/home/widgets/overview/video.dart'; -import 'package:didvan/views/home/widgets/search_field.dart'; -import 'package:didvan/views/widgets/didvan/divider.dart'; -import 'package:didvan/views/widgets/didvan/icon_button.dart'; -import 'package:didvan/views/widgets/didvan/radial_button.dart'; -import 'package:didvan/views/widgets/item_title.dart'; -import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; +import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:flutter/material.dart'; -import 'package:provider/provider.dart'; -class Studio extends StatefulWidget { +class Studio extends StatelessWidget { const Studio({Key? key}) : super(key: key); - @override - State createState() => _StudioState(); -} - -class _StudioState extends State { - final _focusNode = FocusNode(); - - @override - void initState() { - context.read().init(); - super.initState(); - } - @override Widget build(BuildContext context) { - return CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: Row( - children: [ - const Expanded(child: LogoAppBar(type: 'studio')), - Padding( - padding: - EdgeInsets.only(top: MediaQuery.of(context).padding.top), - child: DidvanIconButton( - icon: DidvanIcons.bookmark_regular, - onPressed: () {}, - ), - ), - ], - ), - ), - const SliverToBoxAdapter( - child: StudioTabBar(), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(16.0), - child: SearchField( - title: 'جستجو در استودیو', - onChanged: (value) {}, - focusNode: _focusNode, - ), - ), - ), - const SliverToBoxAdapter( - child: StudioSlider(), - ), - const SliverPadding( - padding: EdgeInsets.symmetric(horizontal: 16), - sliver: SliverToBoxAdapter( - child: DidvanDivider( - verticalPadding: 0, - ), - ), - ), - SliverPadding( - padding: const EdgeInsets.symmetric(horizontal: 16), - sliver: SliverToBoxAdapter( - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - const ItemTitle(title: 'تازه‌ترین‌ها'), - DidvanIconButton( - gestureSize: 36, - icon: DidvanIcons.sort_regular, - onPressed: _showSortDialog, - ), - ], - ), - ), - ), - Consumer( - builder: (context, state, child) => SliverStateHandler( - state: state, - itemPadding: const EdgeInsets.only( - bottom: 8, - left: 16, - right: 16, - ), - placeholder: state.videosSelected - ? VideoOverview.placeHolder - : PodcastOverview.placeholder, - builder: (context, state, index) => state.videosSelected - ? VideoOverview( - onMarkChanged: state.changeMark, - hasUnmarkConfirmation: false, - video: state.studios[index], - onCommentsChanged: state.onCommentsChanged, - studioRequestArgs: StudioRequestArgs( - page: state.page, - order: state.order, - search: state.search, - type: state.type, - ), - ) - : PodcastOverview( - podcast: state.studios[index], - onMarkChanged: state.changeMark, - studioRequestArgs: StudioRequestArgs( - page: state.page, - order: state.order, - search: state.search, - type: state.type, - ), - ), - childCount: state.studios.length, - onRetry: () => state.getStudioOverviews(page: 1), + return Column( + children: [ + const LogoAppBar(), + Expanded( + child: EmptyState( + asset: Assets.emptyStudio, + title: 'استودیو آینده', + subtitle: 'به زودی...', + titleColor: Theme.of(context).colorScheme.title, ), ), ], ); } - - void _showSortDialog() { - final state = context.read(); - ActionSheetUtils.showBottomSheet( - data: ActionSheetData( - content: StatefulBuilder( - builder: (context, setState) => Column( - children: [ - DidvanRadialButton( - title: 'جدیدترین‌ها', - onSelected: () => setState( - () => state.selectedSortTypeIndex = 0, - ), - value: state.selectedSortTypeIndex == 0, - ), - const SizedBox(height: 24), - DidvanRadialButton( - title: 'پربازدیدترین‌ها', - onSelected: () => setState( - () => state.selectedSortTypeIndex = 1, - ), - value: state.selectedSortTypeIndex == 1, - ), - const SizedBox(height: 24), - DidvanRadialButton( - title: 'پربحث‌ترین‌ها', - onSelected: () => setState( - () => state.selectedSortTypeIndex = 2, - ), - value: state.selectedSortTypeIndex == 2, - ), - ], - ), - ), - title: 'مرتب‌‌سازی', - titleIcon: DidvanIcons.sort_regular, - hasDismissButton: false, - confrimTitle: 'مرتب سازی', - onConfirmed: () => state.getStudioOverviews(page: 1), - ), - ); - } } diff --git a/pubspec.yaml b/pubspec.yaml index b3e664b..7bae68f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -15,7 +15,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion. # Read more about iOS versioning at # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -version: 1.1.4+4 +version: 1.2.0+1 environment: sdk: ">=2.12.0 <3.0.0"