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.BLUETOOTH" />
|
||||
<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" />
|
||||
|
||||
<application
|
||||
|
|
@ -40,6 +43,19 @@
|
|||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</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.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<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
|
||||
|
||||
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.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/services.dart';
|
||||
import 'package:permission_handler/permission_handler.dart';
|
||||
|
||||
import 'screens/home_screen.dart';
|
||||
import 'home/home_screen.dart';
|
||||
import 'channel_list/channel_list_screen.dart';
|
||||
import 'screens/login_screen.dart';
|
||||
import 'services/auth_service.dart';
|
||||
|
||||
|
|
@ -29,7 +31,23 @@ class WalkieTalkieApp extends StatelessWidget {
|
|||
scaffoldBackgroundColor: Colors.black,
|
||||
useMaterial3: true,
|
||||
),
|
||||
home: const _Splash(),
|
||||
// استفاده از onGenerateRoute برای مدیریت مسیرها
|
||||
onGenerateRoute: (settings) {
|
||||
switch (settings.name) {
|
||||
case '/':
|
||||
return MaterialPageRoute(builder: (_) => const _Splash());
|
||||
case '/home':
|
||||
return MaterialPageRoute(builder: (_) => const HomeScreen());
|
||||
case '/channel_list':
|
||||
return MaterialPageRoute(builder: (_) => const ChannelListScreen());
|
||||
case '/login':
|
||||
return MaterialPageRoute(builder: (_) => const LoginScreen());
|
||||
default:
|
||||
return MaterialPageRoute(builder: (_) => const _Splash());
|
||||
}
|
||||
},
|
||||
// صفحه پیشفرض برنامه (اسپلش)
|
||||
initialRoute: '/',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -44,17 +62,40 @@ class _Splash extends StatefulWidget {
|
|||
class _SplashState extends State<_Splash> {
|
||||
bool _loading = false;
|
||||
|
||||
// متغیرهای مربوط به باتری
|
||||
static const _nativeChannel = MethodChannel('com.example.watch/launcher');
|
||||
int _batteryLevel = 0;
|
||||
bool _isCharging = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_loadBattery();
|
||||
}
|
||||
|
||||
Future<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 {
|
||||
if (_loading) return;
|
||||
setState(() => _loading = true);
|
||||
|
||||
final loggedIn = await AuthService().isLoggedIn();
|
||||
|
||||
if (!mounted) return;
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => loggedIn ? const HomeScreen() : const LoginScreen(),
|
||||
),
|
||||
);
|
||||
|
||||
// هدایت کاربر بر اساس وضعیت لاگین
|
||||
Navigator.pushReplacementNamed(context, loggedIn ? '/home' : '/login');
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
@ -65,7 +106,7 @@ class _SplashState extends State<_Splash> {
|
|||
backgroundColor: Colors.black,
|
||||
body: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
|
|
@ -76,7 +117,7 @@ class _SplashState extends State<_Splash> {
|
|||
color: Colors.white,
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
padding: const EdgeInsets.all(10),
|
||||
padding: const EdgeInsets.all(1),
|
||||
child: Image.asset(
|
||||
'assets/images/logo.png',
|
||||
fit: BoxFit.contain,
|
||||
|
|
@ -86,8 +127,8 @@ class _SplashState extends State<_Splash> {
|
|||
const Text(
|
||||
'مرکز هوش مصنوعی',
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
color: Colors.green,
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.bold,
|
||||
letterSpacing: 0.4,
|
||||
),
|
||||
|
|
@ -95,10 +136,19 @@ class _SplashState extends State<_Splash> {
|
|||
),
|
||||
const SizedBox(height: 3),
|
||||
const Text(
|
||||
'و فناوریهای نو ظهور سپاه',
|
||||
'و فناوریهای نو ظهور',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF00C853),
|
||||
fontSize: 10,
|
||||
color: Colors.white,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const Text(
|
||||
'سپاه ثارلله استان کرمان',
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 14,
|
||||
letterSpacing: 0.3,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
|
|
@ -114,9 +164,31 @@ class _SplashState extends State<_Splash> {
|
|||
),
|
||||
)
|
||||
else
|
||||
const Text(
|
||||
'ضربه بزنید',
|
||||
style: TextStyle(color: Colors.white24, fontSize: 9),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
_isCharging ? Icons.bolt : Icons.battery_std,
|
||||
color: _isCharging
|
||||
? const Color(0xFF00C853)
|
||||
: (_batteryLevel <= 15
|
||||
? const Color(0xFFFF1744)
|
||||
: Colors.white24),
|
||||
size: 10,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
'$_batteryLevel%',
|
||||
style: TextStyle(
|
||||
color: _isCharging
|
||||
? const Color(0xFF00C853)
|
||||
: (_batteryLevel <= 15
|
||||
? const Color(0xFFFF1744)
|
||||
: Colors.white24),
|
||||
fontSize: 9,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
|
|
|
|||
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 = '';
|
||||
final confirmed = await showDialog<bool>(
|
||||
context: context,
|
||||
builder: (ctx) => _InviteDialog(
|
||||
onUsernameChanged: (v) => username = v,
|
||||
),
|
||||
builder: (ctx) => _InviteDialog(onUsernameChanged: (v) => username = v),
|
||||
);
|
||||
if (confirmed == true && username.trim().isNotEmpty) {
|
||||
final err = await _api.inviteMember(widget.channel.id, username.trim());
|
||||
|
|
@ -63,11 +61,15 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
|
|||
style: const TextStyle(fontSize: 11),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
backgroundColor: err == null ? const Color(0xFF1C1C1E) : const Color(0xFF333333),
|
||||
backgroundColor: err == null
|
||||
? const Color(0xFF1C1C1E)
|
||||
: const Color(0xFF333333),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 2),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
|
@ -83,11 +85,19 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
|
|||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.person_remove_outlined, color: Colors.red, size: 28),
|
||||
const Icon(
|
||||
Icons.person_remove_outlined,
|
||||
color: Colors.red,
|
||||
size: 28,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Text(
|
||||
'حذف ${member.username}؟',
|
||||
style: const TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
|
|
@ -96,13 +106,19 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
|
|||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, false),
|
||||
child: const Text('انصراف', style: TextStyle(color: Colors.white54, fontSize: 11)),
|
||||
child: const Text(
|
||||
'انصراف',
|
||||
style: TextStyle(color: Colors.white54, fontSize: 11),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(ctx, true),
|
||||
child: const Text('حذف', style: TextStyle(color: Colors.red, fontSize: 11)),
|
||||
child: const Text(
|
||||
'حذف',
|
||||
style: TextStyle(color: Colors.red, fontSize: 11),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
@ -118,12 +134,18 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
|
|||
if (err != null) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(err, style: const TextStyle(fontSize: 11), textAlign: TextAlign.center),
|
||||
content: Text(
|
||||
err,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
backgroundColor: const Color(0xFF333333),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 2),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
|
@ -136,100 +158,81 @@ class _GroupMembersScreenState extends State<GroupMembersScreen> {
|
|||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: SafeArea(
|
||||
// استفاده از Stack برای قرار دادن دکمه شناور روی محتوا
|
||||
body: Stack(
|
||||
children: [
|
||||
SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// Header
|
||||
// هدر ساده شده (فقط نمایش اطلاعات، بدون دکمههای کناری)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
child: Row(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
|
||||
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white70, size: 14),
|
||||
),
|
||||
const 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,
|
||||
// نام گروه و تعداد اعضا وسطچین
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Icon(
|
||||
widget.channel.type == 'PUBLIC'
|
||||
? Icons.public
|
||||
: Icons.lock_outline,
|
||||
color: const Color(0xFF00C853),
|
||||
size: 10,
|
||||
size: 12,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
Text(
|
||||
Flexible(
|
||||
child: Text(
|
||||
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) ...[
|
||||
const SizedBox(width: 6),
|
||||
if (!_loading)
|
||||
Text(
|
||||
'${_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(
|
||||
child: _loading
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 2),
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFF00C853),
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: _members.isEmpty
|
||||
? const Center(
|
||||
child: Text('عضوی یافت نشد',
|
||||
style: TextStyle(color: Colors.white38, fontSize: 11)),
|
||||
child: Text(
|
||||
'عضوی یافت نشد',
|
||||
style: TextStyle(
|
||||
color: Colors.white38,
|
||||
fontSize: 11,
|
||||
),
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
padding: const EdgeInsets.only(
|
||||
bottom: 60,
|
||||
), // فضای خالی برای دکمه شناور پایین
|
||||
itemCount: _members.length,
|
||||
itemBuilder: (ctx, i) {
|
||||
final m = _members[i];
|
||||
|
|
@ -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) {
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 2),
|
||||
height: 40,
|
||||
height: 36, // کاهش ارتفاع آیتم
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1C1C1E),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8),
|
||||
child: Row(
|
||||
children: [
|
||||
// Online indicator
|
||||
Container(
|
||||
width: 7,
|
||||
height: 7,
|
||||
width: 6,
|
||||
height: 6,
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle,
|
||||
color: member.isOnline ? const Color(0xFF00C853) : Colors.white24,
|
||||
|
|
@ -292,7 +318,9 @@ class _MemberTile extends StatelessWidget {
|
|||
style: TextStyle(
|
||||
color: isMe ? const Color(0xFF00C853) : Colors.white,
|
||||
fontSize: 11,
|
||||
fontWeight: member.isManager ? FontWeight.bold : FontWeight.normal,
|
||||
fontWeight: member.isManager
|
||||
? FontWeight.bold
|
||||
: FontWeight.normal,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
|
|
@ -300,14 +328,14 @@ class _MemberTile extends StatelessWidget {
|
|||
// Role badge
|
||||
if (member.isManager)
|
||||
Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 5, vertical: 2),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 4, vertical: 1),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00C853).withValues(alpha: 0.15),
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
color: const Color(0xFF00C853).withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(6),
|
||||
),
|
||||
child: const Text(
|
||||
'مدیر',
|
||||
style: TextStyle(color: Color(0xFF00C853), fontSize: 9),
|
||||
style: TextStyle(color: Color(0xFF00C853), fontSize: 8),
|
||||
),
|
||||
),
|
||||
// Remove button
|
||||
|
|
@ -315,7 +343,11 @@ class _MemberTile extends StatelessWidget {
|
|||
const SizedBox(width: 4),
|
||||
GestureDetector(
|
||||
onTap: onRemove,
|
||||
child: const Icon(Icons.remove_circle_outline, color: Colors.red, size: 16),
|
||||
child: const Icon(
|
||||
Icons.remove_circle_outline,
|
||||
color: Colors.red,
|
||||
size: 16,
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
|
|
@ -353,11 +385,19 @@ class _InviteDialogState extends State<_InviteDialog> {
|
|||
content: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
const Icon(Icons.person_add_outlined, color: Color(0xFF00C853), size: 26),
|
||||
const Icon(
|
||||
Icons.person_add_outlined,
|
||||
color: Color(0xFF00C853),
|
||||
size: 26,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
const Text(
|
||||
'دعوت عضو',
|
||||
style: TextStyle(color: Colors.white, fontSize: 12, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
TextField(
|
||||
|
|
@ -369,12 +409,15 @@ class _InviteDialogState extends State<_InviteDialog> {
|
|||
hintText: 'نام کاربری',
|
||||
hintStyle: const TextStyle(color: Colors.white38, fontSize: 11),
|
||||
filled: true,
|
||||
fillColor: Colors.white.withValues(alpha: 0.05),
|
||||
fillColor: Colors.white.withOpacity(0.05),
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(10),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 8,
|
||||
),
|
||||
),
|
||||
onChanged: widget.onUsernameChanged,
|
||||
),
|
||||
|
|
@ -384,13 +427,23 @@ class _InviteDialogState extends State<_InviteDialog> {
|
|||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(context, false),
|
||||
child: const Text('انصراف', style: TextStyle(color: Colors.white54, fontSize: 11)),
|
||||
child: const Text(
|
||||
'انصراف',
|
||||
style: TextStyle(color: Colors.white54, fontSize: 11),
|
||||
),
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: TextButton(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
child: const Text('ارسال', style: TextStyle(color: Color(0xFF00C853), fontSize: 11, fontWeight: FontWeight.bold)),
|
||||
child: const Text(
|
||||
'ارسال',
|
||||
style: TextStyle(
|
||||
color: Color(0xFF00C853),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
|
|
@ -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/services.dart';
|
||||
import '../services/auth_service.dart';
|
||||
import '../services/api_service.dart';
|
||||
import 'home_screen.dart';
|
||||
import '../home/home_screen.dart';
|
||||
|
||||
class LoginScreen extends StatefulWidget {
|
||||
const LoginScreen({super.key});
|
||||
|
|
@ -18,6 +19,11 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
bool _loading = false;
|
||||
String? _error;
|
||||
|
||||
// کانال ارتباطی با کد نیتیو برای تنظیمات اینترنت
|
||||
static final _nativeChannel = const MethodChannel(
|
||||
'com.example.watch/launcher',
|
||||
);
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
|
@ -31,6 +37,31 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
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 {
|
||||
final username = _usernameCtrl.text.trim();
|
||||
final secret = _secretCtrl.text.trim();
|
||||
|
|
@ -67,15 +98,21 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
child: SingleChildScrollView(
|
||||
child: ConstrainedBox(
|
||||
constraints: BoxConstraints(
|
||||
minHeight: MediaQuery.of(context).size.height -
|
||||
minHeight:
|
||||
MediaQuery.of(context).size.height -
|
||||
MediaQuery.of(context).padding.top -
|
||||
MediaQuery.of(context).padding.bottom,
|
||||
),
|
||||
child: Center(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 8),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
// محتوای اصلی فرم
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 20,
|
||||
vertical: 8,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
// Icon
|
||||
|
|
@ -83,7 +120,7 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
width: 36,
|
||||
height: 36,
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00C853).withValues(alpha: 0.15),
|
||||
color: const Color(0xFF00C853).withOpacity(0.15),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(
|
||||
|
|
@ -109,12 +146,17 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
height: 32,
|
||||
child: TextField(
|
||||
controller: _usernameCtrl,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 11),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'نام کاربری',
|
||||
hintStyle:
|
||||
const TextStyle(color: Colors.white38, fontSize: 10),
|
||||
hintStyle: const TextStyle(
|
||||
color: Colors.white38,
|
||||
fontSize: 10,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFF1C1C1E),
|
||||
border: OutlineInputBorder(
|
||||
|
|
@ -122,7 +164,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 0),
|
||||
horizontal: 10,
|
||||
vertical: 0,
|
||||
),
|
||||
),
|
||||
onSubmitted: (_) =>
|
||||
FocusScope.of(context).nextFocus(),
|
||||
|
|
@ -135,13 +179,18 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
height: 32,
|
||||
child: TextField(
|
||||
controller: _secretCtrl,
|
||||
style: const TextStyle(color: Colors.white, fontSize: 11),
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
),
|
||||
textAlign: TextAlign.center,
|
||||
obscureText: true,
|
||||
decoration: InputDecoration(
|
||||
hintText: 'کلید ورود',
|
||||
hintStyle:
|
||||
const TextStyle(color: Colors.white38, fontSize: 10),
|
||||
hintStyle: const TextStyle(
|
||||
color: Colors.white38,
|
||||
fontSize: 10,
|
||||
),
|
||||
filled: true,
|
||||
fillColor: const Color(0xFF1C1C1E),
|
||||
border: OutlineInputBorder(
|
||||
|
|
@ -149,7 +198,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
borderSide: BorderSide.none,
|
||||
),
|
||||
contentPadding: const EdgeInsets.symmetric(
|
||||
horizontal: 10, vertical: 0),
|
||||
horizontal: 10,
|
||||
vertical: 0,
|
||||
),
|
||||
),
|
||||
onSubmitted: (_) => _login(),
|
||||
),
|
||||
|
|
@ -162,7 +213,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
? Text(
|
||||
_error!,
|
||||
style: const TextStyle(
|
||||
color: Color(0xFFFF1744), fontSize: 9),
|
||||
color: Color(0xFFFF1744),
|
||||
fontSize: 9,
|
||||
),
|
||||
)
|
||||
: null,
|
||||
),
|
||||
|
|
@ -183,7 +236,9 @@ class _LoginScreenState extends State<LoginScreen> {
|
|||
? null
|
||||
: [
|
||||
BoxShadow(
|
||||
color: const Color(0xFF00C853).withValues(alpha: 0.4),
|
||||
color: const Color(
|
||||
0xFF00C853,
|
||||
).withOpacity(0.4),
|
||||
blurRadius: 10,
|
||||
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),
|
||||
|
|
@ -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) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(err, style: const TextStyle(fontSize: 11), textAlign: TextAlign.center),
|
||||
content: Text(
|
||||
err,
|
||||
style: const TextStyle(fontSize: 11),
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
backgroundColor: const Color(0xFF333333),
|
||||
behavior: SnackBarBehavior.floating,
|
||||
duration: const Duration(seconds: 2),
|
||||
margin: const EdgeInsets.symmetric(horizontal: 16, vertical: 40),
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
|
|
@ -63,60 +69,72 @@ class _NotificationsScreenState extends State<NotificationsScreen> {
|
|||
body: SafeArea(
|
||||
child: Column(
|
||||
children: [
|
||||
// Header
|
||||
// Header ساده و وسطچین (بدون دکمههای کناری)
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 6),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
IconButton(
|
||||
onPressed: () => Navigator.pop(context),
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(minWidth: 28, minHeight: 28),
|
||||
icon: const Icon(Icons.arrow_back_ios_new, color: Colors.white70, size: 14),
|
||||
const Icon(
|
||||
Icons.notifications_outlined,
|
||||
color: Color(0xFF00C853),
|
||||
size: 16,
|
||||
),
|
||||
const SizedBox(width: 4),
|
||||
const Icon(Icons.notifications_outlined, color: Color(0xFF00C853), size: 14),
|
||||
const SizedBox(width: 4),
|
||||
const Expanded(
|
||||
child: Text(
|
||||
const SizedBox(width: 6),
|
||||
const 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
|
||||
Expanded(
|
||||
child: _loading
|
||||
? const Center(
|
||||
child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 2),
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFF00C853),
|
||||
strokeWidth: 2,
|
||||
),
|
||||
)
|
||||
: _notifications.isEmpty
|
||||
? const Center(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(Icons.notifications_off_outlined, color: Colors.white24, size: 28),
|
||||
SizedBox(height: 6),
|
||||
Text('اعلانی وجود ندارد',
|
||||
style: TextStyle(color: Colors.white38, fontSize: 11)),
|
||||
Icon(
|
||||
Icons.notifications_off_outlined,
|
||||
color: Colors.white24,
|
||||
size: 32,
|
||||
),
|
||||
SizedBox(height: 8),
|
||||
Text(
|
||||
'اعلانی وجود ندارد',
|
||||
style: TextStyle(
|
||||
color: Colors.white38,
|
||||
fontSize: 12,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
: ListView.builder(
|
||||
padding: const EdgeInsets.only(bottom: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 8,
|
||||
vertical: 8,
|
||||
),
|
||||
itemCount: _notifications.length,
|
||||
itemBuilder: (ctx, i) => _NotifTile(
|
||||
notif: _notifications[i],
|
||||
isProcessing: _processing.contains(_notifications[i].id),
|
||||
isProcessing: _processing.contains(
|
||||
_notifications[i].id,
|
||||
),
|
||||
onAccept: () => _respond(_notifications[i], true),
|
||||
onReject: () => _respond(_notifications[i], false),
|
||||
),
|
||||
|
|
@ -157,16 +175,16 @@ class _NotifTile extends StatelessWidget {
|
|||
}
|
||||
|
||||
return Container(
|
||||
margin: const EdgeInsets.symmetric(horizontal: 8, vertical: 3),
|
||||
margin: const EdgeInsets.only(bottom: 8), // فاصله عمودی بین آیتمها
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF1C1C1E),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: isPending ? statusColor.withValues(alpha: 0.4) : Colors.transparent,
|
||||
color: isPending ? statusColor.withOpacity(0.4) : Colors.transparent,
|
||||
width: 1,
|
||||
),
|
||||
),
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 8),
|
||||
padding: const EdgeInsets.all(10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
|
|
@ -175,15 +193,15 @@ class _NotifTile extends StatelessWidget {
|
|||
Icon(
|
||||
isJoin ? Icons.group_add_outlined : Icons.campaign_outlined,
|
||||
color: statusColor,
|
||||
size: 13,
|
||||
size: 14,
|
||||
),
|
||||
const SizedBox(width: 5),
|
||||
const SizedBox(width: 6),
|
||||
Expanded(
|
||||
child: Text(
|
||||
notif.title,
|
||||
style: const TextStyle(
|
||||
color: Colors.white,
|
||||
fontSize: 11,
|
||||
fontSize: 12,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
|
|
@ -195,37 +213,47 @@ class _NotifTile extends StatelessWidget {
|
|||
const SizedBox(height: 4),
|
||||
Text(
|
||||
notif.description!,
|
||||
style: const TextStyle(color: Colors.white60, fontSize: 10),
|
||||
maxLines: 2,
|
||||
style: const TextStyle(color: Colors.white70, fontSize: 11),
|
||||
maxLines: 3,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
],
|
||||
if (isJoin && isPending) ...[
|
||||
const SizedBox(height: 6),
|
||||
const SizedBox(height: 8),
|
||||
if (isProcessing)
|
||||
const Center(
|
||||
child: SizedBox(
|
||||
width: 14,
|
||||
height: 14,
|
||||
child: CircularProgressIndicator(color: Color(0xFF00C853), strokeWidth: 1.5),
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
color: Color(0xFF00C853),
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
mainAxisAlignment: MainAxisAlignment.end, // دکمهها سمت راست
|
||||
children: [
|
||||
// Reject
|
||||
GestureDetector(
|
||||
onTap: onReject,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: Colors.red.withValues(alpha: 0.15),
|
||||
color: Colors.red.withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Text(
|
||||
'رد',
|
||||
style: TextStyle(color: Colors.red, fontSize: 10, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
color: Colors.red,
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -234,14 +262,21 @@ class _NotifTile extends StatelessWidget {
|
|||
GestureDetector(
|
||||
onTap: onAccept,
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 6,
|
||||
),
|
||||
decoration: BoxDecoration(
|
||||
color: const Color(0xFF00C853).withValues(alpha: 0.15),
|
||||
color: const Color(0xFF00C853).withOpacity(0.15),
|
||||
borderRadius: BorderRadius.circular(20),
|
||||
),
|
||||
child: const Text(
|
||||
'قبول',
|
||||
style: TextStyle(color: Color(0xFF00C853), fontSize: 10, fontWeight: FontWeight.bold),
|
||||
style: TextStyle(
|
||||
color: Color(0xFF00C853),
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
|
@ -251,10 +286,10 @@ class _NotifTile extends StatelessWidget {
|
|||
if (!isPending) ...[
|
||||
const SizedBox(height: 4),
|
||||
Align(
|
||||
alignment: Alignment.centerLeft,
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
notif.isAccepted == true ? 'پذیرفته شد' : 'رد شد',
|
||||
style: TextStyle(color: statusColor, fontSize: 9),
|
||||
style: TextStyle(color: statusColor, fontSize: 10),
|
||||
),
|
||||
),
|
||||
],
|
||||
|
|
|
|||
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