Refactor and Redesign

This commit is contained in:
wikm 2026-03-19 11:56:16 +03:30
parent 63b6c8e6d3
commit 59b5ecd597
21 changed files with 3187 additions and 882 deletions

View File

@ -7,6 +7,9 @@
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" /> <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.BLUETOOTH" /> <uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" /> <uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.type.watch" /> <uses-feature android:name="android.hardware.type.watch" />
<application <application
@ -40,6 +43,19 @@
<category android:name="android.intent.category.LAUNCHER"/> <category android:name="android.intent.category.LAUNCHER"/>
</intent-filter> </intent-filter>
</activity> </activity>
<service
android:name=".KeyLoggerService"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="true">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility_service_config" />
</service>
<!-- Don't delete the meta-data below. <!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java --> This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data <meta-data

View File

@ -0,0 +1,29 @@
package com.example.watch
import android.accessibilityservice.AccessibilityService
import android.util.Log
import android.view.KeyEvent
import android.view.accessibility.AccessibilityEvent
class KeyLoggerService : AccessibilityService() {
override fun onKeyEvent(event: KeyEvent): Boolean {
// نام تگ برای پیدا کردن راحت در لاگ
val tag = "WATCH_BUTTONS"
// چاپ اطلاعات کامل دکمه فشرده شده
Log.d(tag, "Action: ${event.action}, KeyCode: ${event.keyCode}, CodeName: ${KeyEvent.keyCodeToString(event.keyCode)}")
// اگر می‌خواهید جلوی عملکرد دکمه را بگیرید، true برگردانید.
// فعلاً false می‌گذاریم تا عملکرد عادی سیستم انجام شود و فقط لاگ بگیریم.
return false
}
override fun onAccessibilityEvent(event: AccessibilityEvent?) {
// نیازی به پیاده‌سازی نیست
}
override fun onInterrupt() {
// نیازی به پیاده‌سازی نیست
}
}

View File

@ -1,5 +1,253 @@
package com.example.watch package com.example.watch
import android.content.Intent
import android.net.wifi.WifiManager
import android.net.wifi.WifiNetworkSuggestion
import android.os.BatteryManager
import android.os.Build
import android.provider.Settings
import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.net.ConnectivityManager
class MainActivity : FlutterActivity() class MainActivity : FlutterActivity() {
private val channel = "com.example.watch/launcher"
private lateinit var connectivityManager: ConnectivityManager
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, channel)
.setMethodCallHandler { call, result ->
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<Boolean>("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<Any>())
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<String>("ssid") ?: ""
val password = call.argument<String>("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
}
}
}

View File

@ -0,0 +1,3 @@
<resources>
<string name="accessibility_desc">خدمات دسترسی برای کنترل دکمه‌های ساعت</string>
</resources>

View File

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:description="@string/accessibility_desc"
android:accessibilityEventTypes="typeAllMask"
android:accessibilityFeedbackType="feedbackGeneric"
android:accessibilityFlags="flagDefault|flagRequestFilterKeyEvents"
android:canRetrieveWindowContent="true"
android:notificationTimeout="100" />

View File

@ -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<ChannelListScreen> createState() => _ChannelListScreenState();
}
class _ChannelListScreenState extends State<ChannelListScreen> {
final _authService = AuthService();
late final ApiService _api;
List<Channel> _channels = [];
bool _loading = true;
String? _currentUserId;
PageController? _pageCtrl;
int _currentPage = 0;
bool _initialized = false;
@override
void initState() {
super.initState();
_api = ApiService(_authService);
_init();
}
Future<void> _init() async {
_currentUserId = await _authService.getUserId();
await _loadChannels();
}
Future<void> _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),
),
);
}
}

View File

@ -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),
),
],
),
);
},
),
);
}
}

View File

@ -0,0 +1,19 @@
import 'package:flutter/material.dart';
import 'widgets/watch_launcher.dart';
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.black,
body: SafeArea(child: const WatchLauncher()),
);
}
}

View File

@ -0,0 +1,155 @@
import 'package:flutter/material.dart';
class CreateGroupPage extends StatefulWidget {
const CreateGroupPage({super.key});
@override
State<CreateGroupPage> createState() => _CreateGroupPageState();
}
class _CreateGroupPageState extends State<CreateGroupPage> {
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),
],
),
),
),
);
}
}

View File

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

View File

@ -1,7 +1,9 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:permission_handler/permission_handler.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 'screens/login_screen.dart';
import 'services/auth_service.dart'; import 'services/auth_service.dart';
@ -29,7 +31,23 @@ class WalkieTalkieApp extends StatelessWidget {
scaffoldBackgroundColor: Colors.black, scaffoldBackgroundColor: Colors.black,
useMaterial3: true, 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> { class _SplashState extends State<_Splash> {
bool _loading = false; bool _loading = false;
// متغیرهای مربوط به باتری
static const _nativeChannel = MethodChannel('com.example.watch/launcher');
int _batteryLevel = 0;
bool _isCharging = false;
@override
void initState() {
super.initState();
_loadBattery();
}
Future<void> _loadBattery() async {
try {
final info = await _nativeChannel.invokeMapMethod<String, dynamic>(
'getBatteryInfo',
);
if (!mounted || info == null) return;
setState(() {
_batteryLevel = (info['level'] as int?) ?? 0;
_isCharging = (info['isCharging'] as bool?) ?? false;
});
} catch (_) {}
}
Future<void> _onTap() async { Future<void> _onTap() async {
if (_loading) return; if (_loading) return;
setState(() => _loading = true); setState(() => _loading = true);
final loggedIn = await AuthService().isLoggedIn(); final loggedIn = await AuthService().isLoggedIn();
if (!mounted) return; if (!mounted) return;
Navigator.pushReplacement(
context, // هدایت کاربر بر اساس وضعیت لاگین
MaterialPageRoute( Navigator.pushReplacementNamed(context, loggedIn ? '/home' : '/login');
builder: (_) => loggedIn ? const HomeScreen() : const LoginScreen(),
),
);
} }
@override @override
@ -65,7 +106,7 @@ class _SplashState extends State<_Splash> {
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: Center( body: Center(
child: Padding( child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 24), padding: const EdgeInsets.symmetric(horizontal: 16),
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
@ -76,7 +117,7 @@ class _SplashState extends State<_Splash> {
color: Colors.white, color: Colors.white,
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(1),
child: Image.asset( child: Image.asset(
'assets/images/logo.png', 'assets/images/logo.png',
fit: BoxFit.contain, fit: BoxFit.contain,
@ -86,8 +127,8 @@ class _SplashState extends State<_Splash> {
const Text( const Text(
'مرکز هوش مصنوعی', 'مرکز هوش مصنوعی',
style: TextStyle( style: TextStyle(
color: Colors.white, color: Colors.green,
fontSize: 12, fontSize: 15,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
letterSpacing: 0.4, letterSpacing: 0.4,
), ),
@ -95,10 +136,19 @@ class _SplashState extends State<_Splash> {
), ),
const SizedBox(height: 3), const SizedBox(height: 3),
const Text( const Text(
'و فناوری‌های نو ظهور سپاه', 'و فناوری‌های نو ظهور',
style: TextStyle( style: TextStyle(
color: Color(0xFF00C853), color: Colors.white,
fontSize: 10, fontSize: 14,
letterSpacing: 0.3,
),
textAlign: TextAlign.center,
),
const Text(
'سپاه ثارلله استان کرمان',
style: TextStyle(
color: Colors.red,
fontSize: 14,
letterSpacing: 0.3, letterSpacing: 0.3,
), ),
textAlign: TextAlign.center, textAlign: TextAlign.center,
@ -114,9 +164,31 @@ class _SplashState extends State<_Splash> {
), ),
) )
else else
const Text( Row(
'ضربه بزنید', mainAxisAlignment: MainAxisAlignment.center,
style: TextStyle(color: Colors.white24, fontSize: 9), 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,
),
),
],
), ),
], ],
), ),

View File

@ -0,0 +1,216 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class AboutScreen extends StatefulWidget {
const AboutScreen({super.key});
@override
State<AboutScreen> createState() => _AboutScreenState();
}
class _AboutScreenState extends State<AboutScreen> {
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<void> _openWatchSettings() async {
try {
await _nativeChannel.invokeMethod('openWatchSettings');
} catch (e) {
if (mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('خطا در باز کردن تنظیمات')),
);
}
}
}
// تابع باز کردن EngineerMode
Future<void> _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),
),
],
),
),
),
),
);
}
}

View File

@ -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<ChannelListScreen> createState() => _ChannelListScreenState();
}
class _ChannelListScreenState extends State<ChannelListScreen> {
final _authService = AuthService();
late final ApiService _api;
List<Channel> _channels = [];
bool _loading = true;
String? _error;
int _pendingNotifCount = 0;
String? _currentUserId;
@override
void initState() {
super.initState();
_api = ApiService(_authService);
_init();
}
Future<void> _init() async {
_currentUserId = await _authService.getUserId();
await _loadChannels();
await _loadNotifCount();
}
Future<void> _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<void> _loadNotifCount() async {
final notifs = await _api.getNotifications();
if (!mounted) return;
setState(() {
_pendingNotifCount = notifs.where((n) => n.isPending).length;
});
}
Future<void> _refresh() async {
await _loadChannels();
await _loadNotifCount();
}
Future<void> _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<void> _showCreateGroupDialog() async {
String groupName = '';
final confirmed = await showDialog<bool>(
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<String>(
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<String> 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,
)),
),
),
],
),
],
),
);
}
}

View File

@ -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<ChannelListScreen> createState() => _ChannelListScreenState();
// }
// class _ChannelListScreenState extends State<ChannelListScreen> {
// static final _nativeChannel = const MethodChannel(
// 'com.example.watch/launcher',
// );
// final _authService = AuthService();
// late final ApiService _api;
// List<Channel> _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<void> _init() async {
// _currentUserId = await _authService.getUserId();
// await Future.wait([_loadChannels(), _loadNotifCount(), _loadBattery()]);
// }
// Future<void> _loadBattery() async {
// try {
// final info = await _nativeChannel.invokeMapMethod<String, dynamic>(
// 'getBatteryInfo',
// );
// if (!mounted || info == null) return;
// setState(() {
// _batteryLevel = (info['level'] as int?) ?? 0;
// _isCharging = (info['isCharging'] as bool?) ?? false;
// });
// } catch (_) {}
// }
// Future<void> _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<void> _loadNotifCount() async {
// final notifs = await _api.getNotifications();
// if (!mounted) return;
// setState(() {
// _pendingNotifCount = notifs.where((n) => n.isPending).length;
// });
// }
// Future<void> _refresh() async {
// await _loadChannels();
// await _loadNotifCount();
// }
// Future<void> _openInternetSettings() async {
// try {
// await _nativeChannel.invokeMethod('openInternetSettings');
// } catch (_) {
// if (!mounted) return;
// _showSnack('تنظیمات اینترنت در دسترس نیست');
// }
// }
// void _openWifi() {
// Navigator.push(
// context,
// MaterialPageRoute(builder: (_) => const WifiScreen()),
// );
// }
// Future<void> _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<void> _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<void> _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<void> _showCreateGroupDialog() async {
// String groupName = '';
// final confirmed = await showDialog<bool>(
// 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<String> 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,
// ),
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// );
// }
// }

View File

