didvan-app/lib/views/widgets/hoshan_home_app_bar.dart

765 lines
30 KiB
Dart

import 'package:didvan/config/theme_data.dart';
import 'package:didvan/constants/assets.dart';
import 'package:didvan/models/ai/ai_chat_args.dart';
import 'package:didvan/providers/user.dart';
import 'package:didvan/views/ai/history_ai_chat_state.dart';
import 'package:didvan/views/widgets/didvan/text.dart';
import 'package:flutter/material.dart';
import 'package:flutter_svg/flutter_svg.dart';
import 'package:provider/provider.dart';
import 'package:didvan/routes/routes.dart';
// ignore: depend_on_referenced_packages
import 'package:shamsi_date/shamsi_date.dart';
class HoshanHomeAppBar extends StatefulWidget {
const HoshanHomeAppBar({super.key});
@override
State<HoshanHomeAppBar> createState() => _HoshanHomeAppBarState();
}
class _HoshanHomeAppBarState extends State<HoshanHomeAppBar> {
// String? _welcomeMessage;
// bool _isLoadingWelcome = true;
// @override
// void initState() {
// super.initState();
// fetchWelcomeMessage();
// }
// Future<void> fetchWelcomeMessage() async { ... }
void showHistoryDrawer(BuildContext context) {
showGeneralDialog(
context: context,
barrierDismissible: true,
barrierLabel: MaterialLocalizations.of(context).modalBarrierDismissLabel,
barrierColor: Colors.black54,
transitionDuration: const Duration(milliseconds: 300),
pageBuilder: (context, animation, secondaryAnimation) {
return const HistoryDrawerContent();
},
transitionBuilder: (context, animation, secondaryAnimation, child) {
return SlideTransition(
position: Tween<Offset>(
begin: const Offset(-1, 0),
end: Offset.zero,
).animate(CurvedAnimation(
parent: animation,
curve: Curves.easeInOut,
)),
child: child,
);
},
);
}
void _startNewChat(BuildContext context) {
final historyState = context.read<HistoryAiChatState>();
void navigateToNewChat() {
if (historyState.bots.isNotEmpty) {
Navigator.of(context).pushNamed(
Routes.aiChat,
arguments: AiChatArgs(
bot: historyState.bots.first,
isTool: historyState.bots,
),
);
}
}
if (historyState.bots.isEmpty) {
historyState.getBots();
Future.delayed(const Duration(milliseconds: 500), navigateToNewChat);
} else {
navigateToNewChat();
}
}
@override
Widget build(BuildContext context) {
final userProvider = context.watch<UserProvider>();
return Container(
decoration: const BoxDecoration(
color: Color.fromRGBO(230, 242, 246, 1),
borderRadius: BorderRadius.only(
bottomLeft: Radius.circular(32),
bottomRight: Radius.circular(32),
),
),
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SvgPicture.asset(
Assets.horizontalLogoWithText,
height: 60,
color: Theme.of(context).colorScheme.title,
),
const Spacer(),
GestureDetector(
onTap: () {
print('history bottom tapped');
final historyState = context.read<HistoryAiChatState>();
if (historyState.chats.isEmpty) {
historyState.getChats();
}
showHistoryDrawer(context);
},
child: Container(
padding: const EdgeInsets.all(8),
child: SvgPicture.asset(
'lib/assets/icons/history.svg',
height: 24,
),
),
),
],
),
Container(
padding: const EdgeInsets.fromLTRB(8, 35, 8, 35),
alignment: Alignment.center,
child: userProvider.isLoadingWelcome
? const SizedBox(height: 20)
: userProvider.welcomeMessage != null
? DidvanText(
userProvider.welcomeMessage!,
fontSize: 21,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.title,
)
: const SizedBox(height: 20),
),
GestureDetector(
onTap: () {
_startNewChat(context);
},
child: Container(
padding:
const EdgeInsets.symmetric(horizontal: 16, vertical: 11),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(30),
border: Border.all(
color: const Color.fromARGB(255, 25, 93, 128),
width: 1),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 12,
offset: const Offset(0, 4),
),
],
),
child: Row(
children: [
GestureDetector(
onTap: () {
_startNewChat(context);
},
child: Container(
padding: const EdgeInsets.all(8),
decoration: const BoxDecoration(
color: Color.fromARGB(255, 25, 93, 128),
shape: BoxShape.circle,
),
child: SvgPicture.asset(
'lib/assets/icons/microphone-2.svg',
width: 20,
height: 20,
colorFilter: const ColorFilter.mode(
Colors.white,
BlendMode.srcIn,
),
),
),
),
const SizedBox(width: 12),
const Expanded(
child: DidvanText(
'سوال خود را بپرسید...',
color: Color.fromARGB(255, 18, 17, 16),
fontSize: 14,
),
),
const SizedBox(width: 12),
GestureDetector(
onTap: () {
_startNewChat(context);
},
child: Container(
padding: const EdgeInsets.all(1),
child: SvgPicture.asset(
'lib/assets/icons/send_bold.svg',
width: 30,
height: 30,
),
),
),
],
),
),
),
const SizedBox(height: 12),
const DidvanText(
'مدل‌های هوش مصنوعی می‌توانند اشتباه کنند، صحت اطلاعات مهم را بررسی کنید و از وارد کردن اطلاعات حساس بپرهیزید.',
textAlign: TextAlign.center,
fontSize: 11,
color: Color.fromARGB(255, 18, 17, 16),
maxLines: 3,
),
],
),
),
),
);
}
}
class HistoryDrawerContent extends StatefulWidget {
const HistoryDrawerContent({super.key});
@override
State<HistoryDrawerContent> createState() => _HistoryDrawerContentState();
}
class _HistoryDrawerContentState extends State<HistoryDrawerContent> {
final TextEditingController _searchController = TextEditingController();
String _searchQuery = '';
@override
void initState() {
super.initState();
_searchController.addListener(() {
setState(() {
_searchQuery = _searchController.text;
});
});
}
@override
void dispose() {
_searchController.dispose();
super.dispose();
}
DateTime? _parseDateString(String? dateString) {
if (dateString == null) return null;
try {
return DateTime.parse(dateString);
} catch (e) {
return null;
}
}
String _getOlderGroupName(DateTime date) {
final now = DateTime.now();
final nowJalali = Jalali.fromDateTime(now);
final dateJalali = Jalali.fromDateTime(date);
if (nowJalali.year != dateJalali.year) {
return dateJalali.year.toString();
}
return _getMonthName(dateJalali.month);
}
String _getMonthName(int month) {
const monthNames = [
'فروردین', // 1
'اردیبهشت', // 2
'خرداد', // 3
'تیر', // 4
'مرداد', // 5
'شهریور', // 6
'مهر', // 7
'آبان', // 8
'آذر', // 9
'دی', // 10
'بهمن', // 11
'اسفند', // 12
];
if (month >= 1 && month <= 12) {
return monthNames[month - 1];
}
return month.toString();
}
@override
Widget build(BuildContext context) {
return Align(
alignment: Alignment.centerLeft,
child: Material(
color: Colors.transparent,
child: Container(
width: MediaQuery.of(context).size.width * 0.75,
height: MediaQuery.of(context).size.height,
decoration: BoxDecoration(
color: const Color.fromARGB(255, 237, 237, 237),
boxShadow: [
BoxShadow(
// ignore: deprecated_member_use
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
offset: const Offset(5, 0),
),
],
),
child: Consumer<HistoryAiChatState>(
builder: (context, historyState, child) {
final filteredChats = historyState.chats.where((chat) {
final title = chat.title?.toLowerCase() ?? 'گفتگو';
final query = _searchQuery.toLowerCase();
return title.contains(query);
}).toList();
final now = DateTime.now();
final today = DateTime(now.year, now.month, now.day);
final yesterday = today.subtract(const Duration(days: 1));
final last7Days = today.subtract(const Duration(days: 7));
final last30Days = today.subtract(const Duration(days: 30));
final List<dynamic> todayChats = [];
final List<dynamic> yesterdayChats = [];
final List<dynamic> last7DaysChats = [];
final List<dynamic> last30DaysChats = [];
final Map<String, List<dynamic>> olderChatsGrouped = {};
for (final chat in filteredChats) {
final chatDate = _parseDateString(chat.updatedAt);
final chatDay = chatDate != null
? DateTime(chatDate.year, chatDate.month, chatDate.day)
: null;
if (chatDay == null) {
const groupName = 'قدیمی‌تر';
if (olderChatsGrouped[groupName] == null) {
olderChatsGrouped[groupName] = [];
}
olderChatsGrouped[groupName]!.add(chat);
} else if (chatDay.isAtSameMomentAs(today)) {
todayChats.add(chat);
} else if (chatDay.isAtSameMomentAs(yesterday)) {
yesterdayChats.add(chat);
} else if (chatDay.isAfter(last7Days)) {
last7DaysChats.add(chat);
} else if (chatDay.isAfter(last30Days)) {
last30DaysChats.add(chat);
} else {
final String groupName = _getOlderGroupName(chatDate!);
if (olderChatsGrouped[groupName] == null) {
olderChatsGrouped[groupName] = [];
}
olderChatsGrouped[groupName]!.add(chat);
}
}
return Column(
children: [
SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
children: [
const SizedBox(height: 50),
Row(
mainAxisAlignment: MainAxisAlignment.start,
children: [
SvgPicture.asset(
'lib/assets/icons/Online Chat Support.svg',
height: 60,
),
],
),
const SizedBox(height: 26),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 12, vertical: 11),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: const Color.fromARGB(
255, 223, 223, 223),
width: 1)),
child: Row(
children: [
SvgPicture.asset(
'lib/assets/icons/search-normal2.svg'),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _searchController,
decoration: const InputDecoration(
hintText: 'جستجو',
hintStyle: TextStyle(
color: Color.fromARGB(
255, 161, 160, 160),
fontSize: 14),
border: InputBorder.none,
isDense: true,
contentPadding: EdgeInsets.zero,
),
),
),
if (_searchQuery.isNotEmpty)
GestureDetector(
onTap: () {
_searchController.clear();
},
child: Icon(Icons.close,
color: Colors.grey[600], size: 20),
),
],
),
),
],
),
),
),
const Padding(
padding: EdgeInsets.fromLTRB(40, 10, 40, 0),
child: Divider(
height: 3,
color: Color.fromARGB(255, 161, 160, 160),
),
),
Expanded(
child: historyState.loadinggetAll
? const Center(child: CircularProgressIndicator())
: historyState.chats.isEmpty
? Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(
Icons.chat_bubble_outline,
size: 64,
color: Colors.grey[400],
),
const SizedBox(height: 16),
DidvanText(
'هنوز گفتگویی\nوجود ندارد',
textAlign: TextAlign.center,
color: Colors.grey[600],
),
],
),
)
: filteredChats.isEmpty
? Center(
child: DidvanText(
'موردی یافت نشد',
textAlign: TextAlign.center,
color: Colors.grey[600],
),
)
: ListView(
padding:
const EdgeInsets.symmetric(vertical: 4),
children: [
if (todayChats.isNotEmpty) ...[
_buildSectionHeader('امروز'),
...todayChats.map((chat) =>
_buildChatItem(chat, historyState)),
],
if (yesterdayChats.isNotEmpty) ...[
_buildSectionHeader('دیروز'),
...yesterdayChats.map((chat) =>
_buildChatItem(chat, historyState)),
],
if (last7DaysChats.isNotEmpty) ...[
_buildSectionHeader('7 روز اخیر'),
...last7DaysChats.map((chat) =>
_buildChatItem(chat, historyState)),
],
if (last30DaysChats.isNotEmpty) ...[
_buildSectionHeader('30 روز اخیر'),
...last30DaysChats.map((chat) =>
_buildChatItem(chat, historyState)),
],
...olderChatsGrouped.keys
.map((groupName) {
return Column(
crossAxisAlignment:
CrossAxisAlignment.start,
children: [
_buildSectionHeader(groupName),
...olderChatsGrouped[groupName]!
.map((chat) => _buildChatItem(
chat, historyState)),
],
);
}),
],
),
),
],
);
},
),
),
),
);
}
Widget _buildSectionHeader(String title) {
return Padding(
padding: const EdgeInsets.fromLTRB(20, 16, 25, 16),
child: DidvanText(
title,
fontSize: 14,
fontWeight: FontWeight.bold,
color: const Color.fromARGB(255, 0, 126, 167),
),
);
}
Widget _buildChatItem(dynamic chat, dynamic historyState) {
final int index = historyState.chats.indexOf(chat);
return Padding(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
child: InkWell(
onTap: () {
Navigator.pop(context);
if (historyState.bots.isNotEmpty) {
Navigator.of(context).pushNamed(
Routes.aiChat,
arguments: AiChatArgs(
chat: chat,
bot: historyState.bots.first,
),
);
}
},
borderRadius: BorderRadius.circular(12),
child: Padding(
padding: const EdgeInsets.fromLTRB(8, 5, 10, 5),
child: SizedBox(
child: Row(
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
DidvanText(
chat.title ?? 'گفتگو',
maxLines: 2,
overflow: TextOverflow.ellipsis,
fontSize: 14,
fontWeight: FontWeight.w500,
),
if (chat.updatedAt != null) ...[
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
DidvanText(
_formatDateFromString(chat.updatedAt),
fontSize: 12,
color: Colors.grey[600],
),
PopupMenuButton<String>(
icon:
SvgPicture.asset('lib/assets/icons/more.svg'),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(16),
),
elevation: 4,
padding: EdgeInsets.zero,
offset: const Offset(0, 10),
onSelected: (value) async {
if (index == -1) return;
switch (value) {
case 'rename':
_showRenameDialog(
chat, historyState, index);
break;
case 'archive':
await historyState.archivedChat(
chat.id!, index,
refresh: false);
historyState.update();
break;
case 'delete':
await historyState.deleteChat(
chat.id!, index,
refresh: false);
historyState.update();
break;
}
},
itemBuilder: (BuildContext context) =>
<PopupMenuEntry<String>>[
PopupMenuItem<String>(
value: 'rename',
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 12),
child: SizedBox(
width: 180,
child: Column(
children: [
Row(
children: [
SvgPicture.asset(
'lib/assets/icons/edit-2.svg',
height: 20,
),
const SizedBox(width: 16),
const Text('تغییر نام',
style: TextStyle(fontSize: 13)),
],
),
const SizedBox(height: 20),
const Divider(
height: 1,
color: Color.fromARGB(
255, 227, 226, 225),
)
],
),
),
),
PopupMenuItem<String>(
value: 'archive',
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 12),
child: SizedBox(
width: 180,
child: Column(
children: [
Row(
children: [
SvgPicture.asset(
'lib/assets/icons/direct-inbox.svg',
height: 20,
),
const SizedBox(width: 16),
const Text('آرشیو',
style: TextStyle(fontSize: 13)),
],
),
const SizedBox(height: 20),
const Divider(
height: 1,
color: Color.fromARGB(
255, 227, 226, 225),
)
],
),
),
),
PopupMenuItem<String>(
value: 'delete',
padding: const EdgeInsets.symmetric(
horizontal: 20, vertical: 1),
child: SizedBox(
width: 150,
child: Row(
children: [
SvgPicture.asset(
'lib/assets/icons/trash.svg',
height: 20,
// ignore: deprecated_member_use
color: const Color.fromARGB(
255, 0, 126, 167),
),
const SizedBox(width: 16),
const Text('حذف',
style: TextStyle(
fontSize: 13,
)),
],
),
),
),
const PopupMenuDivider(height: 10),
],
),
],
),
],
],
),
),
],
),
),
),
),
);
}
String _formatDate(DateTime date) {
final dateJalali = Jalali.fromDateTime(date);
final year = dateJalali.year.toString();
final month = dateJalali.month.toString().padLeft(2, '0');
final day = dateJalali.day.toString().padLeft(2, '0');
final hour = date.hour.toString().padLeft(2, '0');
final minute = date.minute.toString().padLeft(2, '0');
return '$year.$month.$day - $hour:$minute';
}
String _formatDateFromString(String? dateString) {
if (dateString == null) return '';
try {
final date = DateTime.parse(dateString);
return _formatDate(date);
} catch (e) {
return '';
}
}
void _showRenameDialog(
dynamic chat, HistoryAiChatState historyState, int index) {
final controller = TextEditingController(text: chat.title);
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('تغییر نام گفتگو'),
content: TextField(
controller: controller,
decoration: const InputDecoration(
hintText: 'نام جدید را وارد کنید',
border: OutlineInputBorder(),
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('لغو'),
),
TextButton(
onPressed: () async {
if (controller.text.isNotEmpty) {
await historyState.changeNameChat(
chat.id!,
index,
controller.text,
refresh: false,
);
historyState.update();
}
Navigator.pop(context);
},
child: const Text('ذخیره'),
),
],
),
);
}
}