Skip to content

Commit 9e97c33

Browse files
committed
design from figma
1 parent 4183f1c commit 9e97c33

29 files changed

Lines changed: 3445 additions & 497 deletions

lib/bak/main.dart

Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
1+
import 'package:flutter/material.dart';
2+
import 'package:flutter_localizations/flutter_localizations.dart';
3+
import 'package:vpn_client/l10n/app_localizations.dart';
4+
import 'package:provider/provider.dart';
5+
import 'package:vpn_client/pages/apps/apps_page.dart';
6+
import 'dart:ui' as ui;
7+
import 'package:vpn_client/pages/main/main_page.dart';
8+
import 'package:vpn_client/pages/settings/setting_page.dart';
9+
import 'package:vpn_client/pages/servers/servers_page.dart';
10+
import 'package:vpn_client/theme_provider.dart';
11+
import 'package:vpn_client/vpn_state.dart';
12+
import 'package:vpn_client/localization_service.dart';
13+
import 'package:vpn_client/services/config_service.dart';
14+
import 'package:vpn_client/services/onboarding_service.dart';
15+
import 'package:vpn_client/services/deep_link_service.dart';
16+
import 'package:vpn_client/services/vpn_service.dart';
17+
import 'package:vpn_client/pages/onboarding/onboarding_screen.dart';
18+
19+
import 'design/colors.dart';
20+
import 'nav_bar.dart';
21+
22+
void main() async {
23+
WidgetsFlutterBinding.ensureInitialized();
24+
25+
// Инициализация ConfigService (.env конфигурация)
26+
await ConfigService.initialize();
27+
ConfigService.printConfig(); // Вывод конфигурации в debug режиме
28+
29+
Locale userLocale =
30+
ui.PlatformDispatcher.instance.locale; // <-- Get the system locale
31+
await LocalizationService.load(userLocale);
32+
33+
// Инициализация сервисов
34+
final onboardingService = OnboardingService();
35+
await onboardingService.initialize();
36+
37+
// Инициализация Deep Link Service (если включено)
38+
if (ConfigService.enableDeepLinks) {
39+
DeepLinkService().initialize(onboardingService);
40+
}
41+
42+
runApp(
43+
MultiProvider(
44+
providers: [
45+
ChangeNotifierProvider(create: (_) => ThemeProvider()),
46+
ChangeNotifierProvider.value(value: onboardingService),
47+
ChangeNotifierProvider(create: (_) => VpnService()),
48+
],
49+
child: const App(),
50+
),
51+
);
52+
}
53+
54+
class App extends StatelessWidget {
55+
const App({super.key});
56+
57+
@override
58+
Widget build(BuildContext context) {
59+
final themeProvider = Provider.of<ThemeProvider>(context);
60+
final onboardingService = Provider.of<OnboardingService>(context);
61+
final Locale? manualLocale = null;
62+
63+
// Определяем начальный экран
64+
Widget homeScreen;
65+
if (onboardingService.shouldShowOnboarding()) {
66+
homeScreen = OnboardingScreen(onboardingService: onboardingService);
67+
} else {
68+
homeScreen = const MainScreen();
69+
}
70+
71+
return MaterialApp(
72+
localizationsDelegates: const [
73+
GlobalMaterialLocalizations.delegate,
74+
GlobalWidgetsLocalizations.delegate,
75+
GlobalCupertinoLocalizations.delegate,
76+
],
77+
debugShowCheckedModeBanner: false,
78+
title: ConfigService.appName,
79+
theme: lightTheme,
80+
darkTheme: darkTheme,
81+
locale: manualLocale,
82+
localeResolutionCallback: (locale, _) {
83+
if (locale == null) return const Locale('en');
84+
for (var supportedLocale in supportedLocales) {
85+
if (supportedLocale.languageCode == locale.languageCode &&
86+
(supportedLocale.countryCode == null ||
87+
supportedLocale.countryCode == locale.countryCode)) {
88+
return supportedLocale;
89+
}
90+
}
91+
if (locale.languageCode == 'zh') {
92+
return supportedLocales.contains(const Locale('zh'))
93+
? const Locale('zh')
94+
: const Locale('en');
95+
}
96+
return const Locale('en');
97+
},
98+
themeMode: themeProvider.themeMode,
99+
home: homeScreen,
100+
routes: {
101+
'/': (context) => const MainScreen(),
102+
'/onboarding': (context) =>
103+
OnboardingScreen(onboardingService: onboardingService),
104+
},
105+
localizationsDelegates: const [
106+
AppLocalizations.delegate,
107+
GlobalMaterialLocalizations.delegate,
108+
GlobalWidgetsLocalizations.delegate,
109+
GlobalCupertinoLocalizations.delegate,
110+
],
111+
supportedLocales: const [
112+
Locale('en'),
113+
Locale('ru'),
114+
Locale('th'),
115+
Locale('zh'),
116+
],
117+
);
118+
}
119+
}
120+
121+
class MainScreen extends StatefulWidget {
122+
const MainScreen({super.key});
123+
@override
124+
State<MainScreen> createState() => _MainScreenState();
125+
}
126+
127+
class _MainScreenState extends State<MainScreen> {
128+
int _currentIndex = 2;
129+
late List<Widget> _pages;
130+
late List<bool> _pageEnabled;
131+
132+
@override
133+
void initState() {
134+
super.initState();
135+
136+
// Конфигурируемые страницы на основе .env
137+
_pageEnabled = [
138+
ConfigService.showAppsPage, // Apps page
139+
true, // Servers page - всегда включено
140+
true, // Main page - всегда включено
141+
true, // Speed page - всегда включено
142+
ConfigService.showSettingsPage, // Settings page
143+
];
144+
145+
_pages = [
146+
ConfigService.showAppsPage
147+
? const AppsPage()
148+
: const PlaceholderPage(text: 'Apps disabled'),
149+
ServersPage(onNavBarTap: _handleNavBarTap),
150+
const MainPage(),
151+
const PlaceholderPage(text: 'Speed Page'),
152+
ConfigService.showSettingsPage
153+
? SettingPage(onNavBarTap: _handleNavBarTap)
154+
: const PlaceholderPage(text: 'Settings disabled'),
155+
];
156+
157+
// Корректируем начальный индекс если страница отключена
158+
if (!_pageEnabled[_currentIndex]) {
159+
_currentIndex = 2; // Возвращаемся на Main page
160+
}
161+
}
162+
163+
void _handleNavBarTap(int index) {
164+
// Проверяем, включена ли страница
165+
if (_pageEnabled[index]) {
166+
setState(() {
167+
_currentIndex = index;
168+
});
169+
}
170+
}
171+
172+
@override
173+
Widget build(BuildContext context) {
174+
return Scaffold(
175+
body: _pages[_currentIndex],
176+
bottomNavigationBar: NavBar(
177+
initialIndex: _currentIndex,
178+
onItemTapped: _handleNavBarTap,
179+
enabledPages: _pageEnabled,
180+
),
181+
);
182+
}
183+
}
184+
185+
class PlaceholderPage extends StatelessWidget {
186+
final String text;
187+
const PlaceholderPage({super.key, required this.text});
188+
@override
189+
Widget build(BuildContext context) {
190+
return Center(child: Text(text));
191+
}
192+
}

lib/design/app_colors.dart

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
import 'package:flutter/material.dart';
2+
3+
/// Design tokens — colors.
4+
/// Source: Figma "VPN-Client-Pro (Blue)".
5+
/// Palette is intentionally narrow — extend only via the [AppColors] surface
6+
/// so the whole app shifts together when the brand updates.
7+
class AppColors {
8+
AppColors._();
9+
10+
// ─── Brand ─────────────────────────────────────────────────────────────
11+
/// Cyan top of the connect-button gradient.
12+
static const Color brandCyan = Color(0xFF00C6FB);
13+
14+
/// Deep blue bottom of the connect-button gradient + primary action.
15+
static const Color brandBlue = Color(0xFF005BEA);
16+
17+
/// The brand gradient — used on the central connect button and on the
18+
/// CTA in onboarding/empty states.
19+
static const LinearGradient brandGradient = LinearGradient(
20+
begin: Alignment.topCenter,
21+
end: Alignment.bottomCenter,
22+
colors: [brandCyan, brandBlue],
23+
);
24+
25+
// ─── Neutrals (light) ──────────────────────────────────────────────────
26+
/// Page background.
27+
static const Color bgLight = Color(0xFFF8F9FA);
28+
29+
/// Card / surface on light backgrounds.
30+
static const Color surfaceLight = Color(0xFFFFFFFF);
31+
32+
/// Primary text on light surfaces.
33+
static const Color textPrimaryLight = Color(0xFF303F49);
34+
35+
/// Muted / placeholder text.
36+
static const Color textMuted = Color(0xFFB6B6B6);
37+
38+
/// Hairline divider (the rgba(156,178,194,0.1) shadow seen across Figma).
39+
static const Color divider = Color(0x1A9CB2C2);
40+
41+
// ─── Neutrals (dark) ───────────────────────────────────────────────────
42+
static const Color bgDark = Color(0xFF0F1419);
43+
static const Color surfaceDark = Color(0xFF1A2129);
44+
static const Color textPrimaryDark = Color(0xFFE7ECEF);
45+
46+
// ─── Semantic ──────────────────────────────────────────────────────────
47+
static const Color success = Color(0xFF1FB67A);
48+
static const Color warning = Color(0xFFFFB020);
49+
static const Color danger = Color(0xFFE5484D);
50+
51+
/// Ping color scale — used on server tiles.
52+
static Color pingColor(int ms) {
53+
if (ms < 80) return success;
54+
if (ms < 180) return warning;
55+
return danger;
56+
}
57+
}

lib/design/app_spacing.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
/// Design tokens — spacing / radius / elevation.
2+
/// Multiples of 4 with a 30px page gutter, matching the Figma frames.
3+
class AppSpacing {
4+
AppSpacing._();
5+
6+
// Spacing scale
7+
static const double xxs = 4;
8+
static const double xs = 8;
9+
static const double sm = 12;
10+
static const double md = 16;
11+
static const double lg = 20;
12+
static const double xl = 24;
13+
static const double xxl = 32;
14+
15+
/// Outer page gutter — 30 in Figma frames at 390 wide.
16+
static const double pageGutter = 30;
17+
18+
/// Inner row gutter inside cards.
19+
static const double rowGutter = 14;
20+
21+
// Radius
22+
static const double radiusSm = 8;
23+
static const double radiusMd = 12;
24+
static const double radiusLg = 16;
25+
static const double radiusPill = 999;
26+
27+
// Standard heights
28+
static const double tileHeight = 64;
29+
static const double connectBtn = 150;
30+
static const double bottomNav = 92;
31+
32+
// Breakpoints
33+
static const double tabletBreakpoint = 600;
34+
static const double desktopBreakpoint = 1024;
35+
}

0 commit comments

Comments
 (0)