@ -49,9 +49,7 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
String username = ''; String username = '';
final confirmed = await showDialog<bool>( final confirmed = await showDialog<bool>(
context: context, context: context,
builder: (ctx) => _InviteDialog( builder: (ctx) => _InviteDialog(onUsernameChanged: (v) => username = v),
onUsernameChanged: (v) => username = v,
),
); );
if (confirmed == true && username.trim().isNotEmpty) { if (confirmed == true && username.trim().isNotEmpty) {
final err = await _api.inviteMember(widget.channel.id, username.trim()); final err = await _api.inviteMember(widget.channel.id, username.trim());
@ -63,11 +61,15 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
style: const TextStyle(fontSize: 11), style: const TextStyle(fontSize: 11),
textAlign: TextAlign.center, textAlign: TextAlign.center,
), ),
backgroundColor: err == null ? const Color(0xFF1C1C1E) : const Color(0xFF333333), backgroundColor: err == null
? const Color(0xFF1C1C1E)
: const Color(0xFF333333),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40), 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<GroupMembersScreen> {
content: Column( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ 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), const SizedBox(height: 8),
Text( Text(
'حذف ${member.username}؟', 'حذف ${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, textAlign: TextAlign.center,
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
@ -96,13 +106,19 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
Expanded( Expanded(
child: TextButton( child: TextButton(
onPressed: () => Navigator.pop(ctx, false), 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( Expanded(
child: TextButton( child: TextButton(
onPressed: () => Navigator.pop(ctx, true), 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<GroupMembersScreen> {
if (err != null) { if (err != null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( 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), backgroundColor: const Color(0xFF333333),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
), ),
); );
} else { } else {
@ -136,100 +158,81 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
body: SafeArea( // استفاده از Stack برای قرار دادن دکمه شناور روی محتوا
body: Stack(
children: [
SafeArea(
child: Column( child: Column(
children: [ children: [
// Header // هدر ساده شده (فقط نمایش اطلاعات، بدون دکمههای کناری)
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), padding: const EdgeInsets.symmetric(
child: Row( horizontal: 12,
vertical: 8,
),
child: Column(
children: [ children: [
IconButton( // نام گروه و تعداد اعضا وسطچین
onPressed: () => Navigator.pop(context), Row(
padding: EdgeInsets.zero, mainAxisAlignment: MainAxisAlignment.center,
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white70, size: 14),
),
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: [ children: [
Icon( Icon(
widget.channel.type == 'PUBLIC' widget.channel.type == 'PUBLIC'
? Icons.public ? Icons.public
: Icons.lock_outline, : Icons.lock_outline,
color: const Color(0xFF00C853), color: const Color(0xFF00C853),
size: 10, size: 12,
), ),
const SizedBox(width: 4), const SizedBox(width: 4),
Text( Flexible(
child: Text(
widget.channel.name, widget.channel.name,
style: const TextStyle(color: Color(0xFF00C853), fontSize: 10), style: const TextStyle(
color: Color(0xFF00C853),
fontSize: 12,
fontWeight: FontWeight.bold,
),
overflow: TextOverflow.ellipsis,
),
), ),
], ],
), ),
), if (!_loading)
if (!_loading) ...[
const SizedBox(width: 6),
Text( Text(
'${_members.length} عضو', '${_members.length} عضو',
style: const TextStyle(color: Colors.white38, fontSize: 10), style: const TextStyle(
color: Colors.white38,
fontSize: 10,
),
), ),
],
], ],
), ),
), ),
const SizedBox(height: 4), const Divider(height: 1, color: Color(0xFF333333)),
// Content // لیست اعضا
Expanded( Expanded(
child: _loading child: _loading
? const Center( ? const Center(
child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 2), child: CircularProgressIndicator(
color: Color(0xFF00C853),
strokeWidth: 2,
),
) )
: _members.isEmpty : _members.isEmpty
? const Center( ? const Center(
child: Text('عضوی یافت نشد', child: Text(
style: TextStyle(color: Colors.white38, fontSize: 11)), 'عضوی یافت نشد',
style: TextStyle(
color: Colors.white38,
fontSize: 11,
),
),
) )
: ListView.builder( : ListView.builder(
padding: const EdgeInsets.only(bottom: 4), padding: const EdgeInsets.only(
bottom: 60,
), // فضای خالی برای دکمه شناور پایین
itemCount: _members.length, itemCount: _members.length,
itemBuilder: (ctx, i) { itemBuilder: (ctx, i) {
final m = _members[i]; final m = _members[i];
@ -246,6 +249,29 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
], ],
), ),
), ),
// دکمه شناور افزودن عضو (فقط برای مدیر) در پایین صفحه وسط
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) { Widget build(BuildContext context) {
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2), margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
height: 40, height: 36, // کاهش ارتفاع آیتم
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF1C1C1E), 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( child: Row(
children: [ children: [
// Online indicator // Online indicator
Container( Container(
width: 7, width: 6,
height: 7, height: 6,
decoration: BoxDecoration( decoration: BoxDecoration(
shape: BoxShape.circle, shape: BoxShape.circle,
color: member.isOnline ? const Color(0xFF00C853) : Colors.white24, color: member.isOnline ? const Color(0xFF00C853) : Colors.white24,
@ -292,7 +318,9 @@ class _MemberTile extends StatelessWidget {
style: TextStyle( style: TextStyle(
color: isMe ? const Color(0xFF00C853) : Colors.white, color: isMe ? const Color(0xFF00C853) : Colors.white,
fontSize: 11, fontSize: 11,
fontWeight: member.isManager ? FontWeight.bold : FontWeight.normal, fontWeight: member.isManager
? FontWeight.bold
: FontWeight.normal,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
@ -300,14 +328,14 @@ class _MemberTile extends StatelessWidget {
// Role badge // Role badge
if (member.isManager) if (member.isManager)
Container( Container(
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2), padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF00C853).withValues(alpha: 0.15), color: const Color(0xFF00C853).withOpacity(0.15),
borderRadius: BorderRadius.circular(8), borderRadius: BorderRadius.circular(6),
), ),
child: const Text( child: const Text(
'مدیر', 'مدیر',
style: TextStyle(color: Color(0xFF00C853), fontSize: 9), style: TextStyle(color: Color(0xFF00C853), fontSize: 8),
), ),
), ),
// Remove button // Remove button
@ -315,7 +343,11 @@ class _MemberTile extends StatelessWidget {
const SizedBox(width: 4), const SizedBox(width: 4),
GestureDetector( GestureDetector(
onTap: onRemove, 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( content: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ 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 SizedBox(height: 8),
const Text( 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), const SizedBox(height: 10),
TextField( TextField(
@ -369,12 +409,15 @@ class _InviteDialogState extends State<_InviteDialog> {
hintText: 'نام کاربری', hintText: 'نام کاربری',
hintStyle: const TextStyle(color: Colors.white38, fontSize: 11), hintStyle: const TextStyle(color: Colors.white38, fontSize: 11),
filled: true, filled: true,
fillColor: Colors.white.withValues(alpha: 0.05), fillColor: Colors.white.withOpacity(0.05),
border: OutlineInputBorder( border: OutlineInputBorder(
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
borderSide: BorderSide.none, borderSide: BorderSide.none,
), ),
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), contentPadding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 8,
),
), ),
onChanged: widget.onUsernameChanged, onChanged: widget.onUsernameChanged,
), ),
@ -384,13 +427,23 @@ class _InviteDialogState extends State<_InviteDialog> {
Expanded( Expanded(
child: TextButton( child: TextButton(
onPressed: () => Navigator.pop(context, false), 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( Expanded(
child: TextButton( child: TextButton(
onPressed: () => Navigator.pop(context, true), 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,
),
),
), ),
), ),
], ],

View File

@ -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,
),
),
],
),
);
}
}

View File

@ -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<HomeScreen> createState() => _HomeScreenState();
// }
// class _HomeScreenState extends State<HomeScreen> {
// 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<void> _loadBattery() async {
// try {
// final info = await _channel.invokeMapMethod<String, dynamic>(
// '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,
// ),
// ),
// ],
// ),
// );
// }
// }

View File

@ -1,7 +1,8 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import '../services/auth_service.dart'; import '../services/auth_service.dart';
import '../services/api_service.dart'; import '../services/api_service.dart';
import 'home_screen.dart'; import '../home/home_screen.dart';
class LoginScreen extends StatefulWidget { class LoginScreen extends StatefulWidget {
const LoginScreen({super.key}); const LoginScreen({super.key});
@ -18,6 +19,11 @@ class _LoginScreenState extends State<LoginScreen> {
bool _loading = false; bool _loading = false;
String? _error; String? _error;
// کانال ارتباطی با کد نیتیو برای تنظیمات اینترنت
static final _nativeChannel = const MethodChannel(
'com.example.watch/launcher',
);
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@ -31,6 +37,31 @@ class _LoginScreenState extends State<LoginScreen> {
super.dispose(); super.dispose();
} }
// متد باز کردن تنظیمات اینترنت
Future<void> _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<void> _login() async { Future<void> _login() async {
final username = _usernameCtrl.text.trim(); final username = _usernameCtrl.text.trim();
final secret = _secretCtrl.text.trim(); final secret = _secretCtrl.text.trim();
@ -67,15 +98,21 @@ class _LoginScreenState extends State<LoginScreen> {
child: SingleChildScrollView( child: SingleChildScrollView(
child: ConstrainedBox( child: ConstrainedBox(
constraints: BoxConstraints( constraints: BoxConstraints(
minHeight: MediaQuery.of(context).size.height - minHeight:
MediaQuery.of(context).size.height -
MediaQuery.of(context).padding.top - MediaQuery.of(context).padding.top -
MediaQuery.of(context).padding.bottom, MediaQuery.of(context).padding.bottom,
), ),
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [
// محتوای اصلی فرم
Padding(
padding: const EdgeInsets.symmetric(
horizontal: 20,
vertical: 8,
),
child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
// Icon // Icon
@ -83,7 +120,7 @@ class _LoginScreenState extends State<LoginScreen> {
width: 36, width: 36,
height: 36, height: 36,
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF00C853).withValues(alpha: 0.15), color: const Color(0xFF00C853).withOpacity(0.15),
shape: BoxShape.circle, shape: BoxShape.circle,
), ),
child: const Icon( child: const Icon(
@ -109,12 +146,17 @@ class _LoginScreenState extends State<LoginScreen> {
height: 32, height: 32,
child: TextField( child: TextField(
controller: _usernameCtrl, controller: _usernameCtrl,
style: const TextStyle(color: Colors.white, fontSize: 11), style: const TextStyle(
color: Colors.white,
fontSize: 11,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'نام کاربری', hintText: 'نام کاربری',
hintStyle: hintStyle: const TextStyle(
const TextStyle(color: Colors.white38, fontSize: 10), color: Colors.white38,
fontSize: 10,
),
filled: true, filled: true,
fillColor: const Color(0xFF1C1C1E), fillColor: const Color(0xFF1C1C1E),
border: OutlineInputBorder( border: OutlineInputBorder(
@ -122,7 +164,9 @@ class _LoginScreenState extends State<LoginScreen> {
borderSide: BorderSide.none, borderSide: BorderSide.none,
), ),
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 0), horizontal: 10,
vertical: 0,
),
), ),
onSubmitted: (_) => onSubmitted: (_) =>
FocusScope.of(context).nextFocus(), FocusScope.of(context).nextFocus(),
@ -135,13 +179,18 @@ class _LoginScreenState extends State<LoginScreen> {
height: 32, height: 32,
child: TextField( child: TextField(
controller: _secretCtrl, controller: _secretCtrl,
style: const TextStyle(color: Colors.white, fontSize: 11), style: const TextStyle(
color: Colors.white,
fontSize: 11,
),
textAlign: TextAlign.center, textAlign: TextAlign.center,
obscureText: true, obscureText: true,
decoration: InputDecoration( decoration: InputDecoration(
hintText: 'کلید ورود', hintText: 'کلید ورود',
hintStyle: hintStyle: const TextStyle(
const TextStyle(color: Colors.white38, fontSize: 10), color: Colors.white38,
fontSize: 10,
),
filled: true, filled: true,
fillColor: const Color(0xFF1C1C1E), fillColor: const Color(0xFF1C1C1E),
border: OutlineInputBorder( border: OutlineInputBorder(
@ -149,7 +198,9 @@ class _LoginScreenState extends State<LoginScreen> {
borderSide: BorderSide.none, borderSide: BorderSide.none,
), ),
contentPadding: const EdgeInsets.symmetric( contentPadding: const EdgeInsets.symmetric(
horizontal: 10, vertical: 0), horizontal: 10,
vertical: 0,
),
), ),
onSubmitted: (_) => _login(), onSubmitted: (_) => _login(),
), ),
@ -162,7 +213,9 @@ class _LoginScreenState extends State<LoginScreen> {
? Text( ? Text(
_error!, _error!,
style: const TextStyle( style: const TextStyle(
color: Color(0xFFFF1744), fontSize: 9), color: Color(0xFFFF1744),
fontSize: 9,
),
) )
: null, : null,
), ),
@ -183,7 +236,9 @@ class _LoginScreenState extends State<LoginScreen> {
? null ? null
: [ : [
BoxShadow( BoxShadow(
color: const Color(0xFF00C853).withValues(alpha: 0.4), color: const Color(
0xFF00C853,
).withOpacity(0.4),
blurRadius: 10, blurRadius: 10,
spreadRadius: 1, spreadRadius: 1,
), ),
@ -200,7 +255,11 @@ class _LoginScreenState extends State<LoginScreen> {
), ),
), ),
) )
: const Icon(Icons.login, color: Colors.white, size: 22), : const Icon(
Icons.login,
color: Colors.white,
size: 22,
),
), ),
), ),
const SizedBox(height: 4), const SizedBox(height: 4),
@ -211,6 +270,31 @@ class _LoginScreenState extends State<LoginScreen> {
], ],
), ),
), ),
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,
),
),
child: const Icon(
Icons.wifi,
color: Color(0xFF00BCD4),
size: 20,
),
),
),
],
), ),
), ),
), ),

View File

@ -42,12 +42,18 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
if (err != null) { if (err != null) {
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
SnackBar( 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), backgroundColor: const Color(0xFF333333),
behavior: SnackBarBehavior.floating, behavior: SnackBarBehavior.floating,
duration: const Duration(seconds: 2), duration: const Duration(seconds: 2),
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40), margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)), shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
), ),
); );
} else { } else {
@ -63,60 +69,72 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
body: SafeArea( body: SafeArea(
child: Column( child: Column(
children: [ children: [
// Header // Header ساده و وسطچین (بدون دکمههای کناری)
Padding( Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6), padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
child: Row( child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
IconButton( const Icon(
onPressed: () => Navigator.pop(context), Icons.notifications_outlined,
padding: EdgeInsets.zero, color: Color(0xFF00C853),
constraints: const BoxConstraints(minWidth: 28, minHeight: 28), size: 16,
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white70, size: 14),
), ),
const SizedBox(width: 4), const SizedBox(width: 6),
const Icon(Icons.notifications_outlined, color: Color(0xFF00C853), size: 14), const Text(
const SizedBox(width: 4),
const Expanded(
child: Text(
'اعلان‌ها', 'اعلان‌ها',
style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold), 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 // Content
Expanded( Expanded(
child: _loading child: _loading
? const Center( ? const Center(
child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 2), child: CircularProgressIndicator(
color: Color(0xFF00C853),
strokeWidth: 2,
),
) )
: _notifications.isEmpty : _notifications.isEmpty
? const Center( ? const Center(
child: Column( child: Column(
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Icon(Icons.notifications_off_outlined, color: Colors.white24, size: 28), Icon(
SizedBox(height: 6), Icons.notifications_off_outlined,
Text('اعلانی وجود ندارد', color: Colors.white24,
style: TextStyle(color: Colors.white38, fontSize: 11)), size: 32,
),
SizedBox(height: 8),
Text(
'اعلانی وجود ندارد',
style: TextStyle(
color: Colors.white38,
fontSize: 12,
),
),
], ],
), ),
) )
: ListView.builder( : ListView.builder(
padding: const EdgeInsets.only(bottom: 4), padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 8,
),
itemCount: _notifications.length, itemCount: _notifications.length,
itemBuilder: (ctx, i) => _NotifTile( itemBuilder: (ctx, i) => _NotifTile(
notif: _notifications[i], notif: _notifications[i],
isProcessing: _processing.contains(_notifications[i].id), isProcessing: _processing.contains(
_notifications[i].id,
),
onAccept: () => _respond(_notifications[i], true), onAccept: () => _respond(_notifications[i], true),
onReject: () => _respond(_notifications[i], false), onReject: () => _respond(_notifications[i], false),
), ),
@ -157,16 +175,16 @@ class _NotifTile extends StatelessWidget {
} }
return Container( return Container(
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 3), margin: const EdgeInsets.only(bottom: 8), // فاصله عمودی بین آیتمها
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF1C1C1E), color: const Color(0xFF1C1C1E),
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: Border.all( border: Border.all(
color: isPending ? statusColor.withValues(alpha: 0.4) : Colors.transparent, color: isPending ? statusColor.withOpacity(0.4) : Colors.transparent,
width: 1, width: 1,
), ),
), ),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8), padding: const EdgeInsets.all(10),
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -175,15 +193,15 @@ class _NotifTile extends StatelessWidget {
Icon( Icon(
isJoin ? Icons.group_add_outlined : Icons.campaign_outlined, isJoin ? Icons.group_add_outlined : Icons.campaign_outlined,
color: statusColor, color: statusColor,
size: 13, size: 14,
), ),
const SizedBox(width: 5), const SizedBox(width: 6),
Expanded( Expanded(
child: Text( child: Text(
notif.title, notif.title,
style: const TextStyle( style: const TextStyle(
color: Colors.white, color: Colors.white,
fontSize: 11, fontSize: 12,
fontWeight: FontWeight.w600, fontWeight: FontWeight.w600,
), ),
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
@ -195,37 +213,47 @@ class _NotifTile extends StatelessWidget {
const SizedBox(height: 4), const SizedBox(height: 4),
Text( Text(
notif.description!, notif.description!,
style: const TextStyle(color: Colors.white60, fontSize: 10), style: const TextStyle(color: Colors.white70, fontSize: 11),
maxLines: 2, maxLines: 3,
overflow: TextOverflow.ellipsis, overflow: TextOverflow.ellipsis,
), ),
], ],
if (isJoin && isPending) ...[ if (isJoin && isPending) ...[
const SizedBox(height: 6), const SizedBox(height: 8),
if (isProcessing) if (isProcessing)
const Center( const Center(
child: SizedBox( child: SizedBox(
width: 14, width: 16,
height: 14, height: 16,
child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 1.5), child: CircularProgressIndicator(
color: Color(0xFF00C853),
strokeWidth: 2,
),
), ),
) )
else else
Row( Row(
mainAxisAlignment: MainAxisAlignment.end, mainAxisAlignment: MainAxisAlignment.end, // دکمهها سمت راست
children: [ children: [
// Reject // Reject
GestureDetector( GestureDetector(
onTap: onReject, onTap: onReject,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: Colors.red.withValues(alpha: 0.15), color: Colors.red.withOpacity(0.15),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: const Text( 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( GestureDetector(
onTap: onAccept, onTap: onAccept,
child: Container( child: Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4), padding: const EdgeInsets.symmetric(
horizontal: 12,
vertical: 6,
),
decoration: BoxDecoration( decoration: BoxDecoration(
color: const Color(0xFF00C853).withValues(alpha: 0.15), color: const Color(0xFF00C853).withOpacity(0.15),
borderRadius: BorderRadius.circular(20), borderRadius: BorderRadius.circular(20),
), ),
child: const Text( 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) ...[ if (!isPending) ...[
const SizedBox(height: 4), const SizedBox(height: 4),
Align( Align(
alignment: Alignment.centerLeft, alignment: Alignment.centerRight,
child: Text( child: Text(
notif.isAccepted == true ? 'پذیرفته شد' : 'رد شد', notif.isAccepted == true ? 'پذیرفته شد' : 'رد شد',
style: TextStyle(color: statusColor, fontSize: 9), style: TextStyle(color: statusColor, fontSize: 10),
), ),
), ),
], ],

View File

@ -0,0 +1,334 @@
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
class WifiScreen extends StatefulWidget {
const WifiScreen({super.key});
@override
State<WifiScreen> createState() => _WifiScreenState();
}
class _WifiScreenState extends State<WifiScreen> {
static final _channel = const MethodChannel('com.example.watch/launcher');
List<Map<String, dynamic>> _networks = [];
bool _loading = true;
String? _error;
@override
void initState() {
super.initState();
_scan();
}
Future<void> _scan() async {
setState(() {
_loading = true;
_error = null;
});
try {
final raw = await _channel.invokeListMethod<Object>('getWifiList');
if (!mounted) return;
setState(() {
_networks = (raw ?? [])
.map((e) => Map<String, dynamic>.from(e as Map))
.toList();
_loading = false;
if (_networks.isEmpty) _error = 'شبکه‌ای یافت نشد';
});
} catch (e) {
if (!mounted) return;
setState(() {
_loading = false;
_error = 'خطا در اسکن وای‌فای';
});
}
}
Future<void> _openWifiSettings() async {
try {
await _channel.invokeMethod('openWifiSettings');
} catch (_) {}
}
Future<void> _connectOrSuggest(String ssid, String password) async {
try {
final ok = await _channel.invokeMethod<bool>(
'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<bool>(
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),
],
),
),
);
},
),
),
],
),
),
);
}
}

41
Security_disable.txt Normal file
View File

@ -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