diff --git a/app/.gitignore b/app/.gitignore
new file mode 100644
index 0000000..6922819
--- /dev/null
+++ b/app/.gitignore
@@ -0,0 +1,52 @@
+# Miscellaneous
+*.class
+*.log
+*.pyc
+*.swp
+.DS_Store
+.atom/
+.buildlog/
+.history
+.svn/
+migrate_working_dir/
+
+# IntelliJ related
+*.iml
+*.ipr
+*.iws
+.idea/
+
+# The .vscode folder contains launch configuration and tasks you configure in
+# VS Code which you may wish to be included in version control, so this line
+# is commented out by default.
+#.vscode/
+
+# Flutter/Dart/Pub related
+**/doc/api/
+**/ios/Flutter/.last_build_id
+.dart_tool/
+.flutter-plugins
+.flutter-plugins-dependencies
+.pub-cache/
+.pub/
+/build/
+
+# Symbolication related
+app.*.symbols
+
+# Obfuscation related
+app.*.map.json
+
+# Android Studio will place build artifacts here
+/android/app/debug
+/android/app/profile
+/android/app/release
+
+# Android related
+**/android/**/gradle-wrapper.jar
+**/android/.gradle
+**/android/captures/
+**/android/gradlew
+**/android/gradlew.bat
+**/android/local.properties
+**/android/**/GeneratedPluginRegistrant.java
diff --git a/app/.metadata b/app/.metadata
new file mode 100644
index 0000000..8725f3d
--- /dev/null
+++ b/app/.metadata
@@ -0,0 +1,33 @@
+# This file tracks properties of this Flutter project.
+# Used by Flutter tool to assess capabilities and perform upgrades etc.
+#
+# This file should be version controlled and should not be manually edited.
+
+version:
+ revision: "54e66469a933b60ddf175f858f82eaeb97e48c8d"
+ channel: "stable"
+
+project_type: app
+
+# Tracks metadata for the flutter migrate command
+migration:
+ platforms:
+ - platform: root
+ create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
+ base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
+ - platform: android
+ create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
+ base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
+ - platform: web
+ create_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
+ base_revision: 54e66469a933b60ddf175f858f82eaeb97e48c8d
+
+ # User provided section
+
+ # List of Local paths (relative to this file) that should be
+ # ignored by the migrate tool.
+ #
+ # Files that are not part of the templates will be ignored by default.
+ unmanaged_files:
+ - 'lib/main.dart'
+ - 'ios/Runner.xcodeproj/project.pbxproj'
diff --git a/app/analysis_options.yaml b/app/analysis_options.yaml
new file mode 100644
index 0000000..0d29021
--- /dev/null
+++ b/app/analysis_options.yaml
@@ -0,0 +1,28 @@
+# This file configures the analyzer, which statically analyzes Dart code to
+# check for errors, warnings, and lints.
+#
+# The issues identified by the analyzer are surfaced in the UI of Dart-enabled
+# IDEs (https://dart.dev/tools#ides-and-editors). The analyzer can also be
+# invoked from the command line by running `flutter analyze`.
+
+# The following line activates a set of recommended lints for Flutter apps,
+# packages, and plugins designed to encourage good coding practices.
+include: package:flutter_lints/flutter.yaml
+
+linter:
+ # The lint rules applied to this project can be customized in the
+ # section below to disable rules from the `package:flutter_lints/flutter.yaml`
+ # included above or to enable additional rules. A list of all available lints
+ # and their documentation is published at https://dart.dev/lints.
+ #
+ # Instead of disabling a lint rule for the entire project in the
+ # section below, it can also be suppressed for a single line of code
+ # or a specific dart file by using the `// ignore: name_of_lint` and
+ # `// ignore_for_file: name_of_lint` syntax on the line or in the file
+ # producing the lint.
+ rules:
+ # avoid_print: false # Uncomment to disable the `avoid_print` rule
+ # prefer_single_quotes: true # Uncomment to enable the `prefer_single_quotes` rule
+
+# Additional information about this file can be found at
+# https://dart.dev/guides/language/analysis-options
diff --git a/app/android/.gitignore b/app/android/.gitignore
new file mode 100644
index 0000000..6f56801
--- /dev/null
+++ b/app/android/.gitignore
@@ -0,0 +1,13 @@
+gradle-wrapper.jar
+/.gradle
+/captures/
+/gradlew
+/gradlew.bat
+/local.properties
+GeneratedPluginRegistrant.java
+
+# Remember to never publicly share your keystore.
+# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
+key.properties
+**/*.keystore
+**/*.jks
diff --git a/app/android/app/build.gradle b/app/android/app/build.gradle
new file mode 100644
index 0000000..45cc261
--- /dev/null
+++ b/app/android/app/build.gradle
@@ -0,0 +1,67 @@
+plugins {
+ id "com.android.application"
+ id "kotlin-android"
+ id "dev.flutter.flutter-gradle-plugin"
+}
+
+def localProperties = new Properties()
+def localPropertiesFile = rootProject.file('local.properties')
+if (localPropertiesFile.exists()) {
+ localPropertiesFile.withReader('UTF-8') { reader ->
+ localProperties.load(reader)
+ }
+}
+
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+ flutterVersionCode = '1'
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+ flutterVersionName = '1.0'
+}
+
+android {
+ namespace "com.eventlowop.zerotier_magisk_app"
+ compileSdk flutter.compileSdkVersion
+ ndkVersion flutter.ndkVersion
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_8
+ targetCompatibility JavaVersion.VERSION_1_8
+ }
+
+ kotlinOptions {
+ jvmTarget = '1.8'
+ }
+
+ sourceSets {
+ main.java.srcDirs += 'src/main/kotlin'
+ }
+
+ defaultConfig {
+ // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
+ applicationId "com.eventlowop.zerotier_magisk_app"
+ // You can update the following values to match your application needs.
+ // For more information, see: https://docs.flutter.dev/deployment/android#reviewing-the-gradle-build-configuration.
+ minSdkVersion flutter.minSdkVersion
+ targetSdkVersion flutter.targetSdkVersion
+ versionCode flutterVersionCode.toInteger()
+ versionName flutterVersionName
+ }
+
+ buildTypes {
+ release {
+ // TODO: Add your own signing config for the release build.
+ // Signing with the debug keys for now, so `flutter run --release` works.
+ signingConfig signingConfigs.debug
+ }
+ }
+}
+
+flutter {
+ source '../..'
+}
+
+dependencies {}
diff --git a/app/android/app/src/debug/AndroidManifest.xml b/app/android/app/src/debug/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/app/android/app/src/debug/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/android/app/src/main/AndroidManifest.xml b/app/android/app/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..2e15e0a
--- /dev/null
+++ b/app/android/app/src/main/AndroidManifest.xml
@@ -0,0 +1,46 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/android/app/src/main/kotlin/com/eventlowop/zerotier_magisk_app/MainActivity.kt b/app/android/app/src/main/kotlin/com/eventlowop/zerotier_magisk_app/MainActivity.kt
new file mode 100644
index 0000000..0a1acff
--- /dev/null
+++ b/app/android/app/src/main/kotlin/com/eventlowop/zerotier_magisk_app/MainActivity.kt
@@ -0,0 +1,5 @@
+package com.eventlowop.zerotier_magisk_app
+
+import io.flutter.embedding.android.FlutterActivity
+
+class MainActivity: FlutterActivity()
diff --git a/app/android/app/src/main/res/drawable-v21/launch_background.xml b/app/android/app/src/main/res/drawable-v21/launch_background.xml
new file mode 100644
index 0000000..f74085f
--- /dev/null
+++ b/app/android/app/src/main/res/drawable-v21/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/app/android/app/src/main/res/drawable/launch_background.xml b/app/android/app/src/main/res/drawable/launch_background.xml
new file mode 100644
index 0000000..304732f
--- /dev/null
+++ b/app/android/app/src/main/res/drawable/launch_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
diff --git a/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..db77bb4
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/mipmap-hdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-hdpi/launcher_icon.png
new file mode 100644
index 0000000..6d55781
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-hdpi/launcher_icon.png differ
diff --git a/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..17987b7
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/mipmap-mdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-mdpi/launcher_icon.png
new file mode 100644
index 0000000..3db1887
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-mdpi/launcher_icon.png differ
diff --git a/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..09d4391
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png
new file mode 100644
index 0000000..add438e
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xhdpi/launcher_icon.png differ
diff --git a/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..d5f1c8d
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png
new file mode 100644
index 0000000..b306759
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxhdpi/launcher_icon.png differ
diff --git a/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..4d6372e
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png b/app/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png
new file mode 100644
index 0000000..b6e1f78
Binary files /dev/null and b/app/android/app/src/main/res/mipmap-xxxhdpi/launcher_icon.png differ
diff --git a/app/android/app/src/main/res/values-night/styles.xml b/app/android/app/src/main/res/values-night/styles.xml
new file mode 100644
index 0000000..06952be
--- /dev/null
+++ b/app/android/app/src/main/res/values-night/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/android/app/src/main/res/values/strings.xml b/app/android/app/src/main/res/values/strings.xml
new file mode 100644
index 0000000..92ef5f0
--- /dev/null
+++ b/app/android/app/src/main/res/values/strings.xml
@@ -0,0 +1,4 @@
+
+
+ ZeroTier for Magisk
+
\ No newline at end of file
diff --git a/app/android/app/src/main/res/values/styles.xml b/app/android/app/src/main/res/values/styles.xml
new file mode 100644
index 0000000..cb1ef88
--- /dev/null
+++ b/app/android/app/src/main/res/values/styles.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
diff --git a/app/android/app/src/profile/AndroidManifest.xml b/app/android/app/src/profile/AndroidManifest.xml
new file mode 100644
index 0000000..399f698
--- /dev/null
+++ b/app/android/app/src/profile/AndroidManifest.xml
@@ -0,0 +1,7 @@
+
+
+
+
diff --git a/app/android/build.gradle b/app/android/build.gradle
new file mode 100644
index 0000000..64006a2
--- /dev/null
+++ b/app/android/build.gradle
@@ -0,0 +1,19 @@
+allprojects {
+ repositories {
+ google()
+ maven { url 'https://mirrors.tuna.tsinghua.edu.cn/flutter/download.flutter.io' }
+ mavenCentral()
+ }
+}
+
+rootProject.buildDir = '../build'
+subprojects {
+ project.buildDir = "${rootProject.buildDir}/${project.name}"
+}
+subprojects {
+ project.evaluationDependsOn(':app')
+}
+
+tasks.register("clean", Delete) {
+ delete rootProject.buildDir
+}
\ No newline at end of file
diff --git a/app/android/gradle.properties b/app/android/gradle.properties
new file mode 100644
index 0000000..598d13f
--- /dev/null
+++ b/app/android/gradle.properties
@@ -0,0 +1,3 @@
+org.gradle.jvmargs=-Xmx4G
+android.useAndroidX=true
+android.enableJetifier=true
diff --git a/app/android/gradle/wrapper/gradle-wrapper.properties b/app/android/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 0000000..e1ca574
--- /dev/null
+++ b/app/android/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,5 @@
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-7.6.3-all.zip
diff --git a/app/android/settings.gradle b/app/android/settings.gradle
new file mode 100644
index 0000000..a6efc8a
--- /dev/null
+++ b/app/android/settings.gradle
@@ -0,0 +1,26 @@
+pluginManagement {
+ def flutterSdkPath = {
+ def properties = new Properties()
+ file("local.properties").withInputStream { properties.load(it) }
+ def flutterSdkPath = properties.getProperty("flutter.sdk")
+ assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+ return flutterSdkPath
+ }
+ settings.ext.flutterSdkPath = flutterSdkPath()
+
+ includeBuild("${settings.ext.flutterSdkPath}/packages/flutter_tools/gradle")
+
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+plugins {
+ id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+ id "com.android.application" version "7.3.0" apply false
+ id "org.jetbrains.kotlin.android" version "1.7.10" apply false
+}
+
+include ":app"
\ No newline at end of file
diff --git a/app/assets/icon.png b/app/assets/icon.png
new file mode 100644
index 0000000..b36f455
Binary files /dev/null and b/app/assets/icon.png differ
diff --git a/app/lib/authed_client.dart b/app/lib/authed_client.dart
new file mode 100644
index 0000000..881fdca
--- /dev/null
+++ b/app/lib/authed_client.dart
@@ -0,0 +1,36 @@
+import 'dart:io';
+import 'package:http/http.dart' as http;
+
+class AuthedClientInner extends http.BaseClient {
+ final String _secret;
+ final http.Client _inner;
+
+ AuthedClientInner(this._secret, this._inner);
+
+ @override
+ Future send(http.BaseRequest request) {
+ request.headers['X-ZT1-Auth'] = _secret;
+ return _inner.send(request);
+ }
+
+ // @override
+ // Future post(Uri url,
+ // {Map? headers, Object? body, Encoding? encoding}) {
+ // Map hd = headers ?? {};
+ // hd['content-type'] = 'application/json';
+ // return super.post(url, headers: hd, body: body, encoding: encoding);
+ // }
+}
+
+class AuthedClient {
+ late Future client;
+ Future loadSecret() async {
+ final results = await Process.run(
+ 'su', ['-c', 'cat', '/data/adb/zerotier/home/authtoken.secret']);
+ return AuthedClientInner(results.stdout.toString().trim(), http.Client());
+ }
+
+ AuthedClient() {
+ client = loadSecret();
+ }
+}
diff --git a/app/lib/main.dart b/app/lib/main.dart
new file mode 100644
index 0000000..5f6a087
--- /dev/null
+++ b/app/lib/main.dart
@@ -0,0 +1,289 @@
+import 'dart:convert';
+import 'package:flutter/material.dart';
+import 'dart:io';
+import 'dart:developer' as developer;
+import 'package:flutter/services.dart';
+
+import './authed_client.dart';
+
+void main() {
+ runApp(const MyApp());
+}
+
+class MyApp extends StatelessWidget {
+ const MyApp({super.key});
+
+ @override
+ Widget build(BuildContext context) {
+ return MaterialApp(
+ title: 'ZeroTier for Magisk',
+ theme: ThemeData(
+ colorSchemeSeed: Colors.lightBlue,
+ useMaterial3: true,
+ brightness: Brightness.light,
+ ),
+ darkTheme: ThemeData(
+ colorSchemeSeed: Colors.lightBlue,
+ useMaterial3: true,
+ brightness: Brightness.dark,
+ ),
+ home: const MyHomePage(),
+ debugShowCheckedModeBanner: false,
+ );
+ }
+}
+
+class MyHomePage extends StatefulWidget {
+ const MyHomePage({super.key});
+
+ @override
+ State createState() => _MyHomePageState();
+}
+
+Future zerotierCommand(String exe, String command) {
+ return Process.run('su', ['-c', '/data/adb/zerotier/$exe', command],
+ environment: {'LD_LIBRARY_PATH': '/data/adb/zerotier/lib'});
+}
+
+class _MyHomePageState extends State {
+ List networkList = [];
+ bool runningStatus = false;
+
+ final AuthedClient authed = AuthedClient();
+
+ loadNetwork() async {
+ final client = await authed.client;
+ final resp = await client.get(
+ Uri.http('localhost:9993', 'network'),
+ );
+ final body = jsonDecode(resp.body);
+ networkList = List.from(body.map((u) => u['id']));
+ }
+
+ void leaveNetwork(String id) async {
+ final client = await authed.client;
+ final resp = await client.delete(Uri.http('localhost:9993', 'network/$id'));
+ if (resp.statusCode != 200) {
+ // failed
+ return;
+ }
+ // success
+ setState(() {
+ networkList.remove(id);
+ });
+ }
+
+ void joinNetwork(String id) async {
+ final client = await authed.client;
+ final resp = await client.put(Uri.http('localhost:9993', 'network/$id'));
+ if (resp.statusCode != 200) {
+ // failed
+ return;
+ }
+ // success
+ if (!networkList.contains(id)) {
+ setState(() {
+ networkList.add(id);
+ });
+ }
+ }
+
+ Future loadStatus() async {
+ final client = await authed.client;
+ try {
+ await client.get(Uri.http('localhost:9993'));
+ setState(() => runningStatus = true);
+ return;
+ } on SocketException {
+ setState(() => runningStatus = false);
+ } catch (e) {
+ developer.log('error', error: e.toString());
+ // error
+ }
+ }
+
+ late Future loadingNetworkFuture = loadNetwork();
+
+ @override
+ void initState() {
+ super.initState();
+ loadStatus();
+ }
+
+ Function cmdButtonWrapper(func) {
+ bool doing = false;
+ return () => doing
+ ? null
+ : () async {
+ setState(() => doing = true);
+ await func();
+ setState(() => doing = false);
+ };
+ }
+
+ late Function restartFn, startFn, stopFn, joinFn, leaveFn;
+
+ _MyHomePageState() : super() {
+ restartFn =
+ cmdButtonWrapper(() => zerotierCommand('zerotier.sh', 'restart'));
+ startFn = cmdButtonWrapper(() => zerotierCommand('zerotier.sh', 'start'));
+ stopFn = cmdButtonWrapper(() => zerotierCommand('zerotier.sh', 'stop'));
+ joinFn = cmdButtonWrapper(() => joinNetwork(_idInputController.text));
+ }
+
+ final _idInputController = TextEditingController();
+
+ homePage({bool title = false}) {
+ if (title) {
+ return 'Status';
+ }
+ return Column(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
+ Builder(builder: (BuildContext context) {
+ final color = (runningStatus ? Colors.green : Colors.red)[200];
+
+ return Column(children: [
+ Icon(runningStatus ? Icons.play_arrow : Icons.stop, color: color, size: 72),
+ Row(mainAxisAlignment: MainAxisAlignment.center, children: [
+ Text(runningStatus ? 'Running' : 'Stopped', style: TextStyle(color: color, fontSize: 18)),
+ IconButton(icon: const Icon(Icons.refresh), onPressed: loadStatus)
+ ])
+ ]);
+ }),
+ ButtonBar(
+ alignment: MainAxisAlignment.center,
+ mainAxisSize: MainAxisSize.max,
+ children: [
+ OutlinedButton.icon(
+ onPressed: startFn(),
+ label: const Text('Start'),
+ icon: const Icon(Icons.play_arrow),
+ ),
+ OutlinedButton.icon(
+ onPressed: restartFn(),
+ label: const Text('Restart'),
+ icon: const Icon(Icons.restart_alt),
+ ),
+ OutlinedButton.icon(
+ onPressed: stopFn(),
+ label: const Text('Stop'),
+ icon: const Icon(Icons.stop),
+ ),
+ ],
+ )
+ ]);
+ }
+
+ networkPage({bool title = false}) {
+ if (title) {
+ return 'Network';
+ }
+ return Padding(
+ padding: const EdgeInsets.all(12),
+ child: Column(
+ children: [
+ Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
+ SizedBox(
+ width: 200,
+ child: TextField(
+ controller: _idInputController,
+ textAlign: TextAlign.center,
+ inputFormatters: [
+ FilteringTextInputFormatter.allow(RegExp('[0-9a-f]'))
+ ],
+ maxLength: 16,
+ decoration: const InputDecoration(
+ labelText: "network id", icon: Icon(Icons.router)))),
+ OutlinedButton.icon(
+ onPressed: joinFn(),
+ label: const Text('Join'),
+ icon: const Icon(Icons.add)),
+ ]),
+ const SizedBox(height: 30),
+ const Divider(height: 10),
+ ListTile(
+ title: const Text('Networks'),
+ titleTextStyle: const TextStyle(fontSize: 18),
+ trailing: IconButton(
+ icon: const Icon(Icons.refresh),
+ onPressed: () {
+ setState(() {
+ loadingNetworkFuture = loadNetwork();
+ });
+ })),
+ const SizedBox(height: 10),
+ FutureBuilder(
+ future: loadingNetworkFuture,
+ builder: (context, snapshot) {
+ if (snapshot.connectionState == ConnectionState.done) {
+ if (snapshot.hasError) {
+ return Text('Error: ${snapshot.error}');
+ }
+ return ListView.builder(
+ shrinkWrap: true,
+ itemCount: networkList.length,
+ itemBuilder: (BuildContext ctx, int i) {
+ return Card(
+ child: ListTile(
+ title: Text(networkList[i]),
+ trailing: SizedBox(
+ width: 96,
+ child: Row(children: [
+ IconButton(
+ onPressed: () => Clipboard.setData(
+ ClipboardData(
+ text: networkList[i]))
+ .then,
+ tooltip: '复制',
+ icon: const Icon(Icons.copy)),
+ IconButton(
+ onPressed: () =>
+ leaveNetwork(networkList[i]),
+ tooltip: '离开',
+ icon: const Icon(Icons.close)),
+ ]))));
+ });
+ }
+ return const CircularProgressIndicator();
+ },
+ ),
+ ],
+ ));
+ }
+
+ int currentPageIndex = 0;
+ genWidgetList({bool title = false}) {
+ return [
+ homePage(title: title),
+ networkPage(title: title)
+ ][currentPageIndex];
+ }
+
+ @override
+ Widget build(BuildContext context) {
+ return Scaffold(
+ appBar: AppBar(title: Text(genWidgetList(title: true))),
+ bottomNavigationBar: NavigationBar(
+ onDestinationSelected: (int index) {
+ setState(() {
+ currentPageIndex = index;
+ });
+ },
+ selectedIndex: currentPageIndex,
+ destinations: const [
+ NavigationDestination(
+ selectedIcon: Icon(Icons.home),
+ icon: Icon(Icons.home_outlined),
+ label: 'Status',
+ ),
+ NavigationDestination(
+ selectedIcon: Icon(Icons.router),
+ icon: Icon(Icons.router_outlined),
+ label: 'Network',
+ ),
+ ],
+ ),
+ body: Center(
+ child: genWidgetList(),
+ ));
+ }
+}
diff --git a/app/pubspec.lock b/app/pubspec.lock
new file mode 100644
index 0000000..7e225d1
--- /dev/null
+++ b/app/pubspec.lock
@@ -0,0 +1,341 @@
+# Generated by pub
+# See https://dart.dev/tools/pub/glossary#lockfile
+packages:
+ archive:
+ dependency: transitive
+ description:
+ name: archive
+ sha256: ecf4273855368121b1caed0d10d4513c7241dfc813f7d3c8933b36622ae9b265
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "3.5.1"
+ args:
+ dependency: transitive
+ description:
+ name: args
+ sha256: "7cf60b9f0cc88203c5a190b4cd62a99feea42759a7fa695010eb5de1c0b2252a"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "2.5.0"
+ async:
+ dependency: transitive
+ description:
+ name: async
+ sha256: "947bfcf187f74dbc5e146c9eb9c0f10c9f8b30743e341481c1e2ed3ecc18c20c"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "2.11.0"
+ boolean_selector:
+ dependency: transitive
+ description:
+ name: boolean_selector
+ sha256: "6cfb5af12253eaf2b368f07bacc5a80d1301a071c73360d746b7f2e32d762c66"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "2.1.1"
+ characters:
+ dependency: transitive
+ description:
+ name: characters
+ sha256: "04a925763edad70e8443c99234dc3328f442e811f1d8fd1a72f1c8ad0f69a605"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.3.0"
+ checked_yaml:
+ dependency: transitive
+ description:
+ name: checked_yaml
+ sha256: feb6bed21949061731a7a75fc5d2aa727cf160b91af9a3e464c5e3a32e28b5ff
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "2.0.3"
+ cli_util:
+ dependency: transitive
+ description:
+ name: cli_util
+ sha256: c05b7406fdabc7a49a3929d4af76bcaccbbffcbcdcf185b082e1ae07da323d19
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "0.4.1"
+ clock:
+ dependency: transitive
+ description:
+ name: clock
+ sha256: cb6d7f03e1de671e34607e909a7213e31d7752be4fb66a86d29fe1eb14bfb5cf
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.1.1"
+ collection:
+ dependency: transitive
+ description:
+ name: collection
+ sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.18.0"
+ crypto:
+ dependency: transitive
+ description:
+ name: crypto
+ sha256: ff625774173754681d66daaf4a448684fb04b78f902da9cb3d308c19cc5e8bab
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "3.0.3"
+ cupertino_icons:
+ dependency: "direct main"
+ description:
+ name: cupertino_icons
+ sha256: ba631d1c7f7bef6b729a622b7b752645a2d076dba9976925b8f25725a30e1ee6
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.0.8"
+ fake_async:
+ dependency: transitive
+ description:
+ name: fake_async
+ sha256: "511392330127add0b769b75a987850d136345d9227c6b94c96a04cf4a391bf78"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.3.1"
+ flutter:
+ dependency: "direct main"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ flutter_launcher_icons:
+ dependency: "direct dev"
+ description:
+ name: flutter_launcher_icons
+ sha256: "526faf84284b86a4cb36d20a5e45147747b7563d921373d4ee0559c54fcdbcea"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "0.13.1"
+ flutter_lints:
+ dependency: "direct dev"
+ description:
+ name: flutter_lints
+ sha256: "3f41d009ba7172d5ff9be5f6e6e6abb4300e263aab8866d2a0842ed2a70f8f0c"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "4.0.0"
+ flutter_test:
+ dependency: "direct dev"
+ description: flutter
+ source: sdk
+ version: "0.0.0"
+ http:
+ dependency: "direct main"
+ description:
+ name: http
+ sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.2.1"
+ http_parser:
+ dependency: transitive
+ description:
+ name: http_parser
+ sha256: "2aa08ce0341cc9b354a498388e30986515406668dbcc4f7c950c3e715496693b"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "4.0.2"
+ image:
+ dependency: transitive
+ description:
+ name: image
+ sha256: "4c68bfd5ae83e700b5204c1e74451e7bf3cf750e6843c6e158289cf56bda018e"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "4.1.7"
+ json_annotation:
+ dependency: transitive
+ description:
+ name: json_annotation
+ sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "4.9.0"
+ launcher_name:
+ dependency: "direct dev"
+ description:
+ name: launcher_name
+ sha256: "5fc9a8b8de9e255d5f21effc33632b5620771c9b2310d459a7710b725353b305"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.0.2"
+ leak_tracker:
+ dependency: transitive
+ description:
+ name: leak_tracker
+ sha256: "78eb209deea09858f5269f5a5b02be4049535f568c07b275096836f01ea323fa"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "10.0.0"
+ leak_tracker_flutter_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_flutter_testing
+ sha256: b46c5e37c19120a8a01918cfaf293547f47269f7cb4b0058f21531c2465d6ef0
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "2.0.1"
+ leak_tracker_testing:
+ dependency: transitive
+ description:
+ name: leak_tracker_testing
+ sha256: a597f72a664dbd293f3bfc51f9ba69816f84dcd403cdac7066cb3f6003f3ab47
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "2.0.1"
+ lints:
+ dependency: transitive
+ description:
+ name: lints
+ sha256: "976c774dd944a42e83e2467f4cc670daef7eed6295b10b36ae8c85bcbf828235"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "4.0.0"
+ matcher:
+ dependency: transitive
+ description:
+ name: matcher
+ sha256: d2323aa2060500f906aa31a895b4030b6da3ebdcc5619d14ce1aada65cd161cb
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "0.12.16+1"
+ material_color_utilities:
+ dependency: transitive
+ description:
+ name: material_color_utilities
+ sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "0.8.0"
+ meta:
+ dependency: transitive
+ description:
+ name: meta
+ sha256: d584fa6707a52763a52446f02cc621b077888fb63b93bbcb1143a7be5a0c0c04
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.11.0"
+ path:
+ dependency: transitive
+ description:
+ name: path
+ sha256: "087ce49c3f0dc39180befefc60fdb4acd8f8620e5682fe2476afd0b3688bb4af"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.9.0"
+ petitparser:
+ dependency: transitive
+ description:
+ name: petitparser
+ sha256: c15605cd28af66339f8eb6fbe0e541bfe2d1b72d5825efc6598f3e0a31b9ad27
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "6.0.2"
+ sky_engine:
+ dependency: transitive
+ description: flutter
+ source: sdk
+ version: "0.0.99"
+ source_span:
+ dependency: transitive
+ description:
+ name: source_span
+ sha256: "53e943d4206a5e30df338fd4c6e7a077e02254531b138a15aec3bd143c1a8b3c"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.10.0"
+ stack_trace:
+ dependency: transitive
+ description:
+ name: stack_trace
+ sha256: "73713990125a6d93122541237550ee3352a2d84baad52d375a4cad2eb9b7ce0b"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.11.1"
+ stream_channel:
+ dependency: transitive
+ description:
+ name: stream_channel
+ sha256: ba2aa5d8cc609d96bbb2899c28934f9e1af5cddbd60a827822ea467161eb54e7
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "2.1.2"
+ string_scanner:
+ dependency: transitive
+ description:
+ name: string_scanner
+ sha256: "556692adab6cfa87322a115640c11f13cb77b3f076ddcc5d6ae3c20242bedcde"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.2.0"
+ term_glyph:
+ dependency: transitive
+ description:
+ name: term_glyph
+ sha256: a29248a84fbb7c79282b40b8c72a1209db169a2e0542bce341da992fe1bc7e84
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.2.1"
+ test_api:
+ dependency: transitive
+ description:
+ name: test_api
+ sha256: "5c2f730018264d276c20e4f1503fd1308dfbbae39ec8ee63c5236311ac06954b"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "0.6.1"
+ typed_data:
+ dependency: transitive
+ description:
+ name: typed_data
+ sha256: facc8d6582f16042dd49f2463ff1bd6e2c9ef9f3d5da3d9b087e244a7b564b3c
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "1.3.2"
+ vector_math:
+ dependency: transitive
+ description:
+ name: vector_math
+ sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "2.1.4"
+ vm_service:
+ dependency: transitive
+ description:
+ name: vm_service
+ sha256: b3d56ff4341b8f182b96aceb2fa20e3dcb336b9f867bc0eafc0de10f1048e957
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "13.0.0"
+ web:
+ dependency: transitive
+ description:
+ name: web
+ sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "0.5.1"
+ xml:
+ dependency: transitive
+ description:
+ name: xml
+ sha256: b015a8ad1c488f66851d762d3090a21c600e479dc75e68328c52774040cf9226
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "6.5.0"
+ yaml:
+ dependency: transitive
+ description:
+ name: yaml
+ sha256: "75769501ea3489fca56601ff33454fe45507ea3bfb014161abc3b43ae25989d5"
+ url: "https://mirrors.tuna.tsinghua.edu.cn/dart-pub/"
+ source: hosted
+ version: "3.1.2"
+sdks:
+ dart: ">=3.3.4 <4.0.0"
+ flutter: ">=1.17.0"
diff --git a/app/pubspec.yaml b/app/pubspec.yaml
new file mode 100644
index 0000000..8ee27bd
--- /dev/null
+++ b/app/pubspec.yaml
@@ -0,0 +1,101 @@
+name: zerotier_magisk_app
+description: "Controller app for zerotier-magisk"
+# 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.
+version: 1.0.0+1
+
+environment:
+ sdk: '>=3.3.4 <4.0.0'
+
+# 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
+
+
+ # The following adds the Cupertino Icons font to your application.
+ # Use with the CupertinoIcons class for iOS style icons.
+ cupertino_icons: ^1.0.6
+ http: ^1.2.1
+
+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: ^4.0.0
+ flutter_launcher_icons: ^0.13.1
+ launcher_name: ^1.0.2
+
+# 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:
+
+ # 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:
+ # - images/a_dot_burr.jpeg
+ # - images/a_dot_ham.jpeg
+
+ # An image asset can refer to one or more resolution-specific "variants", see
+ # https://flutter.dev/assets-and-images/#resolution-aware
+
+ # For details regarding adding assets from package dependencies, see
+ # https://flutter.dev/assets-and-images/#from-packages
+
+ # 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/custom-fonts/#from-packages
+
+flutter_launcher_icons:
+ android: "launcher_icon"
+ ios: false
+ image_path: "assets/icon.png"
+ min_sdk_android: 21 # android min sdk min:16, default 21
+launcher_name:
+ default: "ZeroTier for Magisk"
\ No newline at end of file