├── .firebaserc
├── .gitattributes
├── .github
└── workflows
│ └── build-release.yml
├── .gitignore
├── LICENSE
├── Privacy Policy.md
├── README.md
├── app
├── .gitignore
├── build.gradle.kts
├── proguard-rules.pro
├── release
│ ├── baselineProfiles
│ │ ├── 0
│ │ │ └── app-release.dm
│ │ └── 1
│ │ │ └── app-release.dm
│ └── output-metadata.json
└── src
│ ├── debug
│ ├── google-services.json
│ ├── ic_launcher-playstore.png
│ └── res
│ │ ├── drawable
│ │ └── ic_launcher_foreground.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_background.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ └── values
│ │ ├── ic_launcher_background.xml
│ │ └── strings.xml
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ └── com
│ │ │ └── kafka
│ │ │ └── user
│ │ │ ├── KafkaApplication.kt
│ │ │ ├── MainActivity.kt
│ │ │ ├── fcm
│ │ │ ├── FcmTokenGenerator.kt
│ │ │ └── FirebaseMessageService.kt
│ │ │ ├── home
│ │ │ ├── AppNavigation.kt
│ │ │ ├── MainScreen.kt
│ │ │ ├── MainViewModel.kt
│ │ │ ├── bottombar
│ │ │ │ ├── HomeNavigation.kt
│ │ │ │ ├── HomeNavigationItems.kt
│ │ │ │ └── NavigationDrawerItem.kt
│ │ │ └── overlays
│ │ │ │ ├── AppMessageDialog.kt
│ │ │ │ ├── AppUpdateDialog.kt
│ │ │ │ └── Overlays.kt
│ │ │ └── injection
│ │ │ ├── AndroidActivityComponent.kt
│ │ │ ├── AndroidApplicationComponent.kt
│ │ │ └── AppComponent.kt
│ └── res
│ │ ├── drawable
│ │ ├── ic_kafka_logo.xml
│ │ └── ic_launcher_foreground.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── values-night
│ │ └── themes.xml
│ │ ├── values
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── themes.xml
│ │ └── xml
│ │ └── data_extraction_rules.xml
│ ├── rc
│ ├── generated
│ │ └── baselineProfiles
│ │ │ ├── baseline-prof.txt
│ │ │ └── startup-prof.txt
│ ├── ic_launcher-playstore.png
│ └── res
│ │ ├── drawable
│ │ └── ic_launcher_foreground.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.webp
│ │ ├── ic_launcher_foreground.webp
│ │ └── ic_launcher_round.webp
│ │ └── values
│ │ ├── ic_launcher_background.xml
│ │ └── strings.xml
│ └── release
│ └── generated
│ └── baselineProfiles
│ ├── baseline-prof.txt
│ └── startup-prof.txt
├── base
├── annotations
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── kafka
│ │ └── base
│ │ └── Annotations.kt
└── domain
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ └── commonMain
│ └── kotlin
│ └── com
│ └── kafka
│ └── base
│ ├── AppInitializer.kt
│ ├── Combine.kt
│ ├── CoroutineDispatchers.kt
│ ├── SecretsProvider.kt
│ ├── TimberExt.kt
│ ├── domain
│ ├── Interactor.kt
│ └── InvokeStatus.kt
│ └── extensions
│ └── CoroutineExtensions.kt
├── baselineprofile
├── .gitignore
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── kafka
│ └── baseline
│ └── profile
│ ├── BaselineProfileGenerator.kt
│ └── StartupBenchmarks.kt
├── build.gradle.kts
├── core
├── ads
│ ├── .gitignore
│ ├── build.gradle
│ └── src
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── org
│ │ │ └── kafka
│ │ │ └── ads
│ │ │ └── admob
│ │ │ ├── NativeAdInitializer.kt
│ │ │ ├── NativeAdProvider.kt
│ │ │ ├── NativeAdState.kt
│ │ │ ├── NativeAdView.kt
│ │ │ ├── RowAd.kt
│ │ │ └── xml
│ │ │ └── RowAd.kt
│ │ └── res
│ │ ├── drawable
│ │ └── ad_attribution_selector.xml
│ │ └── layout
│ │ └── row_ad_container.xml
├── analytics
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── analytics
│ │ │ ├── AnalyticsPlatformComponent.kt
│ │ │ ├── EventRepository.kt
│ │ │ └── logger
│ │ │ └── Analytics.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── analytics
│ │ │ └── AnalyticsPlatformComponent.kt
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── kafka
│ │ └── analytics
│ │ ├── AnalyticsPlatformComponent.kt
│ │ └── logger
│ │ └── AnalyticsImpl.kt
├── downloader
│ ├── build.gradle.kts
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── tm
│ │ │ └── alashow
│ │ │ └── datmusic
│ │ │ └── downloader
│ │ │ ├── DownloadItem.kt
│ │ │ ├── DownloadMessage.kt
│ │ │ ├── Downloader.kt
│ │ │ ├── DownloaderEvent.kt
│ │ │ ├── DownloaderMessages.kt
│ │ │ ├── DownloaderModule.kt
│ │ │ ├── interactors
│ │ │ ├── ObserveDownloadByFileId.kt
│ │ │ ├── ObserveDownloadByItemId.kt
│ │ │ ├── ObserveDownloadedFiles.kt
│ │ │ └── ObserveDownloadedItems.kt
│ │ │ └── observers
│ │ │ └── ObserveDownloads.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── tm
│ │ │ └── alashow
│ │ │ └── datmusic
│ │ │ └── downloader
│ │ │ ├── DownloaderModule.kt
│ │ │ └── observers
│ │ │ └── ObserveDownloads.kt
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ ├── java
│ │ └── tm
│ │ │ └── alashow
│ │ │ └── datmusic
│ │ │ └── downloader
│ │ │ ├── AudioExtensions.kt
│ │ │ ├── DownloadInitializer.kt
│ │ │ ├── DownloadRetryManager.kt
│ │ │ ├── DownloaderImpl.kt
│ │ │ ├── DownloaderModule.kt
│ │ │ ├── DownloaderNotificationManager.kt
│ │ │ ├── manager
│ │ │ ├── DownloadManager.kt
│ │ │ └── FetchDownloadManager.kt
│ │ │ ├── mapper
│ │ │ └── DownloadInfoMapper.kt
│ │ │ └── observers
│ │ │ └── ObserveDownloads.kt
│ │ └── res
│ │ └── values
│ │ └── strings.xml
├── image-loading
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── image
│ │ │ ├── CoilAppInitializer.kt
│ │ │ ├── ImageLoadingPlatformComponent.kt
│ │ │ └── LoadImage.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── image
│ │ │ └── ImageLoadingPlatformComponent.kt
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── kafka
│ │ └── image
│ │ └── ImageLoadingPlatformComponent.kt
├── networking
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── kafka
│ │ └── networking
│ │ ├── ErrorMessages.kt
│ │ ├── NetworkingComponent.kt
│ │ └── SerializationPolymorphicDefaultPair.kt
├── play
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── play
│ │ │ ├── AppReviewManager.kt
│ │ │ ├── AppUpdateManager.kt
│ │ │ └── PlayStoreComponent.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── play
│ │ │ └── PlayStoreComponent.kt
│ │ └── main
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ └── com
│ │ └── kafka
│ │ └── play
│ │ ├── AppReviewManager.kt
│ │ └── PlayStoreComponent.kt
└── remote-config
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── remote
│ │ └── config
│ │ ├── RemoteConfig.kt
│ │ └── RemoteConfigExtensions.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── remote
│ │ └── config
│ │ └── RemoteConfig.kt
│ └── main
│ ├── java
│ └── com
│ │ └── kafka
│ │ └── remote
│ │ └── config
│ │ └── RemoteConfig.kt
│ └── res
│ └── xml
│ └── remote_config_defaults.xml
├── data
├── database
│ ├── .gitignore
│ ├── build.gradle.kts
│ ├── schemas
│ │ └── com.kafka.data.db.KafkaRoomDatabase
│ │ │ ├── 3.json
│ │ │ ├── 4.json
│ │ │ ├── 5.json
│ │ │ ├── 6.json
│ │ │ ├── 7.json
│ │ │ ├── 8.json
│ │ │ └── 9.json
│ └── src
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── data
│ │ │ ├── dao
│ │ │ ├── DownloadRequestsDao.kt
│ │ │ ├── EntityDao.kt
│ │ │ ├── FileDao.kt
│ │ │ ├── ItemDao.kt
│ │ │ ├── ItemDetailDao.kt
│ │ │ ├── RecentSearchDao.kt
│ │ │ └── RecentsDao.kt
│ │ │ ├── db
│ │ │ ├── AppTypeConverters.kt
│ │ │ ├── DatabaseBuilderComponnet.kt
│ │ │ └── KafkaDatabase.kt
│ │ │ ├── entities
│ │ │ ├── BaseEntity.kt
│ │ │ ├── DownloadItem.kt
│ │ │ ├── DownloadRequest.kt
│ │ │ ├── FavoriteItem.kt
│ │ │ ├── File.kt
│ │ │ ├── Homepage.kt
│ │ │ ├── Item.kt
│ │ │ ├── ItemDetail.kt
│ │ │ ├── QueueEntity.kt
│ │ │ ├── RecentItem.kt
│ │ │ ├── RecentSearch.kt
│ │ │ ├── Summary.kt
│ │ │ └── User.kt
│ │ │ └── injection
│ │ │ └── DatabaseModule.kt
│ │ ├── jvmMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── data
│ │ │ └── db
│ │ │ └── DatabaseBuilder.kt
│ │ └── main
│ │ └── java
│ │ └── com
│ │ └── kafka
│ │ └── data
│ │ └── db
│ │ └── DatabaseBuilder.kt
├── models
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ └── commonMain
│ │ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── data
│ │ └── model
│ │ ├── AppConfig.kt
│ │ ├── AppMessage.kt
│ │ ├── ArchiveQuery.kt
│ │ ├── MediaType.kt
│ │ ├── SearchFilter.kt
│ │ ├── StringListSerializer.kt
│ │ ├── UserCountryResponse.kt
│ │ ├── homepage
│ │ └── HomepageResponse.kt
│ │ └── item
│ │ ├── Doc.kt
│ │ ├── File.kt
│ │ ├── ItemDetailResponse.kt
│ │ ├── Metadata.kt
│ │ ├── Params.kt
│ │ ├── Response.kt
│ │ └── SearchResponse.kt
├── platform
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ ├── androidMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── data
│ │ │ └── platform
│ │ │ ├── DataPlatformComponent.kt
│ │ │ ├── app
│ │ │ └── AppVersionComponent.kt
│ │ │ └── device
│ │ │ └── PlatformCountryComponent.kt
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── data
│ │ │ └── platform
│ │ │ ├── AppDirectory.kt
│ │ │ ├── DataPlatformComponent.kt
│ │ │ ├── UserDataRepository.kt
│ │ │ ├── app
│ │ │ └── AppVersion.kt
│ │ │ └── device
│ │ │ ├── PlatformCountryComponent.kt
│ │ │ └── UserCountryRepository.kt
│ │ └── jvmMain
│ │ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── data
│ │ └── platform
│ │ ├── DataPlatformComponent.kt
│ │ ├── app
│ │ └── AppVersionComponent.kt
│ │ └── device
│ │ └── PlatformCountryComponent.kt
├── prefs
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ ├── androidMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── data
│ │ │ └── prefs
│ │ │ └── PreferenceStoreComponent.kt
│ │ ├── commonMain
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── data
│ │ │ └── prefs
│ │ │ ├── ItemReadCounter.kt
│ │ │ ├── PreferenceStoreComponent.kt
│ │ │ ├── PreferenceStoreExtensions.kt
│ │ │ └── PreferencesStore.kt
│ │ └── jvmMain
│ │ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── data
│ │ └── prefs
│ │ └── PreferenceStoreComponent.kt
└── repo
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── data
│ │ ├── UrlUtils.kt
│ │ ├── api
│ │ └── ArchiveService.kt
│ │ ├── feature
│ │ ├── DownloadsRepository.kt
│ │ ├── FavoritesRepository.kt
│ │ ├── RecentItemRepository.kt
│ │ ├── ai
│ │ │ ├── OpenAiRepository.kt
│ │ │ └── SummaryRepository.kt
│ │ ├── auth
│ │ │ └── AccountRepository.kt
│ │ ├── firestore
│ │ │ └── FirestoreGraph.kt
│ │ ├── homepage
│ │ │ ├── HomepageMapper.kt
│ │ │ └── HomepageRepository.kt
│ │ ├── item
│ │ │ ├── FileMapper.kt
│ │ │ ├── ItemDataSource.kt
│ │ │ ├── ItemDetailDataSource.kt
│ │ │ ├── ItemDetailMapper.kt
│ │ │ ├── ItemMapper.kt
│ │ │ ├── ItemRepository.kt
│ │ │ └── ItemWithDownload.kt
│ │ └── recommendation
│ │ │ └── RecommendationRepository.kt
│ │ └── injection
│ │ └── SerializersModule.kt
│ └── main
│ └── AndroidManifest.xml
├── desktop-app
├── .gitignore
├── build.gradle.kts
└── src
│ └── jvmMain
│ └── kotlin
│ └── com
│ └── kafka
│ └── desktop
│ ├── Main.kt
│ ├── MainScreen.kt
│ ├── WindowComponent.kt
│ └── main
│ ├── HomeNavigation.kt
│ └── HomeNavigationItems.kt
├── domain
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── domain
│ │ ├── interactors
│ │ ├── AddRecentSearch.kt
│ │ ├── GetReaderState.kt
│ │ ├── ResumeAlbum.kt
│ │ ├── SearchQueryItems.kt
│ │ ├── UpdateCurrentPage.kt
│ │ ├── UpdateFavorite.kt
│ │ ├── UpdateFeedback.kt
│ │ ├── UpdateHomepage.kt
│ │ ├── UpdateItemDetail.kt
│ │ ├── UpdateItems.kt
│ │ ├── UpdateRecentItem.kt
│ │ ├── UpdateRecommendations.kt
│ │ ├── account
│ │ │ ├── LogoutCredentialManager.kt
│ │ │ ├── LogoutUser.kt
│ │ │ ├── ResetPassword.kt
│ │ │ ├── SignInAnonymously.kt
│ │ │ ├── SignInUser.kt
│ │ │ ├── SignInWithGoogle.kt
│ │ │ └── SignUpUser.kt
│ │ ├── query
│ │ │ ├── BuildLocalQuery.kt
│ │ │ ├── BuildRemoteQuery.kt
│ │ │ └── BuildSearchQuery.kt
│ │ └── recent
│ │ │ ├── AddRecentItem.kt
│ │ │ ├── IsResumableAudio.kt
│ │ │ ├── RemoveAllRecentItems.kt
│ │ │ └── RemoveRecentItem.kt
│ │ └── observers
│ │ ├── ObserveAppMessage.kt
│ │ ├── ObserveAppUpdateConfig.kt
│ │ ├── ObserveCreatorItems.kt
│ │ ├── ObserveFiles.kt
│ │ ├── ObserveHomepage.kt
│ │ ├── ObserveItemDetail.kt
│ │ ├── ObserveQueryItems.kt
│ │ ├── ObserveRecentItems.kt
│ │ ├── ObserveRecentSearch.kt
│ │ ├── ObserveRecentTextItem.kt
│ │ ├── ObserveShareAppIndex.kt
│ │ ├── ObserveUser.kt
│ │ ├── ShouldAutoDownload.kt
│ │ ├── ShouldUseOnlineReader.kt
│ │ ├── library
│ │ ├── ObserveFavoriteStatus.kt
│ │ └── ObserveFavorites.kt
│ │ └── summary
│ │ └── ObserveSummary.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── domain
│ │ └── interactors
│ │ └── account
│ │ ├── LogoutCredentialManager.kt
│ │ └── SignInWithGoogle.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── kafka
│ └── domain
│ └── interactors
│ └── account
│ ├── LogoutCredentialManager.kt
│ └── SignInWithGoogle.kt
├── firebase.json
├── firestore.indexes.json
├── firestore.rules
├── functions
├── .eslintrc.js
├── .gitignore
├── index.js
├── metadata.js
├── package-lock.json
├── package.json
├── recommendation
│ ├── collaborative.js
│ ├── content_filtering.js
│ └── trending.js
└── utils
│ └── bigQueryUtils.js
├── gradle.properties
├── gradle
├── build-logic
│ ├── convention
│ │ ├── build.gradle.kts
│ │ └── src
│ │ │ └── main
│ │ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── gradle
│ │ │ ├── Android.kt
│ │ │ ├── AndroidApplicationConventionPlugin.kt
│ │ │ ├── AndroidApplicationLauncher.kt
│ │ │ ├── AndroidLibraryConventionPlugin.kt
│ │ │ ├── ComposeMultiplatformConventionPlugin.kt
│ │ │ ├── Kotlin.kt
│ │ │ ├── KotlinAndroidConventionPlugin.kt
│ │ │ ├── KotlinMultiplatformConventionPlugin.kt
│ │ │ ├── VersionCatalog.kt
│ │ │ └── Versions.kt
│ ├── gradle.properties
│ └── settings.gradle.kts
├── libs.versions.toml
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── maestro
├── homepage-scroll.yaml
├── item-detail.yaml
├── play-item.yaml
└── search.yaml
├── navigation
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── navigation
│ │ ├── NavigationModule.kt
│ │ ├── Navigator.kt
│ │ ├── deeplink
│ │ ├── Config.kt
│ │ └── DeepLinks.kt
│ │ └── graph
│ │ └── NavigationGraph.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── kafka
│ └── navigation
│ ├── NavigationExtensions.kt
│ └── RememberBottomSheetNavigator.kt
├── public
├── .well-known
│ └── assetlinks.json
├── 404.html
├── favicon.ico
├── images
│ ├── facebook.svg
│ ├── instagram.svg
│ └── whatsapp.svg
├── index.html
└── style.css
├── release
├── clean-secrets.sh
├── decrypt-secrets.sh
├── encrypt-secrets.sh
└── google-services.gpg
├── settings.gradle
├── spotless
├── copyright.txt
└── spotless.gradle
├── storage.rules
└── ui
├── auth
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ ├── composeResources
│ │ ├── drawable
│ │ │ └── ic_kafka_logo.xml
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── auth
│ │ ├── AuthViewModel.kt
│ │ ├── LoginScreen.kt
│ │ └── LoginWithEmail.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── res
│ ├── drawable
│ └── ic_kafka_logo.xml
│ └── values
│ └── strings.xml
├── common
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ ├── composeResources
│ │ ├── drawable
│ │ │ ├── absurd_meditation.xml
│ │ │ └── ic_kafka_logo.xml
│ │ └── values
│ │ │ └── string.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── common
│ │ ├── Clickable.kt
│ │ ├── ContextExtensions.kt
│ │ ├── LazyList.kt
│ │ ├── ObservableLoadingCounter.kt
│ │ ├── PaddingValues.kt
│ │ ├── UiMessage.kt
│ │ ├── adaptive
│ │ ├── LazyGrid.kt
│ │ └── WindowSizeClass.kt
│ │ ├── animation
│ │ ├── SharedXAxisEnterTransition.kt
│ │ └── TimedVisibility.kt
│ │ ├── extensions
│ │ └── ComposeExtensions.kt
│ │ ├── image
│ │ └── Icons.kt
│ │ ├── platform
│ │ └── CommonUiPlatformComponent.kt
│ │ ├── snackbar
│ │ ├── SnackbarManager.kt
│ │ └── UiMessage.kt
│ │ └── widgets
│ │ ├── DeferredIcon.kt
│ │ ├── IconButton.kt
│ │ ├── LocalSnackbarHostState.kt
│ │ ├── Shadow.kt
│ │ └── UiMessage.kt
│ ├── debug
│ └── res
│ │ └── values
│ │ └── string.xml
│ ├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── common
│ │ ├── ContextExtensions.kt
│ │ └── platform
│ │ └── CommonUiPlatformComponent.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── kafka
│ └── common
│ ├── ContextExtensions.kt
│ ├── platform
│ └── CommonUiPlatformComponent.kt
│ └── test
│ └── TestTags.kt
├── components
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ ├── composeResources
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── ui
│ │ └── components
│ │ ├── Labels.kt
│ │ ├── MessageBox.kt
│ │ ├── PagerTab.kt
│ │ ├── Scaffold.kt
│ │ ├── item
│ │ ├── CoverImage.kt
│ │ ├── DownloadStatusIcons.kt
│ │ ├── FeaturedItem.kt
│ │ ├── Item.kt
│ │ ├── LayoutType.kt
│ │ ├── LibraryItem.kt
│ │ ├── PersonItem.kt
│ │ ├── RowItem.kt
│ │ ├── SubjectItem.kt
│ │ ├── SummaryAnimation.kt
│ │ ├── SummaryFeedback.kt
│ │ └── SummaryMessage.kt
│ │ ├── material
│ │ ├── AlertDialog.kt
│ │ ├── Buttons.kt
│ │ ├── Dismissable.kt
│ │ ├── HazeScaffold.kt
│ │ ├── NestedScaffold.kt
│ │ ├── Pager.kt
│ │ ├── Slider.kt
│ │ ├── StaggeredFlowRow.kt
│ │ ├── TextField.kt
│ │ └── TopBar.kt
│ │ ├── placeholder
│ │ ├── Placeholder.kt
│ │ └── PlaceholderHighlight.kt
│ │ ├── progress
│ │ ├── DownloadAnimation.kt
│ │ ├── InfiniteProgressBar.kt
│ │ └── ProgressIndicator.kt
│ │ └── snackbar
│ │ ├── DismissableSnackbar.kt
│ │ └── SnackbarMessagesHost.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── ui
│ │ └── components
│ │ ├── item
│ │ ├── DownloadStatusIcons.kt
│ │ └── SummaryAnimation.kt
│ │ └── progress
│ │ └── DownloadAnimation.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── kafka
│ │ └── ui
│ │ └── components
│ │ ├── animation
│ │ └── colorFilterDynamicProperty.kt
│ │ ├── item
│ │ ├── DownloadStatusIcons.kt
│ │ └── SummaryAnimation.kt
│ │ └── progress
│ │ └── DownloadAnimation.kt
│ └── res
│ ├── raw
│ ├── book.json
│ └── purple_elephant.json
│ └── values
│ └── strings.xml
├── downloader
├── build.gradle.kts
└── src
│ ├── commonMain
│ └── kotlin
│ │ └── tm
│ │ └── alashow
│ │ └── datmusic
│ │ └── ui
│ │ └── downloader
│ │ ├── DownloaderHost.kt
│ │ └── LocalDownloader.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── tm
│ │ └── alashow
│ │ └── datmusic
│ │ └── ui
│ │ └── downloader
│ │ └── DownloaderHost.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── tm
│ │ └── alashow
│ │ └── datmusic
│ │ └── ui
│ │ └── downloader
│ │ ├── DownloaderHost.kt
│ │ └── WriteableOpenDocumentTree.kt
│ └── res
│ └── values
│ └── strings.xml
├── homepage
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ ├── composeResources
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── homepage
│ │ ├── Homepage.kt
│ │ ├── HomepageTopBar.kt
│ │ ├── HomepageViewModel.kt
│ │ ├── HomepageViewState.kt
│ │ ├── components
│ │ ├── Carousels.kt
│ │ └── RecentItems.kt
│ │ └── recent
│ │ ├── RecentItemsScreen.kt
│ │ └── RecentViewModel.kt
│ └── main
│ └── AndroidManifest.xml
├── item
└── detail
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ ├── commonMain
│ ├── composeResources
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── item
│ │ ├── PreloadImages.kt
│ │ ├── detail
│ │ ├── ItemDetail.kt
│ │ ├── ItemDetailActions.kt
│ │ ├── ItemDetailScaffold.kt
│ │ ├── ItemDetailTopBar.kt
│ │ ├── ItemDetailViewModel.kt
│ │ ├── ItemDetailViewState.kt
│ │ └── description
│ │ │ ├── DescriptionDialog.kt
│ │ │ └── ItemDescriptionLayout.kt
│ │ ├── fake
│ │ └── FakeItemData.kt
│ │ └── files
│ │ ├── ExtensionFilter.kt
│ │ ├── File.kt
│ │ ├── Files.kt
│ │ ├── FilesViewModel.kt
│ │ └── FilesViewState.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── res
│ └── values
│ └── strings.xml
├── library
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ ├── composeResources
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── library
│ │ ├── LibraryScreen.kt
│ │ ├── Tabs.kt
│ │ ├── downloads
│ │ ├── DownloadItem.kt
│ │ ├── Downloads.kt
│ │ └── DownloadsViewModel.kt
│ │ └── favorites
│ │ ├── FavoriteViewModel.kt
│ │ └── Favorites.kt
│ └── main
│ └── AndroidManifest.xml
├── profile
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ ├── composeResources
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── profile
│ │ ├── NotificationMenuItem.kt
│ │ ├── ProfileMenu.kt
│ │ ├── ProfileScreen.kt
│ │ ├── ProfileViewModel.kt
│ │ └── feedback
│ │ ├── FeedbackScreen.kt
│ │ └── FeedbackViewModel.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── profile
│ │ └── NotificationMenuItem.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── kafka
│ └── profile
│ └── NotificationMenuItem.kt
├── reader
├── epub
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ ├── commonMain
│ │ ├── composeResources
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── reader
│ │ │ └── epub
│ │ │ ├── EpubReader.kt
│ │ │ ├── EpubReaderViewModel.kt
│ │ │ ├── domain
│ │ │ └── ParseEbook.kt
│ │ │ ├── models
│ │ │ ├── EpubBook.kt
│ │ │ ├── EpubChapter.kt
│ │ │ └── EpubImage.kt
│ │ │ ├── parser
│ │ │ ├── BookTextMapper.kt
│ │ │ ├── EpubParser.kt
│ │ │ ├── EpubParserException.kt
│ │ │ ├── EpubUtils.kt
│ │ │ └── EpubXMLFileParser.kt
│ │ │ ├── settings
│ │ │ └── ReaderSettings.kt
│ │ │ └── ui
│ │ │ └── SettingsSheet.kt
│ │ └── main
│ │ └── AndroidManifest.xml
├── online
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ │ ├── commonMain
│ │ ├── composeResources
│ │ │ └── values
│ │ │ │ └── strings.xml
│ │ └── kotlin
│ │ │ └── com
│ │ │ └── kafka
│ │ │ └── reader
│ │ │ └── online
│ │ │ ├── OnlineReader.kt
│ │ │ └── OnlineReaderViewModel.kt
│ │ └── main
│ │ └── AndroidManifest.xml
└── pdf
│ ├── .gitignore
│ ├── build.gradle.kts
│ └── src
│ ├── commonMain
│ ├── composeResources
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── reader
│ │ ├── DownloadProgress.kt
│ │ ├── ReaderScreen.kt
│ │ ├── ReaderViewModel.kt
│ │ └── pdf
│ │ ├── PdfReader.kt
│ │ ├── PdfReaderViewModel.kt
│ │ └── PdfViewer.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── reader
│ │ └── pdf
│ │ └── PdfViewer.jvm.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── kafka
│ └── reader
│ └── pdf
│ └── PdfViewer.android.kt
├── search
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ ├── composeResources
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── search
│ │ ├── RecentSearches.kt
│ │ ├── SearchScreen.kt
│ │ ├── SearchViewModel.kt
│ │ ├── SearchViewState.kt
│ │ └── widget
│ │ ├── SearchFilter.kt
│ │ ├── SearchWidget.kt
│ │ └── SpeechIcon.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── search
│ │ └── widget
│ │ └── SpeechIcon.kt
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── kafka
│ │ └── search
│ │ └── widget
│ │ └── SpeechIcon.kt
│ └── res
│ └── values
│ └── strings.xml
├── shared
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ ├── composeResources
│ │ └── values
│ │ │ └── strings.xml
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── shared
│ │ ├── SharedApplicationComponent.kt
│ │ ├── injection
│ │ ├── InitializersComponent.kt
│ │ └── PlayerComponent.kt
│ │ ├── main
│ │ └── initializer
│ │ │ ├── AppInitializers.kt
│ │ │ ├── AudioProgressInitializer.kt
│ │ │ └── RemoteConfigLogger.kt
│ │ └── playback
│ │ ├── PlaybackViewModel.kt
│ │ ├── PlayerAudioDataSource.kt
│ │ └── PlayerLogger.kt
│ ├── jvmMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── shared
│ │ ├── DesktopApplicationComponent.kt
│ │ ├── SharedPlatformApplicationComponent.kt
│ │ ├── injection
│ │ └── InitializersPlatformComponent.kt
│ │ └── main
│ │ └── initializer
│ │ └── Initializers.kt
│ └── main
│ ├── AndroidManifest.xml
│ └── java
│ └── com
│ └── kafka
│ └── shared
│ ├── SharedPlatformApplicationComponent.kt
│ ├── injection
│ └── InitializersPlatformComponent.kt
│ └── main
│ └── initializer
│ └── Initializers.kt
├── summary
├── .gitignore
├── build.gradle.kts
└── src
│ ├── commonMain
│ └── kotlin
│ │ └── com
│ │ └── kafka
│ │ └── summary
│ │ ├── SummaryScreen.kt
│ │ ├── SummaryViewModel.kt
│ │ └── summaryMarkdownTypography.kt
│ └── main
│ └── AndroidManifest.xml
├── theme
├── .gitignore
├── build.gradle.kts
└── src
│ ├── androidMain
│ └── kotlin
│ │ └── ui
│ │ └── common
│ │ └── theme
│ │ └── theme
│ │ └── Platform.kt
│ ├── commonMain
│ ├── composeResources
│ │ └── font
│ │ │ ├── inter_black.ttf
│ │ │ ├── inter_bold.ttf
│ │ │ ├── inter_light.ttf
│ │ │ ├── inter_medium.ttf
│ │ │ ├── inter_regular.ttf
│ │ │ └── inter_semibold.ttf
│ └── kotlin
│ │ └── ui
│ │ └── common
│ │ └── theme
│ │ └── theme
│ │ ├── Color.kt
│ │ ├── Dimens.kt
│ │ ├── Platform.kt
│ │ ├── Theme.kt
│ │ └── Type.kt
│ └── jvmMain
│ └── kotlin
│ └── ui
│ └── common
│ └── theme
│ └── theme
│ └── Platform.kt
└── webview
├── .gitignore
├── build.gradle.kts
└── src
├── commonMain
└── kotlin
│ └── com
│ └── kafka
│ └── webview
│ └── WebView.kt
└── main
└── AndroidManifest.xml
/.firebaserc:
--------------------------------------------------------------------------------
1 | {
2 | "projects": {
3 | "default": "kafka-books"
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/.gitattributes:
--------------------------------------------------------------------------------
1 | public/** linguist-vendored
--------------------------------------------------------------------------------
/Privacy Policy.md:
--------------------------------------------------------------------------------
1 | We use READ_PHONE_STATE permission
2 |
--------------------------------------------------------------------------------
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/app/release/baselineProfiles/0/app-release.dm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/release/baselineProfiles/0/app-release.dm
--------------------------------------------------------------------------------
/app/release/baselineProfiles/1/app-release.dm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/release/baselineProfiles/1/app-release.dm
--------------------------------------------------------------------------------
/app/release/output-metadata.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": 3,
3 | "artifactType": {
4 | "type": "APK",
5 | "kind": "Directory"
6 | },
7 | "applicationId": "com.kafka.user",
8 | "variantName": "release",
9 | "elements": [
10 | {
11 | "type": "SINGLE",
12 | "filters": [],
13 | "attributes": [],
14 | "versionCode": 72,
15 | "versionName": "0.32.0",
16 | "outputFile": "app-release.apk"
17 | }
18 | ],
19 | "elementType": "File",
20 | "baselineProfiles": [
21 | {
22 | "minApi": 28,
23 | "maxApi": 30,
24 | "baselineProfiles": [
25 | "baselineProfiles/1/app-release.dm"
26 | ]
27 | },
28 | {
29 | "minApi": 31,
30 | "maxApi": 2147483647,
31 | "baselineProfiles": [
32 | "baselineProfiles/0/app-release.dm"
33 | ]
34 | }
35 | ],
36 | "minSdkVersionForDexing": 24
37 | }
--------------------------------------------------------------------------------
/app/src/debug/google-services.json:
--------------------------------------------------------------------------------
1 | {
2 | "project_info": {
3 | "project_number": "577294117898",
4 | "firebase_url": "https://kafka-books.firebaseio.com",
5 | "project_id": "kafka-books",
6 | "storage_bucket": "kafka-books.appspot.com"
7 | },
8 | "client": [
9 | {
10 | "client_info": {
11 | "mobilesdk_app_id": "1:577294117898:android:1da10a3ca24c29a60d83f3",
12 | "android_client_info": {
13 | "package_name": "com.kafka.user.debug"
14 | }
15 | },
16 | "oauth_client": [
17 | {
18 | "client_id": "577294117898-btog9i7alqueftl59cosgqaqha0bcsrl.apps.googleusercontent.com",
19 | "client_type": 1,
20 | "android_info": {
21 | "package_name": "com.kafka.user.debug",
22 | "certificate_hash": "95c9e896555e3fedb2fc7fd86c4966e5e732335f"
23 | }
24 | }
25 | ],
26 | "api_key": [
27 | {
28 | "current_key": "AIzaSyD1lV11eH3UdewwBDfNX_AmGTCN2LU-9ko"
29 | }
30 | ]
31 | }
32 | ],
33 | "configuration_version": "1"
34 | }
--------------------------------------------------------------------------------
/app/src/debug/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/debug/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
15 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-hdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-mdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_background.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_background.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/debug/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/debug/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #5A5A5A
4 |
--------------------------------------------------------------------------------
/app/src/debug/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Kafka Debug
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/kafka/user/fcm/FcmTokenGenerator.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.user.fcm
2 |
3 | import com.google.android.gms.tasks.OnCompleteListener
4 | import com.google.firebase.messaging.FirebaseMessaging
5 | import com.kafka.base.debug
6 |
7 | object FcmTokenGenerator {
8 | fun logToken() {
9 | FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task ->
10 | if (!task.isSuccessful) {
11 | debug { "Fetching FCM registration token failed ${task.exception}" }
12 | return@OnCompleteListener
13 | }
14 |
15 | val token = task.result
16 | debug { "FCM registration token: $token" }
17 | })
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kafka/user/injection/AndroidActivityComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.user.injection
2 |
3 | import android.app.Activity
4 | import com.kafka.remote.config.RemoteConfig
5 | import com.kafka.user.home.MainScreen
6 | import me.tatarka.inject.annotations.Component
7 | import me.tatarka.inject.annotations.Provides
8 | import com.kafka.base.ActivityScope
9 |
10 | @ActivityScope
11 | @Component
12 | abstract class AndroidActivityComponent(
13 | @get:Provides val activity: Activity,
14 | @Component val applicationComponent: AndroidApplicationComponent,
15 | ) {
16 | abstract val mainScreen: MainScreen
17 | abstract val remoteConfig: RemoteConfig
18 |
19 | companion object
20 | }
21 |
--------------------------------------------------------------------------------
/app/src/main/java/com/kafka/user/injection/AppComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.user.injection
2 |
3 | import androidx.lifecycle.ProcessLifecycleOwner
4 | import androidx.lifecycle.lifecycleScope
5 | import com.kafka.base.ApplicationScope
6 | import com.kafka.base.ProcessLifetime
7 | import com.kafka.shared.SharedApplicationComponent
8 | import kotlinx.coroutines.CoroutineScope
9 | import me.tatarka.inject.annotations.Component
10 | import me.tatarka.inject.annotations.Provides
11 |
12 | @Component
13 | @ApplicationScope
14 | interface AppComponent : SharedApplicationComponent {
15 |
16 | @Provides
17 | @ProcessLifetime
18 | fun provideLongLifetimeScope(): CoroutineScope {
19 | return ProcessLifecycleOwner.get().lifecycleScope
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_kafka_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
16 |
19 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/res/values-night/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
17 |
18 |
19 |
22 |
23 |
--------------------------------------------------------------------------------
/app/src/main/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Kafka
3 | Cancel
4 |
5 | Play
6 | Library
7 | Search
8 | Home
9 |
10 | Update Available
11 | This version is outdated. Please update the app to continue using it.
12 | Update
13 | App update is available
14 |
15 |
--------------------------------------------------------------------------------
/app/src/main/res/values/themes.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/app/src/rc/generated/baselineProfiles/startup-prof.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/generated/baselineProfiles/startup-prof.txt
--------------------------------------------------------------------------------
/app/src/rc/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/rc/res/drawable/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
6 |
10 |
15 |
18 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-hdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-hdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-hdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-hdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-hdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-hdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-mdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-mdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-mdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-mdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-mdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-mdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-xhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-xhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-xhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-xhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-xhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-xhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-xxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-xxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-xxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-xxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-xxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-xxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-xxxhdpi/ic_launcher.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-xxxhdpi/ic_launcher.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-xxxhdpi/ic_launcher_foreground.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-xxxhdpi/ic_launcher_foreground.webp
--------------------------------------------------------------------------------
/app/src/rc/res/mipmap-xxxhdpi/ic_launcher_round.webp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/app/src/rc/res/mipmap-xxxhdpi/ic_launcher_round.webp
--------------------------------------------------------------------------------
/app/src/rc/res/values/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #5D64F8
4 |
--------------------------------------------------------------------------------
/app/src/rc/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Kafka Rc
4 |
5 |
--------------------------------------------------------------------------------
/base/annotations/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/base/annotations/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("java-library")
3 | alias(libs.plugins.kotlin.jvm)
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_17
8 | targetCompatibility = JavaVersion.VERSION_17
9 | }
10 |
11 | dependencies {
12 | api(libs.kotlininject.runtime)
13 | }
14 |
--------------------------------------------------------------------------------
/base/annotations/src/main/java/com/kafka/base/Annotations.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("ktlint:standard:filename")
2 |
3 | package com.kafka.base
4 |
5 | import me.tatarka.inject.annotations.Qualifier
6 | import me.tatarka.inject.annotations.Scope
7 |
8 | @Retention(AnnotationRetention.RUNTIME)
9 | @Qualifier
10 | @MustBeDocumented
11 | annotation class ProcessLifetime
12 |
13 | @Scope
14 | annotation class ApplicationScope
15 |
16 | @Scope
17 | annotation class ActivityScope
18 |
19 | @Qualifier
20 | @Target(
21 | AnnotationTarget.PROPERTY_GETTER,
22 | AnnotationTarget.FUNCTION,
23 | AnnotationTarget.VALUE_PARAMETER,
24 | AnnotationTarget.TYPE
25 | )
26 | annotation class Named(val value: String)
27 |
--------------------------------------------------------------------------------
/base/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/base/domain/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.kafka.kotlin.multiplatform")
3 | alias(libs.plugins.ksp)
4 | }
5 |
6 | kotlin {
7 | sourceSets {
8 | val commonMain by getting {
9 | dependencies {
10 | api(projects.base.annotations)
11 |
12 | api(libs.kotlin.coroutines.swing)
13 |
14 | api(libs.kinject)
15 | api(libs.kotlin.coroutines.core)
16 | api(libs.kotlin.stdlib)
17 | api(libs.kermit)
18 | api(libs.kotlin.immutable)
19 | api(libs.kotlinx.atomicfu)
20 | api(libs.kotlininject.runtime)
21 | }
22 | }
23 |
24 | val jvmMain by getting
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/base/domain/src/commonMain/kotlin/com/kafka/base/AppInitializer.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.base
2 |
3 | interface AppInitializer {
4 | fun init()
5 | }
6 |
--------------------------------------------------------------------------------
/base/domain/src/commonMain/kotlin/com/kafka/base/CoroutineDispatchers.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.base
2 |
3 | import kotlinx.coroutines.CoroutineDispatcher
4 |
5 | data class CoroutineDispatchers(
6 | val io: CoroutineDispatcher,
7 | val computation: CoroutineDispatcher,
8 | val main: CoroutineDispatcher,
9 | )
10 |
--------------------------------------------------------------------------------
/base/domain/src/commonMain/kotlin/com/kafka/base/SecretsProvider.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.base
2 |
3 | interface SecretsProvider {
4 | val googleServerClientId: String?
5 | val openAiApiKey: String?
6 | }
7 |
--------------------------------------------------------------------------------
/base/domain/src/commonMain/kotlin/com/kafka/base/domain/InvokeStatus.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.base.domain
2 |
3 | sealed class InvokeStatus
4 | data object InvokeStarted : InvokeStatus()
5 | data object InvokeSuccess : InvokeStatus()
6 | data class InvokeError(val throwable: Throwable) : InvokeStatus()
7 |
--------------------------------------------------------------------------------
/base/domain/src/commonMain/kotlin/com/kafka/base/extensions/CoroutineExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.base.extensions
2 |
3 | import kotlinx.coroutines.CoroutineScope
4 | import kotlinx.coroutines.async
5 | import kotlinx.coroutines.awaitAll
6 | import kotlinx.coroutines.coroutineScope
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.SharingStarted
9 | import kotlinx.coroutines.flow.stateIn
10 |
11 | /**
12 | * Alias to stateIn with defaults
13 | */
14 | fun Flow.stateInDefault(
15 | scope: CoroutineScope,
16 | initialValue: T,
17 | started: SharingStarted = SharingStarted.WhileSubscribed(5_000),
18 | ) = stateIn(scope, started, initialValue)
19 |
20 | suspend fun List.mapAsync(
21 | mapper: suspend (T) -> R,
22 | ): List = coroutineScope { map { async { mapper(it) } }.awaitAll() }
23 |
--------------------------------------------------------------------------------
/baselineprofile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/baselineprofile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/core/ads/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/ads/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | alias(libs.plugins.android.library)
3 | alias(libs.plugins.cacheFixPlugin)
4 | alias(libs.plugins.kotlin.android)
5 | alias(libs.plugins.kotlin.compose.compiler)
6 | alias(libs.plugins.kotlin.kapt)
7 | }
8 |
9 | kapt {
10 | correctErrorTypes = true
11 | useBuildCache = true
12 | }
13 |
14 | android {
15 | namespace 'com.kafka.core.ads'
16 |
17 | viewBinding {
18 | enabled = true
19 | }
20 | }
21 |
22 | dependencies {
23 | implementation projects.base.domain
24 | implementation projects.core.analytics
25 | implementation projects.ui.common
26 | implementation projects.ui.components
27 |
28 | implementation libs.compose.ui.viewbinding
29 |
30 | implementation libs.google.admob
31 | }
32 |
--------------------------------------------------------------------------------
/core/ads/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/core/ads/src/main/java/org/kafka/ads/admob/NativeAdInitializer.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ads.admob
2 |
3 | import android.app.Application
4 | import android.util.Log
5 | import com.google.android.gms.ads.MobileAds
6 | import com.kafka.base.AppInitializer
7 | import javax.inject.Inject
8 | import javax.inject.Singleton
9 |
10 | @Singleton
11 | class NativeAdInitializer @Inject constructor(
12 | private val application: Application,
13 | ) : AppInitializer {
14 | override fun init() {
15 | MobileAds.initialize(application) { initializationStatus ->
16 | val statusMap = initializationStatus.adapterStatusMap
17 | for (adapterClass in statusMap.keys) {
18 | val status = statusMap[adapterClass]
19 | Log.d(
20 | "MyApp", String.format(
21 | "Adapter name: %s, Description: %s, Latency: %d",
22 | adapterClass, status!!.description, status.latency
23 | )
24 | )
25 | }
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/core/ads/src/main/res/drawable/ad_attribution_selector.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | -
4 |
5 |
6 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/core/analytics/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/analytics/src/commonMain/kotlin/com/kafka/analytics/AnalyticsPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.analytics
2 |
3 | expect interface AnalyticsPlatformComponent
4 |
--------------------------------------------------------------------------------
/core/analytics/src/commonMain/kotlin/com/kafka/analytics/logger/Analytics.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.analytics.logger
2 |
3 | import com.kafka.analytics.EventRepository
4 |
5 | interface Analytics {
6 | fun log(eventInfo: EventInfo)
7 | fun log(eventInfo: EventRepository.() -> EventInfo)
8 | fun logScreenView(label: String, route: String?, arguments: Any?)
9 | }
10 |
11 | typealias EventInfo = Pair>
12 |
--------------------------------------------------------------------------------
/core/analytics/src/jvmMain/kotlin/com/kafka/analytics/AnalyticsPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.analytics
2 |
3 | import com.kafka.analytics.logger.Analytics
4 | import com.kafka.analytics.logger.EventInfo
5 | import me.tatarka.inject.annotations.Provides
6 |
7 | actual interface AnalyticsPlatformComponent {
8 | @Provides
9 | fun provideFirebaseAnalytics(): Analytics = object : Analytics {
10 | override fun log(eventInfo: EventInfo) {
11 | // todo: kmp implement
12 | }
13 |
14 | override fun log(eventInfo: EventRepository.() -> EventInfo) {
15 | // todo: kmp implement
16 | }
17 |
18 | override fun logScreenView(label: String, route: String?, arguments: Any?) {
19 | // todo: kmp implement
20 | }
21 |
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/analytics/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/core/analytics/src/main/java/com/kafka/analytics/AnalyticsPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.analytics
2 |
3 | import me.tatarka.inject.annotations.Provides
4 | import com.kafka.analytics.logger.Analytics
5 | import com.kafka.analytics.logger.AnalyticsImpl
6 | import com.kafka.base.ApplicationScope
7 |
8 | actual interface AnalyticsPlatformComponent {
9 | @ApplicationScope
10 | @Provides
11 | fun provideFirebaseAnalytics(bind: AnalyticsImpl): Analytics = bind
12 | }
13 |
--------------------------------------------------------------------------------
/core/downloader/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("com.kafka.kotlin.multiplatform")
4 | alias(libs.plugins.kotlin.serialization)
5 | }
6 |
7 | kotlin {
8 | sourceSets {
9 | val commonMain by getting {
10 | dependencies {
11 | implementation(projects.base.domain)
12 | implementation(projects.core.analytics)
13 | implementation(projects.core.remoteConfig)
14 | implementation(projects.data.repo)
15 | implementation(projects.ui.common)
16 | }
17 | }
18 |
19 | val jvmCommon by creating {
20 | dependsOn(commonMain)
21 | }
22 |
23 | val jvmMain by getting {
24 | dependsOn(jvmCommon)
25 | }
26 |
27 | val androidMain by getting {
28 | dependsOn(jvmCommon)
29 |
30 | dependencies {
31 | implementation(libs.androidx.documentfile)
32 | implementation(libs.fetch)
33 | implementation(libs.kotlininject.runtime)
34 | implementation(libs.threeTenAbp)
35 | }
36 | }
37 | }
38 | }
39 |
40 | android {
41 | namespace = "com.kafka.downloader"
42 | }
43 |
--------------------------------------------------------------------------------
/core/downloader/src/commonMain/kotlin/tm/alashow/datmusic/downloader/DownloadItem.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2021, Alashov Berkeli
3 | * All rights reserved.
4 | */
5 | package tm.alashow.datmusic.downloader
6 |
7 | import com.kafka.data.entities.DownloadRequest
8 | import com.kafka.data.feature.item.DownloadInfo
9 |
10 | sealed class DownloadItem(
11 | open val downloadRequest: DownloadRequest,
12 | open val downloadInfo: DownloadInfo?,
13 | )
14 |
15 | data class FileDownloadItem(
16 | override val downloadRequest: DownloadRequest,
17 | override val downloadInfo: DownloadInfo,
18 | ) : DownloadItem(downloadRequest, downloadInfo) {
19 | companion object {
20 | fun from(downloadRequest: DownloadRequest, downloadInfo: DownloadInfo) =
21 | FileDownloadItem(downloadRequest, downloadInfo)
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/core/downloader/src/commonMain/kotlin/tm/alashow/datmusic/downloader/DownloadMessage.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2021, Alashov Berkeli
3 | * All rights reserved.
4 | */
5 | package tm.alashow.datmusic.downloader
6 |
7 | import com.kafka.common.snackbar.UiMessage
8 |
9 | sealed class DownloadMessage(open val value: T) {
10 | data class Plain(override val value: String) : DownloadMessage(value)
11 | data class Error(override val value: Throwable) : DownloadMessage(value)
12 | }
13 |
14 | fun DownloadMessage.toUiMessage(): UiMessage = when (this) {
15 | is DownloadMessage.Plain -> UiMessage.Plain(this.value)
16 | is DownloadMessage.Error -> UiMessage.Error(this.value)
17 | }
18 |
19 | interface UiMessageConvertable {
20 | fun toUiMessage(): DownloadMessage<*>
21 | }
22 |
--------------------------------------------------------------------------------
/core/downloader/src/commonMain/kotlin/tm/alashow/datmusic/downloader/DownloaderEvent.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2021, Alashov Berkeli
3 | * All rights reserved.
4 | */
5 | package tm.alashow.datmusic.downloader
6 |
7 | import tm.alashow.datmusic.downloader.DownloaderEvent.ChooseDownloadsLocation.message
8 |
9 | sealed class DownloaderEvent : UiMessageConvertable {
10 | data object ChooseDownloadsLocation : DownloaderEvent() {
11 | val message = DownloadMessage.Plain("Downloads location not selected yet")
12 | }
13 |
14 | data class DownloaderFetchError(val error: Throwable) : DownloaderEvent()
15 |
16 | override fun toUiMessage() = when (this) {
17 | is ChooseDownloadsLocation -> message
18 | is DownloaderFetchError -> DownloadMessage.Error(this.error)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/core/downloader/src/commonMain/kotlin/tm/alashow/datmusic/downloader/DownloaderMessages.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2021, Alashov Berkeli
3 | * All rights reserved.
4 | */
5 | @file:Suppress("FunctionName")
6 |
7 | package tm.alashow.datmusic.downloader
8 |
9 | import com.kafka.data.feature.item.DownloadStatus
10 |
11 | val DownloadsUnknownError = DownloadMessage.Plain("Unknown error")
12 | val DownloadsFolderNotFound =
13 | DownloadMessage.Plain("Selected downloads folder not found")
14 | val AudioDownloadErrorFileCreate =
15 | DownloadMessage.Plain("Couldn\\'t create file in downloads location")
16 | val AudioDownloadErrorInvalidUrl =
17 | DownloadMessage.Plain("Download has invalid url")
18 |
19 | val AudioDownloadResumedExisting =
20 | DownloadMessage.Plain("Resuming existing download")
21 | val AudioDownloadAlreadyQueued =
22 | DownloadMessage.Plain("Item already queued for download")
23 |
24 | fun AudioDownloadExistingUnknownStatus(status: DownloadStatus) =
25 | DownloadMessage.Plain("Download request exists but has unhandled status: $status")
26 |
--------------------------------------------------------------------------------
/core/downloader/src/commonMain/kotlin/tm/alashow/datmusic/downloader/DownloaderModule.kt:
--------------------------------------------------------------------------------
1 | package tm.alashow.datmusic.downloader
2 |
3 | expect interface DownloaderModule
4 |
--------------------------------------------------------------------------------
/core/downloader/src/commonMain/kotlin/tm/alashow/datmusic/downloader/interactors/ObserveDownloadByFileId.kt:
--------------------------------------------------------------------------------
1 | package tm.alashow.datmusic.downloader.interactors
2 |
3 | import com.kafka.data.feature.item.ItemWithDownload
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.flowOn
6 | import kotlinx.coroutines.flow.map
7 | import com.kafka.base.CoroutineDispatchers
8 | import com.kafka.base.domain.SubjectInteractor
9 | import javax.inject.Inject
10 |
11 | class ObserveDownloadByFileId @Inject constructor(
12 | private val dispatchers: CoroutineDispatchers,
13 | private val observeDownloadedFiles: ObserveDownloadedFiles,
14 | ) : SubjectInteractor() {
15 |
16 | @Suppress("PARAMETER_NAME_CHANGED_ON_OVERRIDE")
17 | override fun createObservable(fileId: String): Flow {
18 | return observeDownloadedFiles.createObservable(Unit).map { downloadItems ->
19 | downloadItems.firstOrNull { it.file.fileId == fileId }
20 | }.flowOn(dispatchers.io)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/core/downloader/src/commonMain/kotlin/tm/alashow/datmusic/downloader/interactors/ObserveDownloadedItems.kt:
--------------------------------------------------------------------------------
1 | package tm.alashow.datmusic.downloader.interactors
2 |
3 | import com.kafka.data.feature.item.ItemWithDownload
4 | import kotlinx.coroutines.flow.Flow
5 | import kotlinx.coroutines.flow.flowOn
6 | import kotlinx.coroutines.flow.map
7 | import com.kafka.base.CoroutineDispatchers
8 | import com.kafka.base.domain.SubjectInteractor
9 | import javax.inject.Inject
10 |
11 | class ObserveDownloadedItems @Inject constructor(
12 | private val dispatchers: CoroutineDispatchers,
13 | private val observeDownloadedFiles: ObserveDownloadedFiles,
14 | ) : SubjectInteractor>() {
15 |
16 | override fun createObservable(params: Unit): Flow> {
17 | return observeDownloadedFiles.createObservable(Unit)
18 | .map { it.distinctBy { it.item.itemId } }
19 | .flowOn(dispatchers.io)
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/core/downloader/src/commonMain/kotlin/tm/alashow/datmusic/downloader/observers/ObserveDownloads.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2021, Alashov Berkeli
3 | * All rights reserved.
4 | */
5 | package tm.alashow.datmusic.downloader.observers
6 |
7 | import kotlinx.coroutines.flow.Flow
8 | import tm.alashow.datmusic.downloader.FileDownloadItem
9 |
10 | expect class ObserveDownloads {
11 | fun execute(): Flow
12 | }
13 |
14 | data class DownloadItems(val files: List = emptyList())
15 |
--------------------------------------------------------------------------------
/core/downloader/src/jvmMain/kotlin/tm/alashow/datmusic/downloader/observers/ObserveDownloads.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2021, Alashov Berkeli
3 | * All rights reserved.
4 | */
5 | package tm.alashow.datmusic.downloader.observers
6 |
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.flowOf
9 | import javax.inject.Inject
10 |
11 | actual class ObserveDownloads @Inject constructor() {
12 | actual fun execute(): Flow {
13 | return flowOf(DownloadItems())
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/core/downloader/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/image-loading/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/image-loading/src/commonMain/kotlin/com/kafka/image/ImageLoadingPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.image
2 |
3 | expect interface ImageLoadingPlatformComponent
4 |
--------------------------------------------------------------------------------
/core/image-loading/src/commonMain/kotlin/com/kafka/image/LoadImage.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.image
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.graphics.ColorFilter
7 | import androidx.compose.ui.graphics.painter.Painter
8 | import androidx.compose.ui.layout.ContentScale
9 | import coil3.compose.AsyncImage
10 | import coil3.compose.LocalPlatformContext
11 | import coil3.request.ImageRequest
12 | import coil3.request.crossfade
13 |
14 | @Composable
15 | fun LoadImage(
16 | data: Any?,
17 | modifier: Modifier = Modifier,
18 | contentScale: ContentScale = ContentScale.Crop,
19 | tint: Color? = null,
20 | painter: Painter? = null
21 | ) {
22 | AsyncImage(
23 | model = ImageRequest.Builder(LocalPlatformContext.current)
24 | .data(data)
25 | .crossfade(true)
26 | .build(),
27 | modifier = modifier,
28 | contentScale = contentScale,
29 | contentDescription = null,
30 | placeholder = painter,
31 | colorFilter = tint?.let { ColorFilter.tint(it) },
32 | )
33 | }
34 |
--------------------------------------------------------------------------------
/core/image-loading/src/jvmMain/kotlin/com/kafka/image/ImageLoadingPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.image
2 |
3 | import coil3.PlatformContext
4 | import me.tatarka.inject.annotations.Provides
5 |
6 | actual interface ImageLoadingPlatformComponent {
7 | @Provides
8 | fun providePlatformContext(): PlatformContext = PlatformContext.INSTANCE
9 | }
10 |
--------------------------------------------------------------------------------
/core/image-loading/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/core/image-loading/src/main/java/com/kafka/image/ImageLoadingPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.image
2 |
3 | import android.app.Application
4 | import coil3.PlatformContext
5 | import me.tatarka.inject.annotations.Provides
6 |
7 | actual interface ImageLoadingPlatformComponent {
8 |
9 | @Provides
10 | fun providePlatformContext(application: Application): PlatformContext = application
11 | }
12 |
--------------------------------------------------------------------------------
/core/networking/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/networking/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("java-library")
3 | alias(libs.plugins.kotlin.jvm)
4 | }
5 |
6 | java {
7 | sourceCompatibility = JavaVersion.VERSION_17
8 | targetCompatibility = JavaVersion.VERSION_17
9 | }
10 |
11 | dependencies {
12 | implementation(projects.base.domain)
13 |
14 | implementation(libs.kotlininject.runtime)
15 | implementation(libs.ktor.client.core)
16 | implementation(libs.ktor.client.contentnegotiation)
17 | implementation(libs.ktor.client.java)
18 | implementation(libs.ktor.client.logging)
19 | implementation(libs.ktor.serialization)
20 | }
21 |
--------------------------------------------------------------------------------
/core/networking/src/main/java/com/kafka/networking/SerializationPolymorphicDefaultPair.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.networking
2 |
3 | import kotlin.reflect.KClass
4 |
5 | data class SerializationPolymorphicDefaultPair(
6 | val base: KClass,
7 | val default: KClass,
8 | )
9 |
--------------------------------------------------------------------------------
/core/play/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/play/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("com.kafka.kotlin.multiplatform")
4 | }
5 |
6 | kotlin {
7 | sourceSets {
8 | val commonMain by getting {
9 | dependencies {
10 | implementation(projects.base.domain)
11 | implementation(projects.core.analytics)
12 | }
13 | }
14 |
15 | val jvmCommon by creating {
16 | dependsOn(commonMain)
17 | }
18 |
19 | val jvmMain by getting {
20 | dependsOn(jvmCommon)
21 | }
22 |
23 | val androidMain by getting {
24 | dependsOn(jvmCommon)
25 |
26 | dependencies {
27 | implementation(libs.google.review)
28 | }
29 | }
30 | }
31 | }
32 |
33 | android {
34 | namespace = "com.kafka.core.play"
35 | }
36 |
--------------------------------------------------------------------------------
/core/play/src/commonMain/kotlin/com/kafka/play/AppReviewManager.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.play
2 |
3 | interface AppReviewManager {
4 | fun showReviewDialog(activity: Any?)
5 | }
6 |
--------------------------------------------------------------------------------
/core/play/src/commonMain/kotlin/com/kafka/play/AppUpdateManager.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.play
2 |
3 | interface AppUpdateManager {
4 | fun requestAppUpdate()
5 | }
6 |
--------------------------------------------------------------------------------
/core/play/src/commonMain/kotlin/com/kafka/play/PlayStoreComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.play
2 |
3 | expect interface PlayStoreComponent
4 |
--------------------------------------------------------------------------------
/core/play/src/jvmMain/kotlin/com/kafka/play/PlayStoreComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.play
2 |
3 | import me.tatarka.inject.annotations.Provides
4 | import com.kafka.base.ApplicationScope
5 | import com.kafka.play.AppReviewManager
6 |
7 | actual interface PlayStoreComponent {
8 | @ApplicationScope
9 | @Provides
10 | fun provideAppReviewManager(): AppReviewManager = object : AppReviewManager {
11 | override fun showReviewDialog(activity: Any?) {
12 | // todo: kmp implement
13 | }
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/core/play/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/core/play/src/main/java/com/kafka/play/AppReviewManager.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.play
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import com.google.android.play.core.review.ReviewManagerFactory
6 | import com.kafka.analytics.logger.Analytics
7 | import com.kafka.base.ApplicationScope
8 | import com.kafka.base.errorLog
9 | import com.kafka.play.AppReviewManager
10 | import javax.inject.Inject
11 |
12 | @ApplicationScope
13 | class AppReviewManagerImpl @Inject constructor(
14 | context: Application,
15 | private val analytics: Analytics,
16 | ) : AppReviewManager {
17 | private val manager = ReviewManagerFactory.create(context)
18 |
19 | override fun showReviewDialog(activity: Any?) {
20 | manager.requestReviewFlow().addOnCompleteListener { task ->
21 | if (task.isSuccessful) {
22 | analytics.log { showAppReviewDialog() }
23 | if (activity is Activity) {
24 | manager.launchReviewFlow(activity, task.result)
25 | }
26 | } else {
27 | errorLog(task.exception) { "Error while generating review info" }
28 | }
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/core/play/src/main/java/com/kafka/play/PlayStoreComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.play
2 |
3 | import me.tatarka.inject.annotations.Provides
4 | import com.kafka.base.ApplicationScope
5 | import com.kafka.play.AppReviewManager
6 |
7 | actual interface PlayStoreComponent {
8 | @ApplicationScope
9 | @Provides
10 | fun provideAppReviewManager(bind: AppReviewManagerImpl): AppReviewManager = bind
11 | }
12 |
--------------------------------------------------------------------------------
/core/remote-config/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/core/remote-config/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("com.kafka.kotlin.multiplatform")
4 | alias(libs.plugins.kotlin.serialization)
5 | }
6 |
7 | kotlin {
8 | sourceSets {
9 | val commonMain by getting {
10 | dependencies {
11 | implementation(projects.base.domain)
12 | implementation(libs.kotlin.serialization)
13 | }
14 | }
15 |
16 | val jvmCommon by creating {
17 | dependsOn(commonMain)
18 | }
19 |
20 | val jvmMain by getting {
21 | dependsOn(jvmCommon)
22 | }
23 |
24 | val androidMain by getting {
25 | dependsOn(jvmCommon)
26 |
27 | dependencies {
28 | implementation(project.dependencies.platform(libs.google.bom))
29 | implementation(libs.google.remoteConfig)
30 | }
31 | }
32 | }
33 | }
34 |
35 | android {
36 | namespace = "com.kafka.remote.config"
37 | }
38 |
--------------------------------------------------------------------------------
/core/remote-config/src/commonMain/kotlin/com/kafka/remote/config/RemoteConfig.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.remote.config
2 |
3 | expect class RemoteConfig {
4 | fun get(key: String): String
5 | fun getBoolean(key: String): Boolean
6 | fun getLong(key: String): Long
7 | }
8 |
--------------------------------------------------------------------------------
/core/remote-config/src/jvmMain/kotlin/com/kafka/remote/config/RemoteConfig.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.remote.config
2 |
3 | import com.kafka.base.ApplicationScope
4 | import javax.inject.Inject
5 |
6 | @ApplicationScope
7 | actual class RemoteConfig @Inject constructor() {
8 |
9 | actual fun get(key: String): String = ""
10 |
11 | actual fun getBoolean(key: String): Boolean = false
12 |
13 | actual fun getLong(key: String): Long = 0L
14 | }
15 |
--------------------------------------------------------------------------------
/data/database/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/dao/EntityDao.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.dao
2 |
3 | import androidx.room.Insert
4 | import androidx.room.OnConflictStrategy
5 | import androidx.room.Update
6 | import com.kafka.data.entities.BaseEntity
7 |
8 | interface EntityDao {
9 | @Insert(onConflict = OnConflictStrategy.REPLACE)
10 | suspend fun insert(entity: E): Long
11 |
12 | @Insert(onConflict = OnConflictStrategy.REPLACE)
13 | suspend fun insertAll(vararg entity: E)
14 |
15 | @Insert(onConflict = OnConflictStrategy.REPLACE)
16 | suspend fun insertAll(entities: List)
17 |
18 | @Update(onConflict = OnConflictStrategy.REPLACE)
19 | suspend fun update(entity: E)
20 | }
21 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/dao/ItemDetailDao.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Query
5 | import com.kafka.data.entities.ItemDetail
6 | import kotlinx.coroutines.flow.Flow
7 |
8 | /**
9 | * @author Vipul Kumar; dated 29/11/18.
10 | */
11 | @Dao
12 | abstract class ItemDetailDao : EntityDao {
13 |
14 | @Query("select * from ItemDetail where itemId = :itemId")
15 | abstract fun observeItemDetail(itemId: String): Flow
16 |
17 | @Query("select * from ItemDetail where itemId = :itemId")
18 | abstract suspend fun get(itemId: String): ItemDetail
19 | }
20 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/dao/RecentSearchDao.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.dao
2 |
3 | import androidx.room.Dao
4 | import androidx.room.Query
5 | import androidx.room.Transaction
6 | import com.kafka.data.entities.RecentSearch
7 | import kotlinx.coroutines.flow.Flow
8 |
9 | @Dao
10 | abstract class RecentSearchDao : EntityDao {
11 | @Transaction
12 | @Query("SELECT * FROM recent_search ORDER BY id DESC")
13 | abstract fun observeRecentSearch(): Flow>
14 |
15 | @Query("DELETE FROM recent_search WHERE search_term = :searchTerm")
16 | abstract suspend fun delete(searchTerm: String)
17 | }
18 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/db/DatabaseBuilderComponnet.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.db
2 |
3 | expect interface DatabaseBuilderComponent
4 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/entities/BaseEntity.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.entities
2 |
3 | interface BaseEntity
4 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/entities/DownloadItem.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.entities
2 |
3 | data class DownloadItem(
4 | val id: String = "",
5 | val fileId: String = "",
6 | val downloadUrl: String = "",
7 | val itemId: String = "",
8 | val fileTitle: String = "",
9 | val itemTitle: String = "",
10 | val creator: String = "",
11 | val mediaType: String = "",
12 | val coverImage: String = "",
13 | )
14 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/entities/FavoriteItem.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.entities
2 |
3 | import com.google.firebase.firestore.DocumentId
4 | import dev.gitlive.firebase.firestore.Timestamp
5 | import kotlinx.serialization.SerialName
6 | import kotlinx.serialization.Serializable
7 | import kotlinx.serialization.Transient
8 |
9 | @Serializable
10 | data class FavoriteItem(
11 | @DocumentId
12 | @Transient val itemId: String = "",
13 | @SerialName("title") val title: String = "",
14 | @SerialName("creator") val creator: String = "",
15 | @SerialName("mediaType") val mediaType: String = "",
16 | @SerialName("coverImage") val coverImage: String = "",
17 | @SerialName("createdAt") val createdAt: Timestamp = Timestamp.now(),
18 | )
19 |
20 | fun FavoriteItem.toItem() = Item(
21 | itemId = itemId,
22 | title = title,
23 | creator = Creator(id = creator, name = creator),
24 | mediaType = mediaType,
25 | coverImage = coverImage,
26 | )
27 |
28 | const val listIdFavorites = "items"
29 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/entities/Item.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.entities
2 |
3 | import androidx.room.Embedded
4 | import androidx.room.Entity
5 | import androidx.room.PrimaryKey
6 |
7 | /**
8 | * @author Vipul Kumar; dated 13/02/19.
9 | */
10 | @Entity
11 | data class Item(
12 | @PrimaryKey val itemId: String = "",
13 | @Embedded(prefix = "creator_") val creator: Creator? = null,
14 | val language: List? = null,
15 | val title: String? = null,
16 | val description: String? = null,
17 | val mediaType: String? = null,
18 | val coverImage: String? = null,
19 | val collection: List? = null,
20 | val subject: String? = null,
21 | val uploader: String? = null,
22 | val position: Int = 0,
23 | val rating: Double? = null,
24 | ) : BaseEntity {
25 | val isAudio: Boolean
26 | get() = mediaType == "audio"
27 |
28 | val isInappropriate: Boolean
29 | get() = collection?.contains("no-preview") == true
30 | }
31 |
32 | data class Creator(val id: String, val name: String)
33 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/entities/QueueEntity.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.entities
2 |
3 | import androidx.room.Entity
4 | import androidx.room.PrimaryKey
5 |
6 | @Entity(tableName = "queue_meta_data")
7 | data class QueueEntity(
8 | @PrimaryKey(autoGenerate = false) var id: Long = 0,
9 | var currentSeekPos: Long = 0,
10 | var currentSongId: String? = null,
11 | var isPlaying: Boolean = false,
12 | ) : BaseEntity
13 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/entities/RecentSearch.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.entities
2 |
3 | import androidx.room.ColumnInfo
4 | import androidx.room.Entity
5 | import androidx.room.Index
6 | import androidx.room.PrimaryKey
7 | import com.kafka.data.model.MediaType
8 | import com.kafka.data.model.SearchFilter
9 |
10 | @Entity(
11 | tableName = "recent_search",
12 | indices = [Index(value = ["search_term"], unique = true)],
13 | )
14 | data class RecentSearch(
15 | @PrimaryKey(autoGenerate = true) @ColumnInfo(name = "id") var id: Long = 0,
16 | @ColumnInfo(name = "search_term") var searchTerm: String = "",
17 | @ColumnInfo(name = "filters") var filters: List,
18 | @ColumnInfo(name = "media_types") var mediaTypes: List,
19 | ) : BaseEntity
20 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/entities/Summary.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.entities
2 |
3 | import androidx.annotation.Keep
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | @Keep
9 | data class Summary(
10 | @SerialName("item_id") val itemId: String,
11 | @SerialName("content") val content: String,
12 | ) {
13 | constructor() : this("", "")
14 | }
15 |
--------------------------------------------------------------------------------
/data/database/src/commonMain/kotlin/com/kafka/data/entities/User.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.entities
2 |
3 | import androidx.annotation.Keep
4 |
5 | @Keep
6 | data class User(
7 | val id: String,
8 | val displayName: String,
9 | val email: String?,
10 | val imageUrl: String? = null,
11 | val anonymous: Boolean,
12 | ) {
13 | constructor() : this("", "", null, null, false)
14 | }
15 |
--------------------------------------------------------------------------------
/data/database/src/jvmMain/kotlin/com/kafka/data/db/DatabaseBuilder.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.db
2 |
3 | import androidx.room.Room
4 | import androidx.room.RoomDatabase
5 | import com.kafka.base.ApplicationScope
6 | import com.kafka.data.injection.databaseName
7 | import com.kafka.data.platform.appDirectory
8 | import me.tatarka.inject.annotations.Provides
9 | import java.io.File
10 |
11 | actual interface DatabaseBuilderComponent {
12 | @ApplicationScope
13 | @Provides
14 | fun provideDatabaseBuilder(): RoomDatabase.Builder {
15 | val dbFile = File(System.getProperty(appDirectory), databaseName)
16 | return Room.databaseBuilder(dbFile.absolutePath)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/data/database/src/main/java/com/kafka/data/db/DatabaseBuilder.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.db
2 |
3 | import android.app.Application
4 | import androidx.room.Room
5 | import androidx.room.RoomDatabase
6 | import com.kafka.base.ApplicationScope
7 | import com.kafka.data.injection.databaseName
8 | import me.tatarka.inject.annotations.Provides
9 |
10 | actual interface DatabaseBuilderComponent {
11 | @ApplicationScope
12 | @Provides
13 | fun provideDatabaseBuilder(application: Application): RoomDatabase.Builder {
14 | val dbFile = application.getDatabasePath(databaseName)
15 | return Room.databaseBuilder(application, dbFile.absolutePath)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/data/models/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/data/models/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.kafka.kotlin.multiplatform")
3 | alias(libs.plugins.kotlin.serialization)
4 | }
5 |
6 | kotlin {
7 | sourceSets {
8 | val commonMain by getting {
9 | dependencies {
10 | api(projects.base.domain)
11 | api(libs.kotlin.serialization)
12 | implementation(libs.firebase.firestore)
13 | }
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/AppConfig.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class AppConfig(
8 | @SerialName("app_update") val appUpdate: AppUpdateConfig,
9 | )
10 |
11 | @Serializable
12 | data class AppUpdateConfig(
13 | @SerialName("soft_update_version") val softUpdateVersion: Int,
14 | @SerialName("force_update_version") val forceUpdateVersion: Int,
15 | @SerialName("blocked_update_version") val blockedAppVersions: List,
16 | )
17 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/AppMessage.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class AppMessage(
8 | @SerialName("id") val id: String = "",
9 | @SerialName("title") val title: String = "",
10 | @SerialName("text") val text: String = "",
11 | @SerialName("image") val image: String = "",
12 | @SerialName("enabled") val enabled: Boolean = false,
13 | @SerialName("primary_url") val primaryUrl: String = "",
14 | @SerialName("primary_call_to_action") val primaryAction: String = "",
15 | @SerialName("secondary_call_to_action") val secondaryAction: String = "",
16 | @SerialName("countries") val countries: List = emptyList(),
17 | @SerialName("type") val type: Type,
18 | ) {
19 | enum class Type { Soft, Hard }
20 |
21 | val snackbarMessage: String
22 | get() = title.ifEmpty { text }
23 | }
24 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/MediaType.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model
2 |
3 | enum class MediaType(val value: String) {
4 | Text(_mediaTypeText), Audio(_mediaTypeAudio);
5 | }
6 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/SearchFilter.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model
2 |
3 | import com.kafka.base.debug
4 |
5 | enum class SearchFilter {
6 | Name, Creator, Subject;
7 |
8 | companion object {
9 | private fun fromString(value: String): SearchFilter {
10 | debug { "SearchFilter.fromString: $value" }
11 | return entries.firstOrNull { it.name.lowercase() == value.lowercase() } ?: Name
12 | }
13 |
14 | fun from(value: String): List {
15 | debug { "SearchFilter.fromAll: $value" }
16 | return value.split(",").map { SearchFilter.fromString(it.trim()) }
17 | }
18 |
19 | fun toString(filters: List): String {
20 | return filters.joinToString(",") { it.name }
21 | }
22 |
23 | fun allString(): String {
24 | return entries.joinToString(",") { it.name }
25 | }
26 |
27 | fun all() = entries
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/StringListSerializer.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model
2 |
3 | import kotlinx.serialization.builtins.ListSerializer
4 | import kotlinx.serialization.builtins.serializer
5 | import kotlinx.serialization.json.JsonArray
6 | import kotlinx.serialization.json.JsonElement
7 | import kotlinx.serialization.json.JsonTransformingSerializer
8 |
9 | object StringListSerializer :
10 | JsonTransformingSerializer>(ListSerializer(String.serializer())) {
11 | // If response is not an array, then it is a single object that should be wrapped into the array
12 | override fun transformDeserialize(element: JsonElement): JsonElement =
13 | if (element !is JsonArray) JsonArray(listOf(element)) else element
14 | }
15 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/UserCountryResponse.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class UserCountryResponse(
8 | @SerialName("country")
9 | val country: String
10 | )
11 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/item/File.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model.item
2 |
3 | import com.kafka.data.model.StringListSerializer
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class File(
9 | @SerialName("album")
10 | val album: String? = null,
11 | @SerialName("artist")
12 | val artist: String? = null,
13 | @Serializable(with = StringListSerializer::class)
14 | @SerialName("creator")
15 | val creator: List? = null,
16 | @SerialName("format")
17 | val format: String? = null,
18 | @SerialName("genres")
19 | val genre: String? = null,
20 | @SerialName("length")
21 | val length: String? = null,
22 | @SerialName("name")
23 | val name: String,
24 | @Serializable(with = StringListSerializer::class)
25 | @SerialName("size")
26 | val size: List? = null,
27 | @SerialName("title")
28 | val title: String? = null,
29 | ) {
30 | // todo: improve file ids to make them unique
31 | // this will require a migration to new file ids which might be a breaking change
32 | val fileId: String
33 | get() = (name + format).hashCode().toString()
34 | }
35 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/item/ItemDetailResponse.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model.item
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class ItemDetailResponse(
8 | @SerialName("dir")
9 | val dir: String,
10 | @SerialName("files")
11 | val files: List,
12 | @SerialName("files_count")
13 | val filesCount: Int = 0,
14 | @SerialName("metadata")
15 | val metadata: Metadata,
16 | @SerialName("server")
17 | val server: String = "",
18 | )
19 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/item/Params.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model.item
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Params(
8 | @SerialName("fields")
9 | val fields: String,
10 | @SerialName("json.wrf")
11 | val jsonwrf: String? = null,
12 | @SerialName("qin")
13 | val qin: String? = null,
14 | @SerialName("query")
15 | val query: String,
16 | @SerialName("rows")
17 | val rows: String,
18 | @SerialName("start")
19 | val start: Int,
20 | @SerialName("wt")
21 | val wt: String,
22 | )
23 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/item/Response.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model.item
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class Response(
8 | @SerialName("docs")
9 | val docs: List,
10 | @SerialName("numFound")
11 | val numFound: Int,
12 | @SerialName("start")
13 | val start: Int,
14 | )
15 |
--------------------------------------------------------------------------------
/data/models/src/commonMain/kotlin/com/kafka/data/model/item/SearchResponse.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.model.item
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class SearchResponse(
8 | @SerialName("response")
9 | val response: Response? = null,
10 | )
11 |
--------------------------------------------------------------------------------
/data/platform/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/data/platform/src/androidMain/kotlin/com/kafka/data/platform/DataPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform
2 |
3 | import com.kafka.data.platform.app.AppVersionComponent
4 | import com.kafka.data.platform.device.PlatformCountryComponent
5 |
6 | actual interface DataPlatformComponent : PlatformCountryComponent, AppVersionComponent
7 |
--------------------------------------------------------------------------------
/data/platform/src/androidMain/kotlin/com/kafka/data/platform/app/AppVersionComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform.app
2 |
3 | import android.app.Application
4 | import android.content.pm.PackageManager
5 | import com.kafka.base.ApplicationScope
6 | import com.kafka.base.errorLog
7 | import me.tatarka.inject.annotations.Provides
8 |
9 | actual interface AppVersionComponent {
10 | @ApplicationScope
11 | @Provides
12 | fun provideAppVersionInfo(application: Application): AppVersionInfo {
13 | val packageInfo = try {
14 | application.packageManager.getPackageInfo(application.packageName, 0)
15 | } catch (e: PackageManager.NameNotFoundException) {
16 | errorLog { "Unable to get version name" }
17 | null
18 | }
19 |
20 | return AppVersionInfo(versionName = packageInfo?.versionName)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/data/platform/src/androidMain/kotlin/com/kafka/data/platform/device/PlatformCountryComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform.device
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import com.kafka.base.ApplicationScope
6 | import me.tatarka.inject.annotations.Provides
7 |
8 | actual interface PlatformCountryComponent {
9 | @ApplicationScope
10 | @Provides
11 | fun providePlatformCountry(application: Application): PlatformCountry {
12 | val telephonyManager = application.getSystemService(Context.TELEPHONY_SERVICE)
13 | as android.telephony.TelephonyManager
14 |
15 | val country = telephonyManager.networkCountryIso.takeIf { it.isNotBlank() }
16 |
17 | return PlatformCountry(country)
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/data/platform/src/commonMain/kotlin/com/kafka/data/platform/AppDirectory.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform
2 |
3 | import java.io.File
4 |
5 | val appDirectory: String
6 | get() {
7 | val appName = "kafka"
8 | val appDirectory: String = when (System.getProperty("os.name").lowercase()) {
9 | "windows" -> "${System.getenv("LOCALAPPDATA")}/$appName"
10 | "mac os x", "darwin" -> "${System.getProperty("user.home")}/Library/Application Support/$appName"
11 | else -> System.getProperty("os.name")
12 | }
13 |
14 | val dbDirFile = File(appDirectory)
15 | if (!dbDirFile.exists()) {
16 | dbDirFile.mkdirs()
17 | }
18 |
19 | return appDirectory
20 | }
21 |
--------------------------------------------------------------------------------
/data/platform/src/commonMain/kotlin/com/kafka/data/platform/DataPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform
2 |
3 | expect interface DataPlatformComponent
4 |
--------------------------------------------------------------------------------
/data/platform/src/commonMain/kotlin/com/kafka/data/platform/UserDataRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform
2 |
3 | import com.kafka.base.ApplicationScope
4 | import com.kafka.data.platform.device.UserCountryRepository
5 | import javax.inject.Inject
6 |
7 | @ApplicationScope
8 | class UserDataRepository @Inject constructor(
9 | private val userCountryRepository: UserCountryRepository
10 | ) {
11 | suspend fun getUserCountry(): String? = userCountryRepository.getUserCountry()
12 | }
13 |
--------------------------------------------------------------------------------
/data/platform/src/commonMain/kotlin/com/kafka/data/platform/app/AppVersion.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform.app
2 |
3 | expect interface AppVersionComponent
4 |
5 | data class AppVersionInfo(val versionName: String?)
6 |
--------------------------------------------------------------------------------
/data/platform/src/commonMain/kotlin/com/kafka/data/platform/device/PlatformCountryComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform.device
2 |
3 | expect interface PlatformCountryComponent
4 |
5 | data class PlatformCountry(val country: String?)
6 |
--------------------------------------------------------------------------------
/data/platform/src/jvmMain/kotlin/com/kafka/data/platform/DataPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform
2 |
3 | import com.kafka.data.platform.app.AppVersionComponent
4 | import com.kafka.data.platform.device.PlatformCountryComponent
5 |
6 | actual interface DataPlatformComponent : PlatformCountryComponent, AppVersionComponent
7 |
--------------------------------------------------------------------------------
/data/platform/src/jvmMain/kotlin/com/kafka/data/platform/app/AppVersionComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform.app
2 |
3 | import android.app.Application
4 | import com.kafka.base.ApplicationScope
5 | import me.tatarka.inject.annotations.Provides
6 | import java.io.File
7 |
8 | actual interface AppVersionComponent {
9 | @ApplicationScope
10 | @Provides
11 | fun provideAppVersionInfo(application: Application): AppVersionInfo {
12 | val properties = System.getProperty("java.class.path")
13 | val versionName = properties.split(File.separator).find { it.endsWith(".jar") }
14 | ?.let { File(it).nameWithoutExtension }
15 |
16 | return AppVersionInfo(versionName = versionName)
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/data/platform/src/jvmMain/kotlin/com/kafka/data/platform/device/PlatformCountryComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.platform.device
2 |
3 | import com.kafka.base.ApplicationScope
4 | import me.tatarka.inject.annotations.Provides
5 |
6 | actual interface PlatformCountryComponent {
7 | @ApplicationScope
8 | @Provides
9 | fun providePlatformCountry() = PlatformCountry(null)
10 | }
11 |
--------------------------------------------------------------------------------
/data/prefs/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/data/prefs/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("com.kafka.kotlin.multiplatform")
4 | }
5 |
6 | kotlin {
7 | sourceSets {
8 | val commonMain by getting {
9 | dependencies {
10 | api(projects.base.domain)
11 |
12 | api(libs.dataStore)
13 | api(libs.kinject)
14 | }
15 | }
16 | }
17 | }
18 |
19 | android {
20 | namespace = "com.kafka.data.prefs"
21 | }
22 |
--------------------------------------------------------------------------------
/data/prefs/src/androidMain/kotlin/com/kafka/data/prefs/PreferenceStoreComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.prefs
2 |
3 | import android.app.Application
4 | import com.kafka.base.ApplicationScope
5 | import me.tatarka.inject.annotations.Provides
6 |
7 | actual interface PreferenceStoreComponent {
8 | @ApplicationScope
9 | @Provides
10 | fun providePreferencesStore(context: Application): PreferencesStore {
11 | val dataStore = createDataStore(
12 | producePath = { context.filesDir.resolve(dataStoreFileName).absolutePath }
13 | )
14 |
15 | return PreferencesStore(dataStore)
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/data/prefs/src/commonMain/kotlin/com/kafka/data/prefs/ItemReadCounter.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.prefs
2 |
3 | import androidx.datastore.preferences.core.intPreferencesKey
4 | import kotlinx.coroutines.CoroutineScope
5 | import com.kafka.base.ApplicationScope
6 | import com.kafka.base.ProcessLifetime
7 | import javax.inject.Inject
8 |
9 | @ApplicationScope
10 | class ItemReadCounter @Inject constructor(
11 | preferencesStore: PreferencesStore,
12 | @ProcessLifetime private val coroutineScope: CoroutineScope,
13 | ) {
14 | private val itemOpensPrefKey = intPreferencesKey("item_opens")
15 |
16 | private val itemOpens = preferencesStore.getStateFlow(
17 | keyName = itemOpensPrefKey, scope = coroutineScope, initialValue = 0
18 | )
19 |
20 | fun incrementItemOpenCount() {
21 | itemOpens.value++
22 | }
23 |
24 | val totalItemOpens: Int
25 | get() = itemOpens.value
26 | }
27 |
--------------------------------------------------------------------------------
/data/prefs/src/commonMain/kotlin/com/kafka/data/prefs/PreferenceStoreComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.prefs
2 |
3 | import androidx.datastore.core.DataStore
4 | import androidx.datastore.preferences.core.PreferenceDataStoreFactory
5 | import androidx.datastore.preferences.core.Preferences
6 | import okio.Path.Companion.toPath
7 |
8 | expect interface PreferenceStoreComponent
9 |
10 | fun createDataStore(producePath: () -> String): DataStore =
11 | PreferenceDataStoreFactory.createWithPath(
12 | produceFile = { producePath().toPath() }
13 | )
14 |
15 | internal const val dataStoreFileName = "app_preferences.preferences_pb"
16 |
--------------------------------------------------------------------------------
/data/prefs/src/jvmMain/kotlin/com/kafka/data/prefs/PreferenceStoreComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.prefs
2 |
3 | import com.kafka.base.ApplicationScope
4 | import me.tatarka.inject.annotations.Provides
5 |
6 | actual interface PreferenceStoreComponent {
7 | @ApplicationScope
8 | @Provides
9 | fun providePreferencesStore(): PreferencesStore {
10 | return PreferencesStore(createDataStore { dataStoreFileName })
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/data/repo/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/data/repo/src/commonMain/kotlin/com/kafka/data/UrlUtils.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data
2 |
3 | import io.ktor.http.encodeURLParameter
4 |
5 | fun String.encodeUrl(): String = encodeURLParameter()
6 |
--------------------------------------------------------------------------------
/data/repo/src/commonMain/kotlin/com/kafka/data/feature/DownloadsRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.feature
2 |
3 | import com.kafka.base.ApplicationScope
4 | import com.kafka.data.entities.DownloadItem
5 | import com.kafka.data.feature.auth.AccountRepository
6 | import com.kafka.data.feature.firestore.FirestoreGraph
7 | import javax.inject.Inject
8 |
9 | @ApplicationScope
10 | class DownloadsRepository @Inject constructor(
11 | private val firestoreGraph: FirestoreGraph,
12 | private val accountRepository: AccountRepository,
13 | ) {
14 | suspend fun addDownload(downloadItem: DownloadItem) {
15 | accountRepository.currentFirebaseUser?.uid?.let {
16 | firestoreGraph.getDownloadsCollection(it).document(downloadItem.id)
17 | .set(downloadItem)
18 | }
19 | }
20 |
21 | suspend fun removeDownload(id: String) {
22 | accountRepository.currentFirebaseUser?.uid?.let {
23 | firestoreGraph.getDownloadsCollection(it).document(id).delete()
24 | }
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/data/repo/src/commonMain/kotlin/com/kafka/data/feature/ai/SummaryRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.feature.ai
2 |
3 | import com.kafka.data.entities.Summary
4 | import com.kafka.data.feature.firestore.FirestoreGraph
5 | import kotlinx.coroutines.flow.map
6 | import com.kafka.base.ApplicationScope
7 | import javax.inject.Inject
8 |
9 | @ApplicationScope
10 | class SummaryRepository @Inject constructor(private val firestoreGraph: FirestoreGraph) {
11 |
12 | fun observeSummary(itemId: String) = firestoreGraph.summaryCollection
13 | .document(itemId)
14 | .snapshots()
15 | .map { snapshots -> snapshots.data() }
16 |
17 | suspend fun updateSummary(summary: Summary) {
18 | firestoreGraph.summaryCollection.document(summary.itemId).set(summary)
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/data/repo/src/commonMain/kotlin/com/kafka/data/feature/item/ItemDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.feature.item
2 |
3 | import com.kafka.data.api.ArchiveService
4 | import com.kafka.data.entities.Item
5 | import kotlinx.coroutines.withContext
6 | import com.kafka.base.CoroutineDispatchers
7 | import javax.inject.Inject
8 |
9 | /**
10 | * @author Vipul Kumar; dated 29/11/18.
11 | */
12 | class ItemDataSource @Inject constructor(
13 | private val archiveService: ArchiveService,
14 | private val itemMapper: ItemMapper,
15 | private val dispatchers: CoroutineDispatchers,
16 | ) {
17 |
18 | suspend fun fetchItemsByQuery(query: String): List- {
19 | return withContext(dispatchers.io) {
20 | val response = archiveService.search(query)
21 | itemMapper.map(response)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/data/repo/src/commonMain/kotlin/com/kafka/data/feature/item/ItemDetailDataSource.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.feature.item
2 |
3 | import com.kafka.data.api.ArchiveService
4 | import com.kafka.data.dao.ItemDetailDao
5 | import kotlinx.coroutines.withContext
6 | import com.kafka.base.CoroutineDispatchers
7 | import com.kafka.base.debug
8 | import javax.inject.Inject
9 |
10 | class ItemDetailDataSource @Inject constructor(
11 | private val dispatchers: CoroutineDispatchers,
12 | private val itemDetailDao: ItemDetailDao,
13 | private val itemDetailMapper: ItemDetailMapper,
14 | private val archiveService: ArchiveService,
15 | ) {
16 | suspend fun updateItemDetail(contentId: String) = withContext(dispatchers.io) {
17 | itemDetailMapper.map(archiveService.getItemDetail(contentId)).let {
18 | debug { "Item detail is $it" }
19 | itemDetailDao.insert(it)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/data/repo/src/commonMain/kotlin/com/kafka/data/feature/item/ItemRepository.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.feature.item
2 |
3 | import androidx.room.RoomRawQuery
4 | import com.kafka.data.dao.ItemDao
5 | import com.kafka.data.entities.Item
6 | import com.kafka.data.prefs.PreferencesStore
7 | import com.kafka.data.prefs.observeSafeMode
8 | import kotlinx.coroutines.flow.combine
9 | import javax.inject.Inject
10 |
11 | /**
12 | * @author Vipul Kumar; dated 29/11/18.
13 | *
14 | */
15 | class ItemRepository @Inject constructor(
16 | private val itemDao: ItemDao,
17 | private val remoteDataSource: ItemDataSource,
18 | private val preferencesStore: PreferencesStore,
19 | ) {
20 | fun observeQueryItems(simpleSQLiteQuery: RoomRawQuery) = combine(
21 | itemDao.observeQueryItems(simpleSQLiteQuery),
22 | preferencesStore.observeSafeMode()
23 | ) { items, safeMode ->
24 | items.filter { !safeMode || !it.isInappropriate }
25 | }
26 |
27 | suspend fun updateQuery(query: String) = remoteDataSource.fetchItemsByQuery(query)
28 |
29 | suspend fun saveItems(items: List
- ) = itemDao.insertAll(items)
30 |
31 | suspend fun exists(id: String) = itemDao.exists(id)
32 | }
33 |
--------------------------------------------------------------------------------
/data/repo/src/commonMain/kotlin/com/kafka/data/feature/item/ItemWithDownload.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.data.feature.item
2 |
3 | import android.net.Uri
4 | import com.kafka.data.entities.File
5 | import com.kafka.data.entities.Item
6 |
7 | data class ItemWithDownload(
8 | val downloadInfo: DownloadInfo,
9 | val file: File,
10 | val item: Item,
11 | )
12 |
13 | data class DownloadInfo(
14 | val id: Int,
15 | val progress: Float,
16 | val status: DownloadStatus,
17 | val fileUri: Uri,
18 | val sizeStatus: String?,
19 | )
20 |
21 | enum class DownloadStatus {
22 | QUEUED,
23 | DOWNLOADING,
24 | PAUSED,
25 | COMPLETED,
26 | CANCELLED,
27 | FAILED,
28 | REMOVED,
29 | DELETED,
30 | UNKNOWN,
31 | ;
32 |
33 | fun isActive() = this == DOWNLOADING || this == PAUSED
34 | }
35 |
--------------------------------------------------------------------------------
/data/repo/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/desktop-app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/desktop-app/src/jvmMain/kotlin/com/kafka/desktop/Main.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.desktop
2 |
3 | import androidx.compose.runtime.remember
4 | import androidx.compose.ui.window.Window
5 | import androidx.compose.ui.window.WindowPlacement
6 | import androidx.compose.ui.window.application
7 | import androidx.compose.ui.window.rememberWindowState
8 | import com.kafka.shared.DesktopApplicationComponent
9 | import com.kafka.shared.create
10 | import ui.common.theme.theme.AppTheme
11 |
12 | fun main() = application {
13 | Window(
14 | title = "Kafka",
15 | state = rememberWindowState(placement = WindowPlacement.Fullscreen),
16 | onCloseRequest = ::exitApplication,
17 | ) {
18 | val applicationComponent = remember {
19 | DesktopApplicationComponent::class.create()
20 | }
21 |
22 | val component = remember(applicationComponent) {
23 | WindowComponent::class.create(applicationComponent)
24 | }
25 |
26 | component.appInitializers.forEach { it.init() }
27 |
28 | AppTheme {
29 | component.mainScreen()
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/desktop-app/src/jvmMain/kotlin/com/kafka/desktop/MainScreen.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.desktop
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.kafka.homepage.Homepage
5 | import com.kafka.homepage.HomepageViewModel
6 | import me.tatarka.inject.annotations.Inject
7 |
8 | typealias MainScreen = @Composable () -> Unit
9 |
10 | @Composable
11 | @Inject
12 | fun MainScreen(viewModelFactory: () -> HomepageViewModel) {
13 | Homepage(viewModelFactory)
14 | }
15 |
--------------------------------------------------------------------------------
/desktop-app/src/jvmMain/kotlin/com/kafka/desktop/WindowComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.desktop
2 |
3 | import com.kafka.base.ActivityScope
4 | import com.kafka.base.AppInitializer
5 | import com.kafka.desktop.main.HomeNavigation
6 | import com.kafka.shared.DesktopApplicationComponent
7 | import me.tatarka.inject.annotations.Component
8 |
9 | @ActivityScope
10 | @Component
11 | abstract class WindowComponent(
12 | @Component val applicationComponent: DesktopApplicationComponent,
13 | ) {
14 | abstract val mainScreen: HomeNavigation
15 | abstract val appInitializers: Set
16 |
17 | companion object
18 | }
19 |
--------------------------------------------------------------------------------
/desktop-app/src/jvmMain/kotlin/com/kafka/desktop/main/HomeNavigationItems.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.desktop.main
2 |
3 | import androidx.compose.ui.graphics.vector.ImageVector
4 | import com.kafka.common.image.Icons
5 | import com.kafka.navigation.graph.RootScreen
6 |
7 | internal val HomeNavigationItems = listOf(
8 | HomeNavigationItem(
9 | rootScreen = RootScreen.Home,
10 | labelResId = "Home",
11 | iconImageVector = Icons.Home,
12 | selectedImageVector = Icons.HomeActive,
13 | ),
14 | HomeNavigationItem(
15 | rootScreen = RootScreen.Search,
16 | labelResId = "Search",
17 | iconImageVector = Icons.Search,
18 | selectedImageVector = Icons.SearchActive,
19 | ),
20 | HomeNavigationItem(
21 | rootScreen = RootScreen.Library,
22 | labelResId = "Library",
23 | iconImageVector = Icons.Library,
24 | selectedImageVector = Icons.LibraryActive,
25 | )
26 | )
27 |
28 | internal data class HomeNavigationItem(
29 | val rootScreen: RootScreen,
30 | val labelResId: String,
31 | val iconImageVector: ImageVector,
32 | val selectedImageVector: ImageVector,
33 | )
34 |
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/UpdateCurrentPage.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors
2 |
3 | import com.kafka.data.dao.RecentTextDao
4 | import kotlinx.coroutines.withContext
5 | import com.kafka.base.CoroutineDispatchers
6 | import com.kafka.base.debug
7 | import com.kafka.base.domain.Interactor
8 | import javax.inject.Inject
9 |
10 | class UpdateCurrentPage @Inject constructor(
11 | private val dispatchers: CoroutineDispatchers,
12 | private val recentTextDao: RecentTextDao,
13 | ) : Interactor() {
14 |
15 | override suspend fun doWork(params: Params) {
16 | withContext(dispatchers.io) {
17 | debug { "UpdateCurrentPage: $params" }
18 | recentTextDao.updateCurrentPage(params.fileId, params.currentPage)
19 | }
20 | }
21 |
22 | data class Params(val fileId: String, val currentPage: Int)
23 | }
24 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/UpdateFeedback.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors
2 |
3 | import com.kafka.data.feature.firestore.FirestoreGraph
4 | import kotlinx.coroutines.withContext
5 | import com.kafka.base.CoroutineDispatchers
6 | import com.kafka.base.domain.Interactor
7 | import dev.gitlive.firebase.auth.FirebaseAuth
8 | import javax.inject.Inject
9 |
10 | class UpdateFeedback @Inject constructor(
11 | private val firestoreGraph: FirestoreGraph,
12 | private val auth: FirebaseAuth,
13 | private val dispatchers: CoroutineDispatchers,
14 | ) : Interactor() {
15 | override suspend fun doWork(params: Params) {
16 | withContext(dispatchers.io) {
17 | val email = params.email ?: auth.currentUser?.email
18 | firestoreGraph.feedbackCollection.document.set(
19 | mapOf("text" to params.text, "email" to email),
20 | )
21 | }
22 | }
23 |
24 | data class Params(val text: String, val email: String? = null)
25 | }
26 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/UpdateItemDetail.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors
2 |
3 | import com.kafka.data.feature.item.ItemDetailDataSource
4 | import kotlinx.coroutines.withContext
5 | import com.kafka.base.CoroutineDispatchers
6 | import com.kafka.base.domain.Interactor
7 | import javax.inject.Inject
8 |
9 | class UpdateItemDetail @Inject constructor(
10 | private val dispatchers: CoroutineDispatchers,
11 | private val repository: ItemDetailDataSource,
12 | ) : Interactor() {
13 |
14 | override suspend fun doWork(params: Param) {
15 | withContext(dispatchers.io) {
16 | repository.updateItemDetail(params.contentId)
17 | }
18 | }
19 |
20 | data class Param(val contentId: String)
21 | }
22 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/UpdateItems.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors
2 |
3 | import com.kafka.data.feature.item.ItemRepository
4 | import com.kafka.data.model.ArchiveQuery
5 | import kotlinx.coroutines.withContext
6 | import com.kafka.base.CoroutineDispatchers
7 | import com.kafka.base.debug
8 | import com.kafka.base.domain.Interactor
9 | import com.kafka.domain.interactors.query.BuildRemoteQuery
10 | import javax.inject.Inject
11 |
12 | class UpdateItems @Inject constructor(
13 | private val dispatchers: CoroutineDispatchers,
14 | private val buildRemoteQuery: BuildRemoteQuery,
15 | private val itemRepository: ItemRepository,
16 | ) : Interactor() {
17 |
18 | override suspend fun doWork(params: Params) {
19 | withContext(dispatchers.io) {
20 | itemRepository.updateQuery(buildRemoteQuery(params.archiveQuery)).let {
21 | itemRepository.saveItems(it)
22 | }
23 | debug { "Query updated $params" }
24 | }
25 | }
26 |
27 | data class Params(val archiveQuery: ArchiveQuery)
28 | }
29 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/UpdateRecentItem.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors
2 |
3 | import com.kafka.base.domain.Interactor
4 | import com.kafka.data.entities.RecentItem
5 | import com.kafka.data.feature.firestore.FirestoreGraph
6 | import javax.inject.Inject
7 |
8 | class UpdateRecentItem @Inject constructor(
9 | private val firestoreGraph: FirestoreGraph,
10 | ) : Interactor() {
11 | override suspend fun doWork(params: RecentItem) {
12 | val document = firestoreGraph.recentItemsCollection.document(params.fileId)
13 | document.set(params)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/account/LogoutCredentialManager.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.account
2 |
3 | expect class LogoutCredentialManager {
4 | suspend operator fun invoke(context: Any?): Result
5 | }
6 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/account/LogoutUser.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.account
2 |
3 | import com.kafka.base.CoroutineDispatchers
4 | import com.kafka.base.domain.Interactor
5 | import com.kafka.data.feature.auth.AccountRepository
6 | import kotlinx.coroutines.withContext
7 | import javax.inject.Inject
8 |
9 | class LogoutUser @Inject constructor(
10 | private val accountRepository: AccountRepository,
11 | private val signInAnonymously: SignInAnonymously,
12 | private val logoutCredentialManager: LogoutCredentialManager,
13 | private val coroutineDispatchers: CoroutineDispatchers,
14 | ) : Interactor() {
15 |
16 | override suspend fun doWork(params: Any?) {
17 | withContext(coroutineDispatchers.io) {
18 | logoutCredentialManager(params).getOrThrow()
19 |
20 | accountRepository.signOut()
21 | signInAnonymously.execute(Unit)
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/account/ResetPassword.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.account
2 |
3 | import com.kafka.data.feature.auth.AccountRepository
4 | import kotlinx.coroutines.withContext
5 | import com.kafka.base.CoroutineDispatchers
6 | import com.kafka.base.domain.Interactor
7 | import javax.inject.Inject
8 |
9 | class ResetPassword @Inject constructor(
10 | private val accountRepository: AccountRepository,
11 | private val dispatchers: CoroutineDispatchers,
12 | ) : Interactor() {
13 |
14 | override suspend fun doWork(params: Params) {
15 | withContext(dispatchers.io) {
16 | try {
17 | accountRepository.resetPassword(params.email)
18 | } catch (e: Exception) {
19 | throw e
20 | }
21 | }
22 | }
23 |
24 | data class Params(val email: String)
25 | }
26 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/account/SignInAnonymously.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.account
2 |
3 | import com.kafka.data.feature.auth.AccountRepository
4 | import com.kafka.base.domain.Interactor
5 | import javax.inject.Inject
6 |
7 | class SignInAnonymously @Inject constructor(
8 | private val accountRepository: AccountRepository,
9 | ) : Interactor() {
10 |
11 | override suspend fun doWork(params: Unit) {
12 | if (!accountRepository.isUserLoggedIn) {
13 | accountRepository.signInAnonymously()
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/account/SignInUser.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.account
2 |
3 | import com.kafka.data.feature.auth.AccountRepository
4 | import kotlinx.coroutines.withContext
5 | import com.kafka.analytics.logger.Analytics
6 | import com.kafka.base.CoroutineDispatchers
7 | import com.kafka.base.domain.Interactor
8 | import javax.inject.Inject
9 |
10 | class SignInUser @Inject constructor(
11 | private val accountRepository: AccountRepository,
12 | private val dispatchers: CoroutineDispatchers,
13 | private val analytics: Analytics,
14 | ) : Interactor() {
15 |
16 | override suspend fun doWork(params: Params) {
17 | withContext(dispatchers.io) {
18 | accountRepository.signInUser(params.email, params.password)
19 | analytics.log { login("email") }
20 | }
21 | }
22 |
23 | data class Params(val email: String, val password: String)
24 | }
25 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/account/SignInWithGoogle.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.account
2 |
3 | expect class SignInWithGoogle {
4 | suspend operator fun invoke(params: Any?): Result
5 | }
6 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/query/BuildSearchQuery.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.query
2 |
3 | import com.kafka.data.model.MediaType
4 | import com.kafka.data.model.SearchFilter
5 | import com.kafka.data.model.joinerOr
6 | import javax.inject.Inject
7 |
8 | class BuildSearchQuery @Inject constructor() {
9 |
10 | operator fun invoke(
11 | keyword: String,
12 | searchFilter: List,
13 | mediaTypes: List,
14 | ): String {
15 | val words = keyword.split(" ")
16 |
17 | val filterPart = words.joinToString(" ") { word ->
18 | searchFilter
19 | .joinToString(" $joinerOr ") { filter -> "${filter.key()}:$word" }
20 | .let { "($it)" }
21 | }
22 |
23 | val mediaTypePart = mediaTypes
24 | .joinToString(" $joinerOr ") { mediaType -> mediaType.value }
25 | .let { "(mediaType:($it))" }
26 |
27 | return "$filterPart $mediaTypePart"
28 | }
29 |
30 | private fun SearchFilter.key() = when (this) {
31 | SearchFilter.Creator -> "salients"
32 | SearchFilter.Name -> "title"
33 | SearchFilter.Subject -> "subject"
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/recent/AddRecentItem.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.recent
2 |
3 | import com.kafka.data.dao.ItemDetailDao
4 | import com.kafka.data.entities.RecentItem
5 | import kotlinx.coroutines.withContext
6 | import com.kafka.base.CoroutineDispatchers
7 | import com.kafka.base.domain.Interactor
8 | import com.kafka.domain.interactors.UpdateRecentItem
9 | import javax.inject.Inject
10 |
11 | class AddRecentItem @Inject constructor(
12 | private val dispatchers: CoroutineDispatchers,
13 | private val itemDetailDao: ItemDetailDao,
14 | private val updateRecentItem: UpdateRecentItem,
15 | ) : Interactor() {
16 |
17 | override suspend fun doWork(params: Params) {
18 | withContext(dispatchers.io) {
19 | val item = itemDetailDao.get(params.itemId)
20 | updateRecentItem.execute(RecentItem.fromItem(item))
21 | }
22 | }
23 |
24 | data class Params(val itemId: String)
25 | }
26 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/recent/RemoveAllRecentItems.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.recent
2 |
3 | import com.kafka.base.domain.Interactor
4 | import com.kafka.data.feature.firestore.FirestoreGraph
5 | import kotlinx.coroutines.flow.first
6 | import kotlinx.coroutines.flow.map
7 | import javax.inject.Inject
8 |
9 | class RemoveAllRecentItems @Inject constructor(
10 | private val firestoreGraph: FirestoreGraph,
11 | ) : Interactor() {
12 | override suspend fun doWork(params: Unit) {
13 | val documentIds = firestoreGraph.recentItemsCollection
14 | .snapshots()
15 | .map { it.documents.map { document -> document.id } }
16 | .first()
17 |
18 | documentIds.forEach { documentId ->
19 | firestoreGraph.recentItemsCollection.document(documentId).delete()
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/interactors/recent/RemoveRecentItem.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.recent
2 |
3 | import com.kafka.data.feature.firestore.FirestoreGraph
4 | import com.kafka.base.domain.Interactor
5 | import javax.inject.Inject
6 |
7 | class RemoveRecentItem @Inject constructor(
8 | private val firestoreGraph: FirestoreGraph,
9 | ) : Interactor() {
10 | override suspend fun doWork(params: String) {
11 | val document = firestoreGraph.recentItemsCollection.document(params)
12 | document.delete()
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ObserveAppUpdateConfig.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import com.kafka.base.domain.SubjectInteractor
4 | import com.kafka.data.feature.firestore.FirestoreGraph
5 | import com.kafka.data.model.AppUpdateConfig
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.map
8 | import javax.inject.Inject
9 |
10 | class ObserveAppUpdateConfig @Inject constructor(
11 | private val firestoreGraph: FirestoreGraph
12 | ) : SubjectInteractor() {
13 | override fun createObservable(params: Unit): Flow {
14 | return firestoreGraph.appUpdateConfig.snapshots
15 | .map { it.data() }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ObserveFiles.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import com.kafka.data.dao.FileDao
4 | import com.kafka.data.entities.File
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flowOn
7 | import kotlinx.coroutines.flow.map
8 | import com.kafka.base.CoroutineDispatchers
9 | import com.kafka.base.domain.SubjectInteractor
10 | import javax.inject.Inject
11 |
12 | class ObserveFiles @Inject constructor(
13 | private val dispatchers: CoroutineDispatchers,
14 | private val fileDao: FileDao,
15 | ) : SubjectInteractor>() {
16 |
17 | override fun createObservable(params: Param): Flow
> {
18 | return fileDao.observeByItemId(params.contentId)
19 | .map { it.sortedBy { it.format } }
20 | .flowOn(dispatchers.io)
21 | }
22 |
23 | data class Param(val contentId: String)
24 | }
25 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ObserveHomepage.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import com.kafka.base.CoroutineDispatchers
4 | import com.kafka.base.domain.SubjectInteractor
5 | import com.kafka.data.entities.Homepage
6 | import com.kafka.data.feature.homepage.HomepageRepository
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.flowOn
9 | import kotlinx.coroutines.flow.map
10 | import javax.inject.Inject
11 |
12 | class ObserveHomepage @Inject constructor(
13 | private val coroutineDispatchers: CoroutineDispatchers,
14 | private val homepageRepository: HomepageRepository,
15 | ) : SubjectInteractor() {
16 |
17 | override fun createObservable(params: Unit): Flow {
18 | return homepageRepository.observeHomepageCollection().map {
19 | Homepage(collection = it)
20 | }.flowOn(coroutineDispatchers.io)
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ObserveItemDetail.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import com.kafka.data.dao.ItemDetailDao
4 | import com.kafka.data.entities.ItemDetail
5 | import com.kafka.data.feature.item.ItemDetailDataSource
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.flowOn
8 | import com.kafka.base.CoroutineDispatchers
9 | import com.kafka.base.domain.SubjectInteractor
10 | import javax.inject.Inject
11 |
12 | /**
13 | * @author Vipul Kumar; dated 10/12/18.
14 | *
15 | * Interactor for observing the content detail.
16 | * @see ItemDetailDataSource
17 | *
18 | */
19 | class ObserveItemDetail @Inject constructor(
20 | private val dispatchers: CoroutineDispatchers,
21 | private val itemDetailDao: ItemDetailDao,
22 | ) : SubjectInteractor() {
23 |
24 | override fun createObservable(params: Param): Flow {
25 | return itemDetailDao.observeItemDetail(params.contentId)
26 | .flowOn(dispatchers.io)
27 | }
28 |
29 | data class Param(val contentId: String)
30 | }
31 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ObserveQueryItems.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import com.kafka.data.entities.Item
4 | import com.kafka.data.feature.item.ItemRepository
5 | import com.kafka.data.model.ArchiveQuery
6 | import kotlinx.coroutines.flow.Flow
7 | import kotlinx.coroutines.flow.flowOn
8 | import com.kafka.base.CoroutineDispatchers
9 | import com.kafka.base.domain.SubjectInteractor
10 | import com.kafka.domain.interactors.query.BuildLocalQuery
11 | import javax.inject.Inject
12 |
13 | class ObserveQueryItems @Inject constructor(
14 | private val dispatchers: CoroutineDispatchers,
15 | private val buildLocalQuery: BuildLocalQuery,
16 | private val itemRepository: ItemRepository,
17 | ) : SubjectInteractor>() {
18 |
19 | override fun createObservable(params: Params): Flow> {
20 | return itemRepository.observeQueryItems(buildLocalQuery(params.archiveQuery))
21 | .flowOn(dispatchers.io)
22 | }
23 |
24 | data class Params(val archiveQuery: ArchiveQuery)
25 | }
26 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ObserveRecentItems.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import com.kafka.base.CoroutineDispatchers
4 | import com.kafka.base.domain.SubjectInteractor
5 | import com.kafka.data.entities.RecentItemWithProgress
6 | import com.kafka.data.feature.RecentItemRepository
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.flowOn
9 | import kotlinx.coroutines.flow.map
10 | import javax.inject.Inject
11 |
12 | class ObserveRecentItems @Inject constructor(
13 | private val dispatchers: CoroutineDispatchers,
14 | private val recentItemRepository: RecentItemRepository,
15 | ) : SubjectInteractor>() {
16 |
17 | override fun createObservable(params: Params): Flow> {
18 | return recentItemRepository.observeRecentItems(params.limit)
19 | .map { it.map { RecentItemWithProgress(it, 0) } }
20 | .flowOn(dispatchers.io)
21 | }
22 |
23 | data class Params(val limit: Int)
24 | }
25 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ObserveRecentSearch.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import com.kafka.data.dao.RecentSearchDao
4 | import com.kafka.data.entities.RecentSearch
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flowOn
7 | import kotlinx.coroutines.flow.map
8 | import com.kafka.base.CoroutineDispatchers
9 | import com.kafka.base.domain.SubjectInteractor
10 | import javax.inject.Inject
11 |
12 | class ObserveRecentSearch @Inject constructor(
13 | private val dispatchers: CoroutineDispatchers,
14 | private val recentSearchDao: RecentSearchDao,
15 | ) : SubjectInteractor>() {
16 |
17 | override fun createObservable(params: Unit): Flow> {
18 | return recentSearchDao.observeRecentSearch()
19 | .map { it.take(MaxRecentSearches).distinctBy { it.searchTerm } }
20 | .flowOn(dispatchers.io)
21 | }
22 | }
23 |
24 | private const val MaxRecentSearches = 30
25 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ObserveRecentTextItem.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import com.kafka.data.dao.RecentTextDao
4 | import com.kafka.data.entities.RecentTextItem
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flowOn
7 | import kotlinx.coroutines.flow.onEach
8 | import com.kafka.base.CoroutineDispatchers
9 | import com.kafka.base.debug
10 | import com.kafka.base.domain.SubjectInteractor
11 | import javax.inject.Inject
12 |
13 | /**
14 | * Interactor for updating the homepage.
15 | * */
16 | class ObserveRecentTextItem @Inject constructor(
17 | private val dispatchers: CoroutineDispatchers,
18 | private val recentTextDao: RecentTextDao,
19 | ) : SubjectInteractor() {
20 |
21 | override fun createObservable(params: String): Flow {
22 | return recentTextDao.observe(params)
23 | .onEach { debug { "recent item $it" } }
24 | .flowOn(dispatchers.io)
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ObserveShareAppIndex.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import androidx.datastore.preferences.core.intPreferencesKey
4 | import com.kafka.data.prefs.PreferencesStore
5 | import com.kafka.remote.config.RemoteConfig
6 | import com.kafka.remote.config.shareAppIndex
7 | import kotlinx.coroutines.flow.Flow
8 | import kotlinx.coroutines.flow.flowOn
9 | import kotlinx.coroutines.flow.map
10 | import com.kafka.base.CoroutineDispatchers
11 | import com.kafka.base.domain.SubjectInteractor
12 | import javax.inject.Inject
13 |
14 | class ObserveShareAppIndex @Inject constructor(
15 | private val preferencesStore: PreferencesStore,
16 | private val remoteConfig: RemoteConfig,
17 | private val dispatchers: CoroutineDispatchers
18 | ) : SubjectInteractor() {
19 |
20 | override fun createObservable(params: Unit): Flow {
21 | return preferencesStore.get(intPreferencesKey("item_opens"), 0).map {
22 | if (it > 20) {
23 | remoteConfig.shareAppIndex().toInt()
24 | } else {
25 | -1
26 | }
27 | }.flowOn(dispatchers.io)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/ShouldAutoDownload.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers
2 |
3 | import com.kafka.data.dao.FileDao
4 | import com.kafka.data.dao.ItemDetailDao
5 | import kotlinx.coroutines.flow.Flow
6 | import kotlinx.coroutines.flow.flowOn
7 | import kotlinx.coroutines.flow.map
8 | import com.kafka.base.CoroutineDispatchers
9 | import com.kafka.base.domain.SubjectInteractor
10 | import javax.inject.Inject
11 |
12 | class ShouldAutoDownload @Inject constructor(
13 | private val dispatchers: CoroutineDispatchers,
14 | private val itemDetailDao: ItemDetailDao,
15 | private val fileDao: FileDao,
16 | ) : SubjectInteractor() {
17 |
18 | override fun createObservable(params: Param): Flow {
19 | return itemDetailDao.observeItemDetail(params.itemId)
20 | .map { itemDetail ->
21 | val file = fileDao.getOrNull(itemDetail?.primaryFile.orEmpty())
22 | shouldAutoDownload && (file?.size ?: 0) < 10000000L
23 | }.flowOn(dispatchers.io)
24 | }
25 |
26 | data class Param(val itemId: String)
27 | }
28 |
29 | const val shouldAutoDownload = false
30 |
--------------------------------------------------------------------------------
/domain/src/commonMain/kotlin/com/kafka/domain/observers/library/ObserveFavoriteStatus.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.observers.library
2 |
3 | import kotlinx.coroutines.flow.Flow
4 | import kotlinx.coroutines.flow.flowOn
5 | import kotlinx.coroutines.flow.map
6 | import com.kafka.base.CoroutineDispatchers
7 | import com.kafka.base.domain.SubjectInteractor
8 | import javax.inject.Inject
9 |
10 | class ObserveFavoriteStatus @Inject constructor(
11 | private val dispatchers: CoroutineDispatchers,
12 | private val observeFavorites: ObserveFavorites,
13 | ) : SubjectInteractor() {
14 |
15 | override fun createObservable(params: Params): Flow {
16 | return observeFavorites.execute(Unit)
17 | .map { it.map { it.itemId }.contains(params.itemId) }
18 | .flowOn(dispatchers.io)
19 | }
20 |
21 | data class Params(val itemId: String)
22 | }
23 |
--------------------------------------------------------------------------------
/domain/src/jvmMain/kotlin/com/kafka/domain/interactors/account/LogoutCredentialManager.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.account
2 |
3 | actual class LogoutCredentialManager {
4 | actual suspend operator fun invoke(context: Any?): Result {
5 | // todo: kmp implement
6 | return Result.failure(NotImplementedError())
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/domain/src/jvmMain/kotlin/com/kafka/domain/interactors/account/SignInWithGoogle.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.account
2 |
3 | import javax.inject.Inject
4 |
5 | actual class SignInWithGoogle @Inject constructor() {
6 | actual suspend operator fun invoke(params: Any?): Result {
7 | //todo: kmp implement
8 | return Result.failure(NotImplementedError())
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/domain/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/kafka/domain/interactors/account/LogoutCredentialManager.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.domain.interactors.account
2 |
3 | import android.content.Context
4 | import androidx.credentials.ClearCredentialStateRequest
5 | import androidx.credentials.CredentialManager
6 | import javax.inject.Inject
7 |
8 | actual class LogoutCredentialManager @Inject constructor() {
9 | actual suspend operator fun invoke(context: Any?): Result {
10 | try {
11 | CredentialManager.create(context as Context)
12 | .clearCredentialState(ClearCredentialStateRequest())
13 | return Result.success(Unit)
14 | } catch (e: Exception) {
15 | return Result.failure(e)
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/firebase.json:
--------------------------------------------------------------------------------
1 | {
2 | "storage": {
3 | "rules": "storage.rules"
4 | },
5 | "hosting": {
6 | "public": "public",
7 | "ignore": [
8 | "firebase.json",
9 | "**/.*",
10 | "**/node_modules/**"
11 | ],
12 | "rewrites": [
13 | {
14 | "source": "**",
15 | "destination": "/index.html"
16 | }
17 | ]
18 | },
19 | "firestore": {
20 | "rules": "firestore.rules",
21 | "indexes": "firestore.indexes.json"
22 | },
23 | "functions": [
24 | {
25 | "source": "functions",
26 | "codebase": "default",
27 | "ignore": [
28 | "node_modules",
29 | ".git",
30 | "firebase-debug.log",
31 | "firebase-debug.*.log",
32 | "*.local"
33 | ],
34 | "predeploy": [
35 | "npm --prefix \"$RESOURCE_DIR\" run lint"
36 | ]
37 | }
38 | ]
39 | }
40 |
--------------------------------------------------------------------------------
/firestore.indexes.json:
--------------------------------------------------------------------------------
1 | {
2 | "indexes": [],
3 | "fieldOverrides": []
4 | }
5 |
--------------------------------------------------------------------------------
/functions/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | es6: true,
4 | node: true,
5 | },
6 | parserOptions: {
7 | "ecmaVersion": 2018,
8 | },
9 | extends: [
10 | "eslint:recommended",
11 | "google",
12 | ],
13 | rules: {
14 | "no-restricted-globals": ["error", "name", "length"],
15 | "prefer-arrow-callback": "error",
16 | "quotes": ["error", "double", {"allowTemplateLiterals": true}],
17 | "no-trailing-spaces": "off",
18 | "eol-last": "off",
19 | "indent": "off",
20 | "max-len": "off",
21 | "quotes": "off",
22 | "comma-dangle": "off",
23 | "object-curly-spacing": "off",
24 | "semi": "off",
25 | "padded-blocks": "off",
26 | "no-multiple-empty-lines": "off",
27 | "require-jsdoc": "off",
28 | "arrow-parens": "off",
29 | "guard-for-in": "off",
30 | "no-unused-vars": "off",
31 | "no-dupe-keys": "off",
32 | "prefer-const": "off",
33 | "camelcase": "off",
34 | },
35 | overrides: [
36 | {
37 | files: ["**.spec.*"],
38 | env: {
39 | mocha: true,
40 | },
41 | rules: {},
42 | },
43 | ],
44 | globals: {},
45 | };
46 |
47 |
48 |
49 |
--------------------------------------------------------------------------------
/functions/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | *.local
--------------------------------------------------------------------------------
/functions/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "functions",
3 | "description": "Cloud Functions for Firebase",
4 | "scripts": {
5 | "lint": "eslint .",
6 | "serve": "firebase emulators:start --only functions",
7 | "shell": "firebase functions:shell",
8 | "start": "npm run shell",
9 | "deploy": "firebase deploy --only functions",
10 | "logs": "firebase functions:log"
11 | },
12 | "engines": {
13 | "node": "22"
14 | },
15 | "main": "index.js",
16 | "dependencies": {
17 | "@google-cloud/bigquery": "^7.9.0",
18 | "@google-cloud/storage": "^7.12.1",
19 | "axios": "^1.7.7",
20 | "firebase-admin": "^12.1.0",
21 | "firebase-functions": "^5.1.1",
22 | "natural": "^8.0.1"
23 | },
24 | "devDependencies": {
25 | "eslint": "^8.15.0",
26 | "eslint-config-google": "^0.14.0",
27 | "firebase-functions-test": "^3.1.0"
28 | },
29 | "private": true
30 | }
31 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/com/kafka/gradle/Android.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.gradle
2 |
3 | import com.android.build.gradle.BaseExtension
4 | import org.gradle.api.Project
5 | import org.gradle.kotlin.dsl.configure
6 | import org.gradle.kotlin.dsl.dependencies
7 |
8 | fun Project.configureAndroid() {
9 | android {
10 | compileSdkVersion(Versions.compileSdk)
11 |
12 | defaultConfig {
13 | minSdk = Versions.minSdk
14 | targetSdk = Versions.targetSdk
15 | }
16 |
17 | compileOptions {
18 | // https://developer.android.com/studio/write/java8-support
19 | isCoreLibraryDesugaringEnabled = true
20 | }
21 | }
22 |
23 | dependencies {
24 | // https://developer.android.com/studio/write/java8-support
25 | "coreLibraryDesugaring"(libs.findLibrary("tools.desugarjdklibs").get())
26 | }
27 | }
28 |
29 | private fun Project.android(action: BaseExtension.() -> Unit) =
30 | extensions.configure(action)
31 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/com/kafka/gradle/AndroidApplicationConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.gradle
2 |
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 |
6 | class AndroidApplicationConventionPlugin : Plugin {
7 | override fun apply(target: Project) {
8 | with(target) {
9 | with(pluginManager) {
10 | apply("com.android.application")
11 | apply("org.gradle.android.cache-fix")
12 | }
13 |
14 | configureAndroid()
15 | configureLauncherTasks()
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/com/kafka/gradle/AndroidApplicationLauncher.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.gradle
2 |
3 | import com.android.build.api.variant.ApplicationAndroidComponentsExtension
4 | import org.gradle.api.Project
5 | import org.gradle.configurationcache.extensions.capitalized
6 | import org.gradle.kotlin.dsl.configure
7 |
8 | fun Project.configureLauncherTasks() {
9 | androidComponents {
10 | onVariants { variant ->
11 | tasks.register("open${variant.name.capitalized()}") {
12 | dependsOn(tasks.named("install${variant.name.capitalized()}"))
13 |
14 | doLast {
15 | exec {
16 | commandLine =
17 | "adb shell monkey -p ${variant.applicationId.get()} -c android.intent.category.LAUNCHER 1".split(
18 | " "
19 | )
20 | }
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
27 | private fun Project.androidComponents(action: ApplicationAndroidComponentsExtension.() -> Unit) =
28 | extensions.configure(action)
29 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/com/kafka/gradle/AndroidLibraryConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.gradle
2 |
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 |
6 | class AndroidLibraryConventionPlugin : Plugin {
7 | override fun apply(target: Project) {
8 | with(target) {
9 | with(pluginManager) {
10 | apply("com.android.library")
11 | apply("org.gradle.android.cache-fix")
12 | }
13 |
14 | configureAndroid()
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/com/kafka/gradle/Kotlin.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.gradle
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.api.plugins.JavaPluginExtension
5 | import org.gradle.jvm.toolchain.JavaLanguageVersion
6 | import org.gradle.kotlin.dsl.configure
7 |
8 | fun Project.configureKotlin() {
9 | // Configure Java to use our chosen language level. Kotlin will automatically pick this up
10 | configureJava()
11 | }
12 |
13 | fun Project.configureJava() {
14 | java {
15 | toolchain {
16 | languageVersion.set(JavaLanguageVersion.of(17))
17 | }
18 | }
19 | }
20 |
21 | private fun Project.java(action: JavaPluginExtension.() -> Unit) =
22 | extensions.configure(action)
23 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/com/kafka/gradle/KotlinAndroidConventionPlugin.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.gradle
2 |
3 | import org.gradle.api.Plugin
4 | import org.gradle.api.Project
5 |
6 | class KotlinAndroidConventionPlugin : Plugin {
7 | override fun apply(target: Project) {
8 | with(target) {
9 | with(pluginManager) {
10 | apply("org.jetbrains.kotlin.android")
11 | }
12 |
13 | configureKotlin()
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/com/kafka/gradle/VersionCatalog.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.gradle
2 |
3 | import org.gradle.api.Project
4 | import org.gradle.api.artifacts.VersionCatalog
5 | import org.gradle.api.artifacts.VersionCatalogsExtension
6 | import org.gradle.kotlin.dsl.getByType
7 |
8 | internal val Project.libs: VersionCatalog
9 | get() = extensions.getByType().named("libs")
10 |
--------------------------------------------------------------------------------
/gradle/build-logic/convention/src/main/kotlin/com/kafka/gradle/Versions.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.gradle
2 |
3 | object Versions {
4 | const val compileSdk = 35
5 | const val minSdk = 24
6 | const val targetSdk = 34
7 | }
8 |
--------------------------------------------------------------------------------
/gradle/build-logic/gradle.properties:
--------------------------------------------------------------------------------
1 | # Gradle properties are not passed to included builds https://github.com/gradle/gradle/issues/2534
2 | org.gradle.parallel=true
3 | org.gradle.caching=true
4 | org.gradle.configureondemand=true
5 |
--------------------------------------------------------------------------------
/gradle/build-logic/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | dependencyResolutionManagement {
2 | repositories {
3 | mavenCentral()
4 | google()
5 | gradlePluginPortal()
6 | }
7 |
8 | versionCatalogs {
9 | create("libs") {
10 | from(files("../libs.versions.toml"))
11 | }
12 | }
13 | }
14 |
15 | buildCache {
16 | val remoteBuildCacheUrl =
17 | providers.gradleProperty("REMOTE_BUILD_CACHE_URL").orNull ?: return@buildCache
18 | val isCi = providers.environmentVariable("CI").isPresent
19 |
20 | local {
21 | isEnabled = !isCi
22 | }
23 |
24 | remote(HttpBuildCache::class) {
25 | url = uri(remoteBuildCacheUrl)
26 | isPush = isCi
27 |
28 | credentials {
29 | username = providers.gradleProperty("REMOTE_BUILD_CACHE_USERNAME").orNull
30 | password = providers.gradleProperty("REMOTE_BUILD_CACHE_PASSWORD").orNull
31 | }
32 | }
33 | }
34 |
35 | include(":convention")
36 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionBase=GRADLE_USER_HOME
2 | distributionPath=wrapper/dists
3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
4 | networkTimeout=10000
5 | validateDistributionUrl=true
6 | zipStoreBase=GRADLE_USER_HOME
7 | zipStorePath=wrapper/dists
--------------------------------------------------------------------------------
/maestro/homepage-scroll.yaml:
--------------------------------------------------------------------------------
1 | appId: com.kafka.user.debug
2 | ---
3 | - launchApp
4 | - assertVisible:
5 | id: homepage_feed_items
6 | - scrollUntilVisible:
7 | element: Romeo and Juliet
8 | - scrollUntilVisible:
9 | element: The Odyssey
10 |
--------------------------------------------------------------------------------
/maestro/item-detail.yaml:
--------------------------------------------------------------------------------
1 | appId: com.kafka.user
2 | ---
3 | - clearState
4 | - launchApp
5 | - assertVisible:
6 | id: homepage_feed_items
7 | - scrollUntilVisible:
8 | element: Romeo and Juliet
9 | - tapOn: Romeo and Juliet
10 | - tapOn:
11 | id: add_favorite
12 | - assertVisible:
13 | id: remove_favorite
14 | - tapOn:
15 | id: item_detail_description
16 | - assertVisible:
17 | id: item_detail_description_dialog
18 | - back
19 | - tapOn:
20 | id: download_files
21 | - assertVisible:
22 | id: download_files_screen
23 | - back
24 | - assertVisible:
25 | id: download_files
26 |
--------------------------------------------------------------------------------
/maestro/play-item.yaml:
--------------------------------------------------------------------------------
1 | appId: com.kafka.user
2 | ---
3 | - launchApp
4 | - assertVisible:
5 | id: homepage_feed_items
6 | - scrollUntilVisible:
7 | element: Romeo and Juliet
8 | - tapOn: Romeo and Juliet
9 | - tapOn: Play
10 | - assertVisible:
11 | id: mini_player
12 | - tapOn:
13 | id: mini_player
14 | - assertNotVisible:
15 | id: mini_player
16 | - assertVisible:
17 | id: playback_sheet
18 | - back
19 | - assertVisible:
20 | id: mini_player
21 | - assertVisible: William Shakespeare
22 | - scrollUntilVisible:
23 | element: A Midsummer Night's Dream
24 |
--------------------------------------------------------------------------------
/maestro/search.yaml:
--------------------------------------------------------------------------------
1 | appId: com.kafka.user
2 | ---
3 | - launchApp
4 | - assertVisible:
5 | id: homepage_feed_items
6 | - scrollUntilVisible:
7 | element: Romeo and Juliet
8 | - tapOn: Romeo and Juliet
9 | - tapOn: William Shakespeare
10 | - tapOn:
11 | id: search_clear
12 | #- tapOn:
13 | # id: search_widget
14 | #- inputText: "Kafka"
15 | #- pressKey: Enter todo: press search
16 | - assertVisible: A Midsummer Night's Dream
17 | - tapOn: A Midsummer Night's Dream
18 | - assertVisible: William Shakespeare
19 | - back
20 |
--------------------------------------------------------------------------------
/navigation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/navigation/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("com.kafka.compose")
4 | id("com.kafka.kotlin.multiplatform")
5 | alias(libs.plugins.kotlin.serialization)
6 | }
7 |
8 | kotlin {
9 | sourceSets {
10 | val commonMain by getting {
11 | dependencies {
12 | implementation(projects.base.annotations)
13 | implementation(projects.data.repo)
14 | implementation(projects.ui.common)
15 |
16 | implementation(compose.material)
17 |
18 | implementation(libs.kotlin.serialization)
19 | }
20 | }
21 |
22 | val jvmCommon by creating {
23 | dependsOn(commonMain)
24 | }
25 |
26 | val jvmMain by getting {
27 | dependsOn(jvmCommon)
28 | }
29 |
30 | val androidMain by getting {
31 | dependsOn(jvmCommon)
32 |
33 | dependencies {
34 | api(libs.androidx.navigation.compose)
35 | implementation(libs.compose.material.navigation)
36 | }
37 | }
38 | }
39 | }
40 |
41 | android {
42 | namespace = "com.kafka.navigation"
43 | }
44 |
--------------------------------------------------------------------------------
/navigation/src/commonMain/kotlin/com/kafka/navigation/NavigationModule.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.navigation
2 |
3 | import me.tatarka.inject.annotations.Provides
4 | import com.kafka.base.ApplicationScope
5 |
6 | interface NavigationModule {
7 | @Provides
8 | @ApplicationScope
9 | fun navigator(bind: NavigatorImpl): Navigator = bind
10 | }
11 |
--------------------------------------------------------------------------------
/navigation/src/commonMain/kotlin/com/kafka/navigation/deeplink/Config.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.navigation.deeplink
2 |
3 | object Config {
4 | const val BASE_URL = "https://kafkaarchives.org/"
5 | const val BASE_URL_ALT = "https://kafka-books.web.app/"
6 | const val PLAY_STORE_URL = "https://play.google.com/store/apps/details?id=com.kafka.user"
7 |
8 | fun archiveDetailUrl(itemId: String) = "https://archive.org/details/$itemId"
9 | }
10 |
--------------------------------------------------------------------------------
/navigation/src/commonMain/kotlin/com/kafka/navigation/deeplink/DeepLinks.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.navigation.deeplink
2 |
3 | import com.kafka.navigation.deeplink.Config.BASE_URL
4 | import com.kafka.navigation.graph.Screen
5 |
6 | object DeepLinks {
7 | fun find(screen: Screen) = when (screen) {
8 | is Screen.ItemDetail -> "${BASE_URL}/item/${screen.itemId}"
9 | else -> ""
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/navigation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/navigation/src/main/java/com/kafka/navigation/RememberBottomSheetNavigator.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.navigation
2 |
3 | import androidx.compose.animation.core.AnimationSpec
4 | import androidx.compose.animation.core.SpringSpec
5 | //noinspection UsingMaterialAndMaterial3Libraries
6 | import androidx.compose.material.ModalBottomSheetValue
7 | //noinspection UsingMaterialAndMaterial3Libraries
8 | import androidx.compose.material.navigation.BottomSheetNavigator
9 | //noinspection UsingMaterialAndMaterial3Libraries
10 | import androidx.compose.material.rememberModalBottomSheetState
11 | import androidx.compose.runtime.Composable
12 | import androidx.compose.runtime.remember
13 |
14 | @Composable
15 | fun rememberBottomSheetNavigator(
16 | animationSpec: AnimationSpec = SpringSpec(),
17 | skipHalfExpanded: Boolean = true,
18 | ): BottomSheetNavigator {
19 | val sheetState = rememberModalBottomSheetState(
20 | initialValue = ModalBottomSheetValue.Hidden,
21 | animationSpec = animationSpec,
22 | confirmValueChange = { true },
23 | skipHalfExpanded = skipHalfExpanded,
24 | )
25 |
26 | return remember(sheetState) {
27 | BottomSheetNavigator(sheetState = sheetState)
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/facebook.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/instagram.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/public/images/whatsapp.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/release/clean-secrets.sh:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | # Delete Release key
18 | rm -f release/app-release.jks
19 |
20 | # Delete Play Store key
21 | rm -f release/play-account.json
22 |
23 | rm -f app/google-services.json
24 |
--------------------------------------------------------------------------------
/release/decrypt-secrets.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Copyright 2021 Google LLC
4 | #
5 | # Licensed under the Apache License, Version 2.0 (the "License");
6 | # you may not use this file except in compliance with the License.
7 | # You may obtain a copy of the License at
8 | #
9 | # https://www.apache.org/licenses/LICENSE-2.0
10 | #
11 | # Unless required by applicable law or agreed to in writing, software
12 | # distributed under the License is distributed on an "AS IS" BASIS,
13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | # See the License for the specific language governing permissions and
15 | # limitations under the License.
16 |
17 | decrypt() {
18 | PASSPHRASE=$1
19 | INPUT=$2
20 | OUTPUT=$3
21 | gpg --quiet --batch --yes --decrypt --passphrase="$PASSPHRASE" --output $OUTPUT $INPUT
22 | }
23 |
24 | if [[ ! -z "$ENCRYPT_KEY" ]]; then
25 | # Decrypt Release key
26 | decrypt ${ENCRYPT_KEY} release/app-release.gpg release/app-release.jks
27 | # Decrypt Play Store key
28 | decrypt ${ENCRYPT_KEY} release/play-account.gpg release/play-account.json
29 | # Decrypt Google Services key
30 | decrypt ${ENCRYPT_KEY} release/google-services.gpg app/google-services.json
31 | else
32 | echo "ENCRYPT_KEY is empty"
33 | fi
--------------------------------------------------------------------------------
/release/google-services.gpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/release/google-services.gpg
--------------------------------------------------------------------------------
/spotless/copyright.txt:
--------------------------------------------------------------------------------
1 | // Copyright $YEAR, Vipul Kumar and Kafka contributors
2 | // SPDX-License-Identifier: Apache-2.0
3 |
4 |
--------------------------------------------------------------------------------
/spotless/spotless.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: "com.diffplug.spotless"
2 | spotless {
3 | java {
4 | target '**/*.java'
5 | googleJavaFormat().aosp()
6 | removeUnusedImports()
7 | trimTrailingWhitespace()
8 | indentWithSpaces()
9 | endWithNewline()
10 | }
11 | kotlin {
12 | target "**/*.kt"
13 | trimTrailingWhitespace()
14 | ktlint()
15 | indentWithSpaces()
16 | endWithNewline()
17 | }
18 | }
--------------------------------------------------------------------------------
/storage.rules:
--------------------------------------------------------------------------------
1 | rules_version = '2';
2 |
3 | // Craft rules based on data in your Firestore database
4 | // allow write: if firestore.get(
5 | // /databases/(default)/documents/users/$(request.auth.uid)).data.isAdmin;
6 | service firebase.storage {
7 | match /b/{bucket}/o {
8 | match /{allPaths=**} {
9 | allow read, write: if false;
10 | }
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/ui/auth/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/auth/src/commonMain/composeResources/drawable/ic_kafka_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/ui/auth/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Please enter a valid email
4 | Please enter a valid password
5 | Password reset link has been sent to your email
6 | Forgot password?
7 | Login
8 | Sign up
9 | Continue with Google
10 |
11 |
--------------------------------------------------------------------------------
/ui/auth/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/ui/auth/src/main/res/drawable/ic_kafka_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/ui/auth/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Please enter a valid email
4 | Please enter a valid password
5 | Password reset link has been sent to your email
6 | Forgot password?
7 | Login
8 | Sign up
9 | Continue with Google
10 |
11 |
--------------------------------------------------------------------------------
/ui/common/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/composeResources/drawable/ic_kafka_logo.xml:
--------------------------------------------------------------------------------
1 |
6 |
11 |
14 |
18 |
19 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/composeResources/values/string.xml:
--------------------------------------------------------------------------------
1 |
2 | Unknown error
3 | Retry
4 |
5 | Library
6 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/kotlin/com/kafka/common/Clickable.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common
2 |
3 | import androidx.compose.foundation.Indication
4 | import androidx.compose.foundation.clickable
5 | import androidx.compose.foundation.interaction.MutableInteractionSource
6 | import androidx.compose.runtime.remember
7 | import androidx.compose.ui.Modifier
8 | import androidx.compose.ui.composed
9 | import androidx.compose.ui.semantics.Role
10 |
11 | fun Modifier.simpleClickable(
12 | interactionSource: MutableInteractionSource? = null,
13 | indication: Indication? = null,
14 | onClick: () -> Unit,
15 | ) = composed {
16 | clickable(
17 | onClick = onClick,
18 | role = Role.Button,
19 | indication = indication,
20 | interactionSource = interactionSource ?: remember { MutableInteractionSource() }
21 | )
22 | }
23 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/kotlin/com/kafka/common/ContextExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | expect fun getContext(): Any?
7 |
8 | expect fun getActivity(context: Any?): Any?
9 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/kotlin/com/kafka/common/LazyList.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common
2 |
3 | import androidx.compose.foundation.lazy.LazyListState
4 | import androidx.compose.ui.unit.Dp
5 | import androidx.compose.ui.unit.dp
6 |
7 | val LazyListState.elevation: Dp
8 | get() = if (firstVisibleItemIndex == 0) {
9 | // For the first element, use the minimum of scroll offset and default elevation
10 | // i.e. a value between 0 and 4.dp
11 | minOf(firstVisibleItemScrollOffset.toFloat().dp, 24.dp)
12 | } else {
13 | // If not the first element, always set elevation and show the shadow
14 | 24.dp
15 | }
16 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/kotlin/com/kafka/common/adaptive/LazyGrid.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common.adaptive
2 |
3 | import androidx.compose.foundation.lazy.grid.GridItemSpan
4 | import androidx.compose.foundation.lazy.grid.LazyGridItemScope
5 | import androidx.compose.foundation.lazy.grid.LazyGridScope
6 | import androidx.compose.foundation.lazy.grid.items
7 | import androidx.compose.runtime.Composable
8 |
9 | fun LazyGridScope.fullSpanItem(
10 | key: Any? = null,
11 | contentType: Any? = null,
12 | content: @Composable LazyGridItemScope.() -> Unit,
13 | ) {
14 | item(
15 | key = key,
16 | span = { GridItemSpan(maxLineSpan) },
17 | contentType = contentType,
18 | content = content,
19 | )
20 | }
21 |
22 | fun LazyGridScope.fullSpanItems(
23 | items: List,
24 | key: ((item: T) -> Any)? = null,
25 | contentType: (item: T) -> Any? = { null },
26 | itemContent: @Composable LazyGridItemScope.(item: T) -> Unit
27 | ) {
28 | items(
29 | items = items,
30 | key = key,
31 | span = { GridItemSpan(maxLineSpan) },
32 | contentType = contentType,
33 | itemContent = itemContent,
34 | )
35 | }
36 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/kotlin/com/kafka/common/adaptive/WindowSizeClass.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common.adaptive
2 |
3 | import androidx.compose.material3.adaptive.currentWindowAdaptiveInfo
4 | import androidx.compose.runtime.Composable
5 | import androidx.window.core.layout.WindowWidthSizeClass
6 |
7 | @Composable
8 | fun windowSizeClass() = currentWindowAdaptiveInfo().windowSizeClass
9 |
10 | @Composable
11 | fun windowWidthSizeClass() = windowSizeClass().windowWidthSizeClass
12 |
13 | @Composable
14 | fun isCompactWidth() = windowWidthSizeClass().isCompact()
15 |
16 | @Composable
17 | fun isMediumWidth() = windowWidthSizeClass().isMedium()
18 |
19 | @Composable
20 | fun isExpandedWidth() = windowWidthSizeClass().isExpanded()
21 |
22 | fun WindowWidthSizeClass.isCompact() = this == WindowWidthSizeClass.COMPACT
23 | fun WindowWidthSizeClass.isNotCompact() = this != WindowWidthSizeClass.COMPACT
24 | fun WindowWidthSizeClass.isMedium() = this == WindowWidthSizeClass.MEDIUM
25 | fun WindowWidthSizeClass.isExpanded() = this == WindowWidthSizeClass.EXPANDED
26 | fun WindowWidthSizeClass.useWideLayout() = isExpanded()
27 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/kotlin/com/kafka/common/platform/CommonUiPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common.platform
2 |
3 | expect interface CommonUiPlatformComponent
4 |
5 | interface ShareUtils {
6 | fun shareText(text: String)
7 | }
8 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/kotlin/com/kafka/common/snackbar/UiMessage.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common.snackbar
2 |
3 | import com.kafka.networking.localizedMessage
4 |
5 | sealed class UiMessage {
6 | data class Plain(val value: String) : UiMessage()
7 | data class Error(val value: Throwable) : UiMessage()
8 |
9 | companion object {
10 | operator fun invoke(value: String) = Plain(value)
11 | }
12 | }
13 |
14 | fun Throwable?.toUiMessage() = when {
15 | else -> when (val message = this.localizedMessage()) {
16 | "Unknown error" -> UiMessage.Plain(
17 | this?.message ?: this?.javaClass?.simpleName ?: ""
18 | )
19 |
20 | else -> UiMessage.Plain(message)
21 | }
22 | }
23 |
24 | fun UiMessage.asString(): String = when (this) {
25 | is UiMessage.Plain -> value
26 | is UiMessage.Error -> value.localizedMessage()
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/kotlin/com/kafka/common/widgets/LocalSnackbarHostState.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common.widgets
2 |
3 | import androidx.compose.material3.SnackbarHostState
4 | import androidx.compose.runtime.staticCompositionLocalOf
5 |
6 | val LocalSnackbarHostState = staticCompositionLocalOf {
7 | error("No LocalSnackbarHostState provided")
8 | }
9 |
--------------------------------------------------------------------------------
/ui/common/src/commonMain/kotlin/com/kafka/common/widgets/Shadow.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common.widgets
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.composed
6 | import androidx.compose.ui.draw.shadow
7 | import androidx.compose.ui.graphics.Color
8 | import androidx.compose.ui.graphics.RectangleShape
9 | import androidx.compose.ui.graphics.Shape
10 | import androidx.compose.ui.unit.Dp
11 | import androidx.compose.ui.unit.dp
12 | import ui.common.theme.theme.shadowMaterial
13 |
14 | fun Modifier.shadowMaterial(
15 | elevation: Dp,
16 | shape: Shape = RectangleShape,
17 | clip: Boolean = elevation > 0.dp,
18 | ambientColor: Color? = null,
19 | spotColor: Color? = null,
20 | ) = composed {
21 | shadow(
22 | elevation = elevation,
23 | shape = shape,
24 | clip = clip,
25 | ambientColor = ambientColor ?: MaterialTheme.colorScheme.shadowMaterial,
26 | spotColor = spotColor ?: MaterialTheme.colorScheme.shadowMaterial
27 | )
28 | }
29 |
--------------------------------------------------------------------------------
/ui/common/src/debug/res/values/string.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/ui/common/src/jvmMain/kotlin/com/kafka/common/ContextExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | actual fun getContext(): Any? = null
7 |
8 | actual fun getActivity(context: Any?): Any? = null
9 |
--------------------------------------------------------------------------------
/ui/common/src/jvmMain/kotlin/com/kafka/common/platform/CommonUiPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common.platform
2 |
3 | import com.kafka.base.ApplicationScope
4 | import me.tatarka.inject.annotations.Provides
5 |
6 | actual interface CommonUiPlatformComponent {
7 | @ApplicationScope
8 | @Provides
9 | fun provideShareUtils() = object : ShareUtils {
10 | override fun shareText(text: String) {
11 | // todo: kmp implement this
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/ui/common/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/common/src/main/java/com/kafka/common/platform/CommonUiPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common.platform
2 |
3 | import android.app.Application
4 | import com.kafka.base.ApplicationScope
5 | import com.kafka.common.shareText
6 | import me.tatarka.inject.annotations.Provides
7 |
8 | actual interface CommonUiPlatformComponent {
9 | @ApplicationScope
10 | @Provides
11 | fun provideShareUtils(application: Application) = object : ShareUtils {
12 | override fun shareText(text: String) {
13 | application.shareText(text)
14 | }
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/ui/common/src/main/java/com/kafka/common/test/TestTags.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.common.test
2 |
3 | import androidx.compose.ui.Modifier
4 | import androidx.compose.ui.platform.testTag
5 | import androidx.compose.ui.semantics.semantics
6 | import androidx.compose.ui.semantics.testTagsAsResourceId
7 |
8 | fun Modifier.testTagUi(tag: String, testTagsAsResource: Boolean = true) = this
9 | .semantics { testTagsAsResourceId = testTagsAsResource }
10 | .testTag(tag)
11 |
--------------------------------------------------------------------------------
/ui/components/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/components/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Back
4 | Download
5 | Change layout
6 | Delete download
7 | Download Queued
8 | Pause download
9 | Resume download
10 | Retry download
11 | Explicit
12 | Download
13 | Beta
14 |
15 |
--------------------------------------------------------------------------------
/ui/components/src/commonMain/kotlin/com/kafka/ui/components/Labels.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.Text
5 | import androidx.compose.runtime.Composable
6 | import androidx.compose.ui.Modifier
7 | import androidx.compose.ui.text.AnnotatedString
8 | import androidx.compose.ui.text.style.TextOverflow
9 |
10 | @Composable
11 | fun LabelMedium(
12 | text: String,
13 | modifier: Modifier = Modifier,
14 | maxLines: Int = Int.MAX_VALUE,
15 | overflow: TextOverflow = TextOverflow.Clip
16 | ) {
17 | LabelMedium(
18 | text = AnnotatedString(text),
19 | modifier = modifier,
20 | maxLines = maxLines,
21 | overflow = overflow
22 | )
23 | }
24 |
25 | @Composable
26 | fun LabelMedium(
27 | text: AnnotatedString,
28 | modifier: Modifier = Modifier,
29 | maxLines: Int = Int.MAX_VALUE,
30 | overflow: TextOverflow = TextOverflow.Clip
31 | ) {
32 | Text(
33 | text = text,
34 | modifier = modifier,
35 | style = MaterialTheme.typography.titleMedium,
36 | maxLines = maxLines,
37 | overflow = overflow
38 | )
39 | }
40 |
--------------------------------------------------------------------------------
/ui/components/src/commonMain/kotlin/com/kafka/ui/components/item/DownloadStatusIcons.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components.item
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.kafka.data.feature.item.DownloadInfo
5 |
6 | @Composable
7 | expect fun DownloadStatusIcons(downloadInfo: DownloadInfo)
--------------------------------------------------------------------------------
/ui/components/src/commonMain/kotlin/com/kafka/ui/components/item/SummaryAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components.item
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.Modifier
6 | import androidx.compose.ui.graphics.Color
7 |
8 | @Composable
9 | expect fun SummaryAnimation(
10 | modifier: Modifier = Modifier,
11 | color: Color = MaterialTheme.colorScheme.secondary,
12 | )
13 |
--------------------------------------------------------------------------------
/ui/components/src/commonMain/kotlin/com/kafka/ui/components/material/TextField.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components.material
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.material3.OutlinedTextFieldDefaults
5 | import androidx.compose.material3.TextFieldColors
6 | import androidx.compose.runtime.Composable
7 |
8 | object OutlinedTextFieldDefaults {
9 | @Composable
10 | fun colors(): TextFieldColors = OutlinedTextFieldDefaults.colors(
11 | focusedLabelColor = MaterialTheme.colorScheme.secondary,
12 | unfocusedLabelColor = MaterialTheme.colorScheme.secondary,
13 | unfocusedContainerColor = MaterialTheme.colorScheme.background,
14 | focusedContainerColor = MaterialTheme.colorScheme.background,
15 | focusedBorderColor = MaterialTheme.colorScheme.primary,
16 | unfocusedBorderColor = MaterialTheme.colorScheme.surfaceVariant
17 | )
18 | }
19 |
--------------------------------------------------------------------------------
/ui/components/src/commonMain/kotlin/com/kafka/ui/components/progress/DownloadAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components.progress
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 |
6 | @Composable
7 | expect fun DownloadAnimation(modifier: Modifier = Modifier)
8 |
--------------------------------------------------------------------------------
/ui/components/src/commonMain/kotlin/com/kafka/ui/components/progress/ProgressIndicator.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components.progress
2 |
3 | import androidx.compose.foundation.background
4 | import androidx.compose.foundation.layout.Box
5 | import androidx.compose.foundation.layout.fillMaxSize
6 | import androidx.compose.foundation.layout.padding
7 | import androidx.compose.material3.MaterialTheme
8 | import androidx.compose.runtime.Composable
9 | import androidx.compose.ui.Alignment
10 | import androidx.compose.ui.Modifier
11 | import androidx.compose.ui.unit.dp
12 | import androidx.compose.ui.zIndex
13 |
14 | @Composable
15 | fun FullScreenProgressBar(modifier: Modifier = Modifier, show: Boolean = true) {
16 | if (show) {
17 | Box(
18 | modifier = modifier
19 | .fillMaxSize()
20 | .background(MaterialTheme.colorScheme.background)
21 | .padding(48.dp)
22 | .zIndex(2f)
23 | ) {
24 | InfiniteProgressBar(Modifier.align(Alignment.Center))
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ui/components/src/jvmMain/kotlin/com/kafka/ui/components/item/DownloadStatusIcons.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components.item
2 |
3 | import androidx.compose.runtime.Composable
4 | import com.kafka.data.feature.item.DownloadInfo
5 |
6 | @Composable
7 | actual fun DownloadStatusIcons(downloadInfo: DownloadInfo) {}
--------------------------------------------------------------------------------
/ui/components/src/jvmMain/kotlin/com/kafka/ui/components/item/SummaryAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components.item
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.graphics.Color
6 |
7 | @Composable
8 | actual fun SummaryAnimation(modifier: Modifier, color: Color) {}
9 |
--------------------------------------------------------------------------------
/ui/components/src/jvmMain/kotlin/com/kafka/ui/components/progress/DownloadAnimation.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components.progress
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 |
6 | @Composable
7 | actual fun DownloadAnimation(modifier: Modifier) {
8 | // todo: kmp implement
9 | }
10 |
--------------------------------------------------------------------------------
/ui/components/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/components/src/main/java/com/kafka/ui/components/animation/colorFilterDynamicProperty.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.ui.components.animation
2 |
3 | import androidx.compose.material3.MaterialTheme
4 | import androidx.compose.runtime.Composable
5 | import androidx.compose.ui.graphics.Color
6 | import androidx.compose.ui.graphics.toArgb
7 | import com.airbnb.lottie.LottieProperty
8 | import com.airbnb.lottie.SimpleColorFilter
9 | import com.airbnb.lottie.compose.rememberLottieDynamicProperties
10 | import com.airbnb.lottie.compose.rememberLottieDynamicProperty
11 |
12 | @Composable
13 | fun colorFilterDynamicProperty(color: Color = MaterialTheme.colorScheme.secondary) =
14 | rememberLottieDynamicProperties(
15 | rememberLottieDynamicProperty(
16 | property = LottieProperty.COLOR_FILTER,
17 | value = SimpleColorFilter(color.toArgb()),
18 | keyPath = arrayOf(
19 | "**",
20 | )
21 | ),
22 | )
23 |
--------------------------------------------------------------------------------
/ui/components/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Back
4 | Download
5 | Change layout
6 | Delete download
7 | Download Queued
8 | Pause download
9 | Resume download
10 | Retry download
11 | Explicit
12 | Download
13 | Beta
14 |
15 |
--------------------------------------------------------------------------------
/ui/downloader/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("com.kafka.compose")
4 | id("com.kafka.kotlin.multiplatform")
5 | }
6 |
7 | kotlin {
8 | sourceSets {
9 | val commonMain by getting {
10 | dependencies {
11 | api(projects.core.downloader)
12 | implementation(projects.ui.common)
13 | implementation(projects.ui.theme)
14 |
15 | implementation(compose.components.resources)
16 | implementation(compose.material3)
17 | }
18 | }
19 |
20 | val jvmCommon by creating {
21 | dependsOn(commonMain)
22 | }
23 |
24 | val jvmMain by getting {
25 | dependsOn(jvmCommon)
26 | }
27 |
28 | val androidMain by getting {
29 | dependsOn(jvmCommon)
30 |
31 | dependencies {
32 | implementation(libs.androidx.activity.compose)
33 | }
34 | }
35 | }
36 | }
37 |
38 | android {
39 | namespace = "com.kafka.ui.downloader"
40 | }
41 |
--------------------------------------------------------------------------------
/ui/downloader/src/commonMain/kotlin/tm/alashow/datmusic/ui/downloader/DownloaderHost.kt:
--------------------------------------------------------------------------------
1 | package tm.alashow.datmusic.ui.downloader
2 |
3 | import androidx.compose.runtime.Composable
4 | import tm.alashow.datmusic.downloader.Downloader
5 |
6 | @Composable
7 | expect fun DownloaderHost(
8 | downloader: Downloader,
9 | content: @Composable () -> Unit,
10 | )
11 |
--------------------------------------------------------------------------------
/ui/downloader/src/commonMain/kotlin/tm/alashow/datmusic/ui/downloader/LocalDownloader.kt:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2022, Alashov Berkeli
3 | * All rights reserved.
4 | */
5 | package tm.alashow.datmusic.ui.downloader
6 |
7 | import androidx.compose.runtime.staticCompositionLocalOf
8 | import tm.alashow.datmusic.downloader.Downloader
9 |
10 | val LocalDownloader = staticCompositionLocalOf {
11 | error("LocalDownloader not provided")
12 | }
13 |
--------------------------------------------------------------------------------
/ui/downloader/src/jvmMain/kotlin/tm/alashow/datmusic/ui/downloader/DownloaderHost.kt:
--------------------------------------------------------------------------------
1 | package tm.alashow.datmusic.ui.downloader
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.runtime.CompositionLocalProvider
5 | import tm.alashow.datmusic.downloader.Downloader
6 |
7 | @Composable
8 | actual fun DownloaderHost(
9 | downloader: Downloader,
10 | content: @Composable () -> Unit,
11 | ) {
12 | CompositionLocalProvider(LocalDownloader provides downloader) {
13 | content()
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/ui/downloader/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/ui/downloader/src/main/java/tm/alashow/datmusic/ui/downloader/WriteableOpenDocumentTree.kt:
--------------------------------------------------------------------------------
1 | package tm.alashow.datmusic.ui.downloader
2 |
3 | import android.content.Context
4 | import android.content.Intent
5 | import android.net.Uri
6 | import androidx.activity.result.contract.ActivityResultContracts
7 |
8 | class WriteableOpenDocumentTree : ActivityResultContracts.OpenDocumentTree() {
9 | override fun createIntent(context: Context, input: Uri?): Intent {
10 | return super.createIntent(context, input).addReadWriteFlags()
11 | }
12 | }
13 |
14 | private fun Intent.addReadWriteFlags() = apply {
15 | addFlags(
16 | Intent.FLAG_GRANT_READ_URI_PERMISSION
17 | or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
18 | or Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
19 | or Intent.FLAG_GRANT_PREFIX_URI_PERMISSION
20 | )
21 | }
22 |
--------------------------------------------------------------------------------
/ui/downloader/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Downloads location
4 | Select a folder location
5 | Next
6 |
7 |
--------------------------------------------------------------------------------
/ui/homepage/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/homepage/src/commonMain/kotlin/com/kafka/homepage/HomepageViewState.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.homepage
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.kafka.data.entities.Homepage
5 | import com.kafka.data.entities.User
6 | import com.kafka.common.snackbar.UiMessage
7 |
8 | @Immutable
9 | data class HomepageViewState(
10 | val homepage: Homepage = Homepage.Empty,
11 | val user: User? = null,
12 | val isLoading: Boolean = true,
13 | val appShareIndex: Int = -1,
14 | val message: UiMessage? = null
15 | ) {
16 | val isFullScreenError: Boolean
17 | get() = homepage.collection.isEmpty() && message != null
18 |
19 | val isFullScreenLoading: Boolean
20 | get() = homepage.collection.isEmpty() && isLoading
21 | }
22 |
--------------------------------------------------------------------------------
/ui/homepage/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/item/detail/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/item/detail/src/commonMain/kotlin/com/kafka/item/PreloadImages.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.item
2 |
3 | import coil3.ImageLoader
4 | import coil3.PlatformContext
5 | import coil3.request.ImageRequest
6 | import coil3.size.Size
7 | import com.kafka.data.entities.Item
8 | import kotlinx.coroutines.Dispatchers
9 | import kotlinx.coroutines.withContext
10 |
11 | suspend fun preloadImages(context: PlatformContext, items: List- ?, size: Int = 200) {
12 | withContext(Dispatchers.IO) {
13 | items?.map { it.coverImage }?.forEach { image ->
14 | val request = ImageRequest.Builder(context)
15 | .data(image)
16 | .size(Size(size, size))
17 | .build()
18 |
19 | ImageLoader.Builder(context).build().enqueue(request)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ui/item/detail/src/commonMain/kotlin/com/kafka/item/files/FilesViewState.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.item.files
2 |
3 | import androidx.compose.runtime.Immutable
4 | import com.kafka.data.entities.File
5 | import com.kafka.data.feature.item.ItemWithDownload
6 | import kotlinx.collections.immutable.ImmutableList
7 | import kotlinx.collections.immutable.persistentListOf
8 |
9 | @Immutable
10 | data class FilesViewState(
11 | val title: String = "",
12 | val downloads: List = emptyList(),
13 | val files: List = emptyList(),
14 | val filteredFiles: List = emptyList(),
15 | val actionLabels: ImmutableList = persistentListOf(),
16 | val isLoading: Boolean = false,
17 | val downloadsWarningMessage: String = ""
18 | )
19 |
--------------------------------------------------------------------------------
/ui/item/detail/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/library/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/library/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Your favorite items will appear here
4 | Your downloaded items will appear here
5 | Downloads are saved on your device. They can be opened or shared freely using any files app.
6 | Log in to sync your favorite items to your account.
7 |
8 |
--------------------------------------------------------------------------------
/ui/library/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/profile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/profile/src/commonMain/kotlin/com/kafka/profile/NotificationMenuItem.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.profile
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | expect fun NotificationMenuItem(logClick: () -> Unit, dismiss: () -> Unit)
7 |
--------------------------------------------------------------------------------
/ui/profile/src/jvmMain/kotlin/com/kafka/profile/NotificationMenuItem.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.profile
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | actual fun NotificationMenuItem(logClick: () -> Unit, dismiss: () -> Unit) {
7 | // do nothing
8 | }
9 |
--------------------------------------------------------------------------------
/ui/profile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/ui/reader/epub/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/reader/epub/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Background
4 |
5 |
--------------------------------------------------------------------------------
/ui/reader/epub/src/commonMain/kotlin/com/kafka/reader/epub/domain/ParseEbook.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.reader.epub.domain
2 |
3 | import com.kafka.base.CoroutineDispatchers
4 | import com.kafka.base.domain.ResultInteractor
5 | import com.kafka.reader.epub.models.EpubBook
6 | import com.kafka.reader.epub.parser.EpubParser
7 | import kotlinx.coroutines.withContext
8 | import javax.inject.Inject
9 |
10 | /**
11 | * Parses an epub book from a given path on local storage.
12 | * */
13 | class ParseEbook @Inject constructor(
14 | private val epubParser: EpubParser,
15 | private val dispatchers: CoroutineDispatchers
16 | ): ResultInteractor() {
17 | override suspend fun doWork(params: String): EpubBook {
18 | return withContext(dispatchers.io) {
19 | epubParser.createEpubBook(params)
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/ui/reader/epub/src/commonMain/kotlin/com/kafka/reader/epub/models/EpubChapter.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (c) [2022 - Present] Stɑrry Shivɑm
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 |
18 | package com.kafka.reader.epub.models
19 |
20 | /**
21 | * Represents a chapter in an epub book.
22 | *
23 | * @param absPath The absolute path of the chapter.
24 | * @param title The title of the chapter.
25 | * @param body The body of the chapter.
26 | */
27 | data class EpubChapter(
28 | val absPath: String,
29 | val title: String,
30 | val body: String
31 | )
--------------------------------------------------------------------------------
/ui/reader/epub/src/commonMain/kotlin/com/kafka/reader/epub/parser/EpubParserException.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.reader.epub.parser
2 |
3 | /**
4 | * Exception thrown when an error occurs while parsing an EPUB file.
5 | *
6 | * @param message The error message.
7 | */
8 | class EpubParserException(message: String) : Exception(message)
9 |
--------------------------------------------------------------------------------
/ui/reader/epub/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/ui/reader/online/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/reader/online/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | "\n Check out %1$s on Kafka\n \n %2$s\n "
4 | Download Complete
5 | Continue reading offline for better experience.
6 | Open Offline
7 | Close
8 | Download
9 |
10 |
--------------------------------------------------------------------------------
/ui/reader/online/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/ui/reader/pdf/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/reader/pdf/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Download is in progress. You can leave this screen and we will notify you when it\'s complete.
4 | "\n Check out %1$s on Kafka\n \n %2$s\n "
5 | Download Complete
6 | Open Offline
7 | Close
8 | Download
9 |
10 |
--------------------------------------------------------------------------------
/ui/reader/pdf/src/commonMain/kotlin/com/kafka/reader/pdf/PdfViewer.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.reader.pdf
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 |
6 | @Composable
7 | expect fun PdfViewer(pdfState: PdfState, modifier: Modifier = Modifier)
8 |
9 | data class PdfState(
10 | val uri: String,
11 | val initialPage: Int = 0,
12 | val onError: (Throwable) -> Unit = {},
13 | val onPageChange: (Int) -> Unit = { _ -> }
14 | )
15 |
16 | const val MaxZoom = 10f
17 |
--------------------------------------------------------------------------------
/ui/reader/pdf/src/jvmMain/kotlin/com/kafka/reader/pdf/PdfViewer.jvm.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.reader.pdf
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 |
6 | @Composable
7 | actual fun PdfViewer(pdfState: PdfState, modifier: Modifier) {
8 | // todo: kmp implementation
9 | }
10 |
--------------------------------------------------------------------------------
/ui/reader/pdf/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/ui/reader/pdf/src/main/java/com/kafka/reader/pdf/PdfViewer.android.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.reader.pdf
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.Modifier
5 | import androidx.compose.ui.viewinterop.AndroidView
6 | import androidx.core.net.toUri
7 | import com.github.barteksc.pdfviewer.PDFView
8 | import com.github.barteksc.pdfviewer.scroll.DefaultScrollHandle
9 |
10 | @Composable
11 | actual fun PdfViewer(pdfState: PdfState, modifier: Modifier) {
12 | AndroidView(
13 | modifier = modifier,
14 | factory = {
15 | PDFView(it, null).apply {
16 | maxZoom = MaxZoom
17 | fromUri(pdfState.uri.toUri())
18 | .defaultPage(pdfState.initialPage)
19 | .spacing(12)
20 | .onPageChange { page, _ -> pdfState.onPageChange(page) }
21 | .scrollHandle(DefaultScrollHandle(it))
22 | .onError { t -> pdfState.onError(t) }
23 | .load()
24 | }
25 | }
26 | ) { }
27 | }
28 |
--------------------------------------------------------------------------------
/ui/search/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/search/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Search...
4 | Recent searches
5 | Clear text
6 |
7 |
--------------------------------------------------------------------------------
/ui/search/src/commonMain/kotlin/com/kafka/search/SearchViewState.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.search
2 |
3 | import com.kafka.data.entities.Item
4 | import com.kafka.data.entities.RecentSearch
5 | import com.kafka.data.model.SearchFilter
6 |
7 | data class SearchViewState(
8 | val keyword: String = "",
9 | val selectedFilters: List = SearchFilter.all(),
10 | val items: List
- ? = null,
11 | val recentSearches: List? = null,
12 | val isLoading: Boolean = false
13 | ) {
14 | val canShowRecentSearches: Boolean
15 | get() = items.isNullOrEmpty()
16 | && !recentSearches.isNullOrEmpty() && !isLoading
17 | }
18 |
--------------------------------------------------------------------------------
/ui/search/src/commonMain/kotlin/com/kafka/search/widget/SpeechIcon.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.search.widget
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | expect fun SpeechIcon(onText: (String) -> Unit)
7 |
--------------------------------------------------------------------------------
/ui/search/src/jvmMain/kotlin/com/kafka/search/widget/SpeechIcon.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.search.widget
2 |
3 | import androidx.compose.runtime.Composable
4 |
5 | @Composable
6 | actual fun SpeechIcon(onText: (String) -> Unit) {
7 |
8 | }
9 |
--------------------------------------------------------------------------------
/ui/search/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/search/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | Try Saying Something
4 | Speech not supported
5 |
6 |
--------------------------------------------------------------------------------
/ui/shared/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/shared/src/commonMain/composeResources/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Kafka
3 | Cancel
4 |
5 | Play
6 | Library
7 | Search
8 | Home
9 |
10 | Update Available
11 | This version is outdated. Please update the app to continue using it.
12 | Update
13 | App update is available
14 |
15 |
--------------------------------------------------------------------------------
/ui/shared/src/commonMain/kotlin/com/kafka/shared/injection/PlayerComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.shared.injection
2 |
3 | import com.kafka.base.ApplicationScope
4 | import com.kafka.shared.playback.KafkaPlayerEventLogger
5 | import com.kafka.shared.playback.PlayerAudioDataSource
6 | import com.kafka.shared.playback.PlayerLogger
7 | import com.sarahang.playback.core.apis.AudioDataSource
8 | import com.sarahang.playback.core.apis.Logger
9 | import com.sarahang.playback.core.apis.PlayerEventLogger
10 | import com.sarahang.playback.core.injection.PlaybackCoreComponent
11 | import me.tatarka.inject.annotations.Provides
12 |
13 | @ApplicationScope
14 | interface PlayerComponent : PlaybackCoreComponent {
15 |
16 | @Provides
17 | @ApplicationScope
18 | fun audioDataSource(bind: PlayerAudioDataSource): AudioDataSource = bind
19 |
20 | @Provides
21 | @ApplicationScope
22 | fun playerEventLogger(bind: KafkaPlayerEventLogger): PlayerEventLogger = bind
23 |
24 | @Provides
25 | @ApplicationScope
26 | fun playerLogger(bind: PlayerLogger): Logger = bind
27 | }
28 |
--------------------------------------------------------------------------------
/ui/shared/src/commonMain/kotlin/com/kafka/shared/main/initializer/AppInitializers.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.shared.main.initializer
2 |
3 | import com.kafka.base.AppInitializer
4 | import com.kafka.remote.config.RemoteConfig
5 | import javax.inject.Inject
6 |
7 | expect class LoggerInitializer : AppInitializer
8 |
9 | expect class FirebaseInitializer : AppInitializer
10 |
11 | class RemoteConfigInitializer @Inject constructor(
12 | private val remoteConfig: RemoteConfig,
13 | ) : AppInitializer {
14 | override fun init() {
15 | remoteConfig
16 | }
17 | }
18 |
19 | class AppInitializers @Inject constructor(
20 | private val initializers: Set,
21 | ) {
22 | fun init() {
23 | initializers.forEach {
24 | it.init()
25 | }
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/ui/shared/src/commonMain/kotlin/com/kafka/shared/main/initializer/RemoteConfigLogger.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.shared.main.initializer
2 |
3 | import com.kafka.analytics.logger.Analytics
4 | import com.kafka.base.AppInitializer
5 | import com.kafka.base.ApplicationScope
6 | import com.kafka.remote.config.RECOMMENDATION_ROW_ENABLED
7 | import com.kafka.remote.config.RemoteConfig
8 | import com.kafka.remote.config.isRecommendationRowEnabled
9 | import javax.inject.Inject
10 |
11 | @ApplicationScope
12 | class RemoteConfigLogger @Inject constructor(
13 | private val remoteConfig: RemoteConfig,
14 | private val analytics: Analytics,
15 | ) : AppInitializer {
16 | override fun init() {
17 | analytics.log {
18 | remoteConfigValue(RECOMMENDATION_ROW_ENABLED, remoteConfig.isRecommendationRowEnabled())
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/ui/shared/src/commonMain/kotlin/com/kafka/shared/playback/PlayerLogger.kt:
--------------------------------------------------------------------------------
1 | @file:Suppress("ktlint:standard:filename")
2 |
3 | package com.kafka.shared.playback
4 |
5 | import com.kafka.analytics.logger.Analytics
6 | import com.kafka.analytics.logger.EventInfo
7 | import com.kafka.base.debug
8 | import com.kafka.base.errorLog
9 | import com.sarahang.playback.core.apis.Logger
10 | import com.sarahang.playback.core.apis.PlayerEventLogger
11 | import javax.inject.Inject
12 |
13 | class KafkaPlayerEventLogger @Inject constructor(
14 | private val analytics: Analytics,
15 | ) : PlayerEventLogger {
16 | override fun logEvent(event: String, data: Map) {
17 | analytics.log(EventInfo(event, data))
18 | }
19 | }
20 |
21 | class PlayerLogger @Inject constructor() : Logger {
22 | override fun i(message: String) = com.kafka.base.i { message }
23 |
24 | override fun d(message: String) = debug { message }
25 |
26 | override fun w(message: String) = com.kafka.base.w { message }
27 |
28 | override fun e(message: String) = errorLog { message }
29 |
30 | override fun e(throwable: Throwable, message: String) =
31 | errorLog(throwable) { message }
32 |
33 | }
34 |
--------------------------------------------------------------------------------
/ui/shared/src/jvmMain/kotlin/com/kafka/shared/DesktopApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.shared
2 |
3 | import com.kafka.base.ApplicationScope
4 | import com.kafka.base.ProcessLifetime
5 | import com.kafka.data.prefs.PreferencesStore
6 | import kotlinx.coroutines.CoroutineScope
7 | import kotlinx.coroutines.Dispatchers
8 | import kotlinx.coroutines.SupervisorJob
9 | import me.tatarka.inject.annotations.Component
10 | import me.tatarka.inject.annotations.Provides
11 |
12 | @Component
13 | @ApplicationScope
14 | abstract class DesktopApplicationComponent : SharedApplicationComponent {
15 | abstract val preferencesStore: PreferencesStore
16 |
17 | @Provides
18 | @ProcessLifetime
19 | fun provideLongLifetimeScope(): CoroutineScope {
20 | return CoroutineScope(SupervisorJob() + Dispatchers.Default)
21 | }
22 |
23 | companion object
24 | }
25 |
--------------------------------------------------------------------------------
/ui/shared/src/jvmMain/kotlin/com/kafka/shared/SharedPlatformApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.shared
2 |
3 | actual interface SharedPlatformApplicationComponent
4 |
--------------------------------------------------------------------------------
/ui/shared/src/jvmMain/kotlin/com/kafka/shared/injection/InitializersPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.shared.injection
2 |
3 | import com.kafka.base.AppInitializer
4 | import com.kafka.base.ApplicationScope
5 | import com.kafka.shared.main.initializer.FirebaseInitializer
6 | import com.kafka.shared.main.initializer.LoggerInitializer
7 | import me.tatarka.inject.annotations.IntoSet
8 | import me.tatarka.inject.annotations.Provides
9 |
10 | actual interface InitializersPlatformComponent {
11 | @Provides
12 | @ApplicationScope
13 | @IntoSet
14 | fun provideLoggerInitializer(bind: LoggerInitializer): AppInitializer = bind
15 |
16 | @Provides
17 | @ApplicationScope
18 | @IntoSet
19 | fun provideFirebaseInitializer(bind: FirebaseInitializer): AppInitializer = bind
20 | }
21 |
--------------------------------------------------------------------------------
/ui/shared/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/ui/shared/src/main/java/com/kafka/shared/SharedPlatformApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.shared
2 |
3 | actual interface SharedPlatformApplicationComponent
4 |
--------------------------------------------------------------------------------
/ui/shared/src/main/java/com/kafka/shared/injection/InitializersPlatformComponent.kt:
--------------------------------------------------------------------------------
1 | package com.kafka.shared.injection
2 |
3 | import com.kafka.base.AppInitializer
4 | import com.kafka.base.ApplicationScope
5 | import com.kafka.shared.main.initializer.FirebaseInitializer
6 | import com.kafka.shared.main.initializer.LoggerInitializer
7 | import me.tatarka.inject.annotations.IntoSet
8 | import me.tatarka.inject.annotations.Provides
9 |
10 | actual interface InitializersPlatformComponent {
11 | @Provides
12 | @ApplicationScope
13 | @IntoSet
14 | fun provideLoggerInitializer(bind: LoggerInitializer): AppInitializer = bind
15 |
16 | @Provides
17 | @ApplicationScope
18 | @IntoSet
19 | fun provideFirebaseInitializer(bind: FirebaseInitializer): AppInitializer = bind
20 | }
21 |
--------------------------------------------------------------------------------
/ui/summary/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/summary/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ui/theme/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/theme/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id("com.android.library")
3 | id("com.kafka.compose")
4 | id("com.kafka.kotlin.multiplatform")
5 | alias(libs.plugins.kotlin.serialization)
6 | }
7 |
8 | kotlin {
9 | sourceSets {
10 | val commonMain by getting {
11 | dependencies {
12 | implementation(projects.data.prefs)
13 |
14 | api(compose.components.resources)
15 | api(compose.material3)
16 | api(compose.runtime)
17 | api(compose.ui)
18 | }
19 | }
20 |
21 | val jvmCommon by creating {
22 | dependsOn(commonMain)
23 | }
24 |
25 | val jvmMain by getting {
26 | dependsOn(jvmCommon)
27 | }
28 |
29 | val androidMain by getting {
30 | dependsOn(jvmCommon)
31 |
32 | dependencies {
33 | implementation(libs.androidx.activity.compose)
34 | }
35 | }
36 | }
37 | }
38 |
39 | android {
40 | namespace = "ui.common.theme"
41 | }
42 |
--------------------------------------------------------------------------------
/ui/theme/src/commonMain/composeResources/font/inter_black.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/ui/theme/src/commonMain/composeResources/font/inter_black.ttf
--------------------------------------------------------------------------------
/ui/theme/src/commonMain/composeResources/font/inter_bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/ui/theme/src/commonMain/composeResources/font/inter_bold.ttf
--------------------------------------------------------------------------------
/ui/theme/src/commonMain/composeResources/font/inter_light.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/ui/theme/src/commonMain/composeResources/font/inter_light.ttf
--------------------------------------------------------------------------------
/ui/theme/src/commonMain/composeResources/font/inter_medium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/ui/theme/src/commonMain/composeResources/font/inter_medium.ttf
--------------------------------------------------------------------------------
/ui/theme/src/commonMain/composeResources/font/inter_regular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/ui/theme/src/commonMain/composeResources/font/inter_regular.ttf
--------------------------------------------------------------------------------
/ui/theme/src/commonMain/composeResources/font/inter_semibold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/vipulyaara/Kafka/ed77950cfc72e22498df7cb4c0a475bc778297d6/ui/theme/src/commonMain/composeResources/font/inter_semibold.ttf
--------------------------------------------------------------------------------
/ui/theme/src/commonMain/kotlin/ui/common/theme/theme/Platform.kt:
--------------------------------------------------------------------------------
1 | package ui.common.theme.theme
2 |
3 | import androidx.compose.material3.ColorScheme
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | internal expect fun colorScheme(
8 | useDarkColors: Boolean,
9 | useTrueContrast: Boolean
10 | ): ColorScheme
11 |
--------------------------------------------------------------------------------
/ui/theme/src/commonMain/kotlin/ui/common/theme/theme/Type.kt:
--------------------------------------------------------------------------------
1 | package ui.common.theme.theme
2 |
3 | import androidx.compose.runtime.Composable
4 | import androidx.compose.ui.text.font.FontFamily
5 | import androidx.compose.ui.text.font.FontWeight
6 | import kafka.ui.theme.generated.resources.Res
7 | import kafka.ui.theme.generated.resources.inter_black
8 | import kafka.ui.theme.generated.resources.inter_bold
9 | import kafka.ui.theme.generated.resources.inter_light
10 | import kafka.ui.theme.generated.resources.inter_medium
11 | import kafka.ui.theme.generated.resources.inter_regular
12 | import kafka.ui.theme.generated.resources.inter_semibold
13 | import org.jetbrains.compose.resources.Font
14 |
15 | val DefaultFont: FontFamily
16 | @Composable get() = FontFamily(
17 | Font(Res.font.inter_light, weight = FontWeight.Light),
18 | Font(Res.font.inter_regular, weight = FontWeight.Normal),
19 | Font(Res.font.inter_medium, weight = FontWeight.Medium),
20 | Font(Res.font.inter_semibold, weight = FontWeight.SemiBold),
21 | Font(Res.font.inter_bold, weight = FontWeight.Bold),
22 | Font(Res.font.inter_black, weight = FontWeight.Black),
23 | )
24 |
--------------------------------------------------------------------------------
/ui/theme/src/jvmMain/kotlin/ui/common/theme/theme/Platform.kt:
--------------------------------------------------------------------------------
1 | package ui.common.theme.theme
2 |
3 | import androidx.compose.material3.ColorScheme
4 | import androidx.compose.runtime.Composable
5 |
6 | @Composable
7 | internal actual fun colorScheme(
8 | useDarkColors: Boolean,
9 | useTrueContrast: Boolean
10 | ): ColorScheme = if (useDarkColors) {
11 | DarkAppColors
12 | } else {
13 | LightAppColors
14 | }
15 |
--------------------------------------------------------------------------------
/ui/webview/.gitignore:
--------------------------------------------------------------------------------
1 | /build
--------------------------------------------------------------------------------
/ui/webview/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------