Merge branch 'dev' into 'master'

Dev

See merge request Didvan/didvan-app!13
This commit is contained in:
MohammadTaha Basiri 2022-03-19 13:51:23 +00:00
commit ce00f619a9
60 changed files with 1616 additions and 274 deletions

View File

@ -1,13 +1,13 @@
kind: ExternalService kind: ExternalService
name: app-test name: app-dev
spec: spec:
allow_http: false allow_http: false
disable_default_domains: true disable_default_domains: true
image: app:1.1.1 image: app-dev:1.1.4
image_pull_policy: IfNotPresent image_pull_policy: IfNotPresent
path: / path: /
replicas: 1 replicas: 1
resources: resources:
memory: 150Mi memory: 100Mi
domains: domains:
- name: web.didvan.app - name: dev.didvan.app

View File

@ -88,6 +88,8 @@ PODS:
- TOCropViewController (2.6.1) - TOCropViewController (2.6.1)
- url_launcher_ios (0.0.1): - url_launcher_ios (0.0.1):
- Flutter - Flutter
- webview_flutter_wkwebview (0.0.1):
- Flutter
DEPENDENCIES: DEPENDENCIES:
- audio_session (from `.symlinks/plugins/audio_session/ios`) - audio_session (from `.symlinks/plugins/audio_session/ios`)
@ -103,6 +105,7 @@ 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`)
- webview_flutter_wkwebview (from `.symlinks/plugins/webview_flutter_wkwebview/ios`)
SPEC REPOS: SPEC REPOS:
trunk: trunk:
@ -145,6 +148,8 @@ 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"
webview_flutter_wkwebview:
:path: ".symlinks/plugins/webview_flutter_wkwebview/ios"
SPEC CHECKSUMS: SPEC CHECKSUMS:
audio_session: 4f3e461722055d21515cf3261b64c973c062f345 audio_session: 4f3e461722055d21515cf3261b64c973c062f345
@ -171,6 +176,7 @@ SPEC CHECKSUMS:
sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904 sqflite: 6d358c025f5b867b29ed92fc697fd34924e11904
TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863 TOCropViewController: edfd4f25713d56905ad1e0b9f5be3fbe0f59c863
url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af url_launcher_ios: 02f1989d4e14e998335b02b67a7590fa34f971af
webview_flutter_wkwebview: 005fbd90c888a42c5690919a1527ecc6649e1162
PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea PODFILE CHECKSUM: fe0e1ee7f3d1f7d00b11b474b62dd62134535aea

View File

@ -43,8 +43,7 @@ class DesignConfig {
static SystemUiOverlayStyle get systemUiOverlayStyle => SystemUiOverlayStyle( static SystemUiOverlayStyle get systemUiOverlayStyle => SystemUiOverlayStyle(
statusBarIconBrightness: statusBarIconBrightness:
brightness == Brightness.dark ? Brightness.light : Brightness.dark, brightness == Brightness.dark ? Brightness.light : Brightness.dark,
statusBarColor: statusBarColor: Theme.of(context!).colorScheme.background,
Theme.of(context!).colorScheme.background.withOpacity(0.5),
systemNavigationBarColor: Theme.of(context!).colorScheme.surface, systemNavigationBarColor: Theme.of(context!).colorScheme.surface,
systemNavigationBarDividerColor: Colors.transparent, systemNavigationBarDividerColor: Colors.transparent,
systemNavigationBarIconBrightness: systemNavigationBarIconBrightness:

View File

@ -6,7 +6,9 @@ class OverviewData {
final String image; final String image;
final String description; final String description;
final int? timeToRead; final int? timeToRead;
final int? duration;
final String? reference; final String? reference;
final String? media;
final bool forManagers; final bool forManagers;
final String createdAt; final String createdAt;
final String type; final String type;
@ -24,6 +26,8 @@ class OverviewData {
required this.marked, required this.marked,
required this.comments, required this.comments,
required this.forManagers, required this.forManagers,
this.media,
this.duration,
this.timeToRead, this.timeToRead,
this.reference, this.reference,
this.categories, this.categories,
@ -39,11 +43,17 @@ class OverviewData {
forManagers: json['forManagers'] ?? false, forManagers: json['forManagers'] ?? false,
comments: json['comments'] ?? 0, comments: json['comments'] ?? 0,
createdAt: json['createdAt'], createdAt: json['createdAt'],
duration: json['duration'],
type: json['type'] ?? '', type: json['type'] ?? '',
marked: json['marked'] ?? false, marked: json['marked'] ?? false,
categories: (json['categories'] as List<dynamic>?) media: json['media'],
?.map((e) => CategoryData.fromJson(e as Map<String, dynamic>)) categories: json['categories'] != null
.toList(), ? List<CategoryData>.from(
json['categories'].map(
(e) => CategoryData.fromJson(e),
),
)
: null,
); );
Map<String, dynamic> toJson() => { Map<String, dynamic> toJson() => {

View File

@ -0,0 +1,13 @@
class StudioRequestArgs {
final int page;
final String? search;
final String? order;
final String? type;
const StudioRequestArgs({
required this.page,
this.search,
this.order,
this.type,
});
}

View File

@ -0,0 +1,61 @@
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/tag.dart';
class StudioDetailsData {
final int id;
final int duration;
final String title;
final String description;
final String image;
final String media;
final String createdAt;
final int order;
bool marked;
int comments;
final List<Tag> tags;
final List<OverviewData> relatedContents = [];
StudioDetailsData({
required this.id,
required this.duration,
required this.title,
required this.description,
required this.image,
required this.media,
required this.createdAt,
required this.order,
required this.marked,
required this.comments,
required this.tags,
});
factory StudioDetailsData.fromJson(Map<String, dynamic> json) {
return StudioDetailsData(
id: json['id'],
duration: json['duration'],
title: json['title'],
description: json['description'],
image: json['image'],
media: json['media'],
createdAt: json['createdAt'],
order: json['order'],
marked: json['marked'],
comments: json['comments'],
tags: List<Tag>.from(json['tags'].map((e) => Tag.fromJson(e))),
);
}
Map<String, dynamic> toJson() => {
'id': id,
'duration': duration,
'title': title,
'description': description,
'image': image,
'media': media,
'createdAt': createdAt,
'order': order,
'marked': marked,
'comments': comments,
'tags': tags.map((e) => e.toJson()).toList(),
};
}

View File

@ -6,7 +6,8 @@ class ActionSheetData {
final String? dismissTitle; final String? dismissTitle;
final VoidCallback? onConfirmed; final VoidCallback? onConfirmed;
final VoidCallback? onDismissed; final VoidCallback? onDismissed;
final String title; final String? title;
final bool hasPadding;
final IconData? titleIcon; final IconData? titleIcon;
final Color? titleColor; final Color? titleColor;
final bool hasDismissButton; final bool hasDismissButton;
@ -16,10 +17,11 @@ class ActionSheetData {
const ActionSheetData({ const ActionSheetData({
required this.content, required this.content,
required this.title, this.title,
this.confrimTitle, this.confrimTitle,
this.onConfirmed, this.onConfirmed,
this.titleColor, this.titleColor,
this.hasPadding = true,
this.hasDismissButton = true, this.hasDismissButton = true,
this.hasConfirmButton = true, this.hasConfirmButton = true,
this.titleIcon, this.titleIcon,

View File

@ -5,6 +5,13 @@ class AppBarData {
final String? subtitle; final String? subtitle;
final bool hasBack; final bool hasBack;
final Widget? trailing; final Widget? trailing;
final bool isSmall;
AppBarData({this.title, this.subtitle, this.hasBack = false, this.trailing}); AppBarData({
this.title,
this.subtitle,
this.hasBack = false,
this.trailing,
this.isSmall = false,
});
} }

View File

@ -8,7 +8,7 @@ class ServerDataProvider {
await _getDirectTypes(); await _getDirectTypes();
} }
static int labelToTypeId(String? label) => label == null static int labelToTypeId(String label) => label.contains('پشتیبانی')
? 7 ? 7
: directTypes.firstWhere((element) => element.value.contains(label)).key; : directTypes.firstWhere((element) => element.value.contains(label)).key;

View File

@ -14,6 +14,7 @@ class UserProvider extends CoreProvier {
static final List<MapEntry> _radarMarkQueue = []; static final List<MapEntry> _radarMarkQueue = [];
static final List<MapEntry> _newsMarkQueue = []; static final List<MapEntry> _newsMarkQueue = [];
static final List<MapEntry> _studioMarkQueue = [];
Future<String?> setAndGetToken({String? newToken}) async { Future<String?> setAndGetToken({String? newToken}) async {
if (newToken == null) { if (newToken == null) {
@ -24,13 +25,16 @@ class UserProvider extends CoreProvier {
return null; return null;
} }
Future<void> getUserInfo() async { Future<bool> getUserInfo() async {
isAuthenticated = true; isAuthenticated = true;
final RequestService service = RequestService(RequestHelper.userInfo); final RequestService service = RequestService(RequestHelper.userInfo);
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
user = User.fromJson(service.result['user']); user = User.fromJson(service.result['user']);
return; return true;
}
if (service.statusCode == 401) {
return false;
} }
throw 'Getting user from API failed!'; throw 'Getting user from API failed!';
} }
@ -138,6 +142,22 @@ class UserProvider extends CoreProvier {
}); });
} }
static Future<void> changeStudioMark(int id, bool value) async {
_studioMarkQueue.add(MapEntry(id, value));
Future.delayed(const Duration(milliseconds: 500), () async {
final MapEntry? lastChange =
_studioMarkQueue.lastWhereOrNull((item) => item.key == id);
if (lastChange == null) return;
final service = RequestService(RequestHelper.markStudio(id));
if (lastChange.value) {
await service.post();
} else {
await service.delete();
}
_studioMarkQueue.removeWhere((element) => element.key == id);
});
}
static Future<void> changeNewsMark(int id, bool value) async { static Future<void> changeNewsMark(int id, bool value) async {
_newsMarkQueue.add(MapEntry(id, value)); _newsMarkQueue.add(MapEntry(id, value));
Future.delayed(const Duration(milliseconds: 500), () async { Future.delayed(const Duration(milliseconds: 500), () async {

View File

@ -25,6 +25,8 @@ import 'package:didvan/views/home/settings/direct_list/direct_list_state.dart';
import 'package:didvan/views/home/settings/general_settings/settings.dart'; import 'package:didvan/views/home/settings/general_settings/settings.dart';
import 'package:didvan/views/home/settings/general_settings/settings_state.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/settings/profile/profile.dart';
import 'package:didvan/views/home/studio/studio_details/studio_details.dart';
import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
import 'package:didvan/views/home/studio/studio_state.dart'; import 'package:didvan/views/home/studio/studio_state.dart';
import 'package:didvan/views/splash/splash.dart'; import 'package:didvan/views/splash/splash.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
@ -62,6 +64,9 @@ class RouteGenerator {
ChangeNotifierProvider<StudioState>( ChangeNotifierProvider<StudioState>(
create: (context) => StudioState(), create: (context) => StudioState(),
), ),
ChangeNotifierProvider<StudioDetailsState>(
create: (context) => StudioDetailsState(),
),
], ],
child: const Home(), child: const Home(),
), ),
@ -99,6 +104,15 @@ class RouteGenerator {
), ),
), ),
); );
case Routes.studioDetails:
return _createRoute(
ChangeNotifierProvider<StudioDetailsState>.value(
value: (settings.arguments as Map<String, dynamic>)['state'],
child: StudioDetails(
pageData: settings.arguments as Map<String, dynamic>,
),
),
);
case Routes.directList: case Routes.directList:
return _createRoute( return _createRoute(
ChangeNotifierProvider<DirectListState>( ChangeNotifierProvider<DirectListState>(
@ -170,7 +184,11 @@ class RouteGenerator {
final shortestSide = MediaQuery.of(context).size.shortestSide; final shortestSide = MediaQuery.of(context).size.shortestSide;
final bool useMobileLayout = shortestSide < 600; final bool useMobileLayout = shortestSide < 600;
if (kIsWeb && !useMobileLayout) { if (kIsWeb && !useMobileLayout) {
return Center(child: AspectRatio(aspectRatio: 9 / 16, child: page)); return Container(
color: Theme.of(context).colorScheme.background,
alignment: Alignment.center,
child: AspectRatio(aspectRatio: 9 / 16, child: page),
);
} }
return Container( return Container(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,

View File

@ -8,6 +8,7 @@ class Routes {
static const String generalSettings = '/general-settings'; static const String generalSettings = '/general-settings';
static const String radarDetails = '/radar-details'; static const String radarDetails = '/radar-details';
static const String newsDetails = '/news-details'; static const String newsDetails = '/news-details';
static const String studioDetails = '/studio-details';
static const String directList = '/direct-list'; static const String directList = '/direct-list';
static const String direct = '/direct'; static const String direct = '/direct';
static const String comments = '/comments'; static const String comments = '/comments';

View File

@ -1,3 +1,5 @@
import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart'; import 'package:didvan/services/network/request_helper.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -7,8 +9,8 @@ import 'package:just_audio/just_audio.dart';
class MediaService { class MediaService {
static final AudioPlayer audioPlayer = AudioPlayer(); static final AudioPlayer audioPlayer = AudioPlayer();
static String? audioPlayerTag; static String? audioPlayerTag;
static String? audioPlayerTitle; static StudioDetailsData? currentPodcast;
static String? audioPlayerCover; static StudioRequestArgs? podcastPlaylistArgs;
static void init() { static void init() {
audioPlayer.positionStream.listen((event) { audioPlayer.positionStream.listen((event) {
@ -21,6 +23,7 @@ class MediaService {
static Future<void> handleAudioPlayback({ static Future<void> handleAudioPlayback({
required dynamic audioSource, required dynamic audioSource,
bool isVoiceMessage = true,
}) async { }) async {
bool isNetworkAudio = audioSource.runtimeType == String; bool isNetworkAudio = audioSource.runtimeType == String;
String tag; String tag;
@ -40,9 +43,11 @@ class MediaService {
audioPlayerTag = tag; audioPlayerTag = tag;
if (isNetworkAudio) { if (isNetworkAudio) {
await audioPlayer.setUrl( await audioPlayer.setUrl(
RequestHelper.baseUrl + isVoiceMessage
? (RequestHelper.baseUrl +
audioSource + audioSource +
'?accessToken=${RequestService.token}', '?accessToken=${RequestService.token}')
: audioSource,
); );
} else { } else {
if (kIsWeb) { if (kIsWeb) {
@ -58,6 +63,8 @@ class MediaService {
static Future<void> resetAudioPlayer() async { static Future<void> resetAudioPlayer() async {
audioPlayerTag = null; audioPlayerTag = null;
currentPodcast = null;
podcastPlaylistArgs = null;
MediaService.audioPlayer.stop(); MediaService.audioPlayer.stop();
} }

View File

@ -5,6 +5,7 @@ import 'package:http_parser/http_parser.dart' as parser;
class RequestService { class RequestService {
static late String token; static late String token;
int? statusCode;
Map<String, dynamic> get result => _body?['result'] ?? const {}; Map<String, dynamic> get result => _body?['result'] ?? const {};
Map<String, dynamic> get errors => _body?['errors'] ?? const {}; Map<String, dynamic> get errors => _body?['errors'] ?? const {};
@ -162,6 +163,7 @@ class RequestService {
} }
void _handleResponse(http.Response? response) { void _handleResponse(http.Response? response) {
statusCode = response?.statusCode;
if (_handleError(response)) { if (_handleError(response)) {
if (response!.body.isNotEmpty) { if (response!.body.isNotEmpty) {
_body = json.decode(response.body); _body = json.decode(response.body);

View File

@ -1,5 +1,6 @@
import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/requests/news.dart';
import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/models/requests/studio.dart';
class RequestHelper { class RequestHelper {
static const String baseUrl = 'https://api.didvan.app'; static const String baseUrl = 'https://api.didvan.app';
@ -18,8 +19,19 @@ class RequestHelper {
static const String checkUsername = _baseUserUrl + '/CheckUsername'; static const String checkUsername = _baseUserUrl + '/CheckUsername';
static const String updateProfile = _baseUserUrl + '/profile/edit'; static const String updateProfile = _baseUserUrl + '/profile/edit';
static const String otp = _baseUserUrl + '/otp'; static const String otp = _baseUserUrl + '/otp';
static String bookmarks({String? type}) => static String bookmarks({
_baseUserUrl + '/marked/${type ?? ''}'; required int page,
String? search,
String? type,
String? studioType,
}) =>
_baseUserUrl +
'/marked/${type ?? ''}' +
_urlConcatGenerator([
MapEntry('page', page),
MapEntry('type', studioType),
MapEntry('search', search),
]);
static const String directTypes = baseUrl + '/direct/types'; static const String directTypes = baseUrl + '/direct/types';
static String direct(int id) => _baseDirectUrl + '/$id'; static String direct(int id) => _baseDirectUrl + '/$id';
@ -35,10 +47,11 @@ class RequestHelper {
baseUrl + baseUrl +
'/tag' + '/tag' +
_urlConcatGenerator([ _urlConcatGenerator([
MapEntry('limit', limit?.toString() ?? '3'), MapEntry('page', page),
MapEntry('limit', limit ?? '3'),
MapEntry('type', type), MapEntry('type', type),
MapEntry('id', itemId?.toString() ?? '1'), MapEntry('id', itemId ?? '1'),
MapEntry('tags', _urlListConcatGenerator(ids)) MapEntry('tags', _urlListConcatGenerator(ids)),
]); ]);
static String markRadar(int id) => _baseRadarUrl + '/$id/mark'; static String markRadar(int id) => _baseRadarUrl + '/$id/mark';
@ -50,7 +63,7 @@ class RequestHelper {
_baseRadarUrl + _baseRadarUrl +
'/$id' + '/$id' +
_urlConcatGenerator([ _urlConcatGenerator([
MapEntry('page', args.page.toString()), MapEntry('page', args.page),
MapEntry('start', args.startDate), MapEntry('start', args.startDate),
MapEntry('end', args.endDate), MapEntry('end', args.endDate),
MapEntry('search', args.search), MapEntry('search', args.search),
@ -59,7 +72,7 @@ class RequestHelper {
static String radarOverviews({required RadarRequestArgs args}) => static String radarOverviews({required RadarRequestArgs args}) =>
_baseRadarUrl + _baseRadarUrl +
_urlConcatGenerator([ _urlConcatGenerator([
MapEntry('page', args.page.toString()), MapEntry('page', args.page),
MapEntry('start', args.startDate), MapEntry('start', args.startDate),
MapEntry('end', args.endDate), MapEntry('end', args.endDate),
MapEntry('search', args.search), MapEntry('search', args.search),
@ -75,7 +88,7 @@ class RequestHelper {
_baseNewsUrl + _baseNewsUrl +
'/$id' + '/$id' +
_urlConcatGenerator([ _urlConcatGenerator([
MapEntry('page', args.page.toString()), MapEntry('page', args.page),
MapEntry('start', args.startDate), MapEntry('start', args.startDate),
MapEntry('end', args.endDate), MapEntry('end', args.endDate),
MapEntry('search', args.search), MapEntry('search', args.search),
@ -83,19 +96,45 @@ class RequestHelper {
static String newsOverviews({required NewsRequestArgs args}) => static String newsOverviews({required NewsRequestArgs args}) =>
_baseNewsUrl + _baseNewsUrl +
_urlConcatGenerator([ _urlConcatGenerator([
MapEntry('page', args.page.toString()), MapEntry('page', args.page),
MapEntry('start', args.startDate), MapEntry('start', args.startDate),
MapEntry('end', args.endDate), MapEntry('end', args.endDate),
MapEntry('search', args.search), MapEntry('search', args.search),
]); ]);
static String _urlConcatGenerator(List<MapEntry<String, String?>> additions) { 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 studioDetails(int id, StudioRequestArgs args) =>
_baseStudioUrl +
'/$id' +
_urlConcatGenerator([
MapEntry('page', args.page),
MapEntry('type', args.type),
MapEntry('order', args.order),
MapEntry('search', args.search),
]);
static String studioOverviews({required StudioRequestArgs args}) =>
_baseStudioUrl +
_urlConcatGenerator([
MapEntry('page', args.page),
MapEntry('type', args.type),
MapEntry('order', args.order),
MapEntry('search', args.search),
]);
static String _urlConcatGenerator(List<MapEntry<String, dynamic>> additions) {
String result = ''; String result = '';
additions.removeWhere((element) => element.value == null); additions.removeWhere(
(element) => element.value == null || element.value.toString().isEmpty,
);
if (additions.isNotEmpty) { if (additions.isNotEmpty) {
result += '?'; result += '?';
for (var i = 0; i < additions.length; i++) { for (var i = 0; i < additions.length; i++) {
result += (additions[i].key + '=' + additions[i].value!); result += (additions[i].key + '=' + additions[i].value!.toString());
if (i != additions.length - 1) { if (i != additions.length - 1) {
result += '&'; result += '&';
} }

View File

@ -79,7 +79,7 @@ class ActionSheetUtils {
isScrollControlled: true, isScrollControlled: true,
context: context, context: context,
builder: (context) => Container( builder: (context) => Container(
padding: const EdgeInsets.all(20), padding: data.hasPadding ? const EdgeInsets.all(20) : EdgeInsets.zero,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical( borderRadius: const BorderRadius.vertical(
@ -91,6 +91,7 @@ class ActionSheetUtils {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const SizedBox(height: 20),
Center( Center(
child: Container( child: Container(
height: 3, height: 3,
@ -99,6 +100,7 @@ class ActionSheetUtils {
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
if (data.title != null)
Row( Row(
children: [ children: [
if (data.titleIcon != null) if (data.titleIcon != null)
@ -109,10 +111,10 @@ class ActionSheetUtils {
), ),
if (data.titleIcon != null) const SizedBox(width: 8), if (data.titleIcon != null) const SizedBox(width: 8),
DidvanText( DidvanText(
data.title, data.title!,
style: Theme.of(context).textTheme.subtitle1, style: Theme.of(context).textTheme.subtitle1,
color: color: data.titleColor ??
data.titleColor ?? Theme.of(context).colorScheme.title, Theme.of(context).colorScheme.title,
) )
], ],
), ),
@ -169,6 +171,7 @@ class ActionSheetUtils {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
if (data.title != null)
Row( Row(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -185,7 +188,7 @@ class ActionSheetUtils {
), ),
Expanded( Expanded(
child: DidvanText( child: DidvanText(
data.title, data.title!,
style: Theme.of(context).textTheme.headline3, style: Theme.of(context).textTheme.headline3,
color: data.titleColor, color: data.titleColor,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,

View File

@ -1,8 +1,10 @@
import 'dart:developer'; import 'dart:developer';
import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/providers/server_data_provider.dart'; import 'package:didvan/providers/server_data_provider.dart';
import 'package:didvan/providers/user_provider.dart'; import 'package:didvan/providers/user_provider.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/authentication/authentication_state.dart'; import 'package:didvan/views/authentication/authentication_state.dart';
import 'package:didvan/views/authentication/widgets/authentication_layout.dart'; import 'package:didvan/views/authentication/widgets/authentication_layout.dart';
import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/button.dart';
@ -76,6 +78,25 @@ class _PasswordInputState extends State<PasswordInput> {
log(token); log(token);
await ServerDataProvider.getData(); await ServerDataProvider.getData();
Navigator.of(context).pushReplacementNamed(Routes.home); Navigator.of(context).pushReplacementNamed(Routes.home);
_showResetPasswordDialog();
} }
} }
void _showResetPasswordDialog() {
ActionSheetUtils.openDialog(
data: ActionSheetData(
content: const DidvanText(
'خوش آمدید!\nبرای امنیت بیشتر، رمز عبور خود را تغییر دهید.',
),
title: 'تغییر رمز عبور',
onConfirmed: () => Navigator.of(ActionSheetUtils.context).pushNamed(
Routes.authenticaion,
arguments: true,
),
confrimTitle: 'تغییر رمز عبور',
onDismissed: Navigator.of(ActionSheetUtils.context).pop,
dismissTitle: 'بعدا',
),
);
}
} }

View File

@ -39,6 +39,7 @@ class _UsernameInputState extends State<UsernameInput> {
if (value.length < 4) { if (value.length < 4) {
return 'نام کاربری نمی‌تواند از 4 کاراکتر کمتر باشد'; return 'نام کاربری نمی‌تواند از 4 کاراکتر کمتر باشد';
} }
return null;
}, },
onChanged: (value) { onChanged: (value) {
state.username = value; state.username = value;

View File

@ -1,3 +1,4 @@
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/providers/server_data_provider.dart'; import 'package:didvan/providers/server_data_provider.dart';
@ -6,6 +7,7 @@ import 'package:didvan/views/home/direct/direct_state.dart';
import 'package:didvan/views/home/direct/widgets/message.dart'; import 'package:didvan/views/home/direct/widgets/message.dart';
import 'package:didvan/views/home/direct/widgets/message_box.dart'; import 'package:didvan/views/home/direct/widgets/message_box.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart';
@ -66,6 +68,14 @@ class _DirectState extends State<Direct> {
sliver: SliverStateHandler<DirectState>( sliver: SliverStateHandler<DirectState>(
itemPadding: const EdgeInsets.only(bottom: 12), itemPadding: const EdgeInsets.only(bottom: 12),
state: state, state: state,
enableEmptyState: state.messages.isEmpty,
emptyState: Padding(
padding: const EdgeInsets.only(bottom: 160),
child: EmptyState(
asset: Assets.emptyChat,
title: 'اولین پیام را بنویسید...',
),
),
builder: (context, state, index) => Message( builder: (context, state, index) => Message(
message: state.messages[index], message: state.messages[index],
), ),

View File

@ -1,7 +1,7 @@
import 'dart:io'; import 'dart:io';
import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/media/media.dart';
import 'package:didvan/views/home/widgets/audio_slider.dart'; import 'package:didvan/views/home/widgets/audio/audio_slider.dart';
import 'package:didvan/views/home/widgets/player_controller_button.dart'; import 'package:didvan/views/home/widgets/player_controller_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';

View File

@ -18,6 +18,7 @@ class MessageBox extends StatelessWidget {
Consumer<DirectState>( Consumer<DirectState>(
builder: (context, state, child) => state.replyRadar != null builder: (context, state, child) => state.replyRadar != null
? _MessageBoxContainer( ? _MessageBoxContainer(
isMessage: false,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: Row( child: Row(
@ -53,6 +54,7 @@ class MessageBox extends StatelessWidget {
: const SizedBox(), : const SizedBox(),
), ),
_MessageBoxContainer( _MessageBoxContainer(
isMessage: true,
child: Consumer<DirectState>( child: Consumer<DirectState>(
builder: (context, state, child) { builder: (context, state, child) {
if (state.isRecording) { if (state.isRecording) {
@ -71,12 +73,17 @@ class MessageBox extends StatelessWidget {
class _MessageBoxContainer extends StatelessWidget { class _MessageBoxContainer extends StatelessWidget {
final Widget child; final Widget child;
const _MessageBoxContainer({Key? key, required this.child}) : super(key: key); final bool isMessage;
const _MessageBoxContainer({
Key? key,
required this.child,
required this.isMessage,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
height: 68, height: isMessage ? 68 : null,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border( border: Border(
top: BorderSide( top: BorderSide(

View File

@ -1,11 +1,12 @@
import 'package:didvan/models/tag.dart'; import 'package:didvan/models/tag.dart';
import 'package:didvan/models/view/app_bar_data.dart'; import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/views/home/hashtag/hashtag_state.dart'; import 'package:didvan/views/home/hashtag/hashtag_state.dart';
import 'package:didvan/views/home/widgets/news_overview.dart'; import 'package:didvan/views/home/widgets/overview/news.dart';
import 'package:didvan/views/home/widgets/radar_overview.dart'; import 'package:didvan/views/home/widgets/overview/radar.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart'; import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class Hashtag extends StatefulWidget { class Hashtag extends StatefulWidget {
@ -37,10 +38,16 @@ class _HashtagState extends State<Hashtag> {
placeholder: RadarOverview.placeholder, placeholder: RadarOverview.placeholder,
builder: (context, state, index) { builder: (context, state, index) {
index++; index++;
if (index % 15 == 0 && index / 15 >= state.page) { if (index % 15 == 0 && state.lastPage != state.page) {
state.getTagItems(page: index ~/ 15 + 1); state.getTagItems(page: state.page + 1);
} }
index--; index--;
if (index == state.items.length) {
return SpinKitThreeBounce(
color: Theme.of(context).colorScheme.primary,
size: 24,
);
}
final item = state.items[index]; final item = state.items[index];
final type = item.type; final type = item.type;
if (type == 'radar') { if (type == 'radar') {
@ -57,8 +64,9 @@ class _HashtagState extends State<Hashtag> {
} }
return Container(); return Container();
}, },
childCount: state.items.length, childCount:
onRetry: () => state.getTagItems(page: 1), state.items.length + (state.page != state.lastPage ? 1 : 0),
onRetry: () => state.getTagItems(page: state.page),
), ),
) )
], ],

View File

@ -9,6 +9,7 @@ class HashtagState extends CoreProvier {
late final int id; late final int id;
int page = 1; int page = 1;
int lastPage = 1;
Future<void> getTagItems({required int page}) async { Future<void> getTagItems({required int page}) async {
this.page = page; this.page = page;

View File

@ -8,7 +8,7 @@ import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/home/news/news_state.dart'; import 'package:didvan/views/home/news/news_state.dart';
import 'package:didvan/views/home/widgets/date_picker_button.dart'; import 'package:didvan/views/home/widgets/date_picker_button.dart';
import 'package:didvan/views/home/widgets/logo_app_bar.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart';
import 'package:didvan/views/home/widgets/news_overview.dart'; import 'package:didvan/views/home/widgets/overview/news.dart';
import 'package:didvan/views/home/widgets/search_field.dart'; import 'package:didvan/views/home/widgets/search_field.dart';
import 'package:didvan/views/widgets/item_title.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/empty_result.dart';
@ -57,7 +57,7 @@ class _NewsState extends State<News> {
builder: (context, state, index) { builder: (context, state, index) {
index += 2; index += 2;
if (index % 15 == 0 && state.lastPage != state.page) { if (index % 15 == 0 && state.lastPage != state.page) {
state.getNews(page: index ~/ 15 + 1); state.getNews(page: state.page + 1);
} }
index -= 2; index -= 2;
if (index >= state.news.length) { if (index >= state.news.length) {

View File

@ -84,7 +84,7 @@ class NewsDetailsState extends CoreProvier {
news.any((n) => newsItem != null && n != null && n.id == newsItem.id); news.any((n) => newsItem != null && n != null && n.id == newsItem.id);
void onCommentsChanged(int count) { void onCommentsChanged(int count) {
news.firstWhere((item) => item!.id == currentNews.id)!.comments = count; news.firstWhere((item) => item?.id == currentNews.id)!.comments = count;
notifyListeners(); notifyListeners();
} }

View File

@ -15,7 +15,7 @@ import 'package:didvan/views/home/radar/widgets/categories_list.dart';
import 'package:didvan/views/home/widgets/date_picker_button.dart'; import 'package:didvan/views/home/widgets/date_picker_button.dart';
import 'package:didvan/views/home/widgets/logo_app_bar.dart'; import 'package:didvan/views/home/widgets/logo_app_bar.dart';
import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/home/widgets/radar_overview.dart'; import 'package:didvan/views/home/widgets/overview/radar.dart';
import 'package:didvan/views/home/widgets/search_field.dart'; import 'package:didvan/views/home/widgets/search_field.dart';
import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/didvan/checkbox.dart'; import 'package:didvan/views/widgets/didvan/checkbox.dart';
@ -130,7 +130,7 @@ class _RadarState extends State<Radar> {
builder: (context, state, index) { builder: (context, state, index) {
index += 2; index += 2;
if (index % 15 == 0 && state.lastPage != state.page) { if (index % 15 == 0 && state.lastPage != state.page) {
state.getRadars(page: index ~/ 15 + 1); state.getRadars(page: state.page + 1);
} }
index -= 2; index -= 2;
if (index >= state.radars.length) { if (index >= state.radars.length) {

View File

@ -116,7 +116,7 @@ class RadarDetailsState extends CoreProvier {
radars.any((r) => radar != null && r != null && r.id == radar.id); radars.any((r) => radar != null && r != null && r.id == radar.id);
void onCommentsChanged(int count) { void onCommentsChanged(int count) {
radars.firstWhere((radar) => radar!.id == currentRadar.id)!.comments = radars.firstWhere((radar) => radar?.id == currentRadar.id)!.comments =
count; count;
notifyListeners(); notifyListeners();
} }

View File

@ -66,10 +66,10 @@ class CategoryItem extends StatelessWidget {
isVisible: !isColapsed, isVisible: !isColapsed,
child: Container( child: Container(
width: !_useWebMobileLayout(context) width: !_useWebMobileLayout(context)
? _width(context) / 1.5 ? _width(context) / 2
: ds.width / 5, : ds.width / 5,
height: !_useWebMobileLayout(context) height: !_useWebMobileLayout(context)
? _width(context) / 1.5 ? _width(context) / 2
: ds.width / 5, : ds.width / 5,
decoration: BoxDecoration( decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,

View File

@ -9,20 +9,27 @@ class BookmarksState extends CoreProvier {
final List<OverviewData> bookmarks = []; final List<OverviewData> bookmarks = [];
String search = ''; String search = '';
String lastSearch = ''; String lastSearch = '';
int page = 1;
int lastPage = 1;
bool get searching => search != ''; bool get searching => search != '';
Future<void> getBookmarks() async { Future<void> getBookmarks({required int page}) async {
if (search != '') { if (search != '') {
lastSearch = search; lastSearch = search;
} }
if (page == 1) {
bookmarks.clear();
}
this.page = page;
appState = AppState.busy; appState = AppState.busy;
final service = RequestService(RequestHelper.bookmarks()); final service =
RequestService(RequestHelper.bookmarks(page: page, search: search));
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
lastPage = service.result['lastPage'];
final marks = service.result['contents']; final marks = service.result['contents'];
bookmarks.clear();
for (var i = 0; i < marks.length; i++) { for (var i = 0; i < marks.length; i++) {
bookmarks.add(OverviewData.fromJson(marks[i])); bookmarks.add(OverviewData.fromJson(marks[i]));
} }

View File

@ -6,7 +6,7 @@ import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/home/settings/bookmarks/bookmark_state.dart'; import 'package:didvan/views/home/settings/bookmarks/bookmark_state.dart';
import 'package:didvan/views/home/widgets/menu_item.dart'; import 'package:didvan/views/home/widgets/menu_item.dart';
import 'package:didvan/views/home/widgets/multitype_overview.dart'; import 'package:didvan/views/home/widgets/overview/multitype.dart';
import 'package:didvan/views/home/widgets/search_field.dart'; import 'package:didvan/views/home/widgets/search_field.dart';
import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
@ -33,7 +33,7 @@ class _BookmarksState extends State<Bookmarks> {
@override @override
void initState() { void initState() {
Future.delayed(Duration.zero, () { Future.delayed(Duration.zero, () {
context.read<BookmarksState>().getBookmarks(); context.read<BookmarksState>().getBookmarks(page: 1);
}); });
super.initState(); super.initState();
} }
@ -74,14 +74,14 @@ class _BookmarksState extends State<Bookmarks> {
), ),
const DidvanDivider(), const DidvanDivider(),
MenuItem( MenuItem(
onTap: () => _onCategorySelected('videos'), onTap: () => _onCategorySelected('video'),
title: 'ویدئو‌ها', title: 'ویدئو‌ها',
icon: DidvanIcons.video_regular, icon: DidvanIcons.video_regular,
iconSize: 24, iconSize: 24,
), ),
const DidvanDivider(), const DidvanDivider(),
MenuItem( MenuItem(
onTap: () => _onCategorySelected('podcasts'), onTap: () => _onCategorySelected('podcast'),
title: 'پادکست‌ها', title: 'پادکست‌ها',
icon: DidvanIcons.podcast_regular, icon: DidvanIcons.podcast_regular,
iconSize: 24, iconSize: 24,
@ -103,26 +103,33 @@ class _BookmarksState extends State<Bookmarks> {
SliverStateHandler<BookmarksState>( SliverStateHandler<BookmarksState>(
state: state, state: state,
centerEmptyState: state.searching, centerEmptyState: state.searching,
builder: (context, state, index) => MultitypeOverview( builder: (context, state, index) {
index++;
if (index % 15 == 0 && state.lastPage != state.page) {
state.getBookmarks(page: state.page + 1);
}
index--;
return MultitypeOverview(
item: state.bookmarks[index], item: state.bookmarks[index],
onMarkChanged: state.onMarkChanged, onMarkChanged: state.onMarkChanged,
hasUnmarkConfirmation: true, hasUnmarkConfirmation: true,
), );
},
placeholder: MultitypeOverview.placeholder, placeholder: MultitypeOverview.placeholder,
itemPadding: const EdgeInsets.only(bottom: 8), itemPadding: const EdgeInsets.only(bottom: 8),
emptyState: state.searching emptyState: state.searching
? EmptyResult(onNewSearch: _focuseNode.requestFocus) ? EmptyResult(onNewSearch: _focuseNode.requestFocus)
: const EmptyList(), : const EmptyList(),
enableEmptyState: state.bookmarks.isEmpty, enableEmptyState: state.bookmarks.isEmpty,
childCount: state.bookmarks.length, childCount:
onRetry: state.getBookmarks, state.bookmarks.length + (state.page != state.lastPage ? 1 : 0),
onRetry: () => state.getBookmarks(page: state.page),
), ),
], ],
); );
} }
void _onCategorySelected(String type) { void _onCategorySelected(String type) {
if (type != 'radar' && type != 'news') return;
FocusScope.of(context).unfocus(); FocusScope.of(context).unfocus();
Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: type); Navigator.of(context).pushNamed(Routes.filteredBookmarks, arguments: type);
} }
@ -135,7 +142,7 @@ class _BookmarksState extends State<Bookmarks> {
_timer?.cancel(); _timer?.cancel();
_timer = Timer(const Duration(seconds: 1), () { _timer = Timer(const Duration(seconds: 1), () {
state.search = value; state.search = value;
state.getBookmarks(); state.getBookmarks(page: 1);
}); });
} }
} }

View File

@ -1,7 +1,7 @@
import 'package:didvan/models/view/app_bar_data.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/settings/bookmarks/filtered_bookmark/filtered_bookmarks_state.dart';
import 'package:didvan/views/home/widgets/news_overview.dart'; import 'package:didvan/views/home/widgets/overview/news.dart';
import 'package:didvan/views/home/widgets/radar_overview.dart'; import 'package:didvan/views/home/widgets/overview/radar.dart';
import 'package:didvan/views/widgets/didvan/scaffold.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/empty_list.dart';
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart'; import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
@ -20,7 +20,7 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
void initState() { void initState() {
Future.delayed( Future.delayed(
Duration.zero, Duration.zero,
context.read<FilteredBookmarksState>().getBookmarks, () => context.read<FilteredBookmarksState>().getBookmarks(page: 1),
); );
super.initState(); super.initState();
} }
@ -54,6 +54,11 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
placeholder: RadarOverview.placeholder, placeholder: RadarOverview.placeholder,
emptyState: const EmptyList(), emptyState: const EmptyList(),
builder: (context, state, index) { builder: (context, state, index) {
index++;
if (index % 15 == 0 && state.lastPage != state.page) {
state.getBookmarks(page: state.page + 1);
}
index--;
if (state.type == 'radar') { if (state.type == 'radar') {
return RadarOverview( return RadarOverview(
radar: state.bookmarks[index], radar: state.bookmarks[index],
@ -69,7 +74,7 @@ class _FilteredBookmarksState extends State<FilteredBookmarks> {
); );
}, },
childCount: state.bookmarks.length, childCount: state.bookmarks.length,
onRetry: () => state.getBookmarks(), onRetry: () => state.getBookmarks(page: state.page),
), ),
), ),
], ],

View File

@ -10,24 +10,42 @@ class FilteredBookmarksState extends CoreProvier {
String lastSearch = ''; String lastSearch = '';
final List<OverviewData> bookmarks = []; final List<OverviewData> bookmarks = [];
final String type; final String type;
int page = 1;
int lastPage = 1;
FilteredBookmarksState(this.type); FilteredBookmarksState(this.type);
bool get searching => search != ''; bool get searching => search != '';
Future<void> getBookmarks() async { Future<void> getBookmarks({required int page}) async {
if (search != '') { if (search != '') {
lastSearch = search; lastSearch = search;
} }
if (page == 1) {
bookmarks.clear();
}
this.page = page;
appState = AppState.busy; appState = AppState.busy;
String typeString = '';
if (type == 'video' || type == 'podcast') {
typeString = 'studios';
} else if (type == 'news') {
typeString = type;
} else {
typeString = type + 's';
}
final service = RequestService( final service = RequestService(
RequestHelper.bookmarks(type: type == 'news' ? type : type + 's'), RequestHelper.bookmarks(
type: typeString,
page: page,
studioType: type == 'podcast' || type == 'video' ? type : null,
),
); );
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
final marks = service.result[type != 'news' ? type + 's' : type]; lastPage = service.result['lastPage'];
bookmarks.clear(); final marks = service.result[typeString];
for (var i = 0; i < marks.length; i++) { for (var i = 0; i < marks.length; i++) {
bookmarks.add(OverviewData.fromJson(marks[i])); bookmarks.add(OverviewData.fromJson(marks[i]));
} }

View File

@ -87,7 +87,7 @@ class Settings extends StatelessWidget {
MenuItem( MenuItem(
icon: DidvanIcons.didvan_solid, icon: DidvanIcons.didvan_solid,
title: 'معرفی دیدوان', title: 'معرفی دیدوان',
onTap: () => Navigator.of(context).pushNamed(Routes.aboutUs), onTap: () => launch('https://didvan.app/'),
), ),
const DidvanDivider(), const DidvanDivider(),
MenuItem( MenuItem(
@ -101,14 +101,14 @@ class Settings extends StatelessWidget {
MenuItem( MenuItem(
icon: DidvanIcons.alert_regular, icon: DidvanIcons.alert_regular,
title: 'حریم خصوصی', title: 'حریم خصوصی',
onTap: () => {}, onTap: () => launch('https://didvan.app/'),
), ),
], ],
), ),
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
DidvanText( DidvanText(
'نسخه نرم‌افزار: آزمایشی', 'نسخه نرم‌افزار: 1.1.4',
style: Theme.of(context).textTheme.caption, style: Theme.of(context).textTheme.caption,
), ),
], ],

View File

@ -1,17 +0,0 @@
import 'package:flutter/material.dart';
class PodcastDetails extends StatelessWidget {
const PodcastDetails({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Material(
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
),
),
);
}
}

View File

@ -0,0 +1,170 @@
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,
),
),
),
],
),
),
],
),
),
),
);
}
}

View File

@ -0,0 +1,141 @@
import 'dart:async';
import 'dart:math';
import 'package:didvan/models/enums.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/providers/core_provider.dart';
import 'package:didvan/services/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 int initialIndex;
late StudioRequestArgs args;
int _selectedDetailsIndex = 0;
bool isFetchingNewItem = false;
final List<int> relatedQueue = [];
int _currentIndex = 0;
int get currentIndex => _currentIndex;
int get selectedDetailsIndex => _selectedDetailsIndex;
set selectedDetailsIndex(int value) {
_selectedDetailsIndex = value;
notifyListeners();
}
StudioDetailsData get currentStudio {
try {
return studios[_currentIndex]!;
} catch (e) {
return studios[_currentIndex + 1]!;
}
}
Future<void> getStudioDetails(int id,
{bool? isForward, StudioRequestArgs? args}) async {
if (args != null) {
this.args = args;
}
if (isForward == null) {
appState = AppState.busy;
} else {
isFetchingNewItem = true;
notifyListeners();
}
final service = RequestService(RequestHelper.studioDetails(id, this.args));
await service.httpGet();
if (service.isSuccess) {
final result = service.result;
final studio = StudioDetailsData.fromJson(result['studio']);
if (this.args.page == 0) {
studios.add(studio);
initialIndex = 0;
appState = AppState.idle;
return;
}
if (this.args.type == 'podcast') {
MediaService.currentPodcast = studio;
MediaService.podcastPlaylistArgs = args;
await MediaService.handleAudioPlayback(
audioSource: studio.media,
isVoiceMessage: false,
);
}
StudioDetailsData? prevStudio;
if (result['prevStudio'].isNotEmpty) {
prevStudio = StudioDetailsData.fromJson(result['prevStudio']);
}
StudioDetailsData? nextStudio;
if (result['nextStudio'].isNotEmpty) {
nextStudio = StudioDetailsData.fromJson(result['nextStudio']);
}
if (isForward == null) {
studios
.addAll(List.generate(max(studio.order - 2, 0), (index) => null));
if (prevStudio != null) {
studios.add(prevStudio);
}
studios.add(studio);
if (nextStudio != null) {
studios.add(nextStudio);
}
_currentIndex = initialIndex = studio.order - 1;
} else if (isForward) {
if (!exists(nextStudio) && nextStudio != null) {
studios.add(nextStudio);
}
_currentIndex++;
} else if (!isForward) {
if (!exists(prevStudio) && prevStudio != null) {
studios[_currentIndex - 2] = prevStudio;
}
_currentIndex--;
}
isFetchingNewItem = false;
appState = AppState.idle;
return;
}
//why? total page state shouldn't die!
if (isForward == null) {
appState = AppState.failed;
}
}
Future<void> getRelatedContents() async {
if (currentStudio.relatedContents.isNotEmpty) return;
relatedQueue.add(currentStudio.id);
final service = RequestService(RequestHelper.tag(
ids: currentStudio.tags.map((tag) => tag.id).toList(),
itemId: currentStudio.id,
type: 'studio',
));
await service.httpGet();
if (service.isSuccess) {
final relateds = service.result['contents'];
for (var i = 0; i < relateds.length; i++) {
studios
.where((element) => element != null)
.firstWhere((element) => element!.id == currentStudio.id)!
.relatedContents
.add(OverviewData.fromJson(relateds[i]));
}
notifyListeners();
}
}
bool exists(StudioDetailsData? studio) =>
studios.any((r) => studio != null && r != null && r.id == studio.id);
void onCommentsChanged(int count) {
studios.firstWhere((studio) => studio?.id == currentStudio.id)!.comments =
count;
notifyListeners();
}
}

View File

@ -0,0 +1,97 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/views/home/studio/studio_details/studio_details_state.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class StudioDetailsWidget extends StatelessWidget {
const StudioDetailsWidget({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Consumer<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,
)
],
),
),
);
}
}

View File

@ -1,7 +1,93 @@
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/core_provider.dart';
import 'package:didvan/providers/user_provider.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
class StudioState extends CoreProvier { class StudioState extends CoreProvier {
final List<OverviewData> studios = [];
String? search;
String? lastSearch;
int page = 1;
int lastPage = 1;
int selectedSortTypeIndex = 0; int selectedSortTypeIndex = 0;
bool videosSelected = true; bool _videosSelected = true;
bool get videosSelected => _videosSelected;
set videosSelected(bool value) {
if (_videosSelected == value) return;
_videosSelected = value;
studios.clear();
getStudioOverviews(page: page);
}
void init() {
search = '';
lastSearch = '';
_videosSelected = true;
selectedSortTypeIndex = 0;
Future.delayed(Duration.zero, () {
getStudioOverviews(page: 1);
});
}
String get order {
if (selectedSortTypeIndex == 0) return 'date';
if (selectedSortTypeIndex == 1) return 'view';
return 'comment';
}
String get type {
if (videosSelected) return 'video';
return 'podcast';
}
Future<void> getStudioOverviews({required int page}) async {
this.page = page;
if (page == 1) {
appState = AppState.busy;
}
final service = RequestService(
RequestHelper.studioOverviews(
args: StudioRequestArgs(
page: page,
type: type,
search: search,
order: order,
),
),
);
await service.httpGet();
if (service.isSuccess) {
if (page == 1) {
studios.clear();
}
lastPage = service.result['lastPage'];
final studioItems = service.result['studios'];
for (var i = 0; i < studioItems.length; i++) {
studios.add(OverviewData.fromJson(studioItems[i]));
}
appState = AppState.idle;
return;
}
appState = AppState.failed;
}
Future<void> changeMark(int id, bool value) async {
studios.firstWhere((element) => element.id == id).marked = value;
notifyListeners();
UserProvider.changeStudioMark(id, value);
}
void onCommentsChanged(int id, int count) {
studios.firstWhere((radar) => radar.id == id).comments = count;
notifyListeners();
}
} }

View File

@ -14,14 +14,15 @@ class StudioTabBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final state = context.watch<StudioState>(); final state = context.watch<StudioState>();
return Container( return AnimatedContainer(
duration: DesignConfig.lowAnimationDuration,
margin: const EdgeInsets.symmetric(horizontal: 16), margin: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: state.videosSelected color: state.videosSelected
? Theme.of(context).colorScheme.secondary ? Theme.of(context).colorScheme.secondary
: Theme.of(context).primaryColor, : Theme.of(context).colorScheme.primary,
), ),
borderRadius: DesignConfig.lowBorderRadius, borderRadius: DesignConfig.lowBorderRadius,
), ),
@ -32,7 +33,7 @@ class StudioTabBar extends StatelessWidget {
icon: DidvanIcons.video_solid, icon: DidvanIcons.video_solid,
selectedColor: Theme.of(context).colorScheme.secondary, selectedColor: Theme.of(context).colorScheme.secondary,
title: 'ویدئو', title: 'ویدئو',
onTap: () {}, onTap: () => state.videosSelected = true,
isSelected: state.videosSelected, isSelected: state.videosSelected,
), ),
), ),
@ -46,7 +47,7 @@ class StudioTabBar extends StatelessWidget {
icon: DidvanIcons.podcast_solid, icon: DidvanIcons.podcast_solid,
selectedColor: Theme.of(context).colorScheme.focusedBorder, selectedColor: Theme.of(context).colorScheme.focusedBorder,
title: 'پادکست', title: 'پادکست',
onTap: () {}, onTap: () => state.videosSelected = false,
isSelected: !state.videosSelected, isSelected: !state.videosSelected,
), ),
), ),

View File

@ -0,0 +1,174 @@
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/services/media/media.dart';
import 'package:didvan/views/home/widgets/audio/audio_slider.dart';
import 'package:didvan/views/home/widgets/bookmark_button.dart';
import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/ink_wrapper.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart';
class AudioPlayerWidget extends StatelessWidget {
final StudioDetailsData podcast;
const AudioPlayerWidget({Key? key, required this.podcast}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
decoration: BoxDecoration(
borderRadius: const BorderRadius.vertical(top: Radius.circular(8)),
color: Theme.of(context).colorScheme.surface,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
margin: const EdgeInsets.symmetric(vertical: 20),
height: 3,
width: 50,
color: Theme.of(context).colorScheme.hint,
),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 24),
child: SkeletonImage(
imageUrl: podcast.image,
aspectRatio: 1 / 1,
),
),
const SizedBox(height: 16),
DidvanText(
podcast.title,
style: Theme.of(context).textTheme.bodyText1,
),
const SizedBox(height: 16),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
child: AudioSlider(
tag: podcast.media,
showTimer: true,
duration: podcast.duration,
),
),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
DidvanIconButton(
icon: DidvanIcons.sleep_timer_regular,
onPressed: () {},
),
Column(
children: [
DidvanIconButton(
size: 32,
icon: DidvanIcons.media_forward_solid,
onPressed: () {
MediaService.audioPlayer.seek(
Duration(
seconds:
MediaService.audioPlayer.position.inSeconds + 30,
),
);
},
),
const DidvanText('30', isEnglishFont: true),
],
),
_PlayPouseAnimatedIcon(
audioSource: podcast.media,
),
Column(
children: [
DidvanIconButton(
size: 32,
icon: DidvanIcons.media_backward_solid,
onPressed: () {
MediaService.audioPlayer.seek(
Duration(
seconds:
MediaService.audioPlayer.position.inSeconds - 10,
),
);
},
),
const DidvanText('10', isEnglishFont: true),
],
),
BookmarkButton(
gestureSize: 48,
value: podcast.marked,
onMarkChanged: (value) {},
),
],
),
],
),
);
}
}
class _PlayPouseAnimatedIcon extends StatefulWidget {
final String audioSource;
const _PlayPouseAnimatedIcon({Key? key, required this.audioSource})
: super(key: key);
@override
State<_PlayPouseAnimatedIcon> createState() => __PlayPouseAnimatedIconState();
}
class __PlayPouseAnimatedIconState extends State<_PlayPouseAnimatedIcon>
with SingleTickerProviderStateMixin {
late final AnimationController _animationController;
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: DesignConfig.lowAnimationDuration,
);
if (MediaService.audioPlayer.playing) {
_animationController.forward();
}
}
@override
Widget build(BuildContext context) {
return InkWrapper(
borderRadius: BorderRadius.circular(100),
onPressed: () {
MediaService.handleAudioPlayback(
audioSource: widget.audioSource,
isVoiceMessage: false,
);
if (MediaService.audioPlayer.playing) {
_animationController.forward();
} else {
_animationController.reverse();
}
},
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.title,
shape: BoxShape.circle,
),
child: AnimatedIcon(
size: 40,
color: Theme.of(context).colorScheme.surface,
icon: AnimatedIcons.play_pause,
progress: _animationController,
),
),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
}

View File

@ -0,0 +1,63 @@
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/services/media/media.dart';
import 'package:flutter/material.dart';
class AudioSlider extends StatelessWidget {
final String tag;
final bool showTimer;
final int? duration;
final bool disableThumb;
const AudioSlider({
Key? key,
required this.tag,
this.showTimer = false,
this.duration,
this.disableThumb = false,
}) : super(key: key);
bool get _isPlaying => MediaService.audioPlayerTag == tag;
@override
Widget build(BuildContext context) {
return IgnorePointer(
ignoring: MediaService.audioPlayerTag != tag,
child: Directionality(
textDirection: TextDirection.ltr,
child: StreamBuilder<Duration>(
stream: _isPlaying ? MediaService.audioPlayer.positionStream : null,
builder: (context, snapshot) => ProgressBar(
thumbColor: Theme.of(context).colorScheme.title,
progressBarColor: DesignConfig.isDark
? Theme.of(context).colorScheme.title
: Theme.of(context).colorScheme.primary,
baseBarColor: Theme.of(context).colorScheme.border,
bufferedBarColor: Theme.of(context).colorScheme.splash,
total: MediaService.audioPlayer.duration ??
Duration(seconds: duration ?? 0),
progress: snapshot.data ?? Duration.zero,
buffered: _isPlaying
? MediaService.audioPlayer.bufferedPosition
: Duration.zero,
thumbRadius: disableThumb ? 0 : 6,
barHeight: 3,
timeLabelTextStyle: TextStyle(
fontSize: showTimer ? null : 0,
height: showTimer ? 3 : 0,
fontFamily: DesignConfig.fontFamily.replaceAll(
'-FA',
'',
),
),
onSeek: (value) => _onSeek(value.inMilliseconds),
),
),
),
);
}
void _onSeek(int value) {
MediaService.audioPlayer.seek(Duration(milliseconds: value));
}
}

View File

@ -1,39 +0,0 @@
import 'package:audio_video_progress_bar/audio_video_progress_bar.dart';
import 'package:didvan/services/media/media.dart';
import 'package:flutter/material.dart';
class AudioSlider extends StatelessWidget {
final String tag;
const AudioSlider({Key? key, required this.tag}) : super(key: key);
bool get _isPlaying => MediaService.audioPlayerTag == tag;
@override
Widget build(BuildContext context) {
return IgnorePointer(
ignoring: MediaService.audioPlayerTag != tag,
child: Directionality(
textDirection: TextDirection.ltr,
child: StreamBuilder<Duration>(
stream: _isPlaying ? MediaService.audioPlayer.positionStream : null,
builder: (context, snapshot) {
return ProgressBar(
total: MediaService.audioPlayer.duration ?? Duration.zero,
progress: snapshot.data ?? Duration.zero,
buffered:
_isPlaying ? MediaService.audioPlayer.bufferedPosition : null,
thumbRadius: 6,
barHeight: 3,
timeLabelTextStyle: const TextStyle(fontSize: 0),
onSeek: (value) => _onSeek(value.inMilliseconds),
);
},
),
),
);
}
void _onSeek(int value) {
MediaService.audioPlayer.seek(Duration(milliseconds: value));
}
}

View File

@ -2,10 +2,16 @@ import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.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/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/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/skeleton_image.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/material.dart';
import 'package:provider/provider.dart';
class DidvanBNB extends StatelessWidget { class DidvanBNB extends StatelessWidget {
final int currentTabIndex; final int currentTabIndex;
@ -15,6 +21,9 @@ class DidvanBNB extends StatelessWidget {
{Key? key, required this.currentTabIndex, required this.onTabChanged}) {Key? key, required this.currentTabIndex, required this.onTabChanged})
: super(key: key); : super(key: key);
bool get _enablePlayerController =>
MediaService.currentPodcast != null || MediaService.audioPlayer.playing;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return StreamBuilder<bool>( return StreamBuilder<bool>(
@ -22,9 +31,12 @@ class DidvanBNB extends StatelessWidget {
builder: (context, snapshot) { builder: (context, snapshot) {
return Stack( return Stack(
children: [ children: [
AnimatedContainer( GestureDetector(
onTap: () => _showPlayerBottomSheet(context),
child: AnimatedContainer(
padding: const EdgeInsets.only(top: 12),
duration: DesignConfig.lowAnimationDuration, duration: DesignConfig.lowAnimationDuration,
height: snapshot.data == true ? 120 : 72, height: _enablePlayerController ? 120 : 72,
decoration: BoxDecoration( decoration: BoxDecoration(
color: DesignConfig.isDark color: DesignConfig.isDark
? Theme.of(context).colorScheme.focused ? Theme.of(context).colorScheme.focused
@ -33,20 +45,77 @@ class DidvanBNB extends StatelessWidget {
top: Radius.circular(16), top: Radius.circular(16),
), ),
), ),
child: !_enablePlayerController
? const SizedBox()
: SizedBox(
height: 48,
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
const DidvanIconButton( Padding(
padding: const EdgeInsets.only(
right: 12,
left: 16,
),
child: DidvanIconButton(
icon: DidvanIcons.close_regular, icon: DidvanIcons.close_regular,
gestureSize: 24, color: DesignConfig.isDark
? null
: Theme.of(context).colorScheme.secondCTA,
gestureSize: 28,
onPressed: MediaService.resetAudioPlayer, onPressed: MediaService.resetAudioPlayer,
), ),
),
SkeletonImage(
imageUrl: MediaService.currentPodcast!.image,
width: 32,
height: 32,
),
const SizedBox(width: 16), const SizedBox(width: 16),
if (MediaService.audioPlayerCover != null) Expanded(
SkeletonImage(imageUrl: MediaService.audioPlayerCover!), child: Column(
const SizedBox(width: 16), 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( Positioned(
bottom: 0, bottom: 0,
left: 0, left: 0,
@ -105,6 +174,55 @@ class DidvanBNB extends StatelessWidget {
); );
}); });
} }
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 { class _NavBarItem extends StatelessWidget {

View File

@ -8,14 +8,14 @@ import 'package:flutter/material.dart';
class BookmarkButton extends StatefulWidget { class BookmarkButton extends StatefulWidget {
final bool value; final bool value;
final void Function(bool value) onMarkChanged; final void Function(bool value) onMarkChanged;
final bool bigGestureSize;
final bool askForConfirmation; final bool askForConfirmation;
final double gestureSize;
const BookmarkButton({ const BookmarkButton({
Key? key, Key? key,
required this.value, required this.value,
this.bigGestureSize = false,
required this.onMarkChanged, required this.onMarkChanged,
this.askForConfirmation = false, this.askForConfirmation = false,
required this.gestureSize,
}) : super(key: key); }) : super(key: key);
@override @override
@ -40,7 +40,7 @@ class _BookmarkButtonState extends State<BookmarkButton> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DidvanIconButton( return DidvanIconButton(
gestureSize: widget.bigGestureSize ? 32 : 24, gestureSize: widget.gestureSize,
icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular, icon: _value ? DidvanIcons.bookmark_solid : DidvanIcons.bookmark_regular,
onPressed: () async { onPressed: () async {
bool confirm = false; bool confirm = false;

View File

@ -0,0 +1,47 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
class DurationWidget extends StatelessWidget {
final int duration;
const DurationWidget({Key? key, required this.duration}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(4),
decoration: BoxDecoration(
border: Border.all(
color: Theme.of(context).colorScheme.focusedBorder,
),
borderRadius: BorderRadius.circular(5),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Icon(
DidvanIcons.timer_regular,
size: 16,
color: Theme.of(context).colorScheme.focusedBorder,
),
const SizedBox(width: 4),
DidvanText(
DateTimeUtils.normalizeTimeDuration(
Duration(seconds: duration),
),
isEnglishFont: true,
color: Theme.of(context).colorScheme.focusedBorder,
),
const SizedBox(width: 4),
Icon(
DidvanIcons.play_circle_regular,
size: 16,
color: Theme.of(context).colorScheme.focusedBorder,
),
],
),
);
}
}

View File

@ -112,7 +112,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
}, },
bigGestureSize: true, gestureSize: 32,
), ),
SizedBox( SizedBox(
width: 60, width: 60,
@ -151,7 +151,7 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
Navigator.of(context).pop(); Navigator.of(context).pop();
} }
}, },
bigGestureSize: true, gestureSize: 32,
), ),
if (widget.isRadar) if (widget.isRadar)
DidvanIconButton( DidvanIconButton(
@ -233,7 +233,19 @@ class _FloatingNavigationBarState extends State<FloatingNavigationBar> {
Navigator.of(context).pop(); Navigator.of(context).pop();
Navigator.of(context).pushNamed( Navigator.of(context).pushNamed(
Routes.direct, Routes.direct,
arguments: {}, arguments: {
'radarAttachment': RadarAttachment(
id: widget.item.id,
title: widget.item.title,
description: widget.item.contents.first.text,
timeToRead: widget.item.timeToRead,
image: widget.item.image,
forManagers: widget.item.forManagers,
categories: widget.item.categories,
createdAt: widget.item.createdAt,
),
'type': 'پشتیبانی'
},
); );
}, },
icon: DidvanIcons.description_regular, icon: DidvanIcons.description_regular,

View File

@ -79,6 +79,7 @@ class NewsOverview extends StatelessWidget {
], ],
), ),
BookmarkButton( BookmarkButton(
gestureSize: 24,
value: news.marked, value: news.marked,
onMarkChanged: (value) => onMarkChanged(news.id, value), onMarkChanged: (value) => onMarkChanged(news.id, value),
askForConfirmation: hasUnmarkConfirmation, askForConfirmation: hasUnmarkConfirmation,

View File

@ -1,87 +1,90 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/overview_data.dart'; import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/news.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/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/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/card.dart';
import 'package:didvan/views/widgets/didvan/divider.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/didvan/text.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class PodcastOverview extends StatelessWidget { class PodcastOverview extends StatelessWidget {
final OverviewData news; final OverviewData podcast;
final NewsRequestArgs? newsRequestArgs;
final void Function(int id, bool value) onMarkChanged; final void Function(int id, bool value) onMarkChanged;
final bool hasUnmarkConfirmation; final StudioRequestArgs? studioRequestArgs;
const PodcastOverview({ const PodcastOverview({
Key? key, Key? key,
required this.news, required this.podcast,
required this.onMarkChanged, required this.onMarkChanged,
this.newsRequestArgs, this.studioRequestArgs,
this.hasUnmarkConfirmation = false,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return DidvanCard( return DidvanCard(
onTap: () => Navigator.of(context).pushNamed( onTap: () {
Routes.newsDetails, context
arguments: { .read<StudioDetailsState>()
'onMarkChanged': onMarkChanged, .getStudioDetails(podcast.id, args: studioRequestArgs);
'id': news.id,
'args': newsRequestArgs,
'hasUnmarkConfirmation': hasUnmarkConfirmation,
}, },
),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
children: [ children: [
SkeletonImage( SkeletonImage(
imageUrl: news.image, imageUrl: podcast.image,
width: 64, width: 64,
height: 64, height: 64,
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Expanded( Expanded(
child: SizedBox( child: Column(
height: 64, crossAxisAlignment: CrossAxisAlignment.start,
child: DidvanText( children: [
news.title, DidvanText(
podcast.title,
style: Theme.of(context).textTheme.bodyText1, style: Theme.of(context).textTheme.bodyText1,
), ),
const SizedBox(height: 4),
DidvanText(
DateTimeUtils.momentGenerator(podcast.createdAt),
style: Theme.of(context).textTheme.overline,
color: Theme.of(context).colorScheme.caption,
),
],
), ),
), ),
], ],
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
DidvanText( DidvanText(
news.description, podcast.description,
maxLines: 3, maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
const DidvanDivider(verticalPadding: 8), const DidvanDivider(verticalPadding: 8),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [ children: [
Row( DurationWidget(duration: podcast.duration!),
children: [ const Spacer(),
DidvanText( DidvanIconButton(
news.reference!, gestureSize: 28,
style: Theme.of(context).textTheme.caption, icon: DidvanIcons.download_regular,
), onPressed: () {},
DidvanText(
' - ' + DateTimeUtils.momentGenerator(news.createdAt),
style: Theme.of(context).textTheme.caption,
),
],
), ),
const SizedBox(width: 16),
BookmarkButton( BookmarkButton(
value: news.marked, gestureSize: 24,
onMarkChanged: (value) => onMarkChanged(news.id, value), value: podcast.marked,
askForConfirmation: hasUnmarkConfirmation, onMarkChanged: (value) => onMarkChanged(podcast.id, value),
), ),
], ],
), ),
@ -95,7 +98,7 @@ class PodcastOverview extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
Row( Row(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
const ShimmerPlaceholder(height: 64, width: 64), const ShimmerPlaceholder(height: 64, width: 64),
const SizedBox(width: 8), const SizedBox(width: 8),
@ -109,7 +112,7 @@ class PodcastOverview extends StatelessWidget {
), ),
], ],
), ),
const SizedBox(height: 12), const SizedBox(height: 16),
const ShimmerPlaceholder( const ShimmerPlaceholder(
height: 16, height: 16,
width: double.infinity, width: double.infinity,
@ -117,19 +120,21 @@ class PodcastOverview extends StatelessWidget {
const SizedBox(height: 8), const SizedBox(height: 8),
const ShimmerPlaceholder( const ShimmerPlaceholder(
height: 16, height: 16,
width: double.infinity, width: 200,
),
const SizedBox(height: 8),
const ShimmerPlaceholder(
height: 16,
width: 100,
), ),
const SizedBox(height: 4),
const DidvanDivider(verticalPadding: 8), const DidvanDivider(verticalPadding: 8),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
children: const [ ShimmerPlaceholder(
ShimmerPlaceholder(height: 12, width: 150), height: 36,
ShimmerPlaceholder(height: 24, width: 24), width: 92,
borderRadius: BorderRadius.circular(5),
),
const Spacer(),
const ShimmerPlaceholder(width: 24, height: 24),
const SizedBox(width: 16),
const ShimmerPlaceholder(width: 24, height: 24),
], ],
), ),
], ],

View File

@ -97,11 +97,13 @@ class RadarOverview extends StatelessWidget {
DidvanText( DidvanText(
radar.description, radar.description,
maxLines: 3, maxLines: 3,
overflow: TextOverflow.ellipsis,
), ),
const DidvanDivider(), const DidvanDivider(),
Row( Row(
children: [ children: [
BookmarkButton( BookmarkButton(
gestureSize: 24,
value: radar.marked, value: radar.marked,
onMarkChanged: (value) => onMarkChanged(radar.id, value), onMarkChanged: (value) => onMarkChanged(radar.id, value),
askForConfirmation: hasUnmarkConfirmation, askForConfirmation: hasUnmarkConfirmation,

View File

@ -0,0 +1,161 @@
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/overview_data.dart';
import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/home/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 bool hasUnmarkConfirmation;
final StudioRequestArgs? studioRequestArgs;
const VideoOverview({
Key? key,
required this.video,
required this.onCommentsChanged,
required this.onMarkChanged,
required this.hasUnmarkConfirmation,
this.studioRequestArgs,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return DidvanCard(
onTap: () => Navigator.of(context).pushNamed(
Routes.studioDetails,
arguments: {
'onMarkChanged': onMarkChanged,
'onCommentsChanged': onCommentsChanged,
'id': video.id,
'args': studioRequestArgs,
'hasUnmarkConfirmation': hasUnmarkConfirmation,
'isVideo': true,
'state': context.read<StudioDetailsState>(),
},
),
child: Row(
children: [
Stack(
children: [
SkeletonImage(
imageUrl: video.image,
height: 108,
width: 108,
),
Positioned.fill(
child: Center(
child: Container(
height: 28,
width: 28,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context)
.colorScheme
.secondary
.withOpacity(0.7),
),
child: Icon(
DidvanIcons.play_solid,
color: Theme.of(context).colorScheme.white,
),
),
),
),
],
),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DidvanText(
video.title,
style: Theme.of(context).textTheme.bodyText1,
),
const SizedBox(height: 4),
Row(
children: [
const Icon(
DidvanIcons.calendar_day_regular,
size: 16,
),
const SizedBox(width: 4),
DidvanText(
DateTimeUtils.momentGenerator(video.createdAt),
style: Theme.of(context).textTheme.overline,
color: Theme.of(context).colorScheme.caption,
),
],
),
const DidvanDivider(verticalPadding: 8),
Row(
children: [
DurationWidget(duration: video.duration!),
const Spacer(),
DidvanIconButton(
gestureSize: 28,
icon: DidvanIcons.download_regular,
onPressed: () {},
),
const SizedBox(width: 16),
BookmarkButton(
gestureSize: 24,
value: video.marked,
onMarkChanged: (value) => onMarkChanged(video.id, value),
),
],
),
],
),
),
],
),
);
}
static Widget get placeHolder => DidvanCard(
child: Row(
children: [
const ShimmerPlaceholder(height: 108, width: 108),
const SizedBox(width: 8),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
const ShimmerPlaceholder(height: 20),
const SizedBox(height: 8),
const ShimmerPlaceholder(width: 100, height: 16),
const DidvanDivider(verticalPadding: 10),
Row(
children: [
ShimmerPlaceholder(
height: 36,
width: 92,
borderRadius: BorderRadius.circular(5),
),
const Spacer(),
const ShimmerPlaceholder(width: 24, height: 24),
const SizedBox(width: 16),
const ShimmerPlaceholder(width: 24, height: 24),
],
),
],
),
),
],
),
);
}

View File

@ -110,7 +110,15 @@ class _SplashState extends State<Splash> {
if (token != null) { if (token != null) {
log(token); log(token);
RequestService.token = token; RequestService.token = token;
await userProvider.getUserInfo(); final result = await userProvider.getUserInfo();
if (!result) {
StorageService.delete(key: 'token');
Navigator.of(context).pushNamedAndRemoveUntil(
Routes.splash,
(_) => false,
);
return;
}
await ServerDataProvider.getData(); await ServerDataProvider.getData();
} }
Navigator.of(context).pushReplacementNamed( Navigator.of(context).pushReplacementNamed(

View File

@ -18,11 +18,10 @@ class DidvanAppBar extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Container( return Container(
height: kToolbarHeight + MediaQuery.of(context).padding.top, height: appBarData.isSmall ? 56 : 72,
width: MediaQuery.of(context).size.width, width: MediaQuery.of(context).size.width,
padding: const EdgeInsets.only(right: 4, left: 20), padding: const EdgeInsets.only(right: 4, left: 20),
decoration: BoxDecoration( decoration: BoxDecoration(
color: backgroundColor,
border: hasBorder border: hasBorder
? Border( ? Border(
bottom: BorderSide( bottom: BorderSide(
@ -53,6 +52,7 @@ class DidvanAppBar extends StatelessWidget {
appBarData.title!, appBarData.title!,
style: Theme.of(context).textTheme.headline3, style: Theme.of(context).textTheme.headline3,
color: Theme.of(context).colorScheme.title, color: Theme.of(context).colorScheme.title,
overflow: TextOverflow.ellipsis,
), ),
if (appBarData.subtitle != null) if (appBarData.subtitle != null)
DidvanText( DidvanText(

View File

@ -3,7 +3,7 @@ import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/home/widgets/multitype_overview.dart'; import 'package:didvan/views/home/widgets/overview/multitype.dart';
import 'package:didvan/views/home/widgets/tag_item.dart'; import 'package:didvan/views/home/widgets/tag_item.dart';
import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/didvan/card.dart'; import 'package:didvan/views/widgets/didvan/card.dart';
@ -14,6 +14,7 @@ import 'package:didvan/views/widgets/item_title.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_html/flutter_html.dart'; import 'package:flutter_html/flutter_html.dart';
import 'package:url_launcher/url_launcher.dart';
class DidvanPageView extends StatefulWidget { class DidvanPageView extends StatefulWidget {
final List items; final List items;
@ -183,9 +184,11 @@ class _DidvanPageViewState extends State<DidvanPageView> {
if (content.text != null) { if (content.text != null) {
return Html( return Html(
data: content.text, data: content.text,
onAnchorTap: (href, context, map, element) => launch(href!),
style: { style: {
'*': Style( '*': Style(
direction: TextDirection.rtl, direction: TextDirection.rtl,
textAlign: TextAlign.right,
lineHeight: LineHeight.percent(135), lineHeight: LineHeight.percent(135),
margin: EdgeInsets.zero, margin: EdgeInsets.zero,
padding: EdgeInsets.zero, padding: EdgeInsets.zero,

View File

@ -5,7 +5,7 @@ import 'package:flutter/material.dart';
class DidvanScaffold extends StatefulWidget { class DidvanScaffold extends StatefulWidget {
final List<Widget>? slivers; final List<Widget>? slivers;
final List<Widget>? children; final List<Widget>? children;
final AppBarData appBarData; final AppBarData? appBarData;
final EdgeInsets padding; final EdgeInsets padding;
final Color? backgroundColor; final Color? backgroundColor;
final bool reverse; final bool reverse;
@ -40,18 +40,15 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
controller: _scrollController, controller: _scrollController,
reverse: widget.reverse, reverse: widget.reverse,
slivers: [ slivers: [
if (!widget.reverse) if (!widget.reverse && widget.appBarData != null)
SliverAppBar( SliverAppBar(
toolbarHeight: kToolbarHeight, toolbarHeight: (widget.appBarData!.isSmall ? 56 : 72) -
statusBarHeight,
backgroundColor: widget.backgroundColor ?? backgroundColor: widget.backgroundColor ??
Theme.of(context).colorScheme.background, Theme.of(context).colorScheme.background,
automaticallyImplyLeading: false, automaticallyImplyLeading: false,
pinned: true, pinned: true,
flexibleSpace: DidvanAppBar(appBarData: widget.appBarData), flexibleSpace: DidvanAppBar(appBarData: widget.appBarData!),
),
if (!widget.reverse)
const SliverToBoxAdapter(
child: SizedBox(height: 16),
), ),
if (widget.children != null) if (widget.children != null)
SliverPadding( SliverPadding(
@ -79,9 +76,9 @@ class _DidvanScaffoldState extends State<DidvanScaffold> {
), ),
], ],
), ),
if (widget.reverse) if (widget.reverse && widget.appBarData != null)
_AppBar( _AppBar(
appBarData: widget.appBarData, appBarData: widget.appBarData!,
scrollController: _scrollController, scrollController: _scrollController,
), ),
], ],

View File

@ -34,7 +34,9 @@ class DidvanText extends StatelessWidget {
fontWeight: fontWeight, fontWeight: fontWeight,
fontSize: fontSize, fontSize: fontSize,
)).copyWith( )).copyWith(
fontFamily: isEnglishFont ? DesignConfig.fontFamily : null, fontFamily: isEnglishFont
? DesignConfig.fontFamily.replaceAll('-FA', '')
: null,
height: 1.7, height: 1.7,
), ),
overflow: overflow, overflow: overflow,

View File

@ -127,6 +127,13 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "1.0.4+1" version: "1.0.4+1"
expandable_bottom_sheet:
dependency: "direct main"
description:
name: expandable_bottom_sheet
url: "https://pub.dartlang.org"
source: hosted
version: "1.1.1+1"
fake_async: fake_async:
dependency: transitive dependency: transitive
description: description:
@ -566,7 +573,7 @@ packages:
name: plugin_platform_interface name: plugin_platform_interface
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.0.2" version: "2.1.2"
process: process:
dependency: transitive dependency: transitive
description: description:
@ -782,6 +789,34 @@ packages:
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "2.1.1" version: "2.1.1"
webview_flutter:
dependency: "direct main"
description:
name: webview_flutter
url: "https://pub.dartlang.org"
source: hosted
version: "3.0.1"
webview_flutter_android:
dependency: transitive
description:
name: webview_flutter_android
url: "https://pub.dartlang.org"
source: hosted
version: "2.8.3"
webview_flutter_platform_interface:
dependency: transitive
description:
name: webview_flutter_platform_interface
url: "https://pub.dartlang.org"
source: hosted
version: "1.8.1"
webview_flutter_wkwebview:
dependency: transitive
description:
name: webview_flutter_wkwebview
url: "https://pub.dartlang.org"
source: hosted
version: "2.7.1"
win32: win32:
dependency: transitive dependency: transitive
description: description:

View File

@ -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. # In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
# Read more about iOS versioning at # Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 1.1.0+1 version: 1.2.0+1
environment: environment:
sdk: ">=2.12.0 <3.0.0" sdk: ">=2.12.0 <3.0.0"
@ -62,6 +62,8 @@ dependencies:
image_cropper: ^1.5.0 image_cropper: ^1.5.0
firebase_messaging: ^11.2.8 firebase_messaging: ^11.2.8
firebase_core: ^1.13.1 firebase_core: ^1.13.1
webview_flutter: ^3.0.1
expandable_bottom_sheet: ^1.1.1+1
dev_dependencies: dev_dependencies: