Neda/Front/lib/home/widgets/watch_launcher.dart
2026-03-19 11:56:16 +03:30

444 lines
13 KiB
Dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../../services/api_service.dart';
// ایمپورت صفحات دیگر برای ناوبری
import '../../channel_list/channel_list_screen.dart';
import '../../screens/notifications_screen.dart';
import 'create_group_page.dart';
import '../../screens/about.dart';
// مدل داده‌ای برای آیکون‌های منو
class LauncherItem {
final String label;
final IconData? icon;
final String? imagePath;
final Color color;
final VoidCallback onTap;
LauncherItem({
required this.label,
this.icon,
this.imagePath,
required this.color,
required this.onTap,
});
}
class WatchLauncher extends StatefulWidget {
const WatchLauncher({super.key});
@override
State<WatchLauncher> createState() => _WatchLauncherState();
}
class _WatchLauncherState extends State<WatchLauncher> {
// کنترلر اسکرول
final ScrollController _scrollController = ScrollController();
// ارتفاع هر آیکون
final double _itemHeight = 100.0;
// کانال ارتباطی با کد نیتیو
static final _nativeChannel = const MethodChannel(
'com.example.watch/launcher',
);
late final ApiService _api;
// لیست آیکون‌های برنامه
late final List<LauncherItem> _items;
// متغیرهای برای ساعت و تاریخ داینامیک
String _currentTime = '';
String _currentDate = '';
late StreamSubscription<DateTime> _timeSubscription;
@override
void initState() {
super.initState();
_items = _generateItems();
// استفاده از Stream برای بهینه‌سازی ساعت
_timeSubscription =
Stream<DateTime>.periodic(
const Duration(seconds: 1),
(count) => DateTime.now(),
).listen((dateTime) {
if (!mounted) return;
final timeString =
'${dateTime.hour.toString().padLeft(2, '0')}:${dateTime.minute.toString().padLeft(2, '0')}';
final dateString =
'${dateTime.year}-${dateTime.month.toString().padLeft(2, '0')}-${dateTime.day.toString().padLeft(2, '0')}';
setState(() {
_currentTime = timeString;
_currentDate = dateString;
});
});
}
@override
void dispose() {
_scrollController.dispose();
_timeSubscription.cancel();
super.dispose();
}
// تولید لیست آیکون‌ها
List<LauncherItem> _generateItems() {
return [
LauncherItem(
label: 'اعلان‌ها',
icon: Icons.notifications_outlined,
color: const Color(0xFFFF1744),
onTap: () => _navigateTo(const NotificationsScreen()),
),
LauncherItem(
label: 'بی‌سیم',
icon: Icons.radio,
color: const Color(0xFF00C853),
onTap: () => _navigateTo(const ChannelListScreen()),
),
LauncherItem(
label: 'گروه جدید',
icon: Icons.add_circle_outline,
color: const Color(0xFF00C853),
onTap: _showCreateGroupDialog,
),
LauncherItem(
label: 'تلفن',
icon: Icons.phone,
color: const Color(0xFF2979FF),
onTap: _openDialer,
),
LauncherItem(
label: 'اینترنت',
icon: Icons.cell_tower,
color: const Color(0xFF00BCD4),
onTap: _openInternetSettings,
),
LauncherItem(
label: 'بلوتوث',
icon: Icons.bluetooth,
color: const Color(0xFF304FFE),
onTap: _openBluetoothSettings,
),
LauncherItem(
label: 'درباره ما',
imagePath: 'assets/images/logo.png',
color: const Color(0xFF9C27B0),
onTap: () => _navigateTo(const AboutScreen()),
),
LauncherItem(
label: 'خروج',
icon: Icons.logout,
color: Colors.redAccent,
onTap: _logout,
),
];
}
// --- متدهای کمکی ---
void _navigateTo(Widget page) {
Navigator.push(context, MaterialPageRoute(builder: (_) => page));
}
void _showSnack(String msg) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
msg,
style: const TextStyle(fontSize: 10, color: Colors.white),
),
backgroundColor: const Color(0xFF2C2C2E),
duration: const Duration(seconds: 2),
behavior: SnackBarBehavior.floating,
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
),
);
}
Future<void> _openDialer() async {
try {
await _nativeChannel.invokeMethod('openDialer');
} catch (_) {
_showSnack('خطا در باز کردن شماره‌گیر');
}
}
Future<void> _openInternetSettings() async {
try {
await _nativeChannel.invokeMethod('openInternetSettings');
} catch (_) {
_showSnack('تنظیمات اینترنت در دسترس نیست');
}
}
Future<void> _openBluetoothSettings() async {
try {
await _nativeChannel.invokeMethod('openBluetoothSettings');
} catch (_) {
_showSnack('خطا در باز کردن بلوتوث');
}
}
Future<void> _logout() async {
try {
if (!mounted) return;
Navigator.pushReplacementNamed(context, '/login');
} catch (e) {
_showSnack('خطا در خروج');
}
}
Future<void> _showCreateGroupDialog() async {
final groupName = await Navigator.push<String>(
context,
MaterialPageRoute(
builder: (ctx) => const CreateGroupPage(),
fullscreenDialog: true,
),
);
if (groupName != null && groupName.trim().isNotEmpty && mounted) {
try {
final newChannel = await _api.createGroup(groupName.trim());
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
newChannel != null ? 'گروه ساخته شد' : 'خطا در ساخت گروه',
style: const TextStyle(fontSize: 11, color: Colors.white),
textAlign: TextAlign.center,
),
backgroundColor: const Color(0xFF2C2C2E),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
);
} catch (e) {
if (!mounted) return;
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('خطا در ارتباط با سرور'),
backgroundColor: Colors.red,
),
);
}
}
}
@override
Widget build(BuildContext context) {
final double screenHeight = MediaQuery.of(context).size.height;
final double verticalPadding = (screenHeight / 2) - (_itemHeight / 2);
return Scaffold(
backgroundColor: Colors.black,
body: Stack(
children: [
// لایه اسکرول آیکون‌ها
Positioned.fill(
child: NotificationListener<ScrollNotification>(
onNotification: (scrollNotification) {
// فقط برای Snapping نیاز به setState نیست، AnimatedBuilder کار را انجام می‌دهد
if (scrollNotification is ScrollEndNotification) {
_snapToItem();
}
return false;
},
child: ListView.builder(
controller: _scrollController,
physics: const BouncingScrollPhysics(),
padding: EdgeInsets.symmetric(vertical: verticalPadding),
itemCount: _items.length,
itemBuilder: (context, index) {
return _buildAnimatedItem(index);
},
),
),
),
// ساعت در سمت چپ
Positioned(
left: 8,
top: 0,
bottom: 0,
child: Center(
child: RotatedBox(
quarterTurns: 3,
child: Text(
_currentTime,
style: const TextStyle(
color: Colors.white,
fontSize: 15,
fontWeight: FontWeight.bold,
letterSpacing: 1,
),
),
),
),
),
// تاریخ در سمت راست
Positioned(
right: 8,
top: 0,
bottom: 0,
child: Center(
child: RotatedBox(
quarterTurns: 1,
child: Text(
_currentDate,
style: const TextStyle(color: Colors.white70, fontSize: 13),
),
),
),
),
],
),
);
}
// متد ساخت آیکون با انیمیشن بهینه
Widget _buildAnimatedItem(int index) {
return AnimatedBuilder(
animation: _scrollController,
builder: (context, child) {
double scrollOffset = _scrollController.hasClients
? _scrollController.offset
: 0.0;
double itemPosition = index * _itemHeight;
double screenHeight = MediaQuery.of(context).size.height;
double centerOffset = scrollOffset + (screenHeight / 2);
double distanceFromCenter = (itemPosition - centerOffset).abs();
// محاسبات انیمیشن
double scale = (1.6 - (distanceFromCenter / 180)).clamp(0.4, 1.6);
double opacity = (1.2 - (distanceFromCenter / 250)).clamp(0.1, 1.0);
// چرخش آیکون‌های دورتر برای افکت سه بعدی
double rotation = (distanceFromCenter / 1000).clamp(-0.2, 0.2);
// شدت سایه بر اساس فاصله از مرکز
double blurRadius = (20 - (distanceFromCenter / 20)).clamp(0, 20);
double shadowOpacity = (0.6 - (distanceFromCenter / 400)).clamp(
0.0,
0.6,
);
return SizedBox(
height: _itemHeight,
child: Center(
child: Transform.scale(
scale: scale,
child: Transform.rotate(
angle: rotation,
child: Opacity(
opacity: opacity,
child: Container(
// حلقه درخشان دور آیکون وقتی وسط است
decoration: BoxDecoration(
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: _items[index].color.withOpacity(shadowOpacity),
blurRadius: blurRadius,
spreadRadius: 2,
),
],
),
child: _buildIconButton(_items[index]),
),
),
),
),
),
);
},
);
}
// متد قفل و زنجیر (Snapping)
void _snapToItem() {
if (!_scrollController.hasClients) return;
double screenHeight = MediaQuery.of(context).size.height;
double currentScroll = _scrollController.offset;
double centerOffset = currentScroll + (screenHeight / 2);
int targetIndex = (centerOffset / _itemHeight).round();
if (targetIndex < 0) targetIndex = 0;
if (targetIndex >= _items.length) targetIndex = _items.length - 1;
double targetScroll =
(targetIndex * _itemHeight) - ((screenHeight / 2) - (_itemHeight / 2));
_scrollController.animateTo(
targetScroll,
duration: const Duration(milliseconds: 300),
curve: Curves.easeOutCubic,
);
}
// ویجت دایره‌ای آیکون
Widget _buildIconButton(LauncherItem item) {
return InkWell(
onTap: item.onTap,
borderRadius: BorderRadius.circular(50),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 60,
height: 60,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: item.color,
border: Border.all(color: item.color.withOpacity(0.5), width: 2),
),
child: item.imagePath != null && item.imagePath!.isNotEmpty
? Padding(
padding: const EdgeInsets.all(12.0),
child: Image.asset(
item.imagePath!,
errorBuilder: (context, error, stackTrace) {
return Icon(
item.icon ?? Icons.error_outline,
color: Colors.white,
size: 30,
);
},
),
)
: Icon(
item.icon ?? Icons.error_outline,
color: Colors.white,
size: 30,
),
),
const SizedBox(height: 4),
Text(
item.label,
style: const TextStyle(
color: Colors.white,
fontSize: 10,
fontWeight: FontWeight.w500,
),
),
],
),
);
}
}