diff --git a/Front/android/app/src/main/AndroidManifest.xml b/Front/android/app/src/main/AndroidManifest.xml
index 969b7f2..e1b1743 100644
--- a/Front/android/app/src/main/AndroidManifest.xml
+++ b/Front/android/app/src/main/AndroidManifest.xml
@@ -7,6 +7,9 @@
+
+
+
+
+
+
+
+
+
+
+
+ when (call.method) {
+
+ // ── Launcher ──────────────────────────────────────────
+ "openHomeSettings" -> {
+ try {
+ startActivity(
+ Intent(Settings.ACTION_HOME_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+ result.success(null)
+ } catch (e: Exception) {
+ result.error("UNAVAILABLE", e.message, null)
+ }
+ }
+
+ // ── Battery ───────────────────────────────────────────
+ "getBatteryInfo" -> {
+ try {
+ val bm = getSystemService(BATTERY_SERVICE) as BatteryManager
+ val level = bm.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
+ val isCharging = bm.isCharging
+ result.success(mapOf("level" to level, "isCharging" to isCharging))
+ } catch (e: Exception) {
+ result.error("ERROR", e.message, null)
+ }
+ }
+
+ // ── Internet (WiFi + mobile data panel) ───────────────
+ "openInternetSettings" -> {
+ try {
+ val intent = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Intent(Settings.Panel.ACTION_INTERNET_CONNECTIVITY)
+ } else {
+ Intent(Settings.ACTION_WIRELESS_SETTINGS)
+ }
+ startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+ result.success(null)
+ } catch (e: Exception) {
+ result.error("UNAVAILABLE", e.message, null)
+ }
+ }
+
+ // ── Mobile Data Toggle ───────────────────────────────
+ "toggleMobileData" -> {
+ val enable = call.argument("enable")
+ if (enable != null) {
+ setMobileDataState(enable)
+ result.success(null)
+ } else {
+ result.error("INVALID_ARGUMENT", "Enable status not provided", null)
+ }
+ }
+ "getMobileDataStatus" -> {
+ val isEnabled = isMobileDataEnabled()
+ result.success(isEnabled)
+ }
+
+ // ── Accessibility Settings ───────────────────────────────
+ "openAccessibilitySettings" -> {
+ try {
+ val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS)
+ startActivity(intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
+ result.success(null)
+ } catch (e: Exception) {
+ result.error("UNAVAILABLE", e.message, null)
+ }
+ }
+
+ // ── SystemSettings ───────────────────────────────
+ "openSystemSettings" -> {
+ try {
+ val intent = packageManager.getLaunchIntentForPackage("com.dw.setting")
+ startActivity(intent)
+ result.success(true)
+ } catch (e: Exception) {
+ result.error("UNAVAILABLE", "تنظیمات باز نشد", null)
+ }
+ }
+
+ // ── EngineerMode ───────────────────────────────
+ "openEngineerMode" -> {
+ try {
+ // ساخت اینتنت برای باز کردن اکتیویتی مهندسی
+ val intent = Intent().apply {
+ // تنظیم پکیج و کلاس دقیق که فرستادید
+ setClassName("com.sprd.engineermode", "com.sprd.engineermode.EngineerModeActivity")
+ // اضافه کردن فلگ برای شروع اکتیویتی جدید در صورت نیاز
+ // flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ startActivity(intent)
+ result.success(true)
+ } catch (e: Exception) {
+ // اگر اپلیکیشن نصب نباشد یا خطایی رخ دهد
+ result.error("ERROR", "خطا در باز کردن EngineerMode: ${e.message}", null)
+ }
+ }
+
+ // ── Dialer ───────────────────────────────
+ "openDialer" -> {
+ try {
+ val intent = packageManager.getLaunchIntentForPackage("com.divo.phone")
+ startActivity(intent)
+ result.success(true)
+ } catch (e: Exception) {
+ result.error("UNAVAILABLE", "شمارهگیر در دسترس نیست", null)
+ }
+ }
+
+ // ── bluetooth ───────────────────────────────
+ "openBluetoothSettings" -> {
+ try {
+ // استفاده از روش دقیق برای باز کردن تنظیمات بلوتوث
+ val intent = Intent().apply {
+ setClassName("com.android.settings", "com.android.settings.bluetooth.BluetoothSettings")
+ flags = Intent.FLAG_ACTIVITY_NEW_TASK
+ }
+ startActivity(intent)
+ result.success(true)
+ } catch (e: Exception) {
+ result.error("UNAVAILABLE", "تنظیمات بلوتوث باز نشد", null)
+ }
+ }
+
+ // ── WiFi settings page ────────────────────────────────
+ "openWifiSettings" -> {
+ try {
+ startActivity(
+ Intent(Settings.ACTION_WIFI_SETTINGS)
+ .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
+ )
+ result.success(null)
+ } catch (e: Exception) {
+ result.error("UNAVAILABLE", e.message, null)
+ }
+ }
+
+ // ── WiFi scan list ────────────────────────────────────
+ "getWifiList" -> {
+ try {
+ val wm = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
+ if (!wm.isWifiEnabled) {
+ result.success(emptyList())
+ return@setMethodCallHandler
+ }
+ @Suppress("DEPRECATION")
+ wm.startScan()
+ @Suppress("DEPRECATION")
+ val list = wm.scanResults
+ .filter { it.SSID.isNotEmpty() }
+ .distinctBy { it.SSID }
+ .sortedByDescending { it.level }
+ .take(25)
+ .map {
+ mapOf(
+ "ssid" to it.SSID,
+ "level" to it.level,
+ "secured" to (it.capabilities.contains("WPA")
+ || it.capabilities.contains("WEP"))
+ )
+ }
+ result.success(list)
+ } catch (e: Exception) {
+ result.error("ERROR", e.message, null)
+ }
+ }
+
+ // ── WiFi connect ──────────────────────────────────────
+ "connectToWifi" -> {
+ try {
+ val ssid = call.argument("ssid") ?: ""
+ val password = call.argument("password") ?: ""
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ val wm = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
+ val builder = WifiNetworkSuggestion.Builder().setSsid(ssid)
+ if (password.isNotEmpty()) builder.setWpa2Passphrase(password)
+ wm.removeNetworkSuggestions(wm.networkSuggestions)
+ val status = wm.addNetworkSuggestions(listOf(builder.build()))
+ result.success(status == WifiManager.STATUS_NETWORK_SUGGESTIONS_SUCCESS)
+ } else {
+ @Suppress("DEPRECATION")
+ val wm = applicationContext.getSystemService(WIFI_SERVICE) as WifiManager
+ @Suppress("DEPRECATION")
+ val conf = android.net.wifi.WifiConfiguration().apply {
+ SSID = "\"$ssid\""
+ if (password.isNotEmpty()) preSharedKey = "\"$password\""
+ else allowedKeyManagement.set(
+ android.net.wifi.WifiConfiguration.KeyMgmt.NONE
+ )
+ }
+ @Suppress("DEPRECATION")
+ val netId = wm.addNetwork(conf)
+ @Suppress("DEPRECATION")
+ wm.disconnect()
+ @Suppress("DEPRECATION")
+ wm.enableNetwork(netId, true)
+ @Suppress("DEPRECATION")
+ wm.reconnect()
+ result.success(true)
+ }
+ } catch (e: Exception) {
+ result.error("ERROR", e.message, null)
+ }
+ }
+
+ else -> result.notImplemented()
+ }
+ }
+ }
+ private fun setMobileDataState(enabled: Boolean) {
+ try {
+ val connectivityManagerClass = Class.forName(connectivityManager.javaClass.name)
+ val method = connectivityManagerClass.getDeclaredMethod("setMobileDataEnabled", Boolean::class.javaPrimitiveType)
+ method.isAccessible = true
+ method.invoke(connectivityManager, enabled)
+ } catch (e: Exception) {
+ e.printStackTrace()
+ }
+ }
+
+ private fun isMobileDataEnabled(): Boolean {
+ return try {
+ val connectivityManagerClass = Class.forName(connectivityManager.javaClass.name)
+ val method = connectivityManagerClass.getDeclaredMethod("getMobileDataEnabled")
+ method.isAccessible = true
+ method.invoke(connectivityManager) as Boolean
+ } catch (e: Exception) {
+ false
+ }
+ }
+}
diff --git a/Front/android/app/src/main/res/values/strings.xml b/Front/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..0c7c6b0
--- /dev/null
+++ b/Front/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,3 @@
+
+ خدمات دسترسی برای کنترل دکمههای ساعت
+
\ No newline at end of file
diff --git a/Front/android/app/src/main/res/xml/accessibility_service_config.xml b/Front/android/app/src/main/res/xml/accessibility_service_config.xml
new file mode 100644
index 0000000..9413582
--- /dev/null
+++ b/Front/android/app/src/main/res/xml/accessibility_service_config.xml
@@ -0,0 +1,8 @@
+
+
\ No newline at end of file
diff --git a/Front/lib/channel_list/channel_list_screen.dart b/Front/lib/channel_list/channel_list_screen.dart
new file mode 100644
index 0000000..1dc93b9
--- /dev/null
+++ b/Front/lib/channel_list/channel_list_screen.dart
@@ -0,0 +1,186 @@
+import 'package:flutter/material.dart';
+import '../models/channel.dart';
+import '../services/api_service.dart';
+import '../services/auth_service.dart';
+import 'widgets/channel_circle_item.dart';
+import '../screens/channel_screen.dart';
+
+class ChannelListScreen extends StatefulWidget {
+ const ChannelListScreen({super.key});
+
+ @override
+ State createState() => _ChannelListScreenState();
+}
+
+class _ChannelListScreenState extends State {
+ final _authService = AuthService();
+ late final ApiService _api;
+ List _channels = [];
+ bool _loading = true;
+ String? _currentUserId;
+ PageController? _pageCtrl;
+ int _currentPage = 0;
+ bool _initialized = false;
+
+ @override
+ void initState() {
+ super.initState();
+ _api = ApiService(_authService);
+ _init();
+ }
+
+ Future _init() async {
+ _currentUserId = await _authService.getUserId();
+ await _loadChannels();
+ }
+
+ Future _loadChannels() async {
+ setState(() => _loading = true);
+ final channels = await _api.getChannels();
+ if (!mounted) return;
+ setState(() {
+ _channels = channels;
+ _loading = false;
+ if (!_initialized) {
+ // صفحه ۰ میشود خانه، صفحه ۱ اولین کانال
+ final startPage = channels.isNotEmpty ? 1 : 0;
+ _pageCtrl = PageController(initialPage: startPage);
+ _currentPage = startPage;
+ _initialized = true;
+ }
+ });
+ }
+
+ void _goHome() {
+ Navigator.pop(context); // بازگشت به صفحه HomeScreen
+ }
+
+ void _enterChannel(Channel ch) {
+ Navigator.push(
+ context,
+ MaterialPageRoute(
+ builder: (_) =>
+ ChannelScreen(channel: ch, currentUserId: _currentUserId),
+ ),
+ );
+ }
+
+ @override
+ void dispose() {
+ _pageCtrl?.dispose();
+ super.dispose();
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ if (_loading || _pageCtrl == null) {
+ return const Scaffold(
+ backgroundColor: Colors.black,
+ body: Center(
+ child: CircularProgressIndicator(
+ color: Color(0xFF00C853),
+ strokeWidth: 2,
+ ),
+ ),
+ );
+ }
+
+ final totalPages = _channels.length + 1; // +1 برای صفحه خانه
+
+ return Scaffold(
+ backgroundColor: Colors.black,
+ body: SafeArea(
+ child: Column(
+ children: [
+ Expanded(
+ child: PageView.builder(
+ controller: _pageCtrl!,
+ scrollDirection: Axis.vertical,
+ onPageChanged: (i) => setState(() => _currentPage = i),
+ itemCount: totalPages,
+ itemBuilder: (ctx, i) {
+ if (i == 0) return _buildHomeOption();
+ return _buildChannelPage(_channels[i - 1]);
+ },
+ ),
+ ),
+ _buildPageIndicator(totalPages),
+ ],
+ ),
+ ),
+ );
+ }
+
+ Widget _buildPageIndicator(int totalPages) {
+ return Padding(
+ padding: const EdgeInsets.only(bottom: 6, top: 2),
+ child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: List.generate(totalPages, (i) {
+ final isActive = i == _currentPage;
+ return Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 2),
+ child: Container(
+ width: isActive ? 6 : 4,
+ height: isActive ? 6 : 4,
+ decoration: BoxDecoration(
+ color: isActive ? const Color(0xFF00C853) : Colors.white24,
+ shape: BoxShape.circle,
+ ),
+ ),
+ );
+ }),
+ ),
+ );
+ }
+
+ // صفحه اول: دکمه خانه
+ Widget _buildHomeOption() {
+ return Center(
+ child: GestureDetector(
+ onTap: _goHome,
+ child: Container(
+ width: 180, // کمی بزرگتر از کانالها
+ height: 180,
+ decoration: BoxDecoration(
+ color: const Color(0xFF1C1C1E),
+ shape: BoxShape.circle,
+ border: Border.all(color: Colors.white24, width: 2),
+ boxShadow: [
+ BoxShadow(
+ color: Colors.white.withOpacity(0.05),
+ blurRadius: 20,
+ spreadRadius: 2,
+ ),
+ ],
+ ),
+ child: const Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(Icons.home, color: Colors.white70, size: 40),
+ SizedBox(height: 10),
+ Text(
+ 'خانه',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 16,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+
+ // صفحات کانال
+ Widget _buildChannelPage(Channel channel) {
+ return Center(
+ child: ChannelCircleItem(
+ channel: channel,
+ onTap: () => _enterChannel(channel),
+ ),
+ );
+ }
+}
diff --git a/Front/lib/channel_list/widgets/channel_circle_item.dart b/Front/lib/channel_list/widgets/channel_circle_item.dart
new file mode 100644
index 0000000..ba32535
--- /dev/null
+++ b/Front/lib/channel_list/widgets/channel_circle_item.dart
@@ -0,0 +1,74 @@
+import 'package:flutter/material.dart';
+import '../../models/channel.dart';
+
+class ChannelCircleItem extends StatelessWidget {
+ final Channel channel;
+ final VoidCallback onTap;
+
+ const ChannelCircleItem({
+ super.key,
+ required this.channel,
+ required this.onTap,
+ });
+
+ @override
+ Widget build(BuildContext context) {
+ return GestureDetector(
+ onTap: onTap,
+ child: LayoutBuilder(
+ builder: (ctx, constraints) {
+ final size = constraints.maxWidth * 0.72;
+ return Container(
+ width: size,
+ height: size,
+ decoration: BoxDecoration(
+ color: const Color(0xFF1C1C1E),
+ shape: BoxShape.circle,
+ border: Border.all(
+ color: const Color(0xFF00C853).withOpacity(0.55),
+ width: 2,
+ ),
+ boxShadow: [
+ BoxShadow(
+ color: const Color(0xFF00C853).withOpacity(0.12),
+ blurRadius: 22,
+ spreadRadius: 2,
+ ),
+ ],
+ ),
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ channel.type == 'PUBLIC' ? Icons.radio : Icons.lock_outline,
+ color: const Color(0xFF00C853),
+ size: 22,
+ ),
+ const SizedBox(height: 8),
+ Padding(
+ padding: const EdgeInsets.symmetric(horizontal: 20),
+ child: Text(
+ channel.name,
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 13,
+ fontWeight: FontWeight.bold,
+ ),
+ textAlign: TextAlign.center,
+ maxLines: 2,
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ const SizedBox(height: 4),
+ Text(
+ channel.type == 'PUBLIC' ? 'عمومی' : 'خصوصی',
+ style: const TextStyle(color: Colors.white38, fontSize: 9),
+ ),
+ ],
+ ),
+ );
+ },
+ ),
+ );
+ }
+}
diff --git a/Front/lib/home/home_screen.dart b/Front/lib/home/home_screen.dart
new file mode 100644
index 0000000..f887bdc
--- /dev/null
+++ b/Front/lib/home/home_screen.dart
@@ -0,0 +1,19 @@
+import 'package:flutter/material.dart';
+import 'widgets/watch_launcher.dart';
+
+class HomeScreen extends StatefulWidget {
+ const HomeScreen({super.key});
+
+ @override
+ State createState() => _HomeScreenState();
+}
+
+class _HomeScreenState extends State {
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Colors.black,
+ body: SafeArea(child: const WatchLauncher()),
+ );
+ }
+}
diff --git a/Front/lib/home/widgets/create_group_page.dart b/Front/lib/home/widgets/create_group_page.dart
new file mode 100644
index 0000000..f46b577
--- /dev/null
+++ b/Front/lib/home/widgets/create_group_page.dart
@@ -0,0 +1,155 @@
+import 'package:flutter/material.dart';
+
+class CreateGroupPage extends StatefulWidget {
+ const CreateGroupPage({super.key});
+
+ @override
+ State createState() => _CreateGroupPageState();
+}
+
+class _CreateGroupPageState extends State {
+ final _controller = TextEditingController();
+
+ @override
+ void dispose() {
+ _controller.dispose();
+ super.dispose();
+ }
+
+ void _confirm() {
+ final name = _controller.text.trim();
+ if (name.isNotEmpty) {
+ Navigator.of(context).pop(name);
+ } else {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(
+ content: Text('نام گروه نمیتواند خالی باشد'),
+ duration: Duration(seconds: 1),
+ ),
+ );
+ }
+ }
+
+ void _cancel() {
+ Navigator.of(context).pop(null);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: Colors.black,
+ body: SafeArea(
+ // استفاده از SingleChildScrollView برای حل مشکل اسکرول و دیده نشدن دکمهها
+ child: SingleChildScrollView(
+ physics: const BouncingScrollPhysics(),
+ padding: const EdgeInsets.all(12.0), // کاهش پدینگ برای فضای بیشتر
+ child: Column(
+ // حذف MainAxisAlignment.center برای جلوگیری از بیرون زدن محتوا
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ const SizedBox(height: 20), // فاصله از بالا
+ // آیکون گروه (کمی کوچکتر شده)
+ Container(
+ width: 60,
+ height: 60,
+ decoration: BoxDecoration(
+ color: const Color(0xFF00C853).withOpacity(0.15),
+ shape: BoxShape.circle,
+ border: Border.all(
+ color: const Color(0xFF00C853).withOpacity(0.5),
+ width: 2,
+ ),
+ ),
+ child: const Icon(
+ Icons.group_add,
+ color: Color(0xFF00C853),
+ size: 30, // کاهش سایز آیکون
+ ),
+ ),
+ const SizedBox(height: 16),
+
+ // عنوان
+ const Text(
+ 'گروه جدید',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 16, // کاهش سایز فونت
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ const SizedBox(height: 20),
+
+ // ورودی متن
+ TextField(
+ controller: _controller,
+ autofocus: true,
+ textAlign: TextAlign.center,
+ textCapitalization: TextCapitalization.sentences,
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 14, // کاهش سایز فونت متن
+ letterSpacing: 0.5,
+ ),
+ decoration: InputDecoration(
+ hintText: 'نام گروه را وارد کنید',
+ hintStyle: const TextStyle(
+ color: Colors.white38,
+ fontSize: 12,
+ ),
+ filled: true,
+ fillColor: Colors.white.withOpacity(0.08),
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(12),
+ borderSide: BorderSide.none,
+ ),
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 12, // کاهش ارتفاع فیلد
+ ),
+ ),
+ onSubmitted: (_) => _confirm(),
+ ),
+ const SizedBox(height: 24),
+
+ // دکمه تایید
+ SizedBox(
+ width: double.infinity,
+ height: 40, // ارتفاع ثابت برای دکمه
+ child: ElevatedButton(
+ onPressed: _confirm,
+ style: ElevatedButton.styleFrom(
+ backgroundColor: const Color(0xFF00C853),
+ foregroundColor: Colors.black,
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(20),
+ ),
+ elevation: 0,
+ ),
+ child: const Text(
+ 'ساخت گروه',
+ style: TextStyle(fontSize: 13, fontWeight: FontWeight.bold),
+ ),
+ ),
+ ),
+ const SizedBox(height: 8),
+
+ // دکمه لغو
+ SizedBox(
+ width: double.infinity,
+ height: 40, // ارتفاع ثابت برای دکمه
+ child: TextButton(
+ onPressed: _cancel,
+ style: TextButton.styleFrom(foregroundColor: Colors.white54),
+ child: const Text('لغو', style: TextStyle(fontSize: 13)),
+ ),
+ ),
+
+ // فضای خالی در پایین برای اسکرول راحتتر
+ const SizedBox(height: 20),
+ ],
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/Front/lib/home/widgets/watch_launcher.dart b/Front/lib/home/widgets/watch_launcher.dart
new file mode 100644
index 0000000..96a5b9e
--- /dev/null
+++ b/Front/lib/home/widgets/watch_launcher.dart
@@ -0,0 +1,443 @@
+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 createState() => _WatchLauncherState();
+}
+
+class _WatchLauncherState extends State {
+ // کنترلر اسکرول
+ 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 _items;
+
+ // متغیرهای برای ساعت و تاریخ داینامیک
+ String _currentTime = '';
+ String _currentDate = '';
+ late StreamSubscription _timeSubscription;
+
+ @override
+ void initState() {
+ super.initState();
+ _items = _generateItems();
+
+ // استفاده از Stream برای بهینهسازی ساعت
+ _timeSubscription =
+ Stream.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 _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 _openDialer() async {
+ try {
+ await _nativeChannel.invokeMethod('openDialer');
+ } catch (_) {
+ _showSnack('خطا در باز کردن شمارهگیر');
+ }
+ }
+
+ Future _openInternetSettings() async {
+ try {
+ await _nativeChannel.invokeMethod('openInternetSettings');
+ } catch (_) {
+ _showSnack('تنظیمات اینترنت در دسترس نیست');
+ }
+ }
+
+ Future _openBluetoothSettings() async {
+ try {
+ await _nativeChannel.invokeMethod('openBluetoothSettings');
+ } catch (_) {
+ _showSnack('خطا در باز کردن بلوتوث');
+ }
+ }
+
+ Future _logout() async {
+ try {
+ if (!mounted) return;
+ Navigator.pushReplacementNamed(context, '/login');
+ } catch (e) {
+ _showSnack('خطا در خروج');
+ }
+ }
+
+ Future _showCreateGroupDialog() async {
+ final groupName = await Navigator.push(
+ 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(
+ 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,
+ ),
+ ),
+ ],
+ ),
+ );
+ }
+}
diff --git a/Front/lib/main.dart b/Front/lib/main.dart
index d9b0752..b047f74 100644
--- a/Front/lib/main.dart
+++ b/Front/lib/main.dart
@@ -1,7 +1,9 @@
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.dart';
-import 'screens/home_screen.dart';
+import 'home/home_screen.dart';
+import 'channel_list/channel_list_screen.dart';
import 'screens/login_screen.dart';
import 'services/auth_service.dart';
@@ -29,7 +31,23 @@ class WalkieTalkieApp extends StatelessWidget {
scaffoldBackgroundColor: Colors.black,
useMaterial3: true,
),
- home: const _Splash(),
+ // استفاده از onGenerateRoute برای مدیریت مسیرها
+ onGenerateRoute: (settings) {
+ switch (settings.name) {
+ case '/':
+ return MaterialPageRoute(builder: (_) => const _Splash());
+ case '/home':
+ return MaterialPageRoute(builder: (_) => const HomeScreen());
+ case '/channel_list':
+ return MaterialPageRoute(builder: (_) => const ChannelListScreen());
+ case '/login':
+ return MaterialPageRoute(builder: (_) => const LoginScreen());
+ default:
+ return MaterialPageRoute(builder: (_) => const _Splash());
+ }
+ },
+ // صفحه پیشفرض برنامه (اسپلش)
+ initialRoute: '/',
);
}
}
@@ -44,17 +62,40 @@ class _Splash extends StatefulWidget {
class _SplashState extends State<_Splash> {
bool _loading = false;
+ // متغیرهای مربوط به باتری
+ static const _nativeChannel = MethodChannel('com.example.watch/launcher');
+ int _batteryLevel = 0;
+ bool _isCharging = false;
+
+ @override
+ void initState() {
+ super.initState();
+ _loadBattery();
+ }
+
+ Future _loadBattery() async {
+ try {
+ final info = await _nativeChannel.invokeMapMethod(
+ 'getBatteryInfo',
+ );
+ if (!mounted || info == null) return;
+ setState(() {
+ _batteryLevel = (info['level'] as int?) ?? 0;
+ _isCharging = (info['isCharging'] as bool?) ?? false;
+ });
+ } catch (_) {}
+ }
+
Future _onTap() async {
if (_loading) return;
setState(() => _loading = true);
+
final loggedIn = await AuthService().isLoggedIn();
+
if (!mounted) return;
- Navigator.pushReplacement(
- context,
- MaterialPageRoute(
- builder: (_) => loggedIn ? const HomeScreen() : const LoginScreen(),
- ),
- );
+
+ // هدایت کاربر بر اساس وضعیت لاگین
+ Navigator.pushReplacementNamed(context, loggedIn ? '/home' : '/login');
}
@override
@@ -65,7 +106,7 @@ class _SplashState extends State<_Splash> {
backgroundColor: Colors.black,
body: Center(
child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 24),
+ padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
@@ -76,7 +117,7 @@ class _SplashState extends State<_Splash> {
color: Colors.white,
shape: BoxShape.circle,
),
- padding: const EdgeInsets.all(10),
+ padding: const EdgeInsets.all(1),
child: Image.asset(
'assets/images/logo.png',
fit: BoxFit.contain,
@@ -86,8 +127,8 @@ class _SplashState extends State<_Splash> {
const Text(
'مرکز هوش مصنوعی',
style: TextStyle(
- color: Colors.white,
- fontSize: 12,
+ color: Colors.green,
+ fontSize: 15,
fontWeight: FontWeight.bold,
letterSpacing: 0.4,
),
@@ -95,10 +136,19 @@ class _SplashState extends State<_Splash> {
),
const SizedBox(height: 3),
const Text(
- 'و فناوریهای نو ظهور سپاه',
+ 'و فناوریهای نو ظهور',
style: TextStyle(
- color: Color(0xFF00C853),
- fontSize: 10,
+ color: Colors.white,
+ fontSize: 14,
+ letterSpacing: 0.3,
+ ),
+ textAlign: TextAlign.center,
+ ),
+ const Text(
+ 'سپاه ثارلله استان کرمان',
+ style: TextStyle(
+ color: Colors.red,
+ fontSize: 14,
letterSpacing: 0.3,
),
textAlign: TextAlign.center,
@@ -114,9 +164,31 @@ class _SplashState extends State<_Splash> {
),
)
else
- const Text(
- 'ضربه بزنید',
- style: TextStyle(color: Colors.white24, fontSize: 9),
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ _isCharging ? Icons.bolt : Icons.battery_std,
+ color: _isCharging
+ ? const Color(0xFF00C853)
+ : (_batteryLevel <= 15
+ ? const Color(0xFFFF1744)
+ : Colors.white24),
+ size: 10,
+ ),
+ const SizedBox(width: 4),
+ Text(
+ '$_batteryLevel%',
+ style: TextStyle(
+ color: _isCharging
+ ? const Color(0xFF00C853)
+ : (_batteryLevel <= 15
+ ? const Color(0xFFFF1744)
+ : Colors.white24),
+ fontSize: 9,
+ ),
+ ),
+ ],
),
],
),
diff --git a/Front/lib/screens/about.dart b/Front/lib/screens/about.dart
new file mode 100644
index 0000000..95fb06d
--- /dev/null
+++ b/Front/lib/screens/about.dart
@@ -0,0 +1,216 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+class AboutScreen extends StatefulWidget {
+ const AboutScreen({super.key});
+
+ @override
+ State createState() => _AboutScreenState();
+}
+
+class _AboutScreenState extends State {
+ static const _nativeChannel = MethodChannel('com.example.watch/launcher');
+
+ // --- متغیرهای مربوط به بخش توضیحات (برای تنظیمات) ---
+ int _descTapCount = 0;
+ DateTime? _descLastTapTime;
+ bool _descIsHolding = false;
+
+ // --- متغیرهای مربوط به بخش نسخه (برای EngineerMode) ---
+ int _verTapCount = 0;
+ DateTime? _verLastTapTime;
+ bool _verIsHolding = false;
+
+ // تابع باز کردن تنظیمات ساعت
+ Future _openWatchSettings() async {
+ try {
+ await _nativeChannel.invokeMethod('openWatchSettings');
+ } catch (e) {
+ if (mounted) {
+ ScaffoldMessenger.of(context).showSnackBar(
+ const SnackBar(content: Text('خطا در باز کردن تنظیمات')),
+ );
+ }
+ }
+ }
+
+ // تابع باز کردن EngineerMode
+ Future _openEngineerMode() async {
+ try {
+ await _nativeChannel.invokeMethod('openEngineerMode');
+ } catch (e) {
+ if (mounted) {
+ ScaffoldMessenger.of(
+ context,
+ ).showSnackBar(const SnackBar(content: Text('خطا در باز کردن مهندسی')));
+ }
+ }
+ }
+
+ // --- هندلرهای بخش توضیحات ---
+ void _handleDescTap() {
+ if (_descIsHolding) return;
+ final now = DateTime.now();
+ if (_descLastTapTime != null &&
+ now.difference(_descLastTapTime!) > const Duration(seconds: 1)) {
+ _descTapCount = 0;
+ }
+ _descTapCount++;
+ _descLastTapTime = now;
+ if (_descTapCount == 5) HapticFeedback.lightImpact();
+ }
+
+ void _handleDescLongPressStart(LongPressStartDetails details) {
+ setState(() => _descIsHolding = true);
+ if (_descTapCount >= 5) {
+ HapticFeedback.heavyImpact();
+ _openWatchSettings();
+ } else {
+ _descTapCount = 0;
+ }
+ }
+
+ void _handleDescLongPressEnd(LongPressEndDetails details) {
+ setState(() => _descIsHolding = false);
+ }
+
+ // --- هندلرهای بخش نسخه ---
+ void _handleVerTap() {
+ if (_verIsHolding) return;
+ final now = DateTime.now();
+ if (_verLastTapTime != null &&
+ now.difference(_verLastTapTime!) > const Duration(seconds: 1)) {
+ _verTapCount = 0;
+ }
+ _verTapCount++;
+ _verLastTapTime = now;
+ if (_verTapCount == 5) HapticFeedback.lightImpact();
+ }
+
+ void _handleVerLongPressStart(LongPressStartDetails details) {
+ setState(() => _verIsHolding = true);
+ if (_verTapCount >= 5) {
+ HapticFeedback.heavyImpact();
+ _openEngineerMode();
+ } else {
+ _verTapCount = 0;
+ }
+ }
+
+ void _handleVerLongPressEnd(LongPressEndDetails details) {
+ setState(() => _verIsHolding = false);
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ backgroundColor: const Color(0xFF000000),
+ body: SafeArea(
+ child: Center(
+ child: SingleChildScrollView(
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ // --- لوگو ---
+ Container(
+ width: 100,
+ height: 100,
+ decoration: const BoxDecoration(
+ color: Colors.white,
+ shape: BoxShape.circle,
+ ),
+ child: Padding(
+ padding: const EdgeInsets.all(15.0),
+ child: Image.asset(
+ 'assets/images/logo.png',
+ errorBuilder: (context, error, stackTrace) {
+ return const Icon(
+ Icons.error_outline,
+ color: Colors.grey,
+ size: 50,
+ );
+ },
+ ),
+ ),
+ ),
+ const SizedBox(height: 30),
+
+ // --- بخش توضیحات سازنده (قابل کلیک برای تنظیمات) ---
+ GestureDetector(
+ onTap: _handleDescTap,
+ onLongPressStart: _handleDescLongPressStart,
+ onLongPressEnd: _handleDescLongPressEnd,
+ behavior: HitTestBehavior.opaque,
+ child: Container(
+ margin: const EdgeInsets.symmetric(horizontal: 20),
+ padding: const EdgeInsets.all(15),
+ decoration: BoxDecoration(
+ color: const Color(0xFF1C1C1E),
+ borderRadius: BorderRadius.circular(15),
+ border: Border.all(color: Colors.white.withOpacity(0.1)),
+ ),
+ child: const Text(
+ 'توسعه یافته توسط تیم هوش مصنوعی و فناوری های نو ظهور سپاه ثارلله استان کرمان',
+ textAlign: TextAlign.center,
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 14,
+ height: 1.5,
+ ),
+ ),
+ ),
+ ),
+ const SizedBox(height: 20),
+
+ // --- بخش نسخه (قابل کلیک برای EngineerMode) ---
+ GestureDetector(
+ onTap: _handleVerTap,
+ onLongPressStart: _handleVerLongPressStart,
+ onLongPressEnd: _handleVerLongPressEnd,
+ behavior: HitTestBehavior.opaque,
+ child: Container(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 20,
+ vertical: 10,
+ ),
+ decoration: BoxDecoration(
+ color: Colors.white.withOpacity(0.05),
+ borderRadius: BorderRadius.circular(20),
+ border: Border.all(color: Colors.white.withOpacity(0.1)),
+ ),
+ child: Row(
+ mainAxisSize: MainAxisSize.min,
+ children: const [
+ Icon(
+ Icons.info_outline,
+ color: Colors.blueAccent,
+ size: 18,
+ ),
+ SizedBox(width: 8),
+ Text(
+ 'نسخه ۱.۰.۰',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 16,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
+ ],
+ ),
+ ),
+ ),
+
+ const SizedBox(height: 40),
+
+ IconButton(
+ onPressed: () => Navigator.pop(context),
+ icon: const Icon(Icons.arrow_back_ios, color: Colors.white),
+ ),
+ ],
+ ),
+ ),
+ ),
+ ),
+ );
+ }
+}
diff --git a/Front/lib/screens/channel_list_screen.dart b/Front/lib/screens/channel_list_screen.dart
deleted file mode 100644
index 9114b8f..0000000
--- a/Front/lib/screens/channel_list_screen.dart
+++ /dev/null
@@ -1,423 +0,0 @@
-import 'package:flutter/material.dart';
-import '../models/channel.dart';
-import '../services/api_service.dart';
-import '../services/auth_service.dart';
-import 'channel_screen.dart';
-import 'login_screen.dart';
-import 'notifications_screen.dart';
-
-class ChannelListScreen extends StatefulWidget {
- const ChannelListScreen({super.key});
-
- @override
- State createState() => _ChannelListScreenState();
-}
-
-class _ChannelListScreenState extends State {
- final _authService = AuthService();
- late final ApiService _api;
-
- List _channels = [];
- bool _loading = true;
- String? _error;
- int _pendingNotifCount = 0;
- String? _currentUserId;
-
- @override
- void initState() {
- super.initState();
- _api = ApiService(_authService);
- _init();
- }
-
- Future _init() async {
- _currentUserId = await _authService.getUserId();
- await _loadChannels();
- await _loadNotifCount();
- }
-
- Future _loadChannels() async {
- setState(() {
- _loading = true;
- _error = null;
- });
- final channels = await _api.getChannels();
- if (!mounted) return;
- if (channels.isEmpty && _channels.isEmpty) {
- setState(() {
- _loading = false;
- _error = 'کانالی یافت نشد';
- });
- } else {
- setState(() {
- _channels = channels;
- _loading = false;
- });
- }
- }
-
- Future _loadNotifCount() async {
- final notifs = await _api.getNotifications();
- if (!mounted) return;
- setState(() {
- _pendingNotifCount = notifs.where((n) => n.isPending).length;
- });
- }
-
- Future _refresh() async {
- await _loadChannels();
- await _loadNotifCount();
- }
-
- Future _logout() async {
- await _authService.logout();
- if (!mounted) return;
- Navigator.pushReplacement(
- context,
- MaterialPageRoute(builder: (_) => const LoginScreen()),
- );
- }
-
- void _enterChannel(Channel ch) {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (_) => ChannelScreen(channel: ch, currentUserId: _currentUserId),
- ),
- );
- }
-
- void _openNotifications() async {
- await Navigator.push(
- context,
- MaterialPageRoute(builder: (_) => const NotificationsScreen()),
- );
- // refresh notif count + reload channels after returning (user may have accepted invite)
- _refresh();
- }
-
- Future _showCreateGroupDialog() async {
- String groupName = '';
- final confirmed = await showDialog(
- context: context,
- builder: (ctx) => _CreateGroupDialog(
- onNameChanged: (v) => groupName = v,
- ),
- );
-
- if (confirmed == true && groupName.trim().isNotEmpty) {
- final newChannel = await _api.createGroup(groupName.trim());
- if (!mounted) return;
- if (newChannel != null) {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: const Text('گروه ساخته شد', style: TextStyle(fontSize: 11), textAlign: TextAlign.center),
- backgroundColor: const Color(0xFF1C1C1E),
- behavior: SnackBarBehavior.floating,
- duration: const Duration(seconds: 2),
- margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
- ),
- );
- _loadChannels();
- } else {
- ScaffoldMessenger.of(context).showSnackBar(
- SnackBar(
- content: const Text('خطا در ساخت گروه', style: TextStyle(fontSize: 11), textAlign: TextAlign.center),
- backgroundColor: const Color(0xFF333333),
- behavior: SnackBarBehavior.floating,
- duration: const Duration(seconds: 2),
- margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
- ),
- );
- }
- }
- }
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: Colors.black,
- body: SafeArea(
- child: Column(
- children: [
- // Header – centered for watch face
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
- child: Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Icon(Icons.settings_input_antenna,
- color: Color(0xFF00C853), size: 14),
- const SizedBox(width: 6),
- const Text(
- 'کانالها',
- style: TextStyle(
- color: Colors.white,
- fontSize: 13,
- fontWeight: FontWeight.bold),
- ),
- const SizedBox(width: 10),
- // Menu button – uses child: to avoid M3 size inflation
- Stack(
- clipBehavior: Clip.none,
- children: [
- PopupMenuButton(
- padding: EdgeInsets.zero,
- child: const Icon(Icons.menu,
- color: Colors.white, size: 22),
- onSelected: (value) {
- switch (value) {
- case 'notifications':
- _openNotifications();
- case 'create':
- _showCreateGroupDialog();
- case 'refresh':
- if (!_loading) _refresh();
- case 'logout':
- _logout();
- }
- },
- itemBuilder: (context) => [
- PopupMenuItem(
- value: 'notifications',
- child: Row(
- children: [
- const Icon(Icons.notifications_outlined,
- size: 16),
- const SizedBox(width: 8),
- Text(_pendingNotifCount > 0
- ? 'اعلانها ($_pendingNotifCount)'
- : 'اعلانها'),
- ],
- ),
- ),
- const PopupMenuItem(
- value: 'create',
- child: Row(
- children: [
- Icon(Icons.add_circle_outline,
- size: 16, color: Color(0xFF00C853)),
- SizedBox(width: 8),
- Text('گروه جدید'),
- ],
- ),
- ),
- PopupMenuItem(
- value: 'refresh',
- enabled: !_loading,
- child: const Row(
- children: [
- Icon(Icons.refresh, size: 16),
- SizedBox(width: 8),
- Text('بروزرسانی'),
- ],
- ),
- ),
- const PopupMenuItem(
- value: 'logout',
- child: Row(
- children: [
- Icon(Icons.logout,
- size: 16, color: Colors.redAccent),
- SizedBox(width: 8),
- Text('خروج',
- style:
- TextStyle(color: Colors.redAccent)),
- ],
- ),
- ),
- ],
- ),
- if (_pendingNotifCount > 0)
- Positioned(
- top: -2,
- right: -2,
- child: Container(
- width: 7,
- height: 7,
- decoration: const BoxDecoration(
- color: Color(0xFFFF1744),
- shape: BoxShape.circle,
- ),
- ),
- ),
- ],
- ),
- ],
- ),
- ),
-
- // Content
- Expanded(
- child: _loading
- ? const Center(
- child: CircularProgressIndicator(
- color: Color(0xFF00C853), strokeWidth: 2),
- )
- : _error != null && _channels.isEmpty
- ? Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Icon(Icons.wifi_off,
- color: Colors.white38, size: 24),
- const SizedBox(height: 6),
- Text(_error!,
- style: const TextStyle(
- color: Colors.white38, fontSize: 11)),
- const SizedBox(height: 8),
- TextButton(
- onPressed: _refresh,
- child: const Text('تلاش مجدد',
- style: TextStyle(
- color: Color(0xFF00C853),
- fontSize: 11)),
- ),
- ],
- ),
- )
- : ListView.builder(
- padding: const EdgeInsets.only(bottom: 4),
- itemCount: _channels.length,
- itemBuilder: (ctx, i) {
- return _ChannelTile(
- channel: _channels[i],
- onTap: () => _enterChannel(_channels[i]),
- );
- },
- ),
- ),
- ],
- ),
- ),
- );
- }
-}
-
-class _ChannelTile extends StatelessWidget {
- final Channel channel;
- final VoidCallback onTap;
-
- const _ChannelTile({required this.channel, required this.onTap});
-
- @override
- Widget build(BuildContext context) {
- return GestureDetector(
- onTap: onTap,
- child: Container(
- margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
- height: 44,
- decoration: BoxDecoration(
- color: const Color(0xFF1C1C1E),
- borderRadius: BorderRadius.circular(14),
- ),
- padding: const EdgeInsets.symmetric(horizontal: 12),
- child: Row(
- children: [
- Icon(
- channel.type == 'PUBLIC' ? Icons.radio : Icons.lock_outline,
- color: const Color(0xFF00C853),
- size: 16,
- ),
- const SizedBox(width: 8),
- Expanded(
- child: Text(
- channel.name,
- style: const TextStyle(
- color: Colors.white,
- fontSize: 12,
- fontWeight: FontWeight.w500),
- overflow: TextOverflow.ellipsis,
- ),
- ),
- const SizedBox(width: 4),
- const Icon(Icons.chevron_right, color: Colors.white24, size: 16),
- ],
- ),
- ),
- );
- }
-}
-
-// ── Create Group Dialog ────────────────────────────────────────────────────
-
-class _CreateGroupDialog extends StatefulWidget {
- final ValueChanged onNameChanged;
-
- const _CreateGroupDialog({required this.onNameChanged});
-
- @override
- State<_CreateGroupDialog> createState() => _CreateGroupDialogState();
-}
-
-class _CreateGroupDialogState extends State<_CreateGroupDialog> {
- final _ctrl = TextEditingController();
-
- @override
- void dispose() {
- _ctrl.dispose();
- super.dispose();
- }
-
- @override
- Widget build(BuildContext context) {
- return AlertDialog(
- backgroundColor: const Color(0xFF1C1C1E),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
- contentPadding: const EdgeInsets.all(16),
- content: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- const Icon(Icons.add_circle_outline, color: Color(0xFF00C853), size: 26),
- const SizedBox(height: 8),
- const Text(
- 'گروه جدید',
- style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
- ),
- const SizedBox(height: 10),
- TextField(
- controller: _ctrl,
- autofocus: true,
- textAlign: TextAlign.center,
- style: const TextStyle(color: Colors.white, fontSize: 12),
- decoration: InputDecoration(
- hintText: 'نام گروه',
- hintStyle: const TextStyle(color: Colors.white38, fontSize: 11),
- filled: true,
- fillColor: Colors.white.withValues(alpha: 0.05),
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(10),
- borderSide: BorderSide.none,
- ),
- contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
- ),
- onChanged: widget.onNameChanged,
- ),
- const SizedBox(height: 12),
- Row(
- children: [
- Expanded(
- child: TextButton(
- onPressed: () => Navigator.pop(context, false),
- child: const Text('انصراف', style: TextStyle(color: Colors.white54, fontSize: 11)),
- ),
- ),
- Expanded(
- child: TextButton(
- onPressed: () => Navigator.pop(context, true),
- child: const Text('ساخت', style: TextStyle(
- color: Color(0xFF00C853),
- fontSize: 11,
- fontWeight: FontWeight.bold,
- )),
- ),
- ),
- ],
- ),
- ],
- ),
- );
- }
-}
diff --git a/Front/lib/screens/channel_list_screen_old.dart b/Front/lib/screens/channel_list_screen_old.dart
new file mode 100644
index 0000000..c300c71
--- /dev/null
+++ b/Front/lib/screens/channel_list_screen_old.dart
@@ -0,0 +1,594 @@
+// import 'package:flutter/material.dart';
+// import 'package:flutter/services.dart';
+// import '../models/channel.dart';
+// import '../services/api_service.dart';
+// import '../services/auth_service.dart';
+// import 'channel_screen.dart';
+// import 'login_screen.dart';
+// import 'notifications_screen.dart';
+// import 'wifi_screen.dart';
+
+// class ChannelListScreen extends StatefulWidget {
+// const ChannelListScreen({super.key});
+
+// @override
+// State createState() => _ChannelListScreenState();
+// }
+
+// class _ChannelListScreenState extends State {
+// static final _nativeChannel = const MethodChannel(
+// 'com.example.watch/launcher',
+// );
+
+// final _authService = AuthService();
+// late final ApiService _api;
+
+// List _channels = [];
+// bool _loading = true;
+// int _pendingNotifCount = 0;
+// String? _currentUserId;
+// PageController? _pageCtrl;
+// int _currentPage = 0;
+// bool _initialized = false;
+
+// // Battery
+// int _batteryLevel = 0;
+// bool _isCharging = false;
+
+// @override
+// void initState() {
+// super.initState();
+// _api = ApiService(_authService);
+// _init();
+// }
+
+// Future _init() async {
+// _currentUserId = await _authService.getUserId();
+// await Future.wait([_loadChannels(), _loadNotifCount(), _loadBattery()]);
+// }
+
+// Future _loadBattery() async {
+// try {
+// final info = await _nativeChannel.invokeMapMethod(
+// 'getBatteryInfo',
+// );
+// if (!mounted || info == null) return;
+// setState(() {
+// _batteryLevel = (info['level'] as int?) ?? 0;
+// _isCharging = (info['isCharging'] as bool?) ?? false;
+// });
+// } catch (_) {}
+// }
+
+// Future _loadChannels() async {
+// setState(() => _loading = true);
+// final channels = await _api.getChannels();
+// if (!mounted) return;
+// setState(() {
+// _channels = channels;
+// _loading = false;
+// if (!_initialized) {
+// final startPage = channels.isNotEmpty ? 1 : 0;
+// _pageCtrl = PageController(initialPage: startPage);
+// _currentPage = startPage;
+// _initialized = true;
+// }
+// });
+// }
+
+// Future _loadNotifCount() async {
+// final notifs = await _api.getNotifications();
+// if (!mounted) return;
+// setState(() {
+// _pendingNotifCount = notifs.where((n) => n.isPending).length;
+// });
+// }
+
+// Future _refresh() async {
+// await _loadChannels();
+// await _loadNotifCount();
+// }
+
+// Future _openInternetSettings() async {
+// try {
+// await _nativeChannel.invokeMethod('openInternetSettings');
+// } catch (_) {
+// if (!mounted) return;
+// _showSnack('تنظیمات اینترنت در دسترس نیست');
+// }
+// }
+
+// void _openWifi() {
+// Navigator.push(
+// context,
+// MaterialPageRoute(builder: (_) => const WifiScreen()),
+// );
+// }
+
+// Future _openAccessibilitySettings() async {
+// try {
+// await _nativeChannel.invokeMethod('openAccessibilitySettings');
+// } catch (e) {
+// _showSnack('خطا در باز کردن تنظیمات');
+// }
+// }
+
+// void _showSnack(String msg) {
+// ScaffoldMessenger.of(context).showSnackBar(
+// SnackBar(
+// content: Text(
+// msg,
+// style: const TextStyle(fontSize: 10, 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),
+// ),
+// );
+// }
+
+// Future _openHomeSettings() async {
+// try {
+// await _nativeChannel.invokeMethod('openHomeSettings');
+// } catch (_) {
+// if (!mounted) return;
+// ScaffoldMessenger.of(context).showSnackBar(
+// const SnackBar(
+// content: Text(
+// 'این قابلیت روی دستگاه شما پشتیبانی نمیشود',
+// style: TextStyle(fontSize: 10, color: Colors.white),
+// textAlign: TextAlign.center,
+// ),
+// backgroundColor: Color(0xFF2C2C2E),
+// behavior: SnackBarBehavior.floating,
+// duration: Duration(seconds: 2),
+// margin: EdgeInsets.symmetric(horizontal: 16, vertical: 40),
+// ),
+// );
+// }
+// }
+
+// Future _logout() async {
+// await _authService.logout();
+// if (!mounted) return;
+// Navigator.pushReplacement(
+// context,
+// MaterialPageRoute(builder: (_) => const LoginScreen()),
+// );
+// }
+
+// void _enterChannel(Channel ch) {
+// Navigator.push(
+// context,
+// MaterialPageRoute(
+// builder: (_) =>
+// ChannelScreen(channel: ch, currentUserId: _currentUserId),
+// ),
+// );
+// }
+
+// void _openNotifications() async {
+// await Navigator.push(
+// context,
+// MaterialPageRoute(builder: (_) => const NotificationsScreen()),
+// );
+// _refresh();
+// }
+
+// Future _showCreateGroupDialog() async {
+// String groupName = '';
+// final confirmed = await showDialog(
+// context: context,
+// builder: (ctx) => _CreateGroupDialog(onNameChanged: (v) => groupName = v),
+// );
+// if (confirmed == true && groupName.trim().isNotEmpty) {
+// 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),
+// ),
+// ),
+// );
+// if (newChannel != null) _loadChannels();
+// }
+// }
+
+// @override
+// void dispose() {
+// _pageCtrl?.dispose();
+// super.dispose();
+// }
+
+// @override
+// Widget build(BuildContext context) {
+// if (_loading || _pageCtrl == null) {
+// return const Scaffold(
+// backgroundColor: Colors.black,
+// body: Center(
+// child: CircularProgressIndicator(
+// color: Color(0xFF00C853),
+// strokeWidth: 2,
+// ),
+// ),
+// );
+// }
+
+// final totalPages = _channels.length + 1; // +1 for menu page
+
+// return Scaffold(
+// backgroundColor: Colors.black,
+// body: SafeArea(
+// child: Column(
+// children: [
+// Expanded(
+// child: PageView.builder(
+// controller: _pageCtrl!,
+// scrollDirection: Axis.vertical,
+// onPageChanged: (i) => setState(() => _currentPage = i),
+// itemCount: totalPages,
+// itemBuilder: (ctx, i) {
+// if (i == 0) return _buildMenuPage();
+// return _buildChannelPage(_channels[i - 1]);
+// },
+// ),
+// ),
+// _buildPageIndicator(totalPages),
+// ],
+// ),
+// ),
+// );
+// }
+
+// Widget _buildPageIndicator(int totalPages) {
+// return Padding(
+// padding: const EdgeInsets.only(bottom: 6, top: 2),
+// child: Row(
+// mainAxisAlignment: MainAxisAlignment.center,
+// children: List.generate(totalPages, (i) {
+// final isActive = i == _currentPage;
+// if (i == 0) {
+// return Padding(
+// padding: const EdgeInsets.symmetric(horizontal: 2),
+// child: Icon(
+// Icons.menu,
+// size: isActive ? 9 : 6,
+// color: isActive ? Colors.white70 : Colors.white24,
+// ),
+// );
+// }
+// return Padding(
+// padding: const EdgeInsets.symmetric(horizontal: 2),
+// child: Container(
+// width: isActive ? 6 : 4,
+// height: isActive ? 6 : 4,
+// decoration: BoxDecoration(
+// color: isActive ? const Color(0xFF00C853) : Colors.white24,
+// shape: BoxShape.circle,
+// ),
+// ),
+// );
+// }),
+// ),
+// );
+// }
+
+// Widget _buildMenuPage() {
+// final batteryColor = _isCharging
+// ? const Color(0xFF00C853)
+// : _batteryLevel <= 15
+// ? const Color(0xFFFF1744)
+// : _batteryLevel <= 30
+// ? const Color(0xFFFFAB00)
+// : Colors.white60;
+
+// // تغییر ListView به SingleChildScrollView و Column
+// return Column(
+// mainAxisSize: MainAxisSize.min, // ستون فقط به اندازه محتوا فضا میگیرد
+// children: [
+// // _// Battery display_
+// Container(
+// height: 32,
+// margin: const EdgeInsets.only(bottom: 6),
+// padding: const EdgeInsets.symmetric(horizontal: 12),
+// decoration: BoxDecoration(
+// color: batteryColor.withValues(alpha: 0.1),
+// borderRadius: BorderRadius.circular(12),
+// border: Border.all(
+// color: batteryColor.withValues(alpha: 0.3),
+// width: 1,
+// ),
+// ),
+// child: Row(
+// children: [
+// Icon(
+// _isCharging ? Icons.bolt : Icons.battery_std,
+// color: batteryColor,
+// size: 14,
+// ),
+// const SizedBox(width: 6),
+// Text(
+// _isCharging
+// ? 'در حال شارژ — $_batteryLevel%'
+// : 'باتری: $_batteryLevel%',
+// style: TextStyle(color: batteryColor, fontSize: 10),
+// ),
+// ],
+// ),
+// ),
+// const SizedBox(height: 6),
+// _MenuItem(
+// icon: Icons.notifications_outlined,
+// label: _pendingNotifCount > 0
+// ? 'اعلانها ($_pendingNotifCount)'
+// : 'اعلانها',
+// color: _pendingNotifCount > 0
+// ? const Color(0xFFFF1744)
+// : Colors.white70,
+// onTap: _openNotifications,
+// ),
+// const SizedBox(height: 6),
+// _MenuItem(
+// icon: Icons.wifi,
+// label: 'وایفای',
+// color: const Color(0xFF2979FF),
+// onTap: _openWifi,
+// ),
+// const SizedBox(height: 6),
+// _MenuItem(
+// icon: Icons.cell_tower,
+// label: 'اینترنت / سیمکارت',
+// color: const Color(0xFF00BCD4),
+// onTap: _openInternetSettings,
+// ),
+// const SizedBox(height: 6),
+// _MenuItem(
+// icon: Icons.accessibility_new,
+// label: 'تنظیمات دکمهها',
+// color: Colors.orangeAccent,
+// onTap: _openAccessibilitySettings,
+// ),
+// const SizedBox(height: 6),
+// _MenuItem(
+// icon: Icons.add_circle_outline,
+// label: 'گروه جدید',
+// color: const Color(0xFF00C853),
+// onTap: _showCreateGroupDialog,
+// ),
+// const SizedBox(height: 6),
+// _MenuItem(
+// icon: Icons.refresh,
+// label: 'بروزرسانی',
+// color: Colors.white70,
+// onTap: _loading ? null : _refresh,
+// ),
+// const SizedBox(height: 6),
+// _MenuItem(
+// icon: Icons.launch,
+// label: 'تغییر لانچر',
+// color: Colors.white54,
+// onTap: _openHomeSettings,
+// ),
+// const SizedBox(height: 6),
+// _MenuItem(
+// icon: Icons.logout,
+// label: 'خروج',
+// color: Colors.redAccent,
+// onTap: _logout,
+// ),
+// ],
+// );
+// }
+
+// Widget _buildChannelPage(Channel channel) {
+// return Center(
+// child: GestureDetector(
+// onTap: () => _enterChannel(channel),
+// child: LayoutBuilder(
+// builder: (ctx, constraints) {
+// final size = constraints.maxWidth * 0.72;
+// return Container(
+// width: size,
+// height: size,
+// decoration: BoxDecoration(
+// color: const Color(0xFF1C1C1E),
+// shape: BoxShape.circle,
+// border: Border.all(
+// color: const Color(0xFF00C853).withValues(alpha: 0.55),
+// width: 2,
+// ),
+// boxShadow: [
+// BoxShadow(
+// color: const Color(0xFF00C853).withValues(alpha: 0.12),
+// blurRadius: 22,
+// spreadRadius: 2,
+// ),
+// ],
+// ),
+// child: Column(
+// mainAxisAlignment: MainAxisAlignment.center,
+// children: [
+// Icon(
+// channel.type == 'PUBLIC' ? Icons.radio : Icons.lock_outline,
+// color: const Color(0xFF00C853),
+// size: 22,
+// ),
+// const SizedBox(height: 8),
+// Padding(
+// padding: const EdgeInsets.symmetric(horizontal: 20),
+// child: Text(
+// channel.name,
+// style: const TextStyle(
+// color: Colors.white,
+// fontSize: 13,
+// fontWeight: FontWeight.bold,
+// ),
+// textAlign: TextAlign.center,
+// maxLines: 2,
+// overflow: TextOverflow.ellipsis,
+// ),
+// ),
+// const SizedBox(height: 4),
+// Text(
+// channel.type == 'PUBLIC' ? 'عمومی' : 'خصوصی',
+// style: const TextStyle(color: Colors.white38, fontSize: 9),
+// ),
+// ],
+// ),
+// );
+// },
+// ),
+// ),
+// );
+// }
+// }
+
+// // ── Menu Item ──────────────────────────────────────────────────────────────
+
+// class _MenuItem extends StatelessWidget {
+// final IconData icon;
+// final String label;
+// final Color color;
+// final VoidCallback? onTap;
+
+// const _MenuItem({
+// required this.icon,
+// required this.label,
+// required this.color,
+// this.onTap,
+// });
+
+// @override
+// Widget build(BuildContext context) {
+// return GestureDetector(
+// onTap: onTap,
+// child: Container(
+// height: 38,
+// padding: const EdgeInsets.symmetric(horizontal: 12),
+// decoration: BoxDecoration(
+// color: const Color(0xFF1C1C1E),
+// borderRadius: BorderRadius.circular(12),
+// ),
+// child: Row(
+// children: [
+// Icon(icon, color: color, size: 16),
+// const SizedBox(width: 8),
+// Expanded(
+// child: Text(label, style: TextStyle(color: color, fontSize: 11)),
+// ),
+// ],
+// ),
+// ),
+// );
+// }
+// }
+
+// // ── Create Group Dialog ────────────────────────────────────────────────────
+
+// class _CreateGroupDialog extends StatefulWidget {
+// final ValueChanged onNameChanged;
+
+// const _CreateGroupDialog({required this.onNameChanged});
+
+// @override
+// State<_CreateGroupDialog> createState() => _CreateGroupDialogState();
+// }
+
+// class _CreateGroupDialogState extends State<_CreateGroupDialog> {
+// final _ctrl = TextEditingController();
+
+// @override
+// void dispose() {
+// _ctrl.dispose();
+// super.dispose();
+// }
+
+// @override
+// Widget build(BuildContext context) {
+// return AlertDialog(
+// backgroundColor: const Color(0xFF1C1C1E),
+// shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
+// contentPadding: const EdgeInsets.all(16),
+// content: Column(
+// mainAxisSize: MainAxisSize.min,
+// children: [
+// const Icon(
+// Icons.add_circle_outline,
+// color: Color(0xFF00C853),
+// size: 26,
+// ),
+// const SizedBox(height: 8),
+// const Text(
+// 'گروه جدید',
+// style: TextStyle(
+// color: Colors.white,
+// fontSize: 12,
+// fontWeight: FontWeight.bold,
+// ),
+// ),
+// const SizedBox(height: 10),
+// TextField(
+// controller: _ctrl,
+// autofocus: true,
+// textAlign: TextAlign.center,
+// style: const TextStyle(color: Colors.white, fontSize: 12),
+// decoration: InputDecoration(
+// hintText: 'نام گروه',
+// hintStyle: const TextStyle(color: Colors.white38, fontSize: 11),
+// filled: true,
+// fillColor: Colors.white.withValues(alpha: 0.05),
+// border: OutlineInputBorder(
+// borderRadius: BorderRadius.circular(10),
+// borderSide: BorderSide.none,
+// ),
+// contentPadding: const EdgeInsets.symmetric(
+// horizontal: 12,
+// vertical: 8,
+// ),
+// ),
+// onChanged: widget.onNameChanged,
+// ),
+// const SizedBox(height: 12),
+// Row(
+// children: [
+// Expanded(
+// child: TextButton(
+// onPressed: () => Navigator.pop(context, false),
+// child: const Text(
+// 'انصراف',
+// style: TextStyle(color: Colors.white54, fontSize: 11),
+// ),
+// ),
+// ),
+// Expanded(
+// child: TextButton(
+// onPressed: () => Navigator.pop(context, true),
+// child: const Text(
+// 'ساخت',
+// style: TextStyle(
+// color: Color(0xFF00C853),
+// fontSize: 11,
+// fontWeight: FontWeight.bold,
+// ),
+// ),
+// ),
+// ),
+// ],
+// ),
+// ],
+// ),
+// );
+// }
+// }
diff --git a/Front/lib/screens/group_members_screen.dart b/Front/lib/screens/group_members_screen.dart
index b046f21..2e5af38 100644
--- a/Front/lib/screens/group_members_screen.dart
+++ b/Front/lib/screens/group_members_screen.dart
@@ -49,9 +49,7 @@ class _GroupMembersScreenState extends State {
String username = '';
final confirmed = await showDialog(
context: context,
- builder: (ctx) => _InviteDialog(
- onUsernameChanged: (v) => username = v,
- ),
+ builder: (ctx) => _InviteDialog(onUsernameChanged: (v) => username = v),
);
if (confirmed == true && username.trim().isNotEmpty) {
final err = await _api.inviteMember(widget.channel.id, username.trim());
@@ -63,11 +61,15 @@ class _GroupMembersScreenState extends State {
style: const TextStyle(fontSize: 11),
textAlign: TextAlign.center,
),
- backgroundColor: err == null ? const Color(0xFF1C1C1E) : const Color(0xFF333333),
+ backgroundColor: err == null
+ ? const Color(0xFF1C1C1E)
+ : const Color(0xFF333333),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(20),
+ ),
),
);
}
@@ -83,11 +85,19 @@ class _GroupMembersScreenState extends State {
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
- const Icon(Icons.person_remove_outlined, color: Colors.red, size: 28),
+ const Icon(
+ Icons.person_remove_outlined,
+ color: Colors.red,
+ size: 28,
+ ),
const SizedBox(height: 8),
Text(
'حذف ${member.username}؟',
- style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
+ ),
textAlign: TextAlign.center,
),
const SizedBox(height: 12),
@@ -96,13 +106,19 @@ class _GroupMembersScreenState extends State {
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(ctx, false),
- child: const Text('انصراف', style: TextStyle(color: Colors.white54, fontSize: 11)),
+ child: const Text(
+ 'انصراف',
+ style: TextStyle(color: Colors.white54, fontSize: 11),
+ ),
),
),
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(ctx, true),
- child: const Text('حذف', style: TextStyle(color: Colors.red, fontSize: 11)),
+ child: const Text(
+ 'حذف',
+ style: TextStyle(color: Colors.red, fontSize: 11),
+ ),
),
),
],
@@ -118,12 +134,18 @@ class _GroupMembersScreenState extends State {
if (err != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
- content: Text(err, style: const TextStyle(fontSize: 11), textAlign: TextAlign.center),
+ content: Text(
+ err,
+ style: const TextStyle(fontSize: 11),
+ textAlign: TextAlign.center,
+ ),
backgroundColor: const Color(0xFF333333),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(20),
+ ),
),
);
} else {
@@ -136,100 +158,81 @@ class _GroupMembersScreenState extends State {
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
- body: SafeArea(
- child: Column(
- children: [
- // Header
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
- child: Row(
- children: [
- IconButton(
- onPressed: () => Navigator.pop(context),
- padding: EdgeInsets.zero,
- constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
- icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white70, size: 14),
+ // استفاده از Stack برای قرار دادن دکمه شناور روی محتوا
+ body: Stack(
+ children: [
+ SafeArea(
+ child: Column(
+ children: [
+ // هدر ساده شده (فقط نمایش اطلاعات، بدون دکمههای کناری)
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 8,
),
- const SizedBox(width: 4),
- const Icon(Icons.group_outlined, color: Color(0xFF00C853), size: 14),
- const SizedBox(width: 4),
- const Expanded(
- child: Text(
- 'اعضا',
- style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
- ),
- ),
- // Invite button
- IconButton(
- onPressed: _showInviteDialog,
- padding: EdgeInsets.zero,
- constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
- icon: const Icon(Icons.person_add_outlined, color: Color(0xFF00C853), size: 16),
- ),
- IconButton(
- onPressed: _loading ? null : _load,
- padding: EdgeInsets.zero,
- constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
- icon: const Icon(Icons.refresh, color: Colors.white54, size: 16),
- ),
- ],
- ),
- ),
-
- // Group name chip
- Padding(
- padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 2),
- child: Row(
- children: [
- Container(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
- decoration: BoxDecoration(
- color: const Color(0xFF00C853).withValues(alpha: 0.12),
- borderRadius: BorderRadius.circular(20),
- ),
- child: Row(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(
- widget.channel.type == 'PUBLIC'
- ? Icons.public
- : Icons.lock_outline,
- color: const Color(0xFF00C853),
- size: 10,
- ),
- const SizedBox(width: 4),
+ child: Column(
+ children: [
+ // نام گروه و تعداد اعضا وسطچین
+ Row(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ Icon(
+ widget.channel.type == 'PUBLIC'
+ ? Icons.public
+ : Icons.lock_outline,
+ color: const Color(0xFF00C853),
+ size: 12,
+ ),
+ const SizedBox(width: 4),
+ Flexible(
+ child: Text(
+ widget.channel.name,
+ style: const TextStyle(
+ color: Color(0xFF00C853),
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
+ ),
+ overflow: TextOverflow.ellipsis,
+ ),
+ ),
+ ],
+ ),
+ if (!_loading)
Text(
- widget.channel.name,
- style: const TextStyle(color: Color(0xFF00C853), fontSize: 10),
+ '${_members.length} عضو',
+ style: const TextStyle(
+ color: Colors.white38,
+ fontSize: 10,
+ ),
),
- ],
- ),
+ ],
),
- if (!_loading) ...[
- const SizedBox(width: 6),
- Text(
- '${_members.length} عضو',
- style: const TextStyle(color: Colors.white38, fontSize: 10),
- ),
- ],
- ],
- ),
- ),
- const SizedBox(height: 4),
+ ),
+ const Divider(height: 1, color: Color(0xFF333333)),
- // Content
- Expanded(
- child: _loading
- ? const Center(
- child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 2),
- )
- : _members.isEmpty
+ // لیست اعضا
+ Expanded(
+ child: _loading
? const Center(
- child: Text('عضوی یافت نشد',
- style: TextStyle(color: Colors.white38, fontSize: 11)),
+ child: CircularProgressIndicator(
+ color: Color(0xFF00C853),
+ strokeWidth: 2,
+ ),
+ )
+ : _members.isEmpty
+ ? const Center(
+ child: Text(
+ 'عضوی یافت نشد',
+ style: TextStyle(
+ color: Colors.white38,
+ fontSize: 11,
+ ),
+ ),
)
: ListView.builder(
- padding: const EdgeInsets.only(bottom: 4),
+ padding: const EdgeInsets.only(
+ bottom: 60,
+ ), // فضای خالی برای دکمه شناور پایین
itemCount: _members.length,
itemBuilder: (ctx, i) {
final m = _members[i];
@@ -242,9 +245,32 @@ class _GroupMembersScreenState extends State {
);
},
),
+ ),
+ ],
),
- ],
- ),
+ ),
+
+ // دکمه شناور افزودن عضو (فقط برای مدیر) در پایین صفحه وسط
+ if (_isManager)
+ Positioned(
+ bottom: 10,
+ left: 0,
+ right: 0,
+ child: Center(
+ child: FloatingActionButton(
+ heroTag: "invite_btn", // جلوگیری از تداخل HeroTag
+ mini: true, // سایز کوچکتر مناسب ساعت
+ onPressed: _showInviteDialog,
+ backgroundColor: const Color(0xFF00C853),
+ child: const Icon(
+ Icons.person_add,
+ color: Colors.black,
+ size: 20,
+ ),
+ ),
+ ),
+ ),
+ ],
),
);
}
@@ -267,18 +293,18 @@ class _MemberTile extends StatelessWidget {
Widget build(BuildContext context) {
return Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
- height: 40,
+ height: 36, // کاهش ارتفاع آیتم
decoration: BoxDecoration(
color: const Color(0xFF1C1C1E),
- borderRadius: BorderRadius.circular(12),
+ borderRadius: BorderRadius.circular(10),
),
- padding: const EdgeInsets.symmetric(horizontal: 10),
+ padding: const EdgeInsets.symmetric(horizontal: 8),
child: Row(
children: [
// Online indicator
Container(
- width: 7,
- height: 7,
+ width: 6,
+ height: 6,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: member.isOnline ? const Color(0xFF00C853) : Colors.white24,
@@ -292,7 +318,9 @@ class _MemberTile extends StatelessWidget {
style: TextStyle(
color: isMe ? const Color(0xFF00C853) : Colors.white,
fontSize: 11,
- fontWeight: member.isManager ? FontWeight.bold : FontWeight.normal,
+ fontWeight: member.isManager
+ ? FontWeight.bold
+ : FontWeight.normal,
),
overflow: TextOverflow.ellipsis,
),
@@ -300,14 +328,14 @@ class _MemberTile extends StatelessWidget {
// Role badge
if (member.isManager)
Container(
- padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
+ padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration(
- color: const Color(0xFF00C853).withValues(alpha: 0.15),
- borderRadius: BorderRadius.circular(8),
+ color: const Color(0xFF00C853).withOpacity(0.15),
+ borderRadius: BorderRadius.circular(6),
),
child: const Text(
'مدیر',
- style: TextStyle(color: Color(0xFF00C853), fontSize: 9),
+ style: TextStyle(color: Color(0xFF00C853), fontSize: 8),
),
),
// Remove button
@@ -315,7 +343,11 @@ class _MemberTile extends StatelessWidget {
const SizedBox(width: 4),
GestureDetector(
onTap: onRemove,
- child: const Icon(Icons.remove_circle_outline, color: Colors.red, size: 16),
+ child: const Icon(
+ Icons.remove_circle_outline,
+ color: Colors.red,
+ size: 16,
+ ),
),
],
],
@@ -353,11 +385,19 @@ class _InviteDialogState extends State<_InviteDialog> {
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
- const Icon(Icons.person_add_outlined, color: Color(0xFF00C853), size: 26),
+ const Icon(
+ Icons.person_add_outlined,
+ color: Color(0xFF00C853),
+ size: 26,
+ ),
const SizedBox(height: 8),
const Text(
'دعوت عضو',
- style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
+ ),
),
const SizedBox(height: 10),
TextField(
@@ -369,12 +409,15 @@ class _InviteDialogState extends State<_InviteDialog> {
hintText: 'نام کاربری',
hintStyle: const TextStyle(color: Colors.white38, fontSize: 11),
filled: true,
- fillColor: Colors.white.withValues(alpha: 0.05),
+ fillColor: Colors.white.withOpacity(0.05),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none,
),
- contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 8,
+ ),
),
onChanged: widget.onUsernameChanged,
),
@@ -384,13 +427,23 @@ class _InviteDialogState extends State<_InviteDialog> {
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(context, false),
- child: const Text('انصراف', style: TextStyle(color: Colors.white54, fontSize: 11)),
+ child: const Text(
+ 'انصراف',
+ style: TextStyle(color: Colors.white54, fontSize: 11),
+ ),
),
),
Expanded(
child: TextButton(
onPressed: () => Navigator.pop(context, true),
- child: const Text('ارسال', style: TextStyle(color: Color(0xFF00C853), fontSize: 11, fontWeight: FontWeight.bold)),
+ child: const Text(
+ 'ارسال',
+ style: TextStyle(
+ color: Color(0xFF00C853),
+ fontSize: 11,
+ fontWeight: FontWeight.bold,
+ ),
+ ),
),
),
],
diff --git a/Front/lib/screens/home_screen.dart b/Front/lib/screens/home_screen.dart
deleted file mode 100644
index e67641d..0000000
--- a/Front/lib/screens/home_screen.dart
+++ /dev/null
@@ -1,129 +0,0 @@
-import 'package:flutter/material.dart';
-import 'channel_list_screen.dart';
-
-class HomeScreen extends StatelessWidget {
- const HomeScreen({super.key});
-
- @override
- Widget build(BuildContext context) {
- return Scaffold(
- backgroundColor: Colors.black,
- body: SafeArea(
- child: Center(
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- const Text(
- 'برنامهها',
- style: TextStyle(
- color: Colors.white38,
- fontSize: 9,
- letterSpacing: 0.5,
- ),
- ),
- const SizedBox(height: 16),
- Row(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- _AppIcon(
- icon: Icons.radio,
- label: 'بیسیم',
- color: const Color(0xFF00C853),
- onTap: () {
- Navigator.push(
- context,
- MaterialPageRoute(
- builder: (_) => const ChannelListScreen(),
- ),
- );
- },
- ),
- const SizedBox(width: 20),
- _AppIcon(
- icon: Icons.phone,
- label: 'تلفن',
- color: const Color(0xFF2979FF),
- onTap: () {
- ScaffoldMessenger.of(context).showSnackBar(
- const SnackBar(
- content: Text(
- 'به زودی',
- textAlign: TextAlign.center,
- style: TextStyle(
- fontSize: 11,
- color: Colors.white,
- ),
- ),
- duration: Duration(seconds: 1),
- backgroundColor: Color(0xFF2C2C2E),
- behavior: SnackBarBehavior.floating,
- margin: EdgeInsets.symmetric(
- horizontal: 24, vertical: 40),
- ),
- );
- },
- ),
- ],
- ),
- ],
- ),
- ),
- ),
- );
- }
-}
-
-class _AppIcon extends StatelessWidget {
- final IconData icon;
- final String label;
- final Color color;
- final VoidCallback onTap;
-
- const _AppIcon({
- required this.icon,
- required this.label,
- required this.color,
- required this.onTap,
- });
-
- @override
- Widget build(BuildContext context) {
- return GestureDetector(
- onTap: onTap,
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Container(
- width: 58,
- height: 58,
- decoration: BoxDecoration(
- color: color.withValues(alpha: 0.13),
- shape: BoxShape.circle,
- border: Border.all(
- color: color.withValues(alpha: 0.45),
- width: 1.5,
- ),
- boxShadow: [
- BoxShadow(
- color: color.withValues(alpha: 0.18),
- blurRadius: 12,
- spreadRadius: 1,
- ),
- ],
- ),
- child: Icon(icon, color: color, size: 28),
- ),
- const SizedBox(height: 7),
- Text(
- label,
- style: const TextStyle(
- color: Colors.white,
- fontSize: 11,
- fontWeight: FontWeight.w500,
- ),
- ),
- ],
- ),
- );
- }
-}
diff --git a/Front/lib/screens/home_screen_old.dart b/Front/lib/screens/home_screen_old.dart
new file mode 100644
index 0000000..6c6dbe4
--- /dev/null
+++ b/Front/lib/screens/home_screen_old.dart
@@ -0,0 +1,247 @@
+// import 'dart:async';
+// import 'package:flutter/material.dart';
+// import 'package:flutter/services.dart';
+// import 'channel_list_screen.dart';
+
+// class HomeScreen extends StatefulWidget {
+// const HomeScreen({super.key});
+
+// @override
+// State createState() => _HomeScreenState();
+// }
+
+// class _HomeScreenState extends State {
+// static final _channel = const MethodChannel('com.example.watch/launcher');
+
+// int _batteryLevel = 0;
+// bool _isCharging = false;
+// Timer? _batteryTimer;
+
+// @override
+// void initState() {
+// super.initState();
+// _loadBattery();
+// _batteryTimer = Timer.periodic(
+// const Duration(seconds: 30),
+// (_) => _loadBattery(),
+// );
+// }
+
+// Future _loadBattery() async {
+// try {
+// final info = await _channel.invokeMapMethod(
+// 'getBatteryInfo',
+// );
+// if (!mounted || info == null) return;
+// setState(() {
+// _batteryLevel = (info['level'] as int?) ?? 0;
+// _isCharging = (info['isCharging'] as bool?) ?? false;
+// });
+// } catch (_) {}
+// }
+
+// @override
+// void dispose() {
+// _batteryTimer?.cancel();
+// super.dispose();
+// }
+
+// Color get _batteryColor {
+// if (_isCharging) return const Color(0xFF00C853);
+// if (_batteryLevel <= 15) return const Color(0xFFFF1744);
+// if (_batteryLevel <= 30) return const Color(0xFFFFAB00);
+// return Colors.white54;
+// }
+
+// @override
+// Widget build(BuildContext context) {
+// return Scaffold(
+// backgroundColor: Colors.black,
+// body: SafeArea(
+// child: Stack(
+// children: [
+// // ── Battery indicator (top right) ──────────────────────────
+// Positioned(
+// top: 4,
+// right: 8,
+// child: Row(
+// mainAxisSize: MainAxisSize.min,
+// children: [
+// if (_isCharging)
+// const Icon(Icons.bolt, color: Color(0xFF00C853), size: 11),
+// Text(
+// '$_batteryLevel%',
+// style: TextStyle(color: _batteryColor, fontSize: 9),
+// ),
+// ],
+// ),
+// ),
+
+// // ── Charging banner ────────────────────────────────────────
+// if (_isCharging)
+// Positioned(
+// bottom: 10,
+// left: 0,
+// right: 0,
+// child: Row(
+// mainAxisAlignment: MainAxisAlignment.center,
+// children: [
+// Container(
+// padding: const EdgeInsets.symmetric(
+// horizontal: 10,
+// vertical: 3,
+// ),
+// decoration: BoxDecoration(
+// color: const Color(0xFF00C853).withValues(alpha: 0.15),
+// borderRadius: BorderRadius.circular(20),
+// border: Border.all(
+// color: const Color(0xFF00C853).withValues(alpha: 0.4),
+// width: 1,
+// ),
+// ),
+// child: Row(
+// mainAxisSize: MainAxisSize.min,
+// children: [
+// const Icon(
+// Icons.bolt,
+// color: Color(0xFF00C853),
+// size: 11,
+// ),
+// const SizedBox(width: 3),
+// Text(
+// 'در حال شارژ — $_batteryLevel%',
+// style: const TextStyle(
+// color: Color(0xFF00C853),
+// fontSize: 9,
+// ),
+// ),
+// ],
+// ),
+// ),
+// ],
+// ),
+// ),
+
+// // ── App grid ───────────────────────────────────────────────
+// Center(
+// child: Column(
+// mainAxisAlignment: MainAxisAlignment.center,
+// children: [
+// const Text(
+// 'برنامهها',
+// style: TextStyle(
+// color: Colors.white38,
+// fontSize: 9,
+// letterSpacing: 0.5,
+// ),
+// ),
+// const SizedBox(height: 16),
+// Row(
+// mainAxisAlignment: MainAxisAlignment.center,
+// children: [
+// _AppIcon(
+// icon: Icons.radio,
+// label: 'بیسیم',
+// color: const Color(0xFF00C853),
+// onTap: () {
+// Navigator.push(
+// context,
+// MaterialPageRoute(
+// builder: (_) => const ChannelListScreen(),
+// ),
+// );
+// },
+// ),
+// const SizedBox(width: 20),
+// _AppIcon(
+// icon: Icons.phone,
+// label: 'تلفن',
+// color: const Color(0xFF2979FF),
+// onTap: () {
+// ScaffoldMessenger.of(context).showSnackBar(
+// const SnackBar(
+// content: Text(
+// 'به زودی',
+// textAlign: TextAlign.center,
+// style: TextStyle(
+// fontSize: 11,
+// color: Colors.white,
+// ),
+// ),
+// duration: Duration(seconds: 1),
+// backgroundColor: Color(0xFF2C2C2E),
+// behavior: SnackBarBehavior.floating,
+// margin: EdgeInsets.symmetric(
+// horizontal: 24,
+// vertical: 40,
+// ),
+// ),
+// );
+// },
+// ),
+// ],
+// ),
+// ],
+// ),
+// ),
+// ],
+// ),
+// ),
+// );
+// }
+// }
+
+// class _AppIcon extends StatelessWidget {
+// final IconData icon;
+// final String label;
+// final Color color;
+// final VoidCallback onTap;
+
+// const _AppIcon({
+// required this.icon,
+// required this.label,
+// required this.color,
+// required this.onTap,
+// });
+
+// @override
+// Widget build(BuildContext context) {
+// return GestureDetector(
+// onTap: onTap,
+// child: Column(
+// mainAxisSize: MainAxisSize.min,
+// children: [
+// Container(
+// width: 58,
+// height: 58,
+// decoration: BoxDecoration(
+// color: color.withValues(alpha: 0.13),
+// shape: BoxShape.circle,
+// border: Border.all(
+// color: color.withValues(alpha: 0.45),
+// width: 1.5,
+// ),
+// boxShadow: [
+// BoxShadow(
+// color: color.withValues(alpha: 0.18),
+// blurRadius: 12,
+// spreadRadius: 1,
+// ),
+// ],
+// ),
+// child: Icon(icon, color: color, size: 28),
+// ),
+// const SizedBox(height: 7),
+// Text(
+// label,
+// style: const TextStyle(
+// color: Colors.white,
+// fontSize: 11,
+// fontWeight: FontWeight.w500,
+// ),
+// ),
+// ],
+// ),
+// );
+// }
+// }
diff --git a/Front/lib/screens/login_screen.dart b/Front/lib/screens/login_screen.dart
index e615e1e..8337fbe 100644
--- a/Front/lib/screens/login_screen.dart
+++ b/Front/lib/screens/login_screen.dart
@@ -1,7 +1,8 @@
import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
import '../services/auth_service.dart';
import '../services/api_service.dart';
-import 'home_screen.dart';
+import '../home/home_screen.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@@ -18,6 +19,11 @@ class _LoginScreenState extends State {
bool _loading = false;
String? _error;
+ // کانال ارتباطی با کد نیتیو برای تنظیمات اینترنت
+ static final _nativeChannel = const MethodChannel(
+ 'com.example.watch/launcher',
+ );
+
@override
void initState() {
super.initState();
@@ -31,6 +37,31 @@ class _LoginScreenState extends State {
super.dispose();
}
+ // متد باز کردن تنظیمات اینترنت
+ Future _openInternetSettings() async {
+ try {
+ await _nativeChannel.invokeMethod('openInternetSettings');
+ } catch (_) {
+ _showSnack('تنظیمات اینترنت در دسترس نیست');
+ }
+ }
+
+ // متد نمایش پیام (SnackBar)
+ 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 _login() async {
final username = _usernameCtrl.text.trim();
final secret = _secretCtrl.text.trim();
@@ -67,150 +98,203 @@ class _LoginScreenState extends State {
child: SingleChildScrollView(
child: ConstrainedBox(
constraints: BoxConstraints(
- minHeight: MediaQuery.of(context).size.height -
+ minHeight:
+ MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top -
MediaQuery.of(context).padding.bottom,
),
- child: Center(
- child: Padding(
- padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
- child: Column(
- mainAxisAlignment: MainAxisAlignment.center,
- mainAxisSize: MainAxisSize.min,
- children: [
- // Icon
- Container(
- width: 36,
- height: 36,
- decoration: BoxDecoration(
- color: const Color(0xFF00C853).withValues(alpha: 0.15),
- shape: BoxShape.circle,
- ),
- child: const Icon(
- Icons.settings_input_antenna,
- color: Color(0xFF00C853),
- size: 18,
- ),
- ),
- const SizedBox(height: 4),
- const Text(
- 'WalkieTalkie',
- style: TextStyle(
- color: Colors.white,
- fontSize: 12,
- fontWeight: FontWeight.bold,
- letterSpacing: 1,
- ),
- ),
- const SizedBox(height: 10),
-
- // Username input
- SizedBox(
- height: 32,
- child: TextField(
- controller: _usernameCtrl,
- style: const TextStyle(color: Colors.white, fontSize: 11),
- textAlign: TextAlign.center,
- decoration: InputDecoration(
- hintText: 'نام کاربری',
- hintStyle:
- const TextStyle(color: Colors.white38, fontSize: 10),
- filled: true,
- fillColor: const Color(0xFF1C1C1E),
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(16),
- borderSide: BorderSide.none,
- ),
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 10, vertical: 0),
- ),
- onSubmitted: (_) =>
- FocusScope.of(context).nextFocus(),
- ),
- ),
- const SizedBox(height: 6),
-
- // Secret input
- SizedBox(
- height: 32,
- child: TextField(
- controller: _secretCtrl,
- style: const TextStyle(color: Colors.white, fontSize: 11),
- textAlign: TextAlign.center,
- obscureText: true,
- decoration: InputDecoration(
- hintText: 'کلید ورود',
- hintStyle:
- const TextStyle(color: Colors.white38, fontSize: 10),
- filled: true,
- fillColor: const Color(0xFF1C1C1E),
- border: OutlineInputBorder(
- borderRadius: BorderRadius.circular(16),
- borderSide: BorderSide.none,
- ),
- contentPadding: const EdgeInsets.symmetric(
- horizontal: 10, vertical: 0),
- ),
- onSubmitted: (_) => _login(),
- ),
- ),
-
- // Error
- SizedBox(
- height: 16,
- child: _error != null
- ? Text(
- _error!,
- style: const TextStyle(
- color: Color(0xFFFF1744), fontSize: 9),
- )
- : null,
- ),
-
- // Login button
- GestureDetector(
- onTap: _loading ? null : _login,
- child: AnimatedContainer(
- duration: const Duration(milliseconds: 200),
- width: 50,
- height: 50,
+ child: Column(
+ mainAxisAlignment: MainAxisAlignment.center,
+ children: [
+ // محتوای اصلی فرم
+ Padding(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 20,
+ vertical: 8,
+ ),
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ // Icon
+ Container(
+ width: 36,
+ height: 36,
decoration: BoxDecoration(
- color: _loading
- ? const Color(0xFF424242)
- : const Color(0xFF00C853),
+ color: const Color(0xFF00C853).withOpacity(0.15),
shape: BoxShape.circle,
- boxShadow: _loading
- ? null
- : [
- BoxShadow(
- color: const Color(0xFF00C853).withValues(alpha: 0.4),
- blurRadius: 10,
- spreadRadius: 1,
- ),
- ],
),
- child: _loading
- ? const Center(
- child: SizedBox(
- width: 18,
- height: 18,
- child: CircularProgressIndicator(
- color: Colors.white,
- strokeWidth: 2,
- ),
+ child: const Icon(
+ Icons.settings_input_antenna,
+ color: Color(0xFF00C853),
+ size: 18,
+ ),
+ ),
+ const SizedBox(height: 4),
+ const Text(
+ 'WalkieTalkie',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 12,
+ fontWeight: FontWeight.bold,
+ letterSpacing: 1,
+ ),
+ ),
+ const SizedBox(height: 10),
+
+ // Username input
+ SizedBox(
+ height: 32,
+ child: TextField(
+ controller: _usernameCtrl,
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 11,
+ ),
+ textAlign: TextAlign.center,
+ decoration: InputDecoration(
+ hintText: 'نام کاربری',
+ hintStyle: const TextStyle(
+ color: Colors.white38,
+ fontSize: 10,
+ ),
+ filled: true,
+ fillColor: const Color(0xFF1C1C1E),
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(16),
+ borderSide: BorderSide.none,
+ ),
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 10,
+ vertical: 0,
+ ),
+ ),
+ onSubmitted: (_) =>
+ FocusScope.of(context).nextFocus(),
+ ),
+ ),
+ const SizedBox(height: 6),
+
+ // Secret input
+ SizedBox(
+ height: 32,
+ child: TextField(
+ controller: _secretCtrl,
+ style: const TextStyle(
+ color: Colors.white,
+ fontSize: 11,
+ ),
+ textAlign: TextAlign.center,
+ obscureText: true,
+ decoration: InputDecoration(
+ hintText: 'کلید ورود',
+ hintStyle: const TextStyle(
+ color: Colors.white38,
+ fontSize: 10,
+ ),
+ filled: true,
+ fillColor: const Color(0xFF1C1C1E),
+ border: OutlineInputBorder(
+ borderRadius: BorderRadius.circular(16),
+ borderSide: BorderSide.none,
+ ),
+ contentPadding: const EdgeInsets.symmetric(
+ horizontal: 10,
+ vertical: 0,
+ ),
+ ),
+ onSubmitted: (_) => _login(),
+ ),
+ ),
+
+ // Error
+ SizedBox(
+ height: 16,
+ child: _error != null
+ ? Text(
+ _error!,
+ style: const TextStyle(
+ color: Color(0xFFFF1744),
+ fontSize: 9,
),
)
- : const Icon(Icons.login, color: Colors.white, size: 22),
+ : null,
+ ),
+
+ // Login button
+ GestureDetector(
+ onTap: _loading ? null : _login,
+ child: AnimatedContainer(
+ duration: const Duration(milliseconds: 200),
+ width: 50,
+ height: 50,
+ decoration: BoxDecoration(
+ color: _loading
+ ? const Color(0xFF424242)
+ : const Color(0xFF00C853),
+ shape: BoxShape.circle,
+ boxShadow: _loading
+ ? null
+ : [
+ BoxShadow(
+ color: const Color(
+ 0xFF00C853,
+ ).withOpacity(0.4),
+ blurRadius: 10,
+ spreadRadius: 1,
+ ),
+ ],
+ ),
+ child: _loading
+ ? const Center(
+ child: SizedBox(
+ width: 18,
+ height: 18,
+ child: CircularProgressIndicator(
+ color: Colors.white,
+ strokeWidth: 2,
+ ),
+ ),
+ )
+ : const Icon(
+ Icons.login,
+ color: Colors.white,
+ size: 22,
+ ),
+ ),
+ ),
+ const SizedBox(height: 4),
+ const Text(
+ 'ورود',
+ style: TextStyle(color: Colors.white38, fontSize: 9),
+ ),
+ ],
+ ),
+ ),
+
+ const SizedBox(height: 20),
+
+ // دکمه تنظیمات اینترنت (پایین صفحه وسط)
+ GestureDetector(
+ onTap: _openInternetSettings,
+ child: Container(
+ width: 40,
+ height: 40,
+ decoration: BoxDecoration(
+ color: const Color(0xFF1C1C1E),
+ shape: BoxShape.circle,
+ border: Border.all(
+ color: const Color(0xFF00BCD4).withOpacity(0.3),
+ width: 1,
),
),
- const SizedBox(height: 4),
- const Text(
- 'ورود',
- style: TextStyle(color: Colors.white38, fontSize: 9),
+ child: const Icon(
+ Icons.wifi,
+ color: Color(0xFF00BCD4),
+ size: 20,
),
- ],
+ ),
),
- ),
+ ],
),
),
),
diff --git a/Front/lib/screens/notifications_screen.dart b/Front/lib/screens/notifications_screen.dart
index 1a34914..b825a66 100644
--- a/Front/lib/screens/notifications_screen.dart
+++ b/Front/lib/screens/notifications_screen.dart
@@ -42,12 +42,18 @@ class _NotificationsScreenState extends State {
if (err != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
- content: Text(err, style: const TextStyle(fontSize: 11), textAlign: TextAlign.center),
+ content: Text(
+ err,
+ style: const TextStyle(fontSize: 11),
+ textAlign: TextAlign.center,
+ ),
backgroundColor: const Color(0xFF333333),
behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
- shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
+ shape: RoundedRectangleBorder(
+ borderRadius: BorderRadius.circular(20),
+ ),
),
);
} else {
@@ -63,64 +69,76 @@ class _NotificationsScreenState extends State {
body: SafeArea(
child: Column(
children: [
- // Header
+ // Header ساده و وسطچین (بدون دکمههای کناری)
Padding(
- padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
+ padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Row(
+ mainAxisAlignment: MainAxisAlignment.center,
children: [
- IconButton(
- onPressed: () => Navigator.pop(context),
- padding: EdgeInsets.zero,
- constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
- icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white70, size: 14),
+ const Icon(
+ Icons.notifications_outlined,
+ color: Color(0xFF00C853),
+ size: 16,
),
- const SizedBox(width: 4),
- const Icon(Icons.notifications_outlined, color: Color(0xFF00C853), size: 14),
- const SizedBox(width: 4),
- const Expanded(
- child: Text(
- 'اعلانها',
- style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
+ const SizedBox(width: 6),
+ const Text(
+ 'اعلانها',
+ style: TextStyle(
+ color: Colors.white,
+ fontSize: 14,
+ fontWeight: FontWeight.bold,
),
),
- IconButton(
- onPressed: _loading ? null : _load,
- padding: EdgeInsets.zero,
- constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
- icon: const Icon(Icons.refresh, color: Colors.white54, size: 16),
- ),
],
),
),
+ const Divider(height: 1, color: Color(0xFF333333)),
// Content
Expanded(
child: _loading
? const Center(
- child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 2),
+ child: CircularProgressIndicator(
+ color: Color(0xFF00C853),
+ strokeWidth: 2,
+ ),
)
: _notifications.isEmpty
- ? const Center(
- child: Column(
- mainAxisSize: MainAxisSize.min,
- children: [
- Icon(Icons.notifications_off_outlined, color: Colors.white24, size: 28),
- SizedBox(height: 6),
- Text('اعلانی وجود ندارد',
- style: TextStyle(color: Colors.white38, fontSize: 11)),
- ],
+ ? const Center(
+ child: Column(
+ mainAxisSize: MainAxisSize.min,
+ children: [
+ Icon(
+ Icons.notifications_off_outlined,
+ color: Colors.white24,
+ size: 32,
),
- )
- : ListView.builder(
- padding: const EdgeInsets.only(bottom: 4),
- itemCount: _notifications.length,
- itemBuilder: (ctx, i) => _NotifTile(
- notif: _notifications[i],
- isProcessing: _processing.contains(_notifications[i].id),
- onAccept: () => _respond(_notifications[i], true),
- onReject: () => _respond(_notifications[i], false),
+ SizedBox(height: 8),
+ Text(
+ 'اعلانی وجود ندارد',
+ style: TextStyle(
+ color: Colors.white38,
+ fontSize: 12,
+ ),
),
+ ],
+ ),
+ )
+ : ListView.builder(
+ padding: const EdgeInsets.symmetric(
+ horizontal: 8,
+ vertical: 8,
+ ),
+ itemCount: _notifications.length,
+ itemBuilder: (ctx, i) => _NotifTile(
+ notif: _notifications[i],
+ isProcessing: _processing.contains(
+ _notifications[i].id,
),
+ onAccept: () => _respond(_notifications[i], true),
+ onReject: () => _respond(_notifications[i], false),
+ ),
+ ),
),
],
),
@@ -157,16 +175,16 @@ class _NotifTile extends StatelessWidget {
}
return Container(
- margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
+ margin: const EdgeInsets.only(bottom: 8), // فاصله عمودی بین آیتمها
decoration: BoxDecoration(
color: const Color(0xFF1C1C1E),
borderRadius: BorderRadius.circular(12),
border: Border.all(
- color: isPending ? statusColor.withValues(alpha: 0.4) : Colors.transparent,
+ color: isPending ? statusColor.withOpacity(0.4) : Colors.transparent,
width: 1,
),
),
- padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
+ padding: const EdgeInsets.all(10),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@@ -175,15 +193,15 @@ class _NotifTile extends StatelessWidget {
Icon(
isJoin ? Icons.group_add_outlined : Icons.campaign_outlined,
color: statusColor,
- size: 13,
+ size: 14,
),
- const SizedBox(width: 5),
+ const SizedBox(width: 6),
Expanded(
child: Text(
notif.title,
style: const TextStyle(
color: Colors.white,
- fontSize: 11,
+ fontSize: 12,
fontWeight: FontWeight.w600,
),
overflow: TextOverflow.ellipsis,
@@ -195,37 +213,47 @@ class _NotifTile extends StatelessWidget {
const SizedBox(height: 4),
Text(
notif.description!,
- style: const TextStyle(color: Colors.white60, fontSize: 10),
- maxLines: 2,
+ style: const TextStyle(color: Colors.white70, fontSize: 11),
+ maxLines: 3,
overflow: TextOverflow.ellipsis,
),
],
if (isJoin && isPending) ...[
- const SizedBox(height: 6),
+ const SizedBox(height: 8),
if (isProcessing)
const Center(
child: SizedBox(
- width: 14,
- height: 14,
- child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 1.5),
+ width: 16,
+ height: 16,
+ child: CircularProgressIndicator(
+ color: Color(0xFF00C853),
+ strokeWidth: 2,
+ ),
),
)
else
Row(
- mainAxisAlignment: MainAxisAlignment.end,
+ mainAxisAlignment: MainAxisAlignment.end, // دکمهها سمت راست
children: [
// Reject
GestureDetector(
onTap: onReject,
child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 6,
+ ),
decoration: BoxDecoration(
- color: Colors.red.withValues(alpha: 0.15),
+ color: Colors.red.withOpacity(0.15),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'رد',
- style: TextStyle(color: Colors.red, fontSize: 10, fontWeight: FontWeight.bold),
+ style: TextStyle(
+ color: Colors.red,
+ fontSize: 11,
+ fontWeight: FontWeight.bold,
+ ),
),
),
),
@@ -234,14 +262,21 @@ class _NotifTile extends StatelessWidget {
GestureDetector(
onTap: onAccept,
child: Container(
- padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
+ padding: const EdgeInsets.symmetric(
+ horizontal: 12,
+ vertical: 6,
+ ),
decoration: BoxDecoration(
- color: const Color(0xFF00C853).withValues(alpha: 0.15),
+ color: const Color(0xFF00C853).withOpacity(0.15),
borderRadius: BorderRadius.circular(20),
),
child: const Text(
'قبول',
- style: TextStyle(color: Color(0xFF00C853), fontSize: 10, fontWeight: FontWeight.bold),
+ style: TextStyle(
+ color: Color(0xFF00C853),
+ fontSize: 11,
+ fontWeight: FontWeight.bold,
+ ),
),
),
),
@@ -251,10 +286,10 @@ class _NotifTile extends StatelessWidget {
if (!isPending) ...[
const SizedBox(height: 4),
Align(
- alignment: Alignment.centerLeft,
+ alignment: Alignment.centerRight,
child: Text(
notif.isAccepted == true ? 'پذیرفته شد' : 'رد شد',
- style: TextStyle(color: statusColor, fontSize: 9),
+ style: TextStyle(color: statusColor, fontSize: 10),
),
),
],
diff --git a/Front/lib/screens/wifi_screen.dart b/Front/lib/screens/wifi_screen.dart
new file mode 100644
index 0000000..8a93bf2
--- /dev/null
+++ b/Front/lib/screens/wifi_screen.dart
@@ -0,0 +1,334 @@
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+
+class WifiScreen extends StatefulWidget {
+ const WifiScreen({super.key});
+
+ @override
+ State createState() => _WifiScreenState();
+}
+
+class _WifiScreenState extends State {
+ static final _channel = const MethodChannel('com.example.watch/launcher');
+
+ List