diff --git a/lib/core/constants/storage_keys.dart b/lib/core/constants/storage_keys.dart new file mode 100644 index 0000000..5277ad4 --- /dev/null +++ b/lib/core/constants/storage_keys.dart @@ -0,0 +1,8 @@ +// lib/core/constants/storage_keys.dart +class StorageKeys { + static const String selectedApps = 'selected_apps'; + static const String selectedServers = 'selected_servers'; + static const String recentlySearchedApps = 'recently_searched_apps'; + static const String recentlySearchedServers = 'recently_searched_servers'; + static const String isDarkTheme = 'isDarkTheme'; +} diff --git a/lib/design/dimensions.dart b/lib/design/dimensions.dart index d840e3b..fd39498 100644 --- a/lib/design/dimensions.dart +++ b/lib/design/dimensions.dart @@ -1,72 +1,3 @@ -import 'package:flutter/material.dart'; - -class CustomString { - final BuildContext context; - late Locale locale; - - CustomString(this.context) { - locale = Localizations.localeOf(context); - } - - String get connected { - return _localized('connected'); - } - - String get disconnected { - return _localized('disconnected'); - } - - String get connecting { - return _localized('connecting'); - } - - String get disconnecting { - return _localized('disconnecting'); - } - - String get allapp { - return _localized('all_apps'); - } - - String _localized(String key) { - switch (locale.languageCode) { - case 'ru': - return { - 'connected': 'ПОДКЛЮЧЕН', - 'disconnected': 'ОТКЛЮЧЕН', - 'connecting': 'ПОДКЛЮЧЕНИЕ', - 'disconnecting': 'ОТКЛЮЧЕНИЕ', - "all_apps": "Все приложения", - }[key]!; - case 'th': - return { - "connected": "เชื่อมต่อแล้ว", - "disconnected": "ไม่ได้เชื่อมต่อ", - "connecting": "กำลังเชื่อมต่อ", - "disconnecting": "กำลังตัดการเชื่อมต่อ", - "all_apps": "แอปทั้งหมด", - }[key]!; - case 'zh': - return { - "connected": "已连接", - "disconnected": "已断开", - "connecting": "正在连接", - "disconnecting": "正在断开", - "all_apps": "所有应用", - }[key]!; - case 'en': - default: - return { - 'connected': 'CONNECTED', - 'disconnected': 'DISCONNECTED', - 'connecting': 'CONNECTING', - 'disconnecting': 'DISCONNECTING', - "all_apps": "All Applications", - }[key]!; - } - } -} - // style const double elevation0 = 0; diff --git a/lib/main.dart b/lib/main.dart index 374dd5c..f9617bb 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:vpn_client/l10n/app_localizations.dart'; import 'package:provider/provider.dart'; import 'package:vpn_client/pages/apps/apps_page.dart'; diff --git a/lib/pages/apps/apps_list.dart b/lib/pages/apps/apps_list.dart index 9074481..a1b2486 100644 --- a/lib/pages/apps/apps_list.dart +++ b/lib/pages/apps/apps_list.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; -import 'package:vpn_client/design/dimensions.dart'; import 'apps_list_item.dart'; +import 'package:vpn_client/l10n/app_localizations.dart'; import 'dart:convert'; +import 'package:vpn_client/core/constants/storage_keys.dart'; // Importar as constantes class AppsList extends StatefulWidget { final Function(List>)? onAppsLoaded; @@ -17,6 +18,7 @@ class AppsList extends StatefulWidget { class AppsListState extends State { List> _apps = []; bool _isLoading = true; + bool _dataLoaded = false; // Flag para controlar o carregamento inicial @override void initState() { @@ -24,25 +26,20 @@ class AppsListState extends State { if (widget.apps != null && widget.apps!.isNotEmpty) { _apps = widget.apps!; _isLoading = false; - if (widget.onAppsLoaded != null) { - widget.onAppsLoaded!(_apps); - } - } else { - _loadApps(); + _dataLoaded = + true; // Marcar como carregado se dados iniciais foram fornecidos + // widget.onAppsLoaded é chamado em didUpdateWidget ou após _loadApps } } late String textallapps; - bool _initialized = false; - @override void didChangeDependencies() { super.didChangeDependencies(); - if (!_initialized) { - final statusText = CustomString(context); - textallapps = statusText.allapp; + if (!_dataLoaded) { + // Carregar apenas se os dados não foram carregados via widget.apps ou anteriormente + textallapps = AppLocalizations.of(context)!.all_apps; _loadApps(); - _initialized = true; } } @@ -53,6 +50,7 @@ class AppsListState extends State { setState(() { _apps = widget.apps!; _isLoading = false; + _dataLoaded = true; }); _saveSelectedApps(); } @@ -60,15 +58,33 @@ class AppsListState extends State { Future _loadApps() async { setState(() { - _isLoading = true; + // Evitar mostrar loading se já estiver carregando ou já carregou + if (!_dataLoaded) _isLoading = true; }); + // Simulação de carregamento + // Em um app real, aqui viria a lógica de buscar dados de uma API ou DB + await Future.delayed(const Duration(milliseconds: 100)); // Simular delay + + // Definir textallapps aqui se ainda não foi definido em didChangeDependencies + // Isso é um fallback, idealmente textallapps já está disponível. + // Adicionando verificação de 'mounted' para o BuildContext + if (!mounted) return; + final localizations = AppLocalizations.of(context); + textallapps = localizations?.all_apps ?? "All Applications"; + try { + // Se os dados já foram carregados (ex: por uma busca anterior que atualizou widget.apps), não recarregar do zero + if (_dataLoaded && _apps.isNotEmpty) { + setState(() => _isLoading = false); + return; + } + List> appsList = [ { 'icon': null, 'image': null, - 'text': textallapps, + 'text': textallapps, // Usar a string localizada 'isSwitch': true, 'isActive': false, }, @@ -106,7 +122,7 @@ class AppsListState extends State { ]); final prefs = await SharedPreferences.getInstance(); - final String? savedApps = prefs.getString('selected_apps'); + final String? savedApps = prefs.getString(StorageKeys.selectedApps); if (savedApps != null) { final List savedAppsList = jsonDecode(savedApps); for (var savedApp in savedAppsList) { @@ -122,6 +138,7 @@ class AppsListState extends State { setState(() { _apps = appsList; _isLoading = false; + _dataLoaded = true; // Marcar que os dados foram carregados }); if (widget.onAppsLoaded != null) { @@ -130,6 +147,7 @@ class AppsListState extends State { } catch (e) { setState(() { _isLoading = false; + _dataLoaded = true; // Marcar como tentado carregar para evitar loop }); debugPrint('Error loading apps: $e'); } @@ -141,7 +159,7 @@ class AppsListState extends State { _apps .map((app) => {'text': app['text'], 'isActive': app['isActive']}) .toList(); - await prefs.setString('selected_apps', jsonEncode(selectedApps)); + await prefs.setString(StorageKeys.selectedApps, jsonEncode(selectedApps)); } List> get apps => _apps; @@ -150,14 +168,19 @@ class AppsListState extends State { setState(() { if (index == 0 && _apps[index]['isSwitch']) { _apps[0]['isActive'] = !_apps[0]['isActive']; + // Se "Todos os aplicativos" for ativado, desabilitar os outros itens da lista (visual) + // A lógica de 'isEnabled' no AppListItem já cuida disso visualmente. + // Aqui, garantimos que os outros não estejam 'isActive' se "Todos" estiver ativo. if (_apps[0]['isActive']) { for (int i = 1; i < _apps.length; i++) { - _apps[i]['isEnabled'] = false; + _apps[i]['isActive'] = + false; // Desmarcar outros se "Todos" for selecionado } } } else { _apps[index]['isActive'] = !_apps[index]['isActive']; - if (_apps[index]['isActive']) { + // Se um app individual for ativado, "Todos os aplicativos" deve ser desativado + if (_apps[index]['isActive'] && index != 0) { _apps[0]['isActive'] = false; } } @@ -170,6 +193,18 @@ class AppsListState extends State { @override Widget build(BuildContext context) { + // Garante que textallapps seja inicializado se didChangeDependencies não for chamado a tempo + // ou se o widget for reconstruído antes. + textallapps = AppLocalizations.of(context)?.all_apps ?? "All Applications"; + + // Atualiza o texto do primeiro item se ele ainda não estiver com o texto localizado + // Isso pode acontecer se _loadApps for chamado antes de textallapps ser definido por didChangeDependencies + if (_apps.isNotEmpty && + _apps[0]['text'] != textallapps && + _apps[0]['isSwitch'] == true) { + _apps[0]['text'] = textallapps; + } + return Container( width: MediaQuery.of(context).size.width, height: MediaQuery.of(context).size.height, @@ -191,7 +226,9 @@ class AppsListState extends State { text: _apps[index]['text'], isSwitch: _apps[index]['isSwitch'], isActive: _apps[index]['isActive'], - isEnabled: index == 0 || !_apps[0]['isActive'], + isEnabled: + index == 0 || + !_apps[0]['isActive'], // Item é habilitado se for o switch "Todos" ou se "Todos" não estiver ativo onTap: () => _onItemTapped(index), ); }), diff --git a/lib/pages/apps/apps_list_item.dart b/lib/pages/apps/apps_list_item.dart index 15573c4..e69a031 100644 --- a/lib/pages/apps/apps_list_item.dart +++ b/lib/pages/apps/apps_list_item.dart @@ -33,12 +33,15 @@ class AppListItem extends StatelessWidget { height: 52, margin: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( - color: Colors.white, + color: + Theme.of(context).colorScheme.onSurface, // Exemplo de uso do tema borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( - color: Colors.grey.withAlpha((255 * 0.2).toInt()), - blurRadius: 10, + color: Theme.of( + context, + ).shadowColor.withAlpha((255 * 0.1).round()), // Exemplo + blurRadius: 5, // Ajuste conforme necessário offset: const Offset(0, 1), ), ], @@ -70,9 +73,11 @@ class AppListItem extends StatelessWidget { child: Text( text, style: const TextStyle( - fontSize: 16, - color: Colors.black, - ), + fontSize: 16, // Considere usar TextTheme + // color: Colors.black, // Removido para usar cor padrão do tema ou definir explicitamente via tema + ).apply( + color: Theme.of(context).colorScheme.primary, + ), // Exemplo ), ), ], @@ -90,7 +95,8 @@ class AppListItem extends StatelessWidget { : Checkbox( value: isActive, onChanged: null, - checkColor: Colors.white, + checkColor: + Theme.of(context).colorScheme.onPrimary, // Exemplo fillColor: WidgetStateProperty.resolveWith((states) { if (!isActive) { return Theme.of(context).colorScheme.onSecondary; @@ -113,7 +119,9 @@ class AppListItem extends StatelessWidget { if (!isEnabled) Container( decoration: BoxDecoration( - color: Colors.grey.withAlpha((255 * 0.2).toInt()), + color: Theme.of( + context, + ).disabledColor.withAlpha((255 * 0.2).round()), // Exemplo borderRadius: BorderRadius.circular(10), ), ), diff --git a/lib/pages/apps/apps_page.dart b/lib/pages/apps/apps_page.dart index 9e45e79..e47199f 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:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:vpn_client/l10n/app_localizations.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 889911f..023b6b0 100644 --- a/lib/pages/main/location_widget.dart +++ b/lib/pages/main/location_widget.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:vpn_client/l10n/app_localizations.dart'; class LocationWidget extends StatelessWidget { final Map? selectedServer; diff --git a/lib/pages/main/main_btn.dart b/lib/pages/main/main_btn.dart index 650a834..4d350c1 100644 --- a/lib/pages/main/main_btn.dart +++ b/lib/pages/main/main_btn.dart @@ -2,9 +2,11 @@ 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:vpn_client/l10n/app_localizations.dart'; import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart'; +enum VpnConnectionState { connected, disconnected, connecting, disconnecting } + class MainBtn extends StatefulWidget { const MainBtn({super.key}); @@ -15,22 +17,25 @@ class MainBtn extends StatefulWidget { class MainBtnState extends State with SingleTickerProviderStateMixin { ///static const platform = MethodChannel('vpnclient_engine2'); /// - late CustomString statusText; - late String connectionStatus; + late VpnConnectionState _vpnState; late String connectionStatusDisconnected; late String connectionStatusDisconnecting; late String connectionStatusConnected; late String connectionStatusConnecting; + bool _initialized = false; @override void didChangeDependencies() { super.didChangeDependencies(); - final statusText = CustomString(context); - connectionStatus = statusText.disconnected; - connectionStatusDisconnected = statusText.disconnected; - connectionStatusConnected = statusText.connected; - connectionStatusDisconnecting = statusText.disconnecting; - connectionStatusConnecting = statusText.connecting; + // Initialize localized strings once + connectionStatusDisconnected = AppLocalizations.of(context)!.disconnected; + connectionStatusConnected = AppLocalizations.of(context)!.connected; + connectionStatusDisconnecting = AppLocalizations.of(context)!.disconnecting; + connectionStatusConnecting = AppLocalizations.of(context)!.connecting; + if (!_initialized) { + _vpnState = VpnConnectionState.disconnected; + _initialized = true; + } } String connectionTime = "00:00:00"; @@ -76,25 +81,38 @@ class MainBtnState extends State with SingleTickerProviderStateMixin { _timer?.cancel(); setState(() { connectionTime = "00:00:00"; - connectionStatus = connectionStatusDisconnected; + _vpnState = VpnConnectionState.disconnected; }); } + String get currentStatusText { + switch (_vpnState) { + case VpnConnectionState.connected: + return connectionStatusConnected; + case VpnConnectionState.disconnected: + return connectionStatusDisconnected; + case VpnConnectionState.connecting: + return connectionStatusConnecting; + case VpnConnectionState.disconnecting: + return connectionStatusDisconnecting; + } + } + Future _handleConnection() async { - if (connectionStatus != connectionStatusConnected && - connectionStatus != connectionStatusDisconnected) { + if (_vpnState == VpnConnectionState.connecting || + _vpnState == VpnConnectionState.disconnecting) { return; } setState(() { - if (connectionStatus == connectionStatusConnected) { - connectionStatus = connectionStatusDisconnecting; - } else if (connectionStatus == connectionStatusDisconnected) { - connectionStatus = connectionStatusConnecting; + if (_vpnState == VpnConnectionState.connected) { + _vpnState = VpnConnectionState.disconnecting; + } else if (_vpnState == VpnConnectionState.disconnected) { + _vpnState = VpnConnectionState.connecting; } }); - if (connectionStatus == connectionStatusConnecting) { + if (_vpnState == VpnConnectionState.connecting) { _animationController.repeat(reverse: true); VPNclientEngine.ClearSubscriptions(); @@ -115,16 +133,16 @@ class MainBtnState extends State with SingleTickerProviderStateMixin { await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 1); startTimer(); setState(() { - connectionStatus = connectionStatusConnected; + _vpnState = VpnConnectionState.connected; }); await _animationController.forward(); _animationController.stop(); - } else if (connectionStatus == connectionStatusDisconnecting) { + } else if (_vpnState == VpnConnectionState.disconnecting) { _animationController.repeat(reverse: true); stopTimer(); await VPNclientEngine.disconnect(); setState(() { - connectionStatus = connectionStatusDisconnected; + _vpnState = VpnConnectionState.disconnected; }); await _animationController.reverse(); _animationController.stop(); @@ -141,7 +159,7 @@ class MainBtnState extends State with SingleTickerProviderStateMixin { fontSize: 40, fontWeight: FontWeight.w600, color: - connectionStatus == connectionStatusConnected + _vpnState == VpnConnectionState.connected ? Theme.of(context).colorScheme.primary : Theme.of(context).colorScheme.secondary, ), @@ -156,7 +174,10 @@ class MainBtnState extends State with SingleTickerProviderStateMixin { width: 150, height: 150, decoration: BoxDecoration( - color: Colors.grey[300], + color: + Theme.of(context) + .colorScheme + .surfaceContainerHighest, // Usar cor do tema conforme sugestão do linter shape: BoxShape.circle, ), ), @@ -188,11 +209,11 @@ class MainBtnState extends State with SingleTickerProviderStateMixin { ), const SizedBox(height: 20), Text( - connectionStatus, + currentStatusText, style: TextStyle( fontSize: 16, fontWeight: FontWeight.w500, - color: Colors.black, + color: Theme.of(context).textTheme.bodyLarge?.color, ), ), ], diff --git a/lib/pages/main/main_page.dart b/lib/pages/main/main_page.dart index 8c41a9f..03e02e5 100644 --- a/lib/pages/main/main_page.dart +++ b/lib/pages/main/main_page.dart @@ -4,7 +4,7 @@ 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:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:vpn_client/l10n/app_localizations.dart'; class MainPage extends StatefulWidget { const MainPage({super.key}); diff --git a/lib/pages/main/stat_bar.dart b/lib/pages/main/stat_bar.dart index 4dc4e94..ea8684a 100644 --- a/lib/pages/main/stat_bar.dart +++ b/lib/pages/main/stat_bar.dart @@ -25,49 +25,57 @@ class StatBarState extends State { Widget _buildStatItem(IconData icon, String text, BuildContext context) { return Container( - width: 100, - height: 75, + width: + (MediaQuery.of(context).size.width / 3) - 20, // Para dar algum espaço + padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 8), decoration: BoxDecoration( + color: Theme.of(context).colorScheme.onSurface, + borderRadius: BorderRadius.circular(12), boxShadow: [ BoxShadow( - color: Color(0x1A9CB2C2), - offset: Offset(0.0, 1.0), - blurRadius: 32.0, + color: Theme.of( + context, + ).shadowColor.withAlpha((255 * 0.1).round()), // Usar cor do tema + offset: const Offset(0.0, 2.0), + blurRadius: 8.0, ), ], ), - child: FloatingActionButton( - elevation: elevation0, - onPressed: () {}, - backgroundColor: Theme.of(context).colorScheme.onSurface, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Container( - alignment: Alignment.center, - width: 24, - height: 24, - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.primary, - borderRadius: BorderRadius.circular(6.0), - ), - child: Icon( - icon, - size: 20, - color: Theme.of(context).colorScheme.onSurface, - ), + // Se precisar de ação de clique, envolva com InkWell ou GestureDetector + // InkWell( + // onTap: () {}, + // borderRadius: BorderRadius.circular(12), + // child: ... + // ) + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Container( + padding: const EdgeInsets.all( + 4, + ), // Espaçamento interno para o ícone + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.primary.withAlpha( + (255 * 0.1).round(), + ), // Cor de fundo suave + borderRadius: BorderRadius.circular(8.0), ), - const SizedBox(height: 6), - Text( - text, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Theme.of(context).colorScheme.primary, - ), + child: Icon( + icon, + size: 22, + color: Theme.of(context).colorScheme.primary, ), - ], - ), + ), + const SizedBox(height: 8), + Text( + text, + style: TextStyle( + fontSize: fontSize14, // Usando constante de dimensions.dart + fontWeight: FontWeight.w500, + color: Theme.of(context).colorScheme.primary, + ), + ), + ], ), ); } diff --git a/lib/pages/servers/servers_list.dart b/lib/pages/servers/servers_list.dart index 7a89d8d..1868347 100644 --- a/lib/pages/servers/servers_list.dart +++ b/lib/pages/servers/servers_list.dart @@ -1,16 +1,22 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:vpn_client/pages/servers/servers_list_item.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:vpn_client/l10n/app_localizations.dart'; import 'dart:convert'; +import 'package:vpn_client/core/constants/storage_keys.dart'; class ServersList extends StatefulWidget { final Function(List>)? onServersLoaded; final List>? servers; + final Function(int)? + onItemTapNavigate; // Renomeado para clareza ou pode ser uma callback mais específica - const ServersList({super.key, this.onServersLoaded, this.servers}); - - get onNavBarTap => null; + const ServersList({ + super.key, + this.onServersLoaded, + this.servers, + this.onItemTapNavigate, + }); @override State createState() => ServersListState(); @@ -19,6 +25,7 @@ class ServersList extends StatefulWidget { class ServersListState extends State { List> _servers = []; bool _isLoading = true; + bool _dataLoaded = false; // Flag para controlar o carregamento inicial @override void initState() { @@ -26,17 +33,17 @@ class ServersListState extends State { if (widget.servers != null && widget.servers!.isNotEmpty) { _servers = widget.servers!; _isLoading = false; - if (widget.onServersLoaded != null) { - widget.onServersLoaded!(_servers); - } + _dataLoaded = + true; // Marcar como carregado se dados iniciais foram fornecidos + // widget.onServersLoaded é chamado em didUpdateWidget ou após _loadServers } } @override void didChangeDependencies() { super.didChangeDependencies(); - - if (_servers.isEmpty) { + if (!_dataLoaded) { + // Carregar apenas se os dados não foram carregados via widget.servers ou anteriormente _loadServers(); } } @@ -48,6 +55,7 @@ class ServersListState extends State { setState(() { _servers = widget.servers!; _isLoading = false; + _dataLoaded = true; }); _saveSelectedServers(); } @@ -55,47 +63,63 @@ class ServersListState extends State { Future _loadServers() async { setState(() { - _isLoading = true; + // Evitar mostrar loading se já estiver carregando ou já carregou + if (!_dataLoaded) _isLoading = true; }); + // Simulação de carregamento + await Future.delayed(const Duration(milliseconds: 100)); // Simular delay + try { + // Se os dados já foram carregados (ex: por uma busca anterior que atualizou widget.servers), não recarregar do zero + if (_dataLoaded && _servers.isNotEmpty) { + setState(() => _isLoading = false); + return; + } + + // É importante que AppLocalizations.of(context) seja chamado quando o context está pronto. + // didChangeDependencies é um bom lugar, ou aqui se garantirmos que o context está disponível. + // Adicionando verificação de 'mounted' para o BuildContext + if (!mounted) return; + final localizations = AppLocalizations.of(context)!; + List> serversList = [ { 'icon': 'assets/images/flags/auto.svg', - 'text': AppLocalizations.of(context)!.auto_select, - 'ping': AppLocalizations.of(context)!.fastest, + 'text': localizations.auto_select, + 'ping': localizations.fastest, 'isActive': true, }, { 'icon': 'assets/images/flags/Kazahstan.svg', - 'text': AppLocalizations.of(context)!.kazakhstan, + 'text': localizations.kazakhstan, 'ping': '48', 'isActive': false, }, { 'icon': 'assets/images/flags/Turkey.svg', - 'text': AppLocalizations.of(context)!.turkey, + 'text': localizations.turkey, 'ping': '142', 'isActive': false, }, { 'icon': 'assets/images/flags/Poland.svg', - 'text': AppLocalizations.of(context)!.poland, + 'text': localizations.poland, 'ping': '298', 'isActive': false, }, ]; final prefs = await SharedPreferences.getInstance(); - final String? savedServers = prefs.getString('selected_servers'); + final String? savedServers = prefs.getString(StorageKeys.selectedServers); if (savedServers != null) { final List savedServersList = jsonDecode(savedServers); - for (var savedApp in savedServersList) { + for (var savedServerItem in savedServersList) { final index = serversList.indexWhere( - (server) => server['text'] == savedApp['text'], + (server) => server['text'] == savedServerItem['text'], ); if (index != -1) { - serversList[index]['isActive'] = savedApp['isActive']; + serversList[index]['isActive'] = savedServerItem['isActive']; } } } @@ -103,6 +127,7 @@ class ServersListState extends State { setState(() { _servers = serversList; _isLoading = false; + _dataLoaded = true; // Marcar que os dados foram carregados }); if (widget.onServersLoaded != null) { @@ -111,6 +136,7 @@ class ServersListState extends State { } catch (e) { setState(() { _isLoading = false; + _dataLoaded = true; // Marcar como tentado carregar para evitar loop }); debugPrint('Error loading servers: $e'); } @@ -118,7 +144,7 @@ class ServersListState extends State { Future _saveSelectedServers() async { final prefs = await SharedPreferences.getInstance(); - final selectedServers = + final selectedServersData = _servers .map( (server) => { @@ -129,17 +155,19 @@ class ServersListState extends State { }, ) .toList(); - await prefs.setString('selected_servers', jsonEncode(selectedServers)); + await prefs.setString( + StorageKeys.selectedServers, + jsonEncode(selectedServersData), + ); } List> get servers => _servers; - void _onItemTapped(int index) { + void _onItemTapped(int indexInFullList) { setState(() { for (int i = 0; i < _servers.length; i++) { - _servers[i]['isActive'] = false; + _servers[i]['isActive'] = (i == indexInFullList); } - _servers[index]['isActive'] = true; }); _saveSelectedServers(); @@ -147,13 +175,24 @@ class ServersListState extends State { widget.onServersLoaded!(_servers); } - if (widget.onNavBarTap != null) { - widget.onNavBarTap!(2); + if (widget.onItemTapNavigate != null) { + widget.onItemTapNavigate!(indexInFullList); } } @override Widget build(BuildContext context) { + // Garante que as strings localizadas sejam usadas se _loadServers for chamado antes de didChangeDependencies + // ou se o widget for reconstruído. + if (_servers.isNotEmpty && AppLocalizations.of(context) != null) { + final localizations = AppLocalizations.of(context)!; + if (_servers[0]['text'] != localizations.auto_select) { + // Isso pode ser perigoso se a ordem dos servidores mudar. + // É melhor garantir que _loadServers seja chamado com o contexto correto. + // Para simplificar, vamos assumir que _loadServers já lidou com isso. + } + } + final activeServers = _servers.where((server) => server['isActive'] == true).toList(); final inactiveServers = @@ -176,14 +215,20 @@ class ServersListState extends State { children: [ if (activeServers.isNotEmpty) ...[ Container( - margin: const EdgeInsets.only(left: 10), + margin: const EdgeInsets.only( + left: 10, + top: 10, + bottom: 5, + ), // Adicionado espaçamento child: Text( AppLocalizations.of(context)!.selected_server, - style: TextStyle(color: Colors.grey), + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontSize: 14, // Consistência de tamanho + ), ), ), - ...List.generate(activeServers.length, (index) { - final server = activeServers[index]; + ...activeServers.map((server) { return ServerListItem( icon: server['icon'], text: server['text'], @@ -195,14 +240,20 @@ class ServersListState extends State { ], if (inactiveServers.isNotEmpty) ...[ Container( - margin: const EdgeInsets.only(left: 10), + margin: const EdgeInsets.only( + left: 10, + top: 15, + bottom: 5, + ), // Adicionado espaçamento child: Text( AppLocalizations.of(context)!.all_servers, - style: TextStyle(color: Colors.grey), + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + fontSize: 14, // Consistência de tamanho + ), ), ), - ...List.generate(inactiveServers.length, (index) { - final server = inactiveServers[index]; + ...inactiveServers.map((server) { return ServerListItem( icon: server['icon'], text: server['text'], diff --git a/lib/pages/servers/servers_list_item.dart b/lib/pages/servers/servers_list_item.dart index cb2187c..884371d 100644 --- a/lib/pages/servers/servers_list_item.dart +++ b/lib/pages/servers/servers_list_item.dart @@ -37,11 +37,13 @@ class ServerListItem extends StatelessWidget { height: 52, margin: const EdgeInsets.symmetric(vertical: 8), decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).colorScheme.onSurface, // Usar cor do tema borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( - color: Colors.grey.withValues(alpha: 0.2), + color: Theme.of( + context, + ).shadowColor.withAlpha((255 * 0.1).round()), // Usar cor do tema blurRadius: 10, offset: const Offset(0, 1), ), @@ -62,7 +64,13 @@ class ServerListItem extends StatelessWidget { height: 52, child: Text( text, - style: const TextStyle(fontSize: 16, color: Colors.black), + style: TextStyle( + fontSize: 16, + color: + Theme.of( + context, + ).colorScheme.primary, // Usar cor do tema + ), ), ), ], @@ -74,7 +82,13 @@ class ServerListItem extends StatelessWidget { children: [ Text( int.tryParse(ping) != null ? '$ping ms' : ping, - style: const TextStyle(fontSize: 14, color: Colors.grey), + style: TextStyle( + fontSize: 14, + color: + Theme.of( + context, + ).colorScheme.secondary, // Usar cor do tema + ), ), if (ping.isNotEmpty) Image.asset(pingImage, width: 52, height: 52), diff --git a/lib/pages/servers/servers_page.dart b/lib/pages/servers/servers_page.dart index 41b0ad2..501e62d 100644 --- a/lib/pages/servers/servers_page.dart +++ b/lib/pages/servers/servers_page.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:vpn_client/pages/servers/servers_list.dart'; import 'package:vpn_client/search_dialog.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:vpn_client/l10n/app_localizations.dart'; class ServersPage extends StatefulWidget { final Function(int) onNavBarTap; @@ -80,6 +80,10 @@ class ServersPageState extends State { }); }, servers: _servers, + onItemTapNavigate: (selectedIndex) { + // Passando a callback + widget.onNavBarTap(2); // Navega para a página principal (índice 2) + }, ), ); } diff --git a/lib/search_dialog.dart b/lib/search_dialog.dart index 67d3a06..89b77bb 100644 --- a/lib/search_dialog.dart +++ b/lib/search_dialog.dart @@ -2,7 +2,8 @@ import 'package:flutter/material.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'package:vpn_client/pages/apps/apps_list_item.dart'; import 'package:vpn_client/pages/servers/servers_list_item.dart'; -import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:vpn_client/l10n/app_localizations.dart'; +import 'package:vpn_client/core/constants/storage_keys.dart'; import 'dart:convert'; class SearchDialog extends StatefulWidget { @@ -23,31 +24,52 @@ class SearchDialog extends StatefulWidget { class _SearchDialogState extends State { final TextEditingController _searchController = TextEditingController(); - late List> _filteredItems; + List> _filteredItems = []; List> _recentlySearchedItems = []; late int _searchDialogType; + String? _allAppsString; + bool _dependenciesInitialized = false; + @override void initState() { super.initState(); _searchDialogType = widget.type; + _searchController.addListener(_filterItems); _loadRecentlySearched(); + } + + @override + void didChangeDependencies() { + super.didChangeDependencies(); + if (!_dependenciesInitialized) { + _allAppsString = AppLocalizations.of(context)!.all_apps; + _initializeFilteredItems(); + _dependenciesInitialized = true; + } + } + + void _initializeFilteredItems() { _filteredItems = widget.items.where((item) { - if (_searchDialogType == 1) { - return item['text'] != 'Все приложения'; + if (_searchDialogType == 1 && _allAppsString != null) { + return item['text'] != _allAppsString; } return true; }).toList(); - _searchController.addListener(_filterItems); + if (_searchController.text.isNotEmpty) { + _filterItems(); + } else { + setState(() {}); + } } Future _loadRecentlySearched() async { final prefs = await SharedPreferences.getInstance(); final String key = _searchDialogType == 1 - ? 'recently_searched_apps' - : 'recently_searched_servers'; + ? StorageKeys.recentlySearchedApps + : StorageKeys.recentlySearchedServers; final String? recentlySearched = prefs.getString(key); if (recentlySearched != null) { setState(() { @@ -58,12 +80,12 @@ class _SearchDialogState extends State { } } - Future _saveRecentlySearched(Map item) async { + Future _addOrUpdateRecentlySearched(Map item) async { final prefs = await SharedPreferences.getInstance(); final String key = _searchDialogType == 1 - ? 'recently_searched_apps' - : 'recently_searched_servers'; + ? StorageKeys.recentlySearchedApps + : StorageKeys.recentlySearchedServers; setState(() { _recentlySearchedItems.removeWhere((i) => i['text'] == item['text']); _recentlySearchedItems.insert(0, item); @@ -79,20 +101,21 @@ class _SearchDialogState extends State { setState(() { _filteredItems = widget.items.where((item) { - if (_searchDialogType == 1) { + if (_searchDialogType == 1 && _allAppsString != null) { return item['text'].toLowerCase().contains(query) && - item['text'] != 'Все приложения'; + item['text'] != _allAppsString; } return item['text'].toLowerCase().contains(query); }).toList(); }); } - void _updateServerSelection(Map selectedItem) { - // Обновляем isActive для всех элементов: выбранный становится активным, остальные — неактивными + void _handleServerSelection(Map selectedItem) { for (var item in widget.items) { item['isActive'] = item['text'] == selectedItem['text']; } + _addOrUpdateRecentlySearched(selectedItem); + Navigator.of(context).pop(widget.items); } @override @@ -104,11 +127,6 @@ class _SearchDialogState extends State { @override Widget build(BuildContext context) { final isQueryEmpty = _searchController.text.isEmpty; - final hasRecentSearches = _recentlySearchedItems.isNotEmpty; - - final showFilteredItems = - !isQueryEmpty || (isQueryEmpty && !hasRecentSearches); - final showRecentSearches = isQueryEmpty && hasRecentSearches; return Dialog( insetPadding: EdgeInsets.zero, @@ -133,67 +151,60 @@ class _SearchDialogState extends State { child: Stack( alignment: Alignment.center, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - AppLocalizations.of(context)!.search, - style: TextStyle( - fontSize: 24, - fontWeight: FontWeight.w600, - color: Theme.of(context).colorScheme.primary, - ), + Center( + child: Text( + AppLocalizations.of(context)!.search, + style: TextStyle( + fontSize: 24, + fontWeight: FontWeight.w600, + color: Theme.of(context).colorScheme.primary, ), - ], + ), ), if (_searchDialogType == 1) - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(widget.items); - }, - child: Text( - AppLocalizations.of(context)!.done, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.blue, - fontSize: 16, - ), + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () { + Navigator.of(context).pop(widget.items); + }, + child: Text( + AppLocalizations.of(context)!.done, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontSize: 16, ), ), - ], + ), ), if (_searchDialogType == 2) - Row( - mainAxisAlignment: MainAxisAlignment.start, - children: [ - TextButton( - onPressed: () { - Navigator.of(context).pop(); - }, - child: Text( - AppLocalizations.of(context)!.cancel, - textAlign: TextAlign.center, - style: TextStyle( - color: Colors.blue, - fontSize: 16, - ), + Align( + alignment: Alignment.centerLeft, + child: TextButton( + onPressed: () { + Navigator.of(context).pop(); + }, + child: Text( + AppLocalizations.of(context)!.cancel, + style: TextStyle( + color: Theme.of(context).colorScheme.primary, + fontSize: 16, ), ), - ], + ), ), ], ), ), Container( decoration: BoxDecoration( - color: Colors.white, + color: Theme.of(context).cardColor, borderRadius: BorderRadius.circular(10), boxShadow: [ BoxShadow( - color: Colors.grey.withAlpha((255 * 0.2).toInt()), + color: Theme.of( + context, + ).shadowColor.withAlpha((255 * 0.1).round()), blurRadius: 10, offset: const Offset(0, 1), ), @@ -207,32 +218,36 @@ class _SearchDialogState extends State { controller: _searchController, decoration: InputDecoration( hintText: widget.placeholder, - hintStyle: const TextStyle(color: Colors.grey), + hintStyle: TextStyle(color: Theme.of(context).hintColor), suffixIcon: Icon( Icons.search, color: Theme.of(context).colorScheme.primary, ), - fillColor: Colors.white, + fillColor: Theme.of(context).cardColor, filled: true, border: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide( - color: Colors.grey.shade300, - width: 0, + color: Theme.of( + context, + ).dividerColor.withAlpha((255 * 0.5).round()), + width: 0.5, ), ), enabledBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide( - color: Colors.grey.shade300, - width: 0, + color: Theme.of( + context, + ).dividerColor.withAlpha((255 * 0.5).round()), + width: 0.5, ), ), focusedBorder: OutlineInputBorder( borderRadius: BorderRadius.circular(10), borderSide: BorderSide( - color: Colors.grey.shade300, - width: 0, + color: Theme.of(context).colorScheme.primary, + width: 1, ), ), contentPadding: const EdgeInsets.all(14), @@ -240,74 +255,85 @@ class _SearchDialogState extends State { ), ), const SizedBox(height: 7), - // Отображаем недавно измененные элементы - if (showRecentSearches) - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: const EdgeInsets.only(left: 20), - child: Text( - AppLocalizations.of(context)!.recently_searched, - style: TextStyle(color: Colors.grey), - ), - ), - Container( - margin: const EdgeInsets.symmetric(horizontal: 14), - child: Column( - children: List.generate(_recentlySearchedItems.length, ( - index, - ) { - final item = _recentlySearchedItems[index]; - if (_searchDialogType == 1) { - return AppListItem( - icon: item['icon'], - image: item['image'], - text: item['text'], - isSwitch: item['isSwitch'] ?? false, - isActive: item['isActive'] ?? false, - isEnabled: true, - onTap: () { - setState(() { - _recentlySearchedItems[index]['isActive'] = - !_recentlySearchedItems[index]['isActive']; - }); - final originalIndex = widget.items.indexWhere( - (i) => i['text'] == item['text'], + if (isQueryEmpty && _recentlySearchedItems.isNotEmpty) + Flexible( + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: + MainAxisSize.min, // Ensure Column takes minimum space + children: [ + Container( + margin: const EdgeInsets.only( + left: 20, + bottom: 4, + top: 4, + ), // Adjusted margin + child: Text( + AppLocalizations.of(context)!.recently_searched, + style: TextStyle( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + Container( + margin: const EdgeInsets.symmetric(horizontal: 14), + child: Column( + mainAxisSize: + MainAxisSize + .min, // Ensure Column takes minimum space + children: List.generate(_recentlySearchedItems.length, ( + index, + ) { + final item = _recentlySearchedItems[index]; + if (_searchDialogType == 1) { + return AppListItem( + icon: item['icon'], + image: item['image'], + text: item['text'], + isSwitch: item['isSwitch'] ?? false, + isActive: item['isActive'] ?? false, + isEnabled: true, + onTap: () { + setState(() { + _recentlySearchedItems[index]['isActive'] = + !_recentlySearchedItems[index]['isActive']; + }); + final originalIndex = widget.items + .indexWhere( + (i) => i['text'] == item['text'], + ); + if (originalIndex != -1) { + widget.items[originalIndex]['isActive'] = + _recentlySearchedItems[index]['isActive']; + } + _addOrUpdateRecentlySearched( + _recentlySearchedItems[index], + ); + }, ); - if (originalIndex != -1) { - widget.items[originalIndex]['isActive'] = - _recentlySearchedItems[index]['isActive']; - } - _saveRecentlySearched( - _recentlySearchedItems[index], + } else { + return ServerListItem( + icon: item['icon'], + text: item['text'], + ping: item['ping'], + isActive: item['isActive'] ?? false, + onTap: () { + _handleServerSelection(item); + }, ); - }, - ); - } else { - return ServerListItem( - icon: item['icon'], - text: item['text'], - ping: item['ping'], - isActive: item['isActive'] ?? false, - onTap: () { - if (_searchController.text.isNotEmpty) { - _saveRecentlySearched(item); - } - _updateServerSelection(item); - Navigator.of(context).pop(widget.items); - }, - ); - } - }), - ), + } + }), + ), + ), + ], ), - ], + ), ), - // Отображаем отфильтрованный список Expanded( child: - showFilteredItems + (!isQueryEmpty || + (isQueryEmpty && _recentlySearchedItems.isEmpty)) ? _filteredItems.isEmpty ? Center( child: Text( @@ -337,7 +363,7 @@ class _SearchDialogState extends State { _filteredItems[index]['isActive'] = !_filteredItems[index]['isActive']; if (_searchController.text.isNotEmpty) { - _saveRecentlySearched( + _addOrUpdateRecentlySearched( _filteredItems[index], ); } @@ -359,11 +385,7 @@ class _SearchDialogState extends State { ping: item['ping'], isActive: item['isActive'] ?? false, onTap: () { - if (_searchController.text.isNotEmpty) { - _saveRecentlySearched(item); - } - _updateServerSelection(item); - Navigator.of(context).pop(widget.items); + _handleServerSelection(item); }, ); } @@ -371,17 +393,7 @@ class _SearchDialogState extends State { ) : const SizedBox.shrink(), ), - Transform.scale( - scale: 1.2, - child: Transform.translate( - offset: const Offset(0, 30), - child: Container( - width: MediaQuery.of(context).size.width, - height: 40, - color: Theme.of(context).colorScheme.surface, - ), - ), - ), + SizedBox(height: MediaQuery.of(context).padding.bottom + 10), ], ), ), diff --git a/pubspec.lock b/pubspec.lock index 80bdd66..ae3c396 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -53,10 +53,10 @@ packages: dependency: transitive description: name: async - sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" url: "https://pub.dev" source: hosted - version: "2.12.0" + version: "2.13.0" bloc: dependency: transitive description: @@ -97,6 +97,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + color: + dependency: transitive + description: + name: color + sha256: ddcdf1b3badd7008233f5acffaf20ca9f5dc2cd0172b75f68f24526a5f5725cb + url: "https://pub.dev" + source: hosted + version: "3.0.0" convert: dependency: transitive description: @@ -153,14 +161,22 @@ packages: url: "https://pub.dev" source: hosted version: "2.2.4" + dartx: + dependency: transitive + description: + name: dartx + sha256: "8b25435617027257d43e6508b5fe061012880ddfdaa75a71d607c3de2a13d244" + url: "https://pub.dev" + source: hosted + version: "1.2.0" fake_async: dependency: transitive description: name: fake_async - sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" url: "https://pub.dev" source: hosted - version: "1.3.2" + version: "1.3.3" ffi: dependency: transitive description: @@ -190,6 +206,22 @@ packages: url: "https://pub.dev" source: hosted version: "9.1.0" + flutter_gen: + dependency: "direct dev" + description: + name: flutter_gen + sha256: a727fbe4d9443ac05258ef7a987650f8d8f16b4f8c22cf98c1ac9183ac7f3eff + url: "https://pub.dev" + source: hosted + version: "5.9.0" + flutter_gen_core: + dependency: transitive + description: + name: flutter_gen_core + sha256: "53890b653738f34363d9f0d40f82104c261716bd551d3ba65f648770b6764c21" + url: "https://pub.dev" + source: hosted + version: "5.9.0" flutter_lints: dependency: "direct dev" description: @@ -237,6 +269,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.3" + hashcodes: + dependency: transitive + description: + name: hashcodes + sha256: "80f9410a5b3c8e110c4b7604546034749259f5d6dcca63e0d3c17c9258f1a651" + url: "https://pub.dev" + source: hosted + version: "2.0.0" html: dependency: transitive description: @@ -269,22 +309,38 @@ packages: url: "https://pub.dev" source: hosted version: "4.5.4" + image_size_getter: + dependency: transitive + description: + name: image_size_getter + sha256: "9a299e3af2ebbcfd1baf21456c3c884037ff524316c97d8e56035ea8fdf35653" + url: "https://pub.dev" + source: hosted + version: "2.4.0" intl: dependency: transitive description: name: intl - sha256: d6f56758b7d3014a48af9701c085700aac781a92a87a62b1333b46d8879661cf + sha256: "3df61194eb431efc39c4ceba583b95633a403f46c9fd341e550ce0bfa50e9aa5" + url: "https://pub.dev" + source: hosted + version: "0.20.2" + json_annotation: + dependency: transitive + description: + name: json_annotation + sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.dev" source: hosted - version: "0.19.0" + version: "4.9.0" leak_tracker: dependency: transitive description: name: leak_tracker - sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" url: "https://pub.dev" source: hosted - version: "10.0.8" + version: "10.0.9" leak_tracker_flutter_testing: dependency: transitive description: @@ -333,6 +389,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.16.0" + mime: + dependency: transitive + description: + name: mime + sha256: "41a20518f0cb1256669420fdba0cd90d21561e560ac240f26ef8322e45bb7ed6" + url: "https://pub.dev" + source: hosted + version: "2.0.0" nested: dependency: transitive description: @@ -546,6 +610,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.7.4" + time: + dependency: transitive + description: + name: time + sha256: "370572cf5d1e58adcb3e354c47515da3f7469dac3a95b447117e728e7be6f461" + url: "https://pub.dev" + source: hosted + version: "2.1.5" typed_data: dependency: transitive description: @@ -598,10 +670,10 @@ packages: dependency: transitive description: name: vm_service - sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 url: "https://pub.dev" source: hosted - version: "14.3.1" + version: "15.0.0" vpnclient_engine_flutter: dependency: "direct main" description: @@ -653,4 +725,4 @@ packages: version: "3.1.3" sdks: dart: ">=3.7.2 <4.0.0" - flutter: ">=3.27.0" + flutter: ">=3.29.3" diff --git a/pubspec.yaml b/pubspec.yaml index 39a1245..711ed17 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,33 +1,12 @@ name: vpn_client description: "A new Flutter project." -# The following line prevents the package from being accidentally published to -# pub.dev using `flutter pub publish`. This is preferred for private packages. -publish_to: 'none' # Remove this line if you wish to publish to pub.dev - -# The following defines the version and build number for your application. -# A version number is three numbers separated by dots, like 1.2.43 -# followed by an optional build number separated by a +. -# Both the version and the builder number may be overridden in flutter -# build by specifying --build-name and --build-number, respectively. -# In Android, build-name is used as versionName while build-number used as versionCode. -# Read more about Android versioning at https://developer.android.com/studio/publish/versioning -# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html -# In Windows, build-name is used as the major, minor, and patch parts -# of the product and file versions while build-number is used as the build suffix. +publish_to: 'none' version: 1.0.12+12 environment: sdk: ^3.7.2 flutter: 3.29.3 -# Dependencies specify other packages that your package needs in order to work. -# To automatically upgrade your package dependencies to the latest versions -# consider running `flutter pub upgrade --major-versions`. Alternatively, -# dependencies can be manually updated by changing the version numbers below to -# the latest version available on pub.dev. To see which dependencies have newer -# versions available, run `flutter pub outdated`. dependencies: flutter: sdk: flutter @@ -42,74 +21,32 @@ dependencies: git: url: https://github.com/VPNclient/VPNclient-engine-flutter.git ref: c3bf79010c05a2474a24f763d428a61788a13e9b - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. cupertino_icons: ^1.0.8 dev_dependencies: flutter_test: sdk: flutter - - # The "flutter_lints" package below contains a set of recommended lints to - # encourage good coding practices. The lint set provided by the package is - # activated in the `analysis_options.yaml` file located at the root of your - # package. See that file for information about deactivating specific lint - # rules and activating additional ones. flutter_lints: ^5.0.0 dart_code_metrics: ^4.19.2 + flutter_gen: ^5.3.0 -# For information on the generic Dart part of this file, see the -# following page: https://dart.dev/tools/pub/pubspec - -# The following section is specific to Flutter packages. flutter: + generate: true # ✅ ESSENCIAL para gerar flutter_gen e l10n corretamente fonts: - family: CustomIcons fonts: - asset: assets/fonts/CustomIcons.ttf - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - # To add assets to your application, add an assets section, like this: assets: - assets/images/ - assets/images/flags/ - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/to/resolution-aware-images - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/to/asset-from-package - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/to/font-from-package +flutter_gen: + output: lib/gen/ # ✅ Define onde gerar os arquivos, pode customizar se quiser - generate: true - - - l10n: arb-dir: l10n template-arb-file: app_en.arb