From 59b5ecd59787be55292ce2f94d4e42f3a3131a67 Mon Sep 17 00:00:00 2001 From: wikm Date: Thu, 19 Mar 2026 11:56:16 +0330 Subject: [PATCH] Refactor and Redesign --- .../android/app/src/main/AndroidManifest.xml | 16 + .../com/example/watch/KeyLoggerService.kt | 29 + .../kotlin/com/example/watch/MainActivity.kt | 250 +++++++- .../app/src/main/res/values/strings.xml | 3 + .../res/xml/accessibility_service_config.xml | 8 + .../lib/channel_list/channel_list_screen.dart | 186 ++++++ .../widgets/channel_circle_item.dart | 74 +++ Front/lib/home/home_screen.dart | 19 + Front/lib/home/widgets/create_group_page.dart | 155 +++++ Front/lib/home/widgets/watch_launcher.dart | 443 +++++++++++++ Front/lib/main.dart | 108 +++- Front/lib/screens/about.dart | 216 +++++++ Front/lib/screens/channel_list_screen.dart | 423 ------------- .../lib/screens/channel_list_screen_old.dart | 594 ++++++++++++++++++ Front/lib/screens/group_members_screen.dart | 285 +++++---- Front/lib/screens/home_screen.dart | 129 ---- Front/lib/screens/home_screen_old.dart | 247 ++++++++ Front/lib/screens/login_screen.dart | 350 +++++++---- Front/lib/screens/notifications_screen.dart | 159 +++-- Front/lib/screens/wifi_screen.dart | 334 ++++++++++ Security_disable.txt | 41 ++ 21 files changed, 3187 insertions(+), 882 deletions(-) create mode 100644 Front/android/app/src/main/kotlin/com/example/watch/KeyLoggerService.kt create mode 100644 Front/android/app/src/main/res/values/strings.xml create mode 100644 Front/android/app/src/main/res/xml/accessibility_service_config.xml create mode 100644 Front/lib/channel_list/channel_list_screen.dart create mode 100644 Front/lib/channel_list/widgets/channel_circle_item.dart create mode 100644 Front/lib/home/home_screen.dart create mode 100644 Front/lib/home/widgets/create_group_page.dart create mode 100644 Front/lib/home/widgets/watch_launcher.dart create mode 100644 Front/lib/screens/about.dart delete mode 100644 Front/lib/screens/channel_list_screen.dart create mode 100644 Front/lib/screens/channel_list_screen_old.dart delete mode 100644 Front/lib/screens/home_screen.dart create mode 100644 Front/lib/screens/home_screen_old.dart create mode 100644 Front/lib/screens/wifi_screen.dart create mode 100644 Security_disable.txt 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> _networks = []; + bool _loading = true; + String? _error; + + @override + void initState() { + super.initState(); + _scan(); + } + + Future _scan() async { + setState(() { + _loading = true; + _error = null; + }); + try { + final raw = await _channel.invokeListMethod('getWifiList'); + if (!mounted) return; + setState(() { + _networks = (raw ?? []) + .map((e) => Map.from(e as Map)) + .toList(); + _loading = false; + if (_networks.isEmpty) _error = 'شبکه‌ای یافت نشد'; + }); + } catch (e) { + if (!mounted) return; + setState(() { + _loading = false; + _error = 'خطا در اسکن وای‌فای'; + }); + } + } + + Future _openWifiSettings() async { + try { + await _channel.invokeMethod('openWifiSettings'); + } catch (_) {} + } + + Future _connectOrSuggest(String ssid, String password) async { + try { + final ok = await _channel.invokeMethod( + 'connectToWifi', + {'ssid': ssid, 'password': password}, + ); + if (!mounted) return; + if (ok == true) { + _showSnack('درخواست اتصال به "$ssid" ارسال شد'); + } else { + _openWifiSettings(); + } + } catch (_) { + _openWifiSettings(); + } + } + + 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), + )); + } + + int _bars(int level) { + if (level >= -50) return 3; + if (level >= -70) return 2; + return 1; + } + + IconData _wifiIcon(int bars) { + if (bars >= 3) return Icons.wifi; + if (bars == 2) return Icons.wifi_2_bar; + return Icons.wifi_1_bar; + } + + void _onNetworkTap(String ssid, bool secured) { + if (secured) { + _showPasswordDialog(ssid); + } else { + _connectOrSuggest(ssid, ''); + } + } + + void _showPasswordDialog(String ssid) async { + String password = ''; + final confirmed = await showDialog( + context: context, + builder: (ctx) => AlertDialog( + backgroundColor: const Color(0xFF1C1C1E), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), + contentPadding: const EdgeInsets.all(14), + content: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Icon(Icons.wifi_password, color: Color(0xFF00C853), size: 22), + const SizedBox(height: 6), + Text(ssid, + style: const TextStyle( + color: Colors.white, + fontSize: 11, + fontWeight: FontWeight.bold), + textAlign: TextAlign.center), + const SizedBox(height: 10), + TextField( + autofocus: true, + obscureText: true, + textAlign: TextAlign.center, + style: const TextStyle(color: Colors.white, fontSize: 11), + decoration: InputDecoration( + hintText: 'رمز عبور', + hintStyle: + const TextStyle(color: Colors.white38, fontSize: 10), + 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: (v) => password = v, + ), + const SizedBox(height: 10), + Row( + children: [ + Expanded( + child: TextButton( + onPressed: () => Navigator.pop(ctx, false), + child: const Text('انصراف', + style: + TextStyle(color: Colors.white54, fontSize: 11)), + ), + ), + Expanded( + child: TextButton( + onPressed: () => Navigator.pop(ctx, true), + child: const Text('اتصال', + style: TextStyle( + color: Color(0xFF00C853), + fontSize: 11, + fontWeight: FontWeight.bold)), + ), + ), + ], + ), + ], + ), + ), + ); + if (confirmed == true && password.isNotEmpty) { + _connectOrSuggest(ssid, password); + } + } + + @override + 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( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () => Navigator.pop(context), + child: const Icon(Icons.arrow_back_ios_new, + color: Colors.white54, size: 14), + ), + const SizedBox(width: 6), + const Icon(Icons.wifi, color: Color(0xFF00C853), size: 14), + const SizedBox(width: 6), + const Text('وای‌فای', + style: TextStyle( + color: Colors.white, + fontSize: 13, + fontWeight: FontWeight.bold)), + const SizedBox(width: 8), + GestureDetector( + onTap: _loading ? null : _scan, + child: Icon(Icons.refresh, + color: _loading ? Colors.white24 : Colors.white54, + size: 14), + ), + ], + ), + ), + + // Content + Expanded( + child: _loading + ? const Center( + child: CircularProgressIndicator( + color: Color(0xFF00C853), strokeWidth: 2)) + : _error != null && _networks.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: 10), + GestureDetector( + onTap: _openWifiSettings, + child: Container( + padding: const EdgeInsets.symmetric( + horizontal: 14, vertical: 6), + decoration: BoxDecoration( + color: const Color(0xFF1C1C1E), + borderRadius: BorderRadius.circular(12), + ), + child: const Text('تنظیمات وای‌فای', + style: TextStyle( + color: Color(0xFF00C853), + fontSize: 10)), + ), + ), + ], + ), + ) + : ListView.builder( + padding: const EdgeInsets.only(bottom: 4), + itemCount: _networks.length + 1, + itemBuilder: (ctx, i) { + // Last item: settings link + if (i == _networks.length) { + return Padding( + padding: const EdgeInsets.symmetric( + horizontal: 8, vertical: 3), + child: GestureDetector( + onTap: _openWifiSettings, + child: Container( + height: 34, + decoration: BoxDecoration( + color: const Color(0xFF1C1C1E), + borderRadius: + BorderRadius.circular(12), + ), + child: const Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Icon(Icons.settings, + size: 12, + color: Colors.white38), + SizedBox(width: 4), + Text('تنظیمات وای‌فای', + style: TextStyle( + color: Colors.white38, + fontSize: 10)), + ], + ), + ), + ), + ); + } + + final net = _networks[i]; + final ssid = (net['ssid'] as String?) ?? ''; + final level = (net['level'] as int?) ?? -100; + final secured = + (net['secured'] as bool?) ?? false; + final bars = _bars(level); + + return GestureDetector( + onTap: () => _onNetworkTap(ssid, secured), + child: Container( + margin: const EdgeInsets.symmetric( + horizontal: 8, vertical: 2), + height: 40, + decoration: BoxDecoration( + color: const Color(0xFF1C1C1E), + borderRadius: BorderRadius.circular(12), + ), + padding: const EdgeInsets.symmetric( + horizontal: 10), + child: Row( + children: [ + Icon(_wifiIcon(bars), + color: const Color(0xFF00C853), + size: 14), + const SizedBox(width: 8), + Expanded( + child: Text( + ssid, + style: const TextStyle( + color: Colors.white, + fontSize: 11), + overflow: TextOverflow.ellipsis, + ), + ), + if (secured) + const Icon(Icons.lock_outline, + color: Colors.white38, size: 10), + ], + ), + ), + ); + }, + ), + ), + ], + ), + ), + ); + } +} diff --git a/Security_disable.txt b/Security_disable.txt new file mode 100644 index 0000000..18388bf --- /dev/null +++ b/Security_disable.txt @@ -0,0 +1,41 @@ +#غیرفعال کردن لانچر اصلی +adb shell pm disable-user com.dw.launcher + +#غیرفعال کردن ui سیستمی و توپ کویک +adb shell pm disable-user plugin.sprd.systemuidynanavigationbar +adb shell pm disable-user com.android.systemui +adb shell reboot + +# غیرفعال کردن فروشگاه‌ها و سرویس‌های گوگل (جلوگیری از دانلود برنامه ناخواسته) +adb shell pm disable-user com.android.vending +adb shell pm disable-user com.google.android.gms +adb shell pm disable-user com.google.android.gsf + +# غیرفعال کردن برنامه‌های پرخطر (مرورگر، مدیریت فایل، انتقال فایل) +adb shell pm disable-user com.android.browser +adb shell pm disable-user com.sprd.fileexplorer +adb shell pm disable-user com.lenovo.anyshare.gps + +# غیرفعال کردن برنامه‌های چندرسانه‌ای و سرگرمی +adb shell pm disable-user com.google.android.youtube +adb shell pm disable-user com.android.gallery3d +adb shell pm disable-user com.android.musicfx +adb shell pm disable-user com.dw.music +adb shell pm disable-user com.dw.calendar +adb shell pm disable-user com.dw.calculator +adb shell pm disable-user com.dw.timer +adb shell pm disable-user com.dw.stopwatch +adb shell pm disable-user com.dw.deskclock + +# غیرفعال کردن برنامه‌های بی‌سیم و بلوتوث اضافی +adb shell pm disable-user com.sprd.wirelesstools +adb shell pm disable-user com.sprd.firewall + +#قطع کامل دوربین +adb shell pm disable-user com.android.camera2 +adb shell settings put secure camera_disabled 1 + +#غیرفعال کردن gps +adb shell pm disable-user com.android.location.fused +adb shell settings put secure location_providers_allowed -gps +adb shell settings put secure location_providers_allowed -network