This commit is contained in:
OkaykOrhmn 2024-11-20 08:50:12 +03:30
parent 7bec995377
commit f28592c421
9 changed files with 324 additions and 535 deletions

View File

@ -16,6 +16,7 @@ import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:didvan/views/widgets/state_handlers/empty_state.dart'; import 'package:didvan/views/widgets/state_handlers/empty_state.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class ActionSheetUtils { class ActionSheetUtils {
final BuildContext context; final BuildContext context;
@ -286,118 +287,83 @@ class ActionSheetUtils {
); );
} }
Future<void> botsDialogSelect( Future<void> botsDialogSelect({
{required final BuildContext context, required final BuildContext context,
required final HistoryAiChatState state}) async { }) async {
ActionSheetUtils(context).openDialog( ActionSheetUtils(context).openDialog(
data: ActionSheetData( data: ActionSheetData(
hasConfirmButton: false, hasConfirmButton: false,
hasDismissButton: false, hasDismissButton: false,
content: Column( content: SizedBox(
children: [ width: double.infinity,
// Row( height: MediaQuery.sizeOf(context).height / 3,
// mainAxisAlignment: MainAxisAlignment.end, child: Consumer<HistoryAiChatState>(
// children: [ builder: (context, state, child) {
// Padding( return state.loadingBots
// padding: const EdgeInsets.symmetric(vertical: 8.0), ? Center(
// child: InkWell( child: Image.asset(
// onTap: () { Assets.loadingAnimation,
// ActionSheetUtils.pop(); width: 60,
// }, height: 60,
// child: const Icon(DidvanIcons.close_solid)), ),
// ) )
// ], : state.bots.isEmpty
// ), ? Padding(
// SearchField( padding:
// title: 'هوش مصنوعی', const EdgeInsets.symmetric(horizontal: 12.0),
// value: state.search, child: EmptyState(
// onChanged: (value) { asset: Assets.emptyResult,
// state.search = value; title: 'نتیجه‌ای پیدا نشد',
// if (value.isEmpty) { height: 120,
// 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 : ListView.builder(
? Padding( itemCount: state.bots.length,
padding: const EdgeInsets.symmetric( physics: const BouncingScrollPhysics(),
horizontal: 12.0), shrinkWrap: true,
child: EmptyState( itemBuilder: (context, index) {
asset: Assets.emptyResult, final bot = state.bots[index];
title: 'نتیجه‌ای پیدا نشد', return InkWell(
height: 120, onTap: () {
), ActionSheetUtils(context).pop();
) state.bot = bot;
: ListView.builder( state.update();
itemCount: state.bots.length, },
physics: const BouncingScrollPhysics(), child: Container(
shrinkWrap: true, alignment: Alignment.center,
itemBuilder: (context, index) { padding:
final bot = state.bots[index]; const EdgeInsets.symmetric(vertical: 8),
return InkWell( decoration: BoxDecoration(
onTap: () { border: index == state.bots.length - 1
ActionSheetUtils(context).pop(); ? null
state.bot = bot; : Border(
state.update(); bottom: BorderSide(
}, color: Theme.of(context)
child: Container( .colorScheme
alignment: Alignment.center, .border,
padding: const EdgeInsets.symmetric( width: 1))),
vertical: 8), child: Row(
decoration: BoxDecoration( children: [
border: index == state.bots.length - 1 SkeletonImage(
? null imageUrl: bot.image.toString(),
: Border( width: 42,
bottom: BorderSide( height: 42,
color: Theme.of(context) borderRadius:
.colorScheme BorderRadius.circular(360),
.border,
width: 1))),
child: Row(
children: [
SkeletonImage(
imageUrl: bot.image.toString(),
width: 42,
height: 42,
borderRadius:
BorderRadius.circular(360),
),
const SizedBox(width: 12),
Expanded(
child: DidvanText(
bot.name.toString(),
maxLines: 1,
overflow: TextOverflow.ellipsis,
))
],
), ),
), const SizedBox(width: 12),
); Expanded(
}), child: DidvanText(
), bot.name.toString(),
) maxLines: 1,
], overflow: TextOverflow.ellipsis,
))
],
),
),
);
});
}),
))); )));
} }

View File

@ -34,9 +34,9 @@ class _AiState extends State<Ai> {
Future.delayed( Future.delayed(
Duration.zero, Duration.zero,
() { () {
if (context.read<HistoryAiChatState>().refresh) { if (state.refresh) {
context.read<HistoryAiChatState>().getChats(); state.getBots();
context.read<HistoryAiChatState>().refresh = false; state.refresh = false;
} }
state.getBots(); state.getBots();
}, },
@ -85,8 +85,7 @@ class _AiState extends State<Ai> {
), ),
InkWell( InkWell(
onTap: () => ActionSheetUtils(context) onTap: () => ActionSheetUtils(context)
.botsDialogSelect( .botsDialogSelect(context: context),
context: context, state: state),
child: Container( child: Container(
decoration: BoxDecoration( decoration: BoxDecoration(
borderRadius: borderRadius:
@ -287,26 +286,29 @@ class _AiState extends State<Ai> {
} }
}, },
), ),
Positioned( Consumer<HistoryAiChatState>(builder: (context, state, child) {
top: 32, if (state.bots.isEmpty) return const SizedBox();
right: 0, return Positioned(
child: InkWell( top: 32,
onTap: () => homeScaffKey.currentState!.openDrawer(), right: 0,
child: Container( child: InkWell(
width: 46, onTap: () => homeScaffKey.currentState!.openDrawer(),
height: 46, child: Container(
decoration: BoxDecoration( width: 46,
color: Theme.of(context).colorScheme.surface, height: 46,
borderRadius: const BorderRadius.only( decoration: BoxDecoration(
topLeft: Radius.circular(12), color: Theme.of(context).colorScheme.surface,
bottomLeft: Radius.circular(12)), borderRadius: const BorderRadius.only(
boxShadow: DesignConfig.defaultShadow), topLeft: Radius.circular(12),
child: Icon( bottomLeft: Radius.circular(12)),
DidvanIcons.angle_left_light, boxShadow: DesignConfig.defaultShadow),
color: Theme.of(context).colorScheme.title, child: Icon(
), DidvanIcons.angle_left_light,
)), color: Theme.of(context).colorScheme.title,
) ),
)),
);
})
], ],
); );
} }

View File

