├── app ├── .gitignore ├── src │ ├── main │ │ ├── ic_launcher-playstore.png │ │ ├── res │ │ │ ├── drawable │ │ │ │ ├── deepl_logo.png │ │ │ │ ├── deepl_mini_logo.png │ │ │ │ ├── settings_black_24dp.png │ │ │ │ ├── splash.xml │ │ │ │ ├── ic_baseline_refresh_32.xml │ │ │ │ ├── ic_deepl_mini_logo.xml │ │ │ │ ├── baseline_settings_24.xml │ │ │ │ ├── app_logo.xml │ │ │ │ ├── ic_launcher_foreground.xml │ │ │ │ └── ic_launcher_monochrome.xml │ │ │ ├── drawable-night │ │ │ │ ├── deepl_logo.png │ │ │ │ └── deepl_mini_logo.png │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── values-v21 │ │ │ │ └── themes.xml │ │ │ ├── values-v29 │ │ │ │ └── arrays.xml │ │ │ ├── values-night │ │ │ │ └── colors.xml │ │ │ ├── values-nl │ │ │ │ └── strings.xml │ │ │ ├── values-es │ │ │ │ └── strings.xml │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher_round.xml │ │ │ │ └── ic_launcher.xml │ │ │ ├── drawable-v23 │ │ │ │ └── splash.xml │ │ │ ├── xml │ │ │ │ ├── network_security_config.xml │ │ │ │ └── root_preferences.xml │ │ │ ├── xml-v25 │ │ │ │ └── shortcuts.xml │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── strings_not_translatable.xml │ │ │ │ ├── arrays.xml │ │ │ │ ├── strings.xml │ │ │ │ └── themes.xml │ │ │ ├── raw │ │ │ │ ├── isrg_root_x2.pem │ │ │ │ └── isrg_root_x1.pem │ │ │ ├── layout │ │ │ │ ├── settings_activity.xml │ │ │ │ ├── popup_layout.xml │ │ │ │ ├── network_err.xml │ │ │ │ └── activity_main.xml │ │ │ ├── values-ja │ │ │ │ └── strings.xml │ │ │ ├── values-zh │ │ │ │ └── strings.xml │ │ │ ├── values-tr │ │ │ │ └── strings.xml │ │ │ ├── values-de │ │ │ │ └── strings.xml │ │ │ ├── values-ro │ │ │ │ └── strings.xml │ │ │ ├── values-ru │ │ │ │ └── strings.xml │ │ │ ├── values-it │ │ │ │ └── strings.xml │ │ │ ├── values-fr │ │ │ │ └── strings.xml │ │ │ └── values-cs │ │ │ │ └── strings.xml │ │ ├── assets │ │ │ ├── debug.js │ │ │ ├── patch-darkThemeFix.js │ │ │ ├── legacy │ │ │ │ ├── patch-clipboard.js │ │ │ │ ├── switchLanguage.html │ │ │ │ └── patch-switchLanguage.js │ │ │ ├── DeepL_Logo_lightBlue_v2.svg │ │ │ ├── hide-elements.js │ │ │ └── DeepL_Text_light.svg │ │ ├── java │ │ │ └── com │ │ │ │ └── example │ │ │ │ └── deeplviewer │ │ │ │ ├── service │ │ │ │ └── QSTileService.kt │ │ │ │ ├── webview │ │ │ │ ├── WebAppInterface.kt │ │ │ │ ├── MyWebViewClient.kt │ │ │ │ └── NestedScrollWebView.kt │ │ │ │ ├── helper │ │ │ │ ├── UrlHelper.kt │ │ │ │ └── CookieManagerHelper.kt │ │ │ │ ├── config │ │ │ │ └── WebViewConfig.kt │ │ │ │ ├── App.kt │ │ │ │ └── activity │ │ │ │ ├── SettingsActivity.kt │ │ │ │ ├── FloatingTextSelection.kt │ │ │ │ └── MainActivity.kt │ │ └── AndroidManifest.xml │ ├── androidTest │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── com │ │ │ └── example │ │ │ └── deeplviewer │ │ │ └── WebViewScreenshotTest.kt │ └── test │ │ └── java │ │ └── com │ │ └── example │ │ └── deeplviewer │ │ └── ExampleUnitTest.kt ├── screenshots │ └── debug │ │ └── unknown_webview_top_screen.png ├── proguard-rules.pro └── build.gradle.kts ├── fastlane ├── metadata │ └── android │ │ ├── ja │ │ ├── short_description.txt │ │ └── full_description.txt │ │ ├── en-US │ │ ├── short_description.txt │ │ ├── images │ │ │ ├── icon.png │ │ │ └── phoneScreenshots │ │ │ │ ├── 1.png │ │ │ │ ├── 2.png │ │ │ │ ├── 3.png │ │ │ │ └── 4.png │ │ └── full_description.txt │ │ ├── cs │ │ ├── short_description.txt │ │ └── full_description.txt │ │ ├── de │ │ ├── short_description.txt │ │ └── full_description.txt │ │ ├── tr │ │ ├── short_description.txt │ │ └── full_description.txt │ │ ├── it │ │ ├── short_description.txt │ │ └── full_description.txt │ │ ├── es │ │ ├── short_description.txt │ │ └── full_description.txt │ │ ├── ru │ │ ├── short_description.txt │ │ └── full_description.txt │ │ └── fr │ │ ├── short_description.txt │ │ └── full_description.txt ├── Appfile └── Fastfile ├── gradle ├── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties └── libs.versions.toml ├── Gemfile ├── .gitignore ├── settings.gradle.kts ├── gradle.properties ├── LICENSE ├── .github └── workflows │ ├── ci_build.yaml │ ├── capture_webview_screenshots.yml │ └── screenshot_diff_check.yml ├── README_ZH.md ├── README_JA.md ├── README_RU.md ├── README.md ├── README_TR.md ├── README_CS.md ├── README_IT.md ├── README_DE.md ├── README_FR.md ├── gradlew.bat ├── Gemfile.lock └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /fastlane/metadata/android/ja/short_description.txt: -------------------------------------------------------------------------------- 1 | DeepL翻訳の非公式Androidアプリ -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/short_description.txt: -------------------------------------------------------------------------------- 1 | Unofficial Android App for DeepL Translator -------------------------------------------------------------------------------- /fastlane/metadata/android/cs/short_description.txt: -------------------------------------------------------------------------------- 1 | Neoficiální Android aplikace pro DeepL Překladač 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/de/short_description.txt: -------------------------------------------------------------------------------- 1 | inoffizielle Android App für den Deepl Translator -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/short_description.txt: -------------------------------------------------------------------------------- 1 | DeepL Çeviri için resmi olmayan Android uygulaması -------------------------------------------------------------------------------- /fastlane/metadata/android/it/short_description.txt: -------------------------------------------------------------------------------- 1 | Un'applicazione Android non ufficiale per traduzioni DeepL -------------------------------------------------------------------------------- /fastlane/metadata/android/es/short_description.txt: -------------------------------------------------------------------------------- 1 | Aplicación Android no oficial para for DeepL Translator 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/ru/short_description.txt: -------------------------------------------------------------------------------- 1 | Неофициальное Android-приложение для DeepL переводчика 2 | -------------------------------------------------------------------------------- /fastlane/metadata/android/fr/short_description.txt: -------------------------------------------------------------------------------- 1 | Application Android non officielle pour le traducteur DeepL 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/deepl_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/drawable/deepl_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/deepl_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/drawable-night/deepl_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/deepl_mini_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/drawable/deepl_mini_logo.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/fastlane/metadata/android/en-US/images/icon.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-night/deepl_mini_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/drawable-night/deepl_mini_logo.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/settings_black_24dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/drawable/settings_black_24dp.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/screenshots/debug/unknown_webview_top_screen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/screenshots/debug/unknown_webview_top_screen.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/1.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/2.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/3.png -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/images/phoneScreenshots/4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sakusaku3939/DeepLAndroid/HEAD/fastlane/metadata/android/en-US/images/phoneScreenshots/4.png -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | source "https://rubygems.org" 4 | 5 | git_source(:github) { |repo_name| "https://github.com/#{repo_name}" } 6 | 7 | # gem "rails" 8 | gem "fastlane" -------------------------------------------------------------------------------- /fastlane/Appfile: -------------------------------------------------------------------------------- 1 | json_key_file("") # Path to the json secret file - Follow https://docs.fastlane.tools/actions/supply/#setup to get one 2 | package_name("com.example.deeplviewer") # e.g. com.krausefx.app 3 | -------------------------------------------------------------------------------- /app/src/main/assets/debug.js: -------------------------------------------------------------------------------- 1 | document.body.addEventListener('click', (e) => { 2 | console.log("class: " + e.target.className); 3 | console.log("id: " + e.target.id); 4 | /*console.log("html: " + e.target.outerHTML);*/ 5 | }); -------------------------------------------------------------------------------- /app/src/main/res/values-v21/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-v29/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @string/theme_light 4 | @string/theme_dark 5 | @string/theme_default 6 | 7 | -------------------------------------------------------------------------------- /fastlane/metadata/android/ja/full_description.txt: -------------------------------------------------------------------------------- 1 | DeepL翻訳の非公式Androidアプリです。AndroidのGUIコンポーネントの「WebView」といわれる技術を使用してDeepL翻訳をネイティブアプリ風にしたものです。必要な要素以外はJavaScriptを使用して隠しています。 2 | 3 | 機能一覧 4 | 5 | * 翻訳画面のみをWebViewで表示 6 | * ダークモードへの切り替え 7 | * 選択メニューからDeepLアプリを起動 8 | * アプリアイコンを長押しで設定画面に入る -------------------------------------------------------------------------------- /app/src/main/res/values-night/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @color/background_material_dark 4 | #ffffff 5 | #ffffff 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/caches 5 | /.idea/libraries 6 | /.idea/modules.xml 7 | /.idea/workspace.xml 8 | /.idea/navEditor.xml 9 | /.idea/assetWizardSettings.xml 10 | .DS_Store 11 | /build 12 | /captures 13 | .externalNativeBuild 14 | /.kotlin 15 | .cxx 16 | .idea -------------------------------------------------------------------------------- /app/src/main/res/values-nl/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeepL 3 | Afbeelding 4 | Tekst is gekopieerd 5 | Oeps, geen verbinding met DeepL server 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values-es/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeepL 3 | Imagen 4 | El texto fue copiado 5 | Vaya, hubo problemas al conectar con los servidores de DeepL 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/assets/patch-darkThemeFix.js: -------------------------------------------------------------------------------- 1 | $('.dl_header_menu_v2__logo__img').attr('src', 'data:image/svg+xml;base64,' + Android.stringToBase64String(Android.getAssetsText('DeepL_Logo_lightBlue_v2.svg'))); 2 | $('.dl_logo_text').attr('src', 'data:image/svg+xml;base64,' + Android.stringToBase64String(Android.getAssetsText('DeepL_Text_light.svg'))); -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=845952a9d6afa783db70bb3b0effaae45ae5542ca2bb7929619e8af49cb634cf 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /app/src/androidTest/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/assets/legacy/patch-clipboard.js: -------------------------------------------------------------------------------- 1 | var copyButton = $('.Target--Dwohg'); 2 | 3 | if (copyButton.length > 1) { 4 | copyButton.last().off('click'); 5 | copyButton.last().on('click', function () { 6 | const text = $('[class^="TextInput-module--textarea--"]').last().val(); 7 | /* Discontinued because the copy button now works on WebView as well 8 | Android.copyClipboard(text); 9 | */ 10 | }); 11 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/example/deeplviewer/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * See [testing documentation](http://d.android.com/tools/testing). 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /fastlane/metadata/android/ru/full_description.txt: -------------------------------------------------------------------------------- 1 | Это неофициальное Android-приложение для DeepL переводчика. Оно использует технологию WebView, компонент интерфейса Android, чтобы выглядеть как нативное приложение. Чтобы скрыть все ненужные элементы, используется JavaScript. 2 | 3 | Функции: 4 | 5 | * Показывать только экран перевода через WebView 6 | * Переключение в темный режим 7 | * Запустить DeepL из меню выделенного текста 8 | * Откройте экран настроек, нажав и удерживая значок приложения -------------------------------------------------------------------------------- /fastlane/metadata/android/en-US/full_description.txt: -------------------------------------------------------------------------------- 1 | This is an unofficial Android app for DeepL translation. It uses a technology called WebView, an Android GUI component, to make it look like a native app. JavaScript is used to hide all but the most necessary elements. 2 | 3 | Features: 4 | 5 | * Display only the translation screen by WebView 6 | * Switching to dark mode 7 | * Launch DeepL from floating text selection action 8 | * Long press on the application icon to access the settings screen -------------------------------------------------------------------------------- /fastlane/metadata/android/tr/full_description.txt: -------------------------------------------------------------------------------- 1 | DeepL çeviri için resmi olmayan bir Android uygulamasıdır. Yerel bir uygulama gibi görünmesini sağlamak için bir Android Arayüz bileşeni olan WebView adlı bir teknoloji kullanır. JavaScript gereksiz öğeleri gizlemek için kullanılır. 2 | 3 | 4 | Özellikler: 5 | 6 | * WebView ile yalnızca çeviri ekranını görüntüleme 7 | * Koyu moda geçme 8 | * DeepL'i kayan metin seçimi eyleminden başlatma 9 | * Uygulama simgesine basılı tutarak ayarlar ekranına erişin -------------------------------------------------------------------------------- /app/src/main/res/drawable-v23/splash.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /fastlane/metadata/android/cs/full_description.txt: -------------------------------------------------------------------------------- 1 | Toto je neoficiální Android aplikace pro DeepL překlady. Využívá technologii WebView, komponentu grafického uživatelského rozhraní systému Android, aby vypadala jako nativní aplikace. JavaScript se používá ke skrytí všech prvků kromě těch nejnutnějších. 2 | 3 | Funkce: 4 | 5 | * Zobrazení pouze obrazovky překladu pomocí WebView 6 | * Přepínání do tmavého režimu 7 | * Spuštění DeepL z akce výběru v plovoucím textu 8 | * Dlouhý stisk ikony aplikace umožňuje otevřít obrazovku s nastavením 9 | -------------------------------------------------------------------------------- /fastlane/metadata/android/it/full_description.txt: -------------------------------------------------------------------------------- 1 | Questa è un'applicazione Android non ufficiale per traduzioni DeepL. Usa una tecnologia chiamata WebView, una componente GUI di Android, per farla sembrare come un'app nativa. JavaScript è usato per nascondere tutto tranne gli elementi più necessari. 2 | 3 | Funzionalità: 4 | 5 | * Mostra solo la schermata di traduzione tramite WebView. 6 | * Cambiare in modalità scura. 7 | * Lancia DeepL dall'azione di selezione del testo. 8 | * Accedere alla schermata delle impostazioni tenendo premuta l'icona dell'applicazione -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_baseline_refresh_32.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /fastlane/metadata/android/es/full_description.txt: -------------------------------------------------------------------------------- 1 | Esta es una aplicación no oficial de Android para el traductor DeepL. Utiliza una tecnología llamada WebView, un componente GUI de Android, para que parezca una aplicación nativa. Se utiliza JavaScript para ocultar todos los elementos excepto los más necesarios. 2 | 3 | Características: 4 | 5 | * Muestra sólo la pantalla de traducción mediante WebView 6 | * Cambiar al modo oscuro 7 | * Lanza DeepL desde la acción de selección de texto flotante 8 | * Aceda ao ecrã de definições premindo e mantendo premido o ícone da aplicação 9 | -------------------------------------------------------------------------------- /app/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /fastlane/metadata/android/de/full_description.txt: -------------------------------------------------------------------------------- 1 | Dies ist eine inoffizielle Android-App für den DeepL-Übersetzer. Es verwendet eine Technologie namens WebView, eine Android-GUI-Komponente, um es wie eine native App aussehen zu lassen. JavaScript wird verwendet, um alle bis auf die notwendigsten Elemente auszublenden. 2 | 3 | Funktionen: 4 | 5 | * Den Übersetzungsbildschirm im WebView anzeigen lassen 6 | * In den Dunkelmodus wechseln 7 | * Starten Sie DeepL über die schwebende Textauswahlaktion 8 | * Rufen Sie den Einstellungsbildschirm auf, indem Sie das Anwendungssymbol drücken und gedrückt halten -------------------------------------------------------------------------------- /app/src/main/res/xml-v25/shortcuts.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 11 | 12 | -------------------------------------------------------------------------------- /fastlane/metadata/android/fr/full_description.txt: -------------------------------------------------------------------------------- 1 | Il s'agit d'une application Android non officielle pour le traducteur DeepL. Elle utilise une technologie appelée WebView, un composant d'interface graphique d'Android, afin de la faire ressembler à une application native. JavaScript est utilisé pour masquer tous les éléments à l'exception des plus nécessaires. 2 | 3 | Fonctionnalités: 4 | 5 | * Afficher uniquement l'écran de traduction via WebView 6 | * Passage en mode sombre 7 | * Lancer DeepL par une action du menu flottant de sélection de texte 8 | * Accédez à l'écran des paramètres en appuyant sur l'icône de l'application et en la maintenant enfoncée -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | google() 4 | mavenCentral() 5 | gradlePluginPortal() 6 | } 7 | 8 | resolutionStrategy{ 9 | eachPlugin{ 10 | when(requested.id.id){ 11 | "shot" -> { 12 | useModule("com.karumi:shot:${requested.version}") 13 | } 14 | } 15 | } 16 | } 17 | } 18 | 19 | dependencyResolutionManagement { 20 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) 21 | repositories { 22 | google() 23 | mavenCentral() 24 | } 25 | } 26 | 27 | rootProject.name = "DeepLViewer" 28 | include(":app") -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #042C49 4 | #ffffff 5 | #00000000 6 | 7 | #204060 8 | #204060 9 | #204060 10 | @color/background_material_light 11 | @color/background 12 | @color/background 13 | 14 | @color/colorAccent 15 | #212121 16 | -------------------------------------------------------------------------------- /app/src/main/assets/legacy/switchLanguage.html: -------------------------------------------------------------------------------- 1 |
3 | 6 | 8 | 10 | 11 |
-------------------------------------------------------------------------------- /app/src/main/res/values/strings_not_translatable.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | defaultDarkMode 5 | isEnabledPopupMode 6 | isEnabledTranslateButton 7 | isEnabledAutoDeleteCookies 8 | appVersion 9 | https://github.com/sakusaku3939/DeepLAndroid/releases 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | @string/theme_light 4 | @string/theme_dark 5 | @string/theme_battery 6 | 7 | 8 | 9 | @string/theme_AppCompatDelegate_MODE_NIGHT_NO 10 | @string/theme_AppCompatDelegate_MODE_NIGHT_YES 11 | @string/theme_AppCompatDelegate_MODE_NIGHT_FOLLOW_SYSTEM 12 | 13 | 14 | 1 15 | 2 16 | -1 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/raw/isrg_root_x2.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICGzCCAaGgAwIBAgIQQdKd0XLq7qeAwSxs6S+HUjAKBggqhkjOPQQDAzBPMQsw 3 | CQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJuZXQgU2VjdXJpdHkgUmVzZWFyY2gg 4 | R3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBYMjAeFw0yMDA5MDQwMDAwMDBaFw00 5 | MDA5MTcxNjAwMDBaME8xCzAJBgNVBAYTAlVTMSkwJwYDVQQKEyBJbnRlcm5ldCBT 6 | ZWN1cml0eSBSZXNlYXJjaCBHcm91cDEVMBMGA1UEAxMMSVNSRyBSb290IFgyMHYw 7 | EAYHKoZIzj0CAQYFK4EEACIDYgAEzZvVn4CDCuwJSvMWSj5cz3es3mcFDR0HttwW 8 | +1qLFNvicWDEukWVEYmO6gbf9yoWHKS5xcUy4APgHoIYOIvXRdgKam7mAHf7AlF9 9 | ItgKbppbd9/w+kHsOdx1ymgHDB/qo0IwQDAOBgNVHQ8BAf8EBAMCAQYwDwYDVR0T 10 | AQH/BAUwAwEB/zAdBgNVHQ4EFgQUfEKWrt5LSDv6kviejM9ti6lyN5UwCgYIKoZI 11 | zj0EAwMDaAAwZQIwe3lORlCEwkSHRhtFcP9Ymd70/aTSVaYgLXTWNLxBo1BfASdW 12 | tL4ndQavEi51mI38AjEAi/V3bNTIZargCyzuFJ0nN6T5U6VR5CmD1/iQMVtCnwr1 13 | /q4AaOeMSQ+2b1tbFfLn 14 | -----END CERTIFICATE----- 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/settings_activity.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 13 | 14 | 19 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -dontwarn com.google.errorprone.annotations.InlineMe 24 | -dontwarn com.google.errorprone.annotations.MustBeClosed -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | ## For more details on how to configure your build environment visit 2 | # http://www.gradle.org/docs/current/userguide/build_environment.html 3 | # 4 | # Specifies the JVM arguments used for the daemon process. 5 | # The setting is particularly useful for tweaking memory settings. 6 | # Default value: -Xmx1024m -XX:MaxPermSize=256m 7 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 8 | # 9 | # When configured, Gradle will run in incubating parallel mode. 10 | # This option should only be used with decoupled projects. More details, visit 11 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 12 | # org.gradle.parallel=true 13 | #Mon Oct 18 15:35:31 JST 2021 14 | kotlin.code.style=official 15 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options=-Xmx2048M 16 | android.useAndroidX=true 17 | android.enableJetifier=false 18 | android.enableR8.fullMode=true 19 | -------------------------------------------------------------------------------- /app/src/main/assets/legacy/patch-switchLanguage.js: -------------------------------------------------------------------------------- 1 | /* Deactivated as it is displayed by default due to an update to the DeepL server */ 2 | if (!$('#lang_switch').length) { 3 | $('html > head').append($('')); 4 | $(Android.getAssetsText('switchLanguage.html')).click(function () { 5 | this.classList.contains("switched") ? this.classList.remove("switched") : this.classList.add("switched"); 6 | window.location = window.location.href.split('#')[0] + 7 | '#' + document.getElementsByClassName('lmt__language_select lmt__language_select--target')[0].getAttribute('dl-selected-lang').split('-')[0] + 8 | '/' + document.getElementsByClassName('lmt__language_select lmt__language_select--source')[0].getAttribute('dl-selected-lang').split('-')[0] + 9 | '/' + encodeURI(document.getElementsByClassName('lmt__textarea lmt__target_textarea lmt__textarea_base_style')[0].value.replace("/", "\\/")); 10 | }).prependTo($('.lmt__language_container')[1]); 11 | } -------------------------------------------------------------------------------- /app/src/main/res/values-ja/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeepL翻訳 3 | 画像 4 | テキストをコピーしました 5 | DeepLサーバーへの接続に失敗しました 6 | 設定 7 | テーマの設定 8 | テーマ 9 | ライト 10 | ダーク 11 | バッテリーセーバーによる設定 12 | システムのデフォルト 13 | 選択メニューのオプション 14 | ポップアップモードを使用 15 | 選択メニューにDeepL翻訳ボタンを表示する 16 | アプリのオプション 17 | 終了時にCookieを自動的に削除する 18 | このアプリについて 19 | バージョン 20 | -------------------------------------------------------------------------------- /fastlane/Fastfile: -------------------------------------------------------------------------------- 1 | # This file contains the fastlane.tools configuration 2 | # You can find the documentation at https://docs.fastlane.tools 3 | # 4 | # For a list of all available actions, check out 5 | # 6 | # https://docs.fastlane.tools/actions 7 | # 8 | # For a list of all available plugins, check out 9 | # 10 | # https://docs.fastlane.tools/plugins/available-plugins 11 | # 12 | 13 | # Uncomment the line if you want fastlane to automatically update itself 14 | # update_fastlane 15 | 16 | default_platform(:android) 17 | 18 | platform :android do 19 | desc "Runs all the tests" 20 | lane :test do 21 | gradle(task: "test") 22 | end 23 | 24 | desc "Submit a new Beta Build to Crashlytics Beta" 25 | lane :beta do 26 | gradle(task: "clean assembleRelease") 27 | crashlytics 28 | 29 | # sh "your_script.sh" 30 | # You can also use other beta testing services here 31 | end 32 | 33 | desc "Deploy a new version to the Google Play" 34 | lane :deploy do 35 | gradle(task: "clean assembleRelease") 36 | upload_to_play_store 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/service/QSTileService.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.service 2 | 3 | import android.annotation.SuppressLint 4 | import android.app.PendingIntent 5 | import android.content.Intent 6 | import android.os.Build 7 | import android.service.quicksettings.TileService 8 | import androidx.annotation.RequiresApi 9 | import com.example.deeplviewer.activity.FloatingTextSelection 10 | 11 | @RequiresApi(Build.VERSION_CODES.N) 12 | class QSTileService : TileService() { 13 | override fun onClick() { 14 | val intent = Intent(applicationContext, FloatingTextSelection::class.java) 15 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) 16 | intent.putExtra(Intent.EXTRA_TEXT, "") 17 | 18 | @SuppressLint("StartActivityAndCollapseDeprecated") 19 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { 20 | startActivityAndCollapse( 21 | PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE) 22 | ) 23 | } else { 24 | startActivityAndCollapse(intent) 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Aoki Yuki 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/src/main/res/values-zh/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | DeepL 4 | 图片 5 | 文本已复制 6 | 哎呀,连接 DeepL 服务失败 7 | 设置 8 | 主题设置 9 | 主题 10 | 浅色 11 | 深色 12 | 由省电模式设置 13 | 系统默认 14 | 菜单选择 15 | 使用弹窗模式 16 | 在文本选择菜单中显示 DeepL 按钮 17 | 应用选项 18 | 退出应用时自动删除 cookie 19 | 关于此应用 20 | 版本 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_deepl_mini_logo.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values-tr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeepL 3 | Resim 4 | Metin kopyalandı 5 | Eyvah! DeepL sunucusuna bağlanılamadı 6 | Ayarlar 7 | Tema ayarları 8 | Tema 9 | Açık 10 | Koyu 11 | Pil tasarrufuna göre ayarla 12 | Sistem varsayılanı 13 | Seçim menüsü seçenekleri 14 | Açılır pencere modunu kullan 15 | Metin seçim menüsünde DeepL tuşunu göster 16 | Uygulama seçenekleri 17 | Uygulamadan çıkarken çerezleri otomatik olarak sil 18 | Bu uygulama hakkında 19 | Sürüm 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/baseline_settings_24.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/values-de/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeepL 3 | Bild 4 | Text wurde kopiert 5 | Hoppla, Verbindung zum DeepL-Server konnte nicht hergestellt werden 6 | Einstellungen 7 | Design-Einstellungen 8 | Design 9 | Hell 10 | Dunkel 11 | Durch Batteriesparmodus eingestellt 12 | Systemstandard 13 | Optionen des Auswahlmenüs 14 | Popup-Modus verwenden 15 | DeepL-Schaltfläche im Textauswahlmenü anzeigen 16 | App-Optionen 17 | Cookies beim Beenden der App automatisch löschen 18 | Über diese App 19 | Version 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values-ro/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeepL 3 | Imagine 4 | Textul a fost copiat 5 | Hopa, nu s-a putut conecta la serverul DeepL 6 | Setări 7 | Setări temă 8 | Temă 9 | Luminoasă 10 | Întunecată 11 | Setat de economisirea bateriei 12 | Implicit sistem 13 | Opțiuni meniu de selecție 14 | Utilizați modul pop-up 15 | Afișați butonul DeepL în meniul de selecție a textului 16 | Opțiuni aplicație 17 | Ștergeți automat cookie-urile la ieșirea din aplicație 18 | Despre această aplicație 19 | Versiune 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/values-ru/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeepL 3 | Изображение 4 | Текст скопирован в буфер обмена 5 | Не удалось подключиться к серверам DeepL 6 | Настройки 7 | Настройки темы 8 | Тема 9 | Светлая 10 | Тёмная 11 | Установленная режимом энергосбережения 12 | Как в системе 13 | Параметры меню выбора 14 | Использовать режим всплывающего окна 15 | Показывать кнопку DeepL в меню выбора текста 16 | Параметры приложения 17 | Автоматически удалять куки при выходе из приложения 18 | О программе 19 | Версия 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/res/layout/popup_layout.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/res/values-it/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeepL 3 | Immagine 4 | Il Testo è stato Copiato 5 | Oops, errore di connessione con il server DeepL 6 | Impostazioni 7 | Impostazioni tema 8 | Tema 9 | Chiaro 10 | Scuro 11 | Impostato da Risparmio Batteria 12 | Predefinito di Sistema 13 | Opzioni del menu di selezione 14 | Usa modalità pop-up 15 | Mostra il pulsante DeepL nel menù di selezione del testo 16 | Opzioni dell\'app 17 | Elimina automaticamente i cookie alla chiusura dell\'app 18 | Informazioni app 19 | Versione 20 | 21 | -------------------------------------------------------------------------------- /.github/workflows/ci_build.yaml: -------------------------------------------------------------------------------- 1 | name: APK Build 2 | on: 3 | push: 4 | branches: 5 | - master 6 | 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | - name: Checkout the code 15 | uses: actions/checkout@v4 16 | 17 | - name: Set up JDK 17 18 | uses: actions/setup-java@v4 19 | with: 20 | distribution: 'temurin' 21 | java-version: '17' 22 | 23 | - name: Set up Gradle 24 | uses: gradle/actions/setup-gradle@v4 25 | 26 | - name: Cache Gradle packages 27 | uses: actions/cache@v3 28 | with: 29 | path: | 30 | ~/.gradle/caches 31 | ~/.gradle/wrapper 32 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 33 | restore-keys: | 34 | ${{ runner.os }}-gradle- 35 | 36 | - name: Build the app 37 | run: | 38 | chmod +x gradlew 39 | ./gradlew build --stacktrace 40 | 41 | - name: Upload APK 42 | uses: actions/upload-artifact@v4 43 | with: 44 | name: debug-apk 45 | path: app/build/outputs/apk/debug/*.apk 46 | if-no-files-found: error 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/webview/WebAppInterface.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.webview 2 | 3 | import android.content.ClipData 4 | import android.content.ClipboardManager 5 | import android.content.Context 6 | import android.util.Base64 7 | import android.webkit.JavascriptInterface 8 | import android.widget.Toast 9 | import androidx.annotation.Keep 10 | import com.example.deeplviewer.R 11 | 12 | @Keep 13 | class WebAppInterface(private val context: Context) { 14 | @JavascriptInterface 15 | fun copyClipboard(text: String) { 16 | val clipboard: ClipboardManager = 17 | context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager 18 | val clip: ClipData = ClipData.newPlainText("translation_text", text) 19 | clipboard.setPrimaryClip(clip) 20 | Toast.makeText(context, context.getString(R.string.copy_clipboard), Toast.LENGTH_SHORT) 21 | .show() 22 | } 23 | 24 | @JavascriptInterface 25 | fun stringToBase64String(s: String): String { 26 | return Base64.encodeToString(s.toByteArray(), Base64.DEFAULT) 27 | } 28 | 29 | @JavascriptInterface 30 | fun getAssetsText(fileName: String): String { 31 | return context.assets.open(fileName).reader(Charsets.UTF_8).use { it.readText() } 32 | } 33 | } -------------------------------------------------------------------------------- /app/src/main/res/values-fr/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | DeepL 3 | Image 4 | Le texte a été copié 5 | Oups, échec de connexion au serveur de DeepL 6 | Paramètres 7 | Paramètres du thème 8 | Thème 9 | Clair 10 | Obscur 11 | Défini par l\'économiseur de batterie 12 | Identique au système 13 | Options de menu de sélection 14 | Utiliser le mode fenêtré 15 | Afficher le bouton DeepL dans le menu de sélection de texte 16 | Options de l\'application 17 | Supprimer automatiquement les cookies à la fermeture de l\'application 18 | À propos de cette app 19 | Version 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/helper/UrlHelper.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.helper 2 | 3 | import android.content.Context 4 | import android.net.Uri 5 | 6 | object UrlHelper { 7 | /** 8 | * Builds the start URL for the DeepL translator based on the provided parameters 9 | */ 10 | fun buildStartUrl( 11 | context: Context, 12 | originUrl: String = "https://www.deepl.com/", 13 | pageType: String = "translator", 14 | urlParam: String = "#en/en/" 15 | ): String { 16 | val configPrefs = context.getSharedPreferences("config", Context.MODE_PRIVATE) 17 | val buildPageType = configPrefs.getString("pageType", pageType) 18 | ?: pageType 19 | val buildUrlParam = configPrefs.getString( 20 | "urlParam", 21 | urlParam 22 | ) ?: urlParam 23 | 24 | return "$originUrl$buildPageType$buildUrlParam" 25 | } 26 | 27 | /** 28 | * Builds a URL for the WebView based on the provided start URL and text 29 | */ 30 | fun buildWebViewUrl(startUrl: String, text: String): String { 31 | if (text.isEmpty()) return startUrl 32 | 33 | val processedText = text.replace("/", "\\/") 34 | val encodedText = Uri.encode(processedText) 35 | return "$startUrl$encodedText" 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | DeepL 4 | Image 5 | Text has been Copied 6 | Oops, failed to connect to DeepL server 7 | Settings 8 | Theme settings 9 | Theme 10 | Light 11 | Dark 12 | Set by Battery Saver 13 | System default 14 | Selection menu options 15 | Use pop-up mode 16 | Show DeepL button in the text selection menu 17 | App options 18 | Automatically delete cookies when exiting the app 19 | About this app 20 | Version 21 | 22 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | #### [EN](https://github.com/sakusaku3939/DeepLAndroid#readme) | [JA](README_JA.md) | [RU](README_RU.md) | [DE](README_DE.md) | [FR](README_FR.md) | [IT](README_IT.md) | [TR](README_TR.md) | [ZH](README_ZH.md) | [CS](README_CS.md) 2 | # DeepL Android 3 | ![platform](https://img.shields.io/badge/platform-android-green) ![release](https://img.shields.io/github/v/release/sakusaku3939/DeepLAndroid.svg) ![license](https://img.shields.io/github/license/sakusaku3939/DeepLAndroid) ![downloads](https://img.shields.io/github/downloads/sakusaku3939/DeepLAndroid/total.svg) ![Screenshot Diff Check](https://github.com/sakusaku3939/DeepLAndroid/actions/workflows/screenshot_diff_check.yml/badge.svg) 4 | 5 | 这是一个非官方的 DeepL 翻译 Android 应用。 6 | 它使用了 WebView 技术,这是 Android 图形用户界面的一个组件,使其看起来像一个原生应用。 7 | 为了隐藏所有不必要的元素,只保留最必要的部分,使用了 JavaScript。 8 | 9 | [在 F-Droid 获取](https://f-droid.org/packages/com.example.deeplviewer) 12 | 13 | ![image](https://user-images.githubusercontent.com/53967490/89320092-fe2fdf00-d6bb-11ea-97d6-84fd66f73395.png) 14 | 15 | ## 功能 16 | - 通过 WebView 仅显示翻译屏幕。 17 | - 可切换至深色模式。 18 | - 从选择文本菜单启动 DeepL。 19 | - 长按应用图标以访问设置屏幕。 20 | 21 | ## 注意事项 22 | 此项目已使用 DeepL 翻译。如果有任何翻译错误,请随时发送拉取请求! 23 | 24 | ## 许可证 25 | [MIT](https://github.com/sakusaku3939/DeepLAndroid/blob/master/LICENSE) 26 | -------------------------------------------------------------------------------- /app/src/main/res/values-cs/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | DeepL 4 | Obrázek 5 | Text byl zkopírován 6 | Ups, nepodařilo se připojit k serveru DeepL 7 | Nastavení 8 | Nastavení motivu 9 | Motiv 10 | Světlý 11 | Tmavý 12 | Nastavení podle spořiče baterie 13 | Výchozí nastavení systému 14 | Možnosti nabídky výběru 15 | Použití režimu vyskakovacího okna 16 | Zobrazit tlačítko DeepL v nabídce výběru textu 17 | Možnosti aplikace 18 | Automatické mazání souborů cookie při ukončení aplikace 19 | O této aplikaci 20 | Verze 21 | 22 | -------------------------------------------------------------------------------- /README_JA.md: -------------------------------------------------------------------------------- 1 | #### [EN](https://github.com/sakusaku3939/DeepLAndroid#readme) | [JA](README_JA.md) | [RU](README_RU.md) | [DE](README_DE.md) | [FR](README_FR.md) | [IT](README_IT.md) | [TR](README_TR.md) | [ZH](README_ZH.md) | [CS](README_CS.md) 2 | # DeepL Android 3 | ![platform](https://img.shields.io/badge/platform-android-green) ![release](https://img.shields.io/github/v/release/sakusaku3939/DeepLAndroid.svg) ![license](https://img.shields.io/github/license/sakusaku3939/DeepLAndroid) ![downloads](https://img.shields.io/github/downloads/sakusaku3939/DeepLAndroid/total.svg) ![Screenshot Diff Check](https://github.com/sakusaku3939/DeepLAndroid/actions/workflows/screenshot_diff_check.yml/badge.svg) 4 | 5 | DeepL翻訳の非公式Androidアプリです。 6 | AndroidのGUIコンポーネントの「WebView」といわれる技術を使用してDeepL翻訳をネイティブアプリ風にしたものです。 7 | 必要な要素以外はJavaScriptを使用して隠しています。 8 | 9 | [Get it on F-Droid](https://f-droid.org/packages/com.example.deeplviewer) 12 | 13 | ![image](https://user-images.githubusercontent.com/53967490/89320092-fe2fdf00-d6bb-11ea-97d6-84fd66f73395.png) 14 | 15 | ## 機能一覧 16 | - 翻訳画面のみをWebViewで表示 17 | - ダークモードへの切り替え 18 | - 選択メニューからDeepLアプリを起動 19 | - アプリアイコンを長押しで設定画面に入る 20 | 21 | ## Notes 22 | このプロジェクトはDeepLを利用して翻訳されています。もし誤訳等がありましたら、お気軽にプルリクエストを送ってください! 23 | 24 | ## License 25 | [MIT](https://github.com/sakusaku3939/DeepLAndroid/blob/master/LICENSE) 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/config/WebViewConfig.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.config 2 | 3 | import android.annotation.SuppressLint 4 | import android.webkit.WebSettings 5 | import android.webkit.WebView 6 | 7 | object WebViewConfig { 8 | @SuppressLint("SetJavaScriptEnabled") 9 | fun applyBasicSettings(webView: WebView) { 10 | webView.settings.apply { 11 | javaScriptEnabled = true 12 | domStorageEnabled = true 13 | 14 | // cache settings 15 | cacheMode = WebSettings.LOAD_DEFAULT 16 | } 17 | } 18 | 19 | @SuppressLint("SetJavaScriptEnabled") 20 | fun applyOptimizedSettings(webView: WebView) { 21 | webView.settings.apply { 22 | // security settings 23 | allowFileAccess = false 24 | allowContentAccess = false 25 | setGeolocationEnabled(false) 26 | 27 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { 28 | // off screen pre-rasterization (API 23+) 29 | offscreenPreRaster = true 30 | } 31 | 32 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { 33 | // safe browsing (API 26+) 34 | safeBrowsingEnabled = true 35 | } 36 | 37 | // mixed content mode 38 | mixedContentMode = WebSettings.MIXED_CONTENT_COMPATIBILITY_MODE 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/network_err.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 17 | 18 | 27 | 28 | 36 | 37 | -------------------------------------------------------------------------------- /app/src/main/res/xml/root_preferences.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 23 | 24 | 25 | 26 | 30 | 31 | 32 | 33 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/App.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer 2 | 3 | import android.app.Application 4 | import android.content.Context 5 | import android.content.pm.ShortcutManager 6 | import android.os.Build 7 | import androidx.annotation.RequiresApi 8 | import androidx.appcompat.app.AppCompatDelegate 9 | 10 | class App : Application() { 11 | 12 | override fun onCreate() { 13 | super.onCreate() 14 | switchTheme() 15 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { 16 | removeOldAppShortcut() 17 | } 18 | } 19 | 20 | private fun switchTheme() { 21 | val config = getSharedPreferences("config", Context.MODE_PRIVATE) 22 | var darkThemeMode = 23 | config.getString( 24 | getString(R.string.key_dark_mode), 25 | AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM.toString() 26 | )!! 27 | 28 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && darkThemeMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM.toString()) { 29 | darkThemeMode = AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY.toString() 30 | } 31 | AppCompatDelegate.setDefaultNightMode(darkThemeMode.toInt()) 32 | } 33 | 34 | @RequiresApi(Build.VERSION_CODES.N_MR1) 35 | private fun removeOldAppShortcut() { 36 | val shortcutManager = getSystemService(ShortcutManager::class.java) 37 | shortcutManager.removeDynamicShortcuts( 38 | listOf( 39 | "theme"//version 5.0(17) Used to launch this app with darkTheme or lightTheme. 40 | ) 41 | ) 42 | } 43 | } -------------------------------------------------------------------------------- /README_RU.md: -------------------------------------------------------------------------------- 1 | #### [EN](https://github.com/sakusaku3939/DeepLAndroid#readme) | [JA](README_JA.md) | [RU](README_RU.md) | [DE](README_DE.md) | [FR](README_FR.md) | [IT](README_IT.md) | [TR](README_TR.md) | [ZH](README_ZH.md) | [CS](README_CS.md) 2 | # DeepL Android 3 | ![platform](https://img.shields.io/badge/platform-android-green) ![release](https://img.shields.io/github/v/release/sakusaku3939/DeepLAndroid.svg) ![license](https://img.shields.io/github/license/sakusaku3939/DeepLAndroid) ![downloads](https://img.shields.io/github/downloads/sakusaku3939/DeepLAndroid/total.svg) ![Screenshot Diff Check](https://github.com/sakusaku3939/DeepLAndroid/actions/workflows/screenshot_diff_check.yml/badge.svg) 4 | 5 | Это неофициальное Android-приложение для DeepL переводчика. 6 | Оно использует технологию WebView, компонент интерфейса Android, чтобы выглядеть как нативное приложение. 7 | Чтобы скрыть все ненужные элементы, используется JavaScript. 8 | 9 | [Get it on F-Droid](https://f-droid.org/packages/com.example.deeplviewer) 12 | 13 | ![image](https://user-images.githubusercontent.com/53967490/89320092-fe2fdf00-d6bb-11ea-97d6-84fd66f73395.png) 14 | 15 | ## Функции 16 | - Показывать только экран перевода через WebView. 17 | - Переключение в темный режим. 18 | - Запустить DeepL из меню выделенного текста. 19 | - Откройте экран настроек, нажав и удерживая значок приложения. 20 | 21 | ## Примечание 22 | Этот проект был переведен при помощи DeepL. Если вы нашли ошибку, можете отправить pull request. 23 | 24 | ## Лицензия 25 | [MIT](https://github.com/sakusaku3939/DeepLAndroid/blob/master/LICENSE) 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #### [EN](https://github.com/sakusaku3939/DeepLAndroid#readme) | [JA](README_JA.md) | [RU](README_RU.md) | [DE](README_DE.md) | [FR](README_FR.md) | [IT](README_IT.md) | [TR](README_TR.md) | [ZH](README_ZH.md) | [CS](README_CS.md) 2 | # DeepL Android 3 | ![platform](https://img.shields.io/badge/platform-android-green) ![release](https://img.shields.io/github/v/release/sakusaku3939/DeepLAndroid.svg) ![license](https://img.shields.io/github/license/sakusaku3939/DeepLAndroid) ![downloads](https://img.shields.io/github/downloads/sakusaku3939/DeepLAndroid/total.svg) ![Screenshot Diff Check](https://github.com/sakusaku3939/DeepLAndroid/actions/workflows/screenshot_diff_check.yml/badge.svg) 4 | 5 | This is an unofficial Android app for DeepL translation. 6 | It uses a technology called WebView, an Android GUI component, to make it look like a native app. 7 | JavaScript is used to hide all but the most necessary elements. 8 | 9 | [Get it on F-Droid](https://f-droid.org/packages/com.example.deeplviewer) 12 | 13 | ![image](https://user-images.githubusercontent.com/53967490/89320092-fe2fdf00-d6bb-11ea-97d6-84fd66f73395.png) 14 | 15 | ## Features 16 | - Display only the translation screen by WebView. 17 | - Switching to dark mode. 18 | - Launch DeepL from floating text selection action. 19 | - Long press on the application icon to access the settings screen. 20 | 21 | ## Notes 22 | This project has been translated using DeepL. If there are any mistranslations, please feel free to send a pull request! 23 | 24 | ## License 25 | [MIT](https://github.com/sakusaku3939/DeepLAndroid/blob/master/LICENSE) 26 | -------------------------------------------------------------------------------- /README_TR.md: -------------------------------------------------------------------------------- 1 | #### [EN](https://github.com/sakusaku3939/DeepLAndroid#readme) | [JA](README_JA.md) | [RU](README_RU.md) | [DE](README_DE.md) | [FR](README_FR.md) | [IT](README_IT.md) | [TR](README_TR.md) | [ZH](README_ZH.md) | [CS](README_CS.md) 2 | # DeepL Android 3 | ![platform](https://img.shields.io/badge/platform-android-green) ![release](https://img.shields.io/github/v/release/sakusaku3939/DeepLAndroid.svg) ![license](https://img.shields.io/github/license/sakusaku3939/DeepLAndroid) ![downloads](https://img.shields.io/github/downloads/sakusaku3939/DeepLAndroid/total.svg) ![Screenshot Diff Check](https://github.com/sakusaku3939/DeepLAndroid/actions/workflows/screenshot_diff_check.yml/badge.svg) 4 | 5 | DeepL çeviri için resmi olmayan bir Android uygulamasıdır. 6 | Yerel bir uygulama gibi görünmesini sağlamak için bir Android Arayüz bileşeni olan WebView adlı bir teknoloji kullanır. 7 | JavaScript gereksiz öğeleri gizlemek için kullanılır. 8 | 9 | [Get it on F-Droid](https://f-droid.org/packages/com.example.deeplviewer) 12 | 13 | ![image](https://user-images.githubusercontent.com/53967490/89320092-fe2fdf00-d6bb-11ea-97d6-84fd66f73395.png) 14 | 15 | ## Özellikler 16 | - WebView ile yalnızca çeviri ekranını görüntüleyin. 17 | - Karanlık moda geçme. 18 | - Kayan metin seçimi eyleminden DeepL'i başlatın. 19 | - Uygulama simgesine basılı tutarak ayarlar ekranına erişin. 20 | 21 | ## Notlar 22 | Bu proje DeepL kullanılarak çevrilmiştir. Herhangi bir yanlış çeviri varsa, lütfen bir çekme isteği göndermekten çekinmeyin! 23 | 24 | ## Lisans 25 | [MIT](https://github.com/sakusaku3939/DeepLAndroid/blob/master/LICENSE) 26 | -------------------------------------------------------------------------------- /README_CS.md: -------------------------------------------------------------------------------- 1 | #### [EN](https://github.com/sakusaku3939/DeepLAndroid#readme) | [JA](README_JA.md) | [RU](README_RU.md) | [DE](README_DE.md) | [FR](README_FR.md) | [IT](README_IT.md) | [TR](README_TR.md) | [ZH](README_ZH.md) | [CS](README_CS.md) 2 | # DeepL Android 3 | ![platform](https://img.shields.io/badge/platform-android-green) ![release](https://img.shields.io/github/v/release/sakusaku3939/DeepLAndroid.svg) ![license](https://img.shields.io/github/license/sakusaku3939/DeepLAndroid) ![downloads](https://img.shields.io/github/downloads/sakusaku3939/DeepLAndroid/total.svg) ![Screenshot Diff Check](https://github.com/sakusaku3939/DeepLAndroid/actions/workflows/screenshot_diff_check.yml/badge.svg) 4 | 5 | Toto je neoficiální Android aplikace pro DeepL překlady. 6 | Využívá technologii WebView, komponentu grafického uživatelského rozhraní systému Android, aby vypadala jako nativní aplikace. 7 | JavaScript se používá ke skrytí všech prvků kromě těch nejnutnějších 8 | 9 | [Get it on F-Droid](https://f-droid.org/packages/com.example.deeplviewer) 12 | 13 | ![image](https://user-images.githubusercontent.com/53967490/89320092-fe2fdf00-d6bb-11ea-97d6-84fd66f73395.png) 14 | 15 | ## Funkce 16 | - Zobrazení pouze obrazovky překladu pomocí WebView. 17 | - Přepínání do tmavého režimu. 18 | - Spuštění DeepL z akce výběru v plovoucím textu. 19 | - Dlouhý stisk ikony aplikace umožňuje otevřít obrazovku s nastavením. 20 | 21 | ## Poznámky 22 | Tento projekt byl přeložen pomocí DeepL. Pokud se v něm vyskytnou nějaké chyby v překladu, neváhejte poslat pull request! 23 | 24 | ## Licence 25 | [MIT](https://github.com/sakusaku3939/DeepLAndroid/blob/master/LICENSE) 26 | -------------------------------------------------------------------------------- /README_IT.md: -------------------------------------------------------------------------------- 1 | #### [EN](https://github.com/sakusaku3939/DeepLAndroid#readme) | [JA](README_JA.md) | [RU](README_RU.md) | [DE](README_DE.md) | [FR](README_FR.md) | [IT](README_IT.md) | [TR](README_TR.md) | [ZH](README_ZH.md) | [CS](README_CS.md) 2 | # DeepL Android 3 | ![platform](https://img.shields.io/badge/platform-android-green) ![release](https://img.shields.io/github/v/release/sakusaku3939/DeepLAndroid.svg) ![license](https://img.shields.io/github/license/sakusaku3939/DeepLAndroid) ![downloads](https://img.shields.io/github/downloads/sakusaku3939/DeepLAndroid/total.svg) ![Screenshot Diff Check](https://github.com/sakusaku3939/DeepLAndroid/actions/workflows/screenshot_diff_check.yml/badge.svg) 4 | 5 | Questa è un'applicazione Android non ufficiale per traduzioni DeepL. 6 | Usa una tecnologia chiamata WebView, una componente GUI di Android, per farla sembrare come un'app nativa. 7 | JavaScript è usato per nascondere tutto tranne gli elementi più necessari. 8 | 9 | [Get it on F-Droid](https://f-droid.org/packages/com.example.deeplviewer) 12 | 13 | ![image](https://user-images.githubusercontent.com/53967490/89320092-fe2fdf00-d6bb-11ea-97d6-84fd66f73395.png) 14 | 15 | ## Funzionalità 16 | - Mostra solo la schermata di traduzione tramite WebView. 17 | - Cambiare in modalità scura. 18 | - Lancia DeepL dall'azione di selezione del testo. 19 | - Accedere alla schermata delle impostazioni tenendo premuta l'icona dell'applicazione. 20 | 21 | ## Note 22 | Questo progetto è stato tradotto usando DeepL. Se ci sono errori di traduzione, sentitevi liberi di inviare una pull request! 23 | 24 | ## Licenza 25 | [MIT](https://github.com/sakusaku3939/DeepLAndroid/blob/master/LICENSE) 26 | -------------------------------------------------------------------------------- /README_DE.md: -------------------------------------------------------------------------------- 1 | #### [EN](https://github.com/sakusaku3939/DeepLAndroid#readme) | [JA](README_JA.md) | [RU](README_RU.md) | [DE](README_DE.md) | [FR](README_FR.md) | [IT](README_IT.md) | [TR](README_TR.md) | [ZH](README_ZH.md) | [CS](README_CS.md) 2 | # DeepL Android 3 | ![platform](https://img.shields.io/badge/platform-android-green) ![release](https://img.shields.io/github/v/release/sakusaku3939/DeepLAndroid.svg) ![license](https://img.shields.io/github/license/sakusaku3939/DeepLAndroid) ![downloads](https://img.shields.io/github/downloads/sakusaku3939/DeepLAndroid/total.svg) ![Screenshot Diff Check](https://github.com/sakusaku3939/DeepLAndroid/actions/workflows/screenshot_diff_check.yml/badge.svg) 4 | 5 | Dies ist eine inoffizielle Android-App für den DeepL-Übersetzer. 6 | Es verwendet eine Technologie namens WebView, eine Android-GUI-Komponente, um es wie eine native App aussehen zu lassen. 7 | JavaScript wird verwendet, um alle bis auf die notwendigsten Elemente auszublenden. 8 | 9 | [Get it on F-Droid](https://f-droid.org/packages/com.example.deeplviewer) 12 | 13 | ![image](https://user-images.githubusercontent.com/53967490/89320092-fe2fdf00-d6bb-11ea-97d6-84fd66f73395.png) 14 | 15 | ## Funktionen 16 | 17 | - Den Übersetzungsbildschirm im WebView anzeigen lassen. 18 | - In den Dunkelmodus wechseln. 19 | - Starten Sie DeepL über die schwebende Textauswahlaktion. 20 | - Rufen Sie den Einstellungsbildschirm auf, indem Sie das Anwendungssymbol drücken und gedrückt halten. 21 | 22 | ## Anmerkung 23 | Dieses Projekt wurde mit DeepL übersetzt. Sollte es irgendwelche Übersetzungsfehler geben, erstellen Sie gerne einen Pull Request! 24 | 25 | ## Lizenz 26 | [MIT](https://github.com/sakusaku3939/DeepLAndroid/blob/master/LICENSE) 27 | -------------------------------------------------------------------------------- /README_FR.md: -------------------------------------------------------------------------------- 1 | #### [EN](https://github.com/sakusaku3939/DeepLAndroid#readme) | [JA](README_JA.md) | [RU](README_RU.md) | [DE](README_DE.md) | [FR](README_FR.md) | [IT](README_IT.md) | [TR](README_TR.md) | [ZH](README_ZH.md) | [CS](README_CS.md) 2 | # DeepL Android 3 | ![platform](https://img.shields.io/badge/platform-android-green) ![release](https://img.shields.io/github/v/release/sakusaku3939/DeepLAndroid.svg) ![license](https://img.shields.io/github/license/sakusaku3939/DeepLAndroid) ![downloads](https://img.shields.io/github/downloads/sakusaku3939/DeepLAndroid/total.svg) ![Screenshot Diff Check](https://github.com/sakusaku3939/DeepLAndroid/actions/workflows/screenshot_diff_check.yml/badge.svg) 4 | 5 | Il s'agit d'une application Android non officielle pour le traducteur DeepL. 6 | Elle utilise une technologie appelée WebView, un composant d'interface graphique d'Android, afin de la faire ressembler à une application native. 7 | JavaScript est utilisé pour masquer tous les éléments à l'exception des plus nécessaires. 8 | 9 | 10 | [Get it on F-Droid](https://f-droid.org/packages/com.example.deeplviewer) 13 | 14 | ![image](https://user-images.githubusercontent.com/53967490/89320092-fe2fdf00-d6bb-11ea-97d6-84fd66f73395.png) 15 | 16 | ## Fonctionnalités: 17 | 18 | - Afficher uniquement l'écran de traduction via WebView 19 | - Passage en mode sombre 20 | - Lancer DeepL par une action du menu flottant de sélection de texte 21 | - Accédez à l'écran des paramètres en appuyant sur l'icône de l'application et en la maintenant enfoncée 22 | 23 | ## Notes 24 | Ce projet a été traduit à l'aide de DeepL. 25 | S'il y a des erreurs de traduction, n'hésitez pas à envoyer une demande de modification! 26 | 27 | ## License 28 | [MIT](https://github.com/sakusaku3939/DeepLAndroid/blob/master/LICENSE) 29 | -------------------------------------------------------------------------------- /app/src/main/assets/DeepL_Logo_lightBlue_v2.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 9 | 12 | 15 | 18 | 20 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/assets/hide-elements.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | const HIDE_SELECTORS = ` 3 | header, 4 | nav[class^="md"], 5 | h2#translation-modes-heading + div > div:first-child, 6 | div[data-testid="translator"] > div:not(:first-child), 7 | footer, 8 | div[data-testid="chrome-extension-toast"], 9 | div[data-testid="firefox-extension-toast"], 10 | div[data-testid="write-banner"], 11 | div[id="cookieBanner"], 12 | div[aria-labelledby="app-stores-banner-description"], 13 | aside 14 | `; 15 | 16 | function injectCSS(rules) { 17 | const style = document.createElement('style'); 18 | style.innerHTML = rules; 19 | document.head.appendChild(style); 20 | } 21 | 22 | /* wait for the Head element to be available */ 23 | function waitForHead(callback) { 24 | if (document.head) { 25 | callback(); 26 | return; 27 | } 28 | 29 | const observer = new MutationObserver(function (mutations) { 30 | if (document.head) { 31 | observer.disconnect(); 32 | callback(); 33 | } 34 | }); 35 | 36 | observer.observe(document, { 37 | childList: true, 38 | subtree: true 39 | }); 40 | } 41 | 42 | /* initially hide elements with `visibility: hidden` */ 43 | window.hideElementsInitially = function () { 44 | waitForHead(function () { 45 | injectCSS(HIDE_SELECTORS + ` { visibility: hidden !important; }`); 46 | }); 47 | }; 48 | 49 | /* finally hide elements with `display: none` */ 50 | window.hideElementsFinal = function () { 51 | injectCSS(` 52 | * { -webkit-tap-highlight-color: rgba(0, 0, 0,.1); } 53 | ${HIDE_SELECTORS} { 54 | display: none !important; 55 | height: 0; 56 | width: 0; 57 | overflow: hidden; 58 | } 59 | `); 60 | }; 61 | })(); -------------------------------------------------------------------------------- /app/src/androidTest/java/com/example/deeplviewer/WebViewScreenshotTest.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer 2 | 3 | import android.os.SystemClock 4 | import android.webkit.WebView 5 | import androidx.test.ext.junit.rules.ActivityScenarioRule 6 | import androidx.test.ext.junit.runners.AndroidJUnit4 7 | import com.example.deeplviewer.activity.MainActivity 8 | import com.example.deeplviewer.webview.MyWebViewClient 9 | import com.karumi.shot.ScreenshotTest 10 | import org.junit.Rule 11 | import org.junit.Test 12 | import org.junit.runner.RunWith 13 | import java.util.concurrent.CountDownLatch 14 | import java.util.concurrent.TimeUnit 15 | 16 | @RunWith(AndroidJUnit4::class) 17 | class WebViewScreenshotTest : ScreenshotTest { 18 | 19 | @get:Rule 20 | val activityRule = ActivityScenarioRule(MainActivity::class.java) 21 | 22 | @Test 23 | fun captureWebViewTopScreen() { 24 | waitForWebViewLoad() 25 | 26 | activityRule.scenario.onActivity { activity -> 27 | val webView = activity.findViewById(R.id.webview) 28 | compareScreenshot(webView, name = "webview_top_screen") 29 | } 30 | } 31 | 32 | private fun waitForWebViewLoad() { 33 | val latch = CountDownLatch(1) 34 | var webViewClient: MyWebViewClient? = null 35 | 36 | activityRule.scenario.onActivity { activity -> 37 | webViewClient = activity.testWebViewClient 38 | webViewClient?.loadFinishedListener = { 39 | latch.countDown() 40 | } 41 | } 42 | 43 | checkNotNull(webViewClient) { "WebViewClient is not available" } 44 | 45 | // Wait for load completion (max 30 seconds) 46 | val loadCompleted = latch.await(30, TimeUnit.SECONDS) 47 | 48 | if (!loadCompleted) { 49 | throw AssertionError("WebView load timeout: failed to complete within 30 seconds") 50 | } 51 | 52 | // Wait for rendering to complete (using SystemClock for instrumentation tests) 53 | SystemClock.sleep(8000) 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/res/raw/isrg_root_x1.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIFazCCA1OgAwIBAgIRAIIQz7DSQONZRGPgu2OCiwAwDQYJKoZIhvcNAQELBQAw 3 | TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh 4 | cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTEwNDM4 5 | WhcNMzUwNjA0MTEwNDM4WjBPMQswCQYDVQQGEwJVUzEpMCcGA1UEChMgSW50ZXJu 6 | ZXQgU2VjdXJpdHkgUmVzZWFyY2ggR3JvdXAxFTATBgNVBAMTDElTUkcgUm9vdCBY 7 | MTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAK3oJHP0FDfzm54rVygc 8 | h77ct984kIxuPOZXoHj3dcKi/vVqbvYATyjb3miGbESTtrFj/RQSa78f0uoxmyF+ 9 | 0TM8ukj13Xnfs7j/EvEhmkvBioZxaUpmZmyPfjxwv60pIgbz5MDmgK7iS4+3mX6U 10 | A5/TR5d8mUgjU+g4rk8Kb4Mu0UlXjIB0ttov0DiNewNwIRt18jA8+o+u3dpjq+sW 11 | T8KOEUt+zwvo/7V3LvSye0rgTBIlDHCNAymg4VMk7BPZ7hm/ELNKjD+Jo2FR3qyH 12 | B5T0Y3HsLuJvW5iB4YlcNHlsdu87kGJ55tukmi8mxdAQ4Q7e2RCOFvu396j3x+UC 13 | B5iPNgiV5+I3lg02dZ77DnKxHZu8A/lJBdiB3QW0KtZB6awBdpUKD9jf1b0SHzUv 14 | KBds0pjBqAlkd25HN7rOrFleaJ1/ctaJxQZBKT5ZPt0m9STJEadao0xAH0ahmbWn 15 | OlFuhjuefXKnEgV4We0+UXgVCwOPjdAvBbI+e0ocS3MFEvzG6uBQE3xDk3SzynTn 16 | jh8BCNAw1FtxNrQHusEwMFxIt4I7mKZ9YIqioymCzLq9gwQbooMDQaHWBfEbwrbw 17 | qHyGO0aoSCqI3Haadr8faqU9GY/rOPNk3sgrDQoo//fb4hVC1CLQJ13hef4Y53CI 18 | rU7m2Ys6xt0nUW7/vGT1M0NPAgMBAAGjQjBAMA4GA1UdDwEB/wQEAwIBBjAPBgNV 19 | HRMBAf8EBTADAQH/MB0GA1UdDgQWBBR5tFnme7bl5AFzgAiIyBpY9umbbjANBgkq 20 | hkiG9w0BAQsFAAOCAgEAVR9YqbyyqFDQDLHYGmkgJykIrGF1XIpu+ILlaS/V9lZL 21 | ubhzEFnTIZd+50xx+7LSYK05qAvqFyFWhfFQDlnrzuBZ6brJFe+GnY+EgPbk6ZGQ 22 | 3BebYhtF8GaV0nxvwuo77x/Py9auJ/GpsMiu/X1+mvoiBOv/2X/qkSsisRcOj/KK 23 | NFtY2PwByVS5uCbMiogziUwthDyC3+6WVwW6LLv3xLfHTjuCvjHIInNzktHCgKQ5 24 | ORAzI4JMPJ+GslWYHb4phowim57iaztXOoJwTdwJx4nLCgdNbOhdjsnvzqvHu7Ur 25 | TkXWStAmzOVyyghqpZXjFaH3pO3JLF+l+/+sKAIuvtd7u+Nxe5AW0wdeRlN8NwdC 26 | jNPElpzVmbUq4JUagEiuTDkHzsxHpFKVK7q4+63SM1N95R1NbdWhscdCb+ZAJzVc 27 | oyi3B43njTOQ5yOf+1CceWxG1bQVs5ZufpsMljq4Ui0/1lvh+wjChP4kqKOJ2qxq 28 | 4RgqsahDYVvTH9w7jXbyLeiNdd8XM2w9U/t7y0Ff/9yi0GE44Za4rF2LN9d11TPA 29 | mRGunUHBcnWEvgJBQl9nJEiU0Zsnvgc/ubhPgXRR4Xq37Z0j4r7g1SgEEzwxA57d 30 | emyPxgcYxn/eR44/KJ4EBs+lVDR3veyJm+kXQ99b21/+jh5Xos1AnX5iItreGCc= 31 | -----END CERTIFICATE----- 32 | -------------------------------------------------------------------------------- /app/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 11 | 12 | 21 | 22 | 25 | 26 | 29 | 30 | 33 | 34 | 37 | 38 | 41 | 42 | 46 | 47 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | agp = "8.10.1" 3 | kotlin = "2.1.21" 4 | 5 | androidx-core-ktx = "1.16.0" 6 | androidx-appcompat = "1.7.0" 7 | androidx-webkit = "1.13.0" 8 | androidx-preference = "1.2.1" 9 | androidx-lifecycle = "2.9.0" 10 | error-prone = "2.30.0" 11 | material = "1.12.0" 12 | shimmer = "0.5.0" 13 | junit = "4.13.2" 14 | androidx-test-ext-junit = "1.2.1" 15 | androidx-test-espresso = "3.6.1" 16 | androidx-test-runner = "1.6.2" 17 | androidx-test-rules = "1.6.1" 18 | shot="6.1.0" 19 | 20 | [libraries] 21 | kotlin-stdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" } 22 | androidx-core-ktx = { module = "androidx.core:core-ktx", version.ref = "androidx-core-ktx" } 23 | androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } 24 | androidx-webkit = { module = "androidx.webkit:webkit", version.ref = "androidx-webkit" } 25 | androidx-preference-ktx = { module = "androidx.preference:preference-ktx", version.ref = "androidx-preference" } 26 | androidx-lifecycle-viewmodel-ktx = { module = "androidx.lifecycle:lifecycle-viewmodel-ktx", version.ref = "androidx-lifecycle" } 27 | error-prone-annotations = { module = "com.google.errorprone:error_prone_annotations", version.ref = "error-prone" } 28 | material = { module = "com.google.android.material:material", version.ref = "material" } 29 | shimmer = { module = "com.facebook.shimmer:shimmer", version.ref = "shimmer" } 30 | junit = { module = "junit:junit", version.ref = "junit" } 31 | androidx-test-ext-junit = { module = "androidx.test.ext:junit", version.ref = "androidx-test-ext-junit" } 32 | androidx-test-espresso-core = { module = "androidx.test.espresso:espresso-core", version.ref = "androidx-test-espresso" } 33 | androidx-test-runner = { module = "androidx.test:runner", version.ref = "androidx-test-runner" } 34 | androidx-test-rules = { module = "androidx.test:rules", version.ref = "androidx-test-rules" } 35 | shot-android = { module = "com.karumi:shot-android", version.ref = "shot" } 36 | shot-classpath = { module = "com.karumi:shot", version.ref = "shot" } 37 | 38 | [plugins] 39 | android-application = { id = "com.android.application", version.ref = "agp" } 40 | kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } 41 | shot = { id="shot", version.ref="shot" } -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/helper/CookieManagerHelper.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.helper 2 | 3 | import android.content.Context 4 | import android.util.Log 5 | import android.webkit.CookieManager 6 | import android.webkit.WebView 7 | import com.example.deeplviewer.R 8 | import java.net.URLEncoder 9 | import androidx.core.content.edit 10 | 11 | class CookieManagerHelper { 12 | companion object { 13 | private fun getCookieManager(): CookieManager { 14 | return CookieManager.getInstance() 15 | } 16 | } 17 | 18 | fun addPrivacyCookie() { 19 | val privacyValue = "{\"v\":2,\"t\":${System.currentTimeMillis().div(1000)},\"m\":\"STRICT\",\"consent\":[\"NECESSARY\"]}" 20 | 21 | getCookieManager().setCookie( 22 | ".deepl.com", 23 | "privacySettings=${URLEncoder.encode(privacyValue, Charsets.UTF_8.name())}" 24 | ) 25 | } 26 | 27 | fun saveCookies(context: Context, webView: WebView) { 28 | val cookieManager = getCookieManager() 29 | val isCookieAutoDeleted = context.getSharedPreferences("config", Context.MODE_PRIVATE) 30 | .getBoolean(context.getString(R.string.key_auto_delete_cookies), false) 31 | 32 | if (isCookieAutoDeleted) { 33 | clearCookies() 34 | } else { 35 | cookieManager.acceptCookie() 36 | cookieManager.setAcceptThirdPartyCookies(webView, true) 37 | cookieManager.flush() 38 | } 39 | } 40 | 41 | 42 | // Disable cookie values in SharedPreferences according to the bug fix for cookie expiration in v8.5 43 | fun migrateCookie(context: Context) { 44 | val sharedPreferences = context.getSharedPreferences("DeepLCookies", Context.MODE_PRIVATE) 45 | val savedCookie = sharedPreferences.getString("cookie", null) 46 | 47 | if (savedCookie != null) { 48 | clearCookies() 49 | sharedPreferences.edit { clear() } 50 | } 51 | } 52 | 53 | private fun clearCookies() { 54 | getCookieManager().removeAllCookies { success -> 55 | if (success) { 56 | Log.d("CookieManagerHelper", "Cookies successfully removed.") 57 | } else { 58 | Log.e("CookieManagerHelper", "Failed to remove cookies.") 59 | } 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 20 | 21 | 25 | 26 | 35 | 36 | 40 | 41 | 51 | 52 | 53 | 54 | 55 | 56 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | alias(libs.plugins.android.application) 3 | alias(libs.plugins.kotlin.android) 4 | alias(libs.plugins.shot) 5 | } 6 | 7 | android { 8 | namespace = "com.example.deeplviewer" 9 | compileSdk = 36 10 | 11 | defaultConfig { 12 | applicationId = "com.example.deeplviewer" 13 | minSdk = 21 14 | targetSdk = 36 15 | versionCode = 46 16 | versionName = "9.2.1" 17 | 18 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner" 19 | testInstrumentationRunner = "com.karumi.shot.ShotTestRunner" 20 | 21 | vectorDrawables { 22 | useSupportLibrary = true 23 | } 24 | } 25 | 26 | buildTypes { 27 | debug { 28 | isMinifyEnabled = false 29 | isShrinkResources = false 30 | proguardFiles( 31 | getDefaultProguardFile("proguard-android-optimize.txt"), 32 | "proguard-rules.pro" 33 | ) 34 | applicationIdSuffix = ".debug" 35 | } 36 | release { 37 | isMinifyEnabled = true 38 | isShrinkResources = true 39 | proguardFiles( 40 | getDefaultProguardFile("proguard-android-optimize.txt"), 41 | "proguard-rules.pro" 42 | ) 43 | } 44 | } 45 | 46 | buildFeatures { 47 | buildConfig = true 48 | viewBinding = true 49 | } 50 | 51 | compileOptions { 52 | sourceCompatibility = JavaVersion.VERSION_1_8 53 | targetCompatibility = JavaVersion.VERSION_1_8 54 | } 55 | 56 | kotlinOptions { 57 | jvmTarget = "1.8" 58 | } 59 | 60 | lint { 61 | disable.add("MissingTranslation") 62 | } 63 | } 64 | 65 | shot { 66 | applicationId = "com.example.deeplviewer" 67 | tolerance = 0.2 68 | } 69 | 70 | dependencies { 71 | implementation(fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))) 72 | implementation(libs.kotlin.stdlib) 73 | implementation(libs.androidx.core.ktx) 74 | implementation(libs.androidx.appcompat) 75 | implementation(libs.androidx.webkit) 76 | implementation(libs.androidx.preference.ktx) 77 | implementation(libs.androidx.lifecycle.viewmodel.ktx) 78 | implementation(libs.error.prone.annotations) 79 | implementation(libs.material) 80 | implementation(libs.shimmer) 81 | 82 | testImplementation(libs.junit) 83 | androidTestImplementation(libs.androidx.test.ext.junit) 84 | androidTestImplementation(libs.androidx.test.espresso.core) 85 | androidTestImplementation(libs.androidx.test.runner) 86 | androidTestImplementation(libs.androidx.test.rules) 87 | androidTestImplementation(libs.shot.android) 88 | testImplementation(kotlin("test")) 89 | } -------------------------------------------------------------------------------- /app/src/main/assets/DeepL_Text_light.svg: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /.github/workflows/capture_webview_screenshots.yml: -------------------------------------------------------------------------------- 1 | name: Capture WebView Screenshots 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | permissions: 7 | contents: write 8 | 9 | jobs: 10 | generate-screenshots: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v4 16 | with: 17 | persist-credentials: true 18 | 19 | - name: Set up JDK 17 20 | uses: actions/setup-java@v4 21 | with: 22 | java-version: '17' 23 | distribution: 'temurin' 24 | 25 | - name: Setup Android SDK 26 | uses: android-actions/setup-android@v3 27 | 28 | - name: Accept Android SDK licenses 29 | run: yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses 30 | 31 | - name: Cache Gradle dependencies 32 | uses: actions/cache@v3 33 | with: 34 | path: | 35 | ~/.gradle/caches 36 | ~/.gradle/wrapper 37 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 38 | restore-keys: | 39 | ${{ runner.os }}-gradle- 40 | 41 | - name: Install Android SDK components 42 | run: | 43 | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0" 44 | 45 | - name: Enable KVM group perms 46 | run: | 47 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 48 | sudo udevadm control --reload-rules 49 | sudo udevadm trigger --name-match=kvm 50 | 51 | - name: AVD cache 52 | uses: actions/cache@v3 53 | id: avd-cache 54 | with: 55 | path: | 56 | ~/.android/avd/* 57 | ~/.android/adb* 58 | key: avd-35-x86_64 59 | 60 | - name: Create AVD and generate snapshot for caching 61 | if: steps.avd-cache.outputs.cache-hit != 'true' 62 | uses: reactivecircus/android-emulator-runner@v2 63 | with: 64 | api-level: 35 65 | arch: x86_64 66 | target: google_apis 67 | force-avd-creation: false 68 | emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none 69 | disable-animations: false 70 | script: echo "Generated AVD snapshot for caching." 71 | 72 | - name: Generate and record screenshots 73 | uses: reactivecircus/android-emulator-runner@v2 74 | with: 75 | api-level: 35 76 | arch: x86_64 77 | target: google_apis 78 | force-avd-creation: false 79 | emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none 80 | disable-animations: false 81 | script: | 82 | ./gradlew executeScreenshotTests -Precord 83 | 84 | - name: Commit and push new screenshots 85 | run: | 86 | git config --local user.email "action@github.com" 87 | git config --local user.name "GitHub Action" 88 | git add app/screenshots/ 89 | git add **/screenshots/ 90 | if git diff --staged --quiet; then 91 | echo "No screenshot changes to commit" 92 | else 93 | git commit -m "Update screenshots" 94 | git push 95 | fi 96 | 97 | - name: Upload screenshots 98 | uses: actions/upload-artifact@v4 99 | if: always() 100 | with: 101 | name: screenshots-${{ github.run_id }} 102 | path: | 103 | app/screenshots/ 104 | **/screenshots/ 105 | retention-days: 30 -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 16 | 17 | 20 | 21 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 52 | 53 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 85 | 86 | 93 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/activity/SettingsActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.activity 2 | 3 | import android.content.ComponentName 4 | import android.content.Intent 5 | import android.content.pm.PackageManager 6 | import android.os.Build 7 | import android.os.Bundle 8 | import android.view.MenuItem 9 | import androidx.appcompat.app.AppCompatActivity 10 | import androidx.appcompat.app.AppCompatDelegate 11 | import androidx.appcompat.widget.Toolbar 12 | import androidx.core.net.toUri 13 | import androidx.preference.DropDownPreference 14 | import androidx.preference.Preference 15 | import androidx.preference.PreferenceFragmentCompat 16 | import androidx.preference.SwitchPreference 17 | import com.example.deeplviewer.BuildConfig 18 | import com.example.deeplviewer.R 19 | 20 | 21 | class SettingsActivity : AppCompatActivity() { 22 | 23 | override fun onCreate(savedInstanceState: Bundle?) { 24 | super.onCreate(savedInstanceState) 25 | setContentView(R.layout.settings_activity) 26 | val toolbar: Toolbar = findViewById(R.id.settingsToolbar) 27 | setSupportActionBar(toolbar) 28 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 29 | } 30 | 31 | override fun onOptionsItemSelected(item: MenuItem): Boolean { 32 | if (item.itemId == android.R.id.home) { 33 | finish() 34 | } 35 | return super.onOptionsItemSelected(item) 36 | } 37 | 38 | class SettingsFragment : PreferenceFragmentCompat() { 39 | 40 | override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { 41 | preferenceManager.sharedPreferencesName = "config" 42 | setPreferencesFromResource(R.xml.root_preferences, rootKey) 43 | 44 | val darkMode = findPreference(getString(R.string.key_dark_mode)) 45 | darkMode?.setOnPreferenceChangeListener { _, newValue -> 46 | 47 | var darkThemeMode = newValue as String 48 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q && darkThemeMode == AppCompatDelegate.MODE_NIGHT_FOLLOW_SYSTEM.toString()) { 49 | darkThemeMode = AppCompatDelegate.MODE_NIGHT_AUTO_BATTERY.toString() 50 | } 51 | AppCompatDelegate.setDefaultNightMode(darkThemeMode.toInt()) 52 | return@setOnPreferenceChangeListener true 53 | } 54 | 55 | val translateButton = 56 | findPreference(getString(R.string.key_switch_translate_button)) 57 | translateButton?.setOnPreferenceChangeListener { _, newValue -> 58 | val packageName = requireContext().packageName 59 | val packageManager = requireContext().packageManager 60 | 61 | val showComponentName = 62 | ComponentName(packageName, "com.example.deeplviewer.FloatingTextSelection_show") 63 | val hideComponentName = 64 | ComponentName(packageName, "com.example.deeplviewer.FloatingTextSelection_hide") 65 | 66 | if (newValue == true) { 67 | packageManager.setComponentEnabledSetting( 68 | showComponentName, 69 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 70 | PackageManager.DONT_KILL_APP 71 | ) 72 | packageManager.setComponentEnabledSetting( 73 | hideComponentName, 74 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 75 | PackageManager.DONT_KILL_APP 76 | ) 77 | } else { 78 | packageManager.setComponentEnabledSetting( 79 | hideComponentName, 80 | PackageManager.COMPONENT_ENABLED_STATE_ENABLED, 81 | PackageManager.DONT_KILL_APP 82 | ) 83 | packageManager.setComponentEnabledSetting( 84 | showComponentName, 85 | PackageManager.COMPONENT_ENABLED_STATE_DISABLED, 86 | PackageManager.DONT_KILL_APP 87 | ) 88 | } 89 | return@setOnPreferenceChangeListener true 90 | } 91 | 92 | val versionButton = findPreference(getString(R.string.key_version)) 93 | versionButton?.summary = "v${BuildConfig.VERSION_NAME}" 94 | versionButton?.setOnPreferenceClickListener { 95 | startActivity( 96 | Intent(Intent.ACTION_VIEW, getString(R.string.link_github_release).toUri()) 97 | ) 98 | return@setOnPreferenceClickListener true 99 | } 100 | } 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /.github/workflows/screenshot_diff_check.yml: -------------------------------------------------------------------------------- 1 | name: Screenshot Diff Check 2 | 3 | on: 4 | workflow_dispatch: 5 | schedule: 6 | - cron: "0 0 * * *" 7 | workflow_run: 8 | workflows: [ "Capture WebView Screenshots" ] 9 | types: 10 | - completed 11 | 12 | permissions: 13 | contents: read 14 | pages: write 15 | id-token: write 16 | 17 | jobs: 18 | compare-screenshots: 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout code 23 | uses: actions/checkout@v4 24 | 25 | - name: Set up JDK 17 26 | uses: actions/setup-java@v4 27 | with: 28 | java-version: '17' 29 | distribution: 'temurin' 30 | 31 | - name: Setup Android SDK 32 | uses: android-actions/setup-android@v3 33 | 34 | - name: Accept Android SDK licenses 35 | run: yes | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager --licenses 36 | 37 | - name: Cache Gradle dependencies 38 | uses: actions/cache@v3 39 | with: 40 | path: | 41 | ~/.gradle/caches 42 | ~/.gradle/wrapper 43 | key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties') }} 44 | restore-keys: | 45 | ${{ runner.os }}-gradle- 46 | 47 | - name: Install Android SDK components 48 | run: | 49 | $ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager "platform-tools" "platforms;android-35" "build-tools;35.0.0" 50 | 51 | - name: Enable KVM group perms 52 | run: | 53 | echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules 54 | sudo udevadm control --reload-rules 55 | sudo udevadm trigger --name-match=kvm 56 | 57 | - name: AVD cache 58 | uses: actions/cache@v3 59 | id: avd-cache 60 | with: 61 | path: | 62 | ~/.android/avd/* 63 | ~/.android/adb* 64 | key: avd-35-x86_64 65 | 66 | - name: Create AVD and generate snapshot for caching 67 | if: steps.avd-cache.outputs.cache-hit != 'true' 68 | uses: reactivecircus/android-emulator-runner@v2 69 | with: 70 | api-level: 35 71 | arch: x86_64 72 | target: google_apis 73 | force-avd-creation: false 74 | emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none 75 | disable-animations: false 76 | script: echo "Generated AVD snapshot for caching." 77 | 78 | - name: Run screenshot tests 79 | uses: reactivecircus/android-emulator-runner@v2 80 | with: 81 | api-level: 35 82 | arch: x86_64 83 | target: google_apis 84 | force-avd-creation: false 85 | emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none 86 | disable-animations: false 87 | script: | 88 | ./gradlew executeScreenshotTests || true 89 | 90 | - name: Prepare report for GitHub Pages 91 | run: | 92 | mkdir -p public 93 | if [ -d "app/build/reports/shot/debug/verification" ]; then 94 | cp -r app/build/reports/shot/debug/verification/* public/ 95 | else 96 | echo "

No screenshot report found

" > public/index.html 97 | fi 98 | - name: Upload artifact for Pages 99 | uses: actions/upload-pages-artifact@v3 100 | with: 101 | path: ./public 102 | 103 | - name: Upload comparison results as artifact 104 | uses: actions/upload-artifact@v4 105 | if: always() 106 | with: 107 | name: screenshot-comparison-${{ github.run_id }} 108 | path: | 109 | app/build/reports/shot/debug/verification/ 110 | public/ 111 | retention-days: 30 112 | 113 | deploy: 114 | needs: compare-screenshots 115 | runs-on: ubuntu-latest 116 | environment: 117 | name: github-pages 118 | url: ${{ steps.deployment.outputs.page_url }} 119 | 120 | permissions: 121 | pages: write 122 | id-token: write 123 | 124 | steps: 125 | - name: Deploy to GitHub Pages 126 | id: deployment 127 | uses: actions/deploy-pages@v4 128 | 129 | check-result: 130 | needs: [ compare-screenshots, deploy ] 131 | runs-on: ubuntu-latest 132 | steps: 133 | - name: Download comparison artifact 134 | uses: actions/download-artifact@v4 135 | with: 136 | name: screenshot-comparison-${{ github.run_id }} 137 | 138 | - name: Fail job if diff found 139 | run: | 140 | REPORT=$(find . -type f -name "TEST-Shot.xml" | head -n 1) 141 | if [ -z "$REPORT" ]; then 142 | echo "❌ TEST-Shot.xml not found" 143 | exit 1 144 | fi 145 | if grep -q " 6 | 10 | 14 | 18 | 22 | 26 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/activity/FloatingTextSelection.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.activity 2 | 3 | import android.annotation.SuppressLint 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.os.Bundle 7 | import android.view.View 8 | import android.view.animation.AlphaAnimation 9 | import android.webkit.WebView 10 | import androidx.appcompat.app.AppCompatActivity 11 | import com.example.deeplviewer.R 12 | import com.example.deeplviewer.config.WebViewConfig 13 | import com.example.deeplviewer.helper.UrlHelper 14 | import com.example.deeplviewer.webview.MyWebViewClient 15 | import com.example.deeplviewer.webview.NestedScrollWebView 16 | import com.example.deeplviewer.webview.WebAppInterface 17 | import com.facebook.shimmer.ShimmerFrameLayout 18 | import com.google.android.material.bottomsheet.BottomSheetDialog 19 | 20 | 21 | class FloatingTextSelection : AppCompatActivity() { 22 | private lateinit var webView: WebView 23 | private lateinit var webViewClient: MyWebViewClient 24 | private lateinit var layout: View 25 | private lateinit var startUrl: String 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | 30 | startUrl = UrlHelper.buildStartUrl(this) 31 | 32 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) { 33 | val androidTranslateFloatingText = 34 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.Q) { 35 | intent.getCharSequenceExtra(Intent.EXTRA_TEXT) 36 | } else { 37 | null 38 | } 39 | 40 | val floatingText = (androidTranslateFloatingText 41 | ?: intent.getCharSequenceExtra(Intent.EXTRA_PROCESS_TEXT)) as String 42 | val config = getSharedPreferences("config", Context.MODE_PRIVATE) 43 | val usePopup = config.getBoolean(getString(R.string.key_switch_popup_mode), true) 44 | 45 | if (usePopup) { 46 | launchPopup(floatingText) 47 | } else { 48 | launchFullscreen(floatingText) 49 | } 50 | } 51 | } 52 | 53 | override fun onPause() { 54 | super.onPause() 55 | finish() 56 | } 57 | 58 | override fun finish() { 59 | super.finish() 60 | overridePendingTransition(0, 0) 61 | } 62 | 63 | /** 64 | * Launches the DeepL WebView in fullscreen mode with the selected text 65 | */ 66 | private fun launchFullscreen(initialText: String) { 67 | val intent = Intent(this, MainActivity::class.java) 68 | intent.putExtra("FLOATING_TEXT", initialText) 69 | intent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT) 70 | startActivity(intent) 71 | overridePendingTransition(0, 0) 72 | finish() 73 | } 74 | 75 | /** 76 | * Launches a popup with the DeepL WebView and the selected text 77 | */ 78 | private fun launchPopup(initialText: String) { 79 | initializeWebView() 80 | setupWebViewClient() 81 | 82 | // Add JavaScript interface for communication 83 | webView.addJavascriptInterface(WebAppInterface(this), "Android") 84 | 85 | webViewClient.loadFinishedListener = { 86 | setWebViewHeight() 87 | showWebViewWithAnimation() 88 | } 89 | 90 | showBottomSheetDialog() 91 | 92 | // Load the initial URL 93 | val targetUrl = UrlHelper.buildWebViewUrl(startUrl, initialText) 94 | webView.loadUrl(targetUrl) 95 | } 96 | 97 | /** 98 | * Initializes the WebView 99 | */ 100 | private fun initializeWebView() { 101 | layout = layoutInflater.inflate(R.layout.popup_layout, null) 102 | webView = layout.findViewById(R.id.webview) 103 | 104 | WebViewConfig.applyBasicSettings(webView) 105 | } 106 | 107 | /** 108 | * Sets up the WebViewClient 109 | */ 110 | private fun setupWebViewClient() { 111 | webViewClient = MyWebViewClient(this) 112 | webView.webViewClient = webViewClient 113 | } 114 | 115 | /** 116 | * Shows the BottomSheetDialog with the WebView 117 | */ 118 | @SuppressLint("RestrictedApi", "VisibleForTests") 119 | private fun showBottomSheetDialog() { 120 | val dialog = BottomSheetDialog(this) 121 | dialog.setContentView(layout) 122 | dialog.setOnDismissListener { finish() } 123 | dialog.behavior.disableShapeAnimations() 124 | dialog.show() 125 | } 126 | 127 | /** 128 | * Sets the height of the WebView to 70% of the screen height 129 | */ 130 | private fun setWebViewHeight() { 131 | val displayMetrics = resources.displayMetrics 132 | val screenHeight = displayMetrics.heightPixels 133 | val webViewHeight = (screenHeight * 0.7).toInt() 134 | 135 | val layoutParams = webView.layoutParams 136 | layoutParams.height = webViewHeight 137 | webView.layoutParams = layoutParams 138 | 139 | webView.measure( 140 | View.MeasureSpec.makeMeasureSpec(webView.width, View.MeasureSpec.EXACTLY), 141 | View.MeasureSpec.makeMeasureSpec(webViewHeight, View.MeasureSpec.EXACTLY) 142 | ) 143 | webView.layout(0, 0, webView.width, webViewHeight) 144 | } 145 | 146 | /** 147 | * Shows the WebView with a fade-in animation 148 | */ 149 | private fun showWebViewWithAnimation() { 150 | val animation = AlphaAnimation(0.0F, 1.0F) 151 | animation.duration = 250 152 | webView.visibility = View.VISIBLE 153 | webView.startAnimation(animation) 154 | layout.findViewById(R.id.shimmer_view_container).hideShimmer() 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 18 | 22 | 26 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_monochrome.xml: -------------------------------------------------------------------------------- 1 | 6 | 10 | 14 | 18 | 22 | 26 | 30 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/activity/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.activity 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.net.Uri 6 | import android.os.Bundle 7 | import android.util.Log 8 | import android.webkit.WebView 9 | import android.widget.ImageButton 10 | import androidx.appcompat.app.AppCompatActivity 11 | import androidx.core.content.edit 12 | import com.example.deeplviewer.R 13 | import com.example.deeplviewer.config.WebViewConfig 14 | import com.example.deeplviewer.helper.CookieManagerHelper 15 | import com.example.deeplviewer.helper.UrlHelper 16 | import com.example.deeplviewer.webview.MyWebViewClient 17 | import com.example.deeplviewer.webview.WebAppInterface 18 | 19 | class MainActivity : AppCompatActivity() { 20 | private lateinit var webView: WebView 21 | private lateinit var webViewClient: MyWebViewClient 22 | private lateinit var startUrl: String 23 | 24 | val testWebViewClient: MyWebViewClient? 25 | get() = if (::webViewClient.isInitialized) webViewClient else null 26 | 27 | override fun onCreate(savedInstanceState: Bundle?) { 28 | super.onCreate(savedInstanceState) 29 | setContentView(R.layout.activity_main) 30 | 31 | startUrl = UrlHelper.buildStartUrl(this) 32 | 33 | initializeWebView() 34 | createWebView(intent, savedInstanceState) 35 | setupSettingsButton() 36 | } 37 | 38 | override fun onNewIntent(intent: Intent?) { 39 | super.onNewIntent(intent) 40 | createWebView(intent) 41 | } 42 | 43 | override fun onSaveInstanceState(outState: Bundle) { 44 | super.onSaveInstanceState(outState) 45 | saveWebViewState(outState) 46 | } 47 | 48 | override fun onDestroy() { 49 | if (::webView.isInitialized) { 50 | // Remove the WebView from its parent view and destroy it 51 | (webView.parent as? android.view.ViewGroup)?.removeView(webView) 52 | webView.destroy() 53 | } 54 | super.onDestroy() 55 | } 56 | 57 | /** 58 | * Creates and configures the WebView with the provided intent or saved instance state 59 | */ 60 | private fun createWebView(intent: Intent?, savedInstanceState: Bundle? = null) { 61 | val receivedText = extractReceivedText(intent, savedInstanceState) 62 | 63 | setupWebViewClient() 64 | setupCookies() 65 | 66 | // Add JavaScript interface for communication 67 | webView.addJavascriptInterface(WebAppInterface(this), "Android") 68 | 69 | // Load the initial URL 70 | val targetUrl = UrlHelper.buildWebViewUrl(startUrl, receivedText) 71 | webView.loadUrl(targetUrl) 72 | } 73 | 74 | /** 75 | * Initializes the WebView 76 | */ 77 | private fun initializeWebView() { 78 | webView = findViewById(R.id.webview) 79 | WebViewConfig.applyBasicSettings(webView) 80 | WebViewConfig.applyOptimizedSettings(webView) 81 | } 82 | 83 | /** 84 | * Extracts the text from the intent or saved instance state 85 | */ 86 | private fun extractReceivedText(intent: Intent?, savedInstanceState: Bundle?): String { 87 | return savedInstanceState?.getString("SavedText") 88 | ?: intent?.getStringExtra("FLOATING_TEXT") 89 | ?: intent?.getStringExtra(Intent.EXTRA_TEXT) 90 | ?: "" 91 | } 92 | 93 | /** 94 | * Sets up the WebViewClient to handle page loading and animations 95 | */ 96 | private fun setupWebViewClient() { 97 | webViewClient = MyWebViewClient(this) 98 | webView.webViewClient = webViewClient 99 | } 100 | 101 | /** 102 | * Sets up cookies for the WebView 103 | */ 104 | private fun setupCookies() { 105 | try { 106 | CookieManagerHelper().apply { 107 | migrateCookie(this@MainActivity) 108 | addPrivacyCookie() 109 | } 110 | } catch (e: Exception) { 111 | Log.w("MainActivity", "Cookie setup failed", e) 112 | } 113 | } 114 | 115 | /** 116 | * Sets up the settings button to open the SettingsActivity 117 | */ 118 | private fun setupSettingsButton() { 119 | findViewById(R.id.settingButton).setOnClickListener { 120 | val intent = Intent(this, SettingsActivity::class.java) 121 | startActivity(intent) 122 | } 123 | } 124 | 125 | /** 126 | * Saves the current state of the WebView to the outState bundle 127 | */ 128 | private fun saveWebViewState(outState: Bundle) { 129 | val webView: WebView = findViewById(R.id.webview) 130 | val url = webView.url ?: "" 131 | 132 | // Extract the page type 'translator' or 'write' from the URL 133 | val pageTypeRegex = Regex("^https://www\\.deepl\\.com/.*/(translator|write).*$") 134 | val pageTypeMatch = pageTypeRegex.find(url) 135 | val pageType = pageTypeMatch?.groupValues?.get(1) 136 | 137 | pageType?.let { 138 | getSharedPreferences("config", Context.MODE_PRIVATE).edit { 139 | putString("pageType", it) 140 | } 141 | 142 | // Save the URL parameter if it changed 143 | val urlParam = url.substringAfter(it) 144 | val originUrlParam = startUrl.substringAfter(it) 145 | val isTextChanged = urlParam != originUrlParam 146 | 147 | if (isTextChanged && urlParam.isNotEmpty()) { 148 | val urlText = urlParam.substring(originUrlParam.length) 149 | val inputText = Uri.decode(urlText).replace("\\/", "/") 150 | outState.putString("SavedText", inputText) 151 | } 152 | } 153 | 154 | try { 155 | CookieManagerHelper().saveCookies(this, webView) 156 | } catch (e: Exception) { 157 | Log.w("MainActivity", "Cookie save failed", e) 158 | } 159 | } 160 | } -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/webview/MyWebViewClient.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.webview 2 | 3 | import android.app.Activity 4 | import android.content.Context 5 | import android.content.Intent 6 | import android.content.res.Configuration 7 | import android.os.Build 8 | import android.util.Log 9 | import android.view.View 10 | import android.webkit.WebResourceError 11 | import android.webkit.WebResourceRequest 12 | import android.webkit.WebView 13 | import android.webkit.WebViewClient 14 | import android.widget.ImageButton 15 | import android.widget.Toast 16 | import androidx.annotation.RequiresApi 17 | import androidx.webkit.WebSettingsCompat 18 | import androidx.webkit.WebViewFeature 19 | import com.example.deeplviewer.R 20 | import com.example.deeplviewer.activity.MainActivity 21 | import androidx.core.content.edit 22 | 23 | class MyWebViewClient( 24 | private val activity: Activity, 25 | ) : WebViewClient() { 26 | private var isSplashFadeDone: Boolean = false 27 | private var param: String = "#en/en/" 28 | 29 | var loadFinishedListener: (() -> Unit)? = null 30 | 31 | override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { 32 | val url = request?.url.toString() 33 | val regex = Regex("^https://www\\.deepl\\.com/.*/(translator|write).*$") 34 | val isDeepLTranslatorUrl = regex.matches(url) 35 | 36 | return !isDeepLTranslatorUrl 37 | } 38 | 39 | override fun onPageStarted(view: WebView, url: String, favicon: android.graphics.Bitmap?) { 40 | // initially hide elements with `visibility: hidden` 41 | view.loadJavaScript("hide-elements.js") 42 | view.evaluateJavascript("hideElementsInitially();", null) 43 | } 44 | 45 | override fun onPageFinished(view: WebView, url: String) { 46 | if (!isSplashFadeDone) { 47 | isSplashFadeDone = true 48 | loadFinishedListener?.invoke() 49 | } 50 | 51 | // finally hide elements with `display: none` 52 | view.evaluateJavascript("hideElementsFinal();", null) 53 | 54 | view.loadJavaScript("jquery-3.6.0.min.js") 55 | 56 | switchTheme(view) 57 | saveUrlParam(view.url) 58 | } 59 | 60 | override fun onReceivedError( 61 | view: WebView?, 62 | request: WebResourceRequest, 63 | error: WebResourceError? 64 | ) { 65 | if (request.isForMainFrame) { 66 | activity.setContentView(R.layout.network_err) 67 | 68 | val button: ImageButton = activity.findViewById(R.id.reload) 69 | val listener = ReloadButtonListener() 70 | button.setOnClickListener(listener) 71 | 72 | val errorDescription = 73 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) error?.description.toString() else "" 74 | Toast.makeText(activity, errorDescription, Toast.LENGTH_LONG).show() 75 | Log.e("onReceivedError", errorDescription) 76 | } 77 | } 78 | 79 | /** 80 | * Saves the URL parameter from the DeepL translator URL 81 | */ 82 | private fun saveUrlParam(url: String?) { 83 | Regex("#(.+?)/(.+?)/").find(url ?: "")?.let { 84 | param = it.value 85 | activity.getSharedPreferences("config", Context.MODE_PRIVATE) 86 | .edit { 87 | putString("urlParam", param) 88 | } 89 | } 90 | } 91 | 92 | /** 93 | * loads a JavaScript file from the assets folder into the WebView 94 | */ 95 | private fun WebView.loadJavaScript(fileName: String) { 96 | val jsCode = getAssetsText(this.context, fileName) 97 | this.evaluateJavascript(jsCode, null) 98 | } 99 | 100 | /** 101 | * Reads a text file from the assets folder 102 | */ 103 | private fun getAssetsText(context: Context, fileName: String): String { 104 | return context.assets.open(fileName).reader(Charsets.UTF_8).use { it.readText() } 105 | } 106 | 107 | /** 108 | * Switches the WebView theme based on the system's dark mode setting 109 | */ 110 | private fun switchTheme(view: WebView) { 111 | val uiMode = view.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK 112 | if (uiMode != Configuration.UI_MODE_NIGHT_YES) return 113 | 114 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { 115 | setAlgorithmicDark(view) 116 | } else { 117 | setForceDark(view) 118 | } 119 | } 120 | 121 | /** 122 | * Applies algorithmic darkening to the WebView if supported 123 | */ 124 | @RequiresApi(Build.VERSION_CODES.Q) 125 | private fun setAlgorithmicDark(view: WebView) { 126 | if (WebViewFeature.isFeatureSupported(WebViewFeature.ALGORITHMIC_DARKENING)) { 127 | WebSettingsCompat.setAlgorithmicDarkeningAllowed(view.settings, true) 128 | view.loadJavaScript("patch-darkThemeFix.js") 129 | } else { 130 | Toast.makeText( 131 | activity, 132 | "Dark mode cannot be used because ALGORITHMIC_DARKENING is not supported", 133 | Toast.LENGTH_LONG 134 | ).show() 135 | } 136 | } 137 | 138 | /** 139 | * Forces dark mode on the WebView 140 | */ 141 | @Suppress("DEPRECATION") 142 | private fun setForceDark(view: WebView) { 143 | if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) { 144 | WebSettingsCompat.setForceDark(view.settings, WebSettingsCompat.FORCE_DARK_ON) 145 | view.loadJavaScript("patch-darkThemeFix.js") 146 | } else { 147 | Toast.makeText( 148 | activity, 149 | "Dark mode cannot be used because FORCE_DARK is not supported", 150 | Toast.LENGTH_LONG 151 | ).show() 152 | } 153 | } 154 | 155 | private inner class ReloadButtonListener : View.OnClickListener { 156 | override fun onClick(view: View) { 157 | val i = Intent(activity, MainActivity::class.java) 158 | activity.finish() 159 | activity.overridePendingTransition(0, 0) 160 | activity.startActivity(i) 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: https://rubygems.org/ 3 | specs: 4 | CFPropertyList (3.0.3) 5 | addressable (2.8.0) 6 | public_suffix (>= 2.0.2, < 5.0) 7 | artifactory (3.0.15) 8 | atomos (0.1.3) 9 | aws-eventstream (1.4.0) 10 | aws-partitions (1.1196.0) 11 | aws-sdk-core (3.240.0) 12 | aws-eventstream (~> 1, >= 1.3.0) 13 | aws-partitions (~> 1, >= 1.992.0) 14 | aws-sigv4 (~> 1.9) 15 | base64 16 | bigdecimal 17 | jmespath (~> 1, >= 1.6.1) 18 | logger 19 | aws-sdk-kms (1.118.0) 20 | aws-sdk-core (~> 3, >= 3.239.1) 21 | aws-sigv4 (~> 1.5) 22 | aws-sdk-s3 (1.208.0) 23 | aws-sdk-core (~> 3, >= 3.234.0) 24 | aws-sdk-kms (~> 1) 25 | aws-sigv4 (~> 1.5) 26 | aws-sigv4 (1.12.1) 27 | aws-eventstream (~> 1, >= 1.0.2) 28 | babosa (1.0.4) 29 | base64 (0.3.0) 30 | bigdecimal (4.0.1) 31 | claide (1.0.3) 32 | colored (1.2) 33 | colored2 (3.1.2) 34 | commander (4.6.0) 35 | highline (~> 2.0.0) 36 | declarative (0.0.20) 37 | digest-crc (0.6.3) 38 | rake (>= 12.0.0, < 14.0.0) 39 | domain_name (0.5.20190701) 40 | unf (>= 0.0.5, < 1.0.0) 41 | dotenv (2.7.6) 42 | emoji_regex (3.2.2) 43 | excon (0.81.0) 44 | faraday (1.4.2) 45 | faraday-em_http (~> 1.0) 46 | faraday-em_synchrony (~> 1.0) 47 | faraday-excon (~> 1.1) 48 | faraday-net_http (~> 1.0) 49 | faraday-net_http_persistent (~> 1.1) 50 | multipart-post (>= 1.2, < 3) 51 | ruby2_keywords (>= 0.0.4) 52 | faraday-cookie_jar (0.0.7) 53 | faraday (>= 0.8.0) 54 | http-cookie (~> 1.0.0) 55 | faraday-em_http (1.0.0) 56 | faraday-em_synchrony (1.0.0) 57 | faraday-excon (1.1.0) 58 | faraday-net_http (1.0.1) 59 | faraday-net_http_persistent (1.1.0) 60 | faraday_middleware (1.0.0) 61 | faraday (~> 1.0) 62 | fastimage (2.2.3) 63 | fastlane (2.184.0) 64 | CFPropertyList (>= 2.3, < 4.0.0) 65 | addressable (>= 2.3, < 3.0.0) 66 | artifactory (~> 3.0) 67 | aws-sdk-s3 (~> 1.0) 68 | babosa (>= 1.0.3, < 2.0.0) 69 | bundler (>= 1.12.0, < 3.0.0) 70 | colored 71 | commander (~> 4.6) 72 | dotenv (>= 2.1.1, < 3.0.0) 73 | emoji_regex (>= 0.1, < 4.0) 74 | excon (>= 0.71.0, < 1.0.0) 75 | faraday (~> 1.0) 76 | faraday-cookie_jar (~> 0.0.6) 77 | faraday_middleware (~> 1.0) 78 | fastimage (>= 2.1.0, < 3.0.0) 79 | gh_inspector (>= 1.1.2, < 2.0.0) 80 | google-apis-androidpublisher_v3 (~> 0.1) 81 | google-apis-playcustomapp_v1 (~> 0.1) 82 | google-cloud-storage (~> 1.31) 83 | highline (~> 2.0) 84 | json (< 3.0.0) 85 | jwt (>= 2.1.0, < 3) 86 | mini_magick (>= 4.9.4, < 5.0.0) 87 | multipart-post (~> 2.0.0) 88 | naturally (~> 2.2) 89 | plist (>= 3.1.0, < 4.0.0) 90 | rubyzip (>= 2.0.0, < 3.0.0) 91 | security (= 0.1.3) 92 | simctl (~> 1.6.3) 93 | terminal-notifier (>= 2.0.0, < 3.0.0) 94 | terminal-table (>= 1.4.5, < 2.0.0) 95 | tty-screen (>= 0.6.3, < 1.0.0) 96 | tty-spinner (>= 0.8.0, < 1.0.0) 97 | word_wrap (~> 1.0.0) 98 | xcodeproj (>= 1.13.0, < 2.0.0) 99 | xcpretty (~> 0.3.0) 100 | xcpretty-travis-formatter (>= 0.0.3) 101 | gh_inspector (1.1.3) 102 | google-apis-androidpublisher_v3 (0.4.0) 103 | google-apis-core (~> 0.1) 104 | google-apis-core (0.3.0) 105 | addressable (~> 2.5, >= 2.5.1) 106 | googleauth (~> 0.14) 107 | httpclient (>= 2.8.1, < 3.0) 108 | mini_mime (~> 1.0) 109 | representable (~> 3.0) 110 | retriable (>= 2.0, < 4.0) 111 | rexml 112 | signet (~> 0.14) 113 | webrick 114 | google-apis-iamcredentials_v1 (0.4.0) 115 | google-apis-core (~> 0.1) 116 | google-apis-playcustomapp_v1 (0.3.0) 117 | google-apis-core (~> 0.1) 118 | google-apis-storage_v1 (0.4.0) 119 | google-apis-core (~> 0.1) 120 | google-cloud-core (1.6.0) 121 | google-cloud-env (~> 1.0) 122 | google-cloud-errors (~> 1.0) 123 | google-cloud-env (1.5.0) 124 | faraday (>= 0.17.3, < 2.0) 125 | google-cloud-errors (1.1.0) 126 | google-cloud-storage (1.31.1) 127 | addressable (~> 2.5) 128 | digest-crc (~> 0.4) 129 | google-apis-iamcredentials_v1 (~> 0.1) 130 | google-apis-storage_v1 (~> 0.1) 131 | google-cloud-core (~> 1.2) 132 | googleauth (~> 0.9) 133 | mini_mime (~> 1.0) 134 | googleauth (0.16.2) 135 | faraday (>= 0.17.3, < 2.0) 136 | jwt (>= 1.4, < 3.0) 137 | memoist (~> 0.16) 138 | multi_json (~> 1.11) 139 | os (>= 0.9, < 2.0) 140 | signet (~> 0.14) 141 | highline (2.0.3) 142 | http-cookie (1.0.3) 143 | domain_name (~> 0.5) 144 | httpclient (2.8.3) 145 | jmespath (1.6.2) 146 | json (2.5.1) 147 | jwt (2.2.3) 148 | logger (1.7.0) 149 | memoist (0.16.2) 150 | mini_magick (4.11.0) 151 | mini_mime (1.1.0) 152 | multi_json (1.15.0) 153 | multipart-post (2.0.0) 154 | nanaimo (0.3.0) 155 | naturally (2.2.1) 156 | os (1.1.1) 157 | plist (3.6.0) 158 | public_suffix (4.0.6) 159 | rake (13.0.3) 160 | representable (3.1.1) 161 | declarative (< 0.1.0) 162 | trailblazer-option (>= 0.1.1, < 0.2.0) 163 | uber (< 0.2.0) 164 | retriable (3.1.2) 165 | rexml (3.4.2) 166 | rouge (2.0.7) 167 | ruby2_keywords (0.0.4) 168 | rubyzip (2.3.0) 169 | security (0.1.3) 170 | signet (0.15.0) 171 | addressable (~> 2.3) 172 | faraday (>= 0.17.3, < 2.0) 173 | jwt (>= 1.5, < 3.0) 174 | multi_json (~> 1.10) 175 | simctl (1.6.8) 176 | CFPropertyList 177 | naturally 178 | terminal-notifier (2.0.0) 179 | terminal-table (1.8.0) 180 | unicode-display_width (~> 1.1, >= 1.1.1) 181 | trailblazer-option (0.1.1) 182 | tty-cursor (0.7.1) 183 | tty-screen (0.8.1) 184 | tty-spinner (0.9.3) 185 | tty-cursor (~> 0.7) 186 | uber (0.1.0) 187 | unf (0.1.4) 188 | unf_ext 189 | unf_ext (0.0.7.7) 190 | unf_ext (0.0.7.7-x64-mingw32) 191 | unicode-display_width (1.7.0) 192 | webrick (1.8.2) 193 | word_wrap (1.0.0) 194 | xcodeproj (1.19.0) 195 | CFPropertyList (>= 2.3.3, < 4.0) 196 | atomos (~> 0.1.3) 197 | claide (>= 1.0.2, < 2.0) 198 | colored2 (~> 3.1) 199 | nanaimo (~> 0.3.0) 200 | xcpretty (0.3.0) 201 | rouge (~> 2.0.7) 202 | xcpretty-travis-formatter (1.0.1) 203 | xcpretty (~> 0.2, >= 0.0.7) 204 | 205 | PLATFORMS 206 | x64-mingw32 207 | x86_64-linux 208 | 209 | DEPENDENCIES 210 | fastlane 211 | 212 | BUNDLED WITH 213 | 2.2.18 214 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /app/src/main/java/com/example/deeplviewer/webview/NestedScrollWebView.kt: -------------------------------------------------------------------------------- 1 | package com.example.deeplviewer.webview 2 | 3 | import android.content.Context 4 | import android.util.AttributeSet 5 | import android.util.Log 6 | import android.view.MotionEvent 7 | import android.view.VelocityTracker 8 | import android.view.ViewConfiguration 9 | import android.webkit.WebView 10 | import android.widget.OverScroller 11 | import androidx.core.view.* 12 | import kotlin.math.abs 13 | 14 | /** 15 | * Based on https://gist.github.com/alexmiragall/0c4c7163f7a17938518ce9794c4a5236 16 | * 17 | * A WebView which implements Nested Scrolling and can handle scrolling of child elements inside the WebView correctly. 18 | * The code was optimized to work with bottom sheets, but it might work for other NestedScroll operations as well. 19 | * 20 | */ 21 | class NestedScrollWebView @JvmOverloads constructor( 22 | context: Context, 23 | attrs: AttributeSet? = null, 24 | defStyleAttr: Int = android.R.attr.webViewStyle 25 | ) : 26 | WebView(context, attrs, defStyleAttr), NestedScrollingChild { 27 | 28 | private val scrollOffset = IntArray(2) 29 | private val scrollConsumed = IntArray(2) 30 | 31 | private val nestedScrollingChildHelper: NestedScrollingChildHelper 32 | private var configuration: ViewConfiguration 33 | private var scroller: OverScroller 34 | 35 | private var lastMotionY = 0 36 | private var nestedYOffset = 0 37 | private var isBeingDragged = false 38 | private var isScrollingDown = false 39 | 40 | private var velocityTracker: VelocityTracker? = null 41 | private var activePointerId = INVALID_POINTER 42 | 43 | companion object { 44 | private const val INVALID_POINTER = -1 45 | private const val TAG = "NestedWebView" 46 | } 47 | 48 | init { 49 | overScrollMode = OVER_SCROLL_NEVER 50 | scroller = OverScroller(context) 51 | configuration = ViewConfiguration.get(context) 52 | nestedScrollingChildHelper = NestedScrollingChildHelper(this) 53 | isNestedScrollingEnabled = true 54 | isVerticalScrollBarEnabled = false 55 | isHorizontalScrollBarEnabled = false 56 | } 57 | 58 | override fun onInterceptTouchEvent(ev: MotionEvent): Boolean { 59 | val action = ev.action 60 | if (action == MotionEvent.ACTION_MOVE && isBeingDragged) { 61 | return true 62 | } 63 | when (action and MotionEvent.ACTION_MASK) { 64 | MotionEvent.ACTION_MOVE -> { 65 | activePointerId 66 | if (activePointerId == INVALID_POINTER) { 67 | return isBeingDragged 68 | } else { 69 | val pointerIndex = ev.findPointerIndex(activePointerId) 70 | if (pointerIndex == -1) { 71 | Log.e( 72 | TAG, "Invalid pointerId=" + activePointerId 73 | + " in onInterceptTouchEvent" 74 | ) 75 | } else { 76 | val y = ev.getY(pointerIndex).toInt() 77 | if (lastMotionY == 0) { 78 | lastMotionY = y 79 | } 80 | val yDiff = y - lastMotionY 81 | if (abs(yDiff) > configuration.scaledTouchSlop 82 | && nestedScrollAxes and ViewCompat.SCROLL_AXIS_VERTICAL == 0 83 | ) { 84 | lastMotionY = y 85 | initVelocityTrackerIfNotExists() 86 | velocityTracker!!.addMovement(ev) 87 | nestedYOffset = 0 88 | parent?.requestDisallowInterceptTouchEvent(true) 89 | } 90 | } 91 | } 92 | } 93 | MotionEvent.ACTION_DOWN -> { 94 | lastMotionY = ev.y.toInt() 95 | activePointerId = ev.getPointerId(0) 96 | initOrResetVelocityTracker() 97 | velocityTracker!!.addMovement(ev) 98 | scroller.computeScrollOffset() 99 | if (scroller.isFinished) { 100 | isBeingDragged = false 101 | } 102 | } 103 | MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> { 104 | isBeingDragged = false 105 | activePointerId = INVALID_POINTER 106 | recycleVelocityTracker() 107 | if (scroller.springBack(scrollX, scrollY, 0, 0, 0, computeVerticalScrollRange())) { 108 | ViewCompat.postInvalidateOnAnimation(this) 109 | } 110 | stopNestedScroll() 111 | } 112 | MotionEvent.ACTION_POINTER_UP -> onSecondaryPointerUp(ev) 113 | } 114 | return isBeingDragged 115 | } 116 | 117 | override fun onTouchEvent(ev: MotionEvent): Boolean { 118 | initVelocityTrackerIfNotExists() 119 | val vtev = MotionEvent.obtain(ev) 120 | val actionMasked = MotionEventCompat.getActionMasked(ev) 121 | if (actionMasked == MotionEvent.ACTION_DOWN) { 122 | nestedYOffset = 0 123 | } 124 | vtev.offsetLocation(0f, nestedYOffset.toFloat()) 125 | when (actionMasked) { 126 | MotionEvent.ACTION_DOWN -> { 127 | if (!scroller.isFinished) { 128 | val parent = parent 129 | parent?.requestDisallowInterceptTouchEvent(true) 130 | } 131 | if (!scroller.isFinished) { 132 | scroller.abortAnimation() 133 | } 134 | lastMotionY = ev.y.toInt() 135 | activePointerId = ev.getPointerId(0) 136 | } 137 | MotionEvent.ACTION_MOVE -> { 138 | val activePointerIndex = ev.findPointerIndex(activePointerId) 139 | if (activePointerIndex == -1) { 140 | Log.e( 141 | TAG, 142 | "Invalid pointerId=$activePointerId in onTouchEvent" 143 | ) 144 | } else { 145 | val y = ev.getY(activePointerIndex).toInt() 146 | if (lastMotionY == 0) { 147 | lastMotionY = y 148 | } 149 | var deltaY = lastMotionY - y 150 | if (deltaY < -configuration.scaledTouchSlop) { 151 | if (isScrollingDown) { 152 | endNestedDrag() 153 | } 154 | isScrollingDown = false 155 | } 156 | if (dispatchNestedPreScroll(0, deltaY, scrollConsumed, scrollOffset)) { 157 | deltaY -= scrollConsumed[1] 158 | vtev.offsetLocation(0f, scrollOffset[1].toFloat()) 159 | nestedYOffset += scrollOffset[1] 160 | } 161 | if (!isBeingDragged && abs(deltaY) > configuration.scaledTouchSlop) { 162 | parent?.requestDisallowInterceptTouchEvent(true) 163 | if (deltaY > 0) { 164 | deltaY -= configuration.scaledTouchSlop 165 | startNestedDrag() 166 | isScrollingDown = true 167 | } else { 168 | deltaY += configuration.scaledTouchSlop 169 | } 170 | } 171 | if (isBeingDragged) { 172 | lastMotionY = y - scrollOffset[1] 173 | val oldY = scrollY 174 | val scrolledDeltaY = scrollY - oldY 175 | val unconsumedY = deltaY - scrolledDeltaY 176 | if (dispatchNestedScroll(0, scrolledDeltaY, 0, unconsumedY, scrollOffset)) { 177 | lastMotionY -= scrollOffset[1] 178 | vtev.offsetLocation(0f, scrollOffset[1].toFloat()) 179 | nestedYOffset += scrollOffset[1] 180 | } 181 | } 182 | } 183 | } 184 | MotionEvent.ACTION_UP -> { 185 | if (isBeingDragged) { 186 | velocityTracker!!.computeCurrentVelocity( 187 | 1000, 188 | configuration.scaledMaximumFlingVelocity.toFloat() 189 | ) 190 | val initialVelocity = VelocityTrackerCompat.getYVelocity( 191 | velocityTracker, 192 | activePointerId 193 | ).toInt() 194 | if (abs(initialVelocity) > configuration.scaledMinimumFlingVelocity) { 195 | flingWithNestedDispatch(-initialVelocity) 196 | } else if (scroller.springBack( 197 | scrollX, scrollY, 0, 0, 0, 198 | computeVerticalScrollRange() 199 | ) 200 | ) { 201 | ViewCompat.postInvalidateOnAnimation(this) 202 | } 203 | } 204 | activePointerId = INVALID_POINTER 205 | endNestedDrag() 206 | } 207 | MotionEvent.ACTION_CANCEL -> { 208 | if (isBeingDragged && childCount > 0) { 209 | if (scroller.springBack( 210 | scrollX, scrollY, 0, 0, 0, 211 | computeVerticalScrollRange() 212 | ) 213 | ) { 214 | ViewCompat.postInvalidateOnAnimation(this) 215 | } 216 | } 217 | activePointerId = INVALID_POINTER 218 | endNestedDrag() 219 | } 220 | MotionEventCompat.ACTION_POINTER_DOWN -> { 221 | val index = MotionEventCompat.getActionIndex(ev) 222 | lastMotionY = ev.getY(index).toInt() 223 | activePointerId = ev.getPointerId(index) 224 | } 225 | MotionEventCompat.ACTION_POINTER_UP -> { 226 | onSecondaryPointerUp(ev) 227 | lastMotionY = ev.getY(ev.findPointerIndex(activePointerId)).toInt() 228 | } 229 | } 230 | velocityTracker?.addMovement(vtev) 231 | vtev.recycle() 232 | return super.onTouchEvent(ev) 233 | } 234 | 235 | override fun onOverScrolled(scrollX: Int, scrollY: Int, clampedX: Boolean, clampedY: Boolean) { 236 | super.onOverScrolled(scrollX, scrollY, clampedX, clampedY) 237 | 238 | if (clampedY && !isBeingDragged) { 239 | parent?.requestDisallowInterceptTouchEvent(true) 240 | initOrResetVelocityTracker() 241 | isScrollingDown = false 242 | lastMotionY = 0 243 | nestedYOffset = 0 244 | startNestedDrag() 245 | } 246 | } 247 | 248 | private fun startNestedDrag() { 249 | isBeingDragged = true 250 | startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL) 251 | } 252 | 253 | private fun endNestedDrag() { 254 | isBeingDragged = false 255 | recycleVelocityTracker() 256 | stopNestedScroll() 257 | } 258 | 259 | private fun onSecondaryPointerUp(ev: MotionEvent) { 260 | val pointerIndex = (ev.action and MotionEvent.ACTION_POINTER_INDEX_MASK 261 | shr MotionEvent.ACTION_POINTER_INDEX_SHIFT) 262 | val pointerId = ev.getPointerId(pointerIndex) 263 | if (pointerId == activePointerId) { 264 | val newPointerIndex = if (pointerIndex == 0) 1 else 0 265 | lastMotionY = ev.getY(newPointerIndex).toInt() 266 | activePointerId = ev.getPointerId(newPointerIndex) 267 | velocityTracker?.clear() 268 | } 269 | } 270 | 271 | private fun initOrResetVelocityTracker() { 272 | if (velocityTracker == null) { 273 | velocityTracker = VelocityTracker.obtain() 274 | } else { 275 | velocityTracker!!.clear() 276 | } 277 | } 278 | 279 | private fun initVelocityTrackerIfNotExists() { 280 | if (velocityTracker == null) { 281 | velocityTracker = VelocityTracker.obtain() 282 | } 283 | } 284 | 285 | private fun recycleVelocityTracker() { 286 | velocityTracker?.recycle() 287 | velocityTracker = null 288 | } 289 | 290 | private fun flingWithNestedDispatch(velocityY: Int) { 291 | val scrollY = scrollY 292 | val canFling = ((scrollY > 0 || velocityY > 0) 293 | && (scrollY < computeVerticalScrollRange() || velocityY < 0)) 294 | if (!dispatchNestedPreFling(0f, velocityY.toFloat())) { 295 | dispatchNestedFling(0f, velocityY.toFloat(), canFling) 296 | if (canFling) { 297 | fling(velocityY) 298 | } 299 | } 300 | } 301 | 302 | private fun fling(velocityY: Int) { 303 | if (childCount > 0) { 304 | val height = height - paddingBottom - paddingTop 305 | val bottom = getChildAt(0).height 306 | scroller.fling( 307 | scrollX, scrollY, 0, velocityY, 0, 0, 0, 308 | (bottom - height).coerceAtLeast(0), 0, height / 2 309 | ) 310 | ViewCompat.postInvalidateOnAnimation(this) 311 | } 312 | } 313 | 314 | override fun isNestedScrollingEnabled(): Boolean { 315 | return nestedScrollingChildHelper.isNestedScrollingEnabled 316 | } 317 | 318 | override fun setNestedScrollingEnabled(enabled: Boolean) { 319 | nestedScrollingChildHelper.isNestedScrollingEnabled = enabled 320 | } 321 | 322 | override fun startNestedScroll(axes: Int): Boolean { 323 | return nestedScrollingChildHelper.startNestedScroll(axes) 324 | } 325 | 326 | override fun stopNestedScroll() { 327 | nestedScrollingChildHelper.stopNestedScroll() 328 | } 329 | 330 | override fun hasNestedScrollingParent(): Boolean { 331 | return nestedScrollingChildHelper.hasNestedScrollingParent() 332 | } 333 | 334 | override fun dispatchNestedScroll( 335 | dxConsumed: Int, dyConsumed: Int, dxUnconsumed: Int, dyUnconsumed: Int, 336 | offsetInWindow: IntArray? 337 | ): Boolean { 338 | return nestedScrollingChildHelper.dispatchNestedScroll( 339 | dxConsumed, 340 | dyConsumed, 341 | dxUnconsumed, 342 | dyUnconsumed, 343 | offsetInWindow 344 | ) 345 | } 346 | 347 | override fun dispatchNestedPreScroll( 348 | dx: Int, 349 | dy: Int, 350 | consumed: IntArray?, 351 | offsetInWindow: IntArray? 352 | ): Boolean { 353 | return nestedScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow) 354 | } 355 | 356 | override fun dispatchNestedFling( 357 | velocityX: Float, 358 | velocityY: Float, 359 | consumed: Boolean 360 | ): Boolean { 361 | return nestedScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed) 362 | } 363 | 364 | override fun dispatchNestedPreFling(velocityX: Float, velocityY: Float): Boolean { 365 | return nestedScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY) 366 | } 367 | 368 | override fun getNestedScrollAxes(): Int { 369 | return ViewCompat.SCROLL_AXIS_NONE 370 | } 371 | } 372 | --------------------------------------------------------------------------------