Skip to content

Commit 418a71f

Browse files
committed
Merge development into feat/setting_page+adapter_telegrambot: resolve conflicts by keeping development versions
2 parents 992ccdf + 8204432 commit 418a71f

18 files changed

Lines changed: 443 additions & 161 deletions

File tree

.github/workflows/quality.yml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ jobs:
2121
runs-on: ubuntu-latest
2222
steps:
2323
- uses: actions/checkout@v4
24-
- name: Setup dart
25-
uses: dart-lang/setup-dart@v1
24+
- name: Setup Flutter
25+
uses: subosito/flutter-action@v2
26+
with:
27+
channel: stable
28+
flutter-version-file: pubspec.yaml
2629
- name: Format code
2730
run: dart format --output=none .
2831
matrics:

.vscode/settings.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"IDX.aI.enableInlineCompletion": true,
3+
"IDX.aI.enableCodebaseIndexing": true
4+
}

CHANGELOG.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Changelog
2+
3+
4+
## [Unreleased]
5+
6+
### Added
7+
- Run scripts for different platforms (Android device/emulator, iOS device/simulator)
8+
- Flutter Gen integration for better asset management
9+
- Improved localization with proper l10n configuration
10+
- Enhanced search dialog UI and state management
11+
12+
### Changed
13+
- Replaced flutter_v2ray with vpnclient_engine_flutter
14+
- Updated dependencies and removed unused code
15+
- Improved UI responsiveness and overflow handling
16+
- Refactored main.dart for better localization support
17+
18+
### Fixed
19+
- Resolved potential UI overflow issues in search dialog
20+
- Enhanced state initialization and lifecycle management
21+
- Improved layout responsiveness and SafeArea integration
22+
23+
## [1.0.12] - 2025-01-XX
24+
25+
### Added
26+
- Initial release of VPN Client app
27+
- Basic VPN functionality
28+
- Multi-language support (English, Russian, Thai, Chinese)
29+
- Dark/Light theme support
30+
- Server selection interface
31+
- Apps management page
32+
33+
### Changed
34+
- Initial project structure and architecture
35+
36+
### Fixed
37+
- Various UI improvements and bug fixes

ios/Podfile.lock

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ EXTERNAL SOURCES:
2626

2727
SPEC CHECKSUMS:
2828
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
29-
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
30-
flutter_v2ray: 21ee520e013877941e2e67b3b48aed9c6870e8dc
31-
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
29+
flutter_native_splash: c32d145d68aeda5502d5f543ee38c192065986cf
30+
flutter_v2ray: 1190bb389b67a1dc9f28ece1d4b308101e38395e
31+
shared_preferences_foundation: 9e1978ff2562383bd5676f64ec4e9aa8fa06a6f7
3232

3333
PODFILE CHECKSUM: f8e15f817b1bf5846cb6aad560a31cf33c165196
3434

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// lib/core/constants/storage_keys.dart
2+
class StorageKeys {
3+
static const String selectedApps = 'selected_apps';
4+
static const String selectedServers = 'selected_servers';
5+
static const String recentlySearchedApps = 'recently_searched_apps';
6+
static const String recentlySearchedServers = 'recently_searched_servers';
7+
static const String isDarkTheme = 'isDarkTheme';
8+
}

lib/design/dimensions.dart

Lines changed: 0 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,3 @@
1-
import 'package:flutter/material.dart';
2-
3-
class CustomString {
4-
final BuildContext context;
5-
late Locale locale;
6-
7-
CustomString(this.context) {
8-
locale = Localizations.localeOf(context);
9-
}
10-
11-
String get connected {
12-
return _localized('connected');
13-
}
14-
15-
String get disconnected {
16-
return _localized('disconnected');
17-
}
18-
19-
String get connecting {
20-
return _localized('connecting');
21-
}
22-
23-
String get disconnecting {
24-
return _localized('disconnecting');
25-
}
26-
27-
String get allapp {
28-
return _localized('all_apps');
29-
}
30-
31-
String _localized(String key) {
32-
switch (locale.languageCode) {
33-
case 'ru':
34-
return {
35-
'connected': 'ПОДКЛЮЧЕН',
36-
'disconnected': 'ОТКЛЮЧЕН',
37-
'connecting': 'ПОДКЛЮЧЕНИЕ',
38-
'disconnecting': 'ОТКЛЮЧЕНИЕ',
39-
"all_apps": "Все приложения",
40-
}[key]!;
41-
case 'th':
42-
return {
43-
"connected": "เชื่อมต่อแล้ว",
44-
"disconnected": "ไม่ได้เชื่อมต่อ",
45-
"connecting": "กำลังเชื่อมต่อ",
46-
"disconnecting": "กำลังตัดการเชื่อมต่อ",
47-
"all_apps": "แอปทั้งหมด",
48-
}[key]!;
49-
case 'zh':
50-
return {
51-
"connected": "已连接",
52-
"disconnected": "已断开",
53-
"connecting": "正在连接",
54-
"disconnecting": "正在断开",
55-
"all_apps": "所有应用",
56-
}[key]!;
57-
case 'en':
58-
default:
59-
return {
60-
'connected': 'CONNECTED',
61-
'disconnected': 'DISCONNECTED',
62-
'connecting': 'CONNECTING',
63-
'disconnecting': 'DISCONNECTING',
64-
"all_apps": "All Applications",
65-
}[key]!;
66-
}
67-
}
68-
}
69-
701
// style
712
const double elevation0 = 0;
723

