last version 3.3.0 houshvan

This commit is contained in:
OkaykOrhmn 2024-09-16 17:05:18 +03:30
parent 9ea80f21fb
commit 1708bcffc8
37 changed files with 582 additions and 462 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 333 KiB

View File

@ -42,6 +42,7 @@ class Assets {
static String get studioLogo => '$_baseLogosPath/studio-$_themeSuffix.svg';
static String loadingAnimation = '$_baseAnimationsPath/loading.gif';
static String bookmarkAnimation = '$_baseAnimationsPath/bookmark.gif';
static String get businessCategoryIcon =>
'$_baseCategoriesPath/business-$_themeSuffix.svg';

View File

@ -61,28 +61,24 @@ Future<void> _backgroundCallbackHomeWidget(Uri? uri) async {
}
void main() async {
await runZonedGuarded(() async {
WidgetsFlutterBinding.ensureInitialized();
WidgetsFlutterBinding.ensureInitialized();
try {
if (!kIsWeb) {
HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget);
HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget);
await NotificationService.initializeNotification();
}
FirebaseMessaging.onBackgroundMessage(_initPushNotification);
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
await FirebaseApi().initNotification();
} catch (e) {
debugPrint;
try {
if (!kIsWeb) {
HomeWidget.registerBackgroundCallback(_backgroundCallbackHomeWidget);
HomeWidget.registerInteractivityCallback(_backgroundCallbackHomeWidget);
await NotificationService.initializeNotification();
}
runApp(const Didvan());
}, (error, stack) async {
error.printError();
});
FirebaseMessaging.onBackgroundMessage(_initPushNotification);
await Firebase.initializeApp(
options: DefaultFirebaseOptions.currentPlatform);
await FirebaseApi().initNotification();
} catch (e) {
debugPrint;
}
runApp(const Didvan());
}
class Didvan extends StatefulWidget {

View File

@ -23,6 +23,7 @@ class Content {
final String createdAt;
bool marked;
bool liked;
int likes;
final List<Tag> tags;
@ -30,6 +31,7 @@ class Content {
{required this.id,
required this.marked,
required this.liked,
required this.likes,
required this.createdAt,
required this.title,
required this.image,
@ -41,6 +43,7 @@ class Content {
id: json['id'],
marked: json['marked'],
liked: json['liked'],
likes: json['likes'],
createdAt: json['createdAt'],
title: json['title'],
image: json['image'],

View File

@ -10,6 +10,7 @@ class NewsDetailsData {
final String createdAt;
bool marked;
bool liked;
int likes;
int comments;
final int order;
final List<Tag> tags;
@ -25,6 +26,7 @@ class NewsDetailsData {
required this.createdAt,
required this.marked,
required this.liked,
required this.likes,
required this.comments,
required this.tags,
required this.contents,
@ -41,6 +43,7 @@ class NewsDetailsData {
createdAt: json['createdAt'],
marked: json['marked'],
liked: json['liked'] ?? false,
likes: json['likes'],
comments: json['comments'],
order: json['order'],
tags: List<Tag>.from(json['tags'].map((tag) => Tag.fromJson(tag))),
@ -59,6 +62,7 @@ class NewsDetailsData {
'createdAt': createdAt,
'marked': marked,
'liked': liked,
'likes': likes,
'comments': comments,
'tags': tags.map((e) => e.toJson()).toList(),
'contents': contents.map((e) => e.toJson()).toList(),

View File

@ -20,6 +20,7 @@ class OverviewData {
int comments;
bool marked;
bool liked;
int likes;
final List<CategoryData>? categories;
OverviewData({
@ -31,6 +32,7 @@ class OverviewData {
required this.type,
required this.marked,
required this.liked,
required this.likes,
required this.comments,
required this.forManagers,
this.category,
@ -87,6 +89,7 @@ class OverviewData {
type: json['type'] ?? '',
marked: json['marked'] ?? true,
liked: json['liked'] ?? true,
likes: json['likes'] ?? 0,
link: json['link'],
iframe: json['iframe'],
categories: json['categories'] != null

View File

@ -4,8 +4,8 @@ class ActionSheetData {
final Widget content;
final String? confrimTitle;
final String? dismissTitle;
final VoidCallback? onConfirmed;
final VoidCallback? onDismissed;
final Function()? onConfirmed;
final Function()? onDismissed;
final String? title;
final bool hasPadding;
final IconData? titleIcon;

View File

@ -132,7 +132,9 @@ class RouteGenerator {
ChangeNotifierProvider<NewStatisticState>(
create: (context) => NewStatisticState())
],
child: const Home(),
child: Home(
showDialogs: settings.arguments as bool?,
),
),
);
case Routes.editProfile:

View File

@ -20,7 +20,7 @@ class FirebaseApi {
e.printError();
}
await _firebaseMessaging.requestPermission(
_firebaseMessaging.requestPermission(
alert: true,
announcement: true,
badge: true,

View File

@ -169,8 +169,8 @@ class ActionSheetUtils {
child: DidvanButton(
style: ButtonStyleMode.primary,
onPressed: () {
Navigator.of(context).pop();
data.onConfirmed?.call();
pop();
},
title: data.confrimTitle ?? 'تایید',
),
@ -251,7 +251,10 @@ class ActionSheetUtils {
if (data.hasDismissButton)
Expanded(
child: DidvanButton(
onPressed: data.onDismissed ?? () => pop(),
onPressed: () {
data.onDismissed?.call();
pop();
},
title: data.dismissTitle ?? 'بازگشت',
style: ButtonStyleMode.flat,
),

View File

@ -58,7 +58,7 @@ class _AiState extends State<Ai> {
Column(
children: [
const SizedBox(
height: 12,
height: 24,
),
Icon(
DidvanIcons.ai_solid,
@ -134,16 +134,6 @@ class _AiState extends State<Ai> {
const SizedBox(
width: 8,
),
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.border),
child: const Icon(
DidvanIcons.mic_regular,
),
),
Expanded(
child: Padding(
padding: const EdgeInsets.symmetric(

View File

@ -409,9 +409,13 @@ class _AiChatPageState extends State<AiChatPage> {
},
);
}),
SpinKitThreeBounce(
color: Theme.of(context).colorScheme.primary,
size: 18,
Padding(
padding:
const EdgeInsets.symmetric(vertical: 8.0),
child: SpinKitThreeBounce(
color: Theme.of(context).colorScheme.primary,
size: 18,
),
)
],
)

View File

@ -34,11 +34,6 @@ class AiChatState extends CoreProvier {
FilesModel? file;
TextEditingController message = TextEditingController();
@override
void dispose() {
super.dispose();
}
Future<void> _scrolledEnd() async {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await scrollController.animateTo(
@ -166,12 +161,8 @@ class AiChatState extends CoreProvier {
file = null;
update();
final stream = res
.transform(utf8.decoder)
.transform(const LineSplitter()); // <--- Add this line
final r = stream.listen((str) {
// var str = utf8.decode(value);
final r = res.listen((value) async {
var str = utf8.decode(value);
if (!kIsWeb) {
if (str.contains('{{{')) {
dataMessgae += str;
@ -197,6 +188,7 @@ class AiChatState extends CoreProvier {
}
}
messageOnstream.value = Stream.value(responseMessgae);
print("responseMessgae: $str");
// update();
});

View File

@ -27,7 +27,6 @@ import 'package:get/get.dart';
import 'package:image_cropper/image_cropper.dart';
import 'package:image_picker/image_picker.dart';
import 'package:path_provider/path_provider.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
import 'package:record/record.dart';
@ -161,23 +160,25 @@ class _AiMessageBarState extends State<AiMessageBar> {
FilePickerResult? result =
await MediaService.pickPdfFile();
if (result != null) {
// if (kIsWeb) {
// Uint8List bytes = result.files.first
// .bytes!; // Access the bytes property
if (kIsWeb) {
Uint8List? bytes = result.files.first
.bytes; // Access the bytes property
String? name = result.files.first.name;
// File file = File.fromRawPath(bytes);
// state.file = FilesModel(file.path,
// name: result.files.first.name,
// bytes: bytes,
// audio: false,
// image: false);
// print(result.files.first.name);
// } else {
state.file = FilesModel(
result.files.single.path!,
// Store bytes and file name directly in your state or model
state.file = FilesModel(
'', // No need for a file path on web
name: name,
bytes: bytes,
audio: false,
image: false);
// }
image: false,
);
} else {
state.file = FilesModel(
result.files.single.path!,
audio: false,
image: false);
}
openAttach = false;
}
@ -243,44 +244,45 @@ class _AiMessageBarState extends State<AiMessageBar> {
);
},
),
if (historyState.bot!.attachmentType!
.contains('audio'))
attachBtn(
title: "صوت",
icon: CupertinoIcons.music_note_2,
color: Colors.indigoAccent,
click: () async {
MediaService.onLoadingPickFile(context);
if (!kIsWeb || !Platform.isIOS)
if (historyState.bot!.attachmentType!
.contains('audio'))
attachBtn(
title: "صوت",
icon: CupertinoIcons.music_note_2,
color: Colors.indigoAccent,
click: () async {
MediaService.onLoadingPickFile(context);
FilePickerResult? result =
await MediaService.pickAudioFile();
if (result != null) {
if (kIsWeb) {
Uint8List bytes = result.files.first
.bytes!; // Access the bytes property
FilePickerResult? result =
await MediaService.pickAudioFile();
if (result != null) {
if (kIsWeb) {
Uint8List bytes = result.files.first
.bytes!; // Access the bytes property
File file = File.fromRawPath(bytes);
File file = File.fromRawPath(bytes);
state.file = FilesModel(file.path,
name: result.files.first.name,
bytes: bytes,
audio: true,
image: false);
print(result.files.first.name);
} else {
state.file = FilesModel(
result.files.single.path!,
audio: true,
image: false);
state.file = FilesModel(file.path,
name: result.files.first.name,
bytes: bytes,
audio: true,
image: false);
print(result.files.first.name);
} else {
state.file = FilesModel(
result.files.single.path!,
audio: true,
image: false);
}
openAttach = false;
}
openAttach = false;
}
await Future.delayed(
Duration.zero,
() => ActionSheetUtils(context).pop(),
);
},
)
await Future.delayed(
Duration.zero,
() => ActionSheetUtils(context).pop(),
);
},
)
],
),
)),
@ -319,7 +321,8 @@ class _AiMessageBarState extends State<AiMessageBar> {
Padding(
padding: const EdgeInsets.only(
bottom: 8.0),
child: (snapshot.hasData &&
child: (!kIsWeb &&
snapshot.hasData &&
snapshot.data! !=
RecordState.stop)
? MessageBarBtn(
@ -342,7 +345,9 @@ class _AiMessageBarState extends State<AiMessageBar> {
state.update();
},
)
: widget.bot.attachmentType!
: (!kIsWeb || !Platform.isIOS) &&
widget.bot
.attachmentType!
.contains(
'audio') &&
value.isEmpty &&
@ -523,15 +528,27 @@ class _AiMessageBarState extends State<AiMessageBar> {
mainAxisAlignment:
MainAxisAlignment
.center,
children: List
.generate(
3,
(index) =>
SpinKitWave(
color: Theme.of(context).colorScheme.primary.withOpacity(0.4),
size: 32,
itemCount: 10,
))),
children: List.generate(
4,
(index) => snapshot.data! == RecordState.pause
? Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
8,
(index) => Padding(
padding: const EdgeInsets.symmetric(horizontal: 1.0, vertical: 12),
child: Container(
width: 3,
height: 8,
decoration: BoxDecoration(color: Theme.of(context).colorScheme.primary.withOpacity(0.4)),
),
)),
)
: SpinKitWave(
color: Theme.of(context).colorScheme.primary.withOpacity(0.4),
size: 32,
itemCount: 10,
))),
),
ValueListenableBuilder<
int>(

View File

@ -11,6 +11,7 @@ import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:get/get.dart';
import 'package:just_audio/just_audio.dart';
@ -35,6 +36,7 @@ class _AudioWaveState extends State<AudioWave> {
double currentPosition = 0;
bool loading = true;
bool faile = false;
bool onChanging = false;
@override
void initState() {
@ -56,7 +58,7 @@ class _AudioWaveState extends State<AudioWave> {
void setRandoms() {
for (var i = 0; i < itemCount; i++) {
randoms.value.add(0);
randomsDisable.value.add(2 + Random().nextDouble() * (42 - 2));
randomsDisable.value.add(1 + Random().nextDouble() * (38 - 1));
}
}
@ -94,7 +96,7 @@ class _AudioWaveState extends State<AudioWave> {
Future<void> listeners() async {
audioPlayer.positionStream.listen((position) async {
if (randomsDisable.value.isEmpty) return;
if (randomsDisable.value.isEmpty || onChanging) return;
try {
for (var i = 0; i < itemCount; i++) {
@ -219,12 +221,9 @@ class _AudioWaveState extends State<AudioWave> {
.primary
.withOpacity(0.4)),
if (totalDuration != Duration.zero)
Opacity(
opacity: 0,
child: Container(
width: 12,
color: Colors.transparent
.withOpacity(1),
Positioned.fill(
child: Opacity(
opacity: 0,
child: Theme(
data: Theme.of(context)
.copyWith(
@ -252,22 +251,22 @@ class _AudioWaveState extends State<AudioWave> {
// audioPlayer.pause();
},
onChanged: (value) {
for (var i = 0;
i < itemCount;
i++) {
if (i <
((value * 40) /
totalDuration
.inMilliseconds)) {
final ran =
randomsDisable
.value[i];
randoms.value[i] =
ran;
} else {
randoms.value[i] = 0;
}
}
// for (var i = 0;
// i < itemCount;
// i++) {
// if (i <
// ((value * 40) /
// totalDuration
// .inMilliseconds)) {
// final ran =
// randomsDisable
// .value[i];
// randoms.value[i] =
// ran;
// } else {
// randoms.value[i] = 0;
// }
// }
setState(() {
currentPosition = value;
});

View File

@ -80,78 +80,11 @@ class _PasswordInputState extends State<PasswordInput> {
await ServerDataProvider.getData();
if (mounted) {
Future.delayed(Duration.zero,
() => Navigator.of(context).pushReplacementNamed(Routes.home));
await Future.delayed(
Duration.zero,
() => Navigator.of(context)
.pushReplacementNamed(Routes.home, arguments: true));
}
_showResetPasswordDialog();
}
}
void _showResetPasswordDialog() {
ActionSheetUtils(context).openDialog(
data: ActionSheetData(
content: const DidvanText(
'خوش آمدید!\nبرای امنیت بیشتر، رمز عبور خود را تغییر دهید.',
),
onConfirmed: () => Navigator.of(navigatorKey.currentContext!).pushNamed(
Routes.authenticaion,
arguments: true,
),
isBackgroundDropBlur: false,
confrimTitle: 'تغییر رمز عبور',
onDismissed: Navigator.of(navigatorKey.currentContext!).pop,
dismissTitle: 'بعدا',
),
);
_showCustomizeDialog();
}
void _showCustomizeDialog() {
ActionSheetUtils(context).openDialog(
data: ActionSheetData(
backgroundColor: Theme.of(context).colorScheme.background,
isBackgroundDropBlur: true,
content: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWrapper(
onPressed: Navigator.of(navigatorKey.currentContext!).pop,
child: const Icon(
DidvanIcons.close_solid,
size: 24,
),
),
DidvanText(
'شخصی سازی محتوا',
style: Theme.of(context).textTheme.displaySmall,
color: Theme.of(context).colorScheme.text,
),
const InkWrapper(
child: Icon(
DidvanIcons.close_regular,
size: 24,
color: Colors.transparent,
),
),
],
),
const SizedBox(
height: 12,
),
const DidvanText(
"کاربر گرامی\nلطفا جهت شخصی‌سازی و استفاده بهتر از برنامه، دسته‌بندی‌های مورد علاقه خود و زمان دریافت اعلانات را انتخاب نمایید.")
],
),
hasDismissButton: false,
onConfirmed: () => Navigator.of(navigatorKey.currentContext!).pushNamed(
Routes.favouritesStep,
arguments: {"toTimer": true},
),
confrimTitle: 'تایید',
),
);
}
}

View File

@ -51,7 +51,7 @@ class _UsernameInputState extends State<UsernameInput> {
Padding(
padding: const EdgeInsets.all(8.0),
child: DidvanText(
'نام کاربری می‌تواند شامل کاراکترهای کوچک و بزرگ انگلیسی و اعداد باشد.',
'نام کاربری می‌تواند شامل کاراکترهای انگلیسی و اعداد باشد.',
style: Theme.of(context).textTheme.labelSmall,
),
),

View File

@ -74,6 +74,7 @@ class _HashtagState extends State<Hashtag> {
liked: item.liked,
onLikedChanged: (_, value, __) =>
_changeLiked(item.id, value, 'banner'),
likes: item.likes,
);
case 'radar':

View File

@ -1,11 +1,14 @@
import 'dart:async';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/view/app_bar_data.dart';
import 'package:didvan/routes/routes.dart';
import 'package:didvan/views/home/bookmarks/bookmark_state.dart';
import 'package:didvan/views/widgets/didvan/scaffold.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/menu_item.dart';
import 'package:didvan/views/widgets/overview/multitype.dart';
// import 'package:didvan/views/widgets/search_field.dart';
@ -124,7 +127,9 @@ class _BookmarksState extends State<Bookmarks> {
),
),
SliverPadding(
padding: const EdgeInsets.symmetric(horizontal: 16),
padding: const EdgeInsets.symmetric(
horizontal: 16,
),
sliver: SliverStateHandler<BookmarksState>(
state: state,
centerEmptyState: state.searching,
@ -144,9 +149,24 @@ class _BookmarksState extends State<Bookmarks> {
},
placeholder: MultitypeOverview.placeholder,
itemPadding: const EdgeInsets.only(bottom: 8),
paddingEmptyState: 0,
emptyState: state.searching
? EmptyResult(onNewSearch: _focuseNode.requestFocus)
: const EmptyList(),
: Column(
children: [
DidvanText(
'در قسمت رصدخانه من، تمامی محتواهایی که در قسمت‌های مختلف سوپراپلیکیشن دیدوان، بوکمارک (نشان‌دار) کرده‌اید، به تفکیک نمایش داده می‌شوند. هم‌چنین امکان درج یادداشت شخصی بصورت ضمیمه برای هر محتوا وجود دارد.',
fontSize: 14,
color: Theme.of(context).colorScheme.title,
textAlign: TextAlign.justify,
),
Image.asset(
Assets.bookmarkAnimation,
width: MediaQuery.sizeOf(context).width,
height: 180,
),
],
),
enableEmptyState: state.bookmarks.isEmpty,
childCount:
state.bookmarks.length + (state.page != state.lastPage ? 1 : 0),

View File

@ -27,6 +27,7 @@ import 'package:didvan/views/home/new_statistic/new_statistic.dart';
import 'package:didvan/views/home/search/search.dart';
import 'package:didvan/views/widgets/didvan/divider.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/ink_wrapper.dart';
import 'package:didvan/views/widgets/logo_app_bar.dart';
import 'package:didvan/views/widgets/didvan/bnb.dart';
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
@ -35,13 +36,15 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:provider/provider.dart';
import '../../services/app_home_widget/home_widget_repository.dart';
final GlobalKey<ScaffoldState> homeScaffKey = GlobalKey<ScaffoldState>();
class Home extends StatefulWidget {
const Home({Key? key}) : super(key: key);
final bool? showDialogs;
const Home({Key? key, this.showDialogs}) : super(key: key);
@override
State<Home> createState() => _HomeState();
@ -51,8 +54,90 @@ class _HomeState extends State<Home>
with SingleTickerProviderStateMixin, WidgetsBindingObserver {
late final TabController _tabController;
Future<void> _showDialog(BuildContext context) async {
WidgetsBinding.instance?.addPostFrameCallback((_) {
ActionSheetUtils(context)
.openDialog(
data: ActionSheetData(
content: const DidvanText(
'خوش آمدید!\nبرای امنیت بیشتر، رمز عبور خود را تغییر دهید.',
),
onConfirmed: () {
Future.delayed(
Duration.zero,
() => Navigator.of(context)
.pushNamed(Routes.authenticaion, arguments: true),
);
},
isBackgroundDropBlur: false,
confrimTitle: 'تغییر رمز عبور',
dismissTitle: 'بعدا',
),
)
.then((value) => ActionSheetUtils(context).openDialog(
data: ActionSheetData(
backgroundColor: Theme.of(context).colorScheme.background,
isBackgroundDropBlur: true,
content: Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
InkWrapper(
onPressed: () {
Future.delayed(
Duration.zero,
() => Navigator.of(context).pop(),
);
},
child: const Icon(
DidvanIcons.close_solid,
size: 24,
),
),
DidvanText(
'شخصی سازی محتوا',
style: Theme.of(context).textTheme.displaySmall,
color: Theme.of(context).colorScheme.text,
),
const InkWrapper(
child: Icon(
DidvanIcons.close_regular,
size: 24,
color: Colors.transparent,
),
),
],
),
const SizedBox(
height: 12,
),
const DidvanText(
"کاربر گرامی\nلطفا جهت شخصی‌سازی و استفاده بهتر از برنامه، دسته‌بندی‌های مورد علاقه خود و زمان دریافت اعلانات را انتخاب نمایید.")
],
),
// hasDismissButton: false,
onConfirmed: () {
Future.delayed(
Duration.zero,
() =>
Navigator.of(navigatorKey.currentContext!).pushNamed(
Routes.favouritesStep,
arguments: {"toTimer": true},
),
);
},
confrimTitle: 'تایید',
),
));
});
}
@override
void initState() {
if (widget.showDialogs ?? false) {
_showDialog(context);
}
if (!kIsWeb) {
NotificationService.startListeningNotificationEvents();
}

View File

@ -142,20 +142,21 @@ class _InfographyScreenState extends State<InfographyScreen> {
Wrap(
children: [
for (var i = 0; i < state.categories.length; i++)
SizedBox(
width: (MediaQuery.of(context).size.width - 40) / 2,
child: DidvanCheckbox(
title: state.categories[i].label,
value: state.selectedCats.contains(state.categories[i]),
onChanged: (value) {
if (value) {
state.selectedCats.add(state.categories[i]);
return;
}
state.selectedCats.remove(state.categories[i]);
},
if (state.categories[i].label != 'هوشان')
SizedBox(
width: (MediaQuery.of(context).size.width - 40) / 2,
child: DidvanCheckbox(
title: state.categories[i].label,
value: state.selectedCats.contains(state.categories[i]),
onChanged: (value) {
if (value) {
state.selectedCats.add(state.categories[i]);
return;
}
state.selectedCats.remove(state.categories[i]);
},
),
),
),
],
),
],
@ -232,6 +233,7 @@ class _InfographyScreenState extends State<InfographyScreen> {
liked: state.contents[index].liked,
onLikedChanged: (id, value, _) =>
state.changeLiked(id, value),
likes: state.contents[index].likes,
),
),
),

View File

@ -58,6 +58,7 @@ class InfographyItem extends StatelessWidget {
final int id;
final bool marked;
final bool liked;
final int likes;
final void Function(int id, bool value, bool shouldUpdate) onMarkChanged;
final void Function(int id, bool value, bool shouldUpdate) onLikedChanged;
@ -72,6 +73,7 @@ class InfographyItem extends StatelessWidget {
required this.id,
required this.marked,
required this.liked,
required this.likes,
required this.onLikedChanged});
void _openInteractiveViewer(BuildContext context, String image) {
@ -152,6 +154,17 @@ class InfographyItem extends StatelessWidget {
),
Row(
children: [
LikedButton(
itemId: id,
type: 'infography',
gestureSize: 32,
value: liked,
onMarkChanged: (value) => onLikedChanged(id, value, true),
likes: likes,
),
const SizedBox(
width: 4.0,
),
DidvanIconButton(
gestureSize: 32,
onPressed: () => Navigator.of(context).pushNamed(
@ -164,19 +177,6 @@ class InfographyItem extends StatelessWidget {
),
icon: DidvanIcons.mention_icon,
),
const SizedBox(
width: 8.0,
),
LikedButton(
itemId: id,
type: 'infography',
gestureSize: 32,
value: liked,
onMarkChanged: (value) => onLikedChanged(id, value, true),
),
const SizedBox(
width: 8.0,
),
BookmarkButton(
itemId: id,
type: 'infography',

View File

@ -50,6 +50,7 @@ class SearchPage extends StatelessWidget {
isColapsed: state.selectedCats.length <= 1,
selectedCats: state.selectedCats,
categories: state.categoryFilters,
disableHoushan: true,
onSelected: (id) {
state.selectedCats.clear();
final cat = state.categoryFilters

View File

@ -70,150 +70,166 @@ class _MentionsState extends State<Mentions> {
}
}
_bottomPadding = bottomViewInset;
return Material(
child: Stack(
children: [
DidvanScaffold(
hidePlayer: true,
physics: const BouncingScrollPhysics(),
backgroundColor: Theme.of(context).colorScheme.surface,
appBarData: _isPage
? AppBarData(
hasBack: true,
title: 'فراخوانی‌ها',
subtitle: widget.pageData['title'],
)
: null,
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 92),
showSliversFirst: false,
slivers: [
Consumer<MentionsState>(
builder: (context, state, child) =>
SliverStateHandler<MentionsState>(
onRetry: state.getComments,
state: state,
itemPadding: const EdgeInsets.symmetric(vertical: 16),
childCount: state.comments.length,
placeholder: const _MentionPlaceholder(),
centerEmptyState: _isPage,
enableEmptyState: state.comments.isEmpty,
emptyState: EmptyState(
asset: Assets.emptyChat,
title: 'دوستان خود را فراخوانی کنید'),
builder: (context, state, index) => Mention(
key: ValueKey(
state.comments[index].id.toString() +
state.comments[index].text,
return WillPopScope(
onWillPop: () async {
if (mentionsState.showUsersForMentionsLayout) {
mentionsState.showUsersForMentionsLayout = false;
mentionsState.searchUsers.text = '';
mentionsState.update();
return false;
}
return true;
},
child: Material(
child: Stack(
children: [
DidvanScaffold(
hidePlayer: true,
physics: const BouncingScrollPhysics(),
backgroundColor: Theme.of(context).colorScheme.surface,
appBarData: _isPage
? AppBarData(
hasBack: true,
title: 'فراخوانی‌ها',
subtitle: widget.pageData['title'],
)
: null,
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 92),
showSliversFirst: false,
slivers: [
Consumer<MentionsState>(
builder: (context, state, child) =>
SliverStateHandler<MentionsState>(
onRetry: state.getComments,
state: state,
itemPadding: const EdgeInsets.symmetric(vertical: 16),
childCount: state.comments.length,
placeholder: const _MentionPlaceholder(),
centerEmptyState: _isPage,
enableEmptyState: state.comments.isEmpty,
emptyState: EmptyState(
asset: Assets.emptyChat,
title: 'دوستان خود را فراخوانی کنید'),
builder: (context, state, index) => Mention(
key: ValueKey(
state.comments[index].id.toString() +
state.comments[index].text,
),
focusNode: _focusNode,
comment: state.comments[index],
),
focusNode: _focusNode,
comment: state.comments[index],
),
),
],
),
AnimatedVisibility(
duration: DesignConfig.lowAnimationDuration,
isVisible: mentionsState.showUsersForMentionsLayout,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: Container(
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.focused
.withOpacity(0.5)),
child: DidvanScaffold(
hidePlayer: true,
appBarData: null,
padding: const EdgeInsets.only(
left: 16, right: 16, top: 16, bottom: 92),
backgroundColor: Colors.white.withOpacity(0.0),
showSliversFirst: false,
slivers: [
Consumer<MentionsState>(
builder: (context, state, child) =>
SliverStateHandler<MentionsState>(
onRetry: () {
state.getUsersMention(
context.read<UserProvider>().user.id);
},
state: state,
childCount: state.users.length,
placeholder: const _UsersPlaceholder(),
centerEmptyState: _isPage,
enableEmptyState: state.users.isEmpty,
emptyState: EmptyState(
asset: Assets.emptyBookmark,
title: 'لیست افراد خالی است',
),
builder: (context, state, index) {
return UserMention(
user: state.users[index],
index: index,
);
},
),
),
],
children: [
Row(
children: [
Expanded(
child: TextField(
controller: mentionsState.searchUsers,
focusNode: _focusNodeMention,
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.send,
style: Theme.of(context).textTheme.bodyMedium,
onEditingComplete: () {},
onChanged: (val) => _onChange(mentionsState, val),
decoration: InputDecoration(
icon: const Icon(DidvanIcons.search_regular),
border: InputBorder.none,
hintText: 'نام فرد مورد نظر را بنویسید...',
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: Theme.of(context)
.colorScheme
.disabledText),
),
),
),
DidvanIconButton(
icon: DidvanIcons.close_regular,
onPressed: () {
mentionsState.showUsersForMentionsLayout =
false;
mentionsState.searchUsers.text = '';
mentionsState.update();
}),
],
),
const SizedBox(
height: 4,
),
Divider(
height: 1,
color: Theme.of(context).colorScheme.splash,
),
const SizedBox(
height: 12,
)
],
),
),
),
],
),
AnimatedVisibility(
duration: DesignConfig.lowAnimationDuration,
isVisible: mentionsState.showUsersForMentionsLayout,
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 8.0, sigmaY: 8.0),
child: Container(
decoration: BoxDecoration(
color:
Theme.of(context).colorScheme.focused.withOpacity(0.5)),
child: DidvanScaffold(
appBarData: null,
padding: const EdgeInsets.only(
left: 16, right: 16, top: 16, bottom: 92),
backgroundColor: Colors.white.withOpacity(0.0),
showSliversFirst: false,
slivers: [
Consumer<MentionsState>(
builder: (context, state, child) =>
SliverStateHandler<MentionsState>(
onRetry: () {
state.getUsersMention(
context.read<UserProvider>().user.id);
},
state: state,
childCount: state.users.length,
placeholder: const _UsersPlaceholder(),
centerEmptyState: _isPage,
enableEmptyState: state.users.isEmpty,
emptyState: EmptyState(
asset: Assets.emptyBookmark,
title: 'لیست افراد خالی است',
),
builder: (context, state, index) {
return UserMention(
user: state.users[index],
index: index,
);
},
),
),
],
children: [
Row(
children: [
Expanded(
child: TextField(
controller: mentionsState.searchUsers,
focusNode: _focusNodeMention,
keyboardType: TextInputType.multiline,
textInputAction: TextInputAction.send,
style: Theme.of(context).textTheme.bodyMedium,
onEditingComplete: () {},
onChanged: (val) => _onChange(mentionsState, val),
decoration: InputDecoration(
icon: const Icon(DidvanIcons.search_regular),
border: InputBorder.none,
hintText: 'نام فرد مورد نظر را بنویسید...',
hintStyle: Theme.of(context)
.textTheme
.bodySmall!
.copyWith(
color: Theme.of(context)
.colorScheme
.disabledText),
),
),
),
DidvanIconButton(
icon: DidvanIcons.close_regular,
onPressed: () {
mentionsState.showUsersForMentionsLayout = false;
mentionsState.searchUsers.text = '';
mentionsState.update();
}),
],
),
const SizedBox(
height: 4,
),
Divider(
height: 1,
color: Theme.of(context).colorScheme.splash,
),
const SizedBox(
height: 12,
)
],
),
),
Positioned(
left: 0,
right: 0,
bottom: MediaQuery.of(context).viewInsets.bottom,
child: _MessageBox(
focusNode: _focusNode,
focusNodeMention: _focusNodeMention,
),
),
),
Positioned(
left: 0,
right: 0,
bottom: MediaQuery.of(context).viewInsets.bottom,
child: _MessageBox(
focusNode: _focusNode,
focusNodeMention: _focusNodeMention,
),
),
],
],
),
),
);
}

View File

@ -80,11 +80,9 @@ class _StudioSliderState extends State<StudioSlider> {
horizontal: 8,
),
decoration: BoxDecoration(
color: (state.videosSelected
? Theme.of(context)
.colorScheme
.secondaryDisabled
: Theme.of(context).colorScheme.focused)
color: Theme.of(context)
.colorScheme
.focused
.withOpacity(0.9),
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(10),
@ -103,9 +101,11 @@ class _StudioSliderState extends State<StudioSlider> {
width: 52,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context)
.colorScheme
.secondary
color: (state.videosSelected
? Theme.of(context).colorScheme.secondary
: Theme.of(context)
.colorScheme
.focusedBorder)
.withOpacity(0.7),
),
child: Icon(
@ -155,13 +155,6 @@ class _SliderIndicator extends StatelessWidget {
required this.isVideo,
}) : super(key: key);
Color _color(BuildContext context) {
if (isVideo) {
return Theme.of(context).colorScheme.secondary;
}
return Theme.of(context).colorScheme.focusedBorder;
}
@override
Widget build(BuildContext context) {
return AnimatedContainer(
@ -171,10 +164,11 @@ class _SliderIndicator extends StatelessWidget {
margin: const EdgeInsets.only(left: 4),
decoration: BoxDecoration(
border: Border.all(
color: _color(context),
color: Theme.of(context).colorScheme.focusedBorder,
),
shape: BoxShape.circle,
color: isCurrentIndex ? _color(context) : null,
color:
isCurrentIndex ? Theme.of(context).colorScheme.focusedBorder : null,
),
);
}

View File

@ -29,7 +29,7 @@ class StudioTabBar extends StatelessWidget {
Expanded(
child: _StudioTypeButton(
icon: DidvanIcons.video_solid,
selectedColor: Theme.of(context).colorScheme.secondary,
selectedColor: Theme.of(context).colorScheme.focusedBorder,
title: 'ویدیو',
onTap: () => state.videosSelected = true,
isSelected: state.videosSelected,

View File

@ -347,7 +347,7 @@ class _ProfilePageState extends State<ProfilePage> {
),
const SizedBox(height: 16),
DidvanText(
'نسخه نرم‌افزار: 3.2.2',
'نسخه نرم‌افزار: 3.3.0',
style: Theme.of(context).textTheme.bodySmall,
),
],

View File

@ -1,6 +1,7 @@
// ignore_for_file: library_private_types_in_public_api, deprecated_member_use
import 'package:didvan/constants/assets.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
@ -62,14 +63,26 @@ class _WebViewState extends State<WebView> {
return true;
}
},
child: loading
? Center(
child: Image.asset(
Assets.loadingAnimation,
width: 60,
height: 60,
),
)
: WebViewWidget(controller: controller));
child: Scaffold(
appBar: AppBar(
title: const Padding(
padding: EdgeInsets.only(top: 12.0),
child: DidvanText(
'بازگشت به دیدوان',
fontSize: 18,
),
),
toolbarHeight: 32,
),
body: loading
? Center(
child: Image.asset(
Assets.loadingAnimation,
width: 60,
height: 60,
),
)
: WebViewWidget(controller: controller),
));
}
}

View File

@ -10,6 +10,7 @@ class CategoriesList extends StatefulWidget {
final bool isAppBar;
final List<CategoryData> selectedCats;
final List<CategoryData> categories;
final bool disableHoushan;
final void Function(int id) onSelected;
final double top;
const CategoriesList({
@ -19,6 +20,7 @@ class CategoriesList extends StatefulWidget {
required this.categories,
required this.onSelected,
this.isAppBar = true,
this.disableHoushan = false,
required this.top,
}) : super(key: key);
@ -69,7 +71,9 @@ class _CategoriesListState extends State<CategoriesList> {
context,
),
for (var i = 0; i < widget.categories.length; i++)
_itemBuilder(widget.categories[i], context),
widget.disableHoushan && widget.categories[i].label == 'هوشان'
? const SizedBox()
: _itemBuilder(widget.categories[i], context),
],
),
);

View File

@ -5,6 +5,7 @@ import 'package:didvan/providers/user.dart';
import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
class LikedButton extends StatefulWidget {
@ -15,6 +16,7 @@ class LikedButton extends StatefulWidget {
final double gestureSize;
final String type;
final int itemId;
final int likes;
const LikedButton({
Key? key,
required this.value,
@ -22,6 +24,7 @@ class LikedButton extends StatefulWidget {
required this.gestureSize,
required this.type,
required this.itemId,
required this.likes,
this.askForConfirmation = false,
this.color,
}) : super(key: key);
@ -45,38 +48,58 @@ class _LikedButtonState extends State<LikedButton> {
super.initState();
}
late int likes = widget.likes;
@override
Widget build(BuildContext context) {
return DidvanIconButton(
gestureSize: widget.gestureSize,
color: widget.color ??
(DesignConfig.isDark || !_value
? null
: Theme.of(context).colorScheme.primary),
icon: _value ? DidvanIcons.like_solid : DidvanIcons.like_regular,
onPressed: () async {
bool confirm = false;
if (widget.askForConfirmation) {
await ActionSheetUtils(context).openDialog(
data: ActionSheetData(
content: const DidvanText(
'آیا می‌خواهید این محتوا از نشان‌ شده‌ها حذف شود؟',
),
titleIcon: DidvanIcons.bookmark_regular,
titleColor: Theme.of(context).colorScheme.secondary,
title: 'تایید عملیات',
onConfirmed: () => confirm = true,
return Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
if (likes != 0)
SizedBox(
height: 16,
child: DidvanText(
likes.toString(),
fontSize: 14,
),
);
}
if (!widget.askForConfirmation || confirm) {
setState(() {
_value = !_value;
});
widget.onMarkChanged(_value);
UserProvider.changeItemLiked(widget.type, widget.itemId, _value);
}
},
),
InkWell(
onTap: () async {
bool confirm = false;
if (widget.askForConfirmation) {
await ActionSheetUtils(context).openDialog(
data: ActionSheetData(
content: const DidvanText(
'آیا می‌خواهید این محتوا از نشان‌ شده‌ها حذف شود؟',
),
titleIcon: DidvanIcons.bookmark_regular,
titleColor: Theme.of(context).colorScheme.secondary,
title: 'تایید عملیات',
onConfirmed: () => confirm = true,
),
);
}
if (!widget.askForConfirmation || confirm) {
setState(() {
_value = !_value;
if (_value) {
likes += 1;
} else {
likes -= 1;
}
});
widget.onMarkChanged(_value);
UserProvider.changeItemLiked(widget.type, widget.itemId, _value);
}
},
child: Icon(
_value ? CupertinoIcons.heart_fill : CupertinoIcons.heart,
size: 24,
color: widget.color ??
(!_value ? null : Theme.of(context).colorScheme.error),
),
),
],
);
}
}

View File

@ -193,21 +193,22 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
Wrap(
children: [
for (var i = 0; i < state.categoryFilters.length; i++)
SizedBox(
width: (MediaQuery.of(context).size.width - 40) / 2,
child: DidvanCheckbox(
title: state.categoryFilters[i].label,
value:
state.selectedCats.contains(state.categoryFilters[i]),
onChanged: (value) {
if (value) {
state.selectedCats.add(state.categoryFilters[i]);
return;
}
state.selectedCats.remove(state.categoryFilters[i]);
},
if (state.categoryFilters[i].label != 'هوشان')
SizedBox(
width: (MediaQuery.of(context).size.width - 40) / 2,
child: DidvanCheckbox(
title: state.categoryFilters[i].label,
value: state.selectedCats
.contains(state.categoryFilters[i]),
onChanged: (value) {
if (value) {
state.selectedCats.add(state.categoryFilters[i]);
return;
}
state.selectedCats.remove(state.categoryFilters[i]);
},
),
),
),
],
),
],

View File

@ -74,6 +74,7 @@ class MultitypeOverview extends StatelessWidget {
case 'podcast':
return DidvanIcons.podcast_light;
case 'delphi':
case 'survey':
return DidvanIcons.saha_light;
case 'infography':
return DidvanIcons.infography_regular;

View File

@ -93,6 +93,10 @@ class NewsOverview extends StatelessWidget {
onMarkChanged: (value) =>
onLikedChanged(news.id, value, false),
askForConfirmation: hasUnmarkConfirmation,
likes: news.likes,
),
const SizedBox(
width: 4.0,
),
BookmarkButton(
itemId: news.id,

View File

@ -132,6 +132,10 @@ class RadarOverview extends StatelessWidget {
onMarkChanged: (value) =>
onLikedChanged(radar.id, value, false),
askForConfirmation: hasUnmarkConfirmation,
likes: radar.likes,
),
const SizedBox(
width: 4.0,
),
BookmarkButton(
itemId: radar.id,

View File

@ -133,21 +133,22 @@ class SearchAppBar extends StatelessWidget implements PreferredSizeWidget {
Wrap(
children: [
for (var i = 0; i < state.categoryFilters.length; i++)
SizedBox(
width: (MediaQuery.of(context).size.width - 40) / 2,
child: DidvanCheckbox(
title: state.categoryFilters[i].label,
value:
state.selectedCats.contains(state.categoryFilters[i]),
onChanged: (value) {
if (value) {
state.selectedCats.add(state.categoryFilters[i]);
return;
}
state.selectedCats.remove(state.categoryFilters[i]);
},
if (state.categoryFilters[i].label != 'هوشان')
SizedBox(
width: (MediaQuery.of(context).size.width - 40) / 2,
child: DidvanCheckbox(
title: state.categoryFilters[i].label,
value: state.selectedCats
.contains(state.categoryFilters[i]),
onChanged: (value) {
if (value) {
state.selectedCats.add(state.categoryFilters[i]);
return;
}
state.selectedCats.remove(state.categoryFilters[i]);
},
),
),
),
],
),
],

View File

@ -15,6 +15,7 @@ class SliverStateHandler<T extends CoreProvier> extends SliverList {
final bool centerEmptyState;
final bool hasConstraints;
final int placeholderCount;
final double? paddingEmptyState;
SliverStateHandler({
Key? key,
required this.state,
@ -28,6 +29,7 @@ class SliverStateHandler<T extends CoreProvier> extends SliverList {
this.centerEmptyState = true,
this.hasConstraints = false,
this.placeholderCount = 3,
this.paddingEmptyState,
}) : super(
key: key,
delegate: SliverChildBuilderDelegate(
@ -45,7 +47,8 @@ class SliverStateHandler<T extends CoreProvier> extends SliverList {
if (enableEmptyState && state.appState == AppState.idle) {
return Padding(
padding: EdgeInsets.only(
top: centerEmptyState ? deviceHight / 4 : deviceHight / 8,
top: paddingEmptyState ??
(centerEmptyState ? deviceHight / 4 : deviceHight / 8),
bottom: 20,
),
child: emptyState,

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.
# Read more about iOS versioning at
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
version: 3.2.2+3220
version: 3.3.0+3300
environment:
sdk: ">=2.19.0 <3.0.0"