From d8f966849e2f89a179676ed7ce326a234f2e000c Mon Sep 17 00:00:00 2001 From: "@tinacious" Date: Wed, 11 Sep 2024 18:06:53 -0400 Subject: [PATCH] stocks app --- .editorconfig | 9 + .github/workflows/android.yml | 19 ++ .github/workflows/deploy.yml | 54 +++++ .gitignore | 17 ++ README.md | 0 app/.gitignore | 1 + app/build.gradle.kts | 119 +++++++++++ app/proguard-rules.pro | 21 ++ .../interviews/stocks/HiltTestRunner.kt | 13 ++ .../stocks/StockSearchBehaviourTest.kt | 93 +++++++++ .../interviews/stocks/UiTestUtils.kt | 49 +++++ .../stocks/db/stock/StockDaoTest.kt | 123 ++++++++++++ .../interviews/stocks/di/AppTestModule.kt | 83 ++++++++ app/src/main/AndroidManifest.xml | 42 ++++ app/src/main/ic_launcher-playstore.png | Bin 0 -> 57949 bytes .../interviews/stocks/MainActivity.kt | 136 +++++++++++++ .../interviews/stocks/StocksApplication.kt | 22 +++ .../interviews/stocks/config/AppConfig.kt | 5 + .../interviews/stocks/db/.gitkeep | 0 .../interviews/stocks/db/AppDatabase.kt | 16 ++ .../interviews/stocks/db/stock/StockDao.kt | 33 ++++ .../interviews/stocks/db/stock/StockEntity.kt | 29 +++ .../interviews/stocks/di/AppModule.kt | 108 ++++++++++ .../interviews/stocks/events/AppEvent.kt | 7 + .../interviews/stocks/events/EventBus.kt | 40 ++++ .../stocks/events/ObserveAsEvents.kt | 27 +++ .../stocks/logging/CrashReportingTree.kt | 26 +++ .../stocks/logging/DebugConsoleLoggingTree.kt | 17 ++ .../interviews/stocks/logging/Logger.kt | 5 + .../interviews/stocks/models/Stock.kt | 37 ++++ .../stocks/navigation/BottomNavigationBar.kt | 64 ++++++ .../stocks/navigation/NavigationRouter.kt | 27 +++ .../interviews/stocks/navigation/Route.kt | 40 ++++ .../interviews/stocks/networking/ApiError.kt | 6 + .../interviews/stocks/networking/ApiResult.kt | 24 +++ .../stocks/networking/api/StocksApi.kt | 14 ++ .../stocks/repositories/StocksRepository.kt | 62 ++++++ .../interviews/stocks/ui/TestTags.kt | 5 + .../stocks/ui/components/Divider.kt | 22 +++ .../stocks/ui/components/EmptyState.kt | 44 +++++ .../stocks/ui/components/SearchInputView.kt | 51 +++++ .../components/StockSearchResultListItem.kt | 64 ++++++ .../ui/components/StockSearchResults.kt | 36 ++++ .../stocks/ui/components/UnstyledButton.kt | 25 +++ .../stocks/ui/icons/IconDrawable.kt | 34 ++++ .../ui/icons/TinaciousDesignLogoIcon.kt | 27 +++ .../stocks/ui/icons/TintedIconDrawable.kt | 22 +++ .../stocks/ui/screens/about/AboutScreen.kt | 59 ++++++ .../screens/stocksearch/StockSearchScreen.kt | 54 +++++ .../stocksearch/StockSearchViewModel.kt | 71 +++++++ .../stocks/ui/snackbar/SnackBarController.kt | 26 +++ .../interviews/stocks/ui/theme/Color.kt | 29 +++ .../interviews/stocks/ui/theme/Theme.kt | 58 ++++++ .../interviews/stocks/ui/theme/Type.kt | 18 ++ .../stocks/ui/utils/KeyboardVisibleState.kt | 43 ++++ .../utils/ObserveInternetConnectionState.kt | 40 ++++ .../stocks/ui/utils/ObserveSnackBarEvents.kt | 36 ++++ .../interviews/stocks/utils/.gitkeep | 0 .../interviews/stocks/utils/IntentUtils.kt | 12 ++ .../interviews/stocks/utils/StringUtils.kt | 4 + .../interviews/stocks/workers/.gitkeep | 0 app/src/main/res/drawable/ic_dollar.xml | 9 + .../res/drawable/ic_launcher_background.xml | 170 ++++++++++++++++ .../res/drawable/ic_launcher_foreground.xml | 30 +++ app/src/main/res/drawable/ic_search.xml | 9 + .../res/drawable/tinacious_design_logo.xml | 26 +++ .../res/mipmap-anydpi-v26/ic_launcher.xml | 5 + .../mipmap-anydpi-v26/ic_launcher_round.xml | 5 + app/src/main/res/mipmap-hdpi/ic_launcher.webp | Bin 0 -> 2202 bytes .../mipmap-hdpi/ic_launcher_foreground.webp | Bin 0 -> 3324 bytes .../res/mipmap-hdpi/ic_launcher_round.webp | Bin 0 -> 3964 bytes app/src/main/res/mipmap-mdpi/ic_launcher.webp | Bin 0 -> 1278 bytes .../mipmap-mdpi/ic_launcher_foreground.webp | Bin 0 -> 1976 bytes .../res/mipmap-mdpi/ic_launcher_round.webp | Bin 0 -> 2336 bytes .../main/res/mipmap-xhdpi/ic_launcher.webp | Bin 0 -> 3294 bytes .../mipmap-xhdpi/ic_launcher_foreground.webp | Bin 0 -> 4420 bytes .../res/mipmap-xhdpi/ic_launcher_round.webp | Bin 0 -> 5840 bytes .../main/res/mipmap-xxhdpi/ic_launcher.webp | Bin 0 -> 5326 bytes .../mipmap-xxhdpi/ic_launcher_foreground.webp | Bin 0 -> 7890 bytes .../res/mipmap-xxhdpi/ic_launcher_round.webp | Bin 0 -> 9642 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.webp | Bin 0 -> 7530 bytes .../ic_launcher_foreground.webp | Bin 0 -> 11436 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.webp | Bin 0 -> 13452 bytes app/src/main/res/values/colors.xml | 3 + .../res/values/ic_launcher_background.xml | 4 + app/src/main/res/values/strings.xml | 26 +++ app/src/main/res/values/themes.xml | 5 + app/src/main/res/xml/backup_rules.xml | 13 ++ .../main/res/xml/data_extraction_rules.xml | 19 ++ .../interviews/stocks/models/StockTest.kt | 112 +++++++++++ .../repositories/StocksRepositoryTest.kt | 79 ++++++++ .../stocks/testutils/TestCoroutineRule.kt | 24 +++ .../stocksearch/StockSearchViewModelTest.kt | 115 +++++++++++ build.gradle.kts | 7 + gradle.properties | 23 +++ gradle/libs.versions.toml | 75 +++++++ gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 59203 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 185 ++++++++++++++++++ gradlew.bat | 89 +++++++++ settings.gradle.kts | 23 +++ 101 files changed, 3230 insertions(+) create mode 100644 .editorconfig create mode 100644 .github/workflows/android.yml create mode 100644 .github/workflows/deploy.yml create mode 100644 .gitignore create mode 100644 README.md create mode 100644 app/.gitignore create mode 100644 app/build.gradle.kts create mode 100644 app/proguard-rules.pro create mode 100644 app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/HiltTestRunner.kt create mode 100644 app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/StockSearchBehaviourTest.kt create mode 100644 app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/UiTestUtils.kt create mode 100644 app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/db/stock/StockDaoTest.kt create mode 100644 app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/di/AppTestModule.kt create mode 100644 app/src/main/AndroidManifest.xml create mode 100644 app/src/main/ic_launcher-playstore.png create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/MainActivity.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/StocksApplication.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/config/AppConfig.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/db/.gitkeep create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/db/AppDatabase.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/db/stock/StockDao.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/db/stock/StockEntity.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/di/AppModule.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/events/AppEvent.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/events/EventBus.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/events/ObserveAsEvents.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/CrashReportingTree.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/DebugConsoleLoggingTree.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/Logger.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/models/Stock.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/BottomNavigationBar.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/NavigationRouter.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/Route.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/ApiError.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/ApiResult.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/api/StocksApi.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/repositories/StocksRepository.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/TestTags.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/Divider.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/EmptyState.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/SearchInputView.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/StockSearchResultListItem.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/StockSearchResults.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/UnstyledButton.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/IconDrawable.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/TinaciousDesignLogoIcon.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/TintedIconDrawable.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/about/AboutScreen.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/stocksearch/StockSearchScreen.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/stocksearch/StockSearchViewModel.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/snackbar/SnackBarController.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Color.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Theme.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Type.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/KeyboardVisibleState.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/ObserveInternetConnectionState.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/ObserveSnackBarEvents.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/.gitkeep create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/IntentUtils.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/StringUtils.kt create mode 100644 app/src/main/java/com/tinaciousdesign/interviews/stocks/workers/.gitkeep create mode 100644 app/src/main/res/drawable/ic_dollar.xml create mode 100644 app/src/main/res/drawable/ic_launcher_background.xml create mode 100644 app/src/main/res/drawable/ic_launcher_foreground.xml create mode 100644 app/src/main/res/drawable/ic_search.xml create mode 100644 app/src/main/res/drawable/tinacious_design_logo.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp create mode 100644 app/src/main/res/values/colors.xml create mode 100644 app/src/main/res/values/ic_launcher_background.xml create mode 100644 app/src/main/res/values/strings.xml create mode 100644 app/src/main/res/values/themes.xml create mode 100644 app/src/main/res/xml/backup_rules.xml create mode 100644 app/src/main/res/xml/data_extraction_rules.xml create mode 100644 app/src/test/java/com/tinaciousdesign/interviews/stocks/models/StockTest.kt create mode 100644 app/src/test/java/com/tinaciousdesign/interviews/stocks/repositories/StocksRepositoryTest.kt create mode 100644 app/src/test/java/com/tinaciousdesign/interviews/stocks/testutils/TestCoroutineRule.kt create mode 100644 app/src/test/java/com/tinaciousdesign/interviews/stocks/ui/screens/stocksearch/StockSearchViewModelTest.kt create mode 100644 build.gradle.kts create mode 100644 gradle.properties create mode 100644 gradle/libs.versions.toml create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 settings.gradle.kts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..5245895 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +indent_size = 4 +indent_style = space +insert_final_newline = true + +[*.yml] +indent_size = 2 diff --git a/.github/workflows/android.yml b/.github/workflows/android.yml new file mode 100644 index 0000000..600dd9d --- /dev/null +++ b/.github/workflows/android.yml @@ -0,0 +1,19 @@ +name: Android CI + +on: + push: +jobs: + build-test: + + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - uses: actions/checkout@v4 + - name: set up JDK 11 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: 'temurin' + - name: Build, Lint, Test with Gradle + run: ./gradlew assemble test diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..a851072 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,54 @@ +name: Deploy to Firebase App Tester +on: + push: + branches: + - main + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + - name: set up JDK 17 + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: 'temurin' + + - name: Service account file + env: + FIREBASE_APP_DISTRIBUTION_KEY: "${{secrets.FIREBASE_APP_DISTRIBUTION_KEY}}" + run: | + echo $FIREBASE_APP_DISTRIBUTION_KEY > service_account.json + + - name: Build release + run: ./gradlew assembleRelease + + # https://github.com/marketplace/actions/sign-android-release + - uses: r0adkll/sign-android-release@v1 + name: Sign app APK + # ID used to access action output + id: sign_app + with: + releaseDirectory: "app/build/outputs/apk/release" + signingKeyBase64: ${{ secrets.ANDROID_INTERNAL_SIGNING_KEYSTORE }} + alias: ${{ secrets.ANDROID_INTERNAL_SIGNING_KEY_ALIAS }} + keyStorePassword: ${{ secrets.ANDROID_INTERNAL_SIGNING_STORE_PASSWORD }} + keyPassword: ${{ secrets.ANDROID_INTERNAL_SIGNING_KEY_PASSWORD }} + env: + BUILD_TOOLS_VERSION: "34.0.0" + + - name: Upload artifact to Firebase App Distribution + uses: wzieba/Firebase-Distribution-Github-Action@v1 + env: + FIREBASE_APP_ID: ${{ secrets.FIREBASE_APP_ID }} + FIREBASE_TEST_TEAM: "anyone" + APP_PATH: ${{steps.sign_app.outputs.signedReleaseFile}} + + with: + appId: ${{env.FIREBASE_APP_ID}} + groups: ${{env.FIREBASE_TEST_TEAM}} + serviceCredentialsFile: "service_account.json" + file: ${{env.APP_PATH}} + releaseNotes: "🚀 New build!" diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..30c3b70 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +*.iml +.gradle +/local.properties +/.idea +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +/.idea/inspectionProfiles +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle.kts b/app/build.gradle.kts new file mode 100644 index 0000000..f790d81 --- /dev/null +++ b/app/build.gradle.kts @@ -0,0 +1,119 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.jetbrains.kotlin.android) + kotlin("kapt") + id("com.google.dagger.hilt.android") + id("dagger.hilt.android.plugin") + alias(libs.plugins.kotlinxSerialization) +} + +android { + namespace = "com.tinaciousdesign.interviews.stocks" + compileSdk = 34 + + defaultConfig { + applicationId = "com.tinaciousdesign.interviews.stocks" + minSdk = 29 + targetSdk = 34 + versionCode = 1 + versionName = "1.0" + + testInstrumentationRunner = "com.tinaciousdesign.interviews.stocks.HiltTestRunner" + vectorDrawables { + useSupportLibrary = true + } + + kotlinOptions { + freeCompilerArgs += arrayOf( + "-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi" + ) + } + } + + buildTypes { + release { + isMinifyEnabled = false + proguardFiles( + getDefaultProguardFile("proguard-android-optimize.txt"), + "proguard-rules.pro" + ) + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + buildFeatures { + buildConfig = true + compose = true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.1" + } + packaging { + resources { + excludes += "/META-INF/{AL2.0,LGPL2.1}" + } + } +} + +dependencies { + + implementation(libs.androidx.core.ktx) + implementation(libs.androidx.lifecycle.runtime.ktx) + implementation(libs.androidx.activity.compose) + implementation(platform(libs.androidx.compose.bom)) + implementation(libs.androidx.ui) + implementation(libs.androidx.ui.graphics) + implementation(libs.androidx.ui.tooling.preview) + implementation(libs.androidx.material3) + + // Logging + implementation(libs.timber) + implementation(libs.square.okhttp.logging.interceptor) + + // DI + implementation(libs.hilt.android) + kapt(libs.hilt.android.compiler) + kapt(libs.dagger.compiler) + kapt(libs.hilt.compiler) + + // Networking + implementation(libs.retrofit) + implementation(libs.retrofit.converter.kotlinx.serialization) + implementation(libs.coil.compose) + + // Navigation + implementation(libs.androidx.navigation.compose) + implementation(libs.androidx.hilt.navigation.compose) + implementation(libs.androidx.hilt.navigation.fragment) + implementation(libs.kotlinx.serialization.json) + + // Room database + implementation(libs.androidx.room.runtime) + annotationProcessor(libs.androidx.room.compiler) + kapt(libs.androidx.room.compiler) + implementation(libs.androidx.room.ktx) + + // WorkManager + implementation(libs.androidx.hilt.work) + implementation(libs.androidx.work.runtime.ktx) + + /// Testing + androidTestImplementation(libs.hilt.android.testing) + kaptAndroidTest(libs.hilt.android.compiler) + testImplementation(libs.junit) + testImplementation(libs.mockk) + testImplementation(libs.kotlinx.coroutines.test) + testImplementation(libs.turbine) + androidTestImplementation(libs.turbine) + androidTestImplementation(libs.androidx.junit) + androidTestImplementation(libs.androidx.espresso.core) + androidTestImplementation(platform(libs.androidx.compose.bom)) + androidTestImplementation(libs.androidx.ui.test.junit4) + debugImplementation(libs.androidx.ui.tooling) + debugImplementation(libs.androidx.ui.test.manifest) +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/HiltTestRunner.kt b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/HiltTestRunner.kt new file mode 100644 index 0000000..355efc9 --- /dev/null +++ b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/HiltTestRunner.kt @@ -0,0 +1,13 @@ +package com.tinaciousdesign.interviews.stocks + +import android.app.Application +import android.content.Context +import androidx.test.runner.AndroidJUnitRunner +import dagger.hilt.android.testing.HiltTestApplication + +class HiltTestRunner : AndroidJUnitRunner() { + + override fun newApplication(cl: ClassLoader?, className: String?, context: Context?): Application { + return super.newApplication(cl, HiltTestApplication::class.java.name, context) + } +} diff --git a/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/StockSearchBehaviourTest.kt b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/StockSearchBehaviourTest.kt new file mode 100644 index 0000000..fee3b9b --- /dev/null +++ b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/StockSearchBehaviourTest.kt @@ -0,0 +1,93 @@ +package com.tinaciousdesign.interviews.stocks + +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.Surface +import androidx.compose.ui.Modifier +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performTextInput +import androidx.compose.ui.unit.dp +import androidx.navigation.compose.rememberNavController +import com.tinaciousdesign.interviews.stocks.di.AppModule +import com.tinaciousdesign.interviews.stocks.navigation.BottomNavigationBar +import com.tinaciousdesign.interviews.stocks.navigation.NavigationRouter +import com.tinaciousdesign.interviews.stocks.ui.TestTags +import com.tinaciousdesign.interviews.stocks.ui.theme.StocksTheme +import dagger.hilt.android.testing.HiltAndroidRule +import dagger.hilt.android.testing.HiltAndroidTest +import dagger.hilt.android.testing.UninstallModules +import org.junit.Before +import org.junit.Rule +import org.junit.Test + + +@HiltAndroidTest +@UninstallModules(AppModule::class) +class StockSearchBehaviourTest { + @get:Rule(order = 0) + val hiltRule = HiltAndroidRule(this) + + @get:Rule(order = 1) + val composeRule = createAndroidComposeRule() + + @Before + fun setUp() { + hiltRule.inject() + + composeRule.activity.setContent { + StocksTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + val navController = rememberNavController() + + Scaffold( + modifier = Modifier.fillMaxSize(), + bottomBar = { BottomNavigationBar(navController = navController) } + ) { innerPadding -> + Box( + modifier = Modifier.padding( + PaddingValues( + 0.dp, + 0.dp, + 0.dp, + innerPadding.calculateBottomPadding() + ) + ) + ) { + NavigationRouter(navController) + } + } + } + } + } + } + + @Test + fun userCanSearchForStocks() { + with(composeRule) { + composeRule.onNodeWithText("Use the search field above to find stocks by ticker or by name").assertIsDisplayed() + + composeRule + .onNodeWithTag(TestTags.searchField) + .performTextInput("ow") + + waitForText("POWL") + + textIsDisplayed("POWL") + textIsDisplayed("Omni Resources") + + textIsDisplayed("FVOW") + textIsDisplayed("Harmony Enterprises") + } + } +} diff --git a/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/UiTestUtils.kt b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/UiTestUtils.kt new file mode 100644 index 0000000..885a33e --- /dev/null +++ b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/UiTestUtils.kt @@ -0,0 +1,49 @@ +package com.tinaciousdesign.interviews.stocks + +import androidx.compose.ui.test.ExperimentalTestApi +import androidx.compose.ui.test.assertIsDisplayed +import androidx.compose.ui.test.hasText +import androidx.compose.ui.test.junit4.ComposeTestRule +import androidx.compose.ui.test.onAllNodesWithText +import androidx.compose.ui.test.onNodeWithText +import org.junit.Assert.assertEquals +import org.junit.Assert.assertTrue + +@OptIn(ExperimentalTestApi::class) +fun ComposeTestRule.waitForText( + text: String, + timeoutMillis: Long = 5000 +) { + waitUntilAtLeastOneExists(hasText(text), timeoutMillis = timeoutMillis) +} + +fun ComposeTestRule.textIsDisplayed( + text: String, + expectedOccurrences: Int = 1 +) { + if (expectedOccurrences == 1) { + onNodeWithText(text).assertIsDisplayed() + } else { + assertEquals(onAllNodesWithText(text).fetchSemanticsNodes().size, expectedOccurrences) + } +} + +fun ComposeTestRule.textIsDisplayedAtLeastOnce( + text: String, + minOccurrences: Int = 1 +) { + assertTrue(this.onAllNodesWithText(text).fetchSemanticsNodes().size >= minOccurrences) +} + +@OptIn(ExperimentalTestApi::class) +fun ComposeTestRule.sleep( + timeoutMillis: Long +) { + @Suppress("SwallowedException") + try { + // Random string that will never be found + waitUntilAtLeastOneExists(hasText("446fc9cdc8e63d9f11fb6bacd2f51ef5!"), timeoutMillis = timeoutMillis) + } catch (t: Throwable) { + // swallow this exception + } +} diff --git a/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/db/stock/StockDaoTest.kt b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/db/stock/StockDaoTest.kt new file mode 100644 index 0000000..1476a86 --- /dev/null +++ b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/db/stock/StockDaoTest.kt @@ -0,0 +1,123 @@ +package com.tinaciousdesign.interviews.stocks.db.stock + +import android.content.Context +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import androidx.test.ext.junit.runners.AndroidJUnit4 +import app.cash.turbine.test +import com.tinaciousdesign.interviews.stocks.db.AppDatabase +import kotlinx.coroutines.test.runTest +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test +import org.junit.runner.RunWith +import java.io.IOException + +@RunWith(AndroidJUnit4::class) +class StockDaoTest { + + private lateinit var stockDao: StockDao + private lateinit var db: AppDatabase + + @Before + fun createDb() { + val context = ApplicationProvider.getApplicationContext() + db = Room.inMemoryDatabaseBuilder(context, AppDatabase::class.java).build() + stockDao = db.stocks() + } + + @After + @Throws(IOException::class) + fun closeDb() { + db.close() + } + + @Test + @Throws(Exception::class) + fun insertStocksAndGetAllStocks(): Unit = runTest { + assertEquals(emptyList(), stockDao.getAll()) + + stockDao.insertAll( + listOf( + StockEntity(ticker ="FFF", name = "FFF Co", price = 100.0), + StockEntity(ticker ="DDD", name = "DDD Co", price = 100.0), + StockEntity(ticker ="CCC", name = "CCC Co", price = 100.0), + StockEntity(ticker ="AB", name = "Absolute", price = 100.0), + StockEntity(ticker ="ABC", name = "ABC Industries", price = 100.0), + StockEntity(ticker ="XYZ", name = "XYZ and ABC", price = 100.0), + ) + ) + + val result = stockDao.getAll() + + assertEquals( + listOf( + StockEntity(id = 1, ticker ="FFF", name = "FFF Co", price = 100.0), + StockEntity(id = 2, ticker ="DDD", name = "DDD Co", price = 100.0), + StockEntity(id = 3, ticker ="CCC", name = "CCC Co", price = 100.0), + StockEntity(id = 4, ticker ="AB", name = "Absolute", price = 100.0), + StockEntity(id = 5, ticker ="ABC", name = "ABC Industries", price = 100.0), + StockEntity(id = 6, ticker ="XYZ", name = "XYZ and ABC", price = 100.0), + ), + result, + ) + } + + @Test + @Throws(Exception::class) + fun searchStocks(): Unit = runTest { + assertEquals(emptyList(), stockDao.getAll()) + + stockDao.insertAll( + listOf( + StockEntity(ticker = "FFF", name = "FFF Co", price = 100.0), + StockEntity(ticker = "DDD", name = "DDD Co", price = 100.0), + StockEntity(ticker = "CCC", name = "CCC Co", price = 100.0), + StockEntity(ticker = "AB", name = "Absolute", price = 100.0), + StockEntity(ticker = "ABC", name = "ABC Industries", price = 100.0), + StockEntity(ticker = "XYZ", name = "XYZ and ABC", price = 100.0), + ) + ) + + val result = stockDao.find("ab") + + assertEquals( + listOf( + StockEntity(id = 4, ticker ="AB", name = "Absolute", price = 100.0), + StockEntity(id = 5, ticker ="ABC", name = "ABC Industries", price = 100.0), + StockEntity(id = 6, ticker ="XYZ", name = "XYZ and ABC", price = 100.0), + ), + result, + ) + } + + @Test + @Throws(Exception::class) + fun searchStocksWithFlow(): Unit = runTest { + assertEquals(emptyList(), stockDao.getAll()) + + stockDao.insertAll( + listOf( + StockEntity(ticker = "FFF", name = "FFF Co", price = 100.0), + StockEntity(ticker = "DDD", name = "DDD Co", price = 100.0), + StockEntity(ticker = "CCC", name = "CCC Co", price = 100.0), + StockEntity(ticker = "AB", name = "Absolute", price = 100.0), + StockEntity(ticker = "ABC", name = "ABC Industries", price = 100.0), + StockEntity(ticker = "XYZ", name = "XYZ and ABC", price = 100.0), + ) + ) + + stockDao.findStocksFlow("abc").test { + val result = awaitItem() + + assertEquals( + listOf( + StockEntity(id = 5, ticker ="ABC", name = "ABC Industries", price = 100.0), + StockEntity(id = 6, ticker ="XYZ", name = "XYZ and ABC", price = 100.0), + ), + result, + ) + } + } +} diff --git a/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/di/AppTestModule.kt b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/di/AppTestModule.kt new file mode 100644 index 0000000..ef71c1b --- /dev/null +++ b/app/src/androidTest/java/com/tinaciousdesign/interviews/stocks/di/AppTestModule.kt @@ -0,0 +1,83 @@ +package com.tinaciousdesign.interviews.stocks.di + +import android.content.Context +import androidx.room.Room +import com.tinaciousdesign.interviews.stocks.BuildConfig +import com.tinaciousdesign.interviews.stocks.config.AppConfig +import com.tinaciousdesign.interviews.stocks.db.AppDatabase +import com.tinaciousdesign.interviews.stocks.db.stock.StockDao +import com.tinaciousdesign.interviews.stocks.events.EventBus +import com.tinaciousdesign.interviews.stocks.networking.api.StocksApi +import com.tinaciousdesign.interviews.stocks.repositories.StocksRepository +import com.tinaciousdesign.interviews.stocks.repositories.StocksRepositoryImpl +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import retrofit2.Retrofit +import retrofit2.converter.kotlinx.serialization.asConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class AppTestModule { + // region Repositories + + @Provides @Singleton + fun provideStocksRepository( + stocksApi: StocksApi, + stockDao: StockDao, + ): StocksRepository = StocksRepositoryImpl( + stocksApi, + stockDao, + ) + + @Provides @Singleton + fun provideEventBus(): EventBus = EventBus() + + // endregion Repositories + + // region Networking + + @Provides @Singleton + fun provideRetrofit(): Retrofit { + val converterFactory = Json.asConverterFactory( + "application/json; charset=UTF8".toMediaType() + ) + + return Retrofit.Builder() + .baseUrl(AppConfig.stocksApiBaseUrl) + .addConverterFactory(converterFactory) + .build() + } + + // region Networking -> API + + @Provides @Singleton + fun provideStocksApi(retrofit: Retrofit): StocksApi = retrofit.create(StocksApi::class.java) + + // endregion Networking -> API + + // endregion Networking + + // region Database + + @Provides @Singleton + fun provideAppDatabase( + @ApplicationContext appContext: Context + ): AppDatabase = + Room.inMemoryDatabaseBuilder( + appContext, + AppDatabase::class.java, + ).build() + + @Provides @Singleton + fun provideStockDao(appDatabase: AppDatabase): StockDao = appDatabase.stocks() + + // endregion Database +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..ff902ed --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000000000000000000000000000000000000..62e2bd8e422baf16f567cc960ca0c611b7390ffe GIT binary patch literal 57949 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�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 U@Je4e`4j=#&kUYcs@aGC4*^`pEC2ui literal 0 HcmV?d00001 diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/MainActivity.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/MainActivity.kt new file mode 100644 index 0000000..aaa777f --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/MainActivity.kt @@ -0,0 +1,136 @@ +package com.tinaciousdesign.interviews.stocks + +import android.content.Context +import android.net.ConnectivityManager +import android.net.Network +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.BottomAppBar +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Scaffold +import androidx.compose.material3.SnackbarHost +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.Surface +import androidx.compose.runtime.getValue +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp +import androidx.lifecycle.lifecycleScope +import androidx.navigation.compose.rememberNavController +import com.tinaciousdesign.interviews.stocks.events.AppEvent +import com.tinaciousdesign.interviews.stocks.events.EventBus +import com.tinaciousdesign.interviews.stocks.navigation.BottomNavigationBar +import com.tinaciousdesign.interviews.stocks.navigation.NavigationRouter +import com.tinaciousdesign.interviews.stocks.ui.snackbar.SnackBarController +import com.tinaciousdesign.interviews.stocks.ui.theme.StocksTheme +import com.tinaciousdesign.interviews.stocks.ui.utils.KeyboardState +import com.tinaciousdesign.interviews.stocks.ui.utils.ObserveInternetConnectionState +import com.tinaciousdesign.interviews.stocks.ui.utils.ObserveSnackBarEvents +import com.tinaciousdesign.interviews.stocks.ui.utils.keyboardVisibleState +import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.launch +import javax.inject.Inject + +@AndroidEntryPoint +class MainActivity : ComponentActivity() { + + @Inject + lateinit var eventBus: EventBus + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + registerNetworkListener() + + setContent { + StocksTheme { + Surface( + modifier = Modifier.fillMaxSize(), + color = MaterialTheme.colorScheme.background + ) { + val scope = rememberCoroutineScope() + val navController = rememberNavController() + val snackBarHostState = remember { SnackbarHostState() } + val keyboardState by keyboardVisibleState() + + ObserveInternetConnectionState(eventBus, scope) + ObserveSnackBarEvents(SnackBarController.events, snackBarHostState, scope) + + Scaffold( + modifier = Modifier.fillMaxSize(), + snackbarHost = { + SnackbarHost(hostState = snackBarHostState) + }, + bottomBar = { + if (keyboardState == KeyboardState.Closed) { + BottomAppBar { + BottomNavigationBar(navController = navController) + } + } + } + ) { innerPadding -> + Box( + modifier = Modifier.padding( + PaddingValues( + 0.dp, + 0.dp, + 0.dp, + innerPadding.calculateBottomPadding() + ) + ) + ) { + NavigationRouter(navController) + } + } + } + } + } + } + + + // region Network Listener + + private fun registerNetworkListener() { + val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager + connectivityManager.registerDefaultNetworkCallback(networkListener) + } + + private var hasDisconnected = false + + private val networkListener = object : ConnectivityManager.NetworkCallback() { + override fun onAvailable(network: Network) { + super.onAvailable(network) + + if (hasDisconnected) { + handleNetworkConnectionRestored() + } + } + + override fun onLost(network: Network) { + super.onLost(network) + + hasDisconnected = true + handleNetworkConnectionLost() + } + } + + private fun handleNetworkConnectionLost() { + lifecycleScope.launch { + eventBus.emitEvent(AppEvent.ConnectionLost) + } + } + + private fun handleNetworkConnectionRestored() { + lifecycleScope.launch { + eventBus.emitEvent(AppEvent.ConnectionRestored) + } + } + + // endregion Network Listener +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/StocksApplication.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/StocksApplication.kt new file mode 100644 index 0000000..c2d670f --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/StocksApplication.kt @@ -0,0 +1,22 @@ +package com.tinaciousdesign.interviews.stocks + +import android.app.Application +import com.tinaciousdesign.interviews.stocks.logging.CrashReportingTree +import com.tinaciousdesign.interviews.stocks.logging.DebugConsoleLoggingTree +import dagger.hilt.android.HiltAndroidApp +import timber.log.Timber + +@HiltAndroidApp +class StocksApplication : Application() { + + override fun onCreate() { + super.onCreate() + + setUpLogging() + } + + private fun setUpLogging() { + val loggingTree = if (BuildConfig.DEBUG) DebugConsoleLoggingTree() else CrashReportingTree() + Timber.plant(loggingTree) + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/config/AppConfig.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/config/AppConfig.kt new file mode 100644 index 0000000..468bb7d --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/config/AppConfig.kt @@ -0,0 +1,5 @@ +package com.tinaciousdesign.interviews.stocks.config + +object AppConfig { + val stocksApiBaseUrl = "https://gist.githubusercontent.com/" +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/.gitkeep b/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/AppDatabase.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/AppDatabase.kt new file mode 100644 index 0000000..3c7fd25 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/AppDatabase.kt @@ -0,0 +1,16 @@ +package com.tinaciousdesign.interviews.stocks.db + +import androidx.room.Database +import androidx.room.RoomDatabase +import com.tinaciousdesign.interviews.stocks.db.stock.StockDao +import com.tinaciousdesign.interviews.stocks.db.stock.StockEntity + +@Database( + entities = [ + StockEntity::class + ], + version = 1 +) +abstract class AppDatabase : RoomDatabase() { + abstract fun stocks(): StockDao +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/stock/StockDao.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/stock/StockDao.kt new file mode 100644 index 0000000..ce172a2 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/stock/StockDao.kt @@ -0,0 +1,33 @@ +package com.tinaciousdesign.interviews.stocks.db.stock + +import androidx.room.Dao +import androidx.room.Delete +import androidx.room.Insert +import androidx.room.OnConflictStrategy +import androidx.room.Query +import kotlinx.coroutines.flow.Flow + +@Dao +interface StockDao { + + @Query("SELECT * FROM stocks") + suspend fun getAll(): List + + @Query("SELECT * FROM stocks") + fun stocksFlow(): Flow> + + @Query("SELECT * FROM stocks WHERE LOWER(ticker) LIKE '%' || :query || '%' OR LOWER(name) LIKE '%' || :query || '%'") + suspend fun find(query: String): List + + @Query("SELECT * FROM stocks WHERE LOWER(ticker) LIKE '%' || :query || '%' OR LOWER(name) LIKE '%' || :query || '%'") + fun findStocksFlow(query: String): Flow> + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insert(stock: StockEntity) + + @Insert(onConflict = OnConflictStrategy.REPLACE) + suspend fun insertAll(stocks: List) + + @Query("DELETE FROM stocks") + suspend fun deleteAll() +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/stock/StockEntity.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/stock/StockEntity.kt new file mode 100644 index 0000000..7723686 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/db/stock/StockEntity.kt @@ -0,0 +1,29 @@ +package com.tinaciousdesign.interviews.stocks.db.stock + +import androidx.room.ColumnInfo +import androidx.room.Entity +import androidx.room.Index +import androidx.room.PrimaryKey +import com.tinaciousdesign.interviews.stocks.models.Stock + +@Entity( + tableName = "stocks", + indices = [ + Index(value = ["ticker"], unique = true) + ] +) +data class StockEntity( + @PrimaryKey(autoGenerate = true) val id: Int = 0, + @ColumnInfo(name = "ticker") val ticker: String, + @ColumnInfo(name = "name") val name: String, + @ColumnInfo(name = "price") val price: Double, +) { + companion object { + fun fromStock(stock: Stock): StockEntity = + StockEntity( + ticker = stock.ticker, + name = stock.name, + price = stock.price, + ) + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/di/AppModule.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/di/AppModule.kt new file mode 100644 index 0000000..b77a239 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/di/AppModule.kt @@ -0,0 +1,108 @@ +package com.tinaciousdesign.interviews.stocks.di + +import android.content.Context +import androidx.room.Room +import com.tinaciousdesign.interviews.stocks.BuildConfig +import com.tinaciousdesign.interviews.stocks.config.AppConfig +import com.tinaciousdesign.interviews.stocks.db.AppDatabase +import com.tinaciousdesign.interviews.stocks.db.stock.StockDao +import com.tinaciousdesign.interviews.stocks.events.EventBus +import com.tinaciousdesign.interviews.stocks.logging.Logger +import com.tinaciousdesign.interviews.stocks.networking.api.StocksApi +import com.tinaciousdesign.interviews.stocks.repositories.StocksRepository +import com.tinaciousdesign.interviews.stocks.repositories.StocksRepositoryImpl +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.android.qualifiers.ApplicationContext +import dagger.hilt.components.SingletonComponent +import kotlinx.serialization.json.Json +import okhttp3.MediaType.Companion.toMediaType +import okhttp3.OkHttpClient +import okhttp3.logging.HttpLoggingInterceptor +import okhttp3.logging.HttpLoggingInterceptor.Level +import retrofit2.Retrofit +import retrofit2.converter.kotlinx.serialization.asConverterFactory +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +class AppModule { + + // region Repositories + + @Provides @Singleton + fun provideStocksRepository( + stocksApi: StocksApi, + stockDao: StockDao, + ): StocksRepository = StocksRepositoryImpl( + stocksApi, + stockDao, + ) + + @Provides @Singleton + fun provideEventBus(): EventBus = EventBus() + + // endregion Repositories + + // region Networking + + @Provides @Singleton + fun provideRetrofit( + okHttpClient: OkHttpClient + ): Retrofit { + val converterFactory = Json.asConverterFactory( + "application/json; charset=UTF8".toMediaType() + ) + + return Retrofit.Builder() + .baseUrl(AppConfig.stocksApiBaseUrl) + .client(okHttpClient) + .addConverterFactory(converterFactory) + .build() + } + + @Provides @Singleton + fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor = + HttpLoggingInterceptor { Logger.tag("HttpLog").d(it) } + .apply { + level = if (BuildConfig.DEBUG) Level.BODY else Level.NONE + } + + @Provides @Singleton + fun provideOkHttpClient( + httpLoggingInterceptor: HttpLoggingInterceptor + ): OkHttpClient { + return OkHttpClient.Builder() + // We can add other interceptors here, e.g. auth + .addInterceptor(httpLoggingInterceptor) + .build() + } + + // region Networking -> API + + @Provides @Singleton + fun provideStocksApi(retrofit: Retrofit): StocksApi = retrofit.create(StocksApi::class.java) + + // endregion Networking -> API + + // endregion Networking + + // region Database + + @Provides @Singleton + fun provideAppDatabase( + @ApplicationContext appContext: Context + ): AppDatabase = + Room.databaseBuilder( + appContext, + AppDatabase::class.java, + "stocks", + ) + .build() + + @Provides @Singleton + fun provideStockDao(appDatabase: AppDatabase): StockDao = appDatabase.stocks() + + // endregion Database +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/events/AppEvent.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/events/AppEvent.kt new file mode 100644 index 0000000..210cb93 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/events/AppEvent.kt @@ -0,0 +1,7 @@ +package com.tinaciousdesign.interviews.stocks.events + +sealed class AppEvent { + data object ConnectionLost : AppEvent() + + data object ConnectionRestored : AppEvent() +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/events/EventBus.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/events/EventBus.kt new file mode 100644 index 0000000..ba55dfa --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/events/EventBus.kt @@ -0,0 +1,40 @@ +package com.tinaciousdesign.interviews.stocks.events + +import com.tinaciousdesign.interviews.stocks.logging.Logger +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.flow.asSharedFlow +import kotlinx.coroutines.flow.collectLatest +import kotlinx.coroutines.flow.filterIsInstance +import kotlinx.coroutines.isActive +import kotlinx.coroutines.launch +import kotlin.coroutines.coroutineContext + +class EventBus { + private val _events = MutableSharedFlow(replay = 10) + val events = _events.asSharedFlow() + + suspend fun emitEvent(event: AppEvent) { + Logger.d("🚌🏁 Emitting event = $event") + _events.emit(event) + } + + suspend inline fun subscribe(crossinline onEvent: (T) -> Unit) { + events.filterIsInstance() + .collectLatest { appEvent -> + if (!coroutineContext.isActive) { + Logger.d("🚌🛑 Coroutine inactive - Not collecting event: $appEvent") + return@collectLatest + } + + Logger.d("🚌🛍️ Collecting event: $appEvent") + onEvent(appEvent) + } + } + + inline fun subscribe(coroutineScope: CoroutineScope, crossinline onEvent: (T) -> Unit) { + coroutineScope.launch { + subscribe(onEvent) + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/events/ObserveAsEvents.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/events/ObserveAsEvents.kt new file mode 100644 index 0000000..2981826 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/events/ObserveAsEvents.kt @@ -0,0 +1,27 @@ +package com.tinaciousdesign.interviews.stocks.events + +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LocalLifecycleOwner +import androidx.lifecycle.repeatOnLifecycle +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.withContext + +@Composable +fun ObserveAsEvents( + flow: Flow, + key1: Any? = null, + key2: Any? = null, + onEvent: (T) -> Unit +) { + val lifecycleOwner = LocalLifecycleOwner.current + LaunchedEffect(lifecycleOwner.lifecycle, key1, key2, flow) { + lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { + withContext(Dispatchers.Main.immediate) { + flow.collect(onEvent) + } + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/CrashReportingTree.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/CrashReportingTree.kt new file mode 100644 index 0000000..43fa342 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/CrashReportingTree.kt @@ -0,0 +1,26 @@ +package com.tinaciousdesign.interviews.stocks.logging + +import android.annotation.SuppressLint +import android.util.Log +import timber.log.Timber + +@SuppressLint("LogNotTimber") +class CrashReportingTree : Timber.Tree() { + override fun log(priority: Int, tag: String?, message: String, t: Throwable?) { + if (priority < Log.INFO) return + + logToConsole(priority, tag, message, t) + logToMonitoringService(priority, tag, message, t) + } + + private fun logToConsole(priority: Int, tag: String?, message: String, t: Throwable?) { + when (priority) { + Log.ASSERT, Log.ERROR -> Log.e(tag, message, t) + Log.WARN -> Log.w(tag, message, t) + } + } + + private fun logToMonitoringService(priority: Int, tag: String?, message: String, t: Throwable?) { + // todo: Implement third-party logging service, e.g. Crashlytics + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/DebugConsoleLoggingTree.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/DebugConsoleLoggingTree.kt new file mode 100644 index 0000000..715a307 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/DebugConsoleLoggingTree.kt @@ -0,0 +1,17 @@ +package com.tinaciousdesign.interviews.stocks.logging + +import timber.log.Timber + +/** + * Should only be used in debug builds since references to [StackTraceElement] will be lost in minified builds + */ +class DebugConsoleLoggingTree : Timber.DebugTree() { + override fun createStackElementTag(element: StackTraceElement): String? { + return String.format( + "%s:%s#%s", + element.fileName.replace(".kt", ""), + element.lineNumber, + element.methodName, + ) + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/Logger.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/Logger.kt new file mode 100644 index 0000000..9484362 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/logging/Logger.kt @@ -0,0 +1,5 @@ +package com.tinaciousdesign.interviews.stocks.logging + +import timber.log.Timber + +typealias Logger = Timber diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/models/Stock.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/models/Stock.kt new file mode 100644 index 0000000..b92da85 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/models/Stock.kt @@ -0,0 +1,37 @@ +package com.tinaciousdesign.interviews.stocks.models + +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable + +@Serializable +data class Stock( + @SerialName("ticker") val ticker: String, + @SerialName("name") val name: String, + @SerialName("currentPrice") val price: Double, +) { + val formattedPrice: String get() = "%.2f".format(price) + + companion object { + fun compareQuery(query: String): Comparator = + Comparator { a, b -> + val queryLowered = query.lowercase() + + val aExactMatch = a.name.lowercase() == queryLowered || + a.ticker.lowercase() == queryLowered + val bExactMatch = b.name.lowercase() == queryLowered || + b.ticker.lowercase() == queryLowered + + if (aExactMatch && bExactMatch) { + return@Comparator 0 + } + + if (aExactMatch) -1 else 1 + } + } +} + +fun List.matches(query: String): List = + this.filter { stock -> + stock.ticker.contains(query.trim(), ignoreCase = true) || + stock.name.contains(query.trim(), ignoreCase = true) + } diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/BottomNavigationBar.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/BottomNavigationBar.kt new file mode 100644 index 0000000..afee008 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/BottomNavigationBar.kt @@ -0,0 +1,64 @@ +package com.tinaciousdesign.interviews.stocks.navigation + +import androidx.compose.material3.NavigationBar +import androidx.compose.material3.NavigationBarItem +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue +import androidx.compose.ui.platform.LocalContext +import androidx.navigation.NavController +import com.tinaciousdesign.interviews.stocks.utils.lastSegment + +@Composable +fun BottomNavigationBar(navController: NavController) { + val context = LocalContext.current + + val navItems = listOf( + Route.StockSearch, + Route.About + ) + var selectedItem by remember { mutableStateOf(Route.StockSearch) } + + // Update the active item's highlighted state and navigate to the desired screen + fun handleRouteClicked(route: Route) { + selectedItem = route + + navController.navigate(route) { + navController.graph.startDestinationRoute?.let { startRoute -> + popUpTo(startRoute) { + saveState = true + } + } + launchSingleTop = true + restoreState = true + } + } + + // Update the active item's highlighted state + LaunchedEffect(0) { + navController.addOnDestinationChangedListener { _, destination, _ -> + navItems.forEach { navItem -> + val current = destination.route?.lastSegment(".") + if (current == navItem.routeName) { + selectedItem = navItem + } + } + } + } + + // Render the tabs with icons and localized titles + NavigationBar { + navItems.forEach { route -> + NavigationBarItem( + selected = route == selectedItem, + label = { Text(context.getString(route.titleRes)) }, + icon = route.icon, + onClick = { handleRouteClicked(route) }, + ) + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/NavigationRouter.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/NavigationRouter.kt new file mode 100644 index 0000000..547ff34 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/NavigationRouter.kt @@ -0,0 +1,27 @@ +package com.tinaciousdesign.interviews.stocks.navigation + +import androidx.compose.runtime.Composable +import androidx.hilt.navigation.compose.hiltViewModel +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost +import androidx.navigation.compose.composable +import com.tinaciousdesign.interviews.stocks.ui.screens.about.AboutScreen +import com.tinaciousdesign.interviews.stocks.ui.screens.stocksearch.StockSearchScreen +import com.tinaciousdesign.interviews.stocks.ui.screens.stocksearch.StockSearchViewModel + +@Composable +fun NavigationRouter( + navHostController: NavHostController +) { + NavHost(navController = navHostController, startDestination = Route.StockSearch) { + composable { backStackEntry -> + val viewModel = hiltViewModel() + + StockSearchScreen(viewModel) + } + + composable { backStackEntry -> + AboutScreen() + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/Route.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/Route.kt new file mode 100644 index 0000000..2536c69 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/navigation/Route.kt @@ -0,0 +1,40 @@ +package com.tinaciousdesign.interviews.stocks.navigation + +import androidx.annotation.Keep +import androidx.annotation.StringRes +import androidx.compose.runtime.Composable +import com.tinaciousdesign.interviews.stocks.R +import com.tinaciousdesign.interviews.stocks.ui.icons.TinaciousDesignLogoIcon +import com.tinaciousdesign.interviews.stocks.ui.icons.TintedIconDrawable +import kotlinx.serialization.Serializable + +@Serializable @Keep +sealed class Route { + abstract val icon: @Composable () -> Unit + + @get:StringRes + abstract val titleRes: Int + + val routeName: String? get() = javaClass.simpleName + + @Serializable @Keep + data object StockSearch : Route() { + override val titleRes: Int get() = R.string.route_stock_search + + override val icon: @Composable () -> Unit = { + TintedIconDrawable( + R.drawable.ic_dollar, + R.string.route_stock_search + ) + } + } + + @Serializable @Keep + data object About : Route() { + override val titleRes: Int get() = R.string.route_about + + override val icon: @Composable () -> Unit = { + TinaciousDesignLogoIcon() + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/ApiError.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/ApiError.kt new file mode 100644 index 0000000..a88e460 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/ApiError.kt @@ -0,0 +1,6 @@ +package com.tinaciousdesign.interviews.stocks.networking + + open class ApiError( + cause: Throwable? = null, + message: String? = null, +) : Exception(message, cause) diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/ApiResult.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/ApiResult.kt new file mode 100644 index 0000000..1b19125 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/ApiResult.kt @@ -0,0 +1,24 @@ +package com.tinaciousdesign.interviews.stocks.networking + +sealed class ApiResult { + data class Success( + override val data: ResultData + ) : ApiResult() { + override val ok: Boolean = true + override val error: Error? = null + } + + data class Failed( + override val error: Error + ) : ApiResult() { + override val ok: Boolean = false + override val data: Result? = null + } + + abstract val data: ResultData? + abstract val error: Error? + + abstract val ok: Boolean + + val failed: Boolean get() = !ok +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/api/StocksApi.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/api/StocksApi.kt new file mode 100644 index 0000000..16b978f --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/networking/api/StocksApi.kt @@ -0,0 +1,14 @@ +package com.tinaciousdesign.interviews.stocks.networking.api + +import com.tinaciousdesign.interviews.stocks.models.Stock +import retrofit2.http.GET + +interface StocksApi { + // This endpoint will allow you to search for "omni" or "lol" and see matches where exact matches are prioritized. + // To use, comment out the provided endpoint GET("...") and comment this one back in +// @GET("tinacious/a3ddc32e49c04b5de21e4bb30eb47e68/raw/5b590f6f369fb92fc49e33a14ab2275eb5629c24/mock-stocks.json") + + // Provided endpoint + @GET("priyanshrastogi/0e1d4f8d517698cfdced49f5e59567be/raw/9158ad254e92aaffe215e950f4846a23a0680703/mock-stocks.json") + suspend fun getStocks(): List +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/repositories/StocksRepository.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/repositories/StocksRepository.kt new file mode 100644 index 0000000..0f03aea --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/repositories/StocksRepository.kt @@ -0,0 +1,62 @@ +package com.tinaciousdesign.interviews.stocks.repositories + +import com.tinaciousdesign.interviews.stocks.db.stock.StockDao +import com.tinaciousdesign.interviews.stocks.db.stock.StockEntity +import com.tinaciousdesign.interviews.stocks.logging.Logger +import com.tinaciousdesign.interviews.stocks.models.Stock +import com.tinaciousdesign.interviews.stocks.networking.ApiError +import com.tinaciousdesign.interviews.stocks.networking.ApiResult +import com.tinaciousdesign.interviews.stocks.networking.api.StocksApi +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.flowOn +import kotlinx.coroutines.flow.map +import javax.inject.Inject + +interface StocksRepository { + class GetStocksError(): ApiError() + + fun findStocksFlow(query: String): Flow> + + suspend fun fetchStocks(forceRefresh: Boolean = false): ApiResult, GetStocksError> +} + +class StocksRepositoryImpl @Inject constructor( + private val stocksApi: StocksApi, + private val stockDao: StockDao, +) : StocksRepository { + private var cachedStocks = listOf() + + override fun findStocksFlow(query: String): Flow> { + return stockDao.findStocksFlow(query).flowOn(Dispatchers.IO).map { stocks -> + stocks.map { stockEntity -> + Stock( + ticker = stockEntity.ticker, + name = stockEntity.name, + price = stockEntity.price + ) + } + } + } + + override suspend fun fetchStocks(forceRefresh: Boolean): ApiResult, StocksRepository.GetStocksError> { + if (cachedStocks.isNotEmpty() && !forceRefresh) { + return ApiResult.Success(cachedStocks) + } + + return try { + val response = stocksApi.getStocks() + + cachedStocks = response + + val stockEntities = response.map(StockEntity::fromStock) + stockDao.deleteAll() + stockDao.insertAll(stockEntities) + + ApiResult.Success(response) + } catch (e: Exception) { + Logger.e(e) + ApiResult.Failed(StocksRepository.GetStocksError()) + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/TestTags.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/TestTags.kt new file mode 100644 index 0000000..b0ddd1a --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/TestTags.kt @@ -0,0 +1,5 @@ +package com.tinaciousdesign.interviews.stocks.ui + +object TestTags { + val searchField = "searchField" +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/Divider.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/Divider.kt new file mode 100644 index 0000000..e403539 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/Divider.kt @@ -0,0 +1,22 @@ +package com.tinaciousdesign.interviews.stocks.ui.components + +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.unit.dp + +@Composable +fun Divider( + color: Color, +) { + Box( + modifier = Modifier + .background(color) + .height(1.dp) + .fillMaxWidth() + ) +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/EmptyState.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/EmptyState.kt new file mode 100644 index 0000000..1e8d7a1 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/EmptyState.kt @@ -0,0 +1,44 @@ +package com.tinaciousdesign.interviews.stocks.ui.components + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp + +@Composable +fun EmptyState( + title: String, + message: String, + modifier: Modifier = Modifier, +) { + Box( + contentAlignment = Alignment.Center, + modifier = modifier, + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier.padding(20.dp), + ) { + Text( + text = title, + textAlign = TextAlign.Center, + fontWeight = FontWeight.Bold, + fontSize = 20.sp, + modifier = Modifier.padding(bottom = 20.dp) + ) + Text( + text = message, + textAlign = TextAlign.Center, + fontSize = 17.sp, + ) + } + + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/SearchInputView.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/SearchInputView.kt new file mode 100644 index 0000000..17d77fc --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/SearchInputView.kt @@ -0,0 +1,51 @@ +package com.tinaciousdesign.interviews.stocks.ui.components + +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Button +import androidx.compose.material3.OutlinedTextField +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.platform.testTag +import androidx.compose.ui.unit.dp +import com.tinaciousdesign.interviews.stocks.R +import com.tinaciousdesign.interviews.stocks.ui.TestTags +import com.tinaciousdesign.interviews.stocks.ui.icons.TintedIconDrawable + +@Composable +fun SearchInputView(currentValue: String, onSearch: (String) -> Unit) { + val context = LocalContext.current + + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding( + vertical = 20.dp, + horizontal = 14.dp, + ) + ) { + OutlinedTextField( + value = currentValue, + onValueChange = onSearch, + placeholder = { + Text(context.getString(R.string.search_field_placeholder)) + }, + leadingIcon = { + TintedIconDrawable(R.drawable.ic_search, R.string.search_icon_content_description) + }, + modifier = Modifier + .weight(1.0f) + .padding(end = 14.dp) + .testTag(TestTags.searchField) + ) + + Button({ + onSearch("") + }) { + Text(context.getString(R.string.search_clear_button)) + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/StockSearchResultListItem.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/StockSearchResultListItem.kt new file mode 100644 index 0000000..c8fcd07 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/StockSearchResultListItem.kt @@ -0,0 +1,64 @@ +package com.tinaciousdesign.interviews.stocks.ui.components + +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.width +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import coil.compose.AsyncImage +import com.tinaciousdesign.interviews.stocks.models.Stock + +@Composable +fun StockSearchResultListItem( + stock: Stock +) { + Row( + verticalAlignment = Alignment.CenterVertically, + modifier = Modifier + .padding( + horizontal = 12.dp, + vertical = 12.dp, + ) + ) { + AsyncImage( + model = "https://api.dicebear.com/9.x/glass/png?seed=${stock.ticker}", + contentDescription = null, + contentScale = ContentScale.Crop, + modifier = Modifier + .width(60.dp) + .height(60.dp) + .clip(CircleShape) + ) + + Column( + modifier = Modifier + .padding(start = 12.dp) + ) { + Text( + text = stock.ticker, + fontSize = 17.sp, + fontWeight = FontWeight.SemiBold, + ) + Text( + text = stock.name, + ) + } + + Spacer( + modifier = Modifier.weight(1.0f) + ) + + Text(stock.formattedPrice) + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/StockSearchResults.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/StockSearchResults.kt new file mode 100644 index 0000000..0e2ca24 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/StockSearchResults.kt @@ -0,0 +1,36 @@ +package com.tinaciousdesign.interviews.stocks.ui.components + +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import com.tinaciousdesign.interviews.stocks.R +import com.tinaciousdesign.interviews.stocks.models.Stock + +@Composable +fun StockSearchResults( + stocks: List, +) { + val context = LocalContext.current + + LazyColumn { + itemsIndexed(stocks) { idx, stock -> + StockSearchResultListItem(stock) + + if (idx < stocks.lastIndex) { + Divider(MaterialTheme.colorScheme.surface) + } + } + } + if (stocks.isEmpty()) { + EmptyState( + title = context.getString(R.string.stock_search_no_results_heading), + message = context.getString(R.string.stock_search_no_results_message), + modifier = Modifier + .fillMaxSize() + ) + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/UnstyledButton.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/UnstyledButton.kt new file mode 100644 index 0000000..9a917a6 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/components/UnstyledButton.kt @@ -0,0 +1,25 @@ +package com.tinaciousdesign.interviews.stocks.ui.components + +import androidx.compose.foundation.layout.RowScope +import androidx.compose.material3.Button +import androidx.compose.material3.ButtonDefaults +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color + +@Composable +fun UnstyledButton( + modifier: Modifier = Modifier, + onClick: () -> Unit, + content: @Composable RowScope.() -> Unit, +) { + Button( + colors = ButtonDefaults.buttonColors( + containerColor = Color.Transparent, + contentColor = Color.Transparent, + ), + modifier = modifier, + onClick = onClick, + content = content + ) +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/IconDrawable.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/IconDrawable.kt new file mode 100644 index 0000000..e283102 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/IconDrawable.kt @@ -0,0 +1,34 @@ +package com.tinaciousdesign.interviews.stocks.ui.icons + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.ColorFilter +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun IconDrawable( + @DrawableRes drawableId: Int, + @StringRes contentDescriptionRes: Int, + tint: Color = Color.Unspecified, + size: Dp = 24.dp +) { + val context = LocalContext.current + + Image( + painterResource(drawableId), + context.getString(contentDescriptionRes), + colorFilter = ColorFilter.tint(tint), + modifier = Modifier + .width(size) + .height(size) + ) +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/TinaciousDesignLogoIcon.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/TinaciousDesignLogoIcon.kt new file mode 100644 index 0000000..0c7ace8 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/TinaciousDesignLogoIcon.kt @@ -0,0 +1,27 @@ +package com.tinaciousdesign.interviews.stocks.ui.icons + +import androidx.compose.foundation.Image +import androidx.compose.foundation.layout.height +import androidx.compose.foundation.layout.width +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.res.painterResource +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp +import com.tinaciousdesign.interviews.stocks.R + +@Composable +fun TinaciousDesignLogoIcon( + size: Dp = 24.dp +) { + val context = LocalContext.current + + Image( + painterResource(R.drawable.tinacious_design_logo), + context.getString(R.string.tinacious_design_logo_content_description), + modifier = Modifier + .width(size) + .height(size) + ) +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/TintedIconDrawable.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/TintedIconDrawable.kt new file mode 100644 index 0000000..263349f --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/icons/TintedIconDrawable.kt @@ -0,0 +1,22 @@ +package com.tinaciousdesign.interviews.stocks.ui.icons + +import androidx.annotation.DrawableRes +import androidx.annotation.StringRes +import androidx.compose.material3.LocalContentColor +import androidx.compose.runtime.Composable +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +@Composable +fun TintedIconDrawable( + @DrawableRes drawableId: Int, + @StringRes contentDescriptionRes: Int, + size: Dp = 24.dp +) { + IconDrawable( + drawableId = drawableId, + contentDescriptionRes = contentDescriptionRes, + size = size, + tint = LocalContentColor.current, + ) +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/about/AboutScreen.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/about/AboutScreen.kt new file mode 100644 index 0000000..ea878e7 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/about/AboutScreen.kt @@ -0,0 +1,59 @@ +package com.tinaciousdesign.interviews.stocks.ui.screens.about + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxHeight +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.material3.Text +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import com.tinaciousdesign.interviews.stocks.R +import com.tinaciousdesign.interviews.stocks.ui.components.UnstyledButton +import com.tinaciousdesign.interviews.stocks.ui.icons.TinaciousDesignLogoIcon +import com.tinaciousdesign.interviews.stocks.utils.openExternalBrowser + +@Composable +fun AboutScreen() { + val context = LocalContext.current + + Box( + contentAlignment = Alignment.Center, + modifier = Modifier + .fillMaxHeight() + ) { + Column( + horizontalAlignment = Alignment.CenterHorizontally, + modifier = Modifier + .fillMaxWidth() + .padding(30.dp) + ) { + UnstyledButton(onClick = { + context.openExternalBrowser("https://tinaciousdesign.com") + }) { + TinaciousDesignLogoIcon(100.dp) + } + + Text( + text = context.getString(R.string.about_screen_title), + fontSize = 20.sp, + fontWeight = FontWeight.Bold, + textAlign = TextAlign.Center, + modifier = Modifier + .padding(bottom = 16.dp, top = 20.dp) + ) + + Text( + text = context.getString(R.string.about_screen_message), + textAlign = TextAlign.Center, + fontSize = 17.sp, + ) + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/stocksearch/StockSearchScreen.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/stocksearch/StockSearchScreen.kt new file mode 100644 index 0000000..c20c17b --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/stocksearch/StockSearchScreen.kt @@ -0,0 +1,54 @@ +package com.tinaciousdesign.interviews.stocks.ui.screens.stocksearch + +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material3.MaterialTheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.lifecycle.Lifecycle +import androidx.lifecycle.compose.LifecycleEventEffect +import androidx.lifecycle.compose.collectAsStateWithLifecycle +import com.tinaciousdesign.interviews.stocks.R +import com.tinaciousdesign.interviews.stocks.ui.components.Divider +import com.tinaciousdesign.interviews.stocks.ui.components.EmptyState +import com.tinaciousdesign.interviews.stocks.ui.components.SearchInputView +import com.tinaciousdesign.interviews.stocks.ui.components.StockSearchResults + +@Composable +fun StockSearchScreen( + viewModel: StockSearchViewModel +) { + val context = LocalContext.current + + val stocks by viewModel.stocks.collectAsStateWithLifecycle() + val searchQuery by viewModel.searchQuery.collectAsStateWithLifecycle() + val isSearching by viewModel.isSearching.collectAsStateWithLifecycle(false) + + LifecycleEventEffect(Lifecycle.Event.ON_START) { + viewModel.loadStocks() + } + + Column { + SearchInputView(searchQuery) { newValue -> + viewModel.onSearch(newValue) + } + + Divider(MaterialTheme.colorScheme.secondary) + + Box(modifier = Modifier.weight(1.0f)) { + if (isSearching) { + StockSearchResults(stocks) + } else { + EmptyState( + title = context.getString(R.string.stock_search_empty_heading), + message = context.getString(R.string.stock_search_empty_message), + modifier = Modifier + .fillMaxSize() + ) + } + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/stocksearch/StockSearchViewModel.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/stocksearch/StockSearchViewModel.kt new file mode 100644 index 0000000..21a12f7 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/screens/stocksearch/StockSearchViewModel.kt @@ -0,0 +1,71 @@ +package com.tinaciousdesign.interviews.stocks.ui.screens.stocksearch + +import androidx.lifecycle.SavedStateHandle +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.tinaciousdesign.interviews.stocks.R +import com.tinaciousdesign.interviews.stocks.models.Stock +import com.tinaciousdesign.interviews.stocks.repositories.StocksRepository +import com.tinaciousdesign.interviews.stocks.ui.snackbar.SnackBarController +import com.tinaciousdesign.interviews.stocks.ui.snackbar.SnackBarEvent +import dagger.hilt.android.lifecycle.HiltViewModel +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.SharingStarted +import kotlinx.coroutines.flow.StateFlow +import kotlinx.coroutines.flow.combine +import kotlinx.coroutines.flow.flatMapLatest +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.flow.stateIn +import kotlinx.coroutines.launch +import javax.inject.Inject + +interface StockSearchVM { + val stocks: StateFlow> + val searchQuery: StateFlow + val isSearching: Flow + + fun loadStocks() + fun onSearch(query: String) +} + +@HiltViewModel +class StockSearchViewModel @Inject constructor( + private val savedStateHandle: SavedStateHandle, + private val stocksRepository: StocksRepository, +) : StockSearchVM, ViewModel() { + + override val searchQuery: StateFlow = savedStateHandle.getStateFlow("searchQuery", "") + + override val stocks: StateFlow> = + searchQuery.flatMapLatest { query -> + stocksRepository.findStocksFlow(query) + } + .combine(searchQuery, ::Pair) + .map { (list, query) -> + list.sortedWith(Stock.compareQuery(query)) + } + .stateIn( + viewModelScope, + SharingStarted.WhileSubscribed(5000), + emptyList(), + ) + + override val isSearching = searchQuery.map { it.isNotBlank() } + + override fun loadStocks() { + viewModelScope.launch { + val result = stocksRepository.fetchStocks() + if (result.failed) { + SnackBarController.sendEvent( + SnackBarEvent({ resources -> + resources.getString(R.string.stock_search_error_stocks_fetch_failed) + }) + ) + } + } + } + + override fun onSearch(query: String) { + savedStateHandle["searchQuery"] = query + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/snackbar/SnackBarController.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/snackbar/SnackBarController.kt new file mode 100644 index 0000000..86b646d --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/snackbar/SnackBarController.kt @@ -0,0 +1,26 @@ +package com.tinaciousdesign.interviews.stocks.ui.snackbar + +import android.content.res.Resources +import androidx.compose.material3.SnackbarDuration +import kotlinx.coroutines.channels.Channel +import kotlinx.coroutines.flow.receiveAsFlow + +data class SnackBarEvent( + val getLocalizedMessage: (Resources) -> String, + val duration: SnackbarDuration = SnackbarDuration.Long, + val action: SnackBarAction? = null +) + +data class SnackBarAction( + val getLocalizedName: (Resources) -> String, + val action: suspend () -> Unit +) + +object SnackBarController { + private val _events = Channel() + val events = _events.receiveAsFlow() + + suspend fun sendEvent(event: SnackBarEvent) { + _events.send(event) + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Color.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Color.kt new file mode 100644 index 0000000..8361e82 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Color.kt @@ -0,0 +1,29 @@ +package com.tinaciousdesign.interviews.stocks.ui.theme + +import androidx.compose.ui.graphics.Color + +/** + * Colours: https://codepen.io/tinacious/pen/pobYoWj + */ +object BrandColours { + val pink = Color(0xFFFF3399) + val blue = Color(0xFF33D5FF) + val green = Color(0xFF00D364) + val turquoise = Color(0xFF00CED1) + val purple = Color(0xFFCC66FF) + val yellow = Color(0xFFFFCC66) + val red = Color(0xFFF10F36) + + // Shades generated for #1d1d26 with: https://colour-tools.pages.dev/shades + object Greys { + val black = Color(0xFF000000) + val grey_50 = Color(0xFF0B0B0E) + val grey_100 = Color(0xFF2C2C3A) + val grey_200 = Color(0xFF585874) + val grey_300 = Color(0xFF8B8BA7) + val grey_400 = Color(0xFFC5C5D3) + val grey_450 = Color(0xFFF1F1F4) + val white = Color(0xFFFFFFFF) + } +} + diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Theme.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Theme.kt new file mode 100644 index 0000000..c31fc7d --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Theme.kt @@ -0,0 +1,58 @@ +package com.tinaciousdesign.interviews.stocks.ui.theme + +import android.app.Activity +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.darkColorScheme +import androidx.compose.material3.lightColorScheme +import androidx.compose.runtime.Composable +import androidx.compose.runtime.SideEffect +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.LocalView +import androidx.core.view.WindowCompat + +private val DarkColorScheme = darkColorScheme( + background = BrandColours.Greys.grey_50, + onBackground = BrandColours.Greys.white, + surface = BrandColours.Greys.grey_100, + onSurface = BrandColours.Greys.white, + surfaceTint = BrandColours.Greys.grey_300, + primary = BrandColours.pink, + secondary = BrandColours.blue, + tertiary = BrandColours.turquoise +) + +private val LightColorScheme = lightColorScheme( + background = BrandColours.Greys.white, + onBackground = BrandColours.Greys.black, + surface = BrandColours.Greys.grey_450, + surfaceVariant = BrandColours.Greys.grey_400, + onSurface = BrandColours.Greys.black, + surfaceTint = BrandColours.Greys.grey_300, + primary = BrandColours.pink, + secondary = BrandColours.blue, + tertiary = BrandColours.turquoise, +) + +@Composable +fun StocksTheme( + darkTheme: Boolean = isSystemInDarkTheme(), + content: @Composable () -> Unit +) { + val colorScheme = if (darkTheme) DarkColorScheme else LightColorScheme + + val view = LocalView.current + if (!view.isInEditMode) { + SideEffect { + val window = (view.context as Activity).window + window.statusBarColor = colorScheme.primary.toArgb() + WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme + } + } + + MaterialTheme( + colorScheme = colorScheme, + typography = Typography, + content = content + ) +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Type.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Type.kt new file mode 100644 index 0000000..7068189 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/theme/Type.kt @@ -0,0 +1,18 @@ +package com.tinaciousdesign.interviews.stocks.ui.theme + +import androidx.compose.material3.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + bodyLarge = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp, + lineHeight = 24.sp, + letterSpacing = 0.5.sp + ) +) diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/KeyboardVisibleState.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/KeyboardVisibleState.kt new file mode 100644 index 0000000..6c00f31 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/KeyboardVisibleState.kt @@ -0,0 +1,43 @@ +package com.tinaciousdesign.interviews.stocks.ui.utils + +import android.graphics.Rect +import android.view.ViewTreeObserver +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.runtime.State +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalView + +enum class KeyboardState { + Opened, + Closed +} + +@Composable +fun keyboardVisibleState(): State { + val keyboardState = remember { mutableStateOf(KeyboardState.Closed) } + val view = LocalView.current + + DisposableEffect(view) { + val onGlobalListener = ViewTreeObserver.OnGlobalLayoutListener { + val rect = Rect() + view.getWindowVisibleDisplayFrame(rect) + val screenHeight = view.rootView.height + val keypadHeight = screenHeight - rect.bottom + + keyboardState.value = if (keypadHeight > screenHeight * 0.15) { + KeyboardState.Opened + } else { + KeyboardState.Closed + } + } + view.viewTreeObserver.addOnGlobalLayoutListener(onGlobalListener) + + onDispose { + view.viewTreeObserver.removeOnGlobalLayoutListener(onGlobalListener) + } + } + + return keyboardState +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/ObserveInternetConnectionState.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/ObserveInternetConnectionState.kt new file mode 100644 index 0000000..37b0346 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/ObserveInternetConnectionState.kt @@ -0,0 +1,40 @@ +package com.tinaciousdesign.interviews.stocks.ui.utils + +import androidx.compose.material3.SnackbarDuration +import androidx.compose.runtime.Composable +import com.tinaciousdesign.interviews.stocks.R +import com.tinaciousdesign.interviews.stocks.events.AppEvent +import com.tinaciousdesign.interviews.stocks.events.EventBus +import com.tinaciousdesign.interviews.stocks.events.ObserveAsEvents +import com.tinaciousdesign.interviews.stocks.ui.snackbar.SnackBarController +import com.tinaciousdesign.interviews.stocks.ui.snackbar.SnackBarEvent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.launch + +@Composable +fun ObserveInternetConnectionState( + eventBus: EventBus, + coroutineScope: CoroutineScope, +) { + ObserveAsEvents(eventBus.events) { appEvent -> + coroutineScope.launch { + when (appEvent) { + AppEvent.ConnectionLost -> { + SnackBarController.sendEvent( + SnackBarEvent({ resources -> + resources.getString(R.string.connection_lost_message) + }, SnackbarDuration.Short) + ) + } + AppEvent.ConnectionRestored -> { + SnackBarController.sendEvent( + SnackBarEvent({ resources -> + resources.getString(R.string.connection_restored_message) + }, SnackbarDuration.Short) + ) + } + else -> {} + } + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/ObserveSnackBarEvents.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/ObserveSnackBarEvents.kt new file mode 100644 index 0000000..49f6469 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/ui/utils/ObserveSnackBarEvents.kt @@ -0,0 +1,36 @@ +package com.tinaciousdesign.interviews.stocks.ui.utils + +import androidx.compose.material3.SnackbarHostState +import androidx.compose.material3.SnackbarResult +import androidx.compose.runtime.Composable +import androidx.compose.ui.platform.LocalContext +import com.tinaciousdesign.interviews.stocks.events.ObserveAsEvents +import com.tinaciousdesign.interviews.stocks.ui.snackbar.SnackBarEvent +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.launch + +@Composable +fun ObserveSnackBarEvents( + snackBarFlow: Flow, + snackBarHostState: SnackbarHostState, + coroutineScope: CoroutineScope, +) { + val resources = LocalContext.current.resources + + ObserveAsEvents(snackBarFlow, snackBarHostState) { event -> + coroutineScope.launch { + snackBarHostState.currentSnackbarData?.dismiss() + + val result = snackBarHostState.showSnackbar( + message = event.getLocalizedMessage(resources), + actionLabel = event.action?.getLocalizedName?.invoke(resources), + duration = event.duration, + ) + + if (result == SnackbarResult.ActionPerformed) { + event.action?.action?.invoke() + } + } + } +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/.gitkeep b/app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/IntentUtils.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/IntentUtils.kt new file mode 100644 index 0000000..9ea051a --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/IntentUtils.kt @@ -0,0 +1,12 @@ +package com.tinaciousdesign.interviews.stocks.utils + +import android.content.Context +import android.content.Intent +import android.net.Uri + +fun Context.openExternalBrowser(url: String) { + val intent = Intent(Intent.ACTION_VIEW).apply { + data = Uri.parse(url) + } + startActivity(intent) +} diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/StringUtils.kt b/app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/StringUtils.kt new file mode 100644 index 0000000..24c81e0 --- /dev/null +++ b/app/src/main/java/com/tinaciousdesign/interviews/stocks/utils/StringUtils.kt @@ -0,0 +1,4 @@ +package com.tinaciousdesign.interviews.stocks.utils + +fun String.lastSegment(delimiter: String): String? = + split(delimiter).lastOrNull() \ No newline at end of file diff --git a/app/src/main/java/com/tinaciousdesign/interviews/stocks/workers/.gitkeep b/app/src/main/java/com/tinaciousdesign/interviews/stocks/workers/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/app/src/main/res/drawable/ic_dollar.xml b/app/src/main/res/drawable/ic_dollar.xml new file mode 100644 index 0000000..6dddcbb --- /dev/null +++ b/app/src/main/res/drawable/ic_dollar.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_search.xml b/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000..d1140de --- /dev/null +++ b/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable/tinacious_design_logo.xml b/app/src/main/res/drawable/tinacious_design_logo.xml new file mode 100644 index 0000000..a5ed7b0 --- /dev/null +++ b/app/src/main/res/drawable/tinacious_design_logo.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..036d09b --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..bfa8dc9a0ee28db14f49f3d9dce31446910df4f9 GIT binary patch literal 2202 zcmV;L2xa$DNk&GJ2mk zuf%wKZ;8JtaSj<~pnfiP+lq+VG_gA{65X{5s{Sf$P=5Kf6J zfgxz+2RnL)W?zw1laPx_#N?ogC)kf?BokiY8q|u*ZFoIwy>e+}A=0G5)iQGU;s>1o zC1%F#@%kYPQD}W3oMYLRB~FiWfgxCSdX@#rwj>s84m2vLS@KA7uS>KJ&AKG2>FF)g z{1zIZrHPwMY2;+gE;VHeIt}bJt_vl^^p$VE3rs4c003snxhiTH8MhC4HY9wbCK`fz zRw6ch%5kWq=F8@+PUkv-Sr(CU0tp7-Hi>{?0CJEOs~fg`-IOu*YszsP4`@k)mT6NM(S&YE)HSRQ z##vb!IT@rvi|$M4T8-AOPNE0^m{vgqDA80CP((iRp(PJp7y#FE znk5{>gQqGcW|TIy>T&>Vq4k|cG4q1h@uhZQyo3}tPA|+^yiTzLvE}1i$L>*a0N`4* zd*WgEDKM9D99AhQ;aR(WOgMnU#w)xl!?L1xz?d$OR*U4BaIUFmXRcM*-EkE2ZHg$v zCS1q!&050)RAXEB_)#;Fa?zy#m+q78XQ_}vNi{uRiP5RK)`Ta-HLx@7G_GUhEz{^b z#tZJqWPwV}aU^*|*fUJM=HHUKGGK*tm^@5gk$qXLxcpS-v0MTGo5`%%BQ$EnCjEUT z4Dd{IVpJPE!St44z6;EAy4mt|yME3>3l!q>2eykia%nexh-ytaj?i_wREjZ#snd=U zOnl|c^Af67UBy!{(!2#BiYqK3gYt4<>e#KsAiq zA?YY2oX1cwoKK#dZbnc>EfWobJWvj*J`~?=Aa*sEalFTx$9oFr+R-jz`O9}Co^?@f zTFGmMiR2p7egJ!-jASAz{S5jGTd$(-l8reRYht`-p&hx$Podp?_o6YoFaU|?-;~2b zR4Mar(Ua59Vg!^%!0h-sNy#?P>EFSffsnf0&|M?&HgXIYfcb-I?@M1EN- z&$^(E?D^BYEu2QtONNo77}rOZ1;KkpY*xdX;0^JaX4=rOI!-3>O!Hz?6QfM$HIvR_ z$gv;bEVNO=0W8X-UMPaC)6a@|tH1G~%XspKu{PGQ2Rf2~ zO#`dcGfO6{?YN_4K$8Y(s%T9MucU#S0!-5`DEXKs1=O{pWMI2U0k&^qPF>W z-0><|@YX{;t0Vy$I+6fg1R)vNB?a6g8PrWk1^3)@&q0J_VD~}4(|7UvV+Rq$W5mG+ zJw^}&N(xA$9(pc@I(!;%^`2p$RbfwGM1TGec5!g<4dIL*ItLN$iWS)xhBf@gL>J_U^|b(oxK>#+sGAIMzHFL6mS!nx)JsM zEnoSe@aHey@;Y=m#`6chvO~P32Tg{oE%^Umc=CFRmyZsM_S?zc9rpD3`#HaV>ai^b zKuHm8#9rIcb_PG_O?6O04`O#?4PqzoBhoH0CuL`IfzyXAS2oM`@djhaQ c;+LWgKt|X~kZJ@-mhtXh9z1?}7?LR}0S;F>wg3PC literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..c93761076e5a95010fa48ebb515b0ee3474bb950 GIT binary patch literal 3324 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..50f6b5ee0e0aea494e6b37d082c81ee3a41d8246 GIT binary patch literal 1976 zcmV;p2S@l)Nk&Gn2LJ$9MM6+kP&iDa2LJ#sYrq;16;Zmj?X=|lsE%#h*2uOs%T#|L z|0mP@0Nd8R)~$=>->ik{S14!yKDSiB}tOx$W20PoHZr124?wyz2X6@DkFQF@`gX~ z=Mn$_EE~zTZQHhO+qP}nwr$(C`Szdf3j?=}6nPns?-3FhxWL++lmWdD!f0g35%Qa1 zCZ7X6Kp8=r2aO4jHXdKkS%@26*z9)nWxnoSQ7(a25hm77J&M}dHXeZ<|Vy_hL z1o>A=1tvkdMH)Pvd`Nv_W~cZUd7({&oA2-*f`&>s@gE9lL5>#^(!?EssCRkK+h=# zu|ZyJ%wj2TRuVMqzbI3l35-T;qXl%h_790H7?37pBC|^+xiI^0A=4ddGdo5{bYeSw zSBgGJvp=PJ8Djz`A~fPT3ig>Z$80%1v^u(4TwCBUIstNp%piY<_K70;KvL9eCUVLw zZV{Pkw4u==MN3oqD|1OrqJ&f;PZVQigj|Mc_<+#Yp*CZJ9)X4B91!PY&M{f(L`3Qh zeXEtwMn@W(B%3fCU9L`%5&4VWnYHX`7KqyE93ds53pDYjg;|M;j;4tu&dFq(NkpBH z-*+LBJDqZnOLRV%h_oS{3}`sj5p*bnXaV8?O2zn=m|7&1Mk=F68C znMLkRWgywH@{3?XXdFbasD@(vq-wPJ5h#u5ftZzvgXfcUYx>hBhza}{>4}LnC1zo1 zb*9X)<1wws2&JFjbb>ZgV^Wf&s!-|?&mj)M761g5le-=v}LF{Eoh%+%+AD1x<4f1M@qEx3( z|NSV-&{%evkJESg>p9C(2c^!1_KsJ5d_1&kMvvy#eD#sSr=Dtx_FxuwY7Kgr!j5+R zw?XPJDL&ukQb(VUX$)pJ3uuS2sQ!Y+M4E|BiUl+yOJt06V|A1IK;-1`hvp0XBX9 z1a^FV4^IC26=-3fw>Kc$7B3&cF`vJK=hl1n0wM8+nYPE+~W~A z;qzCp`HM%8ZKcNp*yiN}$hO|IXK>i33vk?*?_iHNw_wFb15m|^_aE^CJsPc+E`9Fr z)3ZB{SMIOa-`KeRVC~@ijl(Zzem`Hme0O8@)}xivJC0Xw{yp#i(dt=0PwwtGUU|LY zaP{z(qt!1r9IkyF{_*1fo^Nk|T(-9fZa>oRYY*0UZ#`W7z46PZ)mx8N-_Q8=<@3Jp zudXiG+65QxZvEN%<-@_X2kYRvgSEq}_Sd&=J6`*>{mZ-6(xprPD2fD-J?H|o0~!H^ zg5U&H02&n#Jm?e@ARtIkBWN5{0h$BB3J4}4=oYjEdIR->P(Z|>W6&4S0;meaC<+CT zIVcen3u*zqgIK(Ppg{egJ&@~@GeL16JJ4MtkS!>>&IHgHC=7Jd5=0Iv1Vw=|K>t9Q zDgnuWipqeRK^FfC$ODut0zv}?fx1DhAe*QOK^maiG6O-_H9!&qVg>nvh(Hmb4p0v$ K1%xkw9Sj1d)v{;+ literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..70266a7d643710e66c5737369f785a767c677d97 GIT binary patch literal 2336 zcmV+*3E%coNk&E(2><|BMM6+kP&iBs2><{uFTe{B;xLf5P3zzGwqHU-OaN5{AEF#> z{}f62;~Kf^xOaCI8}B8!|5Nwk_`R!;vIOkreVuYb%`!GBYzX4bL=Y&A}`7>2L%9EF`UM+urKzyUw<4 ztF!q4+xG?A>R0vIR>Ht-BSp#bG0ol^t^#dTiUa^RaE)e;j7pwr$@`o!{^G zs5`Lj+FX-uf6{8Z?!dZ%R{h6pZgr-AtL@rsZw+t+03e`rwr$(CjcnVtZ8zDr@rdRg zB+IsKV~={6kp|@Kvk$)5w)14$w#v3$4XCGT2!W(c{~`}?Uhl}jZ6ifq#^ZYgrWFD? ztOCs%u~~GOEy`-lP-u?IZ;ru~k^U*HQJ9yMJ0`5dlq#2TD=*UxJ>_sV#Lu>vY_r%k zr^njYtBx++IWMzD5#l<2mceJsjJn3xm=JZErxco;h{C9>%KTf9fnbClx z^7-V-`Q=1h9!i&Th>YgtnYHBFbq9e0=+VNY5g852wPz>;53U1C9M-6vC}gZko^hKQ z;E55QkdXpdoXOA>YEcQEH!r)7CbvsjF(FB{iYQmi$bcM_O!_nGy|Ku-61#jv<>Zq1 zdY8OTrD;;iQU{1oPUiU&2o&1hFoj$$l4s_-#mZ+~VkRJ$^sEMToTm*5Z8<7i%^FFZ zER_Cv(?F7*#pqhhn<#2>m#qtO^nwiE(SQ@``AbuuSm|1|o zLd@hV5x7EXKez0>$lVVE>0t?g1r_|VspPas-=YMl0pjUgfe|qGm_D8cd2h)fE7&e3YcrD@$2QH9`j)Shn`~+3GJ7VJ}d?8)54VZl@GUt{s#iwBP#EDtEpv1Jm zVsJP8J5(bMXoV?Sg%Od$%P3=;Dm5c|-|x?cO>S|jxFkGNWbQQ1I#4V$U z#WZ~N%^{gOz%QE!#k7C;V3ae`;$Hf;>52KBV>Cigj13!9Zf8-s1(B-{nm2#c+9OG> zajLK+9!;Fss=}h^SMM-U=TUx9pp;W=chOYf*#B(L$?GgDW*Dr|v9K@ftT>X{5V0b50pQI40@F3HAI zX?9<6YTOqxb5DVfUm}+uG0I(*`Ru(+r|*~+tu`qfOykBD%PlEqwY;9WlyV#DcHm(|_uosH_`d`WA8KR-C{%W&0T1 zg_I2A->|t|6u!UdGx#sM>!vZS{4OQ9 zFW+DHUw3BFLI~WHKCPxiSDfvUcgw{gfBu>ByK>LL-mRofDJyg{k4GwS5KIW8=dar1 zYU>_OP)dvauXVUvO4>8W^OqeErc+A0{7)DQA!yUYY4D%bhxfT{=%4m9khDn+R}Ajy z+VIUUlh50&x(I>@!H3XyYbnh07fk_gOObsa#k97B;nL*pzTF!U+xOA?@24RQ&3B3* zP?`O?Jc6J#UxzTZY2wnkg@7(C1}^>bm9950ex!1kq55Z}0;e9`fgnRrFO0eKNEQ7| Ge-H$4oOsay literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000000000000000000000000000000000000..5279dd6effeda23b23fe7deb779cc1ac37edd1f8 GIT binary patch literal 3294 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..6e5214d3e6e5fc888a82b7a2fd6d95b1070fabab GIT binary patch literal 4420 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 K`akvmIQ;`Ej$)1g literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..83acc82cbf2c904496b7fff3936cbee200200906 GIT binary patch literal 5840 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..b2e30a1c30391ccc39490a2e99db75fd56a0038e GIT binary patch literal 9642 zcmV;bB~{u|Nk&GZB>(_dMM6+kP&iDLB>(^~kH8}k>QKNt5uvti8^$ed z%Ng+_VcKNqH(lv(6W}V&4zuq*@jSwg1c0s_DQ^L3q*=e?y-bhP-u*WQ4~O7JoQ1=1 z9ZoR+_y7I>`@b>S57%dSg@oR$`$2zFsuU4-j^_lr0})l;qpWLPM7Xt)NWE+Pq#I7v zngeWvpXzYpJ^|h{M2!hqA7R#JxNyDS7h^MJQ{;-81F#`B?xgYFtasW7rvu=RKut`v z2iU5vqA!34WvbeAQX<=txP(YI?r3d?xT(T>&(Gipu5&yoYeGEe0E=g$QHF_i2{K)P zY^^df?`cfO9jds2s+5ruP>3X3wyor<-scYtvM_=YykJy<-Q$_t9NV^S+qP}nwr$TeZ}<3|=bU?<`44VY?_#^Nvu>O`!EWMH zHLKX>n87Nkvw2o=?|M1ayL9y~#)-`vuadHDW7k!qUsbG?ZEJsG+wSURjNYZ{cChNM z8{5X(wr_0S*w)e>?rx0U#@bC)Z2QKxIatO%v3aWQDq7jQRNdJ41zoYRwT&Bg)rq;g zRlSQ=w$0ttZ*!|>H+L1=<_$(?6FVo^8KYI4uH4n6S6z8hMF9c;f^Bq-ZQHh4+ijBC zwr$(C-EMo^wr$(yokU5JP22j=@$@>pdbVxbwr$(CZ5t8&hv2r6BZ;*ax+kC}b@l8M z#6!IUfCca$6F!gtON($=aqxQ11Ne^%Iu*Nb4E45z6-Ueko%AzB|$6ob`^m@*qL!}g_ZO$U$L&B}!Xdx^+O2kj#Q3w%dz__Ac+y>)*7!Si3 zfid9bzypu5@%>M8rH}4>*NfSvoQeY};d$X`F^uo@8^`yH8!_ux3XbCXMYoSLZ`d(} zaFFyp(fW7{j4$^ar#ui-LBvQP03)+RL`Z~)P+CMHk`xF;FclAc{(tZCL{SujVWInj z)<;3G>?Vv$Fd0%niuhp!EW;m>0umk)SB{_u~nVe^E2Pmxu6EljHZpUZNOO1Oqu6=%4D{7{eG1czKJ!(=mSc zOGGg!7*&CZ-7xONSn~2362{X`rU*lHisDb7Z@&&-Wni32ZgTPtA;XJezvwvEZ)LRK zpJVAFN1qdB4&=Cek>_$-4e zAQ2|cDZG^kiaG&-IR1=l<6eP<^mJ@J@kOOg3$Q!+6Lm$biuhCK3gDU9A}8q4&eqFs`GzP#ck`vkL8yw-KU6Vg=)7stdOfr7adC zd&J40I2OjWR2Ooyme!$7Iz@v4n}~C%F6?G0%2#Rg5x!`6(5n)oc%gT%cPp=XQBqGY zWMl`jA}a9sBh>}phy<7~{00CcDyW`_VRQ-yybcfnq7;k`FDgz%)ve=c82cRoKeiTw zZk>5ZiaW)rVcd!(uVBy#ES}NfF$Fq+1<%~jT-c`O7(^!u(`f~z`K-u{$0Y4O@sbHV zT`BVhaR9KKJ>-)tkvJx#PI@QF?B=+TOG2r1s(CK(e5@s)uX|nza;`u~+y!OwjE1!P z1z9>EONV6hf|74hL&$<>9djY**#XJ{RB#-%5F956N{iIvHqR-yRrc4Afjh&>5nDZ` zU;=`~(`9Oy3IH!E+%#*nRZv|YtfV_2gxHi$%eHC(Y*kcm9!59(N3G9<*m?QGpW(c#M5r6ba>VXy`7v{^%ptJWHcTGM$i`@a!;xjrBY7RdfmvU|A>$ zAxr5F{vL0PVet%^Cr{+pt}eJ*I9+gKA1LfERF<3L0JW3#0Q)Y(Tv z_8-6$1ptm1rSOLtc4j-rR*tD}00iX@-Y(6s!GcQ^xKA8aj@1)NcTmz95QK|YO6lul z0U#-g{k%!&gUEfer?SEV4>>M!K?m%oCINV<cULM#X}PC zt!!!p`M$@7?3zb-h?0v)M#Xxh-mU2ki4W3bWzQLniZF7o>jD>cj9G@BJ85PNp-PyM z9#V*3!Yl_$be#+(G)OYa6f5M4(&?820X#A53fi2zo)LD}om6Nnb)q6<>WDPBtaFuF; zo2AsDFrE@+;}Ve#*D0L98AnmnDJw^$08{M}PpAM=hB+=8fpTBx zytWvP-cuR?GoCY`1#O%Y+E^azytP3|o>742Py|7`qJTZRd|>tf_yU)zG$z|y{u%(z zQgRw6xg#59l=F^VNl>nE#fc`}r2a$tuFh;`Wn*xcerS?p%5ypj*kfL#9*a219Uux? zI$-m>Wc#MdoR^uOOp`tp@3&Disxxn25c7o+%vpa}x<++aDk z0Qe+JA#;N-V4onec1o&IrTSF!+hHjQ;wv-Zb2|W}6RT19j)VO_gll}`)#vqcMQ%1X zhWxcxdvpQfOSRzs%|i2LD`W^_XKXu$)&IA`YzHAOe7~%TpQ>t5rD&Au5OjicJlMbA0%F2zPh} zXWF>lObjE`%Mp#u-}XQP04(z1Ms2D@F0YV{TAAOjVa!oAoUA)H@gZQ0H9I)@dMJB;I?B2%p2`jpD%*NM{Z z7q5jWeLgivr8guW5(gLP+}CL^AD`8O35eQ}5uZlg_n8U?f^KW^E$+_2uMii&_S#K8xJHj-CH{Ae>=794UKqDe71KZcO-N|0`Qo>YT57IM<+lO*aj z$`lXW8FLblMZt!evx5OpHl>qZnOK^LV3EOypc$_wvsXGyDHHWsPJN$DAP5*FL?fGttyvGQSlyUhIZJAYP; z*~TmAfG4;4p;|s-(HV3@3YKr`On*p(P^jIByQSWv@# zC;`g+bjX%@(w#hrh(tB_V~-tYp}}+C4Ae2fN`>Vo*Q_#pnVgoG>ZDY+qXmBUUv%yq z7KOuWv2h###;jX~^4;e7?ELMTN`cry>oi^oa4W;;(!-$qBjpZJLrFELChmc6LPzTK zrZi%`y=!2i1;H^KkR{~)9<+auWT3Vn5E7-`FH;RI+^rl@u@Wpu;hFhtF@)6Xwl-dX z<)PGl_!EzoV$rJZN{E20C~T^GUGa2^AiZQMKzH`eR6k$u{fjL=Zn?{ed2CJS%A2*47Lc?QApRxWB1Dm>Yeb~Xc579NQ?gbOU2Zr+Br)oFR8s!b@$xLnv z9l#b zWD(S+g`crOe-9U)Zr~12r?(F)7*Kbm^jp z{j0Oo`|qczx29{m-%PVt2PI6f!d6W8_(W@T;pK~vkl=F5q7b3=m%E(?zuSy&)#y&s z0l>AE=5fEL003YTN=C7z-ltJ}*i)S|(YifA4a|VEGTYhA3TC!y3|k^ja<~BRrHNT( z)~>&N_79a=u5(a@D2c1+Bg zEv8z}ZRvm_^ZIDQiuU+Q)pqHy*igcp#2^ra<=alPS7tlg`rSzmLL|?C z&qEMu*`DoeXZzll3<^R-Ae>|vnOEpr`&Iof6dFCfkO46Db@p>z*t#I<-6d@qfEQWd zdT~zt(j|}-?Q`6(BQnE=30&zQ&c9In}kazimAYzL}})Z@3s zjJ|$qQ-&LmCFltATB6O-nkW{p+R~g}*UiU(%03YQy^l{Xt`~XB)nJxWauqL_sl=o{ zyxr}qQCSVeG%_BQhp7kvL+FK-Lss%?h($Se`}Tb>mJa6?NNt@A4Y}v4K?wO=E|)+MBAJnt zj#Kixg{Q^dZDo%r(hj~(&S(I5hWb}H<7Aa(h7#%>O{qghvMFuVQnH-pz9w9$WA;&& zYabZaF3a>zoS{c}f)%=fET^RuRn<~HGQ9Z9b)2j3EyF6>9#e8YhbF0UAcqP#)fyW>?_M|ie#L+S>=TPSxpIVwR!l?)M_ImBXMSjM*0)l3=~7+E zI3BHAYRVm2I%D@PZ@1K%XRUHaXPOYr6XWX+9S_GxcxS29V6tK?=|MV^9T3ISxQsuzPFN;!m6*sIg^4-GIk^v~@ zqnvbONAq+5B3+%cTWAB1y)TpaTJ)dhWa2)O$`ZF5?DL(lnS8=N$vJ_?FrMWFG*tcTDKw1y92$TdGI)FvPR?B}YVrZn=C4tz& z7Ms*qni4G^MVG|{YL6X4EKzU#HRPlf&n7oMeWPkQ)GZl<7wCT!xlw<+IIy zJzpfrmUyj>B)fSjMfF6g!_8t+DvQkP5Aa5qrC`YM>iyTMl4(+VIV`ejvBj$9upsu% z>)u~OY@esxPylvLyzZGVExNKyCuH>6I~w`xHZ_vOYR$c$+W6}g=Hx(}D22k)G5~0c z+%_4eqEGxwLErmQarPYgarX{8+691pGf$|HVBz+@DB?vb;}rw(Lh|2ZZUCkLz;9j1 z@INB}V&k}?a{FOUXZ0$}7Xzh%picG|0Dtt!m6@s-h!5m?B9rq)nHG3i4`bNE21<`r zIML*AeCa(Vr}wje8Q&V0s>-|s7H9wj<63jD7T4nlOW-kl;DCP<9OT~!4*FjRQ;8rx zBv=qAg6m)3>GhrJ095os${|Ayi&soG@ftIMFW0}m30z5_+_v|Mu%qLU>WWUU@08Y3 zRF_~P+?YtdMYk7EiT&XE*MG_dCtF#_A#q#rzMW$7ByVMwjYp=~rrV1x&%1a1>$5rt zdo|}YWm|He6PTxyYkSM_Eb{>)dRn( zJ50Py7qB{!y)J1<24I!0(=yNxtJ3n(53N2{ihT6LDzq%R-XnUR4Ec2ZZMs2~|I_l( z2d&W3XSHTl=U02zG@d1a?V5QrFv|cS>E?2*rFf97`F{cmR@c_%4Rv-ry$qOPQoHRf zqvI+G0cdm=d`l%A5Lucg|J2|UJS%Fmwr8HeBnN=3m&d8rl40!Uh6jE%TG0380icGB zzt+L8O20WUxiB;t^7`xB+)0ruK7OpvH+Ocipl*B<+?c`w&&ur;#Q=2Lb3Uh%Trhxc z_t7ybw9;jfv~7r_eM2b4OMMR*hcC@wjMWvrganW$vK)v zH?%otv&w;gb*CTM>b-Sj$c0NHZe8Dt(94$j89c!hqx*4LCrfWvo$`0{_pfa8Hxs+& z3Sg3R%C0WPnH%?FKYL8lyC-pcDzDEzSpVa9!TyM<)PVYK~k?jfZ9?kUW2ceZLy*@Trjl**LdbfGDSy>6qZND%u!98VrbFQWa zwKDt5A>_hE&yoD|cLtB%N&d$91h$Su)wemw)LEX1dIJFT!0K@2+RPiHdZ2sSTS!6T=TZ8b;Ea-=C!w$CpXEKm47qv+WbxOb)>!DIal85dB()L3Ws*r_)7-1$uC zg4B|`c6LaO3ae^`8AU&n^e#TTM8YYp*0hZPvp28|u8BqMxD0??k!;heG4XZza zY5K021i-Sqo}On_WLzcD2|7BSXl&3YN7zwKbER8Z-2+TGP^W1iZj582R*Xatln zEwkqy+Wh|THV+xGSOYpZNG05^?pWP3heeoW!!3npltX};{a zxOYS?Ytw$JnaeYQ`(_#@v;&|QUiZm4GOncq89i|?w~yZbHp>?u zC$w|Kd+V@vXY=aQe7^V?X614KJ~-rGMBKWrOA_+F{YvS!OWn==<9E4z_$G@N9&Oye zs7W_vb-tyq13B;g>JGC5V8qCg{je(l4v<#^u+p3rVl4)?*QX9(rFX_ColG&i-or4l z9dksaA$*%@cDEmY5MZL|u4ZPG+({3-4lwb-cd&_SMST2}-*NC3B_l^WCJ^5ki)%rJzo)%QEfP02Cz* zdsuG)z?^-DUVB((Xn@EOM<9ZA^N_AxuHPZcl)Bh<4;Kjl834DPN!_IRPq|PEBh)W^ zhy+^upFVrLG3i!CQCp3hG$)YL0kA2k_5R^W2hfc5A#auM55Ci!G>xMKfL=BnfNkOF z;nkAhdujd}F#zm`|K-^>8rLBNfLIUYl{)}%P@j=HO+XfO4^*pCvj8~n@S}QnND&b< zPt=zsuX$?roAuP3StRHt2P}n9P^&ci&Gz@?CiRJxDHEC&D*?dYYSL3`&MXw{23e{us9kcTNw=yji76tg2*m)<<_t`l z{bsxiPfHyp3GzkHAsHJ$-_*>kH{&~LV9rMBenQxu-PX>80f2SJCpoFE88+tWaanL` zq}EUm@>Y+^_#~H)&CvGj7-Iz##Velu#&&XsNi#mlA@3R=40RzT$wISXeF5aH@+ISw z94EcW8=qZ4b;PahP5>-3KFL1!eO!9f`jWu<`ntL1-g~}YGxSbvwjFhAtOTIWBwGWp z>%PmCg=bVfyjeIdInq3=uJ3C>z8nE;ZUmayT{(Te%6eu0nm+=vsjjH8 zg1PfkB3l1OQe3pdwx{M^?YnN?z%Z}0mW*7G<+UVSt(!ZLx9ZOh+nPqHZ>*2fYw#+% zpPi@!V8~RmYjJhl!Th|d{npOu9h0ghOCzq`oGLv>{_1}^Y=5DvrE2=R#IxOWm3OhY z?UX|@l0b=dCo+!WYiey!i+c`Mk>YrZQf+SU9KGJ-sq8o5 z<7%0$wdsu`OBiYjLQK0GeRmr*q^f1nj8Af&c)Rm*_I|IhcBVG3f6R{uOw7Y&V?pqw)t;@}Q_HjS|l<>lg5^Y}RP-6qA zo^gwm7t))Xh1P!6Cp&#me&~?sm$C z(N%-Clc|hP@|b8FQ@eMysrP$4m%FcLc~|?TIyJuFv|RO>}x7Z<*Y*UpvQk8W+Yu$bv*HMXCmnM~OB&~^_Z1~7=WKf;8Etb`CW?IZ^EH-4-LbTXNpYMMX-&r}+`C4j gE89dgx~sb)mL|Q-IY+aS<*4wGVeQLOajiY$~wbQSBBVq#Vh>V+z z_+&*BNH_!S(smzo(z zJ`PtZ^F*dHvpYP-yskoYYN@}_bN>_61W-|3N&KtW4g(_u z2XRW;Tup@v9yF|w3l>ho2{;j3qk#tvTM<_RIrLK^Vuc`L4G6TW*|uZbxaXws75Q{H+qNBVV%tt_+qN-xGbg)K|A2OT zYB$@qCVP&Pn+e-Uj*?yNUSnV2ajE(LueUOk-*Yb4-QC^Y-Q6$l?(XjH?(XjH&Jo(V zl|&_x=_HTje?ywBK%)8pcQ~r8LUicAbY{3aF$5FvumBrTiDY&l>jaEIr1G!^k(KC- zaA&xkhfU}fNoNMMF#|jJF2q||;cjpfE5J8W|BZ)5nBgoSfdK%rk!;(xZQHhO+qP}n zwr$(C&F?1=8~`92J#@Bh+qP}YNvq}FMw{0VdYGCfyVhdn%wh=&_p-C>aph)Ia z+540Zds*UbIkTEkLWif^%M$0e@^dBr6(!VHGWVCg2TQhOyILar0v4oh#>6KnBpo2r zU6Q_#)Id@TNzF3%Ue16or@66@|3Lf40sB#5ti(?OIbA0yDB=cAYsqXic9Nuzb52ql zNo+JQC%`>H5Z1irHRl9q;6^uNw>ke$5(h~@4RZqA&lzyNW)hnWSqyK{>b7ohk~$il zR8iQf)C@&+`>k#Zi!&A>Nt{zAi3DEM2ss+r))}nI03>p{!T&p{;}@|h@~^!QpjsAI zEqar5&Tr9&9lW3(GFB+IPT^4Ml>#73nPJ}Oq>zoTva5_~zi`HeUz$1K%c;cmoN5;U zmSN!3Y&AVe-sr?~DTCbVp9!9PG|2@OgIrCTKTkbON==l4=hreNdP(-D$o52^%v(!Zc%=+k9oRV>SX-SV@yjq!<9sVG9F$ zmU%cK(V7%U0yH{i#|4EBlZE%o0%y^NbHk!zazq$(gdBWS33x^WXhIbzojUwh%q{oI z6DR>+`h02~==r2-P?Q4vqsmq~GJ0o#pARhn=hTNSemRJ!oPT zC`tjb+CvC2z5?{4kU10F(t-(0I98j*=fmT(xk6X zb)2Q5l?;o~u7GtIH#=hHF z20BVkT6!55Tv7Ripi&XB6ZAbR`8Skm6Iiow{nIEHPy>HyNvX{ z!FP%Ois`N48q$L-d;*62ODGF0DH_zsZkXx{s|+W(UBFr9AfkflNR><{MGH$ zB3cO_9ISQU(9(L}TIc!Jyyn)+n7}gr1F#izk+uBmk5ZG9HvFc?KfNaO)2kJTIVfOy zhDlzb+ky?XPnhr-N<$RUnGU)|xcRf^v#>AHvsiOfHETUQTfiICA`ImYJYD5Y0IX4p z5=08b91u+M0|zE@mWRW z81EjWPE$q{(9@`}l|?Anf9k4PafZHOB(B@Ag0zY!W9^0wnsRYLV)eY`3j9^yv$UX( z2UuBU)F57~j=L7{{&?1_SVUr50h4x85;Wwij1~+2dUX1K>S0)JG>@uN4SN}c8hA_b zL8>;%vIq@ohLLRTN6icXhG!}R6~u2W_RMEFj(@ z^*ko7%$k*Pi1G$hca<|q=qPUa)fW}iTtn>xmRbrt@~u5aBA)ZKSuXxTmTt*dHSF%JXmh(TqcBW=Ul)r@64xx61M!|pnWS@C^QU}Gi1a7Xkh zs^GlwmpH>P>>*iMtT`#NqZG{r#(xB~7cmmcM8^<&1PEy-PtfO#jCu(NHy!6^Nlw)& zUORz)ux&ci;f?{r9g|8R{`49FX@GSq8ylhm%U>$`n3AU^bxneGaOf`aHYR<0z!-M- zC2|LT-g6S5GxMJ|kIG}Tzg0pBS3G9vs1g+(@^6N{dF8YaDg1r^wDQ_=&zsyh-GafXIw@wJ|Zj-g_8k4*}fIV#qkf>d{W^N}mRx5Xg zE}m&yK^yA^7$7;Q>Ba_JCw4yDma~84bU&}Lnju?e>qIZ`^HEDuEqjIWloQsK&hGl3 z9_W424xj#)y-hkx^nMFgF!<9se6eJ$MYwF`;v&w1Sgc)SESMwBzW+*sw5cuqxw{4n z`hc4l=8!UOO@j2*-4v|gmg1+cx0tG8E~0$vPOswv^t&wWMa*Nv8`d*81t^|Y>ZNKruq6rn}YP-$$6blNY3f%k8%6vRfRRoislS&8y3UzR8PcS0Fwh3=`S8S*VkUE{ z8aEm^cpRbx{KQ+~*A< zd3B?^gI^aJi+s>vHOZDb*tY^=%zqqM>X0sevbE|R3;cMnre|`vd04g;OzK9oL$K5; zUh7rD3U)!H2%tGYmpN%zH@5vHB|*#Ul76;Q>RNl}Y3qNLCVT=jc#C{ma*m4vR>0tT zhD2_OFzy{hkP?IpicR_o768^YY0k%W{Kx0Y*R%ovEHQtPxQ_H1Gcdp!1(9QCDYKaO zO^W@6K}f5RwGrKVxwmuvJd?!iOY(I6`R)c~>Kgcq$B2HUJbW~38!2%@Ns8&NPE%&W z$mevXiq5-+oK&@(*tbsVBxO~zqkKYy1k0tHSqw{l&zD(`d^KSlEjq_izN>kM)u$X8 zj{MZxT!5!`$>KGlpmT!rI}UvS0y3tRA11{H;w;e0=I|2$!B6uVpVo%YUM^hDAE6F~ zwU$UEs>5tH7mTJ`!_-k5W_^ce9f$TmjbXnIp$R_o-LbsCr05?B}6|iiI?;ai_oKW5JRU#y8 z^3QA=*eK`p_3JpiO{Bjtte__TBGGpE3W?Efc)TV|nT_@=Ki;ZmWcT*7K(?d{^B8!O zI!f3KJf;o^cPkAU!=C^$m4*nn9Kj-&e|l)DpszsLsm)`KbOx2vLiR(XTF%-UVhq?y zWp-;wyTYD4RgL^;*=yT@8r6^6_|U1m zC9GgeE6db%-V(fzqbU1AmDTJ$T8mvdQEV zZ8Fi;YxBApo;rN$>{q^F02_vEG1nLW`vsbgk|ZAl>{kz4ZK#gsD!f zi>&lHEw9M{q1SPFP!51)UFy2;Y1(P0-N2>Y&dnd30rCKX#~t-BEc4rCYCmGpyEr;FyujfTqwMQ znDLRZ5#0d%o~a(iDhU?hfoh|DsclbQI2fQftn1FFrg8%x*+^kN z*Ifl;&GJu-yIGj$?tb?0Hg4QBr&zyo^*KX{`3f-n+TLeLvgtm&)H^Re9>}NwW{T$ZZkMy4w|z@L5q+v{xWrvvP;Zn7dP7_?)$}$rw90I z3mGKi>F=5gfk6$1QWjk2Rr3ZIdKIBKpOV}ex9#( z@jj{3ugrT>2El68Z^~m0J&Z_G=1~?V90ZPIp8b^jKEP{gOoG)am^3@dG|TI~(=dm` zZF!PwZPGM)8kIG8Z_Pfg=K|iqb*a5gO0(8cJ|U{*1Yc#d5oxbGvS#7BfO-{UzSrA! zxp%T)j_Nq^Y9&G{BBCTn-R5kqc*YNZ zgie0T)|@}N0rlGUr_cCZ>86;oKIJ;Wo6s=u)s{>Ev+7NLyYn;YtqdDBOz;RB=FrU~ zfk}cPvEKsTA+bS_7%VA0xh)>gzhMP&VR&=Cs$V${5WQcX)jP4w=xhb1*BoGtqLwA| z4+d-Q`UT ze?tGG10(~NBmiu82Oe^t2Hc?MiMJ@Pc*>!E4A4tl7p4PruCs0-`_J5^`!PMN05kAa zj{Dg4*7OpSE$Lsg&bpL$v~v7YQ|1wCj$9M#7-IlqOy}pZcL(lfT?a91)oWj#)gULX zers>=jyZVgtr?zI%&)#mh1cJ=6|`=P*7K-bX8a{uZLi}LrDSafN3EjuPX6Q|tm#-p z`8S;Gc+8;Oz{l<7*7MNkcBDT#>bigGrPnj3o67^ticFtEbSg7hA6HCXBBUPe@Hf=c zs9a{eB|6z>dewHr;{c^1YI5-}U`fiqb;k!AFk9)!3X@$!`6m&HhytA+o~1ksqJ5ql zOwCxv@2;1KDA>_4Kqls=G;bRV`T(pWIZ;`dNXgvGBZO8qy1&0n+^{d%BJ`2`yKYio zoRLoMx2O|WdD^b=#x*)=ba9HxIU&#*QS5?6NyzZqxT8X^)za2_(lbU zIz6q(d5pt$TLH1Yua_~?^*SMyP-4AA`}}&{(;n`s_v2$+FWFZvfR8cn>T}|CO5@Vr z@q{`_jCpw`=Za5G|9=1VU%7#Pd4PYL*hKE2U#`Gk&K1A^;?{BB@Dn$&iA}trOoUC? z)nnxX_#IbHq}|wu+=NVyH&iliZouDv{nvl(o=>Rd+%zuE$4{p)aN&_LlEw^p4{IbY?UHm1;Z)QN^`|@Tgf=tz;H}%c(SFGJ#Tz( z-Hw&k6MH{z7D^rPGKdIav1r)2Oo5Hj4{y_f|LOn!r~Uh<7@OS zZed4`$c|9EbQ;f6-uRw6?aMWr7x>ZW;sjiO(|C|oGAVyjFF>Qd^$?qMQj!k0R+(Pb zbL$eO{{3}YJT9EppDD0jEPJTi`q_HuynHH(&mq7N%NCDM`}3l1Ws-ACK z5kkmZ+zgN}p@#zsi_pvWfEUXe z7FkMJze9!ci4wWP6W+gI>hE9Oj%8C(6DKqdsk18j9p1vKx}D1h3MBQW@hUm0x)o`~ z2Wj}bL58nMq|3@_9IV9FPyYIO5bc^Lz!Bz#3kF|2dl07n{+df#5IJ|906?zjPJ1QS zs`(h*Ill3eCX8^^gXy%W0A{~9OaV3@M`t5(&T(19N{Gqo}k1aSprjjetnE1 zDvZ*RcQ4Ru=gLaf&Wl1}Fj63rWGDngcp}*h0JzhB?(o~nRartBqWG-Xms*@EeSV#Z zh|$+1n%1YBH@>@K#nd@?pg<#{U6Lf+mSa1c8}_ah%O1=T)sZc%RXk^C*!45?*gRLD z70ZL+zCkv(ZSE-)vy?YghH6GXy#$j^7;^$BN_@47LH0W9VTr_L2 zX3N5*F#B{eNxRttTSccB0XwC8Vbh6=^?O#87$mZkg4UCJ15fT270n#X5!sO~tW~;j zbPse}JG)G~jD!uHmrkbq`~pRT&K|-YG6Yz>TwArc)U{d{zf%ln?DKn-ni*9lSu7I7 zqGt_lZl+U=qRHTf04AN}_d&!k6f!jGUq9br;Yg8AVj^nVO*!W205IU>?hJ!=J&~st zxoHRT#CO+doNqF?*{E;5V#Q-|0+N@ML^-z^rmaN00Ir+=qJdO}TqIj&7ZUs{@Gdl$K zi9C(ST_?c&dornf`NW6EPh$T6uFQiDNv(9J+0KxTCy9|xh^d|D)i6zWook0NNV3fU zu;bnPNtq8yA!U$rm7mG>qU<>9OauTV08If%#iplJLP{W|_UqXd?*M8AKsOKoP{xVz z%)B9>7UfdLL8E(fa#u%O*A3pJ6&V89F=zyrPt-~#YVzw}~WEr`uZlK^-fibI zoxAr*eT6Yj0f+$_V0!`EzgPHH_|$jNp0f9)^fJ#`75;NpGv`*fJS_S+Xns zrAoIsunnMW`X_svPPYZUJw9Ell$euxM6uoV06{3^ zm9e2vsNQWW5xwFpa+Ga|`E^x|$Xr#`{V~m`aN}fMG=`y3K*Ol2tCOu1kj>^o22IR~ Ay#N3J literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.webp new file mode 100644 index 0000000000000000000000000000000000000000..91e4d27334f926ced60c61e147b0c7aac102681f GIT binary patch literal 11436 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Μ-@tLUjXXB2b(;N%2Nn~*QS>5WypgLBY z6Cx{f0!qMO;y$wWs}IuORXuZ=qSr2dw^X}1^Z$=#|*iJ()Jnizd+D*bW_3l_dS zWq+NF<#WX6MoJQI6{$2F&;JJo>VSIPI&!ze)GB_j2Q9RB%(roWPZHIu7y*oM?VDLa zYD?OJQf@7T+)*ZJp1pn;2P*%gE)GyKoTy-eNNwl(@Qb7#nbdm>bt!bj1GXCVc#J?J z__sQ@<+ML81if`g*O$mvLHFJtR<+!n^r-J5p*UEIIS;1f%@J@&O!4cclrjfg3O2o>}l?+`Y%?^h@lv8T!XQnFlo789-sND{`%@UqFbrZR8 z0RLVm!d#fkMZ<5k-~PvM+w(2_L_c}P594uRAFfgu+K$DK`DtB_dK?uhy!Rj8i@a}myKCjCZ_l(eDA&?8XLT$zL$RU)LB3spD2oj<;8*+3qrw;9K+wx}+3WnPEAEk0(K5<~ zha(g6-WdM}(M!T1at?bpp^#yJFyWZHE>mCpz))X1+Y_>-Me6zbup6SkU%L}*bBq;}cK0_b zJ*ii-&g11%Tw4cfw8EC+KK6SH?DkTG)O(UGfkxU3xV#dJU`1z09&xNy+?b`8L!)~i z>DFOX-Uy5|66D)2T=P$Cv%f%pI6mj?#Gp0KarM3#*ZIBeWgjPk5}k83#$_-?Fm1(4 z`M=MQQOnYbVE3XM($*U=gWqzoaJAVRco*F1YGb;@+uj7;H{Js2wGG@z>&**Qvy$Mq zL(o+iZn0~WhjNGGbDh{%Q9ED9e&uakNqG!C;`F`Y#@KhI^3J5acX7mN>DMNp_CA*Q zzdMNJ+$P9fy@&7cvW>i}g`4~C7Vy8nht_4s!Mwhj+5@ z`J9int-G*ipmK6#`v3{9mJl*s|LqimU-*t`#u}!5y{|ge0eK-rW&X zMnr=Oh2K<5d*|nCqj}HbYtBt*cRk`Av`6c~drar&dyICqC6Y55j4y>dd0$U~%Rt7jD z8P8gttVd<>eu*OqoA@P_SIM9GBHLUt<=Z`JZ%TGJ60}AK6e@n|99ZStd3BoL>MWja zV2!Rkikz|nT~m zb(%9J9;6+qojSnR&P)G}2GFoAVV8*UP8uyON{#V1V(f~jE8!TM&?wwv{rl#Icw>J3 zN$tqNC9+#Or}3#%{2lNlG9QNoof0)&AJl0SyX8*7E@$(AZa(K6Gm9tuxSDagwA7f+zu^(s|5gdjGgl?MyEW}prT8IK%xUnq zof$3wp_|l7LJcLKqs1&i?X!j?u9d{>CNX*nD*!#4Iz~NHP?r7IzjNdDnQx1_gom65 zvf!5;1h`lGivTRp9ycxt>}488hn^I|{Ol6$QF1=Psj|(gd@sMA<4=7Jdi!g!E@;w* z7C=avy&&h=f44Y*5fphii5F!fsN`26>8dSkSSJDipu*JB1qi5FH`X0}t1DD?&4Ie4 z#Exrh&>K+Bd#rXDB4fRy0$k*;8YbI*pG!=K#WgVu#{F7xL;ub1l?0tjT^n* zQAKZ_k*kq!Ra>nSMik*+6wvOGI$vXc4Bb-YOxDX5O`KK~1B{GICXSbWp*+97SR`7N zEJ99qcr-imSA0`LTD^FA5hBh$BdXx}Zz+2ud~_ z(r|ns#-V_Z9#rGB3mA<*s=M=WdbrH#k`}l9c$MKfmx?wy<;{`1-NCH;?;)YIj;@ z4*p_ta>4mLPQ1W<1O2j3+BA`)UF` z%!WhmGc7z-g9~IY(p%RZezNaUd@~!5t)wh<*U-dH#Q`FLN{4_kD1;jIAj3eVR-kQ! z*{JmB0-fkjbYtA!DXjoi9r>0w?-HQBJgfs z#Aj^ogZ2?FQ||Qs#o%D*IN*iHt*jqp0{ta}*kY)7xkwynpzWP_zjmeVtg`)0BI{}pQS@g8$&U6|k zllTnLr`0vFafd{bESVK-Ik$=RiHnzE)wZVA-Vj@VsLu7Oz%{DSruSd|VjZJq&Br}LM;L96v5260*yKQNl)|!m*8}(au6?ALB0TL1 z4qIDsivW4S9jnj&-rwXO*?JG-n=nWG1P=jR?#yR&HNI7HVJ3`+aV-B3;HI6qGpjr? zq+Fsj#QR7uK&C8JO&|R*TWoc9)5%keK3druj6{603)}1?Oc6Jfn==`s&1stW36Zs5 zY+tg%cjYhop4I23Y0P^%w|hn2F%}{6`JrrW2zeb|;-yDB8lu>^fKhvb;@vn)!%YD5 eyM#epFzfB?+joMMt=M0tIeT{^|G#|t0Qet{=Qy+g literal 0 HcmV?d00001 diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000000000000000000000000000000000000..3557953b989345201e568979124e084af35674fd GIT binary patch literal 13452 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..045e125 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..5eead77 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #023465 + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..4c7d18d --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,26 @@ + + Stocks + Tinacious Design logo + + + Stocks + About + + Lost internet connection. Using cached data if available. + Internet connection restored. + + Failed to fetch stocks. Will be using cached data if available. + + Search + Search for a stock… + Clear + + 🧐 Search for stocks + Use the search field above to find stocks by ticker or by name + + ☹️ No stocks + No stocks matched your search query + + About + This stocks app was built by Tina Holly at Tinacious Design. + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..16d4c58 --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,5 @@ + + + +