mirror of https://github.com/ospab/ostp.git
Fix Tauri build args, split Android into matrix, track flutter/gui, update docs and contacts
This commit is contained in:
parent
0cdb53e4e2
commit
23e2c04bc7
|
|
@ -289,7 +289,7 @@ jobs:
|
|||
run: |
|
||||
npm install
|
||||
cargo build -p ostp-tun-helper --release --target ${{ matrix.target }}
|
||||
npm run build -- --no-bundle --target ${{ matrix.target }}
|
||||
npx tauri build --no-bundle --target ${{ matrix.target }}
|
||||
|
||||
- name: Package Portable ZIP
|
||||
shell: pwsh
|
||||
|
|
@ -312,8 +312,17 @@ jobs:
|
|||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
build-android:
|
||||
name: Build Android Client (Flutter)
|
||||
name: Build Android Client (Flutter) - ${{ matrix.arch }}
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
include:
|
||||
- arch: arm64-v8a
|
||||
flutter_target: android-arm64
|
||||
tun2socks_arch: linux-arm64
|
||||
- arch: armeabi-v7a
|
||||
flutter_target: android-arm
|
||||
tun2socks_arch: linux-armv7
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
|
|
@ -347,42 +356,31 @@ jobs:
|
|||
working-directory: ostp-flutter
|
||||
run: |
|
||||
# 1. Compile JNI
|
||||
mkdir -p android/app/src/main/jniLibs/arm64-v8a
|
||||
mkdir -p android/app/src/main/jniLibs/armeabi-v7a
|
||||
mkdir -p android/app/src/main/jniLibs/${{ matrix.arch }}
|
||||
|
||||
cd ../ostp-jni
|
||||
cargo ndk -t arm64-v8a -t armeabi-v7a -o "../ostp-flutter/android/app/src/main/jniLibs" build --release
|
||||
cargo ndk -t ${{ matrix.arch }} -o "../ostp-flutter/android/app/src/main/jniLibs" build --release
|
||||
cd ../ostp-flutter
|
||||
|
||||
# 2. Download tun2socks
|
||||
if [ ! -f "android/app/src/main/jniLibs/arm64-v8a/libtun2socks.so" ]; then
|
||||
curl -fsSL "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-linux-arm64.zip" -o "t2s64.zip"
|
||||
unzip -o t2s64.zip -d t2s64_tmp
|
||||
cp t2s64_tmp/tun2socks-linux-arm64 android/app/src/main/jniLibs/arm64-v8a/libtun2socks.so
|
||||
rm -rf t2s64.zip t2s64_tmp
|
||||
fi
|
||||
|
||||
if [ ! -f "android/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so" ]; then
|
||||
curl -fsSL "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-linux-armv7.zip" -o "t2s32.zip"
|
||||
unzip -o t2s32.zip -d t2s32_tmp
|
||||
cp t2s32_tmp/tun2socks-linux-armv7 android/app/src/main/jniLibs/armeabi-v7a/libtun2socks.so
|
||||
rm -rf t2s32.zip t2s32_tmp
|
||||
if [ ! -f "android/app/src/main/jniLibs/${{ matrix.arch }}/libtun2socks.so" ]; then
|
||||
curl -fsSL "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-${{ matrix.tun2socks_arch }}.zip" -o "t2s.zip"
|
||||
unzip -o t2s.zip -d t2s_tmp
|
||||
cp t2s_tmp/tun2socks-${{ matrix.tun2socks_arch }} android/app/src/main/jniLibs/${{ matrix.arch }}/libtun2socks.so
|
||||
rm -rf t2s.zip t2s_tmp
|
||||
fi
|
||||
|
||||
# 3. Build Flutter APK
|
||||
flutter build apk --release --split-per-abi
|
||||
flutter build apk --release --target-platform ${{ matrix.flutter_target }}
|
||||
|
||||
# 4. Copy to output
|
||||
cp build/app/outputs/flutter-apk/app-armeabi-v7a-release.apk ostp-android-armv7.apk
|
||||
cp build/app/outputs/flutter-apk/app-arm64-v8a-release.apk ostp-android-arm64.apk
|
||||
cp build/app/outputs/flutter-apk/app-release.apk ostp-android-${{ matrix.arch }}.apk
|
||||
|
||||
- name: Upload to GitHub Release
|
||||
if: ${{ startsWith(github.ref, 'refs/tags/') }}
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: |
|
||||
ostp-flutter/ostp-android-armv7.apk
|
||||
ostp-flutter/ostp-android-arm64.apk
|
||||
files: ostp-flutter/ostp-android-${{ matrix.arch }}.apk
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
/target/
|
||||
/target_build/
|
||||
/target_linux/
|
||||
/ostp-gui/
|
||||
/dist/
|
||||
**/*.rs.bk
|
||||
.idea/
|
||||
|
|
@ -32,4 +31,4 @@ wintun.dll
|
|||
# Dev notes (not for repo)
|
||||
.ai-rules.md
|
||||
turn-harvesting-idea.md
|
||||
ostp-flutter/
|
||||
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -139,8 +139,9 @@ Download pre-built binaries for your platform from [GitHub Releases](https://git
|
|||
|
||||
### 4. Connect via share link (one-liner)
|
||||
```bash
|
||||
./ostp ostp://ACCESS_KEY@server.com:50000
|
||||
./ostp "ostp://ACCESS_KEY@server.com:50000?..."
|
||||
```
|
||||
> **Note**: Always wrap the `ostp://...` link in quotes (`"`) so your terminal doesn't misinterpret special characters like `&` or `?`.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -230,3 +231,10 @@ cargo test -p ostp-core -p ostp-server
|
|||
|
||||
Business Source License 1.1. Free for personal and non-commercial use.
|
||||
Converts to MIT License on May 14, 2030.
|
||||
|
||||
---
|
||||
|
||||
## Contact
|
||||
|
||||
- **Telegram**: [@ospab0](https://t.me/ospab0)
|
||||
- **Email**: gvoprgrg@gmail.com
|
||||
|
|
|
|||
|
|
@ -0,0 +1,45 @@
|
|||
# Miscellaneous
|
||||
*.class
|
||||
*.log
|
||||
*.pyc
|
||||
*.swp
|
||||
.DS_Store
|
||||
.atom/
|
||||
.build/
|
||||
.buildlog/
|
||||
.history
|
||||
.svn/
|
||||
.swiftpm/
|
||||
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-dependencies
|
||||
.pub-cache/
|
||||
.pub/
|
||||
/build/
|
||||
/coverage/
|
||||
|
||||
# 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
|
||||
|
|
@ -0,0 +1,45 @@
|
|||
# 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: "db50e20168db8fee486b9abf32fc912de3bc5b6a"
|
||||
channel: "stable"
|
||||
|
||||
project_type: app
|
||||
|
||||
# Tracks metadata for the flutter migrate command
|
||||
migration:
|
||||
platforms:
|
||||
- platform: root
|
||||
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
- platform: android
|
||||
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
- platform: ios
|
||||
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
- platform: linux
|
||||
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
- platform: macos
|
||||
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
- platform: web
|
||||
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
- platform: windows
|
||||
create_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
base_revision: db50e20168db8fee486b9abf32fc912de3bc5b6a
|
||||
|
||||
# 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'
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
# ostp_client
|
||||
|
||||
A new Flutter project.
|
||||
|
||||
## Getting Started
|
||||
|
||||
This project is a starting point for a Flutter application.
|
||||
|
||||
A few resources to get you started if this is your first Flutter project:
|
||||
|
||||
- [Learn Flutter](https://docs.flutter.dev/get-started/learn-flutter)
|
||||
- [Write your first Flutter app](https://docs.flutter.dev/get-started/codelab)
|
||||
- [Flutter learning resources](https://docs.flutter.dev/reference/learning-resources)
|
||||
|
||||
For help getting started with Flutter development, view the
|
||||
[online documentation](https://docs.flutter.dev/), which offers tutorials,
|
||||
samples, guidance on mobile development, and a full API reference.
|
||||
|
|
@ -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
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
gradle-wrapper.jar
|
||||
/.gradle
|
||||
/captures/
|
||||
/gradlew
|
||||
/gradlew.bat
|
||||
/local.properties
|
||||
GeneratedPluginRegistrant.java
|
||||
.cxx/
|
||||
|
||||
# Remember to never publicly share your keystore.
|
||||
# See https://flutter.dev/to/reference-keystore
|
||||
key.properties
|
||||
**/*.keystore
|
||||
**/*.jks
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
plugins {
|
||||
id("com.android.application")
|
||||
id("kotlin-android")
|
||||
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
|
||||
id("dev.flutter.flutter-gradle-plugin")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "com.ospab.ostp_client"
|
||||
compileSdk = flutter.compileSdkVersion
|
||||
ndkVersion = flutter.ndkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_17
|
||||
targetCompatibility = JavaVersion.VERSION_17
|
||||
}
|
||||
|
||||
kotlinOptions {
|
||||
jvmTarget = JavaVersion.VERSION_17.toString()
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||
applicationId = "com.ospab.ostp_client"
|
||||
// You can update the following values to match your application needs.
|
||||
// For more information, see: https://flutter.dev/to/review-gradle-config.
|
||||
minSdk = maxOf(flutter.minSdkVersion, 24)
|
||||
targetSdk = flutter.targetSdkVersion
|
||||
versionCode = flutter.versionCode
|
||||
versionName = flutter.versionName
|
||||
}
|
||||
|
||||
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.getByName("debug")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
flutter {
|
||||
source = "../.."
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation("androidx.core:core-ktx:1.13.1")
|
||||
}
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE"/>
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK"/>
|
||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||
<application
|
||||
android:label="ostp_client"
|
||||
android:name="${applicationName}"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:extractNativeLibs="true">
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:exported="true"
|
||||
android:launchMode="singleTop"
|
||||
android:taskAffinity=""
|
||||
android:theme="@style/LaunchTheme"
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||
android:hardwareAccelerated="true"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||
the Android process has started. This theme is visible to the user
|
||||
while the Flutter UI initializes. After that, this theme continues
|
||||
to determine the Window background behind the Flutter UI. -->
|
||||
<meta-data
|
||||
android:name="io.flutter.embedding.android.NormalTheme"
|
||||
android:resource="@style/NormalTheme"
|
||||
/>
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN"/>
|
||||
<category android:name="android.intent.category.LAUNCHER"/>
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<!-- Don't delete the meta-data below.
|
||||
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||
<meta-data
|
||||
android:name="flutterEmbedding"
|
||||
android:value="2" />
|
||||
|
||||
<service
|
||||
android:name=".OstpVpnService"
|
||||
android:permission="android.permission.BIND_VPN_SERVICE"
|
||||
android:exported="false">
|
||||
<intent-filter>
|
||||
<action android:name="android.net.VpnService"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
|
||||
<!-- Quick Settings Tile -->
|
||||
<service
|
||||
android:name=".OstpTileService"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="OSTP VPN"
|
||||
android:permission="android.permission.BIND_QUICK_SETTINGS_TILE"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.service.quicksettings.action.QS_TILE"/>
|
||||
</intent-filter>
|
||||
</service>
|
||||
</application>
|
||||
<!-- Required to query activities that can process text, see:
|
||||
https://developer.android.com/training/package-visibility and
|
||||
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
|
||||
|
||||
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
|
||||
<queries>
|
||||
<intent>
|
||||
<action android:name="android.intent.action.PROCESS_TEXT"/>
|
||||
<data android:mimeType="text/plain"/>
|
||||
</intent>
|
||||
</queries>
|
||||
</manifest>
|
||||
Binary file not shown.
|
|
@ -0,0 +1,138 @@
|
|||
package com.ospab.ostp_client
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.VpnService
|
||||
import androidx.annotation.NonNull
|
||||
import io.flutter.embedding.android.FlutterActivity
|
||||
import io.flutter.embedding.engine.FlutterEngine
|
||||
import io.flutter.plugin.common.MethodChannel
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.PackageManager
|
||||
import android.graphics.Bitmap
|
||||
import android.graphics.Canvas
|
||||
import android.util.Base64
|
||||
import java.io.ByteArrayOutputStream
|
||||
|
||||
class MainActivity : FlutterActivity() {
|
||||
private val CHANNEL = "com.ospab.ostp/vpn"
|
||||
private val VPN_REQUEST_CODE = 0x0F
|
||||
private var pendingConfigJson: String? = null
|
||||
|
||||
private fun getAppIconBase64(pm: PackageManager, appInfo: ApplicationInfo): String? {
|
||||
try {
|
||||
val drawable = pm.getApplicationIcon(appInfo)
|
||||
val width = 96
|
||||
val height = 96
|
||||
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
|
||||
val canvas = Canvas(bitmap)
|
||||
drawable.setBounds(0, 0, width, height)
|
||||
drawable.draw(canvas)
|
||||
|
||||
val outputStream = ByteArrayOutputStream()
|
||||
bitmap.compress(Bitmap.CompressFormat.PNG, 90, outputStream)
|
||||
val byteArray = outputStream.toByteArray()
|
||||
return Base64.encodeToString(byteArray, Base64.NO_WRAP)
|
||||
} catch (e: Throwable) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||
super.configureFlutterEngine(flutterEngine)
|
||||
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
|
||||
when (call.method) {
|
||||
"saveConfig" -> {
|
||||
val configJson = call.argument<String>("configJson")
|
||||
val prefs = getSharedPreferences("OstpPrefs", android.content.Context.MODE_PRIVATE)
|
||||
prefs.edit().putString("latest_config_json", configJson).apply()
|
||||
result.success(true)
|
||||
}
|
||||
"startTunnel" -> {
|
||||
pendingConfigJson = call.argument<String>("configJson")
|
||||
val intent = VpnService.prepare(this)
|
||||
if (intent != null) {
|
||||
startActivityForResult(intent, VPN_REQUEST_CODE)
|
||||
result.success(true)
|
||||
} else {
|
||||
startVpnService()
|
||||
result.success(true)
|
||||
}
|
||||
}
|
||||
"stopTunnel" -> {
|
||||
try {
|
||||
val intent = Intent(this, OstpVpnService::class.java)
|
||||
intent.action = "STOP"
|
||||
startService(intent)
|
||||
result.success(true)
|
||||
} catch (e: Throwable) {
|
||||
result.error("ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
"getLogs" -> {
|
||||
try {
|
||||
val logs = net.ostp.client.OstpClientSdk.getLogs()
|
||||
result.success(logs ?: "[]")
|
||||
} catch (e: Throwable) {
|
||||
result.error("ERROR", e.message ?: "Unknown JNI Error", null)
|
||||
}
|
||||
}
|
||||
"clearLogs" -> {
|
||||
try {
|
||||
net.ostp.client.OstpClientSdk.getLogs() // Drain
|
||||
result.success(true)
|
||||
} catch (e: Throwable) {
|
||||
result.error("ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
"isRunning" -> {
|
||||
result.success(OstpVpnService.isRunning)
|
||||
}
|
||||
"getMetrics" -> {
|
||||
try {
|
||||
val metrics = net.ostp.client.OstpClientSdk.getMetrics()
|
||||
result.success(metrics ?: "{}")
|
||||
} catch (e: Throwable) {
|
||||
result.error("ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
"getInstalledApps" -> {
|
||||
try {
|
||||
val pm = packageManager
|
||||
val apps = pm.getInstalledApplications(PackageManager.GET_META_DATA)
|
||||
val list = apps.map { app ->
|
||||
val isSystem = ((app.flags and ApplicationInfo.FLAG_SYSTEM) != 0) &&
|
||||
(pm.getLaunchIntentForPackage(app.packageName) == null)
|
||||
val iconBase64 = getAppIconBase64(pm, app)
|
||||
mapOf(
|
||||
"name" to pm.getApplicationLabel(app).toString(),
|
||||
"package" to app.packageName,
|
||||
"isSystem" to isSystem,
|
||||
"icon" to (iconBase64 ?: "")
|
||||
)
|
||||
}
|
||||
result.success(list)
|
||||
} catch (e: Exception) {
|
||||
result.error("ERROR", e.message, null)
|
||||
}
|
||||
}
|
||||
else -> result.notImplemented()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
|
||||
if (requestCode == VPN_REQUEST_CODE && resultCode == RESULT_OK) {
|
||||
startVpnService()
|
||||
}
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
}
|
||||
|
||||
private fun startVpnService() {
|
||||
val intent = Intent(this, OstpVpnService::class.java)
|
||||
intent.action = "START"
|
||||
if (pendingConfigJson != null) {
|
||||
intent.putExtra("configJson", pendingConfigJson)
|
||||
}
|
||||
startService(intent)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,93 @@
|
|||
package com.ospab.ostp_client
|
||||
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.os.Build
|
||||
import android.service.quicksettings.Tile
|
||||
import android.service.quicksettings.TileService
|
||||
import androidx.annotation.Keep
|
||||
import androidx.annotation.RequiresApi
|
||||
|
||||
@Keep
|
||||
@RequiresApi(Build.VERSION_CODES.N)
|
||||
class OstpTileService : TileService() {
|
||||
|
||||
override fun onStartListening() {
|
||||
super.onStartListening()
|
||||
updateTile()
|
||||
}
|
||||
|
||||
override fun onClick() {
|
||||
super.onClick()
|
||||
if (OstpVpnService.isRunning) {
|
||||
// Отключить VPN
|
||||
val stopIntent = Intent(this, OstpVpnService::class.java).apply { action = "STOP" }
|
||||
startService(stopIntent)
|
||||
// Обновим плитку сразу
|
||||
qsTile?.state = Tile.STATE_INACTIVE
|
||||
qsTile?.label = "OSTP VPN"
|
||||
qsTile?.updateTile()
|
||||
} else {
|
||||
// Включить VPN напрямую
|
||||
val prefs = getSharedPreferences("OstpPrefs", Context.MODE_PRIVATE)
|
||||
val configJson = prefs.getString("latest_config_json", null)
|
||||
|
||||
if (configJson != null) {
|
||||
val startIntent = Intent(this, OstpVpnService::class.java).apply {
|
||||
action = "START"
|
||||
putExtra("configJson", configJson)
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
startForegroundService(startIntent)
|
||||
} else {
|
||||
startService(startIntent)
|
||||
}
|
||||
qsTile?.state = Tile.STATE_ACTIVE
|
||||
qsTile?.label = "OSTP VPN"
|
||||
qsTile?.updateTile()
|
||||
} else {
|
||||
// Если конфигурация еще не сохранена, открыть приложение
|
||||
val appIntent = packageManager.getLaunchIntentForPackage(packageName)?.apply {
|
||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_SINGLE_TOP)
|
||||
putExtra("tile_connect", true)
|
||||
}
|
||||
if (appIntent != null) {
|
||||
startActivityAndCollapse(appIntent)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateTile() {
|
||||
val tile = qsTile ?: return
|
||||
if (OstpVpnService.isRunning) {
|
||||
tile.label = "OSTP VPN"
|
||||
tile.state = Tile.STATE_ACTIVE
|
||||
} else {
|
||||
tile.label = "OSTP VPN"
|
||||
tile.state = Tile.STATE_INACTIVE
|
||||
}
|
||||
tile.updateTile()
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Запрашивает обновление плитки быстрых настроек.
|
||||
* Вызывается из OstpVpnService при изменении состояния.
|
||||
*/
|
||||
@Keep
|
||||
fun requestListeningState(context: Context) {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
try {
|
||||
requestListeningState(
|
||||
context,
|
||||
ComponentName(context, OstpTileService::class.java)
|
||||
)
|
||||
} catch (e: Exception) {
|
||||
// Плитка может быть не добавлена в панель — это нормально
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,261 @@
|
|||
package com.ospab.ostp_client
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.pm.ServiceInfo
|
||||
import android.net.VpnService
|
||||
import android.os.Build
|
||||
import android.os.ParcelFileDescriptor
|
||||
import android.os.PowerManager
|
||||
import android.util.Log
|
||||
import net.ostp.client.OstpClientSdk
|
||||
import java.io.IOException
|
||||
import androidx.annotation.Keep
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
|
||||
@Keep
|
||||
class OstpVpnService : VpnService() {
|
||||
|
||||
@Keep
|
||||
companion object {
|
||||
@Keep
|
||||
var isRunning = false
|
||||
@Keep
|
||||
var instance: OstpVpnService? = null
|
||||
|
||||
private const val NOTIF_ID = 1001
|
||||
private const val CHANNEL_ID = "ostp_vpn_channel"
|
||||
private const val WAKE_LOCK_TAG = "ostp:vpn_wakelock"
|
||||
|
||||
/** Called from Kotlin OstpClientSdk to protect VPN sockets from the VPN itself. */
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun protectSocket(fd: Int): Boolean {
|
||||
return instance?.protect(fd) ?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Called by OstpClientSdk.notifyNetworkChanged() JNI thunk.
|
||||
*/
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun onNetworkChanged() {
|
||||
android.util.Log.d("OstpVpnService", "onNetworkChanged() signaled to Rust bridge")
|
||||
}
|
||||
}
|
||||
|
||||
private var vpnInterface: ParcelFileDescriptor? = null
|
||||
private var wakeLock: PowerManager.WakeLock? = null
|
||||
|
||||
override fun onCreate() {
|
||||
super.onCreate()
|
||||
instance = this
|
||||
createNotificationChannel()
|
||||
}
|
||||
|
||||
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||
val action = intent?.action
|
||||
if (action == "START") {
|
||||
val configJson = intent.getStringExtra("configJson") ?: return START_NOT_STICKY
|
||||
// Launch foreground immediately so Android doesn't kill us
|
||||
startForeground(NOTIF_ID, buildNotification(connecting = true))
|
||||
startVpn(configJson)
|
||||
} else if (action == "STOP") {
|
||||
stopVpn()
|
||||
}
|
||||
return START_STICKY
|
||||
}
|
||||
|
||||
private fun createNotificationChannel() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||
val channel = NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
"OSTP VPN",
|
||||
NotificationManager.IMPORTANCE_LOW
|
||||
).apply {
|
||||
description = "OSTP VPN connection status"
|
||||
setShowBadge(false)
|
||||
}
|
||||
val nm = getSystemService(NotificationManager::class.java)
|
||||
nm.createNotificationChannel(channel)
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildNotification(connecting: Boolean): Notification {
|
||||
val stopIntent = PendingIntent.getService(
|
||||
this,
|
||||
0,
|
||||
Intent(this, OstpVpnService::class.java).apply { action = "STOP" },
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val openIntent = PendingIntent.getActivity(
|
||||
this,
|
||||
1,
|
||||
packageManager.getLaunchIntentForPackage(packageName)
|
||||
?.apply { addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP) },
|
||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
|
||||
)
|
||||
|
||||
val (statusText, actionLabel) = if (connecting) {
|
||||
Pair("Подключение...", "Отмена")
|
||||
} else {
|
||||
Pair("Подключено", "Отключить")
|
||||
}
|
||||
|
||||
return NotificationCompat.Builder(this, CHANNEL_ID)
|
||||
.setContentTitle("OSTP VPN")
|
||||
.setContentText(statusText)
|
||||
.setSmallIcon(android.R.drawable.ic_lock_lock)
|
||||
.setOngoing(true)
|
||||
.setShowWhen(false)
|
||||
.setContentIntent(openIntent)
|
||||
.addAction(android.R.drawable.ic_delete, actionLabel, stopIntent)
|
||||
.setPriority(NotificationCompat.PRIORITY_LOW)
|
||||
.build()
|
||||
}
|
||||
|
||||
fun updateNotification(connected: Boolean) {
|
||||
try {
|
||||
val nm = NotificationManagerCompat.from(this)
|
||||
nm.notify(NOTIF_ID, buildNotification(connecting = !connected))
|
||||
} catch (e: Throwable) {
|
||||
Log.e("OstpVpnService", "Failed to update notification", e)
|
||||
}
|
||||
// Refresh Quick Settings tile state
|
||||
OstpTileService.requestListeningState(applicationContext)
|
||||
}
|
||||
|
||||
private fun acquireWakeLock() {
|
||||
if (wakeLock == null) {
|
||||
val pm = getSystemService(POWER_SERVICE) as PowerManager
|
||||
wakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG)
|
||||
wakeLock?.acquire(24 * 60 * 60 * 1000L) // Max 24h
|
||||
Log.d("OstpVpnService", "WakeLock acquired")
|
||||
}
|
||||
}
|
||||
|
||||
private fun releaseWakeLock() {
|
||||
try {
|
||||
wakeLock?.let {
|
||||
if (it.isHeld) it.release()
|
||||
}
|
||||
wakeLock = null
|
||||
Log.d("OstpVpnService", "WakeLock released")
|
||||
} catch (e: Throwable) {
|
||||
Log.e("OstpVpnService", "Error releasing WakeLock", e)
|
||||
}
|
||||
}
|
||||
|
||||
private fun startVpn(configJson: String) {
|
||||
if (vpnInterface != null) return
|
||||
|
||||
acquireWakeLock()
|
||||
|
||||
try {
|
||||
val json = org.json.JSONObject(configJson)
|
||||
val dnsServer = json.optString("dns_server", "1.1.1.1")
|
||||
val localProxy = json.optJSONObject("local_proxy")?.optString("bind_addr", "127.0.0.1:1088") ?: "127.0.0.1:1088"
|
||||
|
||||
val builder = Builder()
|
||||
.setSession("OSTP Tunnel")
|
||||
.addAddress("10.1.0.2", 24)
|
||||
.addAddress("fd00:1:fd00:1:fd00:1:fd00:1", 128)
|
||||
.addRoute("0.0.0.0", 0)
|
||||
.addRoute("::", 0)
|
||||
.addDnsServer(dnsServer)
|
||||
.setMtu(1300)
|
||||
|
||||
try {
|
||||
builder.allowFamily(android.system.OsConstants.AF_INET)
|
||||
builder.allowFamily(android.system.OsConstants.AF_INET6)
|
||||
} catch (e: Throwable) { }
|
||||
|
||||
val appRules = json.optJSONObject("app_rules")
|
||||
val mode = appRules?.optString("mode", "bypass") ?: "bypass"
|
||||
val packages = appRules?.optJSONArray("packages")
|
||||
|
||||
if (mode == "proxy") {
|
||||
if (packages != null) {
|
||||
for (i in 0 until packages.length()) {
|
||||
val pkg = packages.getString(i)
|
||||
try {
|
||||
builder.addAllowedApplication(pkg)
|
||||
} catch (e: Throwable) {
|
||||
Log.e("OstpVpnService", "Failed to add allowed application $pkg: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
builder.addDisallowedApplication(applicationContext.packageName)
|
||||
} catch (e: Throwable) {
|
||||
Log.e("OstpVpnService", "Failed to disallow our own package: $e")
|
||||
}
|
||||
|
||||
if (packages != null) {
|
||||
for (i in 0 until packages.length()) {
|
||||
val pkg = packages.getString(i)
|
||||
try {
|
||||
builder.addDisallowedApplication(pkg)
|
||||
} catch (e: Throwable) {
|
||||
Log.e("OstpVpnService", "Failed to add disallowed application $pkg: $e")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
vpnInterface = builder.establish()
|
||||
val fd = vpnInterface?.fd ?: throw Exception("Failed to get VPN FD")
|
||||
|
||||
// CRITICAL: Clear O_CLOEXEC so the child process inherits the TUN file descriptor
|
||||
try {
|
||||
android.system.Os.fcntlInt(vpnInterface!!.fileDescriptor, android.system.OsConstants.F_SETFD, 0)
|
||||
} catch (e: Throwable) {
|
||||
Log.e("OstpVpnService", "Failed to clear O_CLOEXEC", e)
|
||||
}
|
||||
|
||||
val t2sBin = applicationInfo.nativeLibraryDir + "/libtun2socks.so"
|
||||
val success = OstpClientSdk.startClient(configJson, fd, t2sBin, localProxy)
|
||||
if (success) {
|
||||
Log.i("OstpVpnService", "OSTP Rust Core & tun2socks started successfully")
|
||||
isRunning = true
|
||||
updateNotification(connected = true)
|
||||
} else {
|
||||
Log.e("OstpVpnService", "Failed to start OSTP Rust Core & tun2socks")
|
||||
stopVpn()
|
||||
}
|
||||
|
||||
} catch (e: Throwable) {
|
||||
Log.e("OstpVpnService", "Error starting VPN", e)
|
||||
stopVpn()
|
||||
}
|
||||
}
|
||||
|
||||
private fun stopVpn() {
|
||||
isRunning = false
|
||||
releaseWakeLock()
|
||||
|
||||
try {
|
||||
OstpClientSdk.stopClient()
|
||||
vpnInterface?.close()
|
||||
vpnInterface = null
|
||||
} catch (e: IOException) {
|
||||
Log.e("OstpVpnService", "Error closing VPN interface", e)
|
||||
}
|
||||
|
||||
stopForeground(true)
|
||||
OstpTileService.requestListeningState(applicationContext)
|
||||
stopSelf()
|
||||
}
|
||||
|
||||
override fun onDestroy() {
|
||||
super.onDestroy()
|
||||
instance = null
|
||||
stopVpn()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
package net.ostp.client
|
||||
|
||||
import androidx.annotation.Keep
|
||||
|
||||
@Keep
|
||||
object OstpClientSdk {
|
||||
init {
|
||||
System.loadLibrary("ostp_jni")
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
fun protectSocket(fd: Int): Boolean {
|
||||
val service = com.ospab.ostp_client.OstpVpnService.instance
|
||||
if (service != null) {
|
||||
val res = service.protect(fd)
|
||||
android.util.Log.i("OstpClientSdk", "VpnService.protect(socketFd=$fd) -> success=$res")
|
||||
return res
|
||||
}
|
||||
android.util.Log.e("OstpClientSdk", "VpnService instance is null! Cannot protect socketFd=$fd")
|
||||
return false
|
||||
}
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
external fun startClient(configJson: String, fd: Int, t2sBinPath: String, localProxy: String): Boolean
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
external fun stopClient(): Boolean
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
external fun getMetrics(): String
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
external fun getLogs(): String
|
||||
|
||||
@Keep
|
||||
@JvmStatic
|
||||
external fun addLog(logMsg: String)
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="?android:colorBackground" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<!-- Modify this file to customize your launch splash screen -->
|
||||
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<item android:drawable="@android:color/white" />
|
||||
|
||||
<!-- You can insert your own image assets here -->
|
||||
<!-- <item>
|
||||
<bitmap
|
||||
android:gravity="center"
|
||||
android:src="@mipmap/launch_image" />
|
||||
</item> -->
|
||||
</layer-list>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 544 B |
Binary file not shown.
|
After Width: | Height: | Size: 442 B |
Binary file not shown.
|
After Width: | Height: | Size: 721 B |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.4 KiB |
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<!-- Show a splash screen on the activity. Automatically removed when
|
||||
the Flutter engine draws its first frame -->
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||
running.
|
||||
|
||||
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">?android:colorBackground</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<!-- The INTERNET permission is required for development. Specifically,
|
||||
the Flutter tool needs it to communicate with the running application
|
||||
to allow setting breakpoints, to provide hot reload, etc.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
</manifest>
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
allprojects {
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
}
|
||||
|
||||
val newBuildDir: Directory =
|
||||
rootProject.layout.buildDirectory
|
||||
.dir("../../build")
|
||||
.get()
|
||||
rootProject.layout.buildDirectory.value(newBuildDir)
|
||||
|
||||
subprojects {
|
||||
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
|
||||
project.layout.buildDirectory.value(newSubprojectBuildDir)
|
||||
}
|
||||
subprojects {
|
||||
project.evaluationDependsOn(":app")
|
||||
}
|
||||
|
||||
tasks.register<Delete>("clean") {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
org.gradle.jvmargs=-Xmx8G -XX:MaxMetaspaceSize=4G -XX:ReservedCodeCacheSize=512m -XX:+HeapDumpOnOutOfMemoryError
|
||||
android.useAndroidX=true
|
||||
|
|
@ -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-8.14-all.zip
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
pluginManagement {
|
||||
val flutterSdkPath =
|
||||
run {
|
||||
val properties = java.util.Properties()
|
||||
file("local.properties").inputStream().use { properties.load(it) }
|
||||
val flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||
require(flutterSdkPath != null) { "flutter.sdk not set in local.properties" }
|
||||
flutterSdkPath
|
||||
}
|
||||
|
||||
includeBuild("$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 "8.11.1" apply false
|
||||
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
|
||||
}
|
||||
|
||||
include(":app")
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "==============================================" -ForegroundColor Cyan
|
||||
Write-Host " OSTP Android App Release Build Pipeline " -ForegroundColor Cyan
|
||||
Write-Host "==============================================" -ForegroundColor Cyan
|
||||
|
||||
# Step 1: Run JNI build script to compile Rust core and download tun2socks
|
||||
Write-Host ""
|
||||
Write-Host "[1/3] Compiling Rust JNI Core & Downloading tun2socks..." -ForegroundColor Yellow
|
||||
$jniScript = Join-Path $PSScriptRoot "build_android_jni.ps1"
|
||||
if (Test-Path $jniScript) {
|
||||
& $jniScript
|
||||
} else {
|
||||
Write-Error "Could not find build_android_jni.ps1 at $jniScript"
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Step 2: Build Flutter APK in release mode
|
||||
Write-Host ""
|
||||
Write-Host "[2/3] Compiling Flutter Application in Release Mode..." -ForegroundColor Yellow
|
||||
Push-Location $PSScriptRoot
|
||||
try {
|
||||
& flutter build apk --release --target-platform android-arm,android-arm64
|
||||
} catch {
|
||||
Write-Host "[ERROR] Flutter build failed! Make sure Flutter SDK is installed and configured in your PATH." -ForegroundColor Red
|
||||
Pop-Location
|
||||
exit 1
|
||||
}
|
||||
Pop-Location
|
||||
|
||||
# Step 3: Copy and rename the final release APK next to this script
|
||||
Write-Host ""
|
||||
Write-Host "[3/3] Copying and packaging release APK..." -ForegroundColor Yellow
|
||||
$apkPath = Join-Path $PSScriptRoot "build\app\outputs\flutter-apk\app-release.apk"
|
||||
$destPath = Join-Path $PSScriptRoot "ostp-client-release.apk"
|
||||
|
||||
if (Test-Path $apkPath) {
|
||||
Copy-Item -Path $apkPath -Destination $destPath -Force
|
||||
Write-Host ""
|
||||
Write-Host "==============================================" -ForegroundColor Green
|
||||
Write-Host " SUCCESS! Build completed successfully! " -ForegroundColor Green
|
||||
Write-Host " Release APK copied to: " -ForegroundColor Green
|
||||
Write-Host " $destPath" -ForegroundColor White
|
||||
Write-Host "==============================================" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[ERROR] Release APK was not found at expected path: $apkPath" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
$ErrorActionPreference = "Stop"
|
||||
|
||||
Write-Host "Building OSTP JNI for Android (arm64-v8a and armeabi-v7a)..."
|
||||
|
||||
$jniLibs = "$PSScriptRoot\android\app\src\main\jniLibs"
|
||||
New-Item -ItemType Directory -Force -Path "$jniLibs\arm64-v8a" | Out-Null
|
||||
New-Item -ItemType Directory -Force -Path "$jniLibs\armeabi-v7a" | Out-Null
|
||||
|
||||
Push-Location "$PSScriptRoot\..\ostp-jni"
|
||||
|
||||
Write-Host "Compiling for aarch64-linux-android and armv7-linux-androideabi..."
|
||||
cargo ndk -t arm64-v8a -t armeabi-v7a -o "$jniLibs" build --release
|
||||
|
||||
$tun2socksArm64 = "$jniLibs\arm64-v8a\libtun2socks.so"
|
||||
$tun2socksArmv7 = "$jniLibs\armeabi-v7a\libtun2socks.so"
|
||||
|
||||
if (-not (Test-Path $tun2socksArm64)) {
|
||||
Write-Host "Downloading tun2socks for arm64-v8a..."
|
||||
Invoke-WebRequest -Uri "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-linux-arm64.zip" -OutFile "$jniLibs\t2s64.zip"
|
||||
Expand-Archive "$jniLibs\t2s64.zip" "$jniLibs\t2s64_tmp" -Force
|
||||
Copy-Item "$jniLibs\t2s64_tmp\tun2socks-linux-arm64" $tun2socksArm64 -Force
|
||||
Remove-Item "$jniLibs\t2s64.zip", "$jniLibs\t2s64_tmp" -Recurse -Force
|
||||
}
|
||||
|
||||
if (-not (Test-Path $tun2socksArmv7)) {
|
||||
Write-Host "Downloading tun2socks for armeabi-v7a..."
|
||||
Invoke-WebRequest -Uri "https://github.com/xjasonlyu/tun2socks/releases/download/v2.6.0/tun2socks-linux-armv7.zip" -OutFile "$jniLibs\t2s32.zip"
|
||||
Expand-Archive "$jniLibs\t2s32.zip" "$jniLibs\t2s32_tmp" -Force
|
||||
Copy-Item "$jniLibs\t2s32_tmp\tun2socks-linux-armv7" $tun2socksArmv7 -Force
|
||||
Remove-Item "$jniLibs\t2s32.zip", "$jniLibs\t2s32_tmp" -Recurse -Force
|
||||
}
|
||||
|
||||
Pop-Location
|
||||
|
||||
Write-Host "Done! The .so files have been copied to $jniLibs"
|
||||
File diff suppressed because it is too large
Load Diff
Binary file not shown.
|
|
@ -0,0 +1,354 @@
|
|||
# Generated by pub
|
||||
# See https://dart.dev/tools/pub/glossary#lockfile
|
||||
packages:
|
||||
async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: async
|
||||
sha256: e2eb0491ba5ddb6177742d2da23904574082139b07c1e33b8503b9f46f3e1a37
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.13.1"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: boolean_selector
|
||||
sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
characters:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: characters
|
||||
sha256: faf38497bda5ead2a8c7615f4f7939df04333478bf32e4173fcb06d428b5716b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
clock:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: clock
|
||||
sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
collection:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: collection
|
||||
sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
cupertino_icons:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: cupertino_icons
|
||||
sha256: "41e005c33bd814be4d3096aff55b1908d419fde52ca656c8c47719ec745873cd"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.9"
|
||||
fake_async:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: fake_async
|
||||
sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.3"
|
||||
ffi:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: ffi
|
||||
sha256: "6d7fd89431262d8f3125e81b50d3847a091d846eafcd4fdb88dd06f36d705a45"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
file:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: file
|
||||
sha256: a3b4f84adafef897088c160faf7dfffb7696046cb13ae90b508c2cbc95d3b8d4
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "7.0.1"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_lints:
|
||||
dependency: "direct dev"
|
||||
description:
|
||||
name: flutter_lints
|
||||
sha256: "3105dc8492f6183fb076ccf1f351ac3d60564bff92e20bfc4af9cc1651f4e7e1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
flutter_web_plugins:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
leak_tracker:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker
|
||||
sha256: "33e2e26bdd85a0112ec15400c8cbffea70d0f9c3407491f672a2fad47915e2de"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "11.0.2"
|
||||
leak_tracker_flutter_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_flutter_testing
|
||||
sha256: "1dbc140bb5a23c75ea9c4811222756104fbcd1a27173f0c34ca01e16bea473c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.10"
|
||||
leak_tracker_testing:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: leak_tracker_testing
|
||||
sha256: "8d5a2d49f4a66b49744b23b018848400d23e54caf9463f4eb20df3eb8acb2eb1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.0.2"
|
||||
lints:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: lints
|
||||
sha256: "12f842a479589fea194fe5c5a3095abc7be0c1f2ddfa9a0e76aed1dbd26a87df"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.0"
|
||||
matcher:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: matcher
|
||||
sha256: dc0b7dc7651697ea4ff3e69ef44b0407ea32c487a39fff6a4004fa585e901861
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.12.19"
|
||||
material_color_utilities:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: material_color_utilities
|
||||
sha256: "9c337007e82b1889149c82ed242ed1cb24a66044e30979c44912381e9be4c48b"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.13.0"
|
||||
meta:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: meta
|
||||
sha256: "23f08335362185a5ea2ad3a4e597f1375e78bce8a040df5c600c8d3552ef2394"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.17.0"
|
||||
mobile_scanner:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: mobile_scanner
|
||||
sha256: d234581c090526676fd8fab4ada92f35c6746e3fb4f05a399665d75a399fb760
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "5.2.3"
|
||||
path:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path
|
||||
sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.9.1"
|
||||
path_provider_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_linux
|
||||
sha256: f7a1fe3a634fe7734c8d3f2766ad746ae2a2884abe22e241a8b301bf5cac3279
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.1"
|
||||
path_provider_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_platform_interface
|
||||
sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.2"
|
||||
path_provider_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: path_provider_windows
|
||||
sha256: bd6f00dbd873bfb70d0761682da2b3a2c2fccc2b9e84c495821639601d81afe7
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.0"
|
||||
platform:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: platform
|
||||
sha256: "5d6b1b0036a5f331ebc77c850ebc8506cbc1e9416c27e59b439f917a902a4984"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.6"
|
||||
plugin_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: plugin_platform_interface
|
||||
sha256: "4820fbfdb9478b1ebae27888254d445073732dae3d6ea81f0b7e06d5dedc3f02"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.8"
|
||||
shared_preferences:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: shared_preferences
|
||||
sha256: c3025c5534b01739267eb7d76959bbc25a6d10f6988e1c2a3036940133dd10bf
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.5"
|
||||
shared_preferences_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: e8d4762b1e2e8578fc4d0fd548cebf24afd24f49719c08974df92834565e2c53
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.23"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_foundation
|
||||
sha256: "4e7eaffc2b17ba398759f1151415869a34771ba11ebbccd1b0145472a619a64f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.5.6"
|
||||
shared_preferences_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_linux
|
||||
sha256: "580abfd40f415611503cae30adf626e6656dfb2f0cee8f465ece7b6defb40f2f"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
shared_preferences_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_platform_interface
|
||||
sha256: "649dc798a33931919ea356c4305c2d1f81619ea6e92244070b520187b5140ef9"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.2"
|
||||
shared_preferences_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_web
|
||||
sha256: c49bd060261c9a3f0ff445892695d6212ff603ef3115edbb448509d407600019
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.3"
|
||||
shared_preferences_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_windows
|
||||
sha256: "94ef0f72b2d71bc3e700e025db3710911bd51a71cefb65cc609dd0d9a982e3c1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.4.1"
|
||||
sky_engine:
|
||||
dependency: transitive
|
||||
description: flutter
|
||||
source: sdk
|
||||
version: "0.0.0"
|
||||
source_span:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: source_span
|
||||
sha256: "56a02f1f4cd1a2d96303c0144c93bd6d909eea6bee6bf5a0e0b685edbd4c47ab"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.10.2"
|
||||
stack_trace:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stack_trace
|
||||
sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.12.1"
|
||||
stream_channel:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: stream_channel
|
||||
sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.1.4"
|
||||
string_scanner:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: string_scanner
|
||||
sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.4.1"
|
||||
term_glyph:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: term_glyph
|
||||
sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.2"
|
||||
test_api:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: test_api
|
||||
sha256: "8161c84903fd860b26bfdefb7963b3f0b68fee7adea0f59ef805ecca346f0c7a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.7.10"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vector_math
|
||||
sha256: d530bd74fea330e6e364cda7a85019c434070188383e1cd8d9777ee586914c5b
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
vm_service:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: "0016aef94fc66495ac78af5859181e3f3bf2026bd8eecc72b9565601e19ab360"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "15.2.0"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: web
|
||||
sha256: "868d88a33d8a87b18ffc05f9f030ba328ffefba92d6c127917a2ba740f9cfe4a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.1"
|
||||
xdg_directories:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: xdg_directories
|
||||
sha256: "7a3f37b05d989967cdddcbb571f1ea834867ae2faa29725fd085180e0883aa15"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.0"
|
||||
sdks:
|
||||
dart: ">=3.11.4 <4.0.0"
|
||||
flutter: ">=3.35.0"
|
||||
|
|
@ -0,0 +1,91 @@
|
|||
name: ostp_client
|
||||
description: "A new Flutter project."
|
||||
# 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.11.4
|
||||
|
||||
# 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.8
|
||||
shared_preferences: ^2.5.5
|
||||
mobile_scanner: ^5.0.0
|
||||
|
||||
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: ^6.0.0
|
||||
|
||||
# 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/to/resolution-aware-images
|
||||
|
||||
# For details regarding adding assets from package dependencies, see
|
||||
# https://flutter.dev/to/asset-from-package
|
||||
|
||||
# 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/to/font-from-package
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
// This is a basic Flutter widget test.
|
||||
//
|
||||
// To perform an interaction with a widget in your test, use the WidgetTester
|
||||
// utility in the flutter_test package. For example, you can send tap and scroll
|
||||
// gestures. You can also use WidgetTester to find child widgets in the widget
|
||||
// tree, read text, and verify that the values of widget properties are correct.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'package:ostp_client/main.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Counter increments smoke test', (WidgetTester tester) async {
|
||||
// Build our app and trigger a frame.
|
||||
await tester.pumpWidget(const MyApp());
|
||||
|
||||
// Verify that our counter starts at 0.
|
||||
expect(find.text('0'), findsOneWidget);
|
||||
expect(find.text('1'), findsNothing);
|
||||
|
||||
// Tap the '+' icon and trigger a frame.
|
||||
await tester.tap(find.byIcon(Icons.add));
|
||||
await tester.pump();
|
||||
|
||||
// Verify that our counter has incremented.
|
||||
expect(find.text('0'), findsNothing);
|
||||
expect(find.text('1'), findsOneWidget);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
|
||||
const workspaceRoot = path.resolve(__dirname, '..');
|
||||
const distDir = path.join(__dirname, 'dist');
|
||||
|
||||
if (!fs.existsSync(distDir)) {
|
||||
fs.mkdirSync(distDir, { recursive: true });
|
||||
}
|
||||
|
||||
const filesToCopy = [
|
||||
{
|
||||
src: path.join(__dirname, 'src-tauri', 'target', 'release', 'ostp-gui.exe'),
|
||||
dest: path.join(distDir, 'ostp-gui.exe')
|
||||
},
|
||||
{
|
||||
src: path.join(workspaceRoot, 'target', 'release', 'ostp-tun-helper.exe'),
|
||||
dest: path.join(distDir, 'ostp-tun-helper.exe')
|
||||
},
|
||||
{
|
||||
src: path.join(workspaceRoot, 't2s_tmp', 'tun2socks-windows-amd64.exe'),
|
||||
dest: path.join(distDir, 'tun2socks.exe')
|
||||
},
|
||||
{
|
||||
src: path.join(workspaceRoot, 'target', 'release', 'wintun.dll'),
|
||||
dest: path.join(distDir, 'wintun.dll')
|
||||
}
|
||||
];
|
||||
|
||||
let success = true;
|
||||
|
||||
for (const file of filesToCopy) {
|
||||
if (fs.existsSync(file.src)) {
|
||||
fs.copyFileSync(file.src, file.dest);
|
||||
console.log(`Copied ${path.basename(file.src)} -> dist/${path.basename(file.dest)}`);
|
||||
} else {
|
||||
console.error(`Error: Missing file ${file.src}`);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
console.log('\nSuccess! All files have been gathered in the ostp-gui/dist/ directory.');
|
||||
} else {
|
||||
console.error('\nSome files were missing. Make sure you have run the build first!');
|
||||
process.exit(1);
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue