houshvan version 3 -- 20/06/1403 -- Rhmn

This commit is contained in:
OkaykOrhmn 2024-09-10 14:50:54 +03:30
parent 66b85af8f2
commit 341f9786a5
50 changed files with 1492 additions and 1213 deletions

View File

@ -0,0 +1,8 @@
<svg width="30" height="24" viewBox="0 0 30 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.9541 13.5H14.9541H23.9541V12H5.9541V13.5Z" fill="#195D80"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4541 10.5C9.61035 10.5 8.9541 9.84375 8.9541 9C8.9541 8.15625 9.61035 7.5 10.4541 7.5C11.251 7.5 11.9541 8.15625 11.9541 9C11.9541 9.79688 11.251 10.5 10.4541 10.5ZM19.4541 10.5C18.6103 10.5 17.9541 9.84375 17.9541 9C17.9541 8.15625 18.6103 7.5 19.4541 7.5C20.251 7.5 20.9541 8.15625 20.9541 9C20.9541 9.79688 20.251 10.5 19.4541 10.5Z" fill="#B8B8B8"/>
<path d="M23.9541 4.5H5.9541V6H23.9541V4.5Z" fill="#195D80"/>
<path d="M2.71972 9.75H4.4541V8.25H2.71972C2.70577 8.23605 2.68663 8.20963 2.66136 8.17478C2.51688 7.97544 2.17228 7.5 1.45411 7.5C-0.473401 7.5 -0.49597 10.4531 1.45411 10.4531C2.12634 10.4531 2.47126 10.0366 2.63113 9.8435C2.67192 9.79424 2.70067 9.75953 2.71972 9.75Z" fill="#195D80"/>
<path d="M29.9541 9C29.9541 8.20312 29.2978 7.5 28.4541 7.5C27.6199 7.5 27.2791 8.03464 27.1623 8.21792L27.1554 8.22869L27.1416 8.25H25.4541V9.75H27.1416L27.1523 9.76636L27.1623 9.78208C27.2791 9.96536 27.6199 10.5 28.4541 10.5C29.2978 10.5 29.9541 9.79688 29.9541 9Z" fill="#195D80"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.4541 16.5V9.75V8.25V3C25.4541 1.35938 24.0947 0 22.4541 0H7.4541C5.7666 0 4.4541 1.35938 4.4541 3V8.25V9.75V16.4531C4.4541 18.0938 5.7666 19.4062 7.4541 19.4062H10.4541V23.3438C10.4541 23.8125 10.6885 24 11.0166 24C11.1103 24 11.2041 24 11.3447 23.9062L17.2041 19.5H22.4541C24.0947 19.5 25.4541 18.1406 25.4541 16.5ZM7.4541 18.0469C6.61035 18.0469 5.9541 17.3906 5.9541 16.5938V13.5V12V6V4.5V3C5.9541 2.20312 6.61035 1.54688 7.4541 1.54688H22.4541C23.251 1.54688 23.9541 2.25 23.9541 3V4.5V6V12V13.5V16.5C23.9541 17.2969 23.251 17.9531 22.4541 17.9531H17.1572C16.8291 17.9531 16.5478 18.0469 16.2666 18.2344L11.9541 21.5625V18.75C11.9541 18.375 11.5791 18.0469 11.2041 18.0469H7.4541Z" fill="#B8B8B8"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -0,0 +1,8 @@
<svg width="30" height="24" viewBox="0 0 30 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.9541 13.5H23.9541V12H5.9541V13.5Z" fill="#B20436"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M10.4541 10.5C9.61035 10.5 8.9541 9.84375 8.9541 9C8.9541 8.15625 9.61035 7.5 10.4541 7.5C11.251 7.5 11.9541 8.15625 11.9541 9C11.9541 9.79688 11.251 10.5 10.4541 10.5ZM19.4541 10.5C18.6103 10.5 17.9541 9.84375 17.9541 9C17.9541 8.15625 18.6103 7.5 19.4541 7.5C20.251 7.5 20.9541 8.15625 20.9541 9C20.9541 9.79688 20.251 10.5 19.4541 10.5Z" fill="#012348"/>
<path d="M23.9541 4.5H5.9541V6H23.9541V4.5Z" fill="#B20436"/>
<path d="M2.71972 9.75H4.4541V8.25H2.71972C2.70577 8.23605 2.68663 8.20963 2.66136 8.17478C2.51688 7.97544 2.17228 7.5 1.45411 7.5C-0.473401 7.5 -0.49597 10.4531 1.45411 10.4531C2.12634 10.4531 2.47126 10.0366 2.63113 9.8435C2.67192 9.79424 2.70067 9.75953 2.71972 9.75Z" fill="#B20436"/>
<path d="M29.9541 9C29.9541 8.20312 29.2978 7.5 28.4541 7.5C27.6199 7.5 27.2791 8.03464 27.1623 8.21792L27.1554 8.22869L27.1416 8.25H25.4541V9.75H27.1416L27.1523 9.76636L27.1623 9.78208C27.2791 9.96536 27.6199 10.5 28.4541 10.5C29.2978 10.5 29.9541 9.79688 29.9541 9Z" fill="#B20436"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M25.4541 16.5V9.75V8.25V3C25.4541 1.35938 24.0947 0 22.4541 0H7.4541C5.7666 0 4.4541 1.35938 4.4541 3V8.25V9.75V16.4531C4.4541 18.0938 5.7666 19.4062 7.4541 19.4062H10.4541V23.3438C10.4541 23.8125 10.6885 24 11.0166 24C11.1103 24 11.2041 24 11.3447 23.9062L17.2041 19.5H22.4541C24.0947 19.5 25.4541 18.1406 25.4541 16.5ZM7.4541 18.0469C6.61035 18.0469 5.9541 17.3906 5.9541 16.5938V13.5V12V6V4.5V3C5.9541 2.20312 6.61035 1.54688 7.4541 1.54688H22.4541C23.251 1.54688 23.9541 2.25 23.9541 3V4.5V6V12V13.5V16.5C23.9541 17.2969 23.251 17.9531 22.4541 17.9531H17.1572C16.8291 17.9531 16.5478 18.0469 16.2666 18.2344L11.9541 21.5625V18.75C11.9541 18.375 11.5791 18.0469 11.2041 18.0469H7.4541Z" fill="#012348"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

View File

@ -78,7 +78,7 @@ class Assets {
static String get podcast => '$_baseFeaturesPath/podcast-$_themeSuffix.svg'; static String get podcast => '$_baseFeaturesPath/podcast-$_themeSuffix.svg';
static String get risk => '$_baseFeaturesPath/risk-$_themeSuffix.svg'; static String get risk => '$_baseFeaturesPath/risk-$_themeSuffix.svg';
static String get saha => '$_baseFeaturesPath/saha-$_themeSuffix.svg'; static String get saha => '$_baseFeaturesPath/saha-$_themeSuffix.svg';
static String get ai => '$_baseFeaturesPath/ai_solid.svg'; static String get ai => '$_baseFeaturesPath/ai-$_themeSuffix.svg';
static String get startup => '$_baseFeaturesPath/startup-$_themeSuffix.svg'; static String get startup => '$_baseFeaturesPath/startup-$_themeSuffix.svg';
static String get stats => '$_baseFeaturesPath/stats-$_themeSuffix.svg'; static String get stats => '$_baseFeaturesPath/stats-$_themeSuffix.svg';
static String get tech => '$_baseFeaturesPath/tech-$_themeSuffix.svg'; static String get tech => '$_baseFeaturesPath/tech-$_themeSuffix.svg';

View File

@ -5,6 +5,7 @@ class BotsModel {
String? description; String? description;
List<String>? attachmentType; List<String>? attachmentType;
int? attachment; int? attachment;
bool? editable;
BotsModel({this.id, this.name, this.image}); BotsModel({this.id, this.name, this.image});
@ -20,6 +21,7 @@ class BotsModel {
}); });
} }
attachment = json['attachment']; attachment = json['attachment'];
editable = json['editable'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -32,6 +34,7 @@ class BotsModel {
data['attachmentType'] = attachmentType!.map((v) => v).toList(); data['attachmentType'] = attachmentType!.map((v) => v).toList();
} }
data['attachment'] = attachment; data['attachment'] = attachment;
data['editable'] = editable;
return data; return data;
} }
} }

View File

@ -96,6 +96,7 @@ class Prompts {
String? createdAt; String? createdAt;
bool? finished; bool? finished;
bool? error; bool? error;
bool? audio;
Prompts({ Prompts({
this.id, this.id,
@ -107,6 +108,7 @@ class Prompts {
this.createdAt, this.createdAt,
this.finished, this.finished,
this.error, this.error,
this.audio,
}); });
Prompts.fromJson(Map<String, dynamic> json) { Prompts.fromJson(Map<String, dynamic> json) {
@ -117,6 +119,7 @@ class Prompts {
fileName = json['fileName']; fileName = json['fileName'];
role = json['role']; role = json['role'];
createdAt = json['createdAt']; createdAt = json['createdAt'];
audio = json['audio'];
} }
Map<String, dynamic> toJson() { Map<String, dynamic> toJson() {
@ -128,6 +131,7 @@ class Prompts {
data['fileName'] = fileName; data['fileName'] = fileName;
data['role'] = role; data['role'] = role;
data['createdAt'] = createdAt; data['createdAt'] = createdAt;
data['audio'] = audio;
return data; return data;
} }
@ -140,7 +144,8 @@ class Prompts {
String? role, String? role,
String? createdAt, String? createdAt,
bool? finished, bool? finished,
bool? error}) { bool? error,
bool? audio}) {
return Prompts( return Prompts(
id: id ?? this.id, id: id ?? this.id,
chatId: chatId ?? this.chatId, chatId: chatId ?? this.chatId,
@ -151,6 +156,7 @@ class Prompts {
createdAt: createdAt ?? this.createdAt, createdAt: createdAt ?? this.createdAt,
finished: finished ?? this.finished, finished: finished ?? this.finished,
error: error ?? this.error, error: error ?? this.error,
audio: audio ?? this.audio,
); );
} }
} }

View File

@ -1,5 +1,6 @@
import 'dart:io'; import 'dart:io';
import 'package:mime/mime.dart';
import 'package:path/path.dart' as p; import 'package:path/path.dart' as p;
class FilesModel { class FilesModel {
@ -7,10 +8,15 @@ class FilesModel {
late String basename; late String basename;
late String extname; late String extname;
late File main; late File main;
final bool isRecorded;
late bool isAudio;
late bool isImage;
FilesModel(this.path) { FilesModel(this.path, {this.isRecorded = false}) {
basename = p.basename(path); basename = p.basename(path);
extname = p.extension(path); extname = p.extension(path);
main = File(path); main = File(path);
isAudio = lookupMimeType(path)?.startsWith('audio/') ?? false;
isImage = lookupMimeType(path)?.startsWith('image/') ?? false;
} }
} }

View File

@ -40,7 +40,7 @@ class NewsDetailsData {
image: json['image'], image: json['image'],
createdAt: json['createdAt'], createdAt: json['createdAt'],
marked: json['marked'], marked: json['marked'],
liked: json['liked'], liked: json['liked'] ?? false,
comments: json['comments'], comments: json['comments'],
order: json['order'], order: json['order'],
tags: List<Tag>.from(json['tags'].map((tag) => Tag.fromJson(tag))), tags: List<Tag>.from(json['tags'].map((tag) => Tag.fromJson(tag))),

View File

@ -53,15 +53,15 @@ class NotificationMessage {
Map<String, String> toPayload() { Map<String, String> toPayload() {
final Map<String, String> data = <String, String>{}; final Map<String, String> data = <String, String>{};
data['notificationType'] = notificationType!; data['notificationType'] = notificationType ?? '';
data['title'] = title!; data['title'] = title ?? '';
data['body'] = body!; data['body'] = body ?? '';
data['id'] = id!; data['id'] = id ?? '';
data['type'] = type!; data['type'] = type ?? '';
data['link'] = link!; data['link'] = link ?? '';
data['image'] = image!; data['image'] = image ?? '';
data['photo'] = photo!; data['photo'] = photo ?? '';
data['userId'] = userId!; data['userId'] = userId ?? '';
return data; return data;
} }
} }

View File

@ -12,7 +12,7 @@ class AppBarData {
AppBarData( AppBarData(
{this.title, {this.title,
this.subtitle, this.subtitle,
this.hasBack = false, this.hasBack = true,
this.trailing, this.trailing,
this.isSmall = false, this.isSmall = false,
this.hasElevation = true, this.hasElevation = true,

View File

@ -56,6 +56,7 @@ import 'package:didvan/views/podcasts/studio_details/studio_details.mobile.dart'
if (dart.library.html) 'package:didvan/views/podcasts/studio_details/studio_details.web.dart'; if (dart.library.html) 'package:didvan/views/podcasts/studio_details/studio_details.web.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';
import 'package:didvan/views/web/web_view.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:home_widget/home_widget.dart'; import 'package:home_widget/home_widget.dart';
@ -310,6 +311,11 @@ class RouteGenerator {
return _createRoute(HistoryAiChatPage( return _createRoute(HistoryAiChatPage(
archived: settings.arguments as bool?, archived: settings.arguments as bool?,
)); ));
case Routes.web:
return _createRoute(WebView(
src: settings.arguments as String,
));
default: default:
return _errorRoute(settings.name ?? ''); return _errorRoute(settings.name ?? '');
} }

View File

@ -34,4 +34,5 @@ class Routes {
static const String notificationTime = '/notification-time'; static const String notificationTime = '/notification-time';
static const String widgetSetting = '/widget-setting'; static const String widgetSetting = '/widget-setting';
static const String newStatic = '/new-static'; static const String newStatic = '/new-static';
static const String web = '/web';
} }

View File

@ -32,7 +32,7 @@ class AiApiService {
request.fields['chatId'] = chatId.toString(); request.fields['chatId'] = chatId.toString();
} }
if (edite != null) { if (edite != null) {
request.fields['edite'] = edite.toString().toLowerCase(); request.fields['edit'] = edite.toString().toLowerCase();
} }
if (file != null) { if (file != null) {
final length = await file.length(); final length = await file.length();
@ -50,6 +50,10 @@ class AiApiService {
mimeType)), // Use MediaType.parse to parse the MIME type mimeType)), // Use MediaType.parse to parse the MIME type
); );
} }
// print("req: ${request.files}");
// print("req: ${request.fields}");
return request; return request;
} }

View File

@ -1,8 +1,10 @@
import 'package:didvan/models/notification_message.dart'; import 'package:didvan/models/notification_message.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/services/storage/storage.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:home_widget/home_widget.dart'; import 'package:home_widget/home_widget.dart';
import 'package:persian_number_utility/persian_number_utility.dart'; import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:url_launcher/url_launcher_string.dart'; // import 'package:url_launcher/url_launcher_string.dart';
import '../../main.dart'; import '../../main.dart';
import '../../models/requests/infography.dart'; import '../../models/requests/infography.dart';
@ -15,6 +17,7 @@ import '../network/request_helper.dart';
class HomeWidgetRepository { class HomeWidgetRepository {
static Future<void> fetchWidget() async { static Future<void> fetchWidget() async {
RequestService.token = await StorageService.getValue(key: 'token');
final service = RequestService( final service = RequestService(
RequestHelper.widgetNews(), RequestHelper.widgetNews(),
); );

View File

@ -14,10 +14,15 @@ import 'package:flutter/material.dart';
import 'package:path_provider/path_provider.dart'; import 'package:path_provider/path_provider.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
enum LaunchMode { inAppWebView }
void launchUrlString(String src, {dynamic mode}) {
navigatorKey.currentState!.pushNamed(Routes.web, arguments: src);
}
class AppInitializer { class AppInitializer {
static String? fcmToken; static String? fcmToken;
static String? clickAction; static String? clickAction;
static Future<void> setupServices(BuildContext context) async { static Future<void> setupServices(BuildContext context) async {
if (!kIsWeb) { if (!kIsWeb) {
StorageService.appDocsDir = StorageService.appDocsDir =

View File

@ -62,7 +62,7 @@ class MediaService {
}, },
); );
} else { } else {
audioPlayer.setAsset( audioPlayer.setFilePath(
audioSource, audioSource,
tag: isVoiceMessage tag: isVoiceMessage
? null ? null

View File

@ -1,4 +1,6 @@
import 'dart:async'; import 'dart:async';
import 'dart:io';
import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'package:bot_toast/bot_toast.dart'; import 'package:bot_toast/bot_toast.dart';
@ -12,6 +14,7 @@ import 'package:didvan/models/view/alert_data.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart';
import 'package:didvan/views/widgets/didvan/button.dart'; import 'package:didvan/views/widgets/didvan/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/state_handlers/empty_state.dart'; import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@ -385,6 +388,49 @@ class ActionSheetUtils {
))); )));
} }
static void openInteractiveViewer(
BuildContext context, String image, bool isFile) {
showDialog(
context: context,
barrierDismissible: true,
builder: (context) => Dialog(
backgroundColor: Colors.transparent,
insetPadding: EdgeInsets.zero,
child: Stack(
children: [
Positioned.fill(
child: InteractiveViewer(
child: Padding(
padding: const EdgeInsets.all(20.0),
child: Center(
child: isFile
? ClipRRect(
borderRadius: DesignConfig.lowBorderRadius,
child: Image.file(File(image)))
: SkeletonImage(
width: min(MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height),
imageUrl: image,
),
),
),
),
),
Positioned(
right: 24,
top: 24,
child: Container(
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Theme.of(context).colorScheme.surface),
child: const BackButton()),
),
],
),
),
);
}
static void pop() { static void pop() {
DesignConfig.updateSystemUiOverlayStyle(); DesignConfig.updateSystemUiOverlayStyle();
Navigator.of(context).pop(); Navigator.of(context).pop();

29
lib/utils/media.dart Normal file
View File

@ -0,0 +1,29 @@
import 'package:flutter/material.dart';
/// Get screen media.
final MediaQueryData media =
// ignore: deprecated_member_use
MediaQueryData.fromView(WidgetsBinding.instance.window);
/// This extention help us to make widget responsive.
extension NumberParsing on num {
double w() => this * media.size.width / 100;
double h() => this * media.size.height / 100;
}
///
extension StringExtension on String {
String? get appendZeroPrefix {
return length <= 1 ? "0$this" : this;
}
}
/// This extention help us to make widget responsive.
extension DurationExtension on Duration {
String get formattedTime {
int sec = inSeconds % 60;
int min = (inSeconds / 60).floor();
return "${min.toString().appendZeroPrefix}:${sec.toString().appendZeroPrefix}";
}
}

View File

@ -28,7 +28,10 @@ class _AiState extends State<Ai> {
Future.delayed( Future.delayed(
Duration.zero, Duration.zero,
() { () {
// state.getChats(); if (context.read<HistoryAiChatState>().refresh) {
context.read<HistoryAiChatState>().getChats();
context.read<HistoryAiChatState>().refresh = false;
}
state.getBots(); state.getBots();
}, },
); );

View File

@ -11,23 +11,22 @@ import 'package:didvan/models/ai/files_model.dart';
import 'package:didvan/models/enums.dart'; import 'package:didvan/models/enums.dart';
import 'package:didvan/models/view/action_sheet_data.dart'; import 'package:didvan/models/view/action_sheet_data.dart';
import 'package:didvan/models/view/alert_data.dart'; import 'package:didvan/models/view/alert_data.dart';
import 'package:didvan/services/storage/storage.dart';
import 'package:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/ai/ai_chat_state.dart'; import 'package:didvan/views/ai/ai_chat_state.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart'; import 'package:didvan/views/ai/history_ai_chat_state.dart';
import 'package:didvan/views/ai/widgets/ai_message_bar.dart'; import 'package:didvan/views/ai/widgets/ai_message_bar.dart';
import 'package:didvan/views/ai/widgets/voice_message_view.dart'; import 'package:didvan/views/ai/widgets/audio_wave.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/marquee_text.dart';
import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_markdown/flutter_markdown.dart'; import 'package:flutter_markdown/flutter_markdown.dart';
import 'package:flutter_spinkit/flutter_spinkit.dart'; import 'package:flutter_spinkit/flutter_spinkit.dart';
import 'package:mime/mime.dart';
import 'package:persian_number_utility/persian_number_utility.dart'; import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:voice_message_package/voice_message_package.dart';
class AiChatPage extends StatefulWidget { class AiChatPage extends StatefulWidget {
final AiChatArgs args; final AiChatArgs args;
@ -71,7 +70,10 @@ class _AiChatPageState extends State<AiChatPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
context.read<HistoryAiChatState>().getChats(); if (context.read<HistoryAiChatState>().refresh) {
context.read<HistoryAiChatState>().getChats();
context.read<HistoryAiChatState>().refresh = false;
}
return true; return true;
}, },
child: Consumer<AiChatState>( child: Consumer<AiChatState>(
@ -85,7 +87,10 @@ class _AiChatPageState extends State<AiChatPage> {
icon: DidvanIcons.angle_right_solid, icon: DidvanIcons.angle_right_solid,
onPressed: () { onPressed: () {
Navigator.of(context).pop(); Navigator.of(context).pop();
context.read<HistoryAiChatState>().getChats(); if (context.read<HistoryAiChatState>().refresh) {
context.read<HistoryAiChatState>().getChats();
context.read<HistoryAiChatState>().refresh = false;
}
}, },
), ),
], ],
@ -99,6 +104,7 @@ class _AiChatPageState extends State<AiChatPage> {
final TextEditingController placeholder = final TextEditingController placeholder =
TextEditingController( TextEditingController(
text: state.chat?.placeholder); text: state.chat?.placeholder);
ActionSheetUtils.context = context;
ActionSheetUtils.openDialog( ActionSheetUtils.openDialog(
data: ActionSheetData( data: ActionSheetData(
hasConfirmButtonClose: false, hasConfirmButtonClose: false,
@ -168,6 +174,10 @@ class _AiChatPageState extends State<AiChatPage> {
fontFamily: DesignConfig fontFamily: DesignConfig
.fontFamily .fontFamily
.padRight(3)), .padRight(3)),
minLines: 4,
maxLines: 4,
keyboardType:
TextInputType.multiline,
decoration: InputDecoration( decoration: InputDecoration(
filled: true, filled: true,
fillColor: Theme.of(context) fillColor: Theme.of(context)
@ -249,7 +259,11 @@ class _AiChatPageState extends State<AiChatPage> {
itemCount: state.messages.length, itemCount: state.messages.length,
shrinkWrap: true, shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
padding: const EdgeInsets.only(bottom: 120), padding: EdgeInsets.only(
bottom: state.file != null &&
!state.file!.isRecorded
? 150
: 100),
itemBuilder: (context, mIndex) { itemBuilder: (context, mIndex) {
final prompts = state.messages[mIndex].prompts; final prompts = state.messages[mIndex].prompts;
final time = state.messages[mIndex].dateTime; final time = state.messages[mIndex].dateTime;
@ -263,6 +277,7 @@ class _AiChatPageState extends State<AiChatPage> {
const NeverScrollableScrollPhysics(), const NeverScrollableScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final message = prompts[index]; final message = prompts[index];
return messageBubble(message, context, return messageBubble(message, context,
state, index, mIndex); state, index, mIndex);
}, },
@ -274,13 +289,9 @@ class _AiChatPageState extends State<AiChatPage> {
bottomSheet: Column( bottomSheet: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Padding( AiMessageBar(
padding: const EdgeInsets.only( bot: widget.args.bot,
top: 8, bottom: 24.0, left: 12, right: 12), focusNode: focusNode,
child: AiMessageBar(
bot: widget.args.bot,
focusNode: focusNode,
),
), ),
], ],
)), )),
@ -308,20 +319,32 @@ class _AiChatPageState extends State<AiChatPage> {
); );
} }
Padding messageBubble(Prompts message, BuildContext context, Widget messageBubble(Prompts message, BuildContext context, AiChatState state,
AiChatState state, int index, int mIndex) { int index, int mIndex) {
FilesModel? file = FilesModel? file =
message.file == null ? null : FilesModel(message.file.toString()); message.file == null ? null : FilesModel(message.file.toString());
MarkdownStyleSheet defaultMarkdownStyleSheet = MarkdownStyleSheet( MarkdownStyleSheet defaultMarkdownStyleSheet = MarkdownStyleSheet(
pPadding: const EdgeInsets.all(0.8),
h1Padding: const EdgeInsets.all(0.8),
h2Padding: const EdgeInsets.all(0.8),
h3Padding: const EdgeInsets.all(0.8),
h4Padding: const EdgeInsets.all(0.8),
h5Padding: const EdgeInsets.all(0.8),
h6Padding: const EdgeInsets.all(0.8),
tablePadding: const EdgeInsets.all(0.8),
blockquotePadding: const EdgeInsets.all(0.8),
listBulletPadding: const EdgeInsets.all(0.8),
tableCellsPadding: const EdgeInsets.all(0.8),
codeblockPadding: const EdgeInsets.all(8),
code: TextStyle( code: TextStyle(
backgroundColor: Theme.of(context).colorScheme.black, backgroundColor: Theme.of(context).colorScheme.black,
color: Theme.of(context).colorScheme.white, color: Theme.of(context).colorScheme.white,
), ),
codeblockPadding: const EdgeInsets.all(8),
codeblockDecoration: BoxDecoration( codeblockDecoration: BoxDecoration(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
color: Theme.of(context).colorScheme.black)); color: Theme.of(context).colorScheme.black));
return Padding( return Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4), padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4),
child: Column( child: Column(
@ -336,8 +359,8 @@ class _AiChatPageState extends State<AiChatPage> {
: MainAxisAlignment.end, : MainAxisAlignment.end,
children: [ children: [
Container( Container(
padding: constraints: BoxConstraints(
const EdgeInsets.symmetric(vertical: 8, horizontal: 16), maxWidth: MediaQuery.sizeOf(context).width / 1.5),
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: DesignConfig.mediumBorderRadius.copyWith( borderRadius: DesignConfig.mediumBorderRadius.copyWith(
bottomLeft: !message.role.toString().contains('user') bottomLeft: !message.role.toString().contains('user')
@ -359,8 +382,6 @@ class _AiChatPageState extends State<AiChatPage> {
), ),
), ),
child: Container( child: Container(
constraints: BoxConstraints(
maxWidth: MediaQuery.sizeOf(context).width / 1.5),
child: message.finished != null && !message.finished! child: message.finished != null && !message.finished!
? Column( ? Column(
children: [ children: [
@ -394,102 +415,29 @@ class _AiChatPageState extends State<AiChatPage> {
children: [ children: [
if (message.role.toString().contains('user') && if (message.role.toString().contains('user') &&
file != null) file != null)
lookupMimeType(file.path)?.startsWith('audio/') ?? file.isAudio
false ? Padding(
? Directionality( padding: const EdgeInsets.symmetric(
textDirection: TextDirection.ltr, horizontal: 16, vertical: 8),
child: FutureBuilder( child: AudioWave(file: file.path),
future: StorageService.getValue(
key: 'token'),
builder: (context, snapshot) {
return MyVoiceMessageView(
size: 32,
controller: VoiceController(
audioSrc: file.path
.startsWith('/uploads')
? 'https://api.didvan.app${file.path}?accessToken=${snapshot.data}'
: file.path,
onComplete: () {
/// do something on complete
},
onPause: () {
/// do something on pause
},
onPlaying: () {
/// do something on playing
},
onError: (err) {
/// do somethin on error
},
isFile: !file.path
.startsWith('/uploads'),
maxDuration:
const Duration(seconds: 10),
),
innerPadding: 0,
cornerRadius: 20,
circlesColor: Theme.of(context)
.colorScheme
.primary,
activeSliderColor:
Theme.of(context)
.colorScheme
.primary,
);
}),
) )
: Container( : file.isImage
decoration: BoxDecoration( ? Padding(
borderRadius: padding: const EdgeInsets.all(8.0),
DesignConfig.mediumBorderRadius, child: messageImage(file),
color: Theme.of(context) )
.colorScheme : Padding(
.border, padding: const EdgeInsets.all(
), 8.0,
constraints:
const BoxConstraints(minWidth: 200),
padding: const EdgeInsets.fromLTRB(
12, 8, 12, 8),
margin: const EdgeInsets.symmetric(
horizontal: 12),
child: Row(
children: [
const Icon(Icons.file_copy),
const SizedBox(
width: 12,
), ),
Expanded( child: messageFile(
child: Column( context, message, state),
crossAxisAlignment: ),
CrossAxisAlignment.start, if (message.text != null &&
children: [ message.text!.isNotEmpty &&
SizedBox( ((message.audio == null ||
width: 200, (message.audio != null &&
child: DidvanText((message !message.audio!))))
.fileName
.toString())),
),
if (state.file != null)
FutureBuilder(
future: state.file!.main
.length(),
builder:
(context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
return DidvanText(
'File Size ${(snapshot.data! / 1000).round()} KB',
fontSize: 12,
);
})
],
),
)
],
),
),
if (message.text != null)
Markdown( Markdown(
data: message.text.toString(), data: message.text.toString(),
selectable: true, selectable: true,
@ -497,24 +445,75 @@ class _AiChatPageState extends State<AiChatPage> {
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
styleSheet: defaultMarkdownStyleSheet, styleSheet: defaultMarkdownStyleSheet,
), ),
Row( Padding(
mainAxisAlignment: MainAxisAlignment.end, padding:
children: [ const EdgeInsets.symmetric(horizontal: 8.0),
if (message.role.toString().contains('user') && child: Row(
index == mainAxisAlignment: MainAxisAlignment.end,
state.messages[mIndex].prompts.length - children: [
2) if (message.role
.toString()
.contains('user') &&
index ==
state.messages[mIndex].prompts
.length -
2 &&
(widget.args.bot.editable != null &&
widget.args.bot.editable!))
Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () async {
state.isEdite = true;
state.message.text =
message.text.toString();
state.update();
},
child: Icon(
Icons.edit_outlined,
size: 18,
color: Theme.of(context)
.colorScheme
.focusedBorder,
),
),
),
if (message.error != null && message.error!)
Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () async {
state.messages.last.prompts
.remove(message);
state.messages.last.prompts.add(
message.copyWith(error: false));
state.update();
await state
.postMessage(widget.args.bot);
},
child: Icon(
DidvanIcons.refresh_solid,
size: 18,
color: Theme.of(context)
.colorScheme
.focusedBorder,
),
),
),
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
state.isEdite = true; await Clipboard.setData(ClipboardData(
state.message.text = text: state.messages[mIndex]
message.text.toString(); .prompts[index].text
state.update(); .toString()));
ActionSheetUtils.showAlert(AlertData(
message: "متن با موفقیت کپی شد",
aLertType: ALertType.success));
}, },
child: Icon( child: Icon(
Icons.edit_outlined, DidvanIcons.copy_regular,
size: 18, size: 18,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
@ -522,21 +521,20 @@ class _AiChatPageState extends State<AiChatPage> {
), ),
), ),
), ),
if (message.error != null && message.error!)
Padding( Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: InkWell( child: InkWell(
onTap: () async { onTap: () async {
state.messages.last.prompts if (message.id != null) {
.remove(message); state.deleteMessage(
state.messages.last.prompts.add( message.id!, mIndex, index);
message.copyWith(error: false)); } else {
state.update(); state.messages[mIndex].prompts
await state .removeAt(index);
.postMessage(widget.args.bot); }
}, },
child: Icon( child: Icon(
DidvanIcons.refresh_solid, DidvanIcons.trash_solid,
size: 18, size: 18,
color: Theme.of(context) color: Theme.of(context)
.colorScheme .colorScheme
@ -544,50 +542,10 @@ class _AiChatPageState extends State<AiChatPage> {
), ),
), ),
), ),
Padding( ],
padding: const EdgeInsets.all(8.0), ),
child: InkWell( ),
onTap: () async { const SizedBox(height: 8),
await Clipboard.setData(ClipboardData(
text: state.messages[mIndex]
.prompts[index].text
.toString()));
ActionSheetUtils.showAlert(AlertData(
message: "متن با موفقیت کپی شد",
aLertType: ALertType.success));
},
child: Icon(
DidvanIcons.copy_regular,
size: 18,
color: Theme.of(context)
.colorScheme
.focusedBorder,
),
),
),
Padding(
padding: const EdgeInsets.all(8.0),
child: InkWell(
onTap: () async {
if (message.id != null) {
state.deleteMessage(
message.id!, mIndex, index);
} else {
state.messages[mIndex].prompts
.removeAt(index);
}
},
child: Icon(
DidvanIcons.trash_solid,
size: 18,
color: Theme.of(context)
.colorScheme
.focusedBorder,
),
),
),
],
)
], ],
), ),
), ),
@ -610,4 +568,67 @@ class _AiChatPageState extends State<AiChatPage> {
), ),
); );
} }
Container messageFile(
BuildContext context, Prompts message, AiChatState state) {
return Container(
decoration: BoxDecoration(
borderRadius: DesignConfig.mediumBorderRadius,
color: Theme.of(context).colorScheme.border,
),
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
margin: const EdgeInsets.fromLTRB(8, 8, 8, 0),
child: Row(
children: [
const Icon(Icons.file_copy),
const SizedBox(
width: 12,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(
width: MediaQuery.sizeOf(context).width,
child: MarqueeText(
text: message.fileName.toString(),
style: const TextStyle(fontSize: 14),
stop: const Duration(seconds: 3),
),
),
if (state.file != null)
FutureBuilder(
future: state.file!.main.length(),
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
return DidvanText(
'File Size ${(snapshot.data! / 1000).round()} KB',
fontSize: 12,
);
})
],
),
)
],
),
);
}
Widget messageImage(FilesModel file) {
return GestureDetector(
onTap: () => ActionSheetUtils.openInteractiveViewer(
context, file.path, !file.path.startsWith('/uploads')),
child: file.path.startsWith('/uploads')
? SkeletonImage(
pWidth: MediaQuery.sizeOf(context).width / 1,
pHeight: MediaQuery.sizeOf(context).height / 6,
imageUrl: file.path,
)
: ClipRRect(
borderRadius: DesignConfig.lowBorderRadius,
child: Image.file(file.main)),
);
}
} }

View File

@ -44,7 +44,8 @@ class AiChatState extends CoreProvier {
Future<void> _onError(e) async { Future<void> _onError(e) async {
onResponsing = false; onResponsing = false;
messages.last.prompts.removeLast(); messages.last.prompts.removeLast();
messages.last.prompts.removeLast(); messages.last.prompts.last =
messages.last.prompts.last.copyWith(error: true);
messageOnstream.value = const Stream.empty(); messageOnstream.value = const Stream.empty();
await ActionSheetUtils.showAlert(AlertData( await ActionSheetUtils.showAlert(AlertData(

View File

@ -51,7 +51,10 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return WillPopScope( return WillPopScope(
onWillPop: () async { onWillPop: () async {
context.read<HistoryAiChatState>().getChats(); if (context.read<HistoryAiChatState>().refresh) {
context.read<HistoryAiChatState>().getChats();
context.read<HistoryAiChatState>().refresh = false;
}
return true; return true;
}, },
child: DidvanScaffold( child: DidvanScaffold(
@ -70,13 +73,17 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
asset: Assets.emptyResult, asset: Assets.emptyResult,
title: 'لیست خالی است', title: 'لیست خالی است',
), ),
enableEmptyState: state.chats.isEmpty, enableEmptyState: archived
? state.archivedChats.isEmpty
: state.chats.isEmpty,
placeholder: const _HistoryPlaceholder(), placeholder: const _HistoryPlaceholder(),
placeholderCount: 8, placeholderCount: 8,
// builder: (context, state, index) => _HistoryPlaceholder(), // builder: (context, state, index) => _HistoryPlaceholder(),
builder: (context, state, index) { builder: (context, state, index) {
final chat = state.chats[index]; final chat = archived
? state.archivedChats[index]
: state.chats[index];
TextEditingController title = TextEditingController title =
TextEditingController(text: state.chats[index].title); TextEditingController(text: state.chats[index].title);
return Dismissible( return Dismissible(
@ -349,8 +356,10 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
), ),
); );
}, },
childCount: state.chats.length, childCount: archived
onRetry: state.getChats); ? state.archivedChats.length
: state.chats.length,
onRetry: () => state.getChats(archived: archived));
}, },
) )
], ],
@ -359,7 +368,10 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
hasBack: true, hasBack: true,
hasElevation: true, hasElevation: true,
backClick: () { backClick: () {
context.read<HistoryAiChatState>().getChats(); if (context.read<HistoryAiChatState>().refresh) {
context.read<HistoryAiChatState>().getChats();
context.read<HistoryAiChatState>().refresh = false;
}
}, },
trailing: Padding( trailing: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20.0), padding: const EdgeInsets.symmetric(horizontal: 20.0),
@ -370,7 +382,7 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
data: ActionSheetData( data: ActionSheetData(
onConfirmed: () async { onConfirmed: () async {
final state = context.read<HistoryAiChatState>(); final state = context.read<HistoryAiChatState>();
await state.deleteAllChat(); archived ? null : await state.deleteAllChat();
}, },
content: Column( content: Column(
children: [ children: [
@ -385,7 +397,9 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
width: 8, width: 8,
), ),
DidvanText( DidvanText(
'پاک کردن همه گفت‌وگوها', archived
? 'خارج کردن همه آرشیو ها'
: 'پاک کردن همه گفت‌وگوها',
color: Theme.of(context).colorScheme.error, color: Theme.of(context).colorScheme.error,
fontSize: 20, fontSize: 20,
), ),

View File

@ -12,12 +12,14 @@ import 'package:flutter/cupertino.dart';
class HistoryAiChatState extends CoreProvier { class HistoryAiChatState extends CoreProvier {
final List<ChatsModel> chats = []; final List<ChatsModel> chats = [];
final List<ChatsModel> archivedChats = [];
// final List<int> chatsToDelete = []; // final List<int> chatsToDelete = [];
final List<BotsModel> bots = []; final List<BotsModel> bots = [];
BotsModel? bot; BotsModel? bot;
ValueNotifier<bool> loadingBots = ValueNotifier(false); ValueNotifier<bool> loadingBots = ValueNotifier(false);
bool loadingchangeTitle = false; bool loadingchangeTitle = false;
bool loadingdeleteAll = false; bool loadingdeleteAll = false;
bool refresh = false;
Timer? timer; Timer? timer;
String search = ''; String search = '';
@ -31,10 +33,12 @@ class HistoryAiChatState extends CoreProvier {
); );
await service.httpGet(); await service.httpGet();
if (service.isSuccess) { if (service.isSuccess) {
chats.clear(); archived != null && archived ? archivedChats.clear() : chats.clear();
final messages = service.result['chats']; final messages = service.result['chats'];
for (var i = 0; i < messages.length; i++) { for (var i = 0; i < messages.length; i++) {
chats.add(ChatsModel.fromJson(messages[i])); archived != null && archived
? archivedChats.add(ChatsModel.fromJson(messages[i]))
: chats.add(ChatsModel.fromJson(messages[i]));
} }
appState = AppState.idle; appState = AppState.idle;
update(); update();
@ -140,7 +144,8 @@ class HistoryAiChatState extends CoreProvier {
// update(); // update();
// } // }
Future<void> changeNameChat(int id, int index, String title) async { Future<void> changeNameChat(int id, int index, String title,
{final bool refresh = true}) async {
loadingchangeTitle = true; loadingchangeTitle = true;
update(); update();
final service = final service =
@ -152,7 +157,7 @@ class HistoryAiChatState extends CoreProvier {
appState = AppState.idle; appState = AppState.idle;
loadingchangeTitle = false; loadingchangeTitle = false;
update(); update();
this.refresh = refresh;
return; return;
} }
appState = AppState.failed; appState = AppState.failed;
@ -163,7 +168,8 @@ class HistoryAiChatState extends CoreProvier {
update(); update();
} }
Future<void> deleteChat(int id, int index) async { Future<void> deleteChat(int id, int index,
{final bool refresh = true}) async {
final service = RequestService(RequestHelper.deleteChat(id)); final service = RequestService(RequestHelper.deleteChat(id));
await service.delete(); await service.delete();
if (service.isSuccess) { if (service.isSuccess) {
@ -173,6 +179,7 @@ class HistoryAiChatState extends CoreProvier {
loadingchangeTitle = false; loadingchangeTitle = false;
update(); update();
this.refresh = refresh;
return; return;
} }
@ -184,7 +191,7 @@ class HistoryAiChatState extends CoreProvier {
update(); update();
} }
Future<void> deleteAllChat() async { Future<void> deleteAllChat({final bool refresh = true}) async {
loadingdeleteAll = true; loadingdeleteAll = true;
update(); update();
final service = RequestService( final service = RequestService(
@ -198,6 +205,7 @@ class HistoryAiChatState extends CoreProvier {
loadingdeleteAll = false; loadingdeleteAll = false;
update(); update();
this.refresh = refresh;
return; return;
} }
@ -209,7 +217,8 @@ class HistoryAiChatState extends CoreProvier {
update(); update();
} }
Future<bool> archivedChat(int id, int index) async { Future<bool> archivedChat(int id, int index,
{final bool refresh = true}) async {
update(); update();
final service = RequestService( final service = RequestService(
RequestHelper.archivedChat(id), RequestHelper.archivedChat(id),
@ -221,6 +230,7 @@ class HistoryAiChatState extends CoreProvier {
appState = AppState.idle; appState = AppState.idle;
update(); update();
this.refresh = refresh;
return true; return true;
} }

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,306 @@
// ignore_for_file: implementation_imports, library_private_types_in_public_api
import 'dart:math';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/services/network/request.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/utils/date_time.dart';
import 'package:didvan/utils/media.dart';
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_spinkit/flutter_spinkit.dart';
import 'package:just_audio/just_audio.dart';
class AudioWave extends StatefulWidget {
final String file;
final double loadingPaddingSize;
const AudioWave({Key? key, required this.file, this.loadingPaddingSize = 0})
: super(key: key);
@override
_AudioWaveState createState() => _AudioWaveState();
}
class _AudioWaveState extends State<AudioWave> {
final int itemCount = 35;
final AudioPlayer audioPlayer = AudioPlayer();
final ValueNotifier<List<double>> randoms = ValueNotifier([]);
final ValueNotifier<List<double>> randomsDisable = ValueNotifier([]);
Duration totalDuration = Duration.zero;
double currentPosition = 0;
bool loading = true;
bool faile = false;
@override
void initState() {
super.initState();
try {
init();
listeners();
} catch (e) {
if (kDebugMode) {
print('Error occurred: $e');
}
rethrow;
}
}
void setRandoms() {
for (var i = 0; i < itemCount; i++) {
randoms.value.add(0);
randomsDisable.value.add(5.74.w() * Random().nextDouble() + .26.w());
}
}
Future<void> init() async {
try {
final path = widget.file;
if (widget.file.startsWith('/uploads')) {
final audioSource = LockCachingAudioSource(Uri.parse(
'${RequestHelper.baseUrl + path}?accessToken=${RequestService.token}'));
totalDuration =
await audioPlayer.setAudioSource(audioSource) ?? Duration.zero;
} else {
totalDuration = await audioPlayer.setFilePath(path) ?? Duration.zero;
}
setRandoms();
setState(() {
loading = false;
});
} catch (e) {
setState(() {
faile = true;
loading = false;
});
if (kDebugMode) {
print('Error occurred: $e');
}
}
}
Future<void> listeners() async {
audioPlayer.positionStream.listen((position) async {
for (var i = 0; i < itemCount; i++) {
if (i <
((position.inMilliseconds * 40) / totalDuration.inMilliseconds)) {
final ran = randomsDisable.value[i];
randoms.value[i] = ran;
} else {
randoms.value[i] = 0;
}
}
if (position.inMilliseconds >= totalDuration.inMilliseconds) {
audioPlayer.stop();
audioPlayer.seek(Duration.zero);
}
});
}
@override
void dispose() {
audioPlayer.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return SizedBox(
height: 46,
child: loading
? Padding(
padding:
EdgeInsets.symmetric(vertical: widget.loadingPaddingSize),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
5,
(index) => SpinKitWave(
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.4),
size: 32,
itemCount: 10,
))),
)
: Directionality(
textDirection: TextDirection.ltr,
child: Row(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
StreamBuilder<PlayerState>(
stream: audioPlayer.playerStateStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
return MessageBarBtn(
enable: true,
icon: faile
? DidvanIcons.refresh_solid
: snapshot.data!.playing
? DidvanIcons.pause_solid
: DidvanIcons.play_solid,
click: () async {
if (faile) {
randoms.value.clear();
randomsDisable.value.clear();
setState(() {
loading = true;
faile = false;
});
init();
return;
}
if (snapshot.data!.playing) {
await audioPlayer.pause();
} else {
await audioPlayer.play();
}
},
);
}),
faile
? const Padding(
padding: EdgeInsets.symmetric(horizontal: 8.0),
child: DidvanText(
'خطا در بارگزاری فایل صوتی',
fontSize: 12,
),
)
: StreamBuilder<Duration>(
stream: audioPlayer.positionStream,
builder: (context, snapshot) {
if (!snapshot.hasData) {
return const SizedBox();
}
currentPosition =
snapshot.data!.inMilliseconds.toDouble();
return Expanded(
child: Row(
children: [
ValueListenableBuilder(
valueListenable: randoms,
builder: (context, value, child) {
return Expanded(
child: Stack(
alignment: Alignment.center,
children: [
noise(values: randoms.value),
noise(
values: randomsDisable.value,
color: Theme.of(context)
.colorScheme
.primary
.withOpacity(0.4)),
if (totalDuration != Duration.zero)
Opacity(
opacity: 0,
child: Container(
width: 50.5.w(),
color: Colors.transparent
.withOpacity(1),
child: Theme(
data: Theme.of(context)
.copyWith(
sliderTheme:
SliderThemeData(
thumbShape:
SliderComponentShape
.noThumb,
minThumbSeparation: 0,
),
splashColor:
Colors.transparent,
),
child: Slider(
value: currentPosition,
max: totalDuration
.inMilliseconds
.toDouble() +
const Duration(
milliseconds:
10)
.inMilliseconds
.toDouble(),
onChangeStart: (value) {
// 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;
}
}
setState(() {
currentPosition = value;
});
},
onChangeEnd: (value) {
audioPlayer.seek(Duration(
milliseconds:
value.round()));
audioPlayer.play();
},
),
),
),
),
],
));
},
),
DidvanText(
DateTimeUtils.normalizeTimeDuration(
snapshot.data! == Duration.zero
? totalDuration
: snapshot.data!)),
],
),
);
},
)
],
),
),
);
}
Row noise({required final List<double> values, final Color? color}) {
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: values
.map(
(e) => Container(
margin: EdgeInsets.symmetric(horizontal: .2.w()),
width: .56.w(),
height: e,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(1000),
color: color ?? Theme.of(context).colorScheme.primary,
),
),
)
.toList(),
);
}
}

View File

@ -1,294 +0,0 @@
// ignore_for_file: implementation_imports, unused_element, empty_catches
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
import 'package:flutter/material.dart';
import 'package:voice_message_package/src/helpers/play_status.dart';
import 'package:voice_message_package/src/helpers/utils.dart';
import 'package:voice_message_package/src/voice_controller.dart';
import 'package:voice_message_package/src/widgets/noises.dart';
import 'package:voice_message_package/src/widgets/play_pause_button.dart';
/// A widget that displays a voice message view with play/pause functionality.
///
/// The [VoiceMessageView] widget is used to display a voice message with customizable appearance and behavior.
/// It provides a play/pause button, a progress slider, and a counter for the remaining time.
/// The appearance of the widget can be customized using various properties such as background color, slider color, and text styles.
///
class MyVoiceMessageView extends StatelessWidget {
const MyVoiceMessageView(
{Key? key,
required this.controller,
this.backgroundColor = Colors.transparent,
this.activeSliderColor = Colors.red,
this.notActiveSliderColor,
this.circlesColor = Colors.red,
this.innerPadding = 12,
this.cornerRadius = 20,
// this.playerWidth = 170,
this.size = 38,
this.refreshIcon = const Icon(
Icons.refresh,
color: Colors.white,
),
this.pauseIcon = const Icon(
Icons.pause_rounded,
color: Colors.white,
),
this.playIcon = const Icon(
Icons.play_arrow_rounded,
color: Colors.white,
),
this.stopDownloadingIcon = const Icon(
Icons.close,
color: Colors.white,
),
this.playPauseButtonDecoration,
this.circlesTextStyle = const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.bold,
),
this.counterTextStyle = const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
),
this.playPauseButtonLoadingColor = Colors.white,
this.trashClick,
this.showSpeed = false})
: super(key: key);
/// The controller for the voice message view.
final VoiceController controller;
/// The background color of the voice message view.
final Color backgroundColor;
///
final Color circlesColor;
/// The color of the active slider.
final Color activeSliderColor;
/// The color of the not active slider.
final Color? notActiveSliderColor;
/// The text style of the circles.
final TextStyle circlesTextStyle;
/// The text style of the counter.
final TextStyle counterTextStyle;
/// The padding between the inner content and the outer container.
final double innerPadding;
/// The corner radius of the outer container.
final double cornerRadius;
/// The size of the play/pause button.
final double size;
/// The refresh icon of the play/pause button.
final Widget refreshIcon;
/// The pause icon of the play/pause button.
final Widget pauseIcon;
/// The play icon of the play/pause button.
final Widget playIcon;
/// The stop downloading icon of the play/pause button.
final Widget stopDownloadingIcon;
/// The play Decoration of the play/pause button.
final Decoration? playPauseButtonDecoration;
/// The loading Color of the play/pause button.
final Color playPauseButtonLoadingColor;
final Function()? trashClick;
final bool showSpeed;
@override
/// Build voice message view.
Widget build(BuildContext context) {
final ThemeData theme = Theme.of(context);
final color = circlesColor;
final newTHeme = theme.copyWith(
sliderTheme: SliderThemeData(
trackShape: CustomTrackShape(),
thumbShape: SliderComponentShape.noThumb,
minThumbSeparation: 0,
),
splashColor: Colors.transparent,
);
return Directionality(
textDirection: TextDirection.ltr,
child: Container(
width: 160 + (controller.noiseCount * .72.w()),
padding: EdgeInsets.all(innerPadding),
decoration: BoxDecoration(
color: backgroundColor,
borderRadius: BorderRadius.circular(cornerRadius),
),
child: ValueListenableBuilder(
/// update ui when change play status
valueListenable: controller.updater,
builder: (context, value, child) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
const SizedBox(width: 12),
/// play pause button
PlayPauseButton(
controller: controller,
color: color,
loadingColor: playPauseButtonLoadingColor,
size: size,
refreshIcon: refreshIcon,
pauseIcon: pauseIcon,
playIcon: playIcon,
stopDownloadingIcon: stopDownloadingIcon,
buttonDecoration: playPauseButtonDecoration,
),
const SizedBox(width: 12),
///
/// slider & noises
Expanded(
child: _noises(newTHeme),
),
const SizedBox(width: 12),
Text(controller.remindingTime, style: counterTextStyle),
const SizedBox(width: 12),
///
/// speed button
if (showSpeed) _changeSpeedButton(color),
///
if (trashClick != null)
MessageBarBtn(
enable: true,
icon: DidvanIcons.trash_solid,
color: Theme.of(context).colorScheme.error,
click: () async {
trashClick?.call();
}),
const SizedBox(width: 12),
],
);
},
),
),
);
}
SizedBox _noises(ThemeData newTHeme) => SizedBox(
child: Stack(
alignment: Alignment.center,
children: [
/// noises
Noises(
rList: controller.randoms!,
activeSliderColor: activeSliderColor,
),
/// slider
AnimatedBuilder(
animation: CurvedAnimation(
parent: controller.animController,
curve: Curves.ease,
),
builder: (BuildContext context, Widget? child) {
return Positioned(
left: controller.animController.value,
child: Container(
width: controller.noiseWidth,
height: 6.w(),
color: notActiveSliderColor ?? backgroundColor,
),
);
},
),
Opacity(
opacity: 0,
child: Container(
width: controller.noiseWidth,
color: Colors.transparent.withOpacity(1),
child: Theme(
data: newTHeme,
child: Slider(
value: controller.currentMillSeconds,
max: controller.maxMillSeconds,
onChangeStart: controller.onChangeSliderStart,
onChanged: controller.onChanging,
onChangeEnd: (value) {
controller.onSeek(
Duration(milliseconds: value.toInt()),
);
controller.play();
},
),
),
),
),
],
),
);
Transform _changeSpeedButton(Color color) => Transform.translate(
offset: const Offset(0, -7),
child: GestureDetector(
onTap: () {
controller.changeSpeed();
},
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 3, vertical: 2),
decoration: BoxDecoration(
color: color,
borderRadius: BorderRadius.circular(4),
),
child: Text(
controller.speed.playSpeedStr,
style: circlesTextStyle,
),
),
),
);
}
///
/// A custom track shape for a slider that is rounded rectangular in shape.
/// Extends the [RoundedRectSliderTrackShape] class.
class CustomTrackShape extends RoundedRectSliderTrackShape {
@override
/// Returns the preferred rectangle for the voice message view.
///
/// The preferred rectangle is calculated based on the current state and layout
/// of the voice message view. It represents the area where the view should be
/// displayed on the screen.
///
/// Returns a [Rect] object representing the preferred rectangle.
Rect getPreferredRect({
required RenderBox parentBox,
Offset offset = Offset.zero,
required SliderThemeData sliderTheme,
bool isEnabled = false,
bool isDiscrete = false,
}) {
const double trackHeight = 10;
final double trackLeft = offset.dx,
trackTop = offset.dy + (parentBox.size.height - trackHeight) / 2;
final double trackWidth = parentBox.size.width;
return Rect.fromLTWH(trackLeft, trackTop, trackWidth, trackHeight);
}
}

View File

@ -1,3 +1,4 @@
import 'package:didvan/services/app_initalizer.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';
@ -6,7 +7,7 @@ import 'package:didvan/views/widgets/didvan/text_field.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; // import 'package:url_launcher/url_launcher.dart';
class UsernameInput extends StatefulWidget { class UsernameInput extends StatefulWidget {
const UsernameInput({ const UsernameInput({
@ -78,10 +79,8 @@ class _UsernameInputState extends State<UsernameInput> {
.bodySmall! .bodySmall!
.copyWith(color: Theme.of(context).colorScheme.primary), .copyWith(color: Theme.of(context).colorScheme.primary),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => launchUrl( ..onTap = () => launchUrlString(
Uri.parse( 'https://didvan.app/terms-of-use#conditions',
'https://didvan.app/terms-of-use#conditions',
),
), ),
), ),
const TextSpan(text: 'و\n'), const TextSpan(text: 'و\n'),
@ -92,10 +91,8 @@ class _UsernameInputState extends State<UsernameInput> {
.bodySmall! .bodySmall!
.copyWith(color: Theme.of(context).colorScheme.primary), .copyWith(color: Theme.of(context).colorScheme.primary),
recognizer: TapGestureRecognizer() recognizer: TapGestureRecognizer()
..onTap = () => launchUrl( ..onTap = () => launchUrlString(
Uri.parse( 'https://didvan.app/terms-of-use#privacy',
'https://didvan.app/terms-of-use#privacy',
),
), ),
), ),
const TextSpan(text: 'را می‌پذیرم'), const TextSpan(text: 'را می‌پذیرم'),

View File

@ -51,6 +51,7 @@ class DirectState extends CoreProvier {
recordedFile!.delete(); recordedFile!.delete();
recordedFile = null; recordedFile = null;
notifyListeners(); notifyListeners();
update();
} }
Future<void> startRecording() async { Future<void> startRecording() async {

View File

@ -1,5 +1,3 @@
// ignore_for_file: unused_element
import 'dart:io'; import 'dart:io';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/config/theme_data.dart';
@ -7,60 +5,62 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/requests/studio.dart'; import 'package:didvan/models/requests/studio.dart';
import 'package:didvan/models/studio_details_data.dart'; import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/services/media/media.dart'; import 'package:didvan/services/media/media.dart';
import 'package:didvan/services/network/request.dart'; import 'package:didvan/views/widgets/audio/audio_slider.dart';
import 'package:didvan/services/network/request_helper.dart';
import 'package:didvan/views/ai/widgets/voice_message_view.dart';
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart'; import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
import 'package:didvan/views/widgets/didvan/icon_button.dart'; import 'package:didvan/views/widgets/didvan/icon_button.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:just_audio/just_audio.dart'; import 'package:just_audio/just_audio.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:voice_message_package/voice_message_package.dart';
class AudioWidget extends StatelessWidget { class AudioWidget extends StatelessWidget {
final String? audioUrl; final String? audioUrl;
final File? audioFile; final File? audioFile;
final int id; final int id;
final StudioDetailsData? audioMetaData; final StudioDetailsData? audioMetaData;
final Function()? deleteClidk;
const AudioWidget({ const AudioWidget({
Key? key, Key? key,
this.audioUrl, this.audioUrl,
this.audioFile, this.audioFile,
required this.id, required this.id,
this.audioMetaData, this.audioMetaData,
this.deleteClidk,
}) : super(key: key); }) : super(key: key);
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return MyVoiceMessageView( return StreamBuilder<bool>(
size: 32, stream: MediaService.audioPlayer.playingStream,
controller: VoiceController( builder: (context, snapshot) {
audioSrc: audioUrl != null return Row(
? '${RequestHelper.baseUrl + audioUrl!}?accessToken=${RequestService.token}' crossAxisAlignment: CrossAxisAlignment.start,
: audioFile!.path, children: [
onComplete: () { Expanded(
/// do something on complete child: Padding(
}, padding: const EdgeInsets.only(top: 12),
onPause: () { child: Row(
/// do something on pause children: [
}, Expanded(
onPlaying: () { child: AudioSlider(
/// do something on playing tag: audioMetaData != null
}, ? '${audioMetaData!.type}-$id'
onError: (err) { : 'message-$id',
/// do somethin on error duration: audioMetaData?.duration,
}, showTimer: true,
isFile: audioFile != null, ),
maxDuration: const Duration(seconds: 10), ),
), ],
innerPadding: 0, ),
cornerRadius: 20, ),
circlesColor: Theme.of(context).colorScheme.primary, ),
activeSliderColor: Theme.of(context).colorScheme.primary, const SizedBox(width: 12),
backgroundColor: Colors.transparent, _AudioControllerButton(
trashClick: deleteClidk, audioFile: audioFile,
audioUrl: audioUrl,
id: id,
audioMetaData: audioMetaData,
),
],
);
},
); );
} }
} }

View File

@ -0,0 +1,30 @@
// ignore_for_file: unused_element
import 'dart:io';
import 'package:didvan/models/studio_details_data.dart';
import 'package:didvan/views/ai/widgets/audio_wave.dart';
import 'package:flutter/material.dart';
class DownloadableAudioWidget extends StatelessWidget {
final String? audioUrl;
final File? audioFile;
final int id;
final StudioDetailsData? audioMetaData;
final Function()? deleteClidk;
const DownloadableAudioWidget({
Key? key,
this.audioUrl,
this.audioFile,
required this.id,
this.audioMetaData,
this.deleteClidk,
}) : super(key: key);
@override
Widget build(BuildContext context) {
return SizedBox(
width: MediaQuery.sizeOf(context).width / 1.6,
child: AudioWave(file: audioUrl != null ? audioUrl! : audioFile!.path));
}
}

View File

@ -4,7 +4,7 @@ import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/message_data/message_data.dart'; import 'package:didvan/models/message_data/message_data.dart';
import 'package:didvan/utils/date_time.dart'; import 'package:didvan/utils/date_time.dart';
import 'package:didvan/views/direct/direct_state.dart'; import 'package:didvan/views/direct/direct_state.dart';
import 'package:didvan/views/direct/widgets/audio_widget.dart'; import 'package:didvan/views/direct/widgets/downloadable_audio_widget.dart';
import 'package:didvan/views/widgets/didvan/divider.dart'; import 'package:didvan/views/widgets/didvan/divider.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';
@ -84,7 +84,7 @@ class Message extends StatelessWidget {
children: [ children: [
if (message.text != null) DidvanText(message.text!), if (message.text != null) DidvanText(message.text!),
if (message.audio != null || message.audioFile != null) if (message.audio != null || message.audioFile != null)
AudioWidget( DownloadableAudioWidget(
audioFile: message.audioFile, audioFile: message.audioFile,
audioUrl: message.audio, audioUrl: message.audio,
id: message.id, id: message.id,

View File

@ -1,8 +1,9 @@
import 'package:didvan/config/design_config.dart'; 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/views/ai/widgets/message_bar_btn.dart';
import 'package:didvan/views/direct/direct_state.dart'; import 'package:didvan/views/direct/direct_state.dart';
import 'package:didvan/views/direct/widgets/audio_widget.dart'; import 'package:didvan/views/direct/widgets/downloadable_audio_widget.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:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
@ -219,21 +220,30 @@ class _RecordChecking extends StatelessWidget {
}, },
color: Theme.of(context).colorScheme.focusedBorder, color: Theme.of(context).colorScheme.focusedBorder,
), ),
const SizedBox(
width: 12,
),
Expanded( Expanded(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(vertical: 8), padding: const EdgeInsets.symmetric(vertical: 8),
child: AudioWidget( child: DownloadableAudioWidget(
audioFile: state.recordedFile!, audioFile: state.recordedFile!,
id: 0, id: 0,
deleteClidk: () => state.deleteRecordedFile, // deleteClidk: () => state.deleteRecordedFile,
), ),
), ),
), ),
// DidvanIconButton( const SizedBox(
// icon: DidvanIcons.trash_solid, width: 12,
// color: Theme.of(context).colorScheme.secondary, ),
// onPressed: state.deleteRecordedFile, MessageBarBtn(
// ), enable: true,
icon: DidvanIcons.trash_solid,
color: Theme.of(context).colorScheme.error,
click: () => state.deleteRecordedFile()),
const SizedBox(
width: 12,
),
], ],
); );
} }

View File

@ -61,7 +61,10 @@ class _HomeState extends State<Home>
_tabController.addListener(() { _tabController.addListener(() {
state.currentPageIndex = _tabController.index; state.currentPageIndex = _tabController.index;
if (_tabController.index == 2) { if (_tabController.index == 2) {
homeScaffKey.currentState!.closeDrawer();
context.read<HistoryAiChatState>().getChats(); context.read<HistoryAiChatState>().getChats();
context.read<HistoryAiChatState>().getBots();
} }
}); });
@ -166,7 +169,8 @@ class _HomeState extends State<Home>
data: ActionSheetData( data: ActionSheetData(
onConfirmed: () async { onConfirmed: () async {
await state await state
.deleteAllChat(); .deleteAllChat(
refresh: false);
}, },
content: Column( content: Column(
children: [ children: [
@ -463,7 +467,8 @@ class _HomeState extends State<Home>
onTap: () async { onTap: () async {
if (title.text.isNotEmpty) { if (title.text.isNotEmpty) {
await state.changeNameChat( await state.changeNameChat(
chat.id!, index, title.text); chat.id!, index, title.text,
refresh: false);
title.clear(); title.clear();
} }
if (chat.isEditing != null) { if (chat.isEditing != null) {
@ -489,12 +494,12 @@ class _HomeState extends State<Home>
onSelected: (value) async { onSelected: (value) async {
switch (value) { switch (value) {
case 'حذف پیام': case 'حذف پیام':
await state.deleteChat(chat.id!, index); await state.deleteChat(chat.id!, index, refresh: false);
break; break;
case 'آرشیو': case 'آرشیو':
await state.archivedChat(chat.id!, index); await state.archivedChat(chat.id!, index,
await state.getChats(); refresh: false);
break; break;
default: default:
} }

View File

@ -196,7 +196,7 @@ class HomeState extends CoreProvier {
MenuItemType( MenuItemType(
label: 'سها', label: 'سها',
asset: Assets.saha, asset: Assets.saha,
link: 'https://saha.didvan.app', link: 'https://saha.didvan.app/app',
), ),
MenuItemType( MenuItemType(
label: 'هوشان', label: 'هوشان',

View File

@ -2,6 +2,7 @@ import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart'; import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/models/home_page_content/home_page_list.dart'; import 'package:didvan/models/home_page_content/home_page_list.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/views/home/main/main_page_state.dart'; import 'package:didvan/views/home/main/main_page_state.dart';
import 'package:didvan/views/home/main/widgets/banner.dart'; import 'package:didvan/views/home/main/widgets/banner.dart';
import 'package:didvan/views/home/main/widgets/general_item.dart'; import 'package:didvan/views/home/main/widgets/general_item.dart';
@ -12,7 +13,7 @@ import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/state_handlers/state_handler.dart'; import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart'; // import 'package:url_launcher/url_launcher_string.dart';
import 'package:didvan/services/network/request.dart'; import 'package:didvan/services/network/request.dart';
class MainPage extends StatefulWidget { class MainPage extends StatefulWidget {

View File

@ -5,11 +5,12 @@ import 'package:didvan/models/requests/news.dart';
import 'package:didvan/models/requests/radar.dart'; import 'package:didvan/models/requests/radar.dart';
import 'package:didvan/providers/core.dart'; import 'package:didvan/providers/core.dart';
import 'package:didvan/routes/routes.dart'; import 'package:didvan/routes/routes.dart';
import 'package:didvan/services/app_initalizer.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:didvan/utils/action_sheet.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:url_launcher/url_launcher_string.dart'; // import 'package:url_launcher/url_launcher_string.dart';
class MainPageState extends CoreProvier { class MainPageState extends CoreProvier {
late MainPageContent content; late MainPageContent content;

View File

@ -1,16 +1,12 @@
import 'dart:math';
import 'package:didvan/config/design_config.dart'; import 'package:didvan/services/app_initalizer.dart';
import 'package:didvan/config/theme_data.dart'; import 'package:didvan/utils/action_sheet.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/views/home/main/main_page_state.dart'; import 'package:didvan/views/home/main/main_page_state.dart';
import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/didvan/slider.dart'; import 'package:didvan/views/widgets/didvan/slider.dart';
import 'package:didvan/views/widgets/ink_wrapper.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'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart'; // import 'package:url_launcher/url_launcher_string.dart';
class MainPageBanner extends StatelessWidget { class MainPageBanner extends StatelessWidget {
final bool isFirst; final bool isFirst;
@ -26,7 +22,8 @@ class MainPageBanner extends StatelessWidget {
padding: const EdgeInsets.symmetric(horizontal: 4), padding: const EdgeInsets.symmetric(horizontal: 4),
child: GestureDetector( child: GestureDetector(
onTap: () => item.link == null || item.link!.isEmpty onTap: () => item.link == null || item.link!.isEmpty
? _openInteractiveViewer(context, item.image) ? ActionSheetUtils.openInteractiveViewer(
context, item.image, false)
: launchUrlString(item.link!, mode: LaunchMode.inAppWebView), : launchUrlString(item.link!, mode: LaunchMode.inAppWebView),
child: SkeletonImage( child: SkeletonImage(
imageUrl: item.image, imageUrl: item.image,
@ -40,62 +37,4 @@ class MainPageBanner extends StatelessWidget {
height: (MediaQuery.of(context).size.width - 8) * 9.0 / 16.0, height: (MediaQuery.of(context).size.width - 8) * 9.0 / 16.0,
); );
} }
void _openInteractiveViewer(BuildContext context, String image) {
showDialog(
context: context,
builder: (context) => Stack(
children: [
Positioned.fill(
child: InteractiveViewer(
child: Center(
child: SkeletonImage(
width: min(MediaQuery.of(context).size.width,
MediaQuery.of(context).size.height),
imageUrl: image,
),
),
),
),
const Positioned(
right: 24,
top: 24,
child: _BackButton(),
),
],
),
);
}
}
class _BackButton extends StatefulWidget {
const _BackButton({Key? key}) : super(key: key);
@override
__BackButtonState createState() => __BackButtonState();
}
class __BackButtonState extends State<_BackButton> {
@override
Widget build(BuildContext context) {
return AnimatedVisibility(
duration: DesignConfig.lowAnimationDuration,
isVisible: true,
child: InkWrapper(
borderRadius: DesignConfig.lowBorderRadius,
onPressed: Navigator.of(context).pop,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.splash,
border: Border.all(color: Theme.of(context).colorScheme.border),
borderRadius: DesignConfig.lowBorderRadius,
),
child: const Icon(
DidvanIcons.back_regular,
size: 32,
),
),
),
);
}
} }

View File

@ -69,26 +69,28 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
SizedBox( Padding(
width: padding:
MediaQuery.of(context).size.width * 2 / 3 - 60, const EdgeInsets.symmetric(horizontal: 12.0),
child: AudioWidget( child: SizedBox(
id: widget.content.id, child: AudioWidget(
audioUrl: widget.content.link,
audioMetaData: StudioDetailsData(
id: widget.content.id, id: widget.content.id,
duration: widget.content.duration!, audioUrl: widget.content.link,
title: widget.content.title, audioMetaData: StudioDetailsData(
description: '', id: widget.content.id,
image: widget.content.image, duration: widget.content.duration!,
link: widget.content.link, title: widget.content.title,
iframe: null, description: '',
createdAt: '', image: widget.content.image,
order: 1, link: widget.content.link,
marked: widget.content.marked, iframe: null,
comments: 0, createdAt: '',
tags: [], order: 1,
type: 'podcast', marked: widget.content.marked,
comments: 0,
tags: [],
type: 'podcast',
),
), ),
), ),
), ),

View File

@ -96,6 +96,9 @@ class _NewStatisticState extends State<NewStatistic> {
itemsInStatics(context, state, 3), itemsInStatics(context, state, 3),
itemsInStatics(context, state, 4), itemsInStatics(context, state, 4),
itemsInStatics(context, state, 5, hasDivider: false), itemsInStatics(context, state, 5, hasDivider: false),
const SizedBox(
height: 24,
),
], ],
), ),
), ),
@ -135,6 +138,7 @@ class _NewStatisticState extends State<NewStatistic> {
physics: const ScrollPhysics(), physics: const ScrollPhysics(),
itemCount: state.contents[id].contents.length, itemCount: state.contents[id].contents.length,
itemBuilder: (context, index) => StatMainCard( itemBuilder: (context, index) => StatMainCard(
id: id,
statistic: state.contents[id].contents[index], statistic: state.contents[id].contents[index],
), ),
), ),

View File

@ -15,7 +15,7 @@ import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:persian_number_utility/persian_number_utility.dart'; import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:didvan/services/app_initalizer.dart';
class SearchResultItem extends StatelessWidget { class SearchResultItem extends StatelessWidget {
final OverviewData item; final OverviewData item;

View File

@ -6,7 +6,7 @@ import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:didvan/services/app_initalizer.dart';
class MainCategories extends StatelessWidget { class MainCategories extends StatelessWidget {
const MainCategories({super.key}); const MainCategories({super.key});

View File

@ -17,7 +17,7 @@ import 'package:didvan/views/widgets/state_handlers/state_handler.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:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:didvan/services/app_initalizer.dart';
class StudioDetailsWidget extends StatelessWidget { class StudioDetailsWidget extends StatelessWidget {
final void Function(int id, bool value) onMarkChanged; final void Function(int id, bool value) onMarkChanged;
@ -65,7 +65,7 @@ class StudioDetailsWidget extends StatelessWidget {
key: ValueKey(state.studio.id), key: ValueKey(state.studio.id),
data: state.studio.description, data: state.studio.description,
onAnchorTap: (href, _, __) => onAnchorTap: (href, _, __) =>
launchUrl(Uri.parse(href!)), launchUrlString(href!),
style: { style: {
'*': Style( '*': Style(
direction: TextDirection.rtl, direction: TextDirection.rtl,

View File

@ -22,7 +22,7 @@ import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart'; import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart'; import 'package:didvan/services/app_initalizer.dart';
class ProfilePage extends StatefulWidget { class ProfilePage extends StatefulWidget {
const ProfilePage({Key? key}) : super(key: key); const ProfilePage({Key? key}) : super(key: key);
@ -222,7 +222,7 @@ class _ProfilePageState extends State<ProfilePage> {
icon: DidvanIcons.info_circle_regular, icon: DidvanIcons.info_circle_regular,
title: 'معرفی دیدوان', title: 'معرفی دیدوان',
onTap: () => onTap: () =>
launchUrl(Uri.parse('https://didvan.app/#info')), launchUrlString('https://didvan.app/#info'),
), ),
const DidvanDivider(), const DidvanDivider(),
MenuOption( MenuOption(
@ -232,12 +232,16 @@ class _ProfilePageState extends State<ProfilePage> {
state.showContactUs = !state.showContactUs; state.showContactUs = !state.showContactUs;
state.update(); state.update();
}, },
trailing: Icon( trailing: Row(
state.showContactUs children: [
? DidvanIcons.angle_up_regular Icon(
: DidvanIcons.angle_down_regular, state.showContactUs
size: 18, ? DidvanIcons.angle_up_regular
color: Theme.of(context).colorScheme.title, : DidvanIcons.angle_down_regular,
size: 18,
color: Theme.of(context).colorScheme.title,
),
],
)), )),
AnimatedVisibility( AnimatedVisibility(
isVisible: state.showContactUs, isVisible: state.showContactUs,
@ -316,9 +320,8 @@ class _ProfilePageState extends State<ProfilePage> {
MenuOption( MenuOption(
icon: DidvanIcons.alert_regular, icon: DidvanIcons.alert_regular,
title: 'حریم خصوصی', title: 'حریم خصوصی',
onTap: () => launchUrl( onTap: () => launchUrlString(
Uri.parse( 'https://didvan.app/terms-of-use#privacy',
'https://didvan.app/terms-of-use#privacy'),
), ),
), ),
], ],

View File

@ -0,0 +1,75 @@
// ignore_for_file: library_private_types_in_public_api, deprecated_member_use
import 'package:didvan/constants/assets.dart';
import 'package:flutter/material.dart';
import 'package:webview_flutter/webview_flutter.dart';
class WebView extends StatefulWidget {
final String src;
const WebView({Key? key, required this.src}) : super(key: key);
@override
_WebViewState createState() => _WebViewState();
}
class _WebViewState extends State<WebView> {
late WebViewController controller;
bool loading = true;
int progress = 0;
@override
void initState() {
controller = WebViewController()
..setJavaScriptMode(JavaScriptMode.unrestricted)
..setNavigationDelegate(
NavigationDelegate(
onProgress: (int progress) {
// Update loading bar.
setState(() {
this.progress = progress;
});
},
onPageStarted: (String url) {},
onPageFinished: (String url) async {
await Future.delayed(const Duration(seconds: 2));
setState(() {
if (progress == 100) loading = false;
});
},
onHttpError: (HttpResponseError error) {},
onWebResourceError: (WebResourceError error) {
// navigatorKey.currentState!.pop();
},
onNavigationRequest: (NavigationRequest request) {
// if (request.url.startsWith('https://www.youtube.com/')) {
// return NavigationDecision.prevent;
// }
return NavigationDecision.navigate;
},
),
)
..loadRequest(Uri.parse(widget.src));
super.initState();
}
@override
Widget build(BuildContext context) {
return WillPopScope(
onWillPop: () async {
if (await controller.canGoBack()) {
await controller.goBack();
return false;
} else {
return true;
}
},
child: loading
? Center(
child: Image.asset(
Assets.loadingAnimation,
width: 60,
height: 60,
),
)
: WebViewWidget(controller: controller));
}
}

View File

@ -0,0 +1,40 @@
// ignore_for_file: library_private_types_in_public_api
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/ink_wrapper.dart';
import 'package:flutter/material.dart';
class BackButton extends StatefulWidget {
const BackButton({Key? key}) : super(key: key);
@override
__BackButtonState createState() => __BackButtonState();
}
class __BackButtonState extends State<BackButton> {
@override
Widget build(BuildContext context) {
return AnimatedVisibility(
duration: DesignConfig.lowAnimationDuration,
isVisible: true,
child: InkWrapper(
borderRadius: DesignConfig.lowBorderRadius,
onPressed: Navigator.of(context).pop,
child: Container(
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.splash,
border: Border.all(color: Theme.of(context).colorScheme.border),
borderRadius: DesignConfig.lowBorderRadius,
),
child: const Icon(
DidvanIcons.back_regular,
size: 32,
),
),
),
);
}
}

View File

@ -23,7 +23,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'; import 'package:didvan/services/app_initalizer.dart';
class DidvanPageView extends StatefulWidget { class DidvanPageView extends StatefulWidget {
final List items; final List items;
@ -309,7 +309,7 @@ class _DidvanPageViewState extends State<DidvanPageView> {
), ),
); );
} else { } else {
launchUrl(Uri.parse(href)); launchUrlString(href);
} }
}, },
style: { style: {

View File

@ -33,7 +33,7 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20)), borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20)),
color: Theme.of(context).colorScheme.surface, color: Theme.of(context).colorScheme.surface,
boxShadow: state.currentPageIndex == 1 || state.filtering boxShadow: state.filtering
? null ? null
: [ : [
BoxShadow( BoxShadow(

View File

@ -19,7 +19,7 @@ import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:persian_number_utility/persian_number_utility.dart'; import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher_string.dart'; import 'package:didvan/services/app_initalizer.dart';
class MultitypeOverview extends StatelessWidget { class MultitypeOverview extends StatelessWidget {
final OverviewData item; final OverviewData item;

View File

@ -11,6 +11,8 @@ class SkeletonImage extends StatelessWidget {
final String imageUrl; final String imageUrl;
final double? width; final double? width;
final double? height; final double? height;
final double? pWidth;
final double? pHeight;
final BorderRadius? borderRadius; final BorderRadius? borderRadius;
final double? aspectRatio; final double? aspectRatio;
const SkeletonImage({ const SkeletonImage({
@ -20,6 +22,8 @@ class SkeletonImage extends StatelessWidget {
this.aspectRatio, this.aspectRatio,
this.width, this.width,
this.height, this.height,
this.pWidth,
this.pHeight,
}) : super(key: key); }) : super(key: key);
@override @override
@ -39,7 +43,10 @@ class SkeletonImage extends StatelessWidget {
imageUrl: imageUrl.startsWith('http') imageUrl: imageUrl.startsWith('http')
? imageUrl ? imageUrl
: RequestHelper.baseUrl + imageUrl, : RequestHelper.baseUrl + imageUrl,
placeholder: (context, _) => const ShimmerPlaceholder(), placeholder: (context, _) => ShimmerPlaceholder(
width: pWidth,
height: pHeight,
),
), ),
), ),
); );

View File

@ -359,7 +359,7 @@ packages:
source: sdk source: sdk
version: "0.0.0" version: "0.0.0"
flutter_cache_manager: flutter_cache_manager:
dependency: transitive dependency: "direct main"
description: description:
name: flutter_cache_manager name: flutter_cache_manager
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba" sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
@ -1074,22 +1074,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "1.2.0" version: "1.2.0"
syncfusion_flutter_core:
dependency: transitive
description:
name: syncfusion_flutter_core
sha256: e17dcc7a1d0701e84d0a83c0040503cdcc6c72e44db0d733ab4c706dd5b8b9f8
url: "https://pub.dev"
source: hosted
version: "25.2.7"
syncfusion_flutter_sliders:
dependency: transitive
description:
name: syncfusion_flutter_sliders
sha256: "842a452fd73fd61fbebbff72d726ffea4cdaacf088ca2738aeaf7f4454b3861e"
url: "https://pub.dev"
source: hosted
version: "25.2.7"
synchronized: synchronized:
dependency: transitive dependency: transitive
description: description:
@ -1154,70 +1138,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "2.2.2" version: "2.2.2"
url_launcher:
dependency: "direct main"
description:
name: url_launcher
sha256: "21b704ce5fa560ea9f3b525b43601c678728ba46725bab9b01187b4831377ed3"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
url_launcher_android:
dependency: transitive
description:
name: url_launcher_android
sha256: "17cd5e205ea615e2c6ea7a77323a11712dffa0720a8a90540db57a01347f9ad9"
url: "https://pub.dev"
source: hosted
version: "6.3.2"
url_launcher_ios:
dependency: transitive
description:
name: url_launcher_ios
sha256: "7068716403343f6ba4969b4173cbf3b84fc768042124bc2c011e5d782b24fe89"
url: "https://pub.dev"
source: hosted
version: "6.3.0"
url_launcher_linux:
dependency: transitive
description:
name: url_launcher_linux
sha256: ab360eb661f8879369acac07b6bb3ff09d9471155357da8443fd5d3cf7363811
url: "https://pub.dev"
source: hosted
version: "3.1.1"
url_launcher_macos:
dependency: transitive
description:
name: url_launcher_macos
sha256: "9a1a42d5d2d95400c795b2914c36fdcb525870c752569438e4ebb09a2b5d90de"
url: "https://pub.dev"
source: hosted
version: "3.2.0"
url_launcher_platform_interface:
dependency: transitive
description:
name: url_launcher_platform_interface
sha256: "552f8a1e663569be95a8190206a38187b531910283c3e982193e4f2733f01029"
url: "https://pub.dev"
source: hosted
version: "2.3.2"
url_launcher_web:
dependency: transitive
description:
name: url_launcher_web
sha256: "8d9e750d8c9338601e709cd0885f95825086bd8b642547f26bda435aade95d8a"
url: "https://pub.dev"
source: hosted
version: "2.3.1"
url_launcher_windows:
dependency: transitive
description:
name: url_launcher_windows
sha256: ecf9725510600aa2bb6d7ddabe16357691b6d2805f66216a97d1b881e21beff7
url: "https://pub.dev"
source: hosted
version: "3.1.1"
uuid: uuid:
dependency: transitive dependency: transitive
description: description:
@ -1306,14 +1226,6 @@ packages:
url: "https://pub.dev" url: "https://pub.dev"
source: hosted source: hosted
version: "13.0.0" version: "13.0.0"
voice_message_package:
dependency: "direct main"
description:
name: voice_message_package
sha256: cd7a717751e60908d37624309c6995c1c62c1bab6b54f7d15c2d71289f0b0cb6
url: "https://pub.dev"
source: hosted
version: "2.2.1"
wakelock_plus: wakelock_plus:
dependency: transitive dependency: transitive
description: description:

View File

@ -57,12 +57,12 @@ dependencies:
bot_toast: ^4.0.1 bot_toast: ^4.0.1
flutter_secure_storage: ^8.0.0 flutter_secure_storage: ^8.0.0
flutter_html: ^3.0.0-alpha.2 flutter_html: ^3.0.0-alpha.2
url_launcher: ^6.0.18 # url_launcher: ^6.0.18
audio_video_progress_bar: ^2.0.0 audio_video_progress_bar: ^2.0.0
image_cropper: ^1.5.0 image_cropper: ^1.5.0
firebase_core: ^3.1.0 firebase_core: ^3.1.0
firebase_messaging: ^15.0.1 firebase_messaging: ^15.0.1
webview_flutter: ^4.2.0 webview_flutter: ^4.8.0
expandable_bottom_sheet: ^1.1.1+1 expandable_bottom_sheet: ^1.1.1+1
permission_handler: ^11.0.0 permission_handler: ^11.0.0
# better_player: # better_player:
@ -89,9 +89,10 @@ dependencies:
flutter_markdown: ^0.7.3+1 flutter_markdown: ^0.7.3+1
file_picker: ^8.0.5 file_picker: ^8.0.5
marquee: ^2.2.3 marquee: ^2.2.3
voice_message_package: ^2.2.1
mime: ^1.0.2 mime: ^1.0.2
path: any path: any
flutter_cache_manager: any
dev_dependencies: dev_dependencies:
flutter_test: flutter_test:
sdk: flutter sdk: flutter