Houshan-Basa/lib/ui/screens/library/library_screen.dart

645 lines
32 KiB
Dart

// ignore_for_file: deprecated_member_use_from_same_package, use_build_context_synchronously
import 'dart:math';
import 'package:easy_debounce/easy_debounce.dart';
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:hoshan/core/gen/assets.gen.dart';
import 'package:hoshan/core/utils/date_time.dart';
import 'package:hoshan/core/utils/strings.dart';
import 'package:hoshan/data/model/ai/chats_history_model.dart';
import 'package:hoshan/data/model/empty_states_enum.dart';
import 'package:hoshan/data/model/popup_menu_model.dart';
import 'package:hoshan/ui/screens/main/home_page.dart';
import 'package:hoshan/ui/screens/library/bloc/chats_history_bloc.dart';
import 'package:hoshan/ui/screens/library/cubit/handle_archive_cubit.dart';
import 'package:hoshan/ui/theme/colors.dart';
import 'package:hoshan/ui/theme/cubit/theme_mode_cubit.dart';
import 'package:hoshan/ui/theme/text.dart';
import 'package:hoshan/ui/screens/library/cubit/chat_row_edit_cubit.dart';
import 'package:hoshan/ui/widgets/components/button/circle_icon_btn.dart';
import 'package:hoshan/ui/widgets/components/dialog/dialog_handler.dart';
import 'package:hoshan/ui/widgets/components/image/network_image.dart';
import 'package:hoshan/ui/widgets/components/text/auth_text_field.dart';
import 'package:hoshan/ui/widgets/components/text/search_text_field.dart';
import 'package:hoshan/ui/widgets/sections/empty/empty_states.dart';
import 'package:hoshan/ui/widgets/sections/header/primary_appbar.dart';
import 'package:hoshan/ui/widgets/sections/loading/default_placeholder.dart';
import 'package:hoshan/ui/widgets/sections/loading/listview_placeholder.dart';
import 'package:shamsi_date/shamsi_date.dart';
class LibraryScreen extends StatefulWidget {
final Function(Chats chat)? onTap;
final String type;
const LibraryScreen({super.key, required this.type, this.onTap});
@override
State<LibraryScreen> createState() => _LibraryScreenState();
}
class _LibraryScreenState extends State<LibraryScreen> {
@override
void dispose() {
super.dispose();
EasyDebounce.cancelAll();
}
ValueNotifier<String?> date = ValueNotifier(null);
Jalali? dateJalali;
final TextEditingController searchTextController = TextEditingController();
bool archive = false;
final ScrollController scrollController = ScrollController();
@override
void initState() {
super.initState();
scrollController.addListener(
() {
if (scrollController.position.pixels ==
scrollController.position.maxScrollExtent &&
context.read<ChatsHistoryBloc>().state is ChatsHistorySuccess) {
context.read<ChatsHistoryBloc>().add(GetAllChats(
type: widget.type,
archive: archive,
date: date.value,
search: searchTextController.text));
}
},
);
}
@override
Widget build(BuildContext context) {
return Column(
children: [
PrimaryAppbar(
context,
onBack: () {
Scaffold.of(context).closeDrawer();
},
actions: [
const SizedBox(
width: 16,
),
GestureDetector(
onTap: () async {
await DialogHandler(context: context).showDeleteItem(
title: 'همه نتایج جستجو پاک شوند؟',
description: 'با این کار اطلاعات شما ازبین خواهد رفت.',
onConfirm: () {
context
.read<ChatsHistoryBloc>()
.add(RemoveAll(archive: archive));
},
);
},
child: SizedBox(
width: 24,
height: 24,
child: Assets.icon.outline.trash
.svg(color: Theme.of(context).colorScheme.onSurface))),
const SizedBox(
width: 24,
),
GestureDetector(
onTap: () async {
if (context.read<ChatsHistoryBloc>().state
is ChatsHistoryLoading) {
return;
}
archive = !archive;
ChatsHistoryBloc.page = 1;
ChatsHistoryBloc.lastPage = null;
context.read<ChatsHistoryBloc>().add(GetAllChats(
type: widget.type,
search: searchTextController.text,
date: date.value,
archive: archive));
setState(() {});
},
child: SizedBox(
width: 24,
height: 24,
child: (archive
? Assets.icon.outline.directSend
: Assets.icon.outline.directInbox)
.svg(color: Theme.of(context).colorScheme.onSurface))),
const SizedBox(
width: 16,
),
],
),
Expanded(
child: RefreshIndicator(
onRefresh: () async {
ChatsHistoryBloc.page = 1;
ChatsHistoryBloc.lastPage = null;
context.read<ChatsHistoryBloc>().add(GetAllChats(
type: widget.type,
search: searchTextController.text,
date: date.value,
archive: archive));
scrollController.jumpTo(0);
},
backgroundColor: Theme.of(context).colorScheme.surface,
color: Theme.of(context).colorScheme.primary,
child: SingleChildScrollView(
controller: scrollController,
physics: const BouncingScrollPhysics(
parent: AlwaysScrollableScrollPhysics()),
child: Column(
children: [
SearchTextField(
focusNode: searchFocus,
controller: searchTextController,
suffixIcon: ValueListenableBuilder<String?>(
valueListenable: date,
builder: (context, d, _) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
if (d != null)
Transform.scale(
scale: 0.8,
child: Transform.rotate(
angle: pi / 4,
child: CircleIconBtn(
icon: Assets.icon.outline.add,
color: context
.read<ThemeModeCubit>()
.isDark()
? AppColors.black[900]
: AppColors.secondryColor[50],
iconColor:
AppColors.secondryColor.defaultShade,
onTap: () {
date.value = null;
dateJalali = null;
ChatsHistoryBloc.page = 1;
ChatsHistoryBloc.lastPage = null;
context.read<ChatsHistoryBloc>().add(
GetAllChats(
type: widget.type,
search:
searchTextController.text,
date: date.value,
archive: archive));
return;
},
),
),
),
Padding(
padding: const EdgeInsets.all(12),
child: GestureDetector(
onTap: () async {
await DialogHandler(context: context)
.showDatePicker(
dateCounts: 1,
onConfirm: (p0) {
if (p0.isEmpty) {
if (date.value != null) {
date.value = null;
dateJalali = null;
ChatsHistoryBloc.page = 1;
ChatsHistoryBloc.lastPage = null;
context
.read<ChatsHistoryBloc>()
.add(GetAllChats(
type: widget.type,
search: searchTextController
.text,
date: date.value,
archive: archive));
}
return;
}
dateJalali = p0.first;
DateTime miladiDate =
dateJalali!.toDateTime();
date.value =
'${miladiDate.year}-${miladiDate.month}-${miladiDate.day}';
ChatsHistoryBloc.page = 1;
ChatsHistoryBloc.lastPage = null;
context.read<ChatsHistoryBloc>().add(
GetAllChats(
type: widget.type,
search:
searchTextController.text,
date: date.value,
archive: archive));
},
);
},
child: Assets.icon.outline.filter.svg(),
),
),
],
);
}),
onChanged: (searchText) {
if (searchText.isEmpty) {
EasyDebounce.cancelAll();
ChatsHistoryBloc.page = 1;
ChatsHistoryBloc.lastPage = null;
context.read<ChatsHistoryBloc>().add(GetAllChats(
type: widget.type,
date: date.value,
archive: archive));
return;
}
EasyDebounce.debounce(
'my-debouncer', // <-- An ID for this particular debouncer
const Duration(
seconds: 1), // <-- The debounce duration
() {
ChatsHistoryBloc.page = 1;
ChatsHistoryBloc.lastPage = null;
context.read<ChatsHistoryBloc>().add(GetAllChats(
type: widget.type,
search: searchText,
date: date.value,
archive: archive));
} // <-- The target method
);
},
),
BlocConsumer<ChatsHistoryBloc, ChatsHistoryState>(
listener: (context, state) {},
builder: (context, state) {
if (state is ChatsHistorySuccess ||
state is ChatsHistoryLoading) {
if (state.chatsInDates.isEmpty) {
return Center(
child: EmptyStates.getEmptyState(
status: archive
? EmptyStatesEnum.archive
: EmptyStatesEnum.messages),
);
}
return SingleChildScrollView(
physics: const NeverScrollableScrollPhysics(),
child: Column(
children: [
ListView.builder(
itemCount: state.chatsInDates.length,
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
itemBuilder: (context, allIndex) {
return state
.chatsInDates[allIndex].chats.isEmpty
? const SizedBox()
: Column(
children: [
if (allIndex != 0)
const SizedBox(
height: 8,
),
Padding(
padding:
const EdgeInsets.symmetric(
horizontal: 12.0),
child: Row(
mainAxisAlignment:
MainAxisAlignment.end,
children: [
Text(
state.chatsInDates[allIndex]
.title,
style: AppTextStyles.body4
.copyWith(
color: Theme.of(
context)
.colorScheme
.onSurface,
fontWeight:
FontWeight
.bold),
),
],
),
),
ListView.builder(
itemCount: state
.chatsInDates[allIndex]
.chats
.length,
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemBuilder:
(context, chatIndex) {
final chat = state
.chatsInDates[allIndex]
.chats[chatIndex];
return chatRow(context, chat);
},
),
],
);
},
),
if (state is ChatsHistoryLoading)
Padding(
padding: const EdgeInsets.symmetric(
vertical: 12.0),
child: LinearProgressIndicator(
color: AppColors.primaryColor.defaultShade,
borderRadius: BorderRadius.circular(16),
),
),
const SizedBox(
height: 36,
)
],
),
);
}
return ListviewPlaceholder(
child: Container(
width: MediaQuery.sizeOf(context).width,
height: 58,
margin: const EdgeInsets.symmetric(
horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(16)),
));
},
),
],
),
),
),
),
],
);
}
Widget chatRow(BuildContext context, Chats chatModel) {
final List<PopupMenuModel> popups = [
PopupMenuModel(
id: 0, title: 'تغییر نام', icon: Assets.icon.outline.edit2),
// PopupMenuModel(
// id: 1, title: 'به اشتراک گذاری', icon: Assets.icon.outline.share),
PopupMenuModel(
id: 2,
title: archive ? 'خارج کردن از آرشیو‌ها' : 'آرشیو کردن',
icon: archive
? Assets.icon.outline.directSend
: Assets.icon.outline.directInbox),
PopupMenuModel(id: 3, title: 'پاک کردن', icon: Assets.icon.outline.trash),
];
late final TextEditingController editingController = TextEditingController(
text: chatModel.title!.replaceAll("\"", ''),
);
ValueNotifier<bool> isEdit = ValueNotifier(false);
late Chats chat = chatModel;
return MultiBlocProvider(
providers: [
BlocProvider<ChatRowEditCubit>(create: (context) => ChatRowEditCubit()),
BlocProvider<HandleArchiveCubit>(
create: (context) => HandleArchiveCubit()),
],
child: BlocConsumer<HandleArchiveCubit, HandleArchiveState>(
listener: (context, archState) {
if (archState is HandleArchiveSuccess) {
context
.read<ChatsHistoryBloc>()
.add(RemoveChat(chats: chat, withCall: false));
}
},
builder: (context, archState) {
if (archState is HandleArchiveLoading) {
return DefaultPlaceHolder(
child: Container(
width: MediaQuery.sizeOf(context).width,
height: 58,
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 8),
decoration: BoxDecoration(
color: Colors.white, borderRadius: BorderRadius.circular(8)),
));
}
return BlocBuilder<ChatRowEditCubit, ChatRowEditState>(
builder: (context, state) {
return GestureDetector(
onTap: () async {
// Scaffold.of(context).closeDrawer();
// await Future.delayed(Duration(milliseconds: 300));
widget.onTap?.call(chat);
},
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 8, vertical: 12),
margin:
const EdgeInsets.symmetric(vertical: 4, horizontal: 4),
width: MediaQuery.sizeOf(context).width,
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(8)),
child: ValueListenableBuilder(
valueListenable: isEdit,
builder: (context, edit, _) {
return Row(
children: [
state is ChatRowEditLoading
? SizedBox(
width: 18,
height: 18,
child: CircularProgressIndicator(
color:
AppColors.primaryColor.defaultShade,
),
)
: edit
? GestureDetector(
onTap: () async {
await context
.read<ChatRowEditCubit>()
.editTitle(
id: chat.id!,
title:
editingController.text);
chat = chat.copyWith(
title: editingController.text);
isEdit.value = false;
},
child: Assets.icon.outline.tickCircle
.svg(
color: AppColors.gray[context
.read<ThemeModeCubit>()
.isDark()
? 600
: 900]))
: PopupMenuButton<PopupMenuModel>(
tooltip: '',
offset: const Offset(0, 18),
onSelected: (value) async {
switch (value.id) {
case 0:
isEdit.value = true;
break;
case 1:
break;
case 2:
archive
? await context
.read<
HandleArchiveCubit>()
.removeFromArchive(
chat.id!)
: await context
.read<
HandleArchiveCubit>()
.addToArchive(chat.id!);
break;
case 3:
// widget.onDelete.call();
await DialogHandler(
context: context)
.showDeleteItem(
title: 'تاریخچه گفتگو پاک شود؟',
description:
'با این کار اطلاعات شما از بین خواهد رفت.',
onConfirm: () {
context
.read<ChatsHistoryBloc>()
.add(RemoveChat(
chats: chat));
},
);
break;
default:
}
},
shape: RoundedRectangleBorder(
borderRadius:
BorderRadius.circular(8)),
itemBuilder: (BuildContext context) {
return <PopupMenuEntry<
PopupMenuModel>>[
...List.generate(
popups.length,
(index) =>
PopupMenuItem<PopupMenuModel>(
value: popups[index],
height: 32,
child: Directionality(
textDirection:
TextDirection.rtl,
child: ListTile(
minTileHeight: 32,
title: Text(
popups[index].title,
style: AppTextStyles
.body5
.copyWith(
color: Theme.of(
context)
.colorScheme
.onSurface,
fontWeight:
FontWeight
.bold),
),
leading: popups[index]
.icon!
.svg(
color: AppColors
.secondryColor
.defaultShade,
width: 16,
height: 16),
)),
),
)
];
},
child: Padding(
padding: const EdgeInsets.symmetric(
horizontal: 10.0),
child: Assets.icon.outline.more.svg(
color: AppColors.gray[context
.read<ThemeModeCubit>()
.isDark()
? 600
: 900]),
)),
const SizedBox(
width: 4,
),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
edit
? AuthTextField(
controller: editingController,
maxLines: 4,
minLines: 4,
)
: Directionality(
textDirection:
chat.title!.startsWithEnglish()
? TextDirection.ltr
: TextDirection.rtl,
child: Text(
chat.title!.replaceAll("\"", ''),
style: AppTextStyles.body5.copyWith(
color: Theme.of(context)
.colorScheme
.onSurface),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
),
const SizedBox(
height: 4,
),
Text(
DateTimeUtils
.convertStringIsoToStringInFormatted(
chat.createdAt!),
style: AppTextStyles.body6.copyWith(
color: AppColors.gray[context
.read<ThemeModeCubit>()
.isDark()
? 600
: 900]),
)
],
),
),
if (widget.type != 'llm')
Row(
children: [
const SizedBox(
width: 8,
),
ImageNetwork(
width: 32,
height: 32,
url: chat.bot!.image,
radius: 360,
),
],
)
],
);
}),
),
);
},
);
},
),
);
}
}