├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── mipmap-hdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-mdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-xxxhdpi
│ │ │ │ ├── ic_launcher.webp
│ │ │ │ └── ic_launcher_round.webp
│ │ │ ├── mipmap-anydpi-v26
│ │ │ │ ├── ic_launcher.xml
│ │ │ │ └── ic_launcher_round.xml
│ │ │ ├── xml
│ │ │ │ ├── backup_rules.xml
│ │ │ │ └── data_extraction_rules.xml
│ │ │ ├── values
│ │ │ │ └── strings.xml
│ │ │ ├── drawable-v24
│ │ │ │ └── ic_launcher_foreground.xml
│ │ │ └── drawable
│ │ │ │ └── ic_launcher_background.xml
│ │ └── AndroidManifest.xml
│ ├── test
│ │ └── java
│ │ │ └── emperorfin
│ │ │ └── android
│ │ │ └── militaryjet
│ │ │ └── ExampleUnitTest.kt
│ └── androidTest
│ │ └── java
│ │ └── emperorfin
│ │ └── android
│ │ └── militaryjet
│ │ └── ExampleInstrumentedTest.kt
├── proguard-rules.pro
└── build.gradle
├── components
├── .gitignore
├── consumer-rules.pro
├── out
│ └── failures
│ │ ├── emperorfin.android.components.screenshots_HelloComposeTest_screenshot_Hello_Paparazzi.png
│ │ └── delta-emperorfin.android.components.screenshots_HelloComposeTest_screenshot_Hello_Paparazzi.png
├── src
│ ├── test
│ │ ├── snapshots
│ │ │ └── images
│ │ │ │ ├── emperorfin.android.components.screenshots_HelloComposeTest_screenshot_Hello_Paparazzi.png
│ │ │ │ ├── emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationErrorDialogTest_dialog_Displays.png
│ │ │ │ ├── emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_In_Button_Disabled.png
│ │ │ │ ├── emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_In_Button_Enabled.png
│ │ │ │ ├── emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_Up_Button_Disabled.png
│ │ │ │ └── emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_Up_Button_Enabled.png
│ │ ├── java
│ │ │ └── emperorfin
│ │ │ │ └── android
│ │ │ │ └── components
│ │ │ │ ├── screenshots
│ │ │ │ ├── ui
│ │ │ │ │ ├── constants
│ │ │ │ │ │ ├── BooleanConstants.kt
│ │ │ │ │ │ └── StringResourceConstants.kt
│ │ │ │ │ └── screens
│ │ │ │ │ │ └── authentication
│ │ │ │ │ │ └── uicomponents
│ │ │ │ │ │ ├── AuthenticationErrorDialogTest.kt
│ │ │ │ │ │ └── AuthenticationButtonTest.kt
│ │ │ │ └── HelloComposeTest.kt
│ │ │ │ └── ExampleUnitTest.kt
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ ├── main
│ │ ├── res
│ │ │ └── values
│ │ │ │ ├── integers.xml
│ │ │ │ ├── bools.xml
│ │ │ │ ├── themes.xml
│ │ │ │ ├── fractions.xml
│ │ │ │ ├── colors.xml
│ │ │ │ ├── dimens.xml
│ │ │ │ └── strings.xml
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── emperorfin
│ │ │ └── android
│ │ │ └── components
│ │ │ └── ui
│ │ │ ├── screens
│ │ │ ├── authentication
│ │ │ │ ├── enums
│ │ │ │ │ ├── AuthenticationMode.kt
│ │ │ │ │ └── PasswordRequirement.kt
│ │ │ │ ├── events
│ │ │ │ │ └── AuthenticationEvent.kt
│ │ │ │ ├── stateholders
│ │ │ │ │ └── AuthenticationUiState.kt
│ │ │ │ ├── uicomponents
│ │ │ │ │ ├── PasswordRequirements.kt
│ │ │ │ │ ├── AuthenticationTitle.kt
│ │ │ │ │ ├── AuthenticationButton.kt
│ │ │ │ │ ├── tags
│ │ │ │ │ │ └── Tags.kt
│ │ │ │ │ ├── AuthenticationToggleMode.kt
│ │ │ │ │ ├── AuthenticationErrorDialog.kt
│ │ │ │ │ ├── AuthenticationContent.kt
│ │ │ │ │ ├── Requirement.kt.another_approach
│ │ │ │ │ ├── EmailInput.kt
│ │ │ │ │ └── Requirement.kt
│ │ │ │ └── AuthenticationScreen.kt
│ │ │ └── MainActivity.kt
│ │ │ ├── res
│ │ │ ├── theme
│ │ │ │ ├── Color.kt
│ │ │ │ ├── Type.kt
│ │ │ │ └── Theme.kt
│ │ │ └── FractionResources.kt
│ │ │ └── fortesting
│ │ │ ├── TextSemantics.kt
│ │ │ ├── CircularProgressIndicatorSemantics.kt
│ │ │ ├── AlertDialogSemantics.kt
│ │ │ ├── IconSemantics.kt
│ │ │ └── TextFieldSemantics.kt
│ └── androidTest
│ │ ├── java
│ │ └── emperorfin
│ │ │ └── android
│ │ │ └── components
│ │ │ ├── ui
│ │ │ ├── constants
│ │ │ │ ├── NothingConstants.kt
│ │ │ │ ├── BooleanConstants.kt
│ │ │ │ ├── ImeActionConstants.kt
│ │ │ │ ├── KeyboardTypeConstants.kt
│ │ │ │ ├── VisualTransformationConstants.kt
│ │ │ │ ├── ImageVectorConstants.kt
│ │ │ │ ├── LongConstants.kt
│ │ │ │ ├── StringConstants.kt
│ │ │ │ ├── ColorArgbConstants.kt
│ │ │ │ └── StringResourceConstants.kt
│ │ │ ├── extensions
│ │ │ │ ├── semanticsmatcher
│ │ │ │ │ ├── AlertDialogSemanticsMatchers.kt
│ │ │ │ │ ├── TextSemanticsMatchers.kt
│ │ │ │ │ ├── CircularProgressIndicatorSemanticsMatchers.kt
│ │ │ │ │ └── IconSemanticsMatchers.kt
│ │ │ │ └── ComposeContentTestRuleExtensions.kt
│ │ │ ├── screens
│ │ │ │ └── authentication
│ │ │ │ │ └── uicomponents
│ │ │ │ │ ├── AuthenticationTitleTest5.kt
│ │ │ │ │ ├── AuthenticationTitleTest4.kt
│ │ │ │ │ └── AuthenticationTitleTest3.kt
│ │ │ └── utils
│ │ │ │ └── KeyboardHelper.kt
│ │ │ └── ExampleInstrumentedTest.kt
│ │ └── res
│ │ └── values
│ │ └── strings.xml
├── proguard-rules.pro
└── build.gradle
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── settings.gradle
├── .gitignore
├── gradle.properties
├── gradlew.bat
├── README.md
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/components/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/components/consumer-rules.pro:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/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/emperorfin/MilitaryJet/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/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/emperorfin/MilitaryJet/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/components/out/failures/emperorfin.android.components.screenshots_HelloComposeTest_screenshot_Hello_Paparazzi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/components/out/failures/emperorfin.android.components.screenshots_HelloComposeTest_screenshot_Hello_Paparazzi.png
--------------------------------------------------------------------------------
/components/out/failures/delta-emperorfin.android.components.screenshots_HelloComposeTest_screenshot_Hello_Paparazzi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/components/out/failures/delta-emperorfin.android.components.screenshots_HelloComposeTest_screenshot_Hello_Paparazzi.png
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Wed May 17 22:46:21 WAT 2023
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
5 | zipStoreBase=GRADLE_USER_HOME
6 | zipStorePath=wrapper/dists
7 |
--------------------------------------------------------------------------------
/components/src/test/snapshots/images/emperorfin.android.components.screenshots_HelloComposeTest_screenshot_Hello_Paparazzi.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/components/src/test/snapshots/images/emperorfin.android.components.screenshots_HelloComposeTest_screenshot_Hello_Paparazzi.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationErrorDialogTest_dialog_Displays.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationErrorDialogTest_dialog_Displays.png
--------------------------------------------------------------------------------
/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_In_Button_Disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_In_Button_Disabled.png
--------------------------------------------------------------------------------
/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_In_Button_Enabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_In_Button_Enabled.png
--------------------------------------------------------------------------------
/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_Up_Button_Disabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_Up_Button_Disabled.png
--------------------------------------------------------------------------------
/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_Up_Button_Enabled.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/emperorfin/MilitaryJet/HEAD/components/src/test/snapshots/images/emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents_AuthenticationButtonTest_sign_Up_Button_Enabled.png
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | gradlePluginPortal()
4 | google()
5 | mavenCentral()
6 | }
7 | }
8 | dependencyResolutionManagement {
9 | repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
10 | repositories {
11 | google()
12 | mavenCentral()
13 | }
14 | }
15 | rootProject.name = "Military Jet"
16 | include ':app'
17 | include ':components'
18 |
--------------------------------------------------------------------------------
/app/src/test/java/emperorfin/android/militaryjet/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | package emperorfin.android.militaryjet
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 | }
--------------------------------------------------------------------------------
/app/src/main/res/xml/backup_rules.xml:
--------------------------------------------------------------------------------
1 |
8 |
9 |
13 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/data_extraction_rules.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
12 |
13 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | Military Jet
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
--------------------------------------------------------------------------------
/components/src/main/res/values/integers.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | 1
20 |
--------------------------------------------------------------------------------
/components/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
--------------------------------------------------------------------------------
/components/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/emperorfin/android/militaryjet/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | package emperorfin.android.militaryjet
2 |
3 | import androidx.test.platform.app.InstrumentationRegistry
4 | import androidx.test.ext.junit.runners.AndroidJUnit4
5 |
6 | import org.junit.Test
7 | import org.junit.runner.RunWith
8 |
9 | import org.junit.Assert.*
10 |
11 | /**
12 | * Instrumented test, which will execute on an Android device.
13 | *
14 | * See [testing documentation](http://d.android.com/tools/testing).
15 | */
16 | @RunWith(AndroidJUnit4::class)
17 | class ExampleInstrumentedTest {
18 | @Test
19 | fun useAppContext() {
20 | // Context of the app under test.
21 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
22 | assertEquals("emperorfin.android.militaryjet", appContext.packageName)
23 | }
24 | }
--------------------------------------------------------------------------------
/components/src/main/res/values/bools.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | true
20 | false
21 |
--------------------------------------------------------------------------------
/components/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/.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 | .cxx
15 | local.properties
16 |
17 | /.idea
18 | /.idea/
19 |
20 | # From https://github.com/github/gitignore/blob/main/Android.gitignore
21 | # Gradle files
22 | .gradle/
23 | build/
24 |
25 | # Local configuration file (sdk path, etc)
26 | local.properties
27 |
28 | # Log/OS Files
29 | *.log
30 |
31 | # Android Studio generated files and folders
32 | captures/
33 | .externalNativeBuild/
34 | .cxx/
35 | *.apk
36 | output.json
37 |
38 | # IntelliJ
39 | *.iml
40 | .idea/
41 | misc.xml
42 | deploymentTargetDropDown.xml
43 | render.experimental.xml
44 |
45 | # Keystore files
46 | *.jks
47 | *.keystore
48 |
49 | # Google Services (e.g. APIs or Firebase)
50 | google-services.json
51 |
52 | # Android Profiling
53 | *.hprof
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/NothingConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 |
20 | /**
21 | * @Author: Francis Nwokelo (emperorfin)
22 | * @Date: Tuesday 21st March, 2023.
23 | */
24 |
25 |
26 | object NothingConstants {
27 |
28 | val NULL = null
29 |
30 | }
--------------------------------------------------------------------------------
/components/src/main/res/values/fractions.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 | - 121%
21 |
22 | - 100%
23 |
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/enums/AuthenticationMode.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.enums
18 |
19 |
20 | /**
21 | * @Author: Francis Nwokelo (emperorfin)
22 | * @Date: November, 2022.
23 | */
24 |
25 |
26 | enum class AuthenticationMode {
27 |
28 | SIGN_IN,
29 | SIGN_UP
30 |
31 | }
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/BooleanConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 |
20 | /**
21 | * @Author: Francis Nwokelo (emperorfin)
22 | * @Date: Tuesday 21st March, 2023.
23 | */
24 |
25 |
26 | object BooleanConstants {
27 |
28 | const val TRUE: Boolean = true
29 | const val FALSE: Boolean = false
30 |
31 | const val SINGLE_LINE_TRUE: Boolean = TRUE
32 |
33 | }
--------------------------------------------------------------------------------
/components/src/test/java/emperorfin/android/components/screenshots/ui/constants/BooleanConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.screenshots.ui.constants
18 |
19 |
20 | /*
21 | * @Author: Francis Nwokelo (emperorfin)
22 | * @Date: Monday 29th May, 2023.
23 | */
24 |
25 |
26 | object BooleanConstants {
27 |
28 | const val TRUE: Boolean = true
29 | const val FALSE: Boolean = false
30 |
31 | const val SINGLE_LINE_TRUE: Boolean = TRUE
32 |
33 | }
--------------------------------------------------------------------------------
/components/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 | #FFBB86FC
20 | #FF6200EE
21 | #FF3700B3
22 | #FF03DAC5
23 | #FF018786
24 | #FF000000
25 | #FFFFFFFF
26 |
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/ImeActionConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 | import androidx.compose.ui.text.input.ImeAction
20 |
21 |
22 | /**
23 | * @Author: Francis Nwokelo (emperorfin)
24 | * @Date: Thursday 30th March, 2023.
25 | */
26 |
27 |
28 | object ImeActionConstants {
29 |
30 | val IME_ACTION_NEXT: ImeAction = ImeAction.Next
31 |
32 | val IME_ACTION_DONE: ImeAction = ImeAction.Done
33 |
34 | }
--------------------------------------------------------------------------------
/components/src/test/java/emperorfin/android/components/ExampleUnitTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components
18 |
19 | import org.junit.Test
20 |
21 | import org.junit.Assert.*
22 |
23 | /**
24 | * Example local unit test, which will execute on the development machine (host).
25 | *
26 | * See [testing documentation](http://d.android.com/tools/testing).
27 | */
28 | class ExampleUnitTest {
29 | @Test
30 | fun addition_isCorrect() {
31 | assertEquals(4, 2 + 2)
32 | }
33 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/res/theme/Color.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.res.theme
18 |
19 | import androidx.compose.ui.graphics.Color
20 |
21 |
22 | /**
23 | * @Author: Francis Nwokelo (emperorfin)
24 | * @Date: November, 2022.
25 | */
26 |
27 |
28 |
29 | val Purple80 = Color(0xFFD0BCFF)
30 | val PurpleGrey80 = Color(0xFFCCC2DC)
31 | val Pink80 = Color(0xFFEFB8C8)
32 |
33 | val Purple40 = Color(0xFF6650a4)
34 | val PurpleGrey40 = Color(0xFF625b71)
35 | val Pink40 = Color(0xFF7D5260)
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/KeyboardTypeConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 | import androidx.compose.ui.text.input.KeyboardType
20 |
21 |
22 | /**
23 | * @Author: Francis Nwokelo (emperorfin)
24 | * @Date: Thursday 30th March, 2023.
25 | */
26 |
27 |
28 | object KeyboardTypeConstants {
29 |
30 | val KEYBOARD_TYPE_EMAIL: KeyboardType = KeyboardType.Email
31 |
32 | val KEYBOARD_TYPE_PASSWORD: KeyboardType = KeyboardType.Password
33 |
34 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/enums/PasswordRequirement.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.enums
18 |
19 | import androidx.annotation.StringRes
20 | import emperorfin.android.components.R
21 |
22 |
23 | /**
24 | * @Author: Francis Nwokelo (emperorfin)
25 | * @Date: November, 2022.
26 | */
27 |
28 |
29 | enum class PasswordRequirement(@StringRes val label: Int) {
30 |
31 | EIGHT_CHARACTERS(R.string.password_requirement_characters),
32 | CAPITAL_LETTER(R.string.password_requirement_capital),
33 | NUMBER(R.string.password_requirement_digit)
34 |
35 | }
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/VisualTransformationConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 | import androidx.compose.ui.text.input.PasswordVisualTransformation
20 | import androidx.compose.ui.text.input.VisualTransformation
21 |
22 |
23 | /**
24 | * @Author: Francis Nwokelo (emperorfin)
25 | * @Date: Friday 31st March, 2023.
26 | */
27 |
28 |
29 | object VisualTransformationConstants {
30 |
31 | val VISUAL_TRANSFORMATION_NONE: VisualTransformation = VisualTransformation.None
32 | val VISUAL_TRANSFORMATION_PASSWORD: VisualTransformation = PasswordVisualTransformation()
33 |
34 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/events/AuthenticationEvent.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.events
18 |
19 |
20 | /**
21 | * @Author: Francis Nwokelo (emperorfin)
22 | * @Date: November, 2022.
23 | */
24 |
25 |
26 | sealed class AuthenticationEvent {
27 |
28 | object AuthenticationToggleModeEvent : AuthenticationEvent()
29 |
30 | class EmailChangedEvent(val emailAddress: String) : AuthenticationEvent()
31 |
32 | class PasswordChangedEvent(val password: String) : AuthenticationEvent()
33 |
34 | object AuthenticateEvent : AuthenticationEvent()
35 |
36 | object ErrorDismissedEvent : AuthenticationEvent()
37 |
38 | }
39 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # AndroidX package structure to make it clearer which packages are bundled with the
15 | # Android operating system, and which are packaged with your app's APK
16 | # https://developer.android.com/topic/libraries/support-library/androidx-rn
17 | android.useAndroidX=true
18 | # Kotlin code style for this project: "official" or "obsolete":
19 | kotlin.code.style=official
20 | # Enables namespacing of each library's R class so that its R class includes only the
21 | # resources declared in the library itself and none from the library's dependencies,
22 | # thereby reducing the size of the R class for that library
23 | android.nonTransitiveRClass=true
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ExampleInstrumentedTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components
18 |
19 | import androidx.test.platform.app.InstrumentationRegistry
20 | import androidx.test.ext.junit.runners.AndroidJUnit4
21 |
22 | import org.junit.Test
23 | import org.junit.runner.RunWith
24 |
25 | import org.junit.Assert.*
26 |
27 | /**
28 | * Instrumented test, which will execute on an Android device.
29 | *
30 | * See [testing documentation](http://d.android.com/tools/testing).
31 | */
32 | @RunWith(AndroidJUnit4::class)
33 | class ExampleInstrumentedTest {
34 | @Test
35 | fun useAppContext() {
36 | // Context of the app under test.
37 | val appContext = InstrumentationRegistry.getInstrumentation().targetContext
38 | assertEquals("emperorfin.android.components.test", appContext.packageName)
39 | }
40 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/fortesting/TextSemantics.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.fortesting
18 |
19 | import androidx.compose.ui.semantics.SemanticsPropertyKey
20 | import androidx.compose.ui.semantics.SemanticsPropertyReceiver
21 |
22 |
23 | /**
24 | * @Author: Francis Nwokelo (emperorfin)
25 | * @Date: Monday 20th March, 2023.
26 | */
27 |
28 |
29 | /**
30 | * Docs on custom semantics: https://stackoverflow.com/a/71389302 or
31 | * https://stackoverflow.com/questions/68662342/jetpack-compose-testing-assert-specific-image-is-set
32 | */
33 |
34 | // TEXT
35 | val TextColorArgbSemantics = SemanticsPropertyKey("TextColorArgbSemantics")
36 | val TextColorResSemantics = SemanticsPropertyKey("TextColorResSemantics")
37 |
38 | var SemanticsPropertyReceiver.textColorArgb by TextColorArgbSemantics
39 | var SemanticsPropertyReceiver.textColorRes by TextColorResSemantics
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/ImageVectorConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 | import androidx.compose.material.icons.Icons
20 | import androidx.compose.material.icons.filled.*
21 | import androidx.compose.ui.graphics.vector.ImageVector
22 |
23 |
24 | /**
25 | * @Author: Francis Nwokelo (emperorfin)
26 | * @Date: Thursday 30th March, 2023.
27 | */
28 |
29 |
30 | object ImageVectorConstants {
31 |
32 | val IMAGE_VECTOR_ICONS_DEFAULT_EMAIL: ImageVector = Icons.Default.Email
33 |
34 | val IMAGE_VECTOR_ICONS_DEFAULT_LOCK: ImageVector = Icons.Default.Lock
35 |
36 | val IMAGE_VECTOR_ICONS_DEFAULT_CHECK: ImageVector = Icons.Default.Check
37 |
38 | val IMAGE_VECTOR_ICONS_DEFAULT_VISIBILITY: ImageVector = Icons.Default.Visibility
39 | val IMAGE_VECTOR_ICONS_DEFAULT_VISIBILITY_OFF: ImageVector = Icons.Default.VisibilityOff
40 |
41 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/res/FractionResources.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.res
18 |
19 | import android.content.Context
20 | import androidx.annotation.FractionRes
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.runtime.ReadOnlyComposable
23 | import androidx.compose.ui.platform.LocalContext
24 |
25 |
26 | /**
27 | * @Author: Francis Nwokelo (emperorfin)
28 | * @Date: November, 2022.
29 | */
30 |
31 |
32 | /**
33 | * Load fraction resource.
34 | *
35 | * Since there doesn't seem to be a float/double resource, the fraction resource can be used for
36 | * this purpose.
37 | *
38 | * @param id the resource identifier
39 | * @return the float associated with the resource
40 | */
41 | @Composable
42 | @ReadOnlyComposable
43 | fun fractionResource(@FractionRes id: Int): Float {
44 |
45 | val context: Context = LocalContext.current
46 |
47 | return context.resources.getFraction(id, ONE, ONE)
48 |
49 | }
50 |
51 | private const val ONE: Int = 1
--------------------------------------------------------------------------------
/components/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
20 | 6dp
21 | 16dp
22 | 8dp
23 | 16dp
24 | 32dp
25 |
26 | 4dp
27 | 8dp
28 |
29 | 16dp
30 | 12dp
31 | 32dp
32 | 40dp
33 |
34 | 12dp
35 | 8dp
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/fortesting/CircularProgressIndicatorSemantics.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.fortesting
18 |
19 | import androidx.compose.ui.semantics.SemanticsPropertyKey
20 | import androidx.compose.ui.semantics.SemanticsPropertyReceiver
21 |
22 |
23 | /**
24 | * @Author: Francis Nwokelo (emperorfin)
25 | * @Date: Monday 20th March, 2023.
26 | */
27 |
28 |
29 | /**
30 | * Docs on custom semantics: https://stackoverflow.com/a/71389302 or
31 | * https://stackoverflow.com/questions/68662342/jetpack-compose-testing-assert-specific-image-is-set
32 | */
33 |
34 | // CIRCULAR PROGRESS INDICATOR
35 | val CircularProgressIndicatorColorArgbSemantics = SemanticsPropertyKey("CircularProgressIndicatorColorArgbSemantics")
36 | val CircularProgressIndicatorColorResSemantics = SemanticsPropertyKey("CircularProgressIndicatorColorResSemantics")
37 |
38 | var SemanticsPropertyReceiver.circularProgressIndicatorColorArgb by CircularProgressIndicatorColorArgbSemantics
39 | var SemanticsPropertyReceiver.circularProgressIndicatorColorRes by CircularProgressIndicatorColorResSemantics
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/LongConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 | import androidx.compose.ui.test.junit4.ComposeContentTestRule
20 |
21 |
22 | /**
23 | * @Author: Francis Nwokelo (emperorfin)
24 | * @Date: Tuesday 21st March, 2023.
25 | */
26 |
27 |
28 | object LongConstants {
29 |
30 | /**
31 | * Since the delay time millis in the authenticate() function in app/src/main/java/emperorfin/android/militaryjet/ui/screens/authentication/stateholders/AuthenticationViewModel.kt
32 | * is 2000L, the [ComposeContentTestRule.waitUntil] test API timeoutMillis should be
33 | * something greater than 2_000L such as 2_010L or 2_500L instead of the default 1_000L
34 | * value.
35 | *
36 | * For scenarios where the amount of time an operation such as network call is unknown, you
37 | * should keep increasing the timeoutMillis of the [ComposeContentTestRule.waitUntil] until
38 | * the test doesn't throw any error or exception related to timeout exception.
39 | */
40 | const val TIMEOUT_MILLIS_2500L: Long = 2_500L
41 |
42 | }
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/StringConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 |
20 | /**
21 | * @Author: Francis Nwokelo (emperorfin)
22 | * @Date: Tuesday 21st March, 2023.
23 | */
24 |
25 |
26 | object StringConstants {
27 |
28 | const val THIS_STRING_MUST_BE_EMPTY: String = ""
29 | const val THIS_STRING_COULD_BE_ANYTHING: String = ""
30 |
31 | const val INPUT_CONTENT_EMAIL_EMPTY: String = ""
32 | const val INPUT_CONTENT_EMAIL: String = "contact@email.com"
33 | const val INPUT_CONTENT_EMAIL_PREFIXED: String = "contact@email.co"
34 | const val INPUT_CONTENT_EMAIL_SUFFIXED: String = ".uk"
35 |
36 | const val INPUT_CONTENT_PASSWORD_EMPTY: String = ""
37 | const val INPUT_CONTENT_PASSWORD: String = "passworD1"
38 | const val INPUT_CONTENT_PASSWORD_PASSWORD: String = "password"
39 | const val INPUT_CONTENT_PASSWORD_PASS: String = "Pass"
40 | const val INPUT_CONTENT_PASSWORD_1PASS: String = "1pass"
41 | const val INPUT_CONTENT_PREFIXED: String = "passworD"
42 | const val INPUT_CONTENT_SUFFIXED: String = "12345"
43 |
44 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/fortesting/AlertDialogSemantics.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.fortesting
18 |
19 | import androidx.compose.ui.semantics.SemanticsPropertyKey
20 | import androidx.compose.ui.semantics.SemanticsPropertyReceiver
21 |
22 |
23 | /**
24 | * @Author: Francis Nwokelo (emperorfin)
25 | * @Date: Monday 20th March, 2023.
26 | */
27 |
28 |
29 | /**
30 | * Docs on custom semantics: https://stackoverflow.com/a/71389302 or
31 | * https://stackoverflow.com/questions/68662342/jetpack-compose-testing-assert-specific-image-is-set
32 | */
33 |
34 | // ALERT DIALOG
35 | val AlertDialogTitleSemantics = SemanticsPropertyKey("AlertDialogTitleSemantics")
36 | val AlertDialogTextSemantics = SemanticsPropertyKey("AlertDialogTextSemantics")
37 | val AlertDialogConfirmButtonTextSemantics = SemanticsPropertyKey("AlertDialogConfirmButtonTextSemantics")
38 |
39 | var SemanticsPropertyReceiver.alertDialogTitle by AlertDialogTitleSemantics
40 | var SemanticsPropertyReceiver.alertDialogText by AlertDialogTextSemantics
41 | var SemanticsPropertyReceiver.alertDialogConfirmButtonText by AlertDialogConfirmButtonTextSemantics
--------------------------------------------------------------------------------
/app/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
15 |
18 |
21 |
22 |
23 |
24 |
30 |
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/res/theme/Type.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.res.theme
18 |
19 | import androidx.compose.material3.Typography
20 | import androidx.compose.ui.text.TextStyle
21 | import androidx.compose.ui.text.font.FontFamily
22 | import androidx.compose.ui.text.font.FontWeight
23 | import androidx.compose.ui.unit.sp
24 |
25 |
26 | /**
27 | * @Author: Francis Nwokelo (emperorfin)
28 | * @Date: November, 2022.
29 | */
30 |
31 |
32 |
33 | // Set of Material typography styles to start with
34 | val Typography = Typography(
35 | bodyLarge = TextStyle(
36 | fontFamily = FontFamily.Default,
37 | fontWeight = FontWeight.Normal,
38 | fontSize = 16.sp,
39 | lineHeight = 24.sp,
40 | letterSpacing = 0.5.sp
41 | )
42 | /* Other default text styles to override
43 | titleLarge = TextStyle(
44 | fontFamily = FontFamily.Default,
45 | fontWeight = FontWeight.Normal,
46 | fontSize = 22.sp,
47 | lineHeight = 28.sp,
48 | letterSpacing = 0.sp
49 | ),
50 | labelSmall = TextStyle(
51 | fontFamily = FontFamily.Default,
52 | fontWeight = FontWeight.Medium,
53 | fontSize = 11.sp,
54 | lineHeight = 16.sp,
55 | letterSpacing = 0.5.sp
56 | )
57 | */
58 | )
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/fortesting/IconSemantics.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.fortesting
18 |
19 | import androidx.compose.ui.graphics.vector.ImageVector
20 | import androidx.compose.ui.semantics.SemanticsPropertyKey
21 | import androidx.compose.ui.semantics.SemanticsPropertyReceiver
22 |
23 |
24 | /**
25 | * @Author: Francis Nwokelo (emperorfin)
26 | * @Date: Monday 20th March, 2023.
27 | */
28 |
29 |
30 | /**
31 | * Docs on custom semantics: https://stackoverflow.com/a/71389302 or
32 | * https://stackoverflow.com/questions/68662342/jetpack-compose-testing-assert-specific-image-is-set
33 | */
34 |
35 | // ICON
36 | val IconImageVectorSemantics = SemanticsPropertyKey("IconImageVectorSemantics")
37 | val IconContentDescriptionSemantics = SemanticsPropertyKey("IconContentDescriptionSemantics")
38 | val IconTintArgbSemantics = SemanticsPropertyKey("IconTintArgbSemantics")
39 | val IconTintResSemantics = SemanticsPropertyKey("IconTintResSemantics")
40 |
41 | var SemanticsPropertyReceiver.iconImageVector by IconImageVectorSemantics
42 | var SemanticsPropertyReceiver.iconContentDescription by IconContentDescriptionSemantics
43 | var SemanticsPropertyReceiver.iconTintArgb by IconTintArgbSemantics
44 | var SemanticsPropertyReceiver.iconTintRes by IconTintResSemantics
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
20 |
21 |
31 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
45 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/extensions/semanticsmatcher/AlertDialogSemanticsMatchers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.extensions.semanticsmatcher
18 |
19 | import androidx.compose.ui.test.SemanticsMatcher
20 | import emperorfin.android.components.ui.fortesting.AlertDialogConfirmButtonTextSemantics
21 | import emperorfin.android.components.ui.fortesting.AlertDialogTextSemantics
22 | import emperorfin.android.components.ui.fortesting.AlertDialogTitleSemantics
23 |
24 |
25 | /**
26 | * @Author: Francis Nwokelo (emperorfin)
27 | * @Date: Monday 20th March, 2023.
28 | */
29 |
30 |
31 | /**
32 | * Docs on custom semantics: https://stackoverflow.com/a/71389302 or
33 | * https://stackoverflow.com/questions/68662342/jetpack-compose-testing-assert-specific-image-is-set
34 | */
35 |
36 | // ALERT DIALOG
37 | fun hasAlertDialogTitle(alertDialogTitle: String): SemanticsMatcher =
38 | SemanticsMatcher.expectValue(AlertDialogTitleSemantics, alertDialogTitle)
39 |
40 | fun hasAlertDialogText(alertDialogText: String): SemanticsMatcher =
41 | SemanticsMatcher.expectValue(AlertDialogTextSemantics, alertDialogText)
42 |
43 | /**
44 | * TODO: Rename [hasAlertDialogConfirmButtonText] to hasAlertDialogConfirmButtonAction
45 | */
46 | fun hasAlertDialogConfirmButtonText(alertDialogConfirmButtonText: String): SemanticsMatcher =
47 | SemanticsMatcher.expectValue(AlertDialogConfirmButtonTextSemantics, alertDialogConfirmButtonText)
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/stateholders/AuthenticationUiState.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.stateholders
18 |
19 | import androidx.annotation.StringRes
20 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode
21 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_IN
22 | import emperorfin.android.components.ui.screens.authentication.enums.PasswordRequirement
23 |
24 |
25 | /**
26 | * @Author: Francis Nwokelo (emperorfin)
27 | * @Date: November, 2022.
28 | */
29 |
30 |
31 | //@Immutable
32 | data class AuthenticationUiState(
33 | val authenticationMode: AuthenticationMode = SIGN_IN,
34 | val email: String? = null,
35 | val password: String? = null,
36 | val passwordRequirements: List = emptyList(),
37 | val isLoading: Boolean = FALSE,
38 | // val error: String? = null
39 | @StringRes val error: Int? = null
40 | ) {
41 |
42 | companion object {
43 |
44 | // BOOLEAN
45 | private const val TRUE: Boolean = true
46 | private const val FALSE: Boolean = false
47 |
48 | }
49 |
50 | fun checkFormValidity(): Boolean {
51 |
52 | return password?.isNotEmpty() == TRUE &&
53 | email?.isNotEmpty() == TRUE &&
54 | (authenticationMode == SIGN_IN || passwordRequirements.containsAll(
55 | PasswordRequirement.values().toList()
56 | ))
57 |
58 | }
59 |
60 | }
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/extensions/semanticsmatcher/TextSemanticsMatchers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.extensions.semanticsmatcher
18 |
19 | import androidx.annotation.IntegerRes
20 | import androidx.compose.ui.test.SemanticsMatcher
21 | import emperorfin.android.components.ui.fortesting.TextColorArgbSemantics
22 | import emperorfin.android.components.ui.fortesting.TextColorResSemantics
23 |
24 |
25 | /**
26 | * @Author: Francis Nwokelo (emperorfin)
27 | * @Date: Monday 20th March, 2023.
28 | */
29 |
30 |
31 | /**
32 | * Docs on custom semantics: https://stackoverflow.com/a/71389302 or
33 | * https://stackoverflow.com/questions/68662342/jetpack-compose-testing-assert-specific-image-is-set
34 | */
35 |
36 | // TEXT
37 | /**
38 | * To easily find out the ARGB of a color to be used as the expected value during assertion,
39 | * there are two approaches to do this:
40 | *
41 | * - you can either call toArgb() on the color object and then logcat the returned value or
42 | * - you can just use any random assertion expected value, run the test case (which should
43 | * fail) and then get and use (as the correct assertion expected value) the assertion actual
44 | * value from the failed test error message (simplest approach).
45 | */
46 | fun hasTextColorArgb(textColorInArgb: Int): SemanticsMatcher =
47 | SemanticsMatcher.expectValue(TextColorArgbSemantics, textColorInArgb)
48 |
49 | fun hasTextColorRes(@IntegerRes textColorRes: Int): SemanticsMatcher =
50 | SemanticsMatcher.expectValue(TextColorResSemantics, textColorRes)
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/PasswordRequirements.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.foundation.layout.Column
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.platform.testTag
23 | import androidx.compose.ui.res.stringResource
24 | import androidx.compose.ui.tooling.preview.Preview
25 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
26 | import emperorfin.android.components.ui.screens.authentication.enums.PasswordRequirement
27 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_PASSWORD_REQUIREMENTS
28 |
29 |
30 | /**
31 | * @Author: Francis Nwokelo (emperorfin)
32 | * @Date: November, 2022.
33 | */
34 |
35 |
36 | @Composable
37 | fun PasswordRequirements(
38 | modifier: Modifier = Modifier,
39 | satisfiedRequirements: List
40 | ) {
41 |
42 | Column(
43 | modifier = modifier.testTag(tag = TAG_AUTHENTICATION_PASSWORD_REQUIREMENTS)
44 | ) {
45 | PasswordRequirement.values().forEach { requirement ->
46 | Requirement(
47 | message = stringResource(id = requirement.label),
48 | satisfied = satisfiedRequirements.contains(requirement)
49 | )
50 | }
51 | }
52 |
53 | }
54 |
55 | @Composable
56 | @Preview(showBackground = TRUE)
57 | private fun PasswordRequirementsPreview() {
58 |
59 | ComposeEmailAuthenticationTheme {
60 | PasswordRequirements(satisfiedRequirements = emptyList())
61 | }
62 |
63 | }
64 |
65 | private const val TRUE: Boolean = true
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/extensions/semanticsmatcher/CircularProgressIndicatorSemanticsMatchers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.extensions.semanticsmatcher
18 |
19 | import androidx.annotation.IntegerRes
20 | import androidx.compose.ui.test.SemanticsMatcher
21 | import emperorfin.android.components.ui.fortesting.CircularProgressIndicatorColorArgbSemantics
22 | import emperorfin.android.components.ui.fortesting.CircularProgressIndicatorColorResSemantics
23 |
24 |
25 | /**
26 | * @Author: Francis Nwokelo (emperorfin)
27 | * @Date: Monday 20th March, 2023.
28 | */
29 |
30 |
31 | /**
32 | * Docs on custom semantics: https://stackoverflow.com/a/71389302 or
33 | * https://stackoverflow.com/questions/68662342/jetpack-compose-testing-assert-specific-image-is-set
34 | */
35 |
36 | // CIRCULAR PROGRESS INDICATOR
37 | /**
38 | * To easily find out the ARGB of a color to be used as the expected value during assertion,
39 | * there are two approaches to do this:
40 | *
41 | * - you can either call toArgb() on the color object and then logcat the returned value or
42 | * - you can just use any random assertion expected value, run the test case (which should
43 | * fail) and then get and use (as the correct assertion expected value) the assertion actual
44 | * value from the failed test error message (simplest approach).
45 | */
46 | fun hasCircularProgressIndicatorColorArgb(circularProgressIndicatorColorInArgb: Int): SemanticsMatcher =
47 | SemanticsMatcher.expectValue(CircularProgressIndicatorColorArgbSemantics, circularProgressIndicatorColorInArgb)
48 |
49 | fun hasCircularProgressIndicatorColorRes(@IntegerRes circularProgressIndicatorColorRes: Int): SemanticsMatcher =
50 | SemanticsMatcher.expectValue(CircularProgressIndicatorColorResSemantics, circularProgressIndicatorColorRes)
51 |
52 |
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/extensions/ComposeContentTestRuleExtensions.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.extensions
18 |
19 | import androidx.compose.ui.test.SemanticsMatcher
20 | import androidx.compose.ui.test.junit4.ComposeContentTestRule
21 |
22 |
23 | /**
24 | * @Author: Francis Nwokelo (emperorfin)
25 | * @Date: Thursday 24th November, 2022.
26 | */
27 |
28 |
29 | fun ComposeContentTestRule.waitUntilNodeCount(
30 | matcher: SemanticsMatcher,
31 | count: Int,
32 | timeoutMillis: Long = TIMEOUT_MILLIS_1000L,
33 | useUnmergedTree: Boolean = FALSE
34 | ) {
35 |
36 | this.waitUntil(timeoutMillis = timeoutMillis) {
37 | this.onAllNodes(
38 | matcher = matcher,
39 | useUnmergedTree = useUnmergedTree
40 | ).fetchSemanticsNodes().size == count
41 | }
42 |
43 | }
44 |
45 | fun ComposeContentTestRule.waitUntilExists(
46 | matcher: SemanticsMatcher,
47 | timeoutMillis: Long = TIMEOUT_MILLIS_1000L,
48 | useUnmergedTree: Boolean = FALSE
49 | ) {
50 |
51 | return this.waitUntilNodeCount(
52 | matcher = matcher,
53 | count = ONE,
54 | timeoutMillis = timeoutMillis,
55 | useUnmergedTree = useUnmergedTree
56 | )
57 |
58 | }
59 |
60 | fun ComposeContentTestRule.waitUntilDoesNotExist(
61 | matcher: SemanticsMatcher,
62 | timeoutMillis: Long = TIMEOUT_MILLIS_1000L,
63 | useUnmergedTree: Boolean = FALSE
64 | ) {
65 |
66 | return this.waitUntilNodeCount(
67 | matcher = matcher,
68 | count = ZERO,
69 | timeoutMillis = timeoutMillis,
70 | useUnmergedTree = useUnmergedTree
71 | )
72 |
73 | }
74 |
75 | private const val FALSE: Boolean = false
76 |
77 | private const val ZERO: Int = 0
78 | private const val ONE: Int = 1
79 |
80 | private const val TIMEOUT_MILLIS_1000L: Long = 1_000L
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/MainActivity.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens
18 |
19 | import android.os.Bundle
20 | import androidx.activity.ComponentActivity
21 | import androidx.activity.compose.setContent
22 | import androidx.compose.foundation.layout.fillMaxSize
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.Surface
25 | import androidx.compose.material3.Text
26 | import androidx.compose.runtime.Composable
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.tooling.preview.Preview
29 | import emperorfin.android.components.ui.screens.authentication.AuthenticationScreen
30 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
31 |
32 |
33 | /**
34 | * @Author: Francis Nwokelo (emperorfin)
35 | * @Date: November, 2022.
36 | */
37 |
38 |
39 | class MainActivity : ComponentActivity() {
40 | override fun onCreate(savedInstanceState: Bundle?) {
41 | super.onCreate(savedInstanceState)
42 | setContent {
43 | ComposeEmailAuthenticationTheme {
44 | // A surface container using the 'background' color from the theme
45 | Surface(
46 | modifier = Modifier.fillMaxSize(),
47 | color = MaterialTheme.colorScheme.background
48 | ) {
49 | // Greeting("Android")
50 | AuthenticationScreen()
51 | }
52 | }
53 | }
54 | }
55 |
56 | @Composable
57 | fun Greeting(name: String) {
58 | Text(text = "Hello $name!")
59 | }
60 |
61 | @Preview(showBackground = true)
62 | @Composable
63 | fun DefaultPreview() {
64 | ComposeEmailAuthenticationTheme {
65 | Greeting("Android")
66 | }
67 | }
68 |
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/AuthenticationScreen.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication
18 |
19 | import androidx.compose.foundation.layout.Box
20 | import androidx.compose.foundation.layout.fillMaxWidth
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.runtime.collectAsState
23 | import androidx.compose.ui.Alignment
24 | import androidx.compose.ui.Modifier
25 | import androidx.compose.ui.platform.testTag
26 | import androidx.compose.ui.tooling.preview.Preview
27 | import androidx.lifecycle.viewmodel.compose.viewModel
28 | import emperorfin.android.components.ui.screens.authentication.stateholders.AuthenticationViewModel
29 | import emperorfin.android.components.ui.screens.authentication.uicomponents.AuthenticationContent
30 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
31 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_SCREEN
32 |
33 |
34 | /**
35 | * @Author: Francis Nwokelo (emperorfin)
36 | * @Date: November, 2022.
37 | */
38 |
39 |
40 | @Composable
41 | fun AuthenticationScreen() {
42 | // MaterialTheme {
43 | //
44 | // }
45 |
46 | val viewModel: AuthenticationViewModel = viewModel()
47 |
48 | /**
49 | * The [AuthenticationContent] composable was wrapped inside of [Box] in order to attach a test
50 | * tag to the [AuthenticationScreen] composable.
51 | */
52 | Box(
53 | modifier = Modifier.testTag(TAG_AUTHENTICATION_SCREEN),
54 | contentAlignment = Alignment.Center
55 | ) {
56 | AuthenticationContent(
57 | modifier = Modifier.fillMaxWidth(),
58 | uiState = viewModel.uiState.collectAsState().value,
59 | handleEvent = viewModel::handleEvent
60 | )
61 | }
62 | }
63 |
64 | @Composable
65 | @Preview(showBackground = true)
66 | private fun AuthenticationScreenPreview() {
67 | ComposeEmailAuthenticationTheme {
68 | AuthenticationScreen()
69 | }
70 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/AuthenticationTitle.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.material3.Text
20 | import androidx.compose.runtime.Composable
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.platform.testTag
23 | import androidx.compose.ui.res.stringResource
24 | import androidx.compose.ui.text.font.FontWeight
25 | import androidx.compose.ui.tooling.preview.Preview
26 | import androidx.compose.ui.unit.TextUnit
27 | import androidx.compose.ui.unit.sp
28 | import emperorfin.android.components.R
29 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode
30 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_IN
31 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
32 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_AUTHENTICATION_TITLE
33 |
34 |
35 | /**
36 | * @Author: Francis Nwokelo (emperorfin)
37 | * @Date: November, 2022.
38 | */
39 |
40 |
41 | @Composable
42 | fun AuthenticationTitle(
43 | modifier: Modifier = Modifier,
44 | authenticationMode: AuthenticationMode
45 | ) {
46 |
47 | Text(
48 | modifier = Modifier
49 | .testTag(tag = TAG_AUTHENTICATION_AUTHENTICATION_TITLE),
50 | text = stringResource(
51 | id = if (authenticationMode == SIGN_IN) {
52 | R.string.label_sign_in_to_account
53 | } else {
54 | R.string.label_sign_up_for_account
55 | }
56 | ),
57 | fontSize = FONT_SIZE_SP_24,
58 | fontWeight = FontWeight.Black
59 | )
60 |
61 | }
62 |
63 | @Composable
64 | @Preview(showBackground = true)
65 | private fun AuthenticationTitlePreview() {
66 | ComposeEmailAuthenticationTheme {
67 | AuthenticationTitle(
68 | authenticationMode = SIGN_IN
69 | )
70 | }
71 | }
72 |
73 | private val FONT_SIZE_SP_24: TextUnit = 24.sp
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/extensions/semanticsmatcher/IconSemanticsMatchers.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.extensions.semanticsmatcher
18 |
19 | import androidx.annotation.IntegerRes
20 | import androidx.compose.ui.graphics.vector.ImageVector
21 | import androidx.compose.ui.test.SemanticsMatcher
22 | import emperorfin.android.components.ui.fortesting.IconContentDescriptionSemantics
23 | import emperorfin.android.components.ui.fortesting.IconImageVectorSemantics
24 | import emperorfin.android.components.ui.fortesting.IconTintArgbSemantics
25 | import emperorfin.android.components.ui.fortesting.IconTintResSemantics
26 |
27 |
28 | /**
29 | * @Author: Francis Nwokelo (emperorfin)
30 | * @Date: Monday 20th March, 2023.
31 | */
32 |
33 |
34 | /**
35 | * Docs on custom semantics: https://stackoverflow.com/a/71389302 or
36 | * https://stackoverflow.com/questions/68662342/jetpack-compose-testing-assert-specific-image-is-set
37 | */
38 |
39 | // ICON
40 | fun hasIconImageVector(iconImageVector: ImageVector): SemanticsMatcher =
41 | SemanticsMatcher.expectValue(IconImageVectorSemantics, iconImageVector)
42 |
43 | fun hasIconContentDescription(iconContentDescription: String): SemanticsMatcher =
44 | SemanticsMatcher.expectValue(IconContentDescriptionSemantics, iconContentDescription)
45 |
46 | /**
47 | * To easily find out the ARGB of a color to be used as the expected value during assertion,
48 | * there are two approaches to do this:
49 | *
50 | * - you can either call toArgb() on the color object and then logcat the returned value or
51 | * - you can just use any random assertion expected value, run the test case (which should
52 | * fail) and then get and use (as the correct assertion expected value) the assertion actual
53 | * value from the failed test error message (simplest approach).
54 | */
55 | fun hasIconTintArgb(iconTintInArgb: Int): SemanticsMatcher =
56 | SemanticsMatcher.expectValue(IconTintArgbSemantics, iconTintInArgb)
57 |
58 | fun hasIconTintRes(@IntegerRes iconTintRes: Int): SemanticsMatcher =
59 | SemanticsMatcher.expectValue(IconTintResSemantics, iconTintRes)
--------------------------------------------------------------------------------
/components/src/test/java/emperorfin/android/components/screenshots/ui/screens/authentication/uicomponents/AuthenticationErrorDialogTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.graphics.Color
23 | import app.cash.paparazzi.DeviceConfig
24 | import app.cash.paparazzi.Paparazzi
25 | import com.android.ide.common.rendering.api.SessionParams
26 | import emperorfin.android.components.screenshots.ui.constants.BooleanConstants
27 | import org.junit.Rule
28 | import emperorfin.android.components.screenshots.ui.constants.StringResourceConstants.MAIN_SOURCE_SET_STRING_RES_TEST_ERROR_MESSAGE
29 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
30 | import emperorfin.android.components.ui.screens.authentication.uicomponents.AuthenticationErrorDialog
31 | import org.junit.Test
32 |
33 |
34 | /*
35 | * @Author: Francis Nwokelo (emperorfin)
36 | * @Date: Thursday 01th June, 2023.
37 | */
38 |
39 |
40 | class AuthenticationErrorDialogTest {
41 |
42 | /**
43 | * Passing [SessionParams.RenderingMode.SHRINK] as the value assigned to the renderingMode
44 | * parameter while instantiating [Paparazzi] would cause the [dialog_Displays] test case to fail.
45 | */
46 | @get:Rule
47 | val paparazzi = Paparazzi(
48 | deviceConfig = DeviceConfig.PIXEL_3,
49 | // renderingMode = SessionParams.RenderingMode.SHRINK,
50 | showSystemUi = false
51 | )
52 |
53 | @Test
54 | fun dialog_Displays() {
55 |
56 | paparazzi.snapshot {
57 | ComposeEmailAuthenticationTheme(isForScreenshotTest = BooleanConstants.TRUE) {
58 | Box(Modifier.background(Color.White)) {
59 | AuthenticationErrorDialog(
60 | error = MAIN_SOURCE_SET_STRING_RES_TEST_ERROR_MESSAGE,
61 | onDismissError = { }
62 | )
63 | }
64 | }
65 | }
66 |
67 | }
68 |
69 | }
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/ColorArgbConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 |
20 | /**
21 | * @Author: Francis Nwokelo (emperorfin)
22 | * @Date: Tuesday 21st March, 2023.
23 | */
24 |
25 |
26 | object ColorArgbConstants {
27 |
28 | /**
29 | * To easily find out the ARGB of a color to be used as the expected value during assertion,
30 | * there are two approaches to do this:
31 | *
32 | * - you can either call toArgb() on the color object and then logcat the returned value or
33 | * - you can just use any random assertion expected value, run the test case (which should
34 | * fail) and then get and use (as the correct assertion expected value) the assertion actual
35 | * value from the failed test error message (simplest approach).
36 | */
37 | const val COLOR_ARGB_TINT_PRIMARY: Int = -11576430
38 | /**
39 | * To easily find out the ARGB of a color to be used as the expected value during assertion,
40 | * there are two approaches to do this:
41 | *
42 | * - you can either call toArgb() on the color object and then logcat the returned value or
43 | * - you can just use any random assertion expected value, run the test case (which should
44 | * fail) and then get and use (as the correct assertion expected value) the assertion actual
45 | * value from the failed test error message (simplest approach).
46 | */
47 | const val COLOR_ARGB_TINT_ON_SURFACE: Int = 1713052447
48 |
49 | /**
50 | * To easily find out the ARGB of a color to be used as the expected value during assertion,
51 | * there are two approaches to do this:
52 | *
53 | * - you can either call toArgb() on the color object and then logcat the returned value or
54 | * - you can just use any random assertion expected value, run the test case (which should
55 | * fail) and then get and use (as the correct assertion expected value) the assertion actual
56 | * value from the failed test error message (simplest approach).
57 | */
58 | const val COLOR_ARGB_CIRCULAR_PROGRESS_INDICATOR_PRESET_COLOR: Int = -11576430
59 |
60 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/AuthenticationButton.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.material3.Button
20 | import androidx.compose.material3.Text
21 | import androidx.compose.runtime.Composable
22 | import androidx.compose.ui.Modifier
23 | import androidx.compose.ui.platform.testTag
24 | import androidx.compose.ui.res.stringResource
25 | import androidx.compose.ui.tooling.preview.Preview
26 | import emperorfin.android.components.R
27 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode
28 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_IN
29 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_UP
30 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
31 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_AUTHENTICATE_BUTTON
32 |
33 |
34 | /**
35 | * @Author: Francis Nwokelo (emperorfin)
36 | * @Date: November, 2022.
37 | */
38 |
39 |
40 | @Composable
41 | fun AuthenticationButton(
42 | modifier: Modifier = Modifier,
43 | authenticationMode: AuthenticationMode,
44 | enableAuthentication: Boolean,
45 | onAuthenticate: () -> Unit
46 | ) {
47 |
48 | Button(
49 | modifier = modifier.testTag(TAG_AUTHENTICATION_AUTHENTICATE_BUTTON),
50 | onClick = {
51 | onAuthenticate()
52 | },
53 | enabled = enableAuthentication
54 | ) {
55 | Text(
56 | text = stringResource(
57 | id = if (authenticationMode == SIGN_IN) {
58 | R.string.action_sign_in
59 | } else {
60 | R.string.action_sign_up
61 | }
62 | )
63 | )
64 | }
65 |
66 | }
67 |
68 | @Composable
69 | @Preview(showBackground = true)
70 | private fun AuthenticationButtonPreview() {
71 |
72 | ComposeEmailAuthenticationTheme {
73 | AuthenticationButton(
74 | authenticationMode = SIGN_UP,
75 | enableAuthentication = TRUE,
76 | onAuthenticate = {}
77 | )
78 | }
79 |
80 | }
81 |
82 | private const val TRUE: Boolean = true
--------------------------------------------------------------------------------
/components/src/test/java/emperorfin/android/components/screenshots/HelloComposeTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.screenshots
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Column
21 | import androidx.compose.foundation.layout.fillMaxSize
22 | import androidx.compose.foundation.layout.wrapContentSize
23 | import androidx.compose.material3.Text
24 | import androidx.compose.runtime.Composable
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.graphics.Color
27 | import androidx.compose.ui.text.TextStyle
28 | import androidx.compose.ui.text.font.FontFamily
29 | import androidx.compose.ui.text.font.FontWeight
30 | import androidx.compose.ui.text.style.TextDecoration
31 | import app.cash.paparazzi.Paparazzi
32 | import org.junit.Rule
33 | import org.junit.Test
34 |
35 |
36 | /*
37 | * @Author: Francis Nwokelo (emperorfin)
38 | * @Date: Thursday 18th May, 2023.
39 | */
40 |
41 |
42 | class HelloComposeTest {
43 | @get:Rule
44 | val paparazzi = Paparazzi()
45 |
46 | // @Test
47 | // fun compose() {
48 | // paparazzi.snapshot { HelloPaparazzi() }
49 | // }
50 |
51 | @Test
52 | fun screenshot_Hello_Paparazzi() {
53 | paparazzi.snapshot { HelloPaparazzi() }
54 | }
55 | }
56 |
57 | @Suppress("TestFunctionName")
58 | @Composable
59 | fun HelloPaparazzi() {
60 | val text = "Hello, Paparazzi"
61 | Column(
62 | Modifier
63 | .background(Color.White)
64 | .fillMaxSize()
65 | .wrapContentSize()
66 | ) {
67 | Text(text)
68 | Text(text, style = TextStyle(fontFamily = FontFamily.Cursive))
69 | Text(
70 | text = text,
71 | style = TextStyle(textDecoration = TextDecoration.LineThrough)
72 | )
73 | Text(
74 | text = text,
75 | style = TextStyle(textDecoration = TextDecoration.Underline)
76 | )
77 | Text(
78 | text = text,
79 | style = TextStyle(
80 | textDecoration = TextDecoration.combine(
81 | listOf(
82 | TextDecoration.Underline,
83 | TextDecoration.LineThrough
84 | )
85 | ),
86 | fontWeight = FontWeight.Bold
87 | )
88 | )
89 | }
90 | }
--------------------------------------------------------------------------------
/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 |
17 | @if "%DEBUG%" == "" @echo off
18 | @rem ##########################################################################
19 | @rem
20 | @rem Gradle startup script for Windows
21 | @rem
22 | @rem ##########################################################################
23 |
24 | @rem Set local scope for the variables with windows NT shell
25 | if "%OS%"=="Windows_NT" setlocal
26 |
27 | set DIRNAME=%~dp0
28 | if "%DIRNAME%" == "" set DIRNAME=.
29 | set APP_BASE_NAME=%~n0
30 | set APP_HOME=%DIRNAME%
31 |
32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter.
33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
34 |
35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
37 |
38 | @rem Find java.exe
39 | if defined JAVA_HOME goto findJavaFromJavaHome
40 |
41 | set JAVA_EXE=java.exe
42 | %JAVA_EXE% -version >NUL 2>&1
43 | if "%ERRORLEVEL%" == "0" goto execute
44 |
45 | echo.
46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
47 | echo.
48 | echo Please set the JAVA_HOME variable in your environment to match the
49 | echo location of your Java installation.
50 |
51 | goto fail
52 |
53 | :findJavaFromJavaHome
54 | set JAVA_HOME=%JAVA_HOME:"=%
55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
56 |
57 | if exist "%JAVA_EXE%" goto execute
58 |
59 | echo.
60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
61 | echo.
62 | echo Please set the JAVA_HOME variable in your environment to match the
63 | echo location of your Java installation.
64 |
65 | goto fail
66 |
67 | :execute
68 | @rem Setup the command line
69 |
70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
71 |
72 |
73 | @rem Execute Gradle
74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
75 |
76 | :end
77 | @rem End local scope for the variables with windows NT shell
78 | if "%ERRORLEVEL%"=="0" goto mainEnd
79 |
80 | :fail
81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
82 | rem the _cmd.exe /c_ return code!
83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
84 | exit /b 1
85 |
86 | :mainEnd
87 | if "%OS%"=="Windows_NT" endlocal
88 |
89 | :omega
90 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MilitaryJet
2 |
3 | [](https://androidweekly.net/issues/issue-563)
4 |
5 | This repo includes production ready samples that showcase my unopinionated way of simply writing
6 | military-grade bug-proof Jetpack Compose UI tests. EVERY single UI component, their possible
7 | behaviours and states were all tested!
8 |
9 | With this repo, you can perform any kind of automated Jetpack Compose UI testing - i.e. you can test
10 | ANYTHING (both visible and invisible (i.e. things that are not visible on the UI but exists in the
11 | node - e.g. content description)) on the UI. For example, you can test whether or not a TextField's
12 | trailing icon and/or it's content description displays/exists on the UI.
13 |
14 | If there is anything that's not covered that you want to test, you can be able to create custom
15 | semantics in order to test ANYTHING on a Jetpack Compose UI!
16 |
17 | So, READY! SET!! [GO](https://github.com/emperorfin/MilitaryJet/tree/master/app/src/androidTest)!!!
18 |
19 | ## Project Tech-stack and Characteristics
20 |
21 | * Android Framework
22 | * Kotlin
23 | * Jetpack Compose
24 | * Jetpack Compose Material Design 3 Components
25 | * Jetpack Compose Material Design Extended Icons
26 | * ViewModel
27 | * Kotlin Coroutine
28 | * StateFlow
29 | * Reactive Programing
30 | * UI Testing and Individual UI Component Testing
31 | * Architecting Jetpack Compose UI Tests
32 | * Composable Previews
33 | * Extension Functions
34 | * Custom semantics (for testing almost anything on the UI such as for asserting whether the leading
35 | icon of a TextField is displayed)
36 |
37 | ## Todo
38 |
39 | - [ ] Screenshot testing using Paparazzi including CI setup
40 | - [ ] More...
41 |
42 | ## Important Note
43 | - Always check and use the highest numbered version of a test case function as that is, in my opinion,
44 | the recommended one to use. But you can choose which ever version of the test case function you preferred.
45 | - For a more structured way of testing, it's highly recommended to use the test classes with the
46 | highest number suffixed on the file/class name.
47 |
48 | ## Why MilitaryJet?
49 |
50 | The name MilitaryJet has NO connection whatsoever with a military/army/jet fighters. The "Jet" in
51 | MilitaryJet is from JETpack Compose and "Military" in MilitaryJet is from MILITARY-grade from this
52 | repo's description which means the level of fight against computer software bugs. And then the
53 | MilitaryJet is the level of weaponry in the fight against Jetpack Compose UI bugs.
54 |
55 | ## Annoyance Alert
56 |
57 | Too many comments. For example, some comments are just for sample purposes for someone who might be
58 | interested in a particular test case. Note that newer test classes with higher numbers suffixed in
59 | their names have very fewer and necessary comments.
60 |
61 | And the [semantic versioning](https://github.com/emperorfin/MilitaryJet/tags) is for sample purpose
62 | in order to promote proper project versioning. Note that the incremental versions represent actual
63 | subsequent changes.
64 |
65 | ## Porting
66 |
67 | I look forward to see [this](https://github.com/emperorfin/MilitaryJet/tree/master/app/src/androidTest)
68 | also getting ported to projects that depend on other client frameworks and UI toolkits such as iOS,
69 | React, Flutter, etc.
70 |
71 |
72 |
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/tags/Tags.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents.tags
18 |
19 |
20 | /**
21 | * @Author: Francis Nwokelo (emperorfin)
22 | * @Date: Tuesday 22nd November, 2022.
23 | */
24 |
25 |
26 | object Tags {
27 |
28 | const val TAG_AUTHENTICATION_AUTHENTICATE_BUTTON: String = "ui.screens.authentication.uicomponents.authentication-authenticate-button"
29 | const val TAG_AUTHENTICATION_TOGGLE_MODE_BUTTON: String = "ui.screens.authentication.uicomponents.authentication-toggle-mode-button"
30 | const val TAG_AUTHENTICATION_AUTHENTICATION_TITLE: String = "ui.screens.authentication.uicomponents.authentication-authentication-title"
31 | const val TAG_AUTHENTICATION_INPUT_EMAIL: String = "ui.screens.authentication.uicomponents.authentication-input-email"
32 | const val TAG_AUTHENTICATION_INPUT_PASSWORD: String = "ui.screens.authentication.uicomponents.authentication-input-password"
33 | const val TAG_AUTHENTICATION_ERROR_DIALOG: String = "ui.screens.authentication.uicomponents.authentication-error-dialog"
34 | const val TAG_AUTHENTICATION_ERROR_DIALOG_CONFIRM_BUTTON: String = "ui.screens.authentication.uicomponents.authentication-error-dialog-confirm-button"
35 | const val TAG_AUTHENTICATION_PROGRESS: String = "ui.screens.authentication.uicomponents.authentication-progress"
36 | const val TAG_AUTHENTICATION_CONTENT: String = "ui.screens.authentication.uicomponents.authentication-content"
37 | const val TAG_AUTHENTICATION_SCREEN: String = "ui.screens.authentication.uicomponents.authentication-screen"
38 | const val TAG_AUTHENTICATION_FORM_CONTENT_AREA: String = "ui.screens.authentication.uicomponents.authentication-form-content-area"
39 | const val TAG_AUTHENTICATION_INPUT_PASSWORD_TRAILING_ICON: String = "ui.screens.authentication.uicomponents.authentication-input-password-trailing-icon"
40 | const val TAG_AUTHENTICATION_INPUT_PASSWORD_TRAILING_ICON_PASSWORD_HIDDEN: String = "ui.screens.authentication.uicomponents.authentication-input-password-trailing-icon-password-hidden-"
41 | const val TAG_AUTHENTICATION_PASSWORD_REQUIREMENTS: String = "ui.screens.authentication.uicomponents.authentication-password-requirements"
42 | const val TAG_AUTHENTICATION_PASSWORD_REQUIREMENT: String = "ui.screens.authentication.uicomponents.authentication-password-requirement-"
43 | const val TAG_AUTHENTICATION_PASSWORD_REQUIREMENT_ICON: String = "ui.screens.authentication.uicomponents.authentication-password-requirement-icon"
44 | const val TAG_AUTHENTICATION_PASSWORD_REQUIREMENT_TEXT: String = "ui.screens.authentication.uicomponents.authentication-password-requirement-text"
45 |
46 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/res/theme/Theme.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.res.theme
18 |
19 | import android.app.Activity
20 | import android.os.Build
21 | import androidx.compose.foundation.isSystemInDarkTheme
22 | import androidx.compose.material3.MaterialTheme
23 | import androidx.compose.material3.darkColorScheme
24 | import androidx.compose.material3.dynamicDarkColorScheme
25 | import androidx.compose.material3.dynamicLightColorScheme
26 | import androidx.compose.material3.lightColorScheme
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.runtime.SideEffect
29 | import androidx.compose.ui.graphics.toArgb
30 | import androidx.compose.ui.platform.LocalContext
31 | import androidx.compose.ui.platform.LocalView
32 | import androidx.core.view.ViewCompat
33 |
34 |
35 | /**
36 | * @Author: Francis Nwokelo (emperorfin)
37 | * @Date: November, 2022.
38 | */
39 |
40 |
41 |
42 | private val DarkColorScheme = darkColorScheme(
43 | primary = Purple80,
44 | secondary = PurpleGrey80,
45 | tertiary = Pink80
46 | )
47 |
48 | private val LightColorScheme = lightColorScheme(
49 | primary = Purple40,
50 | secondary = PurpleGrey40,
51 | tertiary = Pink40
52 |
53 | /* Other default colors to override
54 | background = Color(0xFFFFFBFE),
55 | surface = Color(0xFFFFFBFE),
56 | onPrimary = Color.White,
57 | onSecondary = Color.White,
58 | onTertiary = Color.White,
59 | onBackground = Color(0xFF1C1B1F),
60 | onSurface = Color(0xFF1C1B1F),
61 | */
62 | )
63 |
64 | @Composable
65 | fun ComposeEmailAuthenticationTheme(
66 | darkTheme: Boolean = isSystemInDarkTheme(),
67 | // Dynamic color is available on Android 12+
68 | dynamicColor: Boolean = true,
69 | isForScreenshotTest: Boolean = false,
70 | content: @Composable () -> Unit
71 | ) {
72 | val colorScheme = when {
73 | dynamicColor && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S -> {
74 | val context = LocalContext.current
75 | if (darkTheme) dynamicDarkColorScheme(context) else dynamicLightColorScheme(context)
76 | }
77 | darkTheme -> DarkColorScheme
78 | else -> LightColorScheme
79 | }
80 | val view = LocalView.current
81 | if (!isForScreenshotTest) {
82 | if (!view.isInEditMode) {
83 | SideEffect {
84 | (view.context as Activity).window.statusBarColor = colorScheme.primary.toArgb()
85 | ViewCompat.getWindowInsetsController(view)?.isAppearanceLightStatusBars = darkTheme
86 | }
87 | }
88 | }
89 |
90 | MaterialTheme(
91 | colorScheme = colorScheme,
92 | typography = Typography,
93 | content = content
94 | )
95 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/AuthenticationToggleMode.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.padding
21 | import androidx.compose.material3.MaterialTheme
22 | import androidx.compose.material3.Surface
23 | import androidx.compose.material3.Text
24 | import androidx.compose.material3.TextButton
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Modifier
27 | import androidx.compose.ui.platform.testTag
28 | import androidx.compose.ui.res.dimensionResource
29 | import androidx.compose.ui.res.stringResource
30 | import androidx.compose.ui.tooling.preview.Preview
31 | import emperorfin.android.components.R
32 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode
33 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_IN
34 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
35 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_TOGGLE_MODE_BUTTON
36 |
37 |
38 | /**
39 | * @Author: Francis Nwokelo (emperorfin)
40 | * @Date: November, 2022.
41 | */
42 |
43 |
44 | @Composable
45 | fun AuthenticationToggleMode(
46 | modifier: Modifier = Modifier,
47 | authenticationMode: AuthenticationMode,
48 | toggleAuthentication: () -> Unit
49 | ) {
50 |
51 | Surface(
52 | modifier = modifier.padding(
53 | top = dimensionResource(id = R.dimen.padding_top_16),
54 | ),
55 | shadowElevation = dimensionResource(id = R.dimen.surface_elevation_8)
56 | ) {
57 | TextButton(
58 | modifier = Modifier
59 | .testTag(TAG_AUTHENTICATION_TOGGLE_MODE_BUTTON)
60 | .background(color = MaterialTheme.colorScheme.surface)
61 | .padding(all = dimensionResource(id = R.dimen.padding_all_8)),
62 | // onClick = toggleAuthentication // OR
63 | onClick = {
64 | toggleAuthentication()
65 | }
66 | ) {
67 | Text(
68 | text = stringResource(
69 | id = if (authenticationMode == SIGN_IN) {
70 | R.string.action_need_account
71 | } else {
72 | R.string.action_already_have_account
73 | }
74 | )
75 | )
76 | }
77 | }
78 |
79 | }
80 |
81 | @Composable
82 | @Preview(showBackground = true)
83 | private fun AuthenticationToggleModePreview() {
84 |
85 | ComposeEmailAuthenticationTheme {
86 | AuthenticationToggleMode(
87 | authenticationMode = SIGN_IN,
88 | toggleAuthentication = {}
89 | )
90 | }
91 |
92 | }
--------------------------------------------------------------------------------
/components/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | Military Jet
19 |
20 | At least 8 characters
21 | At least 1 upper-case letter
22 | At least 1 digit
23 | %s, satisfied
24 | %s, needed
25 |
26 | Sign In to your account
27 | Sign Up for an account
28 |
29 | Email Address
30 | Password
31 |
32 | Show Password
33 | Hide Password
34 |
35 | Email Icon
36 | Password input leading icon
37 | Password input trailing icon indicating hidden password.
38 | Password input trailing icon indicating shown password.
39 | Password requirement needed.
40 | Password requirement satisfied.
41 |
42 | Sign In
43 | Sign Up
44 | Need an account?
45 | Already have an account?
46 |
47 | Whoops
48 | OK
49 | Something went wrong!
50 |
51 |
52 | Just a preview message
53 |
54 |
55 | Some error.
56 | Some error.
57 | At least 8 characters, satisfied
58 | At least 1 upper-case letter, satisfied
59 | At least 1 digit, satisfied
60 | At least 8 characters, needed
61 | At least 1 upper-case letter, needed
62 | At least 1 digit, needed
63 |
64 |
--------------------------------------------------------------------------------
/components/src/test/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | Military Jet
19 |
20 | At least 8 characters
21 | At least 1 upper-case letter
22 | At least 1 digit
23 | %s, satisfied
24 | %s, needed
25 |
26 | Sign In to your account
27 | Sign Up for an account
28 |
29 | Email Address
30 | Password
31 |
32 | Show Password
33 | Hide Password
34 |
35 | Email Icon
36 | Password input leading icon
37 | Password input trailing icon indicating hidden password.
38 | Password input trailing icon indicating shown password.
39 | Password requirement needed.
40 | Password requirement satisfied.
41 |
42 | Sign In
43 | Sign Up
44 | Need an account?
45 | Already have an account?
46 |
47 | Whoops
48 | OK
49 | Something went wrong!
50 |
51 |
52 | Just a preview message
53 |
54 |
55 | Some error.
56 | Some error.
57 | At least 8 characters, satisfied
58 | At least 1 upper-case letter, satisfied
59 | At least 1 digit, satisfied
60 | At least 8 characters, needed
61 | At least 1 upper-case letter, needed
62 | At least 1 digit, needed
63 |
64 |
--------------------------------------------------------------------------------
/components/src/androidTest/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
16 |
17 |
18 | Military Jet
19 |
20 | At least 8 characters
21 | At least 1 upper-case letter
22 | At least 1 digit
23 | %s, satisfied
24 | %s, needed
25 |
26 | Sign In to your account
27 | Sign Up for an account
28 |
29 | Email Address
30 | Password
31 |
32 | Show Password
33 | Hide Password
34 |
35 | Email Icon
36 | Password input leading icon
37 | Password input trailing icon indicating hidden password.
38 | Password input trailing icon indicating shown password.
39 | Password requirement needed.
40 | Password requirement satisfied.
41 |
42 | Sign In
43 | Sign Up
44 | Need an account?
45 | Already have an account?
46 |
47 | Whoops
48 | OK
49 | Something went wrong!
50 |
51 |
52 | Just a preview message
53 |
54 |
55 | Some error.
56 | Some error.
57 | At least 8 characters, satisfied
58 | At least 1 upper-case letter, satisfied
59 | At least 1 digit, satisfied
60 | At least 8 characters, needed
61 | At least 1 upper-case letter, needed
62 | At least 1 digit, needed
63 |
64 |
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/AuthenticationErrorDialog.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import androidx.annotation.StringRes
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.foundation.layout.fillMaxWidth
22 | import androidx.compose.material3.AlertDialog
23 | import androidx.compose.material3.Text
24 | import androidx.compose.material3.TextButton
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Alignment
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.platform.testTag
29 | import androidx.compose.ui.res.stringResource
30 | import androidx.compose.ui.semantics.semantics
31 | import androidx.compose.ui.tooling.preview.Preview
32 | import androidx.compose.ui.unit.TextUnit
33 | import androidx.compose.ui.unit.sp
34 | import emperorfin.android.components.R
35 | import emperorfin.android.components.ui.fortesting.alertDialogConfirmButtonText
36 | import emperorfin.android.components.ui.fortesting.alertDialogText
37 | import emperorfin.android.components.ui.fortesting.alertDialogTitle
38 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
39 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_ERROR_DIALOG
40 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_ERROR_DIALOG_CONFIRM_BUTTON
41 |
42 |
43 | /**
44 | * @Author: Francis Nwokelo (emperorfin)
45 | * @Date: November, 2022.
46 | */
47 |
48 |
49 | @Composable
50 | fun AuthenticationErrorDialog(
51 | modifier: Modifier = Modifier,
52 | @StringRes error: Int,
53 | onDismissError: () -> Unit
54 | ) {
55 |
56 | val alertDialogTitle: String = stringResource(id = R.string.error_title)
57 | val alertDialogText: String = stringResource(id = error)
58 | val alertDialogConfirmButtonText: String = stringResource(id = R.string.error_action_ok)
59 |
60 | AlertDialog(
61 | modifier = modifier
62 | .testTag(tag = TAG_AUTHENTICATION_ERROR_DIALOG)
63 | .semantics {
64 | this.alertDialogTitle = alertDialogTitle
65 | this.alertDialogText = alertDialogText
66 | this.alertDialogConfirmButtonText = alertDialogConfirmButtonText
67 | },
68 | onDismissRequest = {
69 | onDismissError()
70 | },
71 | confirmButton = {
72 | Box(
73 | modifier = Modifier.fillMaxWidth(),
74 | contentAlignment = Alignment.CenterEnd
75 | ) {
76 | TextButton(
77 | onClick = { onDismissError() },
78 | modifier = Modifier.testTag(tag = TAG_AUTHENTICATION_ERROR_DIALOG_CONFIRM_BUTTON)
79 | ) {
80 | Text(
81 | text = alertDialogConfirmButtonText
82 | )
83 | }
84 | }
85 | },
86 | title = {
87 | Text(
88 | text = alertDialogTitle,
89 | fontSize = FONT_SIZE_SP_18
90 | )
91 | },
92 | text = {
93 | Text(
94 | text = alertDialogText
95 | )
96 | }
97 | )
98 |
99 | }
100 |
101 | @Composable
102 | @Preview(showBackground = true)
103 | private fun AuthenticationErrorDialogPreview() {
104 |
105 | ComposeEmailAuthenticationTheme {
106 | AuthenticationErrorDialog(
107 | error = R.string.preview_message,
108 | onDismissError = {}
109 | )
110 | }
111 |
112 | }
113 |
114 | private val FONT_SIZE_SP_18: TextUnit = 18.sp
--------------------------------------------------------------------------------
/components/src/test/java/emperorfin/android/components/screenshots/ui/screens/authentication/uicomponents/AuthenticationButtonTest.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.screenshots.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.foundation.background
20 | import androidx.compose.foundation.layout.Box
21 | import androidx.compose.ui.Modifier
22 | import androidx.compose.ui.graphics.Color
23 | import app.cash.paparazzi.DeviceConfig
24 | import app.cash.paparazzi.Paparazzi
25 | import com.android.ide.common.rendering.api.SessionParams
26 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_IN
27 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_UP
28 | import emperorfin.android.components.ui.screens.authentication.uicomponents.AuthenticationButton
29 | import emperorfin.android.components.screenshots.ui.constants.BooleanConstants.FALSE
30 | import emperorfin.android.components.screenshots.ui.constants.BooleanConstants.TRUE
31 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
32 | import org.junit.Rule
33 | import org.junit.Test
34 |
35 |
36 | /*
37 | * @Author: Francis Nwokelo (emperorfin)
38 | * @Date: Monday 29th May, 2023.
39 | */
40 |
41 |
42 | class AuthenticationButtonTest {
43 |
44 | @get:Rule
45 | val paparazzi = Paparazzi(
46 | // theme = "android:Theme.MaterialComponents.Light.NoActionBar"
47 | // deviceConfig = DeviceConfig.NEXUS_5.copy(softButtons = false, screenHeight = 1),
48 | // renderingMode = SessionParams.RenderingMode.V_SCROLL
49 |
50 | // deviceConfig = DeviceConfig.PIXEL.copy(screenWidth = DeviceConfig.PIXEL.screenWidth * 2, softButtons = false)
51 |
52 | deviceConfig = DeviceConfig.PIXEL_3,
53 | renderingMode = SessionParams.RenderingMode.SHRINK,
54 | showSystemUi = false
55 | )
56 |
57 | @Test
58 | fun sign_In_Button_Enabled() {
59 | paparazzi.snapshot {
60 | ComposeEmailAuthenticationTheme(isForScreenshotTest = TRUE) {
61 | Box(Modifier.background(Color.White)) {
62 | AuthenticationButton(
63 | authenticationMode = SIGN_IN,
64 | enableAuthentication = TRUE,
65 | onAuthenticate = { }
66 | )
67 | }
68 | }
69 | }
70 | }
71 |
72 | @Test
73 | fun sign_Up_Button_Enabled() {
74 | paparazzi.snapshot {
75 | ComposeEmailAuthenticationTheme(isForScreenshotTest = TRUE) {
76 | Box(Modifier.background(Color.White)) {
77 | AuthenticationButton(
78 | authenticationMode = SIGN_UP,
79 | enableAuthentication = TRUE,
80 | onAuthenticate = { }
81 | )
82 | }
83 | }
84 | }
85 | }
86 |
87 | @Test
88 | fun sign_In_Button_Disabled() {
89 | paparazzi.snapshot {
90 | ComposeEmailAuthenticationTheme(isForScreenshotTest = TRUE) {
91 | Box(Modifier.background(Color.White)) {
92 | AuthenticationButton(
93 | authenticationMode = SIGN_IN,
94 | enableAuthentication = FALSE,
95 | onAuthenticate = { }
96 | )
97 | }
98 | }
99 | }
100 | }
101 |
102 | @Test
103 | fun sign_Up_Button_Disabled() {
104 | paparazzi.snapshot {
105 | ComposeEmailAuthenticationTheme(isForScreenshotTest = TRUE) {
106 | Box(Modifier.background(Color.White)) {
107 | AuthenticationButton(
108 | authenticationMode = SIGN_UP,
109 | enableAuthentication = FALSE,
110 | onAuthenticate = { }
111 | )
112 | }
113 | }
114 | }
115 | }
116 |
117 | }
--------------------------------------------------------------------------------
/components/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.library'
3 | id 'org.jetbrains.kotlin.android'
4 | id 'app.cash.paparazzi'
5 | }
6 |
7 | android {
8 | namespace 'emperorfin.android.components'
9 | // compileSdk 32
10 | compileSdk 33
11 |
12 | defaultConfig {
13 | minSdk 21
14 | // targetSdk 32
15 | targetSdk 33
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | consumerProguardFiles "consumer-rules.pro"
19 | vectorDrawables {
20 | useSupportLibrary true
21 | }
22 | }
23 |
24 | buildTypes {
25 | release {
26 | minifyEnabled false
27 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
28 | }
29 | }
30 | compileOptions {
31 | sourceCompatibility JavaVersion.VERSION_1_8
32 | targetCompatibility JavaVersion.VERSION_1_8
33 | }
34 | kotlinOptions {
35 | jvmTarget = '1.8'
36 | }
37 | buildFeatures {
38 | compose true
39 | }
40 | composeOptions {
41 | // Compose to Kotlin Compatibility Map ( https://developer.android.com/jetpack/androidx/releases/compose-kotlin )
42 | // kotlinCompilerExtensionVersion '1.1.1'
43 | // kotlinCompilerExtensionVersion '1.4.6'
44 | kotlinCompilerExtensionVersion '1.4.7'
45 | }
46 |
47 | sourceSets {
48 | androidTest {
49 | java.srcDirs = ['src/androidTest/java']
50 | res.srcDirs = ['src/androidTest/res']
51 | }
52 | }
53 |
54 | // sourceSets { debug { res.srcDirs = ['src/main/res', 'src/test/res', 'src/androidTest/res', 'src/debug/res'] } }
55 | // Works. Hovering the mouse on a resource id (e.g. R.string.test_error_message) shows actual
56 | // values categorized under "main" and "debug" with the one from "main" with a strikethrough.
57 | // Note that the both values are exactly the same.
58 | // sourceSets { debug { res.srcDirs = ['src/androidTest/res'] } }
59 | }
60 |
61 | dependencies {
62 |
63 | // IMPLEMENTATIONS
64 | // implementation 'androidx.appcompat:appcompat:1.4.1'
65 | // implementation 'com.google.android.material:material:1.5.0'
66 | implementation "androidx.core:core-ktx:$androidx_core_ktx"
67 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$androidx_lifecycle_runtime_ktx"
68 | // Provides access to the ComponentActivity class that can be used to compose UI components
69 | implementation "androidx.activity:activity-compose:$androidx_activity_compose"
70 | // UI components from the Compose APIs
71 | implementation "androidx.compose.ui:ui:$compose_version"
72 | // Provides tooling functionality for Composables, such as design-time previews
73 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
74 | // Provides Material Design 3 components from the Compose APIs
75 | implementation "androidx.compose.material3:material3:$androidx_compose_material3"
76 |
77 | // Provides an extended collection of Material Iconography
78 | implementation "androidx.compose.material:material-icons-extended:$compose_version"
79 | implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$androidx_lifecycle_viewmodel_compose"
80 | // Provides foundational classes from the Compose APIs
81 | implementation "androidx.compose.foundation:foundation:$compose_version"
82 |
83 | // Force upgrade to 1.1.0 because its required by androidTestImplementation,
84 | // and without this statement AGP will silently downgrade to tracing:1.0.0
85 | // See ( https://github.com/android/android-test/issues/1755#issuecomment-1523810698 )
86 | implementation "androidx.tracing:tracing:1.1.0"
87 |
88 |
89 | // TEST IMPLEMENTATIONS
90 | testImplementation "junit:junit:$junit"
91 |
92 |
93 | // ANDROID IMPLEMENTATIONS
94 | androidTestImplementation "androidx.test.ext:junit:$androidx_test_ext_junit"
95 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidx_test_espresso_core"
96 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
97 |
98 | androidTestImplementation "org.mockito.kotlin:mockito-kotlin:$org_mockito_kotlin"
99 | androidTestImplementation "org.mockito:mockito-android:$org_mockito_android"
100 |
101 | androidTestImplementation "com.google.truth:truth:$com_google_truth"
102 | // androidTestImplementation "com.google.truth.extensions:truth-java8-extension:1.1.3"
103 |
104 |
105 | // DEBUG IMPLEMENTATIONS
106 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
107 | debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
108 | }
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id 'com.android.application'
3 | id 'org.jetbrains.kotlin.android'
4 | }
5 |
6 | android {
7 | namespace 'emperorfin.android.militaryjet'
8 | compileSdk 33
9 |
10 | defaultConfig {
11 | applicationId "emperorfin.android.militaryjet"
12 | minSdk 21
13 | targetSdk 33
14 | versionCode 1
15 | versionName "1.0"
16 |
17 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
18 | vectorDrawables {
19 | useSupportLibrary true
20 | }
21 | }
22 |
23 | buildTypes {
24 | release {
25 | minifyEnabled false
26 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
27 | }
28 | }
29 | compileOptions {
30 | sourceCompatibility JavaVersion.VERSION_1_8
31 | targetCompatibility JavaVersion.VERSION_1_8
32 | }
33 | kotlinOptions {
34 | jvmTarget = '1.8'
35 | }
36 | buildFeatures {
37 | compose true
38 | }
39 | composeOptions {
40 | // Compose to Kotlin Compatibility Map ( https://developer.android.com/jetpack/androidx/releases/compose-kotlin )
41 | // kotlinCompilerExtensionVersion '1.1.1'
42 | // kotlinCompilerExtensionVersion '1.4.6'
43 | kotlinCompilerExtensionVersion '1.4.7'
44 | }
45 | // packagingOptions {
46 | // resources {
47 | // excludes += '/META-INF/{AL2.0,LGPL2.1}'
48 | // }
49 | // }
50 | packagingOptions {
51 | exclude "**/attach_hotspot_windows.dll"
52 | exclude "META-INF/AL2.0"
53 | exclude "META-INF/LGPL2.1"
54 | exclude "META-INF/licenses/ASM"
55 | }
56 |
57 | sourceSets {
58 | androidTest {
59 | java.srcDirs = ['src/androidTest/java']
60 | res.srcDirs = ['src/androidTest/res']
61 | }
62 | }
63 |
64 | // sourceSets { debug { res.srcDirs = ['src/main/res', 'src/test/res', 'src/androidTest/res', 'src/debug/res'] } }
65 | // Works. Hovering the mouse on a resource id (e.g. R.string.test_error_message) shows actual
66 | // values categorized under "main" and "debug" with the one from "main" with a strikethrough.
67 | // Note that the both values are exactly the same.
68 | // sourceSets { debug { res.srcDirs = ['src/androidTest/res'] } }
69 | }
70 |
71 | dependencies {
72 |
73 | // IMPLEMENTATIONS
74 | implementation "androidx.core:core-ktx:$androidx_core_ktx"
75 | implementation "androidx.lifecycle:lifecycle-runtime-ktx:$androidx_lifecycle_runtime_ktx"
76 | // Provides access to the ComponentActivity class that can be used to compose UI components
77 | implementation "androidx.activity:activity-compose:$androidx_activity_compose"
78 | // UI components from the Compose APIs
79 | implementation "androidx.compose.ui:ui:$compose_version"
80 | // Provides tooling functionality for Composables, such as design-time previews
81 | implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
82 | // Provides Material Design 3 components from the Compose APIs
83 | implementation "androidx.compose.material3:material3:$androidx_compose_material3"
84 |
85 | // Provides an extended collection of Material Iconography
86 | implementation "androidx.compose.material:material-icons-extended:$compose_version"
87 | implementation "androidx.lifecycle:lifecycle-viewmodel-compose:$androidx_lifecycle_viewmodel_compose"
88 | // Provides foundational classes from the Compose APIs
89 | implementation "androidx.compose.foundation:foundation:$compose_version"
90 |
91 | // Force upgrade to 1.1.0 because its required by androidTestImplementation,
92 | // and without this statement AGP will silently downgrade to tracing:1.0.0
93 | // See ( https://github.com/android/android-test/issues/1755#issuecomment-1523810698 )
94 | implementation "androidx.tracing:tracing:1.1.0"
95 |
96 | implementation project(":components")
97 |
98 |
99 | // TEST IMPLEMENTATIONS
100 | testImplementation "junit:junit:$junit"
101 |
102 |
103 | // ANDROID IMPLEMENTATIONS
104 | androidTestImplementation "androidx.test.ext:junit:$androidx_test_ext_junit"
105 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidx_test_espresso_core"
106 | androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version"
107 |
108 | androidTestImplementation "org.mockito.kotlin:mockito-kotlin:$org_mockito_kotlin"
109 | androidTestImplementation "org.mockito:mockito-android:$org_mockito_android"
110 |
111 | androidTestImplementation "com.google.truth:truth:$com_google_truth"
112 | // androidTestImplementation "com.google.truth.extensions:truth-java8-extension:1.1.3"
113 |
114 |
115 | // DEBUG IMPLEMENTATIONS
116 | debugImplementation "androidx.compose.ui:ui-tooling:$compose_version"
117 | debugImplementation "androidx.compose.ui:ui-test-manifest:$compose_version"
118 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/fortesting/TextFieldSemantics.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.fortesting
18 |
19 | import androidx.compose.ui.graphics.vector.ImageVector
20 | import androidx.compose.ui.semantics.SemanticsPropertyKey
21 | import androidx.compose.ui.semantics.SemanticsPropertyReceiver
22 | import androidx.compose.ui.text.input.ImeAction
23 | import androidx.compose.ui.text.input.KeyboardType
24 | import androidx.compose.ui.text.input.VisualTransformation
25 |
26 |
27 | /**
28 | * @Author: Francis Nwokelo (emperorfin)
29 | * @Date: Saturday 26th November, 2022.
30 | */
31 |
32 |
33 | /**
34 | * Docs on custom semantics: https://stackoverflow.com/a/71389302 or
35 | * https://stackoverflow.com/questions/68662342/jetpack-compose-testing-assert-specific-image-is-set
36 | */
37 |
38 | // TODO: Should add other semantics for things like TextField placeholder, etc.
39 |
40 | // TEXTFIELD
41 | // ----------- For Leading Icon --------------------
42 | val TextFieldLeadingIconImageVectorSemantics = SemanticsPropertyKey("TextFieldLeadingIconImageVectorSemantics")
43 | val TextFieldLeadingIconContentDescriptionSemantics = SemanticsPropertyKey("TextFieldLeadingIconContentDescriptionSemantics")
44 | val TextFieldLeadingIconTintArgbSemantics = SemanticsPropertyKey("TextFieldLeadingIconTintArgbSemantics")
45 | val TextFieldLeadingIconTintResSemantics = SemanticsPropertyKey("TextFieldLeadingIconTintResSemantics")
46 |
47 | // ----------- For Trailing Icon --------------------
48 | val TextFieldTrailingIconClickLabelSemantics = SemanticsPropertyKey("TextFieldTrailingIconClickLabelSemantics")
49 | val TextFieldTrailingIconImageVectorSemantics = SemanticsPropertyKey("TextFieldTrailingIconImageVectorSemantics")
50 | val TextFieldTrailingIconContentDescriptionSemantics = SemanticsPropertyKey("TextFieldTrailingIconContentDescriptionSemantics")
51 | val TextFieldTrailingIconTintArgbSemantics = SemanticsPropertyKey("TextFieldTrailingIconTintArgbSemantics")
52 | val TextFieldTrailingIconTintResSemantics = SemanticsPropertyKey("TextFieldTrailingIconTintResSemantics")
53 |
54 | // ----------- For Single Line --------------------
55 | val TextFieldSingleLineSemantics = SemanticsPropertyKey("TextFieldSingleLineSemantics")
56 |
57 | // ----------- For Keyboard Options --------------------
58 | val TextFieldKeyboardOptionsKeyboardTypeSemantics = SemanticsPropertyKey("TextFieldKeyboardOptionsKeyboardTypeSemantics")
59 | val TextFieldKeyboardOptionsImeActionSemantics = SemanticsPropertyKey("TextFieldKeyboardOptionsImeActionSemantics")
60 |
61 | // ----------- For Visual Transformation --------------------
62 | val TextFieldVisualTransformationSemantics = SemanticsPropertyKey("TextFieldVisualTransformationSemantics")
63 |
64 |
65 | // ----------- For Leading Icon --------------------
66 | var SemanticsPropertyReceiver.textFieldLeadingIconImageVector by TextFieldLeadingIconImageVectorSemantics
67 | var SemanticsPropertyReceiver.textFieldLeadingIconContentDescription by TextFieldLeadingIconContentDescriptionSemantics
68 | var SemanticsPropertyReceiver.textFieldLeadingIconTintArgb by TextFieldLeadingIconTintArgbSemantics
69 | var SemanticsPropertyReceiver.textFieldLeadingIconTintRes by TextFieldLeadingIconTintResSemantics
70 |
71 | // ----------- For Trailing Icon --------------------
72 | // TODO: Add other semantics such as content description and tint for trailing icon
73 | var SemanticsPropertyReceiver.textFieldTrailingIconClickLabel by TextFieldTrailingIconClickLabelSemantics
74 | var SemanticsPropertyReceiver.textFieldTrailingIconImageVector by TextFieldTrailingIconImageVectorSemantics
75 | var SemanticsPropertyReceiver.textFieldTrailingIconContentDescription by TextFieldTrailingIconContentDescriptionSemantics
76 | var SemanticsPropertyReceiver.textFieldTrailingIconTintArgb by TextFieldTrailingIconTintArgbSemantics
77 | var SemanticsPropertyReceiver.textFieldTrailingIconTintRes by TextFieldTrailingIconTintResSemantics
78 |
79 | // ----------- For Single Line --------------------
80 | var SemanticsPropertyReceiver.textFieldSingleLine by TextFieldSingleLineSemantics
81 |
82 | // ----------- For Keyboard Options --------------------
83 | var SemanticsPropertyReceiver.textFieldKeyboardOptionsKeyboardType by TextFieldKeyboardOptionsKeyboardTypeSemantics
84 | var SemanticsPropertyReceiver.textFieldKeyboardOptionsImeAction by TextFieldKeyboardOptionsImeActionSemantics
85 |
86 | // ----------- For Visual Transformation --------------------
87 | var SemanticsPropertyReceiver.textFieldVisualTransformation by TextFieldVisualTransformationSemantics
88 |
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/constants/StringResourceConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.constants
18 |
19 | import androidx.annotation.StringRes
20 | import emperorfin.android.components.test.R
21 | import emperorfin.android.components.ui.screens.authentication.enums.PasswordRequirement
22 |
23 |
24 | /**
25 | * @Author: Francis Nwokelo (emperorfin)
26 | * @Date: Tuesday 21st March, 2023.
27 | */
28 |
29 |
30 | /**
31 | * TODO: Constant variables with "STRING_RES_" prefixed in their names should be renamed with
32 | * "RES_STRING_" prefixed in their names instead of "STRING_RES_". E.g. [STRING_RES_SIGN_IN_TO_YOUR_ACCOUNT]
33 | * should be renamed to RES_STRING_SIGN_IN_TO_YOUR_ACCOUNT.
34 | */
35 | object StringResourceConstants {
36 |
37 | // TODO: Should be renamed to MAIN_SOURCE_SET_RES_STRING_TEST_ERROR_MESSAGE
38 | @JvmField
39 | @StringRes
40 | val MAIN_SOURCE_SET_STRING_RES_TEST_ERROR_MESSAGE: Int =
41 | emperorfin.android.components.R.string.test_error_message
42 |
43 | @JvmField
44 | @StringRes
45 | val MAIN_SOURCE_SET_STRING_RES_AT_LEAST_EIGHT_CHARACTERS: Int = PasswordRequirement.EIGHT_CHARACTERS.label
46 |
47 | @JvmField
48 | @StringRes
49 | val STRING_RES_SIGN_IN_TO_YOUR_ACCOUNT: Int = R.string.label_sign_in_to_account
50 | @JvmField
51 | @StringRes
52 | val STRING_RES_SIGN_UP_FOR_AN_ACCOUNT: Int = R.string.label_sign_up_for_account
53 |
54 | @JvmField
55 | @StringRes
56 | val STRING_RES_EMAIL_ADDRESS: Int = R.string.label_email
57 |
58 | @JvmField
59 | @StringRes
60 | val STRING_RES_PASSWORD: Int = R.string.label_password
61 | @JvmField
62 | @StringRes
63 | val STRING_RES_PASSWORD_SHOW: Int = R.string.content_description_show_password
64 | @JvmField
65 | @StringRes
66 | val STRING_RES_PASSWORD_HIDE: Int = R.string.content_description_hide_password
67 | @JvmField
68 | @StringRes
69 | val STRING_RES_PASSWORD_INPUT_LEADING_ICON: Int = R.string.content_description_password_input_leading_icon
70 | @JvmField
71 | @StringRes
72 | val STRING_RES_TRAILING_ICON_INDICATES_PASSWORD_SHOWN: Int =
73 | R.string.content_description_password_input_trailing_icon_password_shown
74 | @JvmField
75 | @StringRes
76 | val STRING_RES_TRAILING_ICON_INDICATES_PASSWORD_HIDDEN: Int =
77 | R.string.content_description_password_input_trailing_icon_password_hidden
78 |
79 | @JvmField
80 | @StringRes
81 | val STRING_RES_NEED_AN_ACCOUNT: Int = R.string.action_need_account
82 | @JvmField
83 | @StringRes
84 | val STRING_RES_ALREADY_HAVE_AN_ACCOUNT: Int = R.string.action_already_have_account
85 | @JvmField
86 | @StringRes
87 | val STRING_RES_SIGN_IN: Int = R.string.action_sign_in
88 | @JvmField
89 | @StringRes
90 | val STRING_RES_SIGN_UP: Int = R.string.action_sign_up
91 |
92 | @JvmField
93 | @StringRes
94 | val STRING_RES_AT_LEAST_EIGHT_CHARACTERS: Int = R.string.password_requirement_characters
95 | @JvmField
96 | @StringRes
97 | val STRING_RES_AT_LEAST_ONE_UPPERCASE_LETTER: Int = R.string.password_requirement_capital
98 | @JvmField
99 | @StringRes
100 | val STRING_RES_AT_LEAST_ONE_DIGIT: Int = R.string.password_requirement_digit
101 | @JvmField
102 | @StringRes
103 | val STRING_RES_AT_LEAST_EIGHT_CHARACTERS_NEEDED: Int = R.string.test_password_requirement_needed_characters
104 | @JvmField
105 | @StringRes
106 | val STRING_RES_AT_LEAST_ONE_UPPERCASE_LETTER_NEEDED: Int = R.string.test_password_requirement_needed_capital
107 | @JvmField
108 | @StringRes
109 | val STRING_RES_AT_LEAST_ONE_DIGIT_NEEDED: Int = R.string.test_password_requirement_needed_digit
110 | @JvmField
111 | @StringRes
112 | val STRING_RES_AT_LEAST_EIGHT_CHARACTERS_SATISFIED: Int = R.string.test_password_requirement_satisfied_characters
113 | @JvmField
114 | @StringRes
115 | val STRING_RES_AT_LEAST_ONE_UPPERCASE_LETTER_SATISFIED: Int = R.string.test_password_requirement_satisfied_capital
116 | @JvmField
117 | @StringRes
118 | val STRING_RES_AT_LEAST_ONE_DIGIT_SATISFIED: Int = R.string.test_password_requirement_satisfied_digit
119 |
120 | @JvmField
121 | @StringRes
122 | val STRING_RES_PASSWORD_REQUIREMENT_NEEDED: Int = R.string.content_description_icon_password_requirement_needed
123 | @JvmField
124 | @StringRes
125 | val STRING_RES_PASSWORD_REQUIREMENT_SATISFIED: Int = R.string.content_description_icon_password_requirement_satisfied
126 |
127 | @JvmField
128 | @StringRes
129 | val STRING_RES_WHOOPS: Int = R.string.error_title
130 | @JvmField
131 | @StringRes
132 | val STRING_RES_OK: Int = R.string.error_action_ok
133 | @JvmField
134 | @StringRes
135 | val STRING_RES_SOME_ERROR: Int = R.string.test_error_message
136 |
137 | }
--------------------------------------------------------------------------------
/components/src/test/java/emperorfin/android/components/screenshots/ui/constants/StringResourceConstants.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.screenshots.ui.constants
18 |
19 | import androidx.annotation.StringRes
20 | import emperorfin.android.components.test.R
21 | import emperorfin.android.components.ui.screens.authentication.enums.PasswordRequirement
22 |
23 |
24 | /*
25 | * @Author: Francis Nwokelo (emperorfin)
26 | * @Date: Thursday 1st June, 2023.
27 | */
28 |
29 |
30 | /**
31 | * TODO: Constant variables with "STRING_RES_" prefixed in their names should be renamed with
32 | * "RES_STRING_" prefixed in their names instead of "STRING_RES_". E.g. [STRING_RES_SIGN_IN_TO_YOUR_ACCOUNT]
33 | * should be renamed to RES_STRING_SIGN_IN_TO_YOUR_ACCOUNT.
34 | */
35 | object StringResourceConstants {
36 |
37 | // TODO: Should be renamed to MAIN_SOURCE_SET_RES_STRING_TEST_ERROR_MESSAGE
38 | @JvmField
39 | @StringRes
40 | val MAIN_SOURCE_SET_STRING_RES_TEST_ERROR_MESSAGE: Int =
41 | emperorfin.android.components.R.string.test_error_message
42 |
43 | @JvmField
44 | @StringRes
45 | val MAIN_SOURCE_SET_STRING_RES_AT_LEAST_EIGHT_CHARACTERS: Int = PasswordRequirement.EIGHT_CHARACTERS.label
46 |
47 | @JvmField
48 | @StringRes
49 | val STRING_RES_SIGN_IN_TO_YOUR_ACCOUNT: Int = R.string.label_sign_in_to_account
50 | @JvmField
51 | @StringRes
52 | val STRING_RES_SIGN_UP_FOR_AN_ACCOUNT: Int = R.string.label_sign_up_for_account
53 |
54 | @JvmField
55 | @StringRes
56 | val STRING_RES_EMAIL_ADDRESS: Int = R.string.label_email
57 |
58 | @JvmField
59 | @StringRes
60 | val STRING_RES_PASSWORD: Int = R.string.label_password
61 | @JvmField
62 | @StringRes
63 | val STRING_RES_PASSWORD_SHOW: Int = R.string.content_description_show_password
64 | @JvmField
65 | @StringRes
66 | val STRING_RES_PASSWORD_HIDE: Int = R.string.content_description_hide_password
67 | @JvmField
68 | @StringRes
69 | val STRING_RES_PASSWORD_INPUT_LEADING_ICON: Int = R.string.content_description_password_input_leading_icon
70 | @JvmField
71 | @StringRes
72 | val STRING_RES_TRAILING_ICON_INDICATES_PASSWORD_SHOWN: Int =
73 | R.string.content_description_password_input_trailing_icon_password_shown
74 | @JvmField
75 | @StringRes
76 | val STRING_RES_TRAILING_ICON_INDICATES_PASSWORD_HIDDEN: Int =
77 | R.string.content_description_password_input_trailing_icon_password_hidden
78 |
79 | @JvmField
80 | @StringRes
81 | val STRING_RES_NEED_AN_ACCOUNT: Int = R.string.action_need_account
82 | @JvmField
83 | @StringRes
84 | val STRING_RES_ALREADY_HAVE_AN_ACCOUNT: Int = R.string.action_already_have_account
85 | @JvmField
86 | @StringRes
87 | val STRING_RES_SIGN_IN: Int = R.string.action_sign_in
88 | @JvmField
89 | @StringRes
90 | val STRING_RES_SIGN_UP: Int = R.string.action_sign_up
91 |
92 | @JvmField
93 | @StringRes
94 | val STRING_RES_AT_LEAST_EIGHT_CHARACTERS: Int = R.string.password_requirement_characters
95 | @JvmField
96 | @StringRes
97 | val STRING_RES_AT_LEAST_ONE_UPPERCASE_LETTER: Int = R.string.password_requirement_capital
98 | @JvmField
99 | @StringRes
100 | val STRING_RES_AT_LEAST_ONE_DIGIT: Int = R.string.password_requirement_digit
101 | @JvmField
102 | @StringRes
103 | val STRING_RES_AT_LEAST_EIGHT_CHARACTERS_NEEDED: Int = R.string.test_password_requirement_needed_characters
104 | @JvmField
105 | @StringRes
106 | val STRING_RES_AT_LEAST_ONE_UPPERCASE_LETTER_NEEDED: Int = R.string.test_password_requirement_needed_capital
107 | @JvmField
108 | @StringRes
109 | val STRING_RES_AT_LEAST_ONE_DIGIT_NEEDED: Int = R.string.test_password_requirement_needed_digit
110 | @JvmField
111 | @StringRes
112 | val STRING_RES_AT_LEAST_EIGHT_CHARACTERS_SATISFIED: Int = R.string.test_password_requirement_satisfied_characters
113 | @JvmField
114 | @StringRes
115 | val STRING_RES_AT_LEAST_ONE_UPPERCASE_LETTER_SATISFIED: Int = R.string.test_password_requirement_satisfied_capital
116 | @JvmField
117 | @StringRes
118 | val STRING_RES_AT_LEAST_ONE_DIGIT_SATISFIED: Int = R.string.test_password_requirement_satisfied_digit
119 |
120 | @JvmField
121 | @StringRes
122 | val STRING_RES_PASSWORD_REQUIREMENT_NEEDED: Int = R.string.content_description_icon_password_requirement_needed
123 | @JvmField
124 | @StringRes
125 | val STRING_RES_PASSWORD_REQUIREMENT_SATISFIED: Int = R.string.content_description_icon_password_requirement_satisfied
126 |
127 | @JvmField
128 | @StringRes
129 | val STRING_RES_WHOOPS: Int = R.string.error_title
130 | @JvmField
131 | @StringRes
132 | val STRING_RES_OK: Int = R.string.error_action_ok
133 | @JvmField
134 | @StringRes
135 | val STRING_RES_SOME_ERROR: Int = R.string.test_error_message
136 |
137 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/AuthenticationContent.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.foundation.layout.Box
20 | import androidx.compose.foundation.layout.fillMaxSize
21 | import androidx.compose.material3.CircularProgressIndicator
22 | import androidx.compose.material3.ProgressIndicatorDefaults
23 | import androidx.compose.runtime.Composable
24 | import androidx.compose.ui.Alignment
25 | import androidx.compose.ui.Modifier
26 | import androidx.compose.ui.graphics.Color
27 | import androidx.compose.ui.graphics.toArgb
28 | import androidx.compose.ui.platform.testTag
29 | import androidx.compose.ui.semantics.semantics
30 | import androidx.compose.ui.tooling.preview.Preview
31 | import emperorfin.android.components.ui.fortesting.circularProgressIndicatorColorArgb
32 | import emperorfin.android.components.ui.screens.authentication.stateholders.AuthenticationUiState
33 | import emperorfin.android.components.ui.screens.authentication.events.AuthenticationEvent
34 | import emperorfin.android.components.ui.screens.authentication.events.AuthenticationEvent.EmailChangedEvent
35 | import emperorfin.android.components.ui.screens.authentication.events.AuthenticationEvent.PasswordChangedEvent
36 | import emperorfin.android.components.ui.screens.authentication.events.AuthenticationEvent.AuthenticateEvent
37 | import emperorfin.android.components.ui.screens.authentication.events.AuthenticationEvent.ErrorDismissedEvent
38 | import emperorfin.android.components.ui.screens.authentication.events.AuthenticationEvent.AuthenticationToggleModeEvent
39 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
40 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_PROGRESS
41 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_CONTENT
42 |
43 |
44 | /**
45 | * @Author: Francis Nwokelo (emperorfin)
46 | * @Date: November, 2022.
47 | */
48 |
49 |
50 | @Composable
51 | fun AuthenticationContent(
52 | modifier: Modifier = Modifier,
53 | uiState: AuthenticationUiState,
54 | handleEvent: (event: AuthenticationEvent) -> Unit
55 | ) {
56 |
57 | Box(
58 | modifier = modifier
59 | .testTag(tag = TAG_AUTHENTICATION_CONTENT)
60 | /*.semantics(mergeDescendants = true){}*/,
61 | contentAlignment = Alignment.Center
62 | ) {
63 | if (uiState.isLoading) {
64 | val defaultPresetColor: Color = ProgressIndicatorDefaults.circularColor
65 |
66 | CircularProgressIndicator(
67 | modifier = Modifier
68 | .testTag(tag = TAG_AUTHENTICATION_PROGRESS)
69 | .semantics{
70 | circularProgressIndicatorColorArgb = defaultPresetColor.toArgb()
71 | },
72 | // This is optional if just for testing purposes with the preset color.
73 | color = defaultPresetColor
74 | )
75 | } else {
76 | AuthenticationForm(
77 | modifier = Modifier.fillMaxSize(),
78 | authenticationMode = uiState.authenticationMode,
79 | onToggleMode = {
80 | handleEvent(
81 | AuthenticationToggleModeEvent
82 | )
83 | },
84 | email = uiState.email,
85 | onEmailChanged = { emailAddress ->
86 | handleEvent(
87 | EmailChangedEvent(emailAddress)
88 | )
89 | },
90 | password = uiState.password,
91 | onPasswordChanged = { password ->
92 | handleEvent(
93 | PasswordChangedEvent(password)
94 | )
95 | },
96 | completedPasswordRequirements = uiState.passwordRequirements,
97 | enableAuthentication = uiState.checkFormValidity(),
98 | onAuthenticate = {
99 | handleEvent(
100 | AuthenticateEvent
101 | )
102 | }
103 | )
104 |
105 | uiState.error?.let {
106 | AuthenticationErrorDialog(
107 | error = it,
108 | onDismissError = {
109 | handleEvent(ErrorDismissedEvent)
110 | }
111 | )
112 | }
113 | }
114 | }
115 |
116 | }
117 |
118 | @Composable
119 | @Preview(showBackground = true)
120 | private fun AuthenticationContentPreview() {
121 | ComposeEmailAuthenticationTheme {
122 | AuthenticationContent(uiState = AuthenticationUiState(), handleEvent = {})
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/Requirement.kt.another_approach:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package militaryjet.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.foundation.layout.*
20 | import androidx.compose.material.icons.Icons
21 | import androidx.compose.material.icons.filled.Check
22 | import androidx.compose.material3.Icon
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.Text
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Alignment
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.graphics.Color
29 | import androidx.compose.ui.graphics.toArgb
30 | import androidx.compose.ui.graphics.vector.ImageVector
31 | import androidx.compose.ui.platform.testTag
32 | import androidx.compose.ui.res.dimensionResource
33 | import androidx.compose.ui.res.stringResource
34 | import androidx.compose.ui.semantics.clearAndSetSemantics
35 | import androidx.compose.ui.semantics.semantics
36 | import androidx.compose.ui.semantics.testTag
37 | import androidx.compose.ui.semantics.text
38 | import androidx.compose.ui.text.AnnotatedString
39 | import androidx.compose.ui.tooling.preview.Preview
40 | import androidx.compose.ui.unit.TextUnit
41 | import androidx.compose.ui.unit.sp
42 | import militaryjet.R
43 | import militaryjet.ui.fortesting.iconContentDescription
44 | import militaryjet.ui.fortesting.iconImageVector
45 | import militaryjet.ui.fortesting.iconTintArgb
46 | import militaryjet.ui.fortesting.textColorArgb
47 | import militaryjet.ui.res.theme.ComposeEmailAuthenticationTheme
48 | import militaryjet.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_PASSWORD_REQUIREMENT
49 | import militaryjet.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_PASSWORD_REQUIREMENT_ICON
50 |
51 |
52 | /**
53 | * @Author: Francis Nwokelo (emperorfin)
54 | * @Date: Tuesday 06th December, 2022.
55 | */
56 |
57 |
58 | @Composable
59 | fun Requirement(
60 | modifier: Modifier = Modifier,
61 | message: String,
62 | satisfied: Boolean
63 | ) {
64 |
65 | val iconImageVector: ImageVector = Icons.Default.Check
66 | val iconContentDescription: String = if (satisfied) {
67 | stringResource(
68 | id = R.string.content_description_icon_password_requirement_satisfied
69 | )
70 | } else {
71 | stringResource(
72 | id = R.string.content_description_icon_password_requirement_needed
73 | )
74 | }
75 | val tint: Color = if (satisfied) {
76 | MaterialTheme.colorScheme.primary
77 | } else MaterialTheme.colorScheme.onSurface.copy(alpha = COLOR_ALPHA)
78 |
79 | val requirementStatus: String = if (satisfied) {
80 | stringResource(id = R.string.password_requirement_satisfied, message)
81 | } else {
82 | stringResource(id = R.string.password_requirement_needed, message)
83 | }
84 |
85 | Row(
86 | modifier = Modifier
87 | .padding(
88 | all = dimensionResource(id = R.dimen.padding_all_6)
89 | )
90 | // .semantics(mergeDescendants = TRUE) {
91 | //// this.text = AnnotatedString(requirementStatus) // Or
92 | // text = AnnotatedString(requirementStatus)
93 | // testTag = TAG_AUTHENTICATION_PASSWORD_REQUIREMENT + message
94 | //
95 | // textColorArgb = tint.toArgb()
96 | // },
97 | ,verticalAlignment = Alignment.CenterVertically
98 | ) {
99 | Icon(
100 | modifier = Modifier
101 | .testTag(tag = TAG_AUTHENTICATION_PASSWORD_REQUIREMENT_ICON)
102 | .size(
103 | size = dimensionResource(id = R.dimen.size_12)
104 | )
105 | .semantics {
106 | this.iconImageVector = iconImageVector
107 | this.iconContentDescription = iconContentDescription
108 | this.iconTintArgb = tint.toArgb()
109 | },
110 | imageVector = iconImageVector,
111 | contentDescription = iconContentDescription,
112 | tint = tint
113 | )
114 |
115 | Spacer(
116 | modifier = Modifier.width(
117 | width = dimensionResource(id = R.dimen.width_8)
118 | )
119 | )
120 |
121 | Text(
122 | modifier = Modifier
123 | // .clearAndSetSemantics { },
124 | .testTag(tag = TAG_AUTHENTICATION_PASSWORD_REQUIREMENT + requirementStatus)
125 | .semantics { textColorArgb = tint.toArgb() },
126 | text = message,
127 | fontSize = FONT_SIZE_SP_12,
128 | color = tint
129 | )
130 | }
131 |
132 | }
133 |
134 | @Composable
135 | @Preview(showBackground = TRUE)
136 | private fun RequirementPreview() {
137 | ComposeEmailAuthenticationTheme {
138 | Requirement(
139 | modifier = Modifier,
140 | message = "message",
141 | satisfied = TRUE
142 | )
143 | }
144 | }
145 |
146 | private const val TRUE: Boolean = true
147 |
148 | private const val COLOR_ALPHA: Float = 0.4f
149 |
150 | private val FONT_SIZE_SP_12: TextUnit = 12.sp
151 |
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/screens/authentication/uicomponents/AuthenticationTitleTest5.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import android.content.Context
20 | import androidx.compose.ui.test.junit4.ComposeContentTestRule
21 | import androidx.compose.ui.test.junit4.createComposeRule
22 | import androidx.test.platform.app.InstrumentationRegistry
23 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_IN
24 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_UP
25 | import emperorfin.android.components.ui.screens.authentication.enums.PasswordRequirement
26 | import emperorfin.android.components.ui.utils.AuthenticationTitleTestUtil3
27 | import org.junit.Before
28 | import org.junit.Rule
29 | import org.junit.Test
30 |
31 |
32 | /*
33 | * @Author: Francis Nwokelo (emperorfin)
34 | * @Date: Wednesday 19th April, 2023.
35 | */
36 |
37 |
38 | /**
39 | * Notes:
40 | *
41 | * - If you ever need to pass a resource (e.g. a string resource) into a composable during testing,
42 | * be sure to use the one from the main source set and the R must be the one from
43 | * [emperorfin.android.components.R] and not
44 | * [emperorfin.android.components.test.R].
45 | * - Every other thing during testing that involves the use of a resource (e.g. a string resource)
46 | * such as performing matches or assertions, be sure to use the resource from the androidTest source
47 | * set (which you should've provided a copy and always in sync with the one from the main source set).
48 | * And the R must be the one from [emperorfin.android.components.test.R] instead of
49 | * [emperorfin.android.components.R].
50 | *
51 | * Be sure to have Configured res srcDirs for androidTest sourceSet in app/build.gradle file.
52 | * See the following:
53 | * - https://stackoverflow.com/questions/36955608/espresso-how-to-use-r-string-resources-of-androidtest-folder
54 | * - https://stackoverflow.com/questions/26663539/configuring-res-srcdirs-for-androidtest-sourceset
55 | */
56 | class AuthenticationTitleTest5 {
57 |
58 | /**
59 | * Use this when resources are coming from the main source set, whether directly
60 | * (e.g. R.string.sample_text) or indirectly (e.g. [PasswordRequirement.EIGHT_CHARACTERS.label]
61 | * which is directly using a string resource).
62 | *
63 | * To actually reference the resource, you use
64 | * [emperorfin.android.components.R] and not
65 | * [emperorfin.android.components.test.R]
66 | *
67 | * So let's say if you want to reference a string resource, that string resource should come
68 | * from app/src/main/res/values/strings.xml XML resource file which must be, as you may have
69 | * noticed, from the main source set.
70 | */
71 | private lateinit var mTargetContext: Context
72 |
73 | /**
74 | * Use this when resources are coming from the androidTest or test source set. In this case, the
75 | * resources should come from androidTest (not test) source set.
76 | *
77 | * To actually reference the resource, you use
78 | * [emperorfin.android.components.test.R] and not
79 | * [emperorfin.android.components.R]
80 | *
81 | * So let's say if you want to reference a string resource, that string resource should come
82 | * from app/src/androidTest/res/values/strings.xml XML resource file which must be, as you may
83 | * have noticed, from the androidTest source set. And always update this file with the changes
84 | * made to the app/src/main/res/values/strings.xml XML resource file from the main source set.
85 | * And of course, you may/should re-run existing test(s) to be sure they don't fail as a result
86 | * of the synchronization.
87 | *
88 | * Be sure to have Configured res srcDirs for androidTest sourceSet in app/build.gradle file.
89 | * See the following:
90 | * - https://stackoverflow.com/questions/36955608/espresso-how-to-use-r-string-resources-of-androidtest-folder
91 | * - https://stackoverflow.com/questions/26663539/configuring-res-srcdirs-for-androidtest-sourceset
92 | */
93 | private lateinit var mContext: Context
94 |
95 | @get:Rule
96 | val composeTestRule: ComposeContentTestRule = createComposeRule()
97 |
98 | private lateinit var authenticationTitleTestUtil: AuthenticationTitleTestUtil3
99 |
100 | @Before
101 | fun setUpContexts() {
102 |
103 | // See field's KDoc for more info.
104 | mTargetContext = InstrumentationRegistry.getInstrumentation().targetContext
105 | // mTargetContext = ApplicationProvider.getApplicationContext() // Haven't tested but might work.
106 | // See field's KDoc for more info.
107 | mContext = InstrumentationRegistry.getInstrumentation().context
108 |
109 | authenticationTitleTestUtil = AuthenticationTitleTestUtil3(
110 | mContext = mContext,
111 | mTargetContext = mTargetContext,
112 | composeTestRule = composeTestRule
113 | )
114 |
115 | }
116 |
117 | @Test
118 | fun sign_In_Title_Displayed() {
119 |
120 | authenticationTitleTestUtil
121 | .setContentAsAuthenticationTitleAndAssertItIsDisplayed(
122 | authenticationMode = SIGN_IN
123 | )
124 |
125 | }
126 |
127 | @Test
128 | fun sign_Up_Title_Displayed() {
129 |
130 | authenticationTitleTestUtil
131 | .setContentAsAuthenticationTitleAndAssertItIsDisplayed(
132 | authenticationMode = SIGN_UP
133 | )
134 |
135 | }
136 |
137 | }
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/screens/authentication/uicomponents/AuthenticationTitleTest4.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import android.content.Context
20 | import androidx.compose.ui.test.junit4.ComposeContentTestRule
21 | import androidx.compose.ui.test.junit4.createComposeRule
22 | import androidx.test.platform.app.InstrumentationRegistry
23 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_IN
24 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_UP
25 | import emperorfin.android.components.ui.screens.authentication.enums.PasswordRequirement
26 | import emperorfin.android.components.ui.utils.AuthenticationTitleTestUtil2
27 | import org.junit.Before
28 | import org.junit.Rule
29 | import org.junit.Test
30 |
31 |
32 | /**
33 | * @Author: Francis Nwokelo (emperorfin)
34 | * @Date: Thursday 30th March, 2023.
35 | */
36 |
37 |
38 | /**
39 | * [AuthenticationTitleTest5] class is a revision of this class.
40 | *
41 | * Notes:
42 | *
43 | * - If you ever need to pass a resource (e.g. a string resource) into a composable during testing,
44 | * be sure to use the one from the main source set and the R must be the one from
45 | * [emperorfin.android.components.R] and not
46 | * [emperorfin.android.components.test.R].
47 | * - Every other thing during testing that involves the use of a resource (e.g. a string resource)
48 | * such as performing matches or assertions, be sure to use the resource from the androidTest source
49 | * set (which you should've provided a copy and always in sync with the one from the main source set).
50 | * And the R must be the one from [emperorfin.android.components.test.R] instead of
51 | * [emperorfin.android.components.R].
52 | *
53 | * Be sure to have Configured res srcDirs for androidTest sourceSet in app/build.gradle file.
54 | * See the following:
55 | * - https://stackoverflow.com/questions/36955608/espresso-how-to-use-r-string-resources-of-androidtest-folder
56 | * - https://stackoverflow.com/questions/26663539/configuring-res-srcdirs-for-androidtest-sourceset
57 | */
58 | class AuthenticationTitleTest4 {
59 |
60 | /**
61 | * Use this when resources are coming from the main source set, whether directly
62 | * (e.g. R.string.sample_text) or indirectly (e.g. [PasswordRequirement.EIGHT_CHARACTERS.label]
63 | * which is directly using a string resource).
64 | *
65 | * To actually reference the resource, you use
66 | * [emperorfin.android.components.R] and not
67 | * [emperorfin.android.components.test.R]
68 | *
69 | * So let's say if you want to reference a string resource, that string resource should come
70 | * from app/src/main/res/values/strings.xml XML resource file which must be, as you may have
71 | * noticed, from the main source set.
72 | */
73 | private lateinit var mTargetContext: Context
74 |
75 | /**
76 | * Use this when resources are coming from the androidTest or test source set. In this case, the
77 | * resources should come from androidTest (not test) source set.
78 | *
79 | * To actually reference the resource, you use
80 | * [emperorfin.android.components.test.R] and not
81 | * [emperorfin.android.components.R]
82 | *
83 | * So let's say if you want to reference a string resource, that string resource should come
84 | * from app/src/androidTest/res/values/strings.xml XML resource file which must be, as you may
85 | * have noticed, from the androidTest source set. And always update this file with the changes
86 | * made to the app/src/main/res/values/strings.xml XML resource file from the main source set.
87 | * And of course, you may/should re-run existing test(s) to be sure they don't fail as a result
88 | * of the synchronization.
89 | *
90 | * Be sure to have Configured res srcDirs for androidTest sourceSet in app/build.gradle file.
91 | * See the following:
92 | * - https://stackoverflow.com/questions/36955608/espresso-how-to-use-r-string-resources-of-androidtest-folder
93 | * - https://stackoverflow.com/questions/26663539/configuring-res-srcdirs-for-androidtest-sourceset
94 | */
95 | private lateinit var mContext: Context
96 |
97 | @get:Rule
98 | val composeTestRule: ComposeContentTestRule = createComposeRule()
99 |
100 | private lateinit var authenticationTitleTestUtil: AuthenticationTitleTestUtil2
101 |
102 | @Before
103 | fun setUpContexts() {
104 |
105 | // See field's KDoc for more info.
106 | mTargetContext = InstrumentationRegistry.getInstrumentation().targetContext
107 | // mTargetContext = ApplicationProvider.getApplicationContext() // Haven't tested but might work.
108 | // See field's KDoc for more info.
109 | mContext = InstrumentationRegistry.getInstrumentation().context
110 |
111 | authenticationTitleTestUtil = AuthenticationTitleTestUtil2(
112 | mContext = mContext,
113 | mTargetContext = mTargetContext,
114 | composeTestRule = composeTestRule
115 | )
116 |
117 | }
118 |
119 | @Test
120 | fun sign_In_Title_Displayed() {
121 |
122 | authenticationTitleTestUtil
123 | .setContentAsAuthenticationTitleAndAssertItIsDisplayed(
124 | authenticationMode = SIGN_IN
125 | )
126 |
127 | }
128 |
129 | @Test
130 | fun sign_Up_Title_Displayed() {
131 |
132 | authenticationTitleTestUtil
133 | .setContentAsAuthenticationTitleAndAssertItIsDisplayed(
134 | authenticationMode = SIGN_UP
135 | )
136 |
137 | }
138 |
139 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/EmailInput.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.foundation.text.KeyboardActions
20 | import androidx.compose.foundation.text.KeyboardOptions
21 | import androidx.compose.material.icons.Icons
22 | import androidx.compose.material.icons.filled.Email
23 | import androidx.compose.material3.ExperimentalMaterial3Api
24 | import androidx.compose.material3.Icon
25 | import androidx.compose.material3.Text
26 | import androidx.compose.material3.TextField
27 | import androidx.compose.runtime.Composable
28 | import androidx.compose.ui.Modifier
29 | import androidx.compose.ui.graphics.vector.ImageVector
30 | import androidx.compose.ui.platform.testTag
31 | import androidx.compose.ui.res.stringResource
32 | import androidx.compose.ui.semantics.semantics
33 | import androidx.compose.ui.text.TextRange
34 | import androidx.compose.ui.text.input.ImeAction
35 | import androidx.compose.ui.text.input.KeyboardType
36 | import androidx.compose.ui.text.input.TextFieldValue
37 | import androidx.compose.ui.tooling.preview.Preview
38 | import emperorfin.android.components.ui.fortesting.textFieldKeyboardOptionsImeAction
39 | import emperorfin.android.components.ui.fortesting.textFieldKeyboardOptionsKeyboardType
40 | import emperorfin.android.components.ui.fortesting.textFieldLeadingIconContentDescription
41 | import emperorfin.android.components.ui.fortesting.textFieldLeadingIconImageVector
42 | import emperorfin.android.components.ui.fortesting.textFieldSingleLine
43 | import emperorfin.android.components.R
44 | import emperorfin.android.components.ui.fortesting.*
45 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
46 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_INPUT_EMAIL
47 |
48 |
49 | /**
50 | * @Author: Francis Nwokelo (emperorfin)
51 | * @Date: November, 2022.
52 | */
53 |
54 |
55 | @OptIn(ExperimentalMaterial3Api::class)
56 | @Composable
57 | fun EmailInput(
58 | modifier: Modifier = Modifier,
59 | isTest: Boolean = FALSE,
60 | email: String?,
61 | onEmailChanged: (email: String) -> Unit,
62 | onNextClicked: () -> Unit
63 | ) {
64 |
65 | val emailOrEmptyString: String = email ?: ""
66 |
67 | val textFieldValue = TextFieldValue(
68 | text = emailOrEmptyString,
69 | selection = TextRange(index = email?.length ?: STRING_LENGTH_0)
70 | )
71 |
72 | val leadingIconImageVector: ImageVector = Icons.Default.Email
73 | val leadingIconContentDescription: String = stringResource(
74 | id = R.string.content_description_email_input_leading_icon
75 | )
76 |
77 | val singleLine: Boolean = TRUE
78 |
79 | val keyboardOptionsKeyboardType: KeyboardType = KeyboardType.Email
80 | val keyboardOptionsImeAction: ImeAction = ImeAction.Next
81 |
82 | val modifier2: Modifier = modifier
83 | .testTag(TAG_AUTHENTICATION_INPUT_EMAIL)
84 | // For testing
85 | .semantics {
86 | // this.textFieldLeadingIconImageVector = leadingIconImageVector // Or
87 | textFieldLeadingIconImageVector = leadingIconImageVector
88 |
89 | textFieldLeadingIconContentDescription = leadingIconContentDescription
90 |
91 | textFieldKeyboardOptionsKeyboardType = keyboardOptionsKeyboardType
92 |
93 | textFieldKeyboardOptionsImeAction = keyboardOptionsImeAction
94 |
95 | textFieldSingleLine = singleLine
96 | }
97 |
98 | val label: @Composable () -> Unit = {
99 | Text(
100 | text = stringResource(id = R.string.label_email)
101 | )
102 | }
103 |
104 | val leadingIcon: @Composable () -> Unit = {
105 | Icon(
106 | // imageVector = Icons.Default.Email,
107 | imageVector = leadingIconImageVector,
108 | contentDescription = leadingIconContentDescription
109 | )
110 | }
111 |
112 | val keyboardOptions = KeyboardOptions(
113 | keyboardType = keyboardOptionsKeyboardType,
114 | imeAction = keyboardOptionsImeAction,
115 | )
116 |
117 | val keyboardActions = KeyboardActions(
118 | onNext = {
119 | onNextClicked()
120 | }
121 | )
122 |
123 | if (!isTest) {
124 | TextField(
125 | modifier = modifier2,
126 | value = emailOrEmptyString,
127 | onValueChange = { emailAddress ->
128 | onEmailChanged(emailAddress)
129 | },
130 | label = label,
131 | singleLine = singleLine,
132 | leadingIcon = leadingIcon,
133 | keyboardOptions = keyboardOptions,
134 | keyboardActions = keyboardActions
135 | )
136 | } else {
137 | TextField(
138 | modifier = modifier2,
139 | value = textFieldValue,
140 | onValueChange = { emailTextFieldValue ->
141 | onEmailChanged(emailTextFieldValue.text)
142 | },
143 | label = label,
144 | singleLine = singleLine,
145 | leadingIcon = leadingIcon,
146 | keyboardOptions = keyboardOptions,
147 | keyboardActions = keyboardActions
148 | )
149 | }
150 |
151 | }
152 |
153 | @Composable
154 | @Preview(showBackground = true)
155 | private fun EmailInputPreview() {
156 | ComposeEmailAuthenticationTheme {
157 | EmailInput(
158 | email = "email",
159 | onEmailChanged = {},
160 | onNextClicked = {}
161 | )
162 | }
163 | }
164 |
165 | private const val FALSE: Boolean = false
166 | private const val TRUE: Boolean = true
167 |
168 | private const val STRING_LENGTH_0: Int = 0
169 |
170 |
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/screens/authentication/uicomponents/AuthenticationTitleTest3.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import android.content.Context
20 | import androidx.compose.ui.test.junit4.ComposeContentTestRule
21 | import androidx.compose.ui.test.junit4.createComposeRule
22 | import androidx.test.platform.app.InstrumentationRegistry
23 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_IN
24 | import emperorfin.android.components.ui.screens.authentication.enums.AuthenticationMode.SIGN_UP
25 | import emperorfin.android.components.ui.screens.authentication.enums.PasswordRequirement
26 | import emperorfin.android.components.ui.utils.AuthenticationTitleTestUtil
27 | import org.junit.Before
28 | import org.junit.Rule
29 | import org.junit.Test
30 |
31 |
32 | /**
33 | * @Author: Francis Nwokelo (emperorfin)
34 | * @Date: Wednesday 15th March, 2023.
35 | */
36 |
37 |
38 | /**
39 | * The following classes are revisions of this class:
40 | * [AuthenticationTitleTest4]
41 | * [AuthenticationTitleTest5]
42 | *
43 | * Notes:
44 | *
45 | * - If you ever need to pass a resource (e.g. a string resource) into a composable during testing,
46 | * be sure to use the one from the main source set and the R must be the one from
47 | * [emperorfin.android.components.R] and not
48 | * [emperorfin.android.components.test.R].
49 | * - Every other thing during testing that involves the use of a resource (e.g. a string resource)
50 | * such as performing matches or assertions, be sure to use the resource from the androidTest source
51 | * set (which you should've provided a copy and always in sync with the one from the main source set).
52 | * And the R must be the one from [emperorfin.android.components.test.R] instead of
53 | * [emperorfin.android.components.R].
54 | *
55 | * Be sure to have Configured res srcDirs for androidTest sourceSet in app/build.gradle file.
56 | * See the following:
57 | * - https://stackoverflow.com/questions/36955608/espresso-how-to-use-r-string-resources-of-androidtest-folder
58 | * - https://stackoverflow.com/questions/26663539/configuring-res-srcdirs-for-androidtest-sourceset
59 | */
60 | class AuthenticationTitleTest3 {
61 |
62 | private companion object {
63 |
64 | private const val FALSE: Boolean = false
65 |
66 | }
67 |
68 | /**
69 | * Use this when resources are coming from the main source set, whether directly
70 | * (e.g. R.string.sample_text) or indirectly (e.g. [PasswordRequirement.EIGHT_CHARACTERS.label]
71 | * which is directly using a string resource).
72 | *
73 | * To actually reference the resource, you use
74 | * [emperorfin.android.components.R] and not
75 | * [emperorfin.android.components.test.R]
76 | *
77 | * So let's say if you want to reference a string resource, that string resource should come
78 | * from app/src/main/res/values/strings.xml XML resource file which must be, as you may have
79 | * noticed, from the main source set.
80 | */
81 | private lateinit var mTargetContext: Context
82 |
83 | /**
84 | * Use this when resources are coming from the androidTest or test source set. In this case, the
85 | * resources should come from androidTest (not test) source set.
86 | *
87 | * To actually reference the resource, you use
88 | * [emperorfin.android.components.test.R] and not
89 | * [emperorfin.android.components.R]
90 | *
91 | * So let's say if you want to reference a string resource, that string resource should come
92 | * from app/src/androidTest/res/values/strings.xml XML resource file which must be, as you may
93 | * have noticed, from the androidTest source set. And always update this file with the changes
94 | * made to the app/src/main/res/values/strings.xml XML resource file from the main source set.
95 | * And of course, you may/should re-run existing test(s) to be sure they don't fail as a result
96 | * of the synchronization.
97 | *
98 | * Be sure to have Configured res srcDirs for androidTest sourceSet in app/build.gradle file.
99 | * See the following:
100 | * - https://stackoverflow.com/questions/36955608/espresso-how-to-use-r-string-resources-of-androidtest-folder
101 | * - https://stackoverflow.com/questions/26663539/configuring-res-srcdirs-for-androidtest-sourceset
102 | */
103 | private lateinit var mContext: Context
104 |
105 | @get:Rule
106 | val composeTestRule: ComposeContentTestRule = createComposeRule()
107 |
108 | private lateinit var authenticationTitleTestUtil: AuthenticationTitleTestUtil
109 |
110 | @Before
111 | fun setUpContexts() {
112 |
113 | // See field's KDoc for more info.
114 | mTargetContext = InstrumentationRegistry.getInstrumentation().targetContext
115 | // mTargetContext = ApplicationProvider.getApplicationContext() // Haven't tested but might work.
116 | // See field's KDoc for more info.
117 | mContext = InstrumentationRegistry.getInstrumentation().context
118 |
119 | authenticationTitleTestUtil = AuthenticationTitleTestUtil(
120 | mContext = mContext,
121 | mTargetContext = mTargetContext,
122 | composeTestRule = composeTestRule
123 | )
124 |
125 | }
126 |
127 | @Test
128 | fun sign_In_Title_Displayed() {
129 |
130 | authenticationTitleTestUtil
131 | .setContentAsAuthenticationTitleAndAssertItIsDisplayed(
132 | authenticationMode = SIGN_IN
133 | )
134 |
135 | }
136 |
137 | @Test
138 | fun sign_Up_Title_Displayed() {
139 |
140 | authenticationTitleTestUtil
141 | .setContentAsAuthenticationTitleAndAssertItIsDisplayed(
142 | authenticationMode = SIGN_UP
143 | )
144 |
145 | }
146 |
147 | }
--------------------------------------------------------------------------------
/components/src/main/java/emperorfin/android/components/ui/screens/authentication/uicomponents/Requirement.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2023 Francis Nwokelo (emperorfin)
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package emperorfin.android.components.ui.screens.authentication.uicomponents
18 |
19 | import androidx.compose.foundation.layout.*
20 | import androidx.compose.material.icons.Icons
21 | import androidx.compose.material.icons.filled.Check
22 | import androidx.compose.material3.Icon
23 | import androidx.compose.material3.MaterialTheme
24 | import androidx.compose.material3.Text
25 | import androidx.compose.runtime.Composable
26 | import androidx.compose.ui.Alignment
27 | import androidx.compose.ui.Modifier
28 | import androidx.compose.ui.graphics.Color
29 | import androidx.compose.ui.graphics.toArgb
30 | import androidx.compose.ui.graphics.vector.ImageVector
31 | import androidx.compose.ui.platform.testTag
32 | import androidx.compose.ui.res.dimensionResource
33 | import androidx.compose.ui.res.stringResource
34 | import androidx.compose.ui.semantics.clearAndSetSemantics
35 | import androidx.compose.ui.semantics.semantics
36 | import androidx.compose.ui.semantics.testTag
37 | import androidx.compose.ui.semantics.text
38 | import androidx.compose.ui.text.AnnotatedString
39 | import androidx.compose.ui.tooling.preview.Preview
40 | import androidx.compose.ui.unit.TextUnit
41 | import androidx.compose.ui.unit.sp
42 | import emperorfin.android.components.R
43 | import emperorfin.android.components.ui.fortesting.iconContentDescription
44 | import emperorfin.android.components.ui.fortesting.iconImageVector
45 | import emperorfin.android.components.ui.fortesting.iconTintArgb
46 | import emperorfin.android.components.ui.fortesting.textColorArgb
47 | import emperorfin.android.components.ui.res.theme.ComposeEmailAuthenticationTheme
48 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_PASSWORD_REQUIREMENT
49 | import emperorfin.android.components.ui.screens.authentication.uicomponents.tags.Tags.TAG_AUTHENTICATION_PASSWORD_REQUIREMENT_ICON
50 |
51 |
52 | /**
53 | * @Author: Francis Nwokelo (emperorfin)
54 | * @Date: November, 2022.
55 | */
56 |
57 |
58 | // TODO: Rename this composable from Requirement to PasswordRequirement and this should reflect in
59 | // occurrences such as in utility function names such as setContentAsRequirement() (to
60 | // setContentAsPasswordRequirement()) in
61 | // app/src/androidTest/java/emperorfin/android/militaryjet/ui/screens/authentication/uicomponents/RequirementTest2.kt
62 | // Also, the following file name should changes accordingly:
63 | // app/src/main/java/emperorfin/android/militaryjet/ui/screens/authentication/uicomponents/Requirement.kt.another_approach
64 | @Composable
65 | fun Requirement(
66 | modifier: Modifier = Modifier,
67 | message: String,
68 | satisfied: Boolean
69 | ) {
70 |
71 | val iconImageVector: ImageVector = Icons.Default.Check
72 | val iconContentDescription: String = if (satisfied) {
73 | stringResource(
74 | id = R.string.content_description_icon_password_requirement_satisfied
75 | )
76 | } else {
77 | stringResource(
78 | id = R.string.content_description_icon_password_requirement_needed
79 | )
80 | }
81 | val tint: Color = if (satisfied) {
82 | MaterialTheme.colorScheme.primary
83 | } else MaterialTheme.colorScheme.onSurface.copy(alpha = COLOR_ALPHA)
84 |
85 | val requirementStatus: String = if (satisfied) {
86 | stringResource(id = R.string.password_requirement_satisfied, message)
87 | } else {
88 | stringResource(id = R.string.password_requirement_needed, message)
89 | }
90 |
91 | Row(
92 | modifier = Modifier
93 | .padding(
94 | all = dimensionResource(id = R.dimen.padding_all_6)
95 | )
96 | .semantics(mergeDescendants = TRUE) {
97 | // this.text = AnnotatedString(requirementStatus) // Or
98 | text = AnnotatedString(requirementStatus)
99 | testTag = TAG_AUTHENTICATION_PASSWORD_REQUIREMENT + message
100 |
101 | textColorArgb = tint.toArgb()
102 | },
103 | verticalAlignment = Alignment.CenterVertically
104 | ) {
105 | Icon(
106 | modifier = Modifier
107 | .testTag(tag = TAG_AUTHENTICATION_PASSWORD_REQUIREMENT_ICON)
108 | .size(
109 | size = dimensionResource(id = R.dimen.size_12)
110 | )
111 | .semantics {
112 | this.iconImageVector = iconImageVector
113 | this.iconContentDescription = iconContentDescription
114 | this.iconTintArgb = tint.toArgb()
115 | },
116 | imageVector = iconImageVector,
117 | contentDescription = iconContentDescription,
118 | tint = tint
119 | )
120 |
121 | Spacer(
122 | modifier = Modifier.width(
123 | width = dimensionResource(id = R.dimen.width_8)
124 | )
125 | )
126 |
127 | Text(
128 | modifier = Modifier
129 | .clearAndSetSemantics { },
130 | // .testTag(tag = TAG_AUTHENTICATION_PASSWORD_REQUIREMENT + requirementStatus)
131 | // .semantics { textColorArgb = tint.toArgb() },
132 | text = message,
133 | fontSize = FONT_SIZE_SP_12,
134 | color = tint
135 | )
136 | }
137 |
138 | }
139 |
140 | @Composable
141 | @Preview(showBackground = TRUE)
142 | private fun RequirementPreview() {
143 | ComposeEmailAuthenticationTheme {
144 | Requirement(
145 | modifier = Modifier,
146 | message = "message",
147 | satisfied = TRUE
148 | )
149 | }
150 | }
151 |
152 | private const val TRUE: Boolean = true
153 |
154 | private const val COLOR_ALPHA: Float = 0.4f
155 |
156 | private val FONT_SIZE_SP_12: TextUnit = 12.sp
157 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | #
4 | # Copyright 2015 the original author or 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 |
19 | ##############################################################################
20 | ##
21 | ## Gradle start up script for UN*X
22 | ##
23 | ##############################################################################
24 |
25 | # Attempt to set APP_HOME
26 | # Resolve links: $0 may be a link
27 | PRG="$0"
28 | # Need this for relative symlinks.
29 | while [ -h "$PRG" ] ; do
30 | ls=`ls -ld "$PRG"`
31 | link=`expr "$ls" : '.*-> \(.*\)$'`
32 | if expr "$link" : '/.*' > /dev/null; then
33 | PRG="$link"
34 | else
35 | PRG=`dirname "$PRG"`"/$link"
36 | fi
37 | done
38 | SAVED="`pwd`"
39 | cd "`dirname \"$PRG\"`/" >/dev/null
40 | APP_HOME="`pwd -P`"
41 | cd "$SAVED" >/dev/null
42 |
43 | APP_NAME="Gradle"
44 | APP_BASE_NAME=`basename "$0"`
45 |
46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
48 |
49 | # Use the maximum available, or set MAX_FD != -1 to use that value.
50 | MAX_FD="maximum"
51 |
52 | warn () {
53 | echo "$*"
54 | }
55 |
56 | die () {
57 | echo
58 | echo "$*"
59 | echo
60 | exit 1
61 | }
62 |
63 | # OS specific support (must be 'true' or 'false').
64 | cygwin=false
65 | msys=false
66 | darwin=false
67 | nonstop=false
68 | case "`uname`" in
69 | CYGWIN* )
70 | cygwin=true
71 | ;;
72 | Darwin* )
73 | darwin=true
74 | ;;
75 | MINGW* )
76 | msys=true
77 | ;;
78 | NONSTOP* )
79 | nonstop=true
80 | ;;
81 | esac
82 |
83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
84 |
85 |
86 | # Determine the Java command to use to start the JVM.
87 | if [ -n "$JAVA_HOME" ] ; then
88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
89 | # IBM's JDK on AIX uses strange locations for the executables
90 | JAVACMD="$JAVA_HOME/jre/sh/java"
91 | else
92 | JAVACMD="$JAVA_HOME/bin/java"
93 | fi
94 | if [ ! -x "$JAVACMD" ] ; then
95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
96 |
97 | Please set the JAVA_HOME variable in your environment to match the
98 | location of your Java installation."
99 | fi
100 | else
101 | JAVACMD="java"
102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
103 |
104 | Please set the JAVA_HOME variable in your environment to match the
105 | location of your Java installation."
106 | fi
107 |
108 | # Increase the maximum file descriptors if we can.
109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
110 | MAX_FD_LIMIT=`ulimit -H -n`
111 | if [ $? -eq 0 ] ; then
112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
113 | MAX_FD="$MAX_FD_LIMIT"
114 | fi
115 | ulimit -n $MAX_FD
116 | if [ $? -ne 0 ] ; then
117 | warn "Could not set maximum file descriptor limit: $MAX_FD"
118 | fi
119 | else
120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
121 | fi
122 | fi
123 |
124 | # For Darwin, add options to specify how the application appears in the dock
125 | if $darwin; then
126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
127 | fi
128 |
129 | # For Cygwin or MSYS, switch paths to Windows format before running java
130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
133 |
134 | JAVACMD=`cygpath --unix "$JAVACMD"`
135 |
136 | # We build the pattern for arguments to be converted via cygpath
137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
138 | SEP=""
139 | for dir in $ROOTDIRSRAW ; do
140 | ROOTDIRS="$ROOTDIRS$SEP$dir"
141 | SEP="|"
142 | done
143 | OURCYGPATTERN="(^($ROOTDIRS))"
144 | # Add a user-defined pattern to the cygpath arguments
145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
147 | fi
148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
149 | i=0
150 | for arg in "$@" ; do
151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
153 |
154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
156 | else
157 | eval `echo args$i`="\"$arg\""
158 | fi
159 | i=`expr $i + 1`
160 | done
161 | case $i in
162 | 0) set -- ;;
163 | 1) set -- "$args0" ;;
164 | 2) set -- "$args0" "$args1" ;;
165 | 3) set -- "$args0" "$args1" "$args2" ;;
166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
172 | esac
173 | fi
174 |
175 | # Escape application args
176 | save () {
177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
178 | echo " "
179 | }
180 | APP_ARGS=`save "$@"`
181 |
182 | # Collect all arguments for the java command, following the shell quoting and substitution rules
183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
184 |
185 | exec "$JAVACMD" "$@"
186 |
--------------------------------------------------------------------------------
/components/src/androidTest/java/emperorfin/android/components/ui/utils/KeyboardHelper.kt:
--------------------------------------------------------------------------------
1 | package emperorfin.android.components.ui.utils
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.ContextWrapper
6 | import android.os.Build
7 | import android.view.View
8 | import android.view.Window
9 | import android.view.WindowInsets
10 | import android.view.WindowInsetsAnimation
11 | import android.view.inputmethod.InputMethodManager
12 | import androidx.annotation.RequiresApi
13 | import androidx.compose.runtime.Composable
14 | import androidx.compose.ui.ExperimentalComposeUiApi
15 | import androidx.compose.ui.platform.LocalView
16 | import androidx.compose.ui.test.junit4.ComposeContentTestRule
17 | import androidx.compose.ui.window.DialogWindowProvider
18 | import androidx.core.view.WindowInsetsCompat
19 | import androidx.core.view.WindowInsetsControllerCompat
20 | import com.google.common.truth.Truth.assertThat
21 | import java.util.concurrent.CountDownLatch
22 | import java.util.concurrent.TimeUnit
23 |
24 | /**
25 | * @Author: Francis Nwokelo (emperorfin)
26 | * @Date: Friday 16th December, 2022.
27 | */
28 |
29 | /**
30 | * Helper methods for hiding and showing the keyboard in tests.
31 | * Call [initialize] from your test rule's content before calling any other methods on this class.
32 | *
33 | * This class is a copy of [this](https://github.com/androidx/androidx/blob/androidx-main/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/KeyboardHelper.kt)
34 | */
35 | @OptIn(ExperimentalComposeUiApi::class)
36 | class KeyboardHelper(
37 | private val composeRule: ComposeContentTestRule,
38 | private val timeout: Long = 15_000L
39 | ) {
40 | /**
41 | * The [View] hosting the compose rule's content. Must be set before calling any methods on this
42 | * class.
43 | */
44 | private lateinit var view: View
45 | private val imm by lazy {
46 | view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
47 | }
48 |
49 | /**
50 | * Call this at the top of your test composition before using the helper.
51 | */
52 | @Composable
53 | fun initialize() {
54 | view = LocalView.current
55 | }
56 |
57 | /**
58 | * Requests the keyboard to be hidden without waiting for it.
59 | */
60 | fun hideKeyboard() {
61 | composeRule.runOnIdle {
62 | // Use both techniques to hide it, at least one of them will hopefully work.
63 | hideKeyboardWithInsets()
64 | hideKeyboardWithImm()
65 | }
66 | }
67 |
68 | /**
69 | * Blocks until the [timeout] or the keyboard's visibility matches [visible].
70 | * May be called from the test thread or the main thread.
71 | */
72 | fun waitForKeyboardVisibility(visible: Boolean) {
73 | waitUntil(timeout) {
74 | isSoftwareKeyboardShown() == visible
75 | }
76 | }
77 |
78 | fun hideKeyboardIfShown() {
79 | if (composeRule.runOnIdle { isSoftwareKeyboardShown() }) {
80 | hideKeyboard()
81 | waitForKeyboardVisibility(visible = false)
82 | }
83 | }
84 |
85 | fun isSoftwareKeyboardShown(): Boolean {
86 | return if (Build.VERSION.SDK_INT >= 23) {
87 | isSoftwareKeyboardShownWithInsets()
88 | } else {
89 | isSoftwareKeyboardShownWithImm()
90 | }
91 | }
92 |
93 | @RequiresApi(23)
94 | private fun isSoftwareKeyboardShownWithInsets(): Boolean {
95 | val insets = view.rootWindowInsets ?: return false
96 | val insetsCompat = WindowInsetsCompat.toWindowInsetsCompat(insets, view)
97 | return insetsCompat.isVisible(WindowInsetsCompat.Type.ime())
98 | }
99 |
100 | private fun isSoftwareKeyboardShownWithImm(): Boolean {
101 | // TODO(b/163742556): This is just a proxy for software keyboard visibility. Find a better
102 | // way to check if the software keyboard is shown.
103 | return imm.isAcceptingText
104 | }
105 |
106 | private fun hideKeyboardWithImm() {
107 | view.post {
108 | imm.hideSoftInputFromWindow(view.windowToken, 0)
109 | }
110 | }
111 |
112 | private fun hideKeyboardWithInsets() {
113 | view.findWindow()?.let { WindowInsetsControllerCompat(it, view) }
114 | ?.hide(WindowInsetsCompat.Type.ime())
115 | }
116 |
117 | private fun waitUntil(timeout: Long, condition: () -> Boolean) {
118 | if (Build.VERSION.SDK_INT >= 30) {
119 | view.waitForWindowInsetsUntil(timeout, condition)
120 | } else {
121 | composeRule.waitUntil(timeout, condition)
122 | }
123 | }
124 |
125 | // TODO(b/221889664) Replace with composition local when available.
126 | private fun View.findWindow(): Window? =
127 | (parent as? DialogWindowProvider)?.window
128 | ?: context.findWindow()
129 |
130 | private tailrec fun Context.findWindow(): Window? =
131 | when (this) {
132 | is Activity -> window
133 | is ContextWrapper -> baseContext.findWindow()
134 | else -> null
135 | }
136 |
137 | @RequiresApi(30)
138 | fun View.waitForWindowInsetsUntil(timeoutMillis: Long, condition: () -> Boolean) {
139 | val latch = CountDownLatch(1)
140 | rootView.setOnApplyWindowInsetsListener { view, windowInsets ->
141 | if (condition()) {
142 | latch.countDown()
143 | }
144 | view.onApplyWindowInsets(windowInsets)
145 | windowInsets
146 | }
147 | rootView.setWindowInsetsAnimationCallback(
148 | InsetAnimationCallback {
149 | if (condition()) {
150 | latch.countDown()
151 | }
152 | }
153 | )
154 |
155 | // if condition already met return
156 | if (condition()) return
157 |
158 | // else wait for condition to be met
159 | val conditionMet = latch.await(timeoutMillis, TimeUnit.MILLISECONDS)
160 | assertThat(conditionMet).isTrue()
161 | }
162 | }
163 |
164 | @RequiresApi(30)
165 | private class InsetAnimationCallback(val block: () -> Unit) :
166 | WindowInsetsAnimation.Callback(DISPATCH_MODE_CONTINUE_ON_SUBTREE) {
167 |
168 | override fun onProgress(
169 | insets: WindowInsets,
170 | runningAnimations: MutableList
171 | ) = insets
172 |
173 | override fun onEnd(animation: WindowInsetsAnimation) {
174 | block()
175 | super.onEnd(animation)
176 | }
177 | }
--------------------------------------------------------------------------------