new video player basic version

This commit is contained in:
MohammadTaha Basiri 2022-04-04 14:16:21 +04:30
parent 48387b0b79
commit 00f108710a
9 changed files with 331 additions and 353 deletions

View File

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project # 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. # CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true' ENV['COCOAPODS_DISABLE_STATS'] = 'true'

View File

@ -1,33 +1,40 @@
PODS: PODS:
- audio_session (0.0.1): - audio_session (0.0.1):
- Flutter - Flutter
- Firebase/CoreOnly (8.11.0): - better_player (0.0.1):
- FirebaseCore (= 8.11.0) - Cache (~> 6.0.0)
- Firebase/Messaging (8.11.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 8.11.0)
- firebase_core (1.13.1):
- Firebase/CoreOnly (= 8.11.0)
- Flutter - Flutter
- firebase_messaging (11.2.8): - GCDWebServer
- Firebase/Messaging (= 8.11.0) - HLSCachingReverseProxyServer
- PINCache
- Cache (6.0.0)
- Firebase/CoreOnly (8.14.0):
- FirebaseCore (= 8.14.0)
- Firebase/Messaging (8.14.0):
- Firebase/CoreOnly
- FirebaseMessaging (~> 8.14.0)
- firebase_core (1.14.0):
- Firebase/CoreOnly (= 8.14.0)
- Flutter
- firebase_messaging (11.2.12):
- Firebase/Messaging (= 8.14.0)
- firebase_core - firebase_core
- Flutter - Flutter
- FirebaseCore (8.11.0): - FirebaseCore (8.14.0):
- FirebaseCoreDiagnostics (~> 8.0) - FirebaseCoreDiagnostics (~> 8.0)
- GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7) - GoogleUtilities/Logger (~> 7.7)
- FirebaseCoreDiagnostics (8.12.0): - FirebaseCoreDiagnostics (8.14.0):
- GoogleDataTransport (~> 9.1) - GoogleDataTransport (~> 9.1)
- GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/Logger (~> 7.7) - GoogleUtilities/Logger (~> 7.7)
- nanopb (~> 2.30908.0) - nanopb (~> 2.30908.0)
- FirebaseInstallations (8.12.0): - FirebaseInstallations (8.14.0):
- FirebaseCore (~> 8.0) - FirebaseCore (~> 8.0)
- GoogleUtilities/Environment (~> 7.7) - GoogleUtilities/Environment (~> 7.7)
- GoogleUtilities/UserDefaults (~> 7.7) - GoogleUtilities/UserDefaults (~> 7.7)
- PromisesObjC (< 3.0, >= 1.2) - PromisesObjC (< 3.0, >= 1.2)
- FirebaseMessaging (8.11.0): - FirebaseMessaging (8.14.0):
- FirebaseCore (~> 8.0) - FirebaseCore (~> 8.0)
- FirebaseInstallations (~> 8.0) - FirebaseInstallations (~> 8.0)
- GoogleDataTransport (~> 9.1) - GoogleDataTransport (~> 9.1)
@ -44,6 +51,9 @@ PODS:
- FMDB (2.7.5): - FMDB (2.7.5):
- FMDB/standard (= 2.7.5) - FMDB/standard (= 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): - GoogleDataTransport (9.1.2):
- GoogleUtilities/Environment (~> 7.2) - GoogleUtilities/Environment (~> 7.2)
- nanopb (~> 2.30908.0) - nanopb (~> 2.30908.0)
@ -65,6 +75,9 @@ PODS:
- GoogleUtilities/Logger - GoogleUtilities/Logger
- GoogleUtilities/UserDefaults (7.7.0): - GoogleUtilities/UserDefaults (7.7.0):
- GoogleUtilities/Logger - GoogleUtilities/Logger
- HLSCachingReverseProxyServer (0.1.0):
- GCDWebServer (~> 3.5)
- PINCache (>= 3.0.1-beta.3)
- image_cropper (0.0.4): - image_cropper (0.0.4):
- Flutter - Flutter
- TOCropViewController (~> 2.6.1) - TOCropViewController (~> 2.6.1)
@ -79,8 +92,16 @@ PODS:
- nanopb/encode (2.30908.0) - nanopb/encode (2.30908.0)
- path_provider_ios (0.0.1): - path_provider_ios (0.0.1):
- Flutter - Flutter
- permission_handler_apple (9.0.2): - permission_handler_apple (9.0.4):
- Flutter - 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) - PromisesObjC (2.0.0)
- record (0.0.1): - record (0.0.1):
- Flutter - Flutter
@ -90,11 +111,16 @@ PODS:
- TOCropViewController (2.6.1) - TOCropViewController (2.6.1)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
- video_player_avfoundation (0.0.1):
- Flutter
- wakelock (0.0.1):
- Flutter
- webview_flutter_wkwebview (0.0.1): - webview_flutter_wkwebview (0.0.1):
- Flutter - Flutter
DEPENDENCIES: DEPENDENCIES:
- audio_session (from `.symlinks/plugins/audio_session/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`)
- better_player (from `.symlinks/plugins/better_player/ios`)
- firebase_core (from `.symlinks/plugins/firebase_core/ios`) - firebase_core (from `.symlinks/plugins/firebase_core/ios`)
- firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`) - firebase_messaging (from `.symlinks/plugins/firebase_messaging/ios`)
- Flutter (from `Flutter`) - Flutter (from `Flutter`)
@ -108,25 +134,34 @@ DEPENDENCIES:
- record (from `.symlinks/plugins/record/ios`) - record (from `.symlinks/plugins/record/ios`)
- sqflite (from `.symlinks/plugins/sqflite/ios`) - sqflite (from `.symlinks/plugins/sqflite/ios`)
- url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`)
- video_player_avfoundation (from `.symlinks/plugins/video_player_avfoundation/ios`)
- wakelock (from `.symlinks/plugins/wakelock/ios`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`) - webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
- Cache
- Firebase - Firebase
- FirebaseCore - FirebaseCore
- FirebaseCoreDiagnostics - FirebaseCoreDiagnostics
- FirebaseInstallations - FirebaseInstallations
- FirebaseMessaging - FirebaseMessaging
- FMDB - FMDB
- GCDWebServer
- GoogleDataTransport - GoogleDataTransport
- GoogleUtilities - GoogleUtilities
- HLSCachingReverseProxyServer
- nanopb - nanopb
- PINCache
- PINOperation
- PromisesObjC - PromisesObjC
- TOCropViewController - TOCropViewController
EXTERNAL SOURCES: EXTERNAL SOURCES:
audio_session: audio_session:
:path: ".symlinks/plugins/audio_session/ios" :path: ".symlinks/plugins/audio_session/ios"
better_player:
:path: ".symlinks/plugins/better_player/ios"
firebase_core: firebase_core:
:path: ".symlinks/plugins/firebase_core/ios" :path: ".symlinks/plugins/firebase_core/ios"
firebase_messaging: firebase_messaging:
@ -153,37 +188,49 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/sqflite/ios" :path: ".symlinks/plugins/sqflite/ios"
url_launcher_ios: url_launcher_ios:
:path: ".symlinks/plugins/url_launcher_ios/ios" :path: ".symlinks/plugins/url_launcher_ios/ios"
video_player_avfoundation:
:path: ".symlinks/plugins/video_player_avfoundation/ios"
wakelock:
:path: ".symlinks/plugins/wakelock/ios"
webview_flutter_wkwebview: webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios" :path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
audio_session: 4f3e461722055d21515cf3261b64c973c062f345 audio_session: 4f3e461722055d21515cf3261b64c973c062f345
Firebase: 44dd9724c84df18b486639e874f31436eaa9a20c better_player: 2406bfe8175203c7a46fa15f9d778d73b12e1646
firebase_core: 08f6a85f62060111de5e98d6a214810d11365de9 Cache: 4ca7e00363fca5455f26534e5607634c820ffc2d
firebase_messaging: 36238f3d0b933af8c919aef608408aae06ba22e8 Firebase: 7e8fe528c161b9271d365217a74c16aaf834578e
FirebaseCore: 2f4f85b453cc8fea4bb2b37e370007d2bcafe3f0 firebase_core: b0b382f1497ab407aceb25e41e3036c8798c1609
FirebaseCoreDiagnostics: 3b40dfadef5b90433a60ae01f01e90fe87aa76aa firebase_messaging: 34dd10d1aa6d8f40d03660eeacd0452d62eec7aa
FirebaseInstallations: 25764cf322e77f99449395870a65b2bef88e1545 FirebaseCore: b84a44ee7ba999e0f9f76d198a9c7f60a797b848
FirebaseMessaging: 02e248e8997f71fa8cc9d78e9d49ec1a701ba14a FirebaseCoreDiagnostics: fd0c8490f34287229c1d6c103d3a55f81ec85712
FirebaseInstallations: 7d1d967a307c12f1aadd76844fc321cef699b1ce
FirebaseMessaging: 5ebc42d281567658a2cb72b9ef3506e4a1a1a6e4
Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a Flutter: 50d75fe2f02b26cc09d224853bb45737f8b3214a
flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec flutter_secure_storage: 7953c38a04c3fdbb00571bcd87d8e3b5ceb9daec
flutter_vibrate: 9f4c2ab57008965f78969472367c329dd77eb801 flutter_vibrate: 9f4c2ab57008965f78969472367c329dd77eb801
FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a FMDB: 2ce00b547f966261cd18927a3ddb07cb6f3db82a
GCDWebServer: 2c156a56c8226e2d5c0c3f208a3621ccffbe3ce4
GoogleDataTransport: 629c20a4d363167143f30ea78320d5a7eb8bd940 GoogleDataTransport: 629c20a4d363167143f30ea78320d5a7eb8bd940
GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1 GoogleUtilities: e0913149f6b0625b553d70dae12b49fc62914fd1
HLSCachingReverseProxyServer: 59935e1e0244ad7f3375d75b5ef46e8eb26ab181
image_cropper: 60c2789d1f1a78c873235d4319ca0c34a69f2d98 image_cropper: 60c2789d1f1a78c873235d4319ca0c34a69f2d98
image_picker: 9aa50e1d8cdacdbed739e925b7eea16d014367e6 image_picker: 541dcbb3b9cf32d87eacbd957845d8651d6c62c3
just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa just_audio: baa7252489dbcf47a4c7cc9ca663e9661c99aafa
nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96 nanopb: a0ba3315591a9ae0a16a309ee504766e90db0c96
path_provider_ios: 7d7ce634493af4477d156294792024ec3485acd5 path_provider_ios: 14f3d2fd28c4fdb42f44e0f751d12861c43cee02
permission_handler_apple: d21b38e1a4b2e041c63af9568f9165e114e507a6 permission_handler_apple: 44366e37eaf29454a1e7b1b7d736c2cceaeb17ce
PINCache: 7a8fc1a691173d21dbddbf86cd515de6efa55086
PINOperation: 00c935935f1e8cf0d1e2d6b542e75b88fc3e5e20
PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58 PromisesObjC: 68159ce6952d93e17b2dfe273b8c40907db5ba58
record: 7ee2393532f8553bbb09fa19e95478323b7c0a99 record: 7ee2393532f8553bbb09fa19e95478323b7c0a99
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af url_launcher_ios: 839c58cdb4279282219f5e248c3321761ff3c4de
video_player_avfoundation: e489aac24ef5cf7af82702979ed16f2a5ef84cff
wakelock: d0fc7c864128eac40eba1617cb5264d9c940b46f
webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162 webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162
PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea PODFILE CHECKSUM: 7368163408c647b7eb699d0d788ba6718e18fb8d
COCOAPODS: 1.11.2 COCOAPODS: 1.11.2

View File

@ -1,16 +1,15 @@
import 'dart:io'; import 'dart:io';
import 'package:didvan/config/design_config.dart'; import 'package:better_player/better_player.dart';
import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/services/media/media.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/studio_details_state.dart';
import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart'; import 'package:didvan/views/home/studio/studio_details/widgets/details_tab_bar.dart';
import 'package:didvan/views/home/studio/studio_details/widgets/studio_details_widget.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/home/widgets/bookmark_button.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/app_bar.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:webview_flutter/webview_flutter.dart'; import 'package:webview_flutter/webview_flutter.dart';
@ -26,61 +25,23 @@ class StudioDetails extends StatefulWidget {
class _StudioDetailsState extends State<StudioDetails> { class _StudioDetailsState extends State<StudioDetails> {
final _scrollController = ScrollController(); final _scrollController = ScrollController();
bool _isFullScreen = false;
bool _isInit = true;
double _dwInPortrait = 0;
double _scaleInPortrait = 1;
@override @override
void initState() { void initState() {
final state = context.read<StudioDetailsState>(); final state = context.read<StudioDetailsState>();
state.args = widget.pageData['args'];
Future.delayed( Future.delayed(
Duration.zero, Duration.zero,
() => state.getStudioDetails(widget.pageData['id']), () => state.getStudioDetails(widget.pageData['id']),
); );
state.args = widget.pageData['args'];
if (Platform.isAndroid) WebView.platform = AndroidWebView(); if (Platform.isAndroid) WebView.platform = AndroidWebView();
super.initState(); 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 @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ds = MediaQuery.of(context).size; final d = MediaQuery.of(context);
if (_isInit) {
_dwInPortrait = MediaQuery.of(context).size.width;
_scaleInPortrait = _dwInPortrait / 576;
_isInit = false;
}
return Consumer<StudioDetailsState>( return Consumer<StudioDetailsState>(
builder: (context, state, child) => StateHandler<StudioDetailsState>( builder: (context, state, child) => StateHandler<StudioDetailsState>(
@ -95,23 +56,19 @@ class _StudioDetailsState extends State<StudioDetails> {
builder: (context, state) { builder: (context, state) {
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
if (_isFullScreen) {
await _changeFullSceen(false);
return false;
}
if (MediaService.currentPodcast != null) { if (MediaService.currentPodcast != null) {
state.studio = MediaService.currentPodcast!; state.studio = MediaService.currentPodcast!;
} }
return true; return true;
}, },
child: DidvanScaffold( child: SafeArea(
key: ValueKey(state.studio.id), child: Scaffold(
scrollController: _scrollController, key: ValueKey(state.studio.id),
backgroundColor: Theme.of(context).colorScheme.surface, backgroundColor: Theme.of(context).colorScheme.surface,
padding: EdgeInsets.zero, appBar: PreferredSize(
appBarData: _isFullScreen preferredSize: const Size.fromHeight(56),
? null child: DidvanAppBar(
: AppBarData( appBarData: AppBarData(
trailing: BookmarkButton( trailing: BookmarkButton(
itemId: state.studio.id, itemId: state.studio.id,
type: 'video', type: 'video',
@ -125,103 +82,41 @@ class _StudioDetailsState extends State<StudioDetails> {
isSmall: true, isSmall: true,
title: state.studio.title, title: state.studio.title,
), ),
showSliversFirst: true,
slivers: [
SliverAppBar(
automaticallyImplyLeading: false,
pinned: true,
backgroundColor: Theme.of(context).colorScheme.surface,
toolbarHeight:
(_isFullScreen ? ds.height : ds.width * 9 / 16) +
72 -
MediaQuery.of(context).padding.top,
elevation: 0,
flexibleSpace: Column(
children: [
SizedBox(
width: ds.width,
height: _isFullScreen ? ds.height : ds.width * 9 / 16,
child: Stack(
children: [
WebView(
zoomEnabled: false,
backgroundColor: Colors.black,
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.studio.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,
),
),
),
],
),
),
DetailsTabBar(
isVideo: true,
onCommentsTabSelected: () => Future.delayed(
const Duration(milliseconds: 100),
() => _scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: DesignConfig.lowAnimationDuration,
curve: Curves.easeIn,
),
),
),
],
), ),
), ),
], body: Column(
children: [ children: [
StudioDetailsWidget( Directionality(
scrollController: _scrollController, textDirection: TextDirection.ltr,
onMarkChanged: (id, value) => child: BetterPlayer.network(
widget.pageData['onMarkChanged'](id, value, true), 'https://studio-didvan.arvanvod.com/Vz01Bxq2bQ/nylPWJ4B63/h_,144_200,240_400,360_800,480_1215,720_1215,k.mp4.list/master.m3u8',
betterPlayerConfiguration:
const BetterPlayerConfiguration(
aspectRatio: 16 / 9,
controlsConfiguration:
BetterPlayerControlsConfiguration(
enablePlaybackSpeed: false,
enableSubtitles: false,
enableAudioTracks: false,
),
autoDetectFullscreenAspectRatio: true,
autoDetectFullscreenDeviceOrientation: true,
fullScreenAspectRatio: 16 / 9,
),
),
),
const DetailsTabBar(
isVideo: true,
),
Expanded(
child: StudioDetailsWidget(
onMarkChanged: (id, value) =>
widget.pageData['onMarkChanged'](id, value, true),
),
),
],
), ),
], ),
), ),
); );
}, },

View File

@ -24,18 +24,17 @@ class StudioDetails extends StatefulWidget {
} }
class _StudioDetailsState extends State<StudioDetails> { class _StudioDetailsState extends State<StudioDetails> {
final _scrollController = ScrollController();
bool _isFullScreen = false; bool _isFullScreen = false;
@override @override
void initState() { void initState() {
final state = context.read<StudioDetailsState>(); final state = context.read<StudioDetailsState>();
state.args = widget.pageData['args'];
Future.delayed( Future.delayed(
Duration.zero, Duration.zero,
() => state.getStudioDetails(widget.pageData['id']), () => state.getStudioDetails(widget.pageData['id']),
); );
state.args = widget.pageData['args'];
super.initState(); super.initState();
} }
@ -100,7 +99,6 @@ class _StudioDetailsState extends State<StudioDetails> {
return true; return true;
}, },
child: DidvanScaffold( child: DidvanScaffold(
scrollController: _scrollController,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,
appBarData: _isFullScreen appBarData: _isFullScreen
? null ? null
@ -127,21 +125,13 @@ class _StudioDetailsState extends State<StudioDetails> {
72 - 72 -
MediaQuery.of(context).padding.top, MediaQuery.of(context).padding.top,
flexibleSpace: Column( flexibleSpace: Column(
children: [ children: const [
const AspectRatio( AspectRatio(
aspectRatio: 16 / 9, aspectRatio: 16 / 9,
child: HtmlElementView(viewType: 'video'), child: HtmlElementView(viewType: 'video'),
), ),
DetailsTabBar( DetailsTabBar(
isVideo: true, isVideo: true,
onCommentsTabSelected: () => Future.delayed(
const Duration(milliseconds: 100),
() => _scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: DesignConfig.lowAnimationDuration,
curve: Curves.easeIn,
),
),
), ),
], ],
), ),
@ -149,7 +139,6 @@ class _StudioDetailsState extends State<StudioDetails> {
], ],
children: [ children: [
StudioDetailsWidget( StudioDetailsWidget(
scrollController: _scrollController,
onMarkChanged: widget.pageData['onMarkChanged'], onMarkChanged: widget.pageData['onMarkChanged'],
), ),
], ],

View File

@ -8,12 +8,10 @@ import 'package:provider/provider.dart';
class DetailsTabBar extends StatelessWidget { class DetailsTabBar extends StatelessWidget {
final bool isVideo; final bool isVideo;
final VoidCallback onCommentsTabSelected;
const DetailsTabBar({ const DetailsTabBar({
Key? key, Key? key,
required this.isVideo, required this.isVideo,
required this.onCommentsTabSelected,
}) : super(key: key); }) : super(key: key);
@override @override
@ -55,7 +53,6 @@ class DetailsTabBar extends StatelessWidget {
title: 'نظرات', title: 'نظرات',
onTap: () { onTap: () {
state.selectedDetailsIndex = 1; state.selectedDetailsIndex = 1;
onCommentsTabSelected();
}, },
isSelected: state.selectedDetailsIndex == 1, isSelected: state.selectedDetailsIndex == 1,
isVideo: isVideo, isVideo: isVideo,

View File

@ -19,165 +19,164 @@ import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:url_launcher/url_launcher.dart';
class StudioDetailsWidget extends StatelessWidget { class StudioDetailsWidget extends StatelessWidget {
final ScrollController? scrollController;
final VoidCallback? onCommentsTabSelected;
final void Function(int id, bool value) onMarkChanged; final void Function(int id, bool value) onMarkChanged;
const StudioDetailsWidget({ const StudioDetailsWidget({
Key? key, Key? key,
required this.onMarkChanged, required this.onMarkChanged,
this.onCommentsTabSelected,
this.scrollController,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ds = MediaQuery.of(context).size; final ds = MediaQuery.of(context).size;
return Consumer<StudioDetailsState>( return SafeArea(
builder: (context, state, child) { bottom: true,
bool isVideo = state.studio.media.contains('iframe'); child: Consumer<StudioDetailsState>(
return Container( builder: (context, state, child) {
color: Theme.of(context).colorScheme.surface, bool isVideo = state.studio.media.contains('iframe');
child: Column( return Container(
mainAxisSize: MainAxisSize.min, color: Theme.of(context).colorScheme.surface,
children: [ child: Column(
if (!isVideo) mainAxisSize: MainAxisSize.min,
DetailsTabBar( children: [
isVideo: isVideo, if (!isVideo)
onCommentsTabSelected: onCommentsTabSelected ?? () {}, DetailsTabBar(
), isVideo: isVideo,
const SizedBox(height: 16), ),
ConstrainedBox( const SizedBox(height: 16),
constraints: BoxConstraints( ConstrainedBox(
maxHeight: ds.height - constraints: BoxConstraints(
ds.width * 9 / 16 - maxHeight: isVideo
144 - ? double.infinity
MediaQuery.of(context).padding.top, : ds.height -
), ds.width * 9 / 16 -
child: StateHandler<StudioDetailsState>( 144 -
onRetry: () {}, MediaQuery.of(context).padding.top,
state: state, ),
builder: (context, state) { child: StateHandler<StudioDetailsState>(
if (state.selectedDetailsIndex == 0) { onRetry: () {},
return SingleChildScrollView( state: state,
physics: const BouncingScrollPhysics(), builder: (context, state) {
padding: const EdgeInsets.symmetric(horizontal: 16), if (state.selectedDetailsIndex == 0) {
child: Column( return SingleChildScrollView(
crossAxisAlignment: CrossAxisAlignment.start, physics: const BouncingScrollPhysics(),
mainAxisSize: MainAxisSize.min, padding: const EdgeInsets.symmetric(horizontal: 16),
children: [ child: Column(
Html( crossAxisAlignment: CrossAxisAlignment.start,
key: ValueKey(state.studio.id), mainAxisSize: MainAxisSize.min,
data: state.studio.description, children: [
onAnchorTap: (href, context, map, element) => Html(
launch(href!), key: ValueKey(state.studio.id),
style: { data: state.studio.description,
'*': Style( onAnchorTap: (href, context, map, element) =>
direction: TextDirection.rtl, launch(href!),
textAlign: TextAlign.right, style: {
lineHeight: LineHeight.percent(135), '*': Style(
margin: EdgeInsets.zero, direction: TextDirection.rtl,
padding: EdgeInsets.zero, textAlign: TextAlign.right,
), lineHeight: LineHeight.percent(135),
}, margin: EdgeInsets.zero,
), padding: EdgeInsets.zero,
if (state.studio.tags.isNotEmpty) ),
},
),
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), const SizedBox(height: 20),
Wrap( Row(
spacing: 8, mainAxisAlignment:
runSpacing: 8, MainAxisAlignment.spaceBetween,
children: [ children: [
for (var i = 0; const SizedBox(),
i < state.studio.tags.length; if (state.nextStudio != null &&
i++) state.alongSideState == AppState.idle)
TagItem( _StudioPreview(
tag: state.studio.tags[i], isNext: true,
onMarkChanged: (id, value) => studio: state.nextStudio!,
_onMarkChanged(id, value, state), ),
type: isVideo ? 'video' : 'podcast', 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: Comments(
pageData: {
'id': state.studio.id,
'type': 'studio',
'title': state.studio.title,
'onCommentsChanged': state.onCommentsChanged,
'isPage': false,
},
),
);
}
return SingleChildScrollView(
child: 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,
), ),
const SizedBox(height: 20), for (var i = 0;
Row( i < state.studio.relatedContents.length;
mainAxisAlignment: MainAxisAlignment.spaceBetween, i++)
children: [
const SizedBox(),
if (state.nextStudio != null &&
state.alongSideState == AppState.idle)
_StudioPreview(
isNext: true,
studio: state.nextStudio!,
scrollController: scrollController,
),
if (state.alongSideState == AppState.busy)
_StudioPreview.placeHolder,
if (state.prevStudio != null &&
state.alongSideState == AppState.idle)
_StudioPreview(
isNext: false,
studio: state.prevStudio!,
scrollController: scrollController,
),
if (state.alongSideState == AppState.busy)
_StudioPreview.placeHolder,
const SizedBox(),
],
),
],
),
);
}
if (state.selectedDetailsIndex == 1) {
return ChangeNotifierProvider<CommentsState>(
create: (context) => CommentsState(),
child: Comments(
pageData: {
'id': state.studio.id,
'type': 'studio',
'title': state.studio.title,
'onCommentsChanged': state.onCommentsChanged,
'isPage': false,
},
),
);
}
return SingleChildScrollView(
child: Column(
children: [
if (state.studio.relatedContents.isEmpty)
for (var i = 0; i < 3; i++)
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
bottom: 8, bottom: 8,
left: 16, left: 16,
right: 16, right: 16,
), ),
child: MultitypeOverview.placeholder, child: MultitypeOverview(
item: state.studio.relatedContents[i],
onMarkChanged: (id, value) {},
),
), ),
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) {},
),
),
],
),
);
},
), ),
), ],
], ),
), );
); },
}, ),
); );
} }
@ -196,13 +195,11 @@ class StudioDetailsWidget extends StatelessWidget {
class _StudioPreview extends StatelessWidget { class _StudioPreview extends StatelessWidget {
final bool isNext; final bool isNext;
final StudioDetailsData studio; final StudioDetailsData studio;
final ScrollController? scrollController; const _StudioPreview({
const _StudioPreview( Key? key,
{Key? key, required this.isNext,
required this.isNext, required this.studio,
required this.studio, }) : super(key: key);
this.scrollController})
: super(key: key);
String get _previewTitle { String get _previewTitle {
if (studio.media.contains('iframe')) { if (studio.media.contains('iframe')) {
@ -221,11 +218,6 @@ class _StudioPreview extends StatelessWidget {
args: state.args, args: state.args,
isForward: isNext, isForward: isNext,
); );
scrollController?.animateTo(
0,
duration: DesignConfig.lowAnimationDuration,
curve: Curves.easeIn,
);
}, },
child: Container( child: Container(
width: 88, width: 88,

View File

@ -339,12 +339,6 @@ class _PlayerNavBar extends StatelessWidget {
), ),
) )
: StudioDetailsWidget( : StudioDetailsWidget(
onCommentsTabSelected: () {
Future.delayed(
const Duration(milliseconds: 100),
sheetKey.currentState?.expand,
);
},
onMarkChanged: (id, value) => onMarkChanged: (id, value) =>
context.read<StudioState>().changeMark(id, value, true), context.read<StudioState>().changeMark(id, value, true),
), ),

View File

@ -22,6 +22,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.10.0" 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: boolean_selector:
dependency: transitive dependency: transitive
description: description:
@ -315,6 +322,20 @@ packages:
description: flutter description: flutter
source: sdk source: sdk
version: "0.0.0" 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: graphs:
dependency: transitive dependency: transitive
description: description:
@ -824,6 +845,48 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" 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: webview_flutter:
dependency: "direct main" dependency: "direct main"
description: description:

View File

@ -65,6 +65,7 @@ dependencies:
webview_flutter: ^3.0.1 webview_flutter: ^3.0.1
expandable_bottom_sheet: ^1.1.1+1 expandable_bottom_sheet: ^1.1.1+1
permission_handler: ^9.2.0 permission_handler: ^9.2.0
better_player: ^0.0.81
dev_dependencies: dev_dependencies: