diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..da3b139 --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,92 @@ +name: Android CI + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + test: + name: Unit Test + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 15 + uses: actions/setup-java@v1 + with: + java-version: 15 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Run Unit test + run: ./gradlew test + + apk: + name: Generate APK + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: set up JDK 15 + uses: actions/setup-java@v1 + with: + java-version: 15 + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Assemble App APK + run: ./gradlew assembleDebug + - name: Upload App APK + uses: actions/upload-artifact@v1 + with: + name: app-debug + path: app/build/outputs/apk/debug/app-debug.apk + + - name: Assemble Android Instrumented Unit Test + run: ./gradlew assembleDebugAndroidTest + - name: Upload Android Test APK + uses: actions/upload-artifact@v1 + with: + name: app-debug-androidTest + path: app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk + + firebase: + name: Run UI tests with Firebase Test Lab + needs: apk + runs-on: ubuntu-18.04 + + steps: + - uses: actions/checkout@v1 + + - name: Download app APK + uses: actions/download-artifact@v1 + with: + name: app-debug + + - name: Download Android test APK + uses: actions/download-artifact@v1 + with: + name: app-debug-androidTest + + - name: Login to Google Cloud + uses: google-github-actions/setup-gcloud@master + with: + version: '270.0.0' + service_account_key: ${{ secrets.FIREBASE_SERVICES_KEY }} + + - name: Set current project + run: "gcloud config set project ${{ secrets.FIREBASE_PROJECT_ID }}" + + - name: Run Instrumentation Tests in Firebase Test Lab + run: "gcloud firebase test android run + --type instrumentation + --app app-debug/app-debug.apk + --test app-debug-androidTest/app-debug-androidTest.apk + --device model=redfin,version=30,locale=en,orientation=portrait + --device model=greatlteks,version=28,locale=en,orientation=portrait + --device model=OnePlus3T,version=26,locale=en,orientation=portrait + --device model=HWMHA,version=24,locale=en,orientation=portrait + --device model=grandppltedx,version=23,locale=en,orientation=portrait + --device model=hwALE-H,version=21,locale=en,orientation=portrait" diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a0cb0f9..0000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: android -jdk: openjdk8 - -android: - components: - - tools - - build-tools-29.0.2 - - android-29 - - extra-android-m2repository - - extra-android-support - -notifications: - email: false - -before_install: - - chmod +x gradlew - -script: ./gradlew clean diff --git a/CHANGELOG.md b/CHANGELOG.md index 339c8ac..7e879ba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,51 @@ Release Notes ==== + +1.2.11 +-- +* [Bug] Bug fixed [#105](https://github.com/akexorcist/Localization/pull/105) [#118](https://github.com/akexorcist/Localization/pull/118) [#114](https://github.com/akexorcist/Localization/pull/114) [#118](https://github.com/akexorcist/Localization/pull/118) +* [PR] [#125](https://github.com/akexorcist/Localization/pull/125) - Fix WebView crashing on Android 5 +* Update Gradle and dependencies version + * Compile SDK Version 31 + * Target SDK Version 31 + * Kotlin : 1.6.10 + * Gradle : 7.0.2 + * Gradle Plugin : 4.2.2 + * Activity KTX : 1.4.0 + * AndroidX AppCompat 1.4.0 + +1.2.10 +-- +* [Bug] Bug fixed [#105](https://github.com/akexorcist/Localization/issues/105) [#103](https://github.com/akexorcist/Localization/issues/103) [#92](https://github.com/akexorcist/Localization/issues/92) +* [PR] [#108](https://github.com/akexorcist/Localization/pull/108) - Fix resource not found crash +* Fix incorrect Configuration creation +* Using centralize localized logic (See `LocalizationUtility`) +* Remove `LocalizationContext` (no need anymore) +* Prevent BadParcelableException in `LocalizationActivityDelegate.checkBeforeLocaleChanging()` with try-catch +* Change `LocalizationDelegate.getResources(Context)` to `LocalizationDelegate.getResources(Context, Resources)` because resources from super class is require in this method. That's mean who does not using `LocalizationApplication` must update this method + +1.2.9 +-- +* [Bug] Bug fixed [#93](https://github.com/akexorcist/Localization/issues/93) [#95](https://github.com/akexorcist/Localization/issues/95) +* [PR] [#101](https://github.com/akexorcist/Localization/pull/101) - Update README.md +* Add LocalizationService and LocalizationServiceDelegate for Service +* Add `Context.toLocalizedContext()` extension function to convert context to localized context +* Add more sample code and UI test for Dialog Fragment, Dialog + WebView, and BroadcastReceiver +* Update Gradle and dependencies version + * Kotlin : 1.4.32 + * Gradle Plugin : 4.1.3 + * Activity KTX : 1.2.2 + +1.2.8 +---- +* [PR] [#96](https://github.com/akexorcist/Localization/issues/96) - Fix compatibility with Hilt ViewModel injection +* Add CI with GitHub Actions + 1.2.7 ---- -* [pr] [#74](https://github.com/akexorcist/Localization/pull/74) - Add setLanguageWithoutNotification -* [pr] [#83](https://github.com/akexorcist/Localization/pull/83) - fix: activity Intent can be NULL -* [bug] Bug fixed [#72](https://github.com/akexorcist/Localization/issues/72) [#75](https://github.com/akexorcist/Localization/issues/75) +* [PR] [#74](https://github.com/akexorcist/Localization/pull/74) - Add setLanguageWithoutNotification +* [PR] [#83](https://github.com/akexorcist/Localization/pull/83) - fix: activity Intent can be NULL +* [Bug] Bug fixed [#72](https://github.com/akexorcist/Localization/issues/72) [#75](https://github.com/akexorcist/Localization/issues/75) * Migrate sample project from Java to Kotlin * New UI for sample project * Add more sample code for Dark Theme and Hilt Dependency Injection @@ -16,9 +57,9 @@ Release Notes 1.2.6 ---- -* [pr] [#66](https://github.com/akexorcist/Android-Localization/issues/66) - Fix incompatible with AppCompat 1.2.0 or higher -* [pr] [#60](https://github.com/akexorcist/Android-Localization/issues/60) - Supporting locale variants -* [bug] Bug fixed [#65](https://github.com/akexorcist/Android-Localization/issues/65) +* [PR] [#66](https://github.com/akexorcist/Android-Localization/issues/66) - Fix incompatible with AppCompat 1.2.0 or higher +* [PR] [#60](https://github.com/akexorcist/Android-Localization/issues/60) - Supporting locale variants +* [Bug] Bug fixed [#65](https://github.com/akexorcist/Android-Localization/issues/65) * Add layout resource support in LocalizationActivity's constructor * Fix base context and application context not update in some version * Update UI test @@ -30,7 +71,7 @@ Release Notes 1.2.5 ---- -* [bug] Bug fixed [#53](https://github.com/akexorcist/Android-Localization/issues/53) [#52](https://github.com/akexorcist/Android-Localization/issues/52) [#41](https://github.com/akexorcist/Android-Localization/issues/41) +* [Bug] Bug fixed [#53](https://github.com/akexorcist/Android-Localization/issues/53) [#52](https://github.com/akexorcist/Android-Localization/issues/52) [#41](https://github.com/akexorcist/Android-Localization/issues/41) * Removed dummy activity for fade in/out transition when language changing (if you want, create by yourself when onBeforeLocaleChanged(...) called) * API level 14 supported ([#54](https://github.com/akexorcist/Android-Localization/issues/54)) * Added example code for AndroidX Preferences @@ -41,8 +82,8 @@ Release Notes 1.2.3 ---- -* [bug] Bug fixed : Incorrect behavior in API level 24-27 (Android 7.0 - 8.1) [#30](https://github.com/akexorcist/Android-Localization/issues/30) [#37](https://github.com/akexorcist/Android-Localization/issues/37) -* [bug] Bug fixed : setDefaultLanguage does not work properly [#28](https://github.com/akexorcist/Android-Localization/issues/28) +* [Bug] Bug fixed : Incorrect behavior in API level 24-27 (Android 7.0 - 8.1) [#30](https://github.com/akexorcist/Android-Localization/issues/30) [#37](https://github.com/akexorcist/Android-Localization/issues/37) +* [Bug] Bug fixed : setDefaultLanguage does not work properly [#28](https://github.com/akexorcist/Android-Localization/issues/28) * Migrate to AndroidX and latest Gradle * Migrate to Kotlin * Add UI automated test in example code @@ -51,7 +92,7 @@ Release Notes 1.2.2 ---- -* [bug] Bug fixed [#18](https://github.com/akexorcist/Android-Localization/issues/18) +* [Bug] Bug fixed [#18](https://github.com/akexorcist/Android-Localization/issues/18) * Remove java 1.8 and lambda from the library * Update gradle to 3.0 stable @@ -63,6 +104,6 @@ Release Notes 1.2.0 ---- -* [bug] Bug fixed : Android 7.0 language [#14](https://github.com/akexorcist/Android-Localization/issues/14) -* [bug] Language and country support [#5](https://github.com/akexorcist/Android-Localization/issues/5) -* [bug] RTL on orientation changes [#15](https://github.com/akexorcist/Android-Localization/issues/15) [#9](https://github.com/akexorcist/Android-Localization/issues/9) \ No newline at end of file +* [Bug] Bug fixed : Android 7.0 language [#14](https://github.com/akexorcist/Android-Localization/issues/14) +* [Bug] Language and country support [#5](https://github.com/akexorcist/Android-Localization/issues/5) +* [Bug] RTL on orientation changes [#15](https://github.com/akexorcist/Android-Localization/issues/15) [#9](https://github.com/akexorcist/Android-Localization/issues/9) \ No newline at end of file diff --git a/MIGRATE_TO_ANDROIDX.md b/MIGRATE_TO_ANDROIDX.md new file mode 100644 index 0000000..1fe49ac --- /dev/null +++ b/MIGRATE_TO_ANDROIDX.md @@ -0,0 +1,33 @@ +# Migrate to AndroidX +Since AndroidX supports per-app language preferences for backward compatibility. Please migrate this library to AndroidX for more stability, compatibility, and longer supports from Google team. + +* AppCompat `1.6.0-alpha03` or higher is required (see [AppCompat release notes](https://developer.android.com/jetpack/androidx/releases/appcompat)) +* Remove `LocalizationApplicationDelegate` from Application class +* Replace `LocalizationActivity` with `AppCompatActivity` or remove `LocalizationActivityDelegate` from your custom Activity +* Replace `setLanguage(language)` with `AppCompatDelegate.setApplicationLocales()` +```kotlin +val locales = LocaleListCompat.forLanguageTags(language) +AppCompatDelegate.setApplicationLocales(locales) +``` +* Remove `toLocalizedContext()` and use original Context +* Remove `LocalizationServiceDelegate` from Service class +* Add `androidx.appcompat.app.AppLocalesMetadataHolderService` as `` in Android Manifest (see [Support Android 12 and lower](https://developer.android.com/about/versions/13/features/app-languages#android12-impl)) +```xml + + + + + + + + +``` +* Remove this library from your project (`implementation 'com.akexorcist:localization:'`) +* Test your app + +For more information about per-app language preferences, see [Per-app language preferences](https://developer.android.com/about/versions/13/features/app-languages) diff --git a/README.md b/README.md index e2abf0c..fe02409 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,11 @@ [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Android--Localization-brightgreen.svg?style=flat)](http://android-arsenal.com/details/1/2890) -[![JCenter](https://api.bintray.com/packages/akexorcist/maven/localization/images/download.svg)](https://bintray.com/akexorcist/maven/localization) +[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.akexorcist/localization/badge.svg)](https://search.maven.org/artifact/com.akexorcist/localization) ![Minimum SDK Version](https://img.shields.io/badge/minSdkVersion-14-brightgreen) -[![Build Status](https://travis-ci.org/akexorcist/Android-Localization.svg?branch=master)](https://travis-ci.org/akexorcist/Android-Localization) +[![Workflow Status](https://github.com/akexorcist/Android-Localization/actions/workflows/android.yml/badge.svg)](https://github.com/akexorcist/Localization/actions) +Discontinued +==== +Since Google announced Android 13 with per-app language preferences supports. This feature also backport to older Android version with AndroidX. So there's no reason to contribute this library anymore. For more stability, compatibility, and longer supports from Google team, please migrate to AndroidX. See [Migrate to AndroidX guide](./MIGRATE_TO_ANDROIDX.md). Localization Library ==== @@ -26,14 +29,26 @@ Try it at [Google Play](https://play.google.com/store/apps/details?id=com.akexor Download ==== +Since version 1.2.9 will [move from JCenter to MavenCentral](https://developer.android.com/studio/build/jcenter-migration) + +```groovy +// build.gradle (project) +allprojects { + repositories { + mavenCentral() + /* ... */ + } +} +``` + Gradle ```groovy -implementation 'com.akexorcist:localization:1.2.7' +implementation 'com.akexorcist:localization:1.2.11' ``` (Optional) You can exclude `androidx.appcompat:appcompat`, if your project does not use AppCompat. ```groovy -implementation ('com.akexorcist:localization:1.2.7') { +implementation ('com.akexorcist:localization:1.2.11') { exclude group: 'androidx.core', module: 'core' } ``` @@ -45,7 +60,7 @@ Custom application class which extends from `LocalizationApplication` is require ```kotlin class MainApplication: LocalizationApplication() { /* ... */ - override fun getDefaultLanguage() = Locale.ENGLISH + override fun getDefaultLanguage(context: Context) = Locale.ENGLISH } ``` @@ -53,20 +68,24 @@ Either not, using `LocalizationApplicationDelegate` with additional code as belo ```kotlin class MainApplication: Application() { private val localizationDelegate = LocalizationApplicationDelegate() - + override fun attachBaseContext(base: Context) { localizationDelegate.setDefaultLanguage(base, Locale.ENGLISH) super.attachBaseContext(localizationDelegate.attachBaseContext(base)) } - + override fun onConfigurationChanged(newConfig: Configuration) { super.onConfigurationChanged(newConfig) localizationDelegate.onConfigurationChanged(this) } - + override fun getApplicationContext(): Context { return localizationDelegate.getApplicationContext(super.getApplicationContext()) } + + override fun getResources(): Resources { + return localizationDelegate.getResources(baseContext, super.getResources()) + } } ``` @@ -115,7 +134,8 @@ open class CustomActivity : Activity(), OnLocaleChangedListener { localizationDelegate.setLanguage(this, locale!!) } - val getCurrentLanguage: Locale = localizationDelegate.getLanguage(this) + val currentLanguage: Locale + get() = localizationDelegate.getLanguage(this) // Just override method locale change event override fun onBeforeLocaleChanged() {} @@ -207,6 +227,53 @@ Change the language in Fragment Language in fragment will depends on activity. So no need for additional code in Fragment. +Service +--- +For normally usage, just extends from `LocalizationService` +```kotlin +class SimpleService : LocalizationService() { + /* ... */ +} +``` + +Or using `LocalizationServiceDelegate` with additional code +```kotlin +abstract class CustomService : Service() { + private val localizationDelegate: LocalizationServiceDelegate by lazy { + LocalizationServiceDelegate(this) + } + + override fun getBaseContext(): Context { + return localizationDelegate.getBaseContext(super.getBaseContext()) + } + + override fun getApplicationContext(): Context { + return localizationDelegate.getApplicationContext(super.getApplicationContext()) + } + + override fun getResources(): Resources { + return localizationDelegate.getResources(super.getResources()) + } +} +``` + + +BroadcastReceiver +--- +BroadcastReceiver is abstract class. So we cannot create LocalizationBroadcastReceiver fot you. + +In this case, you need to convert the context in `onReceive(context: Context, intent: Intent)` to localized context with `Context.toLocalizedContext()` before using. + +```kotlin +class SimpleBroadcastReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val localizedContext = context.toLocalizedContext() + /* ... */ + } +} +``` + + Language resources optimization in Android App Bundle ==== Change the language by library can cause a crash to your app when you publishing your app with Android App Bundle with language resources optimization enabled. diff --git a/app/build.gradle b/app/build.gradle index 8c9f134..f35ca1f 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -4,14 +4,14 @@ apply plugin: 'kotlin-kapt' apply plugin: 'dagger.hilt.android.plugin' android { - compileSdkVersion Integer.parseInt(project.ANDROID_COMPILE_SDK_VERSION) + compileSdkVersion project.compileSdkVersion defaultConfig { applicationId "com.akexorcist.localizationapp" minSdkVersion 19 - targetSdkVersion Integer.parseInt(project.ANDROID_TARGET_SDK_VERSION) - versionName project.VERSION_NAME - versionCode Integer.parseInt(project.VERSION_CODE) + targetSdkVersion project.targetSdkVersion + versionCode project.versionCode + versionName project.versionName testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } @@ -29,37 +29,38 @@ android { } compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 + sourceCompatibility project.targetJavaVersion + targetCompatibility project.targetJavaVersion } kotlinOptions { - jvmTarget = '1.8' + jvmTarget = project.targetJavaVersion.toString() } buildFeatures { - viewBinding = true + viewBinding true } testOptions { - animationsDisabled = true + animationsDisabled true } } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.appcompat:appcompat:1.4.0' implementation 'androidx.viewpager:viewpager:1.0.0' implementation 'androidx.preference:preference-ktx:1.1.1' implementation "androidx.viewpager2:viewpager2:1.0.0" - implementation "com.google.dagger:hilt-android:2.33-beta" - kapt "com.google.dagger:hilt-compiler:2.33-beta" + implementation 'com.google.dagger:hilt-android:2.40.4' + kapt 'com.google.dagger:hilt-compiler:2.40.4' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' - androidTestImplementation 'androidx.test:runner:1.3.0' - androidTestImplementation 'androidx.test:rules:1.3.0' - androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test:runner:1.4.0' + androidTestImplementation 'androidx.test:rules:1.4.0' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation 'androidx.test.espresso:espresso-web:3.4.0' androidTestImplementation 'androidx.test.uiautomator:uiautomator:2.2.0' androidTestImplementation 'com.agoda.kakao:kakao:2.4.0' diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/AllTest.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/AllTest.kt index 0cfda0c..e71f0f5 100644 --- a/app/src/androidTest/java/com/akexorcist/localizationapp/AllTest.kt +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/AllTest.kt @@ -3,16 +3,19 @@ package com.akexorcist.localizationapp import org.junit.runner.RunWith import org.junit.runners.Suite -@RunWith(Suite::class) -@Suite.SuiteClasses( - SimpleActivityTest::class, - CustomActivityTest::class, - StackedActivityTest::class, - SimpleFragmentTest::class, - NestedFragmentTest::class, - ViewPagerTest::class, - ListPreferencesTest::class, - HiltDependencyInjectionTest::class, +//@RunWith(Suite::class) +//@Suite.SuiteClasses( +// SimpleActivityTest::class, +// CustomActivityTest::class, +// StackedActivityTest::class, +// SimpleFragmentTest::class, +// NestedFragmentTest::class, +// SimpleDialogTest::class, +// DialogWebViewTest::class, +// ViewPagerTest::class, +// ListPreferencesTest::class, +// HiltDependencyInjectionTest::class, +// BroadcastReceiverTest::class, // DarkThemeTest::class, -) -class AllTest +//) +//class AllTest \ No newline at end of file diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/BroadcastReceiverTest.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/BroadcastReceiverTest.kt new file mode 100644 index 0000000..f56319d --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/BroadcastReceiverTest.kt @@ -0,0 +1,101 @@ +package com.akexorcist.localizationapp + +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.agoda.kakao.screen.Screen.Companion.onScreen +import com.akexorcist.localizationapp.data.ExpectedContent +import com.akexorcist.localizationapp.screen.MainActivityScreen +import com.akexorcist.localizationapp.screen.SimpleBroadcastScreen +import org.junit.Ignore +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +class BroadcastReceiverTest { + @JvmField + val uiDevices: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + @Rule + @JvmField + val rule = ActivityScenarioRule(MainActivity::class.java) + + @Test + @Ignore("For local running only, there is some flakiness when running on Firebase Test Lab") + fun broadcastReceiver() { + onScreen { + buttonBroadcastReceiver { + scrollTo() + click() + } + } + onScreen { + // Chinese + buttonChinese { + scrollTo() + click() + } + textViewContent { + hasText(ExpectedContent.APPLE_CHINESE) + } + // Italian + buttonItalian { + scrollTo() + click() + } + textViewContent { + hasText(ExpectedContent.APPLE_ITALIAN) + } + // Japanese + buttonJapanese { + scrollTo() + click() + } + textViewContent { + hasText(ExpectedContent.APPLE_JAPANESE) + } + // Korean + buttonKorean { + scrollTo() + click() + } + textViewContent { + hasText(ExpectedContent.APPLE_KOREAN) + } + // Portuguese + buttonPortuguese { + scrollTo() + click() + } + textViewContent { + hasText(ExpectedContent.APPLE_PORTUGUESE) + } + // Thai + buttonThai { + scrollTo() + click() + } + textViewContent { + hasText(ExpectedContent.APPLE_THAI) + } + // American + buttonAmerican { + scrollTo() + click() + } + textViewContent { + hasText(ExpectedContent.APPLE_AMERICAN) + } + buttonBack { click() } + } + onScreen { + textViewTitle { + hasText(ExpectedContent.HELLO_WORLD_AMERICAN) + uiDevices.setOrientationNatural() + hasText(ExpectedContent.HELLO_WORLD_AMERICAN) + } + } + } +} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/DarkThemeTest.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/DarkThemeTest.kt index 154343a..3e184e0 100644 --- a/app/src/androidTest/java/com/akexorcist/localizationapp/DarkThemeTest.kt +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/DarkThemeTest.kt @@ -2,13 +2,13 @@ package com.akexorcist.localizationapp import android.os.Build import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner import androidx.test.platform.app.InstrumentationRegistry import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiSelector import com.agoda.kakao.screen.Screen.Companion.onScreen import com.akexorcist.localizationapp.annotation.TargetApiLevel import com.akexorcist.localizationapp.data.ExpectedContent -import com.akexorcist.localizationapp.runner.AndroidSdkLevel29TestRunner import com.akexorcist.localizationapp.screen.MainActivityScreen import com.akexorcist.localizationapp.screen.SimpleFragmentScreen import org.junit.Ignore @@ -17,7 +17,7 @@ import org.junit.Test import org.junit.runner.RunWith -@RunWith(AndroidSdkLevel29TestRunner::class) +@RunWith(AndroidJUnit4ClassRunner::class) class DarkThemeTest { @JvmField val uiDevices: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) @@ -27,8 +27,7 @@ class DarkThemeTest { val rule = ActivityScenarioRule(MainActivity::class.java) @Test - @TargetApiLevel(Build.VERSION_CODES.Q) - @Ignore("For local running only, dark theme in quick settings panel is required") + @Ignore("For local running on Android Q+ only, dark theme in quick settings panel is required") fun darkTheme() { onScreen { buttonDarkTheme { diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/DialogWebViewTest.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/DialogWebViewTest.kt new file mode 100644 index 0000000..05f1fab --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/DialogWebViewTest.kt @@ -0,0 +1,96 @@ +package com.akexorcist.localizationapp + +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.agoda.kakao.screen.Screen.Companion.onScreen +import com.akexorcist.localizationapp.data.ExpectedContent +import com.akexorcist.localizationapp.screen.DialogWebViewLanguageChooserScreen +import com.akexorcist.localizationapp.screen.DialogWebViewMainScreen +import com.akexorcist.localizationapp.screen.DialogWebViewSiteScreen +import com.akexorcist.localizationapp.screen.MainActivityScreen +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import androidx.test.espresso.web.webdriver.Locator +import org.junit.Ignore + +@RunWith(AndroidJUnit4ClassRunner::class) +class DialogWebViewTest { + @JvmField + val uiDevices: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + @Rule + @JvmField + val rule = ActivityScenarioRule(MainActivity::class.java) + + @Test + fun dialogAndWebView() { + // Italy + onScreen { + buttonDialogWebView { + scrollTo() + click() + } + } + onScreen { + buttonChangeLanguage { click() } + } + onScreen { + buttonItalian { click() } + } + onScreen { + buttonShowWebsite { click() } + } + onScreen { + webViewContent { + withElement(Locator.ID, "content") { + hasText(ExpectedContent.HELLO_WORLD_ITALIAN) + } + } + buttonBack { click() } + } + onScreen { + buttonBack { click() } + } + onScreen { + textViewTitle { + hasText(ExpectedContent.HELLO_WORLD_ITALIAN) + } + } + + // American + onScreen { + buttonDialogWebView { + scrollTo() + click() + } + } + onScreen { + buttonChangeLanguage { click() } + } + onScreen { + buttonAmerican { click() } + } + onScreen { + buttonShowWebsite { click() } + } + onScreen { + webViewContent { + withElement(Locator.ID, "content") { + hasText(ExpectedContent.HELLO_WORLD_AMERICAN) + } + } + buttonBack { click() } + } + onScreen { + buttonBack { click() } + } + onScreen { + textViewTitle { + hasText(ExpectedContent.HELLO_WORLD_AMERICAN) + } + } + } +} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleActivityTest.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleActivityTest.kt index 37857f2..5582823 100644 --- a/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleActivityTest.kt +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleActivityTest.kt @@ -24,7 +24,10 @@ class SimpleActivityTest { @Test fun simpleActivity() { onScreen { - buttonSimpleActivity { click() } + buttonSimpleActivity { + scrollTo() + click() + } } // Chinese onScreen { @@ -45,7 +48,10 @@ class SimpleActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_CHINESE) } - buttonSimpleActivity { click() } + buttonSimpleActivity { + scrollTo() + click() + } } // Italian onScreen { @@ -69,7 +75,10 @@ class SimpleActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_ITALIAN) } - buttonSimpleActivity { click() } + buttonSimpleActivity { + scrollTo() + click() + } } // Japanese onScreen { @@ -93,7 +102,10 @@ class SimpleActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_JAPANESE) } - buttonSimpleActivity { click() } + buttonSimpleActivity { + scrollTo() + click() + } } // Korean onScreen { @@ -117,7 +129,10 @@ class SimpleActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_KOREAN) } - buttonSimpleActivity { click() } + buttonSimpleActivity { + scrollTo() + click() + } } // Portuguese onScreen { @@ -141,7 +156,10 @@ class SimpleActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_PORTUGUESE) } - buttonSimpleActivity { click() } + buttonSimpleActivity { + scrollTo() + click() + } } // Thai onScreen { @@ -165,7 +183,10 @@ class SimpleActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_THAI) } - buttonSimpleActivity { click() } + buttonSimpleActivity { + scrollTo() + click() + } } // American onScreen { diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleDialogTest.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleDialogTest.kt new file mode 100644 index 0000000..fc26276 --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleDialogTest.kt @@ -0,0 +1,177 @@ +package com.akexorcist.localizationapp + +import androidx.test.ext.junit.rules.ActivityScenarioRule +import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.uiautomator.UiDevice +import com.agoda.kakao.screen.Screen.Companion.onScreen +import com.akexorcist.localizationapp.data.ExpectedContent +import com.akexorcist.localizationapp.screen.MainActivityScreen +import com.akexorcist.localizationapp.screen.SimpleDialogContentScreen +import com.akexorcist.localizationapp.screen.SimpleDialogLanguageChooserScreen +import com.akexorcist.localizationapp.screen.SimpleDialogMainScreen +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4ClassRunner::class) +class SimpleDialogTest { + @JvmField + val uiDevices: UiDevice = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()) + + @Rule + @JvmField + val rule = ActivityScenarioRule(MainActivity::class.java) + + @Test + fun simpleDialog() { + onScreen { + buttonSimpleDialog { + scrollTo() + click() + } + } + // Chinese + onScreen { + buttonChangeLanguage { click() } + } + onScreen { + buttonChinese { click() } + } + onScreen { + buttonShowContent { click() } + } + onScreen { + textViewContent { + hasText(ExpectedContent.YOUTUBE_CHINESE) + uiDevices.setOrientationRight() + hasText(ExpectedContent.YOUTUBE_CHINESE) + } + pressBack() + uiDevices.setOrientationNatural() + } + // Italian + onScreen { + buttonChangeLanguage { click() } + } + onScreen { + buttonItalian { click() } + } + onScreen { + buttonShowContent { click() } + } + onScreen { + textViewContent { + hasText(ExpectedContent.YOUTUBE_ITALIAN) + uiDevices.setOrientationRight() + hasText(ExpectedContent.YOUTUBE_ITALIAN) + } + pressBack() + uiDevices.setOrientationNatural() + } + // Japanese + onScreen { + buttonChangeLanguage { click() } + } + onScreen { + buttonJapanese { click() } + } + onScreen { + buttonShowContent { click() } + } + onScreen { + textViewContent { + hasText(ExpectedContent.YOUTUBE_JAPANESE) + uiDevices.setOrientationRight() + hasText(ExpectedContent.YOUTUBE_JAPANESE) + } + pressBack() + uiDevices.setOrientationNatural() + } + // Korean + onScreen { + buttonChangeLanguage { click() } + } + onScreen { + buttonKorean { click() } + } + onScreen { + buttonShowContent { click() } + } + onScreen { + textViewContent { + hasText(ExpectedContent.YOUTUBE_KOREAN) + uiDevices.setOrientationRight() + hasText(ExpectedContent.YOUTUBE_KOREAN) + } + pressBack() + uiDevices.setOrientationNatural() + } + // Portuguese + onScreen { + buttonChangeLanguage { click() } + } + onScreen { + buttonPortuguese { click() } + } + onScreen { + buttonShowContent { click() } + } + onScreen { + textViewContent { + hasText(ExpectedContent.YOUTUBE_PORTUGUESE) + uiDevices.setOrientationRight() + hasText(ExpectedContent.YOUTUBE_PORTUGUESE) + } + pressBack() + uiDevices.setOrientationNatural() + } + // Thai + onScreen { + buttonChangeLanguage { click() } + } + onScreen { + buttonThai { click() } + } + onScreen { + buttonShowContent { click() } + } + onScreen { + textViewContent { + hasText(ExpectedContent.YOUTUBE_THAI) + uiDevices.setOrientationRight() + hasText(ExpectedContent.YOUTUBE_THAI) + } + pressBack() + uiDevices.setOrientationNatural() + } + // American + onScreen { + buttonChangeLanguage { click() } + } + onScreen { + buttonAmerican { click() } + } + onScreen { + buttonShowContent { click() } + } + onScreen { + textViewContent { + hasText(ExpectedContent.YOUTUBE_AMERICAN) + uiDevices.setOrientationRight() + hasText(ExpectedContent.YOUTUBE_AMERICAN) + } + pressBack() + uiDevices.setOrientationNatural() + } + // End + onScreen { + buttonBack { click() } + } + onScreen { + textViewTitle { + hasText(ExpectedContent.HELLO_WORLD_AMERICAN) + } + } + } +} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleFragmentTest.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleFragmentTest.kt index 3e9c5f5..dfae560 100644 --- a/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleFragmentTest.kt +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/SimpleFragmentTest.kt @@ -24,7 +24,10 @@ class SimpleFragmentTest { @Test fun simpleFragment() { onScreen { - buttonSimpleFragment { click() } + buttonSimpleFragment { + scrollTo() + click() + } } // Chinese onScreen { @@ -45,7 +48,10 @@ class SimpleFragmentTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_CHINESE) } - buttonSimpleFragment { click() } + buttonSimpleFragment { + scrollTo() + click() + } } // Italian onScreen { @@ -69,7 +75,10 @@ class SimpleFragmentTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_ITALIAN) } - buttonSimpleFragment { click() } + buttonSimpleFragment { + scrollTo() + click() + } } // Japanese onScreen { @@ -93,7 +102,10 @@ class SimpleFragmentTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_JAPANESE) } - buttonSimpleFragment { click() } + buttonSimpleFragment { + scrollTo() + click() + } } // Korean onScreen { @@ -117,7 +129,10 @@ class SimpleFragmentTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_KOREAN) } - buttonSimpleFragment { click() } + buttonSimpleFragment { + scrollTo() + click() + } } // Portuguese onScreen { @@ -141,7 +156,10 @@ class SimpleFragmentTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_PORTUGUESE) } - buttonSimpleFragment { click() } + buttonSimpleFragment { + scrollTo() + click() + } } // Thai onScreen { @@ -165,7 +183,10 @@ class SimpleFragmentTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_THAI) } - buttonSimpleFragment { click() } + buttonSimpleFragment { + scrollTo() + click() + } } // American onScreen { diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/StackedActivityTest.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/StackedActivityTest.kt index 7318d9e..e6c4574 100644 --- a/app/src/androidTest/java/com/akexorcist/localizationapp/StackedActivityTest.kt +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/StackedActivityTest.kt @@ -25,7 +25,10 @@ class StackedActivityTest { @Test fun stackedActivity() { onScreen { - buttonStackedActivity { click() } + buttonStackedActivity { + scrollTo() + click() + } } // Chinese onScreen { @@ -48,7 +51,10 @@ class StackedActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_CHINESE) } - buttonStackedActivity { click() } + buttonStackedActivity { + scrollTo() + click() + } } // Italian onScreen { @@ -74,7 +80,10 @@ class StackedActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_ITALIAN) } - buttonStackedActivity { click() } + buttonStackedActivity { + scrollTo() + click() + } } // Japanese onScreen { @@ -100,7 +109,10 @@ class StackedActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_JAPANESE) } - buttonStackedActivity { click() } + buttonStackedActivity { + scrollTo() + click() + } } // Korean onScreen { @@ -126,7 +138,10 @@ class StackedActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_KOREAN) } - buttonStackedActivity { click() } + buttonStackedActivity { + scrollTo() + click() + } } // Portuguese onScreen { @@ -152,7 +167,10 @@ class StackedActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_PORTUGUESE) } - buttonStackedActivity { click() } + buttonStackedActivity { + scrollTo() + click() + } } // Thai onScreen { @@ -178,7 +196,10 @@ class StackedActivityTest { uiDevices.setOrientationNatural() hasText(ExpectedContent.HELLO_WORLD_THAI) } - buttonStackedActivity { click() } + buttonStackedActivity { + scrollTo() + click() + } } // American onScreen { diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/runner/AndroidSdkLevel29TestRunner.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/runner/AndroidSdkLevel29TestRunner.kt deleted file mode 100644 index 6680a9a..0000000 --- a/app/src/androidTest/java/com/akexorcist/localizationapp/runner/AndroidSdkLevel29TestRunner.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.akexorcist.localizationapp.runner - -import android.os.Build -import androidx.test.internal.runner.junit4.AndroidJUnit4ClassRunner -import androidx.test.internal.util.AndroidRunnerParams -import com.akexorcist.localizationapp.annotation.TargetApiLevel -import org.junit.runner.notification.RunNotifier -import org.junit.runners.model.FrameworkMethod - -class AndroidSdkLevel29TestRunner: AndroidJUnit4ClassRunner { - constructor(klass: Class<*>?, runnerParams: AndroidRunnerParams?) : super(klass, runnerParams) - constructor(klass: Class<*>?) : super(klass) - - override fun runChild(method: FrameworkMethod, notifier: RunNotifier) { - val condition: TargetApiLevel = method.getAnnotation(TargetApiLevel::class.java) - if (condition.value < Build.VERSION_CODES.Q) { - notifier.fireTestIgnored(describeChild(method)) - } else { - super.runChild(method, notifier) - } - } -} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewLanguageChooserScreen.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewLanguageChooserScreen.kt new file mode 100644 index 0000000..af9b1fc --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewLanguageChooserScreen.kt @@ -0,0 +1,15 @@ +package com.akexorcist.localizationapp.screen + +import com.agoda.kakao.screen.Screen +import com.agoda.kakao.text.KButton +import com.akexorcist.localizationapp.R + +class DialogWebViewLanguageChooserScreen : Screen() { + val buttonAmerican = KButton { withId(R.id.btn_american) } + val buttonChinese = KButton { withId(R.id.btn_chinese) } + val buttonItalian = KButton { withId(R.id.btn_italian) } + val buttonJapanese = KButton { withId(R.id.btn_japanese) } + val buttonKorean = KButton { withId(R.id.btn_korean) } + val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } + val buttonThai = KButton { withId(R.id.btn_thai) } +} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewMainScreen.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewMainScreen.kt new file mode 100644 index 0000000..afb9130 --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewMainScreen.kt @@ -0,0 +1,11 @@ +package com.akexorcist.localizationapp.screen + +import com.agoda.kakao.screen.Screen +import com.agoda.kakao.text.KButton +import com.akexorcist.localizationapp.R + +class DialogWebViewMainScreen : Screen() { + val buttonBack = KButton { withId(R.id.btn_back) } + val buttonShowWebsite = KButton { withId(R.id.btn_show_website) } + val buttonChangeLanguage = KButton { withId(R.id.btn_change_language) } +} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewSiteScreen.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewSiteScreen.kt new file mode 100644 index 0000000..6cf65e0 --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/DialogWebViewSiteScreen.kt @@ -0,0 +1,11 @@ +package com.akexorcist.localizationapp.screen + +import com.agoda.kakao.screen.Screen +import com.agoda.kakao.text.KButton +import com.agoda.kakao.web.KWebView +import com.akexorcist.localizationapp.R + +class DialogWebViewSiteScreen : Screen() { + val buttonBack = KButton { withId(R.id.btn_back) } + val webViewContent = KWebView { withId(R.id.wv_content) } +} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/screen/MainActivityScreen.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/MainActivityScreen.kt index 44c122c..876249b 100644 --- a/app/src/androidTest/java/com/akexorcist/localizationapp/screen/MainActivityScreen.kt +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/MainActivityScreen.kt @@ -12,8 +12,11 @@ open class MainActivityScreen : Screen() { val buttonStackedActivity = KButton { withId(R.id.btn_stacked_activity) } val buttonSimpleFragment = KButton { withId(R.id.btn_simple_fragment) } val buttonNestedFragment = KButton { withId(R.id.btn_nested_fragment) } + val buttonSimpleDialog = KButton { withId(R.id.btn_dialog_fragment) } + val buttonDialogWebView = KButton { withId(R.id.btn_dialog_web_view) } val buttonViewPager = KButton { withId(R.id.btn_view_pager) } val buttonListPreferences = KButton { withId(R.id.btn_list_preferences) } val buttonDarkTheme = KButton { withId(R.id.btn_dark_theme) } val buttonHilt = KButton { withId(R.id.btn_hilt) } + val buttonBroadcastReceiver = KButton { withId(R.id.btn_broadcast_receiver) } } diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleBroadcastScreen.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleBroadcastScreen.kt new file mode 100644 index 0000000..715643c --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleBroadcastScreen.kt @@ -0,0 +1,18 @@ +package com.akexorcist.localizationapp.screen + +import com.agoda.kakao.screen.Screen +import com.agoda.kakao.text.KButton +import com.agoda.kakao.text.KTextView +import com.akexorcist.localizationapp.R + +class SimpleBroadcastScreen : Screen() { + val buttonBack = KButton { withId(R.id.btn_back) } + val buttonAmerican = KButton { withId(R.id.btn_american) } + val buttonChinese = KButton { withId(R.id.btn_chinese) } + val buttonItalian = KButton { withId(R.id.btn_italian) } + val buttonJapanese = KButton { withId(R.id.btn_japanese) } + val buttonKorean = KButton { withId(R.id.btn_korean) } + val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } + val buttonThai = KButton { withId(R.id.btn_thai) } + val textViewContent = KTextView { withId(R.id.text_view_content) } +} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogContentScreen.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogContentScreen.kt new file mode 100644 index 0000000..db74cd1 --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogContentScreen.kt @@ -0,0 +1,9 @@ +package com.akexorcist.localizationapp.screen + +import com.agoda.kakao.screen.Screen +import com.agoda.kakao.text.KTextView +import com.akexorcist.localizationapp.R + +class SimpleDialogContentScreen : Screen() { + val textViewContent = KTextView { withId(R.id.text_view_content) } +} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogLanguageChooserScreen.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogLanguageChooserScreen.kt new file mode 100644 index 0000000..46f1f91 --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogLanguageChooserScreen.kt @@ -0,0 +1,15 @@ +package com.akexorcist.localizationapp.screen + +import com.agoda.kakao.screen.Screen +import com.agoda.kakao.text.KButton +import com.akexorcist.localizationapp.R + +class SimpleDialogLanguageChooserScreen : Screen() { + val buttonAmerican = KButton { withId(R.id.btn_american) } + val buttonChinese = KButton { withId(R.id.btn_chinese) } + val buttonItalian = KButton { withId(R.id.btn_italian) } + val buttonJapanese = KButton { withId(R.id.btn_japanese) } + val buttonKorean = KButton { withId(R.id.btn_korean) } + val buttonPortuguese = KButton { withId(R.id.btn_portuguese) } + val buttonThai = KButton { withId(R.id.btn_thai) } +} diff --git a/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogMainScreen.kt b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogMainScreen.kt new file mode 100644 index 0000000..3cd0ceb --- /dev/null +++ b/app/src/androidTest/java/com/akexorcist/localizationapp/screen/SimpleDialogMainScreen.kt @@ -0,0 +1,11 @@ +package com.akexorcist.localizationapp.screen + +import com.agoda.kakao.screen.Screen +import com.agoda.kakao.text.KButton +import com.akexorcist.localizationapp.R + +class SimpleDialogMainScreen : Screen() { + val buttonBack = KButton { withId(R.id.btn_back) } + val buttonChangeLanguage = KButton { withId(R.id.btn_change_language) } + val buttonShowContent = KButton { withId(R.id.btn_show_content) } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 6049735..3316e98 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -10,25 +10,69 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/AppTheme"> - + - - - - - - - - + + + + + + + + - + + + + + + + + + + + + diff --git a/app/src/main/java/com/akexorcist/localizationapp/MainActivity.kt b/app/src/main/java/com/akexorcist/localizationapp/MainActivity.kt index c48c720..8a49fc8 100644 --- a/app/src/main/java/com/akexorcist/localizationapp/MainActivity.kt +++ b/app/src/main/java/com/akexorcist/localizationapp/MainActivity.kt @@ -3,9 +3,12 @@ package com.akexorcist.localizationapp import android.content.Intent import android.os.Bundle import com.akexorcist.localizationactivity.ui.LocalizationActivity +import com.akexorcist.localizationapp.broadcast.SimpleBroadcastActivity import com.akexorcist.localizationapp.customactivity.SimpleCustomActivity import com.akexorcist.localizationapp.darktheme.DarkThemeActivity import com.akexorcist.localizationapp.databinding.ActivityMainBinding +import com.akexorcist.localizationapp.dialogwebview.DialogWebViewMainActivity +import com.akexorcist.localizationapp.simpledialog.SimpleDialogMainActivity import com.akexorcist.localizationapp.hilt.HiltActivity import com.akexorcist.localizationapp.nestedfragment.NestedFragmentActivity import com.akexorcist.localizationapp.preferences.ListPreferencesActivity @@ -27,10 +30,13 @@ class MainActivity : LocalizationActivity() { binding.btnStackedActivity.setOnClickListener { goToActivity(StackedHomeActivity::class.java) } binding.btnSimpleFragment.setOnClickListener { goToActivity(SimpleFragmentActivity::class.java) } binding.btnNestedFragment.setOnClickListener { goToActivity(NestedFragmentActivity::class.java) } + binding.btnDialogFragment.setOnClickListener { goToActivity(SimpleDialogMainActivity::class.java) } + binding.btnDialogWebView.setOnClickListener { goToActivity(DialogWebViewMainActivity::class.java) } binding.btnViewPager.setOnClickListener { goToActivity(ViewPagerActivity::class.java) } binding.btnListPreferences.setOnClickListener { goToActivity(ListPreferencesActivity::class.java) } binding.btnDarkTheme.setOnClickListener { goToActivity(DarkThemeActivity::class.java) } binding.btnHilt.setOnClickListener { goToActivity(HiltActivity::class.java) } + binding.btnBroadcastReceiver.setOnClickListener { goToActivity(SimpleBroadcastActivity::class.java) } } private fun goToActivity(activity: Class<*>?) { diff --git a/app/src/main/java/com/akexorcist/localizationapp/MainApplication.kt b/app/src/main/java/com/akexorcist/localizationapp/MainApplication.kt index 41b0087..0984129 100644 --- a/app/src/main/java/com/akexorcist/localizationapp/MainApplication.kt +++ b/app/src/main/java/com/akexorcist/localizationapp/MainApplication.kt @@ -1,12 +1,38 @@ package com.akexorcist.localizationapp -import com.akexorcist.localizationactivity.ui.LocalizationApplication +import android.app.Application +import android.content.Context +import android.content.res.Configuration +import android.content.res.Resources +import android.webkit.WebView +import com.akexorcist.localizationactivity.core.LocalizationApplicationDelegate import dagger.hilt.android.HiltAndroidApp import java.util.* @HiltAndroidApp -class MainApplication : LocalizationApplication() { - override fun getDefaultLanguage(): Locale { - return Locale.ENGLISH +class MainApplication : Application() { + private val localizationDelegate = LocalizationApplicationDelegate() + + override fun onCreate() { + super.onCreate() + localizationDelegate.onCreate(this) + } + + override fun attachBaseContext(base: Context) { + localizationDelegate.setDefaultLanguage(base, Locale.ENGLISH) + super.attachBaseContext(localizationDelegate.attachBaseContext(base)) + } + + override fun onConfigurationChanged(newConfig: Configuration) { + super.onConfigurationChanged(newConfig) + localizationDelegate.onConfigurationChanged(this) + } + + override fun getApplicationContext(): Context { + return localizationDelegate.getApplicationContext(super.getApplicationContext()) + } + + override fun getResources(): Resources { + return localizationDelegate.getResources(baseContext, super.getResources()) } } diff --git a/app/src/main/java/com/akexorcist/localizationapp/broadcast/BroadcastEvent.kt b/app/src/main/java/com/akexorcist/localizationapp/broadcast/BroadcastEvent.kt new file mode 100644 index 0000000..211cf2b --- /dev/null +++ b/app/src/main/java/com/akexorcist/localizationapp/broadcast/BroadcastEvent.kt @@ -0,0 +1,10 @@ +package com.akexorcist.localizationapp.broadcast + +object BroadcastEvent { + // Activity -> Broadcast Receiver + const val ACTION_TO_BROADCAST = "com.akexorcist.localizationapp.receiver.action.TO_BROADCAST" + + // Broadcast Receiver -> Activity + const val ACTION_TO_ACTIVITY = "com.akexorcist.localizationapp.receiver.action.TO_ACTIVITY" + const val EXTRA_CONTENT = "com.akexorcist.localizationapp.receiver.extra.CONTENT" +} diff --git a/app/src/main/java/com/akexorcist/localizationapp/broadcast/SimpleBroadcastActivity.kt b/app/src/main/java/com/akexorcist/localizationapp/broadcast/SimpleBroadcastActivity.kt new file mode 100644 index 0000000..cdd0429 --- /dev/null +++ b/app/src/main/java/com/akexorcist/localizationapp/broadcast/SimpleBroadcastActivity.kt @@ -0,0 +1,57 @@ +package com.akexorcist.localizationapp.broadcast + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import android.content.IntentFilter +import android.os.Bundle +import com.akexorcist.localizationactivity.ui.LocalizationActivity +import com.akexorcist.localizationapp.databinding.ActivitySimpleBroadcastBinding + +class SimpleBroadcastActivity : LocalizationActivity() { + private val binding by lazy { ActivitySimpleBroadcastBinding.inflate(layoutInflater) } + + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + + binding.btnBack.setOnClickListener { super.onBackPressed() } + binding.layoutLanguageChooser.btnAmerican.setOnClickListener { onChangeLanguageButtonClicked("en") } + binding.layoutLanguageChooser.btnChinese.setOnClickListener { onChangeLanguageButtonClicked("zh") } + binding.layoutLanguageChooser.btnItalian.setOnClickListener { onChangeLanguageButtonClicked("it") } + binding.layoutLanguageChooser.btnJapanese.setOnClickListener { onChangeLanguageButtonClicked("ja") } + binding.layoutLanguageChooser.btnKorean.setOnClickListener { onChangeLanguageButtonClicked("ko") } + binding.layoutLanguageChooser.btnPortuguese.setOnClickListener { onChangeLanguageButtonClicked("pt") } + binding.layoutLanguageChooser.btnThai.setOnClickListener { onChangeLanguageButtonClicked("th") } + } + + private fun onChangeLanguageButtonClicked(language: String) { + setLanguage(language) + getContentFromBroadcastReceiver() + } + + private fun getContentFromBroadcastReceiver() { + val intent = Intent(BroadcastEvent.ACTION_TO_BROADCAST).apply { + `package` = packageName + } + sendBroadcast(intent) + } + + override fun onStart() { + super.onStart() + val filter = IntentFilter(BroadcastEvent.ACTION_TO_ACTIVITY) + registerReceiver(broadcastReceiver, filter) + } + + override fun onStop() { + super.onStop() + unregisterReceiver(broadcastReceiver) + } + + private val broadcastReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val content = intent.getStringExtra(BroadcastEvent.EXTRA_CONTENT) + binding.textViewContent.text = content + } + } +} diff --git a/app/src/main/java/com/akexorcist/localizationapp/broadcast/SimpleBroadcastReceiver.kt b/app/src/main/java/com/akexorcist/localizationapp/broadcast/SimpleBroadcastReceiver.kt new file mode 100644 index 0000000..a4766c1 --- /dev/null +++ b/app/src/main/java/com/akexorcist/localizationapp/broadcast/SimpleBroadcastReceiver.kt @@ -0,0 +1,19 @@ +package com.akexorcist.localizationapp.broadcast + +import android.content.BroadcastReceiver +import android.content.Context +import android.content.Intent +import com.akexorcist.localizationactivity.core.toLocalizedContext +import com.akexorcist.localizationapp.R + +class SimpleBroadcastReceiver : BroadcastReceiver() { + override fun onReceive(context: Context, intent: Intent) { + val localizedContext = context.toLocalizedContext() + val content = localizedContext.getString(R.string.sample_apple_story) + val broadcastIntent = Intent(BroadcastEvent.ACTION_TO_ACTIVITY).apply { + `package` = context.packageName + putExtra(BroadcastEvent.EXTRA_CONTENT, content) + } + context.sendBroadcast(broadcastIntent) + } +} diff --git a/app/src/main/java/com/akexorcist/localizationapp/customactivity/CustomActivity.kt b/app/src/main/java/com/akexorcist/localizationapp/customactivity/CustomActivity.kt index 25e53d9..edda101 100644 --- a/app/src/main/java/com/akexorcist/localizationapp/customactivity/CustomActivity.kt +++ b/app/src/main/java/com/akexorcist/localizationapp/customactivity/CustomActivity.kt @@ -9,7 +9,9 @@ import com.akexorcist.localizationactivity.core.OnLocaleChangedListener import java.util.* open class CustomActivity : Activity(), OnLocaleChangedListener { - private val localizationDelegate = LocalizationActivityDelegate(this) + private val localizationDelegate by lazy { + LocalizationActivityDelegate(this) + } public override fun onCreate(savedInstanceState: Bundle?) { localizationDelegate.addOnLocaleChangedListener(this) diff --git a/app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewLanguageChooserFragment.kt b/app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewLanguageChooserFragment.kt new file mode 100644 index 0000000..044a95c --- /dev/null +++ b/app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewLanguageChooserFragment.kt @@ -0,0 +1,39 @@ +package com.akexorcist.localizationapp.dialogwebview + +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.DialogFragment +import com.akexorcist.localizationactivity.ui.LocalizationActivity +import com.akexorcist.localizationapp.databinding.FragmentDialogWebviewLanguageChooserBinding + +class DialogWebViewLanguageChooserFragment : DialogFragment() { + private lateinit var binding: FragmentDialogWebviewLanguageChooserBinding + + companion object { + fun newInstance(): DialogWebViewLanguageChooserFragment = DialogWebViewLanguageChooserFragment() + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + binding = FragmentDialogWebviewLanguageChooserBinding.inflate(layoutInflater, container, false) + return binding.root + } + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + + binding.btnAmerican.setOnClickListener { changeLanguage("en") } + binding.btnChinese.setOnClickListener { changeLanguage("zh") } + binding.btnItalian.setOnClickListener { changeLanguage("it") } + binding.btnJapanese.setOnClickListener { changeLanguage("ja") } + binding.btnKorean.setOnClickListener { changeLanguage("ko") } + binding.btnPortuguese.setOnClickListener { changeLanguage("pt") } + binding.btnThai.setOnClickListener { changeLanguage("th") } + } + + private fun changeLanguage(language: String) { + (requireActivity() as? LocalizationActivity)?.setLanguage(language) + dismiss() + } +} diff --git a/app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewMainActivity.kt b/app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewMainActivity.kt new file mode 100644 index 0000000..2109459 --- /dev/null +++ b/app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewMainActivity.kt @@ -0,0 +1,23 @@ +package com.akexorcist.localizationapp.dialogwebview + +import android.content.Intent +import android.os.Bundle +import com.akexorcist.localizationactivity.ui.LocalizationActivity +import com.akexorcist.localizationapp.databinding.ActivityDialogWebviewMainBinding + +class DialogWebViewMainActivity : LocalizationActivity() { + private val binding: ActivityDialogWebviewMainBinding by lazy { ActivityDialogWebviewMainBinding.inflate(layoutInflater) } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + + binding.btnBack.setOnClickListener { super.onBackPressed() } + binding.btnShowWebsite.setOnClickListener { + startActivity(Intent(this, DialogWebViewSiteActivity::class.java)) + } + binding.btnChangeLanguage.setOnClickListener { + DialogWebViewLanguageChooserFragment.newInstance().show(supportFragmentManager, null) + } + } +} diff --git a/app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewSiteActivity.kt b/app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewSiteActivity.kt new file mode 100644 index 0000000..15e9742 --- /dev/null +++ b/app/src/main/java/com/akexorcist/localizationapp/dialogwebview/DialogWebViewSiteActivity.kt @@ -0,0 +1,42 @@ +package com.akexorcist.localizationapp.dialogwebview + +import android.annotation.SuppressLint +import android.graphics.Color +import android.os.Bundle +import com.akexorcist.localizationactivity.ui.LocalizationActivity +import com.akexorcist.localizationapp.R +import com.akexorcist.localizationapp.databinding.ActivityDialogWebviewSiteBinding + +class DialogWebViewSiteActivity : LocalizationActivity() { + private val binding: ActivityDialogWebviewSiteBinding by lazy { ActivityDialogWebviewSiteBinding.inflate(layoutInflater) } + + @SuppressLint("SetJavaScriptEnabled") + public override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(binding.root) + binding.btnBack.setOnClickListener { super.onBackPressed() } + binding.wvContent.run { + settings.javaScriptEnabled = true + setBackgroundColor(Color.TRANSPARENT) + loadData(htmlData, "text/html", "UTF-8") + } + } + + private val htmlData: String + get() = """ + + + + + +

+ ${getString(R.string.hello_world)} +

+ + + """.trimIndent() +} diff --git a/app/src/main/java/com/akexorcist/localizationapp/hilt/HiltActivity.kt b/app/src/main/java/com/akexorcist/localizationapp/hilt/HiltActivity.kt index 4a16cff..cf71cba 100644 --- a/app/src/main/java/com/akexorcist/localizationapp/hilt/HiltActivity.kt +++ b/app/src/main/java/com/akexorcist/localizationapp/hilt/HiltActivity.kt @@ -13,36 +13,8 @@ class HiltActivity : LocalizationActivity() { @Inject lateinit var storyProvider: StoryProvider - companion object { - private const val KEY_SCROLL_X = "scroll_x" - } - override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(binding.root) - - binding.textViewContent.text = storyProvider.getAppleStory() - binding.btnBack.setOnClickListener { super.onBackPressed() } - binding.layoutLanguageChooser.btnAmerican.setOnClickListener { setLanguage("en") } - binding.layoutLanguageChooser.btnChinese.setOnClickListener { setLanguage("zh") } - binding.layoutLanguageChooser.btnItalian.setOnClickListener { setLanguage("it") } - binding.layoutLanguageChooser.btnJapanese.setOnClickListener { setLanguage("ja") } - binding.layoutLanguageChooser.btnKorean.setOnClickListener { setLanguage("ko") } - binding.layoutLanguageChooser.btnPortuguese.setOnClickListener { setLanguage("pt") } - binding.layoutLanguageChooser.btnThai.setOnClickListener { setLanguage("th") } - } - - public override fun onSaveInstanceState(outState: Bundle) { - // Save x-position of horizontal scroll view. - outState.putInt(KEY_SCROLL_X, binding.layoutLanguageChooser.svLanguageChooser.scrollX) - super.onSaveInstanceState(outState) - } - - public override fun onRestoreInstanceState(savedInstanceState: Bundle) { - super.onRestoreInstanceState(savedInstanceState) - // Restore x-position of horizontal scroll view. - binding.layoutLanguageChooser.svLanguageChooser.scrollTo( - savedInstanceState.getInt(KEY_SCROLL_X), 0 - ) } } diff --git a/app/src/main/java/com/akexorcist/localizationapp/hilt/HiltFragmentWithViewModel.kt b/app/src/main/java/com/akexorcist/localizationapp/hilt/HiltFragmentWithViewModel.kt new file mode 100644 index 0000000..36dcf50 --- /dev/null +++ b/app/src/main/java/com/akexorcist/localizationapp/hilt/HiltFragmentWithViewModel.kt @@ -0,0 +1,69 @@ +package com.akexorcist.localizationapp.hilt + +import android.os.Bundle +import android.view.View +import android.widget.Button +import android.widget.ImageButton +import android.widget.ScrollView +import android.widget.TextView +import androidx.fragment.app.Fragment +import androidx.fragment.app.viewModels +import com.akexorcist.localizationactivity.ui.LocalizationActivity +import com.akexorcist.localizationapp.R +import dagger.hilt.android.AndroidEntryPoint + + +@AndroidEntryPoint +class HiltFragmentWithViewModel : Fragment(R.layout.fragment_hilt) { + + private val hiltViewModel: HiltViewModel by viewModels() + + private var languageChooserScrollView: ScrollView? = null + + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { + super.onViewCreated(view, savedInstanceState) + setupUi(view) + } + + private fun setupUi(view: View) { + languageChooserScrollView = view.findViewById(R.id.sv_language_chooser) + + view.findViewById(R.id.text_view_content).text = hiltViewModel.storyProvider.getAppleStory() + view.findViewById