├── .idea ├── .name ├── .gitignore ├── codeStyles │ └── codeStyleConfig.xml └── inspectionProfiles │ └── Project_Default.xml ├── shared ├── data │ ├── .gitignore │ ├── consumer-rules.pro │ ├── proguard-rules.pro │ ├── database │ │ ├── src │ │ │ ├── commonMain │ │ │ │ ├── kotlin │ │ │ │ │ └── dev │ │ │ │ │ │ └── ohoussein │ │ │ │ │ │ └── cryptoapp │ │ │ │ │ │ └── data │ │ │ │ │ │ └── database │ │ │ │ │ │ ├── DatabaseDriverFactory.kt │ │ │ │ │ │ ├── DatabaseFactory.kt │ │ │ │ │ │ ├── DatabaseModule.kt │ │ │ │ │ │ └── crypto │ │ │ │ │ │ ├── CryptoDAO.kt │ │ │ │ │ │ ├── CryptoDAOImpl.kt │ │ │ │ │ │ └── DBModelMapper.kt │ │ │ │ └── sqldelight │ │ │ │ │ └── dev │ │ │ │ │ └── ohoussein │ │ │ │ │ └── cryptoapp │ │ │ │ │ └── db │ │ │ │ │ └── Crypto.sq │ │ │ ├── androidUnitTest │ │ │ │ └── kotlin │ │ │ │ │ └── dev │ │ │ │ │ └── ohoussein │ │ │ │ │ └── cryptoapp │ │ │ │ │ └── data │ │ │ │ │ └── database │ │ │ │ │ ├── DatabaseDriverFactory.kt │ │ │ │ │ └── crypto │ │ │ │ │ ├── mock │ │ │ │ │ └── TestDataFactory.kt │ │ │ │ │ └── CryptoDAOImplTest.kt │ │ │ ├── iosMain │ │ │ │ └── kotlin │ │ │ │ │ └── dev │ │ │ │ │ └── ohoussein │ │ │ │ │ └── cryptoapp │ │ │ │ │ └── data │ │ │ │ │ └── database │ │ │ │ │ ├── DatabaseDriverFactory.kt │ │ │ │ │ └── PlatformDatabaseModule.kt │ │ │ ├── androidMain │ │ │ │ └── kotlin │ │ │ │ │ └── dev │ │ │ │ │ └── ohoussein │ │ │ │ │ └── cryptoapp │ │ │ │ │ └── data │ │ │ │ │ └── database │ │ │ │ │ ├── DatabaseDriverFactory.kt │ │ │ │ │ └── PlatformDatabaseModule.kt │ │ │ ├── desktopMain │ │ │ │ └── kotlin │ │ │ │ │ └── dev │ │ │ │ │ └── ohoussein │ │ │ │ │ └── cryptoapp │ │ │ │ │ └── data │ │ │ │ │ └── database │ │ │ │ │ ├── PlatformDatabaseModule.kt │ │ │ │ │ └── DatabaseDriverFactory.kt │ │ │ └── main │ │ │ │ └── sqldelight │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── db │ │ │ │ └── Crypto.sq │ │ └── build.gradle.kts │ ├── cache │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── data │ │ │ │ └── cache │ │ │ │ ├── CacheDataSource.kt │ │ │ │ ├── CachedDataRepository.kt │ │ │ │ └── InMemoryCacheDataSource.kt │ │ │ └── commonTest │ │ │ └── kotlin │ │ │ └── dev │ │ │ └── ohoussein │ │ │ └── cryptoapp │ │ │ └── data │ │ │ └── cache │ │ │ ├── utils │ │ │ └── FakeTimeSource.kt │ │ │ ├── CachedDataRepositoryImplTest.kt │ │ │ └── InMemoryCacheDataSourceTest.kt │ └── network │ │ ├── src │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── data │ │ │ │ └── network │ │ │ │ ├── NetworkModule.kt │ │ │ │ ├── crypto │ │ │ │ ├── service │ │ │ │ │ ├── ApiCryptoService.kt │ │ │ │ │ └── ApiCryptoServiceImpl.kt │ │ │ │ └── model │ │ │ │ │ └── CryptoApiResponse.kt │ │ │ │ └── NetworkBuilder.kt │ │ └── commonTest │ │ │ └── kotlin │ │ │ └── dev │ │ │ └── ohoussein │ │ │ └── cryptoapp │ │ │ └── data │ │ │ └── network │ │ │ └── crypto │ │ │ └── service │ │ │ ├── utils │ │ │ └── HttpMock.kt │ │ │ ├── mocks │ │ │ └── MockHistoricalPricesJson.kt │ │ │ └── ApiCryptoServiceImplTest.kt │ │ └── build.gradle.kts ├── core │ ├── router │ │ ├── src │ │ │ ├── commonMain │ │ │ │ └── kotlin │ │ │ │ │ └── dev │ │ │ │ │ └── ohoussein │ │ │ │ │ └── cryptoapp │ │ │ │ │ └── core │ │ │ │ │ └── router │ │ │ │ │ ├── Router.kt │ │ │ │ │ └── RouterModule.kt │ │ │ ├── androidMain │ │ │ │ └── kotlin │ │ │ │ │ └── dev │ │ │ │ │ └── ohoussein │ │ │ │ │ └── cryptoapp │ │ │ │ │ └── core │ │ │ │ │ └── router │ │ │ │ │ ├── NativeRouterModule.android.kt │ │ │ │ │ └── RouterImpl.android.kt │ │ │ ├── iosMain │ │ │ │ └── kotlin │ │ │ │ │ └── dev │ │ │ │ │ └── ohoussein │ │ │ │ │ └── cryptoapp │ │ │ │ │ └── core │ │ │ │ │ └── router │ │ │ │ │ ├── NativeRouterModule.ios.kt │ │ │ │ │ └── RouterImpl.ios.kt │ │ │ └── desktopMain │ │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── core │ │ │ │ └── router │ │ │ │ ├── NativeRouterModule.desktop.kt │ │ │ │ └── RouterImpl.desktop.kt │ │ └── build.gradle.kts │ └── formatter │ │ ├── src │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── core │ │ │ │ └── formatter │ │ │ │ ├── PercentFormatter.kt │ │ │ │ ├── PriceFormatter.kt │ │ │ │ └── FormatModule.kt │ │ ├── commonTest │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── core │ │ │ │ └── formatter │ │ │ │ ├── CommonPriceFormatterTest.kt │ │ │ │ └── CommonPercentFormatterTest.kt │ │ ├── androidMain │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── core │ │ │ │ └── formatter │ │ │ │ ├── AndroidPercentFormatter.kt │ │ │ │ └── AndroidPriceFormatter.kt │ │ ├── desktopMain │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── core │ │ │ │ └── formatter │ │ │ │ ├── DesktopPercentFormatter.kt │ │ │ │ └── DesktopPriceFormatter.kt │ │ └── iosMain │ │ │ └── kotlin │ │ │ └── dev │ │ │ └── ohoussein │ │ │ └── cryptoapp │ │ │ └── core │ │ │ └── formatter │ │ │ ├── IOSPercentFormatter.kt │ │ │ └── IOSPriceFormatter.kt │ │ └── build.gradle.kts ├── designsystem │ ├── src │ │ ├── commonMain │ │ │ ├── composeResources │ │ │ │ ├── font │ │ │ │ │ ├── montserrat_medium.ttf │ │ │ │ │ └── paytone_one_regular.ttf │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── designsystem │ │ │ │ ├── graph │ │ │ │ └── model │ │ │ │ │ ├── GraphPoint.kt │ │ │ │ │ └── GridPoint.kt │ │ │ │ ├── theme │ │ │ │ ├── Shape.kt │ │ │ │ ├── Color.kt │ │ │ │ ├── Type.kt │ │ │ │ └── Theme.kt │ │ │ │ ├── base │ │ │ │ ├── LinkText.kt │ │ │ │ ├── CryptoAppScaffold.kt │ │ │ │ └── StateComponent.kt │ │ │ │ └── core │ │ │ │ └── AnnotatedString.kt │ │ └── commonTest │ │ │ └── kotlin │ │ │ └── dev │ │ │ └── ohoussein │ │ │ └── cryptoapp │ │ │ └── designsystem │ │ │ └── core │ │ │ └── AnnotatedStringTest.kt │ └── build.gradle.kts ├── presentation │ ├── src │ │ ├── commonMain │ │ │ ├── composeResources │ │ │ │ └── drawable │ │ │ │ │ └── app_icon_256.png │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── presentation │ │ │ │ ├── App.kt │ │ │ │ └── SharedPresentationModules.kt │ │ ├── androidMain │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── AppFactory.kt │ │ ├── iosMain │ │ │ └── kotlin │ │ │ │ └── presentation │ │ │ │ └── MainViewController.kt │ │ ├── desktopMain │ │ │ └── kotlin │ │ │ │ └── AppFactory.kt │ │ ├── desktopTest │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── presentation │ │ │ │ └── SharedPresentationModulesTest.kt │ │ └── androidUnitTest │ │ │ └── kotlin │ │ │ └── dev │ │ │ └── ohoussein │ │ │ └── cryptoapp │ │ │ └── presentation │ │ │ └── SharedPresentationModulesTest.kt │ └── build.gradle.kts └── crypto │ ├── presentation │ ├── src │ │ ├── commonMain │ │ │ ├── kotlin │ │ │ │ └── dev │ │ │ │ │ └── ohoussein │ │ │ │ │ └── cryptoapp │ │ │ │ │ └── crypto │ │ │ │ │ └── presentation │ │ │ │ │ ├── UIConfig.kt │ │ │ │ │ ├── list │ │ │ │ │ ├── CryptoListEvents.kt │ │ │ │ │ ├── CryptoListState.kt │ │ │ │ │ └── CryptoListViewModel.kt │ │ │ │ │ ├── graph │ │ │ │ │ ├── CryptoPriceGraphEvents.kt │ │ │ │ │ ├── CryptoPriceGraphState.kt │ │ │ │ │ ├── CryptoPriceGraphViewModel.kt │ │ │ │ │ ├── CryptoPriceGraph.kt │ │ │ │ │ └── GraphGridGenerator.kt │ │ │ │ │ ├── model │ │ │ │ │ ├── DataStatus.kt │ │ │ │ │ ├── GraphInterval.kt │ │ │ │ │ └── Crypto.kt │ │ │ │ │ ├── CryptoFeatNavPath.kt │ │ │ │ │ ├── details │ │ │ │ │ ├── CryptoDetailsEvents.kt │ │ │ │ │ ├── CryptoDetailsState.kt │ │ │ │ │ └── CryptoDetailsViewModel.kt │ │ │ │ │ ├── core │ │ │ │ │ ├── DatetimeExt.kt │ │ │ │ │ ├── ViewModel.kt │ │ │ │ │ └── Math.kt │ │ │ │ │ ├── di │ │ │ │ │ └── CryptoPresentationModule.kt │ │ │ │ │ ├── nav │ │ │ │ │ └── CryptoNavGraph.kt │ │ │ │ │ └── mapper │ │ │ │ │ └── DomainModelMapper.kt │ │ │ └── composeResources │ │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ │ └── drawable │ │ │ │ └── ic_bear.xml │ │ └── commonTest │ │ │ └── kotlin │ │ │ └── dev │ │ │ └── ohoussein │ │ │ └── cryptoapp │ │ │ └── crypto │ │ │ └── presentation │ │ │ ├── fake │ │ │ ├── FakePercentFormatter.kt │ │ │ ├── FakeRouter.kt │ │ │ ├── FakePriceFormatter.kt │ │ │ ├── FakeGetTopCryptoListUseCase.kt │ │ │ └── FakeGetCryptoDetailsUseCase.kt │ │ │ ├── core │ │ │ └── MathTest.kt │ │ │ ├── list │ │ │ └── CryptoListViewModelTest.kt │ │ │ └── graph │ │ │ └── CryptoPriceGraphViewModelTest.kt │ ├── build.gradle.kts │ └── presentation.podspec │ ├── domain │ ├── src │ │ ├── commonMain │ │ │ └── kotlin │ │ │ │ └── dev │ │ │ │ └── ohoussein │ │ │ │ └── cryptoapp │ │ │ │ └── crypto │ │ │ │ └── domain │ │ │ │ ├── model │ │ │ │ ├── HistoricalPrice.kt │ │ │ │ ├── Locale.kt │ │ │ │ ├── CryptoModel.kt │ │ │ │ └── FakeCryptoModel.kt │ │ │ │ ├── usecase │ │ │ │ ├── GetTopCryptoListUseCase.kt │ │ │ │ ├── GetTopCryptoListUseCaseImpl.kt │ │ │ │ ├── GetCryptoDetailsUseCase.kt │ │ │ │ └── GetCryptoDetailsUseCaseImpl.kt │ │ │ │ ├── repo │ │ │ │ └── ICryptoRepository.kt │ │ │ │ └── CryptoDomainModule.kt │ │ └── commonTest │ │ │ └── kotlin │ │ │ └── dev │ │ │ └── ohoussein │ │ │ └── cryptoapp │ │ │ └── crypto │ │ │ └── domain │ │ │ └── usecase │ │ │ ├── GetTopCryptoListUseCaseImplTest.kt │ │ │ ├── stub │ │ │ └── MockedCryptoRepository.kt │ │ │ └── GetCryptoDetailsUseCaseImplTest.kt │ └── build.gradle.kts │ └── data │ ├── build.gradle.kts │ └── src │ └── commonMain │ └── kotlin │ └── dev │ └── ohoussein │ └── cryptoapp │ └── crypto │ └── data │ ├── CryptoDataModule.kt │ ├── mapper │ └── ApiDomainModelMapper.kt │ └── repository │ └── CryptoRepository.kt ├── e2e_tests ├── .gitignore ├── ios │ ├── ios.yml │ ├── ios-screenshots.yml │ ├── crypto_list.yml │ └── crypto_details.yml ├── android │ ├── android.yml │ ├── android-screenshots.yml │ ├── crypto_list.yml │ └── crypto_details.yml ├── test_android.sh ├── test_ios.sh ├── readme.MD └── generate_screenshots.sh ├── .gitattributes ├── gradle.properties ├── app-iOS ├── Configuration │ └── Config.xcconfig ├── appiOS │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── cryptoAppIconiOS.png │ │ │ └── Contents.json │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── iOSApp.swift │ ├── ContentView.swift │ └── Info.plist ├── .gitignore └── appiOS.xcodeproj │ ├── project.xcworkspace │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ └── xcschemes │ └── appiOS.xcscheme ├── design ├── ios_crypto_list_light.png ├── android_crypto_list_dark.png ├── desktop_crypto_list_dark.png ├── ios_crypto_details_light.png ├── android_crypto_details_dark.png ├── desktop_crypto_details_dark.png └── architecture.drawio ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── app-android ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── ic_launcher_background.xml │ │ │ ├── colors.xml │ │ │ └── themes.xml │ │ ├── xml │ │ │ ├── backup_rules.xml │ │ │ └── network_security_config.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.webp │ │ │ └── ic_launcher_round.webp │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ └── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ ├── ic_launcher-playstore.png │ │ ├── java │ │ └── dev │ │ │ └── ohoussein │ │ │ └── cryptoapp │ │ │ ├── MainActivity.kt │ │ │ └── App.kt │ │ └── AndroidManifest.xml ├── build.gradle.kts └── proguard-rules.pro ├── app-desktop ├── src │ └── main │ │ ├── resources │ │ └── icon │ │ │ ├── app_icon.icns │ │ │ ├── app_icon.ico │ │ │ └── app_icon.png │ │ └── kotlin │ │ └── dev │ │ └── ohoussein │ │ └── cryptoapp │ │ └── Main.kt └── build.gradle.kts ├── .gitignore ├── .fleet └── run.json ├── .github ├── dependabot.yml └── workflows │ └── main_ci.yml ├── settings.gradle.kts ├── gradlew.bat └── README.md /.idea/.name: -------------------------------------------------------------------------------- 1 | cryptoapp -------------------------------------------------------------------------------- /shared/data/.gitignore: -------------------------------------------------------------------------------- 1 | /build -------------------------------------------------------------------------------- /e2e_tests/.gitignore: -------------------------------------------------------------------------------- 1 | screenshots -------------------------------------------------------------------------------- /shared/data/consumer-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /shared/data/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | **/snapshots/**/*.png filter=lfs diff=lfs merge=lfs -text 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | artifacts 5 | modules -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | android.useAndroidX=true 2 | org.gradle.jvmargs=-Xmx2536m 3 | # org.gradle.parallel=true -------------------------------------------------------------------------------- /app-iOS/Configuration/Config.xcconfig: -------------------------------------------------------------------------------- 1 | TEAM_ID= 2 | BUNDLE_ID=com.ohoussein.cryptoapp 3 | APP_NAME=CryptoApp 4 | -------------------------------------------------------------------------------- /design/ios_crypto_list_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/design/ios_crypto_list_light.png -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app-android/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CryptoApp 3 | 4 | -------------------------------------------------------------------------------- /app-iOS/appiOS/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /design/android_crypto_list_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/design/android_crypto_list_dark.png -------------------------------------------------------------------------------- /design/desktop_crypto_list_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/design/desktop_crypto_list_dark.png -------------------------------------------------------------------------------- /design/ios_crypto_details_light.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/design/ios_crypto_details_light.png -------------------------------------------------------------------------------- /design/android_crypto_details_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/design/android_crypto_details_dark.png -------------------------------------------------------------------------------- /design/desktop_crypto_details_dark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/design/desktop_crypto_details_dark.png -------------------------------------------------------------------------------- /app-android/src/main/res/xml/backup_rules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /e2e_tests/ios/ios.yml: -------------------------------------------------------------------------------- 1 | appId: com.ohoussein.cryptoapp 2 | --- 3 | - launchApp 4 | - runFlow: crypto_list.yml 5 | - runFlow: crypto_details.yml -------------------------------------------------------------------------------- /app-android/src/main/ic_launcher-playstore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/ic_launcher-playstore.png -------------------------------------------------------------------------------- /e2e_tests/android/android.yml: -------------------------------------------------------------------------------- 1 | appId: dev.ohoussein.cryptoapp 2 | --- 3 | - launchApp 4 | - runFlow: crypto_list.yml 5 | - runFlow: crypto_details.yml -------------------------------------------------------------------------------- /app-desktop/src/main/resources/icon/app_icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-desktop/src/main/resources/icon/app_icon.icns -------------------------------------------------------------------------------- /app-desktop/src/main/resources/icon/app_icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-desktop/src/main/resources/icon/app_icon.ico -------------------------------------------------------------------------------- /app-desktop/src/main/resources/icon/app_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-desktop/src/main/resources/icon/app_icon.png -------------------------------------------------------------------------------- /app-iOS/appiOS/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app-desktop/src/main/kotlin/dev/ohoussein/cryptoapp/Main.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp 2 | 3 | import createApp 4 | 5 | fun main() { 6 | createApp() 7 | } 8 | -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /app-android/src/main/res/values/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #263238 4 | -------------------------------------------------------------------------------- /app-iOS/.gitignore: -------------------------------------------------------------------------------- 1 | # Mac OS X 2 | *.DS_Store 3 | 4 | # Xcode 5 | *.pbxuser 6 | *.mode1v3 7 | *.mode2v3 8 | *.perspectivev3 9 | *.xcuserstate 10 | project.xcworkspace/ 11 | xcuserdata/ -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /app-iOS/appiOS/Assets.xcassets/AppIcon.appiconset/cryptoAppIconiOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/app-iOS/appiOS/Assets.xcassets/AppIcon.appiconset/cryptoAppIconiOS.png -------------------------------------------------------------------------------- /shared/core/router/src/commonMain/kotlin/dev/ohoussein/cryptoapp/core/router/Router.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.core.router 2 | 3 | interface Router { 4 | fun openUrl(url: String) 5 | } 6 | -------------------------------------------------------------------------------- /shared/designsystem/src/commonMain/composeResources/font/montserrat_medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/shared/designsystem/src/commonMain/composeResources/font/montserrat_medium.ttf -------------------------------------------------------------------------------- /shared/presentation/src/commonMain/composeResources/drawable/app_icon_256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/shared/presentation/src/commonMain/composeResources/drawable/app_icon_256.png -------------------------------------------------------------------------------- /shared/designsystem/src/commonMain/composeResources/font/paytone_one_regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OHoussein/CryptoApp/HEAD/shared/designsystem/src/commonMain/composeResources/font/paytone_one_regular.ttf -------------------------------------------------------------------------------- /shared/designsystem/src/commonMain/kotlin/dev/ohoussein/cryptoapp/designsystem/graph/model/GraphPoint.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.designsystem.graph.model 2 | 3 | data class GraphPoint(val x: Double, val y: Double) 4 | -------------------------------------------------------------------------------- /app-iOS/appiOS/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/UIConfig.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation 2 | 3 | object UIConfig { 4 | const val CRYPTO_DESCRIPTION_MAX_LINES = 4 5 | } 6 | -------------------------------------------------------------------------------- /e2e_tests/test_android.sh: -------------------------------------------------------------------------------- 1 | bash ../gradlew :app-android:installRelease 2 | adb shell screenrecord /sdcard/tmp/crypto_test.mp4 & 3 | maestro test android/android.yml 4 | kill $! 5 | sleep 2 6 | adb pull /sdcard/tmp/crypto_test.mp4 screenshots/record_android.mp4 -------------------------------------------------------------------------------- /shared/designsystem/src/commonMain/kotlin/dev/ohoussein/cryptoapp/designsystem/graph/model/GridPoint.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.designsystem.graph.model 2 | 3 | data class GridPoint( 4 | val position: Double, 5 | val label: String, 6 | ) 7 | -------------------------------------------------------------------------------- /shared/crypto/domain/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/domain/model/HistoricalPrice.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.domain.model 2 | 3 | data class HistoricalPrice( 4 | val timestampMillis: Long, 5 | val price: Double, 6 | ) 7 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/list/CryptoListEvents.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.list 2 | 3 | sealed interface CryptoListEvents { 4 | data object Refresh : CryptoListEvents 5 | } 6 | -------------------------------------------------------------------------------- /shared/core/router/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("dev.ohoussein.cryptoapp.kotlin.multiplatform.library") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | commonMain.dependencies { 8 | implementation(libs.koin.core) 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Feb 05 10:35:00 CET 2025 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip 5 | zipStoreBase=GRADLE_USER_HOME 6 | zipStorePath=wrapper/dists 7 | -------------------------------------------------------------------------------- /shared/data/database/src/commonMain/kotlin/dev/ohoussein/cryptoapp/data/database/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.data.database 2 | 3 | import app.cash.sqldelight.db.SqlDriver 4 | 5 | expect class DatabaseDriverFactory { 6 | fun createDriver(): SqlDriver 7 | } 8 | -------------------------------------------------------------------------------- /shared/crypto/domain/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/domain/model/Locale.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.domain.model 2 | 3 | val defaultLocale = Locale("USD", "en") 4 | 5 | data class Locale( 6 | val currencyCode: String, 7 | val languageCode: String, 8 | ) 9 | -------------------------------------------------------------------------------- /shared/designsystem/src/commonMain/composeResources/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | CryptoApp 4 | 5 | RETRY 6 | Back 7 | -------------------------------------------------------------------------------- /e2e_tests/ios/ios-screenshots.yml: -------------------------------------------------------------------------------- 1 | appId: OHoussein.CryptoAppiOS 2 | --- 3 | - launchApp 4 | - assertVisible: "Bitcoin" 5 | - takeScreenshot: screenshots/ios_crypto_list_${test_name} 6 | - tapOn: "Bitcoin" 7 | - assertVisible: "Bitcoin (BTC)" 8 | - takeScreenshot: screenshots/ios_crypto_details_${test_name} -------------------------------------------------------------------------------- /e2e_tests/android/android-screenshots.yml: -------------------------------------------------------------------------------- 1 | appId: dev.ohoussein.cryptoapp 2 | --- 3 | - launchApp 4 | - assertVisible: "Bitcoin" 5 | - takeScreenshot: screenshots/android_crypto_list_${test_name} 6 | - tapOn: "Bitcoin" 7 | - assertVisible: "Bitcoin (BTC)" 8 | - takeScreenshot: screenshots/android_crypto_details_${test_name} -------------------------------------------------------------------------------- /app-android/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #455A64 4 | #263238 5 | #4FC3F7 6 | #FFF 7 | 8 | -------------------------------------------------------------------------------- /shared/core/formatter/src/commonMain/kotlin/dev/ohoussein/cryptoapp/core/formatter/PercentFormatter.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.core.formatter 2 | 3 | interface PercentFormatter { 4 | operator fun invoke(percent: Double): String 5 | } 6 | 7 | expect fun getPercentFormatter(localeId: String): PercentFormatter 8 | -------------------------------------------------------------------------------- /shared/core/formatter/src/commonMain/kotlin/dev/ohoussein/cryptoapp/core/formatter/PriceFormatter.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.core.formatter 2 | 3 | interface PriceFormatter { 4 | operator fun invoke(price: Double, currencyCode: String): String 5 | } 6 | 7 | expect fun getPriceFormatter(localeId: String): PriceFormatter 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | **.iml 2 | **/local.properties 3 | .idea/*.xml 4 | .idea/libraries 5 | .idea/modules 6 | .idea/shelf 7 | .idea/sonarlint 8 | **.DS_Store 9 | **/build 10 | **/captures 11 | **.externalNativeBuild 12 | **/app/release/ 13 | **/app/prod/ 14 | **/app/mock/ 15 | **/app/.cxx/ 16 | **/apk 17 | *.jks 18 | .gradle 19 | .kotlin 20 | -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /shared/core/router/src/androidMain/kotlin/dev/ohoussein/cryptoapp/core/router/NativeRouterModule.android.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.core.router 2 | 3 | import org.koin.core.module.Module 4 | import org.koin.dsl.module 5 | 6 | actual val nativeRouterModule: Module = module { 7 | factory { RouterImpl(get()) } 8 | } 9 | -------------------------------------------------------------------------------- /.fleet/run.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "type": "xcode-app", 5 | "name": "Xcode-app configuration", 6 | "buildTarget": { 7 | "project": "", 8 | "target": "" 9 | }, 10 | "configuration": "" 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /app-android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app-iOS/appiOS.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /shared/core/formatter/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("dev.ohoussein.cryptoapp.kotlin.multiplatform.library") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | commonMain.dependencies { 8 | implementation(libs.koin.core) 9 | implementation(libs.core.kotlin.datetime) 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /shared/core/router/src/commonMain/kotlin/dev/ohoussein/cryptoapp/core/router/RouterModule.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.core.router 2 | 3 | import org.koin.core.module.Module 4 | import org.koin.dsl.module 5 | 6 | val routerModule = module { 7 | includes(nativeRouterModule) 8 | } 9 | 10 | expect val nativeRouterModule: Module 11 | -------------------------------------------------------------------------------- /app-iOS/appiOS/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "cryptoAppIconiOS.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /e2e_tests/ios/crypto_list.yml: -------------------------------------------------------------------------------- 1 | appId: CryptoAppiOS 2 | --- 3 | - assertVisible: "Bitcoin" 4 | - assertVisible: "BTC" 5 | - assertVisible: "Ethereum" 6 | - assertVisible: "ETH" 7 | - assertVisible: \$[0-9]+,?[0-9].*(\.[0-9]+)? # crypto price 8 | - assertVisible: ^[0-9]+(\.[0-9])?% # 24h change % 9 | - takeScreenshot: screenshots/ios_crypto_list 10 | -------------------------------------------------------------------------------- /shared/presentation/src/androidMain/kotlin/dev/ohoussein/cryptoapp/AppFactory.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp 2 | 3 | import SharedApp 4 | import androidx.activity.ComponentActivity 5 | import androidx.activity.compose.setContent 6 | 7 | fun ComponentActivity.createApp() { 8 | setContent { 9 | SharedApp() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /e2e_tests/android/crypto_list.yml: -------------------------------------------------------------------------------- 1 | appId: dev.ohoussein.cryptoapp 2 | --- 3 | - assertVisible: "Bitcoin" 4 | - takeScreenshot: screenshots/android_crypto_list 5 | - assertVisible: "BTC" 6 | - assertVisible: "Ethereum" 7 | - assertVisible: "ETH" 8 | - assertVisible: \$[0-9]+,?[0-9].*(\.[0-9]+)? # crypto price 9 | - assertVisible: ^[0-9]+(\.[0-9])?% # 24h change % -------------------------------------------------------------------------------- /shared/core/router/src/iosMain/kotlin/dev/ohoussein/cryptoapp/core/router/NativeRouterModule.ios.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.core.router 2 | 3 | import org.koin.core.module.Module 4 | import org.koin.core.module.dsl.factoryOf 5 | import org.koin.dsl.module 6 | 7 | actual val nativeRouterModule: Module = module { 8 | factoryOf(::RouterImpl) 9 | } 10 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonTest/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/fake/FakePercentFormatter.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.fake 2 | 3 | import dev.ohoussein.cryptoapp.core.formatter.PercentFormatter 4 | 5 | class FakePercentFormatter : PercentFormatter { 6 | override fun invoke(percent: Double): String = "$percent%" 7 | } 8 | -------------------------------------------------------------------------------- /shared/core/router/src/desktopMain/kotlin/dev/ohoussein/cryptoapp/core/router/NativeRouterModule.desktop.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.core.router 2 | 3 | import org.koin.core.module.Module 4 | import org.koin.core.module.dsl.factoryOf 5 | import org.koin.dsl.module 6 | 7 | actual val nativeRouterModule: Module = module { 8 | factoryOf(::RouterImpl) 9 | } 10 | -------------------------------------------------------------------------------- /app-android/src/main/java/dev/ohoussein/cryptoapp/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp 2 | 3 | import android.os.Bundle 4 | import androidx.activity.ComponentActivity 5 | 6 | class MainActivity : ComponentActivity() { 7 | override fun onCreate(savedInstanceState: Bundle?) { 8 | super.onCreate(savedInstanceState) 9 | createApp() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /app-iOS/appiOS/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | import Presentation 3 | 4 | @main 5 | struct iOSApp: App { 6 | @UIApplicationDelegateAdaptor(AppDelegate.self) 7 | var appDelegate: AppDelegate 8 | 9 | var body: some Scene { 10 | WindowGroup { 11 | ContentView() 12 | } 13 | } 14 | } 15 | 16 | 17 | class AppDelegate: NSObject, UIApplicationDelegate { 18 | 19 | } 20 | -------------------------------------------------------------------------------- /e2e_tests/test_ios.sh: -------------------------------------------------------------------------------- 1 | xcodebuild \ 2 | -workspace ../app-iOS/appiOS.xcodeproj/project.xcworkspace \ 3 | -configuration Release \ 4 | -scheme appiOS \ 5 | -sdk iphonesimulator \ 6 | -derivedDataPath app-iOS/build 7 | 8 | xcrun simctl install Booted ../app-iOS/build/Build/Products/Debug-iphonesimulator/CryptoAppiOS.app 9 | maestro test ios/ios.yml -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/graph/CryptoPriceGraphEvents.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.graph 2 | 3 | import dev.ohoussein.cryptoapp.crypto.presentation.model.GraphInterval 4 | 5 | sealed interface CryptoPriceGraphEvents { 6 | data class SelectInterval(val interval: GraphInterval) : CryptoPriceGraphEvents 7 | } 8 | -------------------------------------------------------------------------------- /shared/data/cache/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("dev.ohoussein.cryptoapp.kotlin.multiplatform.library") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | commonMain.dependencies { 8 | implementation(libs.core.kotlin.coroutines.core) 9 | } 10 | commonTest.dependencies { 11 | implementation(libs.test.coroutines) 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /shared/data/database/src/commonMain/kotlin/dev/ohoussein/cryptoapp/data/database/DatabaseFactory.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.data.database 2 | 3 | import app.cash.sqldelight.db.SqlDriver 4 | import dev.ohoussein.cryptoapp.database.CryptoDB 5 | 6 | fun buildCryptoDB(driver: SqlDriver): CryptoDB { 7 | runCatching { CryptoDB.Schema.create(driver) } 8 | return CryptoDB(driver) 9 | } 10 | -------------------------------------------------------------------------------- /shared/data/cache/src/commonMain/kotlin/dev/ohoussein/cryptoapp/data/cache/CacheDataSource.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.data.cache 2 | 3 | import kotlin.time.Duration 4 | 5 | interface CacheDataSource { 6 | 7 | suspend fun read(key: Key, ttl: Duration? = null): Data? 8 | 9 | suspend fun write(key: Key, data: Data?) 10 | 11 | suspend fun clearAll() 12 | } 13 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonTest/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/fake/FakeRouter.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.fake 2 | 3 | import dev.ohoussein.cryptoapp.core.router.Router 4 | 5 | class FakeRouter : Router { 6 | val openedUrls = mutableListOf() 7 | override fun openUrl(url: String) { 8 | openedUrls.add(url) 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/model/DataStatus.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.model 2 | 3 | import androidx.compose.runtime.Stable 4 | 5 | @Stable 6 | sealed interface DataStatus { 7 | data object Success : DataStatus 8 | data class Error(val message: String) : DataStatus 9 | data object Loading : DataStatus 10 | } 11 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/model/GraphInterval.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.model 2 | 3 | @Suppress("MagicNumber") 4 | enum class GraphInterval(val countDays: Int) { 5 | INTERVAL_1_DAY(1), 6 | INTERVAL_7_DAYS(7), 7 | INTERVAL_1_MONTH(30), 8 | INTERVAL_3_MONTHS(30 * 3), 9 | INTERVAL_1_YEAR(30 * 12), 10 | } 11 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/CryptoFeatNavPath.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation 2 | 3 | object CryptoFeatNavPath { 4 | const val HOME = "home" 5 | 6 | object CryptoDetailsPath { 7 | const val ARG_CRYPTO_ID = "id" 8 | 9 | const val PATH = "crypto/{$ARG_CRYPTO_ID}" 10 | 11 | fun path(id: String) = "crypto/$id" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /shared/crypto/domain/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("dev.ohoussein.cryptoapp.kotlin.multiplatform.library") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | commonMain.dependencies { 8 | implementation(libs.core.kotlin.coroutines.core) 9 | implementation(libs.koin.core) 10 | } 11 | commonTest.dependencies { 12 | implementation(libs.test.coroutines) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/details/CryptoDetailsEvents.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.details 2 | 3 | sealed interface CryptoDetailsEvents { 4 | data object Refresh : CryptoDetailsEvents 5 | data object HomePageClicked : CryptoDetailsEvents 6 | data object BlockchainSiteClicked : CryptoDetailsEvents 7 | data object SourceCodeClicked : CryptoDetailsEvents 8 | } 9 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/list/CryptoListState.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.list 2 | 3 | import dev.ohoussein.cryptoapp.crypto.presentation.model.Crypto 4 | import dev.ohoussein.cryptoapp.crypto.presentation.model.DataStatus 5 | 6 | data class CryptoListState( 7 | val cryptoList: List? = null, 8 | val status: DataStatus = DataStatus.Loading, 9 | ) 10 | -------------------------------------------------------------------------------- /shared/designsystem/src/commonMain/kotlin/dev/ohoussein/cryptoapp/designsystem/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.designsystem.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material.Shapes 5 | import androidx.compose.ui.unit.dp 6 | 7 | val Shapes = Shapes( 8 | small = RoundedCornerShape(4.dp), 9 | medium = RoundedCornerShape(24.dp), 10 | large = RoundedCornerShape(0.dp) 11 | ) 12 | -------------------------------------------------------------------------------- /e2e_tests/readme.MD: -------------------------------------------------------------------------------- 1 | ## Installation 2 | 3 | [Documentation](https://maestro.mobile.dev/getting-started/installing-maestro) 4 | 5 | ``` 6 | curl -Ls "https://get.maestro.mobile.dev" | bash 7 | ``` 8 | 9 | ## run end to end test 10 | ``` 11 | cd e2e_tests/ 12 | ./test_android.sh 13 | ``` 14 | ## Build Android and run end to end test 15 | ``` 16 | ./gradlew e2eTest 17 | ``` 18 | ## Generate screenshots 19 | ``` 20 | cd e2e_tests/ 21 | ./generate_screenshots.sh 22 | ``` -------------------------------------------------------------------------------- /shared/data/database/src/androidUnitTest/kotlin/dev/ohoussein/cryptoapp/data/database/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.data.database 2 | 3 | import app.cash.sqldelight.driver.jdbc.sqlite.JdbcSqliteDriver 4 | import dev.ohoussein.cryptoapp.database.CryptoDB 5 | 6 | fun createDatabase(): CryptoDB { 7 | val driver = JdbcSqliteDriver(JdbcSqliteDriver.IN_MEMORY) 8 | CryptoDB.Schema.create(driver) 9 | return CryptoDB(driver) 10 | } 11 | -------------------------------------------------------------------------------- /shared/data/database/src/iosMain/kotlin/dev/ohoussein/cryptoapp/data/database/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.data.database 2 | 3 | import app.cash.sqldelight.db.SqlDriver 4 | import app.cash.sqldelight.driver.native.NativeSqliteDriver 5 | import dev.ohoussein.cryptoapp.database.CryptoDB 6 | 7 | actual class DatabaseDriverFactory { 8 | actual fun createDriver(): SqlDriver = NativeSqliteDriver(CryptoDB.Schema, "CryptoDatabase.db") 9 | } 10 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonTest/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/fake/FakePriceFormatter.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.fake 2 | 3 | import dev.ohoussein.cryptoapp.core.formatter.PriceFormatter 4 | import kotlin.math.roundToInt 5 | 6 | class FakePriceFormatter : PriceFormatter { 7 | override fun invoke(price: Double, currencyCode: String): String { 8 | return "${price.roundToInt()} $currencyCode" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /shared/presentation/src/iosMain/kotlin/presentation/MainViewController.kt: -------------------------------------------------------------------------------- 1 | package presentation 2 | 3 | import SharedApp 4 | import androidx.compose.ui.window.ComposeUIViewController 5 | import dev.ohoussein.cryptoapp.presentation.sharedPresentationModules 6 | import org.koin.compose.KoinApplication 7 | 8 | fun mainViewController() = ComposeUIViewController { 9 | KoinApplication(application = { 10 | modules(sharedPresentationModules) 11 | }) { 12 | SharedApp() 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /shared/core/router/src/desktopMain/kotlin/dev/ohoussein/cryptoapp/core/router/RouterImpl.desktop.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.core.router 2 | 3 | import java.awt.Desktop 4 | import java.net.URI 5 | 6 | class RouterImpl : Router { 7 | override fun openUrl(url: String) { 8 | runCatching { Desktop.getDesktop().browse(URI.create(url)) } 9 | .onFailure { 10 | println("Errr opening url $url: $it") 11 | it.printStackTrace() 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "gradle" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | open-pull-requests-limit: 4 8 | groups: 9 | android-build-tools: 10 | patterns: 11 | - "com.android.tools.build*" 12 | kotlin-dependencies: 13 | patterns: 14 | - "org.jetbrains.kotlinx*" 15 | - "org.jetbrains.kotlin*" 16 | app-dependencies: 17 | patterns: 18 | - "*" 19 | -------------------------------------------------------------------------------- /shared/data/network/src/commonMain/kotlin/dev/ohoussein/cryptoapp/data/network/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.data.network 2 | 3 | import dev.ohoussein.cryptoapp.data.network.crypto.service.ApiCryptoService 4 | import dev.ohoussein.cryptoapp.data.network.crypto.service.ApiCryptoServiceImpl 5 | import org.koin.dsl.module 6 | 7 | val networkModule = module { 8 | single { 9 | NetworkBuilder.httpClient() 10 | } 11 | 12 | single { 13 | ApiCryptoServiceImpl(get()) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /shared/crypto/presentation/src/commonMain/kotlin/dev/ohoussein/cryptoapp/crypto/presentation/details/CryptoDetailsState.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.crypto.presentation.details 2 | 3 | import cryptoapp.shared.crypto.presentation.generated.resources.* 4 | import dev.ohoussein.cryptoapp.crypto.presentation.model.CryptoDetails 5 | import dev.ohoussein.cryptoapp.crypto.presentation.model.DataStatus 6 | 7 | data class CryptoDetailsState( 8 | val cryptoDetails: CryptoDetails? = null, 9 | val status: DataStatus = DataStatus.Loading, 10 | ) 11 | -------------------------------------------------------------------------------- /shared/core/formatter/src/commonMain/kotlin/dev/ohoussein/cryptoapp/core/formatter/FormatModule.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.core.formatter 2 | 3 | import kotlinx.datetime.TimeZone 4 | import org.koin.core.qualifier.named 5 | import org.koin.dsl.module 6 | 7 | val formatModule = module { 8 | factory { 9 | getPercentFormatter(get(named("languageCode"))) 10 | } 11 | factory { 12 | getPriceFormatter(get(named("languageCode"))) 13 | } 14 | factory { 15 | TimeZone.currentSystemDefault() 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /shared/data/database/src/androidMain/kotlin/dev/ohoussein/cryptoapp/data/database/DatabaseDriverFactory.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.data.database 2 | 3 | import android.content.Context 4 | import app.cash.sqldelight.db.SqlDriver 5 | import app.cash.sqldelight.driver.android.AndroidSqliteDriver 6 | import dev.ohoussein.cryptoapp.database.CryptoDB 7 | 8 | actual class DatabaseDriverFactory(private val context: Context) { 9 | actual fun createDriver(): SqlDriver = 10 | AndroidSqliteDriver(CryptoDB.Schema, context, "CryptoDatabase.db") 11 | } 12 | -------------------------------------------------------------------------------- /shared/designsystem/src/commonMain/kotlin/dev/ohoussein/cryptoapp/designsystem/theme/Color.kt: -------------------------------------------------------------------------------- 1 | @file:Suppress("MagicNumber") 2 | 3 | package dev.ohoussein.cryptoapp.designsystem.theme 4 | 5 | import androidx.compose.ui.graphics.Color 6 | 7 | val LightBlue300 = Color(0xFF4FC3F7) 8 | val LightBlue500 = Color(0xFF03A9F4) 9 | 10 | val BlueGrey50 = Color(0xFFECEFF1) 11 | val BlueGrey700 = Color(0xFF455A64) 12 | val BlueGrey900 = Color(0xFF263238) 13 | 14 | val LIME700 = Color(0xFFAFB42B) 15 | 16 | val Green500 = Color(0XFF4CAF50) 17 | val Red500 = Color(0XFFF44336) 18 | -------------------------------------------------------------------------------- /app-android/src/main/res/xml/network_security_config.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | localhost 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app-iOS/appiOS/ContentView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import Presentation 4 | 5 | struct ComposeView: UIViewControllerRepresentable { 6 | 7 | func makeUIViewController(context: Context) -> UIViewController { 8 | MainViewControllerKt.mainViewController() 9 | } 10 | 11 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} 12 | } 13 | 14 | struct ContentView: View { 15 | var body: some View { 16 | ComposeView() 17 | .ignoresSafeArea(.keyboard) 18 | } 19 | } 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /shared/data/database/src/commonMain/kotlin/dev/ohoussein/cryptoapp/data/database/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package dev.ohoussein.cryptoapp.data.database 2 | 3 | import dev.ohoussein.cryptoapp.data.database.crypto.DBModelMapper 4 | import org.koin.core.module.Module 5 | import org.koin.core.module.dsl.factoryOf 6 | import org.koin.core.module.dsl.singleOf 7 | import org.koin.dsl.module 8 | 9 | val databaseModule = module { 10 | includes(platformDatabaseModule) 11 | 12 | singleOf(::buildCryptoDB) 13 | factoryOf(::DBModelMapper) 14 | } 15 | 16 | expect val platformDatabaseModule: Module 17 | -------------------------------------------------------------------------------- /shared/crypto/data/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("dev.ohoussein.cryptoapp.kotlin.multiplatform.library") 3 | } 4 | 5 | kotlin { 6 | sourceSets { 7 | commonMain.dependencies { 8 | implementation(libs.core.kotlin.coroutines.core) 9 | implementation(libs.koin.core) 10 | implementation(project(":shared:data:network")) 11 | implementation(project(":shared:data:database")) 12 | implementation(project(":shared:data:cache")) 13 | implementation(project(":shared:crypto:domain")) 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /app-android/src/main/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 | 11 |