lib/l10n/app_en.arb

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
3+
"app_name": "VPN Client",
4+
"apps_selection": "App Selection",
5+
"search": "Search",
6+
"your_location": "Your Location",
7+
"auto_select": "Auto Select",
8+
"kazakhstan": "Kazakhstan",
9+
"turkey": "Turkey",
10+
"poland": "Poland",
11+
"fastest": "Fastest",
12+
"selected_server": "Selected server",
13+
"server_selection": "Server selection",
14+
"all_servers": "All servers",
15+
"country_name": "Country name",
16+
"all_apps": "All Applications",
17+
"done": "Done",
18+
"cancel": "Cancel",
19+
"recently_searched": "Recently searched",
20+
"nothing_found": "Nothing found",
21+
"connected": "CONNECTED",
22+
"disconnected": "DISCONNECTED",
23+
"reconnecting": "RECONNECTING",
24+
"connecting": "CONNECTING",
25+
"disconnecting": "DISCONNECTING"
26+
}

lib/models/nav_item.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import 'package:flutter/material.dart';
2+
3+
class NavItem {
4+
final Widget inactiveIcon;
5+
final Widget activeIcon;
6+
7+
NavItem({required this.inactiveIcon, required this.activeIcon});
8+
}

lib/pages/apps/apps_list.dart

Lines changed: 56 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import 'package:flutter/material.dart';
22
import 'package:shared_preferences/shared_preferences.dart';
3-
import 'package:vpn_client/design/dimensions.dart';
43
import 'apps_list_item.dart';
4+
import 'package:vpn_client/l10n/app_localizations.dart';
55
import 'dart:convert';
6+
import 'package:vpn_client/core/constants/storage_keys.dart'; // Importar as constantes
67

78
class AppsList extends StatefulWidget {
89
final Function(List<Map<String, dynamic>>)? onAppsLoaded;
@@ -17,32 +18,28 @@ class AppsList extends StatefulWidget {
1718
class AppsListState extends State<AppsList> {
1819
List<Map<String, dynamic>> _apps = [];
1920
bool _isLoading = true;
21+
bool _dataLoaded = false; // Flag para controlar o carregamento inicial
2022

2123
@override
2224
void initState() {
2325
super.initState();
2426
if (widget.apps != null && widget.apps!.isNotEmpty) {
2527
_apps = widget.apps!;
2628
_isLoading = false;
27-
if (widget.onAppsLoaded != null) {
28-
widget.onAppsLoaded!(_apps);
29-
}
30-
} else {
31-
_loadApps();
29+
_dataLoaded =
30+
true; // Marcar como carregado se dados iniciais foram fornecidos
31+
// widget.onAppsLoaded é chamado em didUpdateWidget ou após _loadApps
3232
}
3333
}
3434

3535
late String textallapps;
36-
bool _initialized = false;
37-
3836
@override
3937
void didChangeDependencies() {
4038
super.didChangeDependencies();
41-
if (!_initialized) {
42-
final statusText = CustomString(context);
43-
textallapps = statusText.allapp;
39+
if (!_dataLoaded) {
40+
// Carregar apenas se os dados não foram carregados via widget.apps ou anteriormente
41+
textallapps = AppLocalizations.of(context)!.all_apps;
4442
_loadApps();
45-
_initialized = true;
4643
}
4744
}
4845

@@ -53,22 +50,41 @@ class AppsListState extends State<AppsList> {
5350
setState(() {
5451
_apps = widget.apps!;
5552
_isLoading = false;
53+
_dataLoaded = true;
5654
});
5755
_saveSelectedApps();
5856
}
5957
}
6058

6159
Future<void> _loadApps() async {
6260
setState(() {
63-
_isLoading = true;
61+
// Evitar mostrar loading se já estiver carregando ou já carregou
62+
if (!_dataLoaded) _isLoading = true;
6463
});
6564

65+
// Simulação de carregamento
66+
// Em um app real, aqui viria a lógica de buscar dados de uma API ou DB
67+
await Future.delayed(const Duration(milliseconds: 100)); // Simular delay
68+
69+
// Definir textallapps aqui se ainda não foi definido em didChangeDependencies
70+
// Isso é um fallback, idealmente textallapps já está disponível.
71+
// Adicionando verificação de 'mounted' para o BuildContext
72+
if (!mounted) return;
73+
final localizations = AppLocalizations.of(context);
74+
textallapps = localizations?.all_apps ?? "All Applications";
75+
6676
try {
77+
// Se os dados já foram carregados (ex: por uma busca anterior que atualizou widget.apps), não recarregar do zero
78+
if (_dataLoaded && _apps.isNotEmpty) {
79+
setState(() => _isLoading = false);
80+
return;
81+
}
82+
6783
List<Map<String, dynamic>> appsList = [
6884
{
6985
'icon': null,
7086
'image': null,
71-
'text': textallapps,
87+
'text': textallapps, // Usar a string localizada
7288
'isSwitch': true,
7389
'isActive': false,
7490
},
@@ -106,7 +122,7 @@ class AppsListState extends State<AppsList> {
106122
]);
107123

108124
final prefs = await SharedPreferences.getInstance();
109-
final String? savedApps = prefs.getString('selected_apps');
125+
final String? savedApps = prefs.getString(StorageKeys.selectedApps);
110126
if (savedApps != null) {
111127
final List<dynamic> savedAppsList = jsonDecode(savedApps);
112128
for (var savedApp in savedAppsList) {
@@ -122,6 +138,7 @@ class AppsListState extends State<AppsList> {
122138
setState(() {
123139
_apps = appsList;
124140
_isLoading = false;
141+
_dataLoaded = true; // Marcar que os dados foram carregados
125142
});
126143

127144
if (widget.onAppsLoaded != null) {
@@ -130,6 +147,7 @@ class AppsListState extends State<AppsList> {
130147
} catch (e) {
131148
setState(() {
132149
_isLoading = false;
150+
_dataLoaded = true; // Marcar como tentado carregar para evitar loop
133151
});
134152
debugPrint('Error loading apps: $e');
135153
}
@@ -141,7 +159,7 @@ class AppsListState extends State<AppsList> {
141159
_apps
142160
.map((app) => {'text': app['text'], 'isActive': app['isActive']})
143161
.toList();
144-
await prefs.setString('selected_apps', jsonEncode(selectedApps));
162+
await prefs.setString(StorageKeys.selectedApps, jsonEncode(selectedApps));
145163
}
146164

147165
List<Map<String, dynamic>> get apps => _apps;
@@ -150,14 +168,19 @@ class AppsListState extends State<AppsList> {
150168
setState(() {
151169
if (index == 0 && _apps[index]['isSwitch']) {
152170
_apps[0]['isActive'] = !_apps[0]['isActive'];
171+
// Se "Todos os aplicativos" for ativado, desabilitar os outros itens da lista (visual)
172+
// A lógica de 'isEnabled' no AppListItem já cuida disso visualmente.
173+
// Aqui, garantimos que os outros não estejam 'isActive' se "Todos" estiver ativo.
153174
if (_apps[0]['isActive']) {
154175
for (int i = 1; i < _apps.length; i++) {
155-
_apps[i]['isEnabled'] = false;
176+
_apps[i]['isActive'] =
177+
false; // Desmarcar outros se "Todos" for selecionado
156178
}
157179
}
158180
} else {
159181
_apps[index]['isActive'] = !_apps[index]['isActive'];
160-
if (_apps[index]['isActive']) {
182+
// Se um app individual for ativado, "Todos os aplicativos" deve ser desativado
183+
if (_apps[index]['isActive'] && index != 0) {
161184
_apps[0]['isActive'] = false;
162185
}
163186
}
@@ -170,6 +193,18 @@ class AppsListState extends State<AppsList> {
170193

171194
@override
172195
Widget build(BuildContext context) {
196+
// Garante que textallapps seja inicializado se didChangeDependencies não for chamado a tempo
197+
// ou se o widget for reconstruído antes.
198+
textallapps = AppLocalizations.of(context)?.all_apps ?? "All Applications";
199+
200+
// Atualiza o texto do primeiro item se ele ainda não estiver com o texto localizado
201+
// Isso pode acontecer se _loadApps for chamado antes de textallapps ser definido por didChangeDependencies
202+
if (_apps.isNotEmpty &&
203+
_apps[0]['text'] != textallapps &&
204+
_apps[0]['isSwitch'] == true) {
205+
_apps[0]['text'] = textallapps;
206+
}
207+
173208
return Container(
174209
width: MediaQuery.of(context).size.width,
175210
height: MediaQuery.of(context).size.height,
@@ -191,7 +226,9 @@ class AppsListState extends State<AppsList> {
191226
text: _apps[index]['text'],
192227
isSwitch: _apps[index]['isSwitch'],
193228
isActive: _apps[index]['isActive'],
194-
isEnabled: index == 0 || !_apps[0]['isActive'],
229+
isEnabled:
230+
index == 0 ||
231+
!_apps[0]['isActive'], // Item é habilitado se for o switch "Todos" ou se "Todos" não estiver ativo
195232
onTap: () => _onItemTapped(index),
196233
);
197234
}),

0 commit comments

Comments
 (0)