didvan-app/lib/views/ai/history_ai_chat_page.dart

724 lines
41 KiB
Dart

// ignore_for_file: library_private_types_in_public_api, deprecated_member_use
import 'dart:async';
import 'package:didvan/config/design_config.dart';
import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/app_icons.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/main.dart';
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/models/view/action_sheet_data.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/ai/widgets/hoshan_drawer.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:didvan/views/widgets/hoshan_app_bar.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/sliver_state_handler.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
import 'package:persian_number_utility/persian_number_utility.dart';
import 'package:provider/provider.dart';
class HistoryAiChatPage extends StatefulWidget {
final bool? archived;
const HistoryAiChatPage({Key? key, required this.archived}) : super(key: key);
@override
_HistoryAiChatPageState createState() => _HistoryAiChatPageState();
}
class _HistoryAiChatPageState extends State<HistoryAiChatPage> {
final ScrollController scrollController = ScrollController();
final GlobalKey<ScaffoldState> scaffKey = GlobalKey<ScaffoldState>();
Timer? _timer;
late bool archived = widget.archived ?? false;
@override
void initState() {
final state = context.read<HistoryAiChatState>();
Future.delayed(
Duration.zero,
() => state.getChats(archived: archived),
);
super.initState();
}
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return WillPopScope(
onWillPop: () async {
if (context.read<HistoryAiChatState>().refresh) {
context.read<HistoryAiChatState>().getChats();
context.read<HistoryAiChatState>().refresh = false;
}
return true;
},
child: Scaffold(
key: scaffKey,
appBar: HoshanAppBar(
onBack: () {
if (context.read<HistoryAiChatState>().refresh) {
context.read<HistoryAiChatState>().getChats();
context.read<HistoryAiChatState>().refresh = false;
}
Navigator.pop(context);
},
withActions: false,
withInfo: true,
),
drawer: HoshanDrawer(
scaffKey: scaffKey,
),
body: CustomScrollView(
physics: const BouncingScrollPhysics(),
controller: scrollController,
slivers: [
SliverAppBar(
backgroundColor: Theme.of(context).colorScheme.surface,
scrolledUnderElevation: 0,
automaticallyImplyLeading: false,
pinned: true,
toolbarHeight: archived ? 50 : 110,
flexibleSpace: Container(
color: Theme.of(context).colorScheme.surface,
padding: const EdgeInsets.fromLTRB(20, 0, 20, 0),
child: Column(
mainAxisAlignment: MainAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Padding(
padding: const EdgeInsets.all(8.0),
child: Text('فهرست آرشیوشده‌ها',
style: TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: DesignConfig.isDark
? const Color.fromARGB(255, 0, 90, 119)
: const Color.fromARGB(255, 0, 53, 70))),
),
// SearchField(
// title: 'گفت‌و‌گو‌ها',
// onChanged: (value) {
// final state = context.read<HistoryAiChatState>();
// if (value.isEmpty) {
// state.getChats(archived: archived);
// return;
// }
// _timer?.cancel();
// _timer = Timer(const Duration(seconds: 1), () {
// state.search = value;
// state.getSearchChats(q: value, archived: archived);
// });
// },
// focusNode: FocusNode(),
// ),
if (!archived)
Padding(
padding: const EdgeInsets.only(top: 0.0),
child: InkWell(
onTap: () {
Navigator.of(context)
.pushNamed(Routes.aiArchivedHistory);
},
child: Row(
children: [
Icon(
DidvanIcons.ai_regular,
size: 18,
color: theme.colorScheme.primary,
),
const SizedBox(width: 8),
DidvanText(
'گفت‌وگوهای آرشیو شده',
style: TextStyle(
color: theme.colorScheme.primary,
fontWeight: FontWeight.bold,
fontSize: 14,
),
),
],
),
),
),
],
),
),
),
Consumer<HistoryAiChatState>(
builder: (context, state, child) {
return SliverStateHandler(
paddingEmptyState: 0,
state: state,
centerEmptyState: false,
emptyState: EmptyState(
asset: Assets.emptyResult,
height: 400,
title: 'لیست خالی است',
titleColor: const Color.fromARGB(255, 2, 126, 167),
subtitle:
'در حال حاضر آیتمی در این بخش ثبت نشده است. هر زمان مورد جدیدی اضافه شود، در اینجا نمایش داده می‌شود.',
),
enableEmptyState: archived
? state.archivedChats.isEmpty
: state.chats.isEmpty,
placeholder: const _HistoryPlaceholder(),
placeholderCount: 8,
builder: (context, state, index) {
final chat = archived
? state.archivedChats[index]
: state.chats[index];
TextEditingController title =
TextEditingController(text: chat.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;
if (direction == DismissDirection.startToEnd) {
await ActionSheetUtils(context).openDialog(
data: ActionSheetData(
onConfirmed: () async {
final state =
context.read<HistoryAiChatState>();
await state.deleteChat(chat.id!, index,
archived: archived);
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,
fontFamily: DesignConfig
.fontFamily),
children: [
TextSpan(
text: "\"${chat.title}\"",
style: TextStyle(
fontFamily:
DesignConfig
.fontFamily,
fontWeight:
FontWeight.bold)),
TextSpan(
style: TextStyle(
fontFamily:
DesignConfig
.fontFamily),
text:
' با هوشان اطمینان دارید؟ '),
]),
),
),
],
)));
} else {
result = await state.archivedChat(chat.id!, index,
archived: archived);
}
return result;
},
child: InkWell(
onTap: () {
if (chat.bot != null) {
navigatorKey.currentState!.pushNamed(
Routes.aiChat,
arguments: AiChatArgs(
bot: chat.bot!,
chat: chat,
assistantsName: chat.assistantsName));
} else {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content:
Text('امکان باز کردن این چت وجود ندارد.'),
),
);
}
},
onLongPress: () {
if (archived) {
state.archivedChats[index] = state
.archivedChats[index]
.copyWith(isEditing: true);
} else {
state.chats[index] =
state.chats[index].copyWith(isEditing: true);
}
state.update();
},
child: Container(
padding: const EdgeInsets.symmetric(
vertical: 12, horizontal: 20),
decoration: BoxDecoration(
border: Border(
bottom: BorderSide(
color: Theme.of(context).colorScheme.border,
))),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// SkeletonImage(
// imageUrl: chat.bot!.image.toString(),
// width: 46,
// height: 46,
// borderRadius: BorderRadius.circular(360),
// ),
// const SizedBox(
// width: 18,
// ),
Expanded(
child: Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
// Row(
// mainAxisAlignment:
// MainAxisAlignment.spaceBetween,
// children: [
// // DidvanText(
// // chat.assistantsName ??
// // chat.bot!.name.toString(),
// // fontWeight: FontWeight.bold,
// // ),
// ],
// ),
SizedBox(
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),
)
],
)
: Row(
mainAxisAlignment:
MainAxisAlignment
.spaceBetween,
children: [
Expanded(
child: Column(
mainAxisAlignment:
MainAxisAlignment
.start,
crossAxisAlignment:
CrossAxisAlignment
.start,
children: [
DidvanText(
chat.title
.toString(),
maxLines: 1,
overflow:
TextOverflow
.ellipsis,
),
DidvanText(
DateTime.parse(chat
.updatedAt
.toString())
.toPersianDateStr(monthString: ''),
style:
const TextStyle(
fontSize:
12)),
],
),
),
const SizedBox(
width: 10,
),
PopupMenuButton<String>(
icon: SvgPicture.asset(
'lib/assets/icons/more.svg'),
shape:
const RoundedRectangleBorder(
borderRadius:
BorderRadius
.only(
topRight:
Radius.circular(
16),
topLeft:
Radius.circular(
0),
bottomLeft:
Radius.circular(
16),
bottomRight:
Radius.circular(
16),
)),
color: const Color
.fromARGB(
255, 246, 246, 246),
padding:
EdgeInsets.zero,
borderRadius:
const BorderRadius
.only(
topRight:
Radius.circular(
16),
topLeft:
Radius.circular(
0),
bottomLeft:
Radius.circular(
16),
bottomRight:
Radius.circular(
16),
),
onSelected:
(value) async {
switch (value) {
case 'حذف پیام':
await state
.deleteChat(
chat.id!,
index,
archived:
archived);
break;
case 'آرشیو':
case 'خارج کردن از آرشیو':
await state
.archivedChat(
chat.id!,
index,
archived:
archived);
break;
default:
}
},
itemBuilder:
(BuildContext
context) =>
<PopupMenuEntry<
String>>[
PopupMenuItem<String>(
value:
'خارج کردن از آرشیو',
padding:
const EdgeInsets
.symmetric(
horizontal:
20,
vertical:
12),
child: SizedBox(
width: 180,
child: Column(
children: [
Row(
children: [
SvgPicture
.asset(
'lib/assets/icons/direct-send.svg',
height:
20,
),
const SizedBox(
width:
16),
const Text(
'خارج کردن از آرشیو',
style: TextStyle(
fontSize:
13,
color: Color.fromARGB(
255,
61,
61,
61)),
),
],
),
const SizedBox(
height:
20),
const Divider(
height: 1,
color: Color
.fromARGB(
255,
227,
226,
225),
)
],
),
),
),
PopupMenuItem<String>(
value: 'حذف پیام',
padding:
const EdgeInsets
.symmetric(
horizontal:
20,
vertical:
12),
child: SizedBox(
width: 180,
child: Column(
children: [
Row(
children: [
SvgPicture
.asset(
'lib/assets/icons/trash.svg',
height:
20,
color: const Color
.fromARGB(
255,
0,
126,
167),
),
const SizedBox(
width:
16),
const Text(
'پاک کردن',
style: TextStyle(
fontSize: 13,
color: Color.fromARGB(255, 61, 61, 61))),
],
),
],
),
),
),
],
),
// if (chat.prompts != null &&
// chat.prompts!.isNotEmpty &&
// chat.prompts![0].text != null)
// DidvanText(
// chat.prompts![0].text
// .toString(),
// maxLines: 1,
// overflow: TextOverflow.ellipsis,
// fontSize: 12,
// ),
],
),
]))
],
),
),
],
),
),
),
);
},
childCount: archived
? state.archivedChats.length
: state.chats.length,
onRetry: () => state.getChats(archived: archived));
},
)
],
),
),
);
}
}
class _HistoryPlaceholder extends StatelessWidget {
const _HistoryPlaceholder({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.symmetric(vertical: 18.0, horizontal: 20),
child: Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ClipOval(
child: ShimmerPlaceholder(
height: 46,
width: 46,
),
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
ShimmerPlaceholder(
height: 20,
width: 100,
),
ShimmerPlaceholder(
height: 14,
width: 100,
),
],
),
SizedBox(height: 12),
ShimmerPlaceholder(
height: 16,
width: double.infinity,
),
SizedBox(height: 8),
ShimmerPlaceholder(
height: 16,
width: double.infinity,
),
],
),
),
],
),
);
}
}