Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions ios/Podfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,12 @@ PODS:
- shared_preferences_foundation (0.0.1):
- Flutter
- FlutterMacOS
- vpnclient_engine_flutter (0.0.1):
- Flutter

DEPENDENCIES:
- Flutter (from `Flutter`)
- flutter_native_splash (from `.symlinks/plugins/flutter_native_splash/ios`)
- flutter_v2ray (from `.symlinks/plugins/flutter_v2ray/ios`)
- shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`)
- vpnclient_engine_flutter (from `.symlinks/plugins/vpnclient_engine_flutter/ios`)

EXTERNAL SOURCES:
Flutter:
Expand All @@ -26,15 +23,12 @@ EXTERNAL SOURCES:
:path: ".symlinks/plugins/flutter_v2ray/ios"
shared_preferences_foundation:
:path: ".symlinks/plugins/shared_preferences_foundation/darwin"
vpnclient_engine_flutter:
:path: ".symlinks/plugins/vpnclient_engine_flutter/ios"

SPEC CHECKSUMS:
Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7
flutter_native_splash: df59bb2e1421aa0282cb2e95618af4dcb0c56c29
flutter_v2ray: 21ee520e013877941e2e67b3b48aed9c6870e8dc
shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78
vpnclient_engine_flutter: f6003e0a6005c7d97f81295159d59e039c8065c9

PODFILE CHECKSUM: f8e15f817b1bf5846cb6aad560a31cf33c165196

Expand Down
4 changes: 0 additions & 4 deletions ios/Runner.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -354,14 +354,10 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
Expand Down
9 changes: 8 additions & 1 deletion lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,20 @@ import 'package:vpn_client/pages/apps/apps_page.dart';
import 'package:vpn_client/pages/main/main_page.dart';
import 'package:vpn_client/pages/servers/servers_page.dart';
import 'package:vpn_client/theme_provider.dart';
import 'package:vpn_client/vpn_state.dart';

import 'design/colors.dart';
import 'nav_bar.dart';

void main() {
runApp(
ChangeNotifierProvider(create: (_) => ThemeProvider(), child: const App()),
Comment thread
pkongkha marked this conversation as resolved.
MultiProvider(
providers: [
ChangeNotifierProvider(create: (_) => ThemeProvider()),
ChangeNotifierProvider(create: (_) => VpnState()),
],
child: const App(),
),
);
}

Expand Down
156 changes: 59 additions & 97 deletions lib/pages/main/main_btn.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:provider/provider.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:vpnclient_engine_flutter/vpnclient_engine_flutter.dart';
import 'package:flutter_v2ray/flutter_v2ray.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
import 'package:vpn_client/vpn_state.dart';

final FlutterV2ray flutterV2ray = FlutterV2ray(
onStatusChanged: (status) {
// Handle status changes if needed
},
);

class MainBtn extends StatefulWidget {
const MainBtn({super.key});
Expand All @@ -13,142 +20,101 @@ class MainBtn extends StatefulWidget {
}

class MainBtnState extends State<MainBtn> with SingleTickerProviderStateMixin {
///static const platform = MethodChannel('vpnclient_engine2');
///
late CustomString statusText;
late String connectionStatus;
late String connectionStatusDisconnected;
late String connectionStatusDisconnecting;
late String connectionStatusConnected;
late String connectionStatusConnecting;

@override
void didChangeDependencies() {
super.didChangeDependencies();
final statusText = CustomString(context);
connectionStatus = statusText.disconnected;
connectionStatusDisconnected = statusText.disconnected;
connectionStatusConnected = statusText.connected;
connectionStatusDisconnecting = statusText.disconnecting;
connectionStatusConnecting = statusText.connecting;
}

String connectionTime = "00:00:00";
Timer? _timer;
late AnimationController _animationController;
late Animation<double> _sizeAnimation;

@override
void initState() {
super.initState();

_animationController = AnimationController(
vsync: this,
duration: const Duration(seconds: 1),
);
_sizeAnimation = Tween<double>(begin: 0, end: 150).animate(
CurvedAnimation(parent: _animationController, curve: Curves.ease),
);

// Синхронизировать анимацию с текущим статусом подключения
WidgetsBinding.instance.addPostFrameCallback((_) {
final vpnState = Provider.of<VpnState>(context, listen: false);
final localizations = AppLocalizations.of(context)!;
if (vpnState.connectionStatus == localizations.connected) {
_animationController.forward();
}
});
}

@override
void dispose() {
_timer?.cancel();
_animationController.dispose();
super.dispose();
}

void startTimer() {
int seconds = 1;
_timer = Timer.periodic(const Duration(seconds: 1), (timer) {
setState(() {
int hours = seconds ~/ 3600;
int minutes = (seconds % 3600) ~/ 60;
int remainingSeconds = seconds % 60;
connectionTime =
'${hours.toString().padLeft(2, '0')}:${minutes.toString().padLeft(2, '0')}:${remainingSeconds.toString().padLeft(2, '0')}';
});
seconds++;
});
}

void stopTimer() {
_timer?.cancel();
setState(() {
connectionTime = "00:00:00";
connectionStatus = connectionStatusDisconnected;
});
}
Future<void> _handleConnection(BuildContext context) async {
final vpnState = Provider.of<VpnState>(context, listen: false);
final localizations = AppLocalizations.of(context)!;

Future<void> _handleConnection() async {
if (connectionStatus != connectionStatusConnected &&
connectionStatus != connectionStatusDisconnected) {
if (vpnState.connectionStatus != localizations.connected &&
vpnState.connectionStatus != localizations.disconnected) {
return;
}

setState(() {
if (connectionStatus == connectionStatusConnected) {
connectionStatus = connectionStatusDisconnecting;
} else if (connectionStatus == connectionStatusDisconnected) {
connectionStatus = connectionStatusConnecting;
}
});

if (connectionStatus == connectionStatusConnecting) {
if (vpnState.connectionStatus == localizations.connected) {
vpnState.setConnectionStatus(localizations.disconnecting);
_animationController.repeat(reverse: true);
await flutterV2ray.stopV2Ray();
// await VPNclientEngine.disconnect();
vpnState.stopTimer();
vpnState.setConnectionStatus(localizations.disconnected);
await _animationController.reverse();
_animationController.stop();
} else if (vpnState.connectionStatus == localizations.disconnected) {
vpnState.setConnectionStatus(localizations.connecting);
_animationController.repeat(reverse: true);

VPNclientEngine.ClearSubscriptions();
VPNclientEngine.addSubscription(
subscriptionURL: "https://pastebin.com/raw/ZCYiJ98W",
);
await VPNclientEngine.updateSubscription(subscriptionIndex: 0);
VPNclientEngine.pingServer(subscriptionIndex: 0, index: 1);
VPNclientEngine.onPingResult.listen((result) {
log(
"Ping result: ${result.latencyInMs} ms",
name: 'PingLogger',
); // <- Use dev.log instead of print.(It build to log meta data)
});

///final result = await platform.invokeMethod('startVPN');
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,
);
}

await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 1);
startTimer();
setState(() {
connectionStatus = connectionStatusConnected;
});
// await VPNclientEngine.connect(subscriptionIndex: 0, serverIndex: 1);
vpnState.startTimer();
vpnState.setConnectionStatus(localizations.connected);
await _animationController.forward();
_animationController.stop();
} else if (connectionStatus == connectionStatusDisconnecting) {
_animationController.repeat(reverse: true);
stopTimer();
await VPNclientEngine.disconnect();
setState(() {
connectionStatus = connectionStatusDisconnected;
});
await _animationController.reverse();
_animationController.stop();
}
}

@override
Widget build(BuildContext context) {
final vpnState = Provider.of<VpnState>(context);
final localizations = AppLocalizations.of(context)!;

return Column(
children: [
Text(
connectionTime,
vpnState.connectionTime,
style: TextStyle(
fontSize: 40,
fontWeight: FontWeight.w600,
color:
connectionStatus == connectionStatusConnected
vpnState.connectionStatus == localizations.connected
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondary,
),
),
const SizedBox(height: 70),
GestureDetector(
onTap: _handleConnection,
onTap: () => _handleConnection(context),
child: Stack(
alignment: Alignment.center,
children: [
Expand Down Expand Up @@ -188,7 +154,7 @@ class MainBtnState extends State<MainBtn> with SingleTickerProviderStateMixin {
),
const SizedBox(height: 20),
Text(
connectionStatus,
vpnState.connectionStatus,
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w500,
Expand All @@ -199,7 +165,3 @@ class MainBtnState extends State<MainBtn> with SingleTickerProviderStateMixin {
);
}
}

void main() {
runApp(MaterialApp(home: Scaffold(body: Center(child: MainBtn()))));
}
40 changes: 28 additions & 12 deletions lib/pages/main/main_page.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
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/vpn_state.dart';
import 'package:flutter_gen/gen_l10n/app_localizations.dart';

class MainPage extends StatefulWidget {
Expand All @@ -15,13 +17,32 @@ class MainPage extends StatefulWidget {

class MainPageState extends State<MainPage> {
Map<String, dynamic>? _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((_) {
final vpnState = Provider.of<VpnState>(context, listen: false);
final localizations = AppLocalizations.of(context)!;
// Инициализировать статус только если он пустой или равен начальному значению
if (vpnState.connectionStatus.isEmpty ||
vpnState.connectionStatus == 'Disconnected') {
Comment thread
pkongkha marked this conversation as resolved.
Outdated
vpnState.setConnectionStatus(localizations.disconnected);
}
});
_isInitialized = true;
}
}

Future<void> _loadSelectedServer() async {
final prefs = await SharedPreferences.getInstance();
final String? savedServers = prefs.getString('selected_servers');
Expand Down Expand Up @@ -54,18 +75,13 @@ class MainPageState extends State<MainPage> {
elevation: 0,
),
body: SafeArea(
// I change to SafeArea to prevent screen over flow
child: SingleChildScrollView(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
children: [
const StatBar(),
const SizedBox(height: 20),
const MainBtn(),
const SizedBox(height: 20),
LocationWidget(selectedServer: _selectedServer),
],
),
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
const StatBar(),
const MainBtn(),
LocationWidget(selectedServer: _selectedServer),
],
),
),
);
Expand Down
17 changes: 10 additions & 7 deletions lib/pages/main/stat_bar.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,16 @@ class StatBar extends StatefulWidget {
class StatBarState extends State<StatBar> {
@override
Widget build(BuildContext context) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildStatItem(CustomIcons.download, '0 Mb/s', context),
_buildStatItem(CustomIcons.upload, '0 Mb/s', context),
_buildStatItem(CustomIcons.ping, '0 ms', context),
],
return Container(
margin: EdgeInsets.only(top: 37),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
_buildStatItem(CustomIcons.download, '0 Mb/s', context),
_buildStatItem(CustomIcons.upload, '0 Mb/s', context),
_buildStatItem(CustomIcons.ping, '0 ms', context),
],
),
);
}

Expand Down
Loading