@ -63,7 +63,7 @@ class _CreateBotAssistantsPageState extends State<CreateBotAssistantsPage> {
super.initState(); super.initState();
} }
void onConfirm(CreateBotAssistantsState state, int selectedBotId) async { void onConfirm(CreateBotAssistantsState state) async {
bool isValid = true; bool isValid = true;
// if (!_formYouTubeKey.currentState!.validate()) { // if (!_formYouTubeKey.currentState!.validate()) {
// isValid = false; // isValid = false;
@ -95,7 +95,7 @@ class _CreateBotAssistantsPageState extends State<CreateBotAssistantsPage> {
type: state.selectedBotType, type: state.selectedBotType,
name: state.name, name: state.name,
description: state.desc, description: state.desc,
botId: selectedBotId, botId: state.initialBot!.id!,
prompt: state.prompt, prompt: state.prompt,
webLinks: webLinks:
state.countOfLink.first.isEmpty ? null : state.countOfLink, state.countOfLink.first.isEmpty ? null : state.countOfLink,
@ -135,9 +135,6 @@ class _CreateBotAssistantsPageState extends State<CreateBotAssistantsPage> {
), ),
body: Consumer<CreateBotAssistantsState>(builder: (BuildContext context, body: Consumer<CreateBotAssistantsState>(builder: (BuildContext context,
CreateBotAssistantsState state, Widget? child) { CreateBotAssistantsState state, Widget? child) {
int selectedBotId = state.selectedBotType == 'text'
? state.allBots.first.id!
: state.imageBots.first.id!;
return state.loading return state.loading
? Center( ? Center(
child: SpinKitThreeBounce( child: SpinKitThreeBounce(
@ -400,7 +397,7 @@ class _CreateBotAssistantsPageState extends State<CreateBotAssistantsPage> {
.surface), .surface),
// hintText: "انتخاب کنید", // hintText: "انتخاب کنید",
onChanged: (value) { onChanged: (value) {
selectedBotId = value!.id!; state.initialBot = value;
}, },
), ),
), ),
@ -804,7 +801,7 @@ class _CreateBotAssistantsPageState extends State<CreateBotAssistantsPage> {
? ' ' ? ' '
: 'ذخیره تغییرات', : 'ذخیره تغییرات',
onPressed: () async => onPressed: () async =>
onConfirm(state, selectedBotId), onConfirm(state),
), ),
if (state.loadingCreate) if (state.loadingCreate)
const Positioned.fill( const Positioned.fill(
@ -871,8 +868,7 @@ class _CreateBotAssistantsPageState extends State<CreateBotAssistantsPage> {
DidvanButton( DidvanButton(
title: title:
state.loadingCreate ? ' ' : 'ذخیره', state.loadingCreate ? ' ' : 'ذخیره',
onPressed: () async => onPressed: () async => onConfirm(state)),
onConfirm(state, selectedBotId)),
if (state.loadingCreate) if (state.loadingCreate)
const Positioned.fill( const Positioned.fill(
child: Center( child: Center(

View File

@ -29,7 +29,7 @@ class CreateBotAssistantsState extends CoreProvier {
navigatorKey.currentContext!.read<HistoryAiChatState>().bots; navigatorKey.currentContext!.read<HistoryAiChatState>().bots;
final List<String> botModels = ['مدل زبانی', 'مدل تصویری']; final List<String> botModels = ['مدل زبانی', 'مدل تصویری'];
BotsModel? initialBot; late BotsModel? initialBot = allBots.first;
List<String> countOfLink = ['']; List<String> countOfLink = [''];
List<FileCreateAssistantsModel> files = []; List<FileCreateAssistantsModel> files = [];
@ -123,6 +123,9 @@ class CreateBotAssistantsState extends CoreProvier {
} }
selectedBotType = initialBot?.responseType ?? 'text'; selectedBotType = initialBot?.responseType ?? 'text';
} }
if (initialBot == null) {
selectedBotType == 'text' ? allBots.first : imageBots.first;
}
appState = AppState.idle; appState = AppState.idle;
loading = false; loading = false;
update(); update();

View File

@ -9,7 +9,6 @@ import 'package:didvan/providers/core.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';
class HistoryAiChatState extends CoreProvier { class HistoryAiChatState extends CoreProvier {
final List<ChatsModel> chats = []; final List<ChatsModel> chats = [];
@ -17,15 +16,17 @@ class HistoryAiChatState extends CoreProvier {
// final List<int> chatsToDelete = []; // final List<int> chatsToDelete = [];
final List<BotsModel> bots = []; final List<BotsModel> bots = [];
BotsModel? bot; BotsModel? bot;
ValueNotifier<bool> loadingBots = ValueNotifier(false); bool loadingBots = false;
bool loadingchangeTitle = false; bool loadingchangeTitle = false;
bool loadingdeleteAll = false; bool loadingdeleteAll = false;
bool loadinggetAll = false;
bool refresh = false; bool refresh = false;
Timer? timer; Timer? timer;
String search = ''; String search = '';
Future<void> getChats({final bool archived = false}) async { Future<void> getChats({final bool archived = false}) async {
appState = AppState.busy; appState = AppState.busy;
loadinggetAll = true;
update(); update();
final service = RequestService( final service = RequestService(
archived ? RequestHelper.aiArchived() : RequestHelper.aiChats(), archived ? RequestHelper.aiArchived() : RequestHelper.aiChats(),
@ -39,10 +40,13 @@ class HistoryAiChatState extends CoreProvier {
.add(ChatsModel.fromJson(messages[i])); .add(ChatsModel.fromJson(messages[i]));
} }
appState = AppState.idle; appState = AppState.idle;
loadinggetAll = false;
update(); update();
return; return;
} }
appState = AppState.failed; appState = AppState.failed;
loadinggetAll = false;
update(); update();
} }
@ -72,7 +76,9 @@ class HistoryAiChatState extends CoreProvier {
} }
Future<void> getBots() async { Future<void> getBots() async {
loadingBots.value = true; appState = AppState.busy;
loadingBots = true;
update();
final service = RequestService( final service = RequestService(
RequestHelper.aiBots(), RequestHelper.aiBots(),
); );
@ -83,19 +89,23 @@ class HistoryAiChatState extends CoreProvier {
for (var i = 0; i < messages.length; i++) { for (var i = 0; i < messages.length; i++) {
bots.add(BotsModel.fromJson(messages[i])); bots.add(BotsModel.fromJson(messages[i]));
} }
appState = AppState.idle;
loadingBots.value = false;
bot = bots.first; bot = bots.first;
appState = AppState.idle;
loadingBots = false;
update(); update();
getChats();
return; return;
} }
appState = AppState.failed; appState = AppState.failed;
loadingBots.value = false; loadingBots = false;
update(); update();
} }
Future<void> getSearchBots(String q) async { Future<void> getSearchBots(String q) async {
loadingBots.value = true; appState = AppState.busy;
loadingBots = true;
final service = RequestService( final service = RequestService(
RequestHelper.aiSearchBots(q), RequestHelper.aiSearchBots(q),
); );
@ -107,13 +117,13 @@ class HistoryAiChatState extends CoreProvier {
bots.add(BotsModel.fromJson(messages[i])); bots.add(BotsModel.fromJson(messages[i]));
} }
appState = AppState.idle; appState = AppState.idle;
loadingBots.value = false; loadingBots = false;
update(); update();
return; return;
} }
appState = AppState.failed; appState = AppState.failed;
loadingBots.value = false; loadingBots = false;
update(); update();
} }

View File

@ -17,7 +17,6 @@ import 'package:didvan/services/media/media.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/widgets/audio_wave.dart'; import 'package:didvan/views/ai/widgets/audio_wave.dart';
import 'package:didvan/views/ai/widgets/message_bar_btn.dart'; import 'package:didvan/views/ai/widgets/message_bar_btn.dart';
import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/animated_visibility.dart';
@ -66,7 +65,6 @@ class _AiMessageBarState extends State<AiMessageBar> {
Timer? _timer; Timer? _timer;
final theSource = AudioSource.microphone; final theSource = AudioSource.microphone;
final ValueNotifier<Duration> _countTimer = ValueNotifier(Duration.zero); final ValueNotifier<Duration> _countTimer = ValueNotifier(Duration.zero);
late HistoryAiChatState historyState = context.read<HistoryAiChatState>();
@override @override
void initState() { void initState() {
@ -256,7 +254,7 @@ class _AiMessageBarState extends State<AiMessageBar> {
child: Row( child: Row(
crossAxisAlignment: CrossAxisAlignment.end, crossAxisAlignment: CrossAxisAlignment.end,
children: [ children: [
recorderAndSendButton(state, historyState), recorderAndSendButton(state),
if (!(_mRecorder!.isStopped)) if (!(_mRecorder!.isStopped))
ValueListenableBuilder( ValueListenableBuilder(
valueListenable: _countTimer, valueListenable: _countTimer,
@ -453,13 +451,13 @@ class _AiMessageBarState extends State<AiMessageBar> {
); );
} }
recorderAndSendButton(AiChatState state, HistoryAiChatState historyState) { recorderAndSendButton(AiChatState state) {
return ValueListenableBuilder( return ValueListenableBuilder(
valueListenable: state.message, valueListenable: state.message,
builder: (context, message, child) => Padding( builder: (context, message, child) => Padding(
padding: const EdgeInsets.fromLTRB(12, 0, 12, 8), padding: const EdgeInsets.fromLTRB(12, 0, 12, 8),
child: message.text.isEmpty && child: message.text.isEmpty &&
historyState.bot!.attachmentType!.contains('audio') && widget.bot.attachmentType!.contains('audio') &&
state.file == null && state.file == null &&
widget.bot.attachment != 0 widget.bot.attachment != 0
? MessageBarBtn( ? MessageBarBtn(
@ -531,150 +529,6 @@ class _AiMessageBarState extends State<AiMessageBar> {
); );
} }
// AnimatedVisibility attachmentLayout(AiChatState state,
// HistoryAiChatState historyState, BuildContext context) {
// return AnimatedVisibility(
// isVisible: openAttach,
// duration: DesignConfig.lowAnimationDuration,
// child: Container(
// padding: const EdgeInsets.symmetric(horizontal: 0, vertical: 12),
// child: state.file != null && !(state.file!.isRecorded)
// ? fileContainer()
// : Row(
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// if (historyState.bot!.attachmentType!.contains('pdf'))
// attachBtn(
// title: "PDF",
// icon: CupertinoIcons.doc_fill,
// color: Colors.redAccent,
// click: () async {
// MediaService.onLoadingPickFile(context);
// FilePickerResult? result =
// await MediaService.pickPdfFile();
// if (result != null) {
// if (kIsWeb) {
// Uint8List? bytes = result.files.first
// .bytes; // Access the bytes property
// String? name = result.files.first.name;
// // Store bytes and file name directly in your state or model
// state.file = FilesModel(
// '', // No need for a file path on web
// name: name,
// bytes: bytes,
// audio: false,
// image: false,
// );
// } else {
// state.file = FilesModel(result.files.single.path!,
// audio: false, image: false);
// }
// }
// Future.delayed(
// Duration.zero,
// () => ActionSheetUtils(context).pop(),
// );
// },
// ),
// if (historyState.bot!.attachmentType!.contains('image'))
// attachBtn(
// title: "تصویر",
// icon: CupertinoIcons.photo,
// color: Colors.deepOrangeAccent,
// click: () async {
// MediaService.onLoadingPickFile(context);
// 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) {
// await Future.delayed(
// Duration.zero,
// () => ActionSheetUtils(context).pop(),
// );
// return;
// }
// }
// if (pickedFile == null) {
// await Future.delayed(
// Duration.zero,
// () => ActionSheetUtils(context).pop(),
// );
// return;
// }
// state.file = kIsWeb
// ? FilesModel(pickedFile.path,
// name: pickedFile.name,
// image: true,
// audio: false)
// : FilesModel(file!.path,
// image: true, audio: false);
// await Future.delayed(
// Duration.zero,
// () => ActionSheetUtils(context).pop(),
// );
// },
// ),
// // if (!kIsWeb && !Platform.isIOS)
// if (historyState.bot!.attachmentType!.contains('audio'))
// attachBtn(
// title: "صوت",
// icon: CupertinoIcons.music_note_2,
// color: Colors.indigoAccent,
// click: () async {
// MediaService.onLoadingPickFile(context);
// FilePickerResult? result =
// await MediaService.pickAudioFile();
// if (result != null) {
// if (kIsWeb) {
// Uint8List? bytes = result.files.first
// .bytes; // Access the bytes property
// String? name = result.files.first.name;
// // final blob = html.Blob([bytes]);
// // final blobUrl =
// // html.Url.createObjectUrlFromBlob(blob);
// state.file = FilesModel(
// "", // No need for a file path on web
// name: name,
// bytes: bytes,
// audio: true,
// image: false,
// );
// } else {
// state.file = FilesModel(result.files.single.path!,
// audio: true, image: false);
// }
// }
// await Future.delayed(
// Duration.zero,
// () => ActionSheetUtils(context).pop(),
// );
// },
// )
// ],
// ),
// ));
// }
AnimatedVisibility attachmentLayout(AiChatState state, BuildContext context) { AnimatedVisibility attachmentLayout(AiChatState state, BuildContext context) {
return AnimatedVisibility( return AnimatedVisibility(
isVisible: openAttach, isVisible: openAttach,
@ -683,45 +537,48 @@ class _AiMessageBarState extends State<AiMessageBar> {
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
if (historyState.bot!.attachmentType!.contains('pdf')) if (widget.bot.attachmentType!.contains('pdf'))
MessageBarBtn(
enable: true,
icon: CupertinoIcons.doc_fill,
click: () async {
MediaService.onLoadingPickFile(context);
FilePickerResult? result = await MediaService.pickPdfFile();
if (result != null) {
String? name = result.files.single.name;
if (kIsWeb) {
Uint8List? bytes =
result.files.first.bytes; // Access the bytes property
// Store bytes and file name directly in your state or model
state.file = FilesModel(
'', // No need for a file path on web
name: name,
bytes: bytes,
audio: false,
image: false,
);
} else {
state.file = FilesModel(result.files.single.path!,
audio: false, image: false, name: name);
}
}
Future.delayed(
Duration.zero,
() => ActionSheetUtils(context).pop(),
);
openAttach = !openAttach;
state.update();
},
),
if (historyState.bot!.attachmentType!.contains('image'))
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8.0), padding: const EdgeInsets.only(right: 8.0),
child: MessageBarBtn(
enable: true,
icon: CupertinoIcons.doc_fill,
click: () async {
MediaService.onLoadingPickFile(context);
FilePickerResult? result = await MediaService.pickPdfFile();
if (result != null) {
String? name = result.files.single.name;
if (kIsWeb) {
Uint8List? bytes = result
.files.first.bytes; // Access the bytes property
// Store bytes and file name directly in your state or model
state.file = FilesModel(
'', // No need for a file path on web
name: name,
bytes: bytes,
audio: false,
image: false,
);
} else {
state.file = FilesModel(result.files.single.path!,
audio: false, image: false, name: name);
}
}
Future.delayed(
Duration.zero,
() => ActionSheetUtils(context).pop(),
);
openAttach = !openAttach;
state.update();
},
),
),
if (widget.bot.attachmentType!.contains('image'))
Padding(
padding: const EdgeInsets.only(right: 8.0),
child: MessageBarBtn( child: MessageBarBtn(
enable: true, enable: true,
icon: CupertinoIcons.photo, icon: CupertinoIcons.photo,
@ -777,86 +634,53 @@ class _AiMessageBarState extends State<AiMessageBar> {
), ),
), ),
// if (!kIsWeb && !Platform.isIOS) // if (!kIsWeb && !Platform.isIOS)
if (historyState.bot!.attachmentType!.contains('audio')) if (widget.bot.attachmentType!.contains('audio'))
MessageBarBtn( Padding(
enable: true, padding: const EdgeInsets.only(right: 8.0),
icon: CupertinoIcons.music_note_2, child: MessageBarBtn(
click: () async { enable: true,
MediaService.onLoadingPickFile(context); icon: CupertinoIcons.music_note_2,
click: () async {
MediaService.onLoadingPickFile(context);
FilePickerResult? result = await MediaService.pickAudioFile(); FilePickerResult? result =
if (result != null) { await MediaService.pickAudioFile();
String? name = result.files.first.name; if (result != null) {
String? name = result.files.first.name;
if (kIsWeb) { if (kIsWeb) {
Uint8List? bytes = Uint8List? bytes = result
result.files.first.bytes; // Access the bytes property .files.first.bytes; // Access the bytes property
final blob = html.Blob([bytes]); final blob = html.Blob([bytes]);
final blobUrl = html.Url.createObjectUrlFromBlob(blob); final blobUrl = html.Url.createObjectUrlFromBlob(blob);
state.file = FilesModel( state.file = FilesModel(
blobUrl, // No need for a file path on web blobUrl, // No need for a file path on web
name: name, name: name,
bytes: bytes, bytes: bytes,
audio: true, audio: true,
image: false, image: false,
); );
} else { } else {
state.file = FilesModel(result.files.single.path!, state.file = FilesModel(result.files.single.path!,
name: name, audio: true, image: false); name: name, audio: true, image: false);
}
} }
} await Future.delayed(
await Future.delayed( Duration.zero,
Duration.zero, () => ActionSheetUtils(context).pop(),
() => ActionSheetUtils(context).pop(), );
); openAttach = !openAttach;
openAttach = !openAttach;
state.update(); state.update();
}, },
),
) )
], ],
)); ));
} }
// InkWell attachBtn(
// {required final String title,
// required final IconData icon,
// final Color? color,
// final Function()? click}) {
// final state = context.read<AiChatState>();
// return InkWell(
// onTap: () async {
// await click?.call();
// state.update();
// },
// child: Column(
// children: [
// Container(
// width: 40,
// height: 40,
// decoration: BoxDecoration(shape: BoxShape.circle, color: color),
// padding: const EdgeInsets.all(0.8),
// margin: const EdgeInsets.symmetric(horizontal: 12),
// child: Icon(
// icon,
// size: 20,
// color: Theme.of(context).colorScheme.white,
// )),
// const SizedBox(
// height: 4,
// ),
// DidvanText(
// title,
// fontSize: 12,
// )
// ],
// ),
// );
// }
Widget audioContainer() { Widget audioContainer() {
final state = context.watch<AiChatState>(); final state = context.watch<AiChatState>();

View File

@ -1,6 +1,5 @@
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/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/ai/chats_model.dart'; import 'package:didvan/models/ai/chats_model.dart';
@ -15,8 +14,9 @@ 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/shimmer_placeholder.dart'; import 'package:didvan/views/widgets/shimmer_placeholder.dart';
import 'package:didvan/views/widgets/skeleton_image.dart'; import 'package:didvan/views/widgets/skeleton_image.dart';
import 'package:didvan/views/widgets/state_handlers/empty_list.dart';
import 'package:didvan/views/widgets/state_handlers/sliver_state_handler.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
class HoshanDrawer extends StatefulWidget { class HoshanDrawer extends StatefulWidget {
@ -27,6 +27,11 @@ class HoshanDrawer extends StatefulWidget {
} }
class _HoshanDrawerState extends State<HoshanDrawer> { class _HoshanDrawerState extends State<HoshanDrawer> {
@override
initState() {
super.initState();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Drawer( return Drawer(
@ -139,88 +144,28 @@ class _HoshanDrawerState extends State<HoshanDrawer> {
// height: 12, // height: 12,
// ), // ),
Expanded( Expanded(
child: state.loadingdeleteAll || child: CustomScrollView(
state.appState == AppState.busy slivers: [
? ListView.builder( SliverStateHandler(
shrinkWrap: true, state: state,
itemCount: 10, centerEmptyState: true,
padding: const EdgeInsets.symmetric( emptyState: const EmptyList(),
horizontal: 12), // enableEmptyState: state.chats.isEmpty,
physics: placeholder: chatRowPlaceholder(),
const NeverScrollableScrollPhysics(), placeholderCount: 10,
itemBuilder: (context, index) { builder: (context, state, index) {
return const Padding( final chat = state.chats[index];
padding: EdgeInsets.symmetric( TextEditingController title =
vertical: 12.0), TextEditingController(
child: Row( text: chat.title);
crossAxisAlignment:
CrossAxisAlignment.start, return chatRow(chat, title, state, index);
children: [ },
ClipOval( childCount: state.chats.length,
child: ShimmerPlaceholder( onRetry: () => state.getChats())
height: 24, ],
width: 24, )),
),
),
SizedBox(width: 12),
Expanded(
child: ShimmerPlaceholder(
height: 24,
),
),
SizedBox(width: 12),
ShimmerPlaceholder(
height: 24,
width: 24,
),
SizedBox(width: 8),
ShimmerPlaceholder(
height: 24,
width: 12,
),
],
),
);
},
)
: state.chats.isEmpty
? Padding(
padding: const EdgeInsets.all(12.0),
child: Column(
children: [
SvgPicture.asset(
Assets.emptyResult,
height:
MediaQuery.sizeOf(context)
.height /
10,
),
const DidvanText(
'لیست خالی است',
fontSize: 14,
fontWeight: FontWeight.bold,
)
],
),
)
: 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 chatRow(
chat, title, state, index);
},
),
),
// SizedBox( // SizedBox(
// height: 12, // height: 12,
// ), // ),
@ -268,6 +213,39 @@ class _HoshanDrawerState extends State<HoshanDrawer> {
); );
} }
Padding chatRowPlaceholder() {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 12.0),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ClipOval(
child: ShimmerPlaceholder(
height: 24,
width: 24,
),
),
SizedBox(width: 12),
Expanded(
child: ShimmerPlaceholder(
height: 24,
),
),
SizedBox(width: 12),
ShimmerPlaceholder(
height: 24,
width: 24,
),
SizedBox(width: 8),
ShimmerPlaceholder(
height: 24,
width: 12,
),
],
),
);
}
Widget drawerBtn( Widget drawerBtn(
{final CrossAxisAlignment? crossAxisAlignment, {final CrossAxisAlignment? crossAxisAlignment,
required final IconData icon, required final IconData icon,

View File

@ -142,7 +142,6 @@ class _HomeState extends State<Home>
if (_tabController.index == 2) { if (_tabController.index == 2) {
homeScaffKey.currentState!.closeDrawer(); homeScaffKey.currentState!.closeDrawer();
context.read<HistoryAiChatState>().getChats();
context.read<HistoryAiChatState>().getBots(); context.read<HistoryAiChatState>().getBots();
} }
}); });

View File

@ -1,18 +1,18 @@
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/utils/extension.dart';
import 'package:didvan/views/widgets/animated_visibility.dart'; import 'package:didvan/views/widgets/animated_visibility.dart';
import 'package:didvan/views/widgets/didvan/text.dart'; import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
class DidvanTextField extends StatefulWidget { class DidvanTextField extends StatefulWidget {
final void Function(String value)? onChanged; final void Function(String value)? onChanged;
final void Function(String value)? onSubmitted; final void Function(String value)? onSubmitted;
final bool enabled; final bool enabled;
final bool autoFocus; final bool autoFocus;
final TextAlign textAlign; final TextAlign? textAlign;
final String? title; final String? title;
final String? hintText; final String? hintText;
final dynamic initialValue; final dynamic initialValue;
@ -37,7 +37,7 @@ class DidvanTextField extends StatefulWidget {
this.initialValue, this.initialValue,
this.validator, this.validator,
this.textInputType, this.textInputType,
this.textAlign = TextAlign.right, this.textAlign,
this.obsecureText = false, this.obsecureText = false,
this.autoFocus = false, this.autoFocus = false,
this.onSubmitted, this.onSubmitted,
@ -69,7 +69,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
} }
_hideContent = widget.obsecureText; _hideContent = widget.obsecureText;
_focusNode.addListener(() { _focusNode.addListener(() {
setState(() {}); // setState(() {});
}); });
super.initState(); super.initState();
} }
@ -98,47 +98,58 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
? null ? null
: Border.all(color: _borderColor()), : Border.all(color: _borderColor()),
), ),
child: TextFormField( child: ValueListenableBuilder(
inputFormatters: <TextInputFormatter>[ valueListenable: _controller,
if (!widget.acceptSpace) builder: (context, val, _) {
FilteringTextInputFormatter.allow(RegExp("[0-9a-zA-Z]")), return Directionality(
], textDirection: val.text.startsWithEnglish()
autofocus: widget.autoFocus, ? TextDirection.ltr
obscureText: _hideContent, : TextDirection.rtl,
textAlign: widget.textAlign, child: TextFormField(
keyboardType: widget.textInputType, inputFormatters: <TextInputFormatter>[
focusNode: _focusNode, if (!widget.acceptSpace)
controller: _controller, FilteringTextInputFormatter.allow(
onFieldSubmitted: widget.onSubmitted, RegExp("[0-9a-zA-Z]")),
onChanged: _onChanged, ],
validator: _validator, autofocus: widget.autoFocus,
maxLines: widget.maxLine, obscureText: _hideContent,
minLines: widget.minLine, textAlign: widget.textAlign ?? TextAlign.start,
maxLength: widget.maxLength, keyboardType: widget.textInputType,
obscuringCharacter: '*', focusNode: _focusNode,
buildCounter: widget.showLen controller: _controller,
? null onFieldSubmitted: widget.onSubmitted,
: (context, onChanged: _onChanged,
{required currentLength, validator: _validator,
required isFocused, maxLines: widget.maxLine,
required maxLength}) => minLines: widget.minLine,
const SizedBox(), maxLength: widget.maxLength,
style: (widget.isSmall obscuringCharacter: '*',
? Theme.of(context).textTheme.bodySmall! buildCounter: widget.showLen
: Theme.of(context).textTheme.bodyMedium!) ? null
.copyWith(fontFamily: DesignConfig.fontFamily.padRight(3)), : (context,
decoration: InputDecoration( {required currentLength,
suffixIcon: _suffixBuilder(), required isFocused,
enabled: widget.enabled, required maxLength}) =>
border: InputBorder.none, const SizedBox(),
hintText: widget.hintText, style: (widget.isSmall
errorStyle: const TextStyle(height: 0.01), ? Theme.of(context).textTheme.bodySmall!
hintStyle: (widget.isSmall : Theme.of(context).textTheme.bodyMedium!)
? Theme.of(context).textTheme.bodySmall! .copyWith(
: Theme.of(context).textTheme.bodyMedium!) fontFamily: DesignConfig.fontFamily.padRight(3)),
.copyWith(color: Theme.of(context).colorScheme.hint), decoration: InputDecoration(
), suffixIcon: _suffixBuilder(),
), enabled: widget.enabled,
border: InputBorder.none,
hintText: widget.hintText,
errorStyle: const TextStyle(height: 0.01),
hintStyle: (widget.isSmall
? Theme.of(context).textTheme.bodySmall!
: Theme.of(context).textTheme.bodyMedium!)
.copyWith(color: Theme.of(context).colorScheme.hint),
),
),
);
}),
), ),
AnimatedVisibility( AnimatedVisibility(
isVisible: _error != null, isVisible: _error != null,
@ -224,7 +235,7 @@ class _DidvanTextFieldState extends State<DidvanTextField> {
setState(() { setState(() {
_error = null; _error = null;
}); });
value = value.toEnglishDigit(); // value = value.toEnglishDigit();
widget.onChanged?.call(value); widget.onChanged?.call(value);
} }