Merge branch 'dev' into 'master'
Dev See merge request Didvan/didvan-app!14
This commit is contained in:
commit
f9de89dd0e
|
|
@ -3,10 +3,14 @@
|
|||
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<application
|
||||
android:label="Didvan"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:usesCleartextTraffic="true">
|
||||
android:usesCleartextTraffic="true"
|
||||
android:requestLegacyExternalStorage="true">
|
||||
|
||||
|
||||
<activity
|
||||
|
||||
android:name=".MainActivity"
|
||||
|
|
|
|||
|
|
@ -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'
|
||||
|
|
|
|||
115
ios/Podfile.lock
115
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
|
||||
|
|
|
|||
|
|
@ -28,6 +28,10 @@
|
|||
<string>Main</string>
|
||||
<key>FirebaseAppDelegateProxyEnabled</key>
|
||||
<false/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
</array>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>We need to access to the microphone to record audio file</string>
|
||||
<key>NSPhotoLibraryUsageDescription</key>
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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<MediaProvider>(
|
||||
create: (context) => MediaProvider(),
|
||||
),
|
||||
ChangeNotifierProvider<UserProvider>(
|
||||
create: (context) => UserProvider(),
|
||||
),
|
||||
ChangeNotifierProvider<ThemeProvider>(
|
||||
create: (context) => ThemeProvider(),
|
||||
),
|
||||
ChangeNotifierProvider<StudioDetailsState>(
|
||||
create: (context) => StudioDetailsState(),
|
||||
),
|
||||
],
|
||||
child: Consumer<ThemeProvider>(
|
||||
builder: (context, themeProvider, child) => MaterialApp(
|
||||
|
|
|
|||
|
|
@ -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<Reply> replies;
|
||||
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> json) => FeedbackData(
|
||||
like: json['like'],
|
||||
|
|
|
|||
|
|
@ -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,18 +28,23 @@ 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<String, dynamic> json) => OverviewData(
|
||||
factory OverviewData.fromJson(Map<String, dynamic> 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: json['description'],
|
||||
description: parsedString,
|
||||
timeToRead: json['timeToRead'],
|
||||
reference: json['reference'],
|
||||
forManagers: json['forManagers'] ?? false,
|
||||
|
|
@ -45,8 +52,9 @@ class OverviewData {
|
|||
createdAt: json['createdAt'],
|
||||
duration: json['duration'],
|
||||
type: json['type'] ?? '',
|
||||
marked: json['marked'] ?? false,
|
||||
media: json['media'],
|
||||
marked: json['marked'] ?? true,
|
||||
link: json['link'],
|
||||
iframe: json['iframe'],
|
||||
categories: json['categories'] != null
|
||||
? List<CategoryData>.from(
|
||||
json['categories'].map(
|
||||
|
|
@ -55,6 +63,7 @@ class OverviewData {
|
|||
)
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> json) => SliderData(
|
||||
id: json['id'],
|
||||
title: json['title'],
|
||||
image: json['image'],
|
||||
link: json['link'],
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'image': image,
|
||||
};
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
@ -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<int> downloadedItemIds = [];
|
||||
final List<String> downloadQueue = [];
|
||||
|
||||
Future<void> 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<void> 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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<MapEntry> 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<void> _getDirectTypes() async {
|
||||
final service = RequestService(RequestHelper.directTypes);
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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<void> _registerFirebaseToken() async {
|
||||
final service = RequestService(RequestHelper.firebaseToken, body: {
|
||||
'token': AppInitializer.fcmToken,
|
||||
});
|
||||
await service.put();
|
||||
}
|
||||
|
||||
Future<bool> 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 {
|
||||
|
|
@ -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<StudioState>(
|
||||
create: (context) => StudioState(),
|
||||
),
|
||||
ChangeNotifierProvider<StudioDetailsState>(
|
||||
create: (context) => StudioDetailsState(),
|
||||
),
|
||||
],
|
||||
child: const Home(),
|
||||
),
|
||||
|
|
@ -106,12 +103,9 @@ class RouteGenerator {
|
|||
);
|
||||
case Routes.studioDetails:
|
||||
return _createRoute(
|
||||
ChangeNotifierProvider<StudioDetailsState>.value(
|
||||
value: (settings.arguments as Map<String, dynamic>)['state'],
|
||||
child: StudioDetails(
|
||||
StudioDetails(
|
||||
pageData: settings.arguments as Map<String, dynamic>,
|
||||
),
|
||||
),
|
||||
);
|
||||
case Routes.directList:
|
||||
return _createRoute(
|
||||
|
|
@ -147,17 +141,19 @@ class RouteGenerator {
|
|||
return _createRoute(
|
||||
ChangeNotifierProvider<HashtagState>(
|
||||
create: (context) => HashtagState(),
|
||||
child: Hashtag(tag: settings.arguments as Tag),
|
||||
child:
|
||||
Hashtag(pageData: settings.arguments as Map<String, dynamic>),
|
||||
),
|
||||
);
|
||||
|
||||
case Routes.filteredBookmarks:
|
||||
return _createRoute(
|
||||
ChangeNotifierProvider<FilteredBookmarksState>(
|
||||
create: (context) => FilteredBookmarksState(
|
||||
settings.arguments as String,
|
||||
(settings.arguments as Map<String, dynamic>)['type'],
|
||||
),
|
||||
child: const FilteredBookmarks(),
|
||||
child: FilteredBookmarks(
|
||||
onDeleted:
|
||||
(settings.arguments as Map<String, dynamic>)['onDeleted']),
|
||||
),
|
||||
);
|
||||
default:
|
||||
|
|
@ -184,18 +180,31 @@ class RouteGenerator {
|
|||
final shortestSide = MediaQuery.of(context).size.shortestSide;
|
||||
final bool useMobileLayout = shortestSide < 600;
|
||||
if (kIsWeb && !useMobileLayout) {
|
||||
return Container(
|
||||
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(
|
||||
return MediaQuery(
|
||||
data: MediaQuery.of(context).copyWith(textScaleFactor: 1.0),
|
||||
child: Container(
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
child: SafeArea(
|
||||
child: page,
|
||||
top: false,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<void> 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<void> _initializeFirebase() async {
|
||||
static Future<void> 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,
|
||||
|
|
|
|||
|
|
@ -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<void> 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();
|
||||
await audioPlayer.playOrPause();
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
await audioPlayer.stop();
|
||||
audioPlayerTag = tag;
|
||||
Audio audio;
|
||||
String source;
|
||||
if (isNetworkAudio) {
|
||||
await audioPlayer.setUrl(
|
||||
isVoiceMessage
|
||||
? (RequestHelper.baseUrl +
|
||||
if (isVoiceMessage) {
|
||||
source = RequestHelper.baseUrl +
|
||||
audioSource +
|
||||
'?accessToken=${RequestService.token}')
|
||||
: audioSource,
|
||||
'?accessToken=${RequestService.token}';
|
||||
} else {
|
||||
source = audioSource;
|
||||
}
|
||||
audio = Audio.network(
|
||||
kIsWeb ? source.replaceAll('%3A', ':') : source,
|
||||
metas: isVoiceMessage
|
||||
? null
|
||||
: Metas(
|
||||
artist: 'استودیو دیدوان',
|
||||
title: currentPodcast!.title,
|
||||
),
|
||||
);
|
||||
} else {
|
||||
if (kIsWeb) {
|
||||
await audioPlayer
|
||||
.setUrl(audioSource!.uri.path.replaceAll('%3A', ':'));
|
||||
} else {
|
||||
await audioPlayer.setFilePath(audioSource.path);
|
||||
}
|
||||
}
|
||||
audioPlayer.play();
|
||||
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<void> resetAudioPlayer() async {
|
||||
|
|
|
|||
|
|
@ -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<void> 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)) {
|
||||
|
|
|
|||
|
|
@ -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<MapEntry<String, dynamic>> additions) {
|
||||
String result = '';
|
||||
additions.removeWhere(
|
||||
|
|
|
|||
|
|
@ -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<void> 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<void> setValue({
|
||||
required String key,
|
||||
required dynamic value,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ class ActionSheetUtils {
|
|||
|
||||
static Future<void> 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(
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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<PasswordInput> {
|
|||
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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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<Comments> {
|
|||
void initState() {
|
||||
final state = context.read<CommentsState>();
|
||||
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<Comments> {
|
|||
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<Comments> {
|
|||
child: Stack(
|
||||
children: [
|
||||
DidvanScaffold(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
backgroundColor: Theme.of(context).colorScheme.surface,
|
||||
appBarData: AppBarData(
|
||||
appBarData: _isPage
|
||||
? AppBarData(
|
||||
hasBack: true,
|
||||
title: 'نظرات',
|
||||
subtitle: widget.pageData['title'],
|
||||
),
|
||||
)
|
||||
: null,
|
||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 92),
|
||||
slivers: [
|
||||
Consumer<CommentsState>(
|
||||
|
|
@ -71,7 +78,17 @@ class _CommentsState extends State<Comments> {
|
|||
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],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<CommentData> comments = [];
|
||||
final Map<int, MapEntry<bool, bool>> _feedbackQueue = {};
|
||||
|
||||
bool isRadar = true;
|
||||
int itemId = 0;
|
||||
|
||||
Future<void> 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<void> feedback(int id, bool like, bool dislike) async {
|
||||
Future<void> 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),
|
||||
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) {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class CommentState extends State<Comment> {
|
|||
duration: DesignConfig.lowAnimationDuration,
|
||||
isVisible: _showSubComments,
|
||||
child: _commentBuilder(
|
||||
isSubComment: true,
|
||||
isReply: true,
|
||||
comment: _comment.replies[i],
|
||||
),
|
||||
),
|
||||
|
|
@ -57,11 +57,10 @@ class CommentState extends State<Comment> {
|
|||
);
|
||||
}
|
||||
|
||||
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<Comment> {
|
|||
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<Comment> {
|
|||
],
|
||||
),
|
||||
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<Comment> {
|
|||
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<Comment> {
|
|||
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);
|
||||
},
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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<void> startRecording() async {
|
||||
text = null;
|
||||
await _recorder.hasPermission();
|
||||
if (!kIsWeb) {
|
||||
Vibrate.feedback(FeedbackType.medium);
|
||||
|
|
@ -88,6 +90,7 @@ class DirectState extends CoreProvier {
|
|||
|
||||
Future<void> sendMessage() async {
|
||||
if ((text == null || text!.isEmpty) && recordedFile == null) return;
|
||||
MediaService.audioPlayer.stop();
|
||||
messages.insert(
|
||||
0,
|
||||
MessageData(
|
||||
|
|
|
|||
|
|
@ -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<bool>(
|
||||
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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> pageData;
|
||||
const Hashtag({Key? key, required this.pageData}) : super(key: key);
|
||||
|
||||
@override
|
||||
_HashtagState createState() => _HashtagState();
|
||||
}
|
||||
|
||||
class _HashtagState extends State<Hashtag> {
|
||||
Tag get _tag => widget.pageData['tag'];
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final state = context.read<HashtagState>();
|
||||
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<Hashtag> {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DidvanScaffold(
|
||||
appBarData: AppBarData(title: '#' + widget.tag.label, hasBack: true),
|
||||
appBarData: AppBarData(title: '#' + _tag.label, hasBack: true),
|
||||
slivers: [
|
||||
Consumer<HashtagState>(
|
||||
builder: (context, state, child) => SliverStateHandler<HashtagState>(
|
||||
|
|
@ -50,19 +55,38 @@ class _HashtagState extends State<Hashtag> {
|
|||
}
|
||||
final item = state.items[index];
|
||||
final type = item.type;
|
||||
if (type == 'radar') {
|
||||
switch (type) {
|
||||
case 'radar':
|
||||
return RadarOverview(
|
||||
radar: item,
|
||||
onCommentsChanged: (id, count) => item.comments = count,
|
||||
onMarkChanged: (id, value) => item.marked = value,
|
||||
onMarkChanged: (_, value, __) =>
|
||||
_changeMark(item.id, value, type),
|
||||
onCommentsChanged: (_, count) => item.comments = count,
|
||||
);
|
||||
} else if (type == 'news') {
|
||||
case 'news':
|
||||
return NewsOverview(
|
||||
news: item,
|
||||
onMarkChanged: (id, value) => item.marked = value,
|
||||
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<Hashtag> {
|
|||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _changeMark(int id, bool value, String type) {
|
||||
final state = context.read<HashtagState>();
|
||||
state.items
|
||||
.firstWhere((element) => element.id == id && element.type == type)
|
||||
.marked = value;
|
||||
state.update();
|
||||
if (type == widget.pageData['type']) {
|
||||
widget.pageData['onMarkChanged'](id, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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]));
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import 'package:didvan/providers/core_provider.dart';
|
||||
import 'package:didvan/providers/core.dart';
|
||||
|
||||
class HomeState extends CoreProvier {
|
||||
int _currentPageIndex = 2;
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class _NewsState extends State<News> {
|
|||
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,
|
||||
|
|
|
|||
|
|
@ -38,12 +38,16 @@ class _NewsDetailsState extends State<NewsDetails> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Consumer<NewsDetailsState>(
|
||||
builder: (context, state, child) => StateHandler<NewsDetailsState>(
|
||||
builder: (context, state, child) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
state.handleTracking(sendRequest: true);
|
||||
return true;
|
||||
},
|
||||
child: StateHandler<NewsDetailsState>(
|
||||
onRetry: () => state.getNewsDetails(state.currentNews.id),
|
||||
state: state,
|
||||
builder: (context, state) => Stack(
|
||||
children: [
|
||||
if (state.news.isNotEmpty)
|
||||
IgnorePointer(
|
||||
ignoring: state.isFetchingNewItem,
|
||||
child: DidvanPageView(
|
||||
|
|
@ -53,9 +57,10 @@ class _NewsDetailsState extends State<NewsDetails> {
|
|||
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,
|
||||
|
|
@ -77,6 +82,7 @@ class _NewsDetailsState extends State<NewsDetails> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<void> _handleTracking({bool sendRequest = true}) async {
|
||||
Future<void> 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<void> getRelatedContents() async {
|
||||
|
|
|
|||
|
|
@ -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<void> onMarkChanged(int id, bool value) async {
|
||||
Future<void> onMarkChanged(int id, bool value, bool shouldUpdate) async {
|
||||
news.firstWhere((element) => element.id == id).marked = value;
|
||||
if (shouldUpdate) {
|
||||
notifyListeners();
|
||||
UserProvider.changeNewsMark(id, value);
|
||||
}
|
||||
}
|
||||
|
||||
bool get isFiltering => startDate != null || endDate != null;
|
||||
|
|
|
|||
|
|
@ -139,7 +139,7 @@ class _RadarState extends State<Radar> {
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -38,12 +38,16 @@ class _RadarDetailsState extends State<RadarDetails> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Consumer<RadarDetailsState>(
|
||||
builder: (context, state, child) => StateHandler<RadarDetailsState>(
|
||||
builder: (context, state, child) => WillPopScope(
|
||||
onWillPop: () async {
|
||||
state.handleTracking(sendRequest: true);
|
||||
return true;
|
||||
},
|
||||
child: StateHandler<RadarDetailsState>(
|
||||
onRetry: () => state.getRadarDetails(widget.pageData['id']),
|
||||
state: state,
|
||||
builder: (context, state) => Stack(
|
||||
children: [
|
||||
if (state.radars.isNotEmpty)
|
||||
IgnorePointer(
|
||||
ignoring: state.isFetchingNewItem,
|
||||
child: DidvanPageView(
|
||||
|
|
@ -53,9 +57,10 @@ class _RadarDetailsState extends State<RadarDetails> {
|
|||
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,
|
||||
|
|
@ -84,6 +89,7 @@ class _RadarDetailsState extends State<RadarDetails> {
|
|||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<void> _handleTracking({bool sendRequest = true}) async {
|
||||
Future<void> 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
|
||||
|
|
|
|||
|
|
@ -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<void> changeMark(int id, bool value) async {
|
||||
Future<void> changeMark(int id, bool value, bool shouldUpdate) async {
|
||||
radars.firstWhere((element) => element.id == id).marked = value;
|
||||
if (shouldUpdate) {
|
||||
notifyListeners();
|
||||
UserProvider.changeRadarMark(id, value);
|
||||
}
|
||||
}
|
||||
|
||||
void onCommentsChanged(int id, int count) {
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -131,7 +131,15 @@ class _BookmarksState extends State<Bookmarks> {
|
|||
|
||||
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<BookmarksState>();
|
||||
state.bookmarks
|
||||
.removeWhere((element) => element.id == id && element.type == type);
|
||||
state.update();
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
void _onChanged(String value) {
|
||||
|
|
|
|||
|
|
@ -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,11 +71,29 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
|
|||
hasUnmarkConfirmation: true,
|
||||
);
|
||||
}
|
||||
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,
|
||||
);
|
||||
},
|
||||
childCount: state.bookmarks.length,
|
||||
onRetry: () => state.getBookmarks(page: state.page),
|
||||
|
|
@ -81,9 +103,10 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
|
|||
);
|
||||
}
|
||||
|
||||
Future<void> _onBookmarkChanged(int id, bool value) async {
|
||||
Future<void> _onBookmarkChanged(int id, bool value, bool shouldUpdate) async {
|
||||
if (value) return;
|
||||
final state = context.read<FilteredBookmarksState>();
|
||||
state.onMarkChanged(id, false);
|
||||
widget.onDeleted?.call(id);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<OverviewData> bookmarks = [];
|
||||
final String type;
|
||||
int page = 1;
|
||||
|
|
@ -15,17 +12,8 @@ class FilteredBookmarksState extends CoreProvier {
|
|||
|
||||
FilteredBookmarksState(this.type);
|
||||
|
||||
bool get searching => search != '';
|
||||
|
||||
Future<void> 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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,10 +36,13 @@ class _DirectListState extends State<DirectList> {
|
|||
title: 'پیامها',
|
||||
trailing: state.unreadCount == 0
|
||||
? null
|
||||
: DidvanBadge(
|
||||
: Padding(
|
||||
padding: const EdgeInsets.only(left: 20),
|
||||
child: DidvanBadge(
|
||||
text: state.unreadCount.toString(),
|
||||
),
|
||||
),
|
||||
),
|
||||
slivers: [
|
||||
SliverStateHandler<DirectListState>(
|
||||
onRetry: state.getDirectsList,
|
||||
|
|
|
|||
|
|
@ -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<void> getDirectsList() async {
|
||||
appState = AppState.busy;
|
||||
final RequestService service = RequestService(RequestHelper.directs);
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
|
|
|
|||
|
|
@ -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<DirectListState>();
|
||||
int unreadCount = chatRoom.unread;
|
||||
chatRoom.unread = 0;
|
||||
state.unreadCount -= unreadCount;
|
||||
},
|
||||
child: Container(
|
||||
color: Colors.transparent,
|
||||
|
|
|
|||
|
|
@ -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<GeneralSettings> {
|
|||
return 'کوچک';
|
||||
}
|
||||
|
||||
int _intervalStart = 0;
|
||||
int _intervalEnd = 24;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Consumer<GeneralSettingsState>(
|
||||
|
|
@ -50,7 +54,13 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
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<GeneralSettings> {
|
|||
}
|
||||
|
||||
Future<void> _pickTimeRange(BuildContext context) async {
|
||||
final state = context.read<GeneralSettingsState>();
|
||||
_intervalStart = state.notificationTimeRange[0];
|
||||
_intervalEnd = state.notificationTimeRange[1];
|
||||
ActionSheetUtils.showBottomSheet(
|
||||
data: ActionSheetData(
|
||||
content: Row(
|
||||
|
|
@ -198,6 +211,9 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
),
|
||||
title: 'زمان دریافت اعلان',
|
||||
titleIcon: DidvanIcons.notification_regular,
|
||||
onConfirmed: () {
|
||||
state.notificationTimeRange = [_intervalStart, _intervalEnd];
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -222,7 +238,9 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
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<GeneralSettings> {
|
|||
}
|
||||
|
||||
Future<void> _openTimePicker(BuildContext context, int index) async {
|
||||
final GeneralSettingsState state = context.read<GeneralSettingsState>();
|
||||
await Navigator.of(context).push(
|
||||
showPicker(
|
||||
okText: 'تایید',
|
||||
|
|
@ -240,8 +257,9 @@ class _GeneralSettingsState extends State<GeneralSettings> {
|
|||
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<GeneralSettings> {
|
|||
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;
|
||||
},
|
||||
),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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<int> _notificationTimeRange = [0, 24];
|
||||
String _fontFamily = 'Dana-FA';
|
||||
double _fontSizeScale = 1;
|
||||
String _brightness = 'light';
|
||||
|
||||
set notificationTimeRange(List value) {
|
||||
set notificationTimeRange(List<int> 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<int> get notificationTimeRange => _notificationTimeRange;
|
||||
|
||||
set fontFamily(String value) {
|
||||
_fontFamily = value;
|
||||
|
|
@ -59,12 +63,27 @@ class GeneralSettingsState extends CoreProvier {
|
|||
|
||||
String get brightness => _brightness;
|
||||
|
||||
Future<void> _setSilenceInterval() async {
|
||||
final service = RequestService(RequestHelper.silenceInterval, body: {
|
||||
'start': notificationTimeRange[0],
|
||||
'end': notificationTimeRange[1]
|
||||
});
|
||||
await service.put();
|
||||
}
|
||||
|
||||
Future<void> 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');
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
|
|
@ -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<ProfilePhoto> {
|
|||
cancelButtonTitle: 'بازگشت',
|
||||
),
|
||||
androidUiSettings: const AndroidUiSettings(toolbarTitle: 'برش تصویر'),
|
||||
compressQuality: 70,
|
||||
compressQuality: 30,
|
||||
);
|
||||
if (file == null) return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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<Studio> createState() => _StudioState();
|
||||
}
|
||||
|
||||
class _StudioState extends State<Studio> {
|
||||
final _focusNode = FocusNode();
|
||||
Timer? _timer;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
context.read<StudioState>().init();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
return Consumer<StudioState>(
|
||||
builder: (context, state, child) => CustomScrollView(
|
||||
slivers: [
|
||||
SliverToBoxAdapter(
|
||||
child: Row(
|
||||
children: [
|
||||
const LogoAppBar(),
|
||||
Expanded(
|
||||
child: EmptyState(
|
||||
asset: Assets.emptyStudio,
|
||||
title: 'استودیو آینده',
|
||||
subtitle: 'به زودی...',
|
||||
titleColor: Theme.of(context).colorScheme.title,
|
||||
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<StudioState>(
|
||||
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<StudioState>();
|
||||
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<StudioState>();
|
||||
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),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<String, dynamic> pageData;
|
||||
|
||||
const StudioDetails({Key? key, required this.pageData}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StudioDetails> createState() => _StudioDetailsState();
|
||||
}
|
||||
|
||||
class _StudioDetailsState extends State<StudioDetails> {
|
||||
bool _isFullScreen = false;
|
||||
bool _isInit = true;
|
||||
|
||||
double _dwInPortrait = 0;
|
||||
double _scaleInPortrait = 1;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
final state = context.read<StudioDetailsState>();
|
||||
|
||||
Future.delayed(
|
||||
Duration.zero,
|
||||
() => state.getStudioDetails(widget.pageData['id']),
|
||||
);
|
||||
|
||||
state.args = widget.pageData['args'];
|
||||
if (Platform.isAndroid) WebView.platform = AndroidWebView();
|
||||
super.initState();
|
||||
}
|
||||
|
||||
Future<void> _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<StudioDetailsState>(
|
||||
builder: (context, state, child) => StateHandler<StudioDetailsState>(
|
||||
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(
|
||||
'''
|
||||
<html>
|
||||
<head>
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width, initial-scale=$_scaleInPortrait"
|
||||
/>
|
||||
<style>
|
||||
* {
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
overflow: hidden;
|
||||
}
|
||||
iframe {
|
||||
max-height: 100vh;
|
||||
}
|
||||
.r1_iframe_embed {
|
||||
height: ${MediaQuery.of(context).size.width / _scaleInPortrait}px !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
@media(max-width:580px){
|
||||
.r1_iframe_embed {
|
||||
height: ${_dwInPortrait * 9 / 16 / _scaleInPortrait}px !important;
|
||||
padding-top: 0 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
${state.currentStudio.media}
|
||||
</body>
|
||||
</html>
|
||||
''',
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, dynamic> pageData;
|
||||
|
||||
const StudioDetails({Key? key, required this.pageData}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StudioDetails> createState() => _StudioDetailsState();
|
||||
}
|
||||
|
||||
class _StudioDetailsState extends State<StudioDetails> {
|
||||
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<StudioDetailsState>();
|
||||
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<StudioDetailsState>(
|
||||
builder: (context, state, child) => StateHandler<StudioDetailsState>(
|
||||
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<void> _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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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<String, dynamic> pageData;
|
||||
|
||||
const StudioDetails({Key? key, required this.pageData}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<StudioDetails> createState() => _StudioDetailsState();
|
||||
}
|
||||
|
||||
class _StudioDetailsState extends State<StudioDetails> {
|
||||
@override
|
||||
void initState() {
|
||||
final state = context.read<StudioDetailsState>();
|
||||
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<StudioDetailsState>(
|
||||
builder: (context, state, child) => StateHandler<StudioDetailsState>(
|
||||
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(
|
||||
'<style>*{padding: 0 ; margin: 0; background: black;}</style>' +
|
||||
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),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<StudioDetailsData?> studios = [];
|
||||
late StudioDetailsData studio;
|
||||
StudioDetailsData? nextStudio;
|
||||
StudioDetailsData? prevStudio;
|
||||
late int initialIndex;
|
||||
late StudioRequestArgs args;
|
||||
int _selectedDetailsIndex = 0;
|
||||
bool isFetchingNewItem = false;
|
||||
StudioRequestArgs? podcastArgs;
|
||||
final List<int> 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<void> getStudioDetails(int id,
|
||||
{bool? isForward, StudioRequestArgs? args}) async {
|
||||
Future<void> 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;
|
||||
studio = StudioDetailsData.fromJson(result['studio']);
|
||||
if (result['nextStudio'].isNotEmpty && this.args.page != 0) {
|
||||
nextStudio = StudioDetailsData.fromJson(result['nextStudio']);
|
||||
}
|
||||
if (result['prevStudio'].isNotEmpty && this.args.page != 0) {
|
||||
prevStudio = StudioDetailsData.fromJson(result['prevStudio']);
|
||||
}
|
||||
if (isForward == null && !fetchOnly) {
|
||||
await _handlePodcastPlayback(studio);
|
||||
}
|
||||
alongSideState = AppState.idle;
|
||||
appState = AppState.idle;
|
||||
return;
|
||||
}
|
||||
if (this.args.type == 'podcast') {
|
||||
if (isForward == null) {
|
||||
appState = AppState.failed;
|
||||
} else {
|
||||
alongSideState = AppState.failed;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _handlePodcastPlayback(StudioDetailsData studio) async {
|
||||
if (args.type == 'podcast') {
|
||||
MediaService.currentPodcast = studio;
|
||||
MediaService.podcastPlaylistArgs = args;
|
||||
await MediaService.handleAudioPlayback(
|
||||
audioSource: studio.media,
|
||||
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);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
StudioDetailsData? prevStudio;
|
||||
if (result['prevStudio'].isNotEmpty) {
|
||||
prevStudio = StudioDetailsData.fromJson(result['prevStudio']);
|
||||
}
|
||||
|
||||
StudioDetailsData? nextStudio;
|
||||
if (result['nextStudio'].isNotEmpty) {
|
||||
nextStudio = StudioDetailsData.fromJson(result['nextStudio']);
|
||||
}
|
||||
|
||||
if (isForward == null) {
|
||||
studios
|
||||
.addAll(List.generate(max(studio.order - 2, 0), (index) => null));
|
||||
if (prevStudio != null) {
|
||||
studios.add(prevStudio);
|
||||
}
|
||||
studios.add(studio);
|
||||
if (nextStudio != null) {
|
||||
studios.add(nextStudio);
|
||||
}
|
||||
_currentIndex = initialIndex = studio.order - 1;
|
||||
} else if (isForward) {
|
||||
if (!exists(nextStudio) && nextStudio != null) {
|
||||
studios.add(nextStudio);
|
||||
}
|
||||
_currentIndex++;
|
||||
} else if (!isForward) {
|
||||
if (!exists(prevStudio) && prevStudio != null) {
|
||||
studios[_currentIndex - 2] = prevStudio;
|
||||
}
|
||||
_currentIndex--;
|
||||
}
|
||||
isFetchingNewItem = false;
|
||||
appState = AppState.idle;
|
||||
if (nextStudio != null && !_positionListenerActivated) {
|
||||
_positionListenerActivated = true;
|
||||
MediaService.audioPlayer.currentPosition.listen((event) {
|
||||
if (MediaService.audioPlayerTag?.contains('message') == true) {
|
||||
return;
|
||||
}
|
||||
//why? total page state shouldn't die!
|
||||
if (isForward == null) {
|
||||
appState = AppState.failed;
|
||||
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<void> 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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<StudioDetailsState>();
|
||||
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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<StudioDetailsState>(
|
||||
builder: (context, state, child) => StateHandler<StudioDetailsState>(
|
||||
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,
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<StudioDetailsState>(
|
||||
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<StudioDetailsState>(
|
||||
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<CommentsState>(
|
||||
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<StudioDetailsState>();
|
||||
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),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -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<OverviewData> studios = [];
|
||||
final List<SliderData> 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<void> _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<void> getStudioOverviews({required int page}) async {
|
||||
Future<void> 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<void> changeMark(int id, bool value) async {
|
||||
Future<void> changeMark(int id, bool value, bool shouldUpdate) async {
|
||||
studios.firstWhere((element) => element.id == id).marked = value;
|
||||
if (shouldUpdate) {
|
||||
notifyListeners();
|
||||
UserProvider.changeStudioMark(id, value);
|
||||
}
|
||||
}
|
||||
|
||||
void onCommentsChanged(int id, int count) {
|
||||
|
|
|
|||
|
|
@ -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<StudioSlider> createState() => _StudioSliderState();
|
||||
}
|
||||
|
||||
class _StudioSliderState extends State<StudioSlider> {
|
||||
int selectedIndex = 0;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final state = context.watch<StudioState>();
|
||||
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<StudioDetailsState>().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,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,14 +89,12 @@ class _StudioTypeButton extends StatelessWidget {
|
|||
size: 32,
|
||||
color: _color(context),
|
||||
),
|
||||
if (!isSelected) const SizedBox(height: 18),
|
||||
if (isSelected)
|
||||
Container(
|
||||
width: 88,
|
||||
AnimatedContainer(
|
||||
duration: DesignConfig.lowAnimationDuration,
|
||||
width: isSelected ? 88 : 0,
|
||||
height: 1,
|
||||
color: _color(context),
|
||||
),
|
||||
if (isSelected)
|
||||
DidvanText(
|
||||
title,
|
||||
style: Theme.of(context).textTheme.overline,
|
||||
|
|
|
|||
|
|
@ -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<StudioDetailsState>();
|
||||
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: [
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: StatefulBuilder(
|
||||
builder: (context, setState) => Column(
|
||||
children: [
|
||||
DidvanIconButton(
|
||||
icon: DidvanIcons.sleep_timer_regular,
|
||||
onPressed: () {},
|
||||
icon: state.timer == null && !state.stopOnPodcastEnds
|
||||
? DidvanIcons.sleep_timer_regular
|
||||
: DidvanIcons.sleep_enabled_regular,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
onPressed: () => _showSleepTimer(
|
||||
state,
|
||||
() => setState(() {}),
|
||||
),
|
||||
Column(
|
||||
),
|
||||
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.position.inSeconds + 30,
|
||||
seconds: MediaService.audioPlayer.currentPosition
|
||||
.value.inSeconds +
|
||||
30,
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const DidvanText('30', isEnglishFont: true),
|
||||
DidvanText(
|
||||
'30',
|
||||
isEnglishFont: true,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
_PlayPouseAnimatedIcon(
|
||||
audioSource: podcast.media,
|
||||
),
|
||||
Column(
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: StreamBuilder<bool>(
|
||||
stream: MediaService.audioPlayer.isPlaying,
|
||||
builder: (context, snapshot) {
|
||||
return _PlayPouseAnimatedIcon(
|
||||
audioSource: podcast.link,
|
||||
id: podcast.id,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
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:
|
||||
MediaService.audioPlayer.position.inSeconds - 10,
|
||||
seconds: max(
|
||||
0,
|
||||
MediaService.audioPlayer.currentPosition.value
|
||||
.inSeconds -
|
||||
10,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
const DidvanText('10', isEnglishFont: true),
|
||||
DidvanText(
|
||||
'10',
|
||||
isEnglishFont: true,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
),
|
||||
],
|
||||
),
|
||||
BookmarkButton(
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Center(
|
||||
child: BookmarkButton(
|
||||
itemId: state.studio.id,
|
||||
type: 'podcast',
|
||||
gestureSize: 48,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
value: podcast.marked,
|
||||
onMarkChanged: (value) {},
|
||||
onMarkChanged: (value) => context
|
||||
.read<StudioState>()
|
||||
.changeMark(podcast.id, value, true),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
@ -108,11 +186,136 @@ class AudioPlayerWidget extends StatelessWidget {
|
|||
),
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> _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),
|
||||
|
|
|
|||
|
|
@ -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<Duration>(
|
||||
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',
|
||||
'',
|
||||
|
|
|
|||
|
|
@ -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<bool>(
|
||||
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<ExpandableBottomSheetState>();
|
||||
bool isExpanded = false;
|
||||
final detailsState = context.read<StudioDetailsState>();
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => ChangeNotifierProvider<StudioDetailsState>.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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<BookmarkButton> {
|
|||
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<BookmarkButton> {
|
|||
_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:
|
||||
}
|
||||
}
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -104,6 +104,11 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
|
|||
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<FloatingNavigationBar> {
|
|||
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<FloatingNavigationBar> {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
onTap: () {
|
||||
if (item.type == 'podcast') {
|
||||
context.read<StudioDetailsState>().getStudioDetails(
|
||||
item.id,
|
||||
args: StudioRequestArgs(page: 0, type: item.type),
|
||||
);
|
||||
return;
|
||||
}
|
||||
Navigator.of(context).pushNamed(
|
||||
_targetPageRouteName,
|
||||
arguments: {
|
||||
'onMarkChanged': onMarkChanged,
|
||||
'id': item.id,
|
||||
'args': item.type == 'radar'
|
||||
? const RadarRequestArgs(page: 0)
|
||||
: const NewsRequestArgs(page: 0),
|
||||
'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,
|
||||
),
|
||||
]
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
Consumer<MediaProvider>(
|
||||
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,
|
||||
icon: DidvanIcons.download_regular,
|
||||
onPressed: () {},
|
||||
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(
|
||||
gestureSize: 24,
|
||||
itemId: podcast.id,
|
||||
type: 'podcast',
|
||||
askForConfirmation: hasUnmarkConfirmation,
|
||||
gestureSize: 32,
|
||||
value: podcast.marked,
|
||||
onMarkChanged: (value) => onMarkChanged(podcast.id, value),
|
||||
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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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,43 +32,36 @@ 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<StudioDetailsState>(),
|
||||
},
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Stack(
|
||||
alignment: Alignment.center,
|
||||
children: [
|
||||
SkeletonImage(
|
||||
imageUrl: video.image,
|
||||
height: 108,
|
||||
width: 108,
|
||||
),
|
||||
Positioned.fill(
|
||||
child: Center(
|
||||
child: Container(
|
||||
Container(
|
||||
height: 28,
|
||||
width: 28,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.secondary
|
||||
.withOpacity(0.7),
|
||||
color:
|
||||
Theme.of(context).colorScheme.secondary.withOpacity(0.7),
|
||||
),
|
||||
child: Icon(
|
||||
DidvanIcons.play_solid,
|
||||
color: Theme.of(context).colorScheme.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
|
|
@ -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<StudioState>().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,
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -125,6 +125,7 @@ class _SearchFieldState extends State<SearchField> {
|
|||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.focusNode.removeListener(() {});
|
||||
super.dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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<Splash> {
|
|||
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<Splash> {
|
|||
.removeWhere((key, value) => key == 'image-cache');
|
||||
});
|
||||
}
|
||||
await AppInitializer.setupServices();
|
||||
final settingsData = await AppInitializer.initilizeSettings();
|
||||
final themeProvider = context.read<ThemeProvider>();
|
||||
themeProvider.themeMode = settingsData.themeMode;
|
||||
|
|
@ -105,10 +105,12 @@ class _SplashState extends State<Splash> {
|
|||
_isGettingThemeData = false;
|
||||
}),
|
||||
);
|
||||
await AppInitializer.setupServices();
|
||||
final userProvider = context.read<UserProvider>();
|
||||
final String? token = await userProvider.setAndGetToken();
|
||||
if (token != null) {
|
||||
log(token);
|
||||
context.read<MediaProvider>().getDownloadsList();
|
||||
RequestService.token = token;
|
||||
final result = await userProvider.getUserInfo();
|
||||
if (!result) {
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -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<bool>(
|
||||
stream: MediaService.audioPlayer.isPlaying,
|
||||
builder: (context, snapshot) => GestureDetector(
|
||||
onTap: () => MediaService.currentPodcast == null
|
||||
? null
|
||||
: _showPlayerBottomSheet(context),
|
||||
child: Consumer<StudioDetailsState>(
|
||||
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<PlayingAudio?>(
|
||||
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<ExpandableBottomSheetState>();
|
||||
bool isExpanded = false;
|
||||
final detailsState = context.read<StudioDetailsState>();
|
||||
if (detailsState.args.type == 'video') {
|
||||
detailsState.getStudioDetails(
|
||||
MediaService.currentPodcast!.id,
|
||||
args: detailsState.podcastArgs,
|
||||
fetchOnly: true,
|
||||
);
|
||||
}
|
||||
final state = context.read<StudioState>();
|
||||
showModalBottomSheet(
|
||||
backgroundColor: Colors.transparent,
|
||||
context: context,
|
||||
isScrollControlled: true,
|
||||
builder: (context) => ChangeNotifierProvider<StudioState>.value(
|
||||
value: state,
|
||||
child: Consumer<StudioDetailsState>(
|
||||
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<StudioState>().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(),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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<DidvanPageView> {
|
|||
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',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -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<DidvanScaffold> {
|
||||
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<DidvanScaffold> {
|
|||
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<DidvanScaffold> {
|
|||
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<DidvanScaffold> {
|
|||
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(
|
||||
|
|
|
|||
|
|
@ -24,29 +24,23 @@ class SkeletonImage extends StatelessWidget {
|
|||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return _aspectRatioGenerator(
|
||||
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,
|
||||
imageBuilder: (context, imageProvider) => ClipRRect(
|
||||
borderRadius: borderRadius ?? DesignConfig.lowBorderRadius,
|
||||
child: Image(
|
||||
image: imageProvider,
|
||||
fit: BoxFit.cover,
|
||||
),
|
||||
),
|
||||
progressIndicatorBuilder: (context, url, progress) =>
|
||||
ShimmerPlaceholder(
|
||||
borderRadius: borderRadius,
|
||||
placeholder: (context, _) => const ShimmerPlaceholder(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _aspectRatioGenerator({required Widget child}) => aspectRatio == null
|
||||
? SizedBox(key: ValueKey(imageUrl), child: child)
|
||||
? child
|
||||
: AspectRatio(
|
||||
key: ValueKey(imageUrl),
|
||||
aspectRatio: aspectRatio!,
|
||||
|
|
|
|||
|
|
@ -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<T extends CoreProvier> 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,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
|
|
|
|||
210
pubspec.lock
210
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"
|
||||
|
|
|
|||
50
pubspec.yaml
50
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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -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!"
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue