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/.idea/codeStyleConfig.xml b/.idea/codeStyleConfig.xml new file mode 100644 index 0000000..673acd8 --- /dev/null +++ b/.idea/codeStyleConfig.xml @@ -0,0 +1,12 @@ + + + + + + + \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..391e560 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "[dart]": { + "editor.rulers": [ + 100 + ], + "editor.formatOnSave": true + }, + "dart.lineLength": 100 +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 9809846..7dbb2ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,23 +1,30 @@ +## 0.2.0 + +* update SDK constraints. +* updates documentation. +* adds tests. +* adds .vscode and .idea IDE configuration settings. + ## 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/analysis_options.yaml b/analysis_options.yaml new file mode 100644 index 0000000..f17506e --- /dev/null +++ b/analysis_options.yaml @@ -0,0 +1,8 @@ +include: package:very_good_analysis/analysis_options.yaml + +formatter: + page_width: 100 + +linter: + rules: + lines_longer_than_80_chars: false \ No newline at end of file diff --git a/android/build.gradle b/android/build.gradle index ba1187e..47564ac 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -1,31 +1,27 @@ -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" - } +plugins { + id 'com.android.library' + id 'org.jetbrains.kotlin.android' } -rootProject.allprojects { +allprojects { repositories { + gradlePluginPortal() google() - jcenter() + mavenCentral() } } -apply plugin: 'com.android.library' -apply plugin: 'kotlin-android' +group 'com.victorblaess.native_flutter_proxy' android { - compileSdkVersion 28 + + namespace 'com.victorblaess.native_flutter_proxy' + compileSdk 33 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -40,5 +36,6 @@ android { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KotlinVersion" + implementation "org.jetbrains.kotlin:kotlin-reflect:$KotlinVersion" +} \ No newline at end of file diff --git a/android/gradle.properties b/android/gradle.properties index 38c8d45..236fe15 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -1,4 +1,5 @@ org.gradle.jvmargs=-Xmx1536M -android.enableR8=true android.useAndroidX=true android.enableJetifier=true +AGPVersion=7.4.2 +KotlinVersion=1.8.22 \ No newline at end of file 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..1af9e09 100644 --- a/android/gradle/wrapper/gradle-wrapper.properties +++ b/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.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 index 2922dd1..f5ac3e8 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1 +1,13 @@ -rootProject.name = 'native_flutter_proxy' +pluginManagement { + repositories { + gradlePluginPortal() + google() + mavenCentral() + } + plugins { + id 'com.android.library' version "${AGPVersion}" + id 'org.jetbrains.kotlin.android' version "${KotlinVersion}" + } +} + +rootProject.name = 'native_flutter_proxy' \ No newline at end of file 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..ca412ea 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 @@ -9,12 +9,23 @@ import io.flutter.plugin.common.MethodChannel.Result import io.flutter.plugin.common.PluginRegistry.Registrar import java.util.* -/** FlutterProxyPlugin */ +/** + * FlutterProxyPlugin + * + * Este plugin de Flutter se utiliza para obtener la configuración del proxy del sistema. + * Implementa las interfaces FlutterPlugin y MethodCallHandler. + */ public class FlutterProxyPlugin : FlutterPlugin, MethodCallHandler { + // Canal de método para la comunicación entre Flutter y el código nativo. private var mMethodChannel: MethodChannel? = null; companion object { + /** + * Método estático para registrar el plugin con un registrar. + * + * @param registrar El registrar que se utiliza para registrar el plugin. + */ @JvmStatic fun registerWith(registrar: Registrar) { val instance = FlutterProxyPlugin() @@ -22,21 +33,42 @@ public class FlutterProxyPlugin : FlutterPlugin, MethodCallHandler { } } + /** + * Método privado para adjuntar el plugin al motor de Flutter. + * + * @param messenger El mensajero binario utilizado para la comunicación. + */ private fun onAttachedToEngine(messenger: BinaryMessenger) { mMethodChannel = MethodChannel(messenger, "native_flutter_proxy") mMethodChannel!!.setMethodCallHandler(this) } + /** + * Método llamado cuando el plugin se adjunta al motor de Flutter. + * + * @param binding El enlace del plugin de Flutter. + */ override fun onAttachedToEngine(binding: FlutterPlugin.FlutterPluginBinding) { mMethodChannel = MethodChannel(binding.binaryMessenger, "native_flutter_proxy") mMethodChannel!!.setMethodCallHandler(this) } + /** + * Método llamado cuando el plugin se desadjunta del motor de Flutter. + * + * @param binding El enlace del plugin de Flutter. + */ override fun onDetachedFromEngine(binding: FlutterPlugin.FlutterPluginBinding) { mMethodChannel!!.setMethodCallHandler(null) mMethodChannel = null } + /** + * Método llamado cuando se realiza una llamada de método desde Flutter. + * + * @param call La llamada de método. + * @param result El resultado de la llamada de método. + */ override fun onMethodCall(call: MethodCall, result: Result) { if (call.method == "getProxySetting") { result.success(getProxySetting()) @@ -45,6 +77,11 @@ public class FlutterProxyPlugin : FlutterPlugin, MethodCallHandler { } } + /** + * Método privado para obtener la configuración del proxy del sistema. + * + * @return Un mapa con la configuración del proxy. + */ private fun getProxySetting(): Any? { val map = LinkedHashMap() map["host"] = System.getProperty("http.proxyHost") @@ -52,4 +89,4 @@ public class FlutterProxyPlugin : FlutterPlugin, MethodCallHandler { return map } -} +} \ No newline at end of file diff --git a/example/analysis_options.yaml b/example/analysis_options.yaml new file mode 100644 index 0000000..fac60e2 --- /dev/null +++ b/example/analysis_options.yaml @@ -0,0 +1 @@ +include: ../../analysis_options.yaml \ No newline at end of file diff --git a/example/android/app/build.gradle b/example/android/app/build.gradle index 937a7a6..69135ec 100644 --- a/example/android/app/build.gradle +++ b/example/android/app/build.gradle @@ -1,3 +1,9 @@ +plugins { + id "com.android.application" + id "kotlin-android" + id "dev.flutter.flutter-gradle-plugin" +} + def localProperties = new Properties() def localPropertiesFile = rootProject.file('local.properties') if (localPropertiesFile.exists()) { @@ -21,12 +27,13 @@ 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 + compileSdkVersion 34 + + compileOptions { + sourceCompatibility JavaVersion.VERSION_17 + targetCompatibility JavaVersion.VERSION_17 + } sourceSets { main.java.srcDirs += 'src/main/kotlin' @@ -35,7 +42,7 @@ android { 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 + minSdkVersion flutter.minSdkVersion targetSdkVersion 30 versionCode flutterVersionCode.toInteger() versionName flutterVersionName @@ -55,5 +62,5 @@ flutter { } dependencies { - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" -} + implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$KotlinVersion" +} \ No newline at end of file diff --git a/example/android/build.gradle b/example/android/build.gradle index 9b6ed06..d15fe59 100644 --- a/example/android/build.gradle +++ b/example/android/build.gradle @@ -1,29 +1,19 @@ -buildscript { - ext.kotlin_version = '1.3.50' - repositories { - google() - jcenter() - } - - dependencies { - classpath 'com.android.tools.build:gradle:4.1.0' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - } -} - allprojects { repositories { + gradlePluginPortal() google() - jcenter() + mavenCentral() } } rootProject.buildDir = '../build' subprojects { project.buildDir = "${rootProject.buildDir}/${project.name}" +} +subprojects { project.evaluationDependsOn(':app') } -task clean(type: Delete) { +tasks.register("clean", Delete) { delete rootProject.buildDir -} +} \ No newline at end of file diff --git a/example/android/gradle.properties b/example/android/gradle.properties index 94adc3a..236fe15 100644 --- a/example/android/gradle.properties +++ b/example/android/gradle.properties @@ -1,3 +1,5 @@ org.gradle.jvmargs=-Xmx1536M android.useAndroidX=true android.enableJetifier=true +AGPVersion=7.4.2 +KotlinVersion=1.8.22 \ No newline at end of file diff --git a/example/android/gradle/wrapper/gradle-wrapper.properties b/example/android/gradle/wrapper/gradle-wrapper.properties index bc6a58a..25406ab 100644 --- a/example/android/gradle/wrapper/gradle-wrapper.properties +++ b/example/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Fri Jun 23 08:50:38 CEST 2017 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip +zipStoreBase=GRADLE_USER_HOME +distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-all.zip \ No newline at end of file diff --git a/example/android/settings.gradle b/example/android/settings.gradle index 44e62bc..7ef8a88 100644 --- a/example/android/settings.gradle +++ b/example/android/settings.gradle @@ -1,11 +1,25 @@ -include ':app' +pluginManagement { + def flutterSdkPath = { + def properties = new Properties() + file("local.properties").withInputStream { properties.load(it) } + def flutterSdkPath = properties.getProperty("flutter.sdk") + assert flutterSdkPath != null, "flutter.sdk not set in local.properties" + return flutterSdkPath + }() -def localPropertiesFile = new File(rootProject.projectDir, "local.properties") -def properties = new Properties() + includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") -assert localPropertiesFile.exists() -localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) } + repositories { + google() + mavenCentral() + gradlePluginPortal() + } +} -def flutterSdkPath = properties.getProperty("flutter.sdk") -assert flutterSdkPath != null, "flutter.sdk not set in local.properties" -apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle" +plugins { + id "dev.flutter.flutter-plugin-loader" version "1.0.0" + id "com.android.application" version "${AGPVersion}" apply false + id "org.jetbrains.kotlin.android" version "${KotlinVersion}" apply false +} + +include ":app" \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index 8bd8364..3b200a7 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,125 +1,71 @@ +// ignore_for_file: unused_local_variable + import 'package:flutter/material.dart'; -import 'package:native_flutter_proxy/native_proxy_reader.dart'; -import 'package:native_flutter_proxy/custom_proxy.dart'; +import 'package:native_flutter_proxy/native_flutter_proxy.dart'; void main() async { + // Ensure that the WidgetsBinding is initialized before calling the + // [NativeProxyReader.proxySetting] method. WidgetsFlutterBinding.ensureInitialized(); - bool enabled = false; + // Get the proxy settings from the native platform. + var enabled = false; String? host; int? port; try { - ProxySetting settings = await NativeProxyReader.proxySetting; + final settings = await NativeProxyReader.proxySetting; enabled = settings.enabled; host = settings.host; port = settings.port; } catch (e) { print(e); } + + // Enable the proxy if it is enabled and the host is not null. if (enabled && host != null) { - final proxy = CustomProxy(ipAddress: host, port: port); - proxy.enable(); - print("proxy enabled"); + final proxy = CustomProxy(ipAddress: host, port: port).enable(); + debugPrint('proxy enabled'); } runApp(MyApp()); } class MyApp extends StatelessWidget { - // This widget is the root of your application. @override Widget build(BuildContext context) { return MaterialApp( title: 'Flutter Demo', - theme: ThemeData( - // This is the theme of your application. - // - // Try running your application with "flutter run". You'll see the - // application has a blue toolbar. Then, without quitting the app, try - // changing the primarySwatch below to Colors.green and then invoke - // "hot reload" (press "r" in the console where you ran "flutter run", - // or simply save your changes to "hot reload" in a Flutter IDE). - // Notice that the counter didn't reset back to zero; the application - // is not restarted. - primarySwatch: Colors.blue, - ), + theme: ThemeData(primarySwatch: Colors.blue), home: MyHomePage(title: 'Flutter Demo Home Page'), ); } } class MyHomePage extends StatefulWidget { - MyHomePage({Key? key, required this.title}) : super(key: key); - - // This widget is the home page of your application. It is stateful, meaning - // that it has a State object (defined below) that contains fields that affect - // how it looks. - - // This class is the configuration for the state. It holds the values (in this - // case the title) provided by the parent (in this case the App widget) and - // used by the build method of the State. Fields in a Widget subclass are - // always marked "final". - + MyHomePage({super.key, required this.title}); final String title; @override - _MyHomePageState createState() => _MyHomePageState(); + State createState() => _MyHomePageState(); } class _MyHomePageState extends State { - int _counter = 0; + int counter = 0; - void _incrementCounter() { - setState(() { - // This call to setState tells the Flutter framework that something has - // changed in this State, which causes it to rerun the build method below - // so that the display can reflect the updated values. If we changed - // _counter without calling setState(), then the build method would not be - // called again, and so nothing would appear to happen. - _counter++; - }); - } + void _incrementCounter() => setState(() => counter++); @override Widget build(BuildContext context) { - // This method is rerun every time setState is called, for instance as done - // by the _incrementCounter method above. - // - // The Flutter framework has been optimized to make rerunning build methods - // fast, so that you can just rebuild anything that needs updating rather - // than having to individually change instances of widgets. return Scaffold( - appBar: AppBar( - // Here we take the value from the MyHomePage object that was created by - // the App.build method, and use it to set our appbar title. - title: Text(widget.title), - ), + appBar: AppBar(title: Text(widget.title)), body: Center( - // Center is a layout widget. It takes a single child and positions it - // in the middle of the parent. child: Column( - // Column is also a layout widget. It takes a list of children and - // arranges them vertically. By default, it sizes itself to fit its - // children horizontally, and tries to be as tall as its parent. - // - // Invoke "debug painting" (press "p" in the console, choose the - // "Toggle Debug Paint" action from the Flutter Inspector in Android - // Studio, or the "Toggle Debug Paint" command in Visual Studio Code) - // to see the wireframe for each widget. - // - // Column has various properties to control how it sizes itself and - // how it positions its children. Here we use mainAxisAlignment to - // center the children vertically; the main axis here is the vertical - // axis because Columns are vertical (the cross axis would be - // horizontal). mainAxisAlignment: MainAxisAlignment.center, - children: [ + children: [ + Text('You have pushed the button this many times:'), Text( - 'You have pushed the button this many times:', - ), - Text( - '$_counter', - style: Theme.of(context).textTheme.headline4, + '$counter', + style: Theme.of(context).textTheme.headlineMedium, ), ], ), @@ -128,7 +74,7 @@ class _MyHomePageState extends State { onPressed: _incrementCounter, tooltip: 'Increment', child: Icon(Icons.add), - ), // This trailing comma makes auto-formatting nicer for build methods. + ), ); } } diff --git a/example/pubspec.lock b/example/pubspec.lock index aab9531..3d9ddb2 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -1,161 +1,62 @@ # Generated by pub # See https://dart.dev/tools/pub/glossary#lockfile packages: - async: - dependency: transitive - description: - name: async - url: "https://pub.dartlang.org" - source: hosted - version: "2.8.1" - boolean_selector: - dependency: transitive - description: - name: boolean_selector - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" - clock: - dependency: transitive - description: - name: clock - url: "https://pub.dartlang.org" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.4.0" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.15.0" - cupertino_icons: - dependency: "direct main" - description: - name: cupertino_icons - url: "https://pub.dartlang.org" - source: hosted - version: "1.0.3" - fake_async: - dependency: transitive - description: - name: fake_async - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" + version: "1.19.1" flutter: dependency: "direct main" description: flutter source: sdk version: "0.0.0" - flutter_test: - dependency: "direct dev" - description: flutter - source: sdk - version: "0.0.0" - matcher: + material_color_utilities: dependency: transitive description: - name: matcher - url: "https://pub.dartlang.org" + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" source: hosted - version: "0.12.10" + version: "0.11.1" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.16.0" native_flutter_proxy: - dependency: "direct dev" + dependency: "direct main" description: path: ".." relative: true source: path - version: "0.1.13" - path: - dependency: transitive - description: - name: path - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.0" + version: "0.2.0" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" - source_span: - dependency: transitive - description: - name: source_span - url: "https://pub.dartlang.org" - source: hosted - version: "1.8.1" - stack_trace: - dependency: transitive - description: - name: stack_trace - url: "https://pub.dartlang.org" - source: hosted - version: "1.10.0" - stream_channel: - dependency: transitive - description: - name: stream_channel - url: "https://pub.dartlang.org" - source: hosted - version: "2.1.0" - string_scanner: - dependency: transitive - description: - name: string_scanner - url: "https://pub.dartlang.org" - source: hosted - version: "1.1.0" - term_glyph: - dependency: transitive - description: - name: term_glyph - url: "https://pub.dartlang.org" - source: hosted - version: "1.2.0" - test_api: - dependency: transitive - description: - name: test_api - url: "https://pub.dartlang.org" - source: hosted - version: "0.4.2" - typed_data: - dependency: transitive - description: - name: typed_data - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.0" + version: "0.0.0" vector_math: dependency: transitive description: name: vector_math - url: "https://pub.dartlang.org" + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.12.0" \ No newline at end of file + dart: ">=3.7.0-0 <4.0.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index 3427922..680bb4d 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -1,78 +1,15 @@ name: example description: A new Flutter project. - -# The following line prevents the package from being accidentally published to -# pub.dev using `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 used as CFBundleVersion. -# Read more about iOS versioning at -# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html +publish_to: "none" version: 1.0.0+1 environment: - sdk: ">=2.12.0 <3.0.0" + sdk: ">=3.0.0 <4.0.0" dependencies: flutter: sdk: flutter + native_flutter_proxy: ^0.2.0 - - # The following adds the Cupertino Icons font to your application. - # Use with the CupertinoIcons class for iOS style icons. - cupertino_icons: ^1.0.2 - -dev_dependencies: - flutter_test: - sdk: flutter - native_flutter_proxy: - path: ../ - -# 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. flutter: - - # The following line ensures that the Material Icons font is - # included with your application, so that you can use the icons in - # the material Icons class. uses-material-design: true - - # To add assets to your application, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # For details regarding adding assets from package dependencies, see - # https://flutter.dev/assets-and-images/#from-packages - - # To add custom fonts to your application, add a fonts section here, - # in this "flutter" section. Each entry in this list should have a - # "family" key with the font family name, and a "fonts" key with a - # list giving the asset and other descriptors for the font. For - # example: - # fonts: - # - family: Schyler - # fonts: - # - asset: fonts/Schyler-Regular.ttf - # - asset: fonts/Schyler-Italic.ttf - # style: italic - # - family: Trajan Pro - # fonts: - # - asset: fonts/TrajanPro.ttf - # - asset: fonts/TrajanPro_Bold.ttf - # weight: 700 - # - # For details regarding fonts from package dependencies, - # see https://flutter.dev/custom-fonts/#from-packages \ No newline at end of file diff --git a/example/pubspec_overrides.yaml b/example/pubspec_overrides.yaml new file mode 100644 index 0000000..e817a71 --- /dev/null +++ b/example/pubspec_overrides.yaml @@ -0,0 +1,3 @@ +dependency_overrides: + native_flutter_proxy: + path: ../ \ No newline at end of file diff --git a/example/test/widget_test.dart b/example/test/widget_test.dart deleted file mode 100644 index 747db1d..0000000 --- a/example/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility that Flutter provides. 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:example/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(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); - }); -} diff --git a/ios/Classes/FlutterProxyPlugin.h b/ios/Classes/FlutterProxyPlugin.h index 909e6bc..f4a3e69 100644 --- a/ios/Classes/FlutterProxyPlugin.h +++ b/ios/Classes/FlutterProxyPlugin.h @@ -1,4 +1,11 @@ #import -@interface FlutterProxyPlugin : NSObject -@end +/** + * @class FlutterProxyPlugin + * @brief A plugin class for handling proxy settings in Flutter. + * + * This class serves as an interface for the Flutter plugin, allowing + * the registration of the plugin with the Flutter engine. + */ +@interface FlutterProxyPlugin : NSObject +@end \ No newline at end of file diff --git a/ios/Classes/FlutterProxyPlugin.m b/ios/Classes/FlutterProxyPlugin.m index 2d67deb..f725fd1 100644 --- a/ios/Classes/FlutterProxyPlugin.m +++ b/ios/Classes/FlutterProxyPlugin.m @@ -8,8 +8,21 @@ #import "native_flutter_proxy-Swift.h" #endif +/** + * @class FlutterProxyPlugin + * @brief An Objective-C wrapper for the Swift Flutter plugin. + * + * This class registers the Swift implementation of the plugin with the Flutter engine. + */ @implementation FlutterProxyPlugin + +/** + * Registers the plugin with the Flutter plugin registrar. + * + * @param registrar The Flutter plugin registrar. + */ + (void)registerWithRegistrar:(NSObject*)registrar { [SwiftFlutterProxyPlugin registerWithRegistrar:registrar]; } -@end + +@end \ No newline at end of file diff --git a/ios/Classes/SwiftFlutterProxyPlugin.swift b/ios/Classes/SwiftFlutterProxyPlugin.swift index 3349ece..360c8a2 100644 --- a/ios/Classes/SwiftFlutterProxyPlugin.swift +++ b/ios/Classes/SwiftFlutterProxyPlugin.swift @@ -1,13 +1,32 @@ import Flutter import UIKit +/** + * @class SwiftFlutterProxyPlugin + * @brief A Swift implementation of the Flutter plugin for handling proxy settings. + * + * This class registers the plugin with the Flutter engine and handles method calls + * from Dart code. It provides functionality to retrieve the system proxy settings. + */ public class SwiftFlutterProxyPlugin: NSObject, FlutterPlugin { + + /** + * Registers the plugin with the Flutter plugin registrar. + * + * @param registrar The Flutter plugin registrar. + */ public static func register(with registrar: FlutterPluginRegistrar) { let channel = FlutterMethodChannel(name: "native_flutter_proxy", binaryMessenger: registrar.messenger()) let instance = SwiftFlutterProxyPlugin() registrar.addMethodCallDelegate(instance, channel: channel) } + /** + * Handles method calls from Dart code. + * + * @param call The method call from Dart. + * @param result The result callback to send the response back to Dart. + */ public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { switch call.method { case "getProxySetting": @@ -19,20 +38,25 @@ public class SwiftFlutterProxyPlugin: NSObject, FlutterPlugin { } } - func getProxySetting() -> NSDictionary? { - guard let proxySettings = CFNetworkCopySystemProxySettings()?.takeUnretainedValue(), - let url = URL(string: "https://www.bing.com/") else { - return nil - } - let proxies = CFNetworkCopyProxiesForURL((url as CFURL), proxySettings).takeUnretainedValue() as NSArray - guard let settings = proxies.firstObject as? NSDictionary, - let _ = settings.object(forKey: (kCFProxyTypeKey as String)) as? String else { - return nil - } + /** + * Retrieves the system proxy settings. + * + * @return A dictionary containing the proxy host and port, or nil if not available. + */ + func getProxySetting() -> NSDictionary? { + guard let proxySettings = CFNetworkCopySystemProxySettings()?.takeUnretainedValue(), + let url = URL(string: "https://www.bing.com/") else { + return nil + } + let proxies = CFNetworkCopyProxiesForURL((url as CFURL), proxySettings).takeUnretainedValue() as NSArray + guard let settings = proxies.firstObject as? NSDictionary, + let _ = settings.object(forKey: (kCFProxyTypeKey as String)) as? String else { + return nil + } - if let hostName = settings.object(forKey: (kCFProxyHostNameKey as String)), let port = settings.object(forKey: (kCFProxyPortNumberKey as String)) { - return ["host":hostName, "port":port] - } - return nil; + if let hostName = settings.object(forKey: (kCFProxyHostNameKey as String)), let port = settings.object(forKey: (kCFProxyPortNumberKey as String)) { + return ["host": hostName, "port": port] } + return nil + } } \ No newline at end of file diff --git a/lib/custom_proxy.dart b/lib/custom_proxy.dart deleted file mode 100644 index 33d63e9..0000000 --- a/lib/custom_proxy.dart +++ /dev/null @@ -1,53 +0,0 @@ -import 'dart:io'; -import 'custom_proxy_override.dart'; - -/// Allows you to set and enable a proxy for your app -class CustomProxy { - /// A string representing an IP address for the proxy server - final String ipAddress; - - /// The port number for the proxy server - /// Can be null if port is default. - final int? port; - - /// Set this to true - /// - Warning: Setting this to true in production apps can be dangerous. Use with care! - bool allowBadCertificates; - - /// Initializer - CustomProxy( - {required this.ipAddress, this.port, this.allowBadCertificates = false}); - - /// Initializer from string - static CustomProxy? fromString({required String proxy}) { - // Check if valid - if (proxy.isEmpty) { - assert( - false, "Proxy string passed to CustomProxy.fromString() is invalid."); - return null; - } - - // Build and return - final proxyParts = proxy.split(":"); - final _ipAddress = proxyParts[0]; - final _port = proxyParts.length > 0 ? int.tryParse(proxyParts[1]) : null; - return CustomProxy( - ipAddress: _ipAddress, - port: _port, - ); - } - - /// Enable the proxy - void enable() => HttpOverrides.global = - new CustomProxyHttpOverride.withProxy(this.toString()); - - /// Disable the proxy - void disable() => HttpOverrides.global = null; - - @override - String toString() { - String _proxy = this.ipAddress; - if (this.port != null) _proxy += ":" + this.port.toString(); - return _proxy; - } -} diff --git a/lib/custom_proxy_override.dart b/lib/custom_proxy_override.dart deleted file mode 100644 index 2c71e04..0000000 --- a/lib/custom_proxy_override.dart +++ /dev/null @@ -1,32 +0,0 @@ -import 'dart:io'; - -/// This class overrides the global proxy settings. -class CustomProxyHttpOverride extends HttpOverrides { - /// The entire proxy server - /// Format: "localhost:8888" - final String proxyString; - - /// Set this to true - /// - Warning: Setting this to true in production apps can be dangerous. Use with care! - final bool allowBadCertificates; - - /// Initializer - CustomProxyHttpOverride.withProxy( - this.proxyString, { - this.allowBadCertificates = false, - }); - - /// Override HTTP client creation - @override - HttpClient createHttpClient(SecurityContext? context) { - return super.createHttpClient(context) - ..findProxy = (uri) { - assert(this.proxyString.isNotEmpty, - 'You must set a valid proxy if you enable it!'); - return "PROXY " + this.proxyString + ";"; - }; -/* ..badCertificateCallback = this.allowBadCertificates - ? (X509Certificate cert, String host, int port) => true - : null;*/ - } -} diff --git a/lib/native_flutter_proxy.dart b/lib/native_flutter_proxy.dart new file mode 100644 index 0000000..ab35c9a --- /dev/null +++ b/lib/native_flutter_proxy.dart @@ -0,0 +1,9 @@ +/// This library is used to create a custom proxy for HTTP requests in Flutter. +/// +/// To use, import `package:native_flutter_proxy/native_flutter_proxy.dart`. +/// Find the [native_flutter_proxy package](https://pub.dev/packages/native_flutter_proxy) 🚀 +library; + +export './src/custom_proxy.dart'; +export './src/custom_proxy_override.dart'; +export './src/native_proxy_reader.dart'; diff --git a/lib/native_proxy_reader.dart b/lib/native_proxy_reader.dart deleted file mode 100644 index 7e60ba1..0000000 --- a/lib/native_proxy_reader.dart +++ /dev/null @@ -1,47 +0,0 @@ -import 'dart:async'; -import 'package:flutter/services.dart'; - -/// A flutter plugin to read network proxy info from native. It can be used to set up the network proxy for flutter. -class NativeProxyReader { - /// channel - static const MethodChannel _channel = - const MethodChannel('native_flutter_proxy'); - - /// ProxySetting - static Future get proxySetting async { - return _channel // - .invokeMapMethod('getProxySetting') - .then((e) => ProxySetting._fromMap(e)); - } -} - -/// ProxySetting -class ProxySetting { - /// host - String? host; - - /// port - int? port; - - /// private - ProxySetting._({ - this.host, - this.port, - }); - - /// private - factory ProxySetting._fromMap(Map? map) { - map ??= {}; - return ProxySetting._( - host: map['host'], - port: map['port'] != null // - ? int.parse(map['port'].toString()) - : null, - ); - } - - /// enabled - bool get enabled => - (host?.isNotEmpty ?? false) && // - (port != null && port! > 0); -} diff --git a/lib/src/custom_proxy.dart b/lib/src/custom_proxy.dart new file mode 100644 index 0000000..de5391a --- /dev/null +++ b/lib/src/custom_proxy.dart @@ -0,0 +1,98 @@ +import 'dart:io'; + +import 'package:native_flutter_proxy/native_flutter_proxy.dart'; + +/// {@template custom_proxy} +/// A class that manages custom proxy settings for Flutter applications. +/// +/// This class provides functionality to set up and manage a proxy server configuration, +/// including the ability to enable/disable the proxy and handle custom certificates. +/// +/// Example usage: +/// ```dart +/// final proxy = CustomProxy(ipAddress: '192.168.1.1', port: 8080); +/// proxy.enable(); // Enables the proxy +/// proxy.disable(); // Disables the proxy +/// ``` +/// +/// You can also create a proxy instance from a string: +/// ```dart +/// final proxy = CustomProxy.fromString(proxy: '192.168.1.1:8080'); +/// ``` +/// +/// The class supports: +/// * Setting custom IP address and port +/// * Enabling/disabling proxy settings +/// * Optional bad certificate handling +/// * String representation of proxy settings +/// +/// Note: When [allowBadCertificates] is set to true, it may pose security risks +/// and should be used with caution, especially in production environments. +/// {@endtemplate} +class CustomProxy { + /// {@macro custom_proxy} + const CustomProxy({ + required this.ipAddress, + this.port, + this.allowBadCertificates = false, + }); + + /// A string representing an IP address for the proxy server + final String ipAddress; + + /// The port number for the proxy server + /// Can be null if port is default. + final int? port; + + /// Set this to true + /// - Warning: Setting this to true in production apps can be dangerous. Use with care! + final bool allowBadCertificates; + + /// Creates a [CustomProxy] instance from a string representation. + /// + /// The [proxy] string should be in the format "ipAddress:port". + /// For example: "192.168.1.1:8080" + /// + /// Returns null if: + /// * The proxy string is empty + /// * The port number cannot be parsed to an integer + /// + /// Throws an [AssertionError] in debug mode if the proxy string is empty. + static CustomProxy? fromString({required String proxy}) { + // Check if the proxy string is empty + if (proxy.isEmpty) { + assert(false, 'Proxy string passed to CustomProxy.fromString() is invalid.'); + + return null; + } + + // Split the proxy string into parts and extract the IP address and port number if available + // Format: "ipAddress:port" + final proxyParts = proxy.split(':'); + final ipAddress = proxyParts[0]; + final port = proxyParts.isNotEmpty ? int.tryParse(proxyParts[1]) : null; + + return CustomProxy(ipAddress: ipAddress, port: port); + } + + /// Enables the custom proxy by setting a global HTTP override. + /// + /// Sets [HttpOverrides.global] to a new instance of [CustomProxyHttpOverride] + /// configured with the proxy settings from this object's string representation. + void enable() => HttpOverrides.global = CustomProxyHttpOverride.withProxy(toString()); + + /// Disables the global HTTP proxy settings by setting HttpOverrides.global to null. + /// + /// This method removes any previously configured proxy settings and restores + /// the default HTTP client behavior for network requests. + void disable() => HttpOverrides.global = null; + + @override + String toString() { + var proxy = ipAddress; + + if (port != null) proxy += ':$port'; + + return proxy; + } +} diff --git a/lib/src/custom_proxy_override.dart b/lib/src/custom_proxy_override.dart new file mode 100644 index 0000000..2ba572a --- /dev/null +++ b/lib/src/custom_proxy_override.dart @@ -0,0 +1,57 @@ +import 'dart:io'; + +/// {@template custom_proxy_http_override} +/// A custom HTTP override class that allows setting a global proxy for all HTTP requests. +/// +/// This class extends [HttpOverrides] to provide proxy configuration capabilities. +/// It can be used to route all HTTP traffic through a specified proxy server and +/// optionally allow bad certificates for testing purposes. +/// +/// Example usage: +/// ```dart +/// HttpOverrides.global = CustomProxyHttpOverride.withProxy( +/// 'localhost:8888', +/// allowBadCertificates: true, +/// ); +/// ``` +/// +/// Important: +/// - The proxy string must be in the format "host:port" +/// - Allowing bad certificates should only be used for development/testing +/// - This affects all HTTP requests made by the application +/// +/// Note: Use with caution in production environments as it can compromise +/// security if not configured properly. +/// {@endtemplate} +final class CustomProxyHttpOverride extends HttpOverrides { + /// Create a new instance of [CustomProxyHttpOverride] with the specified proxy settings. + /// + /// {@macro custom_proxy_http_override} + CustomProxyHttpOverride.withProxy( + this.proxyString, { + this.allowBadCertificates = false, + }); + + /// The entire proxy server + /// Format: "localhost:8888" + final String proxyString; + + /// Set this to true + /// - Warning: Setting this to true in production apps can be dangerous. Use with care! + final bool allowBadCertificates; + + /// Override HTTP client creation to set the proxy and bad certificate callback. + @override + HttpClient createHttpClient(SecurityContext? context) { + final client = super.createHttpClient(context) + ..findProxy = (uri) { + assert(proxyString.isNotEmpty, 'You must set a valid proxy if you enable it!'); + + return 'PROXY $proxyString;'; + }; + + if (allowBadCertificates) client.badCertificateCallback = (cert, host, port) => true; + + return client; + } +} diff --git a/lib/src/native_proxy_reader.dart b/lib/src/native_proxy_reader.dart new file mode 100644 index 0000000..042cc00 --- /dev/null +++ b/lib/src/native_proxy_reader.dart @@ -0,0 +1,65 @@ +import 'dart:async'; + +import 'package:flutter/services.dart'; + +/// {@template custom_proxy} +/// A class to read network proxy settings from native platform code. +/// +/// This class provides functionality to retrieve proxy settings like host and +/// port from the native platform (iOS/Android) through a method channel. +/// +/// Example usage: +/// ```dart +/// ProxySetting settings = await NativeProxyReader.proxySetting; +/// if (settings.enabled) { +/// print('Proxy host: ${settings.host}'); +/// print('Proxy port: ${settings.port}'); +/// } +/// ``` +/// {@endtemplate} +abstract final class NativeProxyReader { + /// Method channel for native platform communication.ƒ + static const _channel = MethodChannel('native_flutter_proxy'); + + /// Get the proxy settings from the native platform. + static Future get proxySetting async { + return _channel.invokeMapMethod('getProxySetting').then(ProxySetting._fromMap); + } +} + +/// {@template proxy_setting} +/// A class to hold proxy settings like host and port. +/// {@endtemplate} +class ProxySetting { + /// {@macro proxy_setting} + const ProxySetting._({this.host, this.port}); + + /// Create a new instance of [ProxySetting] from a map. + /// + /// {@macro proxy_setting} + factory ProxySetting._fromMap(Map? map) { + map ??= {}; + + final host = map['host']; + final port = map['port']; + + return ProxySetting._( + host: host is String ? host : null, + port: port != null ? int.tryParse(port.toString()) : null, + ); + } + + /// The proxy server hostname or IP address. + final String? host; + + /// The proxy server port number. + final int? port; + + /// A boolean indicating if proxy settings are valid and can be used. + bool get enabled { + final validHost = host?.isNotEmpty ?? false; + final validPort = port != null && port! > 0; + + return validHost && validPort; + } +} diff --git a/pubspec.lock b/pubspec.lock index 20a4615..939a55c 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,51 +5,50 @@ packages: dependency: transitive description: name: async - url: "https://pub.dartlang.org" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 + url: "https://pub.dev" source: hosted - version: "2.8.1" + version: "2.12.0" boolean_selector: dependency: transitive description: name: boolean_selector - url: "https://pub.dartlang.org" + sha256: "8aab1771e1243a5063b8b0ff68042d67334e3feab9e95b9490f9a6ebf73b42ea" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.2" characters: dependency: transitive description: name: characters - url: "https://pub.dartlang.org" + sha256: f71061c654a3380576a52b451dd5532377954cf9dbd272a78fc8479606670803 + url: "https://pub.dev" source: hosted - version: "1.1.0" - charcode: - dependency: transitive - description: - name: charcode - url: "https://pub.dartlang.org" - source: hosted - version: "1.3.1" + version: "1.4.0" clock: dependency: transitive description: name: clock - url: "https://pub.dartlang.org" + sha256: fddb70d9b5277016c77a80201021d40a2247104d9f4aa7bab7157b7e3f05b84b + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.1.2" collection: dependency: transitive description: name: collection - url: "https://pub.dartlang.org" + sha256: "2f5709ae4d3d59dd8f7cd309b4e023046b57d8a6c82130785d2b0e5868084e76" + url: "https://pub.dev" source: hosted - version: "1.15.0" + version: "1.19.1" fake_async: dependency: transitive description: name: fake_async - url: "https://pub.dartlang.org" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.3.2" flutter: dependency: "direct main" description: flutter @@ -60,88 +59,139 @@ packages: description: flutter source: sdk version: "0.0.0" + leak_tracker: + dependency: transitive + description: + name: leak_tracker + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec + url: "https://pub.dev" + source: hosted + version: "10.0.8" + leak_tracker_flutter_testing: + dependency: transitive + description: + name: leak_tracker_flutter_testing + sha256: f8b613e7e6a13ec79cfdc0e97638fddb3ab848452eff057653abd3edba760573 + url: "https://pub.dev" + source: hosted + version: "3.0.9" + leak_tracker_testing: + dependency: transitive + description: + name: leak_tracker_testing + sha256: "6ba465d5d76e67ddf503e1161d1f4a6bc42306f9d66ca1e8f079a47290fb06d3" + url: "https://pub.dev" + source: hosted + version: "3.0.1" matcher: dependency: transitive description: name: matcher - url: "https://pub.dartlang.org" + sha256: dc58c723c3c24bf8d3e2d3ad3f2f9d7bd9cf43ec6feaa64181775e60190153f2 + url: "https://pub.dev" source: hosted - version: "0.12.10" + version: "0.12.17" + material_color_utilities: + dependency: transitive + description: + name: material_color_utilities + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec + url: "https://pub.dev" + source: hosted + version: "0.11.1" meta: dependency: transitive description: name: meta - url: "https://pub.dartlang.org" + sha256: e3641ec5d63ebf0d9b41bd43201a66e3fc79a65db5f61fc181f04cd27aab950c + url: "https://pub.dev" source: hosted - version: "1.7.0" + version: "1.16.0" path: dependency: transitive description: name: path - url: "https://pub.dartlang.org" + sha256: "75cca69d1490965be98c73ceaea117e8a04dd21217b37b292c9ddbec0d955bc5" + url: "https://pub.dev" source: hosted - version: "1.8.0" + version: "1.9.1" sky_engine: dependency: transitive description: flutter source: sdk - version: "0.0.99" + version: "0.0.0" source_span: dependency: transitive description: name: source_span - url: "https://pub.dartlang.org" + sha256: "254ee5351d6cb365c859e20ee823c3bb479bf4a293c22d17a9f1bf144ce86f7c" + url: "https://pub.dev" source: hosted - version: "1.8.1" + version: "1.10.1" stack_trace: dependency: transitive description: name: stack_trace - url: "https://pub.dartlang.org" + sha256: "8b27215b45d22309b5cddda1aa2b19bdfec9df0e765f2de506401c071d38d1b1" + url: "https://pub.dev" source: hosted - version: "1.10.0" + version: "1.12.1" stream_channel: dependency: transitive description: name: stream_channel - url: "https://pub.dartlang.org" + sha256: "969e04c80b8bcdf826f8f16579c7b14d780458bd97f56d107d3950fdbeef059d" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "2.1.4" string_scanner: dependency: transitive description: name: string_scanner - url: "https://pub.dartlang.org" + sha256: "921cd31725b72fe181906c6a94d987c78e3b98c2e205b397ea399d4054872b43" + url: "https://pub.dev" source: hosted - version: "1.1.0" + version: "1.4.1" term_glyph: dependency: transitive description: name: term_glyph - url: "https://pub.dartlang.org" + sha256: "7f554798625ea768a7518313e58f83891c7f5024f88e46e7182a4558850a4b8e" + url: "https://pub.dev" source: hosted - version: "1.2.0" + version: "1.2.2" test_api: dependency: transitive description: name: test_api - url: "https://pub.dartlang.org" + sha256: fb31f383e2ee25fbbfe06b40fe21e1e458d14080e3c67e7ba0acfde4df4e0bbd + url: "https://pub.dev" source: hosted - version: "0.4.2" - typed_data: + version: "0.7.4" + vector_math: dependency: transitive description: - name: typed_data - url: "https://pub.dartlang.org" + name: vector_math + sha256: "80b3257d1492ce4d091729e3a67a60407d227c27241d6927be0130c98e741803" + url: "https://pub.dev" source: hosted - version: "1.3.0" - vector_math: + version: "2.1.4" + very_good_analysis: + dependency: "direct dev" + description: + name: very_good_analysis + sha256: "62d2b86d183fb81b2edc22913d9f155d26eb5cf3855173adb1f59fac85035c63" + url: "https://pub.dev" + source: hosted + version: "7.0.0" + vm_service: dependency: transitive description: - name: vector_math - url: "https://pub.dartlang.org" + name: vm_service + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" + url: "https://pub.dev" source: hosted - version: "2.1.0" + version: "14.3.1" sdks: - dart: ">=2.12.0 <3.0.0" - flutter: ">=1.12.0" \ No newline at end of file + dart: ">=3.7.0-0 <4.0.0" + flutter: ">=3.18.0-18.0.pre.54" diff --git a/pubspec.yaml b/pubspec.yaml index c1e5bc2..cc0b4c1 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,11 +1,11 @@ name: native_flutter_proxy description: A flutter plugin to read and set network proxy info from native. -version: 0.1.14 +version: 0.2.0 homepage: https://github.com/victorblaess/native_flutter_proxy environment: - sdk: ">=2.12.0 <3.0.0" - flutter: ">=1.12.0" + sdk: ">=3.0.0 <4.0.0" + flutter: ">=3.0.0" dependencies: flutter: @@ -14,16 +14,9 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + very_good_analysis: ^7.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. flutter: - # This section identifies this Flutter project as a plugin project. - # The androidPackage and pluginClass identifiers should not ordinarily - # be modified. They are used by the tooling to maintain consistency when - # adding or updating assets for this project. plugin: platforms: android: @@ -31,34 +24,3 @@ flutter: pluginClass: FlutterProxyPlugin ios: pluginClass: FlutterProxyPlugin - - # To add assets to your plugin package, add an assets section, like this: - # assets: - # - images/a_dot_burr.jpeg - # - images/a_dot_ham.jpeg - # - # For details regarding assets in packages, see - # https://flutter.dev/assets-and-images/#from-packages - # - # An image asset can refer to one or more resolution-specific "variants", see - # https://flutter.dev/assets-and-images/#resolution-aware. - - # To add custom fonts to your plugin package, 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 in packages, see - # https://flutter.dev/custom-fonts/#from-packages diff --git a/test/custom_proxy_override_test.dart b/test/custom_proxy_override_test.dart new file mode 100644 index 0000000..7ceea13 --- /dev/null +++ b/test/custom_proxy_override_test.dart @@ -0,0 +1,16 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:native_flutter_proxy/native_flutter_proxy.dart'; + +void main() { + group('CustomProxyHttpOverride', () { + test('creates instance with proxy settings', () { + final override = CustomProxyHttpOverride.withProxy( + 'localhost:8888', + allowBadCertificates: true, + ); + + expect(override.proxyString, equals('localhost:8888')); + expect(override.allowBadCertificates, isTrue); + }); + }); +} diff --git a/test/custom_proxy_test.dart b/test/custom_proxy_test.dart new file mode 100644 index 0000000..a34b652 --- /dev/null +++ b/test/custom_proxy_test.dart @@ -0,0 +1,47 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:native_flutter_proxy/native_flutter_proxy.dart'; + +void main() { + group('CustomProxy', () { + test('creates instance with required parameters', () { + const proxy = CustomProxy(ipAddress: '192.168.1.1'); + expect(proxy.ipAddress, equals('192.168.1.1')); + expect(proxy.port, isNull); + expect(proxy.allowBadCertificates, isFalse); + }); + + test('creates instance with all parameters', () { + const proxy = CustomProxy( + ipAddress: '192.168.1.1', + port: 8080, + allowBadCertificates: true, + ); + + expect(proxy.ipAddress, equals('192.168.1.1')); + expect(proxy.port, equals(8080)); + expect(proxy.allowBadCertificates, isTrue); + }); + + group('fromString', () { + test('creates instance from valid proxy string with port', () { + final proxy = CustomProxy.fromString(proxy: '192.168.1.1:8080'); + + expect(proxy, isNotNull); + expect(proxy?.ipAddress, equals('192.168.1.1')); + expect(proxy?.port, equals(8080)); + }); + }); + + group('toString', () { + test('returns correct string with port', () { + const proxy = CustomProxy(ipAddress: '192.168.1.1', port: 8080); + expect(proxy.toString(), equals('192.168.1.1:8080')); + }); + + test('returns correct string without port', () { + const proxy = CustomProxy(ipAddress: '192.168.1.1'); + expect(proxy.toString(), equals('192.168.1.1')); + }); + }); + }); +} diff --git a/test/flutter_proxy_test.dart b/test/flutter_proxy_test.dart deleted file mode 100644 index 8417a3c..0000000 --- a/test/flutter_proxy_test.dart +++ /dev/null @@ -1,26 +0,0 @@ -import 'package:flutter/services.dart'; -import 'package:flutter_test/flutter_test.dart'; - -void main() { -/* const MethodChannel channel = MethodChannel('native_flutter_proxy'); - - TestWidgetsFlutterBinding.ensureInitialized(); - - setUp(() { - channel.setMockMethodCallHandler((MethodCall methodCall) async { - return { - 'host': '192.168.1.9', - 'port': 9909, - }; - }); - }); - - tearDown(() { - channel.setMockMethodCallHandler(null); - }); - - test('getProxySetting', () async { - final setting = await NativeProxyReader.proxySetting; - expect(setting.enabled, true); - });*/ -} diff --git a/test/native_proxy_reader_test.dart b/test/native_proxy_reader_test.dart new file mode 100644 index 0000000..e6757e6 --- /dev/null +++ b/test/native_proxy_reader_test.dart @@ -0,0 +1,37 @@ +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:native_flutter_proxy/src/native_proxy_reader.dart'; + +void main() { + const channel = MethodChannel('native_flutter_proxy'); + + TestWidgetsFlutterBinding.ensureInitialized(); + + setUp(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, (call) async { + if (call.method == 'getProxySetting') { + return { + 'host': '192.168.1.9', + 'port': 9909, + }; + } + return null; + }); + }); + + tearDown(() { + TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .setMockMethodCallHandler(channel, null); + }); + + group('NativeProxyReader', () { + test('returns valid proxy settings when data is available', () async { + final setting = await NativeProxyReader.proxySetting; + + expect(setting.host, equals('192.168.1.9')); + expect(setting.port, equals(9909)); + expect(setting.enabled, isTrue); + }); + }); +}