diff --git a/.github/workflows/pre-merge-checks.yml b/.github/workflows/pre-merge-checks.yml new file mode 100644 index 0000000..7f8d93e --- /dev/null +++ b/.github/workflows/pre-merge-checks.yml @@ -0,0 +1,62 @@ +name: Pre-merge checks + +on: + pull_request: + branches: + - main + - master + push: + branches: + - main + - master + +jobs: + test-publish: + runs-on: ubuntu-latest + + steps: + # Checkout repository + - name: Checkout code + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + # Set up Flutter + - name: Set up Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.38.5' # Specify exact version instead of just 'stable' + channel: 'stable' + + # Cache Flutter dependencies to speed up workflow + - name: Cache Flutter dependencies + uses: actions/cache@v4 + with: + path: ~/.pub-cache + key: ${{ runner.os }}-pub-${{ hashFiles('**/pubspec.yaml') }} + restore-keys: | + ${{ runner.os }}-pub- + + # Install dependencies + - name: Install dependencies + run: flutter pub get + + # Run tests + - name: Run tests + run: flutter test + + # Static code analysis + - name: Analyze code + run: flutter analyze --no-fatal-warnings + + # Verify package format is correct + - name: Format check + run: dart format --set-exit-if-changed . + + # Verify package + - name: Verify package + run: flutter pub publish --dry-run + + # Check if package can be published + - name: Check publication readiness + run: flutter pub publish --dry-run \ No newline at end of file diff --git a/.github/workflows/publish-to-pubdev.yml b/.github/workflows/publish-to-pubdev.yml new file mode 100644 index 0000000..addaf66 --- /dev/null +++ b/.github/workflows/publish-to-pubdev.yml @@ -0,0 +1,32 @@ +# .github/workflows/publish.yml +name: Publish to pub.dev + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' # tag pattern on pub.dev: 'v{{version}' + +# Publish using custom workflow +jobs: + publish: + permissions: + id-token: write # Required for authentication using OIDC + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Install Flutter + uses: subosito/flutter-action@v2 + with: + flutter-version: '3.38.5' + channel: 'stable' + - name: Install dependencies 📦 + run: flutter pub get + - name: Analyze 🔍 + run: flutter analyze + - name: Check Publish Warnings 🙏🏽 + run: dart pub publish --dry-run + - name: Clean example project 🧹 + run: cd example && flutter clean . + - uses: dart-lang/setup-dart@v1 + - name: Publish + run: flutter pub publish --force \ No newline at end of file diff --git a/.gitignore b/.gitignore index 0f3e480..c8bc461 100644 --- a/.gitignore +++ b/.gitignore @@ -6,5 +6,12 @@ build/ +coverage/ + +# IntelliJ related .idea/ -.vscode/ \ No newline at end of file +!.idea/codeStyleConfig.xml + +# Visual Studio Code related +.vscode/ +!/.vscode/settings.json diff --git a/.pubignore b/.pubignore new file mode 100644 index 0000000..a664257 --- /dev/null +++ b/.pubignore @@ -0,0 +1,23 @@ +.dart_tool/ +.idea/ +.vscode/ +build/ +.packages +pubspec.lock +.DS_Store +.pub/ +.git/ +.github/ +.gitignore +.travis/ +.travis.yml +test/ +android/.idea +example/build/ +example/.dart_tool/ +example/.packages +example/pubspec.lock + +# Don't exclude these files even though they are in .gitignore +!.idea/codeStyleConfig.xml +!.vscode/settings.json \ No newline at end of file diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 63374b3..0000000 --- a/.travis.yml +++ /dev/null @@ -1,38 +0,0 @@ -language: dart -dart: - - stable -os: - - linux -# sudo: false #deprecated -addons: - apt: - sources: - - ubuntu-toolchain-r-test # you need this source to get the right version of libstdc++6 - packages: - - libstdc++6 - - fonts-noto -install: - - echo 'Avoid default Travis CI install step' # this is to avoid an error with pub in Travis -before_script: - - cd .. - - git clone https://github.com/flutter/flutter.git - - export PATH=`pwd`/flutter/bin:`pwd`/flutter/bin/cache/dart-sdk/bin:$PATH - - flutter doctor -script: - - cd $TRAVIS_BUILD_DIR - - flutter packages get - - flutter analyze --no-pub --no-current-package lib -# - flutter test - - flutter packages pub publish --dry-run -before_deploy: - - chmod +x ./.travis/publish.sh # giving execution permissions to this file to avoid error 127. - - mv ./.travis ../ # moving this out the publication folder as we don't want to publish it. -deploy: - provider: script -# skip_cleanup: true #deprecated - script: '../.travis/publish.sh' - on: - tags: true -cache: - directories: - - $HOME/.pub-cache \ No newline at end of file diff --git a/.travis/publish.sh b/.travis/publish.sh deleted file mode 100644 index 2450446..0000000 --- a/.travis/publish.sh +++ /dev/null @@ -1,15 +0,0 @@ -#!/usr/bin/env bash -mkdir -p .pub-cache - -cat < ~/.pub-cache/credentials.json -{ - "accessToken":"$accessToken", - "refreshToken":"$refreshToken", - "tokenEndpoint":"$tokenEndpoint", - "scopes":["https://www.googleapis.com/auth/plus.me","https://www.googleapis.com/auth/userinfo.email"], - "expiration":$expiration -} -EOF - -echo "Let's publish this!" -pub publish -f \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9809846..ea637f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,51 @@ +## 0.3.1 +* Update changelog + +## 0.3.0 +* Support of latest Android and iOS +* Improvements on example app +* Updates from RandomModderJDK + +## 0.2.3 + +* Minor changes + +## 0.2.2 + +* Minor readme update + +## 0.2.1 + +* Minor readme update + +## 0.2.0 + +* Updated SDK constraints to support newer Dart and Flutter versions. +* Improved documentation with better examples and clearer API descriptions. +* Added comprehensive test suite for better code reliability. +* Fixed several minor bugs and edge cases. +* Optimized performance for proxy settings retrieval. + ## 0.1.14 -* fix MissingPluginException on Android。 +* fix MissingPluginException on Android. ## 0.1.13 -* dartfrmt fix。 +* dartfrmt fix. ## 0.1.12 -* bug fix。 +* bug fix. ## 0.1.11 -* added example and enhanced null-safety on proxy set。 +* added example and enhanced null-safety on proxy set. ## 0.1.10 -* enhance readme。 +* enhance readme. ## 0.1.9 -* add example。 +* add example. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..08449f8 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,25 @@ +# Contribution Guide + +Feel free to contribute to this project. If you want to contribute, please follow the steps below: + +1. Fork the project +2. Commit your changes +3. Create a pull request + +Please make sure that your code is well tested. + +## Running Tests 🧪 + +Install lcov: + +```sh +brew install lcov +``` + +Run and open the report using the following command: + +```sh +flutter test --coverage --test-randomize-ordering-seed random && genhtml coverage/lcov.info -o coverage/ && open coverage/index.html +``` + +Everything should be green! 🎉 diff --git a/README.md b/README.md index df01915..23e5384 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,14 @@ +[![Pub Version](https://img.shields.io/pub/v/native_flutter_proxy)](https://pub.dev/packages/native_flutter_proxy) + # native_flutter_proxy -A flutter plugin to read network proxy info from native. It can be used to set up the network proxy for flutter. -The plugin provides classes to provide the HttpOverrides.global property with a proxy setting. -This ensures that the gap of flutter in supporting proxy communication is filled by a convenient solution. +A Flutter plugin to read system proxy settings from native code and apply them in Dart. +Use it to configure `HttpOverrides.global` with either the system proxy or a custom proxy. + +Key features: +- Read system proxy settings on Android and iOS. +- Auto-update when the system proxy changes via `NativeProxyReader.setProxyChangedCallback`. +- Apply a custom proxy using `CustomProxy` / `CustomProxyHttpOverride`. ## Installing @@ -10,53 +16,57 @@ You should add the following to your `pubspec.yaml` file: ```yaml dependencies: - native_flutter_proxy: ^0.1.14 + native_flutter_proxy: ^0.3.0 ``` +## Quick Start -## Example +```dart +import 'dart:io'; -- Step 1: make your main()-method async -- Step 2: add WidgetsFlutterBinding.ensureInitialized(); to your async-main()-method -- Step 3: read the proxy settings from the wifi profile natively -- Step 4: if enabled, override the proxy settings with the CustomProxy. +import 'package:flutter/widgets.dart'; +import 'package:native_flutter_proxy/native_flutter_proxy.dart'; + +Future applyProxy(ProxySetting settings) async { + if (!settings.enabled || settings.host == null) { + HttpOverrides.global = null; + return; + } + + CustomProxy(ipAddress: settings.host!, port: settings.port).enable(); +} -```dart void main() async { WidgetsFlutterBinding.ensureInitialized(); - bool enabled = false; - String? host; - int? port; try { - ProxySetting settings = await NativeProxyReader.proxySetting; - enabled = settings.enabled; - host = settings.host; - port = settings.port; + final settings = await NativeProxyReader.proxySetting; + await applyProxy(settings); } catch (e) { print(e); } - if (enabled && host != null) { - final proxy = CustomProxy(ipAddress: host, port: port); - proxy.enable(); - print("proxy enabled"); - } + + NativeProxyReader.setProxyChangedCallback((settings) async { + await applyProxy(settings); + }); runApp(MyApp()); } ``` -## Getting Started +## Auto-update on proxy changes -This project is a starting point for a Flutter -[plug-in package](https://flutter.dev/developing-packages/), -a specialized package that includes platform-specific implementation code for -Android and/or iOS. +`NativeProxyReader.setProxyChangedCallback` is invoked whenever the system proxy +changes, so your app can react immediately (including PAC-based proxies on +Android). Register it once at startup and update your overrides there. -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, -samples, guidance on mobile development, and a full API reference. +## Manual proxy (optional) -##Credits +If you want to force a proxy (e.g. for debugging), you can set it directly: + +```dart +final proxy = CustomProxy(ipAddress: '127.0.0.1', port: 8888); +proxy.enable(); +``` -This project was forked from tzh2017 [pub.dev](https://pub.dev/packages/flutter_proxy) and enhance with some custom proxy classes which are making the assignment of proxies more convenient. \ No newline at end of file +For a full example, see `example/lib/main.dart`. diff --git a/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..55e5184 --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,12 @@ +include: package:very_good_analysis/analysis_options.yaml + +formatter: + page_width: 100 + +linter: + rules: + lines_longer_than_80_chars: false + prefer_const_constructors: true + prefer_const_declarations: true + prefer_const_literals_to_create_immutables: true + avoid_catches_without_on_clauses: false diff --git a/android/.gitignore b/android/.gitignore index c6cbe56..880270b 100644 --- a/android/.gitignore +++ b/android/.gitignore @@ -6,3 +6,4 @@ .DS_Store /build /captures +/.cxx/* diff --git a/android/build.gradle b/android/build.gradle deleted file mode 100644 index ba1187e..0000000 --- a/android/build.gradle +++ /dev/null @@ -1,44 +0,0 @@ -group 'com.victorblaess.native_flutter_proxy' -version '1.0-SNAPSHOT' - -buildscript { - ext.kotlin_version = '1.6.10' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:3.5.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - -rootProject.allprojects { - repositories { - google() - jcenter() - } -} - -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' - -android { - compileSdkVersion 28 - - sourceSets { - main.java.srcDirs += 'src/main/kotlin' - } - defaultConfig { - minSdkVersion 16 - testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - } - lintOptions { - disable 'InvalidPackage' - } -} - -dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/android/build.gradle.kts b/android/build.gradle.kts new file mode 100644 index 0000000..6b002da --- /dev/null +++ b/android/build.gradle.kts @@ -0,0 +1,43 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +plugins { + id("com.android.library") + id("kotlin-android") +} + +allprojects { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } +} + +group = "com.victorblaess.native_flutter_proxy" + +android { + namespace = "com.victorblaess.native_flutter_proxy" + ndkVersion = "27.3.13750724" + compileSdk = 36 + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + sourceSets["main"].java.srcDirs("src/main/kotlin") + + defaultConfig { + minSdk = 23 + } + + lint { + disable.add("InvalidPackage") + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } +} diff --git a/android/gradle.properties b/android/gradle.properties index 38c8d45..94adc3a 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,3 @@ org.gradle.jvmargs=-Xmx1536M -android.enableR8=true android.useAndroidX=true android.enableJetifier=true diff --git a/android/gradle/wrapper/gradle-wrapper.jar b/android/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..d64cd49 Binary files /dev/null and b/android/gradle/wrapper/gradle-wrapper.jar differ diff --git a/android/gradle/wrapper/gradle-wrapper.properties b/android/gradle/wrapper/gradle-wrapper.properties index 01a286e..a807037 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,8 @@ +#Thu Mar 27 10:21:41 CET 2025 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-all.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.2-all.zip diff --git a/android/gradlew b/android/gradlew new file mode 100755 index 0000000..1aa94a4 --- /dev/null +++ b/android/gradlew @@ -0,0 +1,249 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/android/gradlew.bat b/android/gradlew.bat new file mode 100644 index 0000000..6689b85 --- /dev/null +++ b/android/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/android/settings.gradle b/android/settings.gradle deleted file mode 100644 index 2922dd1..0000000 --- a/android/settings.gradle +++ /dev/null @@ -1 +0,0 @@ -rootProject.name = 'native_flutter_proxy' diff --git a/android/settings.gradle.kts b/android/settings.gradle.kts new file mode 100644 index 0000000..b059a33 --- /dev/null +++ b/android/settings.gradle.kts @@ -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.library") version "8.13.2" apply false + id("org.jetbrains.kotlin.android") version "2.3.0" apply false +} + +rootProject.name = "native_flutter_proxy" diff --git a/android/src/main/kotlin/com/victorblaess/native_flutter_proxy/FlutterProxyPlugin.kt b/android/src/main/kotlin/com/victorblaess/native_flutter_proxy/FlutterProxyPlugin.kt index e7d8e26..03d550d 100644 --- a/android/src/main/kotlin/com/victorblaess/native_flutter_proxy/FlutterProxyPlugin.kt +++ b/android/src/main/kotlin/com/victorblaess/native_flutter_proxy/FlutterProxyPlugin.kt @@ -1,55 +1,119 @@ package com.victorblaess.native_flutter_proxy +import android.content.BroadcastReceiver +import android.content.Context +import android.content.ContextWrapper +import android.content.Intent +import android.content.IntentFilter +import android.net.ConnectivityManager +import android.net.Proxy +import android.net.ProxyInfo +import android.net.Uri +import android.os.Build +import android.util.Log +import androidx.core.content.ContextCompat.getSystemService import io.flutter.embedding.engine.plugins.FlutterPlugin import io.flutter.plugin.common.BinaryMessenger import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel.MethodCallHandler import io.flutter.plugin.common.MethodChannel.Result -import io.flutter.plugin.common.PluginRegistry.Registrar -import java.util.* -/** FlutterProxyPlugin */ -public class FlutterProxyPlugin : FlutterPlugin, MethodCallHandler { - private var mMethodChannel: MethodChannel? = null; +class FlutterProxyPlugin : FlutterPlugin, MethodCallHandler, BroadcastReceiver() { - companion object { - @JvmStatic - fun registerWith(registrar: Registrar) { - val instance = FlutterProxyPlugin() - instance.onAttachedToEngine(registrar.messenger()); - } - } + private var methodChannel: MethodChannel? = null + private var context: Context? = null - private fun onAttachedToEngine(messenger: BinaryMessenger) { - mMethodChannel = MethodChannel(messenger, "native_flutter_proxy") - mMethodChannel!!.setMethodCallHandler(this) + private fun setupChannel(messenger: BinaryMessenger) { + methodChannel = MethodChannel(messenger, "native_flutter_proxy") + methodChannel!!.setMethodCallHandler(this) } override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { - mMethodChannel = MethodChannel(binding.binaryMessenger, "native_flutter_proxy") - mMethodChannel!!.setMethodCallHandler(this) + setupChannel(binding.binaryMessenger) + context = binding.applicationContext + + // initial default proxy, that could not be captured beforehand + val pi = refreshProxyInfo(null) // this does not look consider proxies from PAC + Log.d("ProxyChangeReceiver", "Properties: ${System.getProperty("http.proxyHost")}:${System.getProperty("http.proxyPort")}") + Log.d("ProxyChangeReceiver", "ProxyInfo without intent: ${pi?.host}:${pi?.port}") + + context!!.registerReceiver(this, IntentFilter(Proxy.PROXY_CHANGE_ACTION)) } override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { - mMethodChannel!!.setMethodCallHandler(null) - mMethodChannel = null + methodChannel?.setMethodCallHandler(null) + methodChannel = null + context!!.unregisterReceiver(this) + context = null } override fun onMethodCall(call: MethodCall, result: Result) { if (call.method == "getProxySetting") { - result.success(getProxySetting()) + result.success(proxySetting) } else { result.notImplemented() } } - private fun getProxySetting(): Any? { - val map = LinkedHashMap() - map["host"] = System.getProperty("http.proxyHost") - map["port"] = System.getProperty("http.proxyPort") - return map + private val proxySetting: LinkedHashMap = LinkedHashMap() + + override fun onReceive(context: Context, intent: Intent) { + if (Proxy.PROXY_CHANGE_ACTION == intent.action) { + // Handle the proxy change here + Log.d("ProxyChangeReceiver", "Proxy settings changed") + val pi = refreshProxyInfo(intent) + Log.d("ProxyChangeReceiver", "ProxyInfo: ${pi?.host}:${pi?.port}") + methodChannel!!.invokeMethod("proxyChangedCallback", proxySetting) + } } + /** + * Get system proxy and update cache with optional intent argument needed for PAC. + */ + private fun refreshProxyInfo(intent: Intent?): ProxyInfo? { + val connectivityManager = getSystemService(context!!,ConnectivityManager::class.java) + var info: ProxyInfo? = connectivityManager!!.defaultProxy + if (info == null) { + proxySetting["host"] = null + proxySetting["port"] = null + return null + } + + // If a proxy is configured using the PAC file use + // Android's injected localhost HTTP proxy. + // + // Android's injected localhost proxy can be accessed using a proxy host + // equal to `localhost` and a proxy port retrieved from intent's 'extras'. + // We cannot take a proxy port from the ProxyInfo object that's exposed by + // the connectivity manager as it's always equal to -1 for cases when PAC + // proxy is configured. + if (info.pacFileUrl != null && info.pacFileUrl !== Uri.EMPTY) { + if (intent == null) { + proxySetting["host"] = null + proxySetting["port"] = null + // PAC proxies are supported only when Intent is present + return null + } + + val extras = intent.extras + if (extras == null) { + proxySetting["host"] = null + proxySetting["port"] = null + return null + } + + info = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + extras.getParcelable("android.intent.extra.PROXY_INFO", ProxyInfo::class.java) + } else { + @Suppress("DEPRECATION") + extras.getParcelable("android.intent.extra.PROXY_INFO") as? ProxyInfo + } + } + + proxySetting["host"] = info!!.host + proxySetting["port"] = info.port + return info + } } diff --git a/example/.gitignore b/example/.gitignore index 0fa6b67..3820a95 100644 --- a/example/.gitignore +++ b/example/.gitignore @@ -5,9 +5,12 @@ *.swp .DS_Store .atom/ +.build/ .buildlog/ .history .svn/ +.swiftpm/ +migrate_working_dir/ # IntelliJ related *.iml @@ -24,15 +27,11 @@ **/doc/api/ **/ios/Flutter/.last_build_id .dart_tool/ -.flutter-plugins .flutter-plugins-dependencies -.packages .pub-cache/ .pub/ /build/ - -# Web related -lib/generated_plugin_registrant.dart +/coverage/ # Symbolication related app.*.symbols diff --git a/example/.metadata b/example/.metadata index 56bfc2c..a046c41 100644 --- a/example/.metadata +++ b/example/.metadata @@ -4,7 +4,30 @@ # This file should be version controlled and should not be manually edited. version: - revision: f4abaa0735eba4dfd8f33f73363911d63931fe03 - channel: stable + revision: "f6ff1529fd6d8af5f706051d9251ac9231c83407" + channel: "stable" project_type: app + +# Tracks metadata for the flutter migrate command +migration: + platforms: + - platform: root + create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + - platform: android + create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + - platform: ios + create_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + base_revision: f6ff1529fd6d8af5f706051d9251ac9231c83407 + + # 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/example/README.md b/example/README.md index a135626..154449a 100644 --- a/example/README.md +++ b/example/README.md @@ -1,6 +1,6 @@ # example -A new Flutter project. +native proxy example ## Getting Started @@ -8,9 +8,9 @@ This project is a starting point for a Flutter application. A few resources to get you started if this is your first Flutter project: -- [Lab: Write your first Flutter app](https://flutter.dev/docs/get-started/codelab) -- [Cookbook: Useful Flutter samples](https://flutter.dev/docs/cookbook) +- [Lab: Write your first Flutter app](https://docs.flutter.dev/get-started/codelab) +- [Cookbook: Useful Flutter samples](https://docs.flutter.dev/cookbook) -For help getting started with Flutter, view our -[online documentation](https://flutter.dev/docs), which offers tutorials, +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. diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..f46ca3b --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1,8 @@ +include: package:flutter_lints/flutter.yaml + +linter: + rules: + lines_longer_than_80_chars: false + prefer_const_constructors: true + prefer_const_declarations: true + prefer_const_literals_to_create_immutables: true diff --git a/example/android/.gitignore b/example/android/.gitignore index 0a741cb..be3943c 100644 --- a/example/android/.gitignore +++ b/example/android/.gitignore @@ -5,7 +5,10 @@ gradle-wrapper.jar /gradlew.bat /local.properties GeneratedPluginRegistrant.java +.cxx/ # Remember to never publicly share your keystore. -# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app +# See https://flutter.dev/to/reference-keystore key.properties +**/*.keystore +**/*.jks diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle deleted file mode 100644 index 937a7a6..0000000 --- a/example/android/app/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -def localProperties = new Properties() -def localPropertiesFile = rootProject.file('local.properties') -if (localPropertiesFile.exists()) { - localPropertiesFile.withReader('UTF-8') { reader -> - localProperties.load(reader) - } -} - -def flutterRoot = localProperties.getProperty('flutter.sdk') -if (flutterRoot == null) { - throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.") -} - -def flutterVersionCode = localProperties.getProperty('flutter.versionCode') -if (flutterVersionCode == null) { - flutterVersionCode = '1' -} - -def flutterVersionName = localProperties.getProperty('flutter.versionName') -if (flutterVersionName == null) { - flutterVersionName = '1.0' -} - -apply plugin: 'com.android.application' -apply plugin: 'kotlin-android' -apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle" - -android { - compileSdkVersion 30 - - 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.veebee.proxy.example.example" - minSdkVersion 16 - targetSdkVersion 30 - 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 { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} diff --git a/example/android/app/build.gradle.kts b/example/android/app/build.gradle.kts new file mode 100644 index 0000000..ce91094 --- /dev/null +++ b/example/android/app/build.gradle.kts @@ -0,0 +1,43 @@ +import org.jetbrains.kotlin.gradle.dsl.JvmTarget + +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.example.example" + compileSdk = 36 + ndkVersion = "27.3.13750724" + + compileOptions { + sourceCompatibility = JavaVersion.VERSION_17 + targetCompatibility = JavaVersion.VERSION_17 + } + + defaultConfig { + applicationId = "com.example.example" + minSdk = flutter.minSdkVersion + targetSdk = 36 + versionCode = flutter.versionCode + versionName = flutter.versionName + } + + buildTypes { + release { + signingConfig = signingConfigs.getByName("debug") + } + } +} + +kotlin { + compilerOptions { + jvmTarget.set(JvmTarget.JVM_17) + } +} + +flutter { + source = "../.." +} diff --git a/example/android/app/src/debug/AndroidManifest.xml b/example/android/app/src/debug/AndroidManifest.xml index 207e1b6..399f698 100644 --- a/example/android/app/src/debug/AndroidManifest.xml +++ b/example/android/app/src/debug/AndroidManifest.xml @@ -1,6 +1,6 @@ - - diff --git a/example/android/app/src/main/AndroidManifest.xml b/example/android/app/src/main/AndroidManifest.xml index 7d207fc..74a78b9 100644 --- a/example/android/app/src/main/AndroidManifest.xml +++ b/example/android/app/src/main/AndroidManifest.xml @@ -1,11 +1,13 @@ - - + - - @@ -38,4 +31,15 @@ android:name="flutterEmbedding" android:value="2" /> + + + + + + + diff --git a/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt new file mode 100644 index 0000000..ac81bae --- /dev/null +++ b/example/android/app/src/main/kotlin/com/example/example/MainActivity.kt @@ -0,0 +1,5 @@ +package com.example.example + +import io.flutter.embedding.android.FlutterActivity + +class MainActivity : FlutterActivity() diff --git a/example/android/app/src/main/kotlin/com/veebee/proxy/example/example/MainActivity.kt b/example/android/app/src/main/kotlin/com/veebee/proxy/example/example/MainActivity.kt deleted file mode 100644 index 61b2141..0000000 --- a/example/android/app/src/main/kotlin/com/veebee/proxy/example/example/MainActivity.kt +++ /dev/null @@ -1,6 +0,0 @@ -package com.veebee.proxy.example.example - -import io.flutter.embedding.android.FlutterActivity - -class MainActivity: FlutterActivity() { -} diff --git a/example/android/app/src/main/res/values-night/styles.xml b/example/android/app/src/main/res/values-night/styles.xml index 449a9f9..06952be 100644 --- a/example/android/app/src/main/res/values-night/styles.xml +++ b/example/android/app/src/main/res/values-night/styles.xml @@ -3,14 +3,14 @@