772 lines
29 KiB
Dart
772 lines
29 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/material.dart';
|
|
import 'package:another_telephony/telephony.dart';
|
|
import '../utils/contact_helper.dart';
|
|
import '../utils/database_helper.dart';
|
|
import 'chat_screen.dart';
|
|
import 'group_chat_screen.dart';
|
|
import 'compose_screen.dart';
|
|
import 'settings_screen.dart';
|
|
import '../utils/secure_messaging_service.dart';
|
|
import '../utils/protocol_helper.dart';
|
|
import '../utils/notification_helper.dart';
|
|
import 'package:flutter_contacts/flutter_contacts.dart';
|
|
import '../utils/app_theme.dart';
|
|
|
|
class HomeScreen extends StatefulWidget {
|
|
const HomeScreen({super.key});
|
|
@override
|
|
State<HomeScreen> createState() => _HomeScreenState();
|
|
}
|
|
|
|
class _HomeScreenState extends State<HomeScreen>
|
|
with SingleTickerProviderStateMixin {
|
|
late TabController _tabController;
|
|
List<Map<String, dynamic>> conversations = [];
|
|
List<Map<String, dynamic>> groups = [];
|
|
List<Contact> contacts = [];
|
|
final TextEditingController _contactSearchController =
|
|
TextEditingController();
|
|
bool isLoading = true;
|
|
bool isLoadingContacts = false;
|
|
final Telephony telephony = Telephony.instance;
|
|
Map<String, int> unreadCounts = {};
|
|
Map<int, int> groupUnreadCounts = {};
|
|
|
|
// رنگهای تم حرفهای
|
|
Color get primaryColor => Theme.of(context).primaryColor;
|
|
Color get secondaryColor => Theme.of(context).colorScheme.secondary;
|
|
final Color backgroundColor = AppTheme.darkBg;
|
|
|
|
@override
|
|
void initState() {
|
|
super.initState();
|
|
_tabController = TabController(length: 3, vsync: this);
|
|
_tabController.addListener(() {
|
|
if (mounted) setState(() {});
|
|
if (_tabController.index == 2 && contacts.isEmpty) {
|
|
_loadContacts();
|
|
}
|
|
});
|
|
// Delay heavy initialization until transition finishes (800ms)
|
|
// This makes the logo transition buttery smooth (60fps)
|
|
Future.delayed(const Duration(milliseconds: 900), () {
|
|
if (mounted) initApp();
|
|
});
|
|
_initMessageStreamListener();
|
|
}
|
|
|
|
StreamSubscription? _messageSubscription;
|
|
StreamSubscription? _notificationSubscription;
|
|
|
|
void _initMessageStreamListener() {
|
|
_messageSubscription =
|
|
SecureMessagingService.instance.messageStream.listen((data) {
|
|
Future.delayed(const Duration(milliseconds: 500), () {
|
|
if (mounted) loadData();
|
|
});
|
|
});
|
|
|
|
_notificationSubscription =
|
|
NotificationHelper.instance.notificationStream.listen((payload) {
|
|
if (!mounted) return;
|
|
if (payload.startsWith('group_')) {
|
|
final groupId = int.tryParse(payload.replaceFirst('group_', ''));
|
|
if (groupId != null) {
|
|
// Find group name
|
|
String name = "گروه";
|
|
for (var g in groups) {
|
|
if (g['id'] == groupId) {
|
|
name = g['name'];
|
|
break;
|
|
}
|
|
}
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) =>
|
|
GroupChatScreen(groupId: groupId, groupName: name),
|
|
),
|
|
).then((_) => loadData());
|
|
}
|
|
} else {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => ChatScreen(address: payload),
|
|
),
|
|
).then((_) => loadData());
|
|
}
|
|
});
|
|
}
|
|
|
|
@override
|
|
void dispose() {
|
|
_tabController.dispose();
|
|
_contactSearchController.dispose();
|
|
_messageSubscription?.cancel();
|
|
_notificationSubscription?.cancel();
|
|
super.dispose();
|
|
}
|
|
|
|
void initApp() {
|
|
loadMessages();
|
|
// همگامسازی نام مخاطبین در پسزمینه
|
|
Future.delayed(const Duration(seconds: 1), () async {
|
|
// ابتدا از کش محلی لود میکنیم برای سرعت
|
|
await ContactHelper.loadFromLocalCache();
|
|
if (mounted) setState(() {});
|
|
|
|
// سپس با دستگاه سینک میکنیم برای آپدیت نامهای جدید
|
|
await ContactHelper.syncWithDevice();
|
|
if (mounted) setState(() {});
|
|
});
|
|
}
|
|
|
|
void loadData() async {
|
|
setState(() => isLoading = true);
|
|
await loadMessages();
|
|
final g = await DatabaseHelper.instance.getGroups();
|
|
final counts = <int, int>{};
|
|
for (final group in g) {
|
|
counts[group['id']] =
|
|
await DatabaseHelper.instance.getGroupUnreadCount(group['id']);
|
|
}
|
|
if (_tabController.index == 2 || contacts.isNotEmpty) {
|
|
await _loadContacts();
|
|
}
|
|
if (mounted) {
|
|
setState(() {
|
|
groups = g;
|
|
groupUnreadCounts = counts;
|
|
isLoading = false;
|
|
});
|
|
}
|
|
}
|
|
|
|
Future<void> _loadContacts() async {
|
|
if (isLoadingContacts) return;
|
|
setState(() => isLoadingContacts = true);
|
|
try {
|
|
final c = await ContactHelper.getContactsLight();
|
|
if (mounted) {
|
|
setState(() {
|
|
contacts = c;
|
|
isLoadingContacts = false;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
if (mounted) setState(() => isLoadingContacts = false);
|
|
}
|
|
}
|
|
|
|
Future<void> _startChatFromContact(Contact contact) async {
|
|
showDialog(
|
|
context: context,
|
|
barrierDismissible: false,
|
|
builder: (_) => const Center(child: CircularProgressIndicator()),
|
|
);
|
|
|
|
try {
|
|
final full = await ContactHelper.getFullContact(contact.id);
|
|
if (mounted) Navigator.pop(context);
|
|
|
|
if (full == null || full.phones.isEmpty) {
|
|
if (mounted) {
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
const SnackBar(content: Text("این مخاطب شماره تلفن ندارد")),
|
|
);
|
|
}
|
|
return;
|
|
}
|
|
|
|
String phone = full.phones.first.number;
|
|
if (full.phones.length > 1) {
|
|
if (!mounted) return;
|
|
final selected = await showDialog<String>(
|
|
context: context,
|
|
builder: (ctx) => SimpleDialog(
|
|
title: Text("انتخاب شماره ${full.displayName}"),
|
|
children: full.phones
|
|
.map((p) => SimpleDialogOption(
|
|
onPressed: () => Navigator.pop(ctx, p.number),
|
|
child: Text(p.number),
|
|
))
|
|
.toList(),
|
|
),
|
|
);
|
|
if (selected == null) return;
|
|
phone = selected;
|
|
}
|
|
|
|
if (mounted) {
|
|
Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => ChatScreen(address: phone),
|
|
),
|
|
).then((_) => loadData());
|
|
}
|
|
} catch (e) {
|
|
if (mounted) Navigator.pop(context);
|
|
}
|
|
}
|
|
|
|
Future<void> loadMessages() async {
|
|
try {
|
|
// Fetch from local cache for instant updates
|
|
final localRows = await DatabaseHelper.instance.getConversations();
|
|
|
|
final installDate =
|
|
await SecureMessagingService.instance.getInstallDate();
|
|
|
|
// Filter by install date
|
|
List<Map<String, dynamic>> filtered = localRows.where((m) {
|
|
if (installDate == null) return true;
|
|
final date = m['date'] as int? ?? 0;
|
|
return date >= installDate;
|
|
}).toList();
|
|
|
|
// Fetch unread counts
|
|
final counts = <String, int>{};
|
|
for (final msg in filtered) {
|
|
final addr = msg['address'] as String?;
|
|
if (addr != null) {
|
|
counts[addr] = await DatabaseHelper.instance.getUnreadCount(addr);
|
|
}
|
|
}
|
|
|
|
if (mounted) {
|
|
setState(() {
|
|
conversations = filtered;
|
|
unreadCounts = counts;
|
|
isLoading = false;
|
|
});
|
|
}
|
|
} catch (e) {
|
|
debugPrint("[SABA] Error in local loadMessages: $e");
|
|
if (mounted) setState(() => isLoading = false);
|
|
}
|
|
}
|
|
|
|
void _deleteGroup(int groupId) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
|
title: const Text("حذف گروه"),
|
|
content: const Text("آیا از حذف این گروه مطمئنید؟"),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx), child: const Text("لغو")),
|
|
ElevatedButton(
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: Colors.red, foregroundColor: Colors.white),
|
|
onPressed: () async {
|
|
Navigator.pop(ctx);
|
|
await DatabaseHelper.instance.deleteGroup(groupId);
|
|
if (!mounted) return;
|
|
loadData();
|
|
ScaffoldMessenger.of(context)
|
|
.showSnackBar(const SnackBar(content: Text("گروه حذف شد")));
|
|
},
|
|
child: const Text("حذف"),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
void _deleteIndividualChat(String address) {
|
|
showDialog(
|
|
context: context,
|
|
builder: (ctx) => AlertDialog(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(15)),
|
|
title: const Text("حذف گفتگو"),
|
|
content: const Text(
|
|
"به دلیل محدودیتهای اندروید، حذف پیامکهای سیستمی فقط توسط برنامه پیشفرض پیامک امکانپذیر است."),
|
|
actions: [
|
|
TextButton(
|
|
onPressed: () => Navigator.pop(ctx), child: const Text("باشه")),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Future<void> _addContactToDevice(String phone) async {
|
|
if (!await FlutterContacts.requestPermission()) return;
|
|
try {
|
|
final contact = Contact(phones: [Phone(phone)]);
|
|
await FlutterContacts.openExternalInsert(contact);
|
|
if (!mounted) return;
|
|
loadData(); // همگامسازی مجدد بعد از افزودن
|
|
} catch (e) {
|
|
if (!mounted) return;
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(content: Text("خطا در افزودن مخاطب: $e")),
|
|
);
|
|
}
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Scaffold(
|
|
backgroundColor: backgroundColor,
|
|
appBar: PreferredSize(
|
|
preferredSize: const Size.fromHeight(85),
|
|
child: AppTheme.glassWrapper(
|
|
radius: 0,
|
|
sigma: 18,
|
|
child: AppBar(
|
|
backgroundColor: Colors.transparent,
|
|
elevation: 0,
|
|
centerTitle: true,
|
|
title: Padding(
|
|
padding: const EdgeInsets.only(top: 15),
|
|
child: Hero(
|
|
tag: 'app_logo',
|
|
child: GestureDetector(
|
|
onTap: () {
|
|
if (mounted) setState(() {});
|
|
},
|
|
child: const Image(
|
|
image: AssetImage('صبا بالا.png'),
|
|
height: 42,
|
|
fit: BoxFit.contain,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
actions: [
|
|
IconButton(
|
|
icon: const Icon(Icons.settings_outlined,
|
|
color: Colors.white, size: 22),
|
|
onPressed: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(builder: (_) => const SettingsScreen()),
|
|
).then((_) => loadData()),
|
|
),
|
|
IconButton(
|
|
icon: const Icon(Icons.refresh_rounded,
|
|
color: Colors.white, size: 22),
|
|
onPressed: loadData,
|
|
),
|
|
const SizedBox(width: 8),
|
|
],
|
|
bottom: PreferredSize(
|
|
preferredSize: const Size.fromHeight(50),
|
|
child: Container(
|
|
color: Colors.transparent, // Let glass show through
|
|
child: TabBar(
|
|
controller: _tabController,
|
|
labelColor: primaryColor,
|
|
unselectedLabelColor: Colors.grey,
|
|
indicatorColor: primaryColor,
|
|
indicatorWeight: 3,
|
|
labelStyle: const TextStyle(
|
|
fontWeight: FontWeight.bold, fontSize: 16),
|
|
dividerColor: Colors.transparent,
|
|
tabs: const [
|
|
Tab(text: "گفتگوها"),
|
|
Tab(text: "گروهها"),
|
|
Tab(text: "مخاطبین"),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
body: TabBarView(
|
|
controller: _tabController,
|
|
children: [
|
|
// --- لیست گفتگوها ---
|
|
isLoading
|
|
? Center(child: CircularProgressIndicator(color: primaryColor))
|
|
: conversations.isEmpty
|
|
? _buildEmptyState("هیچ گفتگویی وجود ندارد")
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.only(top: 10, bottom: 80),
|
|
itemCount: conversations.length,
|
|
itemBuilder: (context, index) {
|
|
final msg = conversations[index];
|
|
final addr = msg['address'] as String? ?? "";
|
|
final displayName = ContactHelper.getName(addr);
|
|
final body = msg['body'] as String? ?? "";
|
|
final parsed = ProtocolHelper.parseMessage(body);
|
|
final isSecure = parsed['type'] != 'plain';
|
|
final displayText =
|
|
isSecure ? "🔒 پیام امن (رمزگذاری شده)" : body;
|
|
|
|
return _buildChatCard(
|
|
index: index,
|
|
title: displayName,
|
|
subtitle: displayText,
|
|
isEncrypted: isSecure,
|
|
unreadCount: unreadCounts[addr] ?? 0,
|
|
avatarText:
|
|
displayName.isNotEmpty ? displayName[0] : "?",
|
|
colorSeed: index,
|
|
showAddButton: ContactHelper.isRawNumber(displayName),
|
|
onAddContact: () => _addContactToDevice(addr),
|
|
onTap: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) =>
|
|
ChatScreen(address: addr)))
|
|
.then((_) => loadData()),
|
|
onLongPress: () => _deleteIndividualChat(addr),
|
|
);
|
|
},
|
|
),
|
|
|
|
// --- لیست گروهها ---
|
|
groups.isEmpty
|
|
? _buildEmptyState("گروهی ساخته نشده است")
|
|
: ListView.builder(
|
|
padding: const EdgeInsets.only(top: 10, bottom: 80),
|
|
itemCount: groups.length,
|
|
itemBuilder: (context, index) {
|
|
final group = groups[index];
|
|
return _buildChatCard(
|
|
index: index,
|
|
title: group['name'],
|
|
subtitle: "پیام گروهی",
|
|
isEncrypted: false,
|
|
isGroup: true,
|
|
unreadCount: groupUnreadCounts[group['id']] ?? 0,
|
|
avatarText: "#",
|
|
colorSeed: index + 5,
|
|
onTap: () => Navigator.push(
|
|
context,
|
|
MaterialPageRoute(
|
|
builder: (_) => GroupChatScreen(
|
|
groupId: group['id'],
|
|
groupName: group['name']))),
|
|
onLongPress: () => _deleteGroup(group['id']),
|
|
);
|
|
},
|
|
),
|
|
|
|
// --- لیست مخاطبین ---
|
|
Column(
|
|
children: [
|
|
Padding(
|
|
padding: const EdgeInsets.fromLTRB(16, 12, 16, 8),
|
|
child: AppTheme.glassWrapper(
|
|
radius: 12,
|
|
sigma: 5,
|
|
child: TextField(
|
|
controller: _contactSearchController,
|
|
onChanged: (val) => setState(() {}),
|
|
style: const TextStyle(color: Colors.white),
|
|
decoration: InputDecoration(
|
|
hintText: "جستجو در مخاطبین...",
|
|
hintStyle: const TextStyle(color: Colors.white60),
|
|
prefixIcon:
|
|
const Icon(Icons.search, color: Colors.white70),
|
|
filled: true,
|
|
fillColor: Colors.white.withValues(alpha: 0.05),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(12),
|
|
borderSide: BorderSide.none),
|
|
contentPadding: const EdgeInsets.symmetric(vertical: 0),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: isLoadingContacts && contacts.isEmpty
|
|
? Center(
|
|
child: CircularProgressIndicator(color: primaryColor))
|
|
: contacts.isEmpty
|
|
? _buildEmptyState("مخاطبی یافت نشد")
|
|
: Builder(builder: (context) {
|
|
final query = _contactSearchController.text
|
|
.trim()
|
|
.toLowerCase();
|
|
final filtered = query.isEmpty
|
|
? contacts
|
|
: contacts.where((c) {
|
|
final name = c.displayName.toLowerCase();
|
|
return name.contains(query);
|
|
}).toList();
|
|
|
|
if (filtered.isEmpty) {
|
|
return _buildEmptyState("نتیجهای یافت نشد");
|
|
}
|
|
|
|
return ListView.builder(
|
|
padding: const EdgeInsets.only(bottom: 80),
|
|
itemCount: filtered.length,
|
|
itemBuilder: (context, index) {
|
|
final contact = filtered[index];
|
|
final displayName = contact.displayName;
|
|
return _buildChatCard(
|
|
index: index,
|
|
title: displayName,
|
|
subtitle: "شروع گفتگوی جدید",
|
|
isEncrypted: false,
|
|
avatarText: displayName.isNotEmpty
|
|
? displayName[0]
|
|
: "?",
|
|
colorSeed: index,
|
|
onTap: () => _startChatFromContact(contact),
|
|
onLongPress: () {},
|
|
);
|
|
},
|
|
);
|
|
}),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
floatingActionButton: FloatingActionButton.extended(
|
|
backgroundColor: primaryColor,
|
|
icon: Icon(_tabController.index == 0 ? Icons.edit : Icons.group_add,
|
|
color: Colors.white),
|
|
label: Text(_tabController.index == 0 ? "پیام جدید" : "گروه جدید",
|
|
style: const TextStyle(
|
|
color: Colors.white, fontWeight: FontWeight.bold)),
|
|
onPressed: () => Navigator.push(context,
|
|
MaterialPageRoute(builder: (_) => const ComposeScreen()))
|
|
.then((res) {
|
|
if (res == true) loadData();
|
|
}),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildEmptyState(String text) {
|
|
return Center(
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(Icons.chat_bubble_outline, size: 80, color: Colors.grey[400]),
|
|
const SizedBox(height: 16),
|
|
Text(text, style: TextStyle(color: Colors.grey[600], fontSize: 18)),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildChatCard({
|
|
required int index,
|
|
required String title,
|
|
required String subtitle,
|
|
required bool isEncrypted,
|
|
required String avatarText,
|
|
required int colorSeed,
|
|
int unreadCount = 0,
|
|
bool isGroup = false,
|
|
bool showAddButton = false,
|
|
VoidCallback? onAddContact,
|
|
required VoidCallback onTap,
|
|
required VoidCallback onLongPress,
|
|
}) {
|
|
final List<Color> avatarColors = [
|
|
Colors.blueAccent,
|
|
Colors.teal,
|
|
Colors.deepPurple,
|
|
Colors.indigo,
|
|
Colors.orangeAccent,
|
|
Colors.pinkAccent,
|
|
Colors.cyan
|
|
];
|
|
final avatarBg =
|
|
isGroup ? Colors.orange : avatarColors[colorSeed % avatarColors.length];
|
|
|
|
// Slide and Fade animation for that premium staggered feel
|
|
return TweenAnimationBuilder<double>(
|
|
duration: const Duration(milliseconds: 450),
|
|
tween: Tween(begin: 0.0, end: 1.0),
|
|
curve: Curves.easeOutQuart,
|
|
// Delay based on index for the staggered effect
|
|
builder: (context, value, child) {
|
|
return Opacity(
|
|
opacity: value,
|
|
child: Transform.translate(
|
|
offset: Offset(30 * (1 - value), 0),
|
|
child: child,
|
|
),
|
|
);
|
|
},
|
|
child: Container(
|
|
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 6),
|
|
decoration: BoxDecoration(
|
|
color: AppTheme.darkCard,
|
|
borderRadius: BorderRadius.circular(18),
|
|
border: Border.all(
|
|
color: Colors.white.withValues(alpha: 0.05), width: 0.5),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: Colors.black.withValues(alpha: 0.4),
|
|
spreadRadius: 1,
|
|
blurRadius: 12,
|
|
offset: const Offset(0, 6),
|
|
),
|
|
],
|
|
),
|
|
child: Material(
|
|
color: Colors.transparent,
|
|
borderRadius: BorderRadius.circular(18),
|
|
child: InkWell(
|
|
borderRadius: BorderRadius.circular(18),
|
|
onTap: onTap,
|
|
onLongPress: onLongPress,
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(14.0),
|
|
child: Row(
|
|
children: [
|
|
Hero(
|
|
tag: 'avatar_${title}_${isGroup ? 'group' : 'ind'}',
|
|
child: Stack(
|
|
clipBehavior: Clip.none,
|
|
children: [
|
|
Container(
|
|
width: 52,
|
|
height: 52,
|
|
decoration: BoxDecoration(
|
|
gradient: LinearGradient(
|
|
colors: [
|
|
avatarBg,
|
|
avatarBg.withValues(alpha: 0.6),
|
|
],
|
|
begin: Alignment.topLeft,
|
|
end: Alignment.bottomRight,
|
|
),
|
|
shape: BoxShape.circle,
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: avatarBg.withValues(alpha: 0.3),
|
|
blurRadius: 8,
|
|
spreadRadius: 1,
|
|
),
|
|
],
|
|
),
|
|
child: Center(
|
|
child: isGroup
|
|
? const Icon(Icons.groups,
|
|
color: Colors.white, size: 28)
|
|
: Text(
|
|
avatarText,
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.bold),
|
|
),
|
|
),
|
|
),
|
|
if (unreadCount > 0)
|
|
Positioned(
|
|
top: -4,
|
|
left: -4,
|
|
child: Container(
|
|
constraints: const BoxConstraints(
|
|
minWidth: 22,
|
|
minHeight: 22,
|
|
),
|
|
padding:
|
|
const EdgeInsets.symmetric(horizontal: 6),
|
|
decoration: BoxDecoration(
|
|
color: const Color(0xFFFF5A5F),
|
|
borderRadius: BorderRadius.circular(12),
|
|
border: Border.all(
|
|
color: AppTheme.darkCard,
|
|
width: 2,
|
|
),
|
|
boxShadow: [
|
|
BoxShadow(
|
|
color: const Color(0xFFFF5A5F)
|
|
.withValues(alpha: 0.35),
|
|
blurRadius: 10,
|
|
spreadRadius: 1,
|
|
),
|
|
],
|
|
),
|
|
child: Center(
|
|
child: Text(
|
|
unreadCount > 99
|
|
? '99+'
|
|
: unreadCount.toString(),
|
|
style: const TextStyle(
|
|
color: Colors.white,
|
|
fontSize: 11,
|
|
fontWeight: FontWeight.w800,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
const SizedBox(width: 16),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontWeight: unreadCount > 0
|
|
? FontWeight.w900
|
|
: FontWeight.bold,
|
|
fontSize: 17,
|
|
color: Colors.white,
|
|
),
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
textDirection: TextDirection.ltr,
|
|
textAlign: TextAlign.right,
|
|
),
|
|
const SizedBox(height: 4),
|
|
Row(
|
|
children: [
|
|
if (isEncrypted)
|
|
Padding(
|
|
padding: const EdgeInsets.only(left: 4),
|
|
child: Icon(Icons.lock,
|
|
size: 14, color: primaryColor),
|
|
),
|
|
Expanded(
|
|
child: Text(
|
|
subtitle,
|
|
maxLines: 1,
|
|
overflow: TextOverflow.ellipsis,
|
|
style: TextStyle(
|
|
color: unreadCount > 0
|
|
? Colors.white.withValues(alpha: 0.9)
|
|
: Colors.white.withValues(alpha: 0.6),
|
|
fontSize: 13,
|
|
fontWeight: unreadCount > 0
|
|
? FontWeight.w600
|
|
: FontWeight.w400,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
),
|
|
if (showAddButton)
|
|
IconButton(
|
|
icon:
|
|
Icon(Icons.person_add_outlined, color: primaryColor),
|
|
onPressed: onAddContact,
|
|
tooltip: "افزودن به مخاطبین",
|
|
),
|
|
Icon(Icons.chevron_right, color: Colors.grey[300]),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|