diff --git a/.gitattributes b/.gitattributes index cefb70276..6c856e798 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,4 +1,3 @@ -android/app/libs/liblantern-all.aar filter=lfs diff=lfs merge=lfs -text android/app/libs/opuslib-release.aar filter=lfs diff=lfs merge=lfs -text screenshots/ filter=lfs diff=lfs merge=lfs -text assets/testing/ filter=lfs diff=lfs merge=lfs -text diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index f8917d843..255dfe46e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,4 +1,4 @@ -name: android +name: Publish releases on: push @@ -7,148 +7,223 @@ permissions: env: GOPRIVATE: github.com/getlantern + S3_BUCKET: lantern jobs: - set-version: - runs-on: ubuntu-latest - outputs: - version: ${{ steps.set-version.outputs.version }} - steps: - - id: set-version - shell: python - run: | - import sys, os - ref = os.environ.get("GITHUB_REF","") - if "refs/tags/lantern" not in ref: - li = 'lantern-installer-dev' - vf = 'version-dev.txt' - version = '9999.99.99-dev' - else: - a = ref.strip().replace('refs/tags/lantern-', '') - parts = a.split('-',1) - suffix = parts[1] if len(parts)>1 else '' - beta = 'beta' in suffix - internal = 'internal' in suffix - if beta: - li = 'lantern-installer-preview' - vf = 'version-beta.txt' - version = parts[0] - elif internal: - li = 'lantern-installer-internal' - vf = 'version-internal.txt' - version = parts[0] + set-version: + runs-on: ubuntu-latest + outputs: + version: ${{ steps.set-version.outputs.version }} + prefix: ${{ steps.set-version.outputs.prefix }} + version_file: ${{ steps.set-version.outputs.version_file }} + steps: + - id: set-version + shell: python + run: | + import sys, os + ref = os.environ.get("GITHUB_REF","") + if "refs/tags/lantern" not in ref: + li = 'lantern-installer-dev' + vf = 'version-android-dev.txt' + version = '9999.99.99-dev' else: - li = 'lantern-installer' - vf = 'version.txt' - version = a - print('Setting version to ' + version) - print('Setting prefix to ' + li) - print('Setting version file to ' + vf) - print(f'::set-output name=version::{version}') - print(f'::set-output name=prefix::{li}') - print(f'::set-output name=version_file::{vf}') - build: - needs: set-version - runs-on: ubuntu-latest-8-cores - steps: - - uses: actions/checkout@v3 - with: - lfs: true - - - name: Pull LFS objects - run: git lfs pull - - # Install Flutter - - uses: subosito/flutter-action@v2 - with: - flutter-version: "3.10.5" - channel: "stable" - - run: flutter --version - - - name: Setup Go - uses: actions/setup-go@v3 - with: - go-version: 1.19 - - - name: Granting private modules access - run: | - git config --global url."https://${{ secrets.GH_TOKEN }}:x-oauth-basic@github.com/".insteadOf "https://github.com/" - - - name: Build Lantern core Android library - run: make android-lib - - - name: Setup Sentry CLI - uses: mathieu-bour/setup-sentry-cli@v1 - with: - version: latest - token: ${{ SECRETS.SENTRY_TOKEN }} # from GitHub secrets - organization: getlantern - project: android - - - name: Setup JDK 11 - uses: actions/setup-java@v3 - with: - distribution: temurin - java-version: 11 - - - name: Setup protoc - uses: arduino/setup-protoc@v1.2.0 - with: - version: '3.x' - - - name: Activate protoc-gen-dart plugin - run: | - echo "${HOME}/.pub-cache/bin" >> $GITHUB_PATH - dart pub global activate protoc_plugin - mkdir -p "${HOME}/.pub-cache/bin" - mv "${FLUTTER_ROOT}/.pub-cache/bin/protoc-gen-dart" "${HOME}/.pub-cache/bin" - - - name: Set gradle properties + a = ref.strip().replace('refs/tags/lantern-', '') + parts = a.split('-',1) + suffix = parts[1] if len(parts)>1 else '' + beta = 'beta' in suffix + internal = 'internal' in suffix + if beta: + li = 'lantern-installer-preview' + vf = 'version-android-beta.txt' + version = parts[0] + elif internal: + li = 'lantern-installer-internal' + vf = 'version-android-internal.txt' + version = parts[0] + else: + li = 'lantern-installer' + vf = 'version-android.txt' + version = a + print('Setting version to ' + version) + print('Setting prefix to ' + li) + print('Setting version file to ' + vf) + print(f'::set-output name=version::{version}') + print(f'::set-output name=prefix::{li}') + print(f'::set-output name=version_file::{vf}') + build-android: + needs: set-version env: - GRADLE_PROPERTIES: ${{ secrets.GRADLE_PROPERTIES }} - run: | - mkdir -p ~/.gradle/ - echo "GRADLE_USER_HOME=${HOME}/.gradle" >> $GITHUB_ENV - echo "${GRADLE_PROPERTIES}" > ~/.gradle/gradle.properties - - - name: Decode Keystore - id: write_file - uses: timheuer/base64-to-file@v1.2 - with: - fileName: 'keystore.release.jks' - fileDir: './android/app' - encodedString: ${{ secrets.KEYSTORE }} - - - name: Build Release with Gradle - run: make android-release - - - name: Build Mobile Bundle - run: make android-bundle ANDROID_ARCH=all - - - name: Setup S3cmd cli tool - uses: s3-actions/s3cmd@v1.4.0 - with: - provider: aws - region: ${{ secrets.AWS_REGION }} - access_key: ${{ secrets.AWS_ACCESS_KEY }} - secret_key: ${{ secrets.AWS_SECRET_KEY }} - - - name: Upload to S3 (QA) - if: ${{ !startsWith(github.ref, 'refs/tags/lantern') }} + version: ${{ needs.set-version.outputs.version }} + version_file: ${{ needs.set-version.outputs.version_file }} + prefix: ${{ needs.set-version.outputs.prefix }} + runs-on: ubuntu-latest-8-cores + steps: + - uses: actions/checkout@v3 + with: + lfs: true + + - name: Pull LFS objects + run: git lfs pull + + # Install Flutter + - uses: subosito/flutter-action@v2 + with: + flutter-version: "3.10.5" + channel: "stable" + - run: flutter --version + + - name: Setup Go + uses: actions/setup-go@v3 + with: + go-version: 1.19 + + - name: Granting private modules access + run: | + git config --global url."https://${{ secrets.GH_TOKEN }}:x-oauth-basic@github.com/".insteadOf "https://github.com/" + + - name: Build Lantern core Android library + run: make android-lib + + - name: Setup Sentry CLI + uses: mathieu-bour/setup-sentry-cli@v1 + with: + version: latest + token: ${{ SECRETS.SENTRY_TOKEN }} # from GitHub secrets + organization: getlantern + project: android + + - name: Setup JDK 11 + uses: actions/setup-java@v3 + with: + distribution: temurin + java-version: 11 + + - name: Setup protoc + uses: arduino/setup-protoc@v1.2.0 + with: + version: '3.x' + + - name: Activate protoc-gen-dart plugin + run: | + echo "${HOME}/.pub-cache/bin" >> $GITHUB_PATH + dart pub global activate protoc_plugin + mkdir -p "${HOME}/.pub-cache/bin" + mv "${FLUTTER_ROOT}/.pub-cache/bin/protoc-gen-dart" "${HOME}/.pub-cache/bin" + + - name: Set gradle properties + env: + GRADLE_PROPERTIES: ${{ secrets.GRADLE_PROPERTIES }} + run: | + mkdir -p ~/.gradle/ + echo "GRADLE_USER_HOME=${HOME}/.gradle" >> $GITHUB_ENV + echo "${GRADLE_PROPERTIES}" > ~/.gradle/gradle.properties + + - name: Decode Keystore + id: write_file + uses: timheuer/base64-to-file@v1.2 + with: + fileName: 'keystore.release.jks' + fileDir: './android/app' + encodedString: ${{ secrets.KEYSTORE }} + + - name: Build Android installers + run: make package-android + env: + INTERSTITIAL_AD_UNIT_ID: "${{ secrets.INTERSTITIAL_AD_UNIT_ID }}" + VERSION: "${{ env.version }}" + + - uses: actions/upload-artifact@v3 + with: + name: android-apk-build + retention-days: 2 + path: | + lantern-installer.apk + + - uses: actions/upload-artifact@v3 + with: + name: android-aab-build + retention-days: 2 + path: | + lantern-installer.aab + + - name: Setup S3cmd cli tool + uses: s3-actions/s3cmd@v1.4.0 + with: + provider: aws + region: ${{ secrets.AWS_REGION }} + access_key: ${{ secrets.AWS_ACCESS_KEY }} + secret_key: ${{ secrets.AWS_SECRET_KEY }} + + - name: Push binaries to s3 + env: + VERSION: "${{ env.version }}" + APK: "${{ env.prefix }}-${{ env.version }}.apk" + AAB: "${{ env.prefix }}-${{ env.version }}.aab" + run: | + mv lantern-installer.apk "$APK" + mv lantern-installer.aab "$AAB" + cp "$APK" ${{ env.prefix }}.apk + cp "$AAB" ${{ env.prefix }}.aab + echo ${{ env.version }} > ${{ env.version_file }} + shasum -a 256 "$APK" | cut -d " " -f 1 > "$APK".sha256 + shasum -a 256 "$AAB" | cut -d " " -f 1 > "$AAB".sha256 + cp "$APK".sha256 ${{ env.prefix }}.apk.sha256 + cp "$AAB".sha256 ${{ env.prefix }}.aab.sha256 + s3cmd put --acl-public "$APK" ${{ env.version_file }} "$APK".sha256 ${{ env.prefix }}.apk.sha256 ${{ env.prefix }}.apk "s3://$S3_BUCKET" + s3cmd put --acl-public "$AAB" "$AAB".sha256 ${{ env.prefix }}.aab.sha256 ${{ env.prefix }}.aab "s3://$S3_BUCKET" + s3cmd modify --add-header='content-type':'application/vnd.android.package-archive' "s3://$S3_BUCKET/$APK" + s3cmd modify --add-header='content-type':'application/vnd.android.package-archive' "s3://$S3_BUCKET/${{ env.prefix }}.apk" + s3cmd modify --add-header='content-type':'application/vnd.android.package-archive' "s3://$S3_BUCKET/$AAB" + s3cmd modify --add-header='content-type':'application/vnd.android.package-archive' "s3://$S3_BUCKET/${{ env.prefix }}.aab" + + push-binaries: + runs-on: ubuntu-latest + needs: [ set-version , build-android ] env: - VERSION: ${{ needs.set-version.outputs.version }} - run: make release-qa - - - name: Upload to S3 (Prod) - if: startsWith(github.ref, 'refs/tags/lantern') - env: - VERSION: ${{ needs.set-version.outputs.version }} - run: make release-prod - - - name: Upload Android Release to Play Store Alpha track - if: startsWith(github.ref, 'refs/tags/lantern') - uses: r0adkll/upload-google-play@v1 - with: - serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} - packageName: org.getlantern.lantern - releaseFiles: lantern-installer-internal.aab - track: alpha + version: ${{ needs.set-version.outputs.version }} + prefix: ${{ needs.set-version.outputs.prefix }} + steps: + - name: Download the apk build output + uses: actions/download-artifact@v3 + with: + name: android-apk-build + - name: Download the aab build output + uses: actions/download-artifact@v3 + with: + name: android-aab-build + - name: Upload Android App bundle to Play Store (beta) + if: needs.set-version.outputs.prefix == 'lantern-installer-preview' + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} + packageName: org.getlantern.lantern + releaseFiles: lantern-installer.aab + track: beta + - name: Upload Android App bundle to Play Store (production) + if: needs.set-version.outputs.prefix == 'lantern-installer' + uses: r0adkll/upload-google-play@v1 + with: + serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }} + packageName: org.getlantern.lantern + releaseFiles: lantern-installer.aab + track: production + - name: Grant private modules access + run: git config --global url."https://${{ secrets.GH_TOKEN }}:x-oauth-basic@github.com/".insteadOf "https://github.com/" + - name: Clone binaries repo + run: git clone --depth 1 https://github.com/getlantern/lantern-binaries + - name: Rename builds + run: | + diff lantern-installer.apk ${{ env.prefix }}.apk || mv -f lantern-installer.apk ${{ env.prefix }}.apk + diff lantern-installer.aab ${{ env.prefix }}.aab || mv -f lantern-installer.aab ${{ env.prefix }}.aab + - name: Prepare sha256 sums + run: | + shasum -a 256 ${{ env.prefix }}.apk | cut -d " " -f 1 > ${{ env.prefix }}.apk.sha256 + shasum -a 256 ${{ env.prefix }}.aab | cut -d " " -f 1 > ${{ env.prefix }}.aab.sha256 + - name: Commit + run: | + mv lantern-installer* ./lantern-binaries/ + cd lantern-binaries + git config user.email "admin@getlantern.org" + git config user.name "Lantern Bot" + git add . + git commit -m "Lantern binaries for version ${{ env.version }}" + git push origin main diff --git a/.gitignore b/.gitignore index a5f6e5b67..e47fba771 100644 --- a/.gitignore +++ b/.gitignore @@ -75,4 +75,5 @@ screenshots profile.cov appium_kotlin/app/src/test/resources/local/local_config.json -ios/internalsdk/Internalsdk.xcframework/ \ No newline at end of file +ios/internalsdk/Internalsdk.xcframework/ +android/app/libs/liblantern-all.aar diff --git a/Makefile b/Makefile index 9100bc33a..4adf1e59b 100644 --- a/Makefile +++ b/Makefile @@ -57,6 +57,7 @@ ADB := $(call get-command,adb) OPENSSL := $(call get-command,openssl) GMSAAS := $(call get-command,gmsaas) SENTRY := $(call get-command,sentry-cli) +BASE64 := $(call get-command,base64) GIT_REVISION_SHORTCODE := $(shell git rev-parse --short HEAD) GIT_REVISION := $(shell git describe --abbrev=0 --tags --exact-match 2> /dev/null || git rev-parse --short HEAD) @@ -67,7 +68,8 @@ BUILD_DATE := $(shell date -u +%Y%m%d.%H%M%S) # We explicitly set a build-id for use in the liblantern ELF binary so that Sentry can successfully associate uploaded debug symbols with corresponding errors/crashes BUILD_ID := 0x$(shell echo '$(REVISION_DATE)-$(BUILD_DATE)' | xxd -c 256 -ps) export CI -CIBASE := $(shell printf "CI=$$CI" | base64) +CIBASE := $(shell printf "CI=$${CI:-false}" | base64) + STAGING = false UPDATE_SERVER_URL ?= @@ -88,6 +90,13 @@ BINARIES_BRANCH ?= main BETA_BASE_NAME ?= $(INSTALLER_NAME)-preview PROD_BASE_NAME ?= $(INSTALLER_NAME) +## vault secrets +VAULT_DD_SECRETS_PATH ?= secret/apps/datadog/android +VAULT_ADS_SECRETS_PATH ?= secret/googleAds + +## vault keys +INTERSTITIAL_AD_UNIT_ID= INTERSTITIAL_AD_UNIT_ID + S3_BUCKET ?= lantern FORCE_PLAY_VERSION ?= false DEBUG_VERSION ?= $(GIT_REVISION) @@ -143,9 +152,9 @@ MOBILE_ANDROID_LIB := $(MOBILE_LIBS)/$(ANDROID_LIB) MOBILE_ANDROID_DEBUG := $(BASE_MOBILE_DIR)/build/app/outputs/apk/prod/debug/app-prod$(APK_QUALIFIER)-debug.apk MOBILE_ANDROID_RELEASE := $(BASE_MOBILE_DIR)/build/app/outputs/apk/prod/sideload/app-prod$(APK_QUALIFIER)-sideload.apk MOBILE_ANDROID_BUNDLE := $(BASE_MOBILE_DIR)/build/app/outputs/bundle/prodPlay/app-prod$(APK_QUALIFIER)-play.aab -MOBILE_RELEASE_APK := $(INSTALLER_NAME)-$(ANDROID_ARCH).apk +MOBILE_RELEASE_APK := $(INSTALLER_NAME).apk MOBILE_DEBUG_APK := $(INSTALLER_NAME)-$(ANDROID_ARCH)-debug.apk -MOBILE_BUNDLE := lantern-$(ANDROID_ARCH).aab +MOBILE_BUNDLE := $(INSTALLER_NAME).aab MOBILE_TEST_APK := $(BASE_MOBILE_DIR)/build/app/outputs/apk/androidTest/autoTest/debug/app-autoTest-debug-androidTest.apk MOBILE_TESTS_APK := $(BASE_MOBILE_DIR)/build/app/outputs/apk/autoTest/debug/app-autoTest-debug.apk @@ -236,93 +245,6 @@ require-magick: require-sentry: @if [[ -z "$(SENTRY)" ]]; then echo 'Missing "sentry-cli" command. See sentry.io for installation instructions.'; exit 1; fi -release-qa: require-version require-s3cmd - @BASE_NAME="$(INSTALLER_NAME)-internal" && \ - VERSION_FILE_NAME="version-qa-android.txt" && \ - rm -f $$BASE_NAME* && \ - cp $(INSTALLER_NAME)-arm32.apk $$BASE_NAME.apk && \ - cp lantern-all.aab $$BASE_NAME.aab && \ - echo "Uploading installer packages and shasums" && \ - for NAME in $$(ls -1 $$BASE_NAME*.*); do \ - shasum -a 256 $$NAME | cut -d " " -f 1 > $$NAME.sha256 && \ - echo "Uploading SHA-256 `cat $$NAME.sha256`" && \ - s3cmd put -P $$NAME.sha256 s3://$(S3_BUCKET) && \ - echo "Uploading $$NAME to S3" && \ - s3cmd put -P $$NAME s3://$(S3_BUCKET) && \ - SUFFIX=$$(echo "$$NAME" | sed s/$$BASE_NAME//g) && \ - VERSIONED=$(INSTALLER_NAME)-$$VERSION$$SUFFIX && \ - echo "Copying $$VERSIONED" && \ - s3cmd cp s3://$(S3_BUCKET)/$$NAME s3://$(S3_BUCKET)/$$VERSIONED && \ - echo "Copied $$VERSIONED ... setting acl to public" && \ - s3cmd setacl s3://$(S3_BUCKET)/$$VERSIONED --acl-public; \ - done && \ - echo "Setting content types for installer packages" && \ - for NAME in $$BASE_NAME.apk $(INSTALLER_NAME)-$$VERSION.apk $$BASE_NAME.aab ; do \ - s3cmd modify --add-header='content-type':'application/vnd.android.package-archive' s3://$(S3_BUCKET)/$$NAME; \ - done && \ - for NAME in update_android_arm ; do \ - cp lantern_$$NAME.bz2 lantern_$$NAME-$$VERSION.bz2 && \ - echo "Copying versioned name lantern_$$NAME-$$VERSION.bz2..." && \ - s3cmd put -P lantern_$$NAME-$$VERSION.bz2 s3://$(S3_BUCKET); \ - done && \ - echo $$VERSION > $$VERSION_FILE_NAME && \ - s3cmd put -P $$VERSION_FILE_NAME s3://$(S3_BUCKET) && \ - echo "Wrote $$VERSION_FILE_NAME as $$(wget -qO - http://$(S3_BUCKET).s3.amazonaws.com/$$VERSION_FILE_NAME)" - -release-beta: require-s3cmd - @BASE_NAME="$(INSTALLER_NAME)-internal" && \ - VERSION_FILE_NAME="version-beta-android.txt" && \ - cd $(BINARIES_PATH) && \ - git pull && \ - cd - && \ - for URL in s3://lantern/$$BASE_NAME.apk s3://lantern/$$BASE_NAME.aab; do \ - NAME=$$(basename $$URL) && \ - BETA=$$(echo $$NAME | sed s/"$$BASE_NAME"/$(BETA_BASE_NAME)/) && \ - s3cmd cp s3://$(S3_BUCKET)/$$NAME s3://$(S3_BUCKET)/$$BETA && \ - s3cmd setacl s3://$(S3_BUCKET)/$$BETA --acl-public && \ - s3cmd get --force s3://$(S3_BUCKET)/$$NAME $(BINARIES_PATH)/$$BETA; \ - done && \ - s3cmd cp s3://$(S3_BUCKET)/version-qa-android.txt s3://$(S3_BUCKET)/$$VERSION_FILE_NAME && \ - s3cmd setacl s3://$(S3_BUCKET)/$$VERSION_FILE_NAME --acl-public && \ - echo "$$VERSION_FILE_NAME is now set to $$(wget -qO - http://$(S3_BUCKET).s3.amazonaws.com/$$VERSION_FILE_NAME)" && \ - cd $(BINARIES_PATH) && \ - git add $(BETA_BASE_NAME)* && \ - (git commit -am "Latest lantern android beta binaries released from QA." && git push origin $(BINARIES_BRANCH)) || true - -release-prod: require-version require-s3cmd require-wget require-lantern-binaries require-magick - @TAG_COMMIT=$$(git rev-list --abbrev-commit -1 $(TAG)) && \ - if [[ -z "$$TAG_COMMIT" ]]; then \ - echo "Could not find given tag $(TAG)."; \ - fi && \ - PROD_BASE_NAME2="$(INSTALLER_NAME)-beta" && \ - VERSION_FILE_NAME="version-android.txt" && \ - for URL in s3://lantern/$(BETA_BASE_NAME).apk s3://lantern/$(BETA_BASE_NAME).aab; do \ - NAME=$$(basename $$URL) && \ - PROD=$$(echo $$NAME | sed s/"$(BETA_BASE_NAME)"/$(PROD_BASE_NAME)/) && \ - PROD2=$$(echo $$NAME | sed s/"$(BETA_BASE_NAME)"/$$PROD_BASE_NAME2/) && \ - s3cmd cp s3://$(S3_BUCKET)/$$NAME s3://$(S3_BUCKET)/$$PROD && \ - s3cmd setacl s3://$(S3_BUCKET)/$$PROD --acl-public && \ - s3cmd cp s3://$(S3_BUCKET)/$$NAME s3://$(S3_BUCKET)/$$PROD2 && \ - s3cmd setacl s3://$(S3_BUCKET)/$$PROD2 --acl-public && \ - echo "Downloading released binary to $(BINARIES_PATH)/$$PROD" && \ - s3cmd get --force s3://$(S3_BUCKET)/$$PROD $(BINARIES_PATH)/$$PROD && \ - cp $(BINARIES_PATH)/$$PROD $(BINARIES_PATH)/$$PROD2; \ - done && \ - s3cmd cp s3://$(S3_BUCKET)/version-beta.txt s3://$(S3_BUCKET)/$$VERSION_FILE_NAME && \ - s3cmd setacl s3://$(S3_BUCKET)/$$VERSION_FILE_NAME --acl-public && \ - echo "$$VERSION_FILE_NAME is now set to $$(wget -qO - http://$(S3_BUCKET).s3.amazonaws.com/$$VERSION_FILE_NAME)" && \ - echo "Uploading released binaries to $(BINARIES_PATH)" - @cd $(BINARIES_PATH) && \ - git checkout $(BINARIES_BRANCH) && \ - git pull && \ - git add $(PROD_BASE_NAME)* && \ - echo -n $$VERSION | $(MAGICK) -font Helvetica -pointsize 30 -size 68x24 label:@- -transparent white version.png && \ - (COMMIT_MESSAGE="Latest binaries for Lantern $$VERSION ($$TAG_COMMIT)." && \ - git add . && \ - git commit -m "$$COMMIT_MESSAGE" && \ - git push origin $(BINARIES_BRANCH) \ - ) || true - release-autoupdate: require-version @TAG_COMMIT=$$(git rev-list --abbrev-commit -1 $(TAG)) && \ if [[ -z "$$TAG_COMMIT" ]]; then \ @@ -357,14 +279,6 @@ $(MOBILE_ANDROID_LIB): $(ANDROID_LIB) .PHONY: android-lib android-lib: $(MOBILE_ANDROID_LIB) -# TODO: The below don't work when doing full builds, but we should indeed make debug builds unstripped and unoptimized. -# .PHONY: android-lib-debug -# android-lib-debug: export GOMOBILE_EXTRA_BUILD_FLAGS += $(DISABLE_OPTIMIZATION_FLAGS) -# android-lib-debug: $(MOBILE_ANDROID_LIB) - -# .PHONY: android-lib-prod -# android-lib-prod: export LDFLAGS += $(LD_STRIP_FLAGS) -# android-lib-prod: $(MOBILE_ANDROID_LIB) $(MOBILE_TEST_APK) $(MOBILE_TESTS_APK): $(MOBILE_SOURCES) $(MOBILE_ANDROID_LIB) @$(GRADLE) -PandroidArch=$(ANDROID_ARCH) \ @@ -372,15 +286,26 @@ $(MOBILE_TEST_APK) $(MOBILE_TESTS_APK): $(MOBILE_SOURCES) $(MOBILE_ANDROID_LIB) -b $(MOBILE_DIR)/app/build.gradle \ :app:assembleAutoTestDebug :app:assembleAutoTestDebugAndroidTest +vault-secret: + @SECRET=$(shell cd $$GOPATH/src/github.com/getlantern/lantern-cloud && bin/vault kv get -field=$(VAULT_FIELD) $(VAULT_PATH)); \ + echo "Retrieved secret: $$SECRET" 1>&2; \ + printf "$$VAULT_FIELD=$$SECRET" | ${BASE64} + +dart-defines-debug: + @DART_DEFINES=$(shell make vault-secret VAULT_FIELD=INTERSTITIAL_AD_UNIT_ID VAULT_PATH=secret/googleAds); \ + DART_DEFINES+=",$(CIBASE)"; \ + echo "$$DART_DEFINES" + do-android-debug: $(MOBILE_SOURCES) $(MOBILE_ANDROID_LIB) @ln -fs $(MOBILE_DIR)/gradle.properties . && \ + DART_DEFINES=`make dart-defines-debug` && \ COUNTRY="$$COUNTRY" && \ PAYMENT_PROVIDER="$$PAYMENT_PROVIDER" && \ STAGING="$$STAGING" && \ STICKY_CONFIG="$$STICKY_CONFIG" && \ CI="$$CI" && \ - echo "Base64 CI: $(CIBASE)" && \ - $(GRADLE) -PlanternVersion=$(DEBUG_VERSION) -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) -PlanternRevisionDate=$(REVISION_DATE) -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -Pdart-defines="$(CIBASE)" -PdevelopmentMode="true" -Pci=$(CI) -b $(MOBILE_DIR)/app/build.gradle \ + echo "DART_DEFINES values: $$DART_DEFINES" && \ + $(GRADLE) -PlanternVersion=$(DEBUG_VERSION) -Pdart-defines="$$DART_DEFINES" -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) -PlanternRevisionDate=$(REVISION_DATE) -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -PdevelopmentMode="true" -Pci=$(CI) -b $(MOBILE_DIR)/app/build.gradle \ assembleProdDebug pubget: @@ -391,6 +316,15 @@ $(MOBILE_DEBUG_APK): $(MOBILE_SOURCES) $(GO_SOURCES) make do-android-debug && \ cp $(MOBILE_ANDROID_DEBUG) $(MOBILE_DEBUG_APK) +env-secret-%: + @SECRET=$(shell echo "$(${*})"); \ + printf ${*}=$$SECRET | ${BASE64} + +dart-defines-release: + @DART_DEFINES=`make env-secret-INTERSTITIAL_AD_UNIT_ID`; \ + DART_DEFINES+=`printf ',' && $(CIBASE)`; \ + printf $$DART_DEFINES + $(MOBILE_RELEASE_APK): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-sentry echo $(MOBILE_ANDROID_LIB) && \ mkdir -p ~/.gradle && \ @@ -401,12 +335,14 @@ $(MOBILE_RELEASE_APK): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) req PAYMENT_PROVIDER="$$PAYMENT_PROVIDER" && \ VERSION_CODE="$$VERSION_CODE" && \ DEVELOPMENT_MODE="$$DEVELOPMENT_MODE" && \ - $(GRADLE) -PlanternVersion=$$VERSION -PlanternRevisionDate=$(REVISION_DATE) -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) -PversionCode=$(VERSION_CODE) -PdevelopmentMode=$(DEVELOPMENT_MODE) -b $(MOBILE_DIR)/app/build.gradle \ + DART_DEFINES=`make dart-defines-release` && \ + $(GRADLE) -PlanternVersion=$$VERSION -PlanternRevisionDate=$(REVISION_DATE) -Pdart-defines="$$DART_DEFINES" -PandroidArch=$(ANDROID_ARCH) -PandroidArchJava="$(ANDROID_ARCH_JAVA)" -PproServerUrl=$(PRO_SERVER_URL) -PpaymentProvider=$(PAYMENT_PROVIDER) -Pcountry=$(COUNTRY) -PplayVersion=$(FORCE_PLAY_VERSION) -PuseStaging=$(STAGING) -PstickyConfig=$(STICKY_CONFIG) -PversionCode=$(VERSION_CODE) -PdevelopmentMode=$(DEVELOPMENT_MODE) -b $(MOBILE_DIR)/app/build.gradle \ assembleProdSideload && \ sentry-cli upload-dif --wait -o getlantern -p android build/app/intermediates/merged_native_libs/prodSideload/out/lib && \ cp $(MOBILE_ANDROID_RELEASE) $(MOBILE_RELEASE_APK) && \ cat $(MOBILE_RELEASE_APK) | bzip2 > lantern_update_android_arm.bz2 + $(MOBILE_BUNDLE): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require-sentry @mkdir -p ~/.gradle && \ ln -fs $(MOBILE_DIR)/gradle.properties . && \ @@ -419,6 +355,7 @@ $(MOBILE_BUNDLE): $(MOBILE_SOURCES) $(GO_SOURCES) $(MOBILE_ANDROID_LIB) require- sentry-cli upload-dif --wait -o getlantern -p android build/app/intermediates/merged_native_libs/prodPlay/out/lib && \ cp $(MOBILE_ANDROID_BUNDLE) $(MOBILE_BUNDLE) + android-debug: $(MOBILE_DEBUG_APK) android-release: pubget $(MOBILE_RELEASE_APK) @@ -432,7 +369,7 @@ android-release-install: $(MOBILE_RELEASE_APK) $(ADB) install -r $(MOBILE_RELEASE_APK) package-android: require-version clean - @make pubget android-release && \ + @ANDROID_ARCH=all make android-release && \ ANDROID_ARCH=all make android-bundle && \ echo "-> $(MOBILE_RELEASE_APK)" diff --git a/README.md b/README.md index c009e6428..6af90a2a8 100644 --- a/README.md +++ b/README.md @@ -151,45 +151,25 @@ Do this to make a release build: ### Building release packages -To build all release packages, run: +Lantern Android release packages are built in CI. You can build installers for beta, for production, or for internal testing by varying the tag syntax. To build for production, you simply tag the current version on `main` with, for example: -``` -VERSION= SECRETS_DIR=$PATH_TO_TOO_MANY_SECRETS make package-android -``` - -### Deploying a release to QA - -``` -VERSION= make release-qa -``` - -### Deploying a release to Beta - -``` -VERSION= make release-beta -``` +`git tag -a "lantern-7.0.0" -f -m "Tagging production release"` -### Deploying a release to Prod +To build a beta, simply include "beta" in the tag, as in: -First, tag the release. +`git tag -a "lantern-7.0.0-beta" -f -m "Tagging beta release"` -``` -VERSION= make tag -``` +Finally, to create an internal build, use "internal", as in: -If you want to tag a specific revision, you can use +`git tag -a "lantern-7.0.0-internal" -f -m "Tagging internal release"` -``` -VERSION= make tag TAG_HEAD= -``` +For all of the above, don't forget to push the tags: -Then, release the sideload installers to production. +`git push --tags -f` -``` -VERSION= make release-prod -``` +You can then find all built binaries in the [lantern-binaries repository](https://github.com/getlantern/lantern-binaries). -Then, go to the Lantern App on the [Google Play Console](https://play.google.com/console/u/0/developers/4642290275832863621/app/4973965144252805146/app-dashboard?timespan=thirtyDays) and create a new release using the [app bundle](lantern-all.aab). +To publish a release on Google Play, go to the Lantern App on the [Google Play Console](https://play.google.com/console/u/0/developers/4642290275832863621/app/4973965144252805146/app-dashboard?timespan=thirtyDays) and create a new release using the [app bundle](lantern-installer.aab). ### Enabling Auto-Update for a Sideloaded Release diff --git a/analysis_options.yaml b/analysis_options.yaml index b23b73421..25fdbcbf5 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,6 +1,9 @@ -include: package:pedantic/analysis_options.yaml +include: package:flutter_lints/flutter.yaml linter: rules: - prefer_const_constructors - - require_trailing_commas \ No newline at end of file + - require_trailing_commas + - avoid_unused_constructor_parameters + - unnecessary_statements + - avoid_unnecessary_containers diff --git a/android/app/build.gradle b/android/app/build.gradle index 73b4e9383..083bda981 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -381,47 +381,36 @@ dependencies { implementation "com.google.protobuf:protobuf-javalite:$protoc_version" implementation fileTree(dir: "libs", include:"liblantern-${androidArch()}.aar") - - implementation(name:'paymentwall-android-sdk', ext: 'aar') - - implementation(name:'alipayadapter-release', ext: 'aar') { - transitive = true - } + // implementation(name:"liblantern-${androidArch()}", ext:'aar') // https://mvnrepository.com/artifact/net.jodah/expiringmap implementation group: 'net.jodah', name: 'expiringmap', version: '0.5.9' - implementation 'com.ogaclejapan.smarttablayout:library:1.6.1@aar' - implementation 'com.ogaclejapan.smarttablayout:utils-v4:1.6.1@aar' - implementation fileTree(dir: 'libs', include: '*.jar') implementation 'com.squareup.okhttp3:okhttp:4.9.2' - implementation "androidx.webkit:webkit:1.6.1" + implementation "androidx.webkit:webkit:1.7.0" implementation 'com.kyleduo.switchbutton:library:2.1.0' - implementation group: 'com.google.code.gson', name: 'gson', version: '2.8.6' - + implementation group: 'com.google.code.gson', name: 'gson', version: '2.10.1' // https://mvnrepository.com/artifact/joda-time/joda-time implementation group: 'joda-time', name: 'joda-time', version: '2.8.2' - debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10' implementation 'androidx.multidex:multidex:2.0.1' - implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.0' + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.6.1' - implementation 'androidx.core:core-ktx:1.10.0' + implementation 'androidx.core:core-ktx:1.10.1' implementation 'androidx.legacy:legacy-support-v4:1.0.0' implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.3.0' + implementation 'com.google.android.material:material:1.9.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - implementation 'androidx.recyclerview:recyclerview:1.3.0' // Google Play libraries - implementation 'com.android.billingclient:billing:5.0.0' + implementation 'com.android.billingclient:billing:6.0.1' implementation 'com.google.android.gms:play-services-base:18.2.0' // lib that simplifies event bus communication between activities, fragments, threads, services, etc @@ -432,22 +421,15 @@ dependencies { // https://mvnrepository.com/artifact/javax.mail/mail implementation group: 'javax.mail', name: 'mail', version: '1.4.7' - implementation (group: 'com.google.guava', name: 'guava', version: '23.0-android') { - exclude group: 'com.google.code.findbugs', module: 'jsr305' - } - implementation 'com.stripe:stripe-android:20.17.0' - implementation 'com.github.bumptech.glide:glide:4.9.0' - annotationProcessor "org.androidannotations:androidannotations:$androidAnnotationsVersion" - kapt 'com.github.bumptech.glide:compiler:4.7.1' implementation("org.androidannotations:androidannotations-api:$androidAnnotationsVersion") kapt "org.androidannotations:androidannotations:$androidAnnotationsVersion" - androidTestImplementation 'androidx.test:rules:1.2.0' + androidTestImplementation 'androidx.test:rules:1.5.0' androidTestImplementation 'androidx.annotation:annotation:1.6.0' - androidTestImplementation 'androidx.test:runner:1.2.0' + androidTestImplementation 'androidx.test:runner:1.5.2' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' androidTestImplementation 'androidx.test.espresso:espresso-idling-resource:3.5.1' androidTestImplementation 'androidx.test.ext:junit:1.1.2' @@ -456,8 +438,7 @@ dependencies { androidTestImplementation 'org.hamcrest:hamcrest-library:1.3' androidTestImplementation 'com.squareup.okhttp3:okhttp:4.9.2' - testImplementation 'junit:junit:4.13.1' - + testImplementation 'junit:junit:4.13.2' implementation "com.github.YarikSOffice:lingver:1.3.0" // from https://github.com/getlantern/opus_android @@ -465,6 +446,7 @@ dependencies { implementation 'com.github.getlantern:secrets-android:f6a7a69f3d' implementation 'com.github.getlantern:messaging-android:1ce4613c8b' implementation 'com.github.getlantern:db-android:573aba1458' + implementation 'com.cleveradssolutions:cas:3.2.4' } apply plugin: 'com.google.gms.google-services' diff --git a/android/app/libs/liblantern-all.aar b/android/app/libs/liblantern-all.aar deleted file mode 100644 index b98826e3c..000000000 --- a/android/app/libs/liblantern-all.aar +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:7ddff221d494a122c022611efb03f849205e0e626c8d3253f1c4d5b34f87890d -size 77995027 diff --git a/android/app/proguard-rules.pro b/android/app/proguard-rules.pro index a28e35cec..7a4e2fd76 100644 --- a/android/app/proguard-rules.pro +++ b/android/app/proguard-rules.pro @@ -33,12 +33,6 @@ -keep class org.getlantern.** { *; } -keep class io.lantern.** { *; } -# PW --keep class com.paymentwall.alipayadapter.** { *; } - -# Optimizely --keep class com.optimizely.ab.** { *; } - # Gson -keepnames class com.google.gson.Gson diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e3c95e786..f95e8b487 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -78,11 +78,6 @@ - - - @@ -94,28 +89,11 @@ android:noHistory="true"> - - - - - - - - - - @@ -162,5 +138,9 @@ + + diff --git a/android/app/src/main/java/org/getlantern/lantern/Navigator.kt b/android/app/src/main/java/org/getlantern/lantern/Navigator.kt deleted file mode 100644 index 6df0f0ebc..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/Navigator.kt +++ /dev/null @@ -1,85 +0,0 @@ -package org.getlantern.lantern - -import android.app.Activity -import android.app.AlarmManager -import android.app.PendingIntent -import android.content.Context -import android.content.Intent -import android.os.Process -import io.flutter.embedding.engine.FlutterEngine -import io.flutter.plugin.common.MethodCall -import io.flutter.plugin.common.MethodChannel -import org.getlantern.lantern.activity.DesktopActivity_ -import org.getlantern.lantern.activity.InviteActivity_ -import org.getlantern.lantern.activity.authorizeDevice.LinkDeviceActivity_ -import org.getlantern.mobilesdk.activity.ReportIssueActivity - -class Navigator( - private val activity: Activity, - flutterEngine: FlutterEngine? = null -) : MethodChannel.MethodCallHandler { - - companion object { - const val SCREEN_INVITE_FRIEND = "SCREEN_INVITE_FRIEND" - const val SCREEN_DESKTOP_VERSION = "SCREEN_DESKTOP_VERSION" - const val SCREEN_LINK_PIN = "SCREEN_LINK_PIN" - const val SCREEN_SCREEN_REPORT_ISSUE = "SCREEN_SCREEN_REPORT_ISSUE" - const val SCREEN_UPGRADE_TO_LANTERN_PRO = "SCREEN_UPGRADE_TO_LANTERN_PRO" - } - - init { - flutterEngine?.let { - MethodChannel( - flutterEngine.dartExecutor.binaryMessenger, - "navigator_method_channel" - ).setMethodCallHandler(this) - } - } - - override fun onMethodCall(call: MethodCall, result: MethodChannel.Result) { - when (call.method) { - "startScreen" -> { - val screenName = call.argument("screenName")!! - val activityClass = toActivityClass(screenName) - activityClass?.let { - activity.startActivity(Intent(activity, activityClass)) - result.success(true) - } ?: result.success(false) - } - } - } - - private fun toActivityClass(screenName: String): Class<*>? { - return when (screenName) { - SCREEN_INVITE_FRIEND -> InviteActivity_::class.java - SCREEN_DESKTOP_VERSION -> DesktopActivity_::class.java - SCREEN_LINK_PIN -> LinkDeviceActivity_::class.java - SCREEN_SCREEN_REPORT_ISSUE -> ReportIssueActivity::class.java - else -> null - } - } -} - -fun Activity.openHome() { - startActivity( - Intent(this, MainActivity::class.java) - .apply { - addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) - } - ) - overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) -} - -fun Activity.restartApp() { - val mStartActivity = Intent(this, MainActivity::class.java) - val mPendingIntentId = 123456 - val mPendingIntent: PendingIntent = PendingIntent.getActivity( - this, - mPendingIntentId, - mStartActivity, - PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE - ) - val mgr: AlarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager - mgr.set(AlarmManager.RTC, java.lang.System.currentTimeMillis() + 100, mPendingIntent) - Process.killProcess(Process.myPid()) -} diff --git a/android/app/src/main/java/org/getlantern/lantern/activity/DesktopActivity.java b/android/app/src/main/java/org/getlantern/lantern/activity/DesktopActivity.java deleted file mode 100644 index 852ed22ce..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/activity/DesktopActivity.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.getlantern.lantern.activity; - -import androidx.fragment.app.FragmentActivity; - -import org.androidannotations.annotations.Click; -import org.androidannotations.annotations.EActivity; -import org.getlantern.lantern.R; -import org.getlantern.lantern.util.IntentUtil; - -@EActivity(R.layout.desktop_option) -public class DesktopActivity extends BaseFragmentActivity { - @Click - void btnShare() { - IntentUtil.INSTANCE.sharePlainText( - this, - getString(R.string.lantern_desktop_link), - getString(R.string.lantern_desktop_link_share_title) - ); - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/activity/InviteActivity.java b/android/app/src/main/java/org/getlantern/lantern/activity/InviteActivity.java deleted file mode 100644 index bcb7b19e5..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/activity/InviteActivity.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.getlantern.lantern.activity; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.content.res.Resources; -import android.os.Bundle; -import android.os.Handler; -import android.view.View; -import android.widget.ImageView; -import android.widget.TextView; - -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentActivity; - -import org.androidannotations.annotations.AfterViews; -import org.androidannotations.annotations.Click; -import org.androidannotations.annotations.EActivity; -import org.androidannotations.annotations.ViewById; -import org.getlantern.lantern.LanternApp; -import org.getlantern.lantern.R; -import org.getlantern.lantern.fragment.ProgressDialogFragment; -import org.getlantern.lantern.model.Utils; -import org.getlantern.lantern.util.IntentUtil; -import org.getlantern.mobilesdk.Logger; - -@EActivity(R.layout.invite_friends) -public class InviteActivity extends BaseFragmentActivity { - - private static final String TAG = InviteActivity.class.getName(); - - private ProgressDialogFragment progressFragment; - private Resources resources; - private String code; - - @ViewById - TextView referralCode; - - @ViewById - ImageView imgvCopy; - - @ViewById - ImageView imgvChecked; - - @ViewById - View bgText; - - private Handler handlerCopyAnim; - - @AfterViews - void afterViews() { - imgvChecked.setAlpha(0f); - bgText.setAlpha(0f); - resources = getResources(); - progressFragment = ProgressDialogFragment.newInstance(R.string.progressMessage2); - } - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - handlerCopyAnim = new Handler(); - } - - @Override - protected void onResume() { - super.onResume(); - this.code = LanternApp.getSession().code(); - Logger.debug(TAG, "referral code is " + this.code); - referralCode.setText(this.code); - } - - @Override - protected void onDestroy() { - handlerCopyAnim.removeCallbacksAndMessages(null); - super.onDestroy(); - } - - private void startProgress() { - progressFragment.show(getSupportFragmentManager(), "progress"); - } - - private void finishProgress() { - progressFragment.dismiss(); - } - - @Click(R.id.imgvCopy) - void referralCodeClicked() { - handlerCopyAnim.removeCallbacksAndMessages(null); - // animate when click the button - long animDuration = 300L; - long delayDuration = 1000L; - imgvCopy.animate().alpha(0f).setDuration(animDuration).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationStart(Animator animation) { - imgvCopy.setClickable(false); - } - }).start(); - bgText.animate().alpha(1f).setDuration(animDuration).start(); - imgvChecked.animate().alpha(1f).setDuration(animDuration).start(); - - handlerCopyAnim.postDelayed(() -> { - imgvCopy.animate().alpha(1f).setDuration(animDuration).setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - imgvCopy.setClickable(true); - } - }).start(); - imgvChecked.animate().alpha(0f).setDuration(animDuration).start(); - bgText.animate().alpha(0f).setDuration(animDuration).start(); - }, delayDuration); - - final CharSequence referralText = referralCode.getText(); - - if (referralText == null) { - return; - } - - Utils.copyToClipboard(this, - getString(R.string.referral_code), - referralText.toString()); - } - - @Click - void btnShare() { - IntentUtil.INSTANCE.sharePlainText( - this, - String.format(resources.getString(R.string.receive_free_month), this.code), - getString(R.string.referral_code_share_title) - ); - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/activity/PrivacyDisclosureActivity.java b/android/app/src/main/java/org/getlantern/lantern/activity/PrivacyDisclosureActivity.java deleted file mode 100644 index 8acec27ba..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/activity/PrivacyDisclosureActivity.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.getlantern.lantern.activity; - -import android.view.View; - -import androidx.fragment.app.FragmentActivity; - -import org.androidannotations.annotations.EActivity; -import org.getlantern.lantern.LanternApp; -import org.getlantern.lantern.R; -import org.getlantern.mobilesdk.Logger; - -@EActivity(R.layout.privacy_disclosure) -public class PrivacyDisclosureActivity extends BaseFragmentActivity { - private static final String TAG = PrivacyDisclosureActivity.class.getName(); - - public void acceptTerms(View view) { - Logger.debug(TAG, "Accepted privacy disclosure"); - LanternApp.getSession().acceptTerms(); - finish(); - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/activity/authorizeDevice/LinkDeviceActivity.java b/android/app/src/main/java/org/getlantern/lantern/activity/authorizeDevice/LinkDeviceActivity.java deleted file mode 100644 index 2077721ef..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/activity/authorizeDevice/LinkDeviceActivity.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.getlantern.lantern.activity.authorizeDevice; - -import android.content.Context; -import android.content.Intent; -import android.content.res.Resources; -import android.os.Handler; -import android.widget.TextView; - -import androidx.fragment.app.FragmentActivity; - -import com.google.gson.JsonObject; - -import org.androidannotations.annotations.AfterViews; -import org.androidannotations.annotations.EActivity; -import org.androidannotations.annotations.ViewById; -import org.getlantern.lantern.LanternApp; -import org.getlantern.lantern.MainActivity; -import org.getlantern.lantern.R; -import org.getlantern.lantern.activity.BaseFragmentActivity; -import org.getlantern.lantern.model.LanternHttpClient; -import org.getlantern.lantern.model.ProError; -import org.getlantern.lantern.util.ActivityExtKt; -import org.getlantern.mobilesdk.Logger; - -import java.lang.ref.WeakReference; - -import okhttp3.FormBody; -import okhttp3.RequestBody; -import okhttp3.Response; - -@EActivity(R.layout.activity_link_device) -public class LinkDeviceActivity extends BaseFragmentActivity { - - private static final String TAG = LinkDeviceActivity.class.getName(); - private static final LanternHttpClient lanternClient = LanternApp.getLanternHttpClient(); - private static int maxRedeemCalls = 20; - private static int initialDelay = 10000; // 10 seconds - private static int retryDelay = 5000; // 5 seconds - - private final Handler linkDeviceHandler = new Handler(); - private int redeemCalls = 0; - - @ViewById - TextView deviceLinkingCode; - - @AfterViews - void afterViews() { - setDeviceCode(LanternApp.getSession().deviceCode()); - } - - private void requestLinkCode() { - final Resources res = getResources(); - final LinkDeviceActivity activity = this; - final RequestBody formBody = new FormBody.Builder() - .add("deviceName", LanternApp.getSession().deviceName()) - .build(); - lanternClient.post(LanternHttpClient.createProUrl("/link-code-request"), formBody, - new LanternHttpClient.ProCallback() { - @Override - public void onFailure(final Throwable throwable, final ProError error) { - Logger.error(TAG, "Error retrieving link code: " + error); - ActivityExtKt.showErrorDialog(activity, - res.getString(R.string.error_device_code)); - } - - @Override - public void onSuccess(final Response response, final JsonObject result) { - Logger.debug(TAG, "Result: " + result); - if (result.get("code") != null) { - final String code = result.get("code").getAsString(); - final Long expireAt = result.get("expireAt").getAsLong(); - LanternApp.getSession().setDeviceCode(code, expireAt); - runOnUiThread(new Runnable() { - @Override - public void run() { - setDeviceCode(code); - } - }); - redeemLinkCode(initialDelay); - } - } - }); - } - - @Override - protected void onResume() { - super.onResume(); - Long codeExp = LanternApp.getSession().getDeviceExp(); - if (codeExp == null || ((codeExp.longValue() - System.currentTimeMillis()) < 60 * 1000)) { - requestLinkCode(); - } else { - redeemLinkCode(initialDelay); - } - } - - private void setDeviceCode(String code) { - if (code != null && !code.equals("")) - deviceLinkingCode.setText(code); - } - - private void redeemLinkCode(Integer delay) { - linkDeviceHandler.postDelayed(new RedeemLinkCode(this), delay); - this.redeemCalls++; - } - - private void retry() { - if (redeemCalls >= maxRedeemCalls) { - Logger.debug(TAG, "Reached max tries attempting to link device.."); - finish(); - return; - } - redeemLinkCode(retryDelay); - } - - private static final class RedeemLinkCode implements Runnable { - private final WeakReference activity; - - protected RedeemLinkCode(LinkDeviceActivity activity) { - this.activity = new WeakReference(activity); - } - - @Override - public void run() { - final LinkDeviceActivity a = activity.get(); - if (a != null) { - final RequestBody formBody = new FormBody.Builder() - .add("code", LanternApp.getSession().deviceCode()) - .add("deviceName", LanternApp.getSession().deviceName()) - .build(); - - lanternClient.post(LanternHttpClient.createProUrl("/link-code-redeem"), - formBody, new LanternHttpClient.ProCallback() { - @Override - public void onFailure(final Throwable throwable, final ProError error) { - Logger.error(TAG, "Error making link redeem request..", throwable); - a.retry(); - } - - @Override - public void onSuccess(final Response response, final JsonObject result) { - if (result.get("token") == null || result.get("userID") == null) { - a.retry(); - return; - } - Logger.debug(TAG, "Successfully redeemed link code"); - final Long userID = result.get("userID").getAsLong(); - final String token = result.get("token").getAsString(); - LanternApp.getSession().setUserIdAndToken(userID, token); - Logger.debug(TAG, "Linked device to user " + userID + " whose token is " + token); - - LanternApp.getSession().linkDevice(); - LanternApp.getSession().setIsProUser(true); - - try { - final Context context = a.getApplicationContext(); - final Intent intent = new Intent(a, MainActivity.class); - intent.putExtra("snackbarMsg", - context.getResources().getString(R.string.device_now_linked)); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); - a.startActivity(intent); - } catch (Exception e) { - Logger.error(TAG, "Unable to resume main activity", e); - } - } - }); - } - } - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/fragment/ProgressDialogFragment.java b/android/app/src/main/java/org/getlantern/lantern/fragment/ProgressDialogFragment.java deleted file mode 100644 index cc8790f4d..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/fragment/ProgressDialogFragment.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.getlantern.lantern.fragment; - -import android.app.Dialog; -import android.app.ProgressDialog; -import android.os.Bundle; -import androidx.annotation.NonNull; -import androidx.fragment.app.DialogFragment; - -public class ProgressDialogFragment extends DialogFragment { - public static ProgressDialogFragment newInstance(int msgId) { - ProgressDialogFragment fragment = new ProgressDialogFragment(); - - Bundle args = new Bundle(); - args.putInt("msgId", msgId); - - fragment.setArguments(args); - - return fragment; - } - - public ProgressDialogFragment() { - // Empty constructor required for DialogFragment - } - - @NonNull - @Override - public Dialog onCreateDialog(Bundle savedInstanceState) { - int msgId = getArguments().getInt("msgId"); - ProgressDialog dialog = new ProgressDialog(getActivity()); - dialog.setMessage(getActivity().getResources().getString(msgId)); - return dialog; - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/AccountInitializationStatus.java b/android/app/src/main/java/org/getlantern/lantern/model/AccountInitializationStatus.java deleted file mode 100644 index a417c2dd1..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/AccountInitializationStatus.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.getlantern.lantern.model; - -public class AccountInitializationStatus { - - private AccountInitializationStatus.Status status; - - public enum Status { - PROCESSING, SUCCESS, FAILURE; - } - - public AccountInitializationStatus(final AccountInitializationStatus.Status status) { - this.status = status; - } - - public AccountInitializationStatus.Status getStatus() { - return status; - } - - public boolean isProcessing() { - return status != null && status.equals(Status.PROCESSING); - } - - public boolean isSuccess() { - return status != null && status.equals(Status.SUCCESS); - } - - public boolean isFailure() { - return status != null && status.equals(Status.FAILURE); - } - -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/ApkProvider.java b/android/app/src/main/java/org/getlantern/lantern/model/ApkProvider.java deleted file mode 100644 index 5e26acfbb..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/ApkProvider.java +++ /dev/null @@ -1,105 +0,0 @@ -package org.getlantern.lantern.model; - -import java.io.File; -import java.io.FileNotFoundException; - -import android.content.ContentProvider; -import android.content.ContentValues; -import android.content.UriMatcher; -import android.database.Cursor; -import android.net.Uri; -import android.os.ParcelFileDescriptor; - -import org.getlantern.mobilesdk.Logger; - -public class ApkProvider extends ContentProvider { - - private static final String CLASS_NAME = "ApkProvider"; - - // The authority is the symbolic name for the provider class - public static final String AUTHORITY = "org.getlantern.lantern.gmailattach.provider"; - - // UriMatcher used to match against incoming requests - private UriMatcher uriMatcher; - - @Override - public boolean onCreate() { - uriMatcher = new UriMatcher(UriMatcher.NO_MATCH); - - // Add a URI to the matcher which will match against the form - // 'content://com.stephendnicholas.gmailattach.provider/*' - // and return 1 in the case that the incoming Uri matches this pattern - uriMatcher.addURI(AUTHORITY, "*", 1); - - return true; - } - - @Override - public ParcelFileDescriptor openFile(Uri uri, String mode) - throws FileNotFoundException { - - String LOG_TAG = CLASS_NAME + " - openFile"; - - Logger.v(LOG_TAG, - "Called with uri: '" + uri + "'." + uri.getLastPathSegment()); - - // Check incoming Uri against the matcher - switch (uriMatcher.match(uri)) { - - // If it returns 1 - then it matches the Uri defined in onCreate - case 1: - - // The desired file name is specified by the last segment of the - // path - // E.g. - // 'content://com.stephendnicholas.gmailattach.provider/Test.txt' - // Take this and build the path to the file - String fileLocation = getContext().getCacheDir() + File.separator - + uri.getLastPathSegment(); - - // Create & return a ParcelFileDescriptor pointing to the file - // Note: I don't care what mode they ask for - they're only getting - // read only - ParcelFileDescriptor pfd = ParcelFileDescriptor.open(new File( - fileLocation), ParcelFileDescriptor.MODE_READ_ONLY); - return pfd; - - // Otherwise unrecognised Uri - default: - Logger.v(LOG_TAG, "Unsupported uri: '" + uri + "'."); - throw new FileNotFoundException("Unsupported uri: " - + uri.toString()); - } - } - - // ////////////////////////////////////////////////////////////// - // Not supported / used / required for this example - // ////////////////////////////////////////////////////////////// - - @Override - public int update(Uri uri, ContentValues contentvalues, String s, - String[] as) { - return 0; - } - - @Override - public int delete(Uri uri, String s, String[] as) { - return 0; - } - - @Override - public Uri insert(Uri uri, ContentValues contentvalues) { - return null; - } - - @Override - public String getType(Uri uri) { - return null; - } - - @Override - public Cursor query(Uri uri, String[] projection, String s, String[] as1, - String s1) { - return null; - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/AuctionInfo.java b/android/app/src/main/java/org/getlantern/lantern/model/AuctionInfo.java deleted file mode 100644 index 10a914206..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/AuctionInfo.java +++ /dev/null @@ -1,45 +0,0 @@ -package org.getlantern.lantern.model; - -import java.text.SimpleDateFormat; -import java.util.Calendar; -import java.util.Date; -import java.util.TimeZone; -import com.google.gson.annotations.SerializedName; - -import org.getlantern.mobilesdk.Logger; - -public class AuctionInfo { - private static final String TAG = AuctionInfo.class.getName(); - - @SerializedName("auctionDatetime") - private String auctionDatetime; - - @SerializedName("tokensReleased") - private Double tokensReleased; - - public String getAuctionDatetime() { - return auctionDatetime; - } - - public Integer getTokensReleased() { - if (tokensReleased != null) { - return tokensReleased.intValue(); - } - return 0; - } - - public Long getTimeLeft() { - try { - final TimeZone utc = TimeZone.getTimeZone("UTC"); - // 2019-01-08T17:00:00Z - SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); - simpleDateFormat.setTimeZone(utc); - final Date endDate = simpleDateFormat.parse(getAuctionDatetime()); - final Calendar rightNow = Calendar.getInstance(utc); - return Math.abs(endDate.getTime() - rightNow.getTimeInMillis()); - } catch (java.text.ParseException e) { - Logger.error(TAG, "Unable to parse auction time", e); - } - return null; - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/CheckUpdate.java b/android/app/src/main/java/org/getlantern/lantern/model/CheckUpdate.java deleted file mode 100644 index df0836493..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/CheckUpdate.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.getlantern.lantern.model; - -public class CheckUpdate { - - private boolean userInitiated; - - public CheckUpdate(final boolean userInitiated) { - this.userInitiated = userInitiated; - } - - public boolean getUserInitiated() { - return userInitiated; - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/Device.java b/android/app/src/main/java/org/getlantern/lantern/model/Device.java deleted file mode 100644 index fa3ae532c..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/Device.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.getlantern.lantern.model; - -import com.google.gson.annotations.SerializedName; - -public class Device { - - @SerializedName("id") - private String id; - - @SerializedName("name") - private String name; - - @SerializedName("created") - private long created; - - public String getId() { - return id; - } - - public void setId(final String id) { - this.id = id; - } - - public String getName() { - return name; - } - - public void setName(final String name) { - this.name = name; - } - - public long getCreated() { - return created; - } - - public void setCreated(final long created) { - this.created = created; - } - - public String toString() { - return String.format("ID: %s Name: %s", id, name); - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/DeviceView.java b/android/app/src/main/java/org/getlantern/lantern/model/DeviceView.java deleted file mode 100644 index d7c41f4a4..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/DeviceView.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.getlantern.lantern.model; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.Button; -import android.widget.LinearLayout; -import android.widget.TextView; - -import org.getlantern.lantern.R; - - -public class DeviceView extends LinearLayout { - public Button unauthorize; - public TextView name; - - private void inflateLayout(Context context) { - LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View view = layoutInflater.inflate(R.layout.device_item, this); - this.unauthorize = (Button)view.findViewById(R.id.unauthorize); - this.name = (TextView)view.findViewById(R.id.deviceName); - } - - public DeviceView(Context context) { - super(context); - inflateLayout(context); - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/DynamicViewPager.java b/android/app/src/main/java/org/getlantern/lantern/model/DynamicViewPager.java deleted file mode 100644 index 5eb85a757..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/DynamicViewPager.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.getlantern.lantern.model; - -import android.content.Context; -import androidx.viewpager.widget.ViewPager; -import android.util.AttributeSet; -import android.view.View; - -public class DynamicViewPager extends ViewPager { - - private int currentPagePosition = 0; - - public DynamicViewPager(Context context) { - super(context); - } - - public DynamicViewPager(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - try { - boolean wrapHeight = MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.AT_MOST; - if (wrapHeight) { - View child = getChildAt(currentPagePosition); - if (child != null) { - child.measure(widthMeasureSpec, MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)); - int h = child.getMeasuredHeight(); - - heightMeasureSpec = MeasureSpec.makeMeasureSpec(h, MeasureSpec.EXACTLY); - } - } - } catch (Exception e) { - e.printStackTrace(); - } - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - } - - public void reMeasureCurrentPage(int position) { - currentPagePosition = position; - requestLayout(); - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/FeatureUi.java b/android/app/src/main/java/org/getlantern/lantern/model/FeatureUi.java deleted file mode 100644 index 545f4d1b0..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/FeatureUi.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.getlantern.lantern.model; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.widget.LinearLayout; -import android.widget.ImageView; -import android.widget.TextView; - -import org.getlantern.lantern.R; - -public class FeatureUi extends LinearLayout { - - private ImageView checkmark; - public TextView text; - - public FeatureUi(final Context context, final int layout) { - super(context); - - LayoutInflater layoutInflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - View view = layoutInflater.inflate(layout, this); - text = (TextView)view.findViewById(R.id.feature_text); - checkmark = (ImageView)view.findViewById(R.id.checkmark); - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/LanternHttpClient.java b/android/app/src/main/java/org/getlantern/lantern/model/LanternHttpClient.java index 0ed03d6b3..ae36661fe 100644 --- a/android/app/src/main/java/org/getlantern/lantern/model/LanternHttpClient.java +++ b/android/app/src/main/java/org/getlantern/lantern/model/LanternHttpClient.java @@ -468,10 +468,6 @@ public interface ProUserCallback { public void onSuccess(Response response, final ProUser userData); } - public interface AuctionInfoCallback { - public void onSuccess(final AuctionInfo auctionInfo); - } - public interface HttpCallback { public void onFailure(@Nullable Throwable throwable); diff --git a/android/app/src/main/java/org/getlantern/lantern/model/ListAdapter.java b/android/app/src/main/java/org/getlantern/lantern/model/ListAdapter.java deleted file mode 100644 index 56e12348c..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/ListAdapter.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.getlantern.lantern.model; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.BaseAdapter; -import android.widget.ImageView; -import android.widget.TextView; - -import java.util.ArrayList; - -import org.getlantern.lantern.R; - -public class ListAdapter extends BaseAdapter { - - private Context context; - private ArrayList navItems; - - public ListAdapter(Context context, ArrayList navItems) { - this.context = context; - this.navItems = navItems; - } - - @Override - public int getCount() { - return navItems.size(); - } - - @Override - public Object getItem(int position) { - return navItems.get(position); - } - - @Override - public long getItemId(int position) { - return 0; - } - - @Override - public View getView(int position, View convertView, ViewGroup parent) { - View view; - - if (convertView == null) { - LayoutInflater inflater = (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); - view = inflater.inflate(R.layout.drawer_item, null); - } - else { - view = convertView; - } - - TextView titleView = (TextView) view.findViewById(R.id.title); - ImageView iconView = (ImageView) view.findViewById(R.id.icon); - - titleView.setText( navItems.get(position).getTitle() ); - iconView.setImageResource(navItems.get(position).getIcon()); - - return view; - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/MaterialUtil.java b/android/app/src/main/java/org/getlantern/lantern/model/MaterialUtil.java deleted file mode 100644 index a148a31d4..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/MaterialUtil.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.getlantern.lantern.model; - -import android.view.View; -import android.widget.EditText; -import android.widget.FrameLayout; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.google.android.material.textfield.TextInputLayout; - -import org.getlantern.lantern.R; -import org.getlantern.lantern.fragment.ClickSpan; - -/** - * MaterialUtil provides UI utils for the new material design UI conventions. - */ -public class MaterialUtil { - public static void clickify(TextView view, final String clickableText, final ClickSpan.OnClickListener listener) { - Utils.clickify(view, clickableText, view.getContext().getResources().getColor(R.color.material_link), listener); - } - - public static void setError(TextInputLayout layout, EditText input, int error) { - if (!input.hasFocus() && input.getText().toString().trim().length() > 0) { - layout.setError(layout.getResources().getString(error)); - if (true) { - return; - } - View child = layout.findViewById(R.id.textinput_error); - FrameLayout parent = (FrameLayout) child.getParent(); - // Pin height - int height = (int) child.getMeasuredHeight(); - ((LinearLayout.LayoutParams) parent.getLayoutParams()).height = height; - ((FrameLayout.LayoutParams) child.getLayoutParams()).height = height; - - View.OnLayoutChangeListener listener = new View.OnLayoutChangeListener() { - @Override - public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { - int[] childLocation = new int[2]; - int[] parentLocation = new int[2]; - child.getLocationOnScreen(childLocation); - parent.getLocationOnScreen(parentLocation); - FrameLayout.LayoutParams layoutParams = ((FrameLayout.LayoutParams) child.getLayoutParams()); - float margin = parentLocation[1] - childLocation[1] + layoutParams.topMargin; - layoutParams.topMargin = (int) margin; - parent.removeOnLayoutChangeListener(this); - parent.invalidate(); - } - }; - - child.addOnLayoutChangeListener(listener); - child.requestLayout(); - } - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/NavItem.java b/android/app/src/main/java/org/getlantern/lantern/model/NavItem.java deleted file mode 100644 index 68dc8d690..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/NavItem.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.getlantern.lantern.model; - -public class NavItem { - private int id; - private String title; - private int icon; - - public NavItem(final int id, final String title, final int icon) { - this.id = id; - this.title = title; - this.icon = icon; - } - - public int getId() { - return id; - } - - public String getTitle() { - return title; - } - - public int getIcon() { - return icon; - } - - public void setTitle(String title) { - this.title = title; - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/PaymentForm.java b/android/app/src/main/java/org/getlantern/lantern/model/PaymentForm.java deleted file mode 100644 index f8a49545f..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/PaymentForm.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.getlantern.lantern.model; - -public interface PaymentForm { - public String getCardNumber(); - public String getCvc(); - public Integer getExpMonth(); - public Integer getExpYear(); -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/ShowAds.java b/android/app/src/main/java/org/getlantern/lantern/model/ShowAds.java deleted file mode 100644 index c4a8c03aa..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/ShowAds.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.getlantern.lantern.model; - -public class ShowAds { - private boolean status; - - public ShowAds(boolean status) { - this.status = status; - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/TintableImageView.java b/android/app/src/main/java/org/getlantern/lantern/model/TintableImageView.java deleted file mode 100644 index 6018ca926..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/TintableImageView.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.getlantern.lantern.model; - -import android.content.Context; -import android.content.res.ColorStateList; -import android.content.res.TypedArray; -import android.util.AttributeSet; -import android.widget.ImageView; - -import org.getlantern.lantern.R; - -/** - * https://gist.github.com/tylerchesley/5d15d859be4f3ce31213 - */ -public class TintableImageView extends ImageView { - - private ColorStateList tintColor; - - public TintableImageView(Context context) { - super(context); - } - - public TintableImageView(Context context, AttributeSet attrs) { - super(context, attrs); - init(context, attrs, 0); - } - - public TintableImageView(Context context, AttributeSet attrs, int defStyle) { - super(context, attrs, defStyle); - init(context, attrs, defStyle); - } - - private void init(Context context, AttributeSet attrs, int defStyle) { - TypedArray a = context.obtainStyledAttributes( - attrs, R.styleable.TintableImageView, defStyle, 0); - tintColor = a.getColorStateList( - R.styleable.TintableImageView_tintColor); - a.recycle(); - } - - @Override - protected void drawableStateChanged() { - super.drawableStateChanged(); - if (tintColor != null && tintColor.isStateful()) { - updateTintColor(); - } - } - - public void setColorFilter(ColorStateList tint) { - this.tintColor = tint; - super.setColorFilter(tintColor.getColorForState(getDrawableState(), 0)); - } - - private void updateTintColor() { - int color = tintColor.getColorForState(getDrawableState(), 0); - setColorFilter(color); - } - -} diff --git a/android/app/src/main/java/org/getlantern/lantern/model/VpnState.java b/android/app/src/main/java/org/getlantern/lantern/model/VpnState.java deleted file mode 100644 index 650e3eca1..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/model/VpnState.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.getlantern.lantern.model; - -public class VpnState { - - private boolean useVpn; - - public VpnState(boolean useVpn) { - this.useVpn = useVpn; - } - - public boolean use() { - return useVpn; - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/service/AutoStarter.java b/android/app/src/main/java/org/getlantern/lantern/service/AutoStarter.java deleted file mode 100644 index dd6b8146a..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/service/AutoStarter.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.getlantern.lantern.service; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; -import android.os.Build; - -import org.getlantern.mobilesdk.Logger; - -public class AutoStarter extends BroadcastReceiver { - private static final String TAG = AutoStarter.class.getName(); - - @Override - public void onReceive(Context context, Intent intent) { - Logger.debug(TAG, "Automatically starting Lantern Service on: " + intent.getAction()); - - Intent serviceIntent = new Intent(context, LanternService_.class); - serviceIntent.putExtra( - LanternService.AUTO_BOOTED, - intent.getAction() == Intent.ACTION_BOOT_COMPLETED - ); - - context.startService(serviceIntent); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/org/getlantern/lantern/service/BackgroundChecker.java b/android/app/src/main/java/org/getlantern/lantern/service/BackgroundChecker.java deleted file mode 100644 index 1aa2f4fc6..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/service/BackgroundChecker.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.getlantern.lantern.service; - -import android.app.Service; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.os.IBinder; - -import androidx.localbroadcastmanager.content.LocalBroadcastManager; - -import org.androidannotations.annotations.EService; -import org.getlantern.lantern.LanternApp; -import org.getlantern.lantern.model.LanternHttpClient; -import org.getlantern.lantern.model.ProError; -import org.getlantern.lantern.model.ProUser; -import org.getlantern.mobilesdk.Logger; - -import java.lang.ref.WeakReference; - -import okhttp3.Response; - -@EService -public class BackgroundChecker extends Service implements LanternHttpClient.ProUserCallback { - - private static final String TAG = BackgroundChecker.class.getName(); - private static final LanternHttpClient lanternClient = LanternApp.getLanternHttpClient(); - private final Handler handler = new Handler(); - - private String nextActivity; - private String provider; - - private boolean asBroadcast; - // isRenewal returns whether or not the user - // was Pro when the background checker started - private boolean isRenewal; - // numCurrentMonths is how long the user has been - // Pro when the background checker started - private int numCurrentProMonths; - - // how many times we've made calls to /user-data - private int callsMade = 0; - // the max number of tries we try calling /user-data - // before giving up - private int maxCalls = 40; - - private static final class Checker implements Runnable { - private final WeakReference service; - - protected Checker(final BackgroundChecker service) { - this.service = new WeakReference(service); - } - - @Override - public void run() { - final BackgroundChecker bc = service.get(); - if (bc != null) { - lanternClient.userData(bc); - } - } - } - - private void sendRequest() { - if (callsMade >= maxCalls) { - Logger.debug(TAG, "Reached max tries running background checker.."); - stopSelf(); - return; - } - final Double backoff = Math.pow(1.7, this.callsMade) / 10; - final Integer timeOut = 12 * 1000 + backoff.intValue(); - handler.postDelayed(new Checker(this), timeOut); - this.callsMade++; - } - - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - if (intent != null && intent.getExtras() != null) { - final Bundle extras = intent.getExtras(); - this.asBroadcast = extras.getBoolean("asBroadcast"); - this.isRenewal = extras.getBoolean("renewal"); - this.numCurrentProMonths = extras.getInt("numProMonths"); - this.provider = extras.getString("provider"); - this.maxCalls = extras.getInt("maxCalls"); - this.nextActivity = extras.getString("nextActivity"); - - Logger.debug(TAG, "User data background checker: max calls " + - this.maxCalls + " " + this.nextActivity); - sendRequest(); - } - return super.onStartCommand(intent, flags, startId); - } - - @Override - public void onSuccess(final Response response, final ProUser user) { - if (user != null && !user.isProUser()) { - sendRequest(); - return; - } else if (user.isProUser() && isRenewal) { - // check number of months has increased if the user is - // already Pro - if (user.monthsLeft() != null && - numCurrentProMonths == user.monthsLeft()) { - sendRequest(); - return; - } - } - - try { - if (asBroadcast) { - Logger.debug(TAG, "Broadcasting user became Pro"); - final Intent intent = new Intent("userBecamePro"); - LocalBroadcastManager.getInstance(getApplicationContext()).sendBroadcast(intent); - } else { - final Class next = Class.forName(nextActivity); - final Intent intent = new Intent(this, next); - intent.putExtra("provider", provider); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - } - } catch (Exception e) { - Logger.error(TAG, "Unable to launch welcome activity", e); - } finally { - stopSelf(); - } - } - - @Override - public void onFailure(final Throwable throwable, final ProError error) { - if (error != null) { - Logger.error(TAG, "Error making user data request:" + error); - } - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/service/LanternService.java b/android/app/src/main/java/org/getlantern/lantern/service/LanternService.java deleted file mode 100644 index 76ea773f3..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/service/LanternService.java +++ /dev/null @@ -1,234 +0,0 @@ -package org.getlantern.lantern.service; - -import android.app.Service; -import android.content.Intent; -import android.os.Handler; -import android.os.IBinder; -import android.os.Looper; -import android.text.TextUtils; - -import androidx.annotation.Nullable; - -import com.google.gson.JsonObject; - -import org.androidannotations.annotations.EService; -import org.getlantern.lantern.BuildConfig; -import org.getlantern.lantern.LanternApp; -import org.getlantern.lantern.R; -import org.getlantern.lantern.model.CheckUpdate; -import org.getlantern.lantern.model.LanternHttpClient; -import org.getlantern.lantern.model.LanternStatus; -import org.getlantern.lantern.model.LanternStatus.Status; -import org.getlantern.lantern.model.AccountInitializationStatus; -import org.getlantern.lantern.util.Json; -import org.getlantern.mobilesdk.model.LoConf; -import org.getlantern.lantern.model.ProError; -import org.getlantern.lantern.model.ProUser; -import org.getlantern.mobilesdk.Lantern; -import org.getlantern.mobilesdk.LanternNotRunningException; -import org.getlantern.mobilesdk.Logger; -import org.getlantern.mobilesdk.Settings; -import org.getlantern.mobilesdk.StartResult; -import org.getlantern.mobilesdk.model.LoConfCallback; -import org.greenrobot.eventbus.EventBus; - -import java.util.Random; -import java.util.concurrent.atomic.AtomicBoolean; - -import okhttp3.HttpUrl; -import okhttp3.Response; - -@EService -public class LanternService extends Service implements Runnable { - - public static String AUTO_BOOTED = "autoBooted"; - - private static final int MAX_CREATE_USER_TRIES = 11; - - private static final String TAG = LanternService.class.getName(); - private static final LanternHttpClient lanternClient = LanternApp.getLanternHttpClient(); - private Thread thread = null; - - private final Handler createUserHandler = new Handler(Looper.getMainLooper()); - private final CreateUser createUserRunnable = new CreateUser(); - - private final Random random = new Random(); - private final int baseCreateUserRetryDelay = 3000; // milliseconds - - private final ServiceHelper helper = new ServiceHelper( - this, - LanternApp.getSession().chatEnabled() ? R.drawable.status_chat : R.drawable.status_plain, - R.string.ready_to_connect); - - private AtomicBoolean started = new AtomicBoolean(); - - @Override - public void onCreate() { - Logger.debug(TAG, "Creating Lantern service"); - super.onCreate(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - boolean autoBooted = intent != null && intent.getBooleanExtra(AUTO_BOOTED, false); - Logger.debug(TAG, "Called onStartCommand, autoBooted?: " + autoBooted); - -// Logger.debug(TAG, "Starting LanternService in foreground so that message processing continues even when UI is closed"); -// helper.makeForeground(); - - if (autoBooted) { -// boolean hasOnboarded = new Boolean(true) -// .equals(LanternApp.messaging.messaging.getDb().get("onBoardingStatus")); - boolean hasOnboarded = false; - if (!hasOnboarded) { - Logger.debug(TAG, "Attempted to auto boot but user has not onboarded to messaging, stop LanternService"); - stopSelf(); - return START_NOT_STICKY; - } - } - - if (started.compareAndSet(false, true)) { - Logger.d(TAG, "Starting Lantern service thread"); - thread = new Thread(this, "LanternService"); - thread.start(); - } - - return super.onStartCommand(intent, flags, startId); - } - - @Nullable - @Override - public IBinder onBind(Intent intent) { - return null; - } - - @Override - public void run() { - // move the current thread of the service to the background - android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND); - - final String locale = LanternApp.getSession().getLanguage(); - final Settings settings = LanternApp.getSession().getSettings(); - try { - Logger.debug(TAG, "Successfully loaded config: " + settings.toString()); - final StartResult result = Lantern.enable(this, locale, LanternApp.getSession().getSettings(), LanternApp.getSession()); - LanternApp.getSession().setStartResult(result); - afterStart(); - - } catch (LanternNotRunningException lnre) { - Logger.e(TAG, "Unable to start LanternService", lnre); - throw new RuntimeException("Could not start Lantern", lnre); - } - } - - private void afterStart() { - if (LanternApp.getSession().userId() == 0) { - // create a user if no user id is stored - EventBus.getDefault().postSticky(new AccountInitializationStatus(AccountInitializationStatus.Status.PROCESSING)); - createUser(0); - } - - if (!BuildConfig.PLAY_VERSION && !BuildConfig.DEVELOPMENT_MODE) { - // check if an update is available - EventBus.getDefault().post(new CheckUpdate(false)); - } - - EventBus.getDefault().postSticky(new LanternStatus(Status.ON)); - - // fetch latest loconf - LoConf.Companion.fetch(new LoConfCallback() { - @Override - public void onSuccess(final LoConf loconf) { - EventBus.getDefault().post(loconf); - } - }); - - } - - private void createUser(final int attempt) { - long waitTime = 0; - if (attempt > 0) { - // on retries, wait a little bit - waitTime = Math.round(baseCreateUserRetryDelay * (1.0 + random.nextFloat())); - } - createUserHandler.postDelayed(createUserRunnable, waitTime); - } - - private static class InvalidUserException extends RuntimeException { - public InvalidUserException(String message) { - super(message); - } - } - - private final class CreateUser implements Runnable, LanternHttpClient.ProCallback { - - private int attempts = 0; - - @Override - public void run() { - final HttpUrl url = LanternHttpClient.createProUrl("/user-create"); - final JsonObject json = new JsonObject(); - json.addProperty("locale", LanternApp.getSession().getLanguage()); - lanternClient.post(url, LanternHttpClient.createJsonBody(json), this); - } - - @Override - public void onFailure(final Throwable throwable, final ProError error) { - Logger.error(TAG, "Unable to create new Lantern user", throwable); - if (attempts < MAX_CREATE_USER_TRIES) { - attempts++; - createUser(attempts); - } else { - final String errorMsg = "Max. number of tries made to create Pro user"; - final InvalidUserException e = new InvalidUserException(errorMsg); - Logger.error(TAG, errorMsg, e); - EventBus.getDefault().postSticky(new AccountInitializationStatus(AccountInitializationStatus.Status.FAILURE)); - } - } - - @Override - public void onSuccess(final Response response, final JsonObject result) { - final ProUser user = Json.gson.fromJson(result, ProUser.class); - if (user == null) { - Logger.error(TAG, "Unable to parse user from JSON"); - return; - } - createUserHandler.removeCallbacks(createUserRunnable); - Logger.debug(TAG, "Created new Lantern user: " + user.newUserDetails()); - LanternApp.getSession().setUserIdAndToken(user.getUserId(), user.getToken()); - final String referral = user.getReferral(); - if (!TextUtils.isEmpty(referral)) { - LanternApp.getSession().setCode(referral); - } - EventBus.getDefault().postSticky(new LanternStatus(Status.ON)); - EventBus.getDefault().postSticky(new AccountInitializationStatus(AccountInitializationStatus.Status.SUCCESS)); - } - } - - @Override - public void onDestroy() { - Logger.debug(TAG, "Destroying LanternService"); - super.onDestroy(); - - if (!started.get()) { - Logger.debug(TAG, "Service never started, exit immediately"); - return; - } - - helper.onDestroy(); - thread.interrupt(); - try { - Logger.debug(TAG, "Unregistering screen state receiver"); - createUserHandler.removeCallbacks(createUserRunnable); - } catch (Exception e) { - Logger.error(TAG, "Exception", e); - } - - // We want to keep the service running as much as possible to allow receiving messages, so - // we start it back up automatically as explained at https://stackoverflow.com/a/52258125. - Intent broadcastIntent = new Intent(); - broadcastIntent.setAction("restartservice"); - broadcastIntent.setClass(this, AutoStarter.class); - this.sendBroadcast(broadcastIntent); - } -} \ No newline at end of file diff --git a/android/app/src/main/java/org/getlantern/lantern/util/Util.java b/android/app/src/main/java/org/getlantern/lantern/util/Util.java deleted file mode 100644 index 1bb0c4ecb..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/util/Util.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.getlantern.lantern.util; - -import android.content.res.Resources; - -public class Util { - public static float dpToPx(float dp) { - return (dp * Resources.getSystem().getDisplayMetrics().density); - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/vpn/GoTun2SocksProvider.java b/android/app/src/main/java/org/getlantern/lantern/vpn/GoTun2SocksProvider.java deleted file mode 100644 index 9bdbc6f31..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/vpn/GoTun2SocksProvider.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.getlantern.lantern.vpn; - -import android.app.PendingIntent; -import android.content.Intent; -import android.content.pm.ApplicationInfo; -import android.content.pm.PackageManager; -import android.net.VpnService; -import android.os.ParcelFileDescriptor; - -import org.getlantern.lantern.LanternApp; -import org.getlantern.lantern.MainActivity; -import org.getlantern.mobilesdk.Logger; -import org.getlantern.mobilesdk.model.SessionManager; - -import java.util.HashSet; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import internalsdk.Internalsdk; - -public class GoTun2SocksProvider implements Provider { - private static final String TAG = "GoTun2SocksProvider"; - - private final static String sessionName = "LanternVpn"; - - private final static String privateAddress = "10.0.0.2"; - private final static int VPN_MTU = 1500; - - private ParcelFileDescriptor mInterface; - - private final PackageManager packageManager; - private final boolean splitTunnelingEnabled; - private final Set appsAllowedAccess; - - public GoTun2SocksProvider( - PackageManager packageManager, - boolean splitTunnelingEnabled, - List appsAllowedAccess - ) { - this.packageManager = packageManager; - this.splitTunnelingEnabled = splitTunnelingEnabled; - this.appsAllowedAccess = new HashSet(appsAllowedAccess); - } - - private synchronized ParcelFileDescriptor createBuilder(final VpnService vpnService, - final VpnService.Builder builder) { - // Set the locale to English - // since the VpnBuilder encounters - // issues with non-English numerals - // See https://code.google.com/p/android/issues/detail?id=61096 - Locale.setDefault(new Locale("en")); - - // Configure a builder while parsing the parameters. - builder.setMtu(VPN_MTU); - builder.addAddress(privateAddress, 24); - // route IPv4 through VPN - builder.addRoute("0.0.0.0", 0); - - if (splitTunnelingEnabled) { - // Exclude any app that's not in our split tunneling allowed list - for (ApplicationInfo installedApp : packageManager.getInstalledApplications(0)) { - if (!appsAllowedAccess.contains(installedApp.packageName)) { - try { - Logger.debug(TAG, "Excluding " + installedApp.packageName + " from VPN"); - builder.addDisallowedApplication(installedApp.packageName); - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException("Unable to exclude " + installedApp.packageName + " from VPN", e); - } - } - } - } - - // Never capture traffic originating from Lantern itself in the VPN. - try { - String ourPackageName = vpnService.getPackageName(); - builder.addDisallowedApplication(ourPackageName); - } catch (PackageManager.NameNotFoundException e) { - throw new RuntimeException("Unable to exclude Lantern from VPN", e); - } - - // don't currently route IPv6 through VPN because our proxies don't currently support IPv6 - // see https://github.com/getlantern/lantern-internal/issues/4961 - // Note - if someone performs a DNS lookup for an IPv6 only host like ipv6.google.com, dnsgrab - // will return an IPv4 address for that site, causing the traffic to get routed through the VPN. - // builder.addRoute("0:0:0:0:0:0:0:0", 0); - - // this is a fake DNS server. The precise IP doesn't matter because Lantern will intercept and - // route all DNS traffic to dnsgrab internally anyway. - builder.addDnsServer(SessionManager.getFakeDnsIP()); - - Intent intent = new Intent(vpnService, MainActivity.class); - PendingIntent pendingIntent = PendingIntent.getActivity(vpnService, 0, intent, PendingIntent.FLAG_IMMUTABLE); - builder.setConfigureIntent(pendingIntent); - - builder.setSession(sessionName); - - // Create a new mInterface using the builder and save the parameters. - mInterface = builder.establish(); - Logger.d(TAG, "New mInterface: " + mInterface); - return mInterface; - } - - public void run(final VpnService vpnService, final VpnService.Builder builder, final String socksAddr, final String dnsGrabAddr) { - Logger.d(TAG, "run"); - - final Locale defaultLocale = Locale.getDefault(); - try { - Logger.debug(TAG, "Creating VpnBuilder before starting tun2socks"); - ParcelFileDescriptor intf = createBuilder(vpnService, builder); - Logger.debug(TAG, "Running tun2socks"); - Internalsdk.tun2Socks(intf.getFd(), socksAddr, dnsGrabAddr, VPN_MTU, LanternApp.getSession()); - } catch (Throwable t) { - Logger.e(TAG, "Exception while handling TUN device", t); - } finally { - Locale.setDefault(defaultLocale); - } - } - - public synchronized void stop() throws Exception { - Logger.d(TAG, "stop"); - Internalsdk.stopTun2Socks(); - if (mInterface != null) { - Logger.d(TAG, "closing interface"); - mInterface.close(); - mInterface = null; - } - } -} diff --git a/android/app/src/main/java/org/getlantern/lantern/vpn/LanternVpnService.java b/android/app/src/main/java/org/getlantern/lantern/vpn/LanternVpnService.java deleted file mode 100644 index a98e76c44..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/vpn/LanternVpnService.java +++ /dev/null @@ -1,142 +0,0 @@ -/* See https://android.googlesource.com/platform/development/+/master/samples/ToyVpn/src/com/example/android/toyvpn/ToyVpnService.java - * for an example of a VpnService implementation. - */ -package org.getlantern.lantern.vpn; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.ServiceConnection; -import android.net.VpnService; -import android.os.Bundle; -import android.os.IBinder; - -import org.getlantern.lantern.LanternApp; -import org.getlantern.lantern.service.LanternService_; -import org.getlantern.mobilesdk.Logger; - -import java.util.List; - - -public class LanternVpnService extends VpnService implements Runnable { - public static final String ACTION_CONNECT = "org.getlantern.lantern.vpn.START"; - public static final String ACTION_DISCONNECT = "org.getlantern.lantern.vpn.STOP"; - - private static final String TAG = "VpnService"; - - private Provider mProvider = null; - - private boolean splitTunnelingEnabled = false; - private List appsAllowedAccess = null; - - private final ServiceConnection lanternServiceConnection = new ServiceConnection() { - @Override - public void onServiceDisconnected(ComponentName name) { - Logger.e(TAG, "LanternService disconnected, disconnecting VPN"); - stop(); - } - - @Override - public void onServiceConnected(ComponentName name, IBinder service) { - } - }; - - private synchronized Provider getOrInitProvider() { - Logger.d(TAG, "getOrInitProvider()"); - if (mProvider == null) { - Logger.d(TAG, "Using Go tun2socks"); - mProvider = new GoTun2SocksProvider( - getPackageManager(), - splitTunnelingEnabled, - appsAllowedAccess - ); - } - return mProvider; - } - - @Override - public void onCreate() { - super.onCreate(); - Logger.d(TAG, "VpnService created"); - bindService(new Intent(this, LanternService_.class), lanternServiceConnection, Context.BIND_AUTO_CREATE); - } - - @Override - public void onDestroy() { - Logger.d(TAG, "destroyed"); - doStop(); - super.onDestroy(); - unbindService(lanternServiceConnection); - } - - @Override - public void onRevoke() { - Logger.d(TAG, "revoked"); - stop(); - } - - @Override - public int onStartCommand(Intent intent, int flags, int startId) { - Logger.d(TAG, "LanternVpnService: onStartCommand()"); - //Somehow we are getting null here when running on Android 5.0 - // Handling null intent scenario - if (intent == null) { - Logger.d(TAG, "LanternVpnService: Received null intent, service is being restarted"); - return START_STICKY; - } - - if (ACTION_DISCONNECT.equals(intent.getAction())) { - stop(); - return START_NOT_STICKY; - } else { - Bundle bundle = intent.getExtras(); - splitTunnelingEnabled = bundle.getBoolean("splitTunnelingEnabled"); - appsAllowedAccess = bundle.getStringArrayList("appsAllowedAccess"); - connect(); - return START_STICKY; - } - } - - private void connect() { - Logger.d(TAG, "connect"); - new Thread(this, "VpnService").start(); - } - - @Override - public void run() { - try { - Logger.d(TAG, "Loading Lantern library"); - getOrInitProvider().run(this, new Builder(), LanternApp.getSession().getSOCKS5Addr(), LanternApp.getSession().getDNSGrabAddr()); - } catch (Exception e) { - e.printStackTrace(); - Logger.error(TAG, "Error running VPN", e); - } finally { - Logger.debug(TAG, "Lantern terminated."); - stop(); - } - } - - private void stop() { - doStop(); - stopSelf(); - Logger.d(TAG, "done stopping"); - } - - private void doStop() { - Logger.d(TAG, "stop"); - try { - Logger.d(TAG, "getting provider"); - Provider provider = getOrInitProvider(); - Logger.d(TAG, "stopping provider"); - provider.stop(); - } catch (Throwable t) { - Logger.e(TAG, "error stopping provider", t); - } - try { - Logger.d(TAG, "updating vpn preference"); - LanternApp.getSession().updateVpnPreference(false); - } catch (Throwable t) { - Logger.e(TAG, "error updating vpn preference", t); - } - } -} \ No newline at end of file diff --git a/android/app/src/main/java/org/getlantern/lantern/vpn/Provider.java b/android/app/src/main/java/org/getlantern/lantern/vpn/Provider.java deleted file mode 100644 index ed9e1ebf2..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/vpn/Provider.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.getlantern.lantern.vpn; - -import android.net.VpnService; - -// A provider provides the implementation of VPN internals. -interface Provider { - void run(final VpnService vpnService, final VpnService.Builder builder, final String socksAddr, final String dnsGrabAddr) throws Exception; - - void stop() throws Exception; -} diff --git a/android/app/src/main/java/org/getlantern/lantern/widget/TextInputLayout.java b/android/app/src/main/java/org/getlantern/lantern/widget/TextInputLayout.java deleted file mode 100644 index 15fac12b7..000000000 --- a/android/app/src/main/java/org/getlantern/lantern/widget/TextInputLayout.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.getlantern.lantern.widget; - -import android.content.Context; -import android.util.AttributeSet; -import android.widget.TextView; - -import org.getlantern.lantern.R; - -public class TextInputLayout extends com.google.android.material.textfield.TextInputLayout { - private static final String TAG = "TextInputLayout"; - - public TextInputLayout(Context context) { - super(context); - } - - public TextInputLayout(Context context, AttributeSet attrs) { - super(context, attrs); - } - - public TextInputLayout(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - } - - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); - if (isErrorEnabled()) { - // This is a workaround to a weird layout issue where sometimes the error text is misaligned - TextView errorView = getErrorView(); - errorView.setY(0); - } - } - - private TextView getErrorView() { - return (TextView) findViewById(R.id.textinput_error); - } - -} \ No newline at end of file diff --git a/android/app/src/main/java/org/getlantern/mobilesdk/activity/ReportIssueActivity.kt b/android/app/src/main/java/org/getlantern/mobilesdk/activity/ReportIssueActivity.kt deleted file mode 100644 index e4c178266..000000000 --- a/android/app/src/main/java/org/getlantern/mobilesdk/activity/ReportIssueActivity.kt +++ /dev/null @@ -1,132 +0,0 @@ -package org.getlantern.mobilesdk.activity - -import android.os.AsyncTask -import android.os.Build -import android.os.Bundle -import android.view.View -import android.widget.ArrayAdapter -import androidx.core.widget.addTextChangedListener -import androidx.fragment.app.FragmentActivity -import org.getlantern.lantern.BuildConfig -import org.getlantern.lantern.LanternApp -import org.getlantern.lantern.R -import org.getlantern.lantern.databinding.ActivityReportIssueBinding -import org.getlantern.lantern.model.Utils -import org.getlantern.lantern.util.showErrorDialog -import org.getlantern.mobilesdk.Logger -import org.getlantern.mobilesdk.model.IssueReporter - -open class ReportIssueActivity : FragmentActivity() { - private lateinit var binding: ActivityReportIssueBinding - - // The below maps indexes of issues in the drop-down to indexes of the corresponding issue type - // as understood by internalsdk.SendIssueReport - private val issueTypeIndexes = hashMapOf( - 0 to 3, // NO_ACCESS - 1 to 0, // PAYMENT_FAIL - 2 to 1, // CANNOT_LOGIN - 3 to 2, // ALWAYS_SPINNING - 4 to 4, // SLOW - 5 to 7, // CHAT_NOT_WORKING, - 6 to 8, // DISCOVER_NOT_WORKING, - 7 to 5, // CANNOT LINK DEVICE - 8 to 6, // CRASHES - 9 to 9, // OTHER - ) - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityReportIssueBinding.inflate(layoutInflater) - setContentView(binding.root) - - val email = LanternApp.getSession().email() - - if ("" != email) { - binding.emailInput.setText(email) - } - - val issueAdapter = ArrayAdapter(this, R.layout.issue_row, issues()) - binding.issue.setAdapter(issueAdapter) - - binding.sendBtn.setOnClickListener { - sendReport(it) - } - binding.issue.addTextChangedListener { - checkValidField() - } - binding.emailInput.addTextChangedListener { - checkValidField() - } - // this is necessary for Appium Test cases - //check if running from CI set no email - // Set dealt issue type - if (BuildConfig.CI) { - binding.emailInput.setText("") - binding.issue.setText(issues()[0]) - } - } - - private fun checkValidField() { - when { - binding.issue.text?.toString()?.isEmpty() == true -> { - binding.sendBtn.isEnabled = false - } - - else -> { - binding.sendBtn.isEnabled = true - } - } - } - - fun sendReport(view: View?) { - val email = binding.emailInput.text.toString() - val issue = binding.issue.text?.toString() - - if (!Utils.isNetworkAvailable(this)) { - showErrorDialog(resources.getString(R.string.no_internet_connection)) - return - } - if (issue.isNullOrEmpty()) { - showErrorDialog(resources.getString(R.string.no_issue_selected)) - return - } - if (email.isNotEmpty() && !Utils.isEmailValid(email)) { - showErrorDialog(resources.getString(R.string.invalid_email)) - return - } - - val issueType = issueTypeIndexes[issues().indexOf(issue)] ?: 9 // default to OTHER - val description = binding.description.text.toString() - - Logger.debug(TAG, "Reporting '$issueType - $issue' on behalf of $email") - LanternApp.getSession().setEmail(email) - - val issueReporter = IssueReporter( - this, - issueType.toString(), - description, - ) - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { - issueReporter.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) - } else { - issueReporter.execute() - } - } - - private fun issues() = arrayOf( - resources.getString(R.string.common_issue_list_0), - resources.getString(R.string.common_issue_list_1), - resources.getString(R.string.common_issue_list_2), - resources.getString(R.string.common_issue_list_3), - resources.getString(R.string.common_issue_list_4), - resources.getString(R.string.common_issue_list_5), - resources.getString(R.string.common_issue_list_6), - resources.getString(R.string.common_issue_list_7), - resources.getString(R.string.common_issue_list_8), - resources.getString(R.string.common_issue_list_9), - ) - - companion object { - private val TAG = ReportIssueActivity::class.java.name - } -} diff --git a/android/app/src/main/java/org/getlantern/mobilesdk/model/IssueReporter.kt b/android/app/src/main/java/org/getlantern/mobilesdk/model/IssueReporter.kt index 28c7d5ed2..1d2267a05 100644 --- a/android/app/src/main/java/org/getlantern/mobilesdk/model/IssueReporter.kt +++ b/android/app/src/main/java/org/getlantern/mobilesdk/model/IssueReporter.kt @@ -15,11 +15,27 @@ import org.getlantern.mobilesdk.Logger // else onError(). class IssueReporter @JvmOverloads constructor( private val context: Context, - private val issueType: String, + private val issue: String, private val description: String?, ) : AsyncTask() { private var dialog: ProgressDialog? = null + + // The below maps indexes of issues in the drop-down to indexes of the corresponding issue type + // as understood by internalsdk.SendIssueReport + private val issueTypeIndexes = hashMapOf( + 0 to 3, // NO_ACCESS + 1 to 0, // PAYMENT_FAIL + 2 to 1, // CANNOT_LOGIN + 3 to 2, // ALWAYS_SPINNING + 4 to 4, // SLOW + 5 to 7, // CHAT_NOT_WORKING, + 6 to 8, // DISCOVER_NOT_WORKING, + 7 to 5, // CANNOT LINK DEVICE + 8 to 6, // CRASHES + 9 to 9, // OTHER + ) + fun onError(message: String) { dialog?.let { dialog -> if (dialog.isShowing) { @@ -60,9 +76,10 @@ class IssueReporter @JvmOverloads constructor( override fun doInBackground(vararg params: String): Boolean { val session = LanternApp.getSession() try { + val issueType = issueTypeIndexes[issues().indexOf(issue)] ?: 9 // default to OTHER internalsdk.Internalsdk.sendIssueReport( session, - issueType, + issueType.toString(), description, if (session.isProUser) "pro" else "free", session.email(), @@ -71,6 +88,7 @@ class IssueReporter @JvmOverloads constructor( "" + Build.VERSION.SDK_INT + " (" + Build.VERSION.RELEASE + ")" ) onSuccess() + Logger.e(TAG, "Report sent successfully:") return true } catch (e: Exception) { Logger.error(TAG, "Error submitting issue report: ", e) @@ -79,6 +97,19 @@ class IssueReporter @JvmOverloads constructor( } } + private fun issues() = arrayOf( + context.resources.getString(R.string.common_issue_list_0), + context.resources.getString(R.string.common_issue_list_1), + context.resources.getString(R.string.common_issue_list_2), + context.resources.getString(R.string.common_issue_list_3), + context.resources.getString(R.string.common_issue_list_4), + context.resources.getString(R.string.common_issue_list_5), + context.resources.getString(R.string.common_issue_list_6), + context.resources.getString(R.string.common_issue_list_7), + context.resources.getString(R.string.common_issue_list_8), + context.resources.getString(R.string.common_issue_list_9), + ) + companion object { private val TAG = IssueReporter::class.java.name } diff --git a/android/app/src/main/java/org/getlantern/mobilesdk/model/LangAdapter.kt b/android/app/src/main/java/org/getlantern/mobilesdk/model/LangAdapter.kt deleted file mode 100644 index 56bef7373..000000000 --- a/android/app/src/main/java/org/getlantern/mobilesdk/model/LangAdapter.kt +++ /dev/null @@ -1,40 +0,0 @@ -package org.getlantern.mobilesdk.model - -import android.content.Context -import android.view.LayoutInflater -import android.view.View -import android.view.ViewGroup -import android.widget.ArrayAdapter -import android.widget.TextView -import org.getlantern.lantern.LanternApp -import org.getlantern.lantern.R -import java.util.Locale - -class LangAdapter(context: Context?, lang: List, val localeMap: Map) : ArrayAdapter(context!!, 0, lang) { - override fun getView(position: Int, _convertView: View?, parent: ViewGroup): View { - var convertView = _convertView - val lang = getItem(position) - val current = LanternApp.getSession().language - var color = context.resources.getColor(R.color.black) - val entry = localeMap[lang] - if (entry != null && entry.toString() == current) { - // the current locale should be highlighted the selected color - color = context.resources.getColor(R.color.selected_item) - } - - // Check if an existing view is being reused, otherwise inflate the view - if (convertView == null) { - convertView = LayoutInflater.from(context).inflate(R.layout.language_item, parent, false) - } - val tv = convertView!!.findViewById(R.id.title) as TextView - tv.text = lang - tv.setTextColor(color) - - // Return the completed view to render on screen - return convertView - } - - companion object { - private val TAG = LangAdapter::class.java.name - } -} diff --git a/android/app/src/main/java/org/getlantern/mobilesdk/model/MailSender.kt b/android/app/src/main/java/org/getlantern/mobilesdk/model/MailSender.kt deleted file mode 100644 index f680bb424..000000000 --- a/android/app/src/main/java/org/getlantern/mobilesdk/model/MailSender.kt +++ /dev/null @@ -1,109 +0,0 @@ -package org.getlantern.mobilesdk.model - -import android.app.Activity -import android.app.ProgressDialog -import android.content.Context -import android.os.AsyncTask -import internalsdk.EmailMessage -import internalsdk.EmailResponseHandler -import org.getlantern.lantern.LanternApp -import org.getlantern.lantern.R -import org.getlantern.lantern.util.showAlertDialog -import org.getlantern.mobilesdk.Logger - -// MailSender calls Go's `internalsdk/email.go:EmailMessage.Send()` method to -// actually send emails through the github.com/getlantern/mandrill package. If -// successful, onSuccess() callback would trigger, else onError() -class MailSender @JvmOverloads constructor( - private val context: Context, - private val template: String, - private val title: String? = null, - private val message: String? = null, -) : AsyncTask(), EmailResponseHandler { - private var dialog: ProgressDialog? = null - private val mergeValues: MutableMap = HashMap() - - override fun onError(message: String) { - dialog?.let { dialog -> - if (dialog.isShowing) { - dialog.dismiss() - } - } - (context as Activity).showAlertDialog( - title ?: getAppName(), - message ?: message, - // Don't close the dialog if there's an error. This'll remove the - // user's input. Let the user close the dialog or try again if they - // want. - finish = false, - ) - } - - override fun onSuccess() { - dialog?.let { dialog -> - if (dialog.isShowing) { - dialog.dismiss() - } - } - (context as Activity).showAlertDialog( - title ?: getAppName(), - message ?: getResponseMessage(), - // Close the dialog after a successful send. - finish = true, - ) - } - - override fun onPreExecute() { - dialog?.let { dialog -> - dialog.setMessage(context.resources.getString(R.string.sending_request)) - dialog.show() - } - } - - protected override fun doInBackground(vararg params: String): Boolean { - val msg = EmailMessage() - msg.template = template - msg.to = params[0] - if (template == "manual-recover-account") { - msg.putInt("userid", LanternApp.getSession().userID) - msg.putString("usertoken", LanternApp.getSession().token) - msg.putString("deviceid", LanternApp.getSession().deviceID) - msg.putString("deviceName", LanternApp.getSession().deviceName()) - msg.putString("email", LanternApp.getSession().email()) - msg.putString("referralCode", LanternApp.getSession().code()) - } - for ((key, value) in mergeValues) { - msg.putString(key, value) - } - try { - // This function calls Go's `internalsdk/email.go:EmailMessage.Send()`. - // It will call `onSuccess()` or `onError()` when it's done with - // the request. This function doesn't block or return an error but - // we're wrapping it with a try-catch just to be safe - msg.send(this) - } catch (e: Exception) { - Logger.error(TAG, "Error trying to send mail: ", e) - return false - } - return true - } - - private fun getResponseMessage(): String { - val msg = R.string.success_email - return context.resources.getString(msg).format(getAppName()) - } - - private fun getAppName(): String { - return context.resources.getString(R.string.app_name) - } - - companion object { - private val TAG = MailSender::class.java.name - } - - init { - dialog = ProgressDialog(context) - dialog!!.setCancelable(false) - dialog!!.setCanceledOnTouchOutside(false) - } -} diff --git a/android/app/src/main/java/org/getlantern/mobilesdk/model/SessionManager.kt b/android/app/src/main/java/org/getlantern/mobilesdk/model/SessionManager.kt index e3107aaf0..1c9292d3a 100644 --- a/android/app/src/main/java/org/getlantern/mobilesdk/model/SessionManager.kt +++ b/android/app/src/main/java/org/getlantern/mobilesdk/model/SessionManager.kt @@ -104,7 +104,7 @@ abstract class SessionManager(application: Application) : Session { val locale = Locale(language) val country = countryCode return country.equals(c, ignoreCase = true) || - listOf(*l).contains(locale) + listOf(*l).contains(locale) } val isEnglishUser: Boolean @@ -235,9 +235,29 @@ abstract class SessionManager(application: Application) : Session { prefs.edit().putBoolean(CHAT_ENABLED, enabled).apply() } -// fun chatEnabled(): Boolean = prefs.getBoolean(CHAT_ENABLED, false) + override fun setShowInterstitialAdsEnabled(enabled: Boolean) { + Logger.d(TAG, "Setting $ADS_ENABLED to $enabled") + prefs.edit().putBoolean(ADS_ENABLED, enabled).apply() + } + + override fun setCASShowInterstitialAdsEnabled(enabled: Boolean) { + Logger.d(TAG, "Setting $CAS_ADS_ENABLED to $enabled") + prefs.edit().putBoolean(CAS_ADS_ENABLED, enabled).apply() + } + + fun shouldShowAdsEnabled(): Boolean { + return prefs.getBoolean(ADS_ENABLED, false) + } + + fun shouldCASShowAdsEnabled(): Boolean { + return prefs.getBoolean(CAS_ADS_ENABLED, false) + } + + // fun chatEnabled(): Boolean = prefs.getBoolean(CHAT_ENABLED, false) // for now, disable Chat completely - fun chatEnabled(): Boolean { return false } + fun chatEnabled(): Boolean { + return false + } fun appVersion(): String { return appVersion @@ -251,13 +271,17 @@ abstract class SessionManager(application: Application) : Session { prefs.edit().putString(EMAIL_ADDRESS, email).apply() } - fun setUserIdAndToken(userId: Long, token: String) { - if (userId == 0L || TextUtils.isEmpty(token)) { - Logger.debug(TAG, "Not setting invalid user ID $userId or token $token") + fun setUserIdAndToken(userId: Long, token: String?) { + if (userId == 0L) { + Logger.debug(TAG, "Not setting invalid user ID $userId") return } - Logger.debug(TAG, "Setting user ID to $userId, token to $token") - prefs.edit().putLong(USER_ID, userId).putString(TOKEN, token).apply() + Logger.debug(TAG, "Setting user ID to $userId") + prefs.edit().putLong(USER_ID, userId).apply() + if (token != null && !TextUtils.isEmpty(token)) { + Logger.debug(TAG, "Setting token to $token") + prefs.edit().putString(TOKEN, token).apply() + } } private fun setDeviceId(deviceId: String?) { @@ -393,6 +417,7 @@ abstract class SessionManager(application: Application) : Session { prefs.edit().remove(HAS_SUCCEEDING_PROXY).apply() } + /** * hasPrefExpired checks whether or not a particular * shared preference has expired (assuming its stored value @@ -413,6 +438,17 @@ abstract class SessionManager(application: Application) : Session { prefs.edit().putLong(name, currentMilliseconds + numSeconds * 1000).apply() } + /**this preference is used for checking we want to show ads or not + * we are only after first session + */ + fun setHasFirstSessionCompleted(status: Boolean) { + prefs.edit().putBoolean(HAS_FIRST_SESSION_COMPLETED, status).apply() + } + + fun hasFirstSessionCompleted(): Boolean { + return prefs.getBoolean(HAS_FIRST_SESSION_COMPLETED, false); + } + fun getInternalHeaders(): Map { val headers: MutableMap = HashMap() for ((key, value) in internalHeaders.all) { @@ -437,6 +473,27 @@ abstract class SessionManager(application: Application) : Session { return gson.toJson(headers) } + // isPlayVersion checks whether or not the user installed Lantern via the Google Play store + override fun isPlayVersion(): Boolean { + if (BuildConfig.PLAY_VERSION || prefs.getBoolean(PLAY_VERSION, false)) { + return true + } + try { + val validInstallers: List = ArrayList( + listOf( + "com.android.vending", + "com.google.android.feedback" + ) + ) + val installer = context.packageManager + .getInstallerPackageName(context.packageName) + return installer != null && validInstallers.contains(installer) + } catch (e: java.lang.Exception) { + Logger.error(TAG, "Error fetching package information: " + e.message) + } + return false + } + companion object { private val TAG = SessionManager::class.java.name const val PREFERENCES_SCHEMA = "session" @@ -449,6 +506,7 @@ abstract class SessionManager(application: Application) : Session { protected const val SERVER_COUNTRY_CODE = "server_country_code" protected const val SERVER_CITY = "server_city" protected const val HAS_SUCCEEDING_PROXY = "hasSucceedingProxy" + protected const val HAS_FIRST_SESSION_COMPLETED = "hasFirstSessionCompleted" protected const val DEVICE_ID = "deviceid" @JvmStatic @@ -481,6 +539,8 @@ abstract class SessionManager(application: Application) : Session { private const val REPLICA_ADDR = "replicaAddr" public const val CHAT_ENABLED = "chatEnabled" + public const val ADS_ENABLED = "adsEnabled" + public const val CAS_ADS_ENABLED = "casAsEnabled" private val chineseLocales = arrayOf( Locale("zh", "CN"), @@ -509,6 +569,7 @@ abstract class SessionManager(application: Application) : Session { db.registerType(2002, Vpn.Plan::class.java) db.registerType(2004, Vpn.PaymentProviders::class.java) db.registerType(2005, Vpn.PaymentMethod::class.java) + db.registerType(2006, Vpn.AppData::class.java) Logger.debug(TAG, "register types finished at ${System.currentTimeMillis() - start}") val prefsAdapter = db.asSharedPreferences( context.getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE), @@ -516,12 +577,14 @@ abstract class SessionManager(application: Application) : Session { prefs = prefsAdapter prefs.edit().putBoolean(DEVELOPMENT_MODE, BuildConfig.DEVELOPMENT_MODE) .putBoolean(PAYMENT_TEST_MODE, prefs.getBoolean(PAYMENT_TEST_MODE, false)) - .putBoolean(PLAY_VERSION, prefs.getBoolean(PLAY_VERSION, false)) + .putBoolean(PLAY_VERSION, isPlayVersion()) .putString(FORCE_COUNTRY, prefs.getString(FORCE_COUNTRY, "")).apply() // initialize email address to empty string (if it doesn't already exist) if (email().isEmpty()) setEmail("") + if (prefs.getInt(ACCEPTED_TERMS_VERSION, 0) == 0) prefs.edit().putInt(ACCEPTED_TERMS_VERSION, 0).apply() + Logger.debug(TAG, "prefs.edit() finished at ${System.currentTimeMillis() - start}") internalHeaders = context.getSharedPreferences( INTERNAL_HEADERS_PREF_NAME, @@ -578,7 +641,8 @@ abstract class SessionManager(application: Application) : Session { val loadedApkCls = Class.forName("android.app.LoadedApk") val receiversField = loadedApkCls.getDeclaredField("mReceivers") receiversField.isAccessible = true - val receivers = receiversField.get(loadedApk) as ArrayMap> + val receivers = + receiversField.get(loadedApk) as ArrayMap> for (receiverMap in receivers.values) { for (rec in (receiverMap as ArrayMap).keys) { val clazz: Class<*> = rec.javaClass diff --git a/android/app/src/main/kotlin/io/lantern/model/MessagingModel.kt b/android/app/src/main/kotlin/io/lantern/model/MessagingModel.kt index 3a0580863..29ac45c73 100644 --- a/android/app/src/main/kotlin/io/lantern/model/MessagingModel.kt +++ b/android/app/src/main/kotlin/io/lantern/model/MessagingModel.kt @@ -21,7 +21,7 @@ import io.lantern.messaging.directContactPath import io.lantern.messaging.inputStream import org.getlantern.lantern.MainActivity import org.getlantern.lantern.R -import org.getlantern.lantern.restartApp +import org.getlantern.lantern.util.restartApp import org.whispersystems.signalservice.internal.util.Util import top.oply.opuslib.OpusRecorder import java.io.* diff --git a/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt b/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt index e7f157bc8..9ef39f399 100644 --- a/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt +++ b/android/app/src/main/kotlin/io/lantern/model/SessionModel.kt @@ -1,36 +1,45 @@ package io.lantern.model import android.app.Activity +import android.os.AsyncTask +import android.os.Build import android.content.Intent import androidx.core.content.ContextCompat import com.google.gson.JsonObject +import com.google.protobuf.ByteString import internalsdk.Internalsdk import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel +import io.lantern.apps.AppsDataProvider +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.launch import okhttp3.FormBody import okhttp3.RequestBody import okhttp3.Response import org.getlantern.lantern.LanternApp +import org.getlantern.lantern.MainActivity import org.getlantern.lantern.R import org.getlantern.lantern.activity.FreeKassaActivity_ import org.getlantern.lantern.activity.WebViewActivity_ -import org.getlantern.lantern.model.CheckUpdate +import org.getlantern.mobilesdk.model.IssueReporter import org.getlantern.lantern.model.LanternHttpClient import org.getlantern.lantern.model.LanternHttpClient.ProCallback import org.getlantern.lantern.model.LanternHttpClient.ProUserCallback import org.getlantern.lantern.model.ProError import org.getlantern.lantern.model.ProUser -import org.getlantern.lantern.openHome -import org.getlantern.lantern.restartApp +import org.getlantern.lantern.model.Utils +import org.getlantern.lantern.util.AutoUpdater import org.getlantern.lantern.util.PaymentsUtil +import org.getlantern.lantern.util.PermissionUtil import org.getlantern.lantern.util.castToBoolean +import org.getlantern.lantern.util.openHome +import org.getlantern.lantern.util.restartApp import org.getlantern.lantern.util.showAlertDialog import org.getlantern.lantern.util.showErrorDialog import org.getlantern.mobilesdk.Logger import org.getlantern.mobilesdk.model.SessionManager -import org.greenrobot.eventbus.EventBus -import java.util.concurrent.* /** * This is a model that uses the same db schema as the preferences in SessionManager so that those @@ -40,7 +49,11 @@ class SessionModel( private val activity: Activity, flutterEngine: FlutterEngine, ) : BaseModel("session", flutterEngine, LanternApp.getSession().db) { + private val appsDataProvider: AppsDataProvider = AppsDataProvider( + activity.packageManager, activity.packageName + ) private val lanternClient = LanternApp.getLanternHttpClient() + private val autoUpdater = AutoUpdater(activity, activity) private val paymentsUtil = PaymentsUtil(activity) companion object { @@ -50,6 +63,9 @@ class SessionModel( const val PATH_SDK_VERSION = "sdkVersion" const val PATH_USER_LEVEL = "userLevel" + + const val PATH_SPLIT_TUNNELING = "/splitTunneling" + const val PATH_APPS_DATA = "/appsData/" } init { @@ -63,32 +79,60 @@ class SessionModel( PATH_USER_LEVEL, tx.get(PATH_USER_LEVEL) ?: "", ) + tx.put( + PATH_SPLIT_TUNNELING, + castToBoolean(tx.get(PATH_SPLIT_TUNNELING), false) + ) // hard disable chat tx.put(SessionManager.CHAT_ENABLED, false) tx.put(PATH_SDK_VERSION, Internalsdk.sdkVersion()) } + updateAppsData() } override fun doOnMethodCall(call: MethodCall, result: MethodChannel.Result) { when (call.method) { "authorizeViaEmail" -> authorizeViaEmail(call.argument("emailAddress")!!, result) + "shouldShowAds" -> { + result.success(shouldShowAdsBasedRegion { + LanternApp.getSession().shouldShowAdsEnabled() + }) + } + "shouldCASShowAds" -> { + result.success(shouldShowAdsBasedRegion { + LanternApp.getSession().shouldCASShowAdsEnabled() + }) + } "checkEmailExists" -> checkEmailExists(call.argument("emailAddress")!!, result) + "requestLinkCode" -> requestLinkCode(result) "resendRecoveryCode" -> sendRecoveryCode(result) "validateRecoveryCode" -> validateRecoveryCode(call.argument("code")!!, result) "approveDevice" -> approveDevice(call.argument("code")!!, result) "removeDevice" -> removeDevice(call.argument("deviceId")!!, result) + "reportIssue" -> reportIssue(call.argument("email")!!, call.argument("issue")!!, call.argument("description")!!, result) "applyRefCode" -> paymentsUtil.applyRefCode(call.argument("refCode")!!, result) - "redeemResellerCode" -> paymentsUtil.redeemResellerCode(call.argument("email")!!, call.argument("resellerCode")!!, result) + "redeemResellerCode" -> paymentsUtil.redeemResellerCode( + call.argument("email")!!, + call.argument("resellerCode")!!, result + ) + + "refreshAppsList" -> { + updateAppsData() + result.success(null) + } + "submitBitcoinPayment" -> paymentsUtil.submitBitcoinPayment( call.argument("planID")!!, call.argument("email")!!, call.argument("refCode")!!, result, ) + "submitGooglePlayPayment" -> paymentsUtil.submitGooglePlayPayment( call.argument("planID")!!, result, ) + "submitStripePayment" -> paymentsUtil.submitStripePayment( call.argument("planID")!!, call.argument("email")!!, @@ -97,6 +141,7 @@ class SessionModel( call.argument("cvc")!!, result, ) + "userStatus" -> userStatus(result) else -> super.doOnMethodCall(call, result) } @@ -112,26 +157,34 @@ class SessionModel( activity.startActivity(intent) } } + "acceptTerms" -> { + LanternApp.getSession().acceptTerms() + } "setLanguage" -> { LanternApp.getSession().setLanguage(call.argument("lang")) } + "setPaymentTestMode" -> { LanternApp.getSession().setPaymentTestMode(call.argument("on") ?: false) activity.restartApp() } + "setPlayVersion" -> { LanternApp.getSession().isPlayVersion = call.argument("on") ?: false activity.restartApp() } + "setForceCountry" -> { LanternApp.getSession().setForceCountry(call.argument("countryCode") ?: "") activity.restartApp() } + "setSelectedTab" -> { db.mutate { tx -> tx.put("/selectedTab", call.argument("tab")!!) } } + "submitFreekassa" -> { val userEmail = call.argument("email") ?: "" val planID = call.argument("planID") ?: "" @@ -144,13 +197,107 @@ class SessionModel( }, ) } + "checkForUpdates" -> { - EventBus.getDefault().post(CheckUpdate(true)) + autoUpdater.checkForUpdates() } + + "setSplitTunneling" -> { + val on = call.argument("on") ?: false + saveSplitTunneling(on) + } + + "allowAppAccess" -> { + updateAppData(call.argument("packageName")!!, true) + } + + "denyAppAccess" -> { + updateAppData(call.argument("packageName")!!, false) + } + else -> super.doMethodCall(call, notImplemented) } } + private fun shouldShowAdsBasedRegion(shouldShow: () -> Boolean): Boolean { + //We just need to check VPN permissions + // if user tried to remove then we need to check + // all other configurations are coming from backend + val session = LanternApp.getSession() + val hasAllNetworkPermissions = PermissionUtil.missingPermissions(activity).isEmpty() + return shouldShow() + && hasAllNetworkPermissions + && session.hasFirstSessionCompleted() + } + + fun splitTunnelingEnabled(): Boolean { + return db.get(PATH_SPLIT_TUNNELING) ?: false + } + + private fun saveSplitTunneling(value: Boolean) { + db.mutate { tx -> + tx.put(PATH_SPLIT_TUNNELING, value) + } + } + + // updateAppData looks up the app data for the given package name and updates whether or + // not the app is allowed access to the VPN connection in the database + private fun updateAppData(packageName: String, allowedAccess: Boolean) { + db.mutate { tx -> + var appData = tx.get(PATH_APPS_DATA + packageName) + appData?.let { + tx.put( + PATH_APPS_DATA + packageName, Vpn.AppData.newBuilder() + .setPackageName(it.packageName).setIcon(it.icon) + .setName(it.name).setAllowedAccess(allowedAccess).build() + ) + } + } + } + + // updateAppsData stores app data for the list of applications installed for the current + // user in the database + private fun updateAppsData() { + // This can be quite slow, run it on its own coroutine + CoroutineScope(Dispatchers.IO).launch { + val appsList = appsDataProvider.listOfApps() + // First add just the app names to get a list quickly + db.mutate { tx -> + appsList.forEach { + val path = PATH_APPS_DATA + it.packageName + if (!tx.contains(path)) { + // App not already in list, add it + tx.put( + path, + Vpn.AppData.newBuilder() + .setPackageName(it.packageName).setName(it.name) + .build() + ) + } + } + } + + // Then add icons + db.mutate { tx -> + appsList.forEach { + val path = PATH_APPS_DATA + it.packageName + tx.get(path)?.let { existing -> + if (existing.icon.isEmpty) { + it.icon.let { icon -> + tx.put( + path, + existing.toBuilder() + .setIcon(ByteString.copyFrom(icon)) + .build(), + ) + } + } + } + } + } + } + } + private fun confirmEmailError(error: ProError) { val errorId = error.id val resources = activity.resources @@ -161,6 +308,70 @@ class SessionModel( } } + private fun requestLinkCode(methodCallResult: MethodChannel.Result) { + val formBody = FormBody.Builder() + .add("deviceName", LanternApp.getSession().deviceName()) + .build() + lanternClient.post( + LanternHttpClient.createProUrl("/link-code-request"), + formBody, + object : ProCallback { + override fun onFailure(t: Throwable?, error: ProError?) { + if (error == null) { + activity.runOnUiThread { + methodCallResult.error("unknownError", null, null) + } + return + } + val errorId = error.id + activity.runOnUiThread { + methodCallResult.error("linkCodeError", errorId, null) + } + } + + override fun onSuccess(response: Response?, result: JsonObject?) { + result?.let { + if (result["code"] == null || result["expireAt"] == null) return + val code = result["code"].asString + val expireAt = result["expireAt"].asLong + LanternApp.getSession().setDeviceCode(code, expireAt) + methodCallResult.success(null) + } + } + }, + ) + } + + private fun redeemLinkCode() { + val formBody = FormBody.Builder() + .add("code", LanternApp.getSession().deviceCode()!!) + .add("deviceName", LanternApp.getSession().deviceName()) + .build() + lanternClient.post( + LanternHttpClient.createProUrl("/link-code-request"), + formBody, + object : ProCallback { + override fun onFailure(t: Throwable?, error: ProError?) { + Logger.error(TAG, "Error making link redeem request..", t) + } + + override fun onSuccess(response: Response?, result: JsonObject?) { + if (result == null || result["token"] == null || result["userID"] == null) return + Logger.debug(TAG, "Successfully redeemed link code") + val userID = result["userID"].asLong + val token = result["token"].asString + LanternApp.getSession().setUserIdAndToken(userID, token) + LanternApp.getSession().linkDevice() + LanternApp.getSession().setIsProUser(true) + val intent = Intent(activity, MainActivity::class.java).apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + activity.startActivity(intent) + } + }, + ) + } + private fun checkEmailExists(emailAddress: String, methodCallResult: MethodChannel.Result) { val params = mapOf("email" to emailAddress) val isPlayVersion = LanternApp.getSession().isPlayVersion() @@ -197,7 +408,8 @@ class SessionModel( if (result!!["token"] != null && result["userID"] != null) { Logger.debug(TAG, "Successfully recovered account") // update token and user ID with those returned by the pro server - LanternApp.getSession().setUserIdAndToken(result["userID"].asLong, result["token"].asString) + LanternApp.getSession() + .setUserIdAndToken(result["userID"].asLong, result["token"].asString) LanternApp.getSession().linkDevice() LanternApp.getSession().setIsProUser(true) activity.showAlertDialog( @@ -272,7 +484,11 @@ class SessionModel( override fun onFailure(t: Throwable?, error: ProError?) { Logger.error(TAG, "Unable to validate link code", t) activity.runOnUiThread { - methodCallResult.error("unableToVerifyRecoveryCode", t?.message, error?.message) + methodCallResult.error( + "unableToVerifyRecoveryCode", + t?.message, + error?.message + ) } if (error == null) { Logger.error(TAG, "Unable to validate recovery code and no error to show") @@ -292,10 +508,15 @@ class SessionModel( Logger.debug(TAG, "Successfully validated recovery code") // update token and user ID with those returned by the pro server // update token and user ID with those returned by the pro server - LanternApp.getSession().setUserIdAndToken(result["userID"].asLong, result["token"].asString) + LanternApp.getSession() + .setUserIdAndToken(result["userID"].asLong, result["token"].asString) LanternApp.getSession().linkDevice() LanternApp.getSession().setIsProUser(true) - activity.showAlertDialog(activity.getString(R.string.device_added), activity.getString(R.string.device_authorized_pro), ContextCompat.getDrawable(activity, R.drawable.ic_filled_check), { activity.openHome() }) + activity.showAlertDialog( + activity.getString(R.string.device_added), + activity.getString(R.string.device_authorized_pro), + ContextCompat.getDrawable(activity, R.drawable.ic_filled_check), + { activity.openHome() }) } } }, @@ -326,12 +547,20 @@ class SessionModel( activity.runOnUiThread { methodCallResult.success("approvedDevice") } - activity.showAlertDialog(activity.resources.getString(R.string.device_added), activity.resources.getString(R.string.device_authorized_pro), ContextCompat.getDrawable(activity, R.drawable.ic_filled_check)) + activity.showAlertDialog( + activity.resources.getString(R.string.device_added), + activity.resources.getString(R.string.device_authorized_pro), + ContextCompat.getDrawable(activity, R.drawable.ic_filled_check) + ) } override fun onFailure(t: Throwable?, error: ProError?) { Logger.error(TAG, "Unable to fetch user data: $t.message") - methodCallResult.error("errorUpdatingUserData", t?.message, error?.message) + methodCallResult.error( + "errorUpdatingUserData", + t?.message, + error?.message + ) } }) } @@ -339,6 +568,25 @@ class SessionModel( ) } + private fun reportIssue(email: String, issue: String, description: String, methodCallResult: MethodChannel.Result) { + if (!Utils.isNetworkAvailable(activity)) { + methodCallResult.error("errorReportingIssue", activity.getString(R.string.no_internet_connection), null) + return + } + Logger.debug(TAG, "Reporting $issue issue on behalf of $email") + LanternApp.getSession().setEmail(email) + val issueReporter = IssueReporter( + activity, + issue, + description, + ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + issueReporter.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR) + } else { + issueReporter.execute() + } + } + private fun removeDevice(deviceId: String, methodCallResult: MethodChannel.Result) { Logger.debug(TAG, "Removing device $deviceId") val formBody: RequestBody = FormBody.Builder() @@ -383,7 +631,11 @@ class SessionModel( override fun onFailure(t: Throwable?, error: ProError?) { Logger.error(TAG, "Unable to fetch user data: $t.message") - methodCallResult.error("errorUpdatingUserData", t?.message, error?.message) + methodCallResult.error( + "errorUpdatingUserData", + t?.message, + error?.message + ) } }) } @@ -400,15 +652,24 @@ class SessionModel( result.success("cachingUserDataSuccess") LanternApp.getSession().setUserLevel(userData.userLevel) } + override fun onFailure(t: Throwable?, error: ProError?) { Logger.error(TAG, "Unable to fetch user data: $t.message") - result.error("cachingUserDataError", "Unable to cache user status", error?.message) // This will be localized Flutter-side + result.error( + "cachingUserDataError", + "Unable to cache user status", + error?.message + ) // This will be localized Flutter-side return } }) } catch (t: Throwable) { Logger.error(TAG, "Error caching user status", t) - result.error("unknownError", "Unable to cache user status", null) // This will be localized Flutter-side + result.error( + "unknownError", + "Unable to cache user status", + null + ) // This will be localized Flutter-side } } } diff --git a/android/app/src/main/kotlin/io/lantern/model/VpnModel.kt b/android/app/src/main/kotlin/io/lantern/model/VpnModel.kt index 902a5b94b..2c367dba5 100644 --- a/android/app/src/main/kotlin/io/lantern/model/VpnModel.kt +++ b/android/app/src/main/kotlin/io/lantern/model/VpnModel.kt @@ -1,11 +1,9 @@ package io.lantern.model import android.app.Activity -import com.google.protobuf.ByteString import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall import io.flutter.plugin.common.MethodChannel -import io.lantern.apps.AppsDataProvider import org.getlantern.lantern.util.castToBoolean import org.getlantern.mobilesdk.Logger @@ -14,9 +12,6 @@ class VpnModel( flutterEngine: FlutterEngine, private var switchLanternHandler: ((vpnOn: Boolean) -> Unit)? = null, ) : BaseModel("vpn", flutterEngine, masterDB.withSchema(VPN_SCHEMA)) { - private val appsDataProvider: AppsDataProvider = AppsDataProvider( - activity.packageManager, activity.packageName - ) companion object { private const val TAG = "VpnModel" @@ -25,37 +20,17 @@ class VpnModel( const val PATH_VPN_STATUS = "/vpn_status" const val PATH_SERVER_INFO = "/server_info" const val PATH_BANDWIDTH = "/bandwidth" - const val PATH_SPLIT_TUNNELING = "/splitTunneling" - const val PATH_APPS_DATA = "/appsData/" } init { val start = System.currentTimeMillis() db.registerType(1000, Vpn.ServerInfo::class.java) db.registerType(1001, Vpn.Bandwidth::class.java) - db.registerType(1002, Vpn.AppData::class.java) - Logger.debug(TAG, "register types finished at ${System.currentTimeMillis() - start}") - db.mutate { tx -> - tx.put( - PATH_SPLIT_TUNNELING, - castToBoolean(tx.get(PATH_SPLIT_TUNNELING), false) - ) - } db.mutate { tx -> // initialize vpn status for fresh install tx.put(PATH_VPN_STATUS, tx.get(PATH_VPN_STATUS) ?: "disconnected") } Logger.debug(TAG, "db.mutate finished at ${System.currentTimeMillis() - start}") - updateAppsData() - } - - override fun doOnMethodCall(call: MethodCall, result: MethodChannel.Result) { - if (call.method == "refreshAppsList") { - updateAppsData() - result.success(null) - } else { - super.doOnMethodCall(call, result) - } } override fun doMethodCall(call: MethodCall, notImplemented: () -> Unit): Any? { @@ -65,104 +40,10 @@ class VpnModel( saveVpnStatus(if (on) "connecting" else "disconnecting") switchLantern(on) } - - "setSplitTunneling" -> { - val on = call.argument("on") ?: false - saveSplitTunneling(on) - } - - "allowAppAccess" -> { - updateAppData(call.argument("packageName")!!, true) - } - - "denyAppAccess" -> { - updateAppData(call.argument("packageName")!!, false) - } - else -> super.doMethodCall(call, notImplemented) } } - fun splitTunnelingEnabled(): Boolean { - return db.get(PATH_SPLIT_TUNNELING) ?: false - } - - // appsAllowedAccess returns a list of package names for those applications that are allowed - // to access the VPN connection. If split tunneling is enabled, and any app is added to - // the list, only those applications (and no others) are allowed access. - fun appsAllowedAccess(): List { - var installedApps = db.list(PATH_APPS_DATA + "%") - val apps = mutableListOf() - for (appData in installedApps) { - if (appData.value.allowedAccess) apps.add(appData.value.packageName) - } - return apps - } - - private fun saveSplitTunneling(value: Boolean) { - db.mutate { tx -> - tx.put(PATH_SPLIT_TUNNELING, value) - } - } - - // updateAppData looks up the app data for the given package name and updates whether or - // not the app is allowed access to the VPN connection in the database - private fun updateAppData(packageName: String, allowedAccess: Boolean) { - db.mutate { tx -> - var appData = tx.get(PATH_APPS_DATA + packageName) - appData?.let { - tx.put( - PATH_APPS_DATA + packageName, Vpn.AppData.newBuilder() - .setPackageName(it.packageName).setIcon(it.icon) - .setName(it.name).setAllowedAccess(allowedAccess).build() - ) - } - } - } - - // updateAppsData stores app data for the list of applications installed for the current - // user in the database - private fun updateAppsData() { - // This can be quite slow, run it on its own thread - Thread { - val appsList = appsDataProvider.listOfApps() - // First add just the app names to get a list quickly - db.mutate { tx -> - appsList.forEach { - val path = PATH_APPS_DATA + it.packageName - if (!tx.contains(path)) { - // App not already in list, add it - tx.put( - path, - Vpn.AppData.newBuilder() - .setPackageName(it.packageName).setName(it.name) - .build() - ) - } - } - } - - // Then add icons - db.mutate { tx -> - appsList.forEach { - val path = PATH_APPS_DATA + it.packageName - tx.get(path)?.let { existing -> - if (existing.icon.isEmpty) { - it.icon.let { icon -> - tx.put( - path, - existing.toBuilder() - .setIcon(ByteString.copyFrom(icon)) - .build(), - ) - } - } - } - } - } - }.start() - } - fun isConnectedToVpn(): Boolean { val vpnStatus = vpnStatus() return vpnStatus == "connected" || vpnStatus == "disconnecting" diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt b/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt index f2595a1a5..9f249ddfd 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/MainActivity.kt @@ -1,9 +1,5 @@ package org.getlantern.lantern -import android.Manifest -import android.app.NotificationManager -import android.content.ComponentName -import android.content.Context import android.content.Intent import android.content.IntentFilter import android.content.pm.PackageManager @@ -16,9 +12,6 @@ import android.widget.TextView import androidx.annotation.NonNull import androidx.appcompat.app.AlertDialog import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import androidx.lifecycle.lifecycleScope -import internalsdk.Internalsdk import io.flutter.embedding.android.FlutterActivity import io.flutter.embedding.engine.FlutterEngine import io.flutter.plugin.common.MethodCall @@ -29,20 +22,15 @@ import io.lantern.model.SessionModel import io.lantern.model.Vpn import io.lantern.model.VpnModel import kotlinx.coroutines.* -import kotlinx.coroutines.Dispatchers import okhttp3.Response -import org.getlantern.lantern.activity.PrivacyDisclosureActivity_ import org.getlantern.lantern.activity.WebViewActivity_ import org.getlantern.lantern.event.EventManager import org.getlantern.lantern.model.AccountInitializationStatus import org.getlantern.lantern.model.Bandwidth -import org.getlantern.lantern.model.CheckUpdate import org.getlantern.lantern.model.LanternHttpClient.PlansCallback import org.getlantern.lantern.model.LanternHttpClient.PlansV3Callback import org.getlantern.lantern.model.LanternHttpClient.ProUserCallback import org.getlantern.lantern.model.LanternStatus -import org.getlantern.lantern.model.PaymentProvider -import org.getlantern.lantern.model.PaymentMethod import org.getlantern.lantern.model.PaymentMethods import org.getlantern.lantern.model.ProError import org.getlantern.lantern.model.ProPlan @@ -53,9 +41,9 @@ import org.getlantern.lantern.model.VpnState import org.getlantern.lantern.notification.NotificationHelper import org.getlantern.lantern.notification.NotificationReceiver import org.getlantern.lantern.service.LanternService_ -import org.getlantern.lantern.util.DeviceInfo -import org.getlantern.lantern.util.Json +import org.getlantern.lantern.util.PermissionUtil import org.getlantern.lantern.util.PlansUtil +import org.getlantern.lantern.util.restartApp import org.getlantern.lantern.util.showAlertDialog import org.getlantern.lantern.vpn.LanternVpnService import org.getlantern.mobilesdk.Logger @@ -66,16 +54,17 @@ import org.getlantern.mobilesdk.model.Survey import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.ThreadMode -import java.util.concurrent.* import java.util.Locale +import java.util.concurrent.* -class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, +class MainActivity : + FlutterActivity(), + MethodChannel.MethodCallHandler, CoroutineScope by MainScope() { private lateinit var messagingModel: MessagingModel private lateinit var vpnModel: VpnModel private lateinit var sessionModel: SessionModel private lateinit var replicaModel: ReplicaModel - private lateinit var navigator: Navigator private lateinit var eventManager: EventManager private lateinit var flutterNavigation: MethodChannel private lateinit var accountInitDialog: AlertDialog @@ -91,7 +80,6 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, vpnModel = VpnModel(this, flutterEngine, ::switchLantern) sessionModel = SessionModel(this, flutterEngine) replicaModel = ReplicaModel(this, flutterEngine) - navigator = Navigator(this, flutterEngine) receiver = NotificationReceiver() notifications = NotificationHelper(this, receiver) eventManager = object : EventManager("lantern_event_channel", flutterEngine) { @@ -100,7 +88,7 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, fetchLoConf() Logger.debug( TAG, - "fetchLoConf() finished at ${System.currentTimeMillis() - start}" + "fetchLoConf() finished at ${System.currentTimeMillis() - start}", ) } LanternApp.getSession().dnsDetector.publishNetworkAvailability() @@ -129,7 +117,7 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, Logger.debug( TAG, - "configureFlutterEngine finished at ${System.currentTimeMillis() - start}" + "configureFlutterEngine finished at ${System.currentTimeMillis() - start}", ) } @@ -182,19 +170,13 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, super.onResume() Logger.debug(TAG, "super.onResume() finished at ${System.currentTimeMillis() - start}") - if (LanternApp.getSession().isPlayVersion) { - if (!LanternApp.getSession().hasAcceptedTerms()) { - startActivity(Intent(this, PrivacyDisclosureActivity_::class.java)) - } - } - - if (vpnModel.isConnectedToVpn() && !Utils.isServiceRunning( - activity, - LanternVpnService::class.java, - ) - ) { + val isServiceRunning = Utils.isServiceRunning(activity, LanternVpnService::class.java) + if (vpnModel.isConnectedToVpn() && !isServiceRunning) { Logger.d(TAG, "LanternVpnService isn't running, clearing VPN preference") vpnModel.setVpnOn(false) + } else if (!vpnModel.isConnectedToVpn() && isServiceRunning) { + Logger.d(TAG, "LanternVpnService is running, updating VPN preference") + vpnModel.setVpnOn(true) } Logger.debug(TAG, "onResume() finished at ${System.currentTimeMillis() - start}") } @@ -275,7 +257,7 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, @Subscribe(threadMode = ThreadMode.MAIN) fun vpnStateChanged(state: VpnState) { - updateStatus(state.use()) + updateStatus(state.useVpn) } @Subscribe(threadMode = ThreadMode.MAIN) @@ -327,7 +309,7 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, // we are setting static user for payment mode if (user?.isProUser == false || LanternApp.getSession().isPaymentTestMode) return - //Switch to free account if device it not linked + // Switch to free account if device it not linked devices?.filter { it.id == deviceID }?.run { if (isEmpty()) { LanternApp.getSession().logout() @@ -351,9 +333,9 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, } LanternApp.getSession().setUserPlans(proPlans) - } - }, null) - } + } + }, null) + } private fun updatePaymentMethods() { lanternClient.plansV3(object : PlansV3Callback { @@ -361,13 +343,16 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, Logger.error(TAG, "Unable to fetch user plans: $error", throwable) } - override fun onSuccess(proPlans: Map, paymentMethods: List) { + override fun onSuccess( + proPlans: Map, + paymentMethods: List + ) { Logger.debug(TAG, "Successfully fetched payment methods") LanternApp.getSession().setPaymentMethods(paymentMethods) - } - }, null) - } + } + }, null) + } @Subscribe(threadMode = ThreadMode.MAIN) fun processLoconf(loconf: LoConf) { @@ -413,13 +398,13 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, ) val userType = survey.userType if (userType != null) { - if (userType == "free" && LanternApp.getSession().isProUser) { + if (userType == "free" && LanternApp.getSession().isProUser()) { Logger.debug( SURVEY_TAG, "Not showing messages targetted to free users to Pro users", ) return - } else if (userType == "pro" && !LanternApp.getSession().isProUser) { + } else if (userType == "pro" && !LanternApp.getSession().isProUser()) { Logger.debug( SURVEY_TAG, "Not showing messages targetted to free users to Pro users", @@ -460,55 +445,6 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, LanternApp.getSession().setSurveyLinkOpened(survey.url) } - private fun noUpdateAvailable(userInitiated: Boolean) { - if (!userInitiated) return - runOnUiThread { - val appName = resources.getString(R.string.app_name) - val noUpdateTitle = resources.getString(R.string.no_update_available) - val noUpdateMsg = String.format( - resources.getString(R.string.have_latest_version), - appName, - LanternApp.getSession().appVersion() - ) - showAlertDialog(noUpdateTitle, noUpdateMsg) - } - } - - private fun startUpdateActivity(updateURL: String) { - val intent = Intent() - intent.component = ComponentName( - activity.packageName, - "org.getlantern.lantern.activity.UpdateActivity_", - ) - intent.putExtra("updateUrl", updateURL) - startActivity(intent) - } - - @Subscribe(threadMode = ThreadMode.MAIN) - fun runCheckUpdate(checkUpdate: CheckUpdate) { - val userInitiated = checkUpdate.userInitiated - if (LanternApp.getSession().isPlayVersion && userInitiated) { - Utils.openPlayStore(context) - return - } - if (autoUpdateJob != null && autoUpdateJob!!.isActive) { - Logger.d(TAG, "Already checking for updates") - return - } - autoUpdateJob = lifecycleScope.launch(Dispatchers.IO) { - try { - val deviceInfo: internalsdk.DeviceInfo = DeviceInfo - val updateURL = Internalsdk.checkForUpdates(deviceInfo) - when { - updateURL.isEmpty() -> noUpdateAvailable(userInitiated) - else -> startUpdateActivity(updateURL) - } - } catch (e: Exception) { - Logger.d(TAG, "Unable to check for update: %s", e.message) - } - } - } - @Throws(Exception::class) private fun switchLantern(on: Boolean) { Logger.d(TAG, "switchLantern to %1\$s", on) @@ -517,11 +453,11 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, // is updating the connection if (on) { // Make sure we have the necessary permissions - val neededPermissions: Array = missingPermissions() + val neededPermissions: Array = PermissionUtil.missingPermissions(context) if (neededPermissions.isNotEmpty()) { val msg = StringBuilder() for (permission in neededPermissions) { - if (!hasPermission(permission)) { + if (!PermissionUtil.hasPermission(permission, context)) { msg.append("

") val pm = packageManager try { @@ -599,6 +535,9 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, TAG, "VPN enabled, starting Lantern...", ) + //If user come here it mean user has all permissions needed + // Also user given permission for VPN service dialog as well + LanternApp.getSession().setHasFirstSessionCompleted(true) updateStatus(true) startVpnService() } @@ -607,33 +546,6 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, } } - /*Note - we do not include Manifest.permission.FOREGROUND_SERVICE because this is automatically - granted based on being included in Manifest and will show as denied even if we're eligible - to get it.*/ - private val allRequiredPermissions = arrayOf( - Manifest.permission.INTERNET, - Manifest.permission.ACCESS_WIFI_STATE, - Manifest.permission.ACCESS_NETWORK_STATE, - ) - - private fun missingPermissions(): Array { - val missingPermissions: MutableList = ArrayList() - for (permission in allRequiredPermissions) { - if (!hasPermission(permission)) { - missingPermissions.add(permission) - } - } - return missingPermissions.toTypedArray() - } - - private fun hasPermission(permission: String): Boolean { - val result = ContextCompat.checkSelfPermission( - applicationContext, - permission, - ) == PackageManager.PERMISSION_GRANTED - Logger.debug(PERMISSIONS_TAG, "has permission %s: %s", permission, result) - return result - } override fun onRequestPermissionsResult( requestCode: Int, @@ -685,20 +597,20 @@ class MainActivity : FlutterActivity(), MethodChannel.MethodCallHandler, updateStatus(useVpn) if (useVpn) { startVpnService() + //This check is for new user that will start app first time + // this mean user has already given + // system permissions + LanternApp.getSession().setHasFirstSessionCompleted(true) } + } } private fun startVpnService() { - val splitTunnelingEnabled = vpnModel.splitTunnelingEnabled() - val appsAllowedAccess = ArrayList(vpnModel.appsAllowedAccess()) - Logger.d(TAG, "Apps allowed access to VPN connection: $appsAllowedAccess") val intent: Intent = Intent( this, LanternVpnService::class.java, ).apply { - putExtra("splitTunnelingEnabled", splitTunnelingEnabled) - putStringArrayListExtra("appsAllowedAccess", appsAllowedAccess) action = LanternVpnService.ACTION_CONNECT } diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/activity/afterNotification.kt b/android/app/src/main/kotlin/org/getlantern/lantern/activity/afterNotification.kt deleted file mode 100644 index 40c31dcb2..000000000 --- a/android/app/src/main/kotlin/org/getlantern/lantern/activity/afterNotification.kt +++ /dev/null @@ -1,5 +0,0 @@ -package org.getlantern.lantern.activity - -class afterNotification { - -} \ No newline at end of file diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/fragment/ProgressDialogFragment.kt b/android/app/src/main/kotlin/org/getlantern/lantern/fragment/ProgressDialogFragment.kt new file mode 100644 index 000000000..07ea40015 --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/fragment/ProgressDialogFragment.kt @@ -0,0 +1,29 @@ +package org.getlantern.lantern.fragment + +import android.app.Dialog +import android.app.ProgressDialog +import android.os.Bundle +import androidx.annotation.NonNull +import androidx.fragment.app.DialogFragment + +open class ProgressDialogFragment : DialogFragment() { + + @NonNull + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val msgId: Int? = getArguments()?.getInt("msgId") + val dialog = ProgressDialog(requireContext()) + if (msgId != null) dialog.setMessage(getString(msgId)) + return dialog + } + + companion object { + @JvmStatic + fun newInstance(msgId: Int): ProgressDialogFragment { + val fragment = ProgressDialogFragment() + val args: Bundle = Bundle() + args.putInt("msgId", msgId) + fragment.setArguments(args) + return fragment + } + } +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/model/AccountInitializationStatus.kt b/android/app/src/main/kotlin/org/getlantern/lantern/model/AccountInitializationStatus.kt new file mode 100644 index 000000000..380767a5e --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/model/AccountInitializationStatus.kt @@ -0,0 +1,20 @@ +package org.getlantern.lantern.model + +data class AccountInitializationStatus(val status: AccountInitializationStatus.Status) { + + enum class Status { + PROCESSING, SUCCESS, FAILURE + } + + fun isProcessing(): Boolean { + return status == Status.PROCESSING + } + + fun isSuccess(): Boolean { + return status == Status.SUCCESS + } + + fun isFailure(): Boolean { + return status == Status.FAILURE + } +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/model/Device.kt b/android/app/src/main/kotlin/org/getlantern/lantern/model/Device.kt new file mode 100644 index 000000000..604e0b0cf --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/model/Device.kt @@ -0,0 +1,7 @@ +package org.getlantern.lantern.model + +data class Device( + val id:String, + val name:String, + val created:Long +) diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/model/LanternSessionManager.kt b/android/app/src/main/kotlin/org/getlantern/lantern/model/LanternSessionManager.kt index 58c1dba26..fd6c278f5 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/model/LanternSessionManager.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/model/LanternSessionManager.kt @@ -71,7 +71,7 @@ class LanternSessionManager(application: Application) : SessionManager(applicati } fun getPaymentProvider(): String? { - return prefs.getString(USER_PAYMENT_GATEWAY, "paymentwall") + return prefs.getString(USER_PAYMENT_GATEWAY, "stripe") } fun setSignature(sig: String?) { @@ -182,6 +182,22 @@ class LanternSessionManager(application: Application) : SessionManager(applicati return prefs.getBoolean(SHOW_RENEWAL_PREF, true) } + // appsAllowedAccess returns a list of package names for those applications that are allowed + // to access the VPN connection. If split tunneling is enabled, and any app is added to + // the list, only those applications (and no others) are allowed access. + fun appsAllowedAccess(): List { + var installedApps = db.list(PATH_APPS_DATA + "%") + val apps = mutableListOf() + for (appData in installedApps) { + if (appData.value.allowedAccess) apps.add(appData.value.packageName) + } + return apps + } + + override fun splitTunnelingEnabled(): Boolean { + return prefs.getBoolean(SPLIT_TUNNELING, false) + } + fun setIsProUser(isProUser: Boolean) { prefs.edit().putBoolean(PRO_USER, isProUser).apply() } @@ -322,23 +338,6 @@ class LanternSessionManager(application: Application) : SessionManager(applicati } } - // isPlayVersion checks whether or not the user installed Lantern via - // the Google Play store - override fun isPlayVersion(): Boolean { - if (BuildConfig.PLAY_VERSION || prefs.getBoolean(PLAY_VERSION, false)) { - return true - } - try { - val validInstallers: List = ArrayList(listOf("com.android.vending", "com.google.android.feedback")) - val installer = context.packageManager - .getInstallerPackageName(context.packageName) - return installer != null && validInstallers.contains(installer) - } catch (e: java.lang.Exception) { - Logger.error(TAG, "Error fetching package information: " + e.message) - } - return false - } - fun setPlayVersion(playVersion: Boolean) { prefs.edit().putBoolean(PLAY_VERSION, playVersion).apply() } @@ -350,6 +349,8 @@ class LanternSessionManager(application: Application) : SessionManager(applicati private const val USER_LEVEL = "userLevel" private const val PRO_USER = "prouser" private const val DEVICES = "devices" + private const val PATH_APPS_DATA = "/appsData/" + private const val SPLIT_TUNNELING = "/splitTunneling" private const val PLANS = "/plans/" private const val PAYMENT_METHODS = "/paymentMethods/" private const val PRO_EXPIRED = "proexpired" diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/model/VpnState.kt b/android/app/src/main/kotlin/org/getlantern/lantern/model/VpnState.kt new file mode 100644 index 000000000..c4fd0ccc7 --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/model/VpnState.kt @@ -0,0 +1,5 @@ +package org.getlantern.lantern.model + +data class VpnState( + val useVpn:Boolean +) diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/service/AutoStarter.kt b/android/app/src/main/kotlin/org/getlantern/lantern/service/AutoStarter.kt new file mode 100644 index 000000000..0c724f126 --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/service/AutoStarter.kt @@ -0,0 +1,20 @@ +package org.getlantern.lantern.service + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import org.getlantern.mobilesdk.Logger + +open class AutoStarter : BroadcastReceiver() { + + override fun onReceive(context: Context, intent: Intent) { + Logger.d(TAG, "Automatically starting Lantern Service on: ${intent.getAction()}") + val serviceIntent = Intent(context, LanternService_::class.java) + .putExtra(LanternService.AUTO_BOOTED, intent.getAction() == Intent.ACTION_BOOT_COMPLETED) + context.startService(serviceIntent) + } + + companion object { + private val TAG = AutoStarter::class.java.simpleName + } +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt b/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt new file mode 100644 index 000000000..ac39595ae --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/service/LanternService.kt @@ -0,0 +1,199 @@ +package org.getlantern.lantern.service + +import android.app.Service +import android.content.Intent +import android.os.Handler +import android.os.IBinder +import android.os.Looper +import androidx.annotation.Nullable +import com.google.gson.JsonObject +import okhttp3.HttpUrl +import okhttp3.Response +import org.androidannotations.annotations.EService +import org.getlantern.lantern.BuildConfig +import org.getlantern.lantern.LanternApp +import org.getlantern.lantern.R +import org.getlantern.lantern.model.AccountInitializationStatus +import org.getlantern.lantern.model.LanternHttpClient +import org.getlantern.lantern.model.LanternStatus +import org.getlantern.lantern.model.LanternStatus.Status +import org.getlantern.lantern.model.ProError +import org.getlantern.lantern.model.ProUser +import org.getlantern.lantern.util.AutoUpdater +import org.getlantern.lantern.util.Json +import org.getlantern.mobilesdk.Lantern +import org.getlantern.mobilesdk.LanternNotRunningException +import org.getlantern.mobilesdk.Logger +import org.getlantern.mobilesdk.StartResult +import org.getlantern.mobilesdk.model.LoConf +import org.getlantern.mobilesdk.model.LoConfCallback +import org.greenrobot.eventbus.EventBus +import java.util.Random +import java.util.concurrent.atomic.AtomicBoolean + +@EService +open class LanternService : Service(), Runnable { + + companion object { + private val TAG = LanternService::class.java.simpleName + private const val MAX_CREATE_USER_TRIES = 11 + private const val baseWaitMs = 3000 + private val lanternClient: LanternHttpClient = LanternApp.getLanternHttpClient() + public val AUTO_BOOTED = "autoBooted" + } + + private var thread: Thread? = null + private val createUserHandler: Handler = Handler(Looper.getMainLooper()) + private val createUserRunnable: CreateUser = CreateUser(this) + private val random: Random = Random() + private val serviceIcon: Int = if (LanternApp.getSession().chatEnabled()) { + R.drawable.status_chat + } else { + R.drawable.status_plain + } + private val helper: ServiceHelper = ServiceHelper(this, serviceIcon, R.string.ready_to_connect) + private val started: AtomicBoolean = AtomicBoolean() + private lateinit var autoUpdater: AutoUpdater + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + autoUpdater = AutoUpdater(this) + val autoBooted = intent.getBooleanExtra(AUTO_BOOTED, false) + Logger.d(TAG, "Called onStartCommand, autoBooted?: $autoBooted") + if (autoBooted) { + Logger.debug( + TAG, + "Attempted to auto boot but user has not onboarded to messaging, stop LanternService", + ) + stopSelf() + return START_NOT_STICKY + } + if (started.compareAndSet(false, true)) { + Logger.d(TAG, "Starting Lantern service thread") + thread = Thread(this, "LanternService") + thread?.start() + } + + return super.onStartCommand(intent, flags, startId) + } + + @Nullable + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun run() { + // move the current thread of the service to the background + android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND) + + val locale = LanternApp.getSession().language + val settings = LanternApp.getSession().settings + + try { + Logger.debug(TAG, "Successfully loaded config: $settings") + val result: StartResult = + Lantern.enable(this, locale, settings, LanternApp.getSession()) + LanternApp.getSession().setStartResult(result) + afterStart() + } catch (lnre: LanternNotRunningException) { + Logger.e(TAG, "Unable to start LanternService", lnre) + throw RuntimeException("Could not start Lantern", lnre) + } + } + + private fun afterStart() { + if (LanternApp.getSession().userId().toInt() == 0) { + // create a user if no user id is stored + EventBus.getDefault().post( + AccountInitializationStatus(AccountInitializationStatus.Status.PROCESSING), + ) + createUser(0) + } + + if (!BuildConfig.PLAY_VERSION && !BuildConfig.DEVELOPMENT_MODE) { + // check if an update is available + autoUpdater.checkForUpdates() + } + + EventBus.getDefault().postSticky(LanternStatus(Status.ON)) + + // fetch latest loconf + LoConf.Companion.fetch(object : LoConfCallback { + override fun onSuccess(loconf: LoConf) { + EventBus.getDefault().post(loconf) + } + }) + } + + private fun createUser(attempt: Int) { + val maxBackOffTime = 60000L // maximum backoff time in milliseconds (e.g., 1 minute) + val timeOut = + (baseWaitMs * Math.pow(2.0, attempt.toDouble())).toLong().coerceAtMost(maxBackOffTime) + createUserHandler.postDelayed(createUserRunnable, timeOut) + } + + private class CreateUser(val service: LanternService) : Runnable, + LanternHttpClient.ProCallback { + + private var attempts: Int = 0 + + override fun run() { + val url: HttpUrl = LanternHttpClient.createProUrl("/user-create") + val json: JsonObject = JsonObject() + json.addProperty("locale", LanternApp.getSession().language) + lanternClient.post(url, LanternHttpClient.createJsonBody(json), this) + } + + override fun onFailure(@Nullable throwable: Throwable?, @Nullable error: ProError?) { + if (attempts >= MAX_CREATE_USER_TRIES) { + Logger.error(TAG, "Max. number of tries made to create Pro user") + EventBus.getDefault().postSticky( + AccountInitializationStatus(AccountInitializationStatus.Status.FAILURE), + ) + return + } + attempts++ + service.createUser(attempts) + } + + override fun onSuccess(response: Response, result: JsonObject) { + val user: ProUser? = Json.gson.fromJson(result, ProUser::class.java) + if (user == null) { + Logger.error(TAG, "Unable to parse user from JSON") + return + } + service.createUserHandler.removeCallbacks(service.createUserRunnable) + Logger.debug(TAG, "Created new Lantern user: ${user.newUserDetails()}") + LanternApp.getSession().setUserIdAndToken(user.getUserId(), user.getToken()) + val referral = user.getReferral() + if (!referral.isEmpty()) { + LanternApp.getSession().setCode(referral) + } + EventBus.getDefault().postSticky(LanternStatus(Status.ON)) + EventBus.getDefault().postSticky( + AccountInitializationStatus(AccountInitializationStatus.Status.SUCCESS), + ) + } + } + + override fun onDestroy() { + super.onDestroy() + if (!started.get()) { + Logger.debug(TAG, "Service never started, exit immediately") + return + } + helper.onDestroy() + thread?.interrupt() + try { + Logger.debug(TAG, "Unregistering screen state receiver") + createUserHandler.removeCallbacks(createUserRunnable) + } catch (e: Exception) { + Logger.error(TAG, "Exception", e) + } + // We want to keep the service running as much as possible to allow receiving messages, so + // we start it back up automatically as explained at https://stackoverflow.com/a/52258125. + val broadcastIntent = Intent() + .setAction("restartservice") + .setClass(this, AutoStarter::class.java) + sendBroadcast(broadcastIntent) + } +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/util/ActivityExt.kt b/android/app/src/main/kotlin/org/getlantern/lantern/util/ActivityExt.kt index 7482c5c7b..324778773 100644 --- a/android/app/src/main/kotlin/org/getlantern/lantern/util/ActivityExt.kt +++ b/android/app/src/main/kotlin/org/getlantern/lantern/util/ActivityExt.kt @@ -1,13 +1,19 @@ package org.getlantern.lantern.util import android.app.Activity +import android.app.AlarmManager +import android.app.PendingIntent +import android.content.Context import android.content.DialogInterface +import android.content.Intent import android.graphics.drawable.Drawable +import android.os.Process import android.view.View import android.widget.ImageView import android.widget.TextView import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.getlantern.lantern.R +import org.getlantern.lantern.MainActivity import org.getlantern.lantern.model.Utils import org.getlantern.mobilesdk.Logger @@ -20,6 +26,30 @@ fun Activity.showErrorDialog( ) } +fun Activity.openHome() { + startActivity( + Intent(this, MainActivity::class.java) + .apply { + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK) + } + ) + overridePendingTransition(android.R.anim.fade_in, android.R.anim.fade_out) +} + +fun Activity.restartApp() { + val mStartActivity = Intent(this, MainActivity::class.java) + val mPendingIntentId = 123456 + val mPendingIntent: PendingIntent = PendingIntent.getActivity( + this, + mPendingIntentId, + mStartActivity, + PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE + ) + val mgr: AlarmManager = getSystemService(Context.ALARM_SERVICE) as AlarmManager + mgr.set(AlarmManager.RTC, java.lang.System.currentTimeMillis() + 100, mPendingIntent) + Process.killProcess(Process.myPid()) +} + @JvmOverloads fun Activity.showAlertDialog( title: CharSequence? = null, diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/util/AutoUpdater.kt b/android/app/src/main/kotlin/org/getlantern/lantern/util/AutoUpdater.kt new file mode 100644 index 000000000..6648e4298 --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/util/AutoUpdater.kt @@ -0,0 +1,76 @@ +package org.getlantern.lantern.util + +import android.app.Activity +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.res.Resources +import internalsdk.Internalsdk +import kotlinx.coroutines.* +import org.getlantern.lantern.LanternApp +import org.getlantern.lantern.R +import org.getlantern.lantern.model.Utils +import org.getlantern.mobilesdk.Logger + +class AutoUpdater(val context: Context, val activity: Activity? = null) { + + private var autoUpdateJob: Job? = null + private var resources: Resources = context.resources + + private fun startUpdateActivity(updateURL: String) { + context.startActivity( + Intent().apply { + component = ComponentName( + context.packageName, + "org.getlantern.lantern.activity.UpdateActivity_", + ) + putExtra("updateUrl", updateURL) + setFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + }, + ) + } + + private fun noUpdateAvailable() { + if (activity == null) return + activity.runOnUiThread { + val appName = resources.getString(R.string.app_name) + val noUpdateTitle = resources.getString(R.string.no_update_available) + val noUpdateMsg = String.format( + resources.getString(R.string.have_latest_version), + appName, + LanternApp.getSession().appVersion(), + ) + activity.showAlertDialog(noUpdateTitle, noUpdateMsg) + } + } + + fun checkForUpdates() { + if (LanternApp.getSession().isPlayVersion && activity != null) { + Utils.openPlayStore(context) + return + } + + if (autoUpdateJob != null && autoUpdateJob!!.isActive) { + Logger.d(TAG, "Already checking for updates") + return + } + Logger.d(TAG, "Checking for updates") + autoUpdateJob = CoroutineScope(Dispatchers.IO).launch { + try { + val deviceInfo: internalsdk.DeviceInfo = DeviceInfo + val updateURL = Internalsdk.checkForUpdates(deviceInfo) + when { + updateURL.isEmpty() -> noUpdateAvailable() + else -> startUpdateActivity(updateURL) + } + } catch (e: Exception) { + Logger.d(TAG, "Unable to check for update: %s", e.message) + } + } + if (activity != null) runBlocking { autoUpdateJob?.let { it.join() } } + } + + companion object { + private val TAG = AutoUpdater::class.java.simpleName + } +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/util/PermissionUtil.kt b/android/app/src/main/kotlin/org/getlantern/lantern/util/PermissionUtil.kt new file mode 100644 index 000000000..cbfb5f786 --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/util/PermissionUtil.kt @@ -0,0 +1,52 @@ +package org.getlantern.lantern.util + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import androidx.core.content.ContextCompat +import org.getlantern.mobilesdk.Logger + +class PermissionUtil { + companion object { + private val TAG = PermissionUtil::class.java.simpleName + private val PERMISSIONS_TAG = "${PermissionUtil.TAG}.permissions" + + + /*Note - we do not include Manifest.permission.FOREGROUND_SERVICE because this is automatically + granted based on being included in Manifest and will show as denied even if we're eligible + to get it.*/ + private val allRequiredPermissions = arrayOf( + Manifest.permission.INTERNET, + Manifest.permission.ACCESS_WIFI_STATE, + Manifest.permission.ACCESS_NETWORK_STATE, + ) + + /** + * This function checks the necessary permissions for the VPN to operate properly. + * If permissions are missing, it collects them into an array and returns it. + * + * @param context the application context is necessary for checking permissions. + * @return An array of strings, where each string represents a missing permission. + */ + fun missingPermissions(context: Context): Array { + val missingPermissions: MutableList = ArrayList() + for (permission in allRequiredPermissions) { + if (!hasPermission(permission, context)) { + missingPermissions.add(permission) + } + } + return missingPermissions.toTypedArray() + } + + + fun hasPermission(permission: String, context: Context): Boolean { + val result = ContextCompat.checkSelfPermission( + context, + permission, + ) == PackageManager.PERMISSION_GRANTED + Logger.debug(PERMISSIONS_TAG, "has permission %s: %s", permission, result) + return result + } + } + +} \ No newline at end of file diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/vpn/GoTun2SocksProvider.kt b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/GoTun2SocksProvider.kt new file mode 100644 index 000000000..5c62def0d --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/GoTun2SocksProvider.kt @@ -0,0 +1,135 @@ +package org.getlantern.lantern.vpn + +import android.app.PendingIntent +import android.content.Intent +import android.content.pm.PackageManager +import android.net.VpnService +import android.os.ParcelFileDescriptor +import internalsdk.Internalsdk +import org.getlantern.lantern.LanternApp +import org.getlantern.lantern.MainActivity +import org.getlantern.mobilesdk.Logger +import org.getlantern.mobilesdk.model.SessionManager +import java.util.Locale + +class GoTun2SocksProvider( + val packageManager: PackageManager, + val splitTunnelingEnabled: Boolean, + val appsAllowedAccess: Set, +) : Provider { + + companion object { + private val TAG = GoTun2SocksProvider::class.java.simpleName + private const val sessionName = "LanternVpn" + private const val privateAddress = "10.0.0.2" + private const val VPN_MTU = 1500 + } + + private var mInterface: ParcelFileDescriptor? = null + + @Synchronized + private fun createBuilder( + vpnService: VpnService, + builder: VpnService.Builder, + ): ParcelFileDescriptor? { + // Set the locale to English + // since the VpnBuilder encounters + // issues with non-English numerals + // See https://code.google.com/p/android/issues/detail?id=61096 + Locale.setDefault(Locale("en")) + + // Configure a builder while parsing the parameters. + builder.setMtu(VPN_MTU) + builder.addAddress(privateAddress, 24) + // route IPv4 through VPN + builder.addRoute("0.0.0.0", 0) + + if (splitTunnelingEnabled) { + // Exclude any app that's not in our split tunneling allowed list + for (installedApp in packageManager.getInstalledApplications(0)) { + if (!appsAllowedAccess.contains(installedApp.packageName)) { + try { + Logger.debug(TAG, "Excluding " + installedApp.packageName + " from VPN") + builder.addDisallowedApplication(installedApp.packageName) + } catch (e: PackageManager.NameNotFoundException) { + throw RuntimeException("Unable to exclude " + installedApp.packageName + " from VPN", e) + } + } + } + } + + // Never capture traffic originating from Lantern itself in the VPN. + try { + val ourPackageName = vpnService.getPackageName() + builder.addDisallowedApplication(ourPackageName) + } catch (e: PackageManager.NameNotFoundException) { + throw RuntimeException("Unable to exclude Lantern from routes", e) + } + // don't currently route IPv6 through VPN because our proxies don't currently support IPv6 + // see https://github.com/getlantern/lantern-internal/issues/4961 + // Note - if someone performs a DNS lookup for an IPv6 only host like ipv6.google.com, dnsgrab + // will return an IPv4 address for that site, causing the traffic to get routed through the VPN. + // builder.addRoute("0:0:0:0:0:0:0:0", 0) + + // this is a fake DNS server. The precise IP doesn't matter because Lantern will intercept and + // route all DNS traffic to dnsgrab internally anyway. + builder.addDnsServer(SessionManager.fakeDnsIP) + + val intent = Intent(vpnService, MainActivity::class.java) + val pendingIntent: PendingIntent = PendingIntent.getActivity( + vpnService, + 0, + intent, + PendingIntent.FLAG_IMMUTABLE, + ) + builder.setConfigureIntent(pendingIntent) + + builder.setSession(sessionName) + + // Create a new mInterface using the builder and save the parameters. + mInterface = builder.establish() + Logger.d(TAG, "New mInterface: " + mInterface) + return mInterface + } + + override fun run( + vpnService: VpnService, + builder: VpnService.Builder, + socksAddr: String, + dnsGrabAddr: String, + ) { + Logger.d(TAG, "run") + + val defaultLocale = Locale.getDefault() + try { + Logger.debug(TAG, "Creating VpnBuilder before starting tun2socks") + val intf: ParcelFileDescriptor? = createBuilder(vpnService, builder) + Logger.debug(TAG, "Running tun2socks") + if (intf != null) { + Internalsdk.tun2Socks( + intf.getFd().toLong(), + socksAddr, + dnsGrabAddr, + VPN_MTU.toLong(), + LanternApp.getSession(), + ) + } + } catch (t: Throwable) { + Logger.e(TAG, "Exception while handling TUN device", t) + } finally { + Locale.setDefault(defaultLocale) + } + } + + @Synchronized + @Throws(Exception::class) + override fun stop() { + Logger.d(TAG, "stop") + Internalsdk.stopTun2Socks() + mInterface?.let { + Logger.d(TAG, "closing interface") + mInterface!!.close() + mInterface = null + } + } +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt new file mode 100644 index 000000000..70f551a93 --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/LanternVpnService.kt @@ -0,0 +1,128 @@ +package org.getlantern.lantern.vpn + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.ServiceConnection +import android.net.VpnService +import android.os.IBinder +import org.getlantern.lantern.LanternApp +import org.getlantern.lantern.service.LanternService_ +import org.getlantern.mobilesdk.Logger + +class LanternVpnService : VpnService(), Runnable { + + companion object { + const val ACTION_CONNECT = "org.getlantern.lantern.vpn.START" + const val ACTION_DISCONNECT = "org.getlantern.lantern.vpn.STOP" + private val TAG = LanternVpnService::class.java.simpleName + } + + private var provider: Provider? = null + + private val lanternServiceConnection: ServiceConnection = object : ServiceConnection { + override fun onServiceDisconnected(name: ComponentName) { + Logger.e(TAG, "LanternService disconnected, disconnecting VPN") + stop() + } + override fun onServiceConnected(name: ComponentName, service: IBinder) {} + } + + override fun onCreate() { + super.onCreate() + Logger.d(TAG, "VpnService created") + bindService( + Intent(this, LanternService_::class.java), + lanternServiceConnection, + Context.BIND_AUTO_CREATE, + ) + } + + override fun onDestroy() { + Logger.d(TAG, "destroyed") + doStop() + super.onDestroy() + unbindService(lanternServiceConnection) + } + + override fun onRevoke() { + Logger.d(TAG, "revoked") + stop() + } + + override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int { + // Somehow we are getting null here when running on Android 5.0 + // Handling null intent scenario + if (intent == null) { + Logger.d(TAG, "LanternVpnService: Received null intent, service is being restarted") + return START_STICKY + } + return if (intent.action == ACTION_DISCONNECT) { + stop() + START_NOT_STICKY + } else { + LanternApp.getSession().updateVpnPreference(true) + connect() + START_STICKY + } + } + + private fun connect() { + Logger.d(TAG, "connect") + Thread(this, "VpnService").start() + } + + override fun run() { + try { + Logger.d(TAG, "Loading Lantern library") + getOrInitProvider()?.run( + this, + Builder(), + LanternApp.getSession().sOCKS5Addr, + LanternApp.getSession().dNSGrabAddr, + ) + } catch (e: Exception) { + Logger.error(TAG, "Error running VPN", e) + } finally { + Logger.debug(TAG, "Lantern terminated.") + stop() + } + } + + fun stop() { + doStop() + stopSelf() + Logger.d(TAG, "Done stopping") + } + + private fun doStop() { + Logger.d(TAG, "stop") + try { + Logger.d(TAG, "getting provider") + val provider: Provider? = getOrInitProvider() + Logger.d(TAG, "stopping provider") + provider?.stop() + } catch (t: Throwable) { + Logger.e(TAG, "error stopping provider", t) + } + try { + Logger.d(TAG, "updating vpn preference") + LanternApp.getSession().updateVpnPreference(false) + } catch (t: Throwable) { + Logger.e(TAG, "error updating vpn preference", t) + } + } + + @Synchronized fun getOrInitProvider(): Provider? { + Logger.d(TAG, "getOrInitProvider()") + if (provider == null) { + Logger.d(TAG, "Using Go tun2socks") + provider = GoTun2SocksProvider( + getPackageManager(), + LanternApp.getSession().splitTunnelingEnabled(), + HashSet(LanternApp.getSession().appsAllowedAccess()), + ) + } + return provider + } +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/vpn/Provider.kt b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/Provider.kt new file mode 100644 index 000000000..3f0af3cb0 --- /dev/null +++ b/android/app/src/main/kotlin/org/getlantern/lantern/vpn/Provider.kt @@ -0,0 +1,12 @@ +package org.getlantern.lantern.vpn + +import android.net.VpnService + +// A provider provides the implementation of VPN internals. +interface Provider { + @Throws(Exception::class) + fun run(vpnService: VpnService, builder: VpnService.Builder, socksAddr: String, dnsGrabAddr: String) + + @Throws(Exception::class) + fun stop() +} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/widget/PinField.kt b/android/app/src/main/kotlin/org/getlantern/lantern/widget/PinField.kt deleted file mode 100644 index 433b4ffc6..000000000 --- a/android/app/src/main/kotlin/org/getlantern/lantern/widget/PinField.kt +++ /dev/null @@ -1,351 +0,0 @@ -package org.getlantern.lantern.widget - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Color -import android.graphics.Paint -import android.text.InputFilter -import android.text.method.PasswordTransformationMethod -import android.text.method.TransformationMethod -import android.util.AttributeSet -import android.view.View -import android.view.inputmethod.EditorInfo -import android.view.inputmethod.InputMethodManager -import androidx.appcompat.widget.AppCompatEditText -import androidx.core.content.ContextCompat -import org.getlantern.lantern.R -import org.getlantern.lantern.util.Util - -open class PinField : AppCompatEditText { - - private val defaultWidth = Util.dpToPx(60f).toInt() - - companion object { - const val DEFAULT_DISTANCE_IN_BETWEEN = -1f - } - - protected var distanceInBetween: Float = 0f - set(value) { - field = value - requestLayout() - invalidate() - } - - var numberOfFields = 4 - set(value) { - field = value - limitCharsToNoOfFields() - invalidate() - } - - protected var singleFieldWidth = 0 - - var lineThickness = Util.dpToPx(1.0f) - set(value) { - field = value - fieldPaint.strokeWidth = field - highlightPaint.strokeWidth = highLightThickness - invalidate() - } - - var fieldColor = ContextCompat.getColor(context, R.color.grey_4) - set(value) { - field = value - fieldPaint.color = field - invalidate() - } - - var highlightPaintColor = ContextCompat.getColor(context, R.color.tertiary_green) - set(value) { - field = value - highlightPaint.color = field - invalidate() - } - - var isCursorEnabled = false - set(value) { - field = value - invalidate() - } - - protected var fieldPaint = Paint() - - protected var textPaint = Paint() - - protected var hintPaint = Paint() - - protected var highlightPaint = Paint() - - protected var yPadding = Util.dpToPx(10f) - - protected var highlightSingleFieldType = HighlightType.ALL_FIELDS - - private var lastCursorChangeState: Long = -1 - - private var cursorCurrentVisible = true - - private val cursorTimeout = 500L - - var isCustomBackground = false - set(value) { - if (!value) { - setBackgroundResource(R.color.transparent) - } - field = value - } - - var highLightThickness = lineThickness - get() { - return lineThickness + lineThickness * 0.7f - } - - var onTextCompleteListener: OnTextCompleteListener? = null - - var fieldBgColor = ContextCompat.getColor(context, R.color.tertiary_green) - set(value) { - field = value - fieldBgPaint.color = fieldBgColor - invalidate() - } - - var fieldBgPaint = Paint() - - init { - limitCharsToNoOfFields() - setWillNotDraw(false) - maxLines = 1 - setSingleLine(true) - - fieldPaint.color = fieldColor - fieldPaint.isAntiAlias = true - fieldPaint.style = Paint.Style.STROKE - fieldPaint.strokeWidth = lineThickness - - textPaint.color = currentTextColor - textPaint.isAntiAlias = true - textPaint.textSize = textSize - textPaint.textAlign = Paint.Align.CENTER - textPaint.style = Paint.Style.FILL - - hintPaint.color = hintTextColors.defaultColor - hintPaint.isAntiAlias = true - hintPaint.textSize = textSize - hintPaint.textAlign = Paint.Align.CENTER - hintPaint.style = Paint.Style.FILL - - highlightPaint = Paint(fieldPaint) - highlightPaint.color = highlightPaintColor - highlightPaint.strokeWidth = highLightThickness - - fieldBgColor = Color.TRANSPARENT - fieldBgPaint.style = Paint.Style.FILL - } - - constructor(context: Context) : super(context) - - constructor(context: Context, attr: AttributeSet) : super(context, attr) { - initParams(attr) - } - - constructor(context: Context, attr: AttributeSet, defStyle: Int) : super(context, attr, defStyle) { - initParams(attr) - } - - private fun initParams(attr: AttributeSet) { - val a = context.theme.obtainStyledAttributes(attr, R.styleable.PinField, 0, 0) - - try { - numberOfFields = a.getInt(R.styleable.PinField_noOfFields, numberOfFields) - lineThickness = a.getDimension(R.styleable.PinField_lineThickness, lineThickness) - distanceInBetween = a.getDimension(R.styleable.PinField_distanceInBetween, DEFAULT_DISTANCE_IN_BETWEEN) - fieldColor = a.getColor(R.styleable.PinField_fieldColor, fieldColor) - highlightPaintColor = a.getColor(R.styleable.PinField_highlightColor, highlightPaintColor) - isCustomBackground = a.getBoolean(R.styleable.PinField_isCustomBackground, false) - isCursorEnabled = a.getBoolean(R.styleable.PinField_isCursorEnabled, false) - highlightSingleFieldType = if (a.getBoolean(R.styleable.PinField_highlightEnabled, true)) HighlightType.ALL_FIELDS else HighlightType.NO_FIELDS - highlightSingleFieldType = if (a.getBoolean(R.styleable.PinField_highlightSingleFieldMode, false)) HighlightType.CURRENT_FIELD else HighlightType.ALL_FIELDS - highlightSingleFieldType = - HighlightType.getEnum(a.getInt(R.styleable.PinField_highlightType, highlightSingleFieldType.code)) - fieldBgColor = a.getColor(R.styleable.PinField_fieldBgColor, fieldBgColor) - singleFieldWidth = a.getDimension(R.styleable.PinField_singleFieldWidth, -1f).toInt() - textPaint.typeface = typeface - } finally { - a.recycle() - } - } - - override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { - if (singleFieldWidth == -1) { - singleFieldWidth = width / numberOfFields - } - val width = singleFieldWidth * numberOfFields + distanceInBetween * (numberOfFields - 1) - setMeasuredDimension(width.toInt(), getViewHeight(singleFieldWidth, heightMeasureSpec)) - } - - protected open fun getViewWidth(desiredWidth: Int, widthMeasureSpec: Int): Int { - val widthMode = View.MeasureSpec.getMode(widthMeasureSpec) - val widthSize = View.MeasureSpec.getSize(widthMeasureSpec) - - // Measure Width - return when (widthMode) { - MeasureSpec.EXACTLY -> widthSize - MeasureSpec.AT_MOST -> Math.min(desiredWidth, widthSize) - MeasureSpec.UNSPECIFIED -> desiredWidth - else -> desiredWidth - } - } - - protected open fun getViewHeight(desiredHeight: Int, heightMeasureSpec: Int): Int { - val heightMode = View.MeasureSpec.getMode(heightMeasureSpec) - val heightSize = View.MeasureSpec.getSize(heightMeasureSpec) - - // Measure Height - return when (heightMode) { - View.MeasureSpec.EXACTLY -> heightSize - View.MeasureSpec.AT_MOST -> Math.min(desiredHeight, heightSize) - View.MeasureSpec.UNSPECIFIED -> desiredHeight - else -> desiredHeight - } - } - - override fun onSelectionChanged(selStart: Int, selEnd: Int) { - this.setSelection(this.text!!.length) - } - - final override fun setWillNotDraw(willNotDraw: Boolean) { - super.setWillNotDraw(willNotDraw) - } - - override fun onCheckIsTextEditor(): Boolean { - return true - } - - protected open fun getDefaultDistanceInBetween(): Float { - return (singleFieldWidth / (numberOfFields - 1)).toFloat() - } - - private fun limitCharsToNoOfFields() { - val filterArray = arrayOfNulls(1) - filterArray[0] = InputFilter.LengthFilter(numberOfFields) - filters = filterArray - } - - override fun onTextChanged(text: CharSequence?, start: Int, lengthBefore: Int, lengthAfter: Int) { - super.onTextChanged(text, start, lengthBefore, lengthAfter) - if (text != null && text.length == numberOfFields) { - val shouldCloseKeyboard = onTextCompleteListener?.onTextComplete(text.toString()) - ?: false - if (shouldCloseKeyboard) { - val imm = context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager - imm.hideSoftInputFromWindow(windowToken, 0) - } - } - } - - protected fun shouldDrawHint(): Boolean { - return (text!!.isEmpty() || text!!.isBlank()) && - !isFocused && (hint != null && hint.isNotBlank() && hint.isNotEmpty()) - } - - protected fun drawCursor(canvas: Canvas?, x: Float, y1: Float, y2: Float, paint: Paint) { - if (System.currentTimeMillis() - lastCursorChangeState > 500) { - cursorCurrentVisible = !cursorCurrentVisible - lastCursorChangeState = System.currentTimeMillis() - } - - if (cursorCurrentVisible) { - canvas?.drawLine(x, y1, x, y2, paint) - } - - postInvalidateDelayed(cursorTimeout) - } - - final override fun setBackgroundResource(resId: Int) { - super.setBackgroundResource(resId) - } - - protected fun highlightNextField(): Boolean { - return highlightSingleFieldType == HighlightType.CURRENT_FIELD - } - - protected fun highlightCompletedFields(): Boolean { - return highlightSingleFieldType == HighlightType.COMPLETED_FIELDS - } - - protected fun highlightAllFields(): Boolean { - return highlightSingleFieldType == HighlightType.ALL_FIELDS - } - - protected fun highlightNoFields(): Boolean { - return highlightSingleFieldType == HighlightType.NO_FIELDS - } - - protected fun highlightLogic(currentPosition: Int, textLength: Int?, onHighlight: () -> Unit) { - if (hasFocus() && !highlightNoFields()) { - when { - highlightNextField() && currentPosition == textLength ?: 0 -> { - onHighlight.invoke() - } - highlightCompletedFields() && currentPosition < textLength ?: 0 -> { - onHighlight.invoke() - } - } - } - } - - // There is a issue where android transformation method is not set to password so as a work around - // the transformation method used is hardcoded - // the check condition used in isPassword() is taken from TextView.java constructor - protected fun getCharAt(i: Int): Char? { - return getPinFieldTransformation().getTransformation(text, this)?.getOrNull(i) ?: text?.getOrNull(i) - } - - private fun getPinFieldTransformation(): TransformationMethod { - if (isPassword()) { - return PasswordTransformationMethod.getInstance() - } - - return transformationMethod - } - - private fun isPassword(): Boolean { - val variation = inputType and (EditorInfo.TYPE_MASK_CLASS or EditorInfo.TYPE_MASK_VARIATION) - val passwordInputType = ( - variation - == EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD - ) - val webPasswordInputType = ( - variation - == EditorInfo.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_WEB_PASSWORD - ) - val numberPasswordInputType = ( - variation - == EditorInfo.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD - ) - - return passwordInputType || webPasswordInputType || - numberPasswordInputType - } - - interface OnTextCompleteListener { - /** - * @return return true if keyboard should be closed after text is entered - */ - fun onTextComplete(enteredText: String): Boolean - } -} - -enum class HighlightType(val code: Int) { - ALL_FIELDS(0), CURRENT_FIELD(1), COMPLETED_FIELDS(2), NO_FIELDS(3); - - companion object { - fun getEnum(code: Int): HighlightType { - for (type in HighlightType.values()) { - if (type.code == code) { - return type - } - } - return ALL_FIELDS - } - } -} diff --git a/android/app/src/main/kotlin/org/getlantern/lantern/widget/SquarePinField.kt b/android/app/src/main/kotlin/org/getlantern/lantern/widget/SquarePinField.kt deleted file mode 100644 index 5e2b34142..000000000 --- a/android/app/src/main/kotlin/org/getlantern/lantern/widget/SquarePinField.kt +++ /dev/null @@ -1,98 +0,0 @@ -package org.getlantern.lantern.widget - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.os.Build -import android.util.AttributeSet -import org.getlantern.lantern.R -import org.getlantern.lantern.util.Util - -class SquarePinField : PinField { - - private var cornerRadius = 0f - set(value) { - field = value - invalidate() - } - - private val cursorPadding = Util.dpToPx(5f) - - constructor(context: Context) : super(context) - - constructor(context: Context, attr: AttributeSet) : super(context, attr) { - initParams(attr) - } - - constructor(context: Context, attr: AttributeSet, defStyle: Int) : super(context, attr, defStyle) { - initParams(attr) - } - - private fun initParams(attr: AttributeSet) { - val a = context.theme.obtainStyledAttributes(attr, R.styleable.SquarePinField, 0, 0) - try { - cornerRadius = a.getDimension(R.styleable.SquarePinField_cornerRadius, cornerRadius) - } finally { - a.recycle() - } - } - - override fun onDraw(canvas: Canvas) { - for (i in 0 until numberOfFields) { - val padding = distanceInBetween - val paddedX1 = i * singleFieldWidth + padding * i - val paddedX2 = paddedX1 + singleFieldWidth - val paddedY1 = 0f - val paddedY2 = height.toFloat() - val textX = ((paddedX2 - paddedX1) / 2) + paddedX1 - val textY = ((paddedY2 - paddedY1) / 2 + paddedY1) + lineThickness + (textPaint.textSize / 4) + Util.dpToPx(4f) - val character: Char? = getCharAt(i) - - drawRect(canvas, paddedX1, paddedY1, paddedX2, paddedY2, fieldBgPaint) - - if (highlightAllFields() && hasFocus()) { - drawRect(canvas, paddedX1, paddedY1, paddedX2, paddedY2, highlightPaint) - } else { - drawRect(canvas, paddedX1, paddedY1, paddedX2, paddedY2, fieldPaint) - } - - if (character != null) { - canvas.drawText(character.toString(), textX, textY, textPaint) - } - - if (shouldDrawHint()) { - val hintChar = hint.getOrNull(i) - if (hintChar != null) { - canvas.drawText(hintChar.toString(), textX, textY, hintPaint) - } - } - - if (hasFocus() && i == text?.length ?: 0) { - if (isCursorEnabled) { - val cursorPadding = cursorPadding + highLightThickness - val cursorY1 = paddedY1 + cursorPadding - val cursorY2 = paddedY2 - cursorPadding - drawCursor(canvas, textX, cursorY1, cursorY2, highlightPaint) - } - } - highlightLogic(i, text?.length) { - drawRect(canvas, paddedX1, paddedY1, paddedX2, paddedY2, highlightPaint) - } - } - } - - private fun drawRect( - canvas: Canvas, - paddedX1: Float, - paddedY1: Float, - paddedX2: Float, - paddedY2: Float, - paint: Paint - ) { - if (cornerRadius > 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { - canvas.drawRoundRect(paddedX1, paddedY1, paddedX2, paddedY2, cornerRadius, cornerRadius, paint) - } else { - canvas.drawRect(paddedX1, paddedY1, paddedX2, paddedY2, paint) - } - } -} diff --git a/android/app/src/main/res/drawable-xxhdpi/ic_lantern_bg.png b/android/app/src/main/res/drawable-xxhdpi/ic_lantern_bg.png deleted file mode 100644 index cf9ab3e9c..000000000 Binary files a/android/app/src/main/res/drawable-xxhdpi/ic_lantern_bg.png and /dev/null differ diff --git a/android/app/src/main/res/layout/activity_check_out_reseller.xml b/android/app/src/main/res/layout/activity_check_out_reseller.xml deleted file mode 100644 index 7d87f6bed..000000000 --- a/android/app/src/main/res/layout/activity_check_out_reseller.xml +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/res/layout/activity_link_device.xml b/android/app/src/main/res/layout/activity_link_device.xml deleted file mode 100644 index 487be60db..000000000 --- a/android/app/src/main/res/layout/activity_link_device.xml +++ /dev/null @@ -1,140 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_report_issue.xml b/android/app/src/main/res/layout/activity_report_issue.xml deleted file mode 100644 index 97e7f10a8..000000000 --- a/android/app/src/main/res/layout/activity_report_issue.xml +++ /dev/null @@ -1,89 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/desktop_option.xml b/android/app/src/main/res/layout/desktop_option.xml deleted file mode 100644 index c5d8bc724..000000000 --- a/android/app/src/main/res/layout/desktop_option.xml +++ /dev/null @@ -1,64 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/invite_friends.xml b/android/app/src/main/res/layout/invite_friends.xml deleted file mode 100644 index d2a820794..000000000 --- a/android/app/src/main/res/layout/invite_friends.xml +++ /dev/null @@ -1,132 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/issue_row.xml b/android/app/src/main/res/layout/issue_row.xml deleted file mode 100644 index c61470c98..000000000 --- a/android/app/src/main/res/layout/issue_row.xml +++ /dev/null @@ -1,11 +0,0 @@ - diff --git a/android/app/src/main/res/layout/item_language.xml b/android/app/src/main/res/layout/item_language.xml deleted file mode 100644 index 0e190cd5d..000000000 --- a/android/app/src/main/res/layout/item_language.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/item_plan.xml b/android/app/src/main/res/layout/item_plan.xml deleted file mode 100644 index 2acf80cbf..000000000 --- a/android/app/src/main/res/layout/item_plan.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/language_item.xml b/android/app/src/main/res/layout/language_item.xml deleted file mode 100644 index 45e3b13dd..000000000 --- a/android/app/src/main/res/layout/language_item.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - diff --git a/android/app/src/main/res/layout/main_switch.xml b/android/app/src/main/res/layout/main_switch.xml deleted file mode 100644 index 5bb0f2478..000000000 --- a/android/app/src/main/res/layout/main_switch.xml +++ /dev/null @@ -1,54 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/main_switch_wrapper.xml b/android/app/src/main/res/layout/main_switch_wrapper.xml deleted file mode 100644 index e4ec6a614..000000000 --- a/android/app/src/main/res/layout/main_switch_wrapper.xml +++ /dev/null @@ -1,4 +0,0 @@ - \ No newline at end of file diff --git a/android/app/src/main/res/layout/most_popular_price.xml b/android/app/src/main/res/layout/most_popular_price.xml deleted file mode 100644 index ec25041c6..000000000 --- a/android/app/src/main/res/layout/most_popular_price.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/layout/privacy_disclosure.xml b/android/app/src/main/res/layout/privacy_disclosure.xml deleted file mode 100644 index 7b5e8e1c4..000000000 --- a/android/app/src/main/res/layout/privacy_disclosure.xml +++ /dev/null @@ -1,56 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/raw/cas_settings22n.json b/android/app/src/main/res/raw/cas_settings22n.json new file mode 100644 index 000000000..bdd941b0e --- /dev/null +++ b/android/app/src/main/res/raw/cas_settings22n.json @@ -0,0 +1 @@ +{"consentFlow":0,"Interstitial":[15,14,13,12,11,10,9,8,3,7,6,2,1,5,0,17,16,4],"iEcpm":[1,2.334,3.735,7.587,0.8,1.5,4.5,7,10,14,20,30,45,60,70,90,1,1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"Rewarded":[15,14,13,12,11,10,9,8,7,6,5,4],"rEcpm":[-1,-1,-1,-1,0.8,1.5,4.5,7,10,14,20,30,45,60,70,90,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1],"priority":[],"admob_app_id":"ca-app-pub-5636049984270410~3357848663","collectAnalytics":4,"privacyPref":0,"applovin_app_id":"UCIc5ZGtBpQ1CInAFK83XMtqltdWynpJxhuS04r8kPawa6Dr3AGIy5sB5XrEA2s56HeS28712r4eiGa5TG8e5J","allow_endless":1,"providers":[{"net":"InMobi","label":"Bid","settings":"{\"inter_rtb\": \"1692087014187\",\"AccountID\": \"54f9486bbc4c42a4af3404023494c3aa\",\"mediation\": \"cas\"}"},{"net":"AdMob","label":"x1.00","settings":"{\"inter_AdUnit\": \"ca-app-pub-5636049984270410/1449353708\"}"},{"net":"AdMob","label":"x1.60","settings":"{\"inter_AdUnit\": \"ca-app-pub-5636049984270410/1625130442\"}"},{"net":"AdMob","label":"x3.25","settings":"{\"inter_AdUnit\": \"ca-app-pub-5636049984270410/2044766994\"}"},{"net":"Unity","label":"00.80","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_00_80\",\"reward_PlacementID\": \"v_00_80\"}"},{"net":"Unity","label":"01.50","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_01_50\",\"reward_PlacementID\": \"v_01_50\"}"},{"net":"Unity","label":"04.50","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_04_50\",\"reward_PlacementID\": \"v_04_50\"}"},{"net":"Unity","label":"07.00","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_07_00\",\"reward_PlacementID\": \"v_07_00\"}"},{"net":"Unity","label":"10.00","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_10_00\",\"reward_PlacementID\": \"v_10_00\"}"},{"net":"Unity","label":"14.00","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_14_00\",\"reward_PlacementID\": \"v_14_00\"}"},{"net":"Unity","label":"20.00","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_20_00\",\"reward_PlacementID\": \"v_20_00\"}"},{"net":"Unity","label":"30.00","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_30_00\",\"reward_PlacementID\": \"v_30_00\"}"},{"net":"Unity","label":"45.00","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_45_00\",\"reward_PlacementID\": \"v_45_00\"}"},{"net":"Unity","label":"60.00","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_60_00\",\"reward_PlacementID\": \"v_60_00\"}"},{"net":"Unity","label":"70.00","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_70_00\",\"reward_PlacementID\": \"v_70_00\"}"},{"net":"Unity","label":"90.00","settings":"{\"GameID\": \"5377381\",\"inter_PlacementID\": \"i_90_00\",\"reward_PlacementID\": \"v_90_00\"}"},{"net":"Mintegral","label":"Bid","settings":"{\"appId\": \"228589\",\"inter_placement\": \"977182\",\"apiKey\": \"702a2f322dd9bc99d0e6cdfd9d026115\",\"inter_unit\": \"2629488\",\"mediation\": \"cas\"}"},{"net":"IronSource","label":"Bid","settings":"{\"inter_rtb\": \"15476731\",\"appId\": \"1b2f0517d\"}"},{"net":"Kidoz","label":"Default","settings":"{\"AppID\": \"11645\",\"Token\": \"8mvGy08EWJtoUH6CtGjPFUlM2ELDCc1X\"}"},{"net":"AppLovin","label":"01.50","settings":"{\"reward_zoneID\": \"54e820bdcf4f3b6d\",\"banner_zoneID\": \"f04547ada0d77cff\"}"},{"net":"AppLovin","label":"07.00","settings":"{\"reward_zoneID\": \"5ed4032273942a8d\"}"},{"net":"AppLovin","label":"10.00","settings":"{\"reward_zoneID\": \"e1b69959fda3906d\",\"banner_zoneID\": \"766a3ceefe9831a3\"}"},{"net":"AppLovin","label":"20.00","settings":"{\"reward_zoneID\": \"4211dcd8b4db1c30\"}"},{"net":"AppLovin","label":"30.00","settings":"{\"reward_zoneID\": \"e96368e8b2065578\"}"},{"net":"AppLovin","label":"45.00","settings":"{\"reward_zoneID\": \"e1be09327dfbfed1\"}"},{"net":"AppLovin","label":"60.00","settings":"{\"reward_zoneID\": \"182da2e05b8dd8ee\"}"},{"net":"AppLovin","label":"70.00","settings":"{\"reward_zoneID\": \"9c381d7995ef3e90\"}"},{"net":"AppLovin","label":"50.00","settings":"{\"reward_zoneID\": \"892bd49cd724b418\"}"},{"net":"AppLovin","label":"35.00","settings":"{\"reward_zoneID\": \"2de5dd1f80daafe4\"}"},{"net":"AppLovin","label":"80.00","settings":"{\"reward_zoneID\": \"39a81dfbf7d880b1\"}"},{"net":"AppLovin","label":"01.00","settings":"{\"reward_zoneID\": \"5c8b46dca33216e6\",\"banner_zoneID\": \"493c87fdabf05c94\"}"},{"net":"AppLovin","label":"00.20","settings":"{\"banner_zoneID\": \"e4f5a71feca7a38c\"}"},{"net":"AppLovin","label":"00.30","settings":"{\"reward_zoneID\": \"47e339610b38d229\",\"banner_zoneID\": \"f0ab4eed0ae5e147\"}"},{"net":"AppLovin","label":"00.50","settings":"{\"reward_zoneID\": \"6bc5d1da4a0b9910\",\"banner_zoneID\": \"d15a71a688ed16d1\"}"},{"net":"AppLovin","label":"02.00","settings":"{\"reward_zoneID\": \"5997573ad53ba3cd\",\"banner_zoneID\": \"de27c2bdb7b99eba\"}"},{"net":"AppLovin","label":"05.00","settings":"{\"reward_zoneID\": \"a35be980f84fb735\",\"banner_zoneID\": \"f64cd22e39eb7540\"}"},{"net":"AppLovin","label":"03.00","settings":"{\"reward_zoneID\": \"b69b4a5a6c72a943\",\"banner_zoneID\": \"9ab88789d0a33323\"}"},{"net":"AppLovin","label":"08.00","settings":"{\"reward_zoneID\": \"2be0200bcb50223b\"}"},{"net":"AppLovin","label":"00.40","settings":"{\"banner_zoneID\": \"d152d539689c244a\"}"},{"net":"AppLovin","label":"00.70","settings":"{\"banner_zoneID\": \"8ca996e9e88579a7\"}"},{"net":"AppLovin","label":"04.00","settings":"{\"reward_zoneID\": \"7e4be1aba0309fe2\"}"},{"net":"AppLovin","label":"12.00","settings":"{\"reward_zoneID\": \"043ac173bc8f0ef9\"}"},{"net":"AppLovin","label":"15.00","settings":"{\"reward_zoneID\": \"e37a305f447d9e4c\"}"},{"net":"AppLovin","label":"25.00","settings":"{\"reward_zoneID\": \"b46609925f4bfbcf\"}"},{"net":"AppLovin","label":"40.00","settings":"{\"reward_zoneID\": \"5b8c60a59d136cbd\"}"}],"cancelNetLevel":1} \ No newline at end of file diff --git a/android/app/src/main/res/values-bn/strings.xml b/android/app/src/main/res/values-bn/strings.xml index 0087923f2..c20c7b77b 100644 --- a/android/app/src/main/res/values-bn/strings.xml +++ b/android/app/src/main/res/values-bn/strings.xml @@ -311,6 +311,7 @@ প্রতি বন্ধুর সাইনআপ থেকে একটি ফ্রি মাস পান আপনার ক্রেডিট পরবর্তী বিলিং চক্রে আপনার অ্যাকাউন্টে প্রয়োগ করা হবে। Lantern Pro তে যোগ দেবার আমন্ত্রন + এক মাসের জন্য Lantern Pro বিনামূল্যে ব্যাবহার করতে %1$s রেফারেল কোডটি কাজে লাগান। এটা https://lantern.io/download থেকে ডাউনলোড করা যাবে। %2$s এ আমি \"%1$s\" সর্ম্পকে পড়ছি Lantern এর মাধ্যমে। ব্লক সাইট এবং সেবা অ্যাক্সেস করতে Lantern ব্যবহার করুন। আপনি এখানে অ্যান্ড্রয়েড জন্য এটি ডাউনলোড করতে পারেন: https://bit.ly/lanternapk আপনি কি নিশ্চিত? এই ক্রিয়াটি পূর্বাবস্থায় ফেরানো যাবে না! আপনি যে কোন সময় আবার Pro এর জন্য সাইন আপ করতে পারেন। আপনি কি আপনার Pro অ্যাকাউন্ট থেকে এই ডিভাইসটি অপসারন করতে চান? diff --git a/android/app/src/main/res/values/accountids.xml b/android/app/src/main/res/values/accountids.xml deleted file mode 100644 index 8dd09f0ab..000000000 --- a/android/app/src/main/res/values/accountids.xml +++ /dev/null @@ -1,6 +0,0 @@ - - 467e0f982bcdafa35a7a51b34278a985 - - 7eb753422801802e2e7c5cbc43fbf237 - - diff --git a/android/app/src/main/res/values/attrs.xml b/android/app/src/main/res/values/attrs.xml index 7c6651f23..733eef1b3 100644 --- a/android/app/src/main/res/values/attrs.xml +++ b/android/app/src/main/res/values/attrs.xml @@ -15,10 +15,6 @@ - - - - @@ -42,29 +38,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/android/app/src/main/res/values/colors.xml b/android/app/src/main/res/values/colors.xml index 8e0d4e700..9562e1d84 100644 --- a/android/app/src/main/res/values/colors.xml +++ b/android/app/src/main/res/values/colors.xml @@ -20,6 +20,7 @@ #BDBDBD #b2ebf2 #ffffff + #ffffff #303030 #EEEEEE #AAAAAA @@ -38,6 +39,7 @@ #006064 #0097A7 #000000 + #14000000 #616262 #A5A5A5 #e0e0e0 diff --git a/android/app/src/main/res/values/strings.xml b/android/app/src/main/res/values/strings.xml index 75c0d8001..4ebb652bd 100644 --- a/android/app/src/main/res/values/strings.xml +++ b/android/app/src/main/res/values/strings.xml @@ -40,6 +40,7 @@ Confirm Code ON OFF + Unknown Error Lantern is Off. Flip the switch to access blocked sites and services! Lantern is On. You can now access blocked sites and services! You can now access\r\nblocked sites and services diff --git a/android/app/src/main/res/xml/network_security_config.xml b/android/app/src/main/res/xml/network_security_config.xml index 96cfe74b8..97d6089e2 100644 --- a/android/app/src/main/res/xml/network_security_config.xml +++ b/android/app/src/main/res/xml/network_security_config.xml @@ -4,5 +4,6 @@ localhost telegram.me t.me + 127.0.0.1 diff --git a/android/build.gradle b/android/build.gradle index 6a47028cb..759eb1e96 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -28,15 +28,14 @@ ext { enableTestCodeCoverage = true - compileSdkVersion = 33 - targetSdkVersion = 33 - minSdkVersion = 21 + compileSdkVersion = 34 + targetSdkVersion = 34 + minSdkVersion = 23 androidAnnotationsVersion = '4.8.0' androidAnnotationsAPIVersion = '4.8.0' - buildToolsVersion = '33.0.1' + buildToolsVersion = '34.0.0' buildNumber = 'dev' espressoVersion = '3.5.0' - supportLibVersion = '28.0.0' supportTestVersion = '1.0' lanternDir = projectDir.parentFile.parentFile diff --git a/appium_kotlin/app/src/test/java/appium_kotlin/Const.kt b/appium_kotlin/app/src/test/java/appium_kotlin/Const.kt index 0bc19ccd1..47f0e0423 100644 --- a/appium_kotlin/app/src/test/java/appium_kotlin/Const.kt +++ b/appium_kotlin/app/src/test/java/appium_kotlin/Const.kt @@ -15,6 +15,7 @@ const val IP_REQUEST_URL = "https://api64.ipify.org" const val LOGS_DIALED_MESSAGE = "Successfully dialed via" const val ERROR_PAYMENT_PURCHASE = "Error with purchase request" const val PAYMENT_PURCHASE_COMPLETED = "org.getlantern.lantern.util.PaymentsUtil: Purchase Completed" +const val REPORT_ISSUE_SUCCESS = "Report sent successfully" //Finder Keys @@ -30,5 +31,7 @@ const val CHECK_OUT = "check_out" const val RENEWAL_SUCCESS_OK = "renew_success_ok" const val SUPPORT = "support" const val REPORT_AN_ISSUE = "report_issue" +const val REPORT_DESCRIPTION = "report_description" +const val SEND_REPORT = "send_report" diff --git a/appium_kotlin/app/src/test/java/appium_kotlin/tests/AppTest.kt b/appium_kotlin/app/src/test/java/appium_kotlin/tests/AppTest.kt index fcdfab705..5140b56b4 100644 --- a/appium_kotlin/app/src/test/java/appium_kotlin/tests/AppTest.kt +++ b/appium_kotlin/app/src/test/java/appium_kotlin/tests/AppTest.kt @@ -18,6 +18,9 @@ import appium_kotlin.MOST_POPULAR import appium_kotlin.PAYMENT_PURCHASE_COMPLETED import appium_kotlin.RENEWAL_SUCCESS_OK import appium_kotlin.REPORT_AN_ISSUE +import appium_kotlin.REPORT_DESCRIPTION +import appium_kotlin.REPORT_ISSUE_SUCCESS +import appium_kotlin.SEND_REPORT import appium_kotlin.SUPPORT import io.appium.java_client.TouchAction import io.appium.java_client.android.Activity @@ -278,46 +281,30 @@ class AppTest() : BaseTest() { reportIssue.click() Thread.sleep(1000) -// print("TaskId: $taskId", "reportAnIssueFlow-->Switching to NATIVE_APP context.") -// switchToContext(ContextType.NATIVE_APP, androidDriver) -// val issueDropdown = androidDriver.findElement(By.id("org.getlantern.lantern:id/issue")) -// issueDropdown.click() -// Thread.sleep(1000) -// -// print("TaskId: $taskId", "reportAnIssueFlow-->Performing tap action.") -// TouchAction(androidDriver).tap( -// TapOptions.tapOptions().withPosition(PointOption.point(473, 880)) -// ).perform() -// Thread.sleep(1000) - switchToContext(ContextType.NATIVE_APP, androidDriver) print("TaskId: $taskId", "reportAnIssueFlow-->Entering description.") - val description = androidDriver.findElement(By.id("org.getlantern.lantern:id/description")) + val description = flutterFinder.byTooltip(REPORT_DESCRIPTION) description.click() - description.sendKeys("This is sample report and issue running vai Appium Test CI") + description.sendKeys("This is sample report and issue running via Appium Test CI") Thread.sleep(3000) - print("TaskId: $taskId", "reportAnIssueFlow-->Hiding keyboard.") - androidDriver.hideKeyboard() - Thread.sleep(2000) - print( "TaskId: $taskId", "reportAnIssueFlow-->Locating and clicking on the send report button." ) - val sendReportButton = androidDriver.findElement(By.id("org.getlantern.lantern:id/sendBtn")) + val sendReportButton = flutterFinder.byTooltip(SEND_REPORT) sendReportButton.click() Thread.sleep(5000) - print( - "TaskId: $taskId", - "reportAnIssueFlow-->Locating and clicking on the report send button." - ) - val reportSend = androidDriver.findElement(By.id("android:id/button1")) - reportSend.click() - Thread.sleep(2000) + val reportIssueSuccessLogs = captureReportIssueSuccessLogcat(androidDriver) + println("TaskId: $taskId | reportAnIssueFlow Checking for logs-->$reportIssueSuccessLogs") + if (reportIssueSuccessLogs.isBlank()) { + if (!isLocalRun) { + testFail("Fail to submit Report/issue", androidDriver) + } + } print("TaskId: $taskId", "reportAnIssueFlow-->Test passed, assertion true.") - Assertions.assertEquals(true, true) + Assertions.assertEquals(reportIssueSuccessLogs.isNotBlank(), true) } private fun afterTest(driver: AndroidDriver) { @@ -385,13 +372,13 @@ class AppTest() : BaseTest() { } @Synchronized - private fun capturePaymentFailLogcat(androidDriver: AndroidDriver): String { + private fun captureReportIssueSuccessLogcat(androidDriver: AndroidDriver): String { switchToContext(ContextType.NATIVE_APP, androidDriver) val logtypes: Set<*> = androidDriver.manage().logs().availableLogTypes println("supported log types: $logtypes") // [logcat, bugreport, server, client] val logs: LogEntries = androidDriver.manage().logs().get("logcat") for (logEntry in logs) { - if (logEntry.message.contains(ERROR_PAYMENT_PURCHASE)) { + if (logEntry.message.contains(REPORT_ISSUE_SUCCESS)) { println("contain log: ${logEntry.message}") // [logcat, bugreport, server, client] return logEntry.message @@ -400,6 +387,7 @@ class AppTest() : BaseTest() { return "" } + @Synchronized private fun capturePaymentPassLogcat(androidDriver: AndroidDriver): String { switchToContext(ContextType.NATIVE_APP, androidDriver) diff --git a/appium_kotlin/app/src/test/resources/live/live_config.json b/appium_kotlin/app/src/test/resources/live/live_config.json index cdeb992b0..e96189c6f 100644 --- a/appium_kotlin/app/src/test/resources/live/live_config.json +++ b/appium_kotlin/app/src/test/resources/live/live_config.json @@ -29,8 +29,8 @@ "platformName": "Android" }, { - "appium:deviceName": "Samsung Galaxy S8 Plus", - "platformVersion": "7.0", + "appium:deviceName": "Google Pixel 3 XL", + "platformVersion": "9.0", "platformName": "Android" } ] diff --git a/assets/images/lantern_desktop.svg b/assets/images/lantern_desktop.svg new file mode 100644 index 000000000..d9e78eaed --- /dev/null +++ b/assets/images/lantern_desktop.svg @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/locales/ar.po b/assets/locales/ar.po index a052e0a78..9228b5cb5 100644 --- a/assets/locales/ar.po +++ b/assets/locales/ar.po @@ -7,18 +7,19 @@ # lamine Kacimi , 2023 # ouss , 2023 # Mhawesh, 2023 -# 160565ee57b988ee904d03fec374ff93_4f79b8c <73ec7ace4de62e6bb6454cb54e990a4d_721149>, 2023 # e2f , 2023 # Amin Jobran, 2023 # Rima Sghaier, 2023 -# dc64c51d94429ef6835e68b9062d8713_f0bbcb2 , 2023 # Abdulaziz AlObaili , 2023 +# Lantern, 2023 +# dc64c51d94429ef6835e68b9062d8713_f0bbcb2 , 2023 +# 160565ee57b988ee904d03fec374ff93_4f79b8c <73ec7ace4de62e6bb6454cb54e990a4d_721149>, 2023 +# 6ad90dadbe5d08991a98500e1efbdbb4_9d79615 <7392e61add82b364ab0393a04573af48_721156>, 2023 # Abed Abedd , 2023 -# e2f_ar r1 , 2023 # msgid "" msgstr "" -"Last-Translator: e2f_ar r1 , 2023\n" +"Last-Translator: Abed Abedd , 2023\n" "Language-Team: Arabic (https://app.transifex.com/lantern-1/teams/94371/ar/)\n" "Language: ar\n" "Plural-Forms: nplurals=6; plural=n==0 ? 0 : n==1 ? 1 : n==2 ? 2 : n%100>=3 && n%100<=10 ? 3 : n%100>=11 && n%100<=99 ? 4 : 5;\n" @@ -124,6 +125,33 @@ msgstr "الترقية الى لاتيرن برو" msgid "Invite Friends" msgstr "ادعو أصدقائك" +msgid "share_lantern_pro" +msgstr "" +"شارك حساب Lantern Pro مع أحد أصدقائك! عندما يستخدم صديقك رمز الإحالة المخصص " +"لك أثناء شرائه حساب Lantern Pro، فسيحصل كلاكما على خدمة Lantern Pro مجانًا " +"لمدة شهر واحد!" + +msgid "privacy_disclosure_title" +msgstr "الكشف عن خصوصية لانترن" + +msgid "privacy_disclosure_body" +msgstr "" +"تلتزم لانترن بحماية خصوصيتك. نود أن تفهم نوع المعلومات التي نجمعها، ونوع " +"المعلومات التي لا نجمعها، وكيف نستخدمها، وكيف نُخزنها. نحن لا نربط معلومات " +"الدفع الخاصة بك بحسابك على لانترن برو. ولا نجمع سجلات نشاطك، بما في ذلك عدم " +"تسجيل محفوظات التصفح، أو وجهة البيانات، أو محتوى البيانات، أو استعلامات نظام" +" أسماء النطاقات (DNS). كما أننا لا نُخزن مطلقًا سجلات الاتصال، أي لا يتم " +"إنشاء أي سجلات عن عنوان بروتوكول الإنترنت (IP)، أو عنوان بروتوكول الإنترنت " +"لخادم لانترن الصادر الخاص بك المرتبط بالطابع الزمني للاتصال، أو مدة الجلسة. " +"نظرًا لحساب لانترن برو المحدد، يتعذر على لانترن من الناحية التقنية ربط حساب " +"لانترن بهوية المستخدم الحقيقية. ومع وجود عنوان بروتوكول الإنترنت (IP) وطابع " +"زمني دقيق في لانترن، يتعذر على لانترن من الناحية التقنية منح عنوان بروتوكول " +"الإنترنت هذا إلى مستخدم لانترن أو عنوان بروتوكول الإنترنت الخاص بمستخدم " +"لانترن." + +msgid "privacy_disclosure_accept" +msgstr "موافق" + msgid "desktop_version" msgstr "نسخة سطح المكتب" @@ -160,8 +188,29 @@ msgstr "SDK %s" msgid "language" msgstr "لغة" -msgid "report_issue" -msgstr "الإبلاغ عن مشكلة" +msgid "select_an_issue" +msgstr "يرجى اختيار مشكلة" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "لا يمكن الوصول الى المواقع المحظورة" + +msgid "cannot_complete_purchase" +msgstr "لا يمكن إكمال عملية الشراء" + +msgid "cannot_sign_in" +msgstr "لا يُمكن تسجيل الدخول" + +msgid "spinner_loads_endlessly" +msgstr "العجلة تدور بلا نهاية" + +msgid "other" +msgstr "أخرى" + +msgid "issue_description" +msgstr "وصف المشكلة" msgid "check_for_updates" msgstr "تحقق من وجود تحديثات" @@ -199,6 +248,18 @@ msgstr "ربط من خلال PIN" msgid "OR" msgstr "أو" +msgid "device_linking_pin" +msgstr "رقم التعريف الشخصي لربط الجهاز" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "* تأكد من أن كلا الجهازين يعملان بأحدث إصدار من Lantern" + msgid "Authorize Device via Email" msgstr "مصادقة الجهاز عبر البريد الإلكتروني" @@ -214,6 +275,36 @@ msgstr "ربط عبر البريد الإلكتروني" msgid "lantern_pro_email" msgstr "البريد الإلكتروني الخاص بـ Lantern Pro" +msgid "lantern_desktop" +msgstr "لانترن للكمبيوتر المكتبي" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "ابلاغ عن مشكلة" + +msgid "report_sent" +msgstr "تم إرسال التقرير" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"نشكرك على الإبلاغ عن المشكلة، وسنوافيك برسالة إلكترونية في أقرب فرصة ممكنة" + +msgid "send_report" +msgstr "إرسال تقرير" + +msgid "most_recent_lantern_apps" +msgstr "" +"يمكن العثور على أحدث تطبيقات لانترن لسطح المكتب وجميع المنصات الأخرى على " +"الرابط أعلاه." + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "مشاركة رابط تنزيل لانترن" + msgid "email" msgstr "البريد الاكتروني" @@ -271,6 +362,12 @@ msgstr "أدخِل البريد الإلكتروني ورمز التنشيط" msgid "referral_code" msgstr "رمز الإحالة" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "أضف رمز الإحالة" diff --git a/assets/locales/bn.po b/assets/locales/bn.po index ad45511fb..61433578c 100644 --- a/assets/locales/bn.po +++ b/assets/locales/bn.po @@ -2,12 +2,12 @@ # Translators: # Al Shahrior Hasan Sagor , 2021 # Lantern, 2023 -# code smite , 2023 # tx_e2f_bn c3 , 2023 +# code smite , 2023 # msgid "" msgstr "" -"Last-Translator: tx_e2f_bn c3 , 2023\n" +"Last-Translator: code smite , 2023\n" "Language-Team: Bengali (https://app.transifex.com/lantern-1/teams/94371/bn/)\n" "Language: bn\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -115,6 +115,34 @@ msgstr "Lantern প্রো তে আপগ্রেড করুন" msgid "Invite Friends" msgstr "বন্ধুদের আমন্ত্রন করুন" +msgid "share_lantern_pro" +msgstr "" +"কোনও বন্ধুর সাথে Lantern Pro শেয়ার করুন! আপনার বন্ধু তার Lantern Pro কেনার " +"সময় আপনার রেফারেল কোড ব্যবহার করলে, আপনারা দুজনেই একমাসের Lantern Pro " +"পরিষেবা বিনামূল্যে পাবেন!" + +msgid "privacy_disclosure_title" +msgstr "Lantern গোপনীয়্তা প্রকাশ" + +msgid "privacy_disclosure_body" +msgstr "" +"আপনার গোপনীয়তা রক্ষা করার জন্য Lantern অঙ্গীকারবদ্ধ। আমরা কী তথ্য সংগ্রহ " +"করি, কী তথ্য সংগ্রহ করি না এবং কীভাবে আমরা সংগ্রহ ও ব্যবহার করি এবং তথ্য " +"সংরক্ষণ করি তা আমরা আপনাকে বোঝাতে চাই। আমরা আপনার Lantern Pro অ্যাকাউন্টের " +"সাথে আপনার পেমেন্টের তথ্য সংযোগ বিচ্ছিন্ন করি। ব্রাউজ করার ইতিহাস লগ না করা," +" ট্রাফিকের গন্তব্যস্থল, ডেটার বিষয়বস্তু বা DNS জিজ্ঞাস্যগুলি মতো আপনার " +"কার্যকলাপের লগগুলি আমরা সংগ্রহ করি না। উপরন্তু আমরা কখনও সংযোগের লগগুলি, " +"অর্থাৎ, আপনার IP ঠিকানার কোনও লগ, সংযোগের টাইমস্ট্যাম্পের সাথে সংযুক্ত " +"আউটগোয়িং Lantern সার্ভারের IP ঠিকানা, বা সেশনের সময়কালও আমরা সংরক্ষণ করি না।" +" একটি নির্দিষ্ট Lantern Pro অ্যাকাউন্টে, Lantern অ্যাকাউন্টের সাথে " +"ব্যবহারকারীর প্রকৃত পরিচয় সংযুক্ত করতে Lantern প্রযুক্তিগতভাবে অপারগ। একটি " +"নির্দিষ্ট Lantern IP ঠিকানা এবং একটি সুনির্দিষ্ট টাইম স্ট্যাম্পে, এই IP " +"ঠিকানার বৈশিষ্ট্যটি একজন Lantern ব্যবহারকারী বা একটি Lantern ব্যবহারকারীর IP" +" ঠিকানার সাথে সংযুক্ত করতে Lantern প্রযুক্তিগতভাবে অপারগ।" + +msgid "privacy_disclosure_accept" +msgstr "আমি স্বীকার করি" + msgid "desktop_version" msgstr "ডেক্সটপ সংস্করণ" @@ -151,8 +179,29 @@ msgstr "SDK %s" msgid "language" msgstr "ভাষা" -msgid "report_issue" -msgstr "একটি সমস্যা রিপোর্ট করুন" +msgid "select_an_issue" +msgstr "একটি সমস্যা নির্বাচন করুন" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "ব্লক সাইটগুলোতে যেতে পারছিনা" + +msgid "cannot_complete_purchase" +msgstr "কেনা সম্পূর্ণ করা যাচ্ছে না" + +msgid "cannot_sign_in" +msgstr "সাইন ইন করা যাচ্ছে না" + +msgid "spinner_loads_endlessly" +msgstr "স্পিনার অবিরাম লোড হয়" + +msgid "other" +msgstr "অন্যান্য" + +msgid "issue_description" +msgstr "সমস্যার বিবরণ" msgid "check_for_updates" msgstr "নতুন আপডেটের জন্য চেক করুন." @@ -190,6 +239,19 @@ msgstr "PIN দিয়ে লিঙ্ক করুন" msgid "OR" msgstr "অথবা" +msgid "device_linking_pin" +msgstr "ডিভাইস লিঙ্ক করার পিন" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "" +"* দুটি ডিভাইসেই Lantern এর অতিসাম্প্রতিক সংস্করণটি চলছে কিনা সুনিশ্চিত করুন" + msgid "Authorize Device via Email" msgstr "ইমেলের মাধ্যমে ডিভাইসে অনুমোদন দিন" @@ -206,6 +268,39 @@ msgstr "ইমেলের মাধ্যমে লিঙ্ক করুন" msgid "lantern_pro_email" msgstr "Lantern প্রো ইমেল" +msgid "lantern_desktop" +msgstr "Lantern Desktop" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "একটি সমস্যা রিপোর্ট করুন" + +msgid "report_sent" +msgstr "রিপোর্ট পাঠানো হয়েছে" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"আপনার সমস্যাটি জানানোর জন্য ধন্যবাদ। রিপোর্টের পরিমাণ বেশি হওয়ার কারণে, আমরা" +" বেশিরভাগ সমস্যার উত্তর দিতে পারিনি, কিন্তু এই বিষয়ে আপনাকে নিশ্চিত করি যে " +"আপনার রিপোর্টের উপর নির্ভর করে Lantern উন্নত করার জন্য আমরা কঠোর পরিশ্রম " +"করছি।" + +msgid "send_report" +msgstr "রিপোর্ট পাঠান" + +msgid "most_recent_lantern_apps" +msgstr "" +"ডেস্কটপ এবং অন্যান্য প্ল্যাটফর্মগুলির জন্য সর্ব সাম্প্রতিক Lantern অ্যাপ " +"উপরের লিঙ্কটিতে দেখা যেতে পারে।" + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "Lantern ডাউনলোড লিঙ্ক শেয়ার করুন" + msgid "email" msgstr "ইমেইল" @@ -265,6 +360,12 @@ msgstr "ইমেল এবং সক্রিয়করণ কোড লিখ msgid "referral_code" msgstr "রেফারেল কোড" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "রেফারেল কোড যোগ করুন" diff --git a/assets/locales/en.po b/assets/locales/en.po index b3b12eb56..12a54fc97 100644 --- a/assets/locales/en.po +++ b/assets/locales/en.po @@ -95,6 +95,18 @@ msgstr "Upgrade to Lantern Pro" msgid "Invite Friends" msgstr "Invite Friends" +msgid "share_lantern_pro" +msgstr "Share Lantern Pro with a friend! When your friend uses your referral code during their purchase of Lantern Pro, you will both receive one month of Lantern Pro service for free!" + +msgid "privacy_disclosure_title" +msgstr "Lantern Privacy Disclosure" + +msgid "privacy_disclosure_body" +msgstr "Lantern is committed to protecting your privacy. We want you to understand what information we collect, what we don’t collect, and how we collect, use, and store information. We disassociate your payment information with your Lantern Pro account. We do not collect logs of your activity, including no logging of browsing history, traffic destination, data content, or DNS queries. We also never store connection logs, meaning no logs of your IP address, your outgoing Lantern server IP address associated with the connection timestamp, or session duration. Given a specific Lantern Pro account, Lantern is technically unable to associate the Lantern account with the real identity of the user. Given a Lantern IP address and a precise time-stamp, Lantern is technically unable to attribute this IP address to a Lantern user or a Lantern user IP address." + +msgid "privacy_disclosure_accept" +msgstr "I Accept" + msgid "desktop_version" msgstr "Desktop Version" @@ -131,8 +143,29 @@ msgstr "SDK %s" msgid "language" msgstr "Language" -msgid "report_issue" -msgstr "Report an issue" +msgid "select_an_issue" +msgstr "Please Select an Issue" + +msgid "enter_description" +msgstr "Please describe your issue" + +msgid "cannot_access_blocked_sites" +msgstr "Cannot access blocked sites" + +msgid "cannot_complete_purchase" +msgstr "Cannot complete purchase" + +msgid "cannot_sign_in" +msgstr "Cannot sign in" + +msgid "spinner_loads_endlessly" +msgstr "Spinner loads endlessly" + +msgid "other" +msgstr "Other" + +msgid "issue_description" +msgstr "Issue Description" msgid "check_for_updates" msgstr "Check for Updates" @@ -169,6 +202,18 @@ msgstr "Link with PIN" msgid "OR" msgstr "OR" +msgid "device_linking_pin" +msgstr "Device linking pin" + +msgid "link_device_step_one" +msgstr "Open the 'Add Device' menu item on your Pro device." + +msgid "link_device_step_two" +msgstr "Enter the device linking PIN and click submit" + +msgid "ensure_most_recent_version_lantern" +msgstr "* Ensure both devices are running the most recent version of Lantern" + msgid "Authorize Device via Email" msgstr "Authorize Device via Email" @@ -184,6 +229,33 @@ msgstr "Link via Email" msgid "lantern_pro_email" msgstr "Lantern Pro Email" +msgid "lantern_desktop" +msgstr "Lantern Desktop" + +msgid "report_issue" +msgstr "Report Issue" + +msgid "report_an_issue" +msgstr "Report an Issue" + +msgid "report_sent" +msgstr "Report Sent" + +msgid "thank_you_for_reporting_your_issue" +msgstr "Thank you for reporting your issue. Due to volume, we cannot respond to most issues, but we assure you we are working hard to improve Lantern based on your reports." + +msgid "send_report" +msgstr "Send Report" + +msgid "most_recent_lantern_apps" +msgstr "The most recent Lantern apps for desktop and all other platforms can be found at the link above." + +msgid "share_link" +msgstr "Share Link" + +msgid "share_title" +msgstr "Share Lantern download link" + msgid "email" msgstr "Email" @@ -241,6 +313,12 @@ msgstr "Enter Email and Activation code" msgid "referral_code" msgstr "Referral code" +msgid "share_referral_code" +msgstr "Share Referral Code" + +msgid "share_message_referral_code" +msgstr "For a free month of Lantern Pro, use this referral code: %s ; You can download it from here: https://lantern.io/download" + msgid "add_referral_code" msgstr "Add Referral code" diff --git a/assets/locales/es.po b/assets/locales/es.po index 4354b9cc0..36176dd40 100644 --- a/assets/locales/es.po +++ b/assets/locales/es.po @@ -2,19 +2,19 @@ # Translators: # Giorgio Maone , 2021 # e2f_es r3 , 2022 -# Lantern, 2023 # Emerson Castaneda, 2023 -# e2f , 2023 -# tx_e2f_es t1 , 2023 -# strel, 2023 -# e2f_es r1 , 2023 # fffw , 2023 +# tx_e2f_es t1 , 2023 +# Lantern, 2023 # tx_e2f_es t35 , 2023 +# e2f_es r1 , 2023 +# e2f , 2023 # e2f_es r2 , 2023 +# strel, 2023 # msgid "" msgstr "" -"Last-Translator: e2f_es r2 , 2023\n" +"Last-Translator: strel, 2023\n" "Language-Team: Spanish (https://app.transifex.com/lantern-1/teams/94371/es/)\n" "Language: es\n" "Plural-Forms: nplurals=3; plural=n == 1 ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" @@ -122,6 +122,36 @@ msgstr "Actualizar a Lantern Pro" msgid "Invite Friends" msgstr "Invitar amigos" +msgid "share_lantern_pro" +msgstr "" +"¡Comparta Lantern Pro con alguien! Cuando la otra persona use su código de " +"recomendación al comprar Lantern Pro, ambos recibiréis gratis un mes de " +"servicio de Lantern Pro." + +msgid "privacy_disclosure_title" +msgstr "Declaración de privacidad de Lantern" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern se compromete a proteger su privacidad. Queremos que sepa qué " +"información recopilamos, cuál no, y cómo recopilamos, utilizamos y " +"almacenamos sus datos. Su información de pago la tratamos con independencia " +"a su cuenta de Lantern Pro. No recopilamos registros de su actividad, como " +"el registro de su historial de navegación, el destino del tráfico, el " +"contenido de los datos ni las consultas de DNS. Tampoco almacenamos los " +"registros de conexión, lo que significa que no almacenamos su dirección IP, " +"la dirección IP del servidor de Lantern de salida asociado a la marca de " +"tiempo de su conexión ni la duración de sus sesiones. Al tratarse de una " +"cuenta Lantern Pro particular, Lantern no dispone de los medios técnicos " +"necesarios para asociar la cuenta Lantern con la identidad real del usuario." +" Asimismo, al tratarse de una dirección IP de Lantern y una marca de tiempo " +"precisa, Lantern tampoco dispone de los medios necesarios para atribuir esa " +"dirección IP a un usuario de Lantern ni a la dirección IP de un usuario de " +"Lantern." + +msgid "privacy_disclosure_accept" +msgstr "Acepto" + msgid "desktop_version" msgstr "Versión de escritorio" @@ -158,8 +188,29 @@ msgstr "SDK %s" msgid "language" msgstr "Idioma" -msgid "report_issue" -msgstr "Informar de un problema" +msgid "select_an_issue" +msgstr "Por favor, seleccione un problema" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "No se puede acceder a sitios bloqueados" + +msgid "cannot_complete_purchase" +msgstr "No se puede realizar la compra" + +msgid "cannot_sign_in" +msgstr "No se puede iniciar sesión" + +msgid "spinner_loads_endlessly" +msgstr "El indicador carga sin fin" + +msgid "other" +msgstr "Otro" + +msgid "issue_description" +msgstr "Descripción del problema" msgid "check_for_updates" msgstr "Buscar actualizaciones" @@ -197,6 +248,20 @@ msgstr "Vincular con PIN" msgid "OR" msgstr "O" +msgid "device_linking_pin" +msgstr "PIN de vinculación del dispositivo" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "" +"* Asegúrese de que ambos dispositivos tienen la versión más reciente de " +"Lantern" + msgid "Authorize Device via Email" msgstr "Autorizar el dispositivo por correo electrónico" @@ -214,6 +279,37 @@ msgstr "Vincular con correo electrónico" msgid "lantern_pro_email" msgstr "Correo electrónico de Lantern Pro" +msgid "lantern_desktop" +msgstr "Lantern de Escritorio" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "Informar de un problema" + +msgid "report_sent" +msgstr "Informe enviado" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"Gracias por informarnos sobre su problema. Nos pondremos en contacto con " +"usted por correo electrónico en cuanto podamos" + +msgid "send_report" +msgstr "Enviar informe" + +msgid "most_recent_lantern_apps" +msgstr "" +"Puede encontrar las aplicaciones más recientes de Lantern para escritorio y " +"el resto de plataformas en el enlace anterior." + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "Comparta el enlace de descarga de Lantern" + msgid "email" msgstr "Correo electrónico" @@ -271,6 +367,12 @@ msgstr "Introduzca el correo electrónico y el código de activación" msgid "referral_code" msgstr "Código de referencia" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "Añadir código de recomendación" diff --git a/assets/locales/fa.po b/assets/locales/fa.po index ec1e43b26..276ec8227 100644 --- a/assets/locales/fa.po +++ b/assets/locales/fa.po @@ -3,27 +3,27 @@ # Derek F , 2021 # b0b47d46632b78a09a40de799fda9a65, 2022 # Evan , 2022 -# Abdolreza Shafaeiy , 2022 # Mary Sol , 2022 # Amir Moezzi , 2022 # Mokah , 2022 # Chip Stehr , 2022 -# Lantern, 2022 # Jigar, 2023 # 6d974eae263d7aab17b09900ac13d6de_bb09837, 2023 # Vaseto Nora , 2023 -# ali jafarizadeh , 2023 # Sina , 2023 -# NoProfile, 2023 # Pedram Azizallahi , 2023 -# Mohammad Ghaffari , 2023 -# e2f_fa r1 , 2023 # zar24 , 2023 +# NoProfile, 2023 +# Lantern, 2023 +# Abdolreza Shafaeiy , 2023 +# e2f_fa r1 , 2023 +# ali jafarizadeh , 2023 # Mersen M, 2023 +# Mohammad Ghaffari , 2023 # msgid "" msgstr "" -"Last-Translator: Mersen M, 2023\n" +"Last-Translator: Mohammad Ghaffari , 2023\n" "Language-Team: Persian (Iran) (https://app.transifex.com/lantern-1/teams/94371/fa_IR/)\n" "Language: fa_IR\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" @@ -129,6 +129,33 @@ msgstr "ارتقا به نسخه حرفه‌ای" msgid "Invite Friends" msgstr "دعوت از دوستان" +msgid "share_lantern_pro" +msgstr "" +"دوست خود را به نسخه حرفه‌ای لنترن دعوت کنید! وقتی دوست شما از کد دعوت شما " +"برای خرید لنترن حرفه‌ای استفاده کند شما هم به طور رایگان به مدت یک ماه به " +"لنترن نسخه حرفه‌ای دسترسی خواهی داشت!" + +msgid "privacy_disclosure_title" +msgstr "اعلامیه حریم خصوصی Lantern" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern متعهد است از حریم خصوصی شما حفاظت کند. از شما می‌خواهیم که بدانید چه" +" اطلاعاتی را جمع‌آوری می‌کنیم، و چه اطلاعاتی را جمع‌آوری نمی‌کنیم، و چگونه " +"اطلاعات را جمع‌آوری، استفاده و ذخیره می‌کنیم. ما اطلاعات پرداختتان را از " +"حساب Lantern Pro شما جدا می‌کنیم. ما گزارش فعالیت‌های شما، اعم از تاریخچه " +"مرور وب‌سایت‌ها، مقصد ترافیک، محتوای داده‌ها، یا جستارهای DNS را جمع‌آوری " +"نمی‌کنیم. همچنین هرگز گزارش اتصال‌ها را ذخیره نمی‌کنیم، یعنی هیچ گزارشی از " +"آدرس IP شما، آدرس IP سرور Lantern خروجی مرتبط با مهر زمانی اتصال، یا مدت " +"جلسه را ذخیره نمی‌کنیم. با فرض در نظر گرفتن یک حساب خاص Lantern Pro‏، " +"Lantern به لحاظ فنی، قادر به ارتباط دادن این حساب با هویت واقعی کاربر نیست. " +"با فرض در نظر گرفتن یک IP آدرس و یک زمان دقیق از حضور در شبکه، Lantern به " +"لحاظ فنی، قادر به ارتباط دادن این آدرس IP با یک کاربر Lantern یا آدرس IP یکی" +" از کاربران Lantern نیست." + +msgid "privacy_disclosure_accept" +msgstr "می‌پذیرم" + msgid "desktop_version" msgstr "نسخه ویندوز" @@ -165,8 +192,29 @@ msgstr "SDK %s" msgid "language" msgstr "زبان" -msgid "report_issue" -msgstr "گزارش مشکل" +msgid "select_an_issue" +msgstr "لطفاً یک مشکل را انتخاب کنید" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "عدم امکان دسترسی به سایت های بسته شده" + +msgid "cannot_complete_purchase" +msgstr "خرید تکمیل نشد" + +msgid "cannot_sign_in" +msgstr "ورود انجام نشد" + +msgid "spinner_loads_endlessly" +msgstr "اندیکاتور بی‌پایان بارگذاری می‌کند" + +msgid "other" +msgstr "غیره" + +msgid "issue_description" +msgstr "شرح مشکل" msgid "check_for_updates" msgstr "بررسی وجود به‌روزرسانی‌ها" @@ -204,6 +252,18 @@ msgstr "پیوند با پین" msgid "OR" msgstr "یا" +msgid "device_linking_pin" +msgstr "پین اتصال دستگاه" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "*مطمئن شوید که هر دو دستگاه دارای آخرین نسخه Lantern هستند" + msgid "Authorize Device via Email" msgstr "تأیید دستگاه از طریق ایمیل" @@ -220,6 +280,37 @@ msgstr "پیوند از طریق ایمیل" msgid "lantern_pro_email" msgstr "ایمیل Lantern Pro" +msgid "lantern_desktop" +msgstr "فانوس رایانه" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "گزارش یک مشکل" + +msgid "report_sent" +msgstr "گزارش فرستاده‌شد." + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"سپاسگزاریم که مشکل‌تان را گزارش دادید. ما به محض اینکه بتوانیم با ایمیل به " +"شما پاسخ خواهیم داد." + +msgid "send_report" +msgstr "مشکل را گزارش دهید" + +msgid "most_recent_lantern_apps" +msgstr "" +"جدیدترین برنامه‌های Lantern برای رایانه رومیزی و سایر پلتفورم‌ها را " +"می‌توانید در لینک بالا پیدا کنید." + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "لینک دانلود Lantern را به اشتراک بگذارید" + msgid "email" msgstr "ایمیل" @@ -278,6 +369,12 @@ msgstr "ایمیل و کد فعالسازی را وارد کنید" msgid "referral_code" msgstr "کد ارجاع" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "کد معرف را وارد کنید" diff --git a/assets/locales/fr.po b/assets/locales/fr.po index 12938fd2d..56f03049d 100644 --- a/assets/locales/fr.po +++ b/assets/locales/fr.po @@ -2,15 +2,15 @@ # Translators: # Derek F , 2022 # e2f_fr r9 , 2022 -# e2f_fr r16 , 2022 # e2f_fr r4 , 2023 # fffw , 2023 -# AO , 2023 +# e2f_fr r16 , 2023 # e2f_fr r15 , 2023 +# AO , 2023 # msgid "" msgstr "" -"Last-Translator: e2f_fr r15 , 2023\n" +"Last-Translator: AO , 2023\n" "Language-Team: French (https://app.transifex.com/lantern-1/teams/94371/fr/)\n" "Language: fr\n" "Plural-Forms: nplurals=3; plural=(n == 0 || n == 1) ? 0 : n != 0 && n % 1000000 == 0 ? 1 : 2;\n" @@ -120,6 +120,36 @@ msgstr "Passez à Lantern Pro" msgid "Invite Friends" msgstr "Inviter des amis" +msgid "share_lantern_pro" +msgstr "" +"Partagez Lantern Pro avec un ami ! Lorsque votre ami utilise votre code de " +"parrainage pendant son achat de Lantern Pro, vous bénéficiez tous les deux " +"d’un mois de service Lantern Pro gratuit !" + +msgid "privacy_disclosure_title" +msgstr "Avis de protection des données de Lantern" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern s’engage à protéger vos données personnelles. Nous souhaitons que " +"vous compreniez quels renseignements nous recueillons, ce que nous ne " +"recueillons pas, et comment nous recueillons, utilisons et stockons ces " +"renseignements. Nous dissocions vos renseignements de paiement de votre " +"compte Lantern Pro. Nous ne journalisons ni votre activité, ni l’historique " +"de navigation, ni la destination du trafic, ni le contenu des données, ni " +"les requêtes DNS. De plus, nous n’enregistrons jamais aucun journal de " +"connexion, c’est-à-dire aucun journal de votre adresse IP, de l’adresse IP " +"de votre serveur Lantern sortant associée à l’estampille temporelle de " +"connexion ni la durée de la session. Pour un compte Lantern Pro donné, " +"Lantern ne peut techniquement pas associer le compte Lantern à l’identité " +"réelle de l’utilisateur. Pour une adresse IP Lantern et une estampille " +"temporelle précise données, Lantern ne peut techniquement pas attribuer " +"cette adresse IP à un utilisateur de Lantern ni à l’adresse IP d’un " +"utilisateur de Lantern." + +msgid "privacy_disclosure_accept" +msgstr "J’accepte" + msgid "desktop_version" msgstr "Version pour ordinateur" @@ -156,8 +186,29 @@ msgstr "SDK %s" msgid "language" msgstr "Langue" -msgid "report_issue" -msgstr "Signaler un problème" +msgid "select_an_issue" +msgstr "Veuillez choisir un problème" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "Impossible d’accéder à des sites bloqués" + +msgid "cannot_complete_purchase" +msgstr "Impossible de terminer l’achat" + +msgid "cannot_sign_in" +msgstr "Impossible de se connecter" + +msgid "spinner_loads_endlessly" +msgstr "L’indicateur de chargement tourne sans fin" + +msgid "other" +msgstr "Autre" + +msgid "issue_description" +msgstr "Description du problème" msgid "check_for_updates" msgstr "Rechercher des mises à jour" @@ -195,6 +246,20 @@ msgstr "Relier avec un code" msgid "OR" msgstr "OU" +msgid "device_linking_pin" +msgstr "Code de liaison d’appareil" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "" +"* Assurez-vous que la version la plus récente de Lantern est utilisée sur " +"les deux appareils" + msgid "Authorize Device via Email" msgstr "Autoriser l’appareil par courriel" @@ -211,6 +276,38 @@ msgstr "Relier par courriel" msgid "lantern_pro_email" msgstr "Adresse courriel de Lantern Pro" +msgid "lantern_desktop" +msgstr "Lantern pour ordinateur" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "Signaler un problème" + +msgid "report_sent" +msgstr "Le relevé a été envoyé" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"Nous vous remercions d’avoir signalé ce problème. En raison du volume, nous " +"ne pouvons pas répondre à la plupart des problèmes, mais nous nous efforçons" +" d’améliorer Lantern d’après vos signalements." + +msgid "send_report" +msgstr "Envoyer le relevé" + +msgid "most_recent_lantern_apps" +msgstr "" +"Les applis Lantern les plus récentes pour ordinateurs et autres plates-" +"formes sont accessibles à partir du lien ci-dessus." + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "Partager le lien de téléchargement de Lantern" + msgid "email" msgstr "Adresse courriel" @@ -270,6 +367,12 @@ msgstr "Saisissez l’adresse courriel et le code d’activation" msgid "referral_code" msgstr "Code de parrainage" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "Ajouter un code de parrainage" diff --git a/assets/locales/hi.po b/assets/locales/hi.po index 5eb01bed2..8ef84ee8a 100644 --- a/assets/locales/hi.po +++ b/assets/locales/hi.po @@ -2,17 +2,17 @@ # Translators: # Minnie Kaur , 2022 # tx_e2f_hi r2 , 2023 +# tx_e2f_hi c3 , 2023 # Derek F , 2023 # Lantern, 2023 -# e2f , 2023 # Kalyan Dikshit , 2023 # tx_e2f_hi_r3 tx_e2f_hi_r3 , 2023 -# tx_e2f_hi c3 , 2023 # E2F_HI T1 , 2023 +# e2f , 2023 # msgid "" msgstr "" -"Last-Translator: E2F_HI T1 , 2023\n" +"Last-Translator: e2f , 2023\n" "Language-Team: Hindi (India) (https://app.transifex.com/lantern-1/teams/94371/hi_IN/)\n" "Language: hi_IN\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -118,6 +118,33 @@ msgstr "लालटेन के लिए प्रो उन्नयन" msgid "Invite Friends" msgstr "मित्रों को आमंत्रित करें" +msgid "share_lantern_pro" +msgstr "" +"दोस्त के साथ Lantern Pro साझा करें! जब आपका दोस्त Lantern Pro की खरीद के " +"दौरान आपके रेफरल कोड का उपयोग करता है, तो आप दोनों को एक महीने की Lantern " +"Pro सर्विस फ़्री मिलेगी!" + +msgid "privacy_disclosure_title" +msgstr "Lantern गोपनीयता प्रकटीकरण" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern आपकी गोपनीयता की रक्षा के लिए प्रतिबद्ध है. हम चाहते हैं कि आप यह " +"समझें कि हम क्या जानकारी इकट्ठा करते हैं और क्या नहीं, और हम उनका उपयोग " +"कैसे करते हैं, और जानकारी स्टोर करते हैं. हम आपके Lantern Pro अकाउंट से आपकी" +" भुगतान जानकारी को अलग कर देते हैं. हम आपकी गतिविधि के लॉग इकठ्ठा नहीं करते " +"हैं, जिसमें ब्राउज़िंग इतिहास, ट्रैफ़िक गंतव्य, डेटा कंटेंट या DNS प्रश्नों " +"का कोई लॉगिंग शामिल नहीं है. हम कभी भी कनेक्शन लॉग्स को स्टोर नहीं करते हैं," +" जिसका अर्थ है कि आपके IP पते का कोई लॉग, कनेक्शन टाइमस्टैम्प या सत्र अवधि " +"के साथ जुड़ा हुआ आउटगोइंग Lantern सर्वर IP पता. एक विशिष्ट Latern Pro अकाउंट" +" को देखते हुए, Lantern तकनीकी रूप से उपयोगकर्ता की वास्तविक पहचान के साथ " +"Latern अकाउंट को जोड़ने में असमर्थ है। एक Latern IP पते और एक सटीक समय-टिकट " +"को देखते हुए, Latern तकनीकी रूप से इस IP पते को Latern उपयोगकर्ता या Latern " +"उपयोगकर्ता IP पते की विशेषता देने में असमर्थ है." + +msgid "privacy_disclosure_accept" +msgstr "मुझे स्वीकार है" + msgid "desktop_version" msgstr "डेस्कटॉप संस्करण" @@ -154,8 +181,29 @@ msgstr "SDK %s" msgid "language" msgstr "भाषा" -msgid "report_issue" -msgstr "समस्या की रिपोर्ट करें" +msgid "select_an_issue" +msgstr "कृपया एक मुद्दा चयन" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "अवरुद्ध साइटों तक नहीं पहुँच सकते" + +msgid "cannot_complete_purchase" +msgstr "खरीदारी पूरी नहीं की जा सकती है" + +msgid "cannot_sign_in" +msgstr "साइन इन नहीं किया जा सकता है" + +msgid "spinner_loads_endlessly" +msgstr "स्पिनर अंतहीन रूप से लोड होता है" + +msgid "other" +msgstr "अन्य" + +msgid "issue_description" +msgstr "समस्या का विवरण" msgid "check_for_updates" msgstr "अपडेट के लिए चेक करें।" @@ -193,6 +241,19 @@ msgstr " पिन से लिंक करें" msgid "OR" msgstr "या" +msgid "device_linking_pin" +msgstr "डिवाइस लिंकिंग पिन" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "" +"* सुनिश्चित करें कि दोनों उपकरण लालटेन के सबसे हाल के संस्करण को चला रहे हैं" + msgid "Authorize Device via Email" msgstr "ईमेल से डिवाइस अधिकृत करें" @@ -208,6 +269,37 @@ msgstr "ईमेल से लिंक करें" msgid "lantern_pro_email" msgstr "Lantern Pro ईमेल" +msgid "lantern_desktop" +msgstr "लालटेन डेस्कटॉप" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "मामले की रिपोर्ट करें" + +msgid "report_sent" +msgstr "सूचना भेजी गई" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"आपकी समस्या की रिपोर्ट करने के लिए धन्यवाद, जैसे ही हम सक्षम होंगे हम आपको " +"ईमेल के माध्यम से वापस भेज देंगे" + +msgid "send_report" +msgstr "रिपोर्ट भेजो" + +msgid "most_recent_lantern_apps" +msgstr "" +"डेस्कटॉप और अन्य सभी प्लेटफार्मों के लिए सबसे हाल का Lantern ऐप, ऊपर दिए गए " +"लिंक पर पाया जा सकता है." + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "Lantern डाउनलोड लिंक शेयर करें" + msgid "email" msgstr "ईमेल" @@ -266,6 +358,12 @@ msgstr "ईमेल और एक्टिवेशन कोड दर्ज msgid "referral_code" msgstr "रेफरल कोड" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "रेफरल कोड जोड़ें" diff --git a/assets/locales/ms.po b/assets/locales/ms.po index a5b745a28..8379146f5 100644 --- a/assets/locales/ms.po +++ b/assets/locales/ms.po @@ -4,14 +4,14 @@ # Ahmed Noor Kader Mustajir Md Eusoff (kader) , 2023 # Lantern, 2023 # e2f_my c2 , 2023 -# e2f , 2023 -# Alyssa Chung , 2023 # tx_e2f_my t1 , 2023 +# e2f , 2023 # tx_e2f_ms t9 , 2023 +# Alyssa Chung , 2023 # msgid "" msgstr "" -"Last-Translator: tx_e2f_ms t9 , 2023\n" +"Last-Translator: Alyssa Chung , 2023\n" "Language-Team: Malay (https://app.transifex.com/lantern-1/teams/94371/ms/)\n" "Language: ms\n" "Plural-Forms: nplurals=1; plural=0;\n" @@ -119,6 +119,34 @@ msgstr "Upgrade ke Lantern Pro" msgid "Invite Friends" msgstr "Menjemput Rakan-Rakan" +msgid "share_lantern_pro" +msgstr "" +"Kongsikan Lantern Pro dengan rakan! Apabila rakan anda menggunakan kod " +"rujukan semasa pembelian Lantern Pro mereka, anda berdua akan menerima " +"perkhidmatan percuma Lantern Pro selama sebulan!" + +msgid "privacy_disclosure_title" +msgstr "Pendedahan Privasi Lantern" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern komited untuk melindungi privasi anda. Kami ingin anda memahami " +"maklumat yang kami kumpulkan, maklumat yang tidak kami kumpulkan dan cara " +"kami mengumpulkan, menggunakan dan menyimpan maklumat. Kami memisahkan " +"maklumat pembayaran anda dengan akaun Lantern Pro anda. Kami tidak " +"mengumpulkan log aktiviti anda, termasuk tidak mencatat sejarah penyemakan " +"imbas, destinasi trafik, kandungan data atau pertanyaan DNS. Kami juga tidak" +" pernah menyimpan log sambungan, yang bermaksud tidak ada log alamat IP " +"anda, alamat IP pelayan Lantern keluar anda yang dikaitkan dengan cap waktu " +"sambungan, atau tempoh sesi. Disebabkan akaun Lantern Pro yang tertentu, " +"secara teknikal, Lantern tidak dapat mengaitkan akaun Lantern dengan " +"identiti sebenar pengguna. Disebabkan alamat IP Lantern dan cap waktu yang " +"tepat, Lantern secara teknikal tidak dapat menghubungkan alamat IP ini " +"kepada pengguna Lantern atau alamat IP pengguna Lantern." + +msgid "privacy_disclosure_accept" +msgstr "Saya terima" + msgid "desktop_version" msgstr "Versi Desktop" @@ -155,8 +183,29 @@ msgstr "SDK %s" msgid "language" msgstr "Bahasa" -msgid "report_issue" -msgstr "Laporkan isu" +msgid "select_an_issue" +msgstr "Sila Pilih satu Isu" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "Tidak boleh akses laman-laman disekat" + +msgid "cannot_complete_purchase" +msgstr "Tidak dapat melengkapkan pembelian" + +msgid "cannot_sign_in" +msgstr "Tidak boleh mendaftar masuk" + +msgid "spinner_loads_endlessly" +msgstr "Pemutar dimuatkan tanpa henti" + +msgid "other" +msgstr "Lain-lain" + +msgid "issue_description" +msgstr "Penerangan Isu" msgid "check_for_updates" msgstr "Semak kemas kini" @@ -194,6 +243,18 @@ msgstr "Pautkan dengan PIN" msgid "OR" msgstr "ATAU" +msgid "device_linking_pin" +msgstr "Pin pemautan peranti" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "* Pastikan kedua-dua peranti menjalankan versi terbaru Lantern" + msgid "Authorize Device via Email" msgstr "Berikan Kebenaran kepada Peranti melalui E-mel" @@ -210,6 +271,37 @@ msgstr "Pautkan melalui E-mel" msgid "lantern_pro_email" msgstr "E-mel Pro Lantern" +msgid "lantern_desktop" +msgstr "Desktop Lantern" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "Melapor suatu Isu" + +msgid "report_sent" +msgstr "Laporan Dihantar" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"Terima kasih kerana melaporkan isu anda, kami akan menghubungi anda melalui " +"E-mel secepat mungkin" + +msgid "send_report" +msgstr "Hantar Laporan" + +msgid "most_recent_lantern_apps" +msgstr "" +"Aplikasi Lantern terbaru untuk desktop dan semua platform lain boleh " +"didapati di pautan di atas." + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "Kongsikan pautan muat turun Lantern" + msgid "email" msgstr "Emel" @@ -267,6 +359,12 @@ msgstr "Masukkan E-mel dan kod Pengaktifan" msgid "referral_code" msgstr "Kod rujukan" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "Tambah kod Rujukan" diff --git a/assets/locales/ru.po b/assets/locales/ru.po index f219590c3..368a3e549 100644 --- a/assets/locales/ru.po +++ b/assets/locales/ru.po @@ -9,19 +9,19 @@ # e2f , 2022 # e2f_ru r4 , 2023 # Serg , 2023 -# e2f_ru r1 , 2023 # Anna Evdokimova , 2023 +# Helen Russel , 2023 +# e2f_ru r3 , 2023 # Tina T, 2023 -# tx_e2f_ru c4 , 2023 -# sayanvd , 2023 +# e2f_ru r1 , 2023 # e2f_ru c1 , 2023 -# Helen Russel , 2023 +# tx_e2f_ru c4 , 2023 # Deanna D, 2023 -# e2f_ru r3 , 2023 +# sayanvd , 2023 # msgid "" msgstr "" -"Last-Translator: e2f_ru r3 , 2023\n" +"Last-Translator: sayanvd , 2023\n" "Language-Team: Russian (Russia) (https://app.transifex.com/lantern-1/teams/94371/ru_RU/)\n" "Language: ru_RU\n" "Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || (n%100>=11 && n%100<=14)? 2 : 3);\n" @@ -128,6 +128,32 @@ msgstr "Обновление до Lantern Pro" msgid "Invite Friends" msgstr "Пригласи друзей " +msgid "share_lantern_pro" +msgstr "" +"Поделитесь кодом с друзьями - если они купят версию Pro, вы оба получите " +"бесплатный месяц подписки на Lantern Pro!" + +msgid "privacy_disclosure_title" +msgstr "Политика конфиденциальности Lantern" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern серьезно относится к защите вашей конфиденциальности. Для нас важно," +" чтобы вы понимали, какую информацию мы собираем (и не собираем), как мы ее " +"используем и храним. Ваши платежные данные не связаны с аккаунтом Lantern " +"Pro. Мы не собираем логи о вашей активности (в том числе историю браузера, " +"сведения об адресатах трафика, содержание данных или DNS-запросы). Мы " +"никогда не храним логи соединений, то есть ваши зарегистрированные IP-" +"адреса, IP-адрес исходящего сервера Lantern, связанный с временной меткой " +"подключения, а также продолжительность сеансов. У нас нет технической " +"возможности связать аккаунт Lantern Pro с реальной идентифицирующей " +"информацией пользователя. Кроме того, имея IP-адрес Lantern и точную " +"временную метку, мы не можем связать этот адрес с пользователем Lantern или " +"IP-адресом этого пользователя." + +msgid "privacy_disclosure_accept" +msgstr "Я принимаю" + msgid "desktop_version" msgstr "Версия для ПК" @@ -164,8 +190,29 @@ msgstr "SDK %s" msgid "language" msgstr "Язык" -msgid "report_issue" -msgstr "Сообщить о проблеме" +msgid "select_an_issue" +msgstr "Выберите проблему" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "Не удается получить доступ к заблокированным сайтам" + +msgid "cannot_complete_purchase" +msgstr "Ваша покупка не может быть завершена" + +msgid "cannot_sign_in" +msgstr "Не удается войти в аккаунт" + +msgid "spinner_loads_endlessly" +msgstr "Невозможно загрузить страницу " + +msgid "other" +msgstr "Другое" + +msgid "issue_description" +msgstr "Описание проблемы" msgid "check_for_updates" msgstr "Проверить обновления" @@ -203,6 +250,19 @@ msgstr "Авторизация для Lantern Pro" msgid "OR" msgstr "ИЛИ" +msgid "device_linking_pin" +msgstr "Код привязки устройства" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "" +"* Убедитесь, что на обоих устройствах установлена последняя версия Lantern." + msgid "Authorize Device via Email" msgstr "Авторизация с помощью электронной почты" @@ -220,6 +280,38 @@ msgstr "Ссылка по электронной почте" msgid "lantern_pro_email" msgstr "Адрес эл. почты аккаунта Lantern Pro" +msgid "lantern_desktop" +msgstr "Версия Lantern для ПК" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "Сообщить о проблеме" + +msgid "report_sent" +msgstr "Отчет отправлен" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"Спасибо, что сообщили о своей проблеме. Из-за большого потока обращений мы " +"не можем отвечать на большинство из них, однако будьте уверены, мы прилагаем" +" все усилия, чтобы совершенствовать Lantern на основании ваших отзывов." + +msgid "send_report" +msgstr "Отправить отчет" + +msgid "most_recent_lantern_apps" +msgstr "" +"По ссылке выше доступны актуальные версии приложений Lantern для ПК и других" +" платформ." + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "Поделитесь ссылкой на скачивание Lantern" + msgid "email" msgstr "Электронная почта" @@ -279,6 +371,12 @@ msgstr "Введите email и код активации " msgid "referral_code" msgstr "Реферальный код" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "Ввести реферальный код " diff --git a/assets/locales/tr.po b/assets/locales/tr.po index 1f7e6120f..753665af6 100644 --- a/assets/locales/tr.po +++ b/assets/locales/tr.po @@ -7,16 +7,18 @@ # M. Utkun Uyar, 2023 # Emre Deniz, 2023 # Volkan Gezer , 2023 -# e2f , 2023 -# efe kerem sözeri , 2023 # Alexander B. , 2023 # fffw , 2023 -# Teo Westbrook, 2023 +# Gökhan Kalayci , 2023 +# Lantern, 2023 +# efe kerem sözeri , 2023 +# e2f , 2023 # e2f_tr c1 , 2023 +# Teo Westbrook, 2023 # msgid "" msgstr "" -"Last-Translator: e2f_tr c1 , 2023\n" +"Last-Translator: Teo Westbrook, 2023\n" "Language-Team: Turkish (https://app.transifex.com/lantern-1/teams/94371/tr/)\n" "Language: tr\n" "Plural-Forms: nplurals=2; plural=(n > 1);\n" @@ -124,6 +126,34 @@ msgstr "Lantern Pro'ya Yükseltin" msgid "Invite Friends" msgstr "Arkadaşlarını Davet Et" +msgid "share_lantern_pro" +msgstr "" +"Lantern Pro'yu bir arkadaşınızla paylaşın! Arkadaşınız Lantern Pro satın " +"alırken referans kodunuzu kullanırsa ikiniz de bir ay ücretsiz Lantern Pro " +"hizmeti kazanırsınız!" + +msgid "privacy_disclosure_title" +msgstr "Lantern Gizlilik Anlaşması" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern gizliliğinizi korumayı taahhüt eder. Hangi bilgileri topladığımızı, " +"hangilerini toplamadığımızı, bilgileri nasıl topladığımızı, kullandığımızı " +"ve depoladığımızı anlamanızı istiyoruz. Ödeme bilgilerinizi Lantern Pro " +"hesabınızla ilişkilendirmiyoruz. Aktivitenizin günlüklerini toplamıyoruz; " +"örneğin tarama geçmişinizin, trafik hedefinizin, veri içeriğinizin veya DNS " +"sorgularınızın günlüğünü tutmuyoruz. Ayrıca bağlantı günlüklerini hiçbir " +"zaman depolamıyoruz; yani IP adresinizin, bağlantı zaman damgasıyla ilişkili" +" giden Lantern sunucu IP adresinizin veya oturum sürenizin günlüğünü " +"tutmuyoruz. Belirli bir Lantern Pro hesabı bakımından, Lantern'ın bir " +"Lantern hesabını kullanıcının gerçek kimliğiyle ilişkilendirmesi teknik " +"olarak mümkün değildir. Lantern IP adresi ve kesin zaman damgası bakımından," +" Lantern'ın bu IP adresini bir Lantern kullanıcısına veya Lantern kullanıcı " +"IP adresine dayandırması teknik olarak mümkün değildir." + +msgid "privacy_disclosure_accept" +msgstr "Kabul Ediyorum" + msgid "desktop_version" msgstr "Masaüstü Versiyonu" @@ -160,8 +190,29 @@ msgstr "SDK %s" msgid "language" msgstr "Dil" -msgid "report_issue" -msgstr "Sorun bildir" +msgid "select_an_issue" +msgstr "Bir hata seçiniz" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "Engellenen sitelere erişilemiyor" + +msgid "cannot_complete_purchase" +msgstr "Satın alma tamamlanamıyor" + +msgid "cannot_sign_in" +msgstr "Giriş yapılamadı" + +msgid "spinner_loads_endlessly" +msgstr "Yükleme ekranı sürekli dönüyor" + +msgid "other" +msgstr "Diğer" + +msgid "issue_description" +msgstr "Sorun Açıklaması" msgid "check_for_updates" msgstr "Güncellemeleri Kontrol Et" @@ -199,6 +250,19 @@ msgstr "PIN ile Bağla" msgid "OR" msgstr "VEYA" +msgid "device_linking_pin" +msgstr "Cihaz bağlama pini" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "" +"* Her iki cihazın da Lantern'ın en son sürümünü çalıştırdığından emin olun" + msgid "Authorize Device via Email" msgstr "Cihazı E-Posta Üzerinden Yetkilendir" @@ -215,6 +279,37 @@ msgstr "E-Posta Aracılığıyla Bağla" msgid "lantern_pro_email" msgstr "Lantern Pro E-Postası" +msgid "lantern_desktop" +msgstr "Lantern Masaüstü" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "Hata Bildir" + +msgid "report_sent" +msgstr "Rapor Gönderildi" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"Sorununuzu bildirdiğiniz için teşekkür ederiz, mümkün olan en kısa sürede " +"e-posta yoluyla size geri dönüş yapacağız" + +msgid "send_report" +msgstr "Rapor Gönder" + +msgid "most_recent_lantern_apps" +msgstr "" +"Masaüstü ve diğer tüm platformlar için en son Lantern uygulamaları " +"yukarıdaki bağlantıda bulunabilir." + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "Lantern indirme bağlantısı paylaş" + msgid "email" msgstr "E-posta" @@ -274,6 +369,12 @@ msgstr "E-posta ve Aktivasyon kodu girin" msgid "referral_code" msgstr "Referans kodu" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "Referans kodu ekleyin" diff --git a/assets/locales/ur.po b/assets/locales/ur.po index 54a1f1d24..f4df910f4 100644 --- a/assets/locales/ur.po +++ b/assets/locales/ur.po @@ -1,13 +1,13 @@ # # Translators: # tx_e2f_pk r6 , 2023 -# Diversity83 83, 2023 # e2f , 2023 # tx_e2f_ur c1 , 2023 +# Diversity83 83, 2023 # msgid "" msgstr "" -"Last-Translator: tx_e2f_ur c1 , 2023\n" +"Last-Translator: Diversity83 83, 2023\n" "Language-Team: Urdu (https://app.transifex.com/lantern-1/teams/94371/ur/)\n" "Language: ur\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" @@ -116,6 +116,33 @@ msgstr "لینٹرن پرو پر اَپ گریڈ کیجئے" msgid "Invite Friends" msgstr "دوستوں کو مدعو کیجئے" +msgid "share_lantern_pro" +msgstr "" +"Lantern Pro کو دوست کے ساتھ شیئر کریں! آپ کے دوست کی جانب سے Lantern Pro کی " +"خریداری کے دوران آپ کا ریفرل کوڈ استعمال کرنے پر، آپ دونوں ایک ماہ کے لیے " +"Lantern Pro سروس مفت حاصل کریں گے!" + +msgid "privacy_disclosure_title" +msgstr "لینٹرن کی رازداری کا اعلان" + +msgid "privacy_disclosure_body" +msgstr "" +"لینٹرن آپ کی رازداری کے تحفظ کے لئے پرعزم ہے۔ ہم چاہتے ہیں کہ آپ یہ سمجھیں " +"کہ ہم کون سی معلومات اکٹھا کرتے ہیں، کیا اکٹھا نہیں کرتے، اور ہم معلومات کو " +"کس طرح اکٹھا، استعمال اور اسٹور کرتے ہیں۔ ہم آپ کے لینٹرن پرو اکاؤنٹ سے آپ " +"کی ادائیگی کی معلومات کو الگ کردیں۔ ہم آپ کی سرگرمی کے لاگز، بشمول براؤزنگ " +"کی سرگزشت، ٹریفک کی منزل، ڈیٹا کے مشمولات، یا DNS استفسارات اکٹھا نہیں کرتے " +"ہیں۔ ہم کبھی بھی کنیکشن لاگز کو اسٹور نہیں کرتے ہیں، یعنی آپ کے IP ایڈریس کی" +" کوئی لاگز ، آپ کا باہر جانے والا لینٹرن سرور IP ایڈریس جو کنکشن ٹائم " +"اسٹیمپ، یا سیشن کے دورانیے کے ساتھ وابستہ ہے۔ جہاں تک مخصوص لینٹرن پرو " +"اکاؤنٹ کا سوال ہے تو، لینٹرن تکنیکی طور پر لینٹرن اکاؤنٹ کو صارف کی اصل " +"شناخت کے ساتھ منسلک کرنے سے قاصر ہے۔ صرف لینٹرن کا IP ایڈریس اور ایک مختصر " +"ٹائم اسٹیمپ دیئے جانے کے سبب، لینٹرن تکنیکی طور پر اس IP پتے کو کسی لینٹرن " +"صارف یا کسی لینٹرن صارف کے IP پتے سے منسوب کرنے سے قاصر ہے۔" + +msgid "privacy_disclosure_accept" +msgstr "مجھے قبول ہے" + msgid "desktop_version" msgstr "ڈیسک ٹاپ ورژن" @@ -152,8 +179,29 @@ msgstr "SDK %s" msgid "language" msgstr "زبان" -msgid "report_issue" -msgstr "مسئلہ کو رپورٹ کریں" +msgid "select_an_issue" +msgstr "برائے مہربانی ایک مسئلے کا انتخاب کیجئے" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "بلاک سائٹس تک رسائی ممکن نہیں " + +msgid "cannot_complete_purchase" +msgstr "خریداری مکمل نہیں کر سکتے" + +msgid "cannot_sign_in" +msgstr "سائن ان نہیں کر سکتے" + +msgid "spinner_loads_endlessly" +msgstr "اسپنر لا محدود طور پر لوڈ کرتا ہے" + +msgid "other" +msgstr "دوسرے" + +msgid "issue_description" +msgstr "مسئلہ کی تفصیل" msgid "check_for_updates" msgstr "اپ ڈیٹس کے لیے پڑتال کریں" @@ -190,6 +238,19 @@ msgstr "PIN کے ساتھ لنک کریں" msgid "OR" msgstr "یا" +msgid "device_linking_pin" +msgstr "ڈیوائس لنکنگ پن" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "" +"* اس بات کو یقینی بنائیں کہ دونوں ڈیوائسز پر لینٹرن کا حالیہ ورژن چل رہا ہے" + msgid "Authorize Device via Email" msgstr "ای میل کے ذریعہ آلہ کو اجازت دیں" @@ -207,6 +268,38 @@ msgstr "ای میل کے ذریعہ لنک" msgid "lantern_pro_email" msgstr "Lantern پرو ای میل" +msgid "lantern_desktop" +msgstr "لینٹرن ڈیسک ٹاپ" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "مسئلہ رپورٹ کیجئے" + +msgid "report_sent" +msgstr "رپورٹ بھیج دی گئی" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"اپنے مسئلے کو رپورٹ کرنے کا شکریہ۔ کثیر تعداد کے سبب، ہم زیادہ تر مسائل کا " +"جواب نہیں دے سکتے، لیکن ہم آپ کو یقین دلاتے ہیں کہ ہم آپ کی رپورٹس کی بنیاد " +"پر Lantern کو بہتر بنانے کے لیے پوری طرح کوشاں ہیں۔" + +msgid "send_report" +msgstr "رپورٹ بھیجیں" + +msgid "most_recent_lantern_apps" +msgstr "" +"ڈیسک ٹاپ اور دیگر تمام پلیٹ فارمز کے لیے حالیہ ترین لینٹرن ایپس اوپر کے لنک " +"پر مل سکتی ہیں۔" + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "لینٹرن ڈاؤن لوڈ کا لنک شیئر کریں" + msgid "email" msgstr "ای میل" @@ -266,6 +359,12 @@ msgstr "ای میل اور فعالیتی کوڈ درج کریں" msgid "referral_code" msgstr "حوالہ کوڈ" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "ریفرل کوڈ شامل کریں" diff --git a/assets/locales/vi.po b/assets/locales/vi.po index 02ea226d1..3f921645e 100644 --- a/assets/locales/vi.po +++ b/assets/locales/vi.po @@ -1,15 +1,16 @@ # # Translators: # Anh Phan , 2023 +# Yen Le Van , 2023 # Lantern, 2023 -# e2f , 2023 # Nathan Tran, 2023 -# Yen Le Van , 2023 +# e2f , 2023 # e2f_vi r1 , 2023 +# Ngoc Hoang , 2023 # msgid "" msgstr "" -"Last-Translator: e2f_vi r1 , 2023\n" +"Last-Translator: Ngoc Hoang , 2023\n" "Language-Team: Vietnamese (https://app.transifex.com/lantern-1/teams/94371/vi/)\n" "Language: vi\n" "Plural-Forms: nplurals=1; plural=0;\n" @@ -117,6 +118,34 @@ msgstr "Nâng Cấp lên Lantern Pro" msgid "Invite Friends" msgstr "Mời Bạn Bè" +msgid "share_lantern_pro" +msgstr "" +"Chia sẻ Lantern Pro với bạn bè! Khi bạn của bạn sử dụng mã giới thiệu của " +"bạn trong quá trình mua Lantern Pro, cả hai bạn sẽ nhận được một tháng dịch " +"vụ Lantern Pro miễn phí!" + +msgid "privacy_disclosure_title" +msgstr "Tiết lộ quyền riêng tư của Lantern" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern cam kết bảo vệ sự riêng tư của bạn. Chúng tôi muốn bạn hiểu thông " +"tin nào chúng tôi thu thập, thông tin nào chúng tôi không thu thập và cách " +"chúng tôi thu thập, sử dụng và lưu trữ thông tin. Chúng tôi tách thông tin " +"thanh toán của bạn với tài khoản Lantern Pro của bạn. Chúng tôi không thu " +"thập nhật ký hoạt động của bạn, bao gồm không ghi nhật ký lịch sử duyệt web," +" điểm đến của lưu lượng truy cập, nội dung dữ liệu hoặc truy vấn DNS. Chúng " +"tôi cũng không bao giờ lưu trữ nhật ký kết nối, nghĩa là không có nhật ký " +"nào về địa chỉ IP của bạn, địa chỉ IP máy chủ Lantern gửi đi của bạn được " +"liên kết với dấu thời gian kết nối hoặc thời lượng mỗi phiên. Với tài khoản " +"Lantern Pro cụ thể, về mặt kỹ thuật, Lantern không thể liên kết tài khoản " +"Lantern với danh tính thực của người dùng. Với địa chỉ IP của Lantern và dấu" +" thời gian chính xác, về mặt kỹ thuật, Lantern không thể gán địa chỉ IP này " +"cho người dùng Lantern hoặc địa chỉ IP của người dùng Lantern." + +msgid "privacy_disclosure_accept" +msgstr "Tôi đồng ý" + msgid "desktop_version" msgstr "Phiên bản Để Bàn" @@ -153,8 +182,29 @@ msgstr "SDK %s" msgid "language" msgstr "Ngôn ngữ" -msgid "report_issue" -msgstr "Báo cáo lỗi" +msgid "select_an_issue" +msgstr "Hãy chọn 1 vấn đề" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "Không thể truy cập các trang bị chặn" + +msgid "cannot_complete_purchase" +msgstr "Không thể hoàn tất việc mua" + +msgid "cannot_sign_in" +msgstr "Không thể đăng nhập" + +msgid "spinner_loads_endlessly" +msgstr "Spinner tải vô tận" + +msgid "other" +msgstr "Khác" + +msgid "issue_description" +msgstr "Mô tả vấn đề" msgid "check_for_updates" msgstr "Kiểm tra bản cập nhật" @@ -191,6 +241,18 @@ msgstr "Liên kết bằng mã PIN" msgid "OR" msgstr "HOẶC" +msgid "device_linking_pin" +msgstr "Ghim liên kết thiết bị" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "* Đảm bảo cả hai thiết bị đang chạy phiên bản Lantern mới nhất" + msgid "Authorize Device via Email" msgstr "Cấp phép thiết bị qua email" @@ -206,6 +268,37 @@ msgstr "Liên kết qua Email" msgid "lantern_pro_email" msgstr "Lantern Pro Email" +msgid "lantern_desktop" +msgstr "Lantern Để Bàn" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "Báo cáo vấn đề" + +msgid "report_sent" +msgstr "Đã gửi báo cáo" + +msgid "thank_you_for_reporting_your_issue" +msgstr "" +"Cảm ơn bạn đã báo cáo sự cố của mình, chúng tôi sẽ liên hệ lại với bạn qua " +"Email ngay khi có thể" + +msgid "send_report" +msgstr "Gửi báo cáo" + +msgid "most_recent_lantern_apps" +msgstr "" +"Bạn có thể tìm thấy các ứng dụng Lantern mới nhất dành cho máy tính để bàn " +"và tất cả các nền tảng khác tại liên kết ở trên." + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "Chia sẻ liên kết tải xuống Lantern" + msgid "email" msgstr "Email" @@ -263,6 +356,12 @@ msgstr "Nhập Email và Mã kích hoạt" msgid "referral_code" msgstr "Mã giới thiệu" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "Thêm mã giới thiệu" diff --git a/assets/locales/zh-cn.po b/assets/locales/zh-cn.po index e364af6fc..2b701b0c0 100644 --- a/assets/locales/zh-cn.po +++ b/assets/locales/zh-cn.po @@ -2,15 +2,15 @@ # Translators: # 474874f569f96a33809d02f1de71479e_77020aa, 2023 # Chi-Hsun Tsai, 2023 -# Lantern, 2023 # e2f_cn c3 , 2023 +# Lantern, 2023 # CN R10 , 2023 +# Aki, 2023 # e2f , 2023 -# Aki Ltn, 2023 # msgid "" msgstr "" -"Last-Translator: Aki Ltn, 2023\n" +"Last-Translator: e2f , 2023\n" "Language-Team: Chinese Simplified (https://app.transifex.com/lantern-1/teams/94371/zh-Hans/)\n" "Language: zh-Hans\n" "Plural-Forms: nplurals=1; plural=0;\n" @@ -108,6 +108,22 @@ msgstr "升级到蓝灯专业版" msgid "Invite Friends" msgstr "邀请朋友" +msgid "share_lantern_pro" +msgstr "与朋友分享蓝灯专业版账户!您的朋友在购买蓝灯专业版时,只需顺手填入您的推荐码,您和朋友两人都将额外获赠一个月专业版时长!" + +msgid "privacy_disclosure_title" +msgstr "蓝灯隐私披露" + +msgid "privacy_disclosure_body" +msgstr "" +"蓝灯致力于保护您的隐私。我们希望您了解我们收集哪些信息、不收集哪些信息,以及我们如何收集、使用和存储信息。我们不会将您的支付信息与您的蓝灯专业版帐户分开。我们不收集您的活动日志,包括不记录浏览历史、流量目的地、数据内容或" +" DNS 查询。我们也不会储存连接记录,即不会记录您的 IP 地址、与连接时间戳相关的蓝灯服务器的 IP " +"地址或会话持续时间。对于给定的蓝灯专业版帐户,蓝灯在技术上无法将蓝灯帐户与用户的真实身份联系起来。对于给定一个蓝灯 IP " +"地址和精确的时间戳,蓝灯在技术上也无法将此 IP 地址归属于蓝灯用户或蓝灯用户的 IP 地址。" + +msgid "privacy_disclosure_accept" +msgstr "我接受" + msgid "desktop_version" msgstr "桌面版" @@ -144,8 +160,29 @@ msgstr "SDK %s" msgid "language" msgstr "语种" -msgid "report_issue" -msgstr "报告问题" +msgid "select_an_issue" +msgstr "请选择一个问题" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "无法访问被屏蔽的网站" + +msgid "cannot_complete_purchase" +msgstr "无法完成购买" + +msgid "cannot_sign_in" +msgstr "无法登录" + +msgid "spinner_loads_endlessly" +msgstr "始终在加载中" + +msgid "other" +msgstr "其他" + +msgid "issue_description" +msgstr "问题描述" msgid "check_for_updates" msgstr "检查更新" @@ -180,6 +217,18 @@ msgstr "使用 PIN 码连接" msgid "OR" msgstr "或" +msgid "device_linking_pin" +msgstr "设备码授权" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "*确保两台设备都在运行最新版本的蓝灯" + msgid "Authorize Device via Email" msgstr "通过电子邮件授权设备" @@ -195,6 +244,33 @@ msgstr "通过电子邮件连接" msgid "lantern_pro_email" msgstr "蓝灯专业版电子邮件" +msgid "lantern_desktop" +msgstr "蓝灯桌面版" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "报告问题" + +msgid "report_sent" +msgstr "报告已发送" + +msgid "thank_you_for_reporting_your_issue" +msgstr "感谢您报告问题。由于数量众多,我们无法回复大多数问题,但我们向您保证,我们正在努力根据您的报告改进 Lantern。" + +msgid "send_report" +msgstr "发送报告" + +msgid "most_recent_lantern_apps" +msgstr "最新的桌面和所有其他平台的蓝灯应用程序可以在上面的链接中找到。" + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "分享蓝灯下载链接" + msgid "email" msgstr "电子邮件" @@ -252,6 +328,12 @@ msgstr "填写电子邮箱地址和激活码" msgid "referral_code" msgstr "邀请码" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "填入推荐码" diff --git a/assets/locales/zh-hk.po b/assets/locales/zh-hk.po index f546ce259..6e0c72a3b 100644 --- a/assets/locales/zh-hk.po +++ b/assets/locales/zh-hk.po @@ -3,13 +3,13 @@ # e2f_tw r5 , 2021 # 474874f569f96a33809d02f1de71479e_77020aa, 2023 # e2f_tw c1 , 2023 -# e2f , 2023 # TW C2 , 2023 -# Aki Ltn, 2023 +# Aki, 2023 +# e2f , 2023 # msgid "" msgstr "" -"Last-Translator: Aki Ltn, 2023\n" +"Last-Translator: e2f , 2023\n" "Language-Team: Chinese Traditional (https://app.transifex.com/lantern-1/teams/94371/zh-Hant/)\n" "Language: zh-Hant\n" "Plural-Forms: nplurals=1; plural=0;\n" @@ -107,6 +107,24 @@ msgstr "升級到 Lantern Pro" msgid "Invite Friends" msgstr "邀請好友" +msgid "share_lantern_pro" +msgstr "与朋友分享蓝灯专业版账户!您的朋友在购买蓝灯专业版时,只需顺手填入您的推荐码,您和朋友两人都将额外获赠一个月专业版时长!" + +msgid "privacy_disclosure_title" +msgstr "Lantern 私隱權揭露" + +msgid "privacy_disclosure_body" +msgstr "" +"Lantern 承諾保護您的私隱。我們希望您瞭解我們收集哪些資訊、不收集哪些資訊,以及我們收集、使用和存儲資訊的方式。我們會解除您的付款資訊與您 " +"Lantern Pro 帳號的關聯。我們不會收集您的活動記錄,包括不記錄瀏覽歷程記錄、流量目的地、資料內容或 DNS " +"查詢。我們也不會儲存連線記錄檔,也就是說,您的 IP 位址、與連線時間戳記相關的外送 Lantern 伺服器 IP " +"位址,或工作階段持續時間。對於某個特定 Lantern Pro 帳號,Lantern 在技術上無法關聯 Lantern 帳號與使用者的真實身份。給定一個" +" Lantern IP 位址和精確的時間戳記,Lantern 在技術上無法將此 IP 位址歸入 Lantern 使用者或 Lantern 使用者 IP" +" 位址。" + +msgid "privacy_disclosure_accept" +msgstr "我接受" + msgid "desktop_version" msgstr "桌面版本" @@ -143,8 +161,29 @@ msgstr "SDK %s" msgid "language" msgstr "語言" -msgid "report_issue" -msgstr "回報問題" +msgid "select_an_issue" +msgstr "請選擇問題" + +msgid "enter_description" +msgstr "" + +msgid "cannot_access_blocked_sites" +msgstr "無法存取已阻止的網站" + +msgid "cannot_complete_purchase" +msgstr "无法完成购买" + +msgid "cannot_sign_in" +msgstr "无法登录" + +msgid "spinner_loads_endlessly" +msgstr "始终在加载中" + +msgid "other" +msgstr "其他" + +msgid "issue_description" +msgstr "問題描述" msgid "check_for_updates" msgstr "檢查更新" @@ -179,6 +218,18 @@ msgstr "使用連結碼來連結" msgid "OR" msgstr "或" +msgid "device_linking_pin" +msgstr "裝置連結碼" + +msgid "link_device_step_one" +msgstr "" + +msgid "link_device_step_two" +msgstr "" + +msgid "ensure_most_recent_version_lantern" +msgstr "*確保兩個裝置都運行最新版本的 Lantern" + msgid "Authorize Device via Email" msgstr "透過電子郵件授權裝置" @@ -194,6 +245,33 @@ msgstr "透過電子郵件來連結" msgid "lantern_pro_email" msgstr "Lantern 專業版電子郵件" +msgid "lantern_desktop" +msgstr "Lantern 桌上型電腦" + +msgid "report_issue" +msgstr "" + +msgid "report_an_issue" +msgstr "回報問題" + +msgid "report_sent" +msgstr "報告已傳送" + +msgid "thank_you_for_reporting_your_issue" +msgstr "感謝您回報問題。由於回報事項眾多,我們無法回覆大部分的問題,但我們保證已努力解決問題,根據您所回報的內容改善 Lantern。" + +msgid "send_report" +msgstr "發送報告" + +msgid "most_recent_lantern_apps" +msgstr "可以在上面的連結中找到最新桌面及所有其他平台的 Lantern 應用程式。" + +msgid "share_link" +msgstr "" + +msgid "share_title" +msgstr "分享 Lantern 下載連結" + msgid "email" msgstr "電子郵件" @@ -251,6 +329,12 @@ msgstr "填写电子邮箱地址和激活码" msgid "referral_code" msgstr "推薦代碼" +msgid "share_referral_code" +msgstr "" + +msgid "share_message_referral_code" +msgstr "" + msgid "add_referral_code" msgstr "填入推荐码" diff --git a/go.mod b/go.mod index a549ddac9..f48cc9d08 100644 --- a/go.mod +++ b/go.mod @@ -24,7 +24,7 @@ replace github.com/keighl/mandrill => github.com/getlantern/mandrill v0.0.0-2022 // For https://github.com/crawshaw/sqlite/pull/112 and https://github.com/crawshaw/sqlite/pull/103. replace crawshaw.io/sqlite => github.com/getlantern/sqlite v0.0.0-20220301112206-cb2f8bc7cb56 -replace github.com/Jigsaw-Code/outline-ss-server => github.com/getlantern/lantern-shadowsocks v1.3.6-0.20230114153732-0193919d4860 +replace github.com/Jigsaw-Code/outline-ss-server => github.com/getlantern/lantern-shadowsocks v1.3.6-0.20230301223223-150b18ac427d replace github.com/google/netstack => github.com/getlantern/netstack v0.0.0-20220824143118-037ff0cd9c33 @@ -36,6 +36,7 @@ require ( github.com/getlantern/dnsgrab v0.0.0-20211216020425-5d5e155a01a8 github.com/getlantern/errors v1.0.3 github.com/getlantern/eventual/v2 v2.0.2 + github.com/getlantern/flashlight/v7 v7.5.28 github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 github.com/getlantern/idletiming v0.0.0-20201229174729-33d04d220c4e github.com/getlantern/ipproxy v0.0.0-20230511223023-ee52513fd782 @@ -44,8 +45,8 @@ require ( github.com/getlantern/replica v0.3.0 github.com/gorilla/mux v1.8.0 github.com/stretchr/testify v1.8.3 - golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda - golang.org/x/net v0.12.0 + golang.org/x/mobile v0.0.0-20221110043201-43a038452099 + golang.org/x/net v0.10.0 nhooyr.io/websocket v1.8.7 ) @@ -105,11 +106,11 @@ require ( github.com/dustin/go-humanize v1.0.1 // indirect github.com/dvyukov/go-fuzz v0.0.0-20220726122315-1d375ef9f9f6 // indirect github.com/edsrzf/mmap-go v1.1.0 // indirect + github.com/elazarl/goproxy v0.0.0-20221015165544-a0805db90819 // indirect github.com/enobufs/go-nats v0.0.1 // indirect github.com/felixge/httpsnoop v1.0.3 // indirect - github.com/gaukas/godicttls v0.0.3 // indirect github.com/getlantern/borda v0.0.0-20230421223744-4e208135f082 // indirect - github.com/getlantern/broflake v0.0.0-20230822184836-0b9bbcadd5c6 // indirect + github.com/getlantern/broflake v0.0.0-20230720180617-832a5e273f50 // indirect github.com/getlantern/bufconn v0.0.0-20210901195825-fd7c0267b493 // indirect github.com/getlantern/byteexec v0.0.0-20220903142956-e6ed20032cfd // indirect github.com/getlantern/cmux v0.0.0-20230301223233-dac79088a4c0 // indirect @@ -141,7 +142,7 @@ require ( github.com/getlantern/hex v0.0.0-20220104173244-ad7e4b9194dc // indirect github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 // indirect github.com/getlantern/http-proxy v0.0.3-0.20230405160101-eb4bf4e4a733 // indirect - github.com/getlantern/http-proxy-lantern/v2 v2.8.3-0.20230822194736-c6ce61180a3a // indirect + github.com/getlantern/http-proxy-lantern/v2 v2.8.2 // indirect github.com/getlantern/httpseverywhere v0.0.0-20201210200013-19ae11fc4eca // indirect github.com/getlantern/i18n v0.0.0-20181205222232-2afc4f49bb1c // indirect github.com/getlantern/iptool v0.0.0-20230112135223-c00e863b2696 // indirect @@ -165,6 +166,7 @@ require ( github.com/getlantern/proxy/v2 v2.0.1-0.20220303164029-b34b76e0e581 // indirect github.com/getlantern/proxybench v0.0.0-20220404140110-f49055cb86de // indirect github.com/getlantern/psmux v1.5.15-0.20200903210100-947ca5d91683 // indirect + github.com/getlantern/quicproxy v0.0.0-20220808081037-32e9be8ec447 // indirect github.com/getlantern/quicwrapper v0.0.0-20230523101504-1ec066b7f869 // indirect github.com/getlantern/ratelimit v0.0.0-20220926192648-933ab81a6fc7 // indirect github.com/getlantern/reconn v0.0.0-20161128113912-7053d017511c // indirect @@ -258,7 +260,7 @@ require ( github.com/quic-go/qtls-go1-19 v0.3.2 // indirect github.com/quic-go/qtls-go1-20 v0.2.2 // indirect github.com/quic-go/quic-go v0.34.0 // indirect - github.com/refraction-networking/utls v1.3.3 // indirect + github.com/refraction-networking/utls v1.0.0 // indirect github.com/rs/dnscache v0.0.0-20211102005908-e0241e321417 // indirect github.com/ryszard/goskiplist v0.0.0-20150312221310-2dfbae5fcf46 // indirect github.com/samber/lo v1.38.1 // indirect diff --git a/go.sum b/go.sum index 43766c184..8e8d1047e 100644 --- a/go.sum +++ b/go.sum @@ -1,4 +1,3 @@ -bazil.org/fuse v0.0.0-20180421153158-65cc252bf669/go.mod h1:Xbm+BRKSBEpa4q4hTSxohYNQpsxXPbPry4JJWOB3LB8= cloud.google.com/go v0.16.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= @@ -68,14 +67,13 @@ github.com/PuerkitoBio/goquery v1.8.1 h1:uQxhNlArOIdbrH1tr0UXwdVFgDcZDrZVdcpygAc github.com/PuerkitoBio/goquery v1.8.1/go.mod h1:Q8ICL1kNUJ2sXGoAhPGUdYDJvgQgHzJsnnd3H7Ho5jQ= github.com/RoaringBitmap/roaring v0.4.7/go.mod h1:8khRDP4HmeXns4xIj9oGrKSz7XTQiJx2zgh7AcNke4w= github.com/RoaringBitmap/roaring v0.4.17/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= -github.com/RoaringBitmap/roaring v0.4.18/go.mod h1:D3qVegWTmfCaX4Bl5CrBE9hfrSrrXIr8KVNvRsDi1NI= -github.com/RoaringBitmap/roaring v0.4.21/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v0.4.23/go.mod h1:D0gp8kJQgE1A4LQ5wFLggQEyvDi06Mq5mKs52e1TwOo= github.com/RoaringBitmap/roaring v1.2.3 h1:yqreLINqIrX22ErkKI0vY47/ivtJr6n+kMhVOVmhWBY= github.com/RoaringBitmap/roaring v1.2.3/go.mod h1:plvDsJQpxOC5bw8LRteu/MLWHsHez/3y6cubLI4/1yE= github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY= github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Workiva/go-datastructures v1.0.53/go.mod h1:1yZL+zfsztete+ePzZz/Zb1/t5BnDuE2Ya2MMGhzP6A= github.com/Yawning/chacha20 v0.0.0-20170904085104-e3b1f968fc63 h1:I6/SJSN9wJMJ+ZyQaCHUlzoTA4ypU5Bb44YWR1wTY/0= @@ -93,61 +91,35 @@ github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuy github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alexflint/go-arg v1.1.0/go.mod h1:3Rj4baqzWaGGmZA2+bVTV8zQOZEjBQAPBnL5xLT+ftY= -github.com/alexflint/go-arg v1.2.0/go.mod h1:3Rj4baqzWaGGmZA2+bVTV8zQOZEjBQAPBnL5xLT+ftY= -github.com/alexflint/go-scalar v1.0.0/go.mod h1:GpHzbCOZXEKMEcygYQ5n/aa4Aq84zbxjy3MxYW0gjYw= github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc= github.com/alicebob/miniredis v2.5.0+incompatible/go.mod h1:8HZjEj4yU0dwhYHky+DxYx+6BMjkBbe5ONFIF1MXffk= github.com/alicebob/miniredis/v2 v2.15.1/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I= github.com/anacrolix/chansync v0.3.0 h1:lRu9tbeuw3wl+PhMu/r+JJCRu5ArFXIluOgdF0ao6/U= github.com/anacrolix/chansync v0.3.0/go.mod h1:DZsatdsdXxD0WiwcGl0nJVwyjCKMDv+knl1q2iBjA2k= -github.com/anacrolix/confluence v1.3.0/go.mod h1:Hrkv5GfiOO2VqW1wV9b75Gf7ejfcJUzidgAIGqkIcxA= -github.com/anacrolix/confluence v1.11.1-0.20220330234942-3c7a0c445dad h1:KP7qkQCOxvmKKTnr/2hcb0J6/VZ7aUT/9YArZGizeMM= -github.com/anacrolix/confluence v1.11.1-0.20220330234942-3c7a0c445dad/go.mod h1:f4JSWzsXnqHaz/lDXbHRaisy/cUsITruVOgaWhAlsAM= -github.com/anacrolix/dht v0.0.0-20180412060941-24cbf25b72a4/go.mod h1:hQfX2BrtuQsLQMYQwsypFAab/GvHg8qxwVi4OJdR1WI= -github.com/anacrolix/dht v0.0.0-20181129074040-b09db78595aa/go.mod h1:Ayu4t+5TsHQ07/P8XzRJqVofv7lU4R1ZTT7KW5+SPFA= -github.com/anacrolix/dht v1.0.1/go.mod h1:dtcIktBFD8YD/7ZcE5nQuuGGfLxcwa8+18mHl+GU+KA= -github.com/anacrolix/dht/v2 v2.0.1/go.mod h1:GbTT8BaEtfqab/LPd5tY41f3GvYeii3mmDUK300Ycyo= -github.com/anacrolix/dht/v2 v2.0.5-0.20190912223956-bfe5b201d6f7/go.mod h1:IGN/b4wWgoHJtpfInVDLLxvZfWRCdPtDFOdz2ftQ6wE= -github.com/anacrolix/dht/v2 v2.0.5-0.20190913023154-c5780a290ed6/go.mod h1:PEf0ghmOZEfCg4HOSNPdF0XUmGpFm/P4SHY6HYJrQIo= -github.com/anacrolix/dht/v2 v2.2.1-0.20191103020011-1dba080fb358/go.mod h1:d7ARx3WpELh9uOEEr0+8wvQeVTOkPse4UU6dKpv4q0E= -github.com/anacrolix/dht/v2 v2.3.2-0.20200103043204-8dce00767ebd/go.mod h1:cgjKyErDnKS6Mej5D1fEqBKg3KwFF2kpFZJp3L6/fGI= -github.com/anacrolix/dht/v2 v2.5.1/go.mod h1:7RLvyOjm+ZPA7vgFRP+1eRjFzrh27p/nF0VCk5LcjoU= +github.com/anacrolix/confluence v1.13.0 h1:6LQeTShK77CvJea8xOEuYNNGpqcF4i2l3wtVbuVgRw8= +github.com/anacrolix/confluence v1.13.0/go.mod h1:UvknPHUeZNryWHnBhnR+Eqiogijc/eYDm3kEQfYTWGc= github.com/anacrolix/dht/v2 v2.20.0 h1:eDx9lfE9iCSf5sPK0290GToHURNhEFuUGN8iyvhvJDk= github.com/anacrolix/dht/v2 v2.20.0/go.mod h1:SDGC+sEs1pnO2sJGYuhvIis7T8749dDHNfcjtdH4e3g= github.com/anacrolix/envpprof v0.0.0-20180404065416-323002cec2fa/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= github.com/anacrolix/envpprof v1.0.0/go.mod h1:KgHhUaQMc8cC0+cEflSgCFNFbKwi5h54gqtVn8yhP7c= -github.com/anacrolix/envpprof v1.0.1/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= github.com/anacrolix/envpprof v1.1.0/go.mod h1:My7T5oSqVfEn4MD4Meczkw/f5lSIndGAKu/0SM/rkf4= github.com/anacrolix/envpprof v1.3.0 h1:WJt9bpuT7A/CDCxPOv/eeZqHWlle/Y0keJUvc6tcJDk= github.com/anacrolix/envpprof v1.3.0/go.mod h1:7QIG4CaX1uexQ3tqd5+BRa/9e2D02Wcertl6Yh0jCB0= github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68 h1:fyXlBfnlFzZSFckJ8QLb2lfmWfY++4RiUnae7ZMuv0A= github.com/anacrolix/generics v0.0.0-20230428105757-683593396d68/go.mod h1:ff2rHB/joTV03aMSSn/AZNnaIpUw0h3njetGsaXcMy8= -github.com/anacrolix/go-libutp v0.0.0-20180522111405-6baeb806518d/go.mod h1:beQSaSxwH2d9Eeu5ijrEnHei5Qhk+J6cDm1QkWFru4E= -github.com/anacrolix/go-libutp v0.0.0-20180808010927-aebbeb60ea05/go.mod h1:POY/GPlrFKRxnOKH1sGAB+NBWMoP+sI+hHJxgcgWbWw= -github.com/anacrolix/go-libutp v1.0.2/go.mod h1:uIH0A72V++j0D1nnmTjjZUiH/ujPkFxYWkxQ02+7S0U= github.com/anacrolix/go-libutp v1.2.0 h1:sjxoB+/ARiKUR7IK/6wLWyADIBqGmu1fm0xo+8Yy7u0= github.com/anacrolix/go-libutp v1.2.0/go.mod h1:RrJ3KcaDcf9Jqp33YL5V/5CBEc6xMc7aJL8wXfuWL50= -github.com/anacrolix/log v0.0.0-20180412014343-2323884b361d/go.mod h1:sf/7c2aTldL6sRQj/4UKyjgVZBu2+M2z9wf7MmwPiew= -github.com/anacrolix/log v0.1.0/go.mod h1:sf/7c2aTldL6sRQj/4UKyjgVZBu2+M2z9wf7MmwPiew= -github.com/anacrolix/log v0.2.0/go.mod h1:sf/7c2aTldL6sRQj/4UKyjgVZBu2+M2z9wf7MmwPiew= github.com/anacrolix/log v0.3.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= -github.com/anacrolix/log v0.3.1-0.20190913000754-831e4ffe0174/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= -github.com/anacrolix/log v0.3.1-0.20191001111012-13cede988bcd/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= -github.com/anacrolix/log v0.5.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= github.com/anacrolix/log v0.6.0/go.mod h1:lWvLTqzAnCWPJA08T2HCstZi0L1y2Wyvm3FJgwU9jwU= github.com/anacrolix/log v0.10.0/go.mod h1:s5yBP/j046fm9odtUTbHOfDUq/zh1W8OkPpJtnX0oQI= github.com/anacrolix/log v0.10.1-0.20220123034749-3920702c17f8/go.mod h1:GmnE2c0nvz8pOIPUSC9Rawgefy1sDXqposC2wgtBZE4= github.com/anacrolix/log v0.13.1/go.mod h1:D4+CvN8SnruK6zIFS/xPoRJmtvtnxs+CSfDQ+BFxZ68= -github.com/anacrolix/log v0.14.0 h1:mYhTSemILe/Z8tIxbGdTIWWpPspI8W/fhZHpoFbDaL0= -github.com/anacrolix/log v0.14.0/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY= +github.com/anacrolix/log v0.13.2-0.20230518105052-6aef2c4c91f1 h1:Yo4XQhmdmrkB4RGP7RWvl8U+og2rCBsNqoJFTew0plk= +github.com/anacrolix/log v0.13.2-0.20230518105052-6aef2c4c91f1/go.mod h1:1OmJESOtxQGNMlUO5rcv96Vpp9mfMqXXbe2RdinFLdY= github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62 h1:P04VG6Td13FHMgS5ZBcJX23NPC/fiC4cp9bXwYujdYM= github.com/anacrolix/lsan v0.0.0-20211126052245-807000409a62/go.mod h1:66cFKPCO7Sl4vbFnAaSq7e4OXtdMhRSBagJGWgmpJbM= -github.com/anacrolix/missinggo v0.0.0-20180522035225-b4a5853e62ff/go.mod h1:b0p+7cn+rWMIphK1gDH2hrDuwGOcbB6V4VXeSsEfHVk= github.com/anacrolix/missinggo v0.0.0-20180725070939-60ef2fbf63df/go.mod h1:kwGiTUTZ0+p4vAz3VbAI5a30t2YbvemcmspjKwrAz5s= -github.com/anacrolix/missinggo v0.0.0-20181129073415-3237bf955fed/go.mod h1:IN+9GUe7OxKMIs/XeXEbT/rMUolmJzmlZiXHS7FwD/Y= -github.com/anacrolix/missinggo v0.2.1-0.20190310234110-9fbdc9f242a8/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= github.com/anacrolix/missinggo v1.1.0/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= github.com/anacrolix/missinggo v1.1.2-0.20190815015349-b888af804467/go.mod h1:MBJu3Sk/k3ZfGYcS7z18gwfu72Ey/xopPFJJbTi5yIo= github.com/anacrolix/missinggo v1.2.1/go.mod h1:J5cMhif8jPmFoC3+Uvob3OXXNIhOUikzMt+uUjeM21Y= @@ -155,57 +127,36 @@ github.com/anacrolix/missinggo v1.3.0 h1:06HlMsudotL7BAELRZs0yDZ4yVXsHXGi323QBjA github.com/anacrolix/missinggo v1.3.0/go.mod h1:bqHm8cE8xr+15uVfMG3BFui/TxyB6//H5fwlq/TeqMc= github.com/anacrolix/missinggo/perf v1.0.0 h1:7ZOGYziGEBytW49+KmYGTaNfnwUqP1HBsy6BqESAJVw= github.com/anacrolix/missinggo/perf v1.0.0/go.mod h1:ljAFWkBuzkO12MQclXzZrosP5urunoLS0Cbvb4V0uMQ= -github.com/anacrolix/missinggo/v2 v2.1.0/go.mod h1:E0m6VFKLpwm6Os+yzbQKYgPFEKJahwFldvJ+4L+BAso= github.com/anacrolix/missinggo/v2 v2.2.0/go.mod h1:o0jgJoYOyaoYQ4E2ZMISVa9c88BbUBVQQW4QeRkNCGY= -github.com/anacrolix/missinggo/v2 v2.2.1-0.20191103010835-12360f38ced0/go.mod h1:ZzG3/cc3t+5zcYWAgYrJW0MBsSwNwOkTlNquBbP51Bc= -github.com/anacrolix/missinggo/v2 v2.3.0/go.mod h1:ZzG3/cc3t+5zcYWAgYrJW0MBsSwNwOkTlNquBbP51Bc= -github.com/anacrolix/missinggo/v2 v2.3.1/go.mod h1:3XNH0OEmyMUZuvXmYdl+FDfXd0vvSZhvOLy8CFx8tLg= github.com/anacrolix/missinggo/v2 v2.5.1/go.mod h1:WEjqh2rmKECd0t1VhQkLGTdIWXO6f6NLjp5GlMZ+6FA= github.com/anacrolix/missinggo/v2 v2.5.2/go.mod h1:yNvsLrtZYRYCOI+KRH/JM8TodHjtIE/bjOGhQaLOWIE= -github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9 h1:W/oGeHhYwxueeiDjQfmK9G+X9M2xJgfTtow62v0TWAs= -github.com/anacrolix/missinggo/v2 v2.7.2-0.20230527121029-a582b4f397b9/go.mod h1:mIEtp9pgaXqt8VQ3NQxFOod/eQ1H0D1XsZzKUQfwtac= +github.com/anacrolix/missinggo/v2 v2.7.1 h1:Y+wL0JC6D2icpwhDpcrRM4THQB/uFcPNYUtZMbYvQgI= +github.com/anacrolix/missinggo/v2 v2.7.1/go.mod h1:2IZIvmRTizALNYFYXsPR7ofXPzJgyBpKZ4kMqMEICkI= github.com/anacrolix/mmsg v0.0.0-20180515031531-a4a3ba1fc8bb/go.mod h1:x2/ErsYUmT77kezS63+wzZp8E3byYB0gzirM/WMBLfw= github.com/anacrolix/mmsg v1.0.0 h1:btC7YLjOn29aTUAExJiVUhQOuf/8rhm+/nWCMAnL3Hg= github.com/anacrolix/mmsg v1.0.0/go.mod h1:x8kRaJY/dCrY9Al0PEcj1mb/uFHwP6GCJ9fLl4thEPc= -github.com/anacrolix/multiless v0.0.0-20191223025854-070b7994e841/go.mod h1:TrCLEZfIDbMVfLoQt5tOoiBS/uq4y8+ojuEVVvTNPX4= github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7 h1:lOtCD+LzoD1g7bowhYJNR++uV+FyY5bTZXKwnPex9S8= github.com/anacrolix/multiless v0.3.1-0.20221221005021-2d12701f83f7/go.mod h1:zJv1JF9AqdZiHwxqPgjuOZDGWER6nyE48WBCi/OOrMM= -github.com/anacrolix/squirrel v0.4.1-0.20230623120945-75cf0ad9a958 h1:A+tNxHKFCGj/CP8WaQDZC+QwDjjoXUHDByIzKVyzKw4= -github.com/anacrolix/squirrel v0.4.1-0.20230623120945-75cf0ad9a958/go.mod h1:YzgVvikMdFD441oTWlNG189bpKabO9Sbf3uCSVgca04= -github.com/anacrolix/stm v0.1.0/go.mod h1:ZKz7e7ERWvP0KgL7WXfRjBXHNRhlVRlbBQecqFtPq+A= -github.com/anacrolix/stm v0.1.1-0.20191106051447-e749ba3531cf/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= +github.com/anacrolix/squirrel v0.4.1-0.20220903122644-f24aa78cd96c h1:51HwlAaR35gkkBWi9GqFanBXaEhag+TT+coW8Us1p8g= +github.com/anacrolix/squirrel v0.4.1-0.20220903122644-f24aa78cd96c/go.mod h1:YzgVvikMdFD441oTWlNG189bpKabO9Sbf3uCSVgca04= github.com/anacrolix/stm v0.2.0/go.mod h1:zoVQRvSiGjGoTmbM0vSLIiaKjWtNPeTvXUSdJQA4hsg= github.com/anacrolix/stm v0.5.0 h1:9df1KBpttF0TzLgDq51Z+TEabZKMythqgx89f1FQJt8= github.com/anacrolix/stm v0.5.0/go.mod h1:MOwrSy+jCm8Y7HYfMAwPj7qWVu7XoVvjOiYwJmpeB/M= -github.com/anacrolix/sync v0.0.0-20171108081538-eee974e4f8c1/go.mod h1:+u91KiUuf0lyILI6x3n/XrW7iFROCZCG+TjgK8nW52w= -github.com/anacrolix/sync v0.0.0-20180611022320-3c4cb11f5a01/go.mod h1:+u91KiUuf0lyILI6x3n/XrW7iFROCZCG+TjgK8nW52w= github.com/anacrolix/sync v0.0.0-20180808010631-44578de4e778/go.mod h1:s735Etp3joe/voe2sdaXLcqDdJSay1O0OPnM0ystjqk= -github.com/anacrolix/sync v0.2.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g= github.com/anacrolix/sync v0.3.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g= github.com/anacrolix/sync v0.4.0 h1:T+MdO/u87ir/ijWsTFsPYw5jVm0SMm4kVpg8t4KF38o= github.com/anacrolix/sync v0.4.0/go.mod h1:BbecHL6jDSExojhNtgTFSBcdGerzNc64tz3DCOj/I0g= github.com/anacrolix/tagflag v0.0.0-20180109131632-2146c8d41bf0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= -github.com/anacrolix/tagflag v0.0.0-20180605133421-f477c8c2f14c/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= -github.com/anacrolix/tagflag v0.0.0-20180803105420-3a8ff5428f76/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= github.com/anacrolix/tagflag v1.0.0/go.mod h1:1m2U/K6ZT+JZG0+bdMK6qauP49QT4wE5pmhJXOKKCHw= -github.com/anacrolix/tagflag v1.0.1/go.mod h1:gb0fiMQ02qU1djCSqaxGmruMvZGrMwSReidMB0zjdxo= github.com/anacrolix/tagflag v1.1.0/go.mod h1:Scxs9CV10NQatSmbyjqmqmeQNwGzlNe0CMUMIxqHIG8= -github.com/anacrolix/torrent v0.0.0-20180622074351-fefeef4ee9eb/go.mod h1:3vcFVxgOASslNXHdivT8spyMRBanMCenHRpe0u5vpBs= -github.com/anacrolix/torrent v1.0.1/go.mod h1:ZYV1Z2Wx3jXYSh26mDvneAbk8XIUxfvoVil2GW962zY= -github.com/anacrolix/torrent v1.5.2/go.mod h1:UMNOAVR1kVijr+7ZiAbL8b9CUdA63BC0Ymbm/zOxbRM= -github.com/anacrolix/torrent v1.7.1/go.mod h1:uvOcdpOjjrAq3uMP/u1Ide35f6MJ/o8kMnFG8LV3y6g= -github.com/anacrolix/torrent v1.7.2-0.20190913015502-16a03ed4d474/go.mod h1:r6cVclT+Eskf2J8hWbj4HB9VifxjT/5MKTCSee5tv3c= -github.com/anacrolix/torrent v1.9.0/go.mod h1:jJJ6lsd2LD1eLHkUwFOhy7I0FcLYH0tHKw2K7ZYMHCs= -github.com/anacrolix/torrent v1.11.0/go.mod h1:FwBai7SyOFlflvfEOaM88ag/jjcBWxTOqD6dVU/lKKA= -github.com/anacrolix/torrent v1.52.1 h1:y5MW2EkXgkVyWbmoYFj2qVpy/3TchYpCEEulxrV45cE= -github.com/anacrolix/torrent v1.52.1/go.mod h1:3FzugiL498Wp8yMm064ZjARChHXAtlgk36/TrkYhzio= -github.com/anacrolix/upnp v0.1.1/go.mod h1:LXsbsp5h+WGN7YR+0A7iVXm5BL1LYryDev1zuJMWYQo= +github.com/anacrolix/torrent v1.51.2 h1:7UWUElt7rCVZlFF/VMaBOoZfpNtaMDj1zfvgsoX17LA= +github.com/anacrolix/torrent v1.51.2/go.mod h1:yCbMnw1IpltDstasqpZploAMnJrkXNjZFjo7XdDYUDY= github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96 h1:QAVZ3pN/J4/UziniAhJR2OZ9Ox5kOY2053tBbbqUPYA= github.com/anacrolix/upnp v0.1.3-0.20220123035249-922794e51c96/go.mod h1:Wa6n8cYIdaG35x15aH3Zy6d03f7P728QfdcDeD/IEOs= -github.com/anacrolix/utp v0.0.0-20180219060659-9e0e1d1d0572/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk= github.com/anacrolix/utp v0.1.0 h1:FOpQOmIwYsnENnz7tAGohA+r6iXpRjrq8ssKSre2Cp4= github.com/anacrolix/utp v0.1.0/go.mod h1:MDwc+vsGEq7RMw6lr2GKOEqjWny5hO5OZXRVNaBJ2Dk= github.com/andybalholm/brotli v1.0.1/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= +github.com/andybalholm/brotli v1.0.4/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/brotli v1.0.5 h1:8uQZIdzKmjc/iuPu7O2ioW48L81FgatrcpfFmiq/cCs= github.com/andybalholm/brotli v1.0.5/go.mod h1:fO7iG3H7G2nSZ7m0zPUDn85XEX2GTukHGRSepvi9Eig= github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA= @@ -222,7 +173,6 @@ github.com/aristanetworks/goarista v0.0.0-20190628000427-15fc8b0bfcde/go.mod h1: github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aws/aws-sdk-go v1.21.2/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.30.19/go.mod h1:5zCpMtNQVjRREroY7sYe8lOMRSxkhG6MZveU8YkpAk0= github.com/aws/aws-sdk-go-v2 v1.7.1/go.mod h1:L5LuPC1ZgDr2xQS7AmIec/Jlc7O/Y1u2KxJyNVab250= github.com/aws/aws-sdk-go-v2/config v1.5.0/go.mod h1:RWlPOAW3E3tbtNAqTwvSW54Of/yP3oiZXMI0xfUdjyA= @@ -333,7 +283,7 @@ github.com/edsrzf/mmap-go v0.0.0-20170320065105-0bce6a688712/go.mod h1:YO35OhQPt github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/edsrzf/mmap-go v1.1.0 h1:6EUwBLQ/Mcr1EYLE4Tn1VdW1A4ckqCQWZBw8Hr0kjpQ= github.com/edsrzf/mmap-go v1.1.0/go.mod h1:19H/e8pUPLicwkyNgOykDXkJ9F0MHE+Z52B8EIth78Q= -github.com/elgatito/upnp v0.0.0-20180711183757-2f244d205f9a/go.mod h1:afkYpY8JAIL4341N7Zj9xJ5yTovsg6BkWfBFlCzIoF4= +github.com/elazarl/goproxy/ext v0.0.0-20190711103511-473e67f1d7d2 h1:dWB6v3RcOy03t/bUadywsbyrQwCqZeNIEX6M1OtSZOM= github.com/enobufs/go-nats v0.0.1 h1:uzC0mxan4hyGzUFG7cShFmk6c+XYgfoT8yTBgF5CJYw= github.com/enobufs/go-nats v0.0.1/go.mod h1:ZF0vpSk02ALIMFsHkIO4MHXUN1v3nLZssTaG+fgX/io= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -343,7 +293,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ= github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHjkjCrw= github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8= github.com/felixge/httpsnoop v1.0.3 h1:s/nj+GCswXYzN5v2DpNMuMQYe+0DDwt5WVCU6CWBdXk= github.com/felixge/httpsnoop v1.0.3/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= @@ -356,8 +305,6 @@ github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/garyburd/redigo v1.1.1-0.20170914051019-70e1b1943d4f/go.mod h1:NR3MbYisc3/PwhQ00EMzDiPmrwpPxAn5GI05/YaO1SY= -github.com/gaukas/godicttls v0.0.3 h1:YNDIf0d9adcxOijiLrEzpfZGAkNwLRzPaG6OjU7EITk= -github.com/gaukas/godicttls v0.0.3/go.mod h1:l6EenT4TLWgTdwslVb4sEMOCf7Bv0JAK67deKr9/NCI= github.com/getlantern/appdir v0.0.0-20160830121117-659a155d06e8/go.mod h1:3vR6+jQdWfWojZ77w+htCqEF5MO/Y2twJOpAvFuM9po= github.com/getlantern/appdir v0.0.0-20180320102544-7c0f9d241ea7/go.mod h1:3vR6+jQdWfWojZ77w+htCqEF5MO/Y2twJOpAvFuM9po= github.com/getlantern/appdir v0.0.0-20200615192800-a0ef1968f4da h1:T/pxF37Z9SIQCHhMMUITZ3rhKRL0Noi9XxNwxKdBNw0= @@ -366,8 +313,10 @@ github.com/getlantern/autoupdate v0.0.0-20211217175350-d0b211f39ba7 h1:/efTOJpxX github.com/getlantern/autoupdate v0.0.0-20211217175350-d0b211f39ba7/go.mod h1:+X8pAviVhThDBjPEqLUB0iO7EPxhpWk7Q9VYxvz6rCY= github.com/getlantern/borda v0.0.0-20230421223744-4e208135f082 h1:Ka9rIAgef8zYhBr/VgLrt5+Qs7zE33g0OButzpIGcSs= github.com/getlantern/borda v0.0.0-20230421223744-4e208135f082/go.mod h1:oCpQojhSaK0F/6rWMrDvN8/QFHQhTC9Gb3uf7GcqPQQ= -github.com/getlantern/broflake v0.0.0-20230822184836-0b9bbcadd5c6 h1:/G1jb7daSg1FkEEFbvRRM7A927uErx5vEMVwrTcVzp0= -github.com/getlantern/broflake v0.0.0-20230822184836-0b9bbcadd5c6/go.mod h1:Ehdl8IASN5rJi9brldVuCjTDcSU25nvaGRlzNprgeQo= +github.com/getlantern/broflake v0.0.0-20230719204503-7ec419291413 h1:JHbJhR/tjOPYA62g/7/p86owA23nnv+A4siaR2YUKqs= +github.com/getlantern/broflake v0.0.0-20230719204503-7ec419291413/go.mod h1:Dx1qAwekJdF3IfDICzBFJ+fER8F0tU2g9pkFjGSf7qU= +github.com/getlantern/broflake v0.0.0-20230720180617-832a5e273f50 h1:d/kGRNSTVHZDYzdPHPLzU3YhsyM4z027d77zF6WLjsE= +github.com/getlantern/broflake v0.0.0-20230720180617-832a5e273f50/go.mod h1:Ehdl8IASN5rJi9brldVuCjTDcSU25nvaGRlzNprgeQo= github.com/getlantern/bufconn v0.0.0-20190625204133-a08544339f8d/go.mod h1:d6O4RY+V87kIt4o9wru4SaNo7C2NAkD3YnmJFXEpODo= github.com/getlantern/bufconn v0.0.0-20210901195825-fd7c0267b493 h1:8WjDNmpDLFVsAfcnHxqF4pfVKkdAQxyJ9iCHB4LxSfc= github.com/getlantern/bufconn v0.0.0-20210901195825-fd7c0267b493/go.mod h1:d6O4RY+V87kIt4o9wru4SaNo7C2NAkD3YnmJFXEpODo= @@ -427,8 +376,12 @@ github.com/getlantern/fdcount v0.0.0-20210503151800-5decd65b3731/go.mod h1:XZwE+ github.com/getlantern/filepersist v0.0.0-20160317154340-c5f0cd24e799/go.mod h1:8DGAx0LNUfXNnEH+fXI0s3OCBA/351kZCiz/8YSK3i8= github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c h1:mcz27xtAkb1OuOLBct/uFfL1p3XxAIcFct82GbT+UZM= github.com/getlantern/filepersist v0.0.0-20210901195658-ed29a1cb0b7c/go.mod h1:8DGAx0LNUfXNnEH+fXI0s3OCBA/351kZCiz/8YSK3i8= -github.com/getlantern/flashlight/v7 v7.5.35 h1:i/nY+gzLjPTGe8dAPMxaNeavS2mnYzwjGUo+t51P5gU= -github.com/getlantern/flashlight/v7 v7.5.35/go.mod h1:TZUkRM5WEuallvQ9B1N6WxFrD97CnITNsySoX5ivVaU= +github.com/getlantern/flashlight/v7 v7.5.24 h1:GdVnKHPVMFLW+GVws6IVJ67QAx1/Vgh/DVZ4fxgSPQI= +github.com/getlantern/flashlight/v7 v7.5.24/go.mod h1:mmsynGzxevdaRynOOCnzWdIkEqYFwxL9vnPBgA1tjkM= +github.com/getlantern/flashlight/v7 v7.5.25 h1:K5HRS/qLeWBmN22grzKrgpkJOFYIaCB/sDuRshAnd6g= +github.com/getlantern/flashlight/v7 v7.5.25/go.mod h1:xRXjVhT4Ij9Dszh2DrpQEZ1RNWDee7+uv8uSUdjpnB8= +github.com/getlantern/flashlight/v7 v7.5.28 h1:9pO7sb4igfEkpI5+ISuwc9lQ+0Hr8QSTnoWadi8MEV8= +github.com/getlantern/flashlight/v7 v7.5.28/go.mod h1:xRXjVhT4Ij9Dszh2DrpQEZ1RNWDee7+uv8uSUdjpnB8= github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede h1:yrU6Px3ZkvCsDLPryPGi6FN+2iqFPq+JeCb7EFoDBhw= github.com/getlantern/framed v0.0.0-20190601192238-ceb6431eeede/go.mod h1:nhnoiS6DE6zfe+BaCMU4YI01UpsuiXnDqM5S8jxHuuI= github.com/getlantern/fronted v0.0.0-20230601004823-7fec719639d8 h1:r/Z/SPPIfLXDI3QA7/tE6nOfPncrqeUPDjiFjnNGP50= @@ -453,6 +406,8 @@ github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65 h1:NlQedYmPI3pRAX github.com/getlantern/golog v0.0.0-20230503153817-8e72de7e0a65/go.mod h1:+ZU1h+iOVqWReBpky6d5Y2WL0sF2Llxu+QcxJFs2+OU= github.com/getlantern/gonat v0.0.0-20201001145726-634575ba87fb h1:tDQA66mL1vTHKSMu3Ras/9Tk884ipPAhcdQHXpnDhxg= github.com/getlantern/gonat v0.0.0-20201001145726-634575ba87fb/go.mod h1:ysiamkJHyOrnlNmtDCCccH1NbFdgEBSJRg44DWiOxcY= +github.com/getlantern/goproxy v0.0.0-20220805074304-4a43a9ed4ec6 h1:/jwJjghYGhLhpPYmO4IyHLG+VMVVZaIdgp8oe1dSx6E= +github.com/getlantern/goproxy v0.0.0-20220805074304-4a43a9ed4ec6/go.mod h1:96OPoioYRaknNbHjFa4+itGZIJMnJ7wiQB2nz2q1h5Y= github.com/getlantern/gotun v0.0.0-20190809092752-6d35bb1397ee/go.mod h1:zvsZQrsl7Yrmi+ENk5WZFT7dQaYtihAcI0H/9+LacqQ= github.com/getlantern/grtrack v0.0.0-20160824195228-cbf67d3fa0fd/go.mod h1:RkQEgBdrJCH5tYJP2D+a/aJ216V3c9q8w/tCJtEiDoY= github.com/getlantern/grtrack v0.0.0-20210901195719-bdf9e1d12dac h1:WsJhOWm1hJEAqts1OAhEPctQpy7Y0Eiu05mV84ixekc= @@ -468,8 +423,8 @@ github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770 h1:cSrD9ryDfTV2y github.com/getlantern/hidden v0.0.0-20220104173330-f221c5a24770/go.mod h1:GOQsoDnEHl6ZmNIL+5uVo+JWRFWozMEp18Izcb++H+A= github.com/getlantern/http-proxy v0.0.3-0.20230405160101-eb4bf4e4a733 h1:bAVwkX1pvHqqrxuoZ2ElGr2FElnjE/+yOpqUSOrVnJg= github.com/getlantern/http-proxy v0.0.3-0.20230405160101-eb4bf4e4a733/go.mod h1:zsMtNKXEfy+y3RBqe3wcd05VivKAswrIvlVsFrj7Fwk= -github.com/getlantern/http-proxy-lantern/v2 v2.8.3-0.20230822194736-c6ce61180a3a h1:nR1zM2FsFZF4P4hRuAosn9ob0LnkiYyM3SHgPFI5C6Q= -github.com/getlantern/http-proxy-lantern/v2 v2.8.3-0.20230822194736-c6ce61180a3a/go.mod h1:Ghj3WWaSmNBeDbSElm/Zsnb/xUiZjGDTAN9h2javcHE= +github.com/getlantern/http-proxy-lantern/v2 v2.8.2 h1:kET59ivzejmIODw1m/6J1f+MS/pesxYlyaiUaJbnXZc= +github.com/getlantern/http-proxy-lantern/v2 v2.8.2/go.mod h1:QTlMxJhd74PZqtDmZ4g99EYSWBVrWu5d/LBWV/mKRGE= github.com/getlantern/httpseverywhere v0.0.0-20201210200013-19ae11fc4eca h1:Of3VwFEfKbVnK5/VGy05XUbi6QvTs5Y2eLDfPv3O50E= github.com/getlantern/httpseverywhere v0.0.0-20201210200013-19ae11fc4eca/go.mod h1:TNC/xJFmctsSGyXqcnVWwCRCPD/4zGQP7yBVnLDRa/U= github.com/getlantern/i18n v0.0.0-20181205222232-2afc4f49bb1c h1:+JnT+Rwa/3rksc4Zi0u6fJ/WX+tPK58GtsrcXWVUU2U= @@ -498,13 +453,15 @@ github.com/getlantern/keyman v0.0.0-20230503155501-4e864ca2175b h1:iyEuk8ARQC9Hf github.com/getlantern/keyman v0.0.0-20230503155501-4e864ca2175b/go.mod h1:ZJ+yDaZkJ/JU9j7EQa3UUh6ouedrNDDLA5OiowS1Iuk= github.com/getlantern/lampshade v0.0.0-20201109225444-b06082e15f3a h1:z7G1v79GB1qRrkcbzF0nrLzV/+dwdGmamEZAp0ff+z0= github.com/getlantern/lampshade v0.0.0-20201109225444-b06082e15f3a/go.mod h1:cGOfTjvllC9bcwS7cVW6tGT6fXc8Dki384uFjm7XBnw= -github.com/getlantern/lantern-shadowsocks v1.3.6-0.20230114153732-0193919d4860 h1:eMJ05nKDxgglPGWvAqcs4e23RRB3NoofVCtD0DDOV/Q= -github.com/getlantern/lantern-shadowsocks v1.3.6-0.20230114153732-0193919d4860/go.mod h1:YCEW57ujbWaUeGRMhRjF0OgMBqUTNb3QsErbaQZq5K8= +github.com/getlantern/lantern-shadowsocks v1.3.6-0.20230301223223-150b18ac427d h1:YwH3hgY1qtp1J1V8iBx58wB+mAY6L7N1s+qYqNJgDjM= +github.com/getlantern/lantern-shadowsocks v1.3.6-0.20230301223223-150b18ac427d/go.mod h1:Wwa1uDdu6LxVRANcN2dQ+aNI0rY+km+dqHW2G9Qm34k= github.com/getlantern/mandrill v0.0.0-20221004112352-e7c04248adcb h1:oyEMOT9jn4bzKyivF2sVBogsXyL8fBCK7HIT/P6h64Y= github.com/getlantern/mandrill v0.0.0-20221004112352-e7c04248adcb/go.mod h1:gz4iIB+vPk8hWxkAnnZSudQuIpBMnW7i89eHl9Fl+I8= github.com/getlantern/measured v0.0.0-20170302221919-0582bf799783/go.mod h1:5OW2WJitCKExpSw2bploW2fM7PjOd6QnLqyp+IqToqU= github.com/getlantern/measured v0.0.0-20210507000559-ec5307b2b8be h1:rfdOTeKew6zcpf5BQ566WInLINdZARimtWVLcgP/a4I= github.com/getlantern/measured v0.0.0-20210507000559-ec5307b2b8be/go.mod h1:QG6d9+nAxD1PjVjgGLUUHPZBQUp20/h7j8a3kxd/8Rc= +github.com/getlantern/memhelper v0.0.0-20181113170838-777ea7552231 h1:E8kVrX/zRmoiNf1U4SKgtKBLzLOEQRT0V/U/T+fJwxM= +github.com/getlantern/memhelper v0.0.0-20181113170838-777ea7552231/go.mod h1:bcE9B93SzvnTI0AJM+138sfvio7WK9e0NTAI8ba0l5Q= github.com/getlantern/meta-scrubber v0.0.1 h1:WknyffbSpb5kEwDQ6lrN9+KohCGnqhs5zQfGk4sFRGo= github.com/getlantern/meta-scrubber v0.0.1/go.mod h1:nYmOMQXbex3emWMNbt/iDKTXE1q53x1dDxCspxjwxyY= github.com/getlantern/mitm v0.0.0-20180205214248-4ce456bae650/go.mod h1:jmIVbVxSVLdeY5hlD+6chiOR/9CdzPjVgIIQphviCl0= @@ -539,10 +496,6 @@ github.com/getlantern/osversion v0.0.0-20230401075644-c2a30e73c451 h1:3Nn0AqIlIm github.com/getlantern/osversion v0.0.0-20230401075644-c2a30e73c451/go.mod h1:kaUdXyKE1Y8bwPnlN7ChFXWnkADpL0zZrk8F0XbpKcc= github.com/getlantern/packetforward v0.0.0-20201001150407-c68a447b0360 h1:pijUoofaQcAM/8zbDzZM2LQ90kGVbKfnSAkFnQwLZZU= github.com/getlantern/packetforward v0.0.0-20201001150407-c68a447b0360/go.mod h1:nsJPNYUSY96xB+p7uiDW8O4uiKea+KjeUdS5d6tf9IU= -github.com/getlantern/pathdb v0.0.0-20230816122749-de77b6080f2a h1:byVmEhK8AMSTXzD43meAc04muqTG0CUsX8GUN7A4h74= -github.com/getlantern/pathdb v0.0.0-20230816122749-de77b6080f2a/go.mod h1:SFQy+f58IbLpnbq2nVqlq7ccwaUiO7ablKv631WVIuc= -github.com/getlantern/pathdb v0.0.0-20230824172245-c389d5ee88da h1:hnangXrG/rPASfFUDEBGhB1SrYMAHphXG6f3YYN23fQ= -github.com/getlantern/pathdb v0.0.0-20230824172245-c389d5ee88da/go.mod h1:SFQy+f58IbLpnbq2nVqlq7ccwaUiO7ablKv631WVIuc= github.com/getlantern/preconn v0.0.0-20180328114929-0b5766010efe/go.mod h1:FvIxQD61iYA42UjaJyzGl9DNne8jbowbgicdeNk/7kE= github.com/getlantern/preconn v1.0.0 h1:DsY3l/y/BJUj86WyaxXylbJnCC9QbKcc3D6js6rFL60= github.com/getlantern/preconn v1.0.0/go.mod h1:i/AnXvx715Fq7HgZLlmQlw3sGfEkku8BQT5hLHMK4+k= @@ -557,6 +510,10 @@ github.com/getlantern/proxybench v0.0.0-20220404140110-f49055cb86de h1:328hcuyQi github.com/getlantern/proxybench v0.0.0-20220404140110-f49055cb86de/go.mod h1:kF5QcNhyCA0tpZ+MLVdeJb0bZpgXyMjR/CnPtaltlFU= github.com/getlantern/psmux v1.5.15-0.20200903210100-947ca5d91683 h1:Asfm7ajzavuSZC+5b+tPwXwyvlw188NM9hKM7wNMNGg= github.com/getlantern/psmux v1.5.15-0.20200903210100-947ca5d91683/go.mod h1:GtXRvtMItoflWGLPE7GNq+AdL7BnmpaaNLtDQVD1XHU= +github.com/getlantern/quic-go v0.31.1-0.20230104154904-d810c964a217 h1:UXxafrjMrl4j1d2/Ajjv91T2QHR1lsMnL8ofCYqNjNA= +github.com/getlantern/quic-go v0.31.1-0.20230104154904-d810c964a217/go.mod h1:0wFbizLgYzqHqtlyxyCaJKlE7bYgE6JQ+54TLd/Dq2g= +github.com/getlantern/quicproxy v0.0.0-20220808081037-32e9be8ec447 h1:4Fg+1ghYOJfoD4leM0n3ze+SAnpDeQ6EqXtIfm83dwQ= +github.com/getlantern/quicproxy v0.0.0-20220808081037-32e9be8ec447/go.mod h1:LOb27iGUxgV4WM7gZVUawhbMgoEOKI5iuR6ssav7Zik= github.com/getlantern/quicwrapper v0.0.0-20230523101504-1ec066b7f869 h1:nx0sr8jXoEBbI0jHDOHIkM0z5dXotQRUSsE/e7lUGXw= github.com/getlantern/quicwrapper v0.0.0-20230523101504-1ec066b7f869/go.mod h1:0k08ZBlQon93TrW6KmBLhLSz89qHQFR2LstGlIRgYo8= github.com/getlantern/ratelimit v0.0.0-20220926192648-933ab81a6fc7 h1:47FJ5kTeXc3I1VPpi2hWW9I16/Y3K0cpUq/B7oWJGF8= @@ -564,9 +521,8 @@ github.com/getlantern/ratelimit v0.0.0-20220926192648-933ab81a6fc7/go.mod h1:OOq github.com/getlantern/reconn v0.0.0-20161128113912-7053d017511c h1:IkjF+RwRs8B/RsuD638eUFO2K/227OO2B1FLXGp17Ro= github.com/getlantern/reconn v0.0.0-20161128113912-7053d017511c/go.mod h1:kExwbqTx1krUnT9ohmXG3jayDTEBfxUKeoRzU6XucLw= github.com/getlantern/redis-utils v0.0.0-20210823133122-d4f0e525e095/go.mod h1:1X9DhzvePQ89WjJRzY2DA5Xpgi7jI0i5E0rFjYxgPWI= -github.com/getlantern/replica v0.3.0/go.mod h1:0UeX9XPIDdh5dqEblICQXmy4tx8rX7BNr5+wNGjFAYw= -github.com/getlantern/replica v0.14.2 h1:Ajnpvr4n5QUwudqYbZaf3GamONmr6L44bVxVomoGbtU= -github.com/getlantern/replica v0.14.2/go.mod h1:nl+185Mu8FGrTosLNZ/9S1IDAxHdPDXnJQUJ7mAV37k= +github.com/getlantern/replica v0.14.1 h1:0RurcXcrZ474qjxAxZ7dUNp4fTt/ToAJwtqAfA3efG0= +github.com/getlantern/replica v0.14.1/go.mod h1:FgRgghWv+efecWfIvA7YknB+WLeJ0kWgWixIdGfrMx8= github.com/getlantern/rot13 v0.0.0-20220822172233-370767b2f782 h1:A1+qM0Dqm0no8A5qvWoTCFKME9Sa67xsJ1fcV13PvEY= github.com/getlantern/rot13 v0.0.0-20220822172233-370767b2f782/go.mod h1:O0dNqH9hbXlOa9OpVdbACmTBfDPD+ENjbY0cPkzBd9g= github.com/getlantern/rotator v0.0.0-20160829164113-013d4f8e36a2 h1:smFR/kESUKlcdyatoOO3HngBzzrUU6S0LRw2vCBYQPg= @@ -596,6 +552,8 @@ github.com/getlantern/tlsresumption v0.0.0-20211216020551-6a3f901d86b9/go.mod h1 github.com/getlantern/tlsutil v0.2.0/go.mod h1:Vxsyr9DVnYwsqHaEzMYkg9fT8aBrnO2eI+gdICMQbQU= github.com/getlantern/tlsutil v0.5.3 h1:g1FjuG4/OTZe8kkbEmpSxvT9rXzYOG9jO4jHiDeQIxM= github.com/getlantern/tlsutil v0.5.3/go.mod h1:lVgvr4nxuQ1ocOho90UB6LnHFlpP16TXAGpHR8Z0QnI= +github.com/getlantern/utls v0.0.0-20221011213556-17014cb6fc4a h1:A12K3qOcLk8kwp+NB7OonNskzaYA+tIXvTPuk0Gor6I= +github.com/getlantern/utls v0.0.0-20221011213556-17014cb6fc4a/go.mod h1:xleouNszTteqmcvv1JZiPuBY+MnTh/xV34mi0HYmiqk= github.com/getlantern/uuid v1.1.2-0.20190507182000-5c9436b8c718/go.mod h1:uX10hOzZUUDR+oYNSIks+RcozOEiwTNC/K2rw9SUi1k= github.com/getlantern/uuid v1.2.0 h1:pGrGaCV7XEaG6lvjWkwf8Y92BjB/9yFmkKsNFpRQ7rc= github.com/getlantern/uuid v1.2.0/go.mod h1:uX10hOzZUUDR+oYNSIks+RcozOEiwTNC/K2rw9SUi1k= @@ -642,6 +600,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= +github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -772,7 +732,6 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20181103185306-d547d1d9531e/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190309154008-847fc94819f9/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gopherjs/gopherjs v0.0.0-20190328170749-bb2674552d8f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gopherjs/gopherjs v0.0.0-20190910122728-9d188e94fb99/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= @@ -785,10 +744,6 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/gosuri/uilive v0.0.0-20170323041506-ac356e6e42cd/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8= -github.com/gosuri/uilive v0.0.3/go.mod h1:qkLSc0A5EXSP6B04TrN4oQoxqFI7A8XvoXSlJi8cwk8= -github.com/gosuri/uiprogress v0.0.0-20170224063937-d0567a9d84a1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= -github.com/gosuri/uiprogress v0.0.1/go.mod h1:C1RTYn4Sc7iEyf6j8ft5dyoZ4212h8G1ol9QQluh5+0= github.com/gregjones/httpcache v0.0.0-20170920190843-316c5e0ff04e/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= @@ -809,7 +764,6 @@ github.com/hexops/gotextdiff v1.0.3 h1:gitA9+qJrrTCsiCl7+kh75nPqQt1cx4ZkudSTLoUq github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/huandu/xstrings v1.0.0/go.mod h1:4qWG/gcEcfX4z/mBDHJ++3ReCw9ibxbsNJbcucJdbSo= github.com/huandu/xstrings v1.2.0/go.mod h1:DvyZB1rfVYsBIigL8HwpZgxHwXozlTgGqn63UyNX5k4= -github.com/huandu/xstrings v1.2.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.1/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= github.com/huandu/xstrings v1.3.2/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq4ovT0aE= @@ -818,14 +772,12 @@ github.com/huandu/xstrings v1.4.0/go.mod h1:y5/lhBue+AyNmUVz9RLU9xbLR0o4KIIExikq github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= github.com/inconshreveable/log15 v0.0.0-20170622235902-74a0988b5f80/go.mod h1:cOaXtrgN4ScfRrD9Bre7U1thNq5RtJ8ZoP4iXVGRj6o= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/ipfs/go-ipfs v0.4.18/go.mod h1:iXzbK+Wa6eePj3jQg/uY6Uoq5iOwY+GToD/bgaRadto= github.com/jaffee/commandeer v0.6.0 h1:YI44XLWcJN21euhh32sZW8vM/tljPYxhsXIfEPkQKcs= github.com/jaffee/commandeer v0.6.0/go.mod h1:kCwfuSvZ2T0NVEr3LDSo6fDUgi0xSBnAVDdkOKTtpLQ= github.com/jawher/mow.cli v1.1.0/go.mod h1:aNaQlc7ozF3vw6IJ2dHjp2ZFiA4ozMIYY6PyuRJwlUg= github.com/jcmturner/gofork v0.0.0-20180107083740-2aebee971930/go.mod h1:MK8+TM0La+2rjBD4jE12Kj1pCCxK7d2LK/UM3ncEo0o= github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= github.com/jmcvetta/randutil v0.0.0-20150817122601-2bb1b664bcff/go.mod h1:ddfPX8Z28YMjiqoaJhNBzWHapTHXejnB5cDCUWDwriw= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= github.com/jmespath/go-jmespath v0.3.0/go.mod h1:9QtRXoHjLGCJ5IBSaohpXITPlowMeeYCZ7fLUTSywik= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U= @@ -842,7 +794,6 @@ github.com/jtolds/gls v4.2.1+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVY github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/jung-kurt/gofpdf v1.0.3-0.20190309125859-24315acbbda5/go.mod h1:7Id9E/uU8ce6rXgefFLlgrJj/GYY22cpxn+r32jIOes= -github.com/justinas/alice v0.0.0-20171023064455-03f45bd4b7da/go.mod h1:oLH0CmIaxCGXD67VKGR5AacGXZSMznlmeqM8RzPrcY8= github.com/kataras/golog v0.1.8 h1:isP8th4PJH2SrbkciKnylaND9xoTtfxv++NB+DF0l9g= github.com/kataras/pio v0.0.11 h1:kqreJ5KOEXGMwHAWHDwIl+mjfNCPhAwZPa8gK7MKlyw= github.com/kennygrant/sanitize v1.2.4 h1:gN25/otpP5vAsO2djbMhF/LQX6R7+O1TB4yv8NzpJ3o= @@ -854,8 +805,9 @@ github.com/klauspost/compress v1.9.7/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0 github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.11.4/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= github.com/klauspost/compress v1.13.1/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/compress v1.16.6 h1:91SKEy4K37vkp255cJ8QesJhjyRO0hn9i9G0GoUwLsk= -github.com/klauspost/compress v1.16.6/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.16.5 h1:IFV2oUNUzZaz+XyusxpLzpzS8Pt5rh0Z16For/djlyI= +github.com/klauspost/compress v1.16.5/go.mod h1:ntbaceVETuRiXiv4DpjP66DpAtAGkEQskQzEyD//IeE= github.com/klauspost/cpuid v1.2.0/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= @@ -885,22 +837,18 @@ github.com/libp2p/go-buffer-pool v0.1.0 h1:oK4mSFcQz7cTQIfqbe4MIj9gLW+mnanjyFtc6 github.com/libp2p/go-buffer-pool v0.1.0/go.mod h1:N+vh8gMqimBzdKkSMVuydVDq+UV5QTWy5HSiZacSbPg= github.com/lispad/go-generics-tools v1.1.0 h1:mbSgcxdFVmpoyso1X/MJHXbSbSL3dD+qhRryyxk+/XY= github.com/lispad/go-generics-tools v1.1.0/go.mod h1:2csd1EJljo/gy5qG4khXol7ivCPptNjG5Uv2X8MgK84= -github.com/lukechampine/stm v0.0.0-20191022212748-05486c32d236/go.mod h1:wTLsd5FC9rts7GkMpsPGk64CIuea+03yaLAp19Jmlg8= github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= +github.com/marten-seemann/qtls-go1-18 v0.1.4 h1:ogomB+lWV3Vmwiu6RTwDVTMGx+9j7SEi98e8QB35Its= +github.com/marten-seemann/qtls-go1-18 v0.1.4/go.mod h1:mJttiymBAByA49mhlNZZGrH5u1uXYZJ+RW28Py7f4m4= +github.com/marten-seemann/qtls-go1-19 v0.1.2 h1:ZevAEqKXH0bZmoOBPiqX2h5rhQ7cbZi+X+rlq2JUbCE= +github.com/marten-seemann/qtls-go1-19 v0.1.2/go.mod h1:5HTDWtVudo/WFsHKRNuOhWlbdjrfs5JHrYb0wIJqGpI= github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-ieproxy v0.0.1/go.mod h1:pYabZ6IHcRpFh7vIaLfK7rdcWgFEb3SFJ6/gNWuh88E= github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= -github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84= github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= github.com/mattn/go-isatty v0.0.17 h1:BTarxUcIeDqL27Mc+vyvdWYSL28zpIhv3RoTdsLMPng= -github.com/mattn/go-sqlite3 v1.7.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.10.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.13.0/go.mod h1:FPy6KqzDD04eiIsT53CuJW3U88zkxoIYsOqkbpncsNc= -github.com/mattn/go-sqlite3 v1.14.11 h1:gt+cp9c0XGqe9S/wAHTL3n/7MqY+siPWgWJgqdsFrzQ= -github.com/mattn/go-sqlite3 v1.14.11/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/matttproud/golang_protobuf_extensions v1.0.4 h1:mmDVorXM7PCGKw94cs5zkfA9PSy5pEvNWRP0ET0TIVo= github.com/matttproud/golang_protobuf_extensions v1.0.4/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4= @@ -1073,7 +1021,6 @@ github.com/prometheus/common v0.43.0 h1:iq+BVjvYLei5f27wiuNiB1DN6DYQkp1c8Bx0Vykh github.com/prometheus/common v0.43.0/go.mod h1:NCvr5cQIh3Y/gy73/RdVtC9r8xxrxwJnB+2lB3BxrFc= github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190403104016-ea9eea638872/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= @@ -1089,10 +1036,6 @@ github.com/quic-go/quic-go v0.34.0 h1:OvOJ9LFjTySgwOTYUZmNoq0FzVicP8YujpV0kB7m2l github.com/quic-go/quic-go v0.34.0/go.mod h1:+4CVgVppm0FNjpG3UcX8Joi/frKOH7/ciD5yGcwOO1g= github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/reflog/minisentinel v0.0.0-20210817104530-e0dd88cb8bc6/go.mod h1:UdPuN3+X0Rvf3yJ++7j0zj4P8EaI6YwQJS+JbBk2o8k= -github.com/refraction-networking/utls v0.0.0-20190415193640-32987941ebd3/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= -github.com/refraction-networking/utls v0.0.0-20190909200633-43c36d3c1f57/go.mod h1:tz9gX959MEFfFN5whTIocCLUG57WiILqtdVxI8c6Wj0= -github.com/refraction-networking/utls v1.3.3 h1:f/TBLX7KBciRyFH3bwupp+CE4fzoYKCirhdRcC490sw= -github.com/refraction-networking/utls v1.3.3/go.mod h1:DlecWW1LMlMJu+9qpzzQqdHDT/C2LAe03EdpLUz/RL8= github.com/retailnext/hllpp v1.0.0/go.mod h1:RDpi1RftBQPUCDRw6SmxeaREsAaRKnOclghuzp/WRzc= github.com/rickar/props v0.0.0-20170718221555-0b06aeb2f037/go.mod h1:F1p8BNM4IXv2UcptwSp8HJOapKurodd/PYu1D6Gtn9Y= github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg= @@ -1113,6 +1056,7 @@ github.com/sclevine/agouti v3.0.0+incompatible/go.mod h1:b4WX9W9L1sfQKXeJf1mUTLZ github.com/shadowsocks/go-shadowsocks2 v0.1.5 h1:PDSQv9y2S85Fl7VBeOMF9StzeXZyK1HakRm86CUbr28= github.com/shadowsocks/go-shadowsocks2 v0.1.5/go.mod h1:AGGpIoek4HRno4xzyFiAtLHkOpcoznZEkAccaI/rplM= github.com/shirou/gopsutil v2.18.12+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= +github.com/shirou/gopsutil v3.21.5+incompatible h1:OloQyEerMi7JUrXiNzy8wQ5XN+baemxSl12QgIzt0jc= github.com/shirou/gopsutil v3.21.5+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/shirou/w32 v0.0.0-20160930032740-bb4de0191aa4/go.mod h1:qsXQc7+bwAM3Q1u/4XEfrquwF8Lw7D7y5cD8CuHnfIc= github.com/siddontang/go v0.0.0-20170517070808-cb568a3e5cc0/go.mod h1:3yhqj7WBBfRhbBlzyOC3gUxftwsU0u8gqevxwIHQpMw= @@ -1124,13 +1068,10 @@ github.com/siddontang/rdb v0.0.0-20150307021120-fc89ed2e418d/go.mod h1:AMEsy7v5z github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= -github.com/skratchdot/open-golang v0.0.0-20190402232053-79abb63cd66e/go.mod h1:sUM3LWHvSMaG192sy56D9F7CNvL7jUJVXoqM1QKLnog= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/assertions v0.0.0-20190215210624-980c5ac6f3ac/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/assertions v0.0.0-20190401211740-f487f9de1cd3/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v0.0.0-20181108003508-044398e4856c/go.mod h1:XDJAKZRPZ1CvBcN2aX5YOUTYGHki24fSF0Iv48Ibg0s= github.com/smartystreets/goconvey v0.0.0-20190306220146-200a235640ff/go.mod h1:KSQcGKpxUMHk3nbYzs/tIBAM2iDooCn0BmttHOJEbLs= -github.com/smartystreets/goconvey v0.0.0-20190330032615-68dc04aab96a/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/songgao/water v0.0.0-20190725173103-fd331bda3f4b/go.mod h1:P5HUIBuIWKbyjl083/loAegFkfbFNx5i2qEP4CNbm7E= github.com/songgao/water v0.0.0-20200317203138-2b4b6d7c09d8 h1:TG/diQgUe0pntT/2D9tmUCz4VNwm9MfrtPr0SU2qSX8= @@ -1170,11 +1111,8 @@ github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/syncthing/syncthing v0.14.48-rc.4/go.mod h1:nw3siZwHPA6M8iSfjDCWQ402eqvEIasMQOE8nFOxy7M= github.com/syndtr/goleveldb v0.0.0-20160425020131-cfa635847112/go.mod h1:Z4AUp2Km+PwemOoO/VB5AOx9XSsIItzFjoJlOSiYmn0= github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ= -github.com/tchap/go-patricia/v2 v2.3.1 h1:6rQp39lgIYZ+MHmdEq4xzuk1t7OdC35z/xm0BGhTkes= -github.com/tchap/go-patricia/v2 v2.3.1/go.mod h1:VZRHKAb53DLaG+nA9EaYYiaEx6YztwDlLElMsnSHD4k= github.com/templexxx/cpu v0.1.0 h1:wVM+WIJP2nYaxVxqgHPD4wGA2aJ9rvrQRV8CvFzNb40= github.com/templexxx/cpu v0.1.0/go.mod h1:w7Tb+7qgcAlIyX4NhLuDKt78AHA5SzPmq0Wj6HiEnnk= github.com/templexxx/xorsimd v0.4.2 h1:ocZZ+Nvu65LGHmCLZ7OoCtg8Fx8jnHKK37SjvngUoVI= @@ -1192,7 +1130,11 @@ github.com/tinylib/msgp v1.1.5/go.mod h1:eQsjooMTnV42mHu917E26IogZ2930nFyBQdofk1 github.com/tjfoc/gmsm v1.4.1 h1:aMe1GlZb+0bLjn+cKTPEvvn9oUEBlJitaZiiBwsbgho= github.com/tjfoc/gmsm v1.4.1/go.mod h1:j4INPkHWMrhJb38G+J6W4Tw0AbuN8Thu3PbdVYhVcTE= github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/go-sysconf v0.3.11 h1:89WgdJhk5SNwJfu+GKyYveZ4IaJ7xAkecBo+KdJV0CM= +github.com/tklauser/go-sysconf v0.3.11/go.mod h1:GqXfhXY3kiPa0nAXPDIQIWzJbMCB7AmcWpGR8lSZfqI= github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= +github.com/tklauser/numcpus v0.6.0 h1:kebhY2Qt+3U6RNK7UqpYNA+tJ23IBEGKkB7JQBfDYms= +github.com/tklauser/numcpus v0.6.0/go.mod h1:FEZLMke0lhOUG6w2JadTzp0a+Nl8PF/GFkQ5UVIcaL4= github.com/tkuchiki/go-timezone v0.2.0/go.mod h1:b1Ean9v2UXtxSq4TZF0i/TU9NuoWa9hOzOKoGCV2zqY= github.com/tkuchiki/go-timezone v0.2.2 h1:MdHR65KwgVTwWFQrota4SKzc4L5EfuH5SdZZGtk/P2Q= github.com/tkuchiki/go-timezone v0.2.2/go.mod h1:oFweWxYl35C/s7HMVZXiA19Jr9Y0qJHMaG/J2TES4LY= @@ -1212,11 +1154,8 @@ github.com/vharitonsky/iniflags v0.0.0-20180513140207-a33cd0b5f3de/go.mod h1:irM github.com/vishvananda/netns v0.0.0-20180720170159-13995c7128cc/go.mod h1:ZjcWmFBXmLKZu9Nxj3WKYEafiSqer2rnvPr0en9UNpI= github.com/vishvananda/netns v0.0.0-20210104183010-2eb08e3e575f h1:p4VB7kIXpOQvVn1ZaTIVp+3vuYAXFe3OJEvjbUYJLaA= github.com/vulcand/oxy v1.4.2 h1:KibUVdKrwy7eXR3uHS2pYoZ9dCzKVcgDNHD2jkPZmxU= -github.com/willf/bitset v1.1.3/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.9/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= github.com/willf/bitset v1.1.10/go.mod h1:RjeCKbqT1RxIR/KWY6phxZiaY1IyutSBfGjNPySAYV4= -github.com/willf/bloom v0.0.0-20170505221640-54e3b963ee16/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8= -github.com/willf/bloom v2.0.3+incompatible/go.mod h1:MmAltL9pDMNTrvUkxdg0k0q5I0suxmuwp3KbyrZLOZ8= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8 h1:nIPpBwaJSVYIxUFsDv3M8ofmx9yWTog9BfvIu0q41lo= github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMxjDjgmT5uz5wzYJKVo23qUhYTos= github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= @@ -1246,7 +1185,6 @@ gitlab.com/yawning/edwards25519-extra.git v0.0.0-20220726154925-def713fd18e4/go. gitlab.com/yawning/obfs4.git v0.0.0-20220904064028-336a71d6e4cf h1:k9czJST0Jvc6fnz4Jp1sxRmA4dSuiWFq+DVpxLZP5yM= gitlab.com/yawning/obfs4.git v0.0.0-20220904064028-336a71d6e4cf/go.mod h1:9GcM8QNU9/wXtEEH2q8bVOnPI7FtIF6VVLzZ1l6Hgf8= go.etcd.io/bbolt v1.3.2/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= go.etcd.io/bbolt v1.3.5/go.mod h1:G5EMThwa9y8QZGBClrRx5EY+Yw9kAhnjy3bSjsnlVTQ= go.etcd.io/bbolt v1.3.7 h1:j+zJOnnEjF/kyHlDDgGnVL/AIqIJPq8UoB2GSNfkUfQ= go.etcd.io/bbolt v1.3.7/go.mod h1:N9Mkw9X8x5fupy0IKsmuqVtoGDyxsaDlbk4Rd05IAQw= @@ -1320,12 +1258,12 @@ golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWP golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.1.0/go.mod h1:RecgLatLF4+eUMCP1PoPZQb+cVrJcOPbHkTkbkB9sbw= golang.org/x/crypto v0.5.0/go.mod h1:NK/OQwhpMQP3MwtdjgLlYHnH9ebylxKWv3e0fK+mkQU= golang.org/x/crypto v0.8.0/go.mod h1:mRqEX+O9/h5TFCrQhkgjo2yKi0yYA+9ecGkdQoHrywE= +golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.11.0 h1:6Ewdq3tDic1mg5xRO4milcWCfMVQhI4NkqWWvqejpuA= -golang.org/x/crypto v0.11.0/go.mod h1:xgJhtzW8F9jGdVFWZESrid1U1bjeNy4zgy5cRr/CIio= golang.org/x/exp v0.0.0-20180321215751-8460e604b9de/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= @@ -1356,8 +1294,8 @@ golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPI golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= -golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda h1:O+EUvnBNPwI4eLthn8W5K+cS8zQZfgTABPLNm6Bna34= -golang.org/x/mobile v0.0.0-20230531173138-3c911d8e3eda/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= +golang.org/x/mobile v0.0.0-20221110043201-43a038452099 h1:aIu0lKmfdgtn2uTj7JI2oN4TUrQvgB+wzTPO23bCKt8= +golang.org/x/mobile v0.0.0-20221110043201-43a038452099/go.mod h1:aAjjkJNdrh3PMckS4B10TGS2nag27cbKR1y2BpUxsiY= golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= @@ -1367,8 +1305,8 @@ golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk= +golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20180524181706-dfa909b99c79/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -1379,7 +1317,6 @@ golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190318221613-d196dffd7c2b/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= @@ -1394,7 +1331,6 @@ golang.org/x/net v0.0.0-20190912160710-24e19bdeb0f2/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20190923162816-aa69164e4478/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191007182048-72f939374954/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191112182307-2180aed22343/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20191125084936-ffdde1057850/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -1420,6 +1356,7 @@ golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96b golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk= golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211111160137-58aab5ef257a/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.5.0/go.mod h1:DivGGAXEgPSlEBzxGzZI+ZLohi+xUj054jfeKui00ws= @@ -1427,9 +1364,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.7.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns= +golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50= -golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA= golang.org/x/oauth2 v0.0.0-20170912212905-13449ad91cb2/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -1450,20 +1386,17 @@ golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJ golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sync v0.2.0 h1:PUR+T4wwASmuSTYdKjYHI5TD22Wy5ogLU5qZCOLxBrI= +golang.org/x/sync v0.2.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190102155601-82a175fd1598/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190204203706-41f3e6584952/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190318195719-6c81ef8f67ca/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190405154228-4b34438f7a67/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190411185658-b44545bcd369/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1473,11 +1406,9 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190516110030-61b9204099cb/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190712062909-fae7ac547cb7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190910064555-bbd175535a8b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190912141932-bc967efca4b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190924154521-2837fb4f24fe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1485,10 +1416,8 @@ golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191105231009-c1f44814a5cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191112214154-59a1497f0cea/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191126131656-8a8471f7e56d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191224085550-c709ea063b76/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -1530,9 +1459,8 @@ golang.org/x/sys v0.4.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA= -golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -1554,11 +1482,9 @@ golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.6.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4= -golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/time v0.0.0-20170424234030-8be79e1e0910/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1575,7 +1501,6 @@ golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3 golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= @@ -1617,8 +1542,8 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.11.0 h1:EMCa6U9S2LtZXLAMoWiR/R8dAQFRqbAitmbJ2UKhoi8= -golang.org/x/tools v0.11.0/go.mod h1:anzJrxPjNtfgiYQYirP2CPGzGLxrH2u2QBhn6Bf3qY8= +golang.org/x/tools v0.9.1 h1:8WMNJAz3zrtPmnYC7ISf5dEn3MT0gY7jBJfw27yrrLo= +golang.org/x/tools v0.9.1/go.mod h1:owI94Op576fPu3cIGQeHs3joujW/2Oc6MtlxbF5dfNc= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internalsdk/android.go b/internalsdk/android.go index e7f0d4fb2..ce47880a5 100644 --- a/internalsdk/android.go +++ b/internalsdk/android.go @@ -88,6 +88,8 @@ type Session interface { ForceReplica() bool SetChatEnabled(bool) SplitTunnelingEnabled() (bool, error) + SetShowInterstitialAdsEnabled(bool) + SetCASShowInterstitialAdsEnabled(bool) // workaround for lack of any sequence types in gomobile bind... ;_; // used to implement GetInternalHeaders() map[string]string @@ -117,7 +119,8 @@ type panickingSession interface { IsProUser() bool SetChatEnabled(bool) SplitTunnelingEnabled() bool - + SetShowInterstitialAdsEnabled(bool) + SetCASShowInterstitialAdsEnabled(bool) // workaround for lack of any sequence types in gomobile bind... ;_; // used to implement GetInternalHeaders() map[string]string // Should return a JSON encoded map[string]string {"key":"val","key2":"val", ...} @@ -259,6 +262,13 @@ func (s *panickingSessionImpl) SetChatEnabled(enabled bool) { s.wrapped.SetChatEnabled(enabled) } +func (s *panickingSessionImpl) SetShowInterstitialAdsEnabled(enabled bool) { + s.wrapped.SetShowInterstitialAdsEnabled(enabled) +} +func (s *panickingSessionImpl) SetCASShowInterstitialAdsEnabled(enabled bool) { + s.wrapped.SetCASShowInterstitialAdsEnabled(enabled) +} + func (s *panickingSessionImpl) SerializedInternalHeaders() string { result, err := s.wrapped.SerializedInternalHeaders() panicIfNecessary(err) @@ -582,6 +592,22 @@ func run(configDir, locale string, chatEnabled := runner.FeatureEnabled("chat", ApplicationVersion) log.Debugf("Chat enabled? %v", chatEnabled) session.SetChatEnabled(chatEnabled) + + // Check if ads feature is enabled or not + if !session.IsProUser() { + showAdsEnabled := runner.FeatureEnabled("interstitialads", ApplicationVersion) + log.Debugf("Show ads enabled? %v", showAdsEnabled) + session.SetShowInterstitialAdsEnabled(showAdsEnabled) + + //Check for CAS ads for Russia and Iran user + showCASAdsEnabled := runner.FeatureEnabled("casinterstitialads", ApplicationVersion) + session.SetCASShowInterstitialAdsEnabled(showCASAdsEnabled) + } else { + // Explicitly disable ads for Pro users. + session.SetShowInterstitialAdsEnabled(false) + session.SetCASShowInterstitialAdsEnabled(false) + } + } // When features are enabled/disabled, the UI changes. To minimize this, we only check features once on startup, preferring diff --git a/internalsdk/android_test.go b/internalsdk/android_test.go index cc6f24d8f..c83885f23 100644 --- a/internalsdk/android_test.go +++ b/internalsdk/android_test.go @@ -59,17 +59,20 @@ func (c testSession) UpdateStats(string, string, string, int, int, bool) error { func (c testSession) UpdateAdSettings(AdSettings) error { return nil } -func (c testSession) GetAppName() string { return "lantern" } -func (c testSession) AppVersion() (string, error) { return "6.9.0", nil } -func (c testSession) Code() (string, error) { return "1", nil } -func (c testSession) Currency() (string, error) { return "usd", nil } -func (c testSession) DeviceOS() (string, error) { return "android", nil } -func (c testSession) Email() (string, error) { return "test@getlantern.org", nil } -func (c testSession) GetCountryCode() (string, error) { return "us", nil } -func (c testSession) IsStoreVersion() (bool, error) { return false, nil } -func (c testSession) Provider() (string, error) { return "stripe", nil } -func (c testSession) SetChatEnabled(enabled bool) {} -func (c testSession) SetMatomoEnabled(bool) {} +func (c testSession) GetAppName() string { return "lantern" } +func (c testSession) AppVersion() (string, error) { return "6.9.0", nil } +func (c testSession) Code() (string, error) { return "1", nil } +func (c testSession) Currency() (string, error) { return "usd", nil } +func (c testSession) DeviceOS() (string, error) { return "android", nil } +func (c testSession) Email() (string, error) { return "test@getlantern.org", nil } +func (c testSession) GetCountryCode() (string, error) { return "us", nil } +func (c testSession) IsStoreVersion() (bool, error) { return false, nil } +func (c testSession) Provider() (string, error) { return "stripe", nil } +func (c testSession) SetChatEnabled(enabled bool) {} +func (c testSession) SetMatomoEnabled(bool) {} +func (c testSession) IsPlayVersion() (bool, error) { return false, nil } +func (c testSession) SetShowInterstitialAdsEnabled(enabled bool) {} +func (c testSession) SetCASShowInterstitialAdsEnabled(enabled bool) {} func (c testSession) SerializedInternalHeaders() (string, error) { return c.serializedInternalHeaders, nil diff --git a/lib/account/account.dart b/lib/account/account.dart index dc136ea25..9a1a69450 100644 --- a/lib/account/account.dart +++ b/lib/account/account.dart @@ -1,17 +1,17 @@ import 'package:lantern/common/common.dart'; import 'package:lantern/messaging/messaging_model.dart'; - +@RoutePage(name: 'Account') class AccountMenu extends StatelessWidget { AccountMenu({Key? key}) : super(key: key); Future authorizeDeviceForPro(BuildContext context) async => await context.pushRoute(AuthorizePro()); - void inviteFriends() => - LanternNavigator.startScreen(LanternNavigator.SCREEN_INVITE_FRIEND); + void inviteFriends(BuildContext context) async => + await context.pushRoute(InviteFriends()); - void openDesktopVersion() => - LanternNavigator.startScreen(LanternNavigator.SCREEN_DESKTOP_VERSION); + void openDesktopVersion(BuildContext context) async => + await context.pushRoute(LanternDesktop()); void openSettings(BuildContext context) => context.pushRoute(Settings()); @@ -58,7 +58,9 @@ class AccountMenu extends StatelessWidget { ListItemFactory.settingsItem( icon: ImagePaths.star, content: 'Invite Friends'.i18n, - onTap: inviteFriends, + onTap: () { + inviteFriends(context); + }, ), ListItemFactory.settingsItem( icon: ImagePaths.devices, @@ -95,7 +97,9 @@ class AccountMenu extends StatelessWidget { ListItemFactory.settingsItem( icon: ImagePaths.star, content: 'Invite Friends'.i18n, - onTap: inviteFriends, + onTap: () { + inviteFriends(context); + }, ), ListItemFactory.settingsItem( icon: ImagePaths.devices, @@ -111,7 +115,9 @@ class AccountMenu extends StatelessWidget { ListItemFactory.settingsItem( icon: ImagePaths.desktop, content: 'desktop_version'.i18n, - onTap: openDesktopVersion, + onTap: () { + openDesktopVersion(context); + }, ), ListItemFactory.settingsItem( key: AppKeys.support, diff --git a/lib/account/account_management.dart b/lib/account/account_management.dart index 84315f3b4..fdeb2fb2f 100644 --- a/lib/account/account_management.dart +++ b/lib/account/account_management.dart @@ -1,5 +1,6 @@ import 'package:lantern/messaging/messaging.dart'; +@RoutePage(name: 'AccountManagement') class AccountManagement extends StatefulWidget { AccountManagement({Key? key, required this.isPro}) : super(key: key); final bool isPro; @@ -30,108 +31,137 @@ class _AccountManagementState extends State ? 'Pro Account Management'.i18n : 'account_management'.i18n; var textCopied = false; - - return BaseScreen( - title: title, - padHorizontal: false, - body: sessionModel - .deviceId((BuildContext context, String myDeviceId, Widget? child) { - var freeItems = [ - // * Lantern Chat Number - messagingModel.me( - (BuildContext context, Contact me, Widget? child) => - StatefulBuilder( - builder: (context, setState) => ListItemFactory.settingsItem( - header: 'your_chat_number'.i18n, - icon: ImagePaths.chatNumber, - content: me.chatNumber.shortNumber.formattedChatNumber, - trailingArray: [ - Padding( - padding: const EdgeInsetsDirectional.only( - start: 16.0, - end: 16.0, - ), - child: CInkWell( - onTap: () async { - copyText( - context, - me.chatNumber.shortNumber.formattedChatNumber, - ); - setState(() => textCopied = true); - await Future.delayed( - defaultAnimationDuration, - () => setState(() => textCopied = false), - ); - }, - child: CAssetImage( - path: textCopied - ? ImagePaths.check_green - : ImagePaths.content_copy, - ), - ), - ), - mirrorLTR( - context: context, - child: const ContinueArrow(), - ), - ], - onTap: () => context.router.push(const ChatNumberAccount()), - ), - ), - ), - // * RECOVERY KEY - messagingModel.getCopiedRecoveryStatus( - ( - BuildContext context, - bool hasCopiedRecoveryKey, - Widget? child, - ) => - ListItemFactory.settingsItem( - icon: ImagePaths.lock_outline, - content: 'backup_recovery_key'.i18n, - trailingArray: [ - if (!hasCopiedRecoveryKey) - const Padding( - padding: EdgeInsetsDirectional.only(start: 16.0, end: 16.0), - child: CAssetImage( - path: ImagePaths.badge, - ), - ), - mirrorLTR(context: context, child: const ContinueArrow()) - ], - onTap: () => context.router.push(RecoveryKey()), - ), - ), - // * Delete all Chat data - ListItemFactory.settingsItem( - icon: ImagePaths.account_remove, - content: 'delete_chat_data'.i18n, + var freeItems = [ + // * Lantern Chat Number + messagingModel.me( + (BuildContext context, Contact me, Widget? child) => StatefulBuilder( + builder: (context, setState) => ListItemFactory.settingsItem( + header: 'your_chat_number'.i18n, + icon: ImagePaths.chatNumber, + content: me.chatNumber.shortNumber.formattedChatNumber, trailingArray: [ Padding( - padding: const EdgeInsetsDirectional.only(start: 16.0), - child: TextButton( - onPressed: () => CDialog( - iconPath: ImagePaths.account_remove, - title: 'delete_chat_data'.i18n, - description: 'delete_chat_data_description'.i18n, - checkboxLabel: 'delete_chat_data_confirmation'.i18n, - agreeText: 'delete'.i18n, - agreeAction: () async { - await messagingModel.wipeData(); - await context.router.pop(); - return true; - }, - ).show(context), - child: CText( - 'delete'.i18n.toUpperCase(), - style: tsButtonPink, + padding: const EdgeInsetsDirectional.only( + start: 16.0, + end: 16.0, + ), + child: CInkWell( + onTap: () async { + copyText( + context, + me.chatNumber.shortNumber.formattedChatNumber, + ); + setState(() => textCopied = true); + await Future.delayed( + defaultAnimationDuration, + () => setState(() => textCopied = false), + ); + }, + child: CAssetImage( + path: textCopied + ? ImagePaths.check_green + : ImagePaths.content_copy, ), ), ), + mirrorLTR( + context: context, + child: const ContinueArrow(), + ), ], - ) - ]; - + onTap: () => context.router.push(const ChatNumberAccount()), + ), + ), + ), + // * RECOVERY KEY + messagingModel.getCopiedRecoveryStatus( + ( + BuildContext context, + bool hasCopiedRecoveryKey, + Widget? child, + ) => + ListItemFactory.settingsItem( + icon: ImagePaths.lock_outline, + content: 'backup_recovery_key'.i18n, + trailingArray: [ + if (!hasCopiedRecoveryKey) + const Padding( + padding: EdgeInsetsDirectional.only(start: 16.0, end: 16.0), + child: CAssetImage( + path: ImagePaths.badge, + ), + ), + mirrorLTR(context: context, child: const ContinueArrow()) + ], + onTap: () => context.router.push(RecoveryKey()), + ), + ), + // * Delete all Chat data + ListItemFactory.settingsItem( + icon: ImagePaths.account_remove, + content: 'delete_chat_data'.i18n, + trailingArray: [ + Padding( + padding: const EdgeInsetsDirectional.only(start: 16.0), + child: TextButton( + onPressed: () => CDialog( + iconPath: ImagePaths.account_remove, + title: 'delete_chat_data'.i18n, + description: 'delete_chat_data_description'.i18n, + checkboxLabel: 'delete_chat_data_confirmation'.i18n, + agreeText: 'delete'.i18n, + agreeAction: () async { + await messagingModel.wipeData(); + await context.router.pop(); + return true; + }, + ).show(context), + child: CText( + 'delete'.i18n.toUpperCase(), + style: tsButtonPink, + ), + ), + ), + ], + ) + ]; + var proItems = [ + sessionModel.emailAddress(( + BuildContext context, + String emailAddress, + Widget? child, + ) { + return ListItemFactory.settingsItem( + header: 'lantern_pro_email'.i18n, + icon: ImagePaths.email, + content: emailAddress, + trailingArray: [], + ); + }), + sessionModel.expiryDate(( + BuildContext context, + String expirationDate, + Widget? child, + ) { + return ListItemFactory.settingsItem( + key: AppKeys.account_renew, + header: 'Pro Account Expiration'.i18n, + icon: ImagePaths.clock, + content: expirationDate, + onTap: () async { + await context.pushRoute(const PlansPage()); + }, + trailingArray: [ + CText('Renew'.i18n.toUpperCase(), style: tsButtonPink) + ], + ); + }), + ]; + return BaseScreen( + title: title, + padHorizontal: false, + body: sessionModel + .deviceId((BuildContext context, String myDeviceId, Widget? child) { return !widget.isPro ? // * FREE @@ -148,162 +178,164 @@ class _AccountManagementState extends State // * PRO - check onboarding status messagingModel .getOnBoardingStatus((context, hasBeenOnboarded, child) { - return sessionModel.devices( - (BuildContext context, Devices devices, Widget? child) { - var proItems = [ - sessionModel.emailAddress(( - BuildContext context, - String emailAddress, - Widget? child, - ) { - return ListItemFactory.settingsItem( - header: 'lantern_pro_email'.i18n, - icon: ImagePaths.email, - content: emailAddress, - trailingArray: [], - ); - }), - sessionModel.expiryDate(( - BuildContext context, - String expirationDate, - Widget? child, - ) { - return ListItemFactory.settingsItem( - key: AppKeys.account_renew, - header: 'Pro Account Expiration'.i18n, - icon: ImagePaths.clock, - content: expirationDate, - onTap: () async { - await context.pushRoute(const PlansPage()); - }, - trailingArray: [ - CText('Renew'.i18n.toUpperCase(), style: tsButtonPink) - ], - ); - }), - ]; - - proItems.addAll( - devices.devices.map((device) { - var isMyDevice = device.id == myDeviceId; - var allowRemoval = - devices.devices.length > 1 || !isMyDevice; - var index = - devices.devices.indexWhere((d) => d == device); + return sessionModel.chatEnabled((context, chatEnabled, child) => + sessionModel.devices( + (BuildContext context, Devices devices, Widget? child) { + proItems.addAll( + devices.devices.map((device) { + var isMyDevice = device.id == myDeviceId; + var allowRemoval = + devices.devices.length > 1 || !isMyDevice; + var index = + devices.devices.indexWhere((d) => d == device); - return Padding( - padding: const EdgeInsetsDirectional.only(start: 4), - child: ListItemFactory.settingsItem( - header: index == 0 ? 'pro_devices_header'.i18n : null, - content: device.name, - onTap: !allowRemoval - ? null - : () { - showDialog( - context: context, - builder: (context) { - return AlertDialog( - content: CText( - 'confirm_remove_device'.i18n, - style: tsBody1, - ), - actions: [ - TextButton( - onPressed: () { - Navigator.pop(context); - }, - child: CText( - 'No'.i18n, - style: tsButtonGrey, - ), - ), - TextButton( - onPressed: () { - context.loaderOverlay - .show(widget: spinner); - sessionModel - .removeDevice(device.id) - .then((value) { - context.loaderOverlay.hide(); - Navigator.pop(context); - }).onError((error, stackTrace) { - context.loaderOverlay.hide(); - }); - }, - child: CText( - 'Yes'.i18n, - style: tsButtonPink, + return Padding( + padding: const EdgeInsetsDirectional.only(start: 4), + child: ListItemFactory.settingsItem( + header: + index == 0 ? 'pro_devices_header'.i18n : null, + content: device.name, + onTap: !allowRemoval + ? null + : () { + showDialog( + context: context, + builder: (context) { + return AlertDialog( + content: CText( + 'confirm_remove_device'.i18n, + style: tsBody1, ), - ), - ], + actions: [ + TextButton( + onPressed: () { + Navigator.pop(context); + }, + child: CText( + 'No'.i18n, + style: tsButtonGrey, + ), + ), + TextButton( + onPressed: () { + context.loaderOverlay + .show(widget: spinner); + sessionModel + .removeDevice(device.id) + .then((value) { + context.loaderOverlay + .hide(); + Navigator.pop(context); + }).onError( + (error, stackTrace) { + context.loaderOverlay + .hide(); + }); + }, + child: CText( + 'Yes'.i18n, + style: tsButtonPink, + ), + ), + ], + ); + }, ); }, - ); - }, - trailingArray: !allowRemoval - ? [] - : [ - CText( - (isMyDevice ? 'Log Out' : 'Remove') - .i18n - .toUpperCase(), - style: tsButtonPink, - ) - ], - ), + trailingArray: !allowRemoval + ? [] + : [ + CText( + (isMyDevice ? 'Log Out' : 'Remove') + .i18n + .toUpperCase(), + style: tsButtonPink, + ) + ], + ), + ); + }), ); - }), - ); - if (devices.devices.length < 3) { - proItems.add( - ListItemFactory.settingsItem( - content: '', - onTap: () async => - await context.pushRoute(ApproveDevice()), - trailingArray: [ - CText( - 'Link Device'.i18n.toUpperCase(), - style: tsButtonPink, - ) - ], - ), - ); - } - return hasBeenOnboarded == true - // * has been onboarded - ? Column( - children: [ - TabBar( - controller: tabController, - indicator: BoxDecoration( - border: Border( - top: BorderSide.none, - left: BorderSide.none, - right: BorderSide.none, - bottom: BorderSide(width: 3.0, color: pink4), - ), - ), - labelStyle: tsSubtitle2, - labelColor: pink4, - unselectedLabelStyle: tsBody1, - unselectedLabelColor: grey5, - tabs: [ - Tab( - text: 'Lantern Pro'.i18n.toUpperCase(), - ), - Tab( - text: 'chat'.i18n.toUpperCase(), + if (devices.devices.length < 3) { + proItems.add( + ListItemFactory.settingsItem( + content: '', + onTap: () async => + await context.pushRoute(ApproveDevice()), + trailingArray: [ + CText( + 'Link Device'.i18n.toUpperCase(), + style: tsButtonPink, + ) + ], + ), + ); + } + // If chat is enabled and hasBeenOnboarded then only show chat settings + return chatEnabled && hasBeenOnboarded ==true + // * has been onboarded + ? Column( + children: [ + TabBar( + controller: tabController, + indicator: BoxDecoration( + border: Border( + top: BorderSide.none, + left: BorderSide.none, + right: BorderSide.none, + bottom: + BorderSide(width: 3.0, color: pink4), + ), + ), + labelStyle: tsSubtitle2, + labelColor: pink4, + unselectedLabelStyle: tsBody1, + unselectedLabelColor: grey5, + tabs: [ + Tab( + text: 'Lantern Pro'.i18n.toUpperCase(), + ), + Tab( + text: 'chat'.i18n.toUpperCase(), + ), + ], ), + const CDivider(), + Expanded( + child: TabBarView( + controller: tabController, + children: [ + // * PRO TAB + ListView( + padding: + const EdgeInsetsDirectional.only( + start: 16, + end: 16, + bottom: 8, + ), + children: proItems, + ), + // * SECURE CHAT TAB + ListView( + padding: + const EdgeInsetsDirectional.only( + start: 16, + end: 16, + bottom: 8, + ), + children: freeItems, + ), + ], + ), + ) ], - ), - const CDivider(), - Expanded( - child: TabBarView( - controller: tabController, - children: [ - // * PRO TAB - ListView( + ) + // * has not been onboarded + : Column( + children: [ + Expanded( + child: ListView( padding: const EdgeInsetsDirectional.only( start: 16, end: 16, @@ -311,36 +343,10 @@ class _AccountManagementState extends State ), children: proItems, ), - // * SECURE CHAT TAB - ListView( - padding: const EdgeInsetsDirectional.only( - start: 16, - end: 16, - bottom: 8, - ), - children: freeItems, - ), - ], - ), - ) - ], - ) - // * has not been onboarded - : Column( - children: [ - Expanded( - child: ListView( - padding: const EdgeInsetsDirectional.only( - start: 16, - end: 16, - bottom: 8, ), - children: proItems, - ), - ), - ], - ); - }); + ], + ); + })); }); }), ); diff --git a/lib/account/blocked_users.dart b/lib/account/blocked_users.dart index 289a9e831..2570bfd6e 100644 --- a/lib/account/blocked_users.dart +++ b/lib/account/blocked_users.dart @@ -1,5 +1,6 @@ import 'package:lantern/messaging/messaging.dart'; +@RoutePage(name: 'BlockedUsers') class BlockedUsers extends StatelessWidget { BlockedUsers({Key? key}) : super(key: key); diff --git a/lib/account/chat_number_account.dart b/lib/account/chat_number_account.dart index 776c2ac32..e2c53bd2f 100644 --- a/lib/account/chat_number_account.dart +++ b/lib/account/chat_number_account.dart @@ -1,5 +1,6 @@ import 'package:lantern/messaging/messaging.dart'; +@RoutePage(name: 'ChatNumberAccount') class ChatNumberAccount extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/lib/account/device_linking/approve_device.dart b/lib/account/device_linking/approve_device.dart index d8af544f2..05afc1dcb 100644 --- a/lib/account/device_linking/approve_device.dart +++ b/lib/account/device_linking/approve_device.dart @@ -1,32 +1,13 @@ import 'package:lantern/common/common.dart'; +import 'explanation_step.dart'; +@RoutePage(name: 'ApproveDevice') class ApproveDevice extends StatelessWidget { ApproveDevice({Key? key}) : super(key: key); final pinCodeController = TextEditingController(); final formKey = GlobalKey(); - Widget explanationStep({required String icon, required String text}) { - return Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Container( - margin: const EdgeInsetsDirectional.only(end: 16), - child: CAssetImage( - path: icon, - color: Colors.black, - ), - ), - Flexible( - child: CText( - text, - style: tsBody1, - ), - ), - ], - ); - } - @override Widget build(BuildContext context) { return sessionModel.emailAddress( @@ -67,15 +48,11 @@ class ApproveDevice extends StatelessWidget { ), Container( margin: const EdgeInsetsDirectional.only(bottom: 16), - child: explanationStep( - icon: ImagePaths.number_1, - text: 'approve_device_step_1'.i18n, - ), - ), - explanationStep( - icon: ImagePaths.number_2, - text: 'approve_device_step_2'.i18n, + child: ExplanationStep( + ImagePaths.number_1, 'approve_device_step_1'.i18n), ), + ExplanationStep( + ImagePaths.number_2, 'approve_device_step_2'.i18n), const Spacer(), ], ), diff --git a/lib/account/device_linking/authorize_device_for_pro.dart b/lib/account/device_linking/authorize_device_for_pro.dart index 9462d349f..87767e92f 100644 --- a/lib/account/device_linking/authorize_device_for_pro.dart +++ b/lib/account/device_linking/authorize_device_for_pro.dart @@ -1,6 +1,7 @@ import 'package:lantern/common/common.dart'; +@RoutePage(name: 'AuthorizePro') class AuthorizeDeviceForPro extends StatelessWidget { AuthorizeDeviceForPro({Key? key}) : super(key: key); @@ -29,9 +30,7 @@ class AuthorizeDeviceForPro extends StatelessWidget { Button( width: 200, text: 'Link with PIN'.i18n, - onPressed: () { - LanternNavigator.startScreen(LanternNavigator.SCREEN_LINK_PIN); - }, + onPressed: () async => await context.pushRoute(LinkDevice()), ), const Spacer(), Flexible( diff --git a/lib/account/device_linking/authorize_device_via_email.dart b/lib/account/device_linking/authorize_device_via_email.dart index b640e24d0..3eb10cfb4 100644 --- a/lib/account/device_linking/authorize_device_via_email.dart +++ b/lib/account/device_linking/authorize_device_via_email.dart @@ -2,6 +2,7 @@ import 'package:email_validator/email_validator.dart'; import 'package:lantern/common/common.dart'; +@RoutePage(name: 'AuthorizeDeviceEmail') class AuthorizeDeviceViaEmail extends StatelessWidget { AuthorizeDeviceViaEmail({Key? key}) : super(key: key); diff --git a/lib/account/device_linking/authorize_device_via_email_pin.dart b/lib/account/device_linking/authorize_device_via_email_pin.dart index b26cd8a9e..41525721d 100644 --- a/lib/account/device_linking/authorize_device_via_email_pin.dart +++ b/lib/account/device_linking/authorize_device_via_email_pin.dart @@ -1,6 +1,7 @@ import 'package:lantern/common/common.dart'; import 'package:styled_text/styled_text.dart'; +@RoutePage(name: 'AuthorizeDeviceEmailPin') class AuthorizeDeviceViaEmailPin extends StatelessWidget { AuthorizeDeviceViaEmailPin({Key? key}) : super(key: key); @@ -15,8 +16,9 @@ class AuthorizeDeviceViaEmailPin extends StatelessWidget { .i18n .replaceFirst('%s', '$emailAddress'), style: tsBody1, - styles: { - 'highlight': TextStyle(color: blue4, fontWeight: FontWeight.bold), + tags: { + 'highlight': StyledTextTag( + style: TextStyle(color: blue4, fontWeight: FontWeight.bold)), }, ); } diff --git a/lib/account/device_linking/explanation_step.dart b/lib/account/device_linking/explanation_step.dart new file mode 100644 index 000000000..f53b85e79 --- /dev/null +++ b/lib/account/device_linking/explanation_step.dart @@ -0,0 +1,30 @@ +import 'package:lantern/common/common.dart'; + +class ExplanationStep extends StatelessWidget { + ExplanationStep(this.icon, this.text); + + String icon; + String text; + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + margin: const EdgeInsetsDirectional.only(end: 16), + child: CAssetImage( + path: icon, + color: Colors.black, + ), + ), + Flexible( + child: CText( + text, + style: tsBody1, + ), + ), + ], + ); + } +} diff --git a/lib/account/device_linking/link_device.dart b/lib/account/device_linking/link_device.dart new file mode 100644 index 000000000..bdd0dd8ba --- /dev/null +++ b/lib/account/device_linking/link_device.dart @@ -0,0 +1,69 @@ +import 'package:lantern/common/common.dart'; +import 'explanation_step.dart'; + +@RoutePage(name: 'LinkDevice') +class LinkDevice extends StatefulWidget { + LinkDevice({ + Key? key, + }) : super(key: key); + + @override + State createState() => _LinkDeviceState(); +} + +class _LinkDeviceState extends State { + + @override + void initState() { + super.initState(); + sessionModel.requestLinkCode(); + } + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: 'Authorize Device for Pro'.i18n, + body: sessionModel.deviceLinkingCode((BuildContext context, + String deviceCode, Widget? child) => + Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsetsDirectional.only(top: 24), + child: CText( + 'device_linking_pin'.i18n, + textAlign: TextAlign.center, + style: tsSubtitle1, + )), + Text( + deviceCode, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 48, + color: pink4, + ), + ), + LabeledDivider( + padding: const EdgeInsetsDirectional.only(top: 10, bottom: 10), + ), + Container( + margin: const EdgeInsetsDirectional.only(bottom: 16), + child: ExplanationStep( + ImagePaths.number_1, 'link_device_step_one'.i18n), + ), + ExplanationStep(ImagePaths.number_2, 'link_device_step_two'.i18n), + Flexible( + child: CDivider( + height: 26, + ), + ), + CText( + 'ensure_most_recent_version_lantern'.i18n, + textAlign: TextAlign.justify, + style: tsBody2, + ), + ], + )), + ); + } +} diff --git a/lib/account/invite_friends.dart b/lib/account/invite_friends.dart new file mode 100644 index 000000000..e7de7044b --- /dev/null +++ b/lib/account/invite_friends.dart @@ -0,0 +1,69 @@ +import 'package:lantern/common/common.dart'; +import 'package:share_plus/share_plus.dart'; +@RoutePage(name: 'InviteFriends') +class InviteFriends extends StatelessWidget { + @override + Widget build(BuildContext context) { + var referralCodeCopied = false; + return BaseScreen( + title: 'Invite Friends'.i18n, + body: sessionModel.referralCode((BuildContext context, + String referralCode, Widget? child) => + Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsetsDirectional.only(start: 4.0, end: 4.0), + ), + StatefulBuilder( + builder: (context, setState) => ListItemFactory.settingsItem( + header: 'referral_code'.i18n, + content: referralCode, + icon: ImagePaths.star, + onTap: () async { + copyText(context, referralCode); + setState(() => referralCodeCopied = true); + await Future.delayed( + defaultAnimationDuration, + () => setState(() => referralCodeCopied = false), + ); + }, + trailingArray: [ + mirrorLTR( + context: context, + child: referralCodeCopied + ? Icon( + Icons.check_circle, + color: indicatorGreen, + ) + : Icon( + Icons.file_copy, + color: black, + )), + ])), + Container( + padding: const EdgeInsetsDirectional.only( + top: 24, start: 12, end: 12), + child: CText( + 'share_lantern_pro'.i18n, + textAlign: TextAlign.justify, + style: tsBody2, + ), + ), + const Spacer(), + Container( + margin: const EdgeInsetsDirectional.only(bottom: 56), + child: Button( + width: 200, + text: 'share_referral_code'.i18n, + onPressed: () async => await Share.share( + 'share_message_referral_code' + .i18n + .fill([referralCode])), + )), + ], + )), + ); + } +} diff --git a/lib/account/language.dart b/lib/account/language.dart index ea10e1445..9fe4c194d 100644 --- a/lib/account/language.dart +++ b/lib/account/language.dart @@ -2,6 +2,7 @@ import 'package:intl/intl.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/i18n/localization_constants.dart'; +@RoutePage(name: 'Language') class Language extends StatelessWidget { Language({Key? key}) : super(key: key); diff --git a/lib/account/lantern_desktop.dart b/lib/account/lantern_desktop.dart new file mode 100644 index 000000000..223e958ae --- /dev/null +++ b/lib/account/lantern_desktop.dart @@ -0,0 +1,47 @@ +import 'package:lantern/common/common.dart'; +import 'package:share_plus/share_plus.dart'; +import 'package:url_launcher/url_launcher.dart'; + +const SHARE_LINK = 'https://github.com/getlantern/lantern'; + +@RoutePage(name: 'LanternDesktop') +class LanternDesktop extends StatelessWidget { + const LanternDesktop({super.key}); + + @override + Widget build(BuildContext context) { + return BaseScreen( + title: 'lantern_desktop'.i18n, + body: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Padding( + padding: EdgeInsetsDirectional.only(top: 24, bottom: 32), + child: CAssetImage(path: ImagePaths.lantern_desktop, size: 136), + ), + InkWell( + child: CText(SHARE_LINK, style: tsBody3.copiedWith(color: blue4)), + onTap: () => launch(SHARE_LINK)), + Padding( + padding: + const EdgeInsetsDirectional.only(top: 24, start: 12, end: 12), + child: CText( + 'most_recent_lantern_apps'.i18n, + textAlign: TextAlign.justify, + style: tsBody2, + ), + ), + const Spacer(), + Container( + margin: const EdgeInsetsDirectional.only(bottom: 56), + child: Button( + width: 200, + text: 'share_link'.i18n, + onPressed: () async => await Share.share(SHARE_LINK), + )), + ], + ), + ); + } +} diff --git a/lib/account/privacy_disclosure.dart b/lib/account/privacy_disclosure.dart new file mode 100644 index 000000000..c7fac560c --- /dev/null +++ b/lib/account/privacy_disclosure.dart @@ -0,0 +1,38 @@ +import 'package:lantern/common/common.dart'; + +class PrivacyDisclosure extends StatelessWidget { + @override + Widget build(BuildContext context) { + return FullScreenDialog( + widget: Padding( + padding: EdgeInsetsDirectional.only( + start: 33, end: 33), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Container( + margin: const EdgeInsetsDirectional.only(top: 38), + child: CText( + 'privacy_disclosure_title'.i18n, + style: tsSubtitle1.copiedWith(fontSize: 24.0), + )), + Padding( + padding: EdgeInsetsDirectional.only(top: 24, bottom: 24), + child: CText( + 'privacy_disclosure_body'.i18n, + style: tsBody2.copiedWith(fontSize: 14.0, lineHeight: 24.0), + ), + ), + const Spacer(), + Container( + margin: const EdgeInsetsDirectional.only(bottom: 38), + child: Button( + width: 200, + text: 'privacy_disclosure_accept'.i18n, + onPressed: () async => await sessionModel.acceptTerms(), + )), + ])), + ); + } +} diff --git a/lib/account/recovery_key.dart b/lib/account/recovery_key.dart index fb5b74fc6..b388c395f 100644 --- a/lib/account/recovery_key.dart +++ b/lib/account/recovery_key.dart @@ -1,5 +1,6 @@ import 'package:lantern/messaging/messaging.dart'; +@RoutePage(name: 'RecoveryKey') class RecoveryKey extends StatelessWidget { RecoveryKey({Key? key}) : super(key: key); diff --git a/lib/account/report_issue.dart b/lib/account/report_issue.dart new file mode 100644 index 000000000..cf2f134ec --- /dev/null +++ b/lib/account/report_issue.dart @@ -0,0 +1,204 @@ +import 'package:email_validator/email_validator.dart'; +import 'package:lantern/common/common.dart'; + +bool isEmpty(value) => value == null || value == ''; + +@RoutePage(name: 'ReportIssue') +class ReportIssue extends StatefulWidget { + ReportIssue({ + Key? key, + }) : super(key: key); + + @override + State createState() => _ReportIssueState(); +} + +class _ReportIssueState extends State { + final emailFieldKey = GlobalKey(); + late final emailController = CustomTextEditingController( + formKey: emailFieldKey, + validator: (value) => EmailValidator.validate(value ?? '') + ? null + : 'please_enter_a_valid_email_address'.i18n, + ); + final issueFieldKey = GlobalKey(); + late final issueController = CustomTextEditingController( + formKey: issueFieldKey, + validator: (value) => !isEmpty(value) ? null : 'select_an_issue'.i18n, + ); + + final descFieldKey = GlobalKey(); + late final descController = CustomTextEditingController( + formKey: descFieldKey, + validator: (value) => !isEmpty(value) ? null : 'enter_description'.i18n, + ); + + @override + void dispose() { + emailController.dispose(); + issueController.dispose(); + descController.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return sessionModel.emailAddress(( + BuildContext context, + String emailAddress, + Widget? child, + ) { + return sessionModel + .proUser((BuildContext context, bool proUser, Widget? child) { + return BaseScreen( + title: 'report_an_issue'.i18n, + resizeToAvoidBottomInset: false, + body: Padding( + padding: const EdgeInsetsDirectional.only( + start: 25, + end: 23, + ), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // * Email field + Container( + margin: const EdgeInsetsDirectional.only( + top: 24, + bottom: 8, + ), + child: Form( + key: emailFieldKey, + child: CTextField( + initialValue: proUser ? emailAddress : '', + controller: emailController, + autovalidateMode: proUser + ? AutovalidateMode.always + : AutovalidateMode.disabled, + label: 'email'.i18n, + keyboardType: TextInputType.emailAddress, + prefixIcon: const CAssetImage(path: ImagePaths.email), + ), + ), + ), + Container( + margin: const EdgeInsetsDirectional.only( + top: 8, + bottom: 8, + ), + child: Form( + key: issueFieldKey, + child: DropdownButtonFormField( + decoration: InputDecoration( + border: OutlineInputBorder( + borderSide: BorderSide( + width: 1, + color: grey3, + ), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + width: 2, + color: blue4, + ), + ), + prefixIcon: Transform.scale( + scale: 0.4, + child: const CAssetImage( + path: ImagePaths.alert))), + hint: + CText('select_an_issue'.i18n, style: tsBody1), + value: issueController.text != '' + ? issueController.text + : null, + icon: const CAssetImage( + path: ImagePaths.arrow_down), + //iconSize: iconSize, + elevation: 16, + onChanged: (String? newValue) { + issueController.text = newValue!; + }, + items: [ + '', + 'cannot_access_blocked_sites'.i18n, + 'cannot_complete_purchase'.i18n, + 'cannot_sign_in'.i18n, + 'spinner_loads_endlessly'.i18n, + 'other'.i18n + ].map>((String value) { + return DropdownMenuItem( + value: value, + child: CText(value, style: tsBody1), + ); + }).toList(), + ))), + Container( + margin: const EdgeInsetsDirectional.only( + top: 8, + ), + child: Form( + key: descFieldKey, + child: CTextField( + tooltipMessage: AppKeys.reportDescription, + controller: descController, + contentPadding: EdgeInsetsDirectional.all(8.0), + label: '', + hintText: 'issue_description'.i18n, + autovalidateMode: AutovalidateMode.disabled, + maxLines: 8, + keyboardType: TextInputType.multiline, + ), + )), + const Spacer(), + Container( + padding: const EdgeInsetsDirectional.only(bottom: 56), + child: Tooltip( + message: AppKeys.sendReport, + child: Button( + width: 200, + disabled: emailFieldKey.currentState + ?.validate() == + false || + issueFieldKey.currentState?.validate() == + false || + descFieldKey.currentState?.validate() == + false, + text: 'send_report'.i18n, + onPressed: () async { + await sessionModel + .reportIssue( + emailController.value.text, + issueController.value.text, + descController.value.text) + .then((value) async { + CDialog.showInfo( + context, + title: 'report_sent'.i18n, + description: + 'thank_you_for_reporting_your_issue' + .i18n, + actionLabel: 'continue'.i18n, + agreeAction: () async { + await context.pushRoute(Support()); + return true; + }, + ); + }).onError((error, stackTrace) { + CDialog.showError( + context, + error: e, + stackTrace: stackTrace, + description: (error as PlatformException) + .message + .toString(), // This is coming localized + ); + }); + }, + ))), + ])), + ); + }); + }); + } +} diff --git a/lib/account/settings.dart b/lib/account/settings.dart index 071763158..c5c38226e 100644 --- a/lib/account/settings.dart +++ b/lib/account/settings.dart @@ -4,14 +4,34 @@ import 'package:lantern/i18n/localization_constants.dart'; import 'package:lantern/messaging/messaging_model.dart'; import 'package:package_info_plus/package_info_plus.dart'; +@RoutePage(name: 'Settings') class Settings extends StatelessWidget { Settings({Key? key}) : super(key: key); final packageInfo = PackageInfo.fromPlatform(); - void changeLanguage(BuildContext context) => context.pushRoute(Language()); + void changeLanguage(BuildContext context) async => + await context.pushRoute(Language()); - void checkForUpdates() async => await sessionModel.checkForUpdates(); + void reportIssue(BuildContext context) async => + await context.pushRoute(ReportIssue()); + + void showProgressDialog(BuildContext context) { + showDialog( + context: context, + barrierDismissible: false, + barrierColor: Colors.transparent, + builder: (BuildContext context) { + return Center( + child: SizedBox( + width: 40.0, + height: 40.0, + child: CircularProgressIndicator( + valueColor: AlwaysStoppedAnimation(grey5), + ), + )); + }); + } void openSplitTunneling(BuildContext context) => context.pushRoute(SplitTunneling()); @@ -49,13 +69,28 @@ class Settings extends StatelessWidget { mirrorLTR(context: context, child: const ContinueArrow()) ], ), + //* Report + ListItemFactory.settingsItem( + icon: ImagePaths.alert, + content: 'report_issue'.i18n, + trailingArray: [ + mirrorLTR(context: context, child: const ContinueArrow()) + ], + onTap: () { + reportIssue(context); + }, + ), ListItemFactory.settingsItem( icon: ImagePaths.update, content: 'check_for_updates'.i18n, trailingArray: [ mirrorLTR(context: context, child: const ContinueArrow()) ], - onTap: checkForUpdates, + onTap: () async { + showProgressDialog(context); + await sessionModel.checkForUpdates(); + Navigator.pop(context); + }, ), //* Blocked messagingModel.getOnBoardingStatus( @@ -75,7 +110,7 @@ class Settings extends StatelessWidget { : const SizedBox(), ), //* Split tunneling - vpnModel.splitTunneling( + sessionModel.splitTunneling( (BuildContext context, bool value, Widget? child) => ListItemFactory.settingsItem( header: 'VPN'.i18n, diff --git a/lib/vpn/vpn_split_tunneling.dart b/lib/account/split_tunneling.dart similarity index 94% rename from lib/vpn/vpn_split_tunneling.dart rename to lib/account/split_tunneling.dart index 7a04c27e0..63844c95b 100644 --- a/lib/vpn/vpn_split_tunneling.dart +++ b/lib/account/split_tunneling.dart @@ -3,6 +3,7 @@ import 'package:intl/intl.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/vpn/vpn.dart'; +@RoutePage(name: 'SplitTunneling') class SplitTunneling extends StatefulWidget { SplitTunneling({Key? key}); @@ -23,7 +24,7 @@ class _SplitTunnelingState extends State { } void init() async { - unawaited(vpnModel.refreshAppsList()); + unawaited(sessionModel.refreshAppsList()); var _vpnConnected = await vpnModel.isVpnConnected(); setState(() { vpnConnected = _vpnConnected; @@ -34,9 +35,9 @@ class _SplitTunnelingState extends State { Widget build(BuildContext context) { return BaseScreen( title: 'split_tunneling'.i18n, - body: vpnModel.splitTunneling( + body: sessionModel.splitTunneling( (BuildContext context, bool splitTunnelingEnabled, Widget? child) { - return vpnModel.appsData( + return sessionModel.appsData( builder: ( context, Iterable> _appsData, @@ -56,7 +57,7 @@ class _SplitTunnelingState extends State { activeColor: CupertinoColors.activeGreen, onChanged: (bool? value) { var newValue = value ?? false; - vpnModel.setSplitTunneling(newValue); + sessionModel.setSplitTunneling(newValue); showRestartVPNSnackBar(context); }, ), @@ -142,9 +143,9 @@ class _SplitTunnelingState extends State { var allowOrDenyAppAccess = () { if (appData.allowedAccess) { - vpnModel.denyAppAccess(packageName); + sessionModel.denyAppAccess(packageName); } else { - vpnModel.allowAppAccess(packageName); + sessionModel.allowAppAccess(packageName); } showRestartVPNSnackBar(context); }; @@ -199,7 +200,7 @@ class _SplitTunnelingState extends State { class SplitTunnelingWidget extends StatelessWidget { @override Widget build(BuildContext context) { - return vpnModel.splitTunneling( + return sessionModel.splitTunneling( (BuildContext context, bool value, Widget? child) => InkWell( onTap: () { Navigator.push( diff --git a/lib/account/support.dart b/lib/account/support.dart index 1c331249d..022ab72e6 100644 --- a/lib/account/support.dart +++ b/lib/account/support.dart @@ -1,7 +1,7 @@ import 'package:url_launcher/url_launcher.dart'; - import '../common/common.dart'; +@RoutePage(name: 'Support') class Support extends StatelessWidget { const Support({Key? key}) : super(key: key); @@ -27,7 +27,9 @@ class Support extends StatelessWidget { trailingArray: [ mirrorLTR(context: context, child: const ContinueArrow()) ], - onTap: reportIssue, + onTap: () { + reportIssue(context); + }, ), ListItemFactory.settingsItem( content: 'lantern_user_forum'.i18n, @@ -67,8 +69,7 @@ class Support extends StatelessWidget { // class methods and utils - void reportIssue() async => - LanternNavigator.startScreen(LanternNavigator.SCREEN_SCREEN_REPORT_ISSUE); + void reportIssue(BuildContext context) async => context.pushRoute(ReportIssue()); Future faqTap(BuildContext context) async { try { diff --git a/lib/ad_helper.dart b/lib/ad_helper.dart new file mode 100644 index 000000000..66079b996 --- /dev/null +++ b/lib/ad_helper.dart @@ -0,0 +1,270 @@ +import 'dart:io'; + +import 'package:clever_ads_solutions/CAS.dart'; +import 'package:clever_ads_solutions/public/AdCallback.dart'; +import 'package:clever_ads_solutions/public/AdImpression.dart'; +import 'package:clever_ads_solutions/public/AdTypes.dart'; +import 'package:clever_ads_solutions/public/Audience.dart'; +import 'package:clever_ads_solutions/public/ConsentFlow.dart'; +import 'package:clever_ads_solutions/public/InitConfig.dart'; +import 'package:clever_ads_solutions/public/InitializationListener.dart'; +import 'package:clever_ads_solutions/public/MediationManager.dart'; +import 'package:clever_ads_solutions/public/OnDismissListener.dart'; +import 'package:flutter/foundation.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:lantern/replica/common.dart'; + +import 'common/session_model.dart'; + +enum AdType { Google, CAS } + +const privacyPolicy = 'https://lantern.io/privacy'; + +class AdHelper { + static final AdHelper _instance = AdHelper._internal(); + + AdHelper._internal(); + + factory AdHelper() { + return _instance; + } + + AdType? _currentAdType; + MediationManager? casMediationManager; + InterstitialAd? _interstitialAd; + int _failedLoadAttempts = 0; + int _failedCASLoadAttempts = 0; + + //If ads are getting failed to load we want to make lot of calls + // Just try 5 times + final int _maxFailAttempts = 5; + + //Google Test ID if needed to test + // return 'ca-app-pub-3940256099942544/1033173712'; + String get interstitialAdUnitId { + if (Platform.isAndroid) { + return const String.fromEnvironment('INTERSTITIAL_AD_UNIT_ID'); + } else { + throw UnsupportedError('Unsupported platform'); + } + } + + // Private methods to decide whether to load or show Google Ads or CAS ads based on conditions + Future _decideAndLoadAds() async { + final shouldShowGoogleAds = await sessionModel.shouldShowAds(); + final shouldShowCASAds = await sessionModel.shouldCASShowAds(); + + logger.d( + '[Ads Manager] Google Ads enable $shouldShowGoogleAds: CAS Ads $shouldShowCASAds'); + if (shouldShowGoogleAds) { + _currentAdType = AdType.Google; + logger.i('[Ads Manager] Decision: Loading Google Ads.'); + await _loadInterstitialAd(); + } else if (shouldShowCASAds) { + _currentAdType = AdType.CAS; + logger.i('[Ads Manager] Decision: Loading CAS Ads.'); + if (casMediationManager == null) { + await _initializeCAS(); + } + await _loadCASInterstitial(); + } + } + + Future _decideAndShowAds() async { + if (_currentAdType == AdType.Google && _interstitialAd != null) { + await _showInterstitialAd(); + } else if (_currentAdType == AdType.CAS) { + final isCASReady = (await casMediationManager!.isInterstitialReady()); + if (isCASReady) { + await _showCASInterstitial(); + logger.i('[Ads Manager] Request: Showing CAS Ad .'); + } else { + logger.i('[Ads Manager] CAS: Ad is not yet ready to show.'); + } + } + } + + Future _loadInterstitialAd() async { + //To avoid calling multiple ads request repeatedly + if (_interstitialAd == null && _failedLoadAttempts < _maxFailAttempts) { + logger.i('[Ads Manager] Request: Making Google Ad request.'); + await InterstitialAd.load( + adUnitId: interstitialAdUnitId, + request: const AdRequest(), + adLoadCallback: InterstitialAdLoadCallback( + onAdLoaded: (ad) { + ad.fullScreenContentCallback = FullScreenContentCallback( + onAdClicked: (ad) { + logger.i('[Ads Manager] onAdClicked callback'); + }, + onAdShowedFullScreenContent: (ad) { + logger.i('[Ads Manager] Showing Ads'); + }, + onAdFailedToShowFullScreenContent: (ad, error) { + logger.i( + '[Ads Manager] onAdFailedToShowFullScreenContent callback'); + //if ads fail to load let user turn on VPN + _postShowingAds(); + }, + onAdDismissedFullScreenContent: (ad) { + logger.i('[Ads Manager] fullScreenContentCallback callback'); + _postShowingAds(); + }, + ); + _interstitialAd = ad; + logger.i('[Ads Manager] to loaded $ad'); + }, + onAdFailedToLoad: (err) { + _failedLoadAttempts++; // increment the count on failure + logger.i('[Ads Manager] failed to load $err'); + _postShowingAds(); + }, + ), + ); + } + } + + void _postShowingAds() { + if (_currentAdType == AdType.Google) { + _interstitialAd?.dispose(); + _interstitialAd = null; + _failedLoadAttempts = 0; // Reset counter for Google Ads + logger.i( + '[Ads Manager] Post-show: Google Ad displayed. Resetting failed load attempts and requesting a new ad.'); + _loadInterstitialAd(); + } else if (_currentAdType == AdType.CAS) { + _failedCASLoadAttempts = 0; // Reset counter for CAS Ads + logger.i( + '[Ads Manager] Post-show: CAS Ad displayed. Resetting failed load attempts and requesting a new ad.'); + _loadCASInterstitial(); + } + } + + Future _showInterstitialAd() async { + if (_interstitialAd != null) { + await _interstitialAd?.show(); + } + } + + // Public methods + Future loadAds() async { + await _decideAndLoadAds(); + } + + Future showAds() async { + await _decideAndShowAds(); + } + + ///CAS initialization and method and listeners + /// + Future _initializeCAS() async { + await CAS.setDebugMode(kDebugMode); + await CAS.setAnalyticsCollectionEnabled(true); + // CAS.setFlutterVersion("1.20.0"); + // await CAS.validateIntegration(); + + var builder = CAS + .buildManager() + .withCasId('org.getlantern.lantern') + .withAdTypes(AdTypeFlags.Interstitial) + .withInitializationListener(InitializationListenerWrapper()) + .withTestMode(false); + + CAS.buildConsentFlow().withPrivacyPolicy(privacyPolicy); + casMediationManager = builder.initialize(); + // This can be useful when you need to improve application performance by turning off unused formats. + await casMediationManager!.setEnabled(AdTypeFlags.Interstitial, true); + await CAS.setTaggedAudience(Audience.NOT_CHILDREN); + // await CAS.setTestDeviceIds(['D79728264130CE0918737B5A2178D362']); + logger.i('[Ads Manager] Initialization: CAS completed.'); + } + + Future _loadCASInterstitial() async { + if (casMediationManager != null) { + await casMediationManager!.loadInterstitial(); + logger.i('[Ads Manager] Request: Initiating CAS Interstitial loading.'); + } + } + + Future _showCASInterstitial() async { + logger.i('[Ads Manager] Show: Attempting to display CAS Interstitial.'); + await casMediationManager!.showInterstitial(InterstitialListenerWrapper( + onFailed: _onCASAdShowFailed, + onClosedOrComplete: _onCASAdClosedOrComplete)); + } + + void _onCASAdShowFailed() { + logger.e('[Ads Manager] Error: CAS Interstitial failed to display.'); + _failedCASLoadAttempts++; + _postShowingAds(); // Reload or decide the next action + } + + void _onCASAdClosedOrComplete() { + logger.i('[Ads Manager] Completion: CAS Interstitial closed or completed.'); + // Reset the counter when the ad successfully shows and closes/completes + _failedCASLoadAttempts = 0; + _postShowingAds(); + } + + //This method will use used when free user buy Pro version + Future turnOffCASInterstitial() async { + await casMediationManager!.setEnabled(AdTypeFlags.Interstitial, false); + } +} + +class InitializationListenerWrapper extends InitializationListener { + @override + void onCASInitialized(InitConfig initialConfig) { + logger.i('[CASIntegrationHelper] - onCASInitialized $initialConfig'); + } +} + +class InterstitialListenerWrapper extends AdCallback { + final VoidCallback onFailed; + final VoidCallback onClosedOrComplete; + + InterstitialListenerWrapper({ + required this.onFailed, + required this.onClosedOrComplete, + }); + + @override + void onClicked() { + logger.i('[CASIntegrationHelper] - InterstitialListenerWrapper onClicked'); + } + + @override + void onClosed() { + // Called when ad is clicked + onClosedOrComplete(); + logger.i('[CASIntegrationHelper] - InterstitialListenerWrapper onClosed'); + } + + @override + void onComplete() { + // Called when ad is dismissed + onClosedOrComplete(); + logger.i('[CASIntegrationHelper] - InterstitialListenerWrapper onComplete'); + } + + @override + void onImpression(AdImpression? adImpression) { + // Called when ad is paid. + logger.i( + '[CASIntegrationHelper] - InterstitialListenerWrapper onImpression-:$adImpression'); + } + + @override + void onShowFailed(String? message) { + // Called when ad fails to show. + onFailed.call(); + logger.i( + '[CASIntegrationHelper] - InterstitialListenerWrapper onShowFailed-:$message'); + } + + @override + void onShown() { + // Called when ad is shown. + logger.i('[CASIntegrationHelper] - InterstitialListenerWrapper onShown'); + } +} diff --git a/lib/app.dart b/lib/app.dart index 949767836..a14bb5fb8 100644 --- a/lib/app.dart +++ b/lib/app.dart @@ -1,11 +1,10 @@ -import 'dart:ui'; - import 'package:flutter/scheduler.dart'; import 'package:lantern/common/common.dart'; +import 'package:lantern/core/router/router.dart'; import 'package:lantern/messaging/messaging.dart'; final navigatorKey = GlobalKey(); -final globalRouter = AppRouter(navigatorKey); +final globalRouter = AppRouter(); final networkWarningBarHeightRatio = ValueNotifier(0.0); var showConnectivityWarning = false; @@ -71,22 +70,14 @@ class LanternApp extends StatelessWidget { @override Widget build(BuildContext context) { - final currentLocal = window.locale; - print('selected local: ' + currentLocal.languageCode); + final currentLocal = View.of(context).platformDispatcher.locale; + print('selected local: ${currentLocal.languageCode}'); return FutureBuilder( future: translations, builder: (BuildContext context, AsyncSnapshot snapshot) { if (!snapshot.hasData) { return Container(); } - final ThemeData theme = ThemeData( - fontFamily: _getLocaleBasedFont(currentLocal), - brightness: Brightness.light, - primarySwatch: Colors.grey, - appBarTheme: const AppBarTheme( - systemOverlayStyle: SystemUiOverlayStyle.light, - ), - ); return GlobalLoaderOverlay( overlayColor: Colors.black, overlayOpacity: 0.6, @@ -105,33 +96,28 @@ class LanternApp extends StatelessWidget { ColorScheme.fromSwatch().copyWith(secondary: Colors.black), ), title: 'app_name'.i18n, - localizationsDelegates: [ + localizationsDelegates: const [ GlobalMaterialLocalizations.delegate, GlobalWidgetsLocalizations.delegate, GlobalCupertinoLocalizations.delegate, ], routeInformationParser: globalRouter.defaultRouteParser(), - routerDelegate: globalRouter.delegate( - navigatorObservers: () => [ - BotToastNavigatorObserver(), - ], - ), - builder: BotToastInit(), - supportedLocales: [ - const Locale('ar', 'EG'), - const Locale('fr', 'FR'), - const Locale('en', 'US'), - const Locale('fa', 'IR'), - const Locale('th', 'TH'), - const Locale('ms', 'MY'), - const Locale('ru', 'RU'), - const Locale('ur', 'IN'), - const Locale('zh', 'CN'), - const Locale('zh', 'HK'), - const Locale('es', 'ES'), - const Locale('tr', 'TR'), - const Locale('vi', 'VN'), - const Locale('my', 'MM'), + routerDelegate: globalRouter.delegate(), + supportedLocales: const [ + Locale('ar', 'EG'), + Locale('fr', 'FR'), + Locale('en', 'US'), + Locale('fa', 'IR'), + Locale('th', 'TH'), + Locale('ms', 'MY'), + Locale('ru', 'RU'), + Locale('ur', 'IN'), + Locale('zh', 'CN'), + Locale('zh', 'HK'), + Locale('es', 'ES'), + Locale('tr', 'TR'), + Locale('vi', 'VN'), + Locale('my', 'MM'), ], ), ), diff --git a/lib/common/analytics.dart b/lib/common/analytics.dart deleted file mode 100644 index e69de29bb..000000000 diff --git a/lib/common/app_keys.dart b/lib/common/app_keys.dart index 6e8a9f9db..2b593676c 100644 --- a/lib/common/app_keys.dart +++ b/lib/common/app_keys.dart @@ -29,6 +29,7 @@ class AppKeys { //Support static const reportIssue = ValueKey('report_issue'); - + static const reportDescription = 'report_description'; + static const sendReport = 'send_report'; } diff --git a/lib/common/common.dart b/lib/common/common.dart index e0876df78..64a1b3506 100644 --- a/lib/common/common.dart +++ b/lib/common/common.dart @@ -6,7 +6,6 @@ export 'dart:typed_data'; export 'package:auto_route/auto_route.dart'; export 'package:back_button_interceptor/back_button_interceptor.dart'; -export 'package:bot_toast/bot_toast.dart'; export 'package:dotted_border/dotted_border.dart'; export 'package:flag/flag.dart'; export 'package:flutter/foundation.dart'; @@ -22,7 +21,6 @@ export 'package:lantern/core/router/router.gr.dart'; export 'package:lantern/event_extension.dart'; export 'package:lantern/event_manager.dart'; export 'package:lantern/i18n/i18n.dart'; -export 'package:lantern/lantern_navigator.dart'; export 'package:lantern/vpn/protos_shared/vpn.pb.dart'; export 'package:lantern/vpn/vpn_model.dart'; export 'package:loader_overlay/loader_overlay.dart'; @@ -50,6 +48,19 @@ export 'ui/continue_arrow.dart'; export 'ui/copy_text.dart'; export 'ui/countdown_min_sec.dart'; export 'ui/countdown_stopwatch.dart'; +// custom components +export 'ui/custom/asset_image.dart'; +export 'ui/custom/badge.dart'; +export 'ui/custom/dialog.dart'; +export 'ui/custom/divider.dart'; +export 'ui/custom/fullscreen_image_viewer.dart'; +export 'ui/custom/fullscreen_video_viewer.dart'; +export 'ui/custom/fullscreen_viewer.dart'; +export 'ui/custom/ink_well.dart'; +export 'ui/custom/list_item_factory.dart'; +export 'ui/custom/rounded_rectangle_border.dart'; +export 'ui/custom/text.dart'; +export 'ui/custom/text_field.dart'; export 'ui/dimens.dart'; export 'ui/focused_menu.dart'; export 'ui/full_screen_dialog.dart'; @@ -58,6 +69,7 @@ export 'ui/humanize_past_future.dart'; export 'ui/humanize_seconds.dart'; export 'ui/humanized_date.dart'; export 'ui/image_paths.dart'; +export 'ui/info_text_box.dart'; export 'ui/labeled_divider.dart'; export 'ui/list_section_header.dart'; export 'ui/now_builder.dart'; diff --git a/lib/common/session_model.dart b/lib/common/session_model.dart index 8924a61d5..776b14cc3 100644 --- a/lib/common/session_model.dart +++ b/lib/common/session_model.dart @@ -67,6 +67,19 @@ class SessionModel extends Model { }); } + Future acceptTerms() { + return methodChannel.invokeMethod('acceptTerms', { + 'on': true, + }); + } + + Widget acceptedTermsVersion(ValueWidgetBuilder builder) { + return subscribedSingleValueBuilder( + 'accepted_terms_version', + builder: builder, + ); + } + Widget forceCountry(ValueWidgetBuilder builder) { return subscribedSingleValueBuilder( 'forceCountry', @@ -115,6 +128,13 @@ class SessionModel extends Model { ); } + Widget referralCode(ValueWidgetBuilder builder) { + return subscribedSingleValueBuilder( + 'referral', + builder: builder, + ); + } + Widget deviceId(ValueWidgetBuilder builder) { return subscribedSingleValueBuilder('deviceid', builder: builder); } @@ -137,6 +157,21 @@ class SessionModel extends Model { ); } + Future getCountryCode() async { + return await methodChannel + .invokeMethod('getCountryCode', {}); + } + + Future shouldShowAds() async { + return await methodChannel + .invokeMethod('shouldShowAds', {}); + } + + Future shouldCASShowAds() async { + return await methodChannel + .invokeMethod('shouldCASShowAds', {}); + } + Future setLanguage(String lang) { return methodChannel.invokeMethod('setLanguage', { 'lang': lang, @@ -268,6 +303,19 @@ class SessionModel extends Model { }).then((value) => value as String); } + Future reportIssue( + String email, + String issue, + String description + ) async { + return methodChannel.invokeMethod('reportIssue', { + 'email': email, + 'issue': issue, + 'description': description + }).then((value) => value as String); + } + + Widget getUserId(ValueWidgetBuilder builder) { return subscribedSingleValueBuilder( 'userId', @@ -284,6 +332,20 @@ class SessionModel extends Model { ); } + Future requestLinkCode() { + return methodChannel + .invokeMethod('requestLinkCode') + .then((value) => value as String); + } + + Widget deviceLinkingCode(ValueWidgetBuilder builder) { + return subscribedSingleValueBuilder( + 'devicelinkingcode', + defaultValue: '', + builder: builder, + ); + } + Future redeemResellerCode( String email, String resellerCode, @@ -355,4 +417,47 @@ class SessionModel extends Model { 'url': url, }); } + + Future refreshAppsList() async { + await methodChannel.invokeMethod('refreshAppsList'); + } + + Widget splitTunneling(ValueWidgetBuilder builder) { + return subscribedSingleValueBuilder( + '/splitTunneling', + builder: builder, + ); + } + + Future setSplitTunneling(bool on) async { + unawaited( + methodChannel.invokeMethod('setSplitTunneling', { + 'on': on, + }), + ); + } + + Widget appsData({ + required ValueWidgetBuilder>> builder, + }) { + return subscribedListBuilder( + '/appsData/', + builder: builder, + deserialize: (Uint8List serialized) { + return AppData.fromBuffer(serialized); + }, + ); + } + + Future allowAppAccess(String packageName) { + return methodChannel.invokeMethod('allowAppAccess', { + 'packageName': packageName, + }); + } + + Future denyAppAccess(String packageName) { + return methodChannel.invokeMethod('denyAppAccess', { + 'packageName': packageName, + }); + } } diff --git a/lib/common/ui/base_screen.dart b/lib/common/ui/base_screen.dart index 33fca3ecd..2c6d5f6e3 100644 --- a/lib/common/ui/base_screen.dart +++ b/lib/common/ui/base_screen.dart @@ -142,9 +142,7 @@ class ConnectivityWarning extends StatelessWidget { description: 'connection_error_des'.i18n, agreeText: 'connection_error_button'.i18n, agreeAction: () async { - LanternNavigator.startScreen( - LanternNavigator.SCREEN_SCREEN_REPORT_ISSUE, - ); + await context.pushRoute(ReportIssue()); return true; }, ).show(context) diff --git a/lib/common/ui/custom/badge.dart b/lib/common/ui/custom/badge.dart index 4bf51d374..d7e0471c6 100644 --- a/lib/common/ui/custom/badge.dart +++ b/lib/common/ui/custom/badge.dart @@ -1,13 +1,13 @@ -import 'package:badges/badges.dart' as badges; -import 'package:lantern/common/common.dart'; +import 'package:badges/badges.dart'; +import 'package:lantern/common/common.dart' hide Badge; class CBadge extends StatelessWidget { final int count; final Widget child; final double fontSize; final bool showBadge; - final double? end; - final double? top; + final double end; + final double top; final Widget? customBadge; final EdgeInsetsGeometry? customPadding; @@ -29,25 +29,23 @@ class CBadge extends StatelessWidget { return child; } - return badges.Badge( - padding: (customPadding != null) - ? customPadding! - : (customBadge != null) - ? const EdgeInsetsDirectional.all(0) - : const EdgeInsetsDirectional.only( - start: 6, - end: 6, - top: 2, - bottom: 2, - ), - position: badges.BadgePosition( - end: end, - top: top, + return Badge( + position: BadgePosition.topEnd(top: top, end: end), + badgeAnimation: const BadgeAnimation.fade(), + badgeStyle: BadgeStyle( + badgeColor: (customBadge != null) ? Colors.white : pink4, + elevation: 0, + padding: (customPadding != null) + ? customPadding! + : (customBadge != null) + ? const EdgeInsetsDirectional.all(0) + : const EdgeInsetsDirectional.only( + start: 6, + end: 6, + top: 2, + bottom: 2, + ), ), - animationType: badges.BadgeAnimationType.fade, - elevation: 0, - // no drop-shadow - badgeColor: (customBadge != null) ? Colors.white : pink4, badgeContent: (customBadge != null) ? customBadge : CText( diff --git a/lib/common/ui/custom/fullscreen_video_viewer.dart b/lib/common/ui/custom/fullscreen_video_viewer.dart index be63282a3..598b69eeb 100644 --- a/lib/common/ui/custom/fullscreen_video_viewer.dart +++ b/lib/common/ui/custom/fullscreen_video_viewer.dart @@ -119,6 +119,9 @@ class FullScreenVideoViewerState return Stack( alignment: Alignment.center, children: [ + if (controller == null) + spinner + else ValueListenableBuilder( valueListenable: controller!, builder: ( diff --git a/lib/common/ui/full_screen_dialog.dart b/lib/common/ui/full_screen_dialog.dart index eb46b0225..d8d27430b 100644 --- a/lib/common/ui/full_screen_dialog.dart +++ b/lib/common/ui/full_screen_dialog.dart @@ -1,6 +1,7 @@ import 'package:lantern/common/common.dart'; /// Shows the supplied widget as a full screen dialog +@RoutePage(name: 'FullScreenDialogPage') class FullScreenDialog extends StatelessWidget { final Widget widget; diff --git a/lib/common/ui/image_paths.dart b/lib/common/ui/image_paths.dart index 228f74759..4adc97d73 100644 --- a/lib/common/ui/image_paths.dart +++ b/lib/common/ui/image_paths.dart @@ -78,6 +78,7 @@ class ImagePaths { static const account_remove = 'assets/images/account_remove.svg'; static const lantern_logo = 'assets/images/lantern_logo.svg'; static const lantern_star = 'assets/images/lantern_star.svg'; + static const lantern_desktop = 'assets/images/lantern_desktop.svg'; // Replica static const discover = 'assets/images/discover.svg'; static const audio = 'assets/images/audio_black.svg'; diff --git a/lib/core/router/router.dart b/lib/core/router/router.dart index c6c9e3e8a..9015c572a 100644 --- a/lib/core/router/router.dart +++ b/lib/core/router/router.dart @@ -1,319 +1,243 @@ import 'package:auto_route/auto_route.dart'; -import 'package:lantern/account/account_management.dart'; -import 'package:lantern/account/blocked_users.dart'; -import 'package:lantern/account/chat_number_account.dart'; -import 'package:lantern/account/device_linking/approve_device.dart'; -import 'package:lantern/account/device_linking/authorize_device_for_pro.dart'; -import 'package:lantern/account/device_linking/authorize_device_via_email.dart'; -import 'package:lantern/account/device_linking/authorize_device_via_email_pin.dart'; -import 'package:lantern/account/language.dart'; -import 'package:lantern/account/recovery_key.dart'; -import 'package:lantern/account/settings.dart'; -import 'package:lantern/account/support.dart'; -import 'package:lantern/common/ui/full_screen_dialog.dart'; import 'package:lantern/common/ui/transitions.dart'; -import 'package:lantern/home.dart'; -import 'package:lantern/messaging/contacts/add_contact_number.dart'; -import 'package:lantern/messaging/contacts/contact_info.dart'; -import 'package:lantern/messaging/contacts/new_chat.dart'; -import 'package:lantern/messaging/conversation/conversation.dart'; -import 'package:lantern/messaging/introductions/introduce.dart'; -import 'package:lantern/messaging/introductions/introductions.dart'; -import 'package:lantern/messaging/onboarding/chat_number_messaging.dart'; -import 'package:lantern/messaging/onboarding/chat_number_recovery.dart'; -import 'package:lantern/plans/checkout.dart'; -import 'package:lantern/plans/plans.dart'; -import 'package:lantern/plans/reseller_checkout.dart'; -import 'package:lantern/plans/stripe_checkout.dart'; -import 'package:lantern/replica/link_handler.dart'; -import 'package:lantern/replica/ui/viewers/audio.dart'; -import 'package:lantern/replica/ui/viewers/image.dart'; -import 'package:lantern/replica/ui/viewers/video.dart'; -import 'package:lantern/replica/ui/viewers/misc.dart'; -import 'package:lantern/replica/upload/title.dart'; -import 'package:lantern/replica/upload/description.dart'; -import 'package:lantern/replica/upload/review.dart'; -import 'package:lantern/vpn/vpn_split_tunneling.dart'; +import 'package:lantern/core/router/router.gr.dart'; -@AdaptiveAutoRouter( + + +@AutoRouterConfig( replaceInRouteName: 'Page,Route,Screen', - routes: [ - AutoRoute( - initial: true, - name: 'Home', - page: HomePage, - path: '/', - ), - CustomRoute( - page: FullScreenDialog, - name: 'FullScreenDialogPage', - path: 'fullScreenDialogPage', - transitionsBuilder: popupTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: AccountManagement, - name: 'AccountManagement', - path: 'accountManagement', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: Settings, - name: 'Settings', - path: 'settings', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: SplitTunneling, - name: 'SplitTunneling', - path: 'splitTunneling', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: Language, - name: 'Language', - path: 'language', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: AuthorizeDeviceForPro, - name: 'AuthorizePro', - path: 'authorizePro', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: AuthorizeDeviceViaEmail, - name: 'AuthorizeDeviceEmail', - path: 'authorizeDeviceEmail', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: AuthorizeDeviceViaEmailPin, - name: 'AuthorizeDeviceEmailPin', - path: 'authorizeDeviceEmailPin', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ApproveDevice, - name: 'ApproveDevice', - path: 'approveDevice', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), -// -// * CHAT ROUTES -// - CustomRoute( - page: RecoveryKey, - name: 'RecoveryKey', - path: 'recoveryKey', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ChatNumberRecovery, - name: 'ChatNumberRecovery', - path: 'chatNumberRecovery', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ChatNumberMessaging, - name: 'ChatNumberMessaging', - path: 'chatNumberMessaging', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: Conversation, - name: 'Conversation', - path: 'conversation', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ContactInfo, - name: 'ContactInfo', - path: 'contactInfo', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: NewChat, - name: 'NewChat', - path: 'newChat', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: AddViaChatNumber, - name: 'AddViaChatNumber', - path: 'addViaChatNumber', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: Introduce, - name: 'Introduce', - path: 'introduce', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: Introductions, - name: 'Introductions', - path: 'introductions', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ChatNumberAccount, - name: 'ChatNumberAccount', - path: 'chatNumberAccount', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: BlockedUsers, - name: 'BlockedUsers', - path: 'blockedUsers', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: Checkout, - name: 'Checkout', - path: 'checkout', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ResellerCodeCheckout, - name: 'ResellerCodeCheckout', - path: 'resellerCodeCheckout', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: StripeCheckout, - name: 'StripeCheckout', - path: 'stripeCheckout', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: PlansPage, - name: 'PlansPage', - path: 'plans', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), -// -// * REPLICA ROUTES -// - CustomRoute( - page: ReplicaUploadTitle, - name: 'ReplicaUploadTitle', - path: 'replicaUploadTitle', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ReplicaUploadDescription, - name: 'ReplicaUploadDescription', - path: 'replicaUploadDescription', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ReplicaUploadReview, - name: 'ReplicaUploadReview', - path: 'replicaUploadReview', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ReplicaLinkHandler, - name: 'ReplicaLinkHandler', - path: 'replicaLinkHandler', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ReplicaMiscViewer, - name: 'ReplicaMiscViewer', - path: 'replicaMiscViewer', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ReplicaImageViewer, - name: 'ReplicaImageViewer', - path: 'replicaImageViewer', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ReplicaVideoViewer, - name: 'ReplicaVideoViewer', - path: 'replicaVideoViewer', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: ReplicaAudioViewer, - name: 'ReplicaAudioViewer', - path: 'replicaAudioViewer', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - CustomRoute( - page: Support, - name: 'Support', - path: 'support', - transitionsBuilder: defaultTransition, - durationInMilliseconds: defaultTransitionMillis, - reverseDurationInMilliseconds: defaultTransitionMillis, - ), - ], ) -class $AppRouter {} +class AppRouter extends $AppRouter { + @override + RouteType get defaultRouteType => const RouteType.adaptive(); + @override + final List routes = [ + AutoRoute(path: '/', page: Home.page), + CustomRoute( + page: FullScreenDialogPage.page, + path: '/fullScreenDialogPage', + transitionsBuilder: popupTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: AccountManagement.page, + path: '/accountManagement', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: Settings.page, + path: '/settings', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: SplitTunneling.page, + path: '/splitTunneling', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: Language.page, + path: '/language', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: AuthorizePro.page, + path: '/authorizePro', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: AuthorizeDeviceEmail.page, + path: '/authorizeDeviceEmail', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: AuthorizeDeviceEmailPin.page, + path: '/authorizeDeviceEmailPin', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ApproveDevice.page, + path: '/approveDevice', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: RecoveryKey.page, + path: '/recoveryKey', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ChatNumberRecovery.page, + path: '/chatNumberRecovery', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ChatNumberMessaging.page, + path: '/chatNumberMessaging', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: Conversation.page, + path: '/conversation', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ContactInfo.page, + path: '/contactInfo', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: NewChat.page, + path: '/newChat', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: AddViaChatNumber.page, + path: '/addViaChatNumber', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: Introduce.page, + path: '/introduce', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: Introductions.page, + path: '/introductions', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ChatNumberAccount.page, + path: '/chatNumberAccount', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: BlockedUsers.page, + path: '/blockedUsers', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: Checkout.page, + path: '/checkout', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ResellerCodeCheckout.page, + path: '/resellerCodeCheckout', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: StripeCheckout.page, + path: '/stripeCheckout', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: PlansPage.page, + path: '/plans', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ReplicaUploadTitle.page, + path: '/replicaUploadTitle', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ReplicaUploadDescription.page, + path: '/replicaUploadDescription', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ReplicaUploadReview.page, + path: '/replicaUploadReview', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ReplicaLinkHandler.page, + path: '/replicaLinkHandler', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ReplicaMiscViewer.page, + path: '/replicaMiscViewer', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ReplicaImageViewer.page, + path: '/replicaImageViewer', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ReplicaVideoViewer.page, + path: '/replicaVideoViewer', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ReplicaAudioViewer.page, + path: '/replicaAudioViewer', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: Support.page, + path: '/support', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis), + CustomRoute( + page: ReportIssue.page, + path: '/reportIssue', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: InviteFriends.page, + path: '/inviteFriends', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: LanternDesktop.page, + path: '/lanternDesktop', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + CustomRoute( + page: LinkDevice.page, + path: '/linkDevice', + transitionsBuilder: defaultTransition, + durationInMilliseconds: defaultTransitionMillis, + reverseDurationInMilliseconds: defaultTransitionMillis, + ), + ]; +} diff --git a/lib/core/router/router.gr.dart b/lib/core/router/router.gr.dart index 7d5cf0021..82434910a 100644 --- a/lib/core/router/router.gr.dart +++ b/lib/core/router/router.gr.dart @@ -1,692 +1,429 @@ -// ************************************************************************** -// AutoRouteGenerator -// ************************************************************************** - // GENERATED CODE - DO NOT MODIFY BY HAND // ************************************************************************** -// AutoRouteGenerator +// AutoRouterGenerator // ************************************************************************** -// + // ignore_for_file: type=lint +// coverage:ignore-file // ignore_for_file: no_leading_underscores_for_library_prefixes -import 'dart:io' as _i38; - -import 'package:auto_route/auto_route.dart' as _i35; -import 'package:flutter/material.dart' as _i36; -import 'package:lantern/account/account_management.dart' as _i3; -import 'package:lantern/account/blocked_users.dart' as _i21; -import 'package:lantern/account/chat_number_account.dart' as _i20; -import 'package:lantern/account/device_linking/approve_device.dart' as _i10; +import 'dart:io' as _i43; + +import 'package:auto_route/auto_route.dart' as _i40; +import 'package:flutter/cupertino.dart' as _i45; +import 'package:lantern/account/account.dart' as _i23; +import 'package:lantern/account/account_management.dart' as _i17; +import 'package:lantern/account/blocked_users.dart' as _i29; +import 'package:lantern/account/chat_number_account.dart' as _i27; +import 'package:lantern/account/device_linking/approve_device.dart' as _i19; import 'package:lantern/account/device_linking/authorize_device_for_pro.dart' - as _i7; + as _i22; import 'package:lantern/account/device_linking/authorize_device_via_email.dart' - as _i8; + as _i21; import 'package:lantern/account/device_linking/authorize_device_via_email_pin.dart' - as _i9; -import 'package:lantern/account/language.dart' as _i6; -import 'package:lantern/account/recovery_key.dart' as _i11; -import 'package:lantern/account/settings.dart' as _i4; -import 'package:lantern/account/support.dart' as _i34; -import 'package:lantern/common/ui/full_screen_dialog.dart' as _i2; + as _i18; +import 'package:lantern/account/device_linking/link_device.dart' as _i20; +import 'package:lantern/account/invite_friends.dart' as _i26; +import 'package:lantern/account/language.dart' as _i30; +import 'package:lantern/account/lantern_desktop.dart' as _i25; +import 'package:lantern/account/recovery_key.dart' as _i16; +import 'package:lantern/account/report_issue.dart' as _i15; +import 'package:lantern/account/settings.dart' as _i31; +import 'package:lantern/account/split_tunneling.dart' as _i28; +import 'package:lantern/account/support.dart' as _i24; +import 'package:lantern/common/common.dart' as _i41; +import 'package:lantern/common/ui/full_screen_dialog.dart' as _i6; import 'package:lantern/home.dart' as _i1; -import 'package:lantern/messaging/contacts/add_contact_number.dart' as _i17; -import 'package:lantern/messaging/contacts/contact_info.dart' as _i15; -import 'package:lantern/messaging/contacts/new_chat.dart' as _i16; -import 'package:lantern/messaging/conversation/conversation.dart' as _i14; -import 'package:lantern/messaging/introductions/introduce.dart' as _i18; -import 'package:lantern/messaging/introductions/introductions.dart' as _i19; -import 'package:lantern/messaging/messaging.dart' as _i37; +import 'package:lantern/messaging/contacts/add_contact_number.dart' as _i32; +import 'package:lantern/messaging/contacts/contact_info.dart' as _i33; +import 'package:lantern/messaging/contacts/new_chat.dart' as _i34; +import 'package:lantern/messaging/conversation/conversation.dart' as _i39; +import 'package:lantern/messaging/introductions/introduce.dart' as _i36; +import 'package:lantern/messaging/introductions/introductions.dart' as _i35; +import 'package:lantern/messaging/messaging.dart' as _i44; import 'package:lantern/messaging/onboarding/chat_number_messaging.dart' - as _i13; -import 'package:lantern/messaging/onboarding/chat_number_recovery.dart' as _i12; -import 'package:lantern/plans/checkout.dart' as _i22; -import 'package:lantern/plans/plans.dart' as _i25; -import 'package:lantern/plans/reseller_checkout.dart' as _i23; -import 'package:lantern/plans/stripe_checkout.dart' as _i24; -import 'package:lantern/replica/common.dart' as _i39; -import 'package:lantern/replica/link_handler.dart' as _i29; -import 'package:lantern/replica/ui/viewers/audio.dart' as _i33; -import 'package:lantern/replica/ui/viewers/image.dart' as _i31; -import 'package:lantern/replica/ui/viewers/misc.dart' as _i30; -import 'package:lantern/replica/ui/viewers/video.dart' as _i32; -import 'package:lantern/replica/upload/description.dart' as _i27; -import 'package:lantern/replica/upload/review.dart' as _i28; -import 'package:lantern/replica/upload/title.dart' as _i26; -import 'package:lantern/vpn/vpn_split_tunneling.dart' as _i5; - -class AppRouter extends _i35.RootStackRouter { - AppRouter([_i36.GlobalKey<_i36.NavigatorState>? navigatorKey]) - : super(navigatorKey); + as _i38; +import 'package:lantern/messaging/onboarding/chat_number_recovery.dart' as _i37; +import 'package:lantern/plans/checkout.dart' as _i5; +import 'package:lantern/plans/plans.dart' as _i3; +import 'package:lantern/plans/reseller_checkout.dart' as _i2; +import 'package:lantern/plans/stripe_checkout.dart' as _i4; +import 'package:lantern/replica/common.dart' as _i42; +import 'package:lantern/replica/link_handler.dart' as _i11; +import 'package:lantern/replica/ui/viewers/audio.dart' as _i7; +import 'package:lantern/replica/ui/viewers/image.dart' as _i8; +import 'package:lantern/replica/ui/viewers/misc.dart' as _i10; +import 'package:lantern/replica/ui/viewers/video.dart' as _i9; +import 'package:lantern/replica/upload/description.dart' as _i13; +import 'package:lantern/replica/upload/review.dart' as _i12; +import 'package:lantern/replica/upload/title.dart' as _i14; + +abstract class $AppRouter extends _i40.RootStackRouter { + $AppRouter({super.navigatorKey}); @override - final Map pagesMap = { + final Map pagesMap = { Home.name: (routeData) { final args = routeData.argsAs(orElse: () => const HomeArgs()); - return _i35.AdaptivePage( + return _i40.AutoRoutePage( routeData: routeData, child: _i1.HomePage(key: args.key), ); }, + ResellerCodeCheckout.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( + routeData: routeData, + child: _i2.ResellerCodeCheckout( + isPro: args.isPro, + key: args.key, + ), + ); + }, + PlansPage.name: (routeData) { + return _i40.AutoRoutePage( + routeData: routeData, + child: _i3.PlansPage(), + ); + }, + StripeCheckout.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( + routeData: routeData, + child: _i4.StripeCheckout( + plan: args.plan, + email: args.email, + refCode: args.refCode, + isPro: args.isPro, + key: args.key, + ), + ); + }, + Checkout.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( + routeData: routeData, + child: _i5.Checkout( + plan: args.plan, + isPro: args.isPro, + key: args.key, + ), + ); + }, FullScreenDialogPage.name: (routeData) { final args = routeData.argsAs(); - return _i35.CustomPage( + return _i40.AutoRoutePage( routeData: routeData, - child: _i2.FullScreenDialog( + child: _i6.FullScreenDialog( widget: args.widget, key: args.key, ), - transitionsBuilder: _i35.TransitionsBuilders.slideBottom, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, ); }, - AccountManagement.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + ReplicaAudioViewer.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i3.AccountManagement( - key: args.key, - isPro: args.isPro, + child: _i7.ReplicaAudioViewer( + replicaApi: args.replicaApi, + item: args.item, + category: args.category, ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, ); }, - Settings.name: (routeData) { - final args = - routeData.argsAs(orElse: () => const SettingsArgs()); - return _i35.CustomPage( + ReplicaImageViewer.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i4.Settings(key: args.key), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i8.ReplicaImageViewer( + replicaApi: args.replicaApi, + item: args.item, + category: args.category, + ), ); }, - SplitTunneling.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const SplitTunnelingArgs()); - return _i35.CustomPage( + ReplicaVideoViewer.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i5.SplitTunneling(key: args.key), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i9.ReplicaVideoViewer( + replicaApi: args.replicaApi, + item: args.item, + category: args.category, + ), ); }, - Language.name: (routeData) { - final args = - routeData.argsAs(orElse: () => const LanguageArgs()); - return _i35.CustomPage( + ReplicaMiscViewer.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i6.Language(key: args.key), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i10.ReplicaMiscViewer( + replicaApi: args.replicaApi, + item: args.item, + category: args.category, + ), ); }, - AuthorizePro.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const AuthorizeProArgs()); - return _i35.CustomPage( + ReplicaLinkHandler.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i7.AuthorizeDeviceForPro(key: args.key), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i11.ReplicaLinkHandler( + key: args.key, + replicaApi: args.replicaApi, + replicaLink: args.replicaLink, + ), ); }, - AuthorizeDeviceEmail.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const AuthorizeDeviceEmailArgs()); - return _i35.CustomPage( + ReplicaUploadReview.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i8.AuthorizeDeviceViaEmail(key: args.key), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i12.ReplicaUploadReview( + key: args.key, + fileToUpload: args.fileToUpload, + fileTitle: args.fileTitle, + fileDescription: args.fileDescription, + ), ); }, - AuthorizeDeviceEmailPin.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const AuthorizeDeviceEmailPinArgs()); - return _i35.CustomPage( + ReplicaUploadDescription.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i9.AuthorizeDeviceViaEmailPin(key: args.key), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i13.ReplicaUploadDescription( + key: args.key, + fileToUpload: args.fileToUpload, + fileTitle: args.fileTitle, + fileDescription: args.fileDescription, + ), ); }, - ApproveDevice.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const ApproveDeviceArgs()); - return _i35.CustomPage( + ReplicaUploadTitle.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( + routeData: routeData, + child: _i14.ReplicaUploadTitle( + key: args.key, + fileToUpload: args.fileToUpload, + fileTitle: args.fileTitle, + fileDescription: args.fileDescription, + ), + ); + }, + ReportIssue.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const ReportIssueArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i10.ApproveDevice(key: args.key), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i15.ReportIssue(key: args.key), ); }, RecoveryKey.name: (routeData) { final args = routeData.argsAs( orElse: () => const RecoveryKeyArgs()); - return _i35.CustomPage( + return _i40.AutoRoutePage( routeData: routeData, - child: _i11.RecoveryKey(key: args.key), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i16.RecoveryKey(key: args.key), ); }, - ChatNumberRecovery.name: (routeData) { - return _i35.CustomPage( + AccountManagement.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i12.ChatNumberRecovery(), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i17.AccountManagement( + key: args.key, + isPro: args.isPro, + ), ); }, - ChatNumberMessaging.name: (routeData) { - return _i35.CustomPage( + AuthorizeDeviceEmailPin.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const AuthorizeDeviceEmailPinArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i13.ChatNumberMessaging(), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i18.AuthorizeDeviceViaEmailPin(key: args.key), ); }, - Conversation.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + ApproveDevice.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const ApproveDeviceArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i14.Conversation( - contactId: args.contactId, - initialScrollIndex: args.initialScrollIndex, - showContactEditingDialog: args.showContactEditingDialog, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i19.ApproveDevice(key: args.key), ); }, - ContactInfo.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + LinkDevice.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const LinkDeviceArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i15.ContactInfo(contact: args.contact), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i20.LinkDevice(key: args.key), ); }, - NewChat.name: (routeData) { - return _i35.CustomPage( + AuthorizeDeviceEmail.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const AuthorizeDeviceEmailArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i16.NewChat(), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i21.AuthorizeDeviceViaEmail(key: args.key), ); }, - AddViaChatNumber.name: (routeData) { - return _i35.CustomPage( + AuthorizePro.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const AuthorizeProArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i17.AddViaChatNumber(), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i22.AuthorizeDeviceForPro(key: args.key), ); }, - Introduce.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + Account.name: (routeData) { + final args = + routeData.argsAs(orElse: () => const AccountArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i18.Introduce( - singleIntro: args.singleIntro, - contactToIntro: args.contactToIntro, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i23.AccountMenu(key: args.key), ); }, - Introductions.name: (routeData) { - return _i35.CustomPage( + Support.name: (routeData) { + return _i40.AutoRoutePage( routeData: routeData, - child: _i19.Introductions(), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: const _i24.Support(), ); }, - ChatNumberAccount.name: (routeData) { - return _i35.CustomPage( + LanternDesktop.name: (routeData) { + return _i40.AutoRoutePage( routeData: routeData, - child: _i20.ChatNumberAccount(), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: const _i25.LanternDesktop(), ); }, - BlockedUsers.name: (routeData) { - final args = routeData.argsAs( - orElse: () => const BlockedUsersArgs()); - return _i35.CustomPage( + InviteFriends.name: (routeData) { + return _i40.AutoRoutePage( routeData: routeData, - child: _i21.BlockedUsers(key: args.key), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i26.InviteFriends(), ); }, - Checkout.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + ChatNumberAccount.name: (routeData) { + return _i40.AutoRoutePage( routeData: routeData, - child: _i22.Checkout( - plan: args.plan, - isPro: args.isPro, - key: args.key, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i27.ChatNumberAccount(), ); }, - ResellerCodeCheckout.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + SplitTunneling.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const SplitTunnelingArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i23.ResellerCodeCheckout( - isPro: args.isPro, - key: args.key, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i28.SplitTunneling(key: args.key), ); }, - StripeCheckout.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + BlockedUsers.name: (routeData) { + final args = routeData.argsAs( + orElse: () => const BlockedUsersArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i24.StripeCheckout( - plan: args.plan, - email: args.email, - refCode: args.refCode, - isPro: args.isPro, - key: args.key, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i29.BlockedUsers(key: args.key), ); }, - PlansPage.name: (routeData) { - return _i35.CustomPage( + Language.name: (routeData) { + final args = + routeData.argsAs(orElse: () => const LanguageArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i25.PlansPage(), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i30.Language(key: args.key), ); }, - ReplicaUploadTitle.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + Settings.name: (routeData) { + final args = + routeData.argsAs(orElse: () => const SettingsArgs()); + return _i40.AutoRoutePage( routeData: routeData, - child: _i26.ReplicaUploadTitle( - key: args.key, - fileToUpload: args.fileToUpload, - fileTitle: args.fileTitle, - fileDescription: args.fileDescription, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i31.Settings(key: args.key), ); }, - ReplicaUploadDescription.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + AddViaChatNumber.name: (routeData) { + return _i40.AutoRoutePage( routeData: routeData, - child: _i27.ReplicaUploadDescription( - key: args.key, - fileToUpload: args.fileToUpload, - fileTitle: args.fileTitle, - fileDescription: args.fileDescription, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i32.AddViaChatNumber(), ); }, - ReplicaUploadReview.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + ContactInfo.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i28.ReplicaUploadReview( - key: args.key, - fileToUpload: args.fileToUpload, - fileTitle: args.fileTitle, - fileDescription: args.fileDescription, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i33.ContactInfo(contact: args.contact), ); }, - ReplicaLinkHandler.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + NewChat.name: (routeData) { + return _i40.AutoRoutePage( routeData: routeData, - child: _i29.ReplicaLinkHandler( - key: args.key, - replicaApi: args.replicaApi, - replicaLink: args.replicaLink, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i34.NewChat(), ); }, - ReplicaMiscViewer.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + Introductions.name: (routeData) { + return _i40.AutoRoutePage( routeData: routeData, - child: _i30.ReplicaMiscViewer( - replicaApi: args.replicaApi, - item: args.item, - category: args.category, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i35.Introductions(), ); }, - ReplicaImageViewer.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + Introduce.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: _i31.ReplicaImageViewer( - replicaApi: args.replicaApi, - item: args.item, - category: args.category, + child: _i36.Introduce( + singleIntro: args.singleIntro, + contactToIntro: args.contactToIntro, ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, ); }, - ReplicaVideoViewer.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + ChatNumberRecovery.name: (routeData) { + return _i40.AutoRoutePage( routeData: routeData, - child: _i32.ReplicaVideoViewer( - replicaApi: args.replicaApi, - item: args.item, - category: args.category, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i37.ChatNumberRecovery(), ); }, - ReplicaAudioViewer.name: (routeData) { - final args = routeData.argsAs(); - return _i35.CustomPage( + ChatNumberMessaging.name: (routeData) { + return _i40.AutoRoutePage( routeData: routeData, - child: _i33.ReplicaAudioViewer( - replicaApi: args.replicaApi, - item: args.item, - category: args.category, - ), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i38.ChatNumberMessaging(), ); }, - Support.name: (routeData) { - return _i35.CustomPage( + Conversation.name: (routeData) { + final args = routeData.argsAs(); + return _i40.AutoRoutePage( routeData: routeData, - child: const _i34.Support(), - transitionsBuilder: _i35.TransitionsBuilders.fadeIn, - durationInMilliseconds: 200, - reverseDurationInMilliseconds: 200, - opaque: true, - barrierDismissible: false, + child: _i39.Conversation( + contactId: args.contactId, + initialScrollIndex: args.initialScrollIndex, + showContactEditingDialog: args.showContactEditingDialog, + ), ); }, }; - - @override - List<_i35.RouteConfig> get routes => [ - _i35.RouteConfig( - Home.name, - path: '/', - ), - _i35.RouteConfig( - FullScreenDialogPage.name, - path: 'fullScreenDialogPage', - ), - _i35.RouteConfig( - AccountManagement.name, - path: 'accountManagement', - ), - _i35.RouteConfig( - Settings.name, - path: 'settings', - ), - _i35.RouteConfig( - SplitTunneling.name, - path: 'splitTunneling', - ), - _i35.RouteConfig( - Language.name, - path: 'language', - ), - _i35.RouteConfig( - AuthorizePro.name, - path: 'authorizePro', - ), - _i35.RouteConfig( - AuthorizeDeviceEmail.name, - path: 'authorizeDeviceEmail', - ), - _i35.RouteConfig( - AuthorizeDeviceEmailPin.name, - path: 'authorizeDeviceEmailPin', - ), - _i35.RouteConfig( - ApproveDevice.name, - path: 'approveDevice', - ), - _i35.RouteConfig( - RecoveryKey.name, - path: 'recoveryKey', - ), - _i35.RouteConfig( - ChatNumberRecovery.name, - path: 'chatNumberRecovery', - ), - _i35.RouteConfig( - ChatNumberMessaging.name, - path: 'chatNumberMessaging', - ), - _i35.RouteConfig( - Conversation.name, - path: 'conversation', - ), - _i35.RouteConfig( - ContactInfo.name, - path: 'contactInfo', - ), - _i35.RouteConfig( - NewChat.name, - path: 'newChat', - ), - _i35.RouteConfig( - AddViaChatNumber.name, - path: 'addViaChatNumber', - ), - _i35.RouteConfig( - Introduce.name, - path: 'introduce', - ), - _i35.RouteConfig( - Introductions.name, - path: 'introductions', - ), - _i35.RouteConfig( - ChatNumberAccount.name, - path: 'chatNumberAccount', - ), - _i35.RouteConfig( - BlockedUsers.name, - path: 'blockedUsers', - ), - _i35.RouteConfig( - Checkout.name, - path: 'checkout', - ), - _i35.RouteConfig( - ResellerCodeCheckout.name, - path: 'resellerCodeCheckout', - ), - _i35.RouteConfig( - StripeCheckout.name, - path: 'stripeCheckout', - ), - _i35.RouteConfig( - PlansPage.name, - path: 'plans', - ), - _i35.RouteConfig( - ReplicaUploadTitle.name, - path: 'replicaUploadTitle', - ), - _i35.RouteConfig( - ReplicaUploadDescription.name, - path: 'replicaUploadDescription', - ), - _i35.RouteConfig( - ReplicaUploadReview.name, - path: 'replicaUploadReview', - ), - _i35.RouteConfig( - ReplicaLinkHandler.name, - path: 'replicaLinkHandler', - ), - _i35.RouteConfig( - ReplicaMiscViewer.name, - path: 'replicaMiscViewer', - ), - _i35.RouteConfig( - ReplicaImageViewer.name, - path: 'replicaImageViewer', - ), - _i35.RouteConfig( - ReplicaVideoViewer.name, - path: 'replicaVideoViewer', - ), - _i35.RouteConfig( - ReplicaAudioViewer.name, - path: 'replicaAudioViewer', - ), - _i35.RouteConfig( - Support.name, - path: 'support', - ), - ]; } /// generated route for /// [_i1.HomePage] -class Home extends _i35.PageRouteInfo { - Home({_i37.Key? key}) - : super( +class Home extends _i40.PageRouteInfo { + Home({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, + }) : super( Home.name, - path: '/', args: HomeArgs(key: key), + initialChildren: children, ); static const String name = 'Home'; + + static const _i40.PageInfo page = _i40.PageInfo(name); } class HomeArgs { const HomeArgs({this.key}); - final _i37.Key? key; + final _i41.Key? key; @override String toString() { @@ -695,932 +432,1173 @@ class HomeArgs { } /// generated route for -/// [_i2.FullScreenDialog] -class FullScreenDialogPage - extends _i35.PageRouteInfo { - FullScreenDialogPage({ - required _i37.Widget widget, - _i37.Key? key, +/// [_i2.ResellerCodeCheckout] +class ResellerCodeCheckout + extends _i40.PageRouteInfo { + ResellerCodeCheckout({ + required bool isPro, + _i41.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( - FullScreenDialogPage.name, - path: 'fullScreenDialogPage', - args: FullScreenDialogPageArgs( - widget: widget, + ResellerCodeCheckout.name, + args: ResellerCodeCheckoutArgs( + isPro: isPro, key: key, ), + initialChildren: children, ); - static const String name = 'FullScreenDialogPage'; + static const String name = 'ResellerCodeCheckout'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class FullScreenDialogPageArgs { - const FullScreenDialogPageArgs({ - required this.widget, +class ResellerCodeCheckoutArgs { + const ResellerCodeCheckoutArgs({ + required this.isPro, this.key, }); - final _i37.Widget widget; + final bool isPro; - final _i37.Key? key; + final _i41.Key? key; @override String toString() { - return 'FullScreenDialogPageArgs{widget: $widget, key: $key}'; + return 'ResellerCodeCheckoutArgs{isPro: $isPro, key: $key}'; } } /// generated route for -/// [_i3.AccountManagement] -class AccountManagement extends _i35.PageRouteInfo { - AccountManagement({ - _i37.Key? key, +/// [_i3.PlansPage] +class PlansPage extends _i40.PageRouteInfo { + const PlansPage({List<_i40.PageRouteInfo>? children}) + : super( + PlansPage.name, + initialChildren: children, + ); + + static const String name = 'PlansPage'; + + static const _i40.PageInfo page = _i40.PageInfo(name); +} + +/// generated route for +/// [_i4.StripeCheckout] +class StripeCheckout extends _i40.PageRouteInfo { + StripeCheckout({ + required _i41.Plan plan, + required String email, + String? refCode, required bool isPro, + _i41.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( - AccountManagement.name, - path: 'accountManagement', - args: AccountManagementArgs( - key: key, + StripeCheckout.name, + args: StripeCheckoutArgs( + plan: plan, + email: email, + refCode: refCode, isPro: isPro, + key: key, ), + initialChildren: children, ); - static const String name = 'AccountManagement'; + static const String name = 'StripeCheckout'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class AccountManagementArgs { - const AccountManagementArgs({ - this.key, +class StripeCheckoutArgs { + const StripeCheckoutArgs({ + required this.plan, + required this.email, + this.refCode, required this.isPro, + this.key, }); - final _i37.Key? key; + final _i41.Plan plan; + + final String email; + + final String? refCode; final bool isPro; + final _i41.Key? key; + @override String toString() { - return 'AccountManagementArgs{key: $key, isPro: $isPro}'; + return 'StripeCheckoutArgs{plan: $plan, email: $email, refCode: $refCode, isPro: $isPro, key: $key}'; } } /// generated route for -/// [_i4.Settings] -class Settings extends _i35.PageRouteInfo { - Settings({_i37.Key? key}) - : super( - Settings.name, - path: 'settings', - args: SettingsArgs(key: key), +/// [_i5.Checkout] +class Checkout extends _i40.PageRouteInfo { + Checkout({ + required _i41.Plan plan, + required bool isPro, + _i41.Key? key, + List<_i40.PageRouteInfo>? children, + }) : super( + Checkout.name, + args: CheckoutArgs( + plan: plan, + isPro: isPro, + key: key, + ), + initialChildren: children, ); - static const String name = 'Settings'; + static const String name = 'Checkout'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class SettingsArgs { - const SettingsArgs({this.key}); +class CheckoutArgs { + const CheckoutArgs({ + required this.plan, + required this.isPro, + this.key, + }); - final _i37.Key? key; + final _i41.Plan plan; + + final bool isPro; + + final _i41.Key? key; @override String toString() { - return 'SettingsArgs{key: $key}'; + return 'CheckoutArgs{plan: $plan, isPro: $isPro, key: $key}'; } } /// generated route for -/// [_i5.SplitTunneling] -class SplitTunneling extends _i35.PageRouteInfo { - SplitTunneling({_i37.Key? key}) - : super( - SplitTunneling.name, - path: 'splitTunneling', - args: SplitTunnelingArgs(key: key), +/// [_i6.FullScreenDialog] +class FullScreenDialogPage + extends _i40.PageRouteInfo { + FullScreenDialogPage({ + required _i41.Widget widget, + _i41.Key? key, + List<_i40.PageRouteInfo>? children, + }) : super( + FullScreenDialogPage.name, + args: FullScreenDialogPageArgs( + widget: widget, + key: key, + ), + initialChildren: children, ); - static const String name = 'SplitTunneling'; + static const String name = 'FullScreenDialogPage'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class SplitTunnelingArgs { - const SplitTunnelingArgs({this.key}); +class FullScreenDialogPageArgs { + const FullScreenDialogPageArgs({ + required this.widget, + this.key, + }); - final _i37.Key? key; + final _i41.Widget widget; + + final _i41.Key? key; @override String toString() { - return 'SplitTunnelingArgs{key: $key}'; + return 'FullScreenDialogPageArgs{widget: $widget, key: $key}'; } } /// generated route for -/// [_i6.Language] -class Language extends _i35.PageRouteInfo { - Language({_i37.Key? key}) - : super( - Language.name, - path: 'language', - args: LanguageArgs(key: key), +/// [_i7.ReplicaAudioViewer] +class ReplicaAudioViewer extends _i40.PageRouteInfo { + ReplicaAudioViewer({ + required _i42.ReplicaApi replicaApi, + required _i42.ReplicaSearchItem item, + required _i42.SearchCategory category, + List<_i40.PageRouteInfo>? children, + }) : super( + ReplicaAudioViewer.name, + args: ReplicaAudioViewerArgs( + replicaApi: replicaApi, + item: item, + category: category, + ), + initialChildren: children, ); - static const String name = 'Language'; + static const String name = 'ReplicaAudioViewer'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class LanguageArgs { - const LanguageArgs({this.key}); +class ReplicaAudioViewerArgs { + const ReplicaAudioViewerArgs({ + required this.replicaApi, + required this.item, + required this.category, + }); + + final _i42.ReplicaApi replicaApi; - final _i37.Key? key; + final _i42.ReplicaSearchItem item; + + final _i42.SearchCategory category; @override String toString() { - return 'LanguageArgs{key: $key}'; + return 'ReplicaAudioViewerArgs{replicaApi: $replicaApi, item: $item, category: $category}'; } } /// generated route for -/// [_i7.AuthorizeDeviceForPro] -class AuthorizePro extends _i35.PageRouteInfo { - AuthorizePro({_i37.Key? key}) - : super( - AuthorizePro.name, - path: 'authorizePro', - args: AuthorizeProArgs(key: key), +/// [_i8.ReplicaImageViewer] +class ReplicaImageViewer extends _i40.PageRouteInfo { + ReplicaImageViewer({ + required _i42.ReplicaApi replicaApi, + required _i42.ReplicaSearchItem item, + required _i42.SearchCategory category, + List<_i40.PageRouteInfo>? children, + }) : super( + ReplicaImageViewer.name, + args: ReplicaImageViewerArgs( + replicaApi: replicaApi, + item: item, + category: category, + ), + initialChildren: children, ); - static const String name = 'AuthorizePro'; + static const String name = 'ReplicaImageViewer'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class AuthorizeProArgs { - const AuthorizeProArgs({this.key}); +class ReplicaImageViewerArgs { + const ReplicaImageViewerArgs({ + required this.replicaApi, + required this.item, + required this.category, + }); + + final _i42.ReplicaApi replicaApi; - final _i37.Key? key; + final _i42.ReplicaSearchItem item; + + final _i42.SearchCategory category; @override String toString() { - return 'AuthorizeProArgs{key: $key}'; + return 'ReplicaImageViewerArgs{replicaApi: $replicaApi, item: $item, category: $category}'; } } /// generated route for -/// [_i8.AuthorizeDeviceViaEmail] -class AuthorizeDeviceEmail - extends _i35.PageRouteInfo { - AuthorizeDeviceEmail({_i37.Key? key}) - : super( - AuthorizeDeviceEmail.name, - path: 'authorizeDeviceEmail', - args: AuthorizeDeviceEmailArgs(key: key), +/// [_i9.ReplicaVideoViewer] +class ReplicaVideoViewer extends _i40.PageRouteInfo { + ReplicaVideoViewer({ + required _i42.ReplicaApi replicaApi, + required _i42.ReplicaSearchItem item, + required _i42.SearchCategory category, + List<_i40.PageRouteInfo>? children, + }) : super( + ReplicaVideoViewer.name, + args: ReplicaVideoViewerArgs( + replicaApi: replicaApi, + item: item, + category: category, + ), + initialChildren: children, ); - static const String name = 'AuthorizeDeviceEmail'; + static const String name = 'ReplicaVideoViewer'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class AuthorizeDeviceEmailArgs { - const AuthorizeDeviceEmailArgs({this.key}); +class ReplicaVideoViewerArgs { + const ReplicaVideoViewerArgs({ + required this.replicaApi, + required this.item, + required this.category, + }); - final _i37.Key? key; + final _i42.ReplicaApi replicaApi; + + final _i42.ReplicaSearchItem item; + + final _i42.SearchCategory category; @override String toString() { - return 'AuthorizeDeviceEmailArgs{key: $key}'; + return 'ReplicaVideoViewerArgs{replicaApi: $replicaApi, item: $item, category: $category}'; } } /// generated route for -/// [_i9.AuthorizeDeviceViaEmailPin] -class AuthorizeDeviceEmailPin - extends _i35.PageRouteInfo { - AuthorizeDeviceEmailPin({_i37.Key? key}) - : super( - AuthorizeDeviceEmailPin.name, - path: 'authorizeDeviceEmailPin', - args: AuthorizeDeviceEmailPinArgs(key: key), +/// [_i10.ReplicaMiscViewer] +class ReplicaMiscViewer extends _i40.PageRouteInfo { + ReplicaMiscViewer({ + required _i42.ReplicaApi replicaApi, + required _i42.ReplicaSearchItem item, + required _i42.SearchCategory category, + List<_i40.PageRouteInfo>? children, + }) : super( + ReplicaMiscViewer.name, + args: ReplicaMiscViewerArgs( + replicaApi: replicaApi, + item: item, + category: category, + ), + initialChildren: children, ); - static const String name = 'AuthorizeDeviceEmailPin'; + static const String name = 'ReplicaMiscViewer'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class AuthorizeDeviceEmailPinArgs { - const AuthorizeDeviceEmailPinArgs({this.key}); +class ReplicaMiscViewerArgs { + const ReplicaMiscViewerArgs({ + required this.replicaApi, + required this.item, + required this.category, + }); + + final _i42.ReplicaApi replicaApi; - final _i37.Key? key; + final _i42.ReplicaSearchItem item; + + final _i42.SearchCategory category; @override String toString() { - return 'AuthorizeDeviceEmailPinArgs{key: $key}'; + return 'ReplicaMiscViewerArgs{replicaApi: $replicaApi, item: $item, category: $category}'; } } /// generated route for -/// [_i10.ApproveDevice] -class ApproveDevice extends _i35.PageRouteInfo { - ApproveDevice({_i37.Key? key}) - : super( - ApproveDevice.name, - path: 'approveDevice', - args: ApproveDeviceArgs(key: key), +/// [_i11.ReplicaLinkHandler] +class ReplicaLinkHandler extends _i40.PageRouteInfo { + ReplicaLinkHandler({ + _i41.Key? key, + required _i42.ReplicaApi replicaApi, + required _i42.ReplicaLink replicaLink, + List<_i40.PageRouteInfo>? children, + }) : super( + ReplicaLinkHandler.name, + args: ReplicaLinkHandlerArgs( + key: key, + replicaApi: replicaApi, + replicaLink: replicaLink, + ), + initialChildren: children, ); - static const String name = 'ApproveDevice'; + static const String name = 'ReplicaLinkHandler'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class ApproveDeviceArgs { - const ApproveDeviceArgs({this.key}); +class ReplicaLinkHandlerArgs { + const ReplicaLinkHandlerArgs({ + this.key, + required this.replicaApi, + required this.replicaLink, + }); + + final _i41.Key? key; - final _i37.Key? key; + final _i42.ReplicaApi replicaApi; + + final _i42.ReplicaLink replicaLink; @override String toString() { - return 'ApproveDeviceArgs{key: $key}'; + return 'ReplicaLinkHandlerArgs{key: $key, replicaApi: $replicaApi, replicaLink: $replicaLink}'; } } /// generated route for -/// [_i11.RecoveryKey] -class RecoveryKey extends _i35.PageRouteInfo { - RecoveryKey({_i37.Key? key}) - : super( - RecoveryKey.name, - path: 'recoveryKey', - args: RecoveryKeyArgs(key: key), +/// [_i12.ReplicaUploadReview] +class ReplicaUploadReview extends _i40.PageRouteInfo { + ReplicaUploadReview({ + _i41.Key? key, + required _i43.File fileToUpload, + required String fileTitle, + String? fileDescription, + List<_i40.PageRouteInfo>? children, + }) : super( + ReplicaUploadReview.name, + args: ReplicaUploadReviewArgs( + key: key, + fileToUpload: fileToUpload, + fileTitle: fileTitle, + fileDescription: fileDescription, + ), + initialChildren: children, ); - static const String name = 'RecoveryKey'; + static const String name = 'ReplicaUploadReview'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class RecoveryKeyArgs { - const RecoveryKeyArgs({this.key}); +class ReplicaUploadReviewArgs { + const ReplicaUploadReviewArgs({ + this.key, + required this.fileToUpload, + required this.fileTitle, + this.fileDescription, + }); + + final _i41.Key? key; - final _i37.Key? key; + final _i43.File fileToUpload; + + final String fileTitle; + + final String? fileDescription; @override String toString() { - return 'RecoveryKeyArgs{key: $key}'; + return 'ReplicaUploadReviewArgs{key: $key, fileToUpload: $fileToUpload, fileTitle: $fileTitle, fileDescription: $fileDescription}'; } } /// generated route for -/// [_i12.ChatNumberRecovery] -class ChatNumberRecovery extends _i35.PageRouteInfo { - const ChatNumberRecovery() - : super( - ChatNumberRecovery.name, - path: 'chatNumberRecovery', +/// [_i13.ReplicaUploadDescription] +class ReplicaUploadDescription + extends _i40.PageRouteInfo { + ReplicaUploadDescription({ + _i41.Key? key, + required _i43.File fileToUpload, + required String fileTitle, + String? fileDescription, + List<_i40.PageRouteInfo>? children, + }) : super( + ReplicaUploadDescription.name, + args: ReplicaUploadDescriptionArgs( + key: key, + fileToUpload: fileToUpload, + fileTitle: fileTitle, + fileDescription: fileDescription, + ), + initialChildren: children, ); - static const String name = 'ChatNumberRecovery'; + static const String name = 'ReplicaUploadDescription'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -/// generated route for -/// [_i13.ChatNumberMessaging] -class ChatNumberMessaging extends _i35.PageRouteInfo { - const ChatNumberMessaging() - : super( - ChatNumberMessaging.name, - path: 'chatNumberMessaging', - ); +class ReplicaUploadDescriptionArgs { + const ReplicaUploadDescriptionArgs({ + this.key, + required this.fileToUpload, + required this.fileTitle, + this.fileDescription, + }); - static const String name = 'ChatNumberMessaging'; + final _i41.Key? key; + + final _i43.File fileToUpload; + + final String fileTitle; + + final String? fileDescription; + + @override + String toString() { + return 'ReplicaUploadDescriptionArgs{key: $key, fileToUpload: $fileToUpload, fileTitle: $fileTitle, fileDescription: $fileDescription}'; + } } /// generated route for -/// [_i14.Conversation] -class Conversation extends _i35.PageRouteInfo { - Conversation({ - required _i37.ContactId contactId, - int? initialScrollIndex, - bool showContactEditingDialog = false, +/// [_i14.ReplicaUploadTitle] +class ReplicaUploadTitle extends _i40.PageRouteInfo { + ReplicaUploadTitle({ + _i41.Key? key, + required _i43.File fileToUpload, + String? fileTitle, + String? fileDescription, + List<_i40.PageRouteInfo>? children, }) : super( - Conversation.name, - path: 'conversation', - args: ConversationArgs( - contactId: contactId, - initialScrollIndex: initialScrollIndex, - showContactEditingDialog: showContactEditingDialog, + ReplicaUploadTitle.name, + args: ReplicaUploadTitleArgs( + key: key, + fileToUpload: fileToUpload, + fileTitle: fileTitle, + fileDescription: fileDescription, ), + initialChildren: children, ); - static const String name = 'Conversation'; + static const String name = 'ReplicaUploadTitle'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class ConversationArgs { - const ConversationArgs({ - required this.contactId, - this.initialScrollIndex, - this.showContactEditingDialog = false, +class ReplicaUploadTitleArgs { + const ReplicaUploadTitleArgs({ + this.key, + required this.fileToUpload, + this.fileTitle, + this.fileDescription, }); - final _i37.ContactId contactId; + final _i41.Key? key; - final int? initialScrollIndex; + final _i43.File fileToUpload; - final bool showContactEditingDialog; + final String? fileTitle; + + final String? fileDescription; @override String toString() { - return 'ConversationArgs{contactId: $contactId, initialScrollIndex: $initialScrollIndex, showContactEditingDialog: $showContactEditingDialog}'; + return 'ReplicaUploadTitleArgs{key: $key, fileToUpload: $fileToUpload, fileTitle: $fileTitle, fileDescription: $fileDescription}'; } } /// generated route for -/// [_i15.ContactInfo] -class ContactInfo extends _i35.PageRouteInfo { - ContactInfo({required _i37.Contact contact}) - : super( - ContactInfo.name, - path: 'contactInfo', - args: ContactInfoArgs(contact: contact), +/// [_i15.ReportIssue] +class ReportIssue extends _i40.PageRouteInfo { + ReportIssue({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, + }) : super( + ReportIssue.name, + args: ReportIssueArgs(key: key), + initialChildren: children, ); - static const String name = 'ContactInfo'; + static const String name = 'ReportIssue'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class ContactInfoArgs { - const ContactInfoArgs({required this.contact}); +class ReportIssueArgs { + const ReportIssueArgs({this.key}); - final _i37.Contact contact; + final _i41.Key? key; @override String toString() { - return 'ContactInfoArgs{contact: $contact}'; + return 'ReportIssueArgs{key: $key}'; } } /// generated route for -/// [_i16.NewChat] -class NewChat extends _i35.PageRouteInfo { - const NewChat() - : super( - NewChat.name, - path: 'newChat', +/// [_i16.RecoveryKey] +class RecoveryKey extends _i40.PageRouteInfo { + RecoveryKey({ + _i44.Key? key, + List<_i40.PageRouteInfo>? children, + }) : super( + RecoveryKey.name, + args: RecoveryKeyArgs(key: key), + initialChildren: children, ); - static const String name = 'NewChat'; + static const String name = 'RecoveryKey'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -/// generated route for -/// [_i17.AddViaChatNumber] -class AddViaChatNumber extends _i35.PageRouteInfo { - const AddViaChatNumber() - : super( - AddViaChatNumber.name, - path: 'addViaChatNumber', - ); +class RecoveryKeyArgs { + const RecoveryKeyArgs({this.key}); - static const String name = 'AddViaChatNumber'; + final _i44.Key? key; + + @override + String toString() { + return 'RecoveryKeyArgs{key: $key}'; + } } /// generated route for -/// [_i18.Introduce] -class Introduce extends _i35.PageRouteInfo { - Introduce({ - required bool singleIntro, - _i37.Contact? contactToIntro, +/// [_i17.AccountManagement] +class AccountManagement extends _i40.PageRouteInfo { + AccountManagement({ + _i44.Key? key, + required bool isPro, + List<_i40.PageRouteInfo>? children, }) : super( - Introduce.name, - path: 'introduce', - args: IntroduceArgs( - singleIntro: singleIntro, - contactToIntro: contactToIntro, + AccountManagement.name, + args: AccountManagementArgs( + key: key, + isPro: isPro, ), + initialChildren: children, ); - static const String name = 'Introduce'; + static const String name = 'AccountManagement'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class IntroduceArgs { - const IntroduceArgs({ - required this.singleIntro, - this.contactToIntro, +class AccountManagementArgs { + const AccountManagementArgs({ + this.key, + required this.isPro, }); - final bool singleIntro; + final _i44.Key? key; - final _i37.Contact? contactToIntro; + final bool isPro; @override String toString() { - return 'IntroduceArgs{singleIntro: $singleIntro, contactToIntro: $contactToIntro}'; + return 'AccountManagementArgs{key: $key, isPro: $isPro}'; } } /// generated route for -/// [_i19.Introductions] -class Introductions extends _i35.PageRouteInfo { - const Introductions() - : super( - Introductions.name, - path: 'introductions', +/// [_i18.AuthorizeDeviceViaEmailPin] +class AuthorizeDeviceEmailPin + extends _i40.PageRouteInfo { + AuthorizeDeviceEmailPin({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, + }) : super( + AuthorizeDeviceEmailPin.name, + args: AuthorizeDeviceEmailPinArgs(key: key), + initialChildren: children, ); - static const String name = 'Introductions'; + static const String name = 'AuthorizeDeviceEmailPin'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -/// generated route for -/// [_i20.ChatNumberAccount] -class ChatNumberAccount extends _i35.PageRouteInfo { - const ChatNumberAccount() - : super( - ChatNumberAccount.name, - path: 'chatNumberAccount', - ); +class AuthorizeDeviceEmailPinArgs { + const AuthorizeDeviceEmailPinArgs({this.key}); - static const String name = 'ChatNumberAccount'; + final _i41.Key? key; + + @override + String toString() { + return 'AuthorizeDeviceEmailPinArgs{key: $key}'; + } } /// generated route for -/// [_i21.BlockedUsers] -class BlockedUsers extends _i35.PageRouteInfo { - BlockedUsers({_i37.Key? key}) - : super( - BlockedUsers.name, - path: 'blockedUsers', - args: BlockedUsersArgs(key: key), - ); +/// [_i19.ApproveDevice] +class ApproveDevice extends _i40.PageRouteInfo { + ApproveDevice({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, + }) : super( + ApproveDevice.name, + args: ApproveDeviceArgs(key: key), + initialChildren: children, + ); - static const String name = 'BlockedUsers'; + static const String name = 'ApproveDevice'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class BlockedUsersArgs { - const BlockedUsersArgs({this.key}); +class ApproveDeviceArgs { + const ApproveDeviceArgs({this.key}); - final _i37.Key? key; + final _i41.Key? key; @override String toString() { - return 'BlockedUsersArgs{key: $key}'; + return 'ApproveDeviceArgs{key: $key}'; } } /// generated route for -/// [_i22.Checkout] -class Checkout extends _i35.PageRouteInfo { - Checkout({ - required _i37.Plan plan, - required bool isPro, - _i37.Key? key, +/// [_i20.LinkDevice] +class LinkDevice extends _i40.PageRouteInfo { + LinkDevice({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( - Checkout.name, - path: 'checkout', - args: CheckoutArgs( - plan: plan, - isPro: isPro, - key: key, - ), + LinkDevice.name, + args: LinkDeviceArgs(key: key), + initialChildren: children, ); - static const String name = 'Checkout'; -} + static const String name = 'LinkDevice'; -class CheckoutArgs { - const CheckoutArgs({ - required this.plan, - required this.isPro, - this.key, - }); - - final _i37.Plan plan; + static const _i40.PageInfo page = + _i40.PageInfo(name); +} - final bool isPro; +class LinkDeviceArgs { + const LinkDeviceArgs({this.key}); - final _i37.Key? key; + final _i41.Key? key; @override String toString() { - return 'CheckoutArgs{plan: $plan, isPro: $isPro, key: $key}'; + return 'LinkDeviceArgs{key: $key}'; } } /// generated route for -/// [_i23.ResellerCodeCheckout] -class ResellerCodeCheckout - extends _i35.PageRouteInfo { - ResellerCodeCheckout({ - required bool isPro, - _i37.Key? key, +/// [_i21.AuthorizeDeviceViaEmail] +class AuthorizeDeviceEmail + extends _i40.PageRouteInfo { + AuthorizeDeviceEmail({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( - ResellerCodeCheckout.name, - path: 'resellerCodeCheckout', - args: ResellerCodeCheckoutArgs( - isPro: isPro, - key: key, - ), + AuthorizeDeviceEmail.name, + args: AuthorizeDeviceEmailArgs(key: key), + initialChildren: children, ); - static const String name = 'ResellerCodeCheckout'; -} + static const String name = 'AuthorizeDeviceEmail'; -class ResellerCodeCheckoutArgs { - const ResellerCodeCheckoutArgs({ - required this.isPro, - this.key, - }); + static const _i40.PageInfo page = + _i40.PageInfo(name); +} - final bool isPro; +class AuthorizeDeviceEmailArgs { + const AuthorizeDeviceEmailArgs({this.key}); - final _i37.Key? key; + final _i41.Key? key; @override String toString() { - return 'ResellerCodeCheckoutArgs{isPro: $isPro, key: $key}'; + return 'AuthorizeDeviceEmailArgs{key: $key}'; } } /// generated route for -/// [_i24.StripeCheckout] -class StripeCheckout extends _i35.PageRouteInfo { - StripeCheckout({ - required _i37.Plan plan, - required String email, - String? refCode, - required bool isPro, - _i37.Key? key, +/// [_i22.AuthorizeDeviceForPro] +class AuthorizePro extends _i40.PageRouteInfo { + AuthorizePro({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( - StripeCheckout.name, - path: 'stripeCheckout', - args: StripeCheckoutArgs( - plan: plan, - email: email, - refCode: refCode, - isPro: isPro, - key: key, - ), + AuthorizePro.name, + args: AuthorizeProArgs(key: key), + initialChildren: children, ); - static const String name = 'StripeCheckout'; + static const String name = 'AuthorizePro'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class StripeCheckoutArgs { - const StripeCheckoutArgs({ - required this.plan, - required this.email, - this.refCode, - required this.isPro, - this.key, - }); +class AuthorizeProArgs { + const AuthorizeProArgs({this.key}); - final _i37.Plan plan; + final _i41.Key? key; - final String email; + @override + String toString() { + return 'AuthorizeProArgs{key: $key}'; + } +} - final String? refCode; +/// generated route for +/// [_i23.AccountMenu] +class Account extends _i40.PageRouteInfo { + Account({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, + }) : super( + Account.name, + args: AccountArgs(key: key), + initialChildren: children, + ); - final bool isPro; + static const String name = 'Account'; - final _i37.Key? key; + static const _i40.PageInfo page = + _i40.PageInfo(name); +} + +class AccountArgs { + const AccountArgs({this.key}); + + final _i41.Key? key; @override String toString() { - return 'StripeCheckoutArgs{plan: $plan, email: $email, refCode: $refCode, isPro: $isPro, key: $key}'; + return 'AccountArgs{key: $key}'; } } /// generated route for -/// [_i25.PlansPage] -class PlansPage extends _i35.PageRouteInfo { - const PlansPage() +/// [_i24.Support] +class Support extends _i40.PageRouteInfo { + const Support({List<_i40.PageRouteInfo>? children}) : super( - PlansPage.name, - path: 'plans', + Support.name, + initialChildren: children, ); - static const String name = 'PlansPage'; + static const String name = 'Support'; + + static const _i40.PageInfo page = _i40.PageInfo(name); } /// generated route for -/// [_i26.ReplicaUploadTitle] -class ReplicaUploadTitle extends _i35.PageRouteInfo { - ReplicaUploadTitle({ - _i37.Key? key, - required _i38.File fileToUpload, - String? fileTitle, - String? fileDescription, - }) : super( - ReplicaUploadTitle.name, - path: 'replicaUploadTitle', - args: ReplicaUploadTitleArgs( - key: key, - fileToUpload: fileToUpload, - fileTitle: fileTitle, - fileDescription: fileDescription, - ), +/// [_i25.LanternDesktop] +class LanternDesktop extends _i40.PageRouteInfo { + const LanternDesktop({List<_i40.PageRouteInfo>? children}) + : super( + LanternDesktop.name, + initialChildren: children, ); - static const String name = 'ReplicaUploadTitle'; + static const String name = 'LanternDesktop'; + + static const _i40.PageInfo page = _i40.PageInfo(name); } -class ReplicaUploadTitleArgs { - const ReplicaUploadTitleArgs({ - this.key, - required this.fileToUpload, - this.fileTitle, - this.fileDescription, - }); +/// generated route for +/// [_i26.InviteFriends] +class InviteFriends extends _i40.PageRouteInfo { + const InviteFriends({List<_i40.PageRouteInfo>? children}) + : super( + InviteFriends.name, + initialChildren: children, + ); - final _i37.Key? key; + static const String name = 'InviteFriends'; - final _i38.File fileToUpload; + static const _i40.PageInfo page = _i40.PageInfo(name); +} - final String? fileTitle; +/// generated route for +/// [_i27.ChatNumberAccount] +class ChatNumberAccount extends _i40.PageRouteInfo { + const ChatNumberAccount({List<_i40.PageRouteInfo>? children}) + : super( + ChatNumberAccount.name, + initialChildren: children, + ); - final String? fileDescription; + static const String name = 'ChatNumberAccount'; - @override - String toString() { - return 'ReplicaUploadTitleArgs{key: $key, fileToUpload: $fileToUpload, fileTitle: $fileTitle, fileDescription: $fileDescription}'; - } + static const _i40.PageInfo page = _i40.PageInfo(name); } /// generated route for -/// [_i27.ReplicaUploadDescription] -class ReplicaUploadDescription - extends _i35.PageRouteInfo { - ReplicaUploadDescription({ - _i37.Key? key, - required _i38.File fileToUpload, - required String fileTitle, - String? fileDescription, +/// [_i28.SplitTunneling] +class SplitTunneling extends _i40.PageRouteInfo { + SplitTunneling({ + _i45.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( - ReplicaUploadDescription.name, - path: 'replicaUploadDescription', - args: ReplicaUploadDescriptionArgs( - key: key, - fileToUpload: fileToUpload, - fileTitle: fileTitle, - fileDescription: fileDescription, - ), + SplitTunneling.name, + args: SplitTunnelingArgs(key: key), + initialChildren: children, ); - static const String name = 'ReplicaUploadDescription'; -} - -class ReplicaUploadDescriptionArgs { - const ReplicaUploadDescriptionArgs({ - this.key, - required this.fileToUpload, - required this.fileTitle, - this.fileDescription, - }); - - final _i37.Key? key; + static const String name = 'SplitTunneling'; - final _i38.File fileToUpload; + static const _i40.PageInfo page = + _i40.PageInfo(name); +} - final String fileTitle; +class SplitTunnelingArgs { + const SplitTunnelingArgs({this.key}); - final String? fileDescription; + final _i45.Key? key; @override String toString() { - return 'ReplicaUploadDescriptionArgs{key: $key, fileToUpload: $fileToUpload, fileTitle: $fileTitle, fileDescription: $fileDescription}'; + return 'SplitTunnelingArgs{key: $key}'; } } /// generated route for -/// [_i28.ReplicaUploadReview] -class ReplicaUploadReview extends _i35.PageRouteInfo { - ReplicaUploadReview({ - _i37.Key? key, - required _i38.File fileToUpload, - required String fileTitle, - String? fileDescription, +/// [_i29.BlockedUsers] +class BlockedUsers extends _i40.PageRouteInfo { + BlockedUsers({ + _i44.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( - ReplicaUploadReview.name, - path: 'replicaUploadReview', - args: ReplicaUploadReviewArgs( - key: key, - fileToUpload: fileToUpload, - fileTitle: fileTitle, - fileDescription: fileDescription, - ), + BlockedUsers.name, + args: BlockedUsersArgs(key: key), + initialChildren: children, ); - static const String name = 'ReplicaUploadReview'; -} - -class ReplicaUploadReviewArgs { - const ReplicaUploadReviewArgs({ - this.key, - required this.fileToUpload, - required this.fileTitle, - this.fileDescription, - }); - - final _i37.Key? key; + static const String name = 'BlockedUsers'; - final _i38.File fileToUpload; + static const _i40.PageInfo page = + _i40.PageInfo(name); +} - final String fileTitle; +class BlockedUsersArgs { + const BlockedUsersArgs({this.key}); - final String? fileDescription; + final _i44.Key? key; @override String toString() { - return 'ReplicaUploadReviewArgs{key: $key, fileToUpload: $fileToUpload, fileTitle: $fileTitle, fileDescription: $fileDescription}'; + return 'BlockedUsersArgs{key: $key}'; } } /// generated route for -/// [_i29.ReplicaLinkHandler] -class ReplicaLinkHandler extends _i35.PageRouteInfo { - ReplicaLinkHandler({ - _i37.Key? key, - required _i39.ReplicaApi replicaApi, - required _i39.ReplicaLink replicaLink, +/// [_i30.Language] +class Language extends _i40.PageRouteInfo { + Language({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( - ReplicaLinkHandler.name, - path: 'replicaLinkHandler', - args: ReplicaLinkHandlerArgs( - key: key, - replicaApi: replicaApi, - replicaLink: replicaLink, - ), + Language.name, + args: LanguageArgs(key: key), + initialChildren: children, ); - static const String name = 'ReplicaLinkHandler'; -} - -class ReplicaLinkHandlerArgs { - const ReplicaLinkHandlerArgs({ - this.key, - required this.replicaApi, - required this.replicaLink, - }); + static const String name = 'Language'; - final _i37.Key? key; + static const _i40.PageInfo page = + _i40.PageInfo(name); +} - final _i39.ReplicaApi replicaApi; +class LanguageArgs { + const LanguageArgs({this.key}); - final _i39.ReplicaLink replicaLink; + final _i41.Key? key; @override String toString() { - return 'ReplicaLinkHandlerArgs{key: $key, replicaApi: $replicaApi, replicaLink: $replicaLink}'; + return 'LanguageArgs{key: $key}'; } } /// generated route for -/// [_i30.ReplicaMiscViewer] -class ReplicaMiscViewer extends _i35.PageRouteInfo { - ReplicaMiscViewer({ - required _i39.ReplicaApi replicaApi, - required _i39.ReplicaSearchItem item, - required _i39.SearchCategory category, +/// [_i31.Settings] +class Settings extends _i40.PageRouteInfo { + Settings({ + _i41.Key? key, + List<_i40.PageRouteInfo>? children, }) : super( - ReplicaMiscViewer.name, - path: 'replicaMiscViewer', - args: ReplicaMiscViewerArgs( - replicaApi: replicaApi, - item: item, - category: category, - ), + Settings.name, + args: SettingsArgs(key: key), + initialChildren: children, ); - static const String name = 'ReplicaMiscViewer'; -} - -class ReplicaMiscViewerArgs { - const ReplicaMiscViewerArgs({ - required this.replicaApi, - required this.item, - required this.category, - }); + static const String name = 'Settings'; - final _i39.ReplicaApi replicaApi; + static const _i40.PageInfo page = + _i40.PageInfo(name); +} - final _i39.ReplicaSearchItem item; +class SettingsArgs { + const SettingsArgs({this.key}); - final _i39.SearchCategory category; + final _i41.Key? key; @override String toString() { - return 'ReplicaMiscViewerArgs{replicaApi: $replicaApi, item: $item, category: $category}'; + return 'SettingsArgs{key: $key}'; } } /// generated route for -/// [_i31.ReplicaImageViewer] -class ReplicaImageViewer extends _i35.PageRouteInfo { - ReplicaImageViewer({ - required _i39.ReplicaApi replicaApi, - required _i39.ReplicaSearchItem item, - required _i39.SearchCategory category, - }) : super( - ReplicaImageViewer.name, - path: 'replicaImageViewer', - args: ReplicaImageViewerArgs( - replicaApi: replicaApi, - item: item, - category: category, - ), +/// [_i32.AddViaChatNumber] +class AddViaChatNumber extends _i40.PageRouteInfo { + const AddViaChatNumber({List<_i40.PageRouteInfo>? children}) + : super( + AddViaChatNumber.name, + initialChildren: children, ); - static const String name = 'ReplicaImageViewer'; + static const String name = 'AddViaChatNumber'; + + static const _i40.PageInfo page = _i40.PageInfo(name); } -class ReplicaImageViewerArgs { - const ReplicaImageViewerArgs({ - required this.replicaApi, - required this.item, - required this.category, - }); +/// generated route for +/// [_i33.ContactInfo] +class ContactInfo extends _i40.PageRouteInfo { + ContactInfo({ + required _i44.Contact contact, + List<_i40.PageRouteInfo>? children, + }) : super( + ContactInfo.name, + args: ContactInfoArgs(contact: contact), + initialChildren: children, + ); + + static const String name = 'ContactInfo'; - final _i39.ReplicaApi replicaApi; + static const _i40.PageInfo page = + _i40.PageInfo(name); +} - final _i39.ReplicaSearchItem item; +class ContactInfoArgs { + const ContactInfoArgs({required this.contact}); - final _i39.SearchCategory category; + final _i44.Contact contact; @override String toString() { - return 'ReplicaImageViewerArgs{replicaApi: $replicaApi, item: $item, category: $category}'; + return 'ContactInfoArgs{contact: $contact}'; } } /// generated route for -/// [_i32.ReplicaVideoViewer] -class ReplicaVideoViewer extends _i35.PageRouteInfo { - ReplicaVideoViewer({ - required _i39.ReplicaApi replicaApi, - required _i39.ReplicaSearchItem item, - required _i39.SearchCategory category, +/// [_i34.NewChat] +class NewChat extends _i40.PageRouteInfo { + const NewChat({List<_i40.PageRouteInfo>? children}) + : super( + NewChat.name, + initialChildren: children, + ); + + static const String name = 'NewChat'; + + static const _i40.PageInfo page = _i40.PageInfo(name); +} + +/// generated route for +/// [_i35.Introductions] +class Introductions extends _i40.PageRouteInfo { + const Introductions({List<_i40.PageRouteInfo>? children}) + : super( + Introductions.name, + initialChildren: children, + ); + + static const String name = 'Introductions'; + + static const _i40.PageInfo page = _i40.PageInfo(name); +} + +/// generated route for +/// [_i36.Introduce] +class Introduce extends _i40.PageRouteInfo { + Introduce({ + required bool singleIntro, + _i44.Contact? contactToIntro, + List<_i40.PageRouteInfo>? children, }) : super( - ReplicaVideoViewer.name, - path: 'replicaVideoViewer', - args: ReplicaVideoViewerArgs( - replicaApi: replicaApi, - item: item, - category: category, + Introduce.name, + args: IntroduceArgs( + singleIntro: singleIntro, + contactToIntro: contactToIntro, ), + initialChildren: children, ); - static const String name = 'ReplicaVideoViewer'; + static const String name = 'Introduce'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class ReplicaVideoViewerArgs { - const ReplicaVideoViewerArgs({ - required this.replicaApi, - required this.item, - required this.category, +class IntroduceArgs { + const IntroduceArgs({ + required this.singleIntro, + this.contactToIntro, }); - final _i39.ReplicaApi replicaApi; - - final _i39.ReplicaSearchItem item; + final bool singleIntro; - final _i39.SearchCategory category; + final _i44.Contact? contactToIntro; @override String toString() { - return 'ReplicaVideoViewerArgs{replicaApi: $replicaApi, item: $item, category: $category}'; + return 'IntroduceArgs{singleIntro: $singleIntro, contactToIntro: $contactToIntro}'; } } /// generated route for -/// [_i33.ReplicaAudioViewer] -class ReplicaAudioViewer extends _i35.PageRouteInfo { - ReplicaAudioViewer({ - required _i39.ReplicaApi replicaApi, - required _i39.ReplicaSearchItem item, - required _i39.SearchCategory category, +/// [_i37.ChatNumberRecovery] +class ChatNumberRecovery extends _i40.PageRouteInfo { + const ChatNumberRecovery({List<_i40.PageRouteInfo>? children}) + : super( + ChatNumberRecovery.name, + initialChildren: children, + ); + + static const String name = 'ChatNumberRecovery'; + + static const _i40.PageInfo page = _i40.PageInfo(name); +} + +/// generated route for +/// [_i38.ChatNumberMessaging] +class ChatNumberMessaging extends _i40.PageRouteInfo { + const ChatNumberMessaging({List<_i40.PageRouteInfo>? children}) + : super( + ChatNumberMessaging.name, + initialChildren: children, + ); + + static const String name = 'ChatNumberMessaging'; + + static const _i40.PageInfo page = _i40.PageInfo(name); +} + +/// generated route for +/// [_i39.Conversation] +class Conversation extends _i40.PageRouteInfo { + Conversation({ + required _i44.ContactId contactId, + int? initialScrollIndex, + bool showContactEditingDialog = false, + List<_i40.PageRouteInfo>? children, }) : super( - ReplicaAudioViewer.name, - path: 'replicaAudioViewer', - args: ReplicaAudioViewerArgs( - replicaApi: replicaApi, - item: item, - category: category, + Conversation.name, + args: ConversationArgs( + contactId: contactId, + initialScrollIndex: initialScrollIndex, + showContactEditingDialog: showContactEditingDialog, ), + initialChildren: children, ); - static const String name = 'ReplicaAudioViewer'; + static const String name = 'Conversation'; + + static const _i40.PageInfo page = + _i40.PageInfo(name); } -class ReplicaAudioViewerArgs { - const ReplicaAudioViewerArgs({ - required this.replicaApi, - required this.item, - required this.category, +class ConversationArgs { + const ConversationArgs({ + required this.contactId, + this.initialScrollIndex, + this.showContactEditingDialog = false, }); - final _i39.ReplicaApi replicaApi; + final _i44.ContactId contactId; - final _i39.ReplicaSearchItem item; + final int? initialScrollIndex; - final _i39.SearchCategory category; + final bool showContactEditingDialog; @override String toString() { - return 'ReplicaAudioViewerArgs{replicaApi: $replicaApi, item: $item, category: $category}'; + return 'ConversationArgs{contactId: $contactId, initialScrollIndex: $initialScrollIndex, showContactEditingDialog: $showContactEditingDialog}'; } } - -/// generated route for -/// [_i34.Support] -class Support extends _i35.PageRouteInfo { - const Support() - : super( - Support.name, - path: 'support', - ); - - static const String name = 'Support'; -} diff --git a/lib/flutter_driver_extensions/add_dummy_contacts_command.dart b/lib/flutter_driver_extensions/add_dummy_contacts_command.dart deleted file mode 100644 index a5c4f7bd5..000000000 --- a/lib/flutter_driver_extensions/add_dummy_contacts_command.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter_driver/flutter_driver.dart'; - -class AddDummyContactsCommand extends Command { - AddDummyContactsCommand({Duration? timeout}) : super(timeout: timeout); - - @override - String get kind => 'AddDummyContactsCommand'; - - AddDummyContactsCommand.deserialize(Map json) - : super.deserialize(json); - - @override - Map serialize() { - return super.serialize(); - } -} - -class AddDummyContactsCommandResult extends Result { - const AddDummyContactsCommandResult(this.added); - - final bool added; - - @override - Map toJson() { - return { - 'added': true, - }; - } -} diff --git a/lib/flutter_driver_extensions/add_dummy_contacts_command_extension.dart b/lib/flutter_driver_extensions/add_dummy_contacts_command_extension.dart deleted file mode 100644 index da3fedce1..000000000 --- a/lib/flutter_driver_extensions/add_dummy_contacts_command_extension.dart +++ /dev/null @@ -1,36 +0,0 @@ -import 'package:flutter_driver/driver_extension.dart'; -import 'package:flutter_driver/flutter_driver.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:lantern/messaging/messaging_model.dart'; - -import 'add_dummy_contacts_command.dart'; - -class AddDummyContactsCommandExtension extends CommandExtension { - @override - String get commandKind => 'AddDummyContactsCommand'; - - @override - Future call( - Command command, - WidgetController prober, - CreateFinderFactory finderFactory, - CommandHandlerFactory handlerFactory, - ) async { - try { - messagingModel.addDummyContacts(); - return const AddDummyContactsCommandResult(true); - } catch (e) { - print('something went wrong while adding dummy contacts'); - return const AddDummyContactsCommandResult(false); - } - } - - @override - Command deserialize( - Map params, - DeserializeFinderFactory finderFactory, - DeserializeCommandFactory commandFactory, - ) { - return AddDummyContactsCommand.deserialize(params); - } -} diff --git a/lib/flutter_driver_extensions/navigate_command.dart b/lib/flutter_driver_extensions/navigate_command.dart deleted file mode 100644 index ccaf97436..000000000 --- a/lib/flutter_driver_extensions/navigate_command.dart +++ /dev/null @@ -1,34 +0,0 @@ -import 'package:flutter_driver/flutter_driver.dart'; - -class NavigateCommand extends Command { - static const String home = 'home'; - - NavigateCommand(this.path, {Duration? timeout}) : super(timeout: timeout); - - @override - String get kind => 'NavigateCommand'; - - final String path; - - NavigateCommand.deserialize(Map json) - : path = json['path']!, - super.deserialize(json); - - @override - Map serialize() { - return super.serialize()..addAll({'path': path}); - } -} - -class NavigateCommandResult extends Result { - const NavigateCommandResult(this.navigated); - - final bool navigated; - - @override - Map toJson() { - return { - 'navigated': true, - }; - } -} diff --git a/lib/flutter_driver_extensions/navigate_command_extension.dart b/lib/flutter_driver_extensions/navigate_command_extension.dart deleted file mode 100644 index 3ea733de3..000000000 --- a/lib/flutter_driver_extensions/navigate_command_extension.dart +++ /dev/null @@ -1,39 +0,0 @@ -import 'package:flutter_driver/driver_extension.dart'; -import 'package:flutter_driver/flutter_driver.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import '../app.dart'; -import '../common/common.dart'; -import 'navigate_command.dart'; - -class NavigateCommandExtension extends CommandExtension { - @override - String get commandKind => 'NavigateCommand'; - - @override - Future call( - Command command, - WidgetController prober, - CreateFinderFactory finderFactory, - CommandHandlerFactory handlerFactory, - ) async { - final navigateCommand = command as NavigateCommand; - - switch (navigateCommand.path) { - case NavigateCommand.home: - navigatorKey.currentContext?.router.popUntilRoot(); - return const NavigateCommandResult(true); - } - - return const NavigateCommandResult(false); - } - - @override - Command deserialize( - Map params, - DeserializeFinderFactory finderFactory, - DeserializeCommandFactory commandFactory, - ) { - return NavigateCommand.deserialize(params); - } -} diff --git a/lib/flutter_driver_extensions/reset_flags_command.dart b/lib/flutter_driver_extensions/reset_flags_command.dart deleted file mode 100644 index db03b10a0..000000000 --- a/lib/flutter_driver_extensions/reset_flags_command.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter_driver/flutter_driver.dart'; - -class ResetFlagsCommand extends Command { - ResetFlagsCommand({Duration? timeout}) : super(timeout: timeout); - - @override - String get kind => 'ResetFlagsCommand'; - - ResetFlagsCommand.deserialize(Map json) - : super.deserialize(json); - - @override - Map serialize() { - return super.serialize(); - } -} - -class ResetFlagsCommandResult extends Result { - const ResetFlagsCommandResult(this.isDevPanelHidden); - - final bool isDevPanelHidden; - - @override - Map toJson() { - return { - 'isDevPanelHidden': true, - }; - } -} diff --git a/lib/flutter_driver_extensions/reset_flags_command_extension.dart b/lib/flutter_driver_extensions/reset_flags_command_extension.dart deleted file mode 100644 index 1fc12468a..000000000 --- a/lib/flutter_driver_extensions/reset_flags_command_extension.dart +++ /dev/null @@ -1,37 +0,0 @@ -import 'package:flutter_driver/driver_extension.dart'; -import 'package:flutter_driver/flutter_driver.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:lantern/messaging/messaging_model.dart'; - -import 'reset_flags_command.dart'; - -class ResetFlagsCommandExtension extends CommandExtension { - @override - String get commandKind => 'ResetFlagsCommand'; - - @override - Future call( - Command command, - WidgetController prober, - CreateFinderFactory finderFactory, - CommandHandlerFactory handlerFactory, - ) async { - try { - await messagingModel.resetFlags(); - await messagingModel.resetTimestamps(); - return const ResetFlagsCommandResult(true); - } catch (e) { - print('something went wrong while resetting Chat flags and timestamps'); - return const ResetFlagsCommandResult(false); - } - } - - @override - Command deserialize( - Map params, - DeserializeFinderFactory finderFactory, - DeserializeCommandFactory commandFactory, - ) { - return ResetFlagsCommand.deserialize(params); - } -} diff --git a/lib/flutter_driver_extensions/send_dummy_files_command.dart b/lib/flutter_driver_extensions/send_dummy_files_command.dart deleted file mode 100644 index a3ec8f82d..000000000 --- a/lib/flutter_driver_extensions/send_dummy_files_command.dart +++ /dev/null @@ -1,29 +0,0 @@ -import 'package:flutter_driver/flutter_driver.dart'; - -class SendDummyFilesCommand extends Command { - SendDummyFilesCommand({Duration? timeout}) : super(timeout: timeout); - - @override - String get kind => 'SendDummyFilesCommand'; - - SendDummyFilesCommand.deserialize(Map json) - : super.deserialize(json); - - @override - Map serialize() { - return super.serialize(); - } -} - -class SendDummyFilesCommandResult extends Result { - const SendDummyFilesCommandResult(this.sent); - - final bool sent; - - @override - Map toJson() { - return { - 'sent': true, - }; - } -} diff --git a/lib/flutter_driver_extensions/send_dummy_files_command_extension.dart b/lib/flutter_driver_extensions/send_dummy_files_command_extension.dart deleted file mode 100644 index a19dfa01d..000000000 --- a/lib/flutter_driver_extensions/send_dummy_files_command_extension.dart +++ /dev/null @@ -1,60 +0,0 @@ -import 'package:flutter_driver/driver_extension.dart'; -import 'package:flutter_driver/flutter_driver.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:lantern/messaging/messaging_model.dart'; - -import 'send_dummy_files_command.dart'; - -class SendDummyFilesCommandExtension extends CommandExtension { - @override - String get commandKind => 'SendDummyFilesCommand'; - - @override - Future call( - Command command, - WidgetController prober, - CreateFinderFactory finderFactory, - CommandHandlerFactory handlerFactory, - ) async { - try { - // download video from URL and save to storage - await Future.wait([ - // file:///storage/emulated/0/Android/data/org.getlantern.lantern/files/testing/test_image.jpg - messagingModel.saveDummyAttachment( - 'https://d2w9rnfcy7mm78.cloudfront.net/1071899/original_6f46cf68dbdec64a3715e98a9ab3cb8c.jpg', - 'test_image.jpg', - ), - // file:///storage/emulated/0/Android/data/org.getlantern.lantern/files/testing/test_video.mov - messagingModel.saveDummyAttachment( - 'https://filesamples.com/samples/video/mov/sample_640x360.mov', - 'test_video.mov', - ), - ]); - - // create attachment in messaging and send to direct contact - await Future.wait([ - messagingModel.sendDummyAttachment('test_image.jpg', { - 'fileName': 'test_image.jpg', - 'fileExtension': 'jpg', - }), - messagingModel.sendDummyAttachment('test_video.mov', { - 'fileName': 'test_video.mov', - 'fileExtension': 'mov', - }), - ]); - return const SendDummyFilesCommandResult(true); - } catch (e) { - print('something went wrong while downloading and sharing dummy files'); - return const SendDummyFilesCommandResult(false); - } - } - - @override - Command deserialize( - Map params, - DeserializeFinderFactory finderFactory, - DeserializeCommandFactory commandFactory, - ) { - return SendDummyFilesCommand.deserialize(params); - } -} diff --git a/lib/generated/assets.dart b/lib/generated/assets.dart deleted file mode 100644 index 64ed75692..000000000 --- a/lib/generated/assets.dart +++ /dev/null @@ -1,148 +0,0 @@ -///This file is automatically generated. DO NOT EDIT, all your changes would be lost. -class Assets { - Assets._(); - - static const String countdownStopwatchTimer0 = 'assets/images/countdown_stopwatch/timer_0.svg'; - static const String countdownStopwatchTimer1 = 'assets/images/countdown_stopwatch/timer_1.svg'; - static const String countdownStopwatchTimer10 = 'assets/images/countdown_stopwatch/timer_10.svg'; - static const String countdownStopwatchTimer11 = 'assets/images/countdown_stopwatch/timer_11.svg'; - static const String countdownStopwatchTimer12 = 'assets/images/countdown_stopwatch/timer_12.svg'; - static const String countdownStopwatchTimer2 = 'assets/images/countdown_stopwatch/timer_2.svg'; - static const String countdownStopwatchTimer3 = 'assets/images/countdown_stopwatch/timer_3.svg'; - static const String countdownStopwatchTimer4 = 'assets/images/countdown_stopwatch/timer_4.svg'; - static const String countdownStopwatchTimer5 = 'assets/images/countdown_stopwatch/timer_5.svg'; - static const String countdownStopwatchTimer6 = 'assets/images/countdown_stopwatch/timer_6.svg'; - static const String countdownStopwatchTimer7 = 'assets/images/countdown_stopwatch/timer_7.svg'; - static const String countdownStopwatchTimer8 = 'assets/images/countdown_stopwatch/timer_8.svg'; - static const String countdownStopwatchTimer9 = 'assets/images/countdown_stopwatch/timer_9.svg'; - static const String fontsRobotoMono = 'assets/fonts/RobotoMono.ttf'; - static const String fontsSamim = 'assets/fonts/Samim.ttf'; - static const String imagesAccount = 'assets/images/account.svg'; - static const String imagesAccountRemove = 'assets/images/account_remove.svg'; - static const String imagesAdd = 'assets/images/add.svg'; - static const String imagesAddCircle = 'assets/images/add_circle.svg'; - static const String imagesAlert = 'assets/images/alert.svg'; - static const String imagesArrowBack = 'assets/images/arrow_back.svg'; - static const String imagesArrowDown = 'assets/images/arrow_down.svg'; - static const String imagesArrowUp = 'assets/images/arrow_up.svg'; - static const String imagesAudioBlack = 'assets/images/audio_black.svg'; - static const String imagesBackup = 'assets/images/backup.svg'; - static const String imagesBackupIcon = 'assets/images/backup_icon.svg'; - static const String imagesBadge = 'assets/images/badge.svg'; - static const String imagesBlock = 'assets/images/block.svg'; - static const String imagesCancel = 'assets/images/cancel.svg'; - static const String imagesChatNumber = 'assets/images/chatNumber.svg'; - static const String imagesCheckBlack = 'assets/images/check_black.svg'; - static const String imagesCheckCircleOutline = 'assets/images/check_circle_outline.svg'; - static const String imagesCheckGreen = 'assets/images/check_green.svg'; - static const String imagesCheckGreenLarge = 'assets/images/check_green_large.svg'; - static const String imagesClock = 'assets/images/clock.svg'; - static const String imagesContentCopy = 'assets/images/content_copy.svg'; - static const String imagesContentCopyOutline = 'assets/images/content_copy_outline.svg'; - static const String imagesDelete = 'assets/images/delete.svg'; - static const String imagesDesktop = 'assets/images/desktop.svg'; - static const String imagesDevices = 'assets/images/devices.svg'; - static const String imagesDiscover = 'assets/images/discover.svg'; - static const String imagesDocBlack = 'assets/images/doc_black.svg'; - static const String imagesDoneAll = 'assets/images/done_all.svg'; - static const String imagesDropdown = 'assets/images/dropdown.svg'; - static const String imagesEmail = 'assets/images/email.svg'; - static const String imagesEmptyChats = 'assets/images/empty_chats.svg'; - static const String imagesEmptyChatsRtl = 'assets/images/empty_chats_rtl.svg'; - static const String imagesEmptySearch = 'assets/images/empty_search.svg'; - static const String imagesError = 'assets/images/error.svg'; - static const String imagesErrorOutline = 'assets/images/error_outline.svg'; - static const String imagesFastForward = 'assets/images/fast_forward.svg'; - static const String imagesFastRewind = 'assets/images/fast_rewind.svg'; - static const String imagesFileDownload = 'assets/images/file_download.svg'; - static const String imagesFileUpload = 'assets/images/file_upload.svg'; - static const String imagesForum = 'assets/images/forum.svg'; - static const String imagesFreeLogo = 'assets/images/free_logo.svg'; - static const String imagesFullscreenIcon = 'assets/images/fullscreen_icon.svg'; - static const String imagesHangup = 'assets/images/hangup.svg'; - static const String imagesImageInactive = 'assets/images/image_inactive.svg'; - static const String imagesInfo = 'assets/images/info.svg'; - static const String imagesInsertDriveFile = 'assets/images/insert_drive_file.svg'; - static const String imagesInsertEmoticon = 'assets/images/insert_emoticon.svg'; - static const String imagesIntroduceContact = 'assets/images/introduce_contact.svg'; - static const String imagesIntroducingIllustrationBubble = 'assets/images/introducing_illustration_bubble.svg'; - static const String imagesIntroducingIllustrationLock = 'assets/images/introducing_illustration_lock.svg'; - static const String imagesKey = 'assets/images/key.svg'; - static const String imagesKeyboard = 'assets/images/keyboard.svg'; - static const String imagesKeyboardArrowRight = 'assets/images/keyboard_arrow_right.svg'; - static const String imagesLanternLogo = 'assets/images/lantern_logo.svg'; - static const String imagesLocationOn = 'assets/images/location_on.svg'; - static const String imagesLockClock = 'assets/images/lock_clock.svg'; - static const String imagesLockOutline = 'assets/images/lock_outline.svg'; - static const String imagesMessages = 'assets/images/messages.svg'; - static const String imagesMic = 'assets/images/mic.svg'; - static const String imagesModeEdit = 'assets/images/mode_edit.svg'; - static const String imagesMoreVert = 'assets/images/more_vert.svg'; - static const String imagesMute = 'assets/images/mute.svg'; - static const String imagesNewspaper = 'assets/images/newspaper.svg'; - static const String imagesNotifications = 'assets/images/notifications.svg'; - static const String imagesNumber1 = 'assets/images/number_1.svg'; - static const String imagesNumber2 = 'assets/images/number_2.svg'; - static const String imagesOpen = 'assets/images/open.svg'; - static const String imagesOpenInNew = 'assets/images/open_in_new.svg'; - static const String imagesPause = 'assets/images/pause.svg'; - static const String imagesPauseCircleFilled = 'assets/images/pause_circle_filled.svg'; - static const String imagesPauseCircleOutline = 'assets/images/pause_circle_outline.svg'; - static const String imagesPauseCircleOutlineCustom = 'assets/images/pause_circle_outline_custom.svg'; - static const String imagesPdfBlack = 'assets/images/pdf_black.svg'; - static const String imagesPending = 'assets/images/pending.svg'; - static const String imagesPeople = 'assets/images/people.svg'; - static const String imagesPersonAddAlt1 = 'assets/images/person_add_alt_1.svg'; - static const String imagesPhone = 'assets/images/phone.svg'; - static const String imagesPlayArrow = 'assets/images/play_arrow.svg'; - static const String imagesPlayCircleFilled = 'assets/images/play_circle_filled.svg'; - static const String imagesPlayCircleFilledCustom = 'assets/images/play_circle_filled_custom.svg'; - static const String imagesProIconBlack = 'assets/images/pro_icon_black.svg'; - static const String imagesProIconYellow = 'assets/images/pro_icon_yellow.svg'; - static const String imagesProLogo = 'assets/images/pro_logo.svg'; - static const String imagesQrCode = 'assets/images/qr_code.svg'; - static const String imagesQrCodeBg = 'assets/images/qr_code_bg.svg'; - static const String imagesQrCodeScanner = 'assets/images/qr_code_scanner.svg'; - static const String imagesReply = 'assets/images/reply.svg'; - static const String imagesSearch = 'assets/images/search.svg'; - static const String imagesSearchEmpty = 'assets/images/search_empty.svg'; - static const String imagesSendRounded = 'assets/images/send_rounded.svg'; - static const String imagesSettings = 'assets/images/settings.svg'; - static const String imagesShare = 'assets/images/share.svg'; - static const String imagesSpeaker = 'assets/images/speaker.svg'; - static const String imagesSplitTunneling = 'assets/images/split_tunneling.svg'; - static const String imagesSpreadsheetBlack = 'assets/images/spreadsheet_black.svg'; - static const String imagesStar = 'assets/images/star.svg'; - static const String imagesStickerFigureBackground = 'assets/images/sticker_figure_background.svg'; - static const String imagesStickerFigureForeground = 'assets/images/sticker_figure_foreground.svg'; - static const String imagesSupport = 'assets/images/support.svg'; - static const String imagesTimer = 'assets/images/timer.svg'; - static const String imagesTrailingIcon = 'assets/images/trailing_icon.svg'; - static const String imagesTranslate = 'assets/images/translate.svg'; - static const String imagesUnknownBlack = 'assets/images/unknown_black.svg'; - static const String imagesUpdate = 'assets/images/update.svg'; - static const String imagesUser = 'assets/images/user.svg'; - static const String imagesVerificationAlert = 'assets/images/verification_alert.svg'; - static const String imagesVerifiedUser = 'assets/images/verified_user.svg'; - static const String imagesVideoBlack = 'assets/images/video_black.svg'; - static const String imagesVolumeUp = 'assets/images/volume_up.svg'; - static const String imagesWelcomeIllustration = 'assets/images/welcome_illustration.svg'; - static const String imagesZipBlack = 'assets/images/zip_black.svg'; - static const String localesAr = 'assets/locales/ar.po'; - static const String localesBn = 'assets/locales/bn.po'; - static const String localesEn = 'assets/locales/en.po'; - static const String localesEs = 'assets/locales/es.po'; - static const String localesFa = 'assets/locales/fa.po'; - static const String localesFr = 'assets/locales/fr.po'; - static const String localesHi = 'assets/locales/hi.po'; - static const String localesMs = 'assets/locales/ms.po'; - static const String localesMy = 'assets/locales/my.po'; - static const String localesRu = 'assets/locales/ru.po'; - static const String localesTr = 'assets/locales/tr.po'; - static const String localesUr = 'assets/locales/ur.po'; - static const String localesVi = 'assets/locales/vi.po'; - static const String localesZhCn = 'assets/locales/zh-cn.po'; - static const String localesZhHk = 'assets/locales/zh-hk.po'; - static const String soundsRinging = 'assets/sounds/ringing.mp3'; - -} diff --git a/lib/home.dart b/lib/home.dart index 28f1ece06..afffaceae 100644 --- a/lib/home.dart +++ b/lib/home.dart @@ -1,5 +1,6 @@ import 'package:lantern/account/account_tab.dart'; import 'package:lantern/account/developer_settings.dart'; +import 'package:lantern/account/privacy_disclosure.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/custom_bottom_bar.dart'; import 'package:lantern/messaging/chats.dart'; @@ -12,8 +13,9 @@ import 'package:logger/logger.dart'; import 'messaging/messaging_model.dart'; +@RoutePage(name: 'Home') class HomePage extends StatefulWidget { - HomePage({Key? key}) : super(key: key); + const HomePage({Key? key}) : super(key: key); @override _HomePageState createState() => _HomePageState(); @@ -116,39 +118,49 @@ class _HomePageState extends State { @override Widget build(BuildContext context) { _context = context; - return const SizedBox(); - // return sessionModel.developmentMode( - // (BuildContext context, bool developmentMode, Widget? child) { - // if (developmentMode) { - // Logger.level = Level.verbose; - // } else { - // Logger.level = Level.error; - // } - // return sessionModel.language( - // (BuildContext context, String lang, Widget? child) { - // Localization.locale = lang; - // return sessionModel.selectedTab( - // (context, selectedTab, child) => - // messagingModel.getOnBoardingStatus((_, isOnboarded, child) { - // final isTesting = const String.fromEnvironment( - // 'driver', - // defaultValue: 'false', - // ).toLowerCase() == - // 'true'; - // return Scaffold( - // body: buildBody(selectedTab, isOnboarded), - // bottomNavigationBar: CustomBottomBar( - // selectedTab: selectedTab, - // isDevelop: developmentMode, - // isTesting: isTesting, - // ), - // ); - // }), - // ); - // }, - // ); - // }, - // ); + return sessionModel.acceptedTermsVersion( + (BuildContext context, int version, Widget? child) { + return sessionModel.developmentMode( + (BuildContext context, bool developmentMode, Widget? child) { + if (developmentMode) { + Logger.level = Level.verbose; + } else { + Logger.level = Level.error; + } + bool isPlayVersion = sessionModel.isPlayVersion.value != null && + sessionModel.isPlayVersion.value!; + if (isPlayVersion && version == 0) { + // show privacy disclosure if it's a Play build and the terms have + // not already been accepted + return PrivacyDisclosure(); + } + return sessionModel.language( + (BuildContext context, String lang, Widget? child) { + Localization.locale = lang; + return sessionModel.selectedTab( + (context, selectedTab, child) => messagingModel + .getOnBoardingStatus((_, isOnboarded, child) { + final isTesting = const String.fromEnvironment( + 'driver', + defaultValue: 'false', + ).toLowerCase() == + 'true'; + return Scaffold( + body: buildBody(selectedTab, isOnboarded), + bottomNavigationBar: CustomBottomBar( + selectedTab: selectedTab, + isDevelop: developmentMode, + isTesting: isTesting, + ), + ); + }), + ); + }, + ); + }, + ); + }, + ); } Widget buildBody(String selectedTab, bool? isOnboarded) { diff --git a/lib/lantern_navigator.dart b/lib/lantern_navigator.dart deleted file mode 100644 index d739f8a95..000000000 --- a/lib/lantern_navigator.dart +++ /dev/null @@ -1,20 +0,0 @@ -import 'package:flutter/services.dart'; - -class LanternNavigator { - static MethodChannel methodChannel = - const MethodChannel('navigator_method_channel'); - - static void startScreen(String screenName) { - methodChannel.invokeMethod( - 'startScreen', - {'screenName': screenName}, - ); - } - - static const String SCREEN_INVITE_FRIEND = 'SCREEN_INVITE_FRIEND'; - static const String SCREEN_DESKTOP_VERSION = 'SCREEN_DESKTOP_VERSION'; - static const String SCREEN_LINK_PIN = 'SCREEN_LINK_PIN'; - static const String SCREEN_SCREEN_REPORT_ISSUE = 'SCREEN_SCREEN_REPORT_ISSUE'; - static const String SCREEN_UPGRADE_TO_LANTERN_PRO = - 'SCREEN_UPGRADE_TO_LANTERN_PRO'; -} diff --git a/lib/main.dart b/lib/main.dart index a3411e2ec..3d0a8fb1d 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,7 +1,9 @@ +import 'package:flutter_driver/driver_extension.dart'; +import 'package:google_mobile_ads/google_mobile_ads.dart'; +import 'package:lantern/app.dart'; import 'package:lantern/catcher_setup.dart'; import 'package:lantern/common/common.dart'; -import 'package:flutter_driver/driver_extension.dart'; -import 'app.dart'; + Future main() async { // CI will be true only when running appium test @@ -11,6 +13,13 @@ Future main() async { enableFlutterDriverExtension(); } WidgetsFlutterBinding.ensureInitialized(); + await _initGoogleMobileAds(); await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); setupCatcherAndRun(LanternApp()); } + +Future _initGoogleMobileAds() async { + await MobileAds.instance.initialize(); + await MobileAds.instance.setAppMuted(true); + // await MobileAds.instance.updateRequestConfiguration(RequestConfiguration(testDeviceIds: ['D79728264130CE0918737B5A2178D362'])); +} diff --git a/lib/messaging/contacts/add_contact_QR.dart b/lib/messaging/contacts/add_contact_QR.dart index 7f2d7a86f..79b9dd094 100644 --- a/lib/messaging/contacts/add_contact_QR.dart +++ b/lib/messaging/contacts/add_contact_QR.dart @@ -335,7 +335,7 @@ class _AddViaQRState extends State with TickerProviderStateMixin { data: widget.me.contactId.id, padding: const EdgeInsets.all(16), backgroundColor: white, - foregroundColor: black, + eyeStyle: QrEyeStyle(color: black), errorCorrectionLevel: QrErrorCorrectLevel.H, ), ), diff --git a/lib/messaging/contacts/add_contact_number.dart b/lib/messaging/contacts/add_contact_number.dart index 6b1352b2f..50f999209 100644 --- a/lib/messaging/contacts/add_contact_number.dart +++ b/lib/messaging/contacts/add_contact_number.dart @@ -1,5 +1,6 @@ import 'package:lantern/messaging/messaging.dart'; +@RoutePage(name: 'AddViaChatNumber') class AddViaChatNumber extends StatefulWidget { @override _AddViaChatNumberState createState() => _AddViaChatNumberState(); diff --git a/lib/messaging/contacts/contact_info.dart b/lib/messaging/contacts/contact_info.dart index 4b60330d1..d3ae65756 100644 --- a/lib/messaging/contacts/contact_info.dart +++ b/lib/messaging/contacts/contact_info.dart @@ -2,6 +2,7 @@ import 'package:lantern/messaging/conversation/call_action.dart'; import '../messaging.dart'; +@RoutePage(name: 'ContactInfo') class ContactInfo extends StatefulWidget { final Contact contact; diff --git a/lib/messaging/contacts/new_chat.dart b/lib/messaging/contacts/new_chat.dart index ae5c3c3d1..f5b3c2ff4 100644 --- a/lib/messaging/contacts/new_chat.dart +++ b/lib/messaging/contacts/new_chat.dart @@ -4,6 +4,7 @@ import 'package:lantern/messaging/messaging.dart'; import 'long_tap_menu.dart'; +@RoutePage(name: 'NewChat') class NewChat extends StatefulWidget { @override _NewChatState createState() => _NewChatState(); diff --git a/lib/messaging/conversation/attachments/video.dart b/lib/messaging/conversation/attachments/video.dart index 476b636e4..fdb77eea1 100644 --- a/lib/messaging/conversation/attachments/video.dart +++ b/lib/messaging/conversation/attachments/video.dart @@ -1,6 +1,6 @@ import 'package:lantern/messaging/conversation/attachments/attachment.dart'; -import 'package:lantern/messaging/messaging.dart'; import 'package:lantern/messaging/conversation/status_row.dart'; +import 'package:lantern/messaging/messaging.dart'; import 'package:video_player/video_player.dart'; /// Base class for displaying a video attachment in Chat (conversation view as well as rendering the Video Viewer when that attachment is tapped). It extends VisualAttachment and overrides its buildViewer() and wrapThumbnail() functions. diff --git a/lib/messaging/conversation/conversation.dart b/lib/messaging/conversation/conversation.dart index 86a1c49a6..efac82e7d 100644 --- a/lib/messaging/conversation/conversation.dart +++ b/lib/messaging/conversation/conversation.dart @@ -20,6 +20,7 @@ import 'show_conversation_options.dart'; import 'show_verification_options.dart'; import 'stopwatch_timer.dart'; +@RoutePage(name: 'Conversation') class Conversation extends StatefulWidget { final ContactId contactId; final int? initialScrollIndex; @@ -32,19 +33,14 @@ class Conversation extends StatefulWidget { }) : super(); @override - ConversationState createState() => ConversationState( - showContactEditingDialog: showContactEditingDialog, - ); + ConversationState createState() => ConversationState(); } class ConversationState extends State with WidgetsBindingObserver { - ConversationState({required this.showContactEditingDialog}); - - bool showContactEditingDialog; bool reactingWithEmoji = false; bool hasPermission = false; - + bool showContactEditingDialog = false; final TextEditingController newMessage = TextEditingController(); final StopWatchTimer stopWatchTimer = StopWatchTimer(); bool isRecording = false; @@ -67,7 +63,11 @@ class ConversationState extends State // default the below to reasonable value, it will get updated when the // keyboard displays - double get defaultKeyboardHeight => MediaQuery.of(context).size.height * 0.4; + double get defaultKeyboardHeight => + MediaQuery + .of(context) + .size + .height * 0.4; static var latestKeyboardHeight = 0.0; Timer? currentConversationTimer; @@ -95,11 +95,18 @@ class ConversationState extends State } var currentKeyboardHeight = max( - EdgeInsets.fromWindowPadding( - WidgetsBinding.instance.window.viewInsets, - WidgetsBinding.instance.window.devicePixelRatio, - ).bottom, - MediaQuery.of(context).viewInsets.bottom, + EdgeInsets + .fromViewPadding( + View + .of(context) + .padding, View + .of(context) + .devicePixelRatio) + .bottom, + MediaQuery + .of(context) + .viewInsets + .bottom, ); if (currentKeyboardHeight > 0) { setState(() { @@ -111,21 +118,21 @@ class ConversationState extends State void subscribeToKeyboardChanges() { keyboardSubscription = keyboardVisibilityController.onChange.listen((bool visible) { - updateKeyboardHeight(); - if (visible) { - if (keyboardMode == KeyboardMode.emojiReaction) { - dismissNativeKeyboard(); - } else { - setState(() { - keyboardMode = KeyboardMode.native; - }); - } - } else if (keyboardMode == KeyboardMode.native) { - setState(() { - keyboardMode = KeyboardMode.none; + updateKeyboardHeight(); + if (visible) { + if (keyboardMode == KeyboardMode.emojiReaction) { + dismissNativeKeyboard(); + } else { + setState(() { + keyboardMode = KeyboardMode.native; + }); + } + } else if (keyboardMode == KeyboardMode.native) { + setState(() { + keyboardMode = KeyboardMode.none; + }); + } }); - } - }); } void dismissAllKeyboards() { @@ -171,7 +178,7 @@ class ConversationState extends State // fresh currentConversationTimer = Timer.periodic( const Duration(seconds: 1), - (_) => + (_) => messagingModel.setCurrentConversationContact(widget.contactId.id), ); break; @@ -187,6 +194,7 @@ class ConversationState extends State @override void initState() { super.initState(); + showContactEditingDialog = widget.showContactEditingDialog; WidgetsBinding.instance.addObserver(this); BackButtonInterceptor.add(interceptBackButton); subscribeToKeyboardChanges(); @@ -211,8 +219,8 @@ class ConversationState extends State } hasPermission = await messagingModel.startRecordingVoiceMemo(); if (hasPermission) { - stopWatchTimer.onExecute.add(StopWatchExecute.reset); - stopWatchTimer.onExecute.add(StopWatchExecute.start); + stopWatchTimer.onResetTimer(); + stopWatchTimer.onStartTimer(); setState(() { isRecording = true; }); @@ -226,7 +234,8 @@ class ConversationState extends State context.loaderOverlay.show(widget: spinner); try { - stopWatchTimer.onExecute.add(StopWatchExecute.stop); + // stopWatchTimer.onExecute.add(StopWatchExecute.stop); + stopWatchTimer.onStopTimer(); recording = await messagingModel.stopRecordingVoiceMemo(); var attachment = StoredAttachment.fromBuffer(recording!); setState(() { @@ -252,9 +261,9 @@ class ConversationState extends State for (var i = 0; i < result.files.length; i++) { final el = result.files[i]; final title = el.path.toString().split('file_picker/')[1].split('.')[ - 0]; // example path: /data/user/0/org.getlantern.lantern/cache/file_picker/alpha_png.png + 0]; // example path: /data/user/0/org.getlantern.lantern/cache/file_picker/alpha_png.png final fileExtension = - el.path.toString().split('file_picker/')[1].split('.')[1]; + el.path.toString().split('file_picker/')[1].split('.')[1]; final metadata = { 'title': title, 'fileExtension': fileExtension, @@ -297,8 +306,7 @@ class ConversationState extends State } // handles backend send message logic - Future sendMessage( - String text, { + Future sendMessage(String text, { List? attachments, String? replyToSenderId, String? replyToId, @@ -341,13 +349,15 @@ class ConversationState extends State // handles client send message logic void send() async { - if (newMessage.value.text.trim().isEmpty && recording == null) { + if (newMessage.value.text + .trim() + .isEmpty && recording == null) { return; } await sendMessage( newMessage.value.text, attachments: - recording != null && recording!.isNotEmpty ? [recording!] : [], + recording != null && recording!.isNotEmpty ? [recording!] : [], replyToSenderId: quotedMessage?.senderId, replyToId: quotedMessage?.id, ); @@ -367,202 +377,210 @@ class ConversationState extends State updateKeyboardHeight(); final keyboardHeight = - latestKeyboardHeight > 0 ? latestKeyboardHeight : defaultKeyboardHeight; + latestKeyboardHeight > 0 ? latestKeyboardHeight : defaultKeyboardHeight; (context.router.currentChild!.name == router_gr.Conversation.name) ? unawaited( - messagingModel.setCurrentConversationContact(widget.contactId.id), - ) + messagingModel.setCurrentConversationContact(widget.contactId.id), + ) : unawaited(messagingModel.clearCurrentConversationContact()); return messagingModel.singleContactById(widget.contactId, - (context, contact, child) { - // * we came here after adding a contact via chat number, show contact name dialog - if (showContactEditingDialog == true && contact.displayName.isEmpty) { - showContactEditingDialog = false; - messagingModel - .getDirectContact(widget.contactId.id) - .then((contact) async { - // We use Future.delayed instead of addPostFrameCallback because - // addPostFrameCallback doesn't work all the time (for some unknown - // reason). - await Future.delayed(const Duration(milliseconds: 250)); - await showDialog( - context: context, - builder: (childContext) => ContactNameDialog( - context: context, - contact: contact, - ), - ); - }); - } + (context, contact, child) { + // * we came here after adding a contact via chat number, show contact name dialog + if (showContactEditingDialog == true && contact.displayName.isEmpty) { + showContactEditingDialog = false; + messagingModel + .getDirectContact(widget.contactId.id) + .then((contact) async { + // We use Future.delayed instead of addPostFrameCallback because + // addPostFrameCallback doesn't work all the time (for some unknown + // reason). + await Future.delayed(const Duration(milliseconds: 250)); + await showDialog( + context: context, + builder: (childContext) => + ContactNameDialog( + context: context, + contact: contact, + ), + ); + }); + } - // determine if we will show the verification warning badge - var verificationReminderLastDismissed = contact + // determine if we will show the verification warning badge + var verificationReminderLastDismissed = contact .applicationData['verificationReminderLastDismissed']?.int_3 .toInt() ?? - 0; - return BaseScreen( - resizeToAvoidBottomInset: false, - centerTitle: false, - padHorizontal: false, - // * Conversation Title - title: dismissKeyboardsOnTap( - CInkWell( - onTap: () async => + 0; + return BaseScreen( + resizeToAvoidBottomInset: false, + centerTitle: false, + padHorizontal: false, + // * Conversation Title + title: dismissKeyboardsOnTap( + CInkWell( + onTap: () async => await context.pushRoute(ContactInfo(contact: contact)), - child: ContactInfoTopBar( - contact: contact, - verifiedColor: verifiedColor, + child: ContactInfoTopBar( + contact: contact, + verifiedColor: verifiedColor, + ), + ), ), - ), - ), - // * Conversation Actions e.g. Verification alert, Call, Menu - actions: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceAround, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - // * show Verification alert badge, resurface every 2 weeks - NowBuilder( - calculate: (now) => + // * Conversation Actions e.g. Verification alert, Call, Menu + actions: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // * show Verification alert badge, resurface every 2 weeks + NowBuilder( + calculate: (now) => now.millisecondsSinceEpoch - verificationReminderLastDismissed >= - twoWeeksInMillis, - builder: (BuildContext context, bool value) { - if (!contact.isMe && contact.isUnverified() && value) { - return IconButton( - key: const ValueKey('verification_badge'), - visualDensity: VisualDensity.compact, - onPressed: () async { - showVerificationOptions( + twoWeeksInMillis, + builder: (BuildContext context, bool value) { + if (!contact.isMe && contact.isUnverified() && value) { + return IconButton( + key: const ValueKey('verification_badge'), + visualDensity: VisualDensity.compact, + onPressed: () async { + showVerificationOptions( + contact: contact, + bottomModalContext: context, + showDismissNotification: shouldShowVerificationAlert, + topBarAnimationCallback: () async { + setState(() => verifiedColor = indicatorGreen); + await Future.delayed( + longAnimationDuration, + () => + setState(() => verifiedColor = black), + ); + }, + ); + }, + icon: const CAssetImage( + path: ImagePaths.verification_alert, + ), + ); + } + return Container(); + }, + ), + if (!contact.isMe) CallAction(contact), + IconButton( + key: const ValueKey('conversation_topbar_more_menu'), + visualDensity: VisualDensity.compact, + icon: const CAssetImage(path: ImagePaths.more_vert), + onPressed: () => + showConversationOptions( + parentContext: context, contact: contact, - bottomModalContext: context, - showDismissNotification: shouldShowVerificationAlert, topBarAnimationCallback: () async { setState(() => verifiedColor = indicatorGreen); await Future.delayed( longAnimationDuration, - () => setState(() => verifiedColor = black), + () => setState(() => verifiedColor = black), ); }, - ); - }, - icon: const CAssetImage( - path: ImagePaths.verification_alert, - ), - ); - } - return Container(); - }, + ), + ) + ], ), - if (!contact.isMe) CallAction(contact), - IconButton( - key: const ValueKey('conversation_topbar_more_menu'), - visualDensity: VisualDensity.compact, - icon: const CAssetImage(path: ImagePaths.more_vert), - onPressed: () => showConversationOptions( - parentContext: context, - contact: contact, - topBarAnimationCallback: () async { - setState(() => verifiedColor = indicatorGreen); - await Future.delayed( - longAnimationDuration, - () => setState(() => verifiedColor = black), - ); - }, - ), - ) ], - ), - ], - // * Conversation body - body: Padding( - padding: EdgeInsetsDirectional.only( - bottom: keyboardMode == KeyboardMode.native ? keyboardHeight : 0.0, - ), - child: Column( - children: [ - if (contact.isUnaccepted()) - UnacceptedContactSticker( - messageCount: messageCount, - contact: contact, - ), - Flexible( - child: dismissKeyboardsOnTap( - Padding( - padding: + // * Conversation body + body: Padding( + padding: EdgeInsetsDirectional.only( + bottom: keyboardMode == KeyboardMode.native + ? keyboardHeight + : 0.0, + ), + child: Column( + children: [ + if (contact.isUnaccepted()) + UnacceptedContactSticker( + messageCount: messageCount, + contact: contact, + ), + Flexible( + child: dismissKeyboardsOnTap( + Padding( + padding: const EdgeInsetsDirectional.only(start: 16, end: 16), - child: buildList(contact), + child: buildList(contact), + ), + ), ), - ), - ), - // * Reply container - if (quotedMessage != null) - Reply( - contact: contact, - message: quotedMessage!, - onCancelReply: () => setState(() => quotedMessage = null), - ), - Divider(height: 1.0, color: grey3), - ConstrainedBox( - constraints: const BoxConstraints( - minHeight: messageBarHeight, - ), - child: Container( - color: isRecording ? grey2 : white, - width: MediaQuery.of(context).size.width, - child: buildMessageBar(), - ), - ), - // * Emoji keyboard - Offstage( - offstage: keyboardMode != KeyboardMode.emoji && - keyboardMode != KeyboardMode.emojiReaction, - child: MessagingEmojiPicker( - height: keyboardHeight, - emptySuggestions: 'no_recents'.i18n, - onBackspacePressed: () { - newMessage - ..text = newMessage.text.characters.skipLast(1).toString() - ..selection = TextSelection.fromPosition( - TextPosition(offset: newMessage.text.length), - ); - }, - onEmojiSelected: (category, emoji) async { - if (mounted && reactingWithEmoji && storedMessage != null) { - await messagingModel.react( - storedMessage!.value, - emoji.emoji, - ); - reactingWithEmoji = false; - storedMessage = null; - dismissAllKeyboards(); - } else { - setState(() => isSendIconVisible = true); - newMessage - ..text += emoji.emoji - ..selection = TextSelection.fromPosition( - TextPosition(offset: newMessage.text.length), - ); - } - }, - ), + // * Reply container + if (quotedMessage != null) + Reply( + contact: contact, + message: quotedMessage!, + onCancelReply: () => setState(() => quotedMessage = null), + ), + Divider(height: 1.0, color: grey3), + ConstrainedBox( + constraints: const BoxConstraints( + minHeight: messageBarHeight, + ), + child: Container( + color: isRecording ? grey2 : white, + width: MediaQuery + .of(context) + .size + .width, + child: buildMessageBar(), + ), + ), + // * Emoji keyboard + Offstage( + offstage: keyboardMode != KeyboardMode.emoji && + keyboardMode != KeyboardMode.emojiReaction, + child: MessagingEmojiPicker( + height: keyboardHeight, + emptySuggestions: 'no_recents'.i18n, + onBackspacePressed: () { + newMessage + ..text = newMessage.text.characters.skipLast(1) + .toString() + ..selection = TextSelection.fromPosition( + TextPosition(offset: newMessage.text.length), + ); + }, + onEmojiSelected: (category, emoji) async { + if (mounted && reactingWithEmoji && + storedMessage != null) { + await messagingModel.react( + storedMessage!.value, + emoji.emoji, + ); + reactingWithEmoji = false; + storedMessage = null; + dismissAllKeyboards(); + } else { + setState(() => isSendIconVisible = true); + newMessage + ..text += emoji.emoji + ..selection = TextSelection.fromPosition( + TextPosition(offset: newMessage.text.length), + ); + } + }, + ), + ), + ], ), - ], - ), - ), - ); - }); + ), + ); + }); } Widget buildList(Contact contact) { return messagingModel.contactMessages( contact, - builder: ( - context, - Iterable> originalMessageRecords, - Widget? child, - ) { + builder: (context, + Iterable> originalMessageRecords, + Widget? child,) { // Build list that includes original message records as well as date // separators. var listItems = []; @@ -624,50 +642,48 @@ class ConversationState extends State ); } - Widget buildMessageBubble( - BuildContext context, - Contact contact, - List listItems, - PathAndValue messageAndPath, - int index, - ) { + Widget buildMessageBubble(BuildContext context, + Contact contact, + List listItems, + PathAndValue messageAndPath, + int index,) { return messagingModel.message(context, messageAndPath, - (BuildContext context, StoredMessage message, Widget? child) { - return MessageBubble( - message: message, - priorMessage: priorMessage(listItems, index)?.value, - nextMessage: nextMessage(listItems, index)?.value, - contact: contact, - onOpenMenu: dismissAllKeyboards, - onEmojiTap: () { - setState(() { - reactingWithEmoji = true; - storedMessage = messageAndPath; - }); - showEmojiKeyboard(true); - }, - onReply: () { - setState(() { - quotedMessage = message; - showNativeKeyboard(); - }); - }, - onTapReply: () { - final scrollToIndex = listItems.toList().indexWhere( - (element) => - element is PathAndValue && + (BuildContext context, StoredMessage message, Widget? child) { + return MessageBubble( + message: message, + priorMessage: priorMessage(listItems, index)?.value, + nextMessage: nextMessage(listItems, index)?.value, + contact: contact, + onOpenMenu: dismissAllKeyboards, + onEmojiTap: () { + setState(() { + reactingWithEmoji = true; + storedMessage = messageAndPath; + }); + showEmojiKeyboard(true); + }, + onReply: () { + setState(() { + quotedMessage = message; + showNativeKeyboard(); + }); + }, + onTapReply: () { + final scrollToIndex = listItems.toList().indexWhere( + (element) => + element is PathAndValue && element.value.id == message.replyToId, ); - if (scrollToIndex != -1 && scrollController.isAttached) { - scrollController.scrollTo( - index: scrollToIndex, - duration: const Duration(seconds: 1), - curve: defaultCurves, - ); - } - }, - ); - }); + if (scrollToIndex != -1 && scrollController.isAttached) { + scrollController.scrollTo( + index: scrollToIndex, + duration: const Duration(seconds: 1), + curve: defaultCurves, + ); + } + }, + ); + }); } PathAndValue? priorMessage(List listItems, int index) { @@ -692,8 +708,11 @@ class ConversationState extends State //* Entry point to audio waveform widget (MessageBarPreviewRecording) Widget buildMessageBar() { - return Container( - width: MediaQuery.of(context).size.width, + return SizedBox( + width: MediaQuery + .of(context) + .size + .width, child: IndexedStack( index: finishedRecording ? 1 : 0, children: [ @@ -701,22 +720,22 @@ class ConversationState extends State audioPreviewController == null ? const SizedBox() : MessageBarPreviewRecording( - audioController: audioPreviewController!, - onCancelRecording: () async { - unawaited(HapticFeedback.lightImpact()); - setState(() { - isRecording = false; - finishedRecording = false; - recording = null; - audioPreviewController = null; - }); - }, - onSend: () { - unawaited(HapticFeedback.lightImpact()); - audio.stop(); - send(); - }, - ), + audioController: audioPreviewController!, + onCancelRecording: () async { + unawaited(HapticFeedback.lightImpact()); + setState(() { + isRecording = false; + finishedRecording = false; + recording = null; + audioPreviewController = null; + }); + }, + onSend: () { + unawaited(HapticFeedback.lightImpact()); + audio.stop(); + send(); + }, + ), ], ), ); @@ -727,50 +746,52 @@ class ConversationState extends State Widget buildMessageBarRecording(BuildContext context) { final leading = isRecording ? Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - Flexible( - child: Padding( - padding: const EdgeInsetsDirectional.only(start: 16), - child: PulsatingIndicator(), - ), - ), - Flexible( - child: Padding( - padding: const EdgeInsetsDirectional.only(start: 16), - child: StopwatchTimer( - stopWatchTimer: stopWatchTimer, - style: tsSubtitle1.copiedWith(color: indicatorRed).short, - ), - ), - ), - ], - ) + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 16), + child: PulsatingIndicator(), + ), + ), + Flexible( + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 16), + child: StopwatchTimer( + stopWatchTimer: stopWatchTimer, + style: tsSubtitle1 + .copiedWith(color: indicatorRed) + .short, + ), + ), + ), + ], + ) : IconButton( - onPressed: () { - { - if (keyboardMode == KeyboardMode.emoji || - keyboardMode == KeyboardMode.emojiReaction) { - keyboardMode = KeyboardMode.native; - showNativeKeyboard(); - } else { - showEmojiKeyboard(false); - } - } - }, - icon: keyboardMode == KeyboardMode.emoji || - keyboardMode == KeyboardMode.emojiReaction - ? const CAssetImage(path: ImagePaths.keyboard) - : const CAssetImage(path: ImagePaths.insert_emoticon), - ); + onPressed: () { + { + if (keyboardMode == KeyboardMode.emoji || + keyboardMode == KeyboardMode.emojiReaction) { + keyboardMode = KeyboardMode.native; + showNativeKeyboard(); + } else { + showEmojiKeyboard(false); + } + } + }, + icon: keyboardMode == KeyboardMode.emoji || + keyboardMode == KeyboardMode.emojiReaction + ? const CAssetImage(path: ImagePaths.keyboard) + : const CAssetImage(path: ImagePaths.insert_emoticon), + ); final content = Stack( alignment: Alignment.center, children: [ if (!isRecording) - // using a SingleChildScrollView to reconcile emoji and native keyboard scrolling to latest character which is only possible with maxLines = null + // using a SingleChildScrollView to reconcile emoji and native keyboard scrolling to latest character which is only possible with maxLines = null SingleChildScrollView( scrollDirection: Axis.vertical, reverse: true, @@ -791,7 +812,8 @@ class ConversationState extends State }, focusNode: focusNode, textCapitalization: TextCapitalization.sentences, - onFieldSubmitted: (value) async => value.isEmpty + onFieldSubmitted: (value) async => + value.isEmpty ? null : await handleMessageBarSubmit(newMessage), decoration: InputDecoration( @@ -821,36 +843,36 @@ class ConversationState extends State ); final trailing = isSendIconVisible && !isRecording ? Row( - children: [ - Padding( - padding: const EdgeInsetsDirectional.only(top: 8, bottom: 8), - child: VerticalDivider(thickness: 1, width: 1, color: grey3), - ), - IconButton( - key: const ValueKey('send_message'), - icon: mirrorLTR( - context: context, - child: - CAssetImage(path: ImagePaths.send_rounded, color: pink4), - ), - onPressed: send, - ), - ], - ) + children: [ + Padding( + padding: const EdgeInsetsDirectional.only(top: 8, bottom: 8), + child: VerticalDivider(thickness: 1, width: 1, color: grey3), + ), + IconButton( + key: const ValueKey('send_message'), + icon: mirrorLTR( + context: context, + child: + CAssetImage(path: ImagePaths.send_rounded, color: pink4), + ), + onPressed: send, + ), + ], + ) : Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.end, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - isRecording - ? const SizedBox() - : IconButton( - key: const ValueKey('filepicker_icon'), - onPressed: () async => await selectFilesToShare(), - icon: const CAssetImage(path: ImagePaths.add_circle), - ), - ], - ); + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + isRecording + ? const SizedBox() + : IconButton( + key: const ValueKey('filepicker_icon'), + onPressed: () async => await selectFilesToShare(), + icon: const CAssetImage(path: ImagePaths.add_circle), + ), + ], + ); // * Stack overlay of [leading, content, trailing] Row and voice recorder return Stack( alignment: isLTR(context) ? Alignment.bottomRight : Alignment.bottomLeft, @@ -867,7 +889,7 @@ class ConversationState extends State fit: FlexFit.tight, child: Container( constraints: - const BoxConstraints(maxHeight: messageBarHeight * 2), + const BoxConstraints(maxHeight: messageBarHeight * 2), child: content, ), ), @@ -887,7 +909,7 @@ class ConversationState extends State isRecording: isRecording, onRecording: () async => await startRecording(), onStopRecording: () async => - hasPermission ? await finishRecording() : null, + hasPermission ? await finishRecording() : null, onTapUpListener: () async => await finishRecording(), ), ], diff --git a/lib/messaging/introductions/introduce.dart b/lib/messaging/introductions/introduce.dart index 302bf5b7d..756030ae4 100644 --- a/lib/messaging/introductions/introduce.dart +++ b/lib/messaging/introductions/introduce.dart @@ -1,6 +1,7 @@ import 'package:lantern/messaging/contacts/grouped_contact_list.dart'; import 'package:lantern/messaging/messaging.dart'; +@RoutePage(name: 'Introduce') class Introduce extends StatefulWidget { final bool singleIntro; final Contact? contactToIntro; diff --git a/lib/messaging/introductions/introductions.dart b/lib/messaging/introductions/introductions.dart index cbcfddd94..c5b062a84 100644 --- a/lib/messaging/introductions/introductions.dart +++ b/lib/messaging/introductions/introductions.dart @@ -1,6 +1,7 @@ import 'package:lantern/messaging/introductions/introduction_extension.dart'; import 'package:lantern/messaging/messaging.dart'; +@RoutePage(name: 'Introductions') class Introductions extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/lib/messaging/notifications.dart b/lib/messaging/notifications.dart index ca9e1843c..e4be1740e 100644 --- a/lib/messaging/notifications.dart +++ b/lib/messaging/notifications.dart @@ -1,9 +1,7 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:flutter_uploader/flutter_uploader.dart'; import 'package:lantern/messaging/messaging.dart'; -import 'package:lantern/replica/models/replica_link.dart'; import 'package:logger/logger.dart'; -import 'package:share_plus/share_plus.dart'; final notifications = Notifications(); final JsonEncoder _encoder = const JsonEncoder(); @@ -65,47 +63,33 @@ class Notifications { const InitializationSettings( android: AndroidInitializationSettings('app_icon'), ), - onSelectNotification: (payloadString) async { - if (payloadString?.isNotEmpty == true) { - var payload = Payload.fromJson(payloadString!); - switch (payload.type) { - // case PayloadType.Ringing: - // Map data = payload.data; - // messagingModel.signaling - // .onMessage(data['peerId'], data['messageJson'], false); - // break; - // TODO <16-12-2021> soltzen: This code does not work as of today: - // The notification click events are not being processed. This'll be - // addressed here: https://github.com/getlantern/lantern-internal/issues/5133 - // TODO <08-10-22, kalli> Building on above comment: - // While we are technically close to being able to share replica links, there are big pieces missing from how this can meaningfully and safely be put in production. Some concerns involve handling cases where someone doesn't know or use Lantern, as well as sharing increasing censor attack exposure area. More context: https://github.com/getlantern/lantern-internal/issues/3577 - case PayloadType.Upload: - // The payload here is a possible JSON response body - // See ReplicaUploader for more info on this payload type - try { - Map resp = jsonDecode(payload.data); - if (!resp.containsKey('replicaLink')) { - return; - } - // XXX <16-12-2021> soltzen: we don't care much for the other - // parameters, but they are here for reference: - // https://github.com/getlantern/replica/blob/c61b1855475391c715a1e8e370da87b31848d514/server/object.go#L12 - var link = ReplicaLink.New(resp['replicaLink']); - if (link == null) { - // TODO <08-22-22, kalli> Don't throw exception directly - throw Exception('Replicalink.New() failed'); - } - // Prompt the user a Share dialog - await Share.share('replica://${link.toMagnetLink()}'); - } catch (ex) { - logger.w( - 'Failed to decode upload json resp: ${payload.data}. Will not process upload notification clicks. Err: $ex', - ); + onDidReceiveNotificationResponse: (payloadString) async { + var payload = Payload.fromJson(payloadString.payload!); + switch (payload.type) { + // case PayloadType.Ringing: + // Map data = payload.data; + // messagingModel.signaling + // .onMessage(data['peerId'], data['messageJson'], false); + // break; + // TODO <16-12-2021> soltzen: This code does not work as of today: + // The notification click events are not being processed. This'll be + // addressed here: https://github.com/getlantern/lantern-internal/issues/5133 + // TODO <08-10-22, kalli> Building on above comment: + // While we are technically close to being able to share replica links, there are big pieces missing from how this can meaningfully and safely be put in production. Some concerns involve handling cases where someone doesn't know or use Lantern, as well as sharing increasing censor attack exposure area. More context: https://github.com/getlantern/lantern-internal/issues/3577 + case PayloadType.Upload: + // The payload here is a possible JSON response body + // See ReplicaUploader for more info on this payload type + try { + Map resp = jsonDecode(payload.data); + if (!resp.containsKey('replicaLink')) { + return; } break; - default: - break; - } + } catch (e) { + return; + } + default: + break; } return; }, @@ -130,7 +114,9 @@ enum PayloadType { Ringing, Upload } extension ToShortString on PayloadType { String toShortString() { - return toString().split('.').last; + return toString() + .split('.') + .last; } } diff --git a/lib/messaging/onboarding/chat_number_messaging.dart b/lib/messaging/onboarding/chat_number_messaging.dart index dd90bd263..fd3fa3810 100644 --- a/lib/messaging/onboarding/chat_number_messaging.dart +++ b/lib/messaging/onboarding/chat_number_messaging.dart @@ -1,5 +1,6 @@ import 'package:lantern/messaging/messaging.dart'; +@RoutePage(name: 'ChatNumberMessaging') class ChatNumberMessaging extends StatelessWidget { ChatNumberMessaging() : super() { messagingModel.dismissTryLanternChatBadge(); diff --git a/lib/messaging/onboarding/chat_number_recovery.dart b/lib/messaging/onboarding/chat_number_recovery.dart index 3bbe46df9..6870143a3 100644 --- a/lib/messaging/onboarding/chat_number_recovery.dart +++ b/lib/messaging/onboarding/chat_number_recovery.dart @@ -1,5 +1,6 @@ import '../messaging.dart'; +@RoutePage(name: 'ChatNumberRecovery') class ChatNumberRecovery extends StatefulWidget { @override State createState() => _ChatNumberRecoveryState(); diff --git a/lib/plans/checkout.dart b/lib/plans/checkout.dart index 0de427b28..30b581df2 100644 --- a/lib/plans/checkout.dart +++ b/lib/plans/checkout.dart @@ -4,6 +4,7 @@ import 'package:lantern/plans/payment_provider.dart'; import 'package:lantern/plans/plan_details.dart'; import 'package:lantern/plans/utils.dart'; +@RoutePage(name: 'Checkout') class Checkout extends StatefulWidget { final Plan plan; final bool isPro; diff --git a/lib/plans/plans.dart b/lib/plans/plans.dart index 6baaa3d93..6ab9f3268 100644 --- a/lib/plans/plans.dart +++ b/lib/plans/plans.dart @@ -9,6 +9,7 @@ final featuresList = [ 'no_ads'.i18n, ]; +@RoutePage(name: "PlansPage") class PlansPage extends StatelessWidget { @override Widget build(BuildContext context) { diff --git a/lib/plans/reseller_checkout.dart b/lib/plans/reseller_checkout.dart index e6111dc7f..19d1ca04f 100644 --- a/lib/plans/reseller_checkout.dart +++ b/lib/plans/reseller_checkout.dart @@ -32,6 +32,7 @@ class ResellerCodeFormatter extends TextInputFormatter { } } +@RoutePage(name: "ResellerCodeCheckout") class ResellerCodeCheckout extends StatefulWidget { final bool isPro; diff --git a/lib/plans/stripe_checkout.dart b/lib/plans/stripe_checkout.dart index 5506df2b4..ce6b89102 100644 --- a/lib/plans/stripe_checkout.dart +++ b/lib/plans/stripe_checkout.dart @@ -32,6 +32,7 @@ class CardExpirationFormatter extends TextInputFormatter { } } +@RoutePage(name: 'StripeCheckout') class StripeCheckout extends StatefulWidget { final Plan plan; final String email; diff --git a/lib/replica/common.dart b/lib/replica/common.dart index 215cc188a..ab90b2013 100644 --- a/lib/replica/common.dart +++ b/lib/replica/common.dart @@ -3,19 +3,16 @@ export 'package:logger/logger.dart'; export 'logic/api.dart'; export 'logic/markdown_link_builder.dart'; export 'logic/uploader.dart'; - export 'models/replica_link.dart'; export 'models/replica_model.dart'; +export 'models/replica_object_info.dart'; export 'models/search_category.dart'; export 'models/search_item.dart'; -export 'models/replica_object_info.dart'; - -export 'ui/utils.dart'; export 'ui/list_item/app_list_item.dart'; export 'ui/list_item/audio_list_item.dart'; export 'ui/list_item/document_list_item.dart'; export 'ui/list_item/image_list_item.dart'; export 'ui/list_item/video_list_item.dart'; - export 'ui/list_view/common_list.dart'; export 'ui/list_view/list_layout.dart'; +export 'ui/utils.dart'; diff --git a/lib/replica/link_handler.dart b/lib/replica/link_handler.dart index 98936fd05..c906c2436 100644 --- a/lib/replica/link_handler.dart +++ b/lib/replica/link_handler.dart @@ -20,6 +20,7 @@ import 'package:lantern/replica/common.dart'; /// tab will not be visible if Replica is not initialized. /// In other words, we will never reach any Replica screen (other than this) if /// Replica is not initialized. +@RoutePage(name: 'ReplicaLinkHandler') class ReplicaLinkHandler extends StatefulWidget { ReplicaLinkHandler({ Key? key, @@ -74,6 +75,7 @@ class _LinkOpenerScreen extends State { // ), // ); return Container(); + } }); diff --git a/lib/replica/logic/api.dart b/lib/replica/logic/api.dart index f45e2e83a..aa7759eb9 100644 --- a/lib/replica/logic/api.dart +++ b/lib/replica/logic/api.dart @@ -140,7 +140,7 @@ class ReplicaApi { : null; logger.v(duration); } catch (err) { - if (err is DioError) { + if (err is DioException) { logger.e( 'Dio Error - failed to fetch duration. Error: ${err.error}', ); diff --git a/lib/replica/models/replica_model.dart b/lib/replica/models/replica_model.dart index d50745d54..8bdc59d70 100644 --- a/lib/replica/models/replica_model.dart +++ b/lib/replica/models/replica_model.dart @@ -77,7 +77,7 @@ class ReplicaModel extends Model { Future getShowNewBadge() async { return methodChannel .invokeMethod('get', '/showNewBadge') - .then((value) => value); + .then((value) => value??false); } Future getSearchTerm() async { diff --git a/lib/replica/search.dart b/lib/replica/search.dart index 0e9aeede6..555cb6343 100644 --- a/lib/replica/search.dart +++ b/lib/replica/search.dart @@ -22,8 +22,8 @@ class ReplicaSearchScreen extends StatefulWidget { class _ReplicaSearchScreenState extends State with TickerProviderStateMixin { late final TabController tabController = - // Video + Image + Audio + Document + App + News = 6 categories - TabController(length: 6, vsync: this); + // Video + Image + Audio + Document + App = 5 categories + TabController(length: 5, vsync: this); final formKey = GlobalKey(debugLabel: 'replicaSearchInput'); late final CustomTextEditingController textEditingController; late String searchQuery = widget.currentQuery; diff --git a/lib/replica/ui/list_item/image_list_item.dart b/lib/replica/ui/list_item/image_list_item.dart index ca11e408d..2dc0a5f8d 100644 --- a/lib/replica/ui/list_item/image_list_item.dart +++ b/lib/replica/ui/list_item/image_list_item.dart @@ -15,32 +15,30 @@ class ReplicaImageListItem extends StatelessWidget { @override Widget build(BuildContext context) { - return Container( - child: GestureDetector( - onTap: onTap, - child: FocusedMenuHolder( - menu: renderReplicaLongPressMenuItem( - context, - replicaApi, - item.replicaLink, - ), - menuWidth: MediaQuery.of(context).size.width * 0.5, - builder: (_) { - return GridTile( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - renderImageThumbnail( - imageUrl: replicaApi.getThumbnailAddr(item.replicaLink), - item: item, - ), - renderName(context), - ], - ), - ); - }, + return GestureDetector( + onTap: onTap, + child: FocusedMenuHolder( + menu: renderReplicaLongPressMenuItem( + context, + replicaApi, + item.replicaLink, ), + menuWidth: MediaQuery.of(context).size.width * 0.5, + builder: (_) { + return GridTile( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + renderImageThumbnail( + imageUrl: replicaApi.getThumbnailAddr(item.replicaLink), + item: item, + ), + renderName(context), + ], + ), + ); + }, ), ); } diff --git a/lib/replica/ui/list_view/list_layout.dart b/lib/replica/ui/list_view/list_layout.dart index 16f8692bb..3d2d4c381 100644 --- a/lib/replica/ui/list_view/list_layout.dart +++ b/lib/replica/ui/list_view/list_layout.dart @@ -135,13 +135,13 @@ class _ReplicaListLayoutState extends ReplicaCommonListViewState { ); case SearchCategory.News: return renderPaginatedListView( - (context, item, index) => ReplicaNewsListItem( + (context, item, index) => ReplicaNewsListItem( key: ValueKey(item.replicaLink.infohash), item: item, replicaApi: widget.replicaApi, onTap: () async { if (item.serpLink != null) { - await launch(item.serpLink as String); + await launchUrl(Uri.parse(item.serpLink as String)); } return true; }, diff --git a/lib/replica/ui/viewers/audio.dart b/lib/replica/ui/viewers/audio.dart index 2fbda555f..54ea7f1d4 100644 --- a/lib/replica/ui/viewers/audio.dart +++ b/lib/replica/ui/viewers/audio.dart @@ -4,6 +4,7 @@ import 'package:lantern/common/common.dart'; import 'package:lantern/replica/common.dart'; import 'package:lantern/replica/ui/viewers/layout.dart'; +@RoutePage(name: 'ReplicaAudioViewer') class ReplicaAudioViewer extends ReplicaViewerLayout { ReplicaAudioViewer({ required ReplicaApi replicaApi, diff --git a/lib/replica/ui/viewers/image.dart b/lib/replica/ui/viewers/image.dart index 0c98d3968..a3b805aec 100644 --- a/lib/replica/ui/viewers/image.dart +++ b/lib/replica/ui/viewers/image.dart @@ -4,6 +4,7 @@ import 'package:lantern/replica/ui/viewers/layout.dart'; /// Renders an embedded image preview with fullscreen option /// wrapped by our reusable ReplicaViewer layout +@RoutePage(name: 'ReplicaImageViewer') class ReplicaImageViewer extends ReplicaViewerLayout { ReplicaImageViewer({ required ReplicaApi replicaApi, diff --git a/lib/replica/ui/viewers/misc.dart b/lib/replica/ui/viewers/misc.dart index 58754062c..ab8e367e1 100644 --- a/lib/replica/ui/viewers/misc.dart +++ b/lib/replica/ui/viewers/misc.dart @@ -9,6 +9,7 @@ import 'package:path_provider/path_provider.dart'; /// 2. Apps /// 3. Unknown /// It does not offer a full screen option unless we have a PDF +@RoutePage(name: 'ReplicaMiscViewer') class ReplicaMiscViewer extends ReplicaViewerLayout { ReplicaMiscViewer({ required ReplicaApi replicaApi, @@ -117,7 +118,7 @@ class _ReplicaMiscViewerState extends ReplicaViewerLayoutState { ), ); } - ; + }, ), ), diff --git a/lib/replica/ui/viewers/video.dart b/lib/replica/ui/viewers/video.dart index d4e08444e..821580fd0 100644 --- a/lib/replica/ui/viewers/video.dart +++ b/lib/replica/ui/viewers/video.dart @@ -1,10 +1,11 @@ -import 'package:video_player/video_player.dart'; import 'package:lantern/common/common.dart'; import 'package:lantern/replica/common.dart'; import 'package:lantern/replica/ui/viewers/layout.dart'; +import 'package:video_player/video_player.dart'; /// Renders an embedded video player with fullscreen option /// wrapped by our reusable ReplicaViewer layout +@RoutePage(name: 'ReplicaVideoViewer') class ReplicaVideoViewer extends ReplicaViewerLayout { ReplicaVideoViewer({ required ReplicaApi replicaApi, diff --git a/lib/replica/upload/description.dart b/lib/replica/upload/description.dart index 0b9a2222e..8b7b2586f 100644 --- a/lib/replica/upload/description.dart +++ b/lib/replica/upload/description.dart @@ -1,5 +1,6 @@ import 'package:lantern/common/common.dart'; +@RoutePage(name: 'ReplicaUploadDescription') class ReplicaUploadDescription extends StatefulWidget { final File fileToUpload; final String fileTitle; diff --git a/lib/replica/upload/review.dart b/lib/replica/upload/review.dart index aaf334865..1e5866ac3 100644 --- a/lib/replica/upload/review.dart +++ b/lib/replica/upload/review.dart @@ -1,8 +1,9 @@ -import 'package:mime/mime.dart'; -import 'package:path/path.dart' as path; import 'package:lantern/common/common.dart'; import 'package:lantern/replica/common.dart'; +import 'package:mime/mime.dart'; +import 'package:path/path.dart' as path; +@RoutePage(name: 'ReplicaUploadReview') class ReplicaUploadReview extends StatefulWidget { final File fileToUpload; final String fileTitle; diff --git a/lib/replica/upload/title.dart b/lib/replica/upload/title.dart index fcca8b8f2..e932b7715 100644 --- a/lib/replica/upload/title.dart +++ b/lib/replica/upload/title.dart @@ -1,9 +1,10 @@ -import 'package:path/path.dart' as path; import 'package:lantern/common/common.dart'; import 'package:lantern/replica/common.dart'; +import 'package:path/path.dart' as path; // ReplicaUploadTitle renders a single-item ListView with the contents of // 'fileToUpload', allowing the user to change the display name of the upload. +@RoutePage(name: 'ReplicaUploadTitle') class ReplicaUploadTitle extends StatefulWidget { final File fileToUpload; final String? fileTitle; diff --git a/lib/vpn/try_lantern_chat.dart b/lib/vpn/try_lantern_chat.dart index 2bffe3c34..7b17fdb34 100644 --- a/lib/vpn/try_lantern_chat.dart +++ b/lib/vpn/try_lantern_chat.dart @@ -70,7 +70,7 @@ class TryLanternChat extends StatelessWidget { const iconSize = 48; final horizontalMargin = 32; - final verticalMargin = 40; + const verticalMargin = 40; final pairWidth = 2 * iconSize + 2 * horizontalMargin; final pairHeight = iconSize + verticalMargin; final numColumns = diff --git a/lib/vpn/vpn_model.dart b/lib/vpn/vpn_model.dart index 7e7107df3..b4f0f9802 100644 --- a/lib/vpn/vpn_model.dart +++ b/lib/vpn/vpn_model.dart @@ -18,21 +18,6 @@ class VpnModel extends Model { ); } - Future refreshAppsList() async { - await methodChannel.invokeMethod('refreshAppsList'); - } - - Widget splitTunneling(ValueWidgetBuilder builder) { - return subscribedSingleValueBuilder('/splitTunneling', - builder: builder,); - } - - Future setSplitTunneling(bool on) async { - unawaited(methodChannel.invokeMethod('setSplitTunneling', { - 'on': on, - }),); - } - Future isVpnConnected() async { final vpnStatus = await methodChannel.invokeMethod('get', '/vpn_status'); return vpnStatus == 'connected'; @@ -57,28 +42,4 @@ class VpnModel extends Model { }, ); } - - Widget appsData({ - required ValueWidgetBuilder>> builder, - }) { - return subscribedListBuilder( - '/appsData/', - builder: builder, - deserialize: (Uint8List serialized) { - return AppData.fromBuffer(serialized); - }, - ); - } - - Future allowAppAccess(String packageName) { - return methodChannel.invokeMethod('allowAppAccess', { - 'packageName': packageName, - }); - } - - Future denyAppAccess(String packageName) { - return methodChannel.invokeMethod('denyAppAccess', { - 'packageName': packageName, - }); - } } diff --git a/lib/vpn/vpn_switch.dart b/lib/vpn/vpn_switch.dart index c19e31ee1..59594956e 100644 --- a/lib/vpn/vpn_switch.dart +++ b/lib/vpn/vpn_switch.dart @@ -1,24 +1,51 @@ import 'package:lantern/vpn/vpn.dart'; +import '../ad_helper.dart'; + +class VPNSwitch extends StatefulWidget { + @override + State createState() => _VPNSwitchState(); +} + +class _VPNSwitchState extends State { + final adHelper = AdHelper(); + + @override + void initState() { + super.initState(); + adHelper.loadAds(); + } + + bool isIdle(String vpnStatus) => + vpnStatus != 'connecting' && vpnStatus != 'disconnecting'; + + Future onSwitchTap(bool newValue, String vpnStatus) async { + unawaited(HapticFeedback.lightImpact()); + if (isIdle(vpnStatus)) { + await vpnModel.switchVPN(newValue); + } + //add delayed to avoid flickering + if (vpnStatus != 'connected') { + Future.delayed( + const Duration(seconds: 1), + () async { + await adHelper.showAds(); + }, + ); + } + } -class VPNSwitch extends StatelessWidget { @override Widget build(BuildContext context) { return Transform.scale( - scale: 2, - child: vpnModel - .vpnStatus((BuildContext context, String vpnStatus, Widget? child) { - return FlutterSwitch( - value: vpnStatus == 'connected' || vpnStatus == 'disconnecting', - activeColor: onSwitchColor, - inactiveColor: offSwitchColor, - onToggle: (bool newValue) async { - unawaited(HapticFeedback.lightImpact()); - if (vpnStatus != 'connecting' || vpnStatus != 'disconnecting') { - await vpnModel.switchVPN(newValue); - } - }, - ); - }), - ); + scale: 2, + child: vpnModel + .vpnStatus((BuildContext context, String vpnStatus, Widget? child) { + return FlutterSwitch( + value: vpnStatus == 'connected' || vpnStatus == 'disconnecting', + activeColor: onSwitchColor, + inactiveColor: offSwitchColor, + onToggle: (bool newValue) => onSwitchTap(newValue, vpnStatus), + ); + })); } } diff --git a/lib/vpn/vpn_tab.dart b/lib/vpn/vpn_tab.dart index 6a01465d0..b9a874a11 100644 --- a/lib/vpn/vpn_tab.dart +++ b/lib/vpn/vpn_tab.dart @@ -1,10 +1,9 @@ import 'package:lantern/messaging/messaging.dart'; +import 'package:lantern/account/split_tunneling.dart'; import 'package:lantern/vpn/vpn.dart'; - import 'vpn_bandwidth.dart'; import 'vpn_pro_banner.dart'; import 'vpn_server_location.dart'; -import 'vpn_split_tunneling.dart'; import 'vpn_status.dart'; import 'vpn_switch.dart'; diff --git a/pubspec.lock b/pubspec.lock index b0e9750af..442ccb02d 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -10,21 +10,13 @@ packages: source: hosted version: "61.0.0" analyzer: - dependency: "direct main" + dependency: transitive description: name: analyzer sha256: ea3d8652bda62982addfd92fdc2d0214e5f82e43325104990d4f4c4a2a313562 url: "https://pub.dev" source: hosted version: "5.13.0" - android_path_provider: - dependency: "direct main" - description: - name: android_path_provider - sha256: "398fb6f83476ba2c6b0930d997ca19376a242d3264c615d3f09ea77382fa09c3" - url: "https://pub.dev" - source: hosted - version: "0.3.0" args: dependency: transitive description: @@ -45,76 +37,76 @@ packages: dependency: "direct main" description: name: audioplayers - sha256: "16451eab798b23ad9307aef6f9ca62bb8fb06542af8810eead0d236d3fd40a42" + sha256: "8e94499b5c123df14cf17c16639de5ff3373e57e537f727e367487fbb7491363" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "5.1.0" audioplayers_android: dependency: transitive description: name: audioplayers_android - sha256: b2c833e6f718b6b030454e329931229afafe9327fdb002874dd544dc8bf2484d + sha256: "1c12b60cc10a3b8617ca3f88b927e7e03768f470d9b4f747efd3d58a8a07ee1b" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "4.0.1" audioplayers_darwin: dependency: transitive description: name: audioplayers_darwin - sha256: e7a3c8759bf11ecfe4b20df338bf9f3d37c7719a5761c46a3833aba0ceeaacff + sha256: "2fb6133ffcf28fb3f9d3e11f8a3ef190e5fedb2b7b95ea865b56a21d1163e670" url: "https://pub.dev" source: hosted - version: "3.0.1" + version: "5.0.1" audioplayers_linux: dependency: transitive description: name: audioplayers_linux - sha256: e95b65e1f4d4764601dac5e65f8d8186fc29401043ab020f1dacec483d708707 + sha256: cca3f272c7186dd2e0025b8864e1413ac5e081d74b17e28b02ceb2df4c110235 url: "https://pub.dev" source: hosted - version: "1.0.4" + version: "3.0.0" audioplayers_platform_interface: dependency: transitive description: name: audioplayers_platform_interface - sha256: "178581a44cb685fd798d2108111d2e98cca3400e30b9c3a05546f124fb37f600" + sha256: "47eae55e99ced11589998cf27e4eaabf5b475a7bd8bea7516ee6c2536a2e1abf" url: "https://pub.dev" source: hosted - version: "4.0.0" + version: "6.0.0" audioplayers_web: dependency: transitive description: name: audioplayers_web - sha256: "859ba09be2a57e57a787273f18c8cf0d9b61383870c5ee4b5632fe9adbc37edf" + sha256: "9f155590c6ba9ba469df637f4729264e4234dc3941ece4690dad63ffac19b5af" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "4.0.0" audioplayers_windows: dependency: transitive description: name: audioplayers_windows - sha256: "622e01c4c357c2aaf1b956c3a0f89d97c3cb40315c03f16e3b6c2a31ff9c38bc" + sha256: "8813b712ba919bb324bde5e3ba97edc81bface945953a54a3dea70b5608bcc70" url: "https://pub.dev" source: hosted - version: "1.1.3" + version: "3.0.0" auto_route: dependency: "direct main" description: name: auto_route - sha256: "12047baeca0e01df93165ef33275b32119d72699ab9a49dc64c20e78f586f96d" + sha256: afa2e3a038efdd9b70478c597161e4c6549a73cd2f5957077d4b311f71104671 url: "https://pub.dev" source: hosted - version: "5.0.4" + version: "7.8.0" auto_route_generator: dependency: "direct dev" description: name: auto_route_generator - sha256: de5bfbc02ae4eebb339dd90d325749ae7536e903f6513ef72b88954072d72b0e + sha256: e7aa9ab44b77cd31a4619d94db645ab5736e543fd0b4c6058c281249e479dfb8 url: "https://pub.dev" source: hosted - version: "5.0.3" + version: "7.3.1" back_button_interceptor: - dependency: "direct main" + dependency: transitive description: name: back_button_interceptor sha256: e47660f2178a4392eb72001f9594d3fdcb5efde93e59d2819d61fda499e781c8 @@ -125,10 +117,10 @@ packages: dependency: "direct main" description: name: badges - sha256: "727580d938b7a1ff47ea42df730d581415606b4224cfa708671c10287f8d3fe6" + sha256: "6e7f3ec561ec08f47f912cfe349d4a1707afdc8dda271e17b046aa6d42c89e77" url: "https://pub.dev" source: hosted - version: "2.0.3" + version: "3.1.1" boolean_selector: dependency: transitive description: @@ -137,14 +129,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.1.1" - bot_toast: - dependency: "direct main" - description: - name: bot_toast - sha256: db6950851aab00ef04b386eb3c76c83739eaffcb6b80d0dc42a675ef7584623b - url: "https://pub.dev" - source: hosted - version: "4.0.4" build: dependency: transitive description: @@ -258,6 +242,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.3" + clever_ads_solutions: + dependency: "direct main" + description: + name: clever_ads_solutions + sha256: fd9386aeb8874c25f8e6dfc85f22cee31b661a2c7a02a92f2f1569ed5dd75da9 + url: "https://pub.dev" + source: hosted + version: "0.1.0" clock: dependency: transitive description: @@ -346,6 +338,14 @@ packages: url: "https://pub.dev" source: hosted version: "2.3.1" + dartz: + dependency: transitive + description: + name: dartz + sha256: e6acf34ad2e31b1eb00948692468c30ab48ac8250e0f0df661e29f12dd252168 + url: "https://pub.dev" + source: hosted + version: "0.10.1" dbus: dependency: transitive description: @@ -371,7 +371,7 @@ packages: source: hosted version: "7.0.0" dio: - dependency: "direct main" + dependency: transitive description: name: dio sha256: a9d76e72985d7087eb7c5e7903224ae52b337131518d127c554b9405936752b8 @@ -403,7 +403,7 @@ packages: source: hosted version: "1.6.1" enum_to_string: - dependency: "direct main" + dependency: transitive description: name: enum_to_string sha256: bd9e83a33b754cb43a75b36a9af2a0b92a757bfd9847d2621ca0b1bed45f8e7a @@ -418,22 +418,6 @@ packages: url: "https://pub.dev" source: hosted version: "2.0.5" - extended_image: - dependency: "direct main" - description: - name: extended_image - sha256: e77d18f956649ba6e5ecebd0cb68542120886336a75ee673788145bd4c3f0767 - url: "https://pub.dev" - source: hosted - version: "8.0.2" - extended_image_library: - dependency: transitive - description: - name: extended_image_library - sha256: af3ff1c09c23ca7663f94272313d63499a6bd19121e99378e375e0cf2ac7a3e4 - url: "https://pub.dev" - source: hosted - version: "3.5.2" fake_async: dependency: transitive description: @@ -462,10 +446,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: b1729fc96627dd44012d0a901558177418818d6bd428df59dcfeb594e5f66432 + sha256: "21145c9c268d54b1f771d8380c195d2d6f655e0567dc1ca2f9c134c02c819e0a" url: "https://pub.dev" source: hosted - version: "5.3.2" + version: "5.3.3" filesize: dependency: "direct main" description: @@ -564,30 +548,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.0.0" + flutter_lints: + dependency: "direct dev" + description: + name: flutter_lints + sha256: aeb0b80a8b3709709c9cc496cdc027c5b3216796bc0af0ce1007eaf24464fd4c + url: "https://pub.dev" + source: hosted + version: "2.0.1" flutter_local_notifications: dependency: "direct main" description: name: flutter_local_notifications - sha256: "57d0012730780fe137260dd180e072c18a73fbeeb924cdc029c18aaa0f338d64" + sha256: "3cc40fe8c50ab8383f3e053a499f00f975636622ecdc8e20a77418ece3b1e975" url: "https://pub.dev" source: hosted - version: "9.9.1" + version: "15.1.0+1" flutter_local_notifications_linux: dependency: transitive description: name: flutter_local_notifications_linux - sha256: b472bfc173791b59ede323661eae20f7fff0b6908fea33dd720a6ef5d576bae8 + sha256: "33f741ef47b5f63cc7f78fe75eeeac7e19f171ff3c3df054d84c1e38bedb6a03" url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "4.0.0+1" flutter_local_notifications_platform_interface: dependency: transitive description: name: flutter_local_notifications_platform_interface - sha256: "21bceee103a66a53b30ea9daf677f990e5b9e89b62f222e60dd241cd08d63d3a" + sha256: "7cf643d6d5022f3baed0be777b0662cce5919c0a7b86e700299f22dc4ae660ef" url: "https://pub.dev" source: hosted - version: "5.0.0" + version: "7.0.0+1" flutter_localizations: dependency: "direct main" description: flutter @@ -605,10 +597,10 @@ packages: dependency: "direct main" description: name: flutter_markdown - sha256: dc6d5258653f6857135b32896ccda7f7af0c54dcec832495ad6835154c6c77c0 + sha256: "2b206d397dd7836ea60035b2d43825c8a303a76a5098e66f42d55a753e18d431" url: "https://pub.dev" source: hosted - version: "0.6.15" + version: "0.6.17+1" flutter_pdfview: dependency: "direct main" description: @@ -626,7 +618,7 @@ packages: source: hosted version: "2.0.15" flutter_svg: - dependency: "direct main" + dependency: transitive description: name: flutter_svg sha256: "8c5d68a82add3ca76d792f058b186a0599414f279f00ece4830b9b231b570338" @@ -660,7 +652,7 @@ packages: source: sdk version: "0.0.0" fluttertoast: - dependency: "direct main" + dependency: transitive description: name: fluttertoast sha256: "474f7d506230897a3cd28c965ec21c5328ae5605fc9c400cd330e9e9d6ac175c" @@ -704,6 +696,14 @@ packages: url: "https://pub.dev" source: hosted version: "4.0.4" + google_mobile_ads: + dependency: "direct main" + description: + name: google_mobile_ads + sha256: "24ee4e9546866cc15ebe565dabf6a6c485ce5fbec0645fde22799800532000f0" + url: "https://pub.dev" + source: hosted + version: "3.0.0" graphs: dependency: transitive description: @@ -729,21 +729,13 @@ packages: source: hosted version: "0.15.4" http: - dependency: "direct main" + dependency: transitive description: name: http sha256: "5895291c13fa8a3bd82e76d5627f69e0d85ca6a30dcac95c4ea19a5d555879c2" url: "https://pub.dev" source: hosted version: "0.13.6" - http_client_helper: - dependency: transitive - description: - name: http_client_helper - sha256: "14c6e756644339f561321dab021215475ba4779aa962466f59ccb3ecf66b36c3" - url: "https://pub.dev" - source: hosted - version: "2.0.4" http_multi_server: dependency: transitive description: @@ -782,7 +774,7 @@ packages: source: sdk version: "0.0.0" intl: - dependency: transitive + dependency: "direct main" description: name: intl sha256: a3715e3bc90294e971cb7dc063fbf3cd9ee0ebf8604ffeafabd9e6f16abbdbe6 @@ -813,22 +805,30 @@ packages: url: "https://pub.dev" source: hosted version: "4.8.1" + lints: + dependency: transitive + description: + name: lints + sha256: "0a217c6c989d21039f1498c3ed9f3ed71b354e69873f13a8dfc3c9fe76f1b452" + url: "https://pub.dev" + source: hosted + version: "2.1.1" loader_overlay: dependency: "direct main" description: name: loader_overlay - sha256: "3f8f17abf4a24c67fc0b6c4b71faaff72370ac19552dd504a22126db890dbe0c" + sha256: "5d712eb5dc0b8618797d39306227a6c3805e2b198f5361e18ec9e325f215e9a4" url: "https://pub.dev" source: hosted - version: "2.2.0" + version: "2.3.0" logger: dependency: "direct main" description: name: logger - sha256: "7ad7215c15420a102ec687bb320a7312afd449bac63bfb1c60d9787c27b9767f" + sha256: "66cb048220ca51cf9011da69fa581e4ee2bed4be6e82870d9e9baae75739da49" url: "https://pub.dev" source: hosted - version: "1.4.0" + version: "2.0.1" logging: dependency: transitive description: @@ -846,7 +846,7 @@ packages: source: hosted version: "6.0.1" markdown: - dependency: "direct main" + dependency: transitive description: name: markdown sha256: "8e332924094383133cee218b676871f42db2514f1f6ac617b6cf6152a7faab8e" @@ -929,10 +929,10 @@ packages: dependency: "direct main" description: name: package_info_plus - sha256: ceb027f6bc6a60674a233b4a90a7658af1aebdea833da0b5b53c1e9821a78c7b + sha256: "6ff267fcd9d48cb61c8df74a82680e8b82e940231bb5f68356672fde0397334a" url: "https://pub.dev" source: hosted - version: "4.0.2" + version: "4.1.0" package_info_plus_platform_interface: dependency: transitive description: @@ -942,7 +942,7 @@ packages: source: hosted version: "2.0.1" path: - dependency: "direct main" + dependency: transitive description: name: path sha256: "8829d8a55c13fc0e37127c29fedf290c102f4e40ae94ada574091fe0ff96c917" @@ -969,52 +969,52 @@ packages: dependency: "direct main" description: name: path_provider - sha256: "3087813781ab814e4157b172f1a11c46be20179fcc9bea043e0fba36bc0acaa2" + sha256: "909b84830485dbcd0308edf6f7368bc8fd76afa26a270420f34cabea2a6467a0" url: "https://pub.dev" source: hosted - version: "2.0.15" + version: "2.1.0" path_provider_android: dependency: transitive description: name: path_provider_android - sha256: "2cec049d282c7f13c594b4a73976b0b4f2d7a1838a6dd5aaf7bd9719196bee86" + sha256: "5d44fc3314d969b84816b569070d7ace0f1dea04bd94a83f74c4829615d22ad8" url: "https://pub.dev" source: hosted - version: "2.0.27" + version: "2.1.0" path_provider_foundation: dependency: transitive description: name: path_provider_foundation - sha256: "1995d88ec2948dac43edf8fe58eb434d35d22a2940ecee1a9fefcd62beee6eb3" + sha256: "1b744d3d774e5a879bb76d6cd1ecee2ba2c6960c03b1020cd35212f6aa267ac5" url: "https://pub.dev" source: hosted - version: "2.2.3" + version: "2.3.0" path_provider_linux: dependency: transitive description: name: path_provider_linux - sha256: ffbb8cc9ed2c9ec0e4b7a541e56fd79b138e8f47d2fb86815f15358a349b3b57 + sha256: ba2b77f0c52a33db09fc8caf85b12df691bf28d983e84cf87ff6d693cfa007b3 url: "https://pub.dev" source: hosted - version: "2.1.11" + version: "2.2.0" path_provider_platform_interface: dependency: transitive description: name: path_provider_platform_interface - sha256: "57585299a729335f1298b43245842678cb9f43a6310351b18fb577d6e33165ec" + sha256: bced5679c7df11190e1ddc35f3222c858f328fff85c3942e46e7f5589bf9eb84 url: "https://pub.dev" source: hosted - version: "2.0.6" + version: "2.1.0" path_provider_windows: dependency: transitive description: name: path_provider_windows - sha256: "1cb68ba4cd3a795033de62ba1b7b4564dace301f952de6bfb3cd91b202b6ee96" + sha256: ee0e0d164516b90ae1f970bdf29f726f1aa730d7cfc449ecc74c495378b705da url: "https://pub.dev" source: hosted - version: "2.1.7" + version: "2.2.0" pedantic: - dependency: "direct main" + dependency: transitive description: name: pedantic sha256: "67fc27ed9639506c856c840ccce7594d0bdcd91bc8d53d6e52359449a1d50602" @@ -1025,42 +1025,42 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "1b6b3e73f0bcbc856548bbdfb1c33084a401c4f143e220629a9055233d76c331" + sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" url: "https://pub.dev" source: hosted - version: "10.3.0" + version: "10.4.3" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "8f6a95ccbca13766882f95d32684d7c9bfe6c45650c32bedba948ef1c6a4ddf7" + sha256: "2ffaf52a21f64ac9b35fe7369bb9533edbd4f698e5604db8645b1064ff4cf221" url: "https://pub.dev" source: hosted - version: "10.2.3" + version: "10.3.3" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "08dcb6ce628ac0b257e429944b4c652c2a4e6af725bdf12b498daa2c6b2b1edb" + sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" url: "https://pub.dev" source: hosted - version: "9.1.0" + version: "9.1.4" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: de20a5c3269229c1ae2e5a6b822f6cb59578b23e8255c93fbeebfc82116e6b11 + sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" url: "https://pub.dev" source: hosted - version: "3.10.0" + version: "3.11.3" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: f67cab14b4328574938ecea2db3475dad7af7ead6afab6338772c5f88963e38b + sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 url: "https://pub.dev" source: hosted - version: "0.1.2" + version: "0.1.3" petitparser: dependency: transitive description: @@ -1185,26 +1185,26 @@ packages: dependency: "direct main" description: name: sentry - sha256: "8f4f1d3ef3ca1801aa3138392bbf0851b39eff703008111765b265f76c8f0190" + sha256: "39c23342fc96105da449914f7774139a17a0ca8a4e70d9ad5200171f7e47d6ba" url: "https://pub.dev" source: hosted - version: "7.8.0" + version: "7.9.0" share_plus: dependency: "direct main" description: name: share_plus - sha256: b1f15232d41e9701ab2f04181f21610c36c83a12ae426b79b4bd011c567934b1 + sha256: "6cec740fa0943a826951223e76218df002804adb588235a8910dc3d6b0654e11" url: "https://pub.dev" source: hosted - version: "6.3.4" + version: "7.1.0" share_plus_platform_interface: dependency: transitive description: name: share_plus_platform_interface - sha256: "0c6e61471bd71b04a138b8b588fa388e66d8b005e6f2deda63371c5c505a0981" + sha256: "357412af4178d8e11d14f41723f80f12caea54cf0d5cd29af9dcdab85d58aea7" url: "https://pub.dev" source: hosted - version: "3.2.1" + version: "3.3.0" shared_preferences: dependency: transitive description: @@ -1374,10 +1374,10 @@ packages: dependency: "direct main" description: name: stop_watch_timer - sha256: eb690e4f6d983ba12ddfbf51a5d489fdba8b64982b9651cb538650888cec2886 + sha256: "3fe040c5bf04004f6d21a454a859e90b0d3e2323d24d2d9e80d5d9df9ff614cc" url: "https://pub.dev" source: hosted - version: "2.0.0" + version: "3.0.1" stream_channel: dependency: transitive description: @@ -1406,10 +1406,10 @@ packages: dependency: "direct main" description: name: styled_text - sha256: "0df99e4ac632c14219ed3be4fef6174898cef1df7404e65624689fc9f98c81c7" + sha256: fd624172cf629751b4f171dd0ecf9acf02a06df3f8a81bb56c0caa4f1df706c3 url: "https://pub.dev" source: hosted - version: "4.0.0+1" + version: "8.1.0" sync_http: dependency: transitive description: @@ -1462,10 +1462,10 @@ packages: dependency: transitive description: name: timezone - sha256: "57b35f6e8ef731f18529695bffc62f92c6189fac2e52c12d478dec1931afb66e" + sha256: "1cfd8ddc2d1cfd836bc93e67b9be88c3adaeca6f40a00ca999104c30693cdca0" url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.9.2" timing: dependency: transitive description: @@ -1494,10 +1494,10 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: eb1e00ab44303d50dd487aab67ebc575456c146c6af44422f9c13889984c00f3 + sha256: "781bd58a1eb16069412365c98597726cd8810ae27435f04b3b4d3a470bacd61e" url: "https://pub.dev" source: hosted - version: "6.1.11" + version: "6.1.12" url_launcher_android: dependency: transitive description: @@ -1598,10 +1598,10 @@ packages: dependency: "direct main" description: name: video_player - sha256: de95f0e9405f29b5582573d4166132e71f83b3158aac14e8ee5767a54f4f1fbd + sha256: "3fd106c74da32f336dc7feb65021da9b0207cb3124392935f1552834f7cce822" url: "https://pub.dev" source: hosted - version: "2.6.1" + version: "2.7.0" video_player_android: dependency: transitive description: @@ -1642,6 +1642,14 @@ packages: url: "https://pub.dev" source: hosted version: "0.5.3" + visibility_detector: + dependency: transitive + description: + name: visibility_detector + sha256: "15c54a459ec2c17b4705450483f3d5a2858e733aee893dcee9d75fd04814940d" + url: "https://pub.dev" + source: hosted + version: "0.3.3" vm_service: dependency: transitive description: @@ -1722,6 +1730,38 @@ packages: url: "https://pub.dev" source: hosted version: "1.2.0" + webview_flutter: + dependency: transitive + description: + name: webview_flutter + sha256: "789d52bd789373cc1e100fb634af2127e86c99cf9abde09499743270c5de8d00" + url: "https://pub.dev" + source: hosted + version: "4.2.2" + webview_flutter_android: + dependency: transitive + description: + name: webview_flutter_android + sha256: d936a09fbfd08cb78f7329e0bbacf6158fbdfe24ffc908b22444c07d295eb193 + url: "https://pub.dev" + source: hosted + version: "3.9.2" + webview_flutter_platform_interface: + dependency: transitive + description: + name: webview_flutter_platform_interface + sha256: "564ef378cafc1a0e29f1d76ce175ef517a0a6115875dff7b43fccbef2b0aeb30" + url: "https://pub.dev" + source: hosted + version: "2.4.0" + webview_flutter_wkwebview: + dependency: transitive + description: + name: webview_flutter_wkwebview + sha256: "5fa098f28b606f699e8ca52d9e4e11edbbfef65189f5f77ae92703ba5408fd25" + url: "https://pub.dev" + source: hosted + version: "3.7.2" win32: dependency: "direct overridden" description: diff --git a/pubspec.yaml b/pubspec.yaml index 030fad0e7..5a54da8e8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -21,139 +21,84 @@ environment: sdk: '>=3.0.3 <4.0.0' dependencies: + # SDKs flutter: sdk: flutter flutter_localizations: sdk: flutter - i18n_extension: ^7.0.0 - - analyzer: ^5.6.0 - - credit_card_validator: ^2.0.1 - + # State management and Data handling provider: ^6.0.5 - protobuf: any + uuid: ^3.0.7 + # UI Enhancements & User Input google_fonts: - flutter_switch: ^0.3.2 - flag: ^7.0.0 + badges: ^3.1.1 + dotted_border: ^2.0.0+3 + styled_text: ^8.1.0 + emoji_picker_flutter: ^1.6.1 + pin_code_text_field: ^1.8.0 + scrollable_positioned_list: ^0.3.8 + infinite_scroll_pagination: ^3.2.0 + wakelock: ^0.6.2 + email_validator: ^2.1.17 + credit_card_validator: ^2.1.0 + # Media & File handling + audioplayers: ^5.1.0 + video_player: ^2.7.0 + video_thumbnail: ^0.5.3 + file_picker: ^5.3.3 + filesize: ^2.0.1 + cached_network_image: ^3.2.3 +# change this with flutter_downloader + flutter_uploader: ^3.0.0-beta.3 + mime: ^1.0.4 + flutter_pdfview: ^1.3.1 - qr_flutter: ^4.0.0 - + # Navigation & Localization + auto_route: ^7.8.0 + i18n_extension: ^7.0.0 + intl: ^0.18.0 + # QR + qr_flutter: ^4.1.0 qr_code_scanner: ^1.0.1 - loader_overlay: ^2.0.0 - - audioplayers: ^3.0.1 - - auto_route: ^5.0.4 - - bot_toast: ^4.0.1 - - stop_watch_timer: ^2.0.0 - - uuid: ^3.0.3 - - pedantic: ^1.11.0 - - # wechat_assets_picker: ^6.0.1 - - # wechat_camera_picker: ^2.3.0 - - flutter_keyboard_visibility: ^5.4.0 - - video_player: ^2.6.0 - - flutter_svg: ^2.0.7 + # Timer & Overlay + stop_watch_timer: ^3.0.1 + loader_overlay: ^2.3.0 + # Keyboard & Color utilities + flutter_keyboard_visibility: ^5.4.1 hexcolor: ^3.0.1 - url_launcher: ^6.1.10 + # URL & Sharing utilities + url_launcher: ^6.1.12 + share_plus: ^7.1.0 - fluttertoast: ^8.2.1 + # Notifications & Logging + flutter_local_notifications: ^15.1.0+1 + logger: ^2.0.1 - # Use a fork of catcher since >=0.6.9 is incompatible with flutter_localizations - # from sdk. See https://github.com/jhomlala/catcher/pull/234 for more details + # Error handling & Package information catcher: git: url: https://github.com/ThexXTURBOXx/catcher.git + sentry: ^7.9.0 + package_info_plus: ^4.1.0 - sentry: ^7.7.0 + # Path, permission & Markdown handling + path_provider: ^2.1.0 + permission_handler: ^10.4.3 + flutter_markdown: ^0.6.17+1 + # Ads + google_mobile_ads: ^3.0.0 + clever_ads_solutions: ^0.1.0 - badges: ^2.0.0-nullsafety.1 - dotted_border: ^2.0.0 - - # Version 1.0.7 seems glitchy, so we're using 1.0.6 explicitly - emoji_picker_flutter: ^1.6.1 - - email_validator: ^2.1.17 - - pin_code_text_field: ^1.8.0 - - styled_text: ^4.0.0+1 - - back_button_interceptor: ^6.0.2 - - scrollable_positioned_list: ^0.3.8 - - extended_image: ^8.0.2 - - file_picker: ^5.2.6 - - # flutter_webrtc: ^0.9.20 - # Use our own fork of flutter_webrtc with more optimized audio quality settings - # flutter_webrtc: - # git: - # url: https://github.com/getlantern/flutter-webrtc.git - # ref: 1bf2cd40af6e8b0c6180c937659051eb157c0a4e - - markdown: ^7.1.0 - - flutter_markdown: ^0.6.8 - - flutter_local_notifications: ^9.9.1 - - http: ^0.13.5 - - enum_to_string: ^2.0.1 - - filesize: ^2.0.1 - - dio: ^5.2.1+1 - - path_provider: ^2.0.15 - - permission_handler: ^10.2.0 - - path: ^1.8.0 - - infinite_scroll_pagination: ^3.2.0 - - share_plus: ^6.3.1 - - package_info_plus: ^4.0.2 - - logger: ^1.3.0 - - cached_network_image: ^3.2.3 - - android_path_provider: ^0.3.0 - - flutter_uploader: ^3.0.0-beta.3 - - mime: ^1.0.4 - - video_thumbnail: ^0.5.3 - - flutter_pdfview: ^1.2.9 - - wakelock: ^0.6.2 # wakelock ^0.6.2 requires win32 ^2.0.0 or ^3.0.0 # See https://github.com/creativecreatorormaybenot/wakelock/issues/211 @@ -167,10 +112,10 @@ dev_dependencies: sdk: flutter flutter_driver: sdk: flutter - + flutter_lints: ^2.0.1 test: ^1.15.7 mockito: ^5.0.12 - auto_route_generator: ^5.0.3 + auto_route_generator: ^7.3.1 build_runner: ^2.0.5 # For information on the generic Dart part of this file, see the