houshvan version 1 -- 11/06/1403 -- Rhmn
This commit is contained in:
parent
5ec13a7a52
commit
23747f5a62
|
|
@ -11,6 +11,7 @@
|
||||||
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
|
||||||
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
<uses-permission android:name="android.permission.ACCESS_NOTIFICATION_POLICY" />
|
||||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||||
|
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
|
|
||||||
|
|
@ -56,5 +56,7 @@
|
||||||
<array>
|
<array>
|
||||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER).MyNotificationServiceExtension</string>
|
<string>$(PRODUCT_BUNDLE_IDENTIFIER).MyNotificationServiceExtension</string>
|
||||||
</array>
|
</array>
|
||||||
|
<key>NSMicrophoneUsageDescription</key>
|
||||||
|
<string>Some message to describe why you need this permission</string>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|
@ -1,8 +1,9 @@
|
||||||
import 'package:didvan/models/ai/bots_model.dart';
|
import 'package:didvan/models/ai/bots_model.dart';
|
||||||
|
import 'package:didvan/models/ai/chats_model.dart';
|
||||||
|
|
||||||
class AiChatArgs {
|
class AiChatArgs {
|
||||||
final BotsModel bot;
|
final BotsModel bot;
|
||||||
final int? chatId;
|
final ChatsModel? chat;
|
||||||
|
|
||||||
AiChatArgs({required this.bot, this.chatId});
|
AiChatArgs({required this.bot, this.chat});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,9 +10,10 @@ class ChatsModel {
|
||||||
String? updatedAt;
|
String? updatedAt;
|
||||||
BotsModel? bot;
|
BotsModel? bot;
|
||||||
List<Prompts>? prompts;
|
List<Prompts>? prompts;
|
||||||
|
bool? isEditing;
|
||||||
|
|
||||||
ChatsModel(
|
ChatsModel({
|
||||||
{this.id,
|
this.id,
|
||||||
this.userId,
|
this.userId,
|
||||||
this.botId,
|
this.botId,
|
||||||
this.title,
|
this.title,
|
||||||
|
|
@ -20,7 +21,9 @@ class ChatsModel {
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.updatedAt,
|
this.updatedAt,
|
||||||
this.bot,
|
this.bot,
|
||||||
this.prompts});
|
this.prompts,
|
||||||
|
this.isEditing = false,
|
||||||
|
});
|
||||||
|
|
||||||
ChatsModel.fromJson(Map<String, dynamic> json) {
|
ChatsModel.fromJson(Map<String, dynamic> json) {
|
||||||
id = json['id'];
|
id = json['id'];
|
||||||
|
|
@ -56,6 +59,31 @@ class ChatsModel {
|
||||||
}
|
}
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ChatsModel copyWith(
|
||||||
|
{int? id,
|
||||||
|
int? userId,
|
||||||
|
int? botId,
|
||||||
|
String? title,
|
||||||
|
String? placeholder,
|
||||||
|
String? createdAt,
|
||||||
|
String? updatedAt,
|
||||||
|
BotsModel? bot,
|
||||||
|
List<Prompts>? prompts,
|
||||||
|
bool? isEditing}) {
|
||||||
|
return ChatsModel(
|
||||||
|
id: id ?? this.id,
|
||||||
|
userId: userId ?? this.userId,
|
||||||
|
botId: botId ?? this.botId,
|
||||||
|
title: title ?? this.title,
|
||||||
|
placeholder: placeholder ?? this.placeholder,
|
||||||
|
bot: bot ?? this.bot,
|
||||||
|
createdAt: createdAt ?? this.createdAt,
|
||||||
|
updatedAt: updatedAt ?? this.updatedAt,
|
||||||
|
prompts: prompts ?? this.prompts,
|
||||||
|
isEditing: isEditing ?? this.isEditing,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Prompts {
|
class Prompts {
|
||||||
|
|
@ -67,16 +95,19 @@ class Prompts {
|
||||||
String? role;
|
String? role;
|
||||||
String? createdAt;
|
String? createdAt;
|
||||||
bool? finished;
|
bool? finished;
|
||||||
|
bool? error;
|
||||||
|
|
||||||
Prompts(
|
Prompts({
|
||||||
{this.id,
|
this.id,
|
||||||
this.chatId,
|
this.chatId,
|
||||||
this.text,
|
this.text,
|
||||||
this.file,
|
this.file,
|
||||||
this.fileName,
|
this.fileName,
|
||||||
this.role,
|
this.role,
|
||||||
this.createdAt,
|
this.createdAt,
|
||||||
this.finished});
|
this.finished,
|
||||||
|
this.error,
|
||||||
|
});
|
||||||
|
|
||||||
Prompts.fromJson(Map<String, dynamic> json) {
|
Prompts.fromJson(Map<String, dynamic> json) {
|
||||||
id = json['id'];
|
id = json['id'];
|
||||||
|
|
@ -100,8 +131,8 @@ class Prompts {
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
Prompts copyWith({
|
Prompts copyWith(
|
||||||
int? id,
|
{int? id,
|
||||||
int? chatId,
|
int? chatId,
|
||||||
String? text,
|
String? text,
|
||||||
String? file,
|
String? file,
|
||||||
|
|
@ -109,7 +140,7 @@ class Prompts {
|
||||||
String? role,
|
String? role,
|
||||||
String? createdAt,
|
String? createdAt,
|
||||||
bool? finished,
|
bool? finished,
|
||||||
}) {
|
bool? error}) {
|
||||||
return Prompts(
|
return Prompts(
|
||||||
id: id ?? this.id,
|
id: id ?? this.id,
|
||||||
chatId: chatId ?? this.chatId,
|
chatId: chatId ?? this.chatId,
|
||||||
|
|
@ -119,6 +150,7 @@ class Prompts {
|
||||||
role: role ?? this.role,
|
role: role ?? this.role,
|
||||||
createdAt: createdAt ?? this.createdAt,
|
createdAt: createdAt ?? this.createdAt,
|
||||||
finished: finished ?? this.finished,
|
finished: finished ?? this.finished,
|
||||||
|
error: error ?? this.error,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
|
||||||
|
class FilesModel {
|
||||||
|
final String path;
|
||||||
|
late String basename;
|
||||||
|
late String extname;
|
||||||
|
late File main;
|
||||||
|
|
||||||
|
FilesModel(this.path) {
|
||||||
|
basename = p.basename(path);
|
||||||
|
extname = p.extension(path);
|
||||||
|
main = File(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -12,6 +12,7 @@ class ActionSheetData {
|
||||||
final Color? titleColor;
|
final Color? titleColor;
|
||||||
final bool hasDismissButton;
|
final bool hasDismissButton;
|
||||||
final bool hasConfirmButton;
|
final bool hasConfirmButton;
|
||||||
|
final bool hasConfirmButtonClose;
|
||||||
final bool withoutButtonMode;
|
final bool withoutButtonMode;
|
||||||
final bool smallDismissButton;
|
final bool smallDismissButton;
|
||||||
final bool isBackgroundDropBlur;
|
final bool isBackgroundDropBlur;
|
||||||
|
|
@ -32,6 +33,7 @@ class ActionSheetData {
|
||||||
this.smallDismissButton = false,
|
this.smallDismissButton = false,
|
||||||
this.withoutButtonMode = false,
|
this.withoutButtonMode = false,
|
||||||
this.isBackgroundDropBlur = false,
|
this.isBackgroundDropBlur = false,
|
||||||
|
this.hasConfirmButtonClose = true,
|
||||||
this.backgroundColor,
|
this.backgroundColor,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -306,6 +306,10 @@ class RouteGenerator {
|
||||||
child: AiChatPage(
|
child: AiChatPage(
|
||||||
args: settings.arguments as AiChatArgs,
|
args: settings.arguments as AiChatArgs,
|
||||||
)));
|
)));
|
||||||
|
case Routes.aiHistory:
|
||||||
|
return _createRoute(HistoryAiChatPage(
|
||||||
|
archived: settings.arguments as bool?,
|
||||||
|
));
|
||||||
default:
|
default:
|
||||||
return _errorRoute(settings.name ?? '');
|
return _errorRoute(settings.name ?? '');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
class Routes {
|
class Routes {
|
||||||
static const String splash = '/';
|
static const String splash = '/';
|
||||||
static const String aiChat = '/ai-chat';
|
static const String aiChat = '/ai-chat';
|
||||||
|
static const String aiHistory = '/ai-history';
|
||||||
|
|
||||||
static const String home = '/home';
|
static const String home = '/home';
|
||||||
static const String radars = '/radars';
|
static const String radars = '/radars';
|
||||||
static const String news = '/news';
|
static const String news = '/news';
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,17 @@
|
||||||
// ignore_for_file: depend_on_referenced_packages
|
// ignore_for_file: depend_on_referenced_packages
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
import 'package:didvan/services/storage/storage.dart';
|
import 'package:didvan/services/storage/storage.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:http/http.dart';
|
import 'package:mime/mime.dart';
|
||||||
import 'package:path/path.dart' as p;
|
import 'package:path/path.dart' as p;
|
||||||
import 'package:http_parser/http_parser.dart' as parser;
|
import 'package:http_parser/http_parser.dart' as parser;
|
||||||
|
|
||||||
class AiApiService {
|
class AiApiService {
|
||||||
static const String baseUrl = 'https://api.didvan.app/ai';
|
static const String baseUrl = 'https://api.didvan.app/ai';
|
||||||
static final _client = http.Client();
|
|
||||||
|
|
||||||
static Future<http.MultipartRequest> initial(
|
static Future<http.MultipartRequest> initial(
|
||||||
{required final String url,
|
{required final String url,
|
||||||
|
|
@ -33,25 +33,40 @@ class AiApiService {
|
||||||
if (file != null) {
|
if (file != null) {
|
||||||
final length = await file.length();
|
final length = await file.length();
|
||||||
String basename = p.basename(file.path);
|
String basename = p.basename(file.path);
|
||||||
String mediaExtension = p.extension(file.path).replaceAll('.', '');
|
String? mimeType =
|
||||||
String mediaFormat = 'image';
|
lookupMimeType(file.path); // Use MIME type instead of file extension
|
||||||
if (mediaExtension.contains('pdf')) {
|
mimeType ??= 'application/octet-stream';
|
||||||
mediaFormat = 'application';
|
if (mimeType.startsWith('audio')) {
|
||||||
|
mimeType = 'audio/${p.extension(file.path).replaceAll('.', '')}';
|
||||||
}
|
}
|
||||||
request.files.add(
|
request.files.add(
|
||||||
http.MultipartFile('file', file.readAsBytes().asStream(), length,
|
http.MultipartFile('file', file.readAsBytes().asStream(), length,
|
||||||
filename: basename,
|
filename: basename,
|
||||||
contentType: parser.MediaType(mediaFormat, mediaExtension)),
|
contentType: parser.MediaType.parse(
|
||||||
|
mimeType)), // Use MediaType.parse to parse the MIME type
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return request;
|
return request;
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<ByteStream> getResponse(http.MultipartRequest request) async {
|
static Future<Stream<List<int>>> getResponse(
|
||||||
final res = _client.send(request).timeout(
|
http.MultipartRequest req) async {
|
||||||
const Duration(seconds: 30),
|
try {
|
||||||
);
|
final response = await http.Client().send(req);
|
||||||
final http.StreamedResponse response = await res;
|
if (response.statusCode == 400) {
|
||||||
|
// Handle 400 response
|
||||||
|
final errorResponse = await response.stream.bytesToString();
|
||||||
|
final errorJson = jsonDecode(errorResponse);
|
||||||
|
throw Exception(errorJson['error'] ?? 'Bad Request');
|
||||||
|
} else if (response.statusCode != 200) {
|
||||||
|
// Handle other non-200 responses
|
||||||
|
throw Exception('Failed to load data');
|
||||||
|
} else {
|
||||||
return response.stream;
|
return response.stream;
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Handle any other errors
|
||||||
|
throw Exception('Failed to load data');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
|
|
||||||
import 'package:didvan/main.dart';
|
import 'package:didvan/main.dart';
|
||||||
import 'package:didvan/models/notification_message.dart';
|
import 'package:didvan/models/notification_message.dart';
|
||||||
import 'package:didvan/models/requests/news.dart';
|
import 'package:didvan/models/requests/news.dart';
|
||||||
|
|
@ -171,4 +170,24 @@ class AppInitializer {
|
||||||
int id = int.parse('${data.userId}${data.id}$t');
|
int id = int.parse('${data.userId}${data.id}$t');
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Map<String, int> messagesData(String dataMessgae) {
|
||||||
|
dataMessgae = dataMessgae.replaceAll('{{{', '');
|
||||||
|
dataMessgae = dataMessgae.replaceAll('}}}', '');
|
||||||
|
final pairs = dataMessgae.substring(1, dataMessgae.length - 1).split(',');
|
||||||
|
|
||||||
|
// Create a map to store the key-value pairs
|
||||||
|
final data = <String, int>{};
|
||||||
|
// Iterate over the key-value pairs and add them to the map
|
||||||
|
for (final pair in pairs) {
|
||||||
|
final keyValue = pair.split(':');
|
||||||
|
if (keyValue.length == 2) {
|
||||||
|
final key = keyValue[0].trim().replaceAll('"', '');
|
||||||
|
final value = keyValue[1].trim().replaceAll('"', '');
|
||||||
|
data[key] = int.parse(value.replaceAll(' ', ''));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -109,4 +109,11 @@ class MediaService {
|
||||||
);
|
);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<FilePickerResult?> pickAudioFile() async {
|
||||||
|
return await FilePicker.platform.pickFiles(
|
||||||
|
type: FileType.audio,
|
||||||
|
allowMultiple: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ class RequestService {
|
||||||
headers: _headers,
|
headers: _headers,
|
||||||
)
|
)
|
||||||
.timeout(
|
.timeout(
|
||||||
const Duration(seconds: 100),
|
const Duration(seconds: 30),
|
||||||
)
|
)
|
||||||
.catchError(
|
.catchError(
|
||||||
(e) => throw e,
|
(e) => throw e,
|
||||||
|
|
@ -86,7 +86,7 @@ class RequestService {
|
||||||
headers: _headers,
|
headers: _headers,
|
||||||
)
|
)
|
||||||
.timeout(
|
.timeout(
|
||||||
const Duration(seconds: 100),
|
const Duration(seconds: 30),
|
||||||
)
|
)
|
||||||
.catchError(
|
.catchError(
|
||||||
(e) {
|
(e) {
|
||||||
|
|
@ -108,7 +108,7 @@ class RequestService {
|
||||||
headers: _headers,
|
headers: _headers,
|
||||||
)
|
)
|
||||||
.timeout(
|
.timeout(
|
||||||
const Duration(seconds: 100),
|
const Duration(seconds: 30),
|
||||||
)
|
)
|
||||||
.catchError(
|
.catchError(
|
||||||
(e) => throw e,
|
(e) => throw e,
|
||||||
|
|
@ -149,7 +149,7 @@ class RequestService {
|
||||||
final streamedResponse = await request
|
final streamedResponse = await request
|
||||||
.send()
|
.send()
|
||||||
.timeout(
|
.timeout(
|
||||||
const Duration(seconds: 100),
|
const Duration(seconds: 30),
|
||||||
)
|
)
|
||||||
.catchError(
|
.catchError(
|
||||||
(e) => throw e,
|
(e) => throw e,
|
||||||
|
|
@ -170,7 +170,7 @@ class RequestService {
|
||||||
headers: _headers,
|
headers: _headers,
|
||||||
)
|
)
|
||||||
.timeout(
|
.timeout(
|
||||||
const Duration(seconds: 100),
|
const Duration(seconds: 30),
|
||||||
)
|
)
|
||||||
.catchError(
|
.catchError(
|
||||||
(e) => throw e,
|
(e) => throw e,
|
||||||
|
|
|
||||||
|
|
@ -201,14 +201,33 @@ class RequestHelper {
|
||||||
static String reportComment(int id) => '$baseUrl/comment/$id/report';
|
static String reportComment(int id) => '$baseUrl/comment/$id/report';
|
||||||
static String widgetNews() => '$baseUrl/user/widget';
|
static String widgetNews() => '$baseUrl/user/widget';
|
||||||
static String aiChats() => '$baseUrl/ai/chat';
|
static String aiChats() => '$baseUrl/ai/chat';
|
||||||
|
static String aiArchived() => '$baseUrl/ai/chat${_urlConcatGenerator([
|
||||||
|
const MapEntry('archived', true),
|
||||||
|
])}';
|
||||||
static String aiBots() => '$baseUrl/ai/bot';
|
static String aiBots() => '$baseUrl/ai/bot';
|
||||||
static String aiSearchBots(String q) =>
|
static String aiSearchBots(String q) =>
|
||||||
'$baseUrl/ai/bot${_urlConcatGenerator([
|
'$baseUrl/ai/bot${_urlConcatGenerator([
|
||||||
MapEntry('q', q),
|
MapEntry('q', q),
|
||||||
])}';
|
])}';
|
||||||
|
static String aiSearchChats(String q) =>
|
||||||
|
'$baseUrl/ai/chat${_urlConcatGenerator([
|
||||||
|
MapEntry('q', q),
|
||||||
|
])}';
|
||||||
|
static String aiSearchArchived(String q) =>
|
||||||
|
'$baseUrl/ai/chat${_urlConcatGenerator([
|
||||||
|
MapEntry('q', q),
|
||||||
|
const MapEntry('archived', true),
|
||||||
|
])}';
|
||||||
static String aiAChat(int id) => '$baseUrl/ai/chat/$id';
|
static String aiAChat(int id) => '$baseUrl/ai/chat/$id';
|
||||||
static String aiChatId() => '$baseUrl/ai/chat/id';
|
static String aiChatId() => '$baseUrl/ai/chat/id';
|
||||||
static String aiDeleteChats() => '$baseUrl/ai/chat';
|
static String aiDeleteChats() => '$baseUrl/ai/chat';
|
||||||
|
static String aiChangeChats(int id) => '$baseUrl/ai/chat/$id/title';
|
||||||
|
static String deleteChat(int id) => '$baseUrl/ai/chat/$id';
|
||||||
|
static String deleteMessage(int chatId, int messageId) =>
|
||||||
|
'$baseUrl/ai/chat/$chatId/message/$messageId';
|
||||||
|
static String deleteAllChats() => '$baseUrl/ai/chat/all';
|
||||||
|
static String archivedChat(int id) => '$baseUrl/ai/chat/$id/archive';
|
||||||
|
static String placeholder(int id) => '$baseUrl/ai/chat/$id/placeholder';
|
||||||
|
|
||||||
static String _urlConcatGenerator(List<MapEntry<String, dynamic>> additions) {
|
static String _urlConcatGenerator(List<MapEntry<String, dynamic>> additions) {
|
||||||
String result = '';
|
String result = '';
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,17 @@ import 'dart:async';
|
||||||
import 'dart:ui';
|
import 'dart:ui';
|
||||||
|
|
||||||
import 'package:bot_toast/bot_toast.dart';
|
import 'package:bot_toast/bot_toast.dart';
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
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/assets.dart';
|
import 'package:didvan/constants/assets.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/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/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';
|
||||||
|
|
||||||
|
|
@ -253,8 +256,10 @@ class ActionSheetUtils {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: DidvanButton(
|
child: DidvanButton(
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
pop();
|
|
||||||
data.onConfirmed?.call();
|
data.onConfirmed?.call();
|
||||||
|
if (data.hasConfirmButtonClose) {
|
||||||
|
pop();
|
||||||
|
}
|
||||||
},
|
},
|
||||||
title: data.confrimTitle ?? 'تایید',
|
title: data.confrimTitle ?? 'تایید',
|
||||||
),
|
),
|
||||||
|
|
@ -269,6 +274,117 @@ class ActionSheetUtils {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future<void> botsDialogSelect(
|
||||||
|
{required final BuildContext context,
|
||||||
|
required final HistoryAiChatState state}) async {
|
||||||
|
ActionSheetUtils.context = context;
|
||||||
|
ActionSheetUtils.openDialog(
|
||||||
|
data: ActionSheetData(
|
||||||
|
hasConfirmButton: false,
|
||||||
|
hasDismissButton: false,
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
// Row(
|
||||||
|
// mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
// children: [
|
||||||
|
// Padding(
|
||||||
|
// padding: const EdgeInsets.symmetric(vertical: 8.0),
|
||||||
|
// child: InkWell(
|
||||||
|
// onTap: () {
|
||||||
|
// ActionSheetUtils.pop();
|
||||||
|
// },
|
||||||
|
// child: const Icon(DidvanIcons.close_solid)),
|
||||||
|
// )
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// SearchField(
|
||||||
|
// title: 'هوش مصنوعی',
|
||||||
|
// value: state.search,
|
||||||
|
// onChanged: (value) {
|
||||||
|
// state.search = value;
|
||||||
|
// if (value.isEmpty) {
|
||||||
|
// state.getBots();
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// state.timer?.cancel();
|
||||||
|
// state.timer = Timer(const Duration(seconds: 1), () {
|
||||||
|
// state.getSearchBots(value);
|
||||||
|
// });
|
||||||
|
// },
|
||||||
|
// focusNode: FocusNode()),
|
||||||
|
// const SizedBox(
|
||||||
|
// height: 12,
|
||||||
|
// ),
|
||||||
|
SizedBox(
|
||||||
|
width: double.infinity,
|
||||||
|
height: MediaQuery.sizeOf(context).height / 3,
|
||||||
|
child: ValueListenableBuilder<bool>(
|
||||||
|
valueListenable: state.loadingBots,
|
||||||
|
builder: (context, value, child) => value
|
||||||
|
? Center(
|
||||||
|
child: Image.asset(
|
||||||
|
Assets.loadingAnimation,
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: state.bots.isEmpty
|
||||||
|
? Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 12.0),
|
||||||
|
child: EmptyState(
|
||||||
|
asset: Assets.emptyResult,
|
||||||
|
title: 'نتیجهای پیدا نشد',
|
||||||
|
height: 120,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(vertical: 12),
|
||||||
|
itemCount: state.bots.length,
|
||||||
|
physics: const BouncingScrollPhysics(),
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemBuilder: (context, index) {
|
||||||
|
final bot = state.bots[index];
|
||||||
|
return InkWell(
|
||||||
|
onTap: () {
|
||||||
|
ActionSheetUtils.pop();
|
||||||
|
state.bot = bot;
|
||||||
|
state.update();
|
||||||
|
},
|
||||||
|
child: Container(
|
||||||
|
alignment: Alignment.center,
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 8),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
border: Border(
|
||||||
|
bottom: BorderSide(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.border,
|
||||||
|
width: 1))),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ClipOval(
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: bot.image.toString(),
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
Text(bot.name.toString())
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
static void pop() {
|
static void pop() {
|
||||||
DesignConfig.updateSystemUiOverlayStyle();
|
DesignConfig.updateSystemUiOverlayStyle();
|
||||||
Navigator.of(context).pop();
|
Navigator.of(context).pop();
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,18 @@
|
||||||
// ignore_for_file: library_private_types_in_public_api
|
// ignore_for_file: library_private_types_in_public_api
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
|
import 'package:didvan/config/design_config.dart';
|
||||||
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
|
import 'package:didvan/constants/assets.dart';
|
||||||
|
import 'package:didvan/models/ai/ai_chat_args.dart';
|
||||||
|
import 'package:didvan/routes/routes.dart';
|
||||||
|
import 'package:didvan/utils/action_sheet.dart';
|
||||||
|
import 'package:didvan/views/ai/history_ai_chat_state.dart';
|
||||||
|
import 'package:didvan/views/home/home.dart';
|
||||||
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class Ai extends StatefulWidget {
|
class Ai extends StatefulWidget {
|
||||||
const Ai({Key? key}) : super(key: key);
|
const Ai({Key? key}) : super(key: key);
|
||||||
|
|
@ -10,8 +22,162 @@ class Ai extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AiState extends State<Ai> {
|
class _AiState extends State<Ai> {
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
final state = context.read<HistoryAiChatState>();
|
||||||
|
Future.delayed(
|
||||||
|
Duration.zero,
|
||||||
|
() {
|
||||||
|
// state.getChats();
|
||||||
|
state.getBots();
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Container();
|
return Consumer<HistoryAiChatState>(
|
||||||
|
builder: (context, state, child) {
|
||||||
|
if (state.bots.isEmpty) {
|
||||||
|
return Center(
|
||||||
|
child: Image.asset(
|
||||||
|
Assets.loadingAnimation,
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
final bot = state.bot!;
|
||||||
|
return Stack(
|
||||||
|
children: [
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
DidvanIcons.ai_solid,
|
||||||
|
size: MediaQuery.sizeOf(context).width / 5,
|
||||||
|
),
|
||||||
|
const DidvanText('هوشان'),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () => ActionSheetUtils.botsDialogSelect(
|
||||||
|
context: context, state: state),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const Icon(DidvanIcons.caret_down_solid),
|
||||||
|
Text(bot.name.toString()),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.sizeOf(context).height / 5,
|
||||||
|
height: MediaQuery.sizeOf(context).height / 5,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: DesignConfig.highBorderRadius,
|
||||||
|
color: Theme.of(context).colorScheme.focused),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: bot.image.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Text(
|
||||||
|
"به هوشان, هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.\n دریافت پاسخ از: ${bot.name}",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
bottom: 32,
|
||||||
|
left: 20,
|
||||||
|
right: 20,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => Navigator.of(context).pushNamed(Routes.aiChat,
|
||||||
|
arguments: AiChatArgs(
|
||||||
|
bot: bot,
|
||||||
|
)),
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
boxShadow: DesignConfig.defaultShadow,
|
||||||
|
color: Theme.of(context).colorScheme.white,
|
||||||
|
borderRadius: BorderRadius.circular(360)),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: Theme.of(context).colorScheme.border),
|
||||||
|
child: const Icon(
|
||||||
|
DidvanIcons.mic_regular,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
),
|
||||||
|
child: Form(
|
||||||
|
child: TextFormField(
|
||||||
|
textInputAction: TextInputAction.newline,
|
||||||
|
style: Theme.of(context).textTheme.bodyMedium,
|
||||||
|
minLines: 1,
|
||||||
|
enabled: false,
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: 'بنویسید...',
|
||||||
|
hintStyle: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall!
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.disabledText),
|
||||||
|
),
|
||||||
|
))))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 32,
|
||||||
|
right: 0,
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () => homeScaffKey.currentState!.openDrawer(),
|
||||||
|
child: Container(
|
||||||
|
width: 46,
|
||||||
|
height: 46,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context).colorScheme.white,
|
||||||
|
borderRadius: const BorderRadius.only(
|
||||||
|
topLeft: Radius.circular(12),
|
||||||
|
bottomLeft: Radius.circular(12)),
|
||||||
|
boxShadow: DesignConfig.defaultShadow),
|
||||||
|
child: const Icon(DidvanIcons.angle_left_light),
|
||||||
|
)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,39 +1,32 @@
|
||||||
// ignore_for_file: library_private_types_in_public_api, deprecated_member_use, depend_on_referenced_packages
|
// ignore_for_file: library_private_types_in_public_api, deprecated_member_use, depend_on_referenced_packages
|
||||||
|
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:cached_network_image/cached_network_image.dart';
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
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/constants/assets.dart';
|
import 'package:didvan/constants/assets.dart';
|
||||||
import 'package:didvan/main.dart';
|
|
||||||
import 'package:didvan/models/ai/ai_chat_args.dart';
|
import 'package:didvan/models/ai/ai_chat_args.dart';
|
||||||
import 'package:didvan/models/ai/chats_model.dart';
|
import 'package:didvan/models/ai/chats_model.dart';
|
||||||
import 'package:didvan/models/ai/messages_model.dart';
|
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/alert_data.dart';
|
import 'package:didvan/models/view/alert_data.dart';
|
||||||
import 'package:didvan/services/media/media.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/widgets/animated_visibility.dart';
|
import 'package:didvan/views/ai/widgets/ai_message_bar.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/state_handlers/empty_state.dart';
|
|
||||||
import 'package:file_picker/file_picker.dart';
|
|
||||||
import 'package:flutter/foundation.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:image_cropper/image_cropper.dart';
|
import 'package:mime/mime.dart';
|
||||||
import 'package:image_picker/image_picker.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:path/path.dart' as p;
|
import 'package:voice_message_package/voice_message_package.dart';
|
||||||
|
|
||||||
class AiChatPage extends StatefulWidget {
|
class AiChatPage extends StatefulWidget {
|
||||||
final AiChatArgs args;
|
final AiChatArgs args;
|
||||||
|
|
@ -44,15 +37,32 @@ class AiChatPage extends StatefulWidget {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _AiChatPageState extends State<AiChatPage> {
|
class _AiChatPageState extends State<AiChatPage> {
|
||||||
TextEditingController message = TextEditingController();
|
FocusNode focusNode = FocusNode();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
final state = context.read<AiChatState>();
|
final state = context.read<AiChatState>();
|
||||||
state.chatId = widget.args.chatId;
|
if (widget.args.chat != null) {
|
||||||
if (state.chatId != null) {
|
state.chatId = widget.args.chat!.id!;
|
||||||
state.getAllMessages(state.chatId!);
|
state.chat = widget.args.chat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
if (state.chatId != null) {
|
||||||
|
state.getAllMessages(state.chatId!).then((value) => Future.delayed(
|
||||||
|
const Duration(
|
||||||
|
milliseconds: 100,
|
||||||
|
),
|
||||||
|
() => focusNode.requestFocus(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
Future.delayed(
|
||||||
|
const Duration(
|
||||||
|
milliseconds: 100,
|
||||||
|
),
|
||||||
|
() => focusNode.requestFocus(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,38 +73,126 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
context.read<HistoryAiChatState>().getChats();
|
context.read<HistoryAiChatState>().getChats();
|
||||||
return true;
|
return true;
|
||||||
},
|
},
|
||||||
child: Scaffold(
|
child: Consumer<AiChatState>(
|
||||||
|
builder: (context, state, child) => Scaffold(
|
||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
shadowColor: Theme.of(context).colorScheme.border,
|
shadowColor: Theme.of(context).colorScheme.border,
|
||||||
title: Row(
|
title: Text(widget.args.bot.name.toString()),
|
||||||
|
leading: Row(
|
||||||
children: [
|
children: [
|
||||||
Padding(
|
DidvanIconButton(
|
||||||
padding: const EdgeInsets.all(12.0),
|
icon: DidvanIcons.angle_right_solid,
|
||||||
child: ClipOval(
|
onPressed: () {
|
||||||
child: CachedNetworkImage(
|
Navigator.of(context).pop();
|
||||||
width: 32,
|
context.read<HistoryAiChatState>().getChats();
|
||||||
height: 32,
|
},
|
||||||
imageUrl: widget.args.bot.image.toString(),
|
|
||||||
),
|
),
|
||||||
),
|
|
||||||
),
|
|
||||||
Text('چت با ${widget.args.bot.name}'),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
automaticallyImplyLeading: false,
|
|
||||||
actions: [
|
actions: [
|
||||||
DidvanIconButton(
|
if (state.chatId != null)
|
||||||
icon: DidvanIcons.angle_left_regular,
|
Padding(
|
||||||
onPressed: () {
|
padding: const EdgeInsets.only(left: 8.0),
|
||||||
context.read<HistoryAiChatState>().getChats();
|
child: InkWell(
|
||||||
navigatorKey.currentState!.pop();
|
onTap: () {
|
||||||
|
final TextEditingController placeholder =
|
||||||
|
TextEditingController(
|
||||||
|
text: state.chat?.placeholder);
|
||||||
|
ActionSheetUtils.openDialog(
|
||||||
|
data: ActionSheetData(
|
||||||
|
hasConfirmButtonClose: false,
|
||||||
|
onConfirmed: () async {
|
||||||
|
final state = context.read<AiChatState>();
|
||||||
|
await state
|
||||||
|
.changePlaceHolder(placeholder.text);
|
||||||
|
ActionSheetUtils.pop();
|
||||||
},
|
},
|
||||||
|
content: ValueListenableBuilder<bool>(
|
||||||
|
valueListenable: state.changingPlaceHolder,
|
||||||
|
builder: (context, value, child) => Column(
|
||||||
|
children: [
|
||||||
|
Stack(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
DidvanText(
|
||||||
|
'شخصیسازی دستورات',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.titleMedium,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: Center(
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
ActionSheetUtils.pop();
|
||||||
|
},
|
||||||
|
child: const Icon(
|
||||||
|
DidvanIcons.close_solid,
|
||||||
|
size: 24,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
const DidvanText(
|
||||||
|
'دوست دارید هوشان، چه چیزهایی را درباره شما بداند تا بتواند پاسخهای بهتری ارائه دهد؟ \nدستورات و اطلاعات ارائه شما، بر روی تمامی پیامهایی که از این به بعد ارسال میکنید، اعمال خواهد شد.'),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
value
|
||||||
|
? Center(
|
||||||
|
child: Image.asset(
|
||||||
|
Assets.loadingAnimation,
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: TextField(
|
||||||
|
controller: placeholder,
|
||||||
|
style: (Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium)!
|
||||||
|
.copyWith(
|
||||||
|
fontFamily: DesignConfig
|
||||||
|
.fontFamily
|
||||||
|
.padRight(3)),
|
||||||
|
decoration: InputDecoration(
|
||||||
|
filled: true,
|
||||||
|
fillColor: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.secondCTA,
|
||||||
|
contentPadding:
|
||||||
|
const EdgeInsets.fromLTRB(
|
||||||
|
10, 10, 10, 0),
|
||||||
|
border: const OutlineInputBorder(
|
||||||
|
borderRadius: DesignConfig
|
||||||
|
.lowBorderRadius),
|
||||||
|
errorStyle: const TextStyle(
|
||||||
|
height: 0.01),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
body: Consumer<AiChatState>(
|
)));
|
||||||
builder: (BuildContext context, AiChatState state, Widget? child) =>
|
},
|
||||||
state.loading
|
child: const Icon(Icons.shopping_bag_outlined)),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
centerTitle: true,
|
||||||
|
automaticallyImplyLeading: false,
|
||||||
|
),
|
||||||
|
body: state.loading
|
||||||
? Center(
|
? Center(
|
||||||
child: Image.asset(
|
child: Image.asset(
|
||||||
Assets.loadingAnimation,
|
Assets.loadingAnimation,
|
||||||
|
|
@ -103,11 +201,45 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
: state.messages.isEmpty
|
: state.messages.isEmpty
|
||||||
? Center(
|
? Column(
|
||||||
child: EmptyState(
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
asset: Assets.emptyChat,
|
children: [
|
||||||
title: 'اولین پیام را بنویسید...',
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
),
|
),
|
||||||
|
Center(
|
||||||
|
child: Icon(
|
||||||
|
DidvanIcons.ai_solid,
|
||||||
|
size: MediaQuery.sizeOf(context).width / 5,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const DidvanText('هوشان'),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
Container(
|
||||||
|
width: MediaQuery.sizeOf(context).height / 5,
|
||||||
|
height: MediaQuery.sizeOf(context).height / 5,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: DesignConfig.highBorderRadius,
|
||||||
|
color: Theme.of(context).colorScheme.focused),
|
||||||
|
padding: const EdgeInsets.all(12),
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: widget.args.bot.image.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Text(
|
||||||
|
"به هوشان, هوش مصنوعی دیدوان خوش آمدید. \nبرای شروع گفتگو پیام مورد نظر خود را در کادر زیر بنویسید.\n دریافت پاسخ از: ${widget.args.bot.name}",
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
: SingleChildScrollView(
|
: SingleChildScrollView(
|
||||||
reverse: true,
|
reverse: true,
|
||||||
|
|
@ -138,297 +270,19 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
);
|
);
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
),
|
bottomSheet: Column(
|
||||||
bottomSheet: Consumer<AiChatState>(
|
|
||||||
builder: (BuildContext context, AiChatState state, Widget? child) {
|
|
||||||
return Container(
|
|
||||||
width: MediaQuery.sizeOf(context).width,
|
|
||||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
top: BorderSide(
|
|
||||||
color: Theme.of(context).colorScheme.cardBorder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
color: Theme.of(context).colorScheme.surface,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (widget.args.bot.attachment! == 2) fileContainer(context),
|
Padding(
|
||||||
Row(
|
padding: const EdgeInsets.only(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
top: 8, bottom: 24.0, left: 12, right: 12),
|
||||||
children: [
|
child: AiMessageBar(
|
||||||
const SizedBox(
|
bot: widget.args.bot,
|
||||||
width: 12,
|
focusNode: focusNode,
|
||||||
),
|
|
||||||
SizedBox(
|
|
||||||
width: 46,
|
|
||||||
height: 46,
|
|
||||||
child: Center(
|
|
||||||
child: state.onResponsing
|
|
||||||
? Center(
|
|
||||||
child: SpinKitThreeBounce(
|
|
||||||
size: 18,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.focusedBorder,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: DidvanIconButton(
|
|
||||||
icon: DidvanIcons.send_solid,
|
|
||||||
size: 32,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.focusedBorder,
|
|
||||||
onPressed: () async {
|
|
||||||
if (state.file == null &&
|
|
||||||
message.text.isEmpty) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (state.messages.isNotEmpty &&
|
|
||||||
DateTime.parse(
|
|
||||||
state.messages.last.dateTime)
|
|
||||||
.toPersianDateStr()
|
|
||||||
.contains(DateTime.parse(
|
|
||||||
DateTime.now()
|
|
||||||
.subtract(
|
|
||||||
const Duration(
|
|
||||||
minutes: 210))
|
|
||||||
.toIso8601String())
|
|
||||||
.toPersianDateStr())) {
|
|
||||||
state.messages.last.prompts.add(Prompts(
|
|
||||||
text: message.text,
|
|
||||||
file: p.basename(state.file!.path),
|
|
||||||
fileName: p.basename(state.file!.path),
|
|
||||||
finished: true,
|
|
||||||
role: 'user',
|
|
||||||
createdAt: DateTime.now()
|
|
||||||
.subtract(
|
|
||||||
const Duration(minutes: 210))
|
|
||||||
.toIso8601String(),
|
|
||||||
));
|
|
||||||
} else {
|
|
||||||
state.messages.add(MessageModel(
|
|
||||||
dateTime: DateTime.now()
|
|
||||||
.subtract(
|
|
||||||
const Duration(minutes: 210))
|
|
||||||
.toIso8601String(),
|
|
||||||
prompts: [
|
|
||||||
Prompts(
|
|
||||||
text: message.text,
|
|
||||||
finished: true,
|
|
||||||
file:
|
|
||||||
p.basename(state.file!.path),
|
|
||||||
fileName:
|
|
||||||
p.basename(state.file!.path),
|
|
||||||
role: 'user',
|
|
||||||
createdAt: DateTime.now()
|
|
||||||
.subtract(const Duration(
|
|
||||||
minutes: 210))
|
|
||||||
.toIso8601String(),
|
|
||||||
)
|
|
||||||
]));
|
|
||||||
}
|
|
||||||
|
|
||||||
await state.postMessage(widget.args.bot);
|
|
||||||
message.clear();
|
|
||||||
state.file = null;
|
|
||||||
},
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 12,
|
|
||||||
),
|
|
||||||
Expanded(
|
|
||||||
child: Form(
|
|
||||||
child: widget.args.bot.attachment! != 1
|
|
||||||
? TextFormField(
|
|
||||||
textInputAction: TextInputAction.newline,
|
|
||||||
style: Theme.of(context).textTheme.bodyMedium,
|
|
||||||
maxLines: 6,
|
|
||||||
minLines: 1,
|
|
||||||
// keyboardType: TextInputType.text,
|
|
||||||
controller: message,
|
|
||||||
enabled: !state.onResponsing,
|
|
||||||
decoration: InputDecoration(
|
|
||||||
border: InputBorder.none,
|
|
||||||
hintText: 'بنویسید...',
|
|
||||||
hintStyle: Theme.of(context)
|
|
||||||
.textTheme
|
|
||||||
.bodySmall!
|
|
||||||
.copyWith(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.disabledText),
|
|
||||||
),
|
|
||||||
onChanged: (value) {},
|
|
||||||
)
|
|
||||||
: fileContainer(context),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(
|
|
||||||
width: 12,
|
|
||||||
),
|
|
||||||
if (widget.args.bot.attachment! != 0)
|
|
||||||
SizedBox(
|
|
||||||
width: 46,
|
|
||||||
height: 46,
|
|
||||||
child: Center(
|
|
||||||
child: PopupMenuButton(
|
|
||||||
onSelected: (value) async {
|
|
||||||
switch (value) {
|
|
||||||
case 'Pdf':
|
|
||||||
FilePickerResult? result =
|
|
||||||
await MediaService.pickPdfFile();
|
|
||||||
if (result != null) {
|
|
||||||
final File file =
|
|
||||||
File(result.files.single.path!);
|
|
||||||
state.file = file;
|
|
||||||
// Do something with the selected PDF file
|
|
||||||
}
|
|
||||||
// else {
|
|
||||||
//// User cancelled the file selection
|
|
||||||
// }
|
|
||||||
break;
|
|
||||||
|
|
||||||
case 'Image':
|
|
||||||
final pickedFile =
|
|
||||||
await MediaService.pickImage(
|
|
||||||
source: ImageSource.gallery);
|
|
||||||
File? file;
|
|
||||||
if (pickedFile != null && !kIsWeb) {
|
|
||||||
file = await ImageCropper().cropImage(
|
|
||||||
sourcePath: pickedFile.path,
|
|
||||||
androidUiSettings:
|
|
||||||
const AndroidUiSettings(
|
|
||||||
toolbarTitle: 'برش تصویر'),
|
|
||||||
iosUiSettings: const IOSUiSettings(
|
|
||||||
title: 'برش تصویر',
|
|
||||||
doneButtonTitle: 'تایید',
|
|
||||||
cancelButtonTitle: 'بازگشت',
|
|
||||||
),
|
|
||||||
compressQuality: 30,
|
|
||||||
);
|
|
||||||
if (file == null) return;
|
|
||||||
}
|
|
||||||
if (pickedFile == null) return;
|
|
||||||
state.file =
|
|
||||||
kIsWeb ? File(pickedFile.path) : file;
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
}
|
|
||||||
|
|
||||||
state.update();
|
|
||||||
},
|
|
||||||
itemBuilder: (BuildContext context) =>
|
|
||||||
<PopupMenuEntry>[
|
|
||||||
popUpBtns(value: 'Pdf'),
|
|
||||||
popUpBtns(value: 'Image'),
|
|
||||||
],
|
|
||||||
offset: const Offset(0, -140),
|
|
||||||
position: PopupMenuPosition.over,
|
|
||||||
child: Icon(
|
|
||||||
Icons.attach_file_rounded,
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.focusedBorder,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
)),
|
||||||
],
|
|
||||||
));
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
AnimatedVisibility fileContainer(BuildContext context) {
|
|
||||||
final state = context.read<AiChatState>();
|
|
||||||
String basename = '';
|
|
||||||
if (state.file != null) {
|
|
||||||
basename = p.basename(state.file!.path);
|
|
||||||
}
|
|
||||||
return AnimatedVisibility(
|
|
||||||
isVisible: state.file != null,
|
|
||||||
duration: DesignConfig.lowAnimationDuration,
|
|
||||||
child: Container(
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
borderRadius: DesignConfig.mediumBorderRadius,
|
|
||||||
color: Theme.of(context).colorScheme.border,
|
|
||||||
),
|
|
||||||
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
|
|
||||||
margin: widget.args.bot.attachment! != 1
|
|
||||||
? const EdgeInsets.symmetric(horizontal: 12)
|
|
||||||
: EdgeInsets.zero,
|
|
||||||
child: Row(
|
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.file_copy),
|
|
||||||
const SizedBox(
|
|
||||||
width: 12,
|
|
||||||
),
|
|
||||||
Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 160,
|
|
||||||
height: 24,
|
|
||||||
child: MarqueeText(
|
|
||||||
text: basename,
|
|
||||||
style: const TextStyle(fontSize: 14),
|
|
||||||
stop: const Duration(seconds: 3),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (state.file != null)
|
|
||||||
FutureBuilder(
|
|
||||||
future: state.file!.length(),
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (!snapshot.hasData) {
|
|
||||||
return const SizedBox();
|
|
||||||
}
|
|
||||||
return DidvanText(
|
|
||||||
'File Size ${(snapshot.data! / 1000).round()} KB',
|
|
||||||
fontSize: 12,
|
|
||||||
);
|
|
||||||
})
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
InkWell(
|
|
||||||
onTap: () {
|
|
||||||
state.file = null;
|
|
||||||
state.update();
|
|
||||||
},
|
|
||||||
child: const Icon(DidvanIcons.close_circle_solid))
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
PopupMenuItem<dynamic> popUpBtns({required final String value}) {
|
|
||||||
return PopupMenuItem(
|
|
||||||
value: value,
|
|
||||||
height: 46,
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
const Icon(Icons.picture_as_pdf_rounded),
|
|
||||||
const SizedBox(
|
|
||||||
width: 12,
|
|
||||||
),
|
|
||||||
DidvanText(
|
|
||||||
value,
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -455,6 +309,9 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
|
|
||||||
Padding messageBubble(Prompts message, BuildContext context,
|
Padding messageBubble(Prompts message, BuildContext context,
|
||||||
AiChatState state, int index, int mIndex) {
|
AiChatState state, int index, int mIndex) {
|
||||||
|
FilesModel? file =
|
||||||
|
message.file == null ? null : FilesModel(message.file.toString());
|
||||||
|
|
||||||
MarkdownStyleSheet defaultMarkdownStyleSheet = MarkdownStyleSheet(
|
MarkdownStyleSheet defaultMarkdownStyleSheet = MarkdownStyleSheet(
|
||||||
code: TextStyle(
|
code: TextStyle(
|
||||||
backgroundColor: Theme.of(context).colorScheme.black,
|
backgroundColor: Theme.of(context).colorScheme.black,
|
||||||
|
|
@ -489,7 +346,9 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
? Radius.zero
|
? Radius.zero
|
||||||
: null,
|
: null,
|
||||||
),
|
),
|
||||||
color: (message.role.toString().contains('user')
|
color: message.error != null && message.error!
|
||||||
|
? Theme.of(context).colorScheme.error.withOpacity(0.4)
|
||||||
|
: (message.role.toString().contains('user')
|
||||||
? Theme.of(context).colorScheme.surface
|
? Theme.of(context).colorScheme.surface
|
||||||
: Theme.of(context).colorScheme.focused)
|
: Theme.of(context).colorScheme.focused)
|
||||||
.withOpacity(0.9),
|
.withOpacity(0.9),
|
||||||
|
|
@ -502,8 +361,13 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
constraints: BoxConstraints(
|
constraints: BoxConstraints(
|
||||||
maxWidth: MediaQuery.sizeOf(context).width / 1.5),
|
maxWidth: MediaQuery.sizeOf(context).width / 1.5),
|
||||||
child: message.finished != null && !message.finished!
|
child: message.finished != null && !message.finished!
|
||||||
? StreamBuilder<String>(
|
? Column(
|
||||||
stream: state.messageOnstream,
|
children: [
|
||||||
|
ValueListenableBuilder<Stream<String>>(
|
||||||
|
valueListenable: state.messageOnstream,
|
||||||
|
builder: (context, value, child) {
|
||||||
|
return StreamBuilder<String>(
|
||||||
|
stream: value,
|
||||||
builder: (context, snapshot) {
|
builder: (context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
|
|
@ -512,24 +376,81 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
data: "${snapshot.data}...",
|
data: "${snapshot.data}...",
|
||||||
selectable: false,
|
selectable: false,
|
||||||
shrinkWrap: true,
|
shrinkWrap: true,
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics:
|
||||||
styleSheet: defaultMarkdownStyleSheet);
|
const NeverScrollableScrollPhysics(),
|
||||||
|
styleSheet:
|
||||||
|
defaultMarkdownStyleSheet);
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
SpinKitThreeBounce(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
size: 18,
|
||||||
|
)
|
||||||
|
],
|
||||||
)
|
)
|
||||||
: Column(
|
: Column(
|
||||||
children: [
|
children: [
|
||||||
if (message.role.toString().contains('user') &&
|
if (message.role.toString().contains('user') &&
|
||||||
message.file != null)
|
file != null)
|
||||||
Container(
|
lookupMimeType(file.path)?.startsWith('audio/') ??
|
||||||
decoration: BoxDecoration(
|
false
|
||||||
borderRadius: DesignConfig.mediumBorderRadius,
|
? Directionality(
|
||||||
color: Theme.of(context).colorScheme.border,
|
textDirection: TextDirection.ltr,
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: StorageService.getValue(
|
||||||
|
key: 'token'),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return VoiceMessageView(
|
||||||
|
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),
|
||||||
),
|
),
|
||||||
padding:
|
innerPadding: 0,
|
||||||
const EdgeInsets.fromLTRB(12, 8, 12, 8),
|
cornerRadius: 20,
|
||||||
margin: widget.args.bot.attachment! != 1
|
circlesColor: Theme.of(context)
|
||||||
? const EdgeInsets.symmetric(horizontal: 12)
|
.colorScheme
|
||||||
: EdgeInsets.zero,
|
.primary,
|
||||||
|
activeSliderColor:
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
: 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(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Icon(Icons.file_copy),
|
const Icon(Icons.file_copy),
|
||||||
|
|
@ -542,13 +463,16 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 200,
|
width: 200,
|
||||||
child: DidvanText(
|
child: DidvanText((message
|
||||||
(message.fileName.toString())),
|
.fileName
|
||||||
|
.toString())),
|
||||||
),
|
),
|
||||||
if (state.file != null)
|
if (state.file != null)
|
||||||
FutureBuilder(
|
FutureBuilder(
|
||||||
future: state.file!.length(),
|
future: state.file!.main
|
||||||
builder: (context, snapshot) {
|
.length(),
|
||||||
|
builder:
|
||||||
|
(context, snapshot) {
|
||||||
if (!snapshot.hasData) {
|
if (!snapshot.hasData) {
|
||||||
return const SizedBox();
|
return const SizedBox();
|
||||||
}
|
}
|
||||||
|
|
@ -570,10 +494,31 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
styleSheet: defaultMarkdownStyleSheet,
|
styleSheet: defaultMarkdownStyleSheet,
|
||||||
),
|
),
|
||||||
if (!message.role.toString().contains('user'))
|
|
||||||
Row(
|
Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
|
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(
|
||||||
|
|
@ -594,7 +539,28 @@ class _AiChatPageState extends State<AiChatPage> {
|
||||||
.focusedBorder,
|
.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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,33 @@
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
import 'dart:io';
|
|
||||||
|
|
||||||
import 'package:didvan/models/ai/bots_model.dart';
|
import 'package:didvan/models/ai/bots_model.dart';
|
||||||
import 'package:didvan/models/ai/chats_model.dart';
|
import 'package:didvan/models/ai/chats_model.dart';
|
||||||
|
import 'package:didvan/models/ai/files_model.dart';
|
||||||
import 'package:didvan/models/ai/messages_model.dart';
|
import 'package:didvan/models/ai/messages_model.dart';
|
||||||
import 'package:didvan/models/enums.dart';
|
import 'package:didvan/models/enums.dart';
|
||||||
import 'package:didvan/models/view/alert_data.dart';
|
import 'package:didvan/models/view/alert_data.dart';
|
||||||
import 'package:didvan/providers/core.dart';
|
import 'package:didvan/providers/core.dart';
|
||||||
import 'package:didvan/services/ai/ai_api_service.dart';
|
import 'package:didvan/services/ai/ai_api_service.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/cupertino.dart';
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
import 'package:persian_number_utility/persian_number_utility.dart';
|
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||||
|
|
||||||
class AiChatState extends CoreProvier {
|
class AiChatState extends CoreProvier {
|
||||||
Stream<String> messageOnstream = const Stream.empty();
|
ValueNotifier<Stream<String>> messageOnstream =
|
||||||
|
ValueNotifier(const Stream.empty());
|
||||||
List<MessageModel> messages = [];
|
List<MessageModel> messages = [];
|
||||||
bool onResponsing = false;
|
bool onResponsing = false;
|
||||||
bool loading = false;
|
bool loading = false;
|
||||||
|
ValueNotifier<bool> changingPlaceHolder = ValueNotifier(false);
|
||||||
final ScrollController scrollController = ScrollController();
|
final ScrollController scrollController = ScrollController();
|
||||||
int? chatId;
|
int? chatId;
|
||||||
File? file;
|
ChatsModel? chat;
|
||||||
|
FilesModel? file;
|
||||||
|
|
||||||
Future<void> _scrolledEnd() async {
|
Future<void> _scrolledEnd() async {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
WidgetsBinding.instance.addPostFrameCallback((_) async {
|
||||||
|
|
@ -38,7 +43,7 @@ class AiChatState extends CoreProvier {
|
||||||
onResponsing = false;
|
onResponsing = false;
|
||||||
messages.last.prompts.removeLast();
|
messages.last.prompts.removeLast();
|
||||||
messages.last.prompts.removeLast();
|
messages.last.prompts.removeLast();
|
||||||
messageOnstream = const Stream.empty();
|
messageOnstream.value = const Stream.empty();
|
||||||
|
|
||||||
await ActionSheetUtils.showAlert(AlertData(
|
await ActionSheetUtils.showAlert(AlertData(
|
||||||
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
|
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
|
||||||
|
|
@ -54,6 +59,7 @@ class AiChatState extends CoreProvier {
|
||||||
if (service.isSuccess) {
|
if (service.isSuccess) {
|
||||||
final id = service.result['id'];
|
final id = service.result['id'];
|
||||||
chatId = id;
|
chatId = id;
|
||||||
|
chat ??= ChatsModel(id: chatId);
|
||||||
}
|
}
|
||||||
return service;
|
return service;
|
||||||
}
|
}
|
||||||
|
|
@ -61,6 +67,7 @@ class AiChatState extends CoreProvier {
|
||||||
Future<void> getAllMessages(int chatId) async {
|
Future<void> getAllMessages(int chatId) async {
|
||||||
loading = true;
|
loading = true;
|
||||||
onResponsing = true;
|
onResponsing = true;
|
||||||
|
update();
|
||||||
final service = RequestService(
|
final service = RequestService(
|
||||||
RequestHelper.aiAChat(chatId),
|
RequestHelper.aiAChat(chatId),
|
||||||
);
|
);
|
||||||
|
|
@ -99,6 +106,35 @@ class AiChatState extends CoreProvier {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> changePlaceHolder(String placeholder) async {
|
||||||
|
changingPlaceHolder.value = true;
|
||||||
|
update();
|
||||||
|
await Future.delayed(const Duration(seconds: 3));
|
||||||
|
|
||||||
|
final service = RequestService(RequestHelper.placeholder(chatId!),
|
||||||
|
body: {'placeholder': placeholder});
|
||||||
|
await service.put();
|
||||||
|
if (service.isSuccess) {
|
||||||
|
appState = AppState.idle;
|
||||||
|
|
||||||
|
// Add this code to scroll to maxScrollExtent after the ListView is built
|
||||||
|
if (chat == null) {
|
||||||
|
chat = ChatsModel(id: chatId, placeholder: placeholder);
|
||||||
|
} else {
|
||||||
|
chat = chat!.copyWith(placeholder: placeholder);
|
||||||
|
}
|
||||||
|
changingPlaceHolder.value = false;
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appState = AppState.failed;
|
||||||
|
changingPlaceHolder.value = false;
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> postMessage(BotsModel bot) async {
|
Future<void> postMessage(BotsModel bot) async {
|
||||||
onResponsing = true;
|
onResponsing = true;
|
||||||
|
|
||||||
|
|
@ -107,6 +143,7 @@ class AiChatState extends CoreProvier {
|
||||||
|
|
||||||
messages.last.prompts.add(Prompts(
|
messages.last.prompts.add(Prompts(
|
||||||
finished: false,
|
finished: false,
|
||||||
|
error: false,
|
||||||
text: '...',
|
text: '...',
|
||||||
role: 'bot',
|
role: 'bot',
|
||||||
createdAt: DateTime.now()
|
createdAt: DateTime.now()
|
||||||
|
|
@ -118,18 +155,27 @@ class AiChatState extends CoreProvier {
|
||||||
url: '/${bot.id}/${bot.name}'.toLowerCase(),
|
url: '/${bot.id}/${bot.name}'.toLowerCase(),
|
||||||
message: message,
|
message: message,
|
||||||
chatId: chatId,
|
chatId: chatId,
|
||||||
file: file);
|
file: file?.main);
|
||||||
final res = await AiApiService.getResponse(req).catchError((e) {
|
final res = await AiApiService.getResponse(req).catchError((e) {
|
||||||
_onError(e);
|
_onError(e);
|
||||||
throw e;
|
return e;
|
||||||
});
|
});
|
||||||
|
|
||||||
String responseMessgae = '';
|
String responseMessgae = '';
|
||||||
|
String dataMessgae = '';
|
||||||
|
file = null;
|
||||||
|
update();
|
||||||
var r = res.listen((value) async {
|
var r = res.listen((value) async {
|
||||||
var str = utf8.decode(value);
|
var str = utf8.decode(value);
|
||||||
responseMessgae += str;
|
if (str.contains('{{{')) {
|
||||||
messageOnstream = Stream.value(responseMessgae);
|
dataMessgae += str;
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
responseMessgae += str;
|
||||||
|
messageOnstream.value = Stream.value(responseMessgae);
|
||||||
|
|
||||||
|
// update();
|
||||||
});
|
});
|
||||||
|
|
||||||
r.onDone(() async {
|
r.onDone(() async {
|
||||||
|
|
@ -141,11 +187,39 @@ class AiChatState extends CoreProvier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onResponsing = false;
|
onResponsing = false;
|
||||||
messages.last.prompts.last = messages.last.prompts.last.copyWith(
|
if (responseMessgae.isEmpty) {
|
||||||
finished: true,
|
messages.last.prompts.removeLast();
|
||||||
text: responseMessgae,
|
messages.last.prompts.last =
|
||||||
);
|
messages.last.prompts.last.copyWith(error: true);
|
||||||
messageOnstream = const Stream.empty();
|
messageOnstream.value = const Stream.empty();
|
||||||
|
update();
|
||||||
|
_scrolledEnd();
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
int? humanMessageId;
|
||||||
|
int? aiMessageId;
|
||||||
|
try {
|
||||||
|
final data = AppInitializer.messagesData(dataMessgae);
|
||||||
|
humanMessageId = data['HUMAN_MESSAGE_ID'];
|
||||||
|
aiMessageId = data['AI_MESSAGE_ID'];
|
||||||
|
} catch (e) {
|
||||||
|
e.printError();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Access the values
|
||||||
|
messages.last.prompts.last = messages.last.prompts.last
|
||||||
|
.copyWith(finished: true, text: responseMessgae, id: aiMessageId);
|
||||||
|
if (messages.last.prompts.length > 2) {
|
||||||
|
messages.last.prompts[messages.last.prompts.length - 2] = messages
|
||||||
|
.last.prompts[messages.last.prompts.length - 2]
|
||||||
|
.copyWith(id: humanMessageId);
|
||||||
|
} else {
|
||||||
|
messages.last.prompts.first =
|
||||||
|
messages.last.prompts.first.copyWith(id: humanMessageId);
|
||||||
|
}
|
||||||
|
messageOnstream.value = const Stream.empty();
|
||||||
|
}
|
||||||
|
|
||||||
update();
|
update();
|
||||||
_scrolledEnd();
|
_scrolledEnd();
|
||||||
|
|
@ -153,4 +227,27 @@ class AiChatState extends CoreProvier {
|
||||||
|
|
||||||
r.onError(_onError);
|
r.onError(_onError);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteMessage(int id, int mIndex, int index) async {
|
||||||
|
final service = RequestService(RequestHelper.deleteMessage(chatId!, id));
|
||||||
|
await service.delete();
|
||||||
|
if (service.isSuccess) {
|
||||||
|
if (messages[mIndex].prompts.length <= 1) {
|
||||||
|
messages.removeAt(mIndex);
|
||||||
|
} else {
|
||||||
|
messages[mIndex].prompts.removeAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
appState = AppState.idle;
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appState = AppState.failed;
|
||||||
|
await ActionSheetUtils.showAlert(AlertData(
|
||||||
|
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
// ignore_for_file: library_private_types_in_public_api
|
// ignore_for_file: library_private_types_in_public_api, deprecated_member_use
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
|
||||||
|
|
@ -9,12 +9,14 @@ import 'package:didvan/constants/assets.dart';
|
||||||
import 'package:didvan/main.dart';
|
import 'package:didvan/main.dart';
|
||||||
import 'package:didvan/models/ai/ai_chat_args.dart';
|
import 'package:didvan/models/ai/ai_chat_args.dart';
|
||||||
import 'package:didvan/models/view/action_sheet_data.dart';
|
import 'package:didvan/models/view/action_sheet_data.dart';
|
||||||
|
import 'package:didvan/models/view/app_bar_data.dart';
|
||||||
import 'package:didvan/routes/routes.dart';
|
import 'package:didvan/routes/routes.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/history_ai_chat_state.dart';
|
import 'package:didvan/views/ai/history_ai_chat_state.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/scaffold.dart';
|
import 'package:didvan/views/widgets/didvan/scaffold.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
|
import 'package:didvan/views/widgets/search_field.dart';
|
||||||
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
import 'package:didvan/views/widgets/shimmer_placeholder.dart';
|
||||||
import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
|
import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
|
||||||
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
|
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
|
||||||
|
|
@ -22,7 +24,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class HistoryAiChatPage extends StatefulWidget {
|
class HistoryAiChatPage extends StatefulWidget {
|
||||||
const HistoryAiChatPage({Key? key}) : super(key: key);
|
final bool? archived;
|
||||||
|
const HistoryAiChatPage({Key? key, required this.archived}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
_HistoryAiChatPageState createState() => _HistoryAiChatPageState();
|
_HistoryAiChatPageState createState() => _HistoryAiChatPageState();
|
||||||
|
|
@ -35,20 +38,29 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
||||||
final state = context.read<HistoryAiChatState>();
|
final state = context.read<HistoryAiChatState>();
|
||||||
Future.delayed(
|
Future.delayed(
|
||||||
Duration.zero,
|
Duration.zero,
|
||||||
() => state.getChats(),
|
() => state.getChats(archived: widget.archived),
|
||||||
);
|
);
|
||||||
|
|
||||||
super.initState();
|
super.initState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Timer? _timer;
|
||||||
|
late bool archived = widget.archived ?? false;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return DidvanScaffold(
|
return WillPopScope(
|
||||||
|
onWillPop: () async {
|
||||||
|
await context.read<HistoryAiChatState>().getChats();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
child: DidvanScaffold(
|
||||||
hidePlayer: true,
|
hidePlayer: true,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
padding: const EdgeInsets.only(left: 16, right: 16, bottom: 92),
|
// floatingActionButton: openAiListBtn(context),
|
||||||
floatingActionButton: openAiListBtn(context),
|
padding: EdgeInsets.zero,
|
||||||
scrollController: scrollController,
|
scrollController: scrollController,
|
||||||
|
showSliversFirst: false,
|
||||||
slivers: [
|
slivers: [
|
||||||
Consumer<HistoryAiChatState>(
|
Consumer<HistoryAiChatState>(
|
||||||
builder: (context, state, child) {
|
builder: (context, state, child) {
|
||||||
|
|
@ -65,43 +77,145 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
||||||
// 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 = state.chats[index];
|
||||||
|
TextEditingController title =
|
||||||
|
TextEditingController(text: state.chats[index].title);
|
||||||
|
return Dismissible(
|
||||||
|
key: UniqueKey(),
|
||||||
|
background: Container(
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
alignment: Alignment.centerRight,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Icon(
|
||||||
|
DidvanIcons.trash_solid,
|
||||||
|
color: Theme.of(context).colorScheme.white,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
secondaryBackground: Container(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
alignment: Alignment.centerLeft,
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Icon(
|
||||||
|
archived
|
||||||
|
? Icons.folder_delete
|
||||||
|
: Icons.create_new_folder_rounded,
|
||||||
|
color: Theme.of(context).colorScheme.white),
|
||||||
|
),
|
||||||
|
movementDuration: const Duration(milliseconds: 600),
|
||||||
|
confirmDismiss: (direction) async {
|
||||||
|
bool result = false;
|
||||||
|
|
||||||
return InkWell(
|
if (direction == DismissDirection.startToEnd) {
|
||||||
|
ActionSheetUtils.context = context;
|
||||||
|
await ActionSheetUtils.openDialog(
|
||||||
|
data: ActionSheetData(
|
||||||
|
onConfirmed: () async {
|
||||||
|
final state =
|
||||||
|
context.read<HistoryAiChatState>();
|
||||||
|
await state.deleteChat(chat.id!, index);
|
||||||
|
result = true;
|
||||||
|
},
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
DidvanIcons.trash_solid,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.error,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
child: DidvanText(
|
||||||
|
'پاک کردن گفتوگو',
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.error,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
SizedBox(
|
||||||
|
child: RichText(
|
||||||
|
text: TextSpan(
|
||||||
|
text:
|
||||||
|
'آیا از پاک کردن گفتوگوی ',
|
||||||
|
style: TextStyle(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.text),
|
||||||
|
children: [
|
||||||
|
TextSpan(
|
||||||
|
text: "\"${chat.title}\"",
|
||||||
|
style: const TextStyle(
|
||||||
|
fontWeight:
|
||||||
|
FontWeight.bold)),
|
||||||
|
const TextSpan(
|
||||||
|
text:
|
||||||
|
' با هوشان اطمینان دارید؟ '),
|
||||||
|
]),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
result = await state.archivedChat(chat.id!, index);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
},
|
||||||
|
child: InkWell(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
if (state.chatsToDelete.isEmpty) {
|
// if (state.chatsToDelete.isEmpty) {
|
||||||
navigatorKey.currentState!.pushNamed(Routes.aiChat,
|
navigatorKey.currentState!.pushNamed(Routes.aiChat,
|
||||||
arguments:
|
arguments:
|
||||||
AiChatArgs(bot: chat.bot!, chatId: chat.id!));
|
AiChatArgs(bot: chat.bot!, chat: chat));
|
||||||
} else {
|
// } else {
|
||||||
if (state.chatsToDelete.contains(chat.id)) {
|
// if (state.chatsToDelete.contains(chat.id)) {
|
||||||
state.chatsToDelete.remove(chat.id!);
|
// state.chatsToDelete.remove(chat.id!);
|
||||||
} else {
|
// } else {
|
||||||
state.chatsToDelete.add(chat.id!);
|
// state.chatsToDelete.add(chat.id!);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
state.update();
|
// state.update();
|
||||||
},
|
},
|
||||||
onLongPress: () {
|
onLongPress: () {
|
||||||
if (state.chatsToDelete.isEmpty) {
|
state.chats[index] =
|
||||||
state.chatsToDelete.add(chat.id!);
|
state.chats[index].copyWith(isEditing: true);
|
||||||
}
|
|
||||||
state.update();
|
state.update();
|
||||||
|
|
||||||
|
// if (state.chatsToDelete.isEmpty) {
|
||||||
|
// state.chatsToDelete.add(chat.id!);
|
||||||
|
// }
|
||||||
|
// state.update();
|
||||||
},
|
},
|
||||||
child: Container(
|
child: Container(
|
||||||
padding: const EdgeInsets.all(12),
|
padding: const EdgeInsets.symmetric(
|
||||||
|
vertical: 12, horizontal: 20),
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
border: Border(
|
border: Border(
|
||||||
bottom: BorderSide(
|
bottom: BorderSide(
|
||||||
color:
|
color: Theme.of(context)
|
||||||
Theme.of(context).colorScheme.border))),
|
.colorScheme
|
||||||
child: Stack(
|
.border))),
|
||||||
children: [
|
child: Row(
|
||||||
Row(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
const SizedBox(
|
SizedBox(
|
||||||
width: 46,
|
width: 46,
|
||||||
height: 46,
|
height: 46,
|
||||||
|
child: ClipOval(
|
||||||
|
child: CachedNetworkImage(
|
||||||
|
imageUrl: chat.bot!.image.toString(),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(
|
const SizedBox(
|
||||||
width: 18,
|
width: 18,
|
||||||
|
|
@ -117,6 +231,7 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
||||||
DidvanText(
|
DidvanText(
|
||||||
chat.bot!.name.toString(),
|
chat.bot!.name.toString(),
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
|
// fontSize: 18,
|
||||||
),
|
),
|
||||||
DidvanText(
|
DidvanText(
|
||||||
DateTimeUtils.momentGenerator(
|
DateTimeUtils.momentGenerator(
|
||||||
|
|
@ -126,10 +241,103 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
SizedBox(
|
SizedBox(
|
||||||
child: DidvanText(
|
child: Column(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
chat.isEditing != null &&
|
||||||
|
chat.isEditing!
|
||||||
|
? Row(
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: TextFormField(
|
||||||
|
controller: title,
|
||||||
|
style:
|
||||||
|
const TextStyle(
|
||||||
|
fontSize: 12),
|
||||||
|
textAlignVertical:
|
||||||
|
TextAlignVertical
|
||||||
|
.bottom,
|
||||||
|
maxLines: 1,
|
||||||
|
decoration:
|
||||||
|
const InputDecoration(
|
||||||
|
isDense: true,
|
||||||
|
contentPadding:
|
||||||
|
EdgeInsets
|
||||||
|
.symmetric(
|
||||||
|
vertical:
|
||||||
|
5,
|
||||||
|
horizontal:
|
||||||
|
10),
|
||||||
|
border:
|
||||||
|
OutlineInputBorder(),
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
state.loadingchangeTitle
|
||||||
|
? const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
height: 12,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator())
|
||||||
|
: InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
if (title.text
|
||||||
|
.toString() ==
|
||||||
|
chat.title
|
||||||
|
.toString()) {
|
||||||
|
chat.isEditing =
|
||||||
|
false;
|
||||||
|
state.update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (title.text
|
||||||
|
.isNotEmpty) {
|
||||||
|
await state
|
||||||
|
.changeNameChat(
|
||||||
|
chat
|
||||||
|
.id!,
|
||||||
|
index,
|
||||||
|
title
|
||||||
|
.text);
|
||||||
|
title.clear();
|
||||||
|
}
|
||||||
|
if (chat.isEditing !=
|
||||||
|
null) {
|
||||||
|
chat.isEditing =
|
||||||
|
!chat
|
||||||
|
.isEditing!;
|
||||||
|
state.update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chat.isEditing =
|
||||||
|
true;
|
||||||
|
|
||||||
|
state.update();
|
||||||
|
},
|
||||||
|
child: const Icon(
|
||||||
|
DidvanIcons
|
||||||
|
.check_circle_solid),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
)
|
||||||
|
: DidvanText(
|
||||||
|
chat.title.toString(),
|
||||||
|
maxLines: 1,
|
||||||
|
overflow:
|
||||||
|
TextOverflow.ellipsis,
|
||||||
|
// fontWeight: FontWeight.bold,
|
||||||
|
// fontSize: 16,
|
||||||
|
),
|
||||||
|
DidvanText(
|
||||||
chat.prompts![0].text.toString(),
|
chat.prompts![0].text.toString(),
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
@ -137,36 +345,6 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
Positioned(
|
|
||||||
bottom: 0,
|
|
||||||
right: 0,
|
|
||||||
top: 0,
|
|
||||||
child: Padding(
|
|
||||||
padding: const EdgeInsets.all(4.0),
|
|
||||||
child: ClipOval(
|
|
||||||
child: CachedNetworkImage(
|
|
||||||
imageUrl: chat.bot!.image.toString(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (state.chatsToDelete.contains(chat.id))
|
|
||||||
Positioned(
|
|
||||||
right: 32,
|
|
||||||
bottom: 0,
|
|
||||||
child: Container(
|
|
||||||
// ignore: prefer_const_constructors
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.scaffoldBackgroundColor,
|
|
||||||
shape: BoxShape.circle),
|
|
||||||
child: Icon(DidvanIcons.check_circle_solid,
|
|
||||||
size: 20,
|
|
||||||
color:
|
|
||||||
Theme.of(context).colorScheme.success),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
@ -176,145 +354,102 @@ class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
appBarData: null,
|
appBarData: AppBarData(
|
||||||
);
|
title: archived ? 'گفتوگوهای آرشیو شده' : 'تاریخچه گفتوگوها',
|
||||||
}
|
hasBack: true,
|
||||||
|
hasElevation: true,
|
||||||
Widget openAiListBtn(BuildContext context) {
|
trailing: Padding(
|
||||||
final watch = context.watch<HistoryAiChatState>();
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
final state = context.read<HistoryAiChatState>();
|
child: InkWell(
|
||||||
return FloatingActionButton(
|
onTap: () async {
|
||||||
backgroundColor: watch.chatsToDelete.isEmpty
|
|
||||||
? Theme.of(context).colorScheme.primary
|
|
||||||
: Theme.of(context).colorScheme.error,
|
|
||||||
shape: const OvalBorder(),
|
|
||||||
mini: true,
|
|
||||||
onPressed: () {
|
|
||||||
if (watch.chatsToDelete.isEmpty) {
|
|
||||||
state.getBots();
|
|
||||||
state.search = '';
|
|
||||||
_botsDialogSelect(context);
|
|
||||||
} else {
|
|
||||||
state.addChatToDelete();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
child: watch.chatsToDelete.isEmpty
|
|
||||||
? const Icon(DidvanIcons.add_regular)
|
|
||||||
: const Icon(DidvanIcons.trash_regular),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
void _botsDialogSelect(BuildContext context) {
|
|
||||||
final state = context.read<HistoryAiChatState>();
|
|
||||||
|
|
||||||
ActionSheetUtils.context = context;
|
ActionSheetUtils.context = context;
|
||||||
ActionSheetUtils.openDialog(
|
await ActionSheetUtils.openDialog(
|
||||||
data: ActionSheetData(
|
data: ActionSheetData(
|
||||||
hasConfirmButton: false,
|
onConfirmed: () async {
|
||||||
hasDismissButton: false,
|
final state = context.read<HistoryAiChatState>();
|
||||||
|
await state.deleteAllChat();
|
||||||
|
},
|
||||||
content: Column(
|
content: Column(
|
||||||
children: [
|
children: [
|
||||||
// Row(
|
Row(
|
||||||
// mainAxisAlignment: MainAxisAlignment.end,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
// children: [
|
|
||||||
// Padding(
|
|
||||||
// padding: const EdgeInsets.symmetric(vertical: 8.0),
|
|
||||||
// child: InkWell(
|
|
||||||
// onTap: () {
|
|
||||||
// ActionSheetUtils.pop();
|
|
||||||
// },
|
|
||||||
// child: const Icon(DidvanIcons.close_solid)),
|
|
||||||
// )
|
|
||||||
// ],
|
|
||||||
// ),
|
|
||||||
// SearchField(
|
|
||||||
// title: 'هوش مصنوعی',
|
|
||||||
// value: state.search,
|
|
||||||
// onChanged: (value) {
|
|
||||||
// state.search = value;
|
|
||||||
// if (value.isEmpty) {
|
|
||||||
// state.getBots();
|
|
||||||
// return;
|
|
||||||
// }
|
|
||||||
// state.timer?.cancel();
|
|
||||||
// state.timer = Timer(const Duration(seconds: 1), () {
|
|
||||||
// state.getSearchBots(value);
|
|
||||||
// });
|
|
||||||
// },
|
|
||||||
// focusNode: FocusNode()),
|
|
||||||
// const SizedBox(
|
|
||||||
// height: 12,
|
|
||||||
// ),
|
|
||||||
SizedBox(
|
|
||||||
width: double.infinity,
|
|
||||||
height: MediaQuery.sizeOf(context).height / 3,
|
|
||||||
child: ValueListenableBuilder<bool>(
|
|
||||||
valueListenable: state.loadingBots,
|
|
||||||
builder: (context, value, child) => value
|
|
||||||
? Center(
|
|
||||||
child: Image.asset(
|
|
||||||
Assets.loadingAnimation,
|
|
||||||
width: 60,
|
|
||||||
height: 60,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: state.bots.isEmpty
|
|
||||||
? Padding(
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
horizontal: 12.0),
|
|
||||||
child: EmptyState(
|
|
||||||
asset: Assets.emptyResult,
|
|
||||||
title: 'نتیجهای پیدا نشد',
|
|
||||||
height: 120,
|
|
||||||
),
|
|
||||||
)
|
|
||||||
: ListView.builder(
|
|
||||||
padding:
|
|
||||||
const EdgeInsets.symmetric(vertical: 12),
|
|
||||||
itemCount: state.bots.length,
|
|
||||||
physics: const BouncingScrollPhysics(),
|
|
||||||
shrinkWrap: true,
|
|
||||||
itemBuilder: (context, index) {
|
|
||||||
final bot = state.bots[index];
|
|
||||||
return InkWell(
|
|
||||||
onTap: () {
|
|
||||||
ActionSheetUtils.pop();
|
|
||||||
navigatorKey.currentState!.pushNamed(
|
|
||||||
Routes.aiChat,
|
|
||||||
arguments: AiChatArgs(bot: bot));
|
|
||||||
},
|
|
||||||
child: Container(
|
|
||||||
alignment: Alignment.center,
|
|
||||||
padding: const EdgeInsets.symmetric(
|
|
||||||
vertical: 8),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
bottom: BorderSide(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.border,
|
|
||||||
width: 1))),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
children: [
|
||||||
ClipOval(
|
Icon(
|
||||||
child: CachedNetworkImage(
|
DidvanIcons.trash_solid,
|
||||||
imageUrl: bot.image.toString(),
|
color: Theme.of(context).colorScheme.error,
|
||||||
width: 42,
|
|
||||||
height: 42,
|
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
DidvanText(
|
||||||
|
'پاک کردن همه گفتوگوها',
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
fontSize: 20,
|
||||||
),
|
),
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text(bot.name.toString())
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
),
|
),
|
||||||
);
|
const DidvanText(
|
||||||
}),
|
'آیا از پاک کردن تمامی گفتوگوهای انجام شده با هوشان اطمینان دارید؟'),
|
||||||
),
|
|
||||||
)
|
|
||||||
],
|
],
|
||||||
)));
|
)));
|
||||||
|
},
|
||||||
|
child: DidvanText(
|
||||||
|
archived ? 'خارج کردن همه' : 'حذف همه',
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
children: [
|
||||||
|
Container(
|
||||||
|
color: Theme.of(context).colorScheme.surface,
|
||||||
|
padding: const EdgeInsets.only(right: 20, left: 20, bottom: 24),
|
||||||
|
child: SearchField(
|
||||||
|
title: 'گفتوگوها',
|
||||||
|
onChanged: (value) {
|
||||||
|
final state = context.read<HistoryAiChatState>();
|
||||||
|
if (value.isEmpty) {
|
||||||
|
state.getChats(archived: widget.archived);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
_timer?.cancel();
|
||||||
|
_timer = Timer(const Duration(seconds: 1), () {
|
||||||
|
state.search = value;
|
||||||
|
state.getSearchChats(q: value, archived: widget.archived);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
focusNode: FocusNode()),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Widget openAiListBtn(BuildContext context) {
|
||||||
|
// final watch = context.watch<HistoryAiChatState>();
|
||||||
|
// final state = context.read<HistoryAiChatState>();
|
||||||
|
// return FloatingActionButton(
|
||||||
|
// backgroundColor: watch.chatsToDelete.isEmpty
|
||||||
|
// ? Theme.of(context).colorScheme.primary
|
||||||
|
// : Theme.of(context).colorScheme.error,
|
||||||
|
// shape: const OvalBorder(),
|
||||||
|
// mini: true,
|
||||||
|
// onPressed: () {
|
||||||
|
// if (watch.chatsToDelete.isEmpty) {
|
||||||
|
// state.getBots();
|
||||||
|
// state.search = '';
|
||||||
|
// _botsDialogSelect(context);
|
||||||
|
// } else {
|
||||||
|
// state.addChatToDelete();
|
||||||
|
// }
|
||||||
|
// },
|
||||||
|
// child: watch.chatsToDelete.isEmpty
|
||||||
|
// ? const Icon(DidvanIcons.add_regular)
|
||||||
|
// : const Icon(DidvanIcons.trash_regular),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HistoryPlaceholder extends StatelessWidget {
|
class _HistoryPlaceholder extends StatelessWidget {
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,20 @@ import 'package:flutter/cupertino.dart';
|
||||||
|
|
||||||
class HistoryAiChatState extends CoreProvier {
|
class HistoryAiChatState extends CoreProvier {
|
||||||
final List<ChatsModel> chats = [];
|
final List<ChatsModel> chats = [];
|
||||||
final List<int> chatsToDelete = [];
|
// final List<int> chatsToDelete = [];
|
||||||
final List<BotsModel> bots = [];
|
final List<BotsModel> bots = [];
|
||||||
|
BotsModel? bot;
|
||||||
ValueNotifier<bool> loadingBots = ValueNotifier(false);
|
ValueNotifier<bool> loadingBots = ValueNotifier(false);
|
||||||
|
bool loadingchangeTitle = false;
|
||||||
|
bool loadingdeleteAll = false;
|
||||||
Timer? timer;
|
Timer? timer;
|
||||||
String search = '';
|
String search = '';
|
||||||
|
|
||||||
Future<void> getChats() async {
|
Future<void> getChats({final bool? archived}) async {
|
||||||
final service = RequestService(
|
final service = RequestService(
|
||||||
RequestHelper.aiChats(),
|
archived != null && archived
|
||||||
|
? RequestHelper.aiArchived()
|
||||||
|
: RequestHelper.aiChats(),
|
||||||
);
|
);
|
||||||
await service.httpGet();
|
await service.httpGet();
|
||||||
if (service.isSuccess) {
|
if (service.isSuccess) {
|
||||||
|
|
@ -37,6 +42,29 @@ class HistoryAiChatState extends CoreProvier {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> getSearchChats(
|
||||||
|
{required final String q, final bool? archived}) async {
|
||||||
|
final service = RequestService(
|
||||||
|
archived != null && archived
|
||||||
|
? RequestHelper.aiSearchArchived(q)
|
||||||
|
: RequestHelper.aiSearchChats(q),
|
||||||
|
);
|
||||||
|
await service.httpGet();
|
||||||
|
if (service.isSuccess) {
|
||||||
|
chats.clear();
|
||||||
|
final ch = service.result['chats'];
|
||||||
|
for (var i = 0; i < ch.length; i++) {
|
||||||
|
chats.add(ChatsModel.fromJson(ch[i]));
|
||||||
|
}
|
||||||
|
appState = AppState.idle;
|
||||||
|
update();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appState = AppState.failed;
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> getBots() async {
|
Future<void> getBots() async {
|
||||||
loadingBots.value = true;
|
loadingBots.value = true;
|
||||||
final service = RequestService(
|
final service = RequestService(
|
||||||
|
|
@ -51,6 +79,7 @@ class HistoryAiChatState extends CoreProvier {
|
||||||
}
|
}
|
||||||
appState = AppState.idle;
|
appState = AppState.idle;
|
||||||
loadingBots.value = false;
|
loadingBots.value = false;
|
||||||
|
bot = bots.first;
|
||||||
update();
|
update();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -82,22 +111,44 @@ class HistoryAiChatState extends CoreProvier {
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addChatToDelete() async {
|
// Future<void> addChatToDelete() async {
|
||||||
final service = RequestService(RequestHelper.aiDeleteChats(),
|
// final service = RequestService(RequestHelper.aiDeleteChats(),
|
||||||
body: {"ids": chatsToDelete});
|
// body: {"ids": chatsToDelete});
|
||||||
await service.delete();
|
// await service.delete();
|
||||||
|
// if (service.isSuccess) {
|
||||||
|
// final List<ChatsModel> cs = [];
|
||||||
|
// for (var chat in chats) {
|
||||||
|
// if (!chatsToDelete.contains(chat.id)) {
|
||||||
|
// cs.add(chat);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// chatsToDelete.clear();
|
||||||
|
// chats.clear();
|
||||||
|
// chats.addAll(cs);
|
||||||
|
|
||||||
|
// appState = AppState.idle;
|
||||||
|
// update();
|
||||||
|
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// appState = AppState.failed;
|
||||||
|
// await ActionSheetUtils.showAlert(AlertData(
|
||||||
|
// message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
|
||||||
|
|
||||||
|
// update();
|
||||||
|
// }
|
||||||
|
|
||||||
|
Future<void> changeNameChat(int id, int index, String title) async {
|
||||||
|
loadingchangeTitle = true;
|
||||||
|
update();
|
||||||
|
final service =
|
||||||
|
RequestService(RequestHelper.aiChangeChats(id), body: {"title": title});
|
||||||
|
await service.put();
|
||||||
if (service.isSuccess) {
|
if (service.isSuccess) {
|
||||||
final List<ChatsModel> cs = [];
|
chats[index].title = title;
|
||||||
for (var chat in chats) {
|
|
||||||
if (!chatsToDelete.contains(chat.id)) {
|
|
||||||
cs.add(chat);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
chatsToDelete.clear();
|
|
||||||
chats.clear();
|
|
||||||
chats.addAll(cs);
|
|
||||||
|
|
||||||
appState = AppState.idle;
|
appState = AppState.idle;
|
||||||
|
loadingchangeTitle = false;
|
||||||
update();
|
update();
|
||||||
|
|
||||||
return;
|
return;
|
||||||
|
|
@ -105,7 +156,77 @@ class HistoryAiChatState extends CoreProvier {
|
||||||
appState = AppState.failed;
|
appState = AppState.failed;
|
||||||
await ActionSheetUtils.showAlert(AlertData(
|
await ActionSheetUtils.showAlert(AlertData(
|
||||||
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
|
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
|
||||||
|
loadingchangeTitle = false;
|
||||||
|
|
||||||
update();
|
update();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> deleteChat(int id, int index) async {
|
||||||
|
final service = RequestService(RequestHelper.deleteChat(id));
|
||||||
|
await service.delete();
|
||||||
|
if (service.isSuccess) {
|
||||||
|
chats.removeAt(index);
|
||||||
|
|
||||||
|
appState = AppState.idle;
|
||||||
|
loadingchangeTitle = false;
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appState = AppState.failed;
|
||||||
|
await ActionSheetUtils.showAlert(AlertData(
|
||||||
|
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
|
||||||
|
loadingchangeTitle = false;
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteAllChat() async {
|
||||||
|
loadingdeleteAll = true;
|
||||||
|
update();
|
||||||
|
final service = RequestService(
|
||||||
|
RequestHelper.deleteAllChats(),
|
||||||
|
);
|
||||||
|
await service.delete();
|
||||||
|
if (service.isSuccess) {
|
||||||
|
chats.clear();
|
||||||
|
|
||||||
|
appState = AppState.idle;
|
||||||
|
loadingdeleteAll = false;
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
appState = AppState.failed;
|
||||||
|
await ActionSheetUtils.showAlert(AlertData(
|
||||||
|
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
|
||||||
|
loadingdeleteAll = false;
|
||||||
|
|
||||||
|
update();
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> archivedChat(int id, int index) async {
|
||||||
|
update();
|
||||||
|
final service = RequestService(
|
||||||
|
RequestHelper.archivedChat(id),
|
||||||
|
);
|
||||||
|
await service.put();
|
||||||
|
if (service.isSuccess) {
|
||||||
|
chats.removeAt(index);
|
||||||
|
|
||||||
|
appState = AppState.idle;
|
||||||
|
|
||||||
|
update();
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
appState = AppState.failed;
|
||||||
|
await ActionSheetUtils.showAlert(AlertData(
|
||||||
|
message: 'خطا در برقراری ارتباط', aLertType: ALertType.error));
|
||||||
|
|
||||||
|
update();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,633 @@
|
||||||
|
import 'dart:async';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:didvan/config/design_config.dart';
|
||||||
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
|
import 'package:didvan/models/ai/bots_model.dart';
|
||||||
|
import 'package:didvan/models/ai/chats_model.dart';
|
||||||
|
import 'package:didvan/models/ai/files_model.dart';
|
||||||
|
import 'package:didvan/models/ai/messages_model.dart';
|
||||||
|
import 'package:didvan/services/media/media.dart';
|
||||||
|
import 'package:didvan/services/storage/storage.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/message_bar_btn.dart';
|
||||||
|
import 'package:didvan/views/ai/widgets/voice_message_view.dart';
|
||||||
|
import 'package:didvan/views/widgets/animated_visibility.dart';
|
||||||
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
|
import 'package:didvan/views/widgets/marquee_text.dart';
|
||||||
|
import 'package:file_picker/file_picker.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_spinkit/flutter_spinkit.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:image_cropper/image_cropper.dart';
|
||||||
|
import 'package:image_picker/image_picker.dart';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
import 'package:persian_number_utility/persian_number_utility.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
import 'package:record/record.dart';
|
||||||
|
|
||||||
|
import 'package:path/path.dart' as p;
|
||||||
|
import 'package:voice_message_package/voice_message_package.dart';
|
||||||
|
|
||||||
|
class AiMessageBar extends StatefulWidget {
|
||||||
|
final FocusNode? focusNode;
|
||||||
|
final BotsModel bot;
|
||||||
|
const AiMessageBar({
|
||||||
|
super.key,
|
||||||
|
this.focusNode,
|
||||||
|
required this.bot,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<AiMessageBar> createState() => _AiMessageBarState();
|
||||||
|
|
||||||
|
static PopupMenuItem<dynamic> popUpBtns({
|
||||||
|
required final String value,
|
||||||
|
required final IconData icon,
|
||||||
|
final Color? color,
|
||||||
|
final double? height,
|
||||||
|
final double? size,
|
||||||
|
}) {
|
||||||
|
return PopupMenuItem(
|
||||||
|
value: value,
|
||||||
|
height: height ?? 46,
|
||||||
|
child: Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
color: color,
|
||||||
|
size: size,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
DidvanText(
|
||||||
|
value,
|
||||||
|
color: color,
|
||||||
|
fontSize: size,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _AiMessageBarState extends State<AiMessageBar> {
|
||||||
|
TextEditingController message = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
widget.focusNode?.addListener(() {});
|
||||||
|
|
||||||
|
super.initState();
|
||||||
|
}
|
||||||
|
|
||||||
|
final record = AudioRecorder();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
super.dispose();
|
||||||
|
record.dispose();
|
||||||
|
try {
|
||||||
|
_timer.cancel();
|
||||||
|
} catch (e) {
|
||||||
|
e.printError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
late Timer _timer;
|
||||||
|
final ValueNotifier<int> _countTimer = ValueNotifier(0);
|
||||||
|
void startTimer() {
|
||||||
|
const oneSec = Duration(seconds: 1);
|
||||||
|
_timer = Timer.periodic(
|
||||||
|
oneSec,
|
||||||
|
(Timer timer) {
|
||||||
|
_countTimer.value++;
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Consumer<AiChatState>(
|
||||||
|
builder: (context, state, child) {
|
||||||
|
final historyState = context.read<HistoryAiChatState>();
|
||||||
|
|
||||||
|
return IgnorePointer(
|
||||||
|
ignoring: state.onResponsing,
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
fileContainer(),
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
audioContainer(),
|
||||||
|
Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
boxShadow: DesignConfig.defaultShadow,
|
||||||
|
color: Theme.of(context).colorScheme.white,
|
||||||
|
borderRadius: BorderRadius.circular(360)),
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: StreamBuilder<RecordState>(
|
||||||
|
stream: record.onStateChanged(),
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
return Row(
|
||||||
|
children: [
|
||||||
|
(snapshot.hasData &&
|
||||||
|
snapshot.data! !=
|
||||||
|
RecordState.stop)
|
||||||
|
? MessageBarBtn(
|
||||||
|
enable: true,
|
||||||
|
icon: DidvanIcons
|
||||||
|
.stop_circle_solid,
|
||||||
|
click: () async {
|
||||||
|
final path =
|
||||||
|
await record.stop();
|
||||||
|
state.file = FilesModel(
|
||||||
|
path.toString());
|
||||||
|
_timer.cancel();
|
||||||
|
_countTimer.value = 0;
|
||||||
|
state.update();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: widget.bot.attachmentType!
|
||||||
|
.contains('audio') &&
|
||||||
|
message.text.isEmpty &&
|
||||||
|
state.file == null &&
|
||||||
|
widget.bot.attachment != 0
|
||||||
|
? MessageBarBtn(
|
||||||
|
enable: false,
|
||||||
|
icon:
|
||||||
|
DidvanIcons.mic_regular,
|
||||||
|
click: () async {
|
||||||
|
if (await record
|
||||||
|
.hasPermission()) {
|
||||||
|
Directory? downloadDir =
|
||||||
|
await getDownloadsDirectory();
|
||||||
|
|
||||||
|
record.start(
|
||||||
|
const RecordConfig(),
|
||||||
|
path:
|
||||||
|
'${downloadDir!.path}/${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a');
|
||||||
|
startTimer();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: MessageBarBtn(
|
||||||
|
enable: message
|
||||||
|
.text.isNotEmpty ||
|
||||||
|
state.file != null,
|
||||||
|
icon:
|
||||||
|
DidvanIcons.send_light,
|
||||||
|
click: () async {
|
||||||
|
if (state.file == null &&
|
||||||
|
message
|
||||||
|
.text.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.messages
|
||||||
|
.isNotEmpty &&
|
||||||
|
DateTime.parse(state
|
||||||
|
.messages
|
||||||
|
.last
|
||||||
|
.dateTime)
|
||||||
|
.toPersianDateStr()
|
||||||
|
.contains(DateTime.parse(DateTime
|
||||||
|
.now()
|
||||||
|
.subtract(const Duration(
|
||||||
|
minutes:
|
||||||
|
210))
|
||||||
|
.toIso8601String())
|
||||||
|
.toPersianDateStr())) {
|
||||||
|
state.messages.last
|
||||||
|
.prompts
|
||||||
|
.add(Prompts(
|
||||||
|
error: false,
|
||||||
|
text: message.text,
|
||||||
|
file:
|
||||||
|
state.file?.path,
|
||||||
|
fileName: state
|
||||||
|
.file ==
|
||||||
|
null
|
||||||
|
? null
|
||||||
|
: p.basename(state
|
||||||
|
.file!.path),
|
||||||
|
finished: true,
|
||||||
|
role: 'user',
|
||||||
|
createdAt: DateTime
|
||||||
|
.now()
|
||||||
|
.subtract(
|
||||||
|
const Duration(
|
||||||
|
minutes:
|
||||||
|
210))
|
||||||
|
.toIso8601String(),
|
||||||
|
));
|
||||||
|
} else {
|
||||||
|
state.messages.add(MessageModel(
|
||||||
|
dateTime: DateTime
|
||||||
|
.now()
|
||||||
|
.subtract(
|
||||||
|
const Duration(
|
||||||
|
minutes:
|
||||||
|
210))
|
||||||
|
.toIso8601String(),
|
||||||
|
prompts: [
|
||||||
|
Prompts(
|
||||||
|
error: false,
|
||||||
|
text: message
|
||||||
|
.text,
|
||||||
|
finished: true,
|
||||||
|
file: state
|
||||||
|
.file?.path,
|
||||||
|
fileName: state
|
||||||
|
.file ==
|
||||||
|
null
|
||||||
|
? null
|
||||||
|
: p.basename(state
|
||||||
|
.file!
|
||||||
|
.path),
|
||||||
|
role: 'user',
|
||||||
|
createdAt: DateTime
|
||||||
|
.now()
|
||||||
|
.subtract(const Duration(
|
||||||
|
minutes:
|
||||||
|
210))
|
||||||
|
.toIso8601String(),
|
||||||
|
)
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
message.clear();
|
||||||
|
await state.postMessage(
|
||||||
|
widget.bot);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(
|
||||||
|
horizontal: 8.0,
|
||||||
|
),
|
||||||
|
child: snapshot.hasData &&
|
||||||
|
(snapshot.data! ==
|
||||||
|
RecordState
|
||||||
|
.record ||
|
||||||
|
snapshot.data! ==
|
||||||
|
RecordState.pause)
|
||||||
|
? Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.all(
|
||||||
|
8.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment:
|
||||||
|
MainAxisAlignment
|
||||||
|
.center,
|
||||||
|
children: [
|
||||||
|
SpinKitWave(
|
||||||
|
color:
|
||||||
|
Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
size: 32,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
),
|
||||||
|
ValueListenableBuilder<
|
||||||
|
int>(
|
||||||
|
valueListenable:
|
||||||
|
_countTimer,
|
||||||
|
builder: (context,
|
||||||
|
value,
|
||||||
|
child) =>
|
||||||
|
DidvanText(DateTimeUtils
|
||||||
|
.normalizeTimeDuration(
|
||||||
|
Duration(
|
||||||
|
seconds:
|
||||||
|
value))),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: Form(
|
||||||
|
child: TextFormField(
|
||||||
|
textInputAction:
|
||||||
|
TextInputAction.newline,
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.bodyMedium,
|
||||||
|
maxLines: 2,
|
||||||
|
minLines: 1,
|
||||||
|
// keyboardType: TextInputType.text,
|
||||||
|
|
||||||
|
controller: message,
|
||||||
|
focusNode: widget.focusNode,
|
||||||
|
enabled: !(state.file !=
|
||||||
|
null &&
|
||||||
|
widget.bot.attachment ==
|
||||||
|
1),
|
||||||
|
|
||||||
|
decoration: InputDecoration(
|
||||||
|
border: InputBorder.none,
|
||||||
|
hintText: 'بنویسید...',
|
||||||
|
hintStyle: Theme.of(
|
||||||
|
context)
|
||||||
|
.textTheme
|
||||||
|
.bodySmall!
|
||||||
|
.copyWith(
|
||||||
|
color: Theme.of(
|
||||||
|
context)
|
||||||
|
.colorScheme
|
||||||
|
.disabledText),
|
||||||
|
),
|
||||||
|
onChanged: (value) {
|
||||||
|
setState(() {});
|
||||||
|
},
|
||||||
|
)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (snapshot.hasData)
|
||||||
|
snapshot.data! == RecordState.record
|
||||||
|
? MessageBarBtn(
|
||||||
|
enable: false,
|
||||||
|
icon: DidvanIcons.pause_solid,
|
||||||
|
click: () async {
|
||||||
|
await record.pause();
|
||||||
|
_timer.cancel();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: snapshot.data! ==
|
||||||
|
RecordState.pause
|
||||||
|
? MessageBarBtn(
|
||||||
|
enable: false,
|
||||||
|
icon: DidvanIcons
|
||||||
|
.play_solid,
|
||||||
|
click: () async {
|
||||||
|
await record.resume();
|
||||||
|
startTimer();
|
||||||
|
},
|
||||||
|
)
|
||||||
|
: const SizedBox(),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
if (context
|
||||||
|
.read<HistoryAiChatState>()
|
||||||
|
.bot!
|
||||||
|
.attachmentType!
|
||||||
|
.isNotEmpty &&
|
||||||
|
(widget.bot.attachment != 0 &&
|
||||||
|
(widget.bot.attachment == 1 &&
|
||||||
|
message.text.isEmpty)) ||
|
||||||
|
widget.bot.attachment == 2)
|
||||||
|
SizedBox(
|
||||||
|
width: 46,
|
||||||
|
height: 46,
|
||||||
|
child: Center(
|
||||||
|
child: PopupMenuButton(
|
||||||
|
onOpened: () {
|
||||||
|
},
|
||||||
|
onSelected: (value) async {
|
||||||
|
switch (value) {
|
||||||
|
case 'Pdf':
|
||||||
|
FilePickerResult? result =
|
||||||
|
await MediaService.pickPdfFile();
|
||||||
|
if (result != null) {
|
||||||
|
state.file =
|
||||||
|
FilesModel(result.files.single.path!);
|
||||||
|
// Do something with the selected PDF file
|
||||||
|
}
|
||||||
|
// else {
|
||||||
|
//// User cancelled the file selection
|
||||||
|
// }
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Image':
|
||||||
|
final pickedFile =
|
||||||
|
await MediaService.pickImage(
|
||||||
|
source: ImageSource.gallery);
|
||||||
|
File? file;
|
||||||
|
if (pickedFile != null && !kIsWeb) {
|
||||||
|
file = await ImageCropper().cropImage(
|
||||||
|
sourcePath: pickedFile.path,
|
||||||
|
androidUiSettings:
|
||||||
|
const AndroidUiSettings(
|
||||||
|
toolbarTitle: 'برش تصویر'),
|
||||||
|
iosUiSettings: const IOSUiSettings(
|
||||||
|
title: 'برش تصویر',
|
||||||
|
doneButtonTitle: 'تایید',
|
||||||
|
cancelButtonTitle: 'بازگشت',
|
||||||
|
),
|
||||||
|
compressQuality: 30,
|
||||||
|
);
|
||||||
|
if (file == null) return;
|
||||||
|
}
|
||||||
|
if (pickedFile == null) return;
|
||||||
|
state.file = kIsWeb
|
||||||
|
? FilesModel(pickedFile.path)
|
||||||
|
: FilesModel(file!.path);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'Audio':
|
||||||
|
FilePickerResult? result =
|
||||||
|
await MediaService.pickAudioFile();
|
||||||
|
if (result != null) {
|
||||||
|
state.file =
|
||||||
|
FilesModel(result.files.single.path!);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
state.update();
|
||||||
|
},
|
||||||
|
itemBuilder: (BuildContext context) => [
|
||||||
|
if (historyState.bot!.attachmentType!
|
||||||
|
.contains('pdf'))
|
||||||
|
AiMessageBar.popUpBtns(
|
||||||
|
value: 'Pdf', icon: Icons.picture_as_pdf),
|
||||||
|
if (historyState.bot!.attachmentType!
|
||||||
|
.contains('image'))
|
||||||
|
AiMessageBar.popUpBtns(
|
||||||
|
value: 'Image', icon: Icons.image),
|
||||||
|
if (historyState.bot!.attachmentType!
|
||||||
|
.contains('audio'))
|
||||||
|
AiMessageBar.popUpBtns(
|
||||||
|
value: 'Audio', icon: Icons.audio_file),
|
||||||
|
],
|
||||||
|
offset: Offset(
|
||||||
|
0, widget.focusNode!.hasFocus ? -999 : 999),
|
||||||
|
position: PopupMenuPosition.over,
|
||||||
|
useRootNavigator: true,
|
||||||
|
child: Icon(
|
||||||
|
Icons.attach_file_rounded,
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.focusedBorder,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility audioContainer() {
|
||||||
|
final state = context.watch<AiChatState>();
|
||||||
|
|
||||||
|
return AnimatedVisibility(
|
||||||
|
isVisible: state.file != null &&
|
||||||
|
lookupMimeType(state.file!.path)!.startsWith('audio/'),
|
||||||
|
duration: DesignConfig.lowAnimationDuration,
|
||||||
|
child: Directionality(
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: StorageService.getValue(key: 'token'),
|
||||||
|
builder: (context, snapshot) => SizedBox(
|
||||||
|
width: MediaQuery.sizeOf(context).width,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.fromLTRB(0, 0, 0, 8),
|
||||||
|
child: Container(
|
||||||
|
height: 46,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
boxShadow: DesignConfig.defaultShadow,
|
||||||
|
color: Theme.of(context).colorScheme.white,
|
||||||
|
borderRadius: BorderRadius.circular(360)),
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20),
|
||||||
|
child: state.file == null
|
||||||
|
? const SizedBox()
|
||||||
|
: MyVoiceMessageView(
|
||||||
|
size: 32,
|
||||||
|
controller: VoiceController(
|
||||||
|
audioSrc: state.file!.path.startsWith('/uploads')
|
||||||
|
? 'https://api.didvan.app${state.file!.path}?accessToken=${snapshot.data}'
|
||||||
|
: state.file!.path,
|
||||||
|
onComplete: () {
|
||||||
|
/// do something on complete
|
||||||
|
},
|
||||||
|
onPause: () {
|
||||||
|
/// do something on pause
|
||||||
|
},
|
||||||
|
onPlaying: () {
|
||||||
|
/// do something on playing
|
||||||
|
},
|
||||||
|
onError: (err) {
|
||||||
|
/// do somethin on error
|
||||||
|
},
|
||||||
|
isFile: state.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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
AnimatedVisibility fileContainer() {
|
||||||
|
final state = context.watch<AiChatState>();
|
||||||
|
String basename = '';
|
||||||
|
if (state.file != null) {
|
||||||
|
basename = p.basename(state.file!.path);
|
||||||
|
}
|
||||||
|
return AnimatedVisibility(
|
||||||
|
isVisible: state.file != null &&
|
||||||
|
!lookupMimeType(state.file!.path)!.startsWith('audio/'),
|
||||||
|
duration: DesignConfig.lowAnimationDuration,
|
||||||
|
child: Container(
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
borderRadius: DesignConfig.mediumBorderRadius,
|
||||||
|
color: Theme.of(context).colorScheme.border,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.fromLTRB(12, 8, 12, 8),
|
||||||
|
margin: const EdgeInsets.only(bottom: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
const Icon(Icons.file_copy),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
SizedBox(
|
||||||
|
width: 260,
|
||||||
|
height: 24,
|
||||||
|
child: MarqueeText(
|
||||||
|
text: basename,
|
||||||
|
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,
|
||||||
|
);
|
||||||
|
})
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
InkWell(
|
||||||
|
onTap: () {
|
||||||
|
state.file = null;
|
||||||
|
state.update();
|
||||||
|
},
|
||||||
|
child: const Icon(DidvanIcons.close_circle_solid))
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MessageBarBtn extends StatelessWidget {
|
||||||
|
final bool enable;
|
||||||
|
final IconData icon;
|
||||||
|
final Function()? click;
|
||||||
|
final Color? color;
|
||||||
|
const MessageBarBtn(
|
||||||
|
{Key? key,
|
||||||
|
required this.enable,
|
||||||
|
required this.icon,
|
||||||
|
this.click,
|
||||||
|
this.color})
|
||||||
|
: super(key: key);
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Container(
|
||||||
|
width: 32,
|
||||||
|
height: 32,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
shape: BoxShape.circle,
|
||||||
|
color: enable
|
||||||
|
? color ?? Theme.of(context).colorScheme.primary
|
||||||
|
: Theme.of(context).colorScheme.border),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: click,
|
||||||
|
child: Icon(
|
||||||
|
icon,
|
||||||
|
size: 18,
|
||||||
|
color: enable ? Theme.of(context).colorScheme.white : null,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,294 @@
|
||||||
|
// ignore_for_file: implementation_imports, unused_element
|
||||||
|
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
|
import 'package:didvan/views/ai/ai_chat_state.dart';
|
||||||
|
import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mime/mime.dart';
|
||||||
|
import 'package:provider/provider.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.white,
|
||||||
|
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})
|
||||||
|
: 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;
|
||||||
|
|
||||||
|
@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,
|
||||||
|
);
|
||||||
|
|
||||||
|
final state = context.read<AiChatState>();
|
||||||
|
|
||||||
|
return 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),
|
||||||
|
|
||||||
|
///
|
||||||
|
Text(controller.remindingTime, style: counterTextStyle),
|
||||||
|
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
/// slider & noises
|
||||||
|
Expanded(
|
||||||
|
child: _noises(newTHeme),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 12),
|
||||||
|
|
||||||
|
///
|
||||||
|
|
||||||
|
/// speed button
|
||||||
|
// _changeSpeedButton(color),
|
||||||
|
|
||||||
|
///
|
||||||
|
if (state.file != null &&
|
||||||
|
(lookupMimeType(state.file!.path)?.startsWith('audio/') ??
|
||||||
|
false))
|
||||||
|
MessageBarBtn(
|
||||||
|
enable: true,
|
||||||
|
icon: DidvanIcons.trash_solid,
|
||||||
|
color: Theme.of(context).colorScheme.error,
|
||||||
|
click: () async {
|
||||||
|
state.file = null;
|
||||||
|
state.update();
|
||||||
|
}),
|
||||||
|
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.withOpacity(.4),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,10 +10,11 @@ import 'package:didvan/services/network/request.dart';
|
||||||
import 'package:didvan/services/network/request_helper.dart';
|
import 'package:didvan/services/network/request_helper.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter_vibrate/flutter_vibrate.dart';
|
import 'package:flutter_vibrate/flutter_vibrate.dart';
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
import 'package:record/record.dart';
|
import 'package:record/record.dart';
|
||||||
|
|
||||||
class DirectState extends CoreProvier {
|
class DirectState extends CoreProvier {
|
||||||
final _recorder = Record();
|
final _recorder = AudioRecorder();
|
||||||
final List<MessageData> messages = [];
|
final List<MessageData> messages = [];
|
||||||
late final int typeId;
|
late final int typeId;
|
||||||
final Map<String, List<int>> dailyMessages = {};
|
final Map<String, List<int>> dailyMessages = {};
|
||||||
|
|
@ -59,7 +60,10 @@ class DirectState extends CoreProvier {
|
||||||
Vibrate.feedback(FeedbackType.medium);
|
Vibrate.feedback(FeedbackType.medium);
|
||||||
}
|
}
|
||||||
isRecording = true;
|
isRecording = true;
|
||||||
_recorder.start();
|
Directory tempDir = await getTemporaryDirectory();
|
||||||
|
_recorder.start(const RecordConfig(),
|
||||||
|
path:
|
||||||
|
'${tempDir.path}/${DateTime.now().millisecondsSinceEpoch ~/ 1000}.m4a');
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,26 +1,40 @@
|
||||||
// ignore_for_file: deprecated_member_use
|
// ignore_for_file: deprecated_member_use
|
||||||
|
|
||||||
|
import 'package:cached_network_image/cached_network_image.dart';
|
||||||
import 'package:didvan/config/design_config.dart';
|
import 'package:didvan/config/design_config.dart';
|
||||||
|
import 'package:didvan/config/theme_data.dart';
|
||||||
|
import 'package:didvan/constants/app_icons.dart';
|
||||||
|
import 'package:didvan/constants/assets.dart';
|
||||||
|
import 'package:didvan/main.dart';
|
||||||
|
import 'package:didvan/models/ai/ai_chat_args.dart';
|
||||||
import 'package:didvan/models/notification_message.dart';
|
import 'package:didvan/models/notification_message.dart';
|
||||||
import 'package:didvan/models/view/action_sheet_data.dart';
|
import 'package:didvan/models/view/action_sheet_data.dart';
|
||||||
import 'package:didvan/providers/theme.dart';
|
import 'package:didvan/providers/theme.dart';
|
||||||
|
import 'package:didvan/routes/routes.dart';
|
||||||
import 'package:didvan/services/app_initalizer.dart';
|
import 'package:didvan/services/app_initalizer.dart';
|
||||||
import 'package:didvan/services/notification/notification_service.dart';
|
import 'package:didvan/services/notification/notification_service.dart';
|
||||||
import 'package:didvan/utils/action_sheet.dart';
|
import 'package:didvan/utils/action_sheet.dart';
|
||||||
import 'package:didvan/views/ai/history_ai_chat_page.dart';
|
import 'package:didvan/views/ai/ai.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/home/categories/categories_page.dart';
|
import 'package:didvan/views/home/categories/categories_page.dart';
|
||||||
import 'package:didvan/views/home/main/main_page.dart';
|
import 'package:didvan/views/home/main/main_page.dart';
|
||||||
import 'package:didvan/views/home/home_state.dart';
|
import 'package:didvan/views/home/home_state.dart';
|
||||||
import 'package:didvan/views/home/new_statistic/new_statistic.dart';
|
import 'package:didvan/views/home/new_statistic/new_statistic.dart';
|
||||||
import 'package:didvan/views/home/search/search.dart';
|
import 'package:didvan/views/home/search/search.dart';
|
||||||
|
import 'package:didvan/views/widgets/didvan/divider.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/text.dart';
|
import 'package:didvan/views/widgets/didvan/text.dart';
|
||||||
import 'package:didvan/views/widgets/logo_app_bar.dart';
|
import 'package:didvan/views/widgets/logo_app_bar.dart';
|
||||||
import 'package:didvan/views/widgets/didvan/bnb.dart';
|
import 'package:didvan/views/widgets/didvan/bnb.dart';
|
||||||
|
import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
|
||||||
|
import 'package:flutter/cupertino.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
import '../../services/app_home_widget/home_widget_repository.dart';
|
import '../../services/app_home_widget/home_widget_repository.dart';
|
||||||
|
|
||||||
|
final GlobalKey<ScaffoldState> homeScaffKey = GlobalKey<ScaffoldState>();
|
||||||
|
|
||||||
class Home extends StatefulWidget {
|
class Home extends StatefulWidget {
|
||||||
const Home({Key? key}) : super(key: key);
|
const Home({Key? key}) : super(key: key);
|
||||||
|
|
||||||
|
|
@ -42,6 +56,9 @@ class _HomeState extends State<Home>
|
||||||
state.tabController = _tabController;
|
state.tabController = _tabController;
|
||||||
_tabController.addListener(() {
|
_tabController.addListener(() {
|
||||||
state.currentPageIndex = _tabController.index;
|
state.currentPageIndex = _tabController.index;
|
||||||
|
if (_tabController.index == 2) {
|
||||||
|
context.read<HistoryAiChatState>().getChats();
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Future.delayed(Duration.zero, () {
|
Future.delayed(Duration.zero, () {
|
||||||
|
|
@ -64,7 +81,420 @@ class _HomeState extends State<Home>
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
appBar: const LogoAppBar(),
|
key: homeScaffKey,
|
||||||
|
appBar: LogoAppBar(
|
||||||
|
canSearch: context.watch<HomeState>().tabController.index != 2,
|
||||||
|
),
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
|
drawer: context.watch<HomeState>().tabController.index == 2
|
||||||
|
? Drawer(
|
||||||
|
child: Consumer<HistoryAiChatState>(
|
||||||
|
builder: (context, state, child) {
|
||||||
|
return Column(
|
||||||
|
children: [
|
||||||
|
const SizedBox(
|
||||||
|
height: 8,
|
||||||
|
),
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.only(left: 20.0, top: 8),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
children: [
|
||||||
|
InkWell(
|
||||||
|
onTap: () =>
|
||||||
|
homeScaffKey.currentState!.closeDrawer(),
|
||||||
|
child: const Icon(
|
||||||
|
DidvanIcons.close_regular,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Icon(
|
||||||
|
DidvanIcons.ai_solid,
|
||||||
|
size: MediaQuery.sizeOf(context).width / 5,
|
||||||
|
),
|
||||||
|
const DidvanText('هوشان'),
|
||||||
|
const SizedBox(
|
||||||
|
height: 24,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 20.0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
children: [
|
||||||
|
drawerBtn(
|
||||||
|
icon: Icons.handshake_rounded,
|
||||||
|
text: 'ساخت دستیار شخصی',
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment.start,
|
||||||
|
enable: false),
|
||||||
|
const DidvanDivider(),
|
||||||
|
drawerBtn(
|
||||||
|
icon: CupertinoIcons.doc_text_search,
|
||||||
|
text: 'جستجو در مدلها',
|
||||||
|
click: () {
|
||||||
|
ActionSheetUtils.botsDialogSelect(
|
||||||
|
context: context, state: state);
|
||||||
|
homeScaffKey.currentState!
|
||||||
|
.closeDrawer();
|
||||||
|
},
|
||||||
|
enable: false),
|
||||||
|
const DidvanDivider(),
|
||||||
|
drawerBtn(
|
||||||
|
icon: DidvanIcons.chats_regular,
|
||||||
|
text: 'تاریخچه همه گفتگوها',
|
||||||
|
label: 'حذف همه',
|
||||||
|
click: () {
|
||||||
|
Navigator.of(context)
|
||||||
|
.pushNamed(Routes.aiHistory);
|
||||||
|
},
|
||||||
|
labelClick: state.chats.isEmpty
|
||||||
|
? null
|
||||||
|
: () async {
|
||||||
|
ActionSheetUtils.context =
|
||||||
|
context;
|
||||||
|
await ActionSheetUtils.openDialog(
|
||||||
|
data: ActionSheetData(
|
||||||
|
onConfirmed: () async {
|
||||||
|
await state
|
||||||
|
.deleteAllChat();
|
||||||
|
},
|
||||||
|
content: Column(
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment:
|
||||||
|
CrossAxisAlignment
|
||||||
|
.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
DidvanIcons
|
||||||
|
.trash_solid,
|
||||||
|
color: Theme.of(
|
||||||
|
context)
|
||||||
|
.colorScheme
|
||||||
|
.error,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
DidvanText(
|
||||||
|
'پاک کردن همه گفتوگوها',
|
||||||
|
color: Theme.of(
|
||||||
|
context)
|
||||||
|
.colorScheme
|
||||||
|
.error,
|
||||||
|
fontSize: 20,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
const DidvanText(
|
||||||
|
'آیا از پاک کردن تمامی گفتوگوهای انجام شده با هوشان اطمینان دارید؟'),
|
||||||
|
],
|
||||||
|
)));
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
// SearchField(
|
||||||
|
// title: 'title',
|
||||||
|
// onChanged: (value) {},
|
||||||
|
// focusNode: FocusNode()),
|
||||||
|
// SizedBox(
|
||||||
|
// height: 12,
|
||||||
|
// ),
|
||||||
|
Expanded(
|
||||||
|
child: state.chats.isEmpty
|
||||||
|
? Padding(
|
||||||
|
padding:
|
||||||
|
const EdgeInsets.all(12.0),
|
||||||
|
child: EmptyState(
|
||||||
|
asset: Assets.emptyResult,
|
||||||
|
title: 'لیست خالی است',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: state.loadingdeleteAll
|
||||||
|
? Center(
|
||||||
|
child: Image.asset(
|
||||||
|
Assets.loadingAnimation,
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: ListView.builder(
|
||||||
|
shrinkWrap: true,
|
||||||
|
itemCount: state.chats.length,
|
||||||
|
padding: const EdgeInsets
|
||||||
|
.symmetric(
|
||||||
|
horizontal: 12),
|
||||||
|
physics:
|
||||||
|
const BouncingScrollPhysics(),
|
||||||
|
itemBuilder:
|
||||||
|
(context, index) {
|
||||||
|
final chat =
|
||||||
|
state.chats[index];
|
||||||
|
TextEditingController
|
||||||
|
title =
|
||||||
|
TextEditingController(
|
||||||
|
text: chat.title);
|
||||||
|
|
||||||
|
return Padding(
|
||||||
|
padding: const EdgeInsets
|
||||||
|
.symmetric(
|
||||||
|
vertical: 8.0),
|
||||||
|
child: InkWell(
|
||||||
|
onTap: () {
|
||||||
|
navigatorKey
|
||||||
|
.currentState!
|
||||||
|
.pushNamed(
|
||||||
|
Routes.aiChat,
|
||||||
|
arguments: AiChatArgs(
|
||||||
|
bot: chat
|
||||||
|
.bot!,
|
||||||
|
chat:
|
||||||
|
chat));
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
ClipOval(
|
||||||
|
child:
|
||||||
|
CachedNetworkImage(
|
||||||
|
imageUrl: chat
|
||||||
|
.bot!.image
|
||||||
|
.toString(),
|
||||||
|
width: 24,
|
||||||
|
height: 24,
|
||||||
|
)),
|
||||||
|
const SizedBox(
|
||||||
|
width: 12,
|
||||||
|
),
|
||||||
|
Expanded(
|
||||||
|
child: chat.isEditing !=
|
||||||
|
null &&
|
||||||
|
chat
|
||||||
|
.isEditing!
|
||||||
|
? TextFormField(
|
||||||
|
controller:
|
||||||
|
title,
|
||||||
|
style: const TextStyle(
|
||||||
|
fontSize:
|
||||||
|
12),
|
||||||
|
textAlignVertical:
|
||||||
|
TextAlignVertical
|
||||||
|
.bottom,
|
||||||
|
maxLines:
|
||||||
|
1,
|
||||||
|
decoration:
|
||||||
|
const InputDecoration(
|
||||||
|
isDense:
|
||||||
|
true,
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
vertical:
|
||||||
|
5,
|
||||||
|
horizontal:
|
||||||
|
10),
|
||||||
|
border:
|
||||||
|
OutlineInputBorder(),
|
||||||
|
))
|
||||||
|
: Text(
|
||||||
|
chat.title
|
||||||
|
.toString(),
|
||||||
|
overflow:
|
||||||
|
TextOverflow
|
||||||
|
.ellipsis,
|
||||||
|
maxLines:
|
||||||
|
1,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 24,
|
||||||
|
),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
chat.isEditing !=
|
||||||
|
null &&
|
||||||
|
chat
|
||||||
|
.isEditing! &&
|
||||||
|
state
|
||||||
|
.loadingchangeTitle
|
||||||
|
? const SizedBox(
|
||||||
|
width:
|
||||||
|
12,
|
||||||
|
height:
|
||||||
|
12,
|
||||||
|
child:
|
||||||
|
CircularProgressIndicator())
|
||||||
|
: InkWell(
|
||||||
|
onTap:
|
||||||
|
() async {
|
||||||
|
if (title
|
||||||
|
.text
|
||||||
|
.isNotEmpty) {
|
||||||
|
await state.changeNameChat(
|
||||||
|
chat.id!,
|
||||||
|
index,
|
||||||
|
title.text);
|
||||||
|
title.clear();
|
||||||
|
}
|
||||||
|
if (chat.isEditing !=
|
||||||
|
null) {
|
||||||
|
chat.isEditing =
|
||||||
|
!chat.isEditing!;
|
||||||
|
state.update();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
chat.isEditing =
|
||||||
|
true;
|
||||||
|
|
||||||
|
state
|
||||||
|
.update();
|
||||||
|
},
|
||||||
|
child:
|
||||||
|
Icon(
|
||||||
|
chat.isEditing != null && chat.isEditing!
|
||||||
|
? Icons.save
|
||||||
|
: Icons.edit_outlined,
|
||||||
|
size:
|
||||||
|
18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
PopupMenuButton(
|
||||||
|
onSelected:
|
||||||
|
(value) async {
|
||||||
|
switch (
|
||||||
|
value) {
|
||||||
|
case 'حذف پیام':
|
||||||
|
await state.deleteChat(
|
||||||
|
chat.id!,
|
||||||
|
index);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'آرشیو':
|
||||||
|
await state.archivedChat(
|
||||||
|
chat.id!,
|
||||||
|
index);
|
||||||
|
await state
|
||||||
|
.getChats();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
state
|
||||||
|
.update();
|
||||||
|
},
|
||||||
|
itemBuilder:
|
||||||
|
(BuildContext
|
||||||
|
context) {
|
||||||
|
return <PopupMenuEntry>[
|
||||||
|
AiMessageBar.popUpBtns(
|
||||||
|
value:
|
||||||
|
'حذف پیام',
|
||||||
|
icon: DidvanIcons
|
||||||
|
.trash_regular,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.error,
|
||||||
|
height:
|
||||||
|
24,
|
||||||
|
size:
|
||||||
|
12),
|
||||||
|
AiMessageBar
|
||||||
|
.popUpBtns(
|
||||||
|
value:
|
||||||
|
'آرشیو',
|
||||||
|
icon: Icons
|
||||||
|
.folder_copy,
|
||||||
|
height:
|
||||||
|
24,
|
||||||
|
size:
|
||||||
|
12,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
},
|
||||||
|
offset:
|
||||||
|
const Offset(
|
||||||
|
0, 0),
|
||||||
|
position:
|
||||||
|
PopupMenuPosition
|
||||||
|
.under,
|
||||||
|
useRootNavigator:
|
||||||
|
true,
|
||||||
|
child:
|
||||||
|
const Icon(
|
||||||
|
Icons
|
||||||
|
.more_vert,
|
||||||
|
size: 18,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
// SizedBox(
|
||||||
|
// height: 12,
|
||||||
|
// ),
|
||||||
|
// Text('نمایش قدیمیترها')
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
const DidvanDivider(),
|
||||||
|
drawerBtn(
|
||||||
|
icon: Icons.folder_copy_outlined,
|
||||||
|
text: 'گفتوگوهای آرشیو شده',
|
||||||
|
click: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
Routes.aiHistory,
|
||||||
|
arguments: true);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 12,
|
||||||
|
),
|
||||||
|
drawerBtn(
|
||||||
|
icon: DidvanIcons.support_regular,
|
||||||
|
text: 'پیام به پشتیبانی',
|
||||||
|
click: () {
|
||||||
|
Navigator.of(context).pushNamed(
|
||||||
|
Routes.direct,
|
||||||
|
arguments: {
|
||||||
|
'type': 'پشتیبانی اپلیکیشن'
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
height: 32,
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
)
|
||||||
|
: null,
|
||||||
body: WillPopScope(
|
body: WillPopScope(
|
||||||
onWillPop: () async {
|
onWillPop: () async {
|
||||||
if (context.read<HomeState>().tabController.index == 0) {
|
if (context.read<HomeState>().tabController.index == 0) {
|
||||||
|
|
@ -101,7 +531,7 @@ class _HomeState extends State<Home>
|
||||||
children: const [
|
children: const [
|
||||||
MainPage(),
|
MainPage(),
|
||||||
CategoriesPage(),
|
CategoriesPage(),
|
||||||
HistoryAiChatPage(),
|
Ai(),
|
||||||
NewStatistic(),
|
NewStatistic(),
|
||||||
//Statistic(),
|
//Statistic(),
|
||||||
// Bookmarks(),
|
// Bookmarks(),
|
||||||
|
|
@ -125,4 +555,58 @@ class _HomeState extends State<Home>
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Widget drawerBtn(
|
||||||
|
{final CrossAxisAlignment? crossAxisAlignment,
|
||||||
|
required final IconData icon,
|
||||||
|
required final String text,
|
||||||
|
final bool enable = true,
|
||||||
|
final String? label,
|
||||||
|
final Function()? labelClick,
|
||||||
|
final Function()? click}) {
|
||||||
|
return InkWell(
|
||||||
|
onTap: enable ? click : null,
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.symmetric(horizontal: 12.0),
|
||||||
|
child: Row(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
|
children: [
|
||||||
|
Row(
|
||||||
|
crossAxisAlignment:
|
||||||
|
crossAxisAlignment ?? CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
Icon(
|
||||||
|
icon,
|
||||||
|
color: enable
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.disabledText,
|
||||||
|
),
|
||||||
|
const SizedBox(
|
||||||
|
width: 8,
|
||||||
|
),
|
||||||
|
Column(
|
||||||
|
children: [
|
||||||
|
DidvanText(text,
|
||||||
|
color: enable
|
||||||
|
? null
|
||||||
|
: Theme.of(context).colorScheme.disabledText),
|
||||||
|
// if (!enable) Text('در حال توسعه ...')
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
if (label != null)
|
||||||
|
InkWell(
|
||||||
|
onTap: labelClick,
|
||||||
|
child: DidvanText(
|
||||||
|
label,
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
fontSize: 12,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -176,8 +176,8 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
.title,
|
.title,
|
||||||
))),
|
))),
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
isVisible: state.showThemes,
|
|
||||||
duration: DesignConfig.lowAnimationDuration,
|
duration: DesignConfig.lowAnimationDuration,
|
||||||
|
isVisible: state.showThemes,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(
|
padding: const EdgeInsets.symmetric(
|
||||||
vertical: 12.0),
|
vertical: 12.0),
|
||||||
|
|
@ -242,6 +242,7 @@ class _ProfilePageState extends State<ProfilePage> {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
isVisible: state.showContactUs,
|
isVisible: state.showContactUs,
|
||||||
duration: DesignConfig.lowAnimationDuration,
|
duration: DesignConfig.lowAnimationDuration,
|
||||||
|
fadeMode: FadeMode.vertical,
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding:
|
padding:
|
||||||
const EdgeInsets.only(right: 8.0, top: 8),
|
const EdgeInsets.only(right: 8.0, top: 8),
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ class DidvanBNB extends StatelessWidget {
|
||||||
),
|
),
|
||||||
_NavBarItem(
|
_NavBarItem(
|
||||||
isSelected: currentTabIndex == 2,
|
isSelected: currentTabIndex == 2,
|
||||||
title: 'هوش مصنوعی',
|
title: 'هوشان',
|
||||||
selectedIcon: DidvanIcons.ai_solid,
|
selectedIcon: DidvanIcons.ai_solid,
|
||||||
unselectedIcon: DidvanIcons.ai_regular,
|
unselectedIcon: DidvanIcons.ai_regular,
|
||||||
onTap: () => onTabChanged(2),
|
onTap: () => onTabChanged(2),
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:provider/provider.dart';
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
|
class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
const LogoAppBar({Key? key}) : super(key: key);
|
final bool canSearch;
|
||||||
|
const LogoAppBar({Key? key, this.canSearch = true}) : super(key: key);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Size get preferredSize => const Size(double.infinity, 144);
|
Size get preferredSize => const Size(double.infinity, 144);
|
||||||
|
|
@ -28,7 +29,7 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
final state = context.read<HomeState>();
|
final state = context.read<HomeState>();
|
||||||
final MediaQueryData d = MediaQuery.of(context);
|
final MediaQueryData d = MediaQuery.of(context);
|
||||||
return Container(
|
return Container(
|
||||||
height: 144,
|
height: canSearch ? 144 : 100,
|
||||||
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,
|
||||||
|
|
@ -100,6 +101,7 @@ class LogoAppBar extends StatelessWidget implements PreferredSizeWidget {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
|
if (canSearch)
|
||||||
Consumer<HomeState>(
|
Consumer<HomeState>(
|
||||||
builder: (context, state, child) => SearchField(
|
builder: (context, state, child) => SearchField(
|
||||||
key: state.search.isEmpty ? ValueKey(state.search) : null,
|
key: state.search.isEmpty ? ValueKey(state.search) : null,
|
||||||
|
|
|
||||||
74
pubspec.lock
74
pubspec.lock
|
|
@ -726,7 +726,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.11.0"
|
version: "1.11.0"
|
||||||
mime:
|
mime:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: mime
|
name: mime
|
||||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||||
|
|
@ -766,7 +766,7 @@ packages:
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.1"
|
version: "3.0.1"
|
||||||
path:
|
path:
|
||||||
dependency: transitive
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: path
|
name: path
|
||||||
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
|
||||||
|
|
@ -937,50 +937,58 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: record
|
name: record
|
||||||
sha256: f703397f5a60d9b2b655b3acc94ba079b2d9a67dc0725bdb90ef2fee2441ebf7
|
sha256: "4a5cf4d083d1ee49e0878823c4397d073f8eb0a775f31215d388e2bc47a9e867"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.4.4"
|
version: "5.1.2"
|
||||||
|
record_android:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: record_android
|
||||||
|
sha256: d7af0b3119725a0f561817c72b5f5eca4d7a76d441deef519ae04e4824c0734c
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.2.6"
|
||||||
|
record_darwin:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: record_darwin
|
||||||
|
sha256: fe90d302acb1f3cee1ade5df9c150ca5cee33b48d8cdf1cf433bf577d7f00134
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.2"
|
||||||
record_linux:
|
record_linux:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_linux
|
name: record_linux
|
||||||
sha256: "348db92c4ec1b67b1b85d791381c8c99d7c6908de141e7c9edc20dad399b15ce"
|
sha256: "74d41a9ebb1eb498a38e9a813dd524e8f0b4fdd627270bda9756f437b110a3e3"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.4.1"
|
version: "0.7.2"
|
||||||
record_macos:
|
|
||||||
dependency: transitive
|
|
||||||
description:
|
|
||||||
name: record_macos
|
|
||||||
sha256: d1d0199d1395f05e218207e8cacd03eb9dc9e256ddfe2cfcbbb90e8edea06057
|
|
||||||
url: "https://pub.dev"
|
|
||||||
source: hosted
|
|
||||||
version: "0.2.2"
|
|
||||||
record_platform_interface:
|
record_platform_interface:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_platform_interface
|
name: record_platform_interface
|
||||||
sha256: "7a2d4ce7ac3752505157e416e4e0d666a54b1d5d8601701b7e7e5e30bec181b4"
|
sha256: "11f8b03ea8a0e279b0e306571dbe0db0202c0b8e866495c9fa1ad2281d5e4c15"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "1.1.0"
|
||||||
record_web:
|
record_web:
|
||||||
dependency: "direct main"
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_web
|
name: record_web
|
||||||
sha256: "219ffb4ca59b4338117857db56d3ffadbde3169bcaf1136f5f4d4656f4a2372d"
|
sha256: "0ef370d1e6553ad33c39dd03103b374e7861f3518b0533e64c94d73f988a5ffa"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.5.0"
|
version: "1.1.0"
|
||||||
record_windows:
|
record_windows:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: record_windows
|
name: record_windows
|
||||||
sha256: "42d545155a26b20d74f5107648dbb3382dbbc84dc3f1adc767040359e57a1345"
|
sha256: e653555aa3fda168aded7c34e11bd82baf0c6ac84e7624553def3c77ffefd36f
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.1"
|
version: "1.0.3"
|
||||||
rive:
|
rive:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -1066,6 +1074,22 @@ 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:
|
||||||
|
|
@ -1282,6 +1306,14 @@ 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:
|
||||||
|
|
|
||||||
|
|
@ -51,8 +51,7 @@ dependencies:
|
||||||
carousel_slider: ^4.0.0
|
carousel_slider: ^4.0.0
|
||||||
flutter_vibrate: ^1.3.0
|
flutter_vibrate: ^1.3.0
|
||||||
universal_html: ^2.0.8
|
universal_html: ^2.0.8
|
||||||
record: ^4.4.3
|
record: ^5.1.2
|
||||||
record_web: ^0.5.0
|
|
||||||
persian_datetime_picker: ^2.6.0
|
persian_datetime_picker: ^2.6.0
|
||||||
persian_number_utility: ^1.1.1
|
persian_number_utility: ^1.1.1
|
||||||
bot_toast: ^4.0.1
|
bot_toast: ^4.0.1
|
||||||
|
|
@ -90,8 +89,12 @@ 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
|
||||||
|
|
||||||
# onesignal_flutter: ^3.5.0
|
# onesignal_flutter: ^3.5.0
|
||||||
|
|
||||||
|
path: any
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
sdk: flutter
|
sdk: flutter
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue