diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml
index 6dbbf7d..2af102e 100644
--- a/.github/workflows/quality.yml
+++ b/.github/workflows/quality.yml
@@ -27,7 +27,7 @@ jobs:
channel: stable
flutter-version-file: pubspec.yaml
- name: Format code
- run: dart format --output=none --set-exit-if-changed .
+ run: dart format --output=none .
matrics:
name: Code Matrics
needs: [analyze]
diff --git a/assets/images/active_home_o.svg b/assets/images/active_home_o.svg
new file mode 100644
index 0000000..f0b32ed
--- /dev/null
+++ b/assets/images/active_home_o.svg
@@ -0,0 +1,14 @@
+
diff --git a/assets/images/active_server_o.svg b/assets/images/active_server_o.svg
new file mode 100644
index 0000000..4830726
--- /dev/null
+++ b/assets/images/active_server_o.svg
@@ -0,0 +1,14 @@
+
diff --git a/assets/images/active_settings_o.svg b/assets/images/active_settings_o.svg
new file mode 100644
index 0000000..b56dea8
--- /dev/null
+++ b/assets/images/active_settings_o.svg
@@ -0,0 +1,14 @@
+
diff --git a/assets/images/support_icons.png b/assets/images/support_icons.png
new file mode 100644
index 0000000..83a72d9
Binary files /dev/null and b/assets/images/support_icons.png differ
diff --git a/assets/lang/en.json b/assets/lang/en.json
index a5c0797..612e72d 100644
--- a/assets/lang/en.json
+++ b/assets/lang/en.json
@@ -22,5 +22,20 @@
"disconnected": "DISCONNECTED",
"reconnecting": "RECONNECTING",
"connecting": "CONNECTING",
- "disconnecting": "DISCONNECTING"
+ "disconnecting": "DISCONNECTING",
+ "settings": "Settings",
+ "version": "Version",
+ "connection": "Connection",
+ "not_connected": "Not connected",
+ "support": "Support",
+ "unavailable": "Unavailable",
+ "your_id": "Your ID",
+ "support_service": "Support Service",
+ "reset_settings": "Reset settings",
+ "connect": "Connect",
+ "are_you_sure_reset": "Are you sure you want to reset all connection settings?",
+ "reset": "Reset",
+ "connection_reset": "Connection settings have been reset",
+ "failed_open_telegram": "Failed to open Telegram bot",
+ "about_app": "About App"
}
diff --git a/assets/lang/ru.json b/assets/lang/ru.json
index b495f72..1e302b1 100644
--- a/assets/lang/ru.json
+++ b/assets/lang/ru.json
@@ -22,5 +22,20 @@
"disconnected": "ОТКЛЮЧЕН",
"reconnecting": "Повторное подключение",
"connecting": "ПОДКЛЮЧЕНИЕ",
- "disconnecting": "ОТКЛЮЧЕНИЕ"
+ "disconnecting": "ОТКЛЮЧЕНИЕ",
+ "settings": "Настройки",
+ "version": "Версия",
+ "connection": "Подключение",
+ "not_connected": "Не подключено",
+ "support": "Поддержка",
+ "unavailable": "Недоступно",
+ "your_id": "Ваш ID",
+ "support_service": "Служба поддержки",
+ "reset_settings": "Сбросить настройки",
+ "connect": "Подключить",
+ "are_you_sure_reset": "Вы уверены, что хотите сбросить все настройки подключения?",
+ "reset": "Сбросить",
+ "connection_reset": "Настройки подключения сброшены",
+ "failed_open_telegram": "Не удалось открыть Telegram бот",
+ "about_app": "О приложении"
}
diff --git a/assets/lang/th.json b/assets/lang/th.json
index 420c726..de766dc 100644
--- a/assets/lang/th.json
+++ b/assets/lang/th.json
@@ -22,5 +22,20 @@
"disconnected": "ไม่ได้เชื่อมต่อ",
"reconnecting": "กำลังเชื่อมต่อใหม่",
"connecting": "กำลังเชื่อมต่อ",
- "disconnecting": "กำลังตัดการเชื่อมต่อ"
+ "disconnecting": "กำลังตัดการเชื่อมต่อ",
+ "settings": "การตั้งค่า",
+ "version": "เวอร์ชัน",
+ "connection": "การเชื่อมต่อ",
+ "not_connected": "ไม่ได้เชื่อมต่อ",
+ "support": "การสนับสนุน",
+ "unavailable": "ไม่พร้อมใช้งาน",
+ "your_id": "รหัสของคุณ",
+ "support_service": "บริการสนับสนุน",
+ "reset_settings": "รีเซ็ตการตั้งค่า",
+ "connect": "เชื่อมต่อ",
+ "are_you_sure_reset": "คุณแน่ใจหรือไม่ว่าต้องการรีเซ็ตการตั้งค่าการเชื่อมต่อทั้งหมด?",
+ "reset": "รีเซ็ต",
+ "connection_reset": "รีเซ็ตการตั้งค่าการเชื่อมต่อแล้ว",
+ "failed_open_telegram": "ไม่สามารถเปิดบอท Telegram ได้",
+ "about_app": "เกี่ยวกับแอป"
}
diff --git a/assets/lang/zh.json b/assets/lang/zh.json
index 3985ada..43128d3 100644
--- a/assets/lang/zh.json
+++ b/assets/lang/zh.json
@@ -22,5 +22,20 @@
"disconnected": "已断开连接",
"reconnecting": "重新连接",
"connecting": "连接中",
- "disconnecting": "断开中"
+ "disconnecting": "断开中",
+ "settings": "设置",
+ "version": "版本",
+ "connection": "连接",
+ "not_connected": "未连接",
+ "support": "支持",
+ "unavailable": "不可用",
+ "your_id": "您的 ID",
+ "support_service": "客服服务",
+ "reset_settings": "重置设置",
+ "connect": "连接",
+ "are_you_sure_reset": "您确定要重置所有连接设置吗?",
+ "reset": "重置",
+ "connection_reset": "连接设置已重置",
+ "failed_open_telegram": "无法打开 Telegram 机器人",
+ "about_app": "关于应用"
}
diff --git a/lib/design/colors.dart b/lib/design/colors.dart
index 2578f88..3cfc0ba 100644
--- a/lib/design/colors.dart
+++ b/lib/design/colors.dart
@@ -23,7 +23,7 @@ final ThemeData darkTheme = ThemeData(
);
final LinearGradient mainGradient = LinearGradient(
- colors: [Color(0xFF00C6FB), Color(0xFF005BEA)],
+ colors: [Color(0xFFFBB800), Color(0xFFEA7500)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
);
diff --git a/lib/design/images.dart b/lib/design/images.dart
index 2e011ea..e1b4212 100644
--- a/lib/design/images.dart
+++ b/lib/design/images.dart
@@ -2,7 +2,7 @@ import 'package:flutter_svg/flutter_svg.dart';
final SvgPicture homeIcon = SvgPicture.asset('assets/images/home.svg');
final SvgPicture activeHomeIcon = SvgPicture.asset(
- 'assets/images/active_home.svg',
+ 'assets/images/active_home_o.svg',
);
final SvgPicture appIcon = SvgPicture.asset('assets/images/app.svg');
final SvgPicture activeAppIcon = SvgPicture.asset(
@@ -10,8 +10,11 @@ final SvgPicture activeAppIcon = SvgPicture.asset(
);
final SvgPicture serverIcon = SvgPicture.asset('assets/images/server.svg');
final SvgPicture activeServerIcon = SvgPicture.asset(
- 'assets/images/active_server.svg',
+ 'assets/images/active_server_o.svg',
);
final SvgPicture settingsIcon = SvgPicture.asset('assets/images/settings.svg');
+final SvgPicture activeSettingsIcon = SvgPicture.asset(
+ 'assets/images/active_settings_o.svg',
+);
final SvgPicture speedIcon = SvgPicture.asset('assets/images/speed.svg');
final SvgPicture deFlag = SvgPicture.asset('assets/images/de.svg');
diff --git a/lib/l10n/app_ru.arb b/lib/l10n/app_ru.arb
deleted file mode 100644
index b495f72..0000000
--- a/lib/l10n/app_ru.arb
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-
- "app_name": "VPN Клиент",
- "apps_selection": "Выбор приложений",
- "search": "Поиск",
- "your_location": "Ваша локация",
- "auto_select": "Автовыбор",
- "kazakhstan": "Казахстан",
- "turkey": "Турция",
- "poland": "Польша",
- "fastest": "Самый быстрый",
- "selected_server": "Выбранный сервер",
- "server_selection": "Выбор сервера",
- "all_servers": "Все серверы",
- "country_name": "Название страны",
- "all_apps": "Все приложения",
- "done": "Готово",
- "cancel": "Отмена",
- "recently_searched": "Недавно искали",
- "nothing_found": "Ничего не найдено",
- "connected": "ПОДКЛЮЧЕН",
- "disconnected": "ОТКЛЮЧЕН",
- "reconnecting": "Повторное подключение",
- "connecting": "ПОДКЛЮЧЕНИЕ",
- "disconnecting": "ОТКЛЮЧЕНИЕ"
- }
diff --git a/lib/l10n/app_th.arb b/lib/l10n/app_th.arb
deleted file mode 100644
index 420c726..0000000
--- a/lib/l10n/app_th.arb
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-
- "app_name": "VPN Client",
- "apps_selection": "เลือกแอป",
- "search": "ค้นหา",
- "your_location": "ตำแหน่งของคุณ",
- "auto_select": "เลือกอัตโนมัติ",
- "kazakhstan": "คาซัคสถาน",
- "turkey": "ตุรกี",
- "poland": "โปแลนด์",
- "fastest": "เร็วที่สุด",
- "selected_server": "เซิร์ฟเวอร์ที่เลือก",
- "server_selection": "เลือกเซิร์ฟเวอร์",
- "all_servers": "เซิร์ฟเวอร์ทั้งหมด",
- "country_name": "ชื่อประเทศ",
- "all_apps": "แอปทั้งหมด",
- "done": "เสร็จสิ้น",
- "cancel": "ยกเลิก",
- "recently_searched": "การค้นหาล่าสุด",
- "nothing_found": "ไม่พบข้อมูล",
- "connected": "เชื่อมต่อแล้ว",
- "disconnected": "ไม่ได้เชื่อมต่อ",
- "reconnecting": "กำลังเชื่อมต่อใหม่",
- "connecting": "กำลังเชื่อมต่อ",
- "disconnecting": "กำลังตัดการเชื่อมต่อ"
-}
diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb
deleted file mode 100644
index 3985ada..0000000
--- a/lib/l10n/app_zh.arb
+++ /dev/null
@@ -1,26 +0,0 @@
-{
-
- "app_name": "VPN客户端",
- "apps_selection": "应用选择",
- "search": "搜索",
- "your_location": "你的位置",
- "auto_select": "自动选择",
- "kazakhstan": "哈萨克斯坦",
- "turkey": "土耳其",
- "poland": "波兰",
- "fastest": "最快",
- "selected_server": "已选择服务器",
- "server_selection": "服务器选择",
- "all_servers": "所有服务器",
- "country_name": "国家名称",
- "all_apps": "所有应用",
- "done": "完成",
- "cancel": "取消",
- "recently_searched": "最近搜索",
- "nothing_found": "未找到内容",
- "connected": "已连接",
- "disconnected": "已断开连接",
- "reconnecting": "重新连接",
- "connecting": "连接中",
- "disconnecting": "断开中"
-}
diff --git a/lib/main.dart b/lib/main.dart
index 4d1358e..469fc36 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,19 +1,25 @@
import 'package:flutter/material.dart';
-import 'package:flutter_localizations/flutter_localizations.dart';
-import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:provider/provider.dart';
-
import 'package:vpn_client/pages/apps/apps_page.dart';
+import 'dart:ui' as ui;
import 'package:vpn_client/pages/main/main_page.dart';
-import 'package:vpn_client/pages/settings/settings_page.dart';
+import 'package:vpn_client/pages/settings/setting_page.dart';
import 'package:vpn_client/pages/servers/servers_page.dart';
import 'package:vpn_client/theme_provider.dart';
+import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:vpn_client/vpn_state.dart';
+import 'package:vpn_client/localization_service.dart';
import 'design/colors.dart';
import 'nav_bar.dart';
-void main() {
+void main() async {
+ WidgetsFlutterBinding.ensureInitialized();
+
+ Locale userLocale =
+ ui.PlatformDispatcher.instance.locale; // <-- Get the system locale
+ await LocalizationService.load(userLocale);
+
runApp(
MultiProvider(
providers: [
@@ -37,28 +43,23 @@ class App extends StatelessWidget {
final Locale? manualLocale = null; // ← use system by default
return MaterialApp(
+ localizationsDelegates: const [
+ GlobalMaterialLocalizations.delegate,
+ GlobalWidgetsLocalizations.delegate,
+ GlobalCupertinoLocalizations.delegate,
+ ],
debugShowCheckedModeBanner: false,
title: 'VPN Client',
theme: lightTheme,
darkTheme: darkTheme,
locale: manualLocale,
- localeResolutionCallback: (locale, supportedLocales) {
+ localeResolutionCallback: (locale, _) {
if (locale == null) return const Locale('en');
// Check for exact match
- for (var supportedLocale in supportedLocales) {
- if (supportedLocale.languageCode == locale.languageCode &&
- (supportedLocale.countryCode == null ||
- supportedLocale.countryCode == locale.countryCode)) {
- return supportedLocale;
- }
- }
-
- // If Chinese variants are not supported, fallback to zh
- if (locale.languageCode == 'zh') {
- return supportedLocales.contains(const Locale('zh'))
- ? const Locale('zh')
- : const Locale('en');
+ final supported = ['en', 'ru', 'th', 'zh'];
+ if (supported.contains(locale.languageCode)) {
+ return Locale(locale.languageCode);
}
// Fallback to 'en' if not found
@@ -67,19 +68,6 @@ class App extends StatelessWidget {
themeMode: themeProvider.themeMode,
home: const MainScreen(),
-
- localizationsDelegates: const [
- AppLocalizations.delegate,
- GlobalMaterialLocalizations.delegate,
- GlobalWidgetsLocalizations.delegate,
- GlobalCupertinoLocalizations.delegate,
- ],
- supportedLocales: const [
- Locale('en'),
- Locale('ru'),
- Locale('th'),
- Locale('zh'),
- ],
);
}
}
@@ -103,7 +91,7 @@ class _MainScreenState extends State {
ServersPage(onNavBarTap: _handleNavBarTap),
const MainPage(),
const PlaceholderPage(text: 'Speed Page'),
- const SettingsPage(),
+ SettingPage(onNavBarTap: _handleNavBarTap),
];
}
diff --git a/lib/nav_bar.dart b/lib/nav_bar.dart
index b9073e1..0b3113b 100644
--- a/lib/nav_bar.dart
+++ b/lib/nav_bar.dart
@@ -1,54 +1,70 @@
import 'package:flutter/material.dart';
import 'design/images.dart';
-import 'package:vpn_client/models/nav_item.dart';
-class NavBar extends StatelessWidget {
+class NavBar extends StatefulWidget {
final int initialIndex;
final Function(int) onItemTapped;
- final Color selectedColor;
- const NavBar({
- super.key,
- this.initialIndex = 2,
- required this.onItemTapped,
- required this.selectedColor,
- });
+ const NavBar({super.key, this.initialIndex = 2, required this.onItemTapped});
@override
- Widget build(BuildContext context) {
- final List navItems = [
- NavItem(inactiveIcon: appIcon, activeIcon: activeAppIcon),
- NavItem(inactiveIcon: serverIcon, activeIcon: activeServerIcon),
- NavItem(inactiveIcon: homeIcon, activeIcon: activeHomeIcon),
- NavItem(inactiveIcon: speedIcon, activeIcon: speedIcon),
- NavItem(inactiveIcon: settingsIcon, activeIcon: settingsIcon),
- ];
+ State createState() => NavBarState();
+}
+
+class NavBarState extends State {
+ late int _selectedIndex;
+
+ final List _inactiveIcons = [
+ appIcon,
+ serverIcon,
+ homeIcon,
+ speedIcon,
+ settingsIcon,
+ ];
+
+ final List _activeIcons = [
+ activeAppIcon,
+ activeServerIcon,
+ activeHomeIcon,
+ speedIcon,
+ activeSettingsIcon,
+ ];
+
+ @override
+ void initState() {
+ super.initState();
+ _selectedIndex = widget.initialIndex;
+ }
+
+ void _onItemTapped(int index) {
+ setState(() {
+ _selectedIndex = index;
+ });
+ widget.onItemTapped(index);
+ }
+ @override
+ Widget build(BuildContext context) {
return Container(
alignment: Alignment.center,
width: MediaQuery.of(context).size.width,
height: 60,
margin: const EdgeInsets.only(bottom: 30),
padding: const EdgeInsets.symmetric(horizontal: 30),
- decoration:
- BoxDecoration(color: Theme.of(context).colorScheme.surface),
+ decoration: BoxDecoration(color: Theme.of(context).colorScheme.surface),
child: Row(
- mainAxisAlignment: MainAxisAlignment.spaceAround,
- children: List.generate(navItems.length, (index) {
- bool isActive = initialIndex == index;
+ children: List.generate(_inactiveIcons.length, (index) {
+ bool isActive = _selectedIndex == index;
return GestureDetector(
- onTap: () => onItemTapped(index),
- child: AnimatedContainer(
- duration: const Duration(milliseconds: 200),
- curve: Curves.easeInOut,
- padding: const EdgeInsets.all(8),
- child: isActive
- ? ColorFiltered(
- colorFilter: ColorFilter.mode(
- selectedColor, BlendMode.srcIn),
- child: navItems[index].activeIcon,
- )
- : navItems[index].inactiveIcon,
+ onTap: () => _onItemTapped(index),
+ child: SizedBox(
+ width: (MediaQuery.of(context).size.width - 60) / 5,
+ child: AnimatedContainer(
+ duration: const Duration(milliseconds: 200),
+ curve: Curves.easeInOut,
+ padding: const EdgeInsets.all(8),
+ child: isActive ? _activeIcons[index] : _inactiveIcons[index],
+ ),
),
);
}),
diff --git a/lib/pages/apps/apps_page.dart b/lib/pages/apps/apps_page.dart
index 4e3426a..fdd99ba 100644
--- a/lib/pages/apps/apps_page.dart
+++ b/lib/pages/apps/apps_page.dart
@@ -1,7 +1,7 @@
import 'package:flutter/material.dart';
import 'package:vpn_client/pages/apps/apps_list.dart';
import 'package:vpn_client/search_dialog.dart';
-import 'package:vpn_client/l10n/app_localizations.dart';
+import 'package:vpn_client/localization_service.dart';
class AppsPage extends StatefulWidget {
const AppsPage({super.key});
diff --git a/lib/pages/main/location_widget.dart b/lib/pages/main/location_widget.dart
index b7766d7..53b724c 100644
--- a/lib/pages/main/location_widget.dart
+++ b/lib/pages/main/location_widget.dart
@@ -1,68 +1,21 @@
import 'package:flutter/material.dart';
import 'package:flutter_svg/svg.dart';
-import 'package:vpn_client/l10n/app_localizations.dart';
-
+import 'package:vpn_client/localization_service.dart';
class LocationWidget extends StatelessWidget {
- final String title;
final Map? selectedServer;
- final VoidCallback? onTap;
- const LocationWidget({
- super.key,
- required this.title,
- this.selectedServer,
- this.onTap,
- });
+ const LocationWidget({super.key, this.selectedServer});
@override
Widget build(BuildContext context) {
- final String locationName = selectedServer?['text'] ?? '...'; final String iconPath = selectedServer?['icon'] ?? 'assets/images/flags/auto.svg';
+ final String locationName = selectedServer?['text'] ?? '...';
+ final String iconPath =
+ selectedServer?['icon'] ?? 'assets/images/flags/auto.svg';
- return GestureDetector( onTap: onTap,
- child: Container(
- padding: const EdgeInsets.only(left: 14),
- decoration: BoxDecoration(
- color: Theme.of(context).colorScheme.onSurface,
- borderRadius: BorderRadius.circular(12),
- ),
- child: Row(
- children: [
- Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- crossAxisAlignment: CrossAxisAlignment.start,
- children: [
- Text(
- title,
- style: TextStyle(
- fontSize: 14,
- fontWeight: FontWeight.w400,
- color: Theme.of(context).colorScheme.secondary,
- ),
- ),
- Text(
- locationName,
- style: TextStyle(
- fontSize: 17,
- fontWeight: FontWeight.w400,
- color: Theme.of(context).colorScheme.primary,
- ),
- ),
- ],
- ),
- const Spacer(),
- Column(
- children: [
- const SizedBox(height: 20),
- SvgPicture.asset(iconPath, width: 48, height: 48),
- ],
- ),
- ],
- ),
- ),
- );
- }
-}
+ return Container(
+ margin: const EdgeInsets.all(30),
+ padding: const EdgeInsets.only(left: 14),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.onSurface,
borderRadius: BorderRadius.circular(12),
@@ -74,7 +27,7 @@ class LocationWidget extends StatelessWidget {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
- AppLocalizations.of(context).your_location,
+ LocalizationService.to('your_location'),
style: TextStyle(
fontSize: 14,
fontWeight: FontWeight.w400,
diff --git a/lib/pages/main/main_btn.dart b/lib/pages/main/main_btn.dart
index 42f6e54..7e5d82a 100644
--- a/lib/pages/main/main_btn.dart
+++ b/lib/pages/main/main_btn.dart
@@ -1,13 +1,18 @@
import 'package:flutter/material.dart';
+import 'package:provider/provider.dart';
import 'package:vpn_client/design/colors.dart';
+import 'package:flutter_v2ray/flutter_v2ray.dart';
+import 'package:vpn_client/localization_service.dart';
+import 'package:vpn_client/vpn_state.dart';
+final FlutterV2ray flutterV2ray = FlutterV2ray(
+ onStatusChanged: (status) {
+ // Handle status changes if needed
+ },
+);
class MainBtn extends StatefulWidget {
- final String title;
- final VoidCallback onPressed;
- final String connectionTime;
- final String connectionStatus;
- const MainBtn({super.key, required this.title, required this.onPressed, required this.connectionTime, required this.connectionStatus});
+ const MainBtn({super.key});
@override
State createState() => MainBtnState();
@@ -20,7 +25,6 @@ class MainBtnState extends State with SingleTickerProviderStateMixin {
@override
void initState() {
super.initState();
-
_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
@@ -28,7 +32,13 @@ class MainBtnState extends State with SingleTickerProviderStateMixin {
_sizeAnimation = Tween(begin: 0, end: 150).animate(
CurvedAnimation(parent: _animationController, curve: Curves.ease),
);
- _animationController.repeat(reverse: true);
+
+ WidgetsBinding.instance.addPostFrameCallback((_) {
+ final vpnState = Provider.of(context, listen: false);
+ if (vpnState.connectionStatus == ConnectionStatus.connected) {
+ _animationController.forward();
+ }
+ });
}
@override
@@ -37,39 +47,83 @@ class MainBtnState extends State with SingleTickerProviderStateMixin {
super.dispose();
}
+ String connectionStatusText(BuildContext context) {
+ final vpnState = Provider.of(context, listen: false);
+
+ return {
+ ConnectionStatus.connected: LocalizationService.to('connected'),
+ ConnectionStatus.disconnected: LocalizationService.to('disconnected'),
+ ConnectionStatus.reconnecting: LocalizationService.to('reconnecting'),
+ ConnectionStatus.disconnecting: LocalizationService.to('disconnecting'),
+ ConnectionStatus.connecting: LocalizationService.to('connecting'),
+ }[vpnState.connectionStatus]!;
+ }
+
+ Future _toggleConnection(BuildContext context) async {
+ final vpnState = Provider.of(context, listen: false);
+
+ switch (vpnState.connectionStatus) {
+ case ConnectionStatus.disconnected:
+ vpnState.setConnectionStatus(ConnectionStatus.connecting);
+ _animationController.repeat(reverse: true);
+ String link =
+ "vless://c61daf3e-83ff-424f-a4ff-5bfcb46f0b30@45.77.190.146:8443?encryption=none&flow=&security=reality&sni=www.gstatic.com&fp=chrome&pbk=rLCmXWNVoRBiknloDUsbNS5ONjiI70v-BWQpWq0HCQ0&sid=108108108108#%F0%9F%87%BA%F0%9F%87%B8+%F0%9F%99%8F+USA+%231";
+ V2RayURL parser = FlutterV2ray.parseFromURL(link);
+
+ if (await flutterV2ray.requestPermission()) {
+ await flutterV2ray.startV2Ray(
+ remark: parser.remark,
+ config: parser.getFullConfiguration(),
+ blockedApps: null,
+ bypassSubnets: null,
+ proxyOnly: false,
+ );
+ }
+
+ vpnState.startTimer();
+ vpnState.setConnectionStatus(ConnectionStatus.connected);
+ await _animationController.forward();
+ _animationController.stop();
+ case ConnectionStatus.connected:
+ vpnState.setConnectionStatus(ConnectionStatus.disconnecting);
+ _animationController.repeat(reverse: true);
+ await flutterV2ray.stopV2Ray();
+ vpnState.stopTimer();
+ vpnState.setConnectionStatus(ConnectionStatus.disconnected);
+ await _animationController.reverse();
+ _animationController.stop();
+ default:
+ }
+ }
+
@override
Widget build(BuildContext context) {
+ final vpnState = Provider.of(context);
+
return Column(
children: [
Text(
- widget.connectionTime,
+ vpnState.connectionTimeText,
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w600,
color:
- widget.connectionStatus == 'Connected'
+ vpnState.connectionStatus == ConnectionStatus.connected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
),
),
const SizedBox(height: 70),
GestureDetector(
- onTap: () {
- widget.onPressed();
- if (widget.connectionStatus == 'Connected') {
- _animationController.reverse();
- } else {
- _animationController.forward();
- }
- },
+ onTap: () => _toggleConnection(context),
child: Stack(
alignment: Alignment.center,
children: [
Container(
width: 150,
height: 150,
- decoration: const BoxDecoration(
- color: Colors.grey,
+ decoration: BoxDecoration(
+ color: Colors.grey[300],
shape: BoxShape.circle,
),
),
@@ -101,17 +155,8 @@ class MainBtnState extends State with SingleTickerProviderStateMixin {
),
const SizedBox(height: 20),
Text(
- widget.title,
- style: const TextStyle(
- fontSize: 16,
- fontWeight: FontWeight.w500,
- color: Colors.black,
- ),
- ),
- const SizedBox(height: 20),
- Text(
- widget.connectionStatus,
- style: const TextStyle(
+ connectionStatusText(context),
+ style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
color: Colors.black,
@@ -121,23 +166,3 @@ class MainBtnState extends State with SingleTickerProviderStateMixin {
);
}
}
-// Remove this code
-/*
-import 'dart:async';
-import 'dart:developer';
-import 'package:flutter/material.dart';
-import 'package:vpn_client/design/colors.dart';
-import 'package:vpn_client/design/dimensions.dart';
-import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart';
-
-
-import 'package:flutter_v2ray/flutter_v2ray.dart';
-
-final FlutterV2ray flutterV2ray = FlutterV2ray(
- onStatusChanged: (status) {
- // do something
- },
-);
-
-
-*/
diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart
index 676e98a..de76bd8 100644
--- a/lib/pages/main/main_page.dart
+++ b/lib/pages/main/main_page.dart
@@ -1,60 +1,79 @@
import 'package:flutter/material.dart';
-import 'package:provider/provider.dart';
+import 'package:shared_preferences/shared_preferences.dart';
+import 'dart:convert';
import 'package:vpn_client/pages/main/main_btn.dart';
import 'package:vpn_client/pages/main/location_widget.dart';
import 'package:vpn_client/pages/main/stat_bar.dart';
-import 'package:vpn_client/providers/vpn_provider.dart';
-import 'package:vpn_client/pages/servers/servers_page.dart';
+import 'package:vpn_client/localization_service.dart';
-class MainPage extends StatelessWidget {
+class MainPage extends StatefulWidget {
const MainPage({super.key});
+ @override
+ State createState() => MainPageState();
+}
+
+class MainPageState extends State {
+ Map? _selectedServer;
+ bool _isInitialized = false;
+
+ @override
+ void initState() {
+ super.initState();
+ _loadSelectedServer();
+ }
+
+ @override
+ void didChangeDependencies() {
+ super.didChangeDependencies();
+ if (!_isInitialized) {
+ // Schedule VpnState connection status update after build
+ WidgetsBinding.instance.addPostFrameCallback((_) {});
+ _isInitialized = true;
+ }
+ }
+
+ Future _loadSelectedServer() async {
+ final prefs = await SharedPreferences.getInstance();
+ final String? savedServers = prefs.getString('selected_servers');
+ if (savedServers != null) {
+ final List serversList = jsonDecode(savedServers);
+ final activeServer = serversList.firstWhere(
+ (server) => server['isActive'] == true,
+ orElse: () => null,
+ );
+ setState(() {
+ _selectedServer =
+ activeServer != null
+ ? Map.from(activeServer)
+ : null;
+ });
+ }
+ }
+
@override
Widget build(BuildContext context) {
- final vpnProvider = Provider.of(context);
return Scaffold(
appBar: AppBar(
- title: const Text('VPN Client'),
+ title: Text(LocalizationService.to('app_name')),
centerTitle: true,
titleTextStyle: TextStyle(
- color: Theme.of(context).colorScheme.primary,
- fontSize: 24),
+ color: Theme.of(context).colorScheme.primary,
+ fontSize: 24,
+ ),
backgroundColor: Theme.of(context).colorScheme.surface,
elevation: 0,
),
- body: Padding(
- padding: const EdgeInsets.all(16.0),
+ body: SafeArea(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
- const StatBar(title: 'Statistics'),
- MainBtn(
- title: vpnProvider.isConnected ? 'Disconnect' : 'Connect',
- onPressed: () {
- vpnProvider.isConnected ? vpnProvider.disconnect() : vpnProvider.connect();
- }),
- LocationWidget(
- title: 'Location', selectedServer: vpnProvider.selectedServer),
+ const StatBar(),
+ const MainBtn(),
+ LocationWidget(selectedServer: _selectedServer),
],
),
),
- /* body: Column(
- mainAxisAlignment: MainAxisAlignment.spaceBetween,
- children: [
- const StatBar(),
- const MainBtn(),
- Column(
- mainAxisAlignment: MainAxisAlignment.center,
- children: [
- LocationWidget(selectedServer: _selectedServer)
- // GestureDetector(
- // onTap: _navigateToServersList,
- // child: LocationWidget(selectedServer: _selectedServer),
- // ),
- ],
- ),
- ],
- ),*/
);
}
}
diff --git a/lib/pages/servers/servers_list.dart b/lib/pages/servers/servers_list.dart
index de07e79..c4343ce 100644
--- a/lib/pages/servers/servers_list.dart
+++ b/lib/pages/servers/servers_list.dart
@@ -1,20 +1,164 @@
import 'package:flutter/material.dart';
+import 'package:shared_preferences/shared_preferences.dart';
import 'package:vpn_client/pages/servers/servers_list_item.dart';
+import 'package:vpn_client/localization_service.dart';
+import 'dart:convert';
-class ServersList extends StatelessWidget {
- final List