Skip to content

Commit fcaa757

Browse files
committed
Implement v2ray connection functionality
1 parent 8ebe4fd commit fcaa757

7 files changed

Lines changed: 168 additions & 121 deletions

File tree

lib/main.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,20 @@ import 'package:vpn_client/pages/apps/apps_page.dart';
77
import 'package:vpn_client/pages/main/main_page.dart';
88
import 'package:vpn_client/pages/servers/servers_page.dart';
99
import 'package:vpn_client/theme_provider.dart';
10+
import 'package:vpn_client/vpn_state.dart';
1011

1112
import 'design/colors.dart';
1213
import 'nav_bar.dart';
1314

1415
void main() {
1516
runApp(
16-
ChangeNotifierProvider(create: (_) => ThemeProvider(), child: const App()),
17+
MultiProvider(
18+
providers: [
19+
ChangeNotifierProvider(create: (_) => ThemeProvider()),
20+
ChangeNotifierProvider(create: (_) => VpnState()),
21+
],
22+
child: const App(),
23+
),
1724
);
1825
}
1926

lib/pages/main/main_btn.dart

Lines changed: 56 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,18 @@
1-
import 'dart:async';
21
import 'dart:developer';
32
import 'package:flutter/material.dart';
3+
import 'package:provider/provider.dart';
44
import 'package:vpn_client/design/colors.dart';
55
import 'package:vpn_client/design/dimensions.dart';
66
import 'package:vpnclient_engine_flutter/vpnclient_engine_flutter.dart';
7+
import 'package:flutter_v2ray/flutter_v2ray.dart';
8+
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
9+
import 'package:vpn_client/vpn_state.dart';
10+
11+
final FlutterV2ray flutterV2ray = FlutterV2ray(
12+
onStatusChanged: (status) {
13+
// Handle status changes if needed
14+
},
15+
);
716

817
class MainBtn extends StatefulWidget {
918
const MainBtn({super.key});
@@ -13,142 +22,100 @@ class MainBtn extends StatefulWidget {
1322
}
1423

1524
class MainBtnState extends State<MainBtn> with SingleTickerProviderStateMixin {
16-
///static const platform = MethodChannel('vpnclient_engine2');
17-
///
18-
late CustomString statusText;
19-
late String connectionStatus;
20-
late String connectionStatusDisconnected;
21-
late String connectionStatusDisconnecting;
22-
late String connectionStatusConnected;
23-
late String connectionStatusConnecting;
24-
25-
@override
26-
void didChangeDependencies() {
27-
super.didChangeDependencies();
28-
final statusText = CustomString(context);
29-
connectionStatus = statusText.disconnected;
30-
connectionStatusDisconnected = statusText.disconnected;
31-
connectionStatusConnected = statusText.connected;
32-
connectionStatusDisconnecting = statusText.disconnecting;
33-
connectionStatusConnecting = statusText.connecting;
34-
}
35-
36-
String connectionTime = "00:00:00";
37-
Timer? _timer;
3825
late AnimationController _animationController;
3926
late Animation<double> _sizeAnimation;
4027

4128
@override
4229
void initState() {
4330
super.initState();
44-
4531
_animationController = AnimationController(
4632
vsync: this,
4733
duration: const Duration(seconds: 1),
4834
);
4935
_sizeAnimation = Tween<double>(begin: 0, end: 150).animate(
5036
CurvedAnimation(parent: _animationController, curve: Curves.ease),
5137
);
38+
39+
// Синхронизировать анимацию с текущим статусом подключения
40+
WidgetsBinding.instance.addPostFrameCallback((_) {
41+
final vpnState = Provider.of<VpnState>(context, listen: false);
42+
final localizations = AppLocalizations.of(context)!;
43+
if (vpnState.connectionStatus == localizations.connected) {
44+
_animationController.forward();
45+
}
46+
});
5247
}
5348

5449
@override
5550
void dispose() {
56-
_timer?.cancel();
5751
_animationController.dispose();
5852
super.dispose();
5953
}
6054

61-
void startTimer() {
62-
int seconds = 1;
63-
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
64-
setState(() {
65-
int hours = seconds ~/ 3600;
66-
int minutes = (seconds % 3600) ~/ 60;
67-
int remainingSeconds = seconds % 60;
68-
connectionTime =
69-
'${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}';
70-
});
71-
seconds++;
72-
});
73-
}
55+
Future<void> _handleConnection(BuildContext context) async {
56+
final vpnState = Provider.of<VpnState>(context, listen: false);
57+
final localizations = AppLocalizations.of(context)!;
7458

75-
void stopTimer() {
76-
_timer?.cancel();
77-
setState(() {
78-
connectionTime = "00:00:00";
79-
connectionStatus = connectionStatusDisconnected;
80-
});
81-
}
82-
83-
Future<void> _handleConnection() async {
84-
if (connectionStatus != connectionStatusConnected &&
85-
connectionStatus != connectionStatusDisconnected) {
59+
if (vpnState.connectionStatus != localizations.connected &&
60+
vpnState.connectionStatus != localizations.disconnected) {
8661
return;
8762
}
8863

89-
setState(() {
90-
if (connectionStatus == connectionStatusConnected) {
91-
connectionStatus = connectionStatusDisconnecting;
92-
} else if (connectionStatus == connectionStatusDisconnected) {
93-
connectionStatus = connectionStatusConnecting;
94-
}
95-
});
96-
97-
if (connectionStatus == connectionStatusConnecting) {
64+
if (vpnState.connectionStatus == localizations.connected) {
65+
vpnState.setConnectionStatus(localizations.disconnecting);
66+
_animationController.repeat(reverse: true);
67+
await flutterV2ray.stopV2Ray();
68+
await VPNclientEngine.disconnect();
69+
vpnState.stopTimer();
70+
vpnState.setConnectionStatus(localizations.disconnected);
71+
await _animationController.reverse();
72+
_animationController.stop();
73+
} else if (vpnState.connectionStatus == localizations.disconnected) {
74+
vpnState.setConnectionStatus(localizations.connecting);
9875
_animationController.repeat(reverse: true);
9976

100-
VPNclientEngine.ClearSubscriptions();
101-
VPNclientEngine.addSubscription(
102-
subscriptionURL: "https://pastebin.com/raw/ZCYiJ98W",
103-
);
104-
await VPNclientEngine.updateSubscription(subscriptionIndex: 0);
105-
VPNclientEngine.pingServer(subscriptionIndex: 0, index: 1);
106-
VPNclientEngine.onPingResult.listen((result) {
107-
log(
108-
"Ping result: ${result.latencyInMs} ms",
109-
name: 'PingLogger',
110-
); // <- Use dev.log instead of print.(It build to log meta data)
111-
});
112-
113-
///final result = await platform.invokeMethod('startVPN');
77+
String link = "https://pastebin.com/raw/K8SYCauu";
78+
V2RayURL parser = FlutterV2ray.parseFromURL(link);
79+
80+
if (await flutterV2ray.requestPermission()) {
81+
await flutterV2ray.startV2Ray(
82+
remark: parser.remark,
83+
config: parser.getFullConfiguration(),
84+
blockedApps: null,
85+
bypassSubnets: null,
86+
proxyOnly: false,
87+
);
88+
}
11489

11590
await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 1);
116-
startTimer();
117-
setState(() {
118-
connectionStatus = connectionStatusConnected;
119-
});
91+
vpnState.startTimer();
92+
vpnState.setConnectionStatus(localizations.connected);
12093
await _animationController.forward();
12194
_animationController.stop();
122-
} else if (connectionStatus == connectionStatusDisconnecting) {
123-
_animationController.repeat(reverse: true);
124-
stopTimer();
125-
await VPNclientEngine.disconnect();
126-
setState(() {
127-
connectionStatus = connectionStatusDisconnected;
128-
});
129-
await _animationController.reverse();
130-
_animationController.stop();
13195
}
13296
}
13397

13498
@override
13599
Widget build(BuildContext context) {
100+
final vpnState = Provider.of<VpnState>(context);
101+
final localizations = AppLocalizations.of(context)!;
102+
136103
return Column(
137104
children: [
138105
Text(
139-
connectionTime,
106+
vpnState.connectionTime,
140107
style: TextStyle(
141108
fontSize: 40,
142109
fontWeight: FontWeight.w600,
143110
color:
144-
connectionStatus == connectionStatusConnected
111+
vpnState.connectionStatus == localizations.connected
145112
? Theme.of(context).colorScheme.primary
146113
: Theme.of(context).colorScheme.secondary,
147114
),
148115
),
149116
const SizedBox(height: 70),
150117
GestureDetector(
151-
onTap: _handleConnection,
118+
onTap: () => _handleConnection(context),
152119
child: Stack(
153120
alignment: Alignment.center,
154121
children: [
@@ -188,7 +155,7 @@ class MainBtnState extends State<MainBtn> with SingleTickerProviderStateMixin {
188155
),
189156
const SizedBox(height: 20),
190157
Text(
191-
connectionStatus,
158+
vpnState.connectionStatus,
192159
style: TextStyle(
193160
fontSize: 16,
194161
fontWeight: FontWeight.w500,
@@ -199,7 +166,3 @@ class MainBtnState extends State<MainBtn> with SingleTickerProviderStateMixin {
199166
);
200167
}
201168
}
202-
203-
void main() {
204-
runApp(MaterialApp(home: Scaffold(body: Center(child: MainBtn()))));
205-
}

lib/pages/main/main_page.dart

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
import 'package:flutter/material.dart';
2+
import 'package:provider/provider.dart';
23
import 'package:shared_preferences/shared_preferences.dart';
34
import 'dart:convert';
45
import 'package:vpn_client/pages/main/main_btn.dart';
56
import 'package:vpn_client/pages/main/location_widget.dart';
67
import 'package:vpn_client/pages/main/stat_bar.dart';
8+
import 'package:vpn_client/vpn_state.dart';
79
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
810

911
class MainPage extends StatefulWidget {
@@ -15,13 +17,32 @@ class MainPage extends StatefulWidget {
1517

1618
class MainPageState extends State<MainPage> {
1719
Map<String, dynamic>? _selectedServer;
20+
bool _isInitialized = false;
1821

1922
@override
2023
void initState() {
2124
super.initState();
2225
_loadSelectedServer();
2326
}
2427

28+
@override
29+
void didChangeDependencies() {
30+
super.didChangeDependencies();
31+
if (!_isInitialized) {
32+
// Schedule VpnState connection status update after build
33+
WidgetsBinding.instance.addPostFrameCallback((_) {
34+
final vpnState = Provider.of<VpnState>(context, listen: false);
35+
final localizations = AppLocalizations.of(context)!;
36+
// Инициализировать статус только если он пустой или равен начальному значению
37+
if (vpnState.connectionStatus.isEmpty ||
38+
vpnState.connectionStatus == 'Disconnected') {
39+
vpnState.setConnectionStatus(localizations.disconnected);
40+
}
41+
});
42+
_isInitialized = true;
43+
}
44+
}
45+
2546
Future<void> _loadSelectedServer() async {
2647
final prefs = await SharedPreferences.getInstance();
2748
final String? savedServers = prefs.getString('selected_servers');
@@ -54,18 +75,13 @@ class MainPageState extends State<MainPage> {
5475
elevation: 0,
5576
),
5677
body: SafeArea(
57-
// I change to SafeArea to prevent screen over flow
58-
child: SingleChildScrollView(
59-
child: Column(
60-
mainAxisAlignment: MainAxisAlignment.start,
61-
children: [
62-
const StatBar(),
63-
const SizedBox(height: 20),
64-
const MainBtn(),
65-
const SizedBox(height: 20),
66-
LocationWidget(selectedServer: _selectedServer),
67-
],
68-
),
78+
child: Column(
79+
mainAxisAlignment: MainAxisAlignment.spaceBetween,
80+
children: [
81+
const StatBar(),
82+
const MainBtn(),
83+
LocationWidget(selectedServer: _selectedServer),
84+
],
6985
),
7086
),
7187
);

lib/pages/main/stat_bar.dart

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,16 @@ class StatBar extends StatefulWidget {
1313
class StatBarState extends State<StatBar> {
1414
@override
1515
Widget build(BuildContext context) {
16-
return Row(
17-
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
18-
children: [
19-
_buildStatItem(CustomIcons.download, '0 Mb/s', context),
20-
_buildStatItem(CustomIcons.upload, '0 Mb/s', context),
21-
_buildStatItem(CustomIcons.ping, '0 ms', context),
22-
],
16+
return Container(
17+
margin: EdgeInsets.only(top: 37),
18+
child: Row(
19+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
20+
children: [
21+
_buildStatItem(CustomIcons.download, '0 Mb/s', context),
22+
_buildStatItem(CustomIcons.upload, '0 Mb/s', context),
23+
_buildStatItem(CustomIcons.ping, '0 ms', context),
24+
],
25+
),
2326
);
2427
}
2528

lib/vpn_state.dart

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import 'dart:async';
2+
import 'package:flutter/material.dart';
3+
import 'package:flutter_v2ray/flutter_v2ray.dart';
4+
5+
class VpnState with ChangeNotifier {
6+
String _connectionStatus =
7+
''; // Изначально пустой, будет установлен в MainPage
8+
String _connectionTime = "00:00:00";
9+
Timer? _timer;
10+
11+
String get connectionStatus => _connectionStatus;
12+
String get connectionTime => _connectionTime;
13+
14+
VpnState() {
15+
// Инициализация V2Ray при создании провайдера
16+
FlutterV2ray(onStatusChanged: (status) {}).initializeV2Ray();
17+
}
18+
19+
void setConnectionStatus(String status) {
20+
_connectionStatus = status;
21+
notifyListeners();
22+
}
23+
24+
void startTimer() {
25+
_timer?.cancel();
26+
int seconds = 1;
27+
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
28+
int hours = seconds ~/ 3600;
29+
int minutes = (seconds % 3600) ~/ 60;
30+
int remainingSeconds = seconds % 60;
31+
_connectionTime =
32+
'${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}';
33+
notifyListeners();
34+
seconds++;
35+
});
36+
}
37+
38+
void stopTimer() {
39+
_timer?.cancel();
40+
_connectionTime = "00:00:00";
41+
notifyListeners();
42+
}
43+
44+
@override
45+
void dispose() {
46+
_timer?.cancel();
47+
super.dispose();
48+
}
49+
}

0 commit comments

Comments
 (0)