diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml
index 34d2e7f..0e7557a 100644
--- a/android/app/src/main/AndroidManifest.xml
+++ b/android/app/src/main/AndroidManifest.xml
@@ -3,10 +3,14 @@
+
+ android:usesCleartextTraffic="true"
+ android:requestLegacyExternalStorage="true">
+
+
+ android:name="com.yalantis.ucrop.UCropActivity"
+ android:screenOrientation="portrait"
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
diff --git a/ios/Podfile b/ios/Podfile
index 9411102..313ea4a 100644
--- a/ios/Podfile
+++ b/ios/Podfile
@@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
-platform :ios, '10.0'
+platform :ios, '11.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
diff --git a/ios/Podfile.lock b/ios/Podfile.lock
index 89b36ba..f1504a2 100644
--- a/ios/Podfile.lock
+++ b/ios/Podfile.lock
@@ -1,33 +1,42 @@
PODS:
- - audio_session (0.0.1):
+ - assets_audio_player (0.0.1):
- Flutter
- - Firebase/CoreOnly (8.11.0):
- - FirebaseCore (= 8.11.0)
- - Firebase/Messaging (8.11.0):
+ - assets_audio_player_web (0.0.1):
+ - Flutter
+ - better_player (0.0.1):
+ - Cache (~> 6.0.0)
+ - Flutter
+ - GCDWebServer
+ - HLSCachingReverseProxyServer
+ - PINCache
+ - Cache (6.0.0)
+ - Firebase/CoreOnly (8.14.0):
+ - FirebaseCore (= 8.14.0)
+ - Firebase/Messaging (8.14.0):
- Firebase/CoreOnly
- - FirebaseMessaging (~> 8.11.0)
- - firebase_core (1.13.1):
- - Firebase/CoreOnly (= 8.11.0)
+ - FirebaseMessaging (~> 8.14.0)
+ - firebase_core (1.14.0):
+ - Firebase/CoreOnly (= 8.14.0)
- Flutter
- - firebase_messaging (11.2.8):
- - Firebase/Messaging (= 8.11.0)
+ - firebase_messaging (11.2.12):
+ - Firebase/Messaging (= 8.14.0)
- firebase_core
- Flutter
- - FirebaseCore (8.11.0):
+ - FirebaseCore (8.14.0):
- FirebaseCoreDiagnostics (~> 8.0)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7)
- - FirebaseCoreDiagnostics (8.12.0):
+ - FirebaseCoreDiagnostics (8.14.0):
- GoogleDataTransport (~> 9.1)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7)
- nanopb (~> 2.30908.0)
- - FirebaseInstallations (8.12.0):
+ - FirebaseInstallations (8.14.0):
- FirebaseCore (~> 8.0)
- GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/UserDefaults (~> 7.7)
- PromisesObjC (< 3.0, >= 1.2)
- - FirebaseMessaging (8.11.0):
+ - FirebaseMessaging (8.14.0):
- FirebaseCore (~> 8.0)
- FirebaseInstallations (~> 8.0)
- GoogleDataTransport (~> 9.1)
@@ -44,6 +53,9 @@ PODS:
- FMDB (2.7.5):
- FMDB/standard (= 2.7.5)
- FMDB/standard (2.7.5)
+ - GCDWebServer (3.5.4):
+ - GCDWebServer/Core (= 3.5.4)
+ - GCDWebServer/Core (3.5.4)
- GoogleDataTransport (9.1.2):
- GoogleUtilities/Environment (~> 7.2)
- nanopb (~> 2.30908.0)
@@ -65,13 +77,14 @@ PODS:
- GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (7.7.0):
- GoogleUtilities/Logger
+ - HLSCachingReverseProxyServer (0.1.0):
+ - GCDWebServer (~> 3.5)
+ - PINCache (>= 3.0.1-beta.3)
- image_cropper (0.0.4):
- Flutter
- TOCropViewController (~> 2.6.1)
- image_picker (0.0.1):
- Flutter
- - just_audio (0.0.1):
- - Flutter
- nanopb (2.30908.0):
- nanopb/decode (= 2.30908.0)
- nanopb/encode (= 2.30908.0)
@@ -79,6 +92,16 @@ PODS:
- nanopb/encode (2.30908.0)
- path_provider_ios (0.0.1):
- Flutter
+ - permission_handler_apple (9.0.4):
+ - Flutter
+ - PINCache (3.0.3):
+ - PINCache/Arc-exception-safe (= 3.0.3)
+ - PINCache/Core (= 3.0.3)
+ - PINCache/Arc-exception-safe (3.0.3):
+ - PINCache/Core
+ - PINCache/Core (3.0.3):
+ - PINOperation (~> 1.2.1)
+ - PINOperation (1.2.1)
- PromisesObjC (2.0.0)
- record (0.0.1):
- Flutter
@@ -88,11 +111,15 @@ PODS:
- TOCropViewController (2.6.1)
- url_launcher_ios (0.0.1):
- Flutter
+ - wakelock (0.0.1):
+ - Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES:
- - audio_session (from `.symlinks/plugins/audio_session/ios`)
+ - assets_audio_player (from `.symlinks/plugins/assets_audio_player/ios`)
+ - assets_audio_player_web (from `.symlinks/plugins/assets_audio_player_web/ios`)
+ - better_player (from `.symlinks/plugins/better_player/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`)
@@ -100,30 +127,40 @@ DEPENDENCIES:
- flutter_vibrate (from `.symlinks/plugins/flutter_vibrate/ios`)
- image_cropper (from `.symlinks/plugins/image_cropper/ios`)
- image_picker (from `.symlinks/plugins/image_picker/ios`)
- - just_audio (from `.symlinks/plugins/just_audio/ios`)
- path_provider_ios (from `.symlinks/plugins/path_provider_ios/ios`)
+ - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`)
- record (from `.symlinks/plugins/record/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
+ - wakelock (from `.symlinks/plugins/wakelock/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS:
trunk:
+ - Cache
- Firebase
- FirebaseCore
- FirebaseCoreDiagnostics
- FirebaseInstallations
- FirebaseMessaging
- FMDB
+ - GCDWebServer
- GoogleDataTransport
- GoogleUtilities
+ - HLSCachingReverseProxyServer
- nanopb
+ - PINCache
+ - PINOperation
- PromisesObjC
- TOCropViewController
EXTERNAL SOURCES:
- audio_session:
- :path: ".symlinks/plugins/audio_session/ios"
+ assets_audio_player:
+ :path: ".symlinks/plugins/assets_audio_player/ios"
+ assets_audio_player_web:
+ :path: ".symlinks/plugins/assets_audio_player_web/ios"
+ better_player:
+ :path: ".symlinks/plugins/better_player/ios"
firebase_core:
:path: ".symlinks/plugins/firebase_core/ios"
firebase_messaging:
@@ -138,46 +175,56 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/image_cropper/ios"
image_picker:
:path: ".symlinks/plugins/image_picker/ios"
- just_audio:
- :path: ".symlinks/plugins/just_audio/ios"
path_provider_ios:
:path: ".symlinks/plugins/path_provider_ios/ios"
+ permission_handler_apple:
+ :path: ".symlinks/plugins/permission_handler_apple/ios"
record:
:path: ".symlinks/plugins/record/ios"
sqflite:
:path: ".symlinks/plugins/sqflite/ios"
url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios"
+ wakelock:
+ :path: ".symlinks/plugins/wakelock/ios"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS:
- audio_session: 4f3e461722055d21515cf3261b64c973c062f345
- Firebase: 44dd9724c84df18b486639e874f31436eaa9a20c
- firebase_core: 08f6a85f62060111de5e98d6a214810d11365de9
- firebase_messaging: 36238f3d0b933af8c919aef608408aae06ba22e8
- FirebaseCore: 2f4f85b453cc8fea4bb2b37e370007d2bcafe3f0
- FirebaseCoreDiagnostics: 3b40dfadef5b90433a60ae01f01e90fe87aa76aa
- FirebaseInstallations: 25764cf322e77f99449395870a65b2bef88e1545
- FirebaseMessaging: 02e248e8997f71fa8cc9d78e9d49ec1a701ba14a
+ assets_audio_player: edee322b9cb625571b830b35872ead1a295fd917
+ assets_audio_player_web: 19826380c44375761aa0b9053665c1e3fbc3b86b
+ better_player: 2406bfe8175203c7a46fa15f9d778d73b12e1646
+ Cache: 4ca7e00363fca5455f26534e5607634c820ffc2d
+ Firebase: 7e8fe528c161b9271d365217a74c16aaf834578e
+ firebase_core: b0b382f1497ab407aceb25e41e3036c8798c1609
+ firebase_messaging: 34dd10d1aa6d8f40d03660eeacd0452d62eec7aa
+ FirebaseCore: b84a44ee7ba999e0f9f76d198a9c7f60a797b848
+ FirebaseCoreDiagnostics: fd0c8490f34287229c1d6c103d3a55f81ec85712
+ FirebaseInstallations: 7d1d967a307c12f1aadd76844fc321cef699b1ce
+ FirebaseMessaging: 5ebc42d281567658a2cb72b9ef3506e4a1a1a6e4
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
flutter_vibrate: 9f4c2ab57008965f78969472367c329dd77eb801
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
+ GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4
GoogleDataTransport: 629c20a4d363167143f30ea78320d5a7eb8bd940
GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1
+ HLSCachingReverseProxyServer: 59935e1e0244ad7f3375d75b5ef46e8eb26ab181
image_cropper: 60c2789d1f1a78c873235d4319ca0c34a69f2d98
- image_picker: 9aa50e1d8cdacdbed739e925b7eea16d014367e6
- just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
+ image_picker: 541dcbb3b9cf32d87eacbd957845d8651d6c62c3
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
- path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5
+ path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
+ permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
+ PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086
+ PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20
PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58
record: 7ee2393532f8553bbb09fa19e95478323b7c0a99
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
- url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
+ url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
+ wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162
-PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea
+PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d
COCOAPODS: 1.11.2
diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist
index 17f2a3a..d87332a 100644
--- a/ios/Runner/Info.plist
+++ b/ios/Runner/Info.plist
@@ -28,6 +28,10 @@
Main
FirebaseAppDelegateProxyEnabled
+ UIBackgroundModes
+
+ audio
+
NSMicrophoneUsageDescription
We need to access to the microphone to record audio file
NSPhotoLibraryUsageDescription
diff --git a/lib/config/design_config.dart b/lib/config/design_config.dart
index bfb0fb8..afa9a76 100644
--- a/lib/config/design_config.dart
+++ b/lib/config/design_config.dart
@@ -1,6 +1,6 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/main.dart';
-import 'package:didvan/providers/theme_provider.dart';
+import 'package:didvan/providers/theme.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart';
diff --git a/lib/main.dart b/lib/main.dart
index 3ed3728..c8b4e77 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,8 +1,10 @@
import 'package:bot_toast/bot_toast.dart';
import 'package:didvan/config/theme_data.dart';
-import 'package:didvan/providers/theme_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/media.dart';
+import 'package:didvan/providers/theme.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/route_generator.dart';
+import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
import 'package:flutter/material.dart';
import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:provider/provider.dart';
@@ -20,12 +22,18 @@ class Didvan extends StatelessWidget {
Widget build(BuildContext context) {
return MultiProvider(
providers: [
+ ChangeNotifierProvider(
+ create: (context) => MediaProvider(),
+ ),
ChangeNotifierProvider(
create: (context) => UserProvider(),
),
ChangeNotifierProvider(
create: (context) => ThemeProvider(),
),
+ ChangeNotifierProvider(
+ create: (context) => StudioDetailsState(),
+ ),
],
child: Consumer(
builder: (context, themeProvider, child) => MaterialApp(
diff --git a/lib/models/comment/comment.dart b/lib/models/comment/comment.dart
index b4f43ea..0d58166 100644
--- a/lib/models/comment/comment.dart
+++ b/lib/models/comment/comment.dart
@@ -6,9 +6,9 @@ class CommentData {
int id;
final String text;
final String createdAt;
- final bool liked;
- final bool disliked;
- FeedbackData feedback;
+ bool liked;
+ bool disliked;
+ final FeedbackData feedback;
final UserOverview user;
final List replies;
diff --git a/lib/models/comment/feedback.dart b/lib/models/comment/feedback.dart
index d51d67f..31696ba 100644
--- a/lib/models/comment/feedback.dart
+++ b/lib/models/comment/feedback.dart
@@ -1,8 +1,8 @@
class FeedbackData {
- final int like;
- final int dislike;
+ int like;
+ int dislike;
- const FeedbackData({required this.like, required this.dislike});
+ FeedbackData({required this.like, required this.dislike});
factory FeedbackData.fromJson(Map json) => FeedbackData(
like: json['like'],
diff --git a/lib/models/overview_data.dart b/lib/models/overview_data.dart
index b72995b..f4995b0 100644
--- a/lib/models/overview_data.dart
+++ b/lib/models/overview_data.dart
@@ -1,4 +1,5 @@
import 'package:didvan/models/category.dart';
+import 'package:html/parser.dart';
class OverviewData {
final int id;
@@ -8,7 +9,8 @@ class OverviewData {
final int? timeToRead;
final int? duration;
final String? reference;
- final String? media;
+ final String? link;
+ final String? iframe;
final bool forManagers;
final String createdAt;
final String type;
@@ -26,35 +28,42 @@ class OverviewData {
required this.marked,
required this.comments,
required this.forManagers,
- this.media,
+ this.link,
+ this.iframe,
this.duration,
this.timeToRead,
this.reference,
this.categories,
});
- factory OverviewData.fromJson(Map json) => OverviewData(
- id: json['id'],
- title: json['title'],
- image: json['image'],
- description: json['description'],
- timeToRead: json['timeToRead'],
- reference: json['reference'],
- forManagers: json['forManagers'] ?? false,
- comments: json['comments'] ?? 0,
- createdAt: json['createdAt'],
- duration: json['duration'],
- type: json['type'] ?? '',
- marked: json['marked'] ?? false,
- media: json['media'],
- categories: json['categories'] != null
- ? List.from(
- json['categories'].map(
- (e) => CategoryData.fromJson(e),
- ),
- )
- : null,
- );
+ factory OverviewData.fromJson(Map json) {
+ final document = parse(json['description']);
+ final String parsedString =
+ parse(document.body!.text).documentElement!.text;
+ return OverviewData(
+ id: json['id'],
+ title: json['title'],
+ image: json['image'],
+ description: parsedString,
+ timeToRead: json['timeToRead'],
+ reference: json['reference'],
+ forManagers: json['forManagers'] ?? false,
+ comments: json['comments'] ?? 0,
+ createdAt: json['createdAt'],
+ duration: json['duration'],
+ type: json['type'] ?? '',
+ marked: json['marked'] ?? true,
+ link: json['link'],
+ iframe: json['iframe'],
+ categories: json['categories'] != null
+ ? List.from(
+ json['categories'].map(
+ (e) => CategoryData.fromJson(e),
+ ),
+ )
+ : null,
+ );
+ }
Map toJson() => {
'id': id,
diff --git a/lib/models/requests/studio.dart b/lib/models/requests/studio.dart
index 74feeaa..221ea0a 100644
--- a/lib/models/requests/studio.dart
+++ b/lib/models/requests/studio.dart
@@ -3,9 +3,11 @@ class StudioRequestArgs {
final String? search;
final String? order;
final String? type;
+ final bool? asc;
const StudioRequestArgs({
required this.page,
+ this.asc,
this.search,
this.order,
this.type,
diff --git a/lib/models/slider_data.dart b/lib/models/slider_data.dart
new file mode 100644
index 0000000..c6ff0ed
--- /dev/null
+++ b/lib/models/slider_data.dart
@@ -0,0 +1,26 @@
+class SliderData {
+ final int id;
+ final String title;
+ final String image;
+ final String link;
+
+ const SliderData({
+ required this.id,
+ required this.title,
+ required this.image,
+ required this.link,
+ });
+
+ factory SliderData.fromJson(Map json) => SliderData(
+ id: json['id'],
+ title: json['title'],
+ image: json['image'],
+ link: json['link'],
+ );
+
+ Map toJson() => {
+ 'id': id,
+ 'title': title,
+ 'image': image,
+ };
+}
diff --git a/lib/models/studio_details_data.dart b/lib/models/studio_details_data.dart
index b83722e..334b4b2 100644
--- a/lib/models/studio_details_data.dart
+++ b/lib/models/studio_details_data.dart
@@ -7,7 +7,8 @@ class StudioDetailsData {
final String title;
final String description;
final String image;
- final String media;
+ final String link;
+ final String? iframe;
final String createdAt;
final int order;
bool marked;
@@ -21,7 +22,8 @@ class StudioDetailsData {
required this.title,
required this.description,
required this.image,
- required this.media,
+ required this.link,
+ required this.iframe,
required this.createdAt,
required this.order,
required this.marked,
@@ -36,7 +38,8 @@ class StudioDetailsData {
title: json['title'],
description: json['description'],
image: json['image'],
- media: json['media'],
+ link: json['link'],
+ iframe: json['iframe'],
createdAt: json['createdAt'],
order: json['order'],
marked: json['marked'],
@@ -51,7 +54,6 @@ class StudioDetailsData {
'title': title,
'description': description,
'image': image,
- 'media': media,
'createdAt': createdAt,
'order': order,
'marked': marked,
diff --git a/lib/providers/core_provider.dart b/lib/providers/core.dart
similarity index 93%
rename from lib/providers/core_provider.dart
rename to lib/providers/core.dart
index 820387d..b9887ef 100644
--- a/lib/providers/core_provider.dart
+++ b/lib/providers/core.dart
@@ -3,7 +3,7 @@ import 'package:didvan/utils/action_sheet.dart';
import 'package:flutter/cupertino.dart';
class CoreProvier with ChangeNotifier {
- AppState _appState = AppState.idle;
+ AppState _appState = AppState.busy;
set appState(AppState newState) {
if (newState == AppState.isolatedBusy) {
diff --git a/lib/providers/media.dart b/lib/providers/media.dart
new file mode 100644
index 0000000..4d93846
--- /dev/null
+++ b/lib/providers/media.dart
@@ -0,0 +1,60 @@
+import 'dart:io';
+
+import 'package:didvan/models/enums.dart';
+import 'package:didvan/providers/core.dart';
+import 'package:didvan/services/network/request.dart';
+import 'package:didvan/services/storage/storage.dart';
+
+class MediaProvider extends CoreProvier {
+ static final List downloadedItemIds = [];
+ final List downloadQueue = [];
+
+ Future getDownloadsList() async {
+ downloadedItemIds.clear();
+ final videosDir = Directory(
+ StorageService.appDocsDir + ('/videos'),
+ );
+ final podcastsDir = Directory(
+ StorageService.appDocsDir + ('/podcasts'),
+ );
+ if (!await videosDir.exists()) {
+ await videosDir.create();
+ }
+ if (!await podcastsDir.exists()) {
+ await podcastsDir.create();
+ }
+ videosDir.list(recursive: false).listen(
+ (event) {
+ downloadedItemIds.add(
+ int.parse(
+ event.path.split('/').last.split('-').last.split('.').first,
+ ),
+ );
+ },
+ );
+ podcastsDir.list(recursive: false).listen(
+ (event) {
+ downloadedItemIds.add(
+ int.parse(
+ event.path.split('/').last.split('-').last.split('.').first,
+ ),
+ );
+ },
+ );
+ await Future.delayed(const Duration(milliseconds: 300), notifyListeners);
+ }
+
+ Future download({
+ required String url,
+ required String fileName,
+ required bool isVideo,
+ }) async {
+ appState = AppState.busy;
+ downloadQueue.add(url);
+ notifyListeners();
+ final service = RequestService(url);
+ await service.download(fileName, isVideo ? 'videos' : 'podcasts');
+ downloadQueue.remove(url);
+ getDownloadsList();
+ }
+}
diff --git a/lib/providers/server_data_provider.dart b/lib/providers/server_data.dart
similarity index 81%
rename from lib/providers/server_data_provider.dart
rename to lib/providers/server_data.dart
index 532bb5e..4ef71bb 100644
--- a/lib/providers/server_data_provider.dart
+++ b/lib/providers/server_data.dart
@@ -1,5 +1,6 @@
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
+import 'package:collection/collection.dart';
class ServerDataProvider {
static final List directTypes = [];
@@ -10,7 +11,10 @@ class ServerDataProvider {
static int labelToTypeId(String label) => label.contains('پشتیبانی')
? 7
- : directTypes.firstWhere((element) => element.value.contains(label)).key;
+ : directTypes
+ .firstWhereOrNull((element) => element.value.contains(label))
+ ?.key ??
+ 7;
static Future _getDirectTypes() async {
final service = RequestService(RequestHelper.directTypes);
diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings.dart
similarity index 83%
rename from lib/providers/settings_provider.dart
rename to lib/providers/settings.dart
index d49f51f..ebb9005 100644
--- a/lib/providers/settings_provider.dart
+++ b/lib/providers/settings.dart
@@ -1,4 +1,4 @@
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:flutter/material.dart';
class SettingsProvider extends CoreProvier {
diff --git a/lib/providers/theme_provider.dart b/lib/providers/theme.dart
similarity index 91%
rename from lib/providers/theme_provider.dart
rename to lib/providers/theme.dart
index b994b5c..128443a 100644
--- a/lib/providers/theme_provider.dart
+++ b/lib/providers/theme.dart
@@ -1,4 +1,4 @@
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:flutter/material.dart';
class ThemeProvider extends CoreProvier {
diff --git a/lib/providers/user_provider.dart b/lib/providers/user.dart
similarity index 87%
rename from lib/providers/user_provider.dart
rename to lib/providers/user.dart
index 19c5192..d87e0ce 100644
--- a/lib/providers/user_provider.dart
+++ b/lib/providers/user.dart
@@ -2,7 +2,8 @@ import 'package:collection/collection.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/user.dart';
import 'package:didvan/models/view/alert_data.dart';
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
+import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/services/storage/storage.dart';
@@ -29,16 +30,26 @@ class UserProvider extends CoreProvier {
isAuthenticated = true;
final RequestService service = RequestService(RequestHelper.userInfo);
await service.httpGet();
- if (service.isSuccess) {
- user = User.fromJson(service.result['user']);
- return true;
- }
- if (service.statusCode == 401) {
+ if (service.statusCode == 401 ||
+ (service.isSuccess && service.result['user'] == null)) {
return false;
}
+ if (service.isSuccess) {
+ user = User.fromJson(service.result['user']);
+ AppInitializer.initializeFirebase().then((_) => _registerFirebaseToken());
+ _registerFirebaseToken();
+ return true;
+ }
throw 'Getting user from API failed!';
}
+ Future _registerFirebaseToken() async {
+ final service = RequestService(RequestHelper.firebaseToken, body: {
+ 'token': AppInitializer.fcmToken,
+ });
+ await service.put();
+ }
+
Future setProfilePhoto(dynamic file) async {
appState = AppState.isolatedBusy;
final RequestService service =
@@ -132,7 +143,7 @@ class UserProvider extends CoreProvier {
final MapEntry? lastChange =
_radarMarkQueue.lastWhereOrNull((item) => item.key == id);
if (lastChange == null) return;
- final service = RequestService(RequestHelper.markRadar(id));
+ final service = RequestService(RequestHelper.mark(id, 'radar'));
if (lastChange.value) {
await service.post();
} else {
@@ -148,7 +159,7 @@ class UserProvider extends CoreProvier {
final MapEntry? lastChange =
_studioMarkQueue.lastWhereOrNull((item) => item.key == id);
if (lastChange == null) return;
- final service = RequestService(RequestHelper.markStudio(id));
+ final service = RequestService(RequestHelper.mark(id, 'studio'));
if (lastChange.value) {
await service.post();
} else {
@@ -164,7 +175,7 @@ class UserProvider extends CoreProvier {
final MapEntry? lastChange =
_newsMarkQueue.lastWhereOrNull((item) => item.key == id);
if (lastChange == null) return;
- final service = RequestService(RequestHelper.markNews(id));
+ final service = RequestService(RequestHelper.mark(id, 'news'));
if (lastChange.value) {
await service.post();
} else {
diff --git a/lib/routes/route_generator.dart b/lib/routes/route_generator.dart
index 7514e7c..308bc61 100644
--- a/lib/routes/route_generator.dart
+++ b/lib/routes/route_generator.dart
@@ -1,4 +1,3 @@
-import 'package:didvan/models/tag.dart';
import 'package:didvan/views/authentication/authentication.dart';
import 'package:didvan/views/authentication/authentication_state.dart';
import 'package:didvan/views/home/comments/comments.dart';
@@ -25,8 +24,9 @@ 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_details/studio_details.mobile.dart'
+ if (dart.library.io) 'package:didvan/views/home/studio/studio_details/studio_details.mobile.dart'
+ if (dart.library.html) 'package:didvan/views/home/studio/studio_details/studio_details.web.dart';
import 'package:didvan/views/home/studio/studio_state.dart';
import 'package:didvan/views/splash/splash.dart';
import 'package:didvan/routes/routes.dart';
@@ -64,9 +64,6 @@ class RouteGenerator {
ChangeNotifierProvider(
create: (context) => StudioState(),
),
- ChangeNotifierProvider(
- create: (context) => StudioDetailsState(),
- ),
],
child: const Home(),
),
@@ -106,11 +103,8 @@ class RouteGenerator {
);
case Routes.studioDetails:
return _createRoute(
- ChangeNotifierProvider.value(
- value: (settings.arguments as Map)['state'],
- child: StudioDetails(
- pageData: settings.arguments as Map,
- ),
+ StudioDetails(
+ pageData: settings.arguments as Map,
),
);
case Routes.directList:
@@ -147,17 +141,19 @@ class RouteGenerator {
return _createRoute(
ChangeNotifierProvider(
create: (context) => HashtagState(),
- child: Hashtag(tag: settings.arguments as Tag),
+ child:
+ Hashtag(pageData: settings.arguments as Map),
),
);
-
case Routes.filteredBookmarks:
return _createRoute(
ChangeNotifierProvider(
create: (context) => FilteredBookmarksState(
- settings.arguments as String,
+ (settings.arguments as Map)['type'],
),
- child: const FilteredBookmarks(),
+ child: FilteredBookmarks(
+ onDeleted:
+ (settings.arguments as Map)['onDeleted']),
),
);
default:
@@ -184,17 +180,30 @@ class RouteGenerator {
final shortestSide = MediaQuery.of(context).size.shortestSide;
final bool useMobileLayout = shortestSide < 600;
if (kIsWeb && !useMobileLayout) {
- return Container(
- color: Theme.of(context).colorScheme.background,
- alignment: Alignment.center,
- child: AspectRatio(aspectRatio: 9 / 16, child: page),
+ final deviceSize = MediaQuery.of(context).size;
+ return MediaQuery(
+ data: MediaQuery.of(context).copyWith(
+ textScaleFactor: 1.0,
+ size: Size(
+ deviceSize.width / 16 * 9,
+ deviceSize.height,
+ ),
+ ),
+ child: 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,
- child: SafeArea(
- child: page,
- top: false,
+ return MediaQuery(
+ data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
+ child: Container(
+ color: Theme.of(context).colorScheme.surface,
+ child: SafeArea(
+ child: page,
+ top: false,
+ ),
),
);
},
diff --git a/lib/services/app_initalizer.dart b/lib/services/app_initalizer.dart
index 56d5f3f..749460e 100644
--- a/lib/services/app_initalizer.dart
+++ b/lib/services/app_initalizer.dart
@@ -1,5 +1,4 @@
import 'package:didvan/models/settings_data.dart';
-import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/storage/storage.dart';
import 'package:firebase_core/firebase_core.dart';
import 'package:firebase_messaging/firebase_messaging.dart';
@@ -8,13 +7,13 @@ import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart';
class AppInitializer {
+ static String? fcmToken;
+
static Future setupServices() async {
if (!kIsWeb) {
StorageService.appDocsDir =
(await getApplicationDocumentsDirectory()).path;
StorageService.appTempsDir = (await getTemporaryDirectory()).path;
- await _initializeFirebase();
- MediaService.init();
}
}
@@ -34,11 +33,11 @@ class AppInitializer {
} else {
await StorageService.setValue(
key: 'notificationTimeRangeStart',
- value: '00:00',
+ value: '0',
);
await StorageService.setValue(
key: 'notificationTimeRangeEnd',
- value: '23:59',
+ value: '24',
);
await StorageService.setValue(
key: 'fontFamily',
@@ -60,7 +59,7 @@ class AppInitializer {
}
}
- static Future _initializeFirebase() async {
+ static Future initializeFirebase() async {
try {
await Firebase.initializeApp(
options: const FirebaseOptions(
@@ -74,6 +73,8 @@ class AppInitializer {
Firebase.app();
}
final FirebaseMessaging fcm = FirebaseMessaging.instance;
+ fcmToken = await fcm.getToken();
+ await fcm.subscribeToTopic('general');
await fcm.requestPermission(
alert: true,
announcement: false,
diff --git a/lib/services/media/media.dart b/lib/services/media/media.dart
index 353b309..a783e23 100644
--- a/lib/services/media/media.dart
+++ b/lib/services/media/media.dart
@@ -1,64 +1,79 @@
import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/models/studio_details_data.dart';
+import 'package:didvan/providers/media.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
+import 'package:didvan/services/storage/storage.dart';
import 'package:flutter/foundation.dart';
import 'package:image_picker/image_picker.dart';
-import 'package:just_audio/just_audio.dart';
+import 'package:assets_audio_player/assets_audio_player.dart';
class MediaService {
- static final AudioPlayer audioPlayer = AudioPlayer();
+ static final audioPlayer = AssetsAudioPlayer();
static String? audioPlayerTag;
static StudioDetailsData? currentPodcast;
static StudioRequestArgs? podcastPlaylistArgs;
- static void init() {
- audioPlayer.positionStream.listen((event) {
- if (audioPlayer.duration != null && audioPlayer.duration! < event) {
- audioPlayer.stop();
- audioPlayer.seek(const Duration(seconds: 0));
- }
- });
- }
+ static Duration? get duration => audioPlayer.current.value?.audio.duration;
static Future handleAudioPlayback({
required dynamic audioSource,
+ required int id,
+ bool isNetworkAudio = true,
bool isVoiceMessage = true,
+ void Function(bool isNext)? onTrackChanged,
}) async {
- bool isNetworkAudio = audioSource.runtimeType == String;
String tag;
- if (isNetworkAudio) {
- tag = audioSource;
- } else {
- tag = audioSource.path;
+ tag = '${isVoiceMessage ? 'message' : 'podcast'}-$id';
+ if (!isVoiceMessage && MediaProvider.downloadedItemIds.contains(id)) {
+ audioSource = StorageService.appDocsDir + '/podcasts/podcast-$id.mp3';
+ isNetworkAudio = false;
}
if (audioPlayerTag == tag) {
- if (audioPlayer.playing) {
- await audioPlayer.pause();
- } else {
- await audioPlayer.play();
- }
- } else {
- await audioPlayer.stop();
- audioPlayerTag = tag;
- if (isNetworkAudio) {
- await audioPlayer.setUrl(
- isVoiceMessage
- ? (RequestHelper.baseUrl +
- audioSource +
- '?accessToken=${RequestService.token}')
- : audioSource,
- );
- } else {
- if (kIsWeb) {
- await audioPlayer
- .setUrl(audioSource!.uri.path.replaceAll('%3A', ':'));
- } else {
- await audioPlayer.setFilePath(audioSource.path);
- }
- }
- audioPlayer.play();
+ await audioPlayer.playOrPause();
+ return;
}
+ await audioPlayer.stop();
+ audioPlayerTag = tag;
+ Audio audio;
+ String source;
+ if (isNetworkAudio) {
+ if (isVoiceMessage) {
+ source = RequestHelper.baseUrl +
+ audioSource +
+ '?accessToken=${RequestService.token}';
+ } else {
+ source = audioSource;
+ }
+ audio = Audio.network(
+ kIsWeb ? source.replaceAll('%3A', ':') : source,
+ metas: isVoiceMessage
+ ? null
+ : Metas(
+ artist: 'استودیو دیدوان',
+ title: currentPodcast!.title,
+ ),
+ );
+ } else {
+ audio = Audio.file(
+ audioSource,
+ metas: isVoiceMessage
+ ? null
+ : Metas(
+ artist: 'استودیو دیدوان',
+ title: currentPodcast!.title,
+ ),
+ );
+ }
+ await audioPlayer.open(
+ audio,
+ showNotification: !isVoiceMessage,
+ notificationSettings: NotificationSettings(
+ customStopAction: (_) => resetAudioPlayer(),
+ customNextAction: (_) => onTrackChanged?.call(true),
+ customPrevAction: (_) => onTrackChanged?.call(false),
+ ),
+ );
}
static Future resetAudioPlayer() async {
diff --git a/lib/services/network/request.dart b/lib/services/network/request.dart
index 67ac373..d444a9a 100644
--- a/lib/services/network/request.dart
+++ b/lib/services/network/request.dart
@@ -1,7 +1,9 @@
import 'dart:convert';
import 'dart:developer';
+import 'package:didvan/services/storage/storage.dart';
import 'package:http/http.dart' as http;
import 'package:http_parser/http_parser.dart' as parser;
+import 'package:permission_handler/permission_handler.dart';
class RequestService {
static late String token;
@@ -162,6 +164,16 @@ class RequestService {
}
}
+ Future download(String fileName, String subDirectory) async {
+ await Permission.storage.request();
+ final response = await http.get(Uri.parse(url));
+ StorageService.createFile(
+ bytes: response.bodyBytes,
+ subDirectory: subDirectory,
+ name: fileName,
+ );
+ }
+
void _handleResponse(http.Response? response) {
statusCode = response?.statusCode;
if (_handleError(response)) {
diff --git a/lib/services/network/request_helper.dart b/lib/services/network/request_helper.dart
index bfddb9a..525622e 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';
@@ -15,6 +15,8 @@ class RequestHelper {
static const String login = _baseUserUrl + '/login';
static const String directs = _baseUserUrl + '/direct';
static const String userInfo = _baseUserUrl + '/info';
+ static const String firebaseToken = _baseUserUrl + '/firebaseToken';
+ static const String silenceInterval = _baseUserUrl + '/silenceInterval';
static const String updateProfilePhoto = _baseUserUrl + '/profile/photo';
static const String checkUsername = _baseUserUrl + '/CheckUsername';
static const String updateProfile = _baseUserUrl + '/profile/edit';
@@ -54,11 +56,6 @@ class RequestHelper {
MapEntry('tags', _urlListConcatGenerator(ids)),
]);
- static String markRadar(int id) => _baseRadarUrl + '/$id/mark';
- static String radarComments(int id) => _baseRadarUrl + '/$id/comments';
- static String addRadarComment(int id) => _baseRadarUrl + '/$id/comments/add';
- static String feedbackRadarComment(int radarId, int id) =>
- _baseRadarUrl + '/$radarId/comments/$id/feedback';
static String radarDetails(int id, RadarRequestArgs args) =>
_baseRadarUrl +
'/$id' +
@@ -79,11 +76,6 @@ class RequestHelper {
MapEntry('categories', _urlListConcatGenerator(args.categories)),
]);
- static String markNews(int id) => _baseNewsUrl + '/$id/mark';
- static String newsComments(int id) => _baseNewsUrl + '/$id/comments';
- static String addNewsComment(int id) => _baseNewsUrl + '/$id/comments/add';
- static String feedbackNewsComment(int radarId, int id) =>
- _baseNewsUrl + '/$radarId/comments/$id/feedback';
static String newsDetails(int id, NewsRequestArgs args) =>
_baseNewsUrl +
'/$id' +
@@ -102,12 +94,10 @@ class RequestHelper {
MapEntry('search', args.search),
]);
- static String markStudio(int id) => _baseStudioUrl + '/$id/mark';
- static String studioComments(int id) => _baseStudioUrl + '/$id/comments';
- static String addStudioComment(int id) =>
- _baseStudioUrl + '/$id/comments/add';
- static String feedbackStudioComment(int studioId, int id) =>
- _baseStudioUrl + '/$studioId/comments/$id/feedback';
+ static String sudioSlider(String type) =>
+ _baseStudioUrl +
+ '/slider' +
+ _urlConcatGenerator([MapEntry('type', type)]);
static String studioDetails(int id, StudioRequestArgs args) =>
_baseStudioUrl +
'/$id' +
@@ -116,6 +106,7 @@ class RequestHelper {
MapEntry('type', args.type),
MapEntry('order', args.order),
MapEntry('search', args.search),
+ MapEntry('asc', args.asc),
]);
static String studioOverviews({required StudioRequestArgs args}) =>
_baseStudioUrl +
@@ -124,8 +115,19 @@ class RequestHelper {
MapEntry('type', args.type),
MapEntry('order', args.order),
MapEntry('search', args.search),
+ MapEntry('asc', args.asc),
]);
+ static String mark(int id, String type) => baseUrl + '/$type/$id/mark';
+ static String tracking(int id, String type) =>
+ baseUrl + '/$type/$id/tracking';
+ static String comments(int id, String type) =>
+ baseUrl + '/$type/$id/comments';
+ static String feedback(int id, int commentId, String type) =>
+ baseUrl + '/$type/$id/comments/$commentId/feedback';
+ static String addComment(int id, String type) =>
+ baseUrl + '/$type/$id/comments/add';
+
static String _urlConcatGenerator(List> additions) {
String result = '';
additions.removeWhere(
diff --git a/lib/services/storage/storage.dart b/lib/services/storage/storage.dart
index 012936c..ed607e8 100644
--- a/lib/services/storage/storage.dart
+++ b/lib/services/storage/storage.dart
@@ -1,3 +1,6 @@
+import 'dart:typed_data';
+import 'dart:io' as io;
+
import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:universal_html/html.dart';
@@ -6,6 +9,21 @@ class StorageService {
static late String appTempsDir;
static const FlutterSecureStorage _storage = FlutterSecureStorage();
+ static Future createFile({
+ required Uint8List bytes,
+ required String subDirectory,
+ required String name,
+ }) async {
+ final dir = io.Directory(appDocsDir + '/$subDirectory');
+ if (!await dir.exists()) {
+ await dir.create(recursive: true);
+ }
+ final file = await io.File(
+ appDocsDir + '/$subDirectory/$name',
+ ).create(recursive: true);
+ await file.writeAsBytes(bytes);
+ }
+
static Future setValue({
required String key,
required dynamic value,
diff --git a/lib/utils/action_sheet.dart b/lib/utils/action_sheet.dart
index 15ea262..bbb3c2c 100644
--- a/lib/utils/action_sheet.dart
+++ b/lib/utils/action_sheet.dart
@@ -17,6 +17,7 @@ class ActionSheetUtils {
static Future showLogoLoadingIndicator() async {
await showDialog(
+ barrierDismissible: false,
context: context,
builder: (context) => Padding(
padding: EdgeInsets.symmetric(
@@ -79,7 +80,9 @@ class ActionSheetUtils {
isScrollControlled: true,
context: context,
builder: (context) => Container(
- padding: data.hasPadding ? const EdgeInsets.all(20) : EdgeInsets.zero,
+ padding: data.hasPadding
+ ? const EdgeInsets.all(20).copyWith(top: 0)
+ : EdgeInsets.zero,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(
diff --git a/lib/views/authentication/authentication_state.dart b/lib/views/authentication/authentication_state.dart
index ffda950..441d661 100644
--- a/lib/views/authentication/authentication_state.dart
+++ b/lib/views/authentication/authentication_state.dart
@@ -1,7 +1,7 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/alert_data.dart';
-import 'package:didvan/providers/core_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/core.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/utils/action_sheet.dart';
diff --git a/lib/views/authentication/screens/password.dart b/lib/views/authentication/screens/password.dart
index 69cdc42..d558a49 100644
--- a/lib/views/authentication/screens/password.dart
+++ b/lib/views/authentication/screens/password.dart
@@ -1,8 +1,8 @@
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/providers/server_data.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/authentication/authentication_state.dart';
@@ -76,7 +76,9 @@ class _PasswordInputState extends State {
final token = await state.login(userProvider);
if (token != null) {
log(token);
+ ActionSheetUtils.showLogoLoadingIndicator();
await ServerDataProvider.getData();
+ ActionSheetUtils.pop();
Navigator.of(context).pushReplacementNamed(Routes.home);
_showResetPasswordDialog();
}
diff --git a/lib/views/authentication/screens/reset_password.dart b/lib/views/authentication/screens/reset_password.dart
index 9985a95..560ea4b 100644
--- a/lib/views/authentication/screens/reset_password.dart
+++ b/lib/views/authentication/screens/reset_password.dart
@@ -1,4 +1,4 @@
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/authentication/authentication_state.dart';
import 'package:didvan/views/authentication/widgets/authentication_layout.dart';
diff --git a/lib/views/authentication/screens/verification.dart b/lib/views/authentication/screens/verification.dart
index e18e5c3..161d73b 100644
--- a/lib/views/authentication/screens/verification.dart
+++ b/lib/views/authentication/screens/verification.dart
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/user.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';
diff --git a/lib/views/home/comments/comments.dart b/lib/views/home/comments/comments.dart
index 0bf0c72..6e03d73 100644
--- a/lib/views/home/comments/comments.dart
+++ b/lib/views/home/comments/comments.dart
@@ -1,6 +1,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/constants/assets.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/views/home/comments/comments_state.dart';
import 'package:didvan/views/home/comments/widgets/comment_item.dart';
@@ -9,6 +10,7 @@ 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/shimmer_placeholder.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:provider/provider.dart';
@@ -32,7 +34,7 @@ class _CommentsState extends State {
void initState() {
final state = context.read();
state.itemId = widget.pageData['id'];
- state.isRadar = widget.pageData['isRadar'];
+ state.type = widget.pageData['type'];
state.onCommentsChanged = widget.pageData['onCommentsChanged'];
Future.delayed(
Duration.zero,
@@ -41,6 +43,8 @@ class _CommentsState extends State {
super.initState();
}
+ bool get _isPage => widget.pageData['isPage'] != false;
+
@override
Widget build(BuildContext context) {
final bottomViewInset = MediaQuery.of(context).viewInsets.bottom;
@@ -55,12 +59,15 @@ class _CommentsState extends State {
child: Stack(
children: [
DidvanScaffold(
+ physics: const BouncingScrollPhysics(),
backgroundColor: Theme.of(context).colorScheme.surface,
- appBarData: AppBarData(
- hasBack: true,
- title: 'نظرات',
- subtitle: widget.pageData['title'],
- ),
+ appBarData: _isPage
+ ? AppBarData(
+ hasBack: true,
+ title: 'نظرات',
+ subtitle: widget.pageData['title'],
+ )
+ : null,
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 92),
slivers: [
Consumer(
@@ -71,7 +78,17 @@ class _CommentsState extends State {
itemPadding: const EdgeInsets.symmetric(vertical: 16),
childCount: state.comments.length,
placeholder: const _CommentPlaceholder(),
+ centerEmptyState: _isPage,
+ enableEmptyState: state.comments.isEmpty,
+ emptyState: EmptyState(
+ asset: Assets.emptyChat,
+ title: 'اولین نظر را بنویسید...',
+ ),
builder: (context, state, index) => Comment(
+ key: ValueKey(
+ state.comments[index].id.toString() +
+ state.comments[index].text,
+ ),
focusNode: _focusNode,
comment: state.comments[index],
),
diff --git a/lib/views/home/comments/comments_state.dart b/lib/views/home/comments/comments_state.dart
index ead8409..4b02d00 100644
--- a/lib/views/home/comments/comments_state.dart
+++ b/lib/views/home/comments/comments_state.dart
@@ -4,8 +4,8 @@ import 'package:didvan/models/comment/feedback.dart';
import 'package:didvan/models/comment/reply.dart';
import 'package:didvan/models/comment/user.dart';
import 'package:didvan/models/enums.dart';
-import 'package:didvan/providers/core_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/core.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:provider/provider.dart';
@@ -17,19 +17,16 @@ class CommentsState extends CoreProvier {
bool showReplyBox = false;
late void Function(int count) onCommentsChanged;
int _count = 0;
+ late String type;
final List comments = [];
final Map> _feedbackQueue = {};
- bool isRadar = true;
int itemId = 0;
Future getComments() async {
- appState = AppState.busy;
final service = RequestService(
- isRadar
- ? RequestHelper.radarComments(itemId)
- : RequestHelper.newsComments(itemId),
+ RequestHelper.comments(itemId, type),
);
await service.httpGet();
if (service.isSuccess) {
@@ -47,18 +44,40 @@ class CommentsState extends CoreProvier {
appState = AppState.failed;
}
- Future feedback(int id, bool like, bool dislike) async {
+ Future feedback({
+ required int id,
+ required bool like,
+ required bool dislike,
+ required int likeCount,
+ required int dislikeCount,
+ int? replyId,
+ }) async {
_feedbackQueue.addAll({id: MapEntry(like, dislike)});
+ dynamic comment;
+ if (replyId == null) {
+ comment = comments.firstWhere((comment) => comment.id == id);
+ } else {
+ comment = comments
+ .firstWhere((comment) => comment.id == id)
+ .replies
+ .firstWhere((element) => element.id == replyId);
+ }
+
+ if (comment != null) {
+ comment.feedback.like = likeCount;
+ comment.feedback.dislike = dislikeCount;
+ comment.disliked = dislike;
+ comment.liked = like;
+ }
Future.delayed(const Duration(milliseconds: 500), () async {
if (!_feedbackQueue.containsKey(id)) return;
final service = RequestService(
- isRadar
- ? RequestHelper.feedbackRadarComment(itemId, id)
- : RequestHelper.feedbackNewsComment(itemId, id),
- body: {
- 'like': _feedbackQueue[id]!.key,
- 'dislike': _feedbackQueue[id]!.value,
- });
+ RequestHelper.feedback(itemId, id, type),
+ body: {
+ 'like': _feedbackQueue[id]!.key,
+ 'dislike': _feedbackQueue[id]!.value,
+ },
+ );
await service.put();
_feedbackQueue.remove(id);
});
@@ -74,7 +93,7 @@ class CommentsState extends CoreProvier {
createdAt: DateTime.now().toString(),
liked: false,
disliked: false,
- feedback: const FeedbackData(like: 0, dislike: 0),
+ feedback: FeedbackData(like: 0, dislike: 0),
toUser: replyingTo!,
user: UserOverview(
id: user.id,
@@ -92,7 +111,7 @@ class CommentsState extends CoreProvier {
createdAt: DateTime.now().toString(),
liked: false,
disliked: false,
- feedback: const FeedbackData(like: 0, dislike: 0),
+ feedback: FeedbackData(like: 0, dislike: 0),
user: UserOverview(
id: user.id,
fullName: user.fullName,
@@ -119,10 +138,9 @@ class CommentsState extends CoreProvier {
update();
body.addAll({'text': text});
final service = RequestService(
- isRadar
- ? RequestHelper.addRadarComment(itemId)
- : RequestHelper.addNewsComment(itemId),
- body: body);
+ RequestHelper.addComment(itemId, type),
+ body: body,
+ );
await service.post();
if (service.isSuccess) {
diff --git a/lib/views/home/comments/widgets/comment_item.dart b/lib/views/home/comments/widgets/comment_item.dart
index 62fc0a2..4748031 100644
--- a/lib/views/home/comments/widgets/comment_item.dart
+++ b/lib/views/home/comments/widgets/comment_item.dart
@@ -49,7 +49,7 @@ class CommentState extends State {
duration: DesignConfig.lowAnimationDuration,
isVisible: _showSubComments,
child: _commentBuilder(
- isSubComment: true,
+ isReply: true,
comment: _comment.replies[i],
),
),
@@ -57,11 +57,10 @@ class CommentState extends State {
);
}
- Widget _commentBuilder({required comment, bool isSubComment = false}) =>
- Container(
+ Widget _commentBuilder({required comment, bool isReply = false}) => Container(
decoration: BoxDecoration(
border: Border(
- right: isSubComment
+ right: isReply
? BorderSide(color: Theme.of(context).colorScheme.caption)
: BorderSide.none,
),
@@ -69,7 +68,7 @@ class CommentState extends State {
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
- if (isSubComment) const SizedBox(width: 12),
+ if (isReply) const SizedBox(width: 12),
if (comment.user.photo == null)
const Icon(DidvanIcons.avatar_light),
if (comment.user.photo != null)
@@ -99,7 +98,7 @@ class CommentState extends State {
],
),
const SizedBox(height: 8),
- if (isSubComment)
+ if (isReply)
DidvanText(
'پاسخ به ${comment.toUser.fullName}',
style: Theme.of(context).textTheme.caption,
@@ -124,8 +123,8 @@ class CommentState extends State {
color: Theme.of(context).colorScheme.primary,
),
),
- if (!isSubComment) const SizedBox(width: 20),
- if (!isSubComment && comment.replies.isNotEmpty)
+ if (!isReply) const SizedBox(width: 20),
+ if (!isReply && comment.replies.isNotEmpty)
InkWrapper(
onPressed: () => setState(
() => _showSubComments = !_showSubComments,
@@ -154,8 +153,15 @@ class CommentState extends State {
dislikeCount: comment.feedback.dislike,
likeValue: comment.liked,
dislikeValue: comment.disliked,
- onFeedback: (like, dislike) =>
- state.feedback(comment.id, like, dislike),
+ onFeedback: (like, dislike, likeCount, dislikeCount) =>
+ state.feedback(
+ id: _comment.id,
+ like: like,
+ dislike: dislike,
+ likeCount: likeCount,
+ dislikeCount: dislikeCount,
+ replyId: isReply ? comment.id : null,
+ ),
),
],
),
@@ -172,7 +178,8 @@ class _FeedbackButtons extends StatefulWidget {
final int dislikeCount;
final bool likeValue;
final bool dislikeValue;
- final void Function(bool like, bool dislike) onFeedback;
+ final void Function(bool like, bool dislike, int likeCount, int dislikeCount)
+ onFeedback;
const _FeedbackButtons({
Key? key,
required this.onFeedback,
@@ -228,7 +235,8 @@ class _FeedbackButtonsState extends State<_FeedbackButtons> {
}
_likeValue = !_likeValue;
});
- widget.onFeedback(_likeValue, _dislikeValue);
+ widget.onFeedback(
+ _likeValue, _dislikeValue, _likeCount, _dislikeCount);
},
),
const SizedBox(width: 16),
@@ -257,7 +265,8 @@ class _FeedbackButtonsState extends State<_FeedbackButtons> {
}
_dislikeValue = !_dislikeValue;
});
- widget.onFeedback(_likeValue, _dislikeValue);
+ widget.onFeedback(
+ _likeValue, _dislikeValue, _likeCount, _dislikeCount);
},
),
],
diff --git a/lib/views/home/direct/direct.dart b/lib/views/home/direct/direct.dart
index 17ddee1..d47be3c 100644
--- a/lib/views/home/direct/direct.dart
+++ b/lib/views/home/direct/direct.dart
@@ -1,7 +1,7 @@
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';
+import 'package:didvan/providers/server_data.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/views/home/direct/direct_state.dart';
import 'package:didvan/views/home/direct/widgets/message.dart';
diff --git a/lib/views/home/direct/direct_state.dart b/lib/views/home/direct/direct_state.dart
index a4a14aa..20964d5 100644
--- a/lib/views/home/direct/direct_state.dart
+++ b/lib/views/home/direct/direct_state.dart
@@ -3,7 +3,8 @@ import 'dart:io';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/message_data/message_data.dart';
import 'package:didvan/models/message_data/radar_attachment.dart';
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
+import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:flutter/foundation.dart';
@@ -46,6 +47,7 @@ class DirectState extends CoreProvier {
}
Future startRecording() async {
+ text = null;
await _recorder.hasPermission();
if (!kIsWeb) {
Vibrate.feedback(FeedbackType.medium);
@@ -88,6 +90,7 @@ class DirectState extends CoreProvier {
Future sendMessage() async {
if ((text == null || text!.isEmpty) && recordedFile == null) return;
+ MediaService.audioPlayer.stop();
messages.insert(
0,
MessageData(
diff --git a/lib/views/home/direct/widgets/audio_widget.dart b/lib/views/home/direct/widgets/audio_widget.dart
index a33827c..5f1c8bd 100644
--- a/lib/views/home/direct/widgets/audio_widget.dart
+++ b/lib/views/home/direct/widgets/audio_widget.dart
@@ -1,31 +1,39 @@
import 'dart:io';
+import 'package:didvan/config/theme_data.dart';
+import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/views/home/widgets/audio/audio_slider.dart';
-import 'package:didvan/views/home/widgets/player_controller_button.dart';
+import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:flutter/material.dart';
class AudioWidget extends StatelessWidget {
final String? audioUrl;
final File? audioFile;
- const AudioWidget({Key? key, this.audioUrl, this.audioFile})
- : super(key: key);
+ final int id;
+ const AudioWidget({
+ Key? key,
+ this.audioUrl,
+ this.audioFile,
+ required this.id,
+ }) : super(key: key);
@override
Widget build(BuildContext context) {
return StreamBuilder(
- stream: MediaService.audioPlayer.playingStream,
+ stream: MediaService.audioPlayer.isPlaying,
builder: (context, snapshot) {
return Row(
children: [
Expanded(
child: AudioSlider(
- tag: audioUrl ?? audioFile!.path,
+ tag: 'message-$id',
),
),
- AudioControllerButton(
+ _AudioControllerButton(
audioFile: audioFile,
audioUrl: audioUrl,
+ id: id,
),
],
);
@@ -33,3 +41,33 @@ class AudioWidget extends StatelessWidget {
);
}
}
+
+class _AudioControllerButton extends StatelessWidget {
+ final String? audioUrl;
+ final File? audioFile;
+ final int id;
+
+ const _AudioControllerButton(
+ {Key? key, this.audioUrl, this.audioFile, required this.id})
+ : super(key: key);
+
+ bool get _nowPlaying => MediaService.audioPlayerTag == 'message-$id';
+
+ @override
+ Widget build(BuildContext context) {
+ return DidvanIconButton(
+ icon: MediaService.audioPlayer.isPlaying.value && _nowPlaying
+ ? DidvanIcons.pause_circle_solid
+ : DidvanIcons.play_circle_solid,
+ color: Theme.of(context).colorScheme.focusedBorder,
+ onPressed: () {
+ MediaService.handleAudioPlayback(
+ audioSource: audioFile?.path ?? audioUrl,
+ id: id,
+ isNetworkAudio: audioFile == null,
+ isVoiceMessage: true,
+ );
+ },
+ );
+ }
+}
diff --git a/lib/views/home/direct/widgets/message.dart b/lib/views/home/direct/widgets/message.dart
index 49ab63d..ff90eb1 100644
--- a/lib/views/home/direct/widgets/message.dart
+++ b/lib/views/home/direct/widgets/message.dart
@@ -65,6 +65,7 @@ class Message extends StatelessWidget {
AudioWidget(
audioFile: message.audioFile,
audioUrl: message.audio,
+ id: message.id,
),
if (message.radar != null) const DidvanDivider(),
if (message.radar != null) const SizedBox(height: 4),
diff --git a/lib/views/home/direct/widgets/message_box.dart b/lib/views/home/direct/widgets/message_box.dart
index c09d592..554e336 100644
--- a/lib/views/home/direct/widgets/message_box.dart
+++ b/lib/views/home/direct/widgets/message_box.dart
@@ -211,7 +211,10 @@ class _RecordChecking extends StatelessWidget {
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
- child: AudioWidget(audioFile: state.recordedFile!),
+ child: AudioWidget(
+ audioFile: state.recordedFile!,
+ id: 0,
+ ),
),
),
DidvanIconButton(
diff --git a/lib/views/home/hashtag/hashtag.dart b/lib/views/home/hashtag/hashtag.dart
index 24a9a7c..c12e92e 100644
--- a/lib/views/home/hashtag/hashtag.dart
+++ b/lib/views/home/hashtag/hashtag.dart
@@ -1,8 +1,11 @@
+import 'package:didvan/models/requests/studio.dart';
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/overview/news.dart';
+import 'package:didvan/views/home/widgets/overview/podcast.dart';
import 'package:didvan/views/home/widgets/overview/radar.dart';
+import 'package:didvan/views/home/widgets/overview/video.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';
@@ -10,18 +13,20 @@ import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:provider/provider.dart';
class Hashtag extends StatefulWidget {
- final Tag tag;
- const Hashtag({Key? key, required this.tag}) : super(key: key);
+ final Map pageData;
+ const Hashtag({Key? key, required this.pageData}) : super(key: key);
@override
_HashtagState createState() => _HashtagState();
}
class _HashtagState extends State {
+ Tag get _tag => widget.pageData['tag'];
+
@override
void initState() {
final state = context.read();
- state.id = widget.tag.id;
+ state.id = _tag.id;
Future.delayed(Duration.zero, () => state.getTagItems(page: 1));
super.initState();
}
@@ -29,7 +34,7 @@ class _HashtagState extends State {
@override
Widget build(BuildContext context) {
return DidvanScaffold(
- appBarData: AppBarData(title: '#' + widget.tag.label, hasBack: true),
+ appBarData: AppBarData(title: '#' + _tag.label, hasBack: true),
slivers: [
Consumer(
builder: (context, state, child) => SliverStateHandler(
@@ -50,19 +55,38 @@ class _HashtagState extends State {
}
final item = state.items[index];
final type = item.type;
- if (type == 'radar') {
- return RadarOverview(
- radar: item,
- onCommentsChanged: (id, count) => item.comments = count,
- onMarkChanged: (id, value) => item.marked = value,
- );
- } else if (type == 'news') {
- return NewsOverview(
- news: item,
- onMarkChanged: (id, value) => item.marked = value,
- );
+ switch (type) {
+ case 'radar':
+ return RadarOverview(
+ radar: item,
+ onMarkChanged: (_, value, __) =>
+ _changeMark(item.id, value, type),
+ onCommentsChanged: (_, count) => item.comments = count,
+ );
+ case 'news':
+ return NewsOverview(
+ news: item,
+ onMarkChanged: (_, value, __) =>
+ _changeMark(item.id, value, type),
+ );
+ case 'podcast':
+ return PodcastOverview(
+ podcast: item,
+ onMarkChanged: (_, value, __) =>
+ _changeMark(item.id, value, type),
+ studioRequestArgs:
+ const StudioRequestArgs(page: 0, type: 'podcast'),
+ );
+ case 'video':
+ return VideoOverview(
+ video: item,
+ onMarkChanged: (_, value, __) =>
+ _changeMark(item.id, value, type),
+ studioRequestArgs:
+ const StudioRequestArgs(page: 0, type: 'video'),
+ );
}
- return Container();
+ return const SizedBox();
},
childCount:
state.items.length + (state.page != state.lastPage ? 1 : 0),
@@ -72,4 +96,15 @@ class _HashtagState extends State {
],
);
}
+
+ void _changeMark(int id, bool value, String type) {
+ final state = context.read();
+ state.items
+ .firstWhere((element) => element.id == id && element.type == type)
+ .marked = value;
+ state.update();
+ if (type == widget.pageData['type']) {
+ widget.pageData['onMarkChanged'](id, value);
+ }
+ }
}
diff --git a/lib/views/home/hashtag/hashtag_state.dart b/lib/views/home/hashtag/hashtag_state.dart
index fc1e5e7..0c41280 100644
--- a/lib/views/home/hashtag/hashtag_state.dart
+++ b/lib/views/home/hashtag/hashtag_state.dart
@@ -1,6 +1,6 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
@@ -18,13 +18,12 @@ class HashtagState extends CoreProvier {
}
final service = RequestService(RequestHelper.tag(
ids: [id],
- itemId: 1,
- type: 'radar',
limit: 15,
page: page,
));
await service.httpGet();
if (service.isSuccess) {
+ lastPage = service.result['lastPage'];
final contents = service.result['contents'];
for (var i = 0; i < contents.length; i++) {
items.add(OverviewData.fromJson(contents[i]));
diff --git a/lib/views/home/home.dart b/lib/views/home/home.dart
index 0e1d5a8..b9c203e 100644
--- a/lib/views/home/home.dart
+++ b/lib/views/home/home.dart
@@ -5,7 +5,7 @@ import 'package:didvan/views/home/radar/radar.dart';
import 'package:didvan/views/home/settings/settings.dart';
import 'package:didvan/views/home/statistics/statistics.dart';
import 'package:didvan/views/home/studio/studio.dart';
-import 'package:didvan/views/home/widgets/bnb.dart';
+import 'package:didvan/views/widgets/didvan/bnb.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
diff --git a/lib/views/home/home_state.dart b/lib/views/home/home_state.dart
index ed4ea99..c866e0e 100644
--- a/lib/views/home/home_state.dart
+++ b/lib/views/home/home_state.dart
@@ -1,4 +1,4 @@
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
class HomeState extends CoreProvier {
int _currentPageIndex = 2;
diff --git a/lib/views/home/news/news.dart b/lib/views/home/news/news.dart
index 421a045..9387dae 100644
--- a/lib/views/home/news/news.dart
+++ b/lib/views/home/news/news.dart
@@ -66,7 +66,7 @@ class _NewsState extends State {
final news = state.news[index];
return NewsOverview(
news: news,
- onMarkChanged: (id, value) => state.onMarkChanged(id, value),
+ onMarkChanged: state.onMarkChanged,
newsRequestArgs: NewsRequestArgs(
page: state.page,
endDate: state.endDate,
diff --git a/lib/views/home/news/news_details/news_details.dart b/lib/views/home/news/news_details/news_details.dart
index 55d09d4..3becf7e 100644
--- a/lib/views/home/news/news_details/news_details.dart
+++ b/lib/views/home/news/news_details/news_details.dart
@@ -38,12 +38,16 @@ class _NewsDetailsState extends State {
Widget build(BuildContext context) {
return Scaffold(
body: Consumer(
- builder: (context, state, child) => StateHandler(
- onRetry: () => state.getNewsDetails(state.currentNews.id),
- state: state,
- builder: (context, state) => Stack(
- children: [
- if (state.news.isNotEmpty)
+ builder: (context, state, child) => WillPopScope(
+ onWillPop: () async {
+ state.handleTracking(sendRequest: true);
+ return true;
+ },
+ child: StateHandler(
+ onRetry: () => state.getNewsDetails(state.currentNews.id),
+ state: state,
+ builder: (context, state) => Stack(
+ children: [
IgnorePointer(
ignoring: state.isFetchingNewItem,
child: DidvanPageView(
@@ -53,9 +57,10 @@ class _NewsDetailsState extends State {
scrollController: _scrollController,
items: state.news,
currentIndex: state.currentIndex,
+ onMarkChanged: (id, value) =>
+ widget.pageData['onMarkChanged'](id, value),
),
),
- if (state.news.isNotEmpty)
Positioned(
bottom: 0,
left: 0,
@@ -73,7 +78,8 @@ class _NewsDetailsState extends State {
isRadar: false,
),
),
- ],
+ ],
+ ),
),
),
),
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 279bc78..8adbb91 100644
--- a/lib/views/home/news/news_details/news_details_state.dart
+++ b/lib/views/home/news/news_details/news_details_state.dart
@@ -5,7 +5,7 @@ import 'package:didvan/models/enums.dart';
import 'package:didvan/models/news_details_data.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/news.dart';
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
@@ -32,7 +32,7 @@ class NewsDetailsState extends CoreProvier {
}
final service = RequestService(RequestHelper.newsDetails(id, args));
await service.httpGet();
- _handleTracking(sendRequest: isForward != null);
+ handleTracking(sendRequest: isForward != null);
if (service.isSuccess) {
final result = service.result;
final newsItem = NewsDetailsData.fromJson(result['news']);
@@ -88,15 +88,21 @@ class NewsDetailsState extends CoreProvier {
notifyListeners();
}
- Future _handleTracking({bool sendRequest = true}) async {
+ Future handleTracking({bool sendRequest = true}) async {
if (!sendRequest) {
+ _trackingTimerCounter = 0;
_trackingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
_trackingTimerCounter++;
});
return;
}
- //send request
- _trackingTimerCounter = 0;
+ final service = RequestService(
+ RequestHelper.tracking(currentNews.id, 'news'),
+ body: {
+ 'sec': _trackingTimerCounter,
+ },
+ );
+ service.put();
}
Future getRelatedContents() async {
diff --git a/lib/views/home/news/news_state.dart b/lib/views/home/news/news_state.dart
index 47f3366..b022538 100644
--- a/lib/views/home/news/news_state.dart
+++ b/lib/views/home/news/news_state.dart
@@ -1,8 +1,7 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/news.dart';
-import 'package:didvan/providers/core_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
@@ -69,10 +68,11 @@ class NewsState extends CoreProvier {
appState = AppState.failed;
}
- Future onMarkChanged(int id, bool value) async {
+ Future onMarkChanged(int id, bool value, bool shouldUpdate) async {
news.firstWhere((element) => element.id == id).marked = value;
- notifyListeners();
- UserProvider.changeNewsMark(id, value);
+ if (shouldUpdate) {
+ notifyListeners();
+ }
}
bool get isFiltering => startDate != null || endDate != null;
diff --git a/lib/views/home/radar/radar.dart b/lib/views/home/radar/radar.dart
index 6058543..6384c96 100644
--- a/lib/views/home/radar/radar.dart
+++ b/lib/views/home/radar/radar.dart
@@ -139,7 +139,7 @@ class _RadarState extends State {
final radar = state.radars[index];
return RadarOverview(
radar: radar,
- onMarkChanged: (id, value) => state.changeMark(id, value),
+ onMarkChanged: state.changeMark,
onCommentsChanged: (id, count) =>
state.onCommentsChanged(id, count),
radarRequestArgs: RadarRequestArgs(
diff --git a/lib/views/home/radar/radar_details/radar_details.dart b/lib/views/home/radar/radar_details/radar_details.dart
index 1c9eb11..c371e5f 100644
--- a/lib/views/home/radar/radar_details/radar_details.dart
+++ b/lib/views/home/radar/radar_details/radar_details.dart
@@ -38,12 +38,16 @@ class _RadarDetailsState extends State {
Widget build(BuildContext context) {
return Scaffold(
body: Consumer(
- builder: (context, state, child) => StateHandler(
- onRetry: () => state.getRadarDetails(widget.pageData['id']),
- state: state,
- builder: (context, state) => Stack(
- children: [
- if (state.radars.isNotEmpty)
+ builder: (context, state, child) => WillPopScope(
+ onWillPop: () async {
+ state.handleTracking(sendRequest: true);
+ return true;
+ },
+ child: StateHandler(
+ onRetry: () => state.getRadarDetails(widget.pageData['id']),
+ state: state,
+ builder: (context, state) => Stack(
+ children: [
IgnorePointer(
ignoring: state.isFetchingNewItem,
child: DidvanPageView(
@@ -53,9 +57,10 @@ class _RadarDetailsState extends State {
scrollController: _scrollController,
items: state.radars,
currentIndex: state.currentIndex,
+ onMarkChanged: (id, value) =>
+ widget.pageData['onMarkChanged']?.call(id, value),
),
),
- if (state.radars.isNotEmpty)
Positioned(
bottom: 0,
left: 0,
@@ -80,7 +85,8 @@ class _RadarDetailsState extends State {
},
),
),
- ],
+ ],
+ ),
),
),
),
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 37ebb42..81240a3 100644
--- a/lib/views/home/radar/radar_details/radar_details_state.dart
+++ b/lib/views/home/radar/radar_details/radar_details_state.dart
@@ -5,7 +5,7 @@ import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/radar_details_data.dart';
import 'package:didvan/models/requests/radar.dart';
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
@@ -38,7 +38,7 @@ class RadarDetailsState extends CoreProvier {
}
final service = RequestService(RequestHelper.radarDetails(id, args));
await service.httpGet();
- _handleTracking(sendRequest: isForward != null);
+ handleTracking(sendRequest: isForward != null);
if (service.isSuccess) {
final result = service.result;
final radar = RadarDetailsData.fromJson(result['radar']);
@@ -121,15 +121,21 @@ class RadarDetailsState extends CoreProvier {
notifyListeners();
}
- Future _handleTracking({bool sendRequest = true}) async {
+ Future handleTracking({bool sendRequest = true}) async {
if (!sendRequest) {
+ _trackingTimerCounter = 0;
_trackingTimer = Timer.periodic(const Duration(seconds: 1), (timer) {
_trackingTimerCounter++;
});
return;
}
- //send request
- _trackingTimerCounter = 0;
+ final service = RequestService(
+ RequestHelper.tracking(currentRadar.id, 'radar'),
+ body: {
+ 'sec': _trackingTimerCounter,
+ },
+ );
+ service.put();
}
@override
diff --git a/lib/views/home/radar/radar_state.dart b/lib/views/home/radar/radar_state.dart
index 75318ee..fb293a2 100644
--- a/lib/views/home/radar/radar_state.dart
+++ b/lib/views/home/radar/radar_state.dart
@@ -3,8 +3,7 @@ import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/models/view/radar_category.dart';
-import 'package:didvan/providers/core_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
@@ -82,10 +81,11 @@ class RadarState extends CoreProvier {
appState = AppState.failed;
}
- Future changeMark(int id, bool value) async {
+ Future changeMark(int id, bool value, bool shouldUpdate) async {
radars.firstWhere((element) => element.id == id).marked = value;
- notifyListeners();
- UserProvider.changeRadarMark(id, value);
+ if (shouldUpdate) {
+ notifyListeners();
+ }
}
void onCommentsChanged(int id, int count) {
diff --git a/lib/views/home/settings/bookmarks/bookmark_state.dart b/lib/views/home/settings/bookmarks/bookmark_state.dart
index 063dc4b..85d5a44 100644
--- a/lib/views/home/settings/bookmarks/bookmark_state.dart
+++ b/lib/views/home/settings/bookmarks/bookmark_state.dart
@@ -1,7 +1,6 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
-import 'package:didvan/providers/core_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
@@ -41,16 +40,6 @@ class BookmarksState extends CoreProvier {
void onMarkChanged(int id, bool value) {
if (value) return;
- final type = bookmarks.firstWhere((element) => element.id == id).type;
- switch (type) {
- case 'radar':
- UserProvider.changeRadarMark(id, value);
- break;
- case 'news':
- UserProvider.changeNewsMark(id, value);
- break;
- default:
- }
bookmarks.removeWhere((element) => element.id == id);
notifyListeners();
}
diff --git a/lib/views/home/settings/bookmarks/bookmarks.dart b/lib/views/home/settings/bookmarks/bookmarks.dart
index 520d880..81ddeab 100644
--- a/lib/views/home/settings/bookmarks/bookmarks.dart
+++ b/lib/views/home/settings/bookmarks/bookmarks.dart
@@ -131,7 +131,15 @@ class _BookmarksState extends State {
void _onCategorySelected(String type) {
FocusScope.of(context).unfocus();
- Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: type);
+ Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: {
+ 'type': type,
+ 'onDeleted': (int id) {
+ final state = context.read();
+ state.bookmarks
+ .removeWhere((element) => element.id == id && element.type == type);
+ state.update();
+ },
+ });
}
void _onChanged(String value) {
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 2291e1a..0ecc547 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,10 @@
+import 'package:didvan/models/requests/studio.dart';
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/overview/news.dart';
+import 'package:didvan/views/home/widgets/overview/podcast.dart';
import 'package:didvan/views/home/widgets/overview/radar.dart';
+import 'package:didvan/views/home/widgets/overview/video.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';
@@ -9,7 +12,8 @@ import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class FilteredBookmarks extends StatefulWidget {
- const FilteredBookmarks({Key? key}) : super(key: key);
+ final void Function(int id)? onDeleted;
+ const FilteredBookmarks({Key? key, this.onDeleted}) : super(key: key);
@override
_FilteredBookmarksState createState() => _FilteredBookmarksState();
@@ -67,8 +71,26 @@ class _FilteredBookmarksState extends State {
hasUnmarkConfirmation: true,
);
}
- return NewsOverview(
- news: state.bookmarks[index],
+ if (state.type == 'news') {
+ return NewsOverview(
+ news: state.bookmarks[index],
+ onMarkChanged: _onBookmarkChanged,
+ hasUnmarkConfirmation: true,
+ );
+ }
+ if (state.type == 'podcast') {
+ return PodcastOverview(
+ studioRequestArgs:
+ const StudioRequestArgs(page: 0, type: 'podcast'),
+ podcast: state.bookmarks[index],
+ onMarkChanged: _onBookmarkChanged,
+ hasUnmarkConfirmation: true,
+ );
+ }
+ return VideoOverview(
+ studioRequestArgs:
+ const StudioRequestArgs(page: 0, type: 'video'),
+ video: state.bookmarks[index],
onMarkChanged: _onBookmarkChanged,
hasUnmarkConfirmation: true,
);
@@ -81,9 +103,10 @@ class _FilteredBookmarksState extends State {
);
}
- Future _onBookmarkChanged(int id, bool value) async {
+ Future _onBookmarkChanged(int id, bool value, bool shouldUpdate) async {
if (value) return;
final state = context.read();
state.onMarkChanged(id, false);
+ widget.onDeleted?.call(id);
}
}
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 7aa0463..0940883 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
@@ -1,13 +1,10 @@
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
-import 'package:didvan/providers/core_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
class FilteredBookmarksState extends CoreProvier {
- String search = '';
- String lastSearch = '';
final List bookmarks = [];
final String type;
int page = 1;
@@ -15,17 +12,8 @@ class FilteredBookmarksState extends CoreProvier {
FilteredBookmarksState(this.type);
- bool get searching => search != '';
-
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';
@@ -56,15 +44,6 @@ class FilteredBookmarksState extends CoreProvier {
}
void onMarkChanged(int id, bool value) {
- switch (type) {
- case 'radar':
- UserProvider.changeRadarMark(id, value);
- break;
- case 'news':
- UserProvider.changeNewsMark(id, value);
- break;
- default:
- }
bookmarks.removeWhere((element) => element.id == id);
notifyListeners();
}
diff --git a/lib/views/home/settings/direct_list/direct_list.dart b/lib/views/home/settings/direct_list/direct_list.dart
index 7c8d264..f0f0966 100644
--- a/lib/views/home/settings/direct_list/direct_list.dart
+++ b/lib/views/home/settings/direct_list/direct_list.dart
@@ -36,8 +36,11 @@ class _DirectListState extends State {
title: 'پیامها',
trailing: state.unreadCount == 0
? null
- : DidvanBadge(
- text: state.unreadCount.toString(),
+ : Padding(
+ padding: const EdgeInsets.only(left: 20),
+ child: DidvanBadge(
+ text: state.unreadCount.toString(),
+ ),
),
),
slivers: [
diff --git a/lib/views/home/settings/direct_list/direct_list_state.dart b/lib/views/home/settings/direct_list/direct_list_state.dart
index 728305a..40aaee9 100644
--- a/lib/views/home/settings/direct_list/direct_list_state.dart
+++ b/lib/views/home/settings/direct_list/direct_list_state.dart
@@ -1,6 +1,6 @@
import 'package:didvan/models/chat_room/chat_room.dart';
import 'package:didvan/models/enums.dart';
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
@@ -15,7 +15,6 @@ class DirectListState extends CoreProvier {
}
Future getDirectsList() async {
- appState = AppState.busy;
final RequestService service = RequestService(RequestHelper.directs);
await service.httpGet();
if (service.isSuccess) {
diff --git a/lib/views/home/settings/direct_list/widgets/direct_item.dart b/lib/views/home/settings/direct_list/widgets/direct_item.dart
index 6aa9a45..da51414 100644
--- a/lib/views/home/settings/direct_list/widgets/direct_item.dart
+++ b/lib/views/home/settings/direct_list/widgets/direct_item.dart
@@ -3,10 +3,12 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/chat_room/chat_room.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/date_time.dart';
+import 'package:didvan/views/home/settings/direct_list/direct_list_state.dart';
import 'package:didvan/views/widgets/didvan/badge.dart';
import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
class ChatRoomItem extends StatelessWidget {
final ChatRoom chatRoom;
@@ -20,7 +22,10 @@ class ChatRoomItem extends StatelessWidget {
Routes.direct,
arguments: {'type': chatRoom.type},
);
+ final state = context.read();
+ int unreadCount = chatRoom.unread;
chatRoom.unread = 0;
+ state.unreadCount -= unreadCount;
},
child: Container(
color: Colors.transparent,
diff --git a/lib/views/home/settings/general_settings/settings.dart b/lib/views/home/settings/general_settings/settings.dart
index 6dcfd2c..41a9947 100644
--- a/lib/views/home/settings/general_settings/settings.dart
+++ b/lib/views/home/settings/general_settings/settings.dart
@@ -6,8 +6,9 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/models/view/app_bar_data.dart';
-import 'package:didvan/providers/theme_provider.dart';
+import 'package:didvan/providers/theme.dart';
import 'package:didvan/utils/action_sheet.dart';
+import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/home/settings/general_settings/settings_state.dart';
import 'package:didvan/views/home/widgets/menu_item.dart';
import 'package:didvan/views/widgets/didvan/card.dart';
@@ -36,6 +37,9 @@ class _GeneralSettingsState extends State {
return 'کوچک';
}
+ int _intervalStart = 0;
+ int _intervalEnd = 24;
+
@override
Widget build(BuildContext context) {
return Consumer(
@@ -50,7 +54,13 @@ class _GeneralSettingsState extends State {
title: 'زمان دریافت اعلان',
onTap: () => _pickTimeRange(context),
icon: DidvanIcons.notification_regular,
- suffix: state.notificationTimeRange[0],
+ suffix: DateTimeUtils.normalizeTimeDuration(
+ Duration(minutes: state.notificationTimeRange[1]),
+ ) +
+ ' - ' +
+ DateTimeUtils.normalizeTimeDuration(
+ Duration(minutes: state.notificationTimeRange[0]),
+ ),
),
),
const ItemTitle(
@@ -183,6 +193,9 @@ class _GeneralSettingsState extends State {
}
Future _pickTimeRange(BuildContext context) async {
+ final state = context.read();
+ _intervalStart = state.notificationTimeRange[0];
+ _intervalEnd = state.notificationTimeRange[1];
ActionSheetUtils.showBottomSheet(
data: ActionSheetData(
content: Row(
@@ -198,6 +211,9 @@ class _GeneralSettingsState extends State {
),
title: 'زمان دریافت اعلان',
titleIcon: DidvanIcons.notification_regular,
+ onConfirmed: () {
+ state.notificationTimeRange = [_intervalStart, _intervalEnd];
+ },
),
);
}
@@ -222,7 +238,9 @@ class _GeneralSettingsState extends State {
color: Theme.of(context).colorScheme.border,
),
),
- child: DidvanText(state.notificationTimeRange[index]),
+ child: DidvanText(DateTimeUtils.normalizeTimeDuration(
+ Duration(minutes: index == 0 ? _intervalStart : _intervalEnd),
+ )),
),
),
),
@@ -230,7 +248,6 @@ class _GeneralSettingsState extends State {
}
Future _openTimePicker(BuildContext context, int index) async {
- final GeneralSettingsState state = context.read();
await Navigator.of(context).push(
showPicker(
okText: 'تایید',
@@ -240,8 +257,9 @@ class _GeneralSettingsState extends State {
cancelStyle: Theme.of(context).textTheme.bodyText2!,
unselectedColor: Theme.of(context).colorScheme.text,
blurredBackground: true,
- hourLabel: 'ساعت',
- minuteLabel: 'دقیقه',
+ disableMinute: true,
+ hourLabel: ':',
+ minuteLabel: '',
is24HrFormat: true,
iosStylePicker: true,
minuteInterval: MinuteInterval.FIFTEEN,
@@ -249,12 +267,11 @@ class _GeneralSettingsState extends State {
value: const TimeOfDay(hour: 0, minute: 0),
themeData: Theme.of(context),
onChange: (time) {
- state.notificationTimeRange = state.notificationTimeRange
- ..replaceRange(
- index,
- index + 1,
- ['${time.hour}:${time.minute}'],
- );
+ if (index == 0) {
+ _intervalStart = time.hour;
+ return;
+ }
+ _intervalEnd = time.hour;
},
),
);
diff --git a/lib/views/home/settings/general_settings/settings_state.dart b/lib/views/home/settings/general_settings/settings_state.dart
index 19d5d5b..8f5249f 100644
--- a/lib/views/home/settings/general_settings/settings_state.dart
+++ b/lib/views/home/settings/general_settings/settings_state.dart
@@ -1,5 +1,7 @@
import 'package:didvan/models/enums.dart';
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
+import 'package:didvan/services/network/request.dart';
+import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/services/storage/storage.dart';
class GeneralSettingsState extends CoreProvier {
@@ -7,24 +9,26 @@ class GeneralSettingsState extends CoreProvier {
getSettingsFromStorage();
}
- List _notificationTimeRange = ['00:00', '23:59'];
+ List _notificationTimeRange = [0, 24];
String _fontFamily = 'Dana-FA';
double _fontSizeScale = 1;
String _brightness = 'light';
- set notificationTimeRange(List value) {
+ set notificationTimeRange(List value) {
_notificationTimeRange = value;
StorageService.setValue(
key: 'notificationTimeRangeStart',
value: value[0],
);
StorageService.setValue(
- key: 'notificationTimeRangeStart',
+ key: 'notificationTimeRangeEnd',
value: value[1],
);
+ notifyListeners();
+ _setSilenceInterval();
}
- List get notificationTimeRange => _notificationTimeRange;
+ List get notificationTimeRange => _notificationTimeRange;
set fontFamily(String value) {
_fontFamily = value;
@@ -59,12 +63,27 @@ class GeneralSettingsState extends CoreProvier {
String get brightness => _brightness;
+ Future _setSilenceInterval() async {
+ final service = RequestService(RequestHelper.silenceInterval, body: {
+ 'start': notificationTimeRange[0],
+ 'end': notificationTimeRange[1]
+ });
+ await service.put();
+ }
+
Future getSettingsFromStorage() async {
appState = AppState.busy;
- _notificationTimeRange[0] =
- await StorageService.getValue(key: 'notificationTimeRangeStart');
- _notificationTimeRange[1] =
- await StorageService.getValue(key: 'notificationTimeRangeEnd');
+ try {
+ _notificationTimeRange[0] = int.parse(
+ await StorageService.getValue(key: 'notificationTimeRangeStart'),
+ );
+ _notificationTimeRange[1] = int.parse(
+ await StorageService.getValue(key: 'notificationTimeRangeEnd'),
+ );
+ } catch (e) {
+ notificationTimeRange = [0, 0];
+ }
+
_fontFamily = await StorageService.getValue(key: 'fontFamily');
_brightness = await StorageService.getValue(key: 'brightness');
final scale = await StorageService.getValue(key: 'fontSizeScale');
diff --git a/lib/views/home/settings/profile/profile.dart b/lib/views/home/settings/profile/profile.dart
index 5b666e7..ca9acf6 100644
--- a/lib/views/home/settings/profile/profile.dart
+++ b/lib/views/home/settings/profile/profile.dart
@@ -2,7 +2,7 @@ import 'dart:async';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/models/view/app_bar_data.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/home/settings/profile/widgets/profile_photo.dart';
import 'package:didvan/views/home/widgets/menu_item.dart';
diff --git a/lib/views/home/settings/profile/widgets/profile_photo.dart b/lib/views/home/settings/profile/widgets/profile_photo.dart
index 62f1018..00e1e01 100644
--- a/lib/views/home/settings/profile/widgets/profile_photo.dart
+++ b/lib/views/home/settings/profile/widgets/profile_photo.dart
@@ -5,7 +5,7 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/models/view/alert_data.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/home/widgets/menu_item.dart';
@@ -149,7 +149,7 @@ class _ProfilePhotoState extends State {
cancelButtonTitle: 'بازگشت',
),
androidUiSettings: const AndroidUiSettings(toolbarTitle: 'برش تصویر'),
- compressQuality: 70,
+ compressQuality: 30,
);
if (file == null) return;
}
diff --git a/lib/views/home/settings/settings.dart b/lib/views/home/settings/settings.dart
index 63c4439..e923da5 100644
--- a/lib/views/home/settings/settings.dart
+++ b/lib/views/home/settings/settings.dart
@@ -1,6 +1,6 @@
import 'package:didvan/constants/app_icons.dart';
-import 'package:didvan/providers/theme_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/theme.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/storage/storage.dart';
import 'package:didvan/views/home/widgets/logo_app_bar.dart';
@@ -108,7 +108,7 @@ class Settings extends StatelessWidget {
),
const SizedBox(height: 16),
DidvanText(
- 'نسخه نرمافزار: 1.1.4',
+ 'نسخه نرمافزار: 1.5.0',
style: Theme.of(context).textTheme.caption,
),
],
diff --git a/lib/views/home/studio/studio.dart b/lib/views/home/studio/studio.dart
index 5f5f4ca..296a552 100644
--- a/lib/views/home/studio/studio.dart
+++ b/lib/views/home/studio/studio.dart
@@ -1,26 +1,227 @@
-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/widgets/state_handlers/empty_state.dart';
-import 'package:flutter/material.dart';
+import 'dart:async';
-class Studio extends StatelessWidget {
+import 'package:didvan/config/design_config.dart';
+import 'package:didvan/constants/app_icons.dart';
+import 'package:didvan/models/enums.dart';
+import 'package:didvan/models/requests/studio.dart';
+import 'package:didvan/models/view/action_sheet_data.dart';
+import 'package:didvan/routes/routes.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/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/animated_visibility.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/empty_result.dart';
+import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class Studio extends StatefulWidget {
const Studio({Key? key}) : super(key: key);
+ @override
+ State createState() => _StudioState();
+}
+
+class _StudioState extends State {
+ final _focusNode = FocusNode();
+ Timer? _timer;
+
+ @override
+ void initState() {
+ context.read().init();
+ super.initState();
+ }
+
@override
Widget build(BuildContext context) {
- return Column(
- children: [
- const LogoAppBar(),
- Expanded(
- child: EmptyState(
- asset: Assets.emptyStudio,
- title: 'استودیو آینده',
- subtitle: 'به زودی...',
- titleColor: Theme.of(context).colorScheme.title,
+ return Consumer(
+ builder: (context, state, child) => 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: () => Navigator.of(context).pushNamed(
+ Routes.filteredBookmarks,
+ arguments: {'type': state.type, 'onDeleted': (_) {}},
+ ),
+ ),
+ ),
+ ],
+ ),
+ ),
+ const SliverToBoxAdapter(
+ child: StudioTabBar(),
+ ),
+ if (state.appState != AppState.failed)
+ SliverToBoxAdapter(
+ child: Padding(
+ padding: const EdgeInsets.all(16.0),
+ child: SearchField(
+ title: 'استودیو',
+ onChanged: _onChanged,
+ focusNode: _focusNode,
+ ),
+ ),
+ ),
+ if (state.appState != AppState.failed)
+ SliverToBoxAdapter(
+ child: AnimatedVisibility(
+ isVisible: !state.searching,
+ duration: DesignConfig.lowAnimationDuration,
+ child: const StudioSlider(),
+ ),
+ ),
+ if (state.appState != AppState.failed && state.studios.isNotEmpty)
+ const SliverPadding(
+ padding: EdgeInsets.symmetric(horizontal: 16),
+ sliver: SliverToBoxAdapter(
+ child: DidvanDivider(
+ verticalPadding: 0,
+ ),
+ ),
+ ),
+ if (state.appState != AppState.failed && state.studios.isNotEmpty)
+ SliverPadding(
+ padding: const EdgeInsets.symmetric(horizontal: 16),
+ sliver: SliverToBoxAdapter(
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.spaceBetween,
+ children: [
+ AnimatedVisibility(
+ isVisible: !state.searching,
+ duration: DesignConfig.lowAnimationDuration,
+ child: ItemTitle(title: state.orderString),
+ ),
+ DidvanIconButton(
+ gestureSize: 36,
+ icon: DidvanIcons.sort_regular,
+ onPressed: _showSortDialog,
+ ),
+ ],
+ ),
+ ),
+ ),
+ SliverStateHandler(
+ state: state,
+ itemPadding: const EdgeInsets.only(
+ bottom: 8,
+ left: 16,
+ right: 16,
+ ),
+ emptyState: EmptyResult(
+ onNewSearch: () => _focusNode.requestFocus(),
+ ),
+ centerEmptyState: true,
+ enableEmptyState: state.studios.isEmpty,
+ placeholder: state.videosSelected
+ ? VideoOverview.placeHolder
+ : PodcastOverview.placeholder,
+ builder: (context, state, index) => state.videosSelected
+ ? VideoOverview(
+ onMarkChanged: state.changeMark,
+ hasUnmarkConfirmation: false,
+ video: state.studios[index],
+ studioRequestArgs: StudioRequestArgs(
+ page: state.page,
+ order: state.order,
+ search: state.search,
+ type: state.type,
+ asc: state.selectedSortTypeIndex == 1,
+ ),
+ )
+ : PodcastOverview(
+ podcast: state.studios[index],
+ onMarkChanged: state.changeMark,
+ studioRequestArgs: StudioRequestArgs(
+ page: state.page,
+ order: state.order,
+ search: state.search,
+ type: state.type,
+ asc: state.selectedSortTypeIndex == 1,
+ ),
+ ),
+ childCount: state.studios.length,
+ onRetry: () => state.getStudios(page: 1),
+ ),
+ ],
+ ),
+ );
+ }
+
+ void _onChanged(String value) {
+ final state = context.read();
+ if (value.length < 4 && value.isNotEmpty || state.lastSearch == value) {
+ return;
+ }
+ _timer?.cancel();
+ _timer = Timer(const Duration(seconds: 1), () {
+ state.search = value;
+ state.getStudios(page: 1);
+ });
+ }
+
+ 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,
+ ),
+ const SizedBox(height: 24),
+ DidvanRadialButton(
+ title: 'پربحثترینها',
+ onSelected: () => setState(
+ () => state.selectedSortTypeIndex = 3,
+ ),
+ value: state.selectedSortTypeIndex == 3,
+ ),
+ ],
),
),
- ],
+ title: 'مرتبسازی',
+ titleIcon: DidvanIcons.sort_regular,
+ hasDismissButton: false,
+ confrimTitle: 'مرتب سازی',
+ onConfirmed: () => state.getStudios(page: 1),
+ ),
);
}
}
diff --git a/lib/views/home/studio/studio_details/studio_details.dart b/lib/views/home/studio/studio_details/studio_details.dart
deleted file mode 100644
index 20ce528..0000000
--- a/lib/views/home/studio/studio_details/studio_details.dart
+++ /dev/null
@@ -1,170 +0,0 @@
-import 'dart:io';
-
-import 'package:didvan/config/design_config.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/widgets/didvan/scaffold.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;
-
- @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(
- backgroundColor: Theme.of(context).colorScheme.surface,
- padding: EdgeInsets.zero,
- appBarData: _isFullScreen
- ? null
- : AppBarData(
- isSmall: true,
- title: state.currentStudio.title,
- ),
- children: [
- SizedBox(
- width: ds.width,
- height: _isFullScreen ? ds.height : ds.width * 9 / 16,
- child: Stack(
- children: [
- WebView(
- allowsInlineMediaPlayback: true,
- 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,
- ),
- ),
- ),
- ],
- ),
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/views/home/studio/studio_details/studio_details.mobile.dart b/lib/views/home/studio/studio_details/studio_details.mobile.dart
new file mode 100644
index 0000000..2c34596
--- /dev/null
+++ b/lib/views/home/studio/studio_details/studio_details.mobile.dart
@@ -0,0 +1,140 @@
+import 'package:better_player/better_player.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/studio/studio_details/widgets/studio_details_widget.dart';
+import 'package:didvan/views/home/widgets/bookmark_button.dart';
+import 'package:didvan/views/widgets/didvan/app_bar.dart';
+import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.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 {
+ int _currentlyPlayingId = 0;
+ late BetterPlayerController _betterPlayerController;
+
+ @override
+ void initState() {
+ _betterPlayerController = BetterPlayerController(
+ const BetterPlayerConfiguration(
+ aspectRatio: 16 / 9,
+ showPlaceholderUntilPlay: true,
+ autoDispose: false,
+ fullScreenAspectRatio: 16 / 9,
+ ),
+ );
+ final state = context.read();
+ state.args = widget.pageData['args'];
+ Future.delayed(
+ Duration.zero,
+ () => state.getStudioDetails(widget.pageData['id']),
+ );
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final d = MediaQuery.of(context);
+ return Consumer(
+ builder: (context, state, child) => StateHandler(
+ state: state,
+ onRetry: () {
+ try {
+ state.getStudioDetails(state.studio.id);
+ } catch (e) {
+ state.getStudioDetails(widget.pageData['id']);
+ }
+ },
+ builder: (context, state) {
+ if (_currentlyPlayingId != state.studio.id) {
+ _handleVideoPlayback(state);
+ }
+ return WillPopScope(
+ onWillPop: () async {
+ if (MediaService.currentPodcast != null) {
+ state.studio = MediaService.currentPodcast!;
+ }
+ return true;
+ },
+ child: SafeArea(
+ child: Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ appBar: PreferredSize(
+ preferredSize: const Size.fromHeight(56),
+ child: DidvanAppBar(
+ appBarData: AppBarData(
+ trailing: BookmarkButton(
+ itemId: state.studio.id,
+ type: 'video',
+ value: state.studio.marked,
+ onMarkChanged: (value) {
+ widget.pageData['onMarkChanged'](
+ state.studio.id, value);
+ },
+ gestureSize: 48,
+ ),
+ isSmall: true,
+ title: state.studio.title,
+ ),
+ ),
+ ),
+ body: SingleChildScrollView(
+ physics: const NeverScrollableScrollPhysics(),
+ child: SizedBox(
+ height: d.size.height - d.padding.top - 56,
+ child: Column(
+ children: [
+ BetterPlayer(controller: _betterPlayerController),
+ Expanded(
+ child: StudioDetailsWidget(
+ onMarkChanged: (id, value) => widget
+ .pageData['onMarkChanged'](id, value, true),
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ Future _handleVideoPlayback(state) async {
+ final betterPlayerDataSource = BetterPlayerDataSource(
+ BetterPlayerDataSourceType.network,
+ state.studio.link,
+ );
+ await _betterPlayerController.clearCache();
+ await _betterPlayerController.setupDataSource(betterPlayerDataSource);
+ _betterPlayerController.setBetterPlayerControlsConfiguration(
+ BetterPlayerControlsConfiguration(
+ enablePlaybackSpeed: false,
+ enableSubtitles: false,
+ enableAudioTracks: false,
+ progressBarPlayedColor: Theme.of(context).colorScheme.secondary,
+ progressBarHandleColor: Theme.of(context).colorScheme.secondary,
+ ),
+ );
+ _currentlyPlayingId = state.studio.id;
+ }
+
+ @override
+ void dispose() {
+ _betterPlayerController.pause();
+ _betterPlayerController.dispose();
+ super.dispose();
+ }
+}
diff --git a/lib/views/home/studio/studio_details/studio_details.web.dart b/lib/views/home/studio/studio_details/studio_details.web.dart
new file mode 100644
index 0000000..b944905
--- /dev/null
+++ b/lib/views/home/studio/studio_details/studio_details.web.dart
@@ -0,0 +1,111 @@
+import 'dart:ui' as ui;
+
+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/studio/studio_details/widgets/studio_details_widget.dart';
+import 'package:didvan/views/home/widgets/bookmark_button.dart';
+import 'package:didvan/views/widgets/didvan/app_bar.dart';
+import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
+import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
+import 'package:universal_html/html.dart' as html;
+
+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 {
+ @override
+ void initState() {
+ final state = context.read();
+ state.args = widget.pageData['args'];
+ Future.delayed(
+ Duration.zero,
+ () => state.getStudioDetails(widget.pageData['id']),
+ );
+ super.initState();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ final d = MediaQuery.of(context);
+ return Consumer(
+ builder: (context, state, child) => StateHandler(
+ state: state,
+ onRetry: () => state.getStudioDetails(state.studio.id),
+ builder: (context, state) {
+ // ignore: undefined_prefixed_name
+ ui.platformViewRegistry.registerViewFactory(
+ "video",
+ (int viewId) => html.IFrameElement()
+ ..allowFullscreen = true
+ ..src = Uri.dataFromString(
+ '' +
+ state.studio.iframe!,
+ mimeType: 'text/html',
+ ).toString()
+ ..style.border = 'none',
+ );
+ return WillPopScope(
+ onWillPop: () async {
+ if (MediaService.currentPodcast != null) {
+ state.studio = MediaService.currentPodcast!;
+ }
+ return true;
+ },
+ child: SafeArea(
+ child: Scaffold(
+ backgroundColor: Theme.of(context).colorScheme.surface,
+ appBar: PreferredSize(
+ preferredSize: const Size.fromHeight(56),
+ child: DidvanAppBar(
+ appBarData: AppBarData(
+ trailing: BookmarkButton(
+ itemId: state.studio.id,
+ type: 'video',
+ value: state.studio.marked,
+ onMarkChanged: (value) {
+ widget.pageData['onMarkChanged'](
+ state.studio.id, value);
+ },
+ gestureSize: 48,
+ ),
+ isSmall: true,
+ title: state.studio.title,
+ ),
+ ),
+ ),
+ body: SingleChildScrollView(
+ physics: const NeverScrollableScrollPhysics(),
+ child: SizedBox(
+ height: d.size.height - d.padding.top - 56,
+ child: Column(
+ children: [
+ const AspectRatio(
+ aspectRatio: 16 / 9,
+ child: HtmlElementView(viewType: 'video'),
+ ),
+ Expanded(
+ child: StudioDetailsWidget(
+ onMarkChanged: (id, value) => widget
+ .pageData['onMarkChanged'](id, value, 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 0a6205b..1d4e994 100644
--- a/lib/views/home/studio/studio_details/studio_details_state.dart
+++ b/lib/views/home/studio/studio_details/studio_details_state.dart
@@ -1,141 +1,170 @@
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/providers/core.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 StudioDetailsData studio;
+ StudioDetailsData? nextStudio;
+ StudioDetailsData? prevStudio;
late int initialIndex;
late StudioRequestArgs args;
- int _selectedDetailsIndex = 0;
- bool isFetchingNewItem = false;
+ StudioRequestArgs? podcastArgs;
final List relatedQueue = [];
+ bool _positionListenerActivated = false;
+ AppState alongSideState = AppState.idle;
- int _currentIndex = 0;
- int get currentIndex => _currentIndex;
+ int _selectedDetailsIndex = 0;
+ Timer? timer;
+ int timerValue = 10;
+ bool stopOnPodcastEnds = false;
int get selectedDetailsIndex => _selectedDetailsIndex;
set selectedDetailsIndex(int value) {
_selectedDetailsIndex = value;
+ if (value == 2) {
+ getRelatedContents();
+ }
notifyListeners();
}
- StudioDetailsData get currentStudio {
- try {
- return studios[_currentIndex]!;
- } catch (e) {
- return studios[_currentIndex + 1]!;
- }
- }
-
- Future getStudioDetails(int id,
- {bool? isForward, StudioRequestArgs? args}) async {
+ Future getStudioDetails(
+ int id, {
+ StudioRequestArgs? args,
+ bool? isForward,
+ bool fetchOnly = false,
+ }) async {
if (args != null) {
this.args = args;
}
+ if (this.args.type == 'podcast') {
+ podcastArgs = this.args;
+ }
+ if (MediaService.currentPodcast?.id == id &&
+ this.args.type == 'podcast' &&
+ !fetchOnly) {
+ return;
+ }
+ _selectedDetailsIndex = 0;
+ if (isForward != null) {
+ if (isForward) {
+ prevStudio = studio;
+ studio = nextStudio!;
+ nextStudio = null;
+ } else {
+ nextStudio = studio;
+ studio = prevStudio!;
+ prevStudio = null;
+ }
+ notifyListeners();
+ _handlePodcastPlayback(studio);
+ }
if (isForward == null) {
+ if (this.args.type == 'podcast') {
+ MediaService.audioPlayerTag =
+ 'podcast-${MediaService.currentPodcast?.id ?? ''}';
+ }
appState = AppState.busy;
} else {
- isFetchingNewItem = true;
+ alongSideState = AppState.busy;
notifyListeners();
}
final service = RequestService(RequestHelper.studioDetails(id, this.args));
await service.httpGet();
+ nextStudio = null;
+ prevStudio = null;
+ if (stopOnPodcastEnds) {
+ timerValue = 10;
+ }
+ stopOnPodcastEnds = false;
if (service.isSuccess) {
final result = service.result;
- final studio = StudioDetailsData.fromJson(result['studio']);
- 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) {
- prevStudio = StudioDetailsData.fromJson(result['prevStudio']);
- }
-
- StudioDetailsData? nextStudio;
- if (result['nextStudio'].isNotEmpty) {
+ studio = StudioDetailsData.fromJson(result['studio']);
+ if (result['nextStudio'].isNotEmpty && this.args.page != 0) {
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--;
+ if (result['prevStudio'].isNotEmpty && this.args.page != 0) {
+ prevStudio = StudioDetailsData.fromJson(result['prevStudio']);
}
- isFetchingNewItem = false;
+ if (isForward == null && !fetchOnly) {
+ await _handlePodcastPlayback(studio);
+ }
+ alongSideState = AppState.idle;
appState = AppState.idle;
return;
}
- //why? total page state shouldn't die!
if (isForward == null) {
appState = AppState.failed;
+ } else {
+ alongSideState = AppState.failed;
+ notifyListeners();
+ }
+ }
+
+ Future _handlePodcastPlayback(StudioDetailsData studio) async {
+ if (args.type == 'podcast') {
+ MediaService.currentPodcast = studio;
+ MediaService.podcastPlaylistArgs = args;
+ await MediaService.handleAudioPlayback(
+ audioSource: studio.link,
+ id: studio.id,
+ isVoiceMessage: false,
+ onTrackChanged: (isNext) {
+ if (isNext && nextStudio != null) {
+ getStudioDetails(nextStudio!.id);
+ } else if (!isNext && prevStudio != null) {
+ getStudioDetails(prevStudio!.id);
+ }
+ },
+ );
+ if (nextStudio != null && !_positionListenerActivated) {
+ _positionListenerActivated = true;
+ MediaService.audioPlayer.currentPosition.listen((event) {
+ if (MediaService.audioPlayerTag?.contains('message') == true) {
+ return;
+ }
+ final duration =
+ MediaService.duration ?? Duration(seconds: studio.duration);
+ if (event.compareTo(duration) > 0 && nextStudio != null) {
+ if (stopOnPodcastEnds) {
+ MediaService.resetAudioPlayer();
+ return;
+ }
+ getStudioDetails(nextStudio!.id, isForward: true);
+ }
+ });
+ }
+ } else {
+ MediaService.audioPlayer.pause();
}
}
Future getRelatedContents() async {
- if (currentStudio.relatedContents.isNotEmpty) return;
- relatedQueue.add(currentStudio.id);
+ if (studio.relatedContents.isNotEmpty) return;
+ relatedQueue.add(studio.id);
final service = RequestService(RequestHelper.tag(
- ids: currentStudio.tags.map((tag) => tag.id).toList(),
- itemId: currentStudio.id,
- type: 'studio',
+ ids: studio.tags.map((tag) => tag.id).toList(),
+ itemId: studio.id,
+ type: args.type,
));
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]));
+ studio.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;
+ studio.comments = count;
notifyListeners();
}
}
diff --git a/lib/views/home/studio/studio_details/widgets/details_tab_bar.dart b/lib/views/home/studio/studio_details/widgets/details_tab_bar.dart
new file mode 100644
index 0000000..ba160e7
--- /dev/null
+++ b/lib/views/home/studio/studio_details/widgets/details_tab_bar.dart
@@ -0,0 +1,126 @@
+import 'package:didvan/config/design_config.dart';
+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:flutter/material.dart';
+import 'package:provider/provider.dart';
+
+class DetailsTabBar extends StatelessWidget {
+ final bool isVideo;
+
+ const DetailsTabBar({
+ Key? key,
+ required this.isVideo,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final state = context.watch();
+ return WillPopScope(
+ onWillPop: () async {
+ state.selectedDetailsIndex = 0;
+ return true;
+ },
+ child: Container(
+ height: 72,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surface,
+ boxShadow: [
+ BoxShadow(
+ color: const Color(0XFF1B3C59).withOpacity(0.15),
+ offset: const Offset(0, 8),
+ blurRadius: 8,
+ spreadRadius: 0,
+ )
+ ],
+ ),
+ child: Row(
+ children: [
+ _TabItem(
+ icon: DidvanIcons.description_solid,
+ title: 'توضیحات',
+ onTap: () => state.selectedDetailsIndex = 0,
+ isSelected: state.selectedDetailsIndex == 0,
+ isVideo: isVideo,
+ ),
+ _TabItem(
+ icon: DidvanIcons.chats_solid,
+ title: 'نظرات',
+ onTap: () {
+ state.selectedDetailsIndex = 1;
+ },
+ isSelected: state.selectedDetailsIndex == 1,
+ isVideo: isVideo,
+ ),
+ _TabItem(
+ icon: DidvanIcons.puzzle_solid,
+ title: 'مطالب مرتبط',
+ onTap: () => state.selectedDetailsIndex = 2,
+ isSelected: state.selectedDetailsIndex == 2,
+ isVideo: isVideo,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+}
+
+class _TabItem extends StatelessWidget {
+ final IconData icon;
+ final String title;
+ final VoidCallback onTap;
+ final bool isSelected;
+ final bool isVideo;
+ const _TabItem({
+ Key? key,
+ required this.icon,
+ required this.title,
+ required this.onTap,
+ required this.isSelected,
+ required this.isVideo,
+ }) : super(key: key);
+
+ Color? _color(context) {
+ if (isSelected) {
+ if (isVideo) {
+ return Theme.of(context).colorScheme.secondary;
+ }
+ return Theme.of(context).colorScheme.focusedBorder;
+ }
+ return Theme.of(context).colorScheme.hint;
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Expanded(
+ child: GestureDetector(
+ onTap: onTap,
+ child: Container(
+ color: Colors.transparent,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ icon,
+ color: _color(context),
+ ),
+ AnimatedContainer(
+ duration: DesignConfig.lowAnimationDuration,
+ width: isSelected ? 64 : 0,
+ height: 1,
+ color: _color(context),
+ ),
+ DidvanText(
+ title,
+ color: _color(context),
+ style: Theme.of(context).textTheme.caption,
+ )
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/views/home/studio/studio_details/widgets/studio_details.dart b/lib/views/home/studio/studio_details/widgets/studio_details.dart
deleted file mode 100644
index 059b4cc..0000000
--- a/lib/views/home/studio/studio_details/widgets/studio_details.dart
+++ /dev/null
@@ -1,97 +0,0 @@
-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/studio/studio_details/widgets/studio_details_widget.dart b/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart
new file mode 100644
index 0000000..268309f
--- /dev/null
+++ b/lib/views/home/studio/studio_details/widgets/studio_details_widget.dart
@@ -0,0 +1,280 @@
+import 'dart:math';
+
+import 'package:didvan/config/theme_data.dart';
+import 'package:didvan/constants/app_icons.dart';
+import 'package:didvan/models/enums.dart';
+import 'package:didvan/models/studio_details_data.dart';
+import 'package:didvan/views/home/comments/comments.dart';
+import 'package:didvan/views/home/comments/comments_state.dart';
+import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
+import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart';
+import 'package:didvan/views/home/widgets/overview/multitype.dart';
+import 'package:didvan/views/home/widgets/tag_item.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:didvan/views/widgets/state_handlers/state_handler.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_html/flutter_html.dart';
+import 'package:provider/provider.dart';
+import 'package:url_launcher/url_launcher.dart';
+
+class StudioDetailsWidget extends StatelessWidget {
+ final void Function(int id, bool value) onMarkChanged;
+ const StudioDetailsWidget({
+ Key? key,
+ required this.onMarkChanged,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ final ds = MediaQuery.of(context).size;
+
+ return SafeArea(
+ bottom: true,
+ child: Consumer(
+ builder: (context, state, child) {
+ bool isVideo = state.studio.iframe != null;
+ return Container(
+ height: max(
+ ds.height -
+ ds.width * 9 / 16 -
+ 72 -
+ MediaQuery.of(context).padding.top,
+ 0),
+ color: Theme.of(context).colorScheme.surface,
+ child: Stack(
+ children: [
+ Positioned(
+ top: 72,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ child: StateHandler(
+ onRetry: () {},
+ state: state,
+ builder: (context, state) {
+ if (state.selectedDetailsIndex == 0) {
+ return SingleChildScrollView(
+ padding: const EdgeInsets.all(16),
+ child: Column(
+ crossAxisAlignment: CrossAxisAlignment.start,
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Html(
+ key: ValueKey(state.studio.id),
+ data: state.studio.description,
+ onAnchorTap: (href, context, map, element) =>
+ launch(href!),
+ style: {
+ '*': Style(
+ direction: TextDirection.rtl,
+ textAlign: TextAlign.right,
+ lineHeight: LineHeight.percent(135),
+ margin: EdgeInsets.zero,
+ padding: EdgeInsets.zero,
+ ),
+ },
+ ),
+ if (state.studio.tags.isNotEmpty)
+ const SizedBox(height: 20),
+ Wrap(
+ spacing: 8,
+ runSpacing: 8,
+ children: [
+ for (var i = 0;
+ i < state.studio.tags.length;
+ i++)
+ TagItem(
+ tag: state.studio.tags[i],
+ onMarkChanged: (id, value) =>
+ _onMarkChanged(id, value, state),
+ type: isVideo ? 'video' : 'podcast',
+ ),
+ ],
+ ),
+ const SizedBox(height: 20),
+ Row(
+ mainAxisAlignment:
+ MainAxisAlignment.spaceBetween,
+ children: [
+ const SizedBox(),
+ if (state.nextStudio != null &&
+ state.alongSideState == AppState.idle)
+ _StudioPreview(
+ isNext: true,
+ studio: state.nextStudio!,
+ ),
+ if (state.alongSideState == AppState.busy)
+ _StudioPreview.placeHolder,
+ if (state.prevStudio != null &&
+ state.alongSideState == AppState.idle)
+ _StudioPreview(
+ isNext: false,
+ studio: state.prevStudio!,
+ ),
+ if (state.alongSideState == AppState.busy)
+ _StudioPreview.placeHolder,
+ const SizedBox(),
+ ],
+ ),
+ ],
+ ),
+ );
+ }
+ if (state.selectedDetailsIndex == 1) {
+ return ChangeNotifierProvider(
+ create: (context) => CommentsState(),
+ child: SizedBox(
+ height: ds.height -
+ ds.width * 9 / 16 -
+ 172 -
+ MediaQuery.of(context).padding.top,
+ child: Comments(
+ pageData: {
+ 'id': state.studio.id,
+ 'type': 'studio',
+ 'title': state.studio.title,
+ 'onCommentsChanged': state.onCommentsChanged,
+ 'isPage': false,
+ },
+ ),
+ ),
+ );
+ }
+ return Column(
+ children: [
+ if (state.studio.relatedContents.isEmpty)
+ for (var i = 0; i < 3; i++)
+ Padding(
+ padding: const EdgeInsets.only(
+ bottom: 8,
+ left: 16,
+ right: 16,
+ ),
+ child: MultitypeOverview.placeholder,
+ ),
+ for (var i = 0;
+ i < state.studio.relatedContents.length;
+ i++)
+ Padding(
+ padding: const EdgeInsets.only(
+ bottom: 8,
+ left: 16,
+ right: 16,
+ ),
+ child: MultitypeOverview(
+ item: state.studio.relatedContents[i],
+ onMarkChanged: (id, value) {},
+ ),
+ ),
+ ],
+ );
+ },
+ ),
+ ),
+ DetailsTabBar(
+ isVideo: isVideo,
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ }
+
+ void _onMarkChanged(id, value, state) {
+ onMarkChanged(id, value);
+ if (state.studio.id == id) {
+ state.studio.marked = value;
+ } else if (state.nextStudio?.id == id) {
+ state.nextStudio!.marked = value;
+ } else if (state.prevStudio?.id == id) {
+ state.prevStudio!.marked = value;
+ }
+ }
+}
+
+class _StudioPreview extends StatelessWidget {
+ final bool isNext;
+ final StudioDetailsData studio;
+ const _StudioPreview({
+ Key? key,
+ required this.isNext,
+ required this.studio,
+ }) : super(key: key);
+
+ String get _previewTitle {
+ if (studio.iframe != null) {
+ return 'ویدئو ${isNext ? 'بعدی' : 'قبلی'} ';
+ }
+ return 'پادکست ${isNext ? 'بعدی' : 'قبلی'} ';
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: () {
+ final state = context.read();
+ state.getStudioDetails(
+ isNext ? state.nextStudio!.id : state.prevStudio!.id,
+ args: state.args,
+ isForward: isNext,
+ );
+ },
+ child: Container(
+ width: 88,
+ height: 216,
+ color: Colors.transparent,
+ child: Column(
+ children: [
+ SkeletonImage(
+ imageUrl: studio.image,
+ aspectRatio: 1 / 1,
+ ),
+ const SizedBox(height: 8),
+ Icon(
+ isNext
+ ? DidvanIcons.angle_right_regular
+ : DidvanIcons.angle_left_regular,
+ ),
+ const SizedBox(height: 8),
+ DidvanText(
+ _previewTitle,
+ style: Theme.of(context).textTheme.caption,
+ textAlign: TextAlign.center,
+ ),
+ const SizedBox(height: 8),
+ DidvanText(
+ studio.title,
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ textAlign: TextAlign.center,
+ style: Theme.of(context).textTheme.overline,
+ color: Theme.of(context).colorScheme.caption,
+ ),
+ ],
+ ),
+ ),
+ );
+ }
+
+ static Widget get placeHolder => SizedBox(
+ width: 88,
+ height: 216,
+ child: Column(
+ children: const [
+ ShimmerPlaceholder(width: 88, height: 88),
+ SizedBox(height: 8),
+ ShimmerPlaceholder(height: 20, width: 20),
+ SizedBox(height: 16),
+ ShimmerPlaceholder(height: 14, width: 60),
+ SizedBox(height: 16),
+ ShimmerPlaceholder(height: 12, width: double.infinity),
+ SizedBox(height: 8),
+ ShimmerPlaceholder(height: 12, width: 40),
+ ],
+ ),
+ );
+}
diff --git a/lib/views/home/studio/studio_state.dart b/lib/views/home/studio/studio_state.dart
index 18b7dc2..bb5b65a 100644
--- a/lib/views/home/studio/studio_state.dart
+++ b/lib/views/home/studio/studio_state.dart
@@ -1,16 +1,19 @@
+import 'dart:async';
+
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/studio.dart';
-import 'package:didvan/providers/core_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/models/slider_data.dart';
+import 'package:didvan/providers/core.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 sliders = [];
- String? search;
- String? lastSearch;
+ String search = '';
+ String lastSearch = '';
int page = 1;
int lastPage = 1;
@@ -20,11 +23,32 @@ class StudioState extends CoreProvier {
bool get videosSelected => _videosSelected;
+ bool get searching => search.isNotEmpty;
+
set videosSelected(bool value) {
- if (_videosSelected == value) return;
+ if (_videosSelected == value || appState == AppState.busy) return;
_videosSelected = value;
- studios.clear();
- getStudioOverviews(page: page);
+ selectedSortTypeIndex = 0;
+ _getSliders();
+ getStudios(page: page);
+ }
+
+ String get order {
+ if (selectedSortTypeIndex == 0 || selectedSortTypeIndex == 1) return 'date';
+ if (selectedSortTypeIndex == 2) return 'view';
+ return 'comment';
+ }
+
+ String get orderString {
+ if (selectedSortTypeIndex == 0) return 'تازهترینها';
+ if (selectedSortTypeIndex == 1) return 'قدیمیترینها';
+ if (selectedSortTypeIndex == 2) return 'پربازدیدترینها';
+ return 'پربحثنرینها';
+ }
+
+ String get type {
+ if (videosSelected) return 'video';
+ return 'podcast';
}
void init() {
@@ -33,23 +57,29 @@ class StudioState extends CoreProvier {
_videosSelected = true;
selectedSortTypeIndex = 0;
Future.delayed(Duration.zero, () {
- getStudioOverviews(page: 1);
+ _getSliders();
+ getStudios(page: 1);
});
}
- String get order {
- if (selectedSortTypeIndex == 0) return 'date';
- if (selectedSortTypeIndex == 1) return 'view';
- return 'comment';
+ Future _getSliders() async {
+ final service = RequestService(
+ RequestHelper.sudioSlider(type),
+ );
+ await service.httpGet();
+ if (service.isSuccess) {
+ sliders.clear();
+ final sliderItems = service.result['studios'];
+ for (var i = 0; i < sliderItems.length; i++) {
+ sliders.add(SliderData.fromJson(sliderItems[i]));
+ }
+ }
+ notifyListeners();
}
- String get type {
- if (videosSelected) return 'video';
- return 'podcast';
- }
-
- Future getStudioOverviews({required int page}) async {
+ Future getStudios({required int page}) async {
this.page = page;
+ lastSearch = search;
if (page == 1) {
appState = AppState.busy;
}
@@ -60,10 +90,10 @@ class StudioState extends CoreProvier {
type: type,
search: search,
order: order,
+ asc: selectedSortTypeIndex == 1,
),
),
);
-
await service.httpGet();
if (service.isSuccess) {
if (page == 1) {
@@ -80,10 +110,11 @@ class StudioState extends CoreProvier {
appState = AppState.failed;
}
- Future changeMark(int id, bool value) async {
+ Future changeMark(int id, bool value, bool shouldUpdate) async {
studios.firstWhere((element) => element.id == id).marked = value;
- notifyListeners();
- UserProvider.changeStudioMark(id, value);
+ if (shouldUpdate) {
+ notifyListeners();
+ }
}
void onCommentsChanged(int id, int count) {
diff --git a/lib/views/home/studio/widgets/slider.dart b/lib/views/home/studio/widgets/slider.dart
index d312b2b..78bf110 100644
--- a/lib/views/home/studio/widgets/slider.dart
+++ b/lib/views/home/studio/widgets/slider.dart
@@ -1,28 +1,146 @@
import 'package:carousel_slider/carousel_slider.dart';
+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/enums.dart';
+import 'package:didvan/models/requests/studio.dart';
+import 'package:didvan/routes/routes.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/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 StudioSlider extends StatelessWidget {
+class StudioSlider extends StatefulWidget {
const StudioSlider({Key? key}) : super(key: key);
+ @override
+ State createState() => _StudioSliderState();
+}
+
+class _StudioSliderState extends State {
+ int selectedIndex = 0;
+
@override
Widget build(BuildContext context) {
+ final state = context.watch();
return Column(
children: [
CarouselSlider(
items: [
- Image.network('https://wallpapercave.com/wp/wp10731650.jpg'),
- Image.network('https://wallpapercave.com/wp/wp10731650.jpg'),
- Image.network('https://wallpapercave.com/wp/wp10731650.jpg'),
- Image.network('https://wallpapercave.com/wp/wp10731650.jpg'),
+ if (state.appState == AppState.busy)
+ const Padding(
+ padding: EdgeInsets.symmetric(horizontal: 4),
+ child: ShimmerPlaceholder(),
+ ),
+ if (state.appState == AppState.idle)
+ for (var i = 0; i < state.sliders.length; i++)
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 4),
+ child: GestureDetector(
+ onTap: () {
+ if (state.videosSelected) {
+ Navigator.of(context)
+ .pushNamed(Routes.studioDetails, arguments: {
+ 'onMarkChanged': state.changeMark,
+ 'id': state.sliders[i].id,
+ 'args':
+ const StudioRequestArgs(page: 0, type: 'video'),
+ 'hasUnmarkConfirmation': false,
+ 'isVideo': true,
+ });
+ return;
+ }
+ context.read().getStudioDetails(
+ state.sliders[i].id,
+ args: const StudioRequestArgs(
+ page: 0,
+ type: 'podcast',
+ ),
+ );
+ },
+ child: Stack(
+ alignment: Alignment.center,
+ children: [
+ SkeletonImage(
+ borderRadius: DesignConfig.mediumBorderRadius,
+ imageUrl: state.sliders[i].image,
+ width: double.infinity,
+ height: double.infinity,
+ ),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ child: Container(
+ padding: const EdgeInsets.symmetric(
+ vertical: 4,
+ horizontal: 8,
+ ),
+ decoration: BoxDecoration(
+ color: (state.videosSelected
+ ? Theme.of(context)
+ .colorScheme
+ .secondaryDisabled
+ : Theme.of(context).colorScheme.focused)
+ .withOpacity(0.9),
+ borderRadius: const BorderRadius.vertical(
+ bottom: Radius.circular(10),
+ ),
+ ),
+ child: DidvanText(
+ state.sliders[i].title,
+ color: Theme.of(context).colorScheme.title,
+ style: Theme.of(context).textTheme.caption,
+ ),
+ ),
+ ),
+ if (state.videosSelected)
+ Container(
+ height: 52,
+ width: 52,
+ 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,
+ size: 48,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
],
options: CarouselOptions(
+ autoPlayAnimationDuration: DesignConfig.mediumAnimationDuration,
+ onPageChanged: (index, reason) => setState(
+ () => selectedIndex = index,
+ ),
viewportFraction: 0.94,
aspectRatio: 16 / 9,
- autoPlay: true,
+ autoPlay: state.appState == AppState.idle,
),
),
- Row(),
+ const SizedBox(height: 16),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ for (var i = 0; i < state.sliders.length; i++)
+ _SliderIndicator(
+ isCurrentIndex: selectedIndex == i,
+ isVideo: state.videosSelected,
+ ),
+ ],
+ ),
+ const SizedBox(height: 16),
],
);
}
@@ -30,21 +148,33 @@ class StudioSlider extends StatelessWidget {
class _SliderIndicator extends StatelessWidget {
final bool isCurrentIndex;
- const _SliderIndicator({Key? key, required this.isCurrentIndex})
- : super(key: key);
+ final bool isVideo;
+ const _SliderIndicator({
+ Key? key,
+ required this.isCurrentIndex,
+ required this.isVideo,
+ }) : super(key: key);
+
+ Color _color(BuildContext context) {
+ if (isVideo) {
+ return Theme.of(context).colorScheme.secondary;
+ }
+ return Theme.of(context).colorScheme.focusedBorder;
+ }
@override
Widget build(BuildContext context) {
- return Container(
+ return AnimatedContainer(
+ duration: DesignConfig.lowAnimationDuration,
height: 8,
width: 8,
+ margin: const EdgeInsets.only(left: 4),
decoration: BoxDecoration(
border: Border.all(
- color: Theme.of(context).colorScheme.focusedBorder,
+ color: _color(context),
),
shape: BoxShape.circle,
- color:
- isCurrentIndex ? Theme.of(context).colorScheme.focusedBorder : null,
+ color: isCurrentIndex ? _color(context) : null,
),
);
}
diff --git a/lib/views/home/studio/widgets/tab_bar.dart b/lib/views/home/studio/widgets/tab_bar.dart
index 3d915a8..ca27288 100644
--- a/lib/views/home/studio/widgets/tab_bar.dart
+++ b/lib/views/home/studio/widgets/tab_bar.dart
@@ -20,9 +20,7 @@ class StudioTabBar extends StatelessWidget {
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
border: Border.all(
- color: state.videosSelected
- ? Theme.of(context).colorScheme.secondary
- : Theme.of(context).colorScheme.primary,
+ color: Theme.of(context).colorScheme.border,
),
borderRadius: DesignConfig.lowBorderRadius,
),
@@ -78,7 +76,10 @@ class _StudioTypeButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GestureDetector(
- onTap: onTap,
+ onTap: () {
+ onTap();
+ FocusScope.of(context).unfocus();
+ },
child: Container(
color: Colors.transparent,
child: Column(
@@ -88,19 +89,17 @@ class _StudioTypeButton extends StatelessWidget {
size: 32,
color: _color(context),
),
- if (!isSelected) const SizedBox(height: 18),
- if (isSelected)
- Container(
- width: 88,
- height: 1,
- color: _color(context),
- ),
- if (isSelected)
- DidvanText(
- title,
- style: Theme.of(context).textTheme.overline,
- color: _color(context),
- )
+ AnimatedContainer(
+ duration: DesignConfig.lowAnimationDuration,
+ width: isSelected ? 88 : 0,
+ height: 1,
+ color: _color(context),
+ ),
+ DidvanText(
+ title,
+ style: Theme.of(context).textTheme.overline,
+ color: _color(context),
+ )
],
),
),
diff --git a/lib/views/home/widgets/audio/audio_player_widget.dart b/lib/views/home/widgets/audio/audio_player_widget.dart
index d369b10..79d1aff 100644
--- a/lib/views/home/widgets/audio/audio_player_widget.dart
+++ b/lib/views/home/widgets/audio/audio_player_widget.dart
@@ -1,15 +1,26 @@
+import 'dart:async';
+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/models/enums.dart';
import 'package:didvan/models/studio_details_data.dart';
+import 'package:didvan/models/view/action_sheet_data.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_state.dart';
+import 'package:didvan/views/home/studio/studio_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/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/item_title.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
class AudioPlayerWidget extends StatelessWidget {
final StudioDetailsData podcast;
@@ -17,6 +28,7 @@ class AudioPlayerWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
+ final state = context.read();
return Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
@@ -47,60 +59,126 @@ class AudioPlayerWidget extends StatelessWidget {
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AudioSlider(
- tag: podcast.media,
+ tag: 'podcast-${podcast.id}',
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,
+ Expanded(
+ child: Center(
+ child: StatefulBuilder(
+ builder: (context, setState) => Column(
+ children: [
+ DidvanIconButton(
+ icon: state.timer == null && !state.stopOnPodcastEnds
+ ? DidvanIcons.sleep_timer_regular
+ : DidvanIcons.sleep_enabled_regular,
+ color: Theme.of(context).colorScheme.title,
+ onPressed: () => _showSleepTimer(
+ state,
+ () => setState(() {}),
+ ),
),
+ if (state.timer != null)
+ DidvanText(
+ state.stopOnPodcastEnds
+ ? 'پایان پادکست'
+ : '\'' + state.timerValue.toString(),
+ isEnglishFont: true,
+ style: Theme.of(context).textTheme.overline,
+ color: Theme.of(context).colorScheme.title,
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ Expanded(
+ child: Center(
+ child: Column(
+ children: [
+ DidvanIconButton(
+ color: Theme.of(context).colorScheme.title,
+ size: 32,
+ icon: DidvanIcons.media_forward_solid,
+ onPressed: () {
+ MediaService.audioPlayer.seek(
+ Duration(
+ seconds: MediaService.audioPlayer.currentPosition
+ .value.inSeconds +
+ 30,
+ ),
+ );
+ },
+ ),
+ DidvanText(
+ '30',
+ isEnglishFont: true,
+ color: Theme.of(context).colorScheme.title,
+ ),
+ ],
+ ),
+ ),
+ ),
+ Expanded(
+ child: Center(
+ child: StreamBuilder(
+ stream: MediaService.audioPlayer.isPlaying,
+ builder: (context, snapshot) {
+ return _PlayPouseAnimatedIcon(
+ audioSource: podcast.link,
+ id: podcast.id,
);
},
),
- 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,
- ),
- );
- },
+ Expanded(
+ child: Center(
+ child: Column(
+ children: [
+ DidvanIconButton(
+ size: 32,
+ icon: DidvanIcons.media_backward_solid,
+ color: Theme.of(context).colorScheme.title,
+ onPressed: () {
+ MediaService.audioPlayer.seek(
+ Duration(
+ seconds: max(
+ 0,
+ MediaService.audioPlayer.currentPosition.value
+ .inSeconds -
+ 10,
+ ),
+ ),
+ );
+ },
+ ),
+ DidvanText(
+ '10',
+ isEnglishFont: true,
+ color: Theme.of(context).colorScheme.title,
+ ),
+ ],
),
- const DidvanText('10', isEnglishFont: true),
- ],
+ ),
),
- BookmarkButton(
- gestureSize: 48,
- value: podcast.marked,
- onMarkChanged: (value) {},
+ Expanded(
+ child: Center(
+ child: BookmarkButton(
+ itemId: state.studio.id,
+ type: 'podcast',
+ gestureSize: 48,
+ color: Theme.of(context).colorScheme.title,
+ value: podcast.marked,
+ onMarkChanged: (value) => context
+ .read()
+ .changeMark(podcast.id, value, true),
+ ),
+ ),
),
],
),
@@ -108,11 +186,136 @@ class AudioPlayerWidget extends StatelessWidget {
),
);
}
+
+ Future _showSleepTimer(StudioDetailsState state, update) async {
+ int timerValue = 10;
+ final controller = FixedExtentScrollController();
+ bool isInit = true;
+ Future.delayed(
+ const Duration(milliseconds: 100),
+ () async {
+ await controller.animateTo(
+ state.timerValue * 10,
+ duration: DesignConfig.lowAnimationDuration,
+ curve: Curves.easeIn,
+ );
+ isInit = false;
+ },
+ );
+ await ActionSheetUtils.showBottomSheet(
+ data: ActionSheetData(
+ content: StatefulBuilder(
+ builder: (context, setState) => Column(
+ children: [
+ const ItemTitle(
+ title: 'زمان خواب',
+ icon: DidvanIcons.sleep_timer_regular,
+ ),
+ const SizedBox(height: 24),
+ DidvanText(
+ timerValue.toString() + ' دقیقه',
+ style: Theme.of(context).textTheme.headline3,
+ ),
+ const SizedBox(height: 12),
+ const Icon(DidvanIcons.caret_down_solid),
+ const SizedBox(height: 8),
+ SizedBox(
+ height: 50,
+ child: RotatedBox(
+ quarterTurns: 3,
+ child: ListWheelScrollView(
+ physics: const FixedExtentScrollPhysics(),
+ controller: controller,
+ itemExtent: 10,
+ onSelectedItemChanged: (index) {
+ if (!isInit) {
+ state.stopOnPodcastEnds = false;
+ }
+ final minutes = index == 0 ? 1 : index;
+ timerValue = minutes;
+ setState(() {});
+ },
+ children: [
+ for (var i = 0; i < 61; i++) ...[
+ if (i % 5 == 0)
+ Center(
+ child: Container(
+ color: Theme.of(context).colorScheme.text,
+ width: 50,
+ height: 3,
+ ),
+ ),
+ if (i % 5 != 0) const SizedBox(height: 3),
+ ],
+ ],
+ ),
+ ),
+ ),
+ const SizedBox(height: 32),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 64),
+ child: DidvanButton(
+ style: state.timerValue == MediaService.duration?.inMinutes &&
+ state.stopOnPodcastEnds
+ ? ButtonStyleMode.primary
+ : ButtonStyleMode.flat,
+ title: 'پایان پادکست',
+ onPressed: () async {
+ state.timerValue = MediaService.duration!.inMinutes -
+ MediaService
+ .audioPlayer.currentPosition.value.inMinutes;
+ await controller.animateTo(
+ state.timerValue * 10,
+ duration: DesignConfig.lowAnimationDuration,
+ curve: Curves.easeIn,
+ );
+ state.stopOnPodcastEnds = true;
+ setState(() {});
+ },
+ ),
+ ),
+ ],
+ ),
+ ),
+ onConfirmed: () {
+ if (!state.stopOnPodcastEnds) {
+ state.timer = Timer.periodic(
+ const Duration(minutes: 1),
+ (timer) {
+ timerValue--;
+ if (timerValue == 0) {
+ MediaService.audioPlayer.stop();
+ state.stopOnPodcastEnds = false;
+ state.timer?.cancel();
+ state.timer = null;
+ state.timerValue = 10;
+ state.update();
+ }
+ },
+ );
+ }
+ state.timerValue = timerValue;
+ update();
+ },
+ confrimTitle: 'شروع زمان خواب',
+ dismissTitle: 'لغو',
+ onDismissed: () {
+ state.timer?.cancel();
+ state.timer = null;
+ state.timerValue = 10;
+ update();
+ },
+ ),
+ );
+ controller.dispose();
+ }
}
class _PlayPouseAnimatedIcon extends StatefulWidget {
final String audioSource;
- const _PlayPouseAnimatedIcon({Key? key, required this.audioSource})
+ final int id;
+ const _PlayPouseAnimatedIcon(
+ {Key? key, required this.audioSource, required this.id})
: super(key: key);
@override
@@ -123,6 +326,12 @@ class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
+ @override
+ void didUpdateWidget(covariant _PlayPouseAnimatedIcon oldWidget) {
+ _handleAnimation();
+ super.didUpdateWidget(oldWidget);
+ }
+
@override
void initState() {
super.initState();
@@ -130,8 +339,13 @@ class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
vsync: this,
duration: DesignConfig.lowAnimationDuration,
);
- if (MediaService.audioPlayer.playing) {
+ }
+
+ void _handleAnimation() {
+ if (MediaService.audioPlayer.isPlaying.value) {
_animationController.forward();
+ } else {
+ _animationController.reverse();
}
}
@@ -143,12 +357,9 @@ class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
MediaService.handleAudioPlayback(
audioSource: widget.audioSource,
isVoiceMessage: false,
+ id: widget.id,
);
- if (MediaService.audioPlayer.playing) {
- _animationController.forward();
- } else {
- _animationController.reverse();
- }
+ _handleAnimation();
},
child: Container(
padding: const EdgeInsets.all(8),
diff --git a/lib/views/home/widgets/audio/audio_slider.dart b/lib/views/home/widgets/audio/audio_slider.dart
index 9c80349..bf8969e 100644
--- a/lib/views/home/widgets/audio/audio_slider.dart
+++ b/lib/views/home/widgets/audio/audio_slider.dart
@@ -22,11 +22,14 @@ class AudioSlider extends StatelessWidget {
@override
Widget build(BuildContext context) {
return IgnorePointer(
- ignoring: MediaService.audioPlayerTag != tag,
+ ignoring: !_isPlaying,
child: Directionality(
textDirection: TextDirection.ltr,
child: StreamBuilder(
- stream: _isPlaying ? MediaService.audioPlayer.positionStream : null,
+ stream:
+ _isPlaying && MediaService.audioPlayer.currentPosition.hasValue
+ ? MediaService.audioPlayer.currentPosition
+ : null,
builder: (context, snapshot) => ProgressBar(
thumbColor: Theme.of(context).colorScheme.title,
progressBarColor: DesignConfig.isDark
@@ -34,17 +37,14 @@ class AudioSlider extends StatelessWidget {
: 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),
+ total: MediaService.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,
+ color: Theme.of(context).colorScheme.text,
fontFamily: DesignConfig.fontFamily.replaceAll(
'-FA',
'',
diff --git a/lib/views/home/widgets/bnb.dart b/lib/views/home/widgets/bnb.dart
deleted file mode 100644
index a7651be..0000000
--- a/lib/views/home/widgets/bnb.dart
+++ /dev/null
@@ -1,292 +0,0 @@
-import 'package:didvan/config/design_config.dart';
-import 'package:didvan/config/theme_data.dart';
-import 'package:didvan/constants/app_icons.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/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';
-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;
- final void Function(int index) onTabChanged;
-
- const DidvanBNB(
- {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(
- stream: MediaService.audioPlayer.playingStream,
- builder: (context, snapshot) {
- return Stack(
- children: [
- 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),
- ),
- ),
- 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(
- bottom: 0,
- left: 0,
- right: 0,
- child: Container(
- height: 72,
- decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.surface,
- borderRadius:
- const BorderRadius.vertical(top: Radius.circular(16)),
- boxShadow: DesignConfig.defaultShadow,
- ),
- padding: const EdgeInsets.symmetric(horizontal: 12),
- child: Row(
- children: [
- _NavBarItem(
- isSelected: currentTabIndex == 0,
- title: 'اخبار',
- selectedIcon: DidvanIcons.news_solid,
- unselectedIcon: DidvanIcons.news_light,
- onTap: () => onTabChanged(0),
- ),
- _NavBarItem(
- isSelected: currentTabIndex == 1,
- title: 'آمار',
- selectedIcon: DidvanIcons.chart_solid,
- unselectedIcon: DidvanIcons.chart_light,
- onTap: () => onTabChanged(1),
- ),
- _NavBarItem(
- isSelected: currentTabIndex == 2,
- title: 'رادار',
- selectedIcon: DidvanIcons.radar_solid,
- unselectedIcon: DidvanIcons.radar_light,
- onTap: () => onTabChanged(2),
- ),
- _NavBarItem(
- isSelected: currentTabIndex == 3,
- title: 'استودیو',
- selectedIcon: DidvanIcons.play_circle_solid,
- unselectedIcon: DidvanIcons.play_circle_light,
- onTap: () => onTabChanged(3),
- ),
- _NavBarItem(
- isSelected: currentTabIndex == 4,
- title: 'تنظیمات',
- selectedIcon: DidvanIcons.setting_solid,
- unselectedIcon: DidvanIcons.setting_light,
- onTap: () => onTabChanged(4),
- ),
- ],
- ),
- ),
- ),
- ],
- );
- });
- }
-
- void _showPlayerBottomSheet(BuildContext context) {
- final sheetKey = GlobalKey();
- bool isExpanded = false;
- final detailsState = context.read();
- showModalBottomSheet(
- backgroundColor: Colors.transparent,
- context: context,
- isScrollControlled: true,
- builder: (context) => ChangeNotifierProvider.value(
- value: detailsState,
- child: ExpandableBottomSheet(
- 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(),
- ),
- ),
- );
- }
-}
-
-class _NavBarItem extends StatelessWidget {
- final VoidCallback onTap;
- final bool isSelected;
- final String title;
- final IconData selectedIcon;
- final IconData unselectedIcon;
- const _NavBarItem({
- Key? key,
- required this.isSelected,
- required this.title,
- required this.selectedIcon,
- required this.unselectedIcon,
- required this.onTap,
- }) : super(key: key);
-
- @override
- Widget build(BuildContext context) {
- return Expanded(
- child: Tooltip(
- message: title,
- decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.title,
- borderRadius: DesignConfig.highBorderRadius,
- boxShadow: DesignConfig.defaultShadow,
- ),
- child: GestureDetector(
- onTap: onTap,
- child: Container(
- color: Colors.transparent,
- child: Column(
- children: [
- const SizedBox(
- height: 4,
- ),
- AnimatedContainer(
- padding: const EdgeInsets.all(4),
- duration: DesignConfig.lowAnimationDuration,
- decoration: BoxDecoration(
- shape: BoxShape.circle,
- color: isSelected
- ? Theme.of(context).colorScheme.focused
- : Theme.of(context).colorScheme.surface,
- ),
- child: Icon(
- isSelected ? selectedIcon : unselectedIcon,
- size: 32,
- color: DesignConfig.isDark
- ? Theme.of(context).colorScheme.text
- : Theme.of(context).colorScheme.title,
- ),
- ),
- DidvanText(
- title,
- style: Theme.of(context).textTheme.caption,
- color: Theme.of(context).colorScheme.title,
- ),
- const Spacer(),
- ],
- ),
- ),
- ),
- ),
- );
- }
-}
diff --git a/lib/views/home/widgets/bookmark_button.dart b/lib/views/home/widgets/bookmark_button.dart
index ca98b06..79939d5 100644
--- a/lib/views/home/widgets/bookmark_button.dart
+++ b/lib/views/home/widgets/bookmark_button.dart
@@ -1,5 +1,7 @@
+import 'package:didvan/config/design_config.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/view/action_sheet_data.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
@@ -7,15 +9,21 @@ import 'package:flutter/material.dart';
class BookmarkButton extends StatefulWidget {
final bool value;
+ final Color? color;
final void Function(bool value) onMarkChanged;
final bool askForConfirmation;
final double gestureSize;
+ final String type;
+ final int itemId;
const BookmarkButton({
Key? key,
required this.value,
required this.onMarkChanged,
- this.askForConfirmation = false,
required this.gestureSize,
+ required this.type,
+ required this.itemId,
+ this.askForConfirmation = false,
+ this.color,
}) : super(key: key);
@override
@@ -41,6 +49,10 @@ class _BookmarkButtonState extends State {
Widget build(BuildContext context) {
return DidvanIconButton(
gestureSize: widget.gestureSize,
+ color: widget.color ??
+ (DesignConfig.isDark || !_value
+ ? null
+ : Theme.of(context).colorScheme.primary),
icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular,
onPressed: () async {
bool confirm = false;
@@ -62,6 +74,21 @@ class _BookmarkButtonState extends State {
_value = !_value;
});
widget.onMarkChanged(_value);
+ switch (widget.type) {
+ case 'radar':
+ UserProvider.changeRadarMark(widget.itemId, _value);
+ break;
+ case 'news':
+ UserProvider.changeNewsMark(widget.itemId, _value);
+ break;
+ case 'podcast':
+ UserProvider.changeStudioMark(widget.itemId, _value);
+ break;
+ case 'video':
+ UserProvider.changeStudioMark(widget.itemId, _value);
+ break;
+ default:
+ }
}
},
);
diff --git a/lib/views/home/widgets/duration_widget.dart b/lib/views/home/widgets/duration_widget.dart
index 025224d..b27f210 100644
--- a/lib/views/home/widgets/duration_widget.dart
+++ b/lib/views/home/widgets/duration_widget.dart
@@ -19,11 +19,10 @@ class DurationWidget extends StatelessWidget {
borderRadius: BorderRadius.circular(5),
),
child: Row(
- crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
DidvanIcons.timer_regular,
- size: 16,
+ size: 18,
color: Theme.of(context).colorScheme.focusedBorder,
),
const SizedBox(width: 4),
@@ -32,12 +31,13 @@ class DurationWidget extends StatelessWidget {
Duration(seconds: duration),
),
isEnglishFont: true,
+ style: Theme.of(context).textTheme.caption,
color: Theme.of(context).colorScheme.focusedBorder,
),
const SizedBox(width: 4),
Icon(
DidvanIcons.play_circle_regular,
- size: 16,
+ size: 18,
color: Theme.of(context).colorScheme.focusedBorder,
),
],
diff --git a/lib/views/home/widgets/floating_navigation_bar.dart b/lib/views/home/widgets/floating_navigation_bar.dart
index 28a88e4..754e40d 100644
--- a/lib/views/home/widgets/floating_navigation_bar.dart
+++ b/lib/views/home/widgets/floating_navigation_bar.dart
@@ -104,6 +104,11 @@ class _FloatingNavigationBarState extends State {
const Spacer(),
if (widget.isRadar)
BookmarkButton(
+ itemId: widget.item.id,
+ type: 'radar',
+ color: DesignConfig.isDark
+ ? Theme.of(context).colorScheme.focusedBorder
+ : Theme.of(context).colorScheme.focused,
askForConfirmation: widget.hasUnmarkConfirmation,
value: widget.item.marked,
onMarkChanged: (value) {
@@ -130,7 +135,7 @@ class _FloatingNavigationBarState extends State {
Routes.comments,
arguments: {
'id': widget.item.id,
- 'isRadar': widget.isRadar,
+ 'type': widget.isRadar ? 'radar' : 'news',
'title': widget.item.title,
'onCommentsChanged': widget.onCommentsChanged,
},
@@ -143,6 +148,11 @@ class _FloatingNavigationBarState extends State {
if (!widget.isRadar) const SizedBox(width: 12),
if (!widget.isRadar)
BookmarkButton(
+ itemId: widget.item.id,
+ type: 'news',
+ color: DesignConfig.isDark
+ ? Theme.of(context).colorScheme.focusedBorder
+ : Theme.of(context).colorScheme.focused,
askForConfirmation: widget.hasUnmarkConfirmation,
value: widget.item.marked,
onMarkChanged: (value) {
diff --git a/lib/views/home/widgets/overview/multitype.dart b/lib/views/home/widgets/overview/multitype.dart
index 0076a05..136d78e 100644
--- a/lib/views/home/widgets/overview/multitype.dart
+++ b/lib/views/home/widgets/overview/multitype.dart
@@ -3,13 +3,17 @@ 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/radar.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/widgets/didvan/card.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';
import 'package:persian_number_utility/persian_number_utility.dart';
+import 'package:provider/provider.dart';
class MultitypeOverview extends StatelessWidget {
final OverviewData item;
@@ -23,20 +27,60 @@ class MultitypeOverview extends StatelessWidget {
this.hasUnmarkConfirmation = false,
}) : super(key: key);
+ get _targetPageArgs {
+ if (item.type == 'radar') {
+ return const RadarRequestArgs(page: 0);
+ }
+ if (item.type == 'news') {
+ return const NewsRequestArgs(page: 0);
+ }
+ return StudioRequestArgs(page: 0, type: item.type);
+ }
+
+ String get _targetPageRouteName {
+ if (item.type == 'radar') {
+ return Routes.radarDetails;
+ }
+ if (item.type == 'news') {
+ return Routes.newsDetails;
+ }
+ return Routes.studioDetails;
+ }
+
+ IconData get _icon {
+ if (item.type == 'radar') {
+ return DidvanIcons.radar_light;
+ }
+ if (item.type == 'news') {
+ return DidvanIcons.news_light;
+ }
+ if (item.type == 'video') {
+ return DidvanIcons.video_light;
+ }
+ return DidvanIcons.podcast_light;
+ }
+
@override
Widget build(BuildContext context) {
return DidvanCard(
- onTap: () => Navigator.of(context).pushNamed(
- item.type == 'radar' ? Routes.radarDetails : Routes.newsDetails,
- arguments: {
- 'onMarkChanged': onMarkChanged,
- 'id': item.id,
- 'args': item.type == 'radar'
- ? const RadarRequestArgs(page: 0)
- : const NewsRequestArgs(page: 0),
- 'hasUnmarkConfirmation': hasUnmarkConfirmation,
- },
- ),
+ onTap: () {
+ if (item.type == 'podcast') {
+ context.read().getStudioDetails(
+ item.id,
+ args: StudioRequestArgs(page: 0, type: item.type),
+ );
+ return;
+ }
+ Navigator.of(context).pushNamed(
+ _targetPageRouteName,
+ arguments: {
+ 'onMarkChanged': onMarkChanged,
+ 'id': item.id,
+ 'args': _targetPageArgs,
+ 'hasUnmarkConfirmation': hasUnmarkConfirmation,
+ },
+ );
+ },
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -52,9 +96,7 @@ class MultitypeOverview extends StatelessWidget {
),
),
child: Icon(
- item.type == 'radar'
- ? DidvanIcons.radar_light
- : DidvanIcons.news_light,
+ _icon,
color: Theme.of(context).colorScheme.white,
size: 18,
),
@@ -86,7 +128,22 @@ class MultitypeOverview extends StatelessWidget {
DateTime.parse(item.createdAt).toPersianDateStr(),
style: Theme.of(context).textTheme.overline,
),
- // DidvanText('text'),
+ const Spacer(),
+ if ((item.timeToRead ?? item.duration) != null) ...[
+ const Icon(
+ DidvanIcons.timer_light,
+ size: 18,
+ ),
+ const SizedBox(width: 4),
+ DidvanText(
+ item.timeToRead != null
+ ? 'خواندن در ${item.timeToRead} دقیقه'
+ : DateTimeUtils.normalizeTimeDuration(
+ Duration(seconds: item.duration!),
+ ),
+ style: Theme.of(context).textTheme.overline,
+ ),
+ ]
],
),
],
diff --git a/lib/views/home/widgets/overview/news.dart b/lib/views/home/widgets/overview/news.dart
index ac163c2..6a8db98 100644
--- a/lib/views/home/widgets/overview/news.dart
+++ b/lib/views/home/widgets/overview/news.dart
@@ -13,7 +13,7 @@ import 'package:flutter/material.dart';
class NewsOverview extends StatelessWidget {
final OverviewData news;
final NewsRequestArgs? newsRequestArgs;
- final void Function(int id, bool value) onMarkChanged;
+ final void Function(int id, bool value, bool shouldUpdate) onMarkChanged;
final bool hasUnmarkConfirmation;
const NewsOverview({
Key? key,
@@ -29,7 +29,7 @@ class NewsOverview extends StatelessWidget {
onTap: () => Navigator.of(context).pushNamed(
Routes.newsDetails,
arguments: {
- 'onMarkChanged': onMarkChanged,
+ 'onMarkChanged': (id, value) => onMarkChanged(id, value, true),
'id': news.id,
'args': newsRequestArgs,
'hasUnmarkConfirmation': hasUnmarkConfirmation,
@@ -79,9 +79,11 @@ class NewsOverview extends StatelessWidget {
],
),
BookmarkButton(
- gestureSize: 24,
+ itemId: news.id,
+ type: 'news',
+ gestureSize: 32,
value: news.marked,
- onMarkChanged: (value) => onMarkChanged(news.id, value),
+ onMarkChanged: (value) => onMarkChanged(news.id, value, false),
askForConfirmation: hasUnmarkConfirmation,
),
],
diff --git a/lib/views/home/widgets/overview/podcast.dart b/lib/views/home/widgets/overview/podcast.dart
index 1a243df..24ce673 100644
--- a/lib/views/home/widgets/overview/podcast.dart
+++ b/lib/views/home/widgets/overview/podcast.dart
@@ -1,7 +1,9 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
+import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/studio.dart';
+import 'package:didvan/providers/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';
@@ -12,18 +14,21 @@ 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/foundation.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class PodcastOverview extends StatelessWidget {
final OverviewData podcast;
- final void Function(int id, bool value) onMarkChanged;
- final StudioRequestArgs? studioRequestArgs;
+ final void Function(int id, bool value, bool shouldUpdate) onMarkChanged;
+ final StudioRequestArgs studioRequestArgs;
+ final bool hasUnmarkConfirmation;
const PodcastOverview({
Key? key,
required this.podcast,
required this.onMarkChanged,
- this.studioRequestArgs,
+ required this.studioRequestArgs,
+ this.hasUnmarkConfirmation = false,
}) : super(key: key);
@override
@@ -71,28 +76,62 @@ class PodcastOverview extends StatelessWidget {
overflow: TextOverflow.ellipsis,
),
const DidvanDivider(verticalPadding: 8),
- Row(
- children: [
- DurationWidget(duration: podcast.duration!),
- const Spacer(),
- DidvanIconButton(
- gestureSize: 28,
- icon: DidvanIcons.download_regular,
- onPressed: () {},
- ),
- const SizedBox(width: 16),
- BookmarkButton(
- gestureSize: 24,
- value: podcast.marked,
- onMarkChanged: (value) => onMarkChanged(podcast.id, value),
- ),
- ],
+ Consumer(
+ builder: (context, state, child) => Row(
+ children: [
+ DurationWidget(duration: podcast.duration!),
+ const Spacer(),
+ if (!kIsWeb) ...[
+ if (state.appState == AppState.idle ||
+ !state.downloadQueue.contains(podcast.link))
+ DidvanIconButton(
+ gestureSize: 28,
+ color: _isDownloaded
+ ? Theme.of(context).colorScheme.primary
+ : null,
+ icon: _isDownloaded
+ ? DidvanIcons.download_solid
+ : DidvanIcons.download_regular,
+ onPressed: _isDownloaded
+ ? () {}
+ : () => state.download(
+ fileName: 'podcast-${podcast.id}.mp3',
+ isVideo: false,
+ url: podcast.link!,
+ ),
+ ),
+ if (state.appState == AppState.busy &&
+ state.downloadQueue.contains(podcast.link))
+ const SizedBox(
+ width: 18,
+ height: 18,
+ child: CircularProgressIndicator(
+ strokeWidth: 2,
+ ),
+ ),
+ const SizedBox(width: 16),
+ ],
+ BookmarkButton(
+ itemId: podcast.id,
+ type: 'podcast',
+ askForConfirmation: hasUnmarkConfirmation,
+ gestureSize: 32,
+ value: podcast.marked,
+ onMarkChanged: (value) =>
+ onMarkChanged(podcast.id, value, false),
+ ),
+ ],
+ ),
),
],
),
);
}
+ bool get _isDownloaded {
+ return MediaProvider.downloadedItemIds.contains(podcast.id);
+ }
+
static Widget get placeholder => DidvanCard(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
diff --git a/lib/views/home/widgets/overview/radar.dart b/lib/views/home/widgets/overview/radar.dart
index cd88ff3..3d0e6ff 100644
--- a/lib/views/home/widgets/overview/radar.dart
+++ b/lib/views/home/widgets/overview/radar.dart
@@ -16,7 +16,7 @@ import 'package:flutter/material.dart';
class RadarOverview extends StatelessWidget {
final OverviewData radar;
final void Function(int id, int count) onCommentsChanged;
- final void Function(int id, bool value) onMarkChanged;
+ final void Function(int id, bool value, bool shouldUpdate) onMarkChanged;
final bool hasUnmarkConfirmation;
final RadarRequestArgs? radarRequestArgs;
const RadarOverview({
@@ -34,7 +34,7 @@ class RadarOverview extends StatelessWidget {
onTap: () => Navigator.of(context).pushNamed(
Routes.radarDetails,
arguments: {
- 'onMarkChanged': onMarkChanged,
+ 'onMarkChanged': (id, value) => onMarkChanged(id, value, true),
'onCommentsChanged': onCommentsChanged,
'id': radar.id,
'args': radarRequestArgs,
@@ -102,13 +102,6 @@ class RadarOverview extends StatelessWidget {
const DidvanDivider(),
Row(
children: [
- BookmarkButton(
- gestureSize: 24,
- value: radar.marked,
- onMarkChanged: (value) => onMarkChanged(radar.id, value),
- askForConfirmation: hasUnmarkConfirmation,
- ),
- const Spacer(),
if (radar.comments != 0) DidvanText(radar.comments.toString()),
const SizedBox(width: 4),
DidvanIconButton(
@@ -117,7 +110,7 @@ class RadarOverview extends StatelessWidget {
onPressed: () => Navigator.of(context).pushNamed(
Routes.comments,
arguments: {
- 'isRadar': true,
+ 'type': 'radar',
'title': radar.title,
'id': radar.id,
'onCommentsChanged': (count) =>
@@ -125,10 +118,19 @@ class RadarOverview extends StatelessWidget {
},
),
),
- const SizedBox(width: 16),
+ // const SizedBox(width: 16),
// const DidvanText('10'),
// const SizedBox(width: 4),
// const Icon(DidvanIcons.evaluation_regular),
+ const Spacer(),
+ BookmarkButton(
+ itemId: radar.id,
+ type: 'radar',
+ gestureSize: 32,
+ value: radar.marked,
+ onMarkChanged: (value) => onMarkChanged(radar.id, value, false),
+ askForConfirmation: hasUnmarkConfirmation,
+ ),
],
),
],
diff --git a/lib/views/home/widgets/overview/video.dart b/lib/views/home/widgets/overview/video.dart
index 3150b34..b70918b 100644
--- a/lib/views/home/widgets/overview/video.dart
+++ b/lib/views/home/widgets/overview/video.dart
@@ -4,31 +4,26 @@ 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';
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';
-import 'package:provider/provider.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 void Function(int id, bool value, bool shouldUpdate) onMarkChanged;
final bool hasUnmarkConfirmation;
- final StudioRequestArgs? studioRequestArgs;
+ final StudioRequestArgs studioRequestArgs;
const VideoOverview({
Key? key,
required this.video,
- required this.onCommentsChanged,
required this.onMarkChanged,
- required this.hasUnmarkConfirmation,
- this.studioRequestArgs,
+ required this.studioRequestArgs,
+ this.hasUnmarkConfirmation = false,
}) : super(key: key);
@override
@@ -37,41 +32,34 @@ class VideoOverview extends StatelessWidget {
onTap: () => Navigator.of(context).pushNamed(
Routes.studioDetails,
arguments: {
- 'onMarkChanged': onMarkChanged,
- 'onCommentsChanged': onCommentsChanged,
+ 'onMarkChanged': (id, value) => onMarkChanged(id, value, true),
'id': video.id,
'args': studioRequestArgs,
'hasUnmarkConfirmation': hasUnmarkConfirmation,
'isVideo': true,
- 'state': context.read(),
},
),
child: Row(
children: [
Stack(
+ alignment: Alignment.center,
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,
- ),
- ),
+ 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,
),
),
],
@@ -105,16 +93,21 @@ class VideoOverview extends StatelessWidget {
children: [
DurationWidget(duration: video.duration!),
const Spacer(),
- DidvanIconButton(
- gestureSize: 28,
- icon: DidvanIcons.download_regular,
- onPressed: () {},
- ),
- const SizedBox(width: 16),
+ // DidvanIconButton(
+ // gestureSize: 28,
+ // icon: DidvanIcons.download_regular,
+ // onPressed: () =>
+ // context.read().download(video.media!),
+ // ),
+ // const SizedBox(width: 16),
BookmarkButton(
- gestureSize: 24,
+ itemId: video.id,
+ type: 'video',
+ gestureSize: 32,
value: video.marked,
- onMarkChanged: (value) => onMarkChanged(video.id, value),
+ onMarkChanged: (value) =>
+ onMarkChanged(video.id, value, false),
+ askForConfirmation: hasUnmarkConfirmation,
),
],
),
diff --git a/lib/views/home/widgets/player_controller_button.dart b/lib/views/home/widgets/player_controller_button.dart
deleted file mode 100644
index 7e2cc14..0000000
--- a/lib/views/home/widgets/player_controller_button.dart
+++ /dev/null
@@ -1,34 +0,0 @@
-import 'dart:io';
-
-import 'package:didvan/config/theme_data.dart';
-import 'package:didvan/constants/app_icons.dart';
-import 'package:didvan/services/media/media.dart';
-import 'package:didvan/views/widgets/didvan/icon_button.dart';
-import 'package:flutter/material.dart';
-
-class AudioControllerButton extends StatelessWidget {
- final String? audioUrl;
- final File? audioFile;
-
- const AudioControllerButton({Key? key, this.audioUrl, this.audioFile})
- : super(key: key);
-
- bool get _nowPlaying =>
- MediaService.audioPlayerTag == audioUrl ||
- audioFile != null && MediaService.audioPlayerTag == audioFile!.path;
-
- @override
- Widget build(BuildContext context) {
- return DidvanIconButton(
- icon: MediaService.audioPlayer.playing == true && _nowPlaying
- ? DidvanIcons.pause_circle_solid
- : DidvanIcons.play_circle_solid,
- color: Theme.of(context).colorScheme.focusedBorder,
- onPressed: () {
- MediaService.handleAudioPlayback(
- audioSource: audioFile ?? audioUrl,
- );
- },
- );
- }
-}
diff --git a/lib/views/home/widgets/search_field.dart b/lib/views/home/widgets/search_field.dart
index dea0aa1..9a979b7 100644
--- a/lib/views/home/widgets/search_field.dart
+++ b/lib/views/home/widgets/search_field.dart
@@ -125,6 +125,7 @@ class _SearchFieldState extends State {
@override
void dispose() {
+ widget.focusNode.removeListener(() {});
super.dispose();
}
}
diff --git a/lib/views/home/widgets/tag_item.dart b/lib/views/home/widgets/tag_item.dart
index 1265361..56d14ef 100644
--- a/lib/views/home/widgets/tag_item.dart
+++ b/lib/views/home/widgets/tag_item.dart
@@ -9,18 +9,26 @@ import 'package:flutter/material.dart';
class TagItem extends StatelessWidget {
final Tag tag;
+ final void Function(int id, bool value) onMarkChanged;
+ final String type;
const TagItem({
Key? key,
required this.tag,
+ required this.onMarkChanged,
+ required this.type,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return InkWrapper(
borderRadius: DesignConfig.lowBorderRadius,
- onPressed: () =>
- Navigator.of(context).pushNamed(Routes.hashtag, arguments: tag),
+ onPressed: () => Navigator.of(context).pushNamed(Routes.hashtag,
+ arguments: {
+ 'tag': tag,
+ 'onMarkChanged': onMarkChanged,
+ 'type': type
+ }),
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 4,
diff --git a/lib/views/splash/splash.dart b/lib/views/splash/splash.dart
index 72e3b26..27943c1 100644
--- a/lib/views/splash/splash.dart
+++ b/lib/views/splash/splash.dart
@@ -2,9 +2,10 @@ import 'dart:developer';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/main.dart';
-import 'package:didvan/providers/server_data_provider.dart';
-import 'package:didvan/providers/theme_provider.dart';
-import 'package:didvan/providers/user_provider.dart';
+import 'package:didvan/providers/media.dart';
+import 'package:didvan/providers/server_data.dart';
+import 'package:didvan/providers/theme.dart';
+import 'package:didvan/providers/user.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/network/request.dart';
@@ -49,8 +50,8 @@ class _SplashState extends State {
value: DesignConfig.systemUiOverlayStyle.copyWith(
systemNavigationBarColor: Theme.of(context).colorScheme.background,
),
- child: Scaffold(
- body: Container(
+ child: Material(
+ child: Container(
alignment: Alignment.center,
padding: const EdgeInsets.all(60),
color: Theme.of(context).colorScheme.background,
@@ -93,7 +94,6 @@ class _SplashState extends State {
.removeWhere((key, value) => key == 'image-cache');
});
}
- await AppInitializer.setupServices();
final settingsData = await AppInitializer.initilizeSettings();
final themeProvider = context.read();
themeProvider.themeMode = settingsData.themeMode;
@@ -105,10 +105,12 @@ class _SplashState extends State {
_isGettingThemeData = false;
}),
);
+ await AppInitializer.setupServices();
final userProvider = context.read();
final String? token = await userProvider.setAndGetToken();
if (token != null) {
log(token);
+ context.read().getDownloadsList();
RequestService.token = token;
final result = await userProvider.getUserInfo();
if (!result) {
diff --git a/lib/views/widgets/didvan/app_bar.dart b/lib/views/widgets/didvan/app_bar.dart
index f7f00e3..81770c8 100644
--- a/lib/views/widgets/didvan/app_bar.dart
+++ b/lib/views/widgets/didvan/app_bar.dart
@@ -20,7 +20,7 @@ class DidvanAppBar extends StatelessWidget {
return Container(
height: appBarData.isSmall ? 56 : 72,
width: MediaQuery.of(context).size.width,
- padding: const EdgeInsets.only(right: 4, left: 20),
+ padding: const EdgeInsets.only(right: 4),
decoration: BoxDecoration(
border: hasBorder
? Border(
diff --git a/lib/views/widgets/didvan/bnb.dart b/lib/views/widgets/didvan/bnb.dart
new file mode 100644
index 0000000..a89de8f
--- /dev/null
+++ b/lib/views/widgets/didvan/bnb.dart
@@ -0,0 +1,413 @@
+import 'package:assets_audio_player/assets_audio_player.dart';
+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/enums.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/studio/studio_details/widgets/studio_details_widget.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:flutter_spinkit/flutter_spinkit.dart';
+import 'package:provider/provider.dart';
+
+class DidvanBNB extends StatelessWidget {
+ final int currentTabIndex;
+ final void Function(int index) onTabChanged;
+
+ const DidvanBNB(
+ {Key? key, required this.currentTabIndex, required this.onTabChanged})
+ : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Stack(
+ children: [
+ const _PlayerNavBar(),
+ Positioned(
+ bottom: 0,
+ left: 0,
+ right: 0,
+ child: Container(
+ height: 72,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.surface,
+ borderRadius:
+ const BorderRadius.vertical(top: Radius.circular(16)),
+ boxShadow: [
+ BoxShadow(
+ color: const Color(0XFF1B3C59).withOpacity(0.15),
+ blurRadius: 8,
+ spreadRadius: 0,
+ offset: const Offset(0, -8),
+ )
+ ],
+ ),
+ padding: const EdgeInsets.symmetric(horizontal: 12),
+ child: Row(
+ children: [
+ _NavBarItem(
+ isSelected: currentTabIndex == 0,
+ title: 'اخبار',
+ selectedIcon: DidvanIcons.news_solid,
+ unselectedIcon: DidvanIcons.news_light,
+ onTap: () => onTabChanged(0),
+ ),
+ _NavBarItem(
+ isSelected: currentTabIndex == 1,
+ title: 'آمار',
+ selectedIcon: DidvanIcons.chart_solid,
+ unselectedIcon: DidvanIcons.chart_light,
+ onTap: () => onTabChanged(1),
+ ),
+ _NavBarItem(
+ isSelected: currentTabIndex == 2,
+ title: 'رادار',
+ selectedIcon: DidvanIcons.radar_solid,
+ unselectedIcon: DidvanIcons.radar_light,
+ onTap: () => onTabChanged(2),
+ ),
+ _NavBarItem(
+ isSelected: currentTabIndex == 3,
+ title: 'استودیو',
+ selectedIcon: DidvanIcons.play_circle_solid,
+ unselectedIcon: DidvanIcons.play_circle_light,
+ onTap: () => onTabChanged(3),
+ ),
+ _NavBarItem(
+ isSelected: currentTabIndex == 4,
+ title: 'تنظیمات',
+ selectedIcon: DidvanIcons.setting_solid,
+ unselectedIcon: DidvanIcons.setting_light,
+ onTap: () => onTabChanged(4),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ],
+ );
+ }
+}
+
+class _PlayerNavBar extends StatelessWidget {
+ const _PlayerNavBar({Key? key}) : super(key: key);
+
+ bool _enablePlayerController(StudioDetailsState state) =>
+ MediaService.currentPodcast != null ||
+ (MediaService.audioPlayerTag?.contains('podcast') ?? false);
+
+ @override
+ Widget build(BuildContext context) {
+ return StreamBuilder(
+ stream: MediaService.audioPlayer.isPlaying,
+ builder: (context, snapshot) => GestureDetector(
+ onTap: () => MediaService.currentPodcast == null
+ ? null
+ : _showPlayerBottomSheet(context),
+ child: Consumer(
+ builder: (context, state, child) => AnimatedContainer(
+ padding: const EdgeInsets.only(top: 12),
+ duration: DesignConfig.lowAnimationDuration,
+ height: _enablePlayerController(state) ? 128 : 72,
+ decoration: BoxDecoration(
+ color: DesignConfig.isDark
+ ? Theme.of(context).colorScheme.focused
+ : Theme.of(context).colorScheme.navigation,
+ borderRadius: const BorderRadius.vertical(
+ top: Radius.circular(16),
+ ),
+ ),
+ alignment: Alignment.topCenter,
+ child: Builder(builder: (context) {
+ if (!_enablePlayerController(state)) return const SizedBox();
+ if (state.appState == AppState.failed) {
+ Future.delayed(const Duration(seconds: 2), () {
+ MediaService.resetAudioPlayer();
+ });
+ return DidvanText(
+ 'اتصال اینترنت برقرار نمیباشد',
+ color: DesignConfig.isDark
+ ? Theme.of(context).colorScheme.title
+ : Theme.of(context).colorScheme.secondCTA,
+ );
+ }
+ if (MediaService.currentPodcast == null) {
+ return SizedBox(
+ height: 32,
+ child: Center(
+ child: SpinKitThreeBounce(
+ size: 18,
+ color: DesignConfig.isDark
+ ? Theme.of(context).colorScheme.title
+ : Theme.of(context).colorScheme.secondCTA,
+ ),
+ ),
+ );
+ }
+ return SizedBox(
+ height: 56,
+ 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,
+ maxLines: 1,
+ overflow: TextOverflow.ellipsis,
+ color: DesignConfig.isDark
+ ? null
+ : Theme.of(context).colorScheme.secondCTA,
+ ),
+ AudioSlider(
+ disableThumb: true,
+ tag: MediaService.audioPlayerTag!,
+ ),
+ ],
+ ),
+ ),
+ StreamBuilder(
+ stream: MediaService.audioPlayer.onReadyToPlay,
+ builder: (context, snapshot) {
+ if (snapshot.data == null ||
+ state.appState == AppState.busy) {
+ return Padding(
+ padding: const EdgeInsets.only(
+ top: 4,
+ left: 16,
+ right: 16,
+ ),
+ child: SizedBox(
+ height: 18,
+ width: 18,
+ child: CircularProgressIndicator(
+ strokeWidth: 2,
+ color: DesignConfig.isDark
+ ? Theme.of(context).colorScheme.title
+ : Theme.of(context).colorScheme.secondCTA,
+ ),
+ ),
+ );
+ }
+ return const SizedBox();
+ },
+ ),
+ if (state.appState != AppState.busy &&
+ snapshot.data != null)
+ 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: () {
+ if (state.args.type == 'video') {
+ state.getStudioDetails(
+ MediaService.currentPodcast!.id,
+ args: state.podcastArgs,
+ fetchOnly: true,
+ );
+ }
+ MediaService.handleAudioPlayback(
+ audioSource: MediaService.currentPodcast!.link,
+ id: MediaService.currentPodcast!.id,
+ isVoiceMessage: false,
+ );
+ },
+ ),
+ ),
+ ],
+ ),
+ );
+ }),
+ ),
+ ),
+ ),
+ );
+ }
+
+ void _showPlayerBottomSheet(BuildContext context) {
+ final sheetKey = GlobalKey();
+ bool isExpanded = false;
+ final detailsState = context.read();
+ if (detailsState.args.type == 'video') {
+ detailsState.getStudioDetails(
+ MediaService.currentPodcast!.id,
+ args: detailsState.podcastArgs,
+ fetchOnly: true,
+ );
+ }
+ final state = context.read();
+ showModalBottomSheet(
+ backgroundColor: Colors.transparent,
+ context: context,
+ isScrollControlled: true,
+ builder: (context) => ChangeNotifierProvider.value(
+ value: state,
+ child: Consumer(
+ builder: (context, state, child) => ExpandableBottomSheet(
+ key: sheetKey,
+ background: Align(
+ alignment: Alignment.bottomCenter,
+ child: Container(
+ height: MediaQuery.of(context).size.height * 0.7,
+ color: Theme.of(context).colorScheme.surface,
+ ),
+ ),
+ persistentHeader: GestureDetector(
+ onVerticalDragUpdate: (details) {
+ if (details.delta.dy > 10) {
+ Navigator.of(context).pop();
+ }
+ },
+ child: 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_up_regular,
+ onPressed: () {
+ if (!isExpanded) {
+ sheetKey.currentState?.expand();
+ isExpanded = true;
+ } else {
+ isExpanded = false;
+ sheetKey.currentState?.contract();
+ }
+ },
+ ),
+ const SizedBox(height: 16),
+ ],
+ ),
+ ),
+ ],
+ ),
+ ),
+ expandableContent: state.appState == AppState.busy
+ ? Container(
+ height: MediaQuery.of(context).size.height / 2,
+ alignment: Alignment.center,
+ child: SpinKitSpinningLines(
+ color: Theme.of(context).colorScheme.primary,
+ ),
+ )
+ : StudioDetailsWidget(
+ onMarkChanged: (id, value) =>
+ context.read().changeMark(id, value, true),
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
+
+class _NavBarItem extends StatelessWidget {
+ final VoidCallback onTap;
+ final bool isSelected;
+ final String title;
+ final IconData selectedIcon;
+ final IconData unselectedIcon;
+ const _NavBarItem({
+ Key? key,
+ required this.isSelected,
+ required this.title,
+ required this.selectedIcon,
+ required this.unselectedIcon,
+ required this.onTap,
+ }) : super(key: key);
+
+ @override
+ Widget build(BuildContext context) {
+ return Expanded(
+ child: Tooltip(
+ message: title,
+ decoration: BoxDecoration(
+ color: Theme.of(context).colorScheme.title,
+ borderRadius: DesignConfig.highBorderRadius,
+ boxShadow: DesignConfig.defaultShadow,
+ ),
+ child: GestureDetector(
+ onTap: onTap,
+ child: Container(
+ color: Colors.transparent,
+ child: Column(
+ children: [
+ const SizedBox(
+ height: 4,
+ ),
+ AnimatedContainer(
+ padding: const EdgeInsets.all(4),
+ duration: DesignConfig.lowAnimationDuration,
+ decoration: BoxDecoration(
+ shape: BoxShape.circle,
+ color: isSelected
+ ? Theme.of(context).colorScheme.focused
+ : Theme.of(context).colorScheme.surface,
+ ),
+ child: Icon(
+ isSelected ? selectedIcon : unselectedIcon,
+ size: 32,
+ color: DesignConfig.isDark
+ ? Theme.of(context).colorScheme.text
+ : Theme.of(context).colorScheme.title,
+ ),
+ ),
+ DidvanText(
+ title,
+ style: Theme.of(context).textTheme.caption,
+ color: Theme.of(context).colorScheme.title,
+ ),
+ const Spacer(),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/lib/views/widgets/didvan/page_view.dart b/lib/views/widgets/didvan/page_view.dart
index fd3a8e9..a7cb236 100644
--- a/lib/views/widgets/didvan/page_view.dart
+++ b/lib/views/widgets/didvan/page_view.dart
@@ -21,6 +21,7 @@ class DidvanPageView extends StatefulWidget {
final int initialIndex;
final int currentIndex;
final bool isRadar;
+ final void Function(int id, bool value) onMarkChanged;
final ScrollController scrollController;
final void Function(int index) onPageChanged;
@@ -32,6 +33,7 @@ class DidvanPageView extends StatefulWidget {
required this.onPageChanged,
required this.isRadar,
required this.currentIndex,
+ required this.onMarkChanged,
}) : super(key: key);
@override
@@ -119,7 +121,11 @@ class _DidvanPageViewState extends State {
runSpacing: 8,
children: [
for (var i = 0; i < item.tags.length; i++)
- TagItem(tag: item.tags[i]),
+ TagItem(
+ tag: item.tags[i],
+ onMarkChanged: widget.onMarkChanged,
+ type: widget.isRadar ? 'radar' : 'news',
+ ),
],
),
),
diff --git a/lib/views/widgets/didvan/scaffold.dart b/lib/views/widgets/didvan/scaffold.dart
index 0dabcf2..38f9dde 100644
--- a/lib/views/widgets/didvan/scaffold.dart
+++ b/lib/views/widgets/didvan/scaffold.dart
@@ -9,15 +9,21 @@ class DidvanScaffold extends StatefulWidget {
final EdgeInsets padding;
final Color? backgroundColor;
final bool reverse;
+ final ScrollPhysics? physics;
+ final ScrollController? scrollController;
+ final bool showSliversFirst;
const DidvanScaffold({
Key? key,
this.slivers,
required this.appBarData,
this.children,
+ this.physics,
this.padding = const EdgeInsets.symmetric(horizontal: 16),
this.backgroundColor,
this.reverse = false,
+ this.scrollController,
+ this.showSliversFirst = false,
}) : super(key: key);
@override
@@ -25,7 +31,13 @@ class DidvanScaffold extends StatefulWidget {
}
class _DidvanScaffoldState extends State {
- final _scrollController = ScrollController();
+ late final ScrollController _scrollController;
+
+ @override
+ void initState() {
+ _scrollController = widget.scrollController ?? ScrollController();
+ super.initState();
+ }
@override
Widget build(BuildContext context) {
@@ -33,10 +45,13 @@ class _DidvanScaffoldState extends State {
return Scaffold(
backgroundColor: widget.backgroundColor,
body: Padding(
- padding: EdgeInsets.only(top: statusBarHeight),
+ padding: widget.appBarData == null
+ ? EdgeInsets.zero
+ : EdgeInsets.only(top: statusBarHeight),
child: Stack(
children: [
CustomScrollView(
+ physics: widget.physics,
controller: _scrollController,
reverse: widget.reverse,
slivers: [
@@ -50,7 +65,7 @@ class _DidvanScaffoldState extends State {
pinned: true,
flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!),
),
- if (widget.children != null)
+ if (widget.children != null && !widget.showSliversFirst)
SliverPadding(
padding: widget.padding,
sliver: SliverList(
@@ -66,6 +81,16 @@ class _DidvanScaffoldState extends State {
padding: widget.padding,
sliver: widget.slivers![i],
),
+ if (widget.children != null && widget.showSliversFirst)
+ SliverPadding(
+ padding: widget.padding,
+ sliver: SliverList(
+ delegate: SliverChildBuilderDelegate(
+ (context, index) => widget.children![index],
+ childCount: widget.children!.length,
+ ),
+ ),
+ ),
if (widget.reverse)
SliverToBoxAdapter(
child: SizedBox(
diff --git a/lib/views/widgets/skeleton_image.dart b/lib/views/widgets/skeleton_image.dart
index c59f431..0c16a7b 100644
--- a/lib/views/widgets/skeleton_image.dart
+++ b/lib/views/widgets/skeleton_image.dart
@@ -24,29 +24,23 @@ class SkeletonImage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _aspectRatioGenerator(
- child: CachedNetworkImage(
- imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
- httpHeaders: {'Authorization': 'Bearer ${RequestService.token}'},
- width: width,
- height: height,
- imageUrl: RequestHelper.baseUrl + imageUrl,
- imageBuilder: (context, imageProvider) => ClipRRect(
- borderRadius: borderRadius ?? DesignConfig.lowBorderRadius,
- child: Image(
- image: imageProvider,
- fit: BoxFit.cover,
- ),
- ),
- progressIndicatorBuilder: (context, url, progress) =>
- ShimmerPlaceholder(
- borderRadius: borderRadius,
+ child: ClipRRect(
+ borderRadius: borderRadius,
+ child: CachedNetworkImage(
+ fit: BoxFit.cover,
+ imageRenderMethodForWeb: ImageRenderMethodForWeb.HttpGet,
+ httpHeaders: {'Authorization': 'Bearer ${RequestService.token}'},
+ width: width,
+ height: height,
+ imageUrl: RequestHelper.baseUrl + imageUrl,
+ placeholder: (context, _) => const ShimmerPlaceholder(),
),
),
);
}
Widget _aspectRatioGenerator({required Widget child}) => aspectRatio == null
- ? SizedBox(key: ValueKey(imageUrl), child: child)
+ ? child
: AspectRatio(
key: ValueKey(imageUrl),
aspectRatio: aspectRatio!,
diff --git a/lib/views/widgets/state_handlers/sliver_state_handler.dart b/lib/views/widgets/state_handlers/sliver_state_handler.dart
index cb762f5..1e70ddb 100644
--- a/lib/views/widgets/state_handlers/sliver_state_handler.dart
+++ b/lib/views/widgets/state_handlers/sliver_state_handler.dart
@@ -1,5 +1,5 @@
import 'package:didvan/models/enums.dart';
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/views/widgets/state_handlers/empty_connection.dart';
import 'package:flutter/material.dart';
@@ -40,7 +40,9 @@ class SliverStateHandler extends SliverList {
if (enableEmptyState && state.appState == AppState.idle) {
return Padding(
padding: EdgeInsets.only(
- top: centerEmptyState ? 120 : 20,
+ top: centerEmptyState
+ ? MediaQuery.of(context).size.height / 4
+ : 20,
bottom: 20,
),
child: emptyState,
diff --git a/lib/views/widgets/state_handlers/state_handler.dart b/lib/views/widgets/state_handlers/state_handler.dart
index 513ac3f..0addd30 100644
--- a/lib/views/widgets/state_handlers/state_handler.dart
+++ b/lib/views/widgets/state_handlers/state_handler.dart
@@ -1,5 +1,5 @@
import 'package:didvan/models/enums.dart';
-import 'package:didvan/providers/core_provider.dart';
+import 'package:didvan/providers/core.dart';
import 'package:didvan/views/widgets/state_handlers/empty_connection.dart';
import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
diff --git a/pubspec.lock b/pubspec.lock
index ab49287..f08bc28 100644
--- a/pubspec.lock
+++ b/pubspec.lock
@@ -1,6 +1,20 @@
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
+ assets_audio_player:
+ dependency: "direct main"
+ description:
+ name: assets_audio_player
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.0.4+1"
+ assets_audio_player_web:
+ dependency: transitive
+ description:
+ name: assets_audio_player_web
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.0.4+1"
async:
dependency: transitive
description:
@@ -8,13 +22,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.2"
- audio_session:
- dependency: transitive
- description:
- name: audio_session
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.1.6+1"
audio_video_progress_bar:
dependency: "direct main"
description:
@@ -22,6 +29,13 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.10.0"
+ better_player:
+ dependency: "direct main"
+ description:
+ name: better_player
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.0.81"
boolean_selector:
dependency: transitive
description:
@@ -126,7 +140,7 @@ packages:
name: day_night_time_picker
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.4+1"
+ version: "1.0.5"
expandable_bottom_sheet:
dependency: "direct main"
description:
@@ -161,7 +175,7 @@ packages:
name: firebase_core
url: "https://pub.dartlang.org"
source: hosted
- version: "1.13.1"
+ version: "1.14.0"
firebase_core_platform_interface:
dependency: transitive
description:
@@ -182,21 +196,21 @@ packages:
name: firebase_messaging
url: "https://pub.dartlang.org"
source: hosted
- version: "11.2.8"
+ version: "11.2.12"
firebase_messaging_platform_interface:
dependency: transitive
description:
name: firebase_messaging_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "3.2.1"
+ version: "3.2.2"
firebase_messaging_web:
dependency: transitive
description:
name: firebase_messaging_web
url: "https://pub.dartlang.org"
source: hosted
- version: "2.2.9"
+ version: "2.2.10"
flutter:
dependency: "direct main"
description: flutter
@@ -208,7 +222,7 @@ packages:
name: flutter_blurhash
url: "https://pub.dartlang.org"
source: hosted
- version: "0.6.0"
+ version: "0.6.4"
flutter_cache_manager:
dependency: transitive
description:
@@ -297,7 +311,7 @@ packages:
name: flutter_svg
url: "https://pub.dartlang.org"
source: hosted
- version: "1.0.0"
+ version: "1.0.3"
flutter_test:
dependency: "direct dev"
description: flutter
@@ -315,6 +329,20 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
+ flutter_widget_from_html_core:
+ dependency: transitive
+ description:
+ name: flutter_widget_from_html_core
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.8.5+1"
+ fwfh_text_style:
+ dependency: transitive
+ description:
+ name: fwfh_text_style
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "2.7.3+1"
graphs:
dependency: transitive
description:
@@ -349,28 +377,28 @@ packages:
name: image_cropper
url: "https://pub.dartlang.org"
source: hosted
- version: "1.5.0"
+ version: "1.5.1"
image_picker:
dependency: "direct main"
description:
name: image_picker
url: "https://pub.dartlang.org"
source: hosted
- version: "0.8.4+4"
+ version: "0.8.4+11"
image_picker_for_web:
dependency: transitive
description:
name: image_picker_for_web
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.4"
+ version: "2.1.6"
image_picker_platform_interface:
dependency: transitive
description:
name: image_picker_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "2.4.1"
+ version: "2.4.4"
intl:
dependency: transitive
description:
@@ -385,27 +413,6 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "0.6.3"
- just_audio:
- dependency: "direct main"
- description:
- name: just_audio
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.9.18"
- just_audio_platform_interface:
- dependency: transitive
- description:
- name: just_audio_platform_interface
- url: "https://pub.dartlang.org"
- source: hosted
- version: "4.0.0"
- just_audio_web:
- dependency: transitive
- description:
- name: just_audio_web
- url: "https://pub.dartlang.org"
- source: hosted
- version: "0.4.2"
lints:
dependency: transitive
description:
@@ -482,49 +489,49 @@ packages:
name: path_provider
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.8"
+ version: "2.0.9"
path_provider_android:
dependency: transitive
description:
name: path_provider_android
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.11"
+ version: "2.0.12"
path_provider_ios:
dependency: transitive
description:
name: path_provider_ios
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.7"
+ version: "2.0.8"
path_provider_linux:
dependency: transitive
description:
name: path_provider_linux
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.4"
+ version: "2.1.5"
path_provider_macos:
dependency: transitive
description:
name: path_provider_macos
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.4"
+ version: "2.0.5"
path_provider_platform_interface:
dependency: transitive
description:
name: path_provider_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.1"
+ version: "2.0.3"
path_provider_windows:
dependency: transitive
description:
name: path_provider_windows
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.4"
+ version: "2.0.5"
pedantic:
dependency: transitive
description:
@@ -532,6 +539,41 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "1.11.1"
+ permission_handler:
+ dependency: "direct main"
+ description:
+ name: permission_handler
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "9.2.0"
+ permission_handler_android:
+ dependency: transitive
+ description:
+ name: permission_handler_android
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "9.0.2+1"
+ permission_handler_apple:
+ dependency: transitive
+ description:
+ name: permission_handler_apple
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "9.0.4"
+ permission_handler_platform_interface:
+ dependency: transitive
+ description:
+ name: permission_handler_platform_interface
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "3.7.0"
+ permission_handler_windows:
+ dependency: transitive
+ description:
+ name: permission_handler_windows
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.1.0"
persian_datetime_picker:
dependency: "direct main"
description:
@@ -594,7 +636,7 @@ packages:
name: record
url: "https://pub.dartlang.org"
source: hosted
- version: "3.0.2"
+ version: "3.0.4"
record_platform_interface:
dependency: transitive
description:
@@ -648,14 +690,14 @@ packages:
name: sqflite
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.1"
+ version: "2.0.2"
sqflite_common:
dependency: transitive
description:
name: sqflite_common
url: "https://pub.dartlang.org"
source: hosted
- version: "2.1.0"
+ version: "2.2.1"
stack_trace:
dependency: transitive
description:
@@ -683,7 +725,7 @@ packages:
name: synchronized
url: "https://pub.dartlang.org"
source: hosted
- version: "3.0.0"
+ version: "3.0.0+2"
term_glyph:
dependency: transitive
description:
@@ -725,63 +767,63 @@ packages:
name: url_launcher
url: "https://pub.dartlang.org"
source: hosted
- version: "6.0.18"
+ version: "6.0.20"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
url: "https://pub.dartlang.org"
source: hosted
- version: "6.0.14"
+ version: "6.0.15"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
url: "https://pub.dartlang.org"
source: hosted
- version: "6.0.14"
+ version: "6.0.15"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.3"
+ version: "3.0.0"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.3"
+ version: "3.0.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.4"
+ version: "2.0.5"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.6"
+ version: "2.0.9"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
url: "https://pub.dartlang.org"
source: hosted
- version: "2.0.2"
+ version: "3.0.0"
uuid:
dependency: transitive
description:
name: uuid
url: "https://pub.dartlang.org"
source: hosted
- version: "3.0.5"
+ version: "3.0.6"
vector_math:
dependency: transitive
description:
@@ -789,6 +831,48 @@ packages:
url: "https://pub.dartlang.org"
source: hosted
version: "2.1.1"
+ visibility_detector:
+ dependency: transitive
+ description:
+ name: visibility_detector
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.2.2"
+ wakelock:
+ dependency: transitive
+ description:
+ name: wakelock
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.5.6"
+ wakelock_macos:
+ dependency: transitive
+ description:
+ name: wakelock_macos
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.4.0"
+ wakelock_platform_interface:
+ dependency: transitive
+ description:
+ name: wakelock_platform_interface
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.3.0"
+ wakelock_web:
+ dependency: transitive
+ description:
+ name: wakelock_web
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.4.0"
+ wakelock_windows:
+ dependency: transitive
+ description:
+ name: wakelock_windows
+ url: "https://pub.dartlang.org"
+ source: hosted
+ version: "0.2.0"
webview_flutter:
dependency: "direct main"
description:
@@ -802,7 +886,7 @@ packages:
name: webview_flutter_android
url: "https://pub.dartlang.org"
source: hosted
- version: "2.8.3"
+ version: "2.8.4"
webview_flutter_platform_interface:
dependency: transitive
description:
@@ -823,14 +907,14 @@ packages:
name: win32
url: "https://pub.dartlang.org"
source: hosted
- version: "2.3.3"
+ version: "2.5.1"
xdg_directories:
dependency: transitive
description:
name: xdg_directories
url: "https://pub.dartlang.org"
source: hosted
- version: "0.2.0"
+ version: "0.2.0+1"
xml:
dependency: transitive
description:
@@ -840,4 +924,4 @@ packages:
version: "5.3.1"
sdks:
dart: ">=2.16.0 <3.0.0"
- flutter: ">=2.5.0"
+ flutter: ">=2.10.0"
diff --git a/pubspec.yaml b/pubspec.yaml
index 7bae68f..dfc7dfa 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.2.0+1
+version: 1.5.0+10
environment:
sdk: ">=2.12.0 <3.0.0"
@@ -40,7 +40,7 @@ dependencies:
pin_code_fields: ^7.3.0
rive: ^0.7.33
image_picker: ^0.8.4+4
- day_night_time_picker: ^1.0.3+1
+ day_night_time_picker: ^1.0.5
path_provider: ^2.0.8
flutter_spinkit: ^5.1.0
flutter_svg: ^1.0.0
@@ -50,7 +50,6 @@ dependencies:
flutter_vibrate: ^1.3.0
universal_html: ^2.0.8
record: ^3.0.2
- just_audio: ^0.9.18
record_web: ^0.2.1
persian_datetime_picker: ^2.4.0
persian_number_utility: ^1.1.1
@@ -64,6 +63,9 @@ dependencies:
firebase_core: ^1.13.1
webview_flutter: ^3.0.1
expandable_bottom_sheet: ^1.1.1+1
+ permission_handler: ^9.2.0
+ better_player: ^0.0.81
+ assets_audio_player: ^3.0.4+1
dev_dependencies:
@@ -90,42 +92,12 @@ flutter:
# To add assets to your application, add an assets section, like this:
assets:
- - lib/assets/images/logos/logo-vertical-dark.svg
- - lib/assets/images/logos/logo-vertical-light.svg
- - lib/assets/images/logos/logo-horizontal-dark.svg
- - lib/assets/images/logos/logo-horizontal-light.svg
- - lib/assets/images/logos/studio-dark.svg
- - lib/assets/images/logos/studio-light.svg
- - lib/assets/images/categories/business-light.svg
- - lib/assets/images/categories/economic-light.svg
- - lib/assets/images/categories/enviromental-light.svg
- - lib/assets/images/categories/political-light.svg
- - lib/assets/images/categories/social-light.svg
- - lib/assets/images/categories/tech-light.svg
- - lib/assets/images/categories/business-dark.svg
- - lib/assets/images/categories/economic-dark.svg
- - lib/assets/images/categories/enviromental-dark.svg
- - lib/assets/images/categories/political-dark.svg
- - lib/assets/images/categories/social-dark.svg
- - lib/assets/images/categories/tech-dark.svg
- - lib/assets/images/themes/theme-light.svg
- - lib/assets/images/themes/theme-dark.svg
- - lib/assets/images/records/record-dark.svg
- - lib/assets/images/records/record-light.svg
- - lib/assets/images/empty_states/bookmark-light.svg
- - lib/assets/images/empty_states/chart-light.svg
- - lib/assets/images/empty_states/chat-light.svg
- - lib/assets/images/empty_states/connection-light.svg
- - lib/assets/images/empty_states/result-light.svg
- - lib/assets/images/empty_states/studio-light.svg
- - lib/assets/images/empty_states/bookmark-dark.svg
- - lib/assets/images/empty_states/chart-dark.svg
- - lib/assets/images/empty_states/chat-dark.svg
- - lib/assets/images/empty_states/connection-dark.svg
- - lib/assets/images/empty_states/result-dark.svg
- - lib/assets/images/empty_states/studio-dark.svg
- - lib/assets/animations/indicator-light.riv
- - lib/assets/animations/indicator-dark.riv
+ - lib/assets/images/logos/
+ - lib/assets/images/categories/
+ - lib/assets/images/themes/
+ - lib/assets/images/records/
+ - lib/assets/images/empty_states/
+ - lib/assets/animations/
- lib/assets/loading.gif
diff --git a/release.sh b/release.sh
new file mode 100644
index 0000000..9913c8f
--- /dev/null
+++ b/release.sh
@@ -0,0 +1,13 @@
+flutter clean
+flutter build apk
+cp build/app/outputs/flutter-apk/app-release.apk /users/arytan/desktop
+flutter build web --web-renderer canvaskit
+cd build/web
+fandogh login --username didvan --password 12799721
+fandogh image publish --version $1
+fandogh service apply -f ../../.fandogh/fandogh.yaml
+cd ../..
+flutter build ipa
+xcodebuild -exportArchive -exportOptionsPlist ios/runner/info.plist -archivePath build/ios/archive/runner.xcarchive -exportPath /users/arytan/desktop/App.ipa
+cp /users/arytan/desktop/didvan.ipa/didvan.ipa /users/arytan/desktop
+echo "Done!"
\ No newline at end of file
diff --git a/web/index.html b/web/index.html
index a2dfb65..b00093f 100644
--- a/web/index.html
+++ b/web/index.html
@@ -60,7 +60,7 @@
}
scriptLoaded = true;
var scriptTag = document.createElement('script');
- scriptTag.src = 'main.dart.js';
+ scriptTag.src = `main.dart.js?version=${Math.random()}`;
scriptTag.type = 'application/javascript';
document.body.append(scriptTag);
}