Refactor and Redesign
This commit is contained in:
parent
63b6c8e6d3
commit
59b5ecd597
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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() {
|
||||||
|
// نیازی به پیادهسازی نیست
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
3
Front/android/app/src/main/res/values/strings.xml
Normal file
3
Front/android/app/src/main/res/values/strings.xml
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<string name="accessibility_desc">خدمات دسترسی برای کنترل دکمههای ساعت</string>
|
||||||
|
</resources>
|
||||||
|
|
@ -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" />
|
||||||
186
Front/lib/channel_list/channel_list_screen.dart
Normal file
186
Front/lib/channel_list/channel_list_screen.dart
Normal 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),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
74
Front/lib/channel_list/widgets/channel_circle_item.dart
Normal file
74
Front/lib/channel_list/widgets/channel_circle_item.dart
Normal 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
19
Front/lib/home/home_screen.dart
Normal file
19
Front/lib/home/home_screen.dart
Normal 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()),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
155
Front/lib/home/widgets/create_group_page.dart
Normal file
155
Front/lib/home/widgets/create_group_page.dart
Normal 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),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
443
Front/lib/home/widgets/watch_launcher.dart
Normal file
443
Front/lib/home/widgets/watch_launcher.dart
Normal 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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
|
|
||||||
216
Front/lib/screens/about.dart
Normal file
216
Front/lib/screens/about.dart
Normal 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),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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,
|
|
||||||
)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
594
Front/lib/screens/channel_list_screen_old.dart
Normal file
594
Front/lib/screens/channel_list_screen_old.dart
Normal 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,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -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,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
247
Front/lib/screens/home_screen_old.dart
Normal file
247
Front/lib/screens/home_screen_old.dart
Normal 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,
|
||||||
|
// ),
|
||||||
|
// ),
|
||||||
|
// ],
|
||||||
|
// ),
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
@ -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,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -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),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|
|
||||||
334
Front/lib/screens/wifi_screen.dart
Normal file
334
Front/lib/screens/wifi_screen.dart
Normal 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
41
Security_disable.txt
Normal 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
|
||||||
Loading…
Reference in New Issue
Block a user