houshvan version 3 -- 20/06/1403 -- Rhmn
This commit is contained in:
parent
66b85af8f2
commit
341f9786a5
|
|
@ -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 |
|
|
@ -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 |
|
|
@ -78,7 +78,7 @@ class Assets {
|
|||
static String get podcast => '$_baseFeaturesPath/podcast-$_themeSuffix.svg';
|
||||
static String get risk => '$_baseFeaturesPath/risk-$_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 stats => '$_baseFeaturesPath/stats-$_themeSuffix.svg';
|
||||
static String get tech => '$_baseFeaturesPath/tech-$_themeSuffix.svg';
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ class BotsModel {
|
|||
String? description;
|
||||
List<String>? attachmentType;
|
||||
int? attachment;
|
||||
bool? editable;
|
||||
|
||||
BotsModel({this.id, this.name, this.image});
|
||||
|
||||
|
|
@ -20,6 +21,7 @@ class BotsModel {
|
|||
});
|
||||
}
|
||||
attachment = json['attachment'];
|
||||
editable = json['editable'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -32,6 +34,7 @@ class BotsModel {
|
|||
data['attachmentType'] = attachmentType!.map((v) => v).toList();
|
||||
}
|
||||
data['attachment'] = attachment;
|
||||
data['editable'] = editable;
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -96,6 +96,7 @@ class Prompts {
|
|||
String? createdAt;
|
||||
bool? finished;
|
||||
bool? error;
|
||||
bool? audio;
|
||||
|
||||
Prompts({
|
||||
this.id,
|
||||
|
|
@ -107,6 +108,7 @@ class Prompts {
|
|||
this.createdAt,
|
||||
this.finished,
|
||||
this.error,
|
||||
this.audio,
|
||||
});
|
||||
|
||||
Prompts.fromJson(Map<String, dynamic> json) {
|
||||
|
|
@ -117,6 +119,7 @@ class Prompts {
|
|||
fileName = json['fileName'];
|
||||
role = json['role'];
|
||||
createdAt = json['createdAt'];
|
||||
audio = json['audio'];
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
|
|
@ -128,6 +131,7 @@ class Prompts {
|
|||
data['fileName'] = fileName;
|
||||
data['role'] = role;
|
||||
data['createdAt'] = createdAt;
|
||||
data['audio'] = audio;
|
||||
return data;
|
||||
}
|
||||
|
||||
|
|
@ -140,7 +144,8 @@ class Prompts {
|
|||
String? role,
|
||||
String? createdAt,
|
||||
bool? finished,
|
||||
bool? error}) {
|
||||
bool? error,
|
||||
bool? audio}) {
|
||||
return Prompts(
|
||||
id: id ?? this.id,
|
||||
chatId: chatId ?? this.chatId,
|
||||
|
|
@ -151,6 +156,7 @@ class Prompts {
|
|||
createdAt: createdAt ?? this.createdAt,
|
||||
finished: finished ?? this.finished,
|
||||
error: error ?? this.error,
|
||||
audio: audio ?? this.audio,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import 'dart:io';
|
||||
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:path/path.dart' as p;
|
||||
|
||||
class FilesModel {
|
||||
|
|
@ -7,10 +8,15 @@ class FilesModel {
|
|||
late String basename;
|
||||
late String extname;
|
||||
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);
|
||||
extname = p.extension(path);
|
||||
main = File(path);
|
||||
isAudio = lookupMimeType(path)?.startsWith('audio/') ?? false;
|
||||
isImage = lookupMimeType(path)?.startsWith('image/') ?? false;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ class NewsDetailsData {
|
|||
image: json['image'],
|
||||
createdAt: json['createdAt'],
|
||||
marked: json['marked'],
|
||||
liked: json['liked'],
|
||||
liked: json['liked'] ?? false,
|
||||
comments: json['comments'],
|
||||
order: json['order'],
|
||||
tags: List<Tag>.from(json['tags'].map((tag) => Tag.fromJson(tag))),
|
||||
|
|
|
|||
|
|
@ -53,15 +53,15 @@ class NotificationMessage {
|
|||
|
||||
Map<String, String> toPayload() {
|
||||
final Map<String, String> data = <String, String>{};
|
||||
data['notificationType'] = notificationType!;
|
||||
data['title'] = title!;
|
||||
data['body'] = body!;
|
||||
data['id'] = id!;
|
||||
data['type'] = type!;
|
||||
data['link'] = link!;
|
||||
data['image'] = image!;
|
||||
data['photo'] = photo!;
|
||||
data['userId'] = userId!;
|
||||
data['notificationType'] = notificationType ?? '';
|
||||
data['title'] = title ?? '';
|
||||
data['body'] = body ?? '';
|
||||
data['id'] = id ?? '';
|
||||
data['type'] = type ?? '';
|
||||
data['link'] = link ?? '';
|
||||
data['image'] = image ?? '';
|
||||
data['photo'] = photo ?? '';
|
||||
data['userId'] = userId ?? '';
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ class AppBarData {
|
|||
AppBarData(
|
||||
{this.title,
|
||||
this.subtitle,
|
||||
this.hasBack = false,
|
||||
this.hasBack = true,
|
||||
this.trailing,
|
||||
this.isSmall = false,
|
||||
this.hasElevation = true,
|
||||
|
|
|
|||
|
|
@ -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';
|
||||
import 'package:didvan/views/splash/splash.dart';
|
||||
import 'package:didvan/routes/routes.dart';
|
||||
import 'package:didvan/views/web/web_view.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:home_widget/home_widget.dart';
|
||||
|
|
@ -310,6 +311,11 @@ class RouteGenerator {
|
|||
return _createRoute(HistoryAiChatPage(
|
||||
archived: settings.arguments as bool?,
|
||||
));
|
||||
|
||||
case Routes.web:
|
||||
return _createRoute(WebView(
|
||||
src: settings.arguments as String,
|
||||
));
|
||||
default:
|
||||
return _errorRoute(settings.name ?? '');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,4 +34,5 @@ class Routes {
|
|||
static const String notificationTime = '/notification-time';
|
||||
static const String widgetSetting = '/widget-setting';
|
||||
static const String newStatic = '/new-static';
|
||||
static const String web = '/web';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ class AiApiService {
|
|||
request.fields['chatId'] = chatId.toString();
|
||||
}
|
||||
if (edite != null) {
|
||||
request.fields['edite'] = edite.toString().toLowerCase();
|
||||
request.fields['edit'] = edite.toString().toLowerCase();
|
||||
}
|
||||
if (file != null) {
|
||||
final length = await file.length();
|
||||
|
|
@ -50,6 +50,10 @@ class AiApiService {
|
|||
mimeType)), // Use MediaType.parse to parse the MIME type
|
||||
);
|
||||
}
|
||||
|
||||
// print("req: ${request.files}");
|
||||
// print("req: ${request.fields}");
|
||||
|
||||
return request;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
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:home_widget/home_widget.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 '../../models/requests/infography.dart';
|
||||
|
|
@ -15,6 +17,7 @@ import '../network/request_helper.dart';
|
|||
|
||||
class HomeWidgetRepository {
|
||||
static Future<void> fetchWidget() async {
|
||||
RequestService.token = await StorageService.getValue(key: 'token');
|
||||
final service = RequestService(
|
||||
RequestHelper.widgetNews(),
|
||||
);
|
||||
|
|
|
|||
|
|
@ -14,10 +14,15 @@ import 'package:flutter/material.dart';
|
|||
import 'package:path_provider/path_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 {
|
||||
static String? fcmToken;
|
||||
static String? clickAction;
|
||||
|
||||
static Future<void> setupServices(BuildContext context) async {
|
||||
if (!kIsWeb) {
|
||||
StorageService.appDocsDir =
|
||||
|
|
|
|||
|
|
@ -62,7 +62,7 @@ class MediaService {
|
|||
},
|
||||
);
|
||||
} else {
|
||||
audioPlayer.setAsset(
|
||||
audioPlayer.setFilePath(
|
||||
audioSource,
|
||||
tag: isVoiceMessage
|
||||
? null
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
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/widgets/didvan/button.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:flutter/foundation.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() {
|
||||
DesignConfig.updateSystemUiOverlayStyle();
|
||||
Navigator.of(context).pop();
|
||||
|
|
|
|||
|
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
|
@ -28,7 +28,10 @@ class _AiState extends State<Ai> {
|
|||
Future.delayed(
|
||||
Duration.zero,
|
||||
() {
|
||||
// state.getChats();
|
||||
if (context.read<HistoryAiChatState>().refresh) {
|
||||
context.read<HistoryAiChatState>().getChats();
|
||||
context.read<HistoryAiChatState>().refresh = false;
|
||||
}
|
||||
state.getBots();
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -11,23 +11,22 @@ import 'package:didvan/models/ai/files_model.dart';
|
|||
import 'package:didvan/models/enums.dart';
|
||||
import 'package:didvan/models/view/action_sheet_data.dart';
|
||||
import 'package:didvan/models/view/alert_data.dart';
|
||||
import 'package:didvan/services/storage/storage.dart';
|
||||
import 'package:didvan/utils/action_sheet.dart';
|
||||
import 'package:didvan/utils/date_time.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/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/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/services.dart';
|
||||
import 'package:flutter_markdown/flutter_markdown.dart';
|
||||
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||
import 'package:mime/mime.dart';
|
||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:voice_message_package/voice_message_package.dart';
|
||||
|
||||
class AiChatPage extends StatefulWidget {
|
||||
final AiChatArgs args;
|
||||
|
|
@ -71,7 +70,10 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
context.read<HistoryAiChatState>().getChats();
|
||||
if (context.read<HistoryAiChatState>().refresh) {
|
||||
context.read<HistoryAiChatState>().getChats();
|
||||
context.read<HistoryAiChatState>().refresh = false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: Consumer<AiChatState>(
|
||||
|
|
@ -85,7 +87,10 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
icon: DidvanIcons.angle_right_solid,
|
||||
onPressed: () {
|
||||
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 =
|
||||
TextEditingController(
|
||||
text: state.chat?.placeholder);
|
||||
ActionSheetUtils.context = context;
|
||||
ActionSheetUtils.openDialog(
|
||||
data: ActionSheetData(
|
||||
hasConfirmButtonClose: false,
|
||||
|
|
@ -168,6 +174,10 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
fontFamily: DesignConfig
|
||||
.fontFamily
|
||||
.padRight(3)),
|
||||
minLines: 4,
|
||||
maxLines: 4,
|
||||
keyboardType:
|
||||
TextInputType.multiline,
|
||||
decoration: InputDecoration(
|
||||
filled: true,
|
||||
fillColor: Theme.of(context)
|
||||
|
|
@ -249,7 +259,11 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
itemCount: state.messages.length,
|
||||
shrinkWrap: true,
|
||||
physics: const NeverScrollableScrollPhysics(),
|
||||
padding: const EdgeInsets.only(bottom: 120),
|
||||
padding: EdgeInsets.only(
|
||||
bottom: state.file != null &&
|
||||
!state.file!.isRecorded
|
||||
? 150
|
||||
: 100),
|
||||
itemBuilder: (context, mIndex) {
|
||||
final prompts = state.messages[mIndex].prompts;
|
||||
final time = state.messages[mIndex].dateTime;
|
||||
|
|
@ -263,6 +277,7 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
const NeverScrollableScrollPhysics(),
|
||||
itemBuilder: (context, index) {
|
||||
final message = prompts[index];
|
||||
|
||||
return messageBubble(message, context,
|
||||
state, index, mIndex);
|
||||
},
|
||||
|
|
@ -274,13 +289,9 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
bottomSheet: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
top: 8, bottom: 24.0, left: 12, right: 12),
|
||||
child: AiMessageBar(
|
||||
bot: widget.args.bot,
|
||||
focusNode: focusNode,
|
||||
),
|
||||
AiMessageBar(
|
||||
bot: widget.args.bot,
|
||||
focusNode: focusNode,
|
||||
),
|
||||
],
|
||||
)),
|
||||
|
|
@ -308,20 +319,32 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
);
|
||||
}
|
||||
|
||||
Padding messageBubble(Prompts message, BuildContext context,
|
||||
AiChatState state, int index, int mIndex) {
|
||||
Widget messageBubble(Prompts message, BuildContext context, AiChatState state,
|
||||
int index, int mIndex) {
|
||||
FilesModel? file =
|
||||
message.file == null ? null : FilesModel(message.file.toString());
|
||||
|
||||
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(
|
||||
backgroundColor: Theme.of(context).colorScheme.black,
|
||||
color: Theme.of(context).colorScheme.white,
|
||||
),
|
||||
codeblockPadding: const EdgeInsets.all(8),
|
||||
codeblockDecoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
color: Theme.of(context).colorScheme.black));
|
||||
|
||||
return Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4),
|
||||
child: Column(
|
||||
|
|
@ -336,8 +359,8 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
: MainAxisAlignment.end,
|
||||
children: [
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(vertical: 8, horizontal: 16),
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.sizeOf(context).width / 1.5),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: DesignConfig.mediumBorderRadius.copyWith(
|
||||
bottomLeft: !message.role.toString().contains('user')
|
||||
|
|
@ -359,8 +382,6 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
),
|
||||
),
|
||||
child: Container(
|
||||
constraints: BoxConstraints(
|
||||
maxWidth: MediaQuery.sizeOf(context).width / 1.5),
|
||||
child: message.finished != null && !message.finished!
|
||||
? Column(
|
||||
children: [
|
||||
|
|
@ -394,102 +415,29 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
children: [
|
||||
if (message.role.toString().contains('user') &&
|
||||
file != null)
|
||||
lookupMimeType(file.path)?.startsWith('audio/') ??
|
||||
false
|
||||
? Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: FutureBuilder(
|
||||
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,
|
||||
);
|
||||
}),
|
||||
file.isAudio
|
||||
? Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 16, vertical: 8),
|
||||
child: AudioWave(file: file.path),
|
||||
)
|
||||
: Container(
|
||||
decoration: BoxDecoration(
|
||||
borderRadius:
|
||||
DesignConfig.mediumBorderRadius,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
.border,
|
||||
),
|
||||
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,
|
||||
: file.isImage
|
||||
? Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: messageImage(file),
|
||||
)
|
||||
: Padding(
|
||||
padding: const EdgeInsets.all(
|
||||
8.0,
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment:
|
||||
CrossAxisAlignment.start,
|
||||
children: [
|
||||
SizedBox(
|
||||
width: 200,
|
||||
child: DidvanText((message
|
||||
.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)
|
||||
child: messageFile(
|
||||
context, message, state),
|
||||
),
|
||||
if (message.text != null &&
|
||||
message.text!.isNotEmpty &&
|
||||
((message.audio == null ||
|
||||
(message.audio != null &&
|
||||
!message.audio!))))
|
||||
Markdown(
|
||||
data: message.text.toString(),
|
||||
selectable: true,
|
||||
|
|
@ -497,24 +445,75 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
physics: const NeverScrollableScrollPhysics(),
|
||||
styleSheet: defaultMarkdownStyleSheet,
|
||||
),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
if (message.role.toString().contains('user') &&
|
||||
index ==
|
||||
state.messages[mIndex].prompts.length -
|
||||
2)
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
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: const EdgeInsets.all(8.0),
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
state.isEdite = true;
|
||||
state.message.text =
|
||||
message.text.toString();
|
||||
state.update();
|
||||
await Clipboard.setData(ClipboardData(
|
||||
text: state.messages[mIndex]
|
||||
.prompts[index].text
|
||||
.toString()));
|
||||
ActionSheetUtils.showAlert(AlertData(
|
||||
message: "متن با موفقیت کپی شد",
|
||||
aLertType: ALertType.success));
|
||||
},
|
||||
child: Icon(
|
||||
Icons.edit_outlined,
|
||||
DidvanIcons.copy_regular,
|
||||
size: 18,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
|
|
@ -522,21 +521,20 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
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);
|
||||
if (message.id != null) {
|
||||
state.deleteMessage(
|
||||
message.id!, mIndex, index);
|
||||
} else {
|
||||
state.messages[mIndex].prompts
|
||||
.removeAt(index);
|
||||
}
|
||||
},
|
||||
child: Icon(
|
||||
DidvanIcons.refresh_solid,
|
||||
DidvanIcons.trash_solid,
|
||||
size: 18,
|
||||
color: Theme.of(context)
|
||||
.colorScheme
|
||||
|
|
@ -544,50 +542,10 @@ class _AiChatPageState extends State<AiChatPage> {
|
|||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: InkWell(
|
||||
onTap: () async {
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -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)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -44,7 +44,8 @@ class AiChatState extends CoreProvier {
|
|||
Future<void> _onError(e) async {
|
||||
onResponsing = false;
|
||||
messages.last.prompts.removeLast();
|
||||
messages.last.prompts.removeLast();
|
||||
messages.last.prompts.last =
|
||||
messages.last.prompts.last.copyWith(error: true);
|
||||
messageOnstream.value = const Stream.empty();
|
||||
|
||||
await ActionSheetUtils.showAlert(AlertData(
|
||||
|
|
|
|||
|
|
@ -51,7 +51,10 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
|||
Widget build(BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () async {
|
||||
context.read<HistoryAiChatState>().getChats();
|
||||
if (context.read<HistoryAiChatState>().refresh) {
|
||||
context.read<HistoryAiChatState>().getChats();
|
||||
context.read<HistoryAiChatState>().refresh = false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
child: DidvanScaffold(
|
||||
|
|
@ -70,13 +73,17 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
|||
asset: Assets.emptyResult,
|
||||
title: 'لیست خالی است',
|
||||
),
|
||||
enableEmptyState: state.chats.isEmpty,
|
||||
enableEmptyState: archived
|
||||
? state.archivedChats.isEmpty
|
||||
: state.chats.isEmpty,
|
||||
placeholder: const _HistoryPlaceholder(),
|
||||
placeholderCount: 8,
|
||||
|
||||
// builder: (context, state, index) => _HistoryPlaceholder(),
|
||||
builder: (context, state, index) {
|
||||
final chat = state.chats[index];
|
||||
final chat = archived
|
||||
? state.archivedChats[index]
|
||||
: state.chats[index];
|
||||
TextEditingController title =
|
||||
TextEditingController(text: state.chats[index].title);
|
||||
return Dismissible(
|
||||
|
|
@ -349,8 +356,10 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
|||
),
|
||||
);
|
||||
},
|
||||
childCount: state.chats.length,
|
||||
onRetry: state.getChats);
|
||||
childCount: archived
|
||||
? state.archivedChats.length
|
||||
: state.chats.length,
|
||||
onRetry: () => state.getChats(archived: archived));
|
||||
},
|
||||
)
|
||||
],
|
||||
|
|
@ -359,7 +368,10 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
|||
hasBack: true,
|
||||
hasElevation: true,
|
||||
backClick: () {
|
||||
context.read<HistoryAiChatState>().getChats();
|
||||
if (context.read<HistoryAiChatState>().refresh) {
|
||||
context.read<HistoryAiChatState>().getChats();
|
||||
context.read<HistoryAiChatState>().refresh = false;
|
||||
}
|
||||
},
|
||||
trailing: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||
|
|
@ -370,7 +382,7 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
|||
data: ActionSheetData(
|
||||
onConfirmed: () async {
|
||||
final state = context.read<HistoryAiChatState>();
|
||||
await state.deleteAllChat();
|
||||
archived ? null : await state.deleteAllChat();
|
||||
},
|
||||
content: Column(
|
||||
children: [
|
||||
|
|
@ -385,7 +397,9 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
|||
width: 8,
|
||||
),
|
||||
DidvanText(
|
||||
'پاک کردن همه گفتوگوها',
|
||||
archived
|
||||
? 'خارج کردن همه آرشیو ها'
|
||||
: 'پاک کردن همه گفتوگوها',
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
fontSize: 20,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -12,12 +12,14 @@ import 'package:flutter/cupertino.dart';
|
|||
|
||||
class HistoryAiChatState extends CoreProvier {
|
||||
final List<ChatsModel> chats = [];
|
||||
final List<ChatsModel> archivedChats = [];
|
||||
// final List<int> chatsToDelete = [];
|
||||
final List<BotsModel> bots = [];
|
||||
BotsModel? bot;
|
||||
ValueNotifier<bool> loadingBots = ValueNotifier(false);
|
||||
bool loadingchangeTitle = false;
|
||||
bool loadingdeleteAll = false;
|
||||
bool refresh = false;
|
||||
Timer? timer;
|
||||
String search = '';
|
||||
|
||||
|
|
@ -31,10 +33,12 @@ class HistoryAiChatState extends CoreProvier {
|
|||
);
|
||||
await service.httpGet();
|
||||
if (service.isSuccess) {
|
||||
chats.clear();
|
||||
archived != null && archived ? archivedChats.clear() : chats.clear();
|
||||
final messages = service.result['chats'];
|
||||
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;
|
||||
update();
|
||||
|
|
@ -140,7 +144,8 @@ class HistoryAiChatState extends CoreProvier {
|
|||
// 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;
|
||||
update();
|
||||
final service =
|
||||
|
|
@ -152,7 +157,7 @@ class HistoryAiChatState extends CoreProvier {
|
|||
appState = AppState.idle;
|
||||
loadingchangeTitle = false;
|
||||
update();
|
||||
|
||||
this.refresh = refresh;
|
||||
return;
|
||||
}
|
||||
appState = AppState.failed;
|
||||
|
|
@ -163,7 +168,8 @@ class HistoryAiChatState extends CoreProvier {
|
|||
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));
|
||||
await service.delete();
|
||||
if (service.isSuccess) {
|
||||
|
|
@ -173,6 +179,7 @@ class HistoryAiChatState extends CoreProvier {
|
|||
loadingchangeTitle = false;
|
||||
|
||||
update();
|
||||
this.refresh = refresh;
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -184,7 +191,7 @@ class HistoryAiChatState extends CoreProvier {
|
|||
update();
|
||||
}
|
||||
|
||||
Future<void> deleteAllChat() async {
|
||||
Future<void> deleteAllChat({final bool refresh = true}) async {
|
||||
loadingdeleteAll = true;
|
||||
update();
|
||||
final service = RequestService(
|
||||
|
|
@ -198,6 +205,7 @@ class HistoryAiChatState extends CoreProvier {
|
|||
loadingdeleteAll = false;
|
||||
|
||||
update();
|
||||
this.refresh = refresh;
|
||||
|
||||
return;
|
||||
}
|
||||
|
|
@ -209,7 +217,8 @@ class HistoryAiChatState extends CoreProvier {
|
|||
update();
|
||||
}
|
||||
|
||||
Future<bool> archivedChat(int id, int index) async {
|
||||
Future<bool> archivedChat(int id, int index,
|
||||
{final bool refresh = true}) async {
|
||||
update();
|
||||
final service = RequestService(
|
||||
RequestHelper.archivedChat(id),
|
||||
|
|
@ -221,6 +230,7 @@ class HistoryAiChatState extends CoreProvier {
|
|||
appState = AppState.idle;
|
||||
|
||||
update();
|
||||
this.refresh = refresh;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -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(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,3 +1,4 @@
|
|||
import 'package:didvan/services/app_initalizer.dart';
|
||||
import 'package:didvan/views/authentication/authentication_state.dart';
|
||||
import 'package:didvan/views/authentication/widgets/authentication_layout.dart';
|
||||
import 'package:didvan/views/widgets/didvan/button.dart';
|
||||
|
|
@ -6,7 +7,7 @@ import 'package:didvan/views/widgets/didvan/text_field.dart';
|
|||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
// import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class UsernameInput extends StatefulWidget {
|
||||
const UsernameInput({
|
||||
|
|
@ -78,10 +79,8 @@ class _UsernameInputState extends State<UsernameInput> {
|
|||
.bodySmall!
|
||||
.copyWith(color: Theme.of(context).colorScheme.primary),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => launchUrl(
|
||||
Uri.parse(
|
||||
'https://didvan.app/terms-of-use#conditions',
|
||||
),
|
||||
..onTap = () => launchUrlString(
|
||||
'https://didvan.app/terms-of-use#conditions',
|
||||
),
|
||||
),
|
||||
const TextSpan(text: 'و\n'),
|
||||
|
|
@ -92,10 +91,8 @@ class _UsernameInputState extends State<UsernameInput> {
|
|||
.bodySmall!
|
||||
.copyWith(color: Theme.of(context).colorScheme.primary),
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () => launchUrl(
|
||||
Uri.parse(
|
||||
'https://didvan.app/terms-of-use#privacy',
|
||||
),
|
||||
..onTap = () => launchUrlString(
|
||||
'https://didvan.app/terms-of-use#privacy',
|
||||
),
|
||||
),
|
||||
const TextSpan(text: 'را میپذیرم'),
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ class DirectState extends CoreProvier {
|
|||
recordedFile!.delete();
|
||||
recordedFile = null;
|
||||
notifyListeners();
|
||||
update();
|
||||
}
|
||||
|
||||
Future<void> startRecording() async {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
// ignore_for_file: unused_element
|
||||
|
||||
import 'dart:io';
|
||||
|
||||
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/studio_details_data.dart';
|
||||
import 'package:didvan/services/media/media.dart';
|
||||
import 'package:didvan/services/network/request.dart';
|
||||
import 'package:didvan/services/network/request_helper.dart';
|
||||
import 'package:didvan/views/ai/widgets/voice_message_view.dart';
|
||||
import 'package:didvan/views/widgets/audio/audio_slider.dart';
|
||||
import 'package:didvan/views/podcasts/studio_details/studio_details_state.dart';
|
||||
import 'package:didvan/views/widgets/didvan/icon_button.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:just_audio/just_audio.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:voice_message_package/voice_message_package.dart';
|
||||
|
||||
class AudioWidget extends StatelessWidget {
|
||||
final String? audioUrl;
|
||||
final File? audioFile;
|
||||
final int id;
|
||||
final StudioDetailsData? audioMetaData;
|
||||
final Function()? deleteClidk;
|
||||
const AudioWidget({
|
||||
Key? key,
|
||||
this.audioUrl,
|
||||
this.audioFile,
|
||||
required this.id,
|
||||
this.audioMetaData,
|
||||
this.deleteClidk,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MyVoiceMessageView(
|
||||
size: 32,
|
||||
controller: VoiceController(
|
||||
audioSrc: audioUrl != null
|
||||
? '${RequestHelper.baseUrl + audioUrl!}?accessToken=${RequestService.token}'
|
||||
: audioFile!.path,
|
||||
onComplete: () {
|
||||
/// do something on complete
|
||||
},
|
||||
onPause: () {
|
||||
/// do something on pause
|
||||
},
|
||||
onPlaying: () {
|
||||
/// do something on playing
|
||||
},
|
||||
onError: (err) {
|
||||
/// do somethin on error
|
||||
},
|
||||
isFile: audioFile != null,
|
||||
maxDuration: const Duration(seconds: 10),
|
||||
),
|
||||
innerPadding: 0,
|
||||
cornerRadius: 20,
|
||||
circlesColor: Theme.of(context).colorScheme.primary,
|
||||
activeSliderColor: Theme.of(context).colorScheme.primary,
|
||||
backgroundColor: Colors.transparent,
|
||||
trashClick: deleteClidk,
|
||||
return StreamBuilder<bool>(
|
||||
stream: MediaService.audioPlayer.playingStream,
|
||||
builder: (context, snapshot) {
|
||||
return Row(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
Expanded(
|
||||
child: AudioSlider(
|
||||
tag: audioMetaData != null
|
||||
? '${audioMetaData!.type}-$id'
|
||||
: 'message-$id',
|
||||
duration: audioMetaData?.duration,
|
||||
showTimer: true,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 12),
|
||||
_AudioControllerButton(
|
||||
audioFile: audioFile,
|
||||
audioUrl: audioUrl,
|
||||
id: id,
|
||||
audioMetaData: audioMetaData,
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import 'package:didvan/constants/app_icons.dart';
|
|||
import 'package:didvan/models/message_data/message_data.dart';
|
||||
import 'package:didvan/utils/date_time.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/text.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
|
|
@ -84,7 +84,7 @@ class Message extends StatelessWidget {
|
|||
children: [
|
||||
if (message.text != null) DidvanText(message.text!),
|
||||
if (message.audio != null || message.audioFile != null)
|
||||
AudioWidget(
|
||||
DownloadableAudioWidget(
|
||||
audioFile: message.audioFile,
|
||||
audioUrl: message.audio,
|
||||
id: message.id,
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
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/ai/widgets/message_bar_btn.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/text.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
|
@ -219,21 +220,30 @@ class _RecordChecking extends StatelessWidget {
|
|||
},
|
||||
color: Theme.of(context).colorScheme.focusedBorder,
|
||||
),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
Expanded(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||
child: AudioWidget(
|
||||
child: DownloadableAudioWidget(
|
||||
audioFile: state.recordedFile!,
|
||||
id: 0,
|
||||
deleteClidk: () => state.deleteRecordedFile,
|
||||
// deleteClidk: () => state.deleteRecordedFile,
|
||||
),
|
||||
),
|
||||
),
|
||||
// DidvanIconButton(
|
||||
// icon: DidvanIcons.trash_solid,
|
||||
// color: Theme.of(context).colorScheme.secondary,
|
||||
// onPressed: state.deleteRecordedFile,
|
||||
// ),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
MessageBarBtn(
|
||||
enable: true,
|
||||
icon: DidvanIcons.trash_solid,
|
||||
color: Theme.of(context).colorScheme.error,
|
||||
click: () => state.deleteRecordedFile()),
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -61,7 +61,10 @@ class _HomeState extends State<Home>
|
|||
_tabController.addListener(() {
|
||||
state.currentPageIndex = _tabController.index;
|
||||
if (_tabController.index == 2) {
|
||||
homeScaffKey.currentState!.closeDrawer();
|
||||
|
||||
context.read<HistoryAiChatState>().getChats();
|
||||
context.read<HistoryAiChatState>().getBots();
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -166,7 +169,8 @@ class _HomeState extends State<Home>
|
|||
data: ActionSheetData(
|
||||
onConfirmed: () async {
|
||||
await state
|
||||
.deleteAllChat();
|
||||
.deleteAllChat(
|
||||
refresh: false);
|
||||
},
|
||||
content: Column(
|
||||
children: [
|
||||
|
|
@ -463,7 +467,8 @@ class _HomeState extends State<Home>
|
|||
onTap: () async {
|
||||
if (title.text.isNotEmpty) {
|
||||
await state.changeNameChat(
|
||||
chat.id!, index, title.text);
|
||||
chat.id!, index, title.text,
|
||||
refresh: false);
|
||||
title.clear();
|
||||
}
|
||||
if (chat.isEditing != null) {
|
||||
|
|
@ -489,12 +494,12 @@ class _HomeState extends State<Home>
|
|||
onSelected: (value) async {
|
||||
switch (value) {
|
||||
case 'حذف پیام':
|
||||
await state.deleteChat(chat.id!, index);
|
||||
await state.deleteChat(chat.id!, index, refresh: false);
|
||||
break;
|
||||
|
||||
case 'آرشیو':
|
||||
await state.archivedChat(chat.id!, index);
|
||||
await state.getChats();
|
||||
await state.archivedChat(chat.id!, index,
|
||||
refresh: false);
|
||||
break;
|
||||
default:
|
||||
}
|
||||
|
|
|
|||
|
|
@ -196,7 +196,7 @@ class HomeState extends CoreProvier {
|
|||
MenuItemType(
|
||||
label: 'سها',
|
||||
asset: Assets.saha,
|
||||
link: 'https://saha.didvan.app',
|
||||
link: 'https://saha.didvan.app/app',
|
||||
),
|
||||
MenuItemType(
|
||||
label: 'هوشان',
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ import 'package:didvan/config/theme_data.dart';
|
|||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/models/home_page_content/home_page_list.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/widgets/banner.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:flutter/material.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';
|
||||
|
||||
class MainPage extends StatefulWidget {
|
||||
|
|
|
|||
|
|
@ -5,11 +5,12 @@ import 'package:didvan/models/requests/news.dart';
|
|||
import 'package:didvan/models/requests/radar.dart';
|
||||
import 'package:didvan/providers/core.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_helper.dart';
|
||||
import 'package:didvan/utils/action_sheet.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 {
|
||||
late MainPageContent content;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,12 @@
|
|||
import 'dart:math';
|
||||
|
||||
import 'package:didvan/config/design_config.dart';
|
||||
import 'package:didvan/config/theme_data.dart';
|
||||
import 'package:didvan/constants/app_icons.dart';
|
||||
import 'package:didvan/services/app_initalizer.dart';
|
||||
import 'package:didvan/utils/action_sheet.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/ink_wrapper.dart';
|
||||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.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 {
|
||||
final bool isFirst;
|
||||
|
|
@ -26,7 +22,8 @@ class MainPageBanner extends StatelessWidget {
|
|||
padding: const EdgeInsets.symmetric(horizontal: 4),
|
||||
child: GestureDetector(
|
||||
onTap: () => item.link == null || item.link!.isEmpty
|
||||
? _openInteractiveViewer(context, item.image)
|
||||
? ActionSheetUtils.openInteractiveViewer(
|
||||
context, item.image, false)
|
||||
: launchUrlString(item.link!, mode: LaunchMode.inAppWebView),
|
||||
child: SkeletonImage(
|
||||
imageUrl: item.image,
|
||||
|
|
@ -40,62 +37,4 @@ class MainPageBanner extends StatelessWidget {
|
|||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -69,26 +69,28 @@ class _MainPagePodcastItemState extends State<MainPagePodcastItem> {
|
|||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
SizedBox(
|
||||
width:
|
||||
MediaQuery.of(context).size.width * 2 / 3 - 60,
|
||||
child: AudioWidget(
|
||||
id: widget.content.id,
|
||||
audioUrl: widget.content.link,
|
||||
audioMetaData: StudioDetailsData(
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12.0),
|
||||
child: SizedBox(
|
||||
child: AudioWidget(
|
||||
id: widget.content.id,
|
||||
duration: widget.content.duration!,
|
||||
title: widget.content.title,
|
||||
description: '',
|
||||
image: widget.content.image,
|
||||
link: widget.content.link,
|
||||
iframe: null,
|
||||
createdAt: '',
|
||||
order: 1,
|
||||
marked: widget.content.marked,
|
||||
comments: 0,
|
||||
tags: [],
|
||||
type: 'podcast',
|
||||
audioUrl: widget.content.link,
|
||||
audioMetaData: StudioDetailsData(
|
||||
id: widget.content.id,
|
||||
duration: widget.content.duration!,
|
||||
title: widget.content.title,
|
||||
description: '',
|
||||
image: widget.content.image,
|
||||
link: widget.content.link,
|
||||
iframe: null,
|
||||
createdAt: '',
|
||||
order: 1,
|
||||
marked: widget.content.marked,
|
||||
comments: 0,
|
||||
tags: [],
|
||||
type: 'podcast',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -96,6 +96,9 @@ class _NewStatisticState extends State<NewStatistic> {
|
|||
itemsInStatics(context, state, 3),
|
||||
itemsInStatics(context, state, 4),
|
||||
itemsInStatics(context, state, 5, hasDivider: false),
|
||||
const SizedBox(
|
||||
height: 24,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
|
@ -135,6 +138,7 @@ class _NewStatisticState extends State<NewStatistic> {
|
|||
physics: const ScrollPhysics(),
|
||||
itemCount: state.contents[id].contents.length,
|
||||
itemBuilder: (context, index) => StatMainCard(
|
||||
id: id,
|
||||
statistic: state.contents[id].contents[index],
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import 'package:didvan/views/widgets/skeleton_image.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:didvan/services/app_initalizer.dart';
|
||||
|
||||
class SearchResultItem extends StatelessWidget {
|
||||
final OverviewData item;
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import 'package:didvan/views/widgets/didvan/text.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.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 {
|
||||
const MainCategories({super.key});
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_html/flutter_html.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:didvan/services/app_initalizer.dart';
|
||||
|
||||
class StudioDetailsWidget extends StatelessWidget {
|
||||
final void Function(int id, bool value) onMarkChanged;
|
||||
|
|
@ -65,7 +65,7 @@ class StudioDetailsWidget extends StatelessWidget {
|
|||
key: ValueKey(state.studio.id),
|
||||
data: state.studio.description,
|
||||
onAnchorTap: (href, _, __) =>
|
||||
launchUrl(Uri.parse(href!)),
|
||||
launchUrlString(href!),
|
||||
style: {
|
||||
'*': Style(
|
||||
direction: TextDirection.rtl,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import 'package:didvan/views/widgets/state_handlers/state_handler.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_svg/svg.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
import 'package:didvan/services/app_initalizer.dart';
|
||||
|
||||
class ProfilePage extends StatefulWidget {
|
||||
const ProfilePage({Key? key}) : super(key: key);
|
||||
|
|
@ -222,7 +222,7 @@ class _ProfilePageState extends State<ProfilePage> {
|
|||
icon: DidvanIcons.info_circle_regular,
|
||||
title: 'معرفی دیدوان',
|
||||
onTap: () =>
|
||||
launchUrl(Uri.parse('https://didvan.app/#info')),
|
||||
launchUrlString('https://didvan.app/#info'),
|
||||
),
|
||||
const DidvanDivider(),
|
||||
MenuOption(
|
||||
|
|
@ -232,12 +232,16 @@ class _ProfilePageState extends State<ProfilePage> {
|
|||
state.showContactUs = !state.showContactUs;
|
||||
state.update();
|
||||
},
|
||||
trailing: Icon(
|
||||
state.showContactUs
|
||||
? DidvanIcons.angle_up_regular
|
||||
: DidvanIcons.angle_down_regular,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
trailing: Row(
|
||||
children: [
|
||||
Icon(
|
||||
state.showContactUs
|
||||
? DidvanIcons.angle_up_regular
|
||||
: DidvanIcons.angle_down_regular,
|
||||
size: 18,
|
||||
color: Theme.of(context).colorScheme.title,
|
||||
),
|
||||
],
|
||||
)),
|
||||
AnimatedVisibility(
|
||||
isVisible: state.showContactUs,
|
||||
|
|
@ -316,9 +320,8 @@ class _ProfilePageState extends State<ProfilePage> {
|
|||
MenuOption(
|
||||
icon: DidvanIcons.alert_regular,
|
||||
title: 'حریم خصوصی',
|
||||
onTap: () => launchUrl(
|
||||
Uri.parse(
|
||||
'https://didvan.app/terms-of-use#privacy'),
|
||||
onTap: () => launchUrlString(
|
||||
'https://didvan.app/terms-of-use#privacy',
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,7 +23,7 @@ import 'package:didvan/views/widgets/item_title.dart';
|
|||
import 'package:didvan/views/widgets/skeleton_image.dart';
|
||||
import 'package:flutter/material.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 {
|
||||
final List items;
|
||||
|
|
@ -309,7 +309,7 @@ class _DidvanPageViewState extends State<DidvanPageView> {
|
|||
),
|
||||
);
|
||||
} else {
|
||||
launchUrl(Uri.parse(href));
|
||||
launchUrlString(href);
|
||||
}
|
||||
},
|
||||
style: {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||
decoration: BoxDecoration(
|
||||
borderRadius: const BorderRadius.only(bottomLeft: Radius.circular(20)),
|
||||
color: Theme.of(context).colorScheme.surface,
|
||||
boxShadow: state.currentPageIndex == 1 || state.filtering
|
||||
boxShadow: state.filtering
|
||||
? null
|
||||
: [
|
||||
BoxShadow(
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import 'package:didvan/views/widgets/skeleton_image.dart';
|
|||
import 'package:flutter/material.dart';
|
||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:url_launcher/url_launcher_string.dart';
|
||||
import 'package:didvan/services/app_initalizer.dart';
|
||||
|
||||
class MultitypeOverview extends StatelessWidget {
|
||||
final OverviewData item;
|
||||
|
|
|
|||
|
|
@ -11,6 +11,8 @@ class SkeletonImage extends StatelessWidget {
|
|||
final String imageUrl;
|
||||
final double? width;
|
||||
final double? height;
|
||||
final double? pWidth;
|
||||
final double? pHeight;
|
||||
final BorderRadius? borderRadius;
|
||||
final double? aspectRatio;
|
||||
const SkeletonImage({
|
||||
|
|
@ -20,6 +22,8 @@ class SkeletonImage extends StatelessWidget {
|
|||
this.aspectRatio,
|
||||
this.width,
|
||||
this.height,
|
||||
this.pWidth,
|
||||
this.pHeight,
|
||||
}) : super(key: key);
|
||||
|
||||
@override
|
||||
|
|
@ -39,7 +43,10 @@ class SkeletonImage extends StatelessWidget {
|
|||
imageUrl: imageUrl.startsWith('http')
|
||||
? imageUrl
|
||||
: RequestHelper.baseUrl + imageUrl,
|
||||
placeholder: (context, _) => const ShimmerPlaceholder(),
|
||||
placeholder: (context, _) => ShimmerPlaceholder(
|
||||
width: pWidth,
|
||||
height: pHeight,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
|||
90
pubspec.lock
90
pubspec.lock
|
|
@ -359,7 +359,7 @@ packages:
|
|||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_cache_manager:
|
||||
dependency: transitive
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_cache_manager
|
||||
sha256: "8207f27539deb83732fdda03e259349046a39a4c767269285f449ade355d54ba"
|
||||
|
|
@ -1074,22 +1074,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1154,70 +1138,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
@ -1306,14 +1226,6 @@ packages:
|
|||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
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:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
|
|
|||
|
|
@ -57,12 +57,12 @@ dependencies:
|
|||
bot_toast: ^4.0.1
|
||||
flutter_secure_storage: ^8.0.0
|
||||
flutter_html: ^3.0.0-alpha.2
|
||||
url_launcher: ^6.0.18
|
||||
# url_launcher: ^6.0.18
|
||||
audio_video_progress_bar: ^2.0.0
|
||||
image_cropper: ^1.5.0
|
||||
firebase_core: ^3.1.0
|
||||
firebase_messaging: ^15.0.1
|
||||
webview_flutter: ^4.2.0
|
||||
webview_flutter: ^4.8.0
|
||||
expandable_bottom_sheet: ^1.1.1+1
|
||||
permission_handler: ^11.0.0
|
||||
# better_player:
|
||||
|
|
@ -89,9 +89,10 @@ dependencies:
|
|||
flutter_markdown: ^0.7.3+1
|
||||
file_picker: ^8.0.5
|
||||
marquee: ^2.2.3
|
||||
voice_message_package: ^2.2.1
|
||||
mime: ^1.0.2
|
||||
path: any
|
||||
flutter_cache_manager: any
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
|
|
|||
Loading…
Reference in New Issue