feat: Initial commit
This commit is contained in:
64
lib/about_page.dart
Normal file
64
lib/about_page.dart
Normal file
@@ -0,0 +1,64 @@
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:url_launcher/url_launcher.dart';
|
||||
|
||||
class AboutPage extends StatelessWidget {
|
||||
const AboutPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
TextStyle defaultStyle = const TextStyle(fontSize: 16.0);
|
||||
TextStyle linkStyle = const TextStyle(color: Colors.blue);
|
||||
return Center(
|
||||
child: SelectableText.rich(
|
||||
TextSpan(
|
||||
style: defaultStyle,
|
||||
children: <TextSpan>[
|
||||
const TextSpan(
|
||||
text: '项目基于 n2n , 一款优秀的跨平台开源p2p VPN软件\n',
|
||||
),
|
||||
const TextSpan(
|
||||
text: 'winui_n2n软件开源地址 ',
|
||||
),
|
||||
TextSpan(
|
||||
text: 'https://github.com/moemoequte/winui_n2n\n',
|
||||
style: linkStyle,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrl(
|
||||
Uri.parse('https://github.com/moemoequte/winui_n2n'));
|
||||
},
|
||||
),
|
||||
const TextSpan(
|
||||
text: '分发和使用请遵循 GPL-3.0 license 开源协议\n\n',
|
||||
),
|
||||
const TextSpan(
|
||||
text: '作者信息\n',
|
||||
),
|
||||
const TextSpan(text: '网站 '),
|
||||
TextSpan(
|
||||
text: 'https://www.cialloo.com\n',
|
||||
style: linkStyle,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrl(Uri.parse('https://www.cialloo.com'));
|
||||
},
|
||||
),
|
||||
const TextSpan(text: '邮箱 '),
|
||||
TextSpan(
|
||||
text: 'admin@cialloo.com',
|
||||
style: linkStyle,
|
||||
recognizer: TapGestureRecognizer()
|
||||
..onTap = () {
|
||||
launchUrl(Uri(
|
||||
scheme: 'mailto',
|
||||
path: 'admin@cialloo.com',
|
||||
));
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
67
lib/application_exit_control.dart
Normal file
67
lib/application_exit_control.dart
Normal file
@@ -0,0 +1,67 @@
|
||||
import 'dart:ui';
|
||||
import 'dart:io';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:winui_n2n/edge_state.dart';
|
||||
import 'package:winui_n2n/home_page.dart';
|
||||
|
||||
class ApplicationExitControl extends StatefulWidget {
|
||||
const ApplicationExitControl({super.key});
|
||||
|
||||
@override
|
||||
State<ApplicationExitControl> createState() => _ApplicationExitControlState();
|
||||
}
|
||||
|
||||
class _ApplicationExitControlState extends State<ApplicationExitControl> {
|
||||
late final AppLifecycleListener _listener;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_listener = AppLifecycleListener(
|
||||
onExitRequested: _handleExitRequest,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_listener.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<AppExitResponse> _handleExitRequest() async {
|
||||
if (EdgeState.instance.isRunning && EdgeState.instance.process != null) {
|
||||
if (EdgeState.instance.process!.kill()) {
|
||||
EdgeState.instance.isRunning = false;
|
||||
EdgeState.instance.process = null;
|
||||
} else {
|
||||
// TODO: Handle abnormal close.
|
||||
return AppExitResponse.cancel;
|
||||
}
|
||||
}
|
||||
|
||||
// Uninstall tap device before exit
|
||||
final findTapResult = await Process.run(
|
||||
"./tools/driver/devcon.exe",
|
||||
["hwids", "tap0901"],
|
||||
);
|
||||
if (!findTapResult.stdout
|
||||
.toString()
|
||||
.contains('No matching devices found.')) {
|
||||
// Unstall tap device
|
||||
await Process.run(
|
||||
"./tools/driver/devcon.exe",
|
||||
[
|
||||
"remove",
|
||||
"tap0901",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
return AppExitResponse.exit;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return const HomePage();
|
||||
}
|
||||
}
|
442
lib/control_page.dart
Normal file
442
lib/control_page.dart
Normal file
@@ -0,0 +1,442 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:winui_n2n/edge_state.dart';
|
||||
import 'package:winui_n2n/saved_connection.dart';
|
||||
import 'package:winui_n2n/shared_pref_singleton.dart';
|
||||
|
||||
class ControlPage extends StatefulWidget {
|
||||
const ControlPage({super.key});
|
||||
|
||||
@override
|
||||
State<ControlPage> createState() => _ControlPageState();
|
||||
}
|
||||
|
||||
class _ControlPageState extends State<ControlPage> {
|
||||
late TextEditingController _supernodeController;
|
||||
late TextEditingController _communityController;
|
||||
late TextEditingController _keyController;
|
||||
late TextEditingController _selfAddressController;
|
||||
late TextEditingController _configNameController;
|
||||
|
||||
bool _edgeConnecting = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_supernodeController = TextEditingController();
|
||||
_communityController = TextEditingController();
|
||||
_keyController = TextEditingController();
|
||||
_selfAddressController = TextEditingController();
|
||||
_configNameController = TextEditingController();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_supernodeController.dispose();
|
||||
_communityController.dispose();
|
||||
_keyController.dispose();
|
||||
_selfAddressController.dispose();
|
||||
_configNameController.dispose();
|
||||
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
width: 300,
|
||||
child: TextField(
|
||||
controller: _supernodeController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: '主服务器',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
width: 300,
|
||||
child: TextField(
|
||||
controller: _communityController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: '网络社区',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
width: 300,
|
||||
child: TextField(
|
||||
controller: _keyController,
|
||||
obscureText: true,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: '社区密码',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: SizedBox(
|
||||
width: 300,
|
||||
child: TextField(
|
||||
controller: _selfAddressController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: '我的地址',
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () => showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => Dialog(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
SizedBox(
|
||||
width: 350,
|
||||
child: TextField(
|
||||
controller: _configNameController,
|
||||
decoration: const InputDecoration(
|
||||
border: OutlineInputBorder(),
|
||||
labelText: '给这个配置备注一个名字',
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
List<dynamic> nodeList = jsonDecode(
|
||||
SharedPrefSingleton().savedConnection);
|
||||
List<SavedConnection> nodes = nodeList
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map((nodeData) =>
|
||||
SavedConnection.fromJson(nodeData))
|
||||
.toList();
|
||||
nodes.add(SavedConnection(
|
||||
_configNameController.text,
|
||||
_supernodeController.text,
|
||||
_communityController.text,
|
||||
_keyController.text,
|
||||
_selfAddressController.text,
|
||||
));
|
||||
|
||||
List<Map<String, dynamic>> nodeMaps = nodes
|
||||
.map((node) => node.toJson())
|
||||
.toList();
|
||||
String jsonString = jsonEncode(nodeMaps);
|
||||
SharedPrefSingleton()
|
||||
.setSavedConnection(jsonString);
|
||||
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
},
|
||||
child: const Text("保存")),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
},
|
||||
child: const Text('取消'),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
child: const Text('保存配置'),
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: _edgeConnecting
|
||||
? null
|
||||
: () async {
|
||||
if (EdgeState.instance.isRunning) {
|
||||
// Turn on firewall
|
||||
if (SharedPrefSingleton().autoFirewall) {
|
||||
await Process.run(
|
||||
"netsh.exe",
|
||||
[
|
||||
"advfirewall",
|
||||
"set",
|
||||
"allprofiles",
|
||||
"state",
|
||||
"on",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Kill the edge process
|
||||
if (EdgeState.instance.process != null) {
|
||||
if (EdgeState.instance.process!.kill()) {
|
||||
EdgeState.instance.isRunning = false;
|
||||
EdgeState.instance.process = null;
|
||||
setState(() {});
|
||||
}
|
||||
}
|
||||
} else if (!EdgeState.instance.isRunning) {
|
||||
// Block user click
|
||||
setState(() {
|
||||
_edgeConnecting = true;
|
||||
});
|
||||
|
||||
// Check for tap device
|
||||
final findTapResult = await Process.run(
|
||||
"./tools/driver/devcon.exe",
|
||||
["hwids", "tap0901"],
|
||||
);
|
||||
if (findTapResult.stdout
|
||||
.toString()
|
||||
.contains('No matching devices found.')) {
|
||||
// Install tap device
|
||||
EdgeState.instance.logger
|
||||
.addLog("Tap device not found, installing tap");
|
||||
debugPrint("Tap device not found, installing tap");
|
||||
final installTapResult = await Process.run(
|
||||
"./tools/driver/devcon.exe",
|
||||
[
|
||||
"install",
|
||||
"./tools/driver/OemVista.inf",
|
||||
"tap0901",
|
||||
],
|
||||
);
|
||||
if (installTapResult.stdout.toString().contains(
|
||||
"Tap driver installed successfully.") ||
|
||||
installTapResult.stdout.toString().contains(
|
||||
"Drivers installed successfully.")) {
|
||||
EdgeState.instance.logger
|
||||
.addLog("Tap driver installed successfully.");
|
||||
debugPrint("Tap driver installed successfully.");
|
||||
} else {
|
||||
EdgeState.instance.logger
|
||||
.addLog("Tap driver install failed.");
|
||||
debugPrint("Tap driver install failed.");
|
||||
}
|
||||
} else {
|
||||
// Ignore.
|
||||
EdgeState.instance.logger
|
||||
.addLog("Tap device already installed");
|
||||
debugPrint("Tap device already installed");
|
||||
}
|
||||
|
||||
// Close firewall
|
||||
if (SharedPrefSingleton().autoFirewall) {
|
||||
await Process.run(
|
||||
"netsh.exe",
|
||||
[
|
||||
"advfirewall",
|
||||
"set",
|
||||
"allprofiles",
|
||||
"state",
|
||||
"off",
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
// Start the edge
|
||||
// edge -c <community> -k <communityKey> -a <selfAddress> -r -l <remoteSuperNodeAddress>
|
||||
Process.start(
|
||||
"./tools/edge/edge.exe",
|
||||
[
|
||||
"-l",
|
||||
_supernodeController.text,
|
||||
"-c",
|
||||
_communityController.text,
|
||||
"-k",
|
||||
_keyController.text,
|
||||
"-a",
|
||||
_selfAddressController.text,
|
||||
"-r",
|
||||
],
|
||||
).then((process) async {
|
||||
EdgeState.instance.isRunning = true;
|
||||
EdgeState.instance.process = process;
|
||||
EdgeState.instance.logger
|
||||
.addLog("edge.exe starting");
|
||||
debugPrint("edge.exe starting");
|
||||
setState(() {
|
||||
_edgeConnecting = false;
|
||||
});
|
||||
process.stdout
|
||||
.transform(const SystemEncoding().decoder)
|
||||
.listen((data) {
|
||||
debugPrint('stdout: $data');
|
||||
EdgeState.instance.logger.addLog(data);
|
||||
});
|
||||
|
||||
await process.exitCode;
|
||||
EdgeState.instance.isRunning = false;
|
||||
EdgeState.instance.process = null;
|
||||
EdgeState.instance.logger
|
||||
.addLog("edge.exe closing");
|
||||
debugPrint("edge.exe closing");
|
||||
if (!mounted) return;
|
||||
setState(() {});
|
||||
});
|
||||
}
|
||||
},
|
||||
child: EdgeState.instance.isRunning
|
||||
? const Text("断开连接")
|
||||
: const Text("开始连接"),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
SavedConnection? selectedSavedConnection;
|
||||
showDialog<String>(
|
||||
context: context,
|
||||
builder: (BuildContext context) => Dialog(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: <Widget>[
|
||||
Builder(builder: (context) {
|
||||
String jsonString =
|
||||
SharedPrefSingleton().savedConnection;
|
||||
List<dynamic> nodeList = jsonDecode(jsonString);
|
||||
List<SavedConnection> nodes = nodeList
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map((nodeData) =>
|
||||
SavedConnection.fromJson(nodeData))
|
||||
.toList();
|
||||
|
||||
return StatefulBuilder(builder:
|
||||
(BuildContext context,
|
||||
StateSetter setState) {
|
||||
return DropdownButton(
|
||||
value: selectedSavedConnection,
|
||||
items: nodes.map<
|
||||
DropdownMenuItem<
|
||||
SavedConnection>>(
|
||||
(SavedConnection value) {
|
||||
return DropdownMenuItem<
|
||||
SavedConnection>(
|
||||
value: value,
|
||||
child: Text(value.name),
|
||||
);
|
||||
}).toList(),
|
||||
onChanged: (value) {
|
||||
setState(
|
||||
() {
|
||||
selectedSavedConnection = value!;
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
}),
|
||||
const SizedBox(height: 15),
|
||||
Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (selectedSavedConnection != null) {
|
||||
_supernodeController.text =
|
||||
selectedSavedConnection!.supernode;
|
||||
_communityController.text =
|
||||
selectedSavedConnection!.community;
|
||||
_keyController.text =
|
||||
selectedSavedConnection!
|
||||
.communityKey;
|
||||
_selfAddressController.text =
|
||||
selectedSavedConnection!
|
||||
.selfAddress;
|
||||
}
|
||||
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
},
|
||||
child: const Text('使用'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
if (selectedSavedConnection == null) {
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
}
|
||||
|
||||
String jsonString =
|
||||
SharedPrefSingleton().savedConnection;
|
||||
List<dynamic> nodeList =
|
||||
jsonDecode(jsonString);
|
||||
List<SavedConnection> nodes = nodeList
|
||||
.cast<Map<String, dynamic>>()
|
||||
.map((nodeData) =>
|
||||
SavedConnection.fromJson(
|
||||
nodeData))
|
||||
.toList();
|
||||
|
||||
nodes.removeWhere(
|
||||
(element) {
|
||||
return element.name ==
|
||||
selectedSavedConnection!.name;
|
||||
},
|
||||
);
|
||||
|
||||
List<Map<String, dynamic>> nodeMaps =
|
||||
nodes
|
||||
.map((node) => node.toJson())
|
||||
.toList();
|
||||
SharedPrefSingleton()
|
||||
.setSavedConnection(
|
||||
jsonEncode(nodeMaps))
|
||||
.then((onValue) {
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
});
|
||||
},
|
||||
child: const Text('删除'),
|
||||
),
|
||||
TextButton(
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
return;
|
||||
},
|
||||
child: const Text("取消"),
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text("使用配置")),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
20
lib/edge_state.dart
Normal file
20
lib/edge_state.dart
Normal file
@@ -0,0 +1,20 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class EdgeState {
|
||||
EdgeState._internal();
|
||||
static final EdgeState instance = EdgeState._internal();
|
||||
bool isRunning = false;
|
||||
Process? process;
|
||||
EdgeLogger logger = EdgeLogger();
|
||||
}
|
||||
|
||||
class EdgeLogger with ChangeNotifier {
|
||||
List<String> logList = [];
|
||||
|
||||
void addLog(String log) {
|
||||
logList.add(log);
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
97
lib/home_page.dart
Normal file
97
lib/home_page.dart
Normal file
@@ -0,0 +1,97 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:winui_n2n/control_page.dart';
|
||||
import 'package:winui_n2n/about_page.dart';
|
||||
import 'package:winui_n2n/logger_page.dart';
|
||||
import 'package:winui_n2n/main.dart';
|
||||
import 'package:winui_n2n/setting_page.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
int _selectedIndex = 0;
|
||||
|
||||
Widget _homePageShow() {
|
||||
if (_selectedIndex == 0) {
|
||||
return const ControlPage();
|
||||
} else if (_selectedIndex == 1) {
|
||||
return const AboutPage();
|
||||
} else if (_selectedIndex == 2) {
|
||||
return const LoggerPage();
|
||||
} else if (_selectedIndex == 3) {
|
||||
return const SettingPage();
|
||||
}
|
||||
|
||||
// Must contain a default widget.
|
||||
return const Text("Default");
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
body: Row(
|
||||
children: <Widget>[
|
||||
NavigationRail(
|
||||
selectedIndex: _selectedIndex,
|
||||
onDestinationSelected: (int index) {
|
||||
setState(() {
|
||||
_selectedIndex = index;
|
||||
});
|
||||
},
|
||||
labelType: NavigationRailLabelType.all,
|
||||
trailing: Expanded(
|
||||
child: Align(
|
||||
alignment: Alignment.bottomCenter,
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8.0),
|
||||
child: IconButton(
|
||||
icon:
|
||||
MainApp.of(context).getCurrentTheme == ThemeMode.light
|
||||
? const Icon(Icons.dark_mode)
|
||||
: const Icon(Icons.light_mode),
|
||||
onPressed: () {
|
||||
MainApp.of(context).getCurrentTheme == ThemeMode.light
|
||||
? MainApp.of(context).changeTheme(ThemeMode.dark)
|
||||
: MainApp.of(context).changeTheme(ThemeMode.light);
|
||||
setState(() {});
|
||||
}),
|
||||
),
|
||||
),
|
||||
),
|
||||
destinations: const <NavigationRailDestination>[
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.favorite_border),
|
||||
selectedIcon: Icon(Icons.favorite),
|
||||
label: Text('主页'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.bookmark_border),
|
||||
selectedIcon: Icon(Icons.book),
|
||||
label: Text('关于'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.pause_circle_filled),
|
||||
selectedIcon: Icon(Icons.pause_circle),
|
||||
label: Text('日志'),
|
||||
),
|
||||
NavigationRailDestination(
|
||||
icon: Icon(Icons.star_border),
|
||||
selectedIcon: Icon(Icons.star),
|
||||
label: Text('设置'),
|
||||
),
|
||||
],
|
||||
),
|
||||
const VerticalDivider(thickness: 1, width: 1),
|
||||
// This is the main content.
|
||||
Expanded(
|
||||
child: _homePageShow(),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
22
lib/logger_page.dart
Normal file
22
lib/logger_page.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:winui_n2n/edge_state.dart';
|
||||
|
||||
class LoggerPage extends StatelessWidget {
|
||||
const LoggerPage({super.key});
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return ListenableBuilder(
|
||||
listenable: EdgeState.instance.logger,
|
||||
builder: (context, child) {
|
||||
return ListView(
|
||||
children: EdgeState.instance.logger.logList.map<SelectableText>(
|
||||
(e) {
|
||||
return SelectableText(e);
|
||||
},
|
||||
).toList(),
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
77
lib/main.dart
Normal file
77
lib/main.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:window_manager/window_manager.dart';
|
||||
import 'package:winui_n2n/application_exit_control.dart';
|
||||
import 'package:winui_n2n/edge_state.dart';
|
||||
import 'package:winui_n2n/shared_pref_singleton.dart';
|
||||
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
await windowManager.ensureInitialized();
|
||||
|
||||
WindowOptions windowOptions = const WindowOptions(
|
||||
size: Size(630, 420),
|
||||
center: true,
|
||||
backgroundColor: Colors.transparent,
|
||||
skipTaskbar: false,
|
||||
titleBarStyle: TitleBarStyle.normal,
|
||||
windowButtonVisibility: false,
|
||||
);
|
||||
windowManager.waitUntilReadyToShow(windowOptions, () async {
|
||||
await windowManager.show();
|
||||
await windowManager.focus();
|
||||
});
|
||||
|
||||
await initSingleton();
|
||||
runApp(const MainApp());
|
||||
}
|
||||
|
||||
class MainApp extends StatefulWidget {
|
||||
const MainApp({super.key});
|
||||
|
||||
@override
|
||||
State<MainApp> createState() => _MainAppState();
|
||||
|
||||
static _MainAppState of(BuildContext context) {
|
||||
return context.findAncestorStateOfType<_MainAppState>()!;
|
||||
}
|
||||
}
|
||||
|
||||
class _MainAppState extends State<MainApp> {
|
||||
// false = dark, true = light
|
||||
ThemeMode _themeMode =
|
||||
SharedPrefSingleton().appTheme ? ThemeMode.light : ThemeMode.dark;
|
||||
ThemeMode get getCurrentTheme => _themeMode;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp(
|
||||
theme: ThemeData(),
|
||||
darkTheme: ThemeData.dark(),
|
||||
themeMode: _themeMode,
|
||||
home: const Scaffold(
|
||||
body: ApplicationExitControl(),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
void changeTheme(ThemeMode mode) {
|
||||
mode == ThemeMode.light
|
||||
? SharedPrefSingleton().setAppTheme(true).then((onValue) {
|
||||
setState(() {
|
||||
_themeMode = mode;
|
||||
});
|
||||
})
|
||||
: SharedPrefSingleton().setAppTheme(false).then((onValue) {
|
||||
setState(() {
|
||||
_themeMode = mode;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> initSingleton() async {
|
||||
EdgeState.instance;
|
||||
await SharedPrefSingleton().initialize();
|
||||
}
|
27
lib/saved_connection.dart
Normal file
27
lib/saved_connection.dart
Normal file
@@ -0,0 +1,27 @@
|
||||
class SavedConnection {
|
||||
final String name;
|
||||
final String supernode;
|
||||
final String community;
|
||||
final String communityKey;
|
||||
final String selfAddress;
|
||||
|
||||
SavedConnection(this.name, this.supernode, this.community, this.communityKey,
|
||||
this.selfAddress);
|
||||
|
||||
factory SavedConnection.fromJson(Map<String, dynamic> json) =>
|
||||
SavedConnection(
|
||||
json['name'] as String,
|
||||
json['supernode'] as String,
|
||||
json['community'] as String,
|
||||
json['communityKey'] as String,
|
||||
json['selfAddress'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> toJson() => {
|
||||
'name': name,
|
||||
'supernode': supernode,
|
||||
'community': community,
|
||||
'communityKey': communityKey,
|
||||
'selfAddress': selfAddress,
|
||||
};
|
||||
}
|
48
lib/setting_page.dart
Normal file
48
lib/setting_page.dart
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:winui_n2n/shared_pref_singleton.dart';
|
||||
|
||||
class SettingPage extends StatefulWidget {
|
||||
const SettingPage({super.key});
|
||||
|
||||
@override
|
||||
State<SettingPage> createState() => _SettingPageState();
|
||||
}
|
||||
|
||||
class _SettingPageState extends State<SettingPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
Row(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
children: [
|
||||
const Text('自动设置防火墙'),
|
||||
Switch(
|
||||
value: SharedPrefSingleton().autoFirewall,
|
||||
onChanged: (value) {
|
||||
final setting = !SharedPrefSingleton().autoFirewall;
|
||||
SharedPrefSingleton().setAutoFirewall(setting).then((ok) {
|
||||
setState(() {});
|
||||
});
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
ElevatedButton(
|
||||
onPressed: () {
|
||||
ScaffoldMessenger.of(context)
|
||||
..removeCurrentSnackBar()
|
||||
..showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text("功能暂未开发, 敬请期待~"),
|
||||
duration: Duration(seconds: 2),
|
||||
),
|
||||
);
|
||||
},
|
||||
child: const Text("检查更新"),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
22
lib/shared_pref_singleton.dart
Normal file
22
lib/shared_pref_singleton.dart
Normal file
@@ -0,0 +1,22 @@
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class SharedPrefSingleton {
|
||||
static final SharedPrefSingleton _instance = SharedPrefSingleton._internal();
|
||||
factory SharedPrefSingleton() => _instance;
|
||||
SharedPrefSingleton._internal();
|
||||
late SharedPreferences _pref;
|
||||
Future<void> initialize() async {
|
||||
_pref = await SharedPreferences.getInstance();
|
||||
}
|
||||
|
||||
Future<bool> setAutoFirewall(bool ok) => _pref.setBool("auto_firewall", ok);
|
||||
bool get autoFirewall => _pref.getBool("auto_firewall") ?? true;
|
||||
|
||||
Future<bool> setAppTheme(bool ok) => _pref.setBool("app_theme", ok);
|
||||
// false = dark, true = light
|
||||
bool get appTheme => _pref.getBool("app_theme") ?? false;
|
||||
|
||||
Future<bool> setSavedConnection(String config) =>
|
||||
_pref.setString("saved_connection", config);
|
||||
String get savedConnection => _pref.getString("saved_connection") ?? "[]";
|
||||
}
|
Reference in New Issue
Block a user