├── iosApp ├── Configuration │ └── Config.xcconfig ├── iosApp │ ├── Assets.xcassets │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── AppIcon_iOS.png │ │ │ └── Contents.json │ │ └── AccentColor.colorset │ │ │ └── Contents.json │ ├── Preview Content │ │ └── Preview Assets.xcassets │ │ │ └── Contents.json │ ├── iOSApp.swift │ ├── Util │ │ └── Extensions.swift │ ├── ContentView.swift │ └── Info.plist └── iosApp.xcodeproj │ └── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── composeApp ├── src │ ├── androidMain │ │ ├── res │ │ │ ├── values │ │ │ │ ├── strings.xml │ │ │ │ ├── colors.xml │ │ │ │ └── themes.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.webp │ │ │ │ ├── ic_launcher_round.webp │ │ │ │ └── ic_launcher_foreground.webp │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ └── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ ├── kotlin │ │ │ ├── network │ │ │ │ ├── di │ │ │ │ │ └── ApiModule.android.kt │ │ │ │ └── NetworkClient.android.kt │ │ │ ├── com │ │ │ │ └── projects │ │ │ │ │ └── cinetracker │ │ │ │ │ ├── core │ │ │ │ │ └── CoreApplication.kt │ │ │ │ │ └── MainActivity.kt │ │ │ ├── common │ │ │ │ └── util │ │ │ │ │ └── platform │ │ │ │ │ ├── PlatformUtils.kt │ │ │ │ │ ├── ScreenSizeInfo.android.kt │ │ │ │ │ ├── DateUtils.kt │ │ │ │ │ ├── StatusBarUpdate.android.kt │ │ │ │ │ └── StringFormat.kt │ │ │ ├── core │ │ │ │ └── di │ │ │ │ │ └── KoinInitializer.android.kt │ │ │ └── database │ │ │ │ └── di │ │ │ │ └── DatabaseModule.android.kt │ │ └── AndroidManifest.xml │ ├── commonMain │ │ ├── kotlin │ │ │ ├── core │ │ │ │ ├── di │ │ │ │ │ ├── KoinInitializer.kt │ │ │ │ │ └── modules │ │ │ │ │ │ ├── InteractorModule.kt │ │ │ │ │ │ └── ViewModelModule.kt │ │ │ │ ├── ImageLoader.kt │ │ │ │ └── LanguageManager.kt │ │ │ ├── network │ │ │ │ ├── NetworkClient.kt │ │ │ │ ├── di │ │ │ │ │ ├── ApiModule.kt │ │ │ │ │ ├── ServiceModule.kt │ │ │ │ │ └── RepositoryModule.kt │ │ │ │ ├── models │ │ │ │ │ ├── content │ │ │ │ │ │ ├── common │ │ │ │ │ │ │ ├── ContentGenre.kt │ │ │ │ │ │ │ ├── ProductionCountry.kt │ │ │ │ │ │ │ ├── VideosByIdResponse.kt │ │ │ │ │ │ │ ├── ContentCreditsResponse.kt │ │ │ │ │ │ │ ├── VideoResponse.kt │ │ │ │ │ │ │ ├── WatchProvidersResponse.kt │ │ │ │ │ │ │ └── ContentCastResponse.kt │ │ │ │ │ │ ├── person │ │ │ │ │ │ │ ├── PersonImagesResponse.kt │ │ │ │ │ │ │ ├── PersonCreditsResponse.kt │ │ │ │ │ │ │ ├── PersonProfileResponse.kt │ │ │ │ │ │ │ └── CrewResponse.kt │ │ │ │ │ │ └── search │ │ │ │ │ │ │ └── ContentPagingResponse.kt │ │ │ │ │ └── ApiError.kt │ │ │ │ ├── util │ │ │ │ │ ├── Parameters.kt │ │ │ │ │ ├── Either.kt │ │ │ │ │ └── ApiResult.kt │ │ │ │ ├── repository │ │ │ │ │ ├── person │ │ │ │ │ │ ├── PersonRepository.kt │ │ │ │ │ │ └── PersonRepositoryImpl.kt │ │ │ │ │ ├── home │ │ │ │ │ │ ├── HomeRepository.kt │ │ │ │ │ │ └── HomeRepositoryImpl.kt │ │ │ │ │ ├── search │ │ │ │ │ │ ├── SearchRepository.kt │ │ │ │ │ │ └── SearchRepositoryImpl.kt │ │ │ │ │ ├── show │ │ │ │ │ │ └── ShowRepository.kt │ │ │ │ │ └── movie │ │ │ │ │ │ └── MovieRepository.kt │ │ │ │ └── services │ │ │ │ │ ├── person │ │ │ │ │ ├── PersonService.kt │ │ │ │ │ └── PersonServiceImpl.kt │ │ │ │ │ ├── home │ │ │ │ │ └── HomeService.kt │ │ │ │ │ ├── search │ │ │ │ │ └── SearchService.kt │ │ │ │ │ ├── show │ │ │ │ │ └── ShowService.kt │ │ │ │ │ └── movie │ │ │ │ │ └── MovieService.kt │ │ │ ├── database │ │ │ │ ├── di │ │ │ │ │ ├── DatabaseModule.kt │ │ │ │ │ ├── DatabaseRepositoryModule.kt │ │ │ │ │ └── DaoModule.kt │ │ │ │ ├── model │ │ │ │ │ ├── ListEntity.kt │ │ │ │ │ └── ContentEntity.kt │ │ │ │ ├── AppDatabase.kt │ │ │ │ ├── dao │ │ │ │ │ ├── ListEntityDao.kt │ │ │ │ │ └── ContentEntityDao.kt │ │ │ │ └── repository │ │ │ │ │ └── DatabaseRepository.kt │ │ │ ├── features │ │ │ │ ├── watchlist │ │ │ │ │ ├── ui │ │ │ │ │ │ ├── model │ │ │ │ │ │ │ ├── WatchlistItemAction.kt │ │ │ │ │ │ │ └── DefaultLists.kt │ │ │ │ │ │ ├── state │ │ │ │ │ │ │ ├── WatchlistSnackbarState.kt │ │ │ │ │ │ │ └── WatchlistState.kt │ │ │ │ │ │ └── components │ │ │ │ │ │ │ ├── WatchlistSortTypeItem.kt │ │ │ │ │ │ │ ├── ListRemovePopupMenu.kt │ │ │ │ │ │ │ └── WatchlistTabItem.kt │ │ │ │ │ ├── WatchlistScreen.kt │ │ │ │ │ └── events │ │ │ │ │ │ └── WatchlistEvent.kt │ │ │ │ ├── home │ │ │ │ │ ├── HomeScreen.kt │ │ │ │ │ ├── events │ │ │ │ │ │ └── HomeEvent.kt │ │ │ │ │ └── ui │ │ │ │ │ │ ├── state │ │ │ │ │ │ └── HomeState.kt │ │ │ │ │ │ └── components │ │ │ │ │ │ └── carousel │ │ │ │ │ │ └── ComingSoonContainer.kt │ │ │ │ ├── browse │ │ │ │ │ ├── BrowseScreen.kt │ │ │ │ │ ├── events │ │ │ │ │ │ └── BrowseEvent.kt │ │ │ │ │ ├── ui │ │ │ │ │ │ └── components │ │ │ │ │ │ │ └── MediaTypeTabItem.kt │ │ │ │ │ └── domain │ │ │ │ │ │ └── BrowseInteractor.kt │ │ │ │ ├── search │ │ │ │ │ ├── SearchScreen.kt │ │ │ │ │ ├── events │ │ │ │ │ │ └── SearchEvent.kt │ │ │ │ │ ├── domain │ │ │ │ │ │ └── SearchInteractor.kt │ │ │ │ │ └── ui │ │ │ │ │ │ └── components │ │ │ │ │ │ └── SearchTypeFilterItem.kt │ │ │ │ └── details │ │ │ │ │ ├── state │ │ │ │ │ ├── DetailsSnackbarState.kt │ │ │ │ │ └── DetailsState.kt │ │ │ │ │ ├── events │ │ │ │ │ └── DetailsEvents.kt │ │ │ │ │ ├── DetailsScreen.kt │ │ │ │ │ ├── util │ │ │ │ │ └── Extensions.kt │ │ │ │ │ └── ui │ │ │ │ │ └── components │ │ │ │ │ └── moreoptions │ │ │ │ │ ├── MoreOptionsTabItem.kt │ │ │ │ │ └── MoreOptionsTab.kt │ │ │ ├── common │ │ │ │ ├── util │ │ │ │ │ ├── platform │ │ │ │ │ │ ├── StringFormat.kt │ │ │ │ │ │ ├── PlatformUtils.kt │ │ │ │ │ │ ├── StatusBarUpdate.kt │ │ │ │ │ │ ├── DateUtils.kt │ │ │ │ │ │ └── ScreenSizeInfo.kt │ │ │ │ │ ├── ConversionUtil.kt │ │ │ │ │ ├── NestedScrollConnection.kt │ │ │ │ │ ├── CardsUtil.kt │ │ │ │ │ └── Constants.kt │ │ │ │ ├── ui │ │ │ │ │ ├── screen │ │ │ │ │ │ └── ErrorScreen.kt │ │ │ │ │ ├── components │ │ │ │ │ │ ├── tab │ │ │ │ │ │ │ └── TabItem.kt │ │ │ │ │ │ ├── popup │ │ │ │ │ │ │ ├── PopupMenuItem.kt │ │ │ │ │ │ │ └── GenericPopupMenu.kt │ │ │ │ │ │ ├── ClassicLoadingIndicator.kt │ │ │ │ │ │ ├── SystemUtil.kt │ │ │ │ │ │ ├── button │ │ │ │ │ │ │ ├── SimpleButton.kt │ │ │ │ │ │ │ ├── GenericButton.kt │ │ │ │ │ │ │ └── SortIconButton.kt │ │ │ │ │ │ ├── NetworkImage.kt │ │ │ │ │ │ ├── ClassicGradientBrush.kt │ │ │ │ │ │ ├── RatingComponent.kt │ │ │ │ │ │ ├── card │ │ │ │ │ │ │ ├── MediaTypeTag.kt │ │ │ │ │ │ │ ├── ImageContentCard.kt │ │ │ │ │ │ │ └── PersonImages.kt │ │ │ │ │ │ ├── bottomsheet │ │ │ │ │ │ │ ├── SortBottomSheetComponents.kt │ │ │ │ │ │ │ └── ModalComponents.kt │ │ │ │ │ │ └── PlaceholderView.kt │ │ │ │ │ └── theme │ │ │ │ │ │ ├── Color.kt │ │ │ │ │ │ ├── Shape.kt │ │ │ │ │ │ └── Theme.kt │ │ │ │ └── domain │ │ │ │ │ └── models │ │ │ │ │ ├── content │ │ │ │ │ ├── BaseMediaContent.kt │ │ │ │ │ ├── Videos.kt │ │ │ │ │ ├── StreamProvider.kt │ │ │ │ │ └── ContentCast.kt │ │ │ │ │ ├── list │ │ │ │ │ └── ListItem.kt │ │ │ │ │ ├── util │ │ │ │ │ ├── ContentListType.kt │ │ │ │ │ ├── SnackbarState.kt │ │ │ │ │ ├── MediaType.kt │ │ │ │ │ └── DataLoadStatus.kt │ │ │ │ │ └── person │ │ │ │ │ ├── PersonImage.kt │ │ │ │ │ └── PersonDetails.kt │ │ │ └── navigation │ │ │ │ ├── Screen.kt │ │ │ │ ├── ScreenUI.kt │ │ │ │ ├── screens │ │ │ │ ├── ErrorScreenUI.kt │ │ │ │ ├── BrowseScreenUI.kt │ │ │ │ ├── SearchScreenUI.kt │ │ │ │ ├── WatchlistScreenUI.kt │ │ │ │ ├── DetailsScreenUI.kt │ │ │ │ └── HomeScreenUI.kt │ │ │ │ ├── MainNavGraph.kt │ │ │ │ └── components │ │ │ │ └── MainNavBarItem.kt │ │ └── composeResources │ │ │ └── drawable │ │ │ ├── cinetracker_name_logo.webp │ │ │ ├── ic_check.xml │ │ │ ├── ic_watchlist_add_list.xml │ │ │ ├── ic_chevron_right.xml │ │ │ ├── ic_watchlist.xml │ │ │ ├── ic_sort.xml │ │ │ ├── ic_back_arrow.xml │ │ │ ├── ic_close.xml │ │ │ ├── ic_star.xml │ │ │ ├── ic_nav_watchlist.xml │ │ │ ├── ic_nav_home.xml │ │ │ ├── ic_more_options.xml │ │ │ ├── ic_nav_browse.xml │ │ │ ├── ic_play_video.xml │ │ │ └── ic_nav_search.xml │ └── iosMain │ │ └── kotlin │ │ ├── network │ │ ├── di │ │ │ └── ApiModule.ios.kt │ │ └── NetworkClient.ios.kt │ │ ├── common │ │ └── util │ │ │ └── platform │ │ │ ├── StatusBarUpdate.ios.kt │ │ │ ├── ScreenSizeInfo.ios.kt │ │ │ ├── PlatformUtils.kt │ │ │ ├── DateUtils.kt │ │ │ └── StringFormat.kt │ │ ├── MainViewController.kt │ │ └── core │ │ └── di │ │ └── KoinInitializer.ios.kt ├── google-services.json └── proguard-rules.pro ├── .kotlin └── metadata │ ├── kotlinTransformedMetadataLibraries │ ├── org.jetbrains.skiko-skiko-0.8.4-iosMain-1T2ZCw.klib │ ├── org.jetbrains.compose.ui-ui-1.6.10-skikoMain-Eh674w.klib │ ├── org.jetbrains.compose.ui-ui-1.6.10-uikitMain-BVAqnw.klib │ ├── org.jetbrains.skiko-skiko-0.8.4-commonMain-DbI_Jg.klib │ ├── org.jetbrains.skiko-skiko-0.8.4-darwinMain-1T2ZCw.klib │ ├── org.jetbrains.skiko-skiko-0.8.4-nativeJsMain-DbI_Jg.klib │ ├── org.jetbrains.skiko-skiko-0.8.4-nativeMain-DbI_Jg.klib │ ├── org.jetbrains.compose.ui-ui-1.6.10-commonMain-Eh674w.klib │ ├── org.jetbrains.compose.ui-ui-1.6.10-darwinMain-BVAqnw.klib │ ├── org.jetbrains.compose.ui-ui-1.6.10-nativeMain-BVAqnw.klib │ ├── org.jetbrains.compose.ui-ui-unit-1.6.10-jbMain-vwDMdg.klib │ ├── org.jetbrains.compose.ui-ui-1.6.10-jsNativeMain-Eh674w.klib │ ├── org.jetbrains.compose.ui-ui-text-1.6.10-skikoMain-aUvkxg.klib │ ├── org.jetbrains.compose.ui-ui-util-1.6.10-uikitMain-4Hpl6Q.klib │ ├── org.jetbrains.kotlinx-atomicfu-0.23.2-commonMain-yBS35w.klib │ ├── org.jetbrains.kotlinx-atomicfu-0.23.2-nativeMain-yBS35w.klib │ ├── org.jetbrains.compose.runtime-runtime-1.6.10-jbMain-CVJWAg.klib │ ├── org.jetbrains.compose.ui-ui-text-1.6.10-commonMain-aUvkxg.klib │ ├── org.jetbrains.compose.ui-ui-text-1.6.10-darwinMain-DK5x5Q.klib │ ├── org.jetbrains.compose.ui-ui-text-1.6.10-nativeMain-DK5x5Q.klib │ ├── org.jetbrains.compose.ui-ui-uikit-1.6.10-uikitMain-DC3XFw.klib │ ├── org.jetbrains.compose.ui-ui-unit-1.6.10-commonMain-vwDMdg.klib │ ├── org.jetbrains.compose.ui-ui-util-1.6.10-commonMain-LLOBPg.klib │ ├── org.jetbrains.kotlin-kotlin-stdlib-2.0.0-commonMain-2bbUHA.klib │ ├── org.jetbrains.compose.runtime-runtime-1.6.10-uikitMain-LSh9lw.klib │ ├── org.jetbrains.compose.ui-ui-geometry-1.6.10-commonMain-zDj2GQ.klib │ ├── org.jetbrains.compose.ui-ui-graphics-1.6.10-commonMain-jqr5iw.klib │ ├── org.jetbrains.compose.ui-ui-graphics-1.6.10-nativeMain-M9RlEw.klib │ ├── org.jetbrains.compose.ui-ui-graphics-1.6.10-skikoMain-jqr5iw.klib │ ├── org.jetbrains.compose.ui-ui-text-1.6.10-jsNativeMain-aUvkxg.klib │ ├── org.jetbrains.compose.ui-ui-unit-1.6.10-jsNativeMain-vwDMdg.klib │ ├── org.jetbrains.compose.material-material-1.6.10-skikoMain-tGo7Ag.klib │ ├── org.jetbrains.compose.runtime-runtime-1.6.10-commonMain-CVJWAg.klib │ ├── org.jetbrains.compose.runtime-runtime-1.6.10-nativeMain-CVJWAg.klib │ ├── org.jetbrains.compose.ui-ui-graphics-1.6.10-jsNativeMain-jqr5iw.klib │ ├── org.jetbrains.compose.animation-animation-1.6.10-commonMain-5jNXZw.klib │ ├── org.jetbrains.compose.animation-animation-1.6.10-nativeMain-tpXTFg.klib │ ├── org.jetbrains.compose.material-material-1.6.10-commonMain-tGo7Ag.klib │ ├── org.jetbrains.compose.material-material-1.6.10-jsNativeMain-tGo7Ag.klib │ ├── org.jetbrains.compose.material-material-1.6.10-nativeMain-33WlwA.klib │ ├── org.jetbrains.compose.runtime-runtime-1.6.10-jsNativeMain-CVJWAg.klib │ ├── org.jetbrains.compose.animation-animation-1.6.10-jsNativeMain-5jNXZw.klib │ ├── org.jetbrains.compose.animation-animation-core-1.6.10-jbMain-jNz1Aw.klib │ ├── org.jetbrains.compose.foundation-foundation-1.6.10-commonMain-dXXsCQ.klib │ ├── org.jetbrains.compose.foundation-foundation-1.6.10-darwinMain-aASdXg.klib │ ├── org.jetbrains.compose.foundation-foundation-1.6.10-nativeMain-aASdXg.klib │ ├── org.jetbrains.compose.foundation-foundation-1.6.10-skikoMain-dXXsCQ.klib │ ├── org.jetbrains.compose.foundation-foundation-1.6.10-uikitMain-aASdXg.klib │ ├── org.jetbrains.compose.animation-animation-core-1.6.10-commonMain-jNz1Aw.klib │ ├── org.jetbrains.compose.animation-animation-core-1.6.10-uikitMain-2J6wbg.klib │ ├── org.jetbrains.compose.foundation-foundation-1.6.10-jsNativeMain-dXXsCQ.klib │ ├── org.jetbrains.compose.material-material-ripple-1.6.10-commonMain-8kHg7A.klib │ ├── org.jetbrains.compose.material-material-ripple-1.6.10-nativeMain-zsMeyQ.klib │ ├── org.jetbrains.compose.runtime-runtime-saveable-1.6.10-commonMain-pCPplQ.klib │ ├── org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-commonMain-UxhG-g.klib │ ├── org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-nativeMain-UxhG-g.klib │ ├── org.jetbrains.androidx.lifecycle-lifecycle-common-2.8.0-commonMain-_oGBew.klib │ ├── org.jetbrains.androidx.lifecycle-lifecycle-common-2.8.0-nonJvmMain-_oGBew.klib │ ├── org.jetbrains.compose.animation-animation-core-1.6.10-jsNativeMain-jNz1Aw.klib │ ├── org.jetbrains.compose.collection-internal-collection-1.6.10-jbMain-hcu3Ug.klib │ ├── org.jetbrains.compose.ui-ui-graphics-1.6.10-skikoExcludingWebMain-jqr5iw.klib │ ├── org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-concurrentMain-UxhG-g.klib │ ├── org.jetbrains.androidx.lifecycle-lifecycle-runtime-2.8.0-commonMain-Cd-IGw.klib │ ├── org.jetbrains.androidx.lifecycle-lifecycle-runtime-2.8.0-nativeMain-Cd-IGw.klib │ ├── org.jetbrains.androidx.lifecycle-lifecycle-runtime-2.8.0-nonJvmMain-Cd-IGw.klib │ ├── org.jetbrains.androidx.lifecycle-lifecycle-viewmodel-2.8.0-commonMain-ydSu5Q.klib │ ├── org.jetbrains.androidx.lifecycle-lifecycle-viewmodel-2.8.0-nativeMain-ydSu5Q.klib │ ├── org.jetbrains.androidx.lifecycle-lifecycle-viewmodel-2.8.0-nonJvmMain-ydSu5Q.klib │ ├── org.jetbrains.compose.components-components-resources-1.6.10-iosMain-mlvQUA.klib │ ├── org.jetbrains.compose.foundation-foundation-layout-1.6.10-commonMain-89e7lw.klib │ ├── org.jetbrains.compose.foundation-foundation-layout-1.6.10-skikoMain-89e7lw.klib │ ├── org.jetbrains.compose.foundation-foundation-layout-1.6.10-uikitMain-BKR0pA.klib │ ├── org.jetbrains.compose.material-material-icons-core-1.6.10-commonMain-XjyzjQ.klib │ ├── org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-nativeDarwinMain-sy5nKg.klib │ ├── org.jetbrains.compose.annotation-internal-annotation-1.6.10-commonMain-cNNKSA.klib │ ├── org.jetbrains.compose.annotation-internal-annotation-1.6.10-nonJvmMain-cNNKSA.klib │ ├── org.jetbrains.compose.collection-internal-collection-1.6.10-commonMain-hcu3Ug.klib │ ├── org.jetbrains.compose.components-components-resources-1.6.10-commonMain-44UCqg.klib │ ├── org.jetbrains.compose.components-components-resources-1.6.10-nativeMain-mlvQUA.klib │ ├── org.jetbrains.compose.components-components-resources-1.6.10-skikoMain-44UCqg.klib │ ├── org.jetbrains.compose.foundation-foundation-layout-1.6.10-jsNativeMain-89e7lw.klib │ ├── org.jetbrains.compose.collection-internal-collection-1.6.10-jsNativeMain-hcu3Ug.klib │ ├── org.jetbrains.compose.components-components-resources-1.6.10-blockingMain-44UCqg.klib │ ├── org.jetbrains.androidx.lifecycle-lifecycle-runtime-compose-2.8.0-commonMain-mvP4Vw.klib │ └── org.jetbrains.compose.components-components-ui-tooling-preview-1.6.10-commonMain--i3iSw.klib │ └── kotlinTransformedCInteropMetadataLibraries │ ├── .composeApp-appleMain.cinteropLibraries │ ├── .composeApp-appleTest.cinteropLibraries │ ├── .composeApp-iosMain.cinteropLibraries │ ├── .composeApp-iosTest.cinteropLibraries │ ├── .composeApp-nativeMain.cinteropLibraries │ └── .composeApp-nativeTest.cinteropLibraries ├── gradle └── wrapper │ └── gradle-wrapper.properties ├── gradle.properties ├── .fleet └── receipt.json ├── settings.gradle.kts └── .gitignore /iosApp/Configuration/Config.xcconfig: -------------------------------------------------------------------------------- 1 | TEAM_ID= 2 | BUNDLE_ID=com.projects.CineTracker 3 | APP_NAME=CineTracker 4 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | CineTracker 3 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/core/di/KoinInitializer.kt: -------------------------------------------------------------------------------- 1 | package core.di 2 | 3 | expect class KoinInitializer { 4 | fun init() 5 | } 6 | -------------------------------------------------------------------------------- /iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/NetworkClient.kt: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import io.ktor.client.HttpClient 4 | 5 | expect val client: HttpClient 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/di/ApiModule.kt: -------------------------------------------------------------------------------- 1 | package network.di 2 | 3 | import org.koin.core.module.Module 4 | 5 | expect val apiModule: Module 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/di/DatabaseModule.kt: -------------------------------------------------------------------------------- 1 | package database.di 2 | 3 | import org.koin.core.module.Module 4 | 5 | expect fun databaseModule(): Module 6 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon_iOS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/AppIcon_iOS.png -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_round.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-hdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-mdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-xhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-xxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/androidMain/res/mipmap-xxxhdpi/ic_launcher_foreground.webp -------------------------------------------------------------------------------- /iosApp/iosApp/iOSApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct iOSApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/watchlist/ui/model/WatchlistItemAction.kt: -------------------------------------------------------------------------------- 1 | package features.watchlist.ui.model 2 | 3 | enum class WatchlistItemAction { 4 | ITEM_REMOVED, 5 | ITEM_MOVED, 6 | } 7 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/cinetracker_name_logo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/composeApp/src/commonMain/composeResources/drawable/cinetracker_name_logo.webp -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/util/platform/StringFormat.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | expect object StringFormat { 4 | fun formatRating(number: Double): String 5 | fun Long.toFormattedCurrency(): String 6 | } 7 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/home/HomeScreen.kt: -------------------------------------------------------------------------------- 1 | package features.home 2 | 3 | import navigation.Screen 4 | 5 | object HomeScreen : Screen { 6 | private const val HOME_ROUTE = "home" 7 | override fun route(): String = HOME_ROUTE 8 | } 9 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-iosMain-1T2ZCw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-iosMain-1T2ZCw.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/screen/ErrorScreen.kt: -------------------------------------------------------------------------------- 1 | package common.ui.screen 2 | 3 | import navigation.Screen 4 | 5 | object ErrorScreen : Screen { 6 | private const val ERROR_ROUTE = "error" 7 | override fun route(): String = ERROR_ROUTE 8 | } 9 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-skikoMain-Eh674w.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-skikoMain-Eh674w.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-uikitMain-BVAqnw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-uikitMain-BVAqnw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-commonMain-DbI_Jg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-commonMain-DbI_Jg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-darwinMain-1T2ZCw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-darwinMain-1T2ZCw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-nativeJsMain-DbI_Jg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-nativeJsMain-DbI_Jg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-nativeMain-DbI_Jg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.skiko-skiko-0.8.4-nativeMain-DbI_Jg.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/browse/BrowseScreen.kt: -------------------------------------------------------------------------------- 1 | package features.browse 2 | 3 | import navigation.Screen 4 | 5 | object BrowseScreen : Screen { 6 | private const val BROWSE_ROUTE = "browse" 7 | override fun route(): String = BROWSE_ROUTE 8 | } 9 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/home/events/HomeEvent.kt: -------------------------------------------------------------------------------- 1 | package features.home.events 2 | 3 | sealed class HomeEvent { 4 | data object LoadHome : HomeEvent() 5 | data object ReloadWatchlist : HomeEvent() 6 | data object OnError : HomeEvent() 7 | } 8 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/search/SearchScreen.kt: -------------------------------------------------------------------------------- 1 | package features.search 2 | 3 | import navigation.Screen 4 | 5 | object SearchScreen : Screen { 6 | private const val SEARCH_ROUTE = "search" 7 | override fun route(): String = SEARCH_ROUTE 8 | } 9 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/network/di/ApiModule.ios.kt: -------------------------------------------------------------------------------- 1 | package network.di 2 | 3 | import io.ktor.client.HttpClient 4 | import network.client 5 | import org.koin.dsl.module 6 | 7 | actual val apiModule = module { 8 | single { client } 9 | } 10 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-commonMain-Eh674w.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-commonMain-Eh674w.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-darwinMain-BVAqnw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-darwinMain-BVAqnw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-nativeMain-BVAqnw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-nativeMain-BVAqnw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-unit-1.6.10-jbMain-vwDMdg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-unit-1.6.10-jbMain-vwDMdg.klib -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/network/di/ApiModule.android.kt: -------------------------------------------------------------------------------- 1 | package network.di 2 | 3 | import io.ktor.client.HttpClient 4 | import network.client 5 | import org.koin.dsl.module 6 | 7 | actual val apiModule = module { 8 | single { client } 9 | } 10 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFA9F26 4 | #000000 5 | #FF191A1D 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/util/platform/PlatformUtils.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | expect object PlatformUtils { 4 | val isIOS: Boolean 5 | fun getUserLanguage(): String 6 | fun getUserCountry(): String 7 | fun getLocale(): String 8 | } 9 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/Screen.kt: -------------------------------------------------------------------------------- 1 | package navigation 2 | 3 | import androidx.navigation.NamedNavArgument 4 | 5 | interface Screen { 6 | fun route(): String 7 | val arguments: List 8 | get() = emptyList() 9 | } 10 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-jsNativeMain-Eh674w.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-1.6.10-jsNativeMain-Eh674w.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-skikoMain-aUvkxg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-skikoMain-aUvkxg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-util-1.6.10-uikitMain-4Hpl6Q.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-util-1.6.10-uikitMain-4Hpl6Q.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-commonMain-yBS35w.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-commonMain-yBS35w.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-nativeMain-yBS35w.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-nativeMain-yBS35w.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/ScreenUI.kt: -------------------------------------------------------------------------------- 1 | package navigation 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.navigation.NavController 5 | 6 | interface ScreenUI { 7 | @Composable 8 | fun UI(navController: NavController) 9 | } 10 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-jbMain-CVJWAg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-jbMain-CVJWAg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-commonMain-aUvkxg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-commonMain-aUvkxg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-darwinMain-DK5x5Q.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-darwinMain-DK5x5Q.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-nativeMain-DK5x5Q.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-nativeMain-DK5x5Q.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-uikit-1.6.10-uikitMain-DC3XFw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-uikit-1.6.10-uikitMain-DC3XFw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-unit-1.6.10-commonMain-vwDMdg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-unit-1.6.10-commonMain-vwDMdg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-util-1.6.10-commonMain-LLOBPg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-util-1.6.10-commonMain-LLOBPg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin-kotlin-stdlib-2.0.0-commonMain-2bbUHA.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlin-kotlin-stdlib-2.0.0-commonMain-2bbUHA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-uikitMain-LSh9lw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-uikitMain-LSh9lw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-geometry-1.6.10-commonMain-zDj2GQ.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-geometry-1.6.10-commonMain-zDj2GQ.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-commonMain-jqr5iw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-commonMain-jqr5iw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-nativeMain-M9RlEw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-nativeMain-M9RlEw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-skikoMain-jqr5iw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-skikoMain-jqr5iw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-jsNativeMain-aUvkxg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-text-1.6.10-jsNativeMain-aUvkxg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-unit-1.6.10-jsNativeMain-vwDMdg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-unit-1.6.10-jsNativeMain-vwDMdg.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/watchlist/WatchlistScreen.kt: -------------------------------------------------------------------------------- 1 | package features.watchlist 2 | 3 | import navigation.Screen 4 | 5 | object WatchlistScreen : Screen { 6 | private const val WATCHLIST_ROUTE = "watchlist" 7 | override fun route(): String = WATCHLIST_ROUTE 8 | } 9 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-1.6.10-skikoMain-tGo7Ag.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-1.6.10-skikoMain-tGo7Ag.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-commonMain-CVJWAg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-commonMain-CVJWAg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-nativeMain-CVJWAg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-nativeMain-CVJWAg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-jsNativeMain-jqr5iw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-jsNativeMain-jqr5iw.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/common/ContentGenre.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.common 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ContentGenre( 7 | val id: Int? = null, 8 | val name: String? = null, 9 | ) 10 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-1.6.10-commonMain-5jNXZw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-1.6.10-commonMain-5jNXZw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-1.6.10-nativeMain-tpXTFg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-1.6.10-nativeMain-tpXTFg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-1.6.10-commonMain-tGo7Ag.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-1.6.10-commonMain-tGo7Ag.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-1.6.10-jsNativeMain-tGo7Ag.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-1.6.10-jsNativeMain-tGo7Ag.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-1.6.10-nativeMain-33WlwA.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-1.6.10-nativeMain-33WlwA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-jsNativeMain-CVJWAg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-1.6.10-jsNativeMain-CVJWAg.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/tab/TabItem.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.tab 2 | 3 | import org.jetbrains.compose.resources.StringResource 4 | 5 | interface TabItem { 6 | val tabResId: StringResource? 7 | val tabName: String? 8 | var tabIndex: Int 9 | } 10 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-1.6.10-jsNativeMain-5jNXZw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-1.6.10-jsNativeMain-5jNXZw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-core-1.6.10-jbMain-jNz1Aw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-core-1.6.10-jbMain-jNz1Aw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-commonMain-dXXsCQ.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-commonMain-dXXsCQ.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-darwinMain-aASdXg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-darwinMain-aASdXg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-nativeMain-aASdXg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-nativeMain-aASdXg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-skikoMain-dXXsCQ.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-skikoMain-dXXsCQ.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-uikitMain-aASdXg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-uikitMain-aASdXg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-core-1.6.10-commonMain-jNz1Aw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-core-1.6.10-commonMain-jNz1Aw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-core-1.6.10-uikitMain-2J6wbg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-core-1.6.10-uikitMain-2J6wbg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-jsNativeMain-dXXsCQ.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-1.6.10-jsNativeMain-dXXsCQ.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-ripple-1.6.10-commonMain-8kHg7A.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-ripple-1.6.10-commonMain-8kHg7A.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-ripple-1.6.10-nativeMain-zsMeyQ.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-ripple-1.6.10-nativeMain-zsMeyQ.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-saveable-1.6.10-commonMain-pCPplQ.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.runtime-runtime-saveable-1.6.10-commonMain-pCPplQ.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-commonMain-UxhG-g.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-commonMain-UxhG-g.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-nativeMain-UxhG-g.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-nativeMain-UxhG-g.klib -------------------------------------------------------------------------------- /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 8 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-common-2.8.0-commonMain-_oGBew.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-common-2.8.0-commonMain-_oGBew.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-common-2.8.0-nonJvmMain-_oGBew.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-common-2.8.0-nonJvmMain-_oGBew.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-core-1.6.10-jsNativeMain-jNz1Aw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.animation-animation-core-1.6.10-jsNativeMain-jNz1Aw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.collection-internal-collection-1.6.10-jbMain-hcu3Ug.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.collection-internal-collection-1.6.10-jbMain-hcu3Ug.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-skikoExcludingWebMain-jqr5iw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.ui-ui-graphics-1.6.10-skikoExcludingWebMain-jqr5iw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-concurrentMain-UxhG-g.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-concurrentMain-UxhG-g.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/popup/PopupMenuItem.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.popup 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | data class PopupMenuItem( 6 | val title: String, 7 | val textColor: Color = Color.White, 8 | val onClick: () -> Unit, 9 | ) 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/common/ProductionCountry.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.common 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ProductionCountry( 7 | val iso_3166_1: String? = null, 8 | val name: String? = null, 9 | ) 10 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-runtime-2.8.0-commonMain-Cd-IGw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-runtime-2.8.0-commonMain-Cd-IGw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-runtime-2.8.0-nativeMain-Cd-IGw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-runtime-2.8.0-nativeMain-Cd-IGw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-runtime-2.8.0-nonJvmMain-Cd-IGw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-runtime-2.8.0-nonJvmMain-Cd-IGw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-viewmodel-2.8.0-commonMain-ydSu5Q.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-viewmodel-2.8.0-commonMain-ydSu5Q.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-viewmodel-2.8.0-nativeMain-ydSu5Q.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-viewmodel-2.8.0-nativeMain-ydSu5Q.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-viewmodel-2.8.0-nonJvmMain-ydSu5Q.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-viewmodel-2.8.0-nonJvmMain-ydSu5Q.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-iosMain-mlvQUA.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-iosMain-mlvQUA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-layout-1.6.10-commonMain-89e7lw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-layout-1.6.10-commonMain-89e7lw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-layout-1.6.10-skikoMain-89e7lw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-layout-1.6.10-skikoMain-89e7lw.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-layout-1.6.10-uikitMain-BKR0pA.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-layout-1.6.10-uikitMain-BKR0pA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-icons-core-1.6.10-commonMain-XjyzjQ.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.material-material-icons-core-1.6.10-commonMain-XjyzjQ.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-nativeDarwinMain-sy5nKg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.kotlinx-kotlinx-coroutines-core-1.8.0-nativeDarwinMain-sy5nKg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.annotation-internal-annotation-1.6.10-commonMain-cNNKSA.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.annotation-internal-annotation-1.6.10-commonMain-cNNKSA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.annotation-internal-annotation-1.6.10-nonJvmMain-cNNKSA.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.annotation-internal-annotation-1.6.10-nonJvmMain-cNNKSA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.collection-internal-collection-1.6.10-commonMain-hcu3Ug.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.collection-internal-collection-1.6.10-commonMain-hcu3Ug.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-commonMain-44UCqg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-commonMain-44UCqg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-nativeMain-mlvQUA.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-nativeMain-mlvQUA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-skikoMain-44UCqg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-skikoMain-44UCqg.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-layout-1.6.10-jsNativeMain-89e7lw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.foundation-foundation-layout-1.6.10-jsNativeMain-89e7lw.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/common/VideosByIdResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.common 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class VideosByIdResponse( 7 | val id: Int? = null, 8 | val results: List? = null, 9 | ) 10 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.collection-internal-collection-1.6.10-jsNativeMain-hcu3Ug.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.collection-internal-collection-1.6.10-jsNativeMain-hcu3Ug.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-blockingMain-44UCqg.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-resources-1.6.10-blockingMain-44UCqg.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/util/platform/StatusBarUpdate.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.graphics.Color 5 | import common.ui.theme.MainBarGreyColor 6 | 7 | @Composable 8 | expect fun SetStatusBarColor(newColor: Color = MainBarGreyColor) 9 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/ApiError.kt: -------------------------------------------------------------------------------- 1 | package network.models 2 | 3 | import kotlinx.serialization.Contextual 4 | import kotlinx.serialization.Serializable 5 | 6 | @Serializable 7 | data class ApiError( 8 | val code: String? = null, 9 | @Contextual val exception: Throwable? = null, 10 | ) 11 | -------------------------------------------------------------------------------- /iosApp/iosApp/Util/Extensions.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | extension Color { 4 | init(hex: UInt) { 5 | self.init( 6 | red: Double((hex >> 16) & 0xFF) / 255.0, 7 | green: Double((hex >> 8) & 0xFF) / 255.0, 8 | blue: Double(hex & 0xFF) / 255.0 9 | ) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-runtime-compose-2.8.0-commonMain-mvP4Vw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.androidx.lifecycle-lifecycle-runtime-compose-2.8.0-commonMain-mvP4Vw.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/model/ListEntity.kt: -------------------------------------------------------------------------------- 1 | package database.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.PrimaryKey 5 | 6 | @Entity(tableName = "list_entity") 7 | data class ListEntity( 8 | @PrimaryKey(autoGenerate = true) val listId: Int = 0, 9 | val listName: String, 10 | ) 11 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/common/util/platform/StatusBarUpdate.ios.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.graphics.Color 5 | 6 | @Composable 7 | actual fun SetStatusBarColor(newColor: Color) { 8 | // TODO status bar color update on IOS 9 | } 10 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/common/ContentCreditsResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.common 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ContentCreditsResponse( 7 | val id: Int = 0, 8 | val cast: List? = emptyList(), 9 | ) 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/person/PersonImagesResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.person 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class PersonImagesResponse( 7 | val id: Int? = null, 8 | val profiles: List? = null, 9 | ) 10 | -------------------------------------------------------------------------------- /iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "AppIcon_iOS.png", 5 | "idiom" : "universal", 6 | "platform" : "ios", 7 | "size" : "1024x1024" 8 | } 9 | ], 10 | "info" : { 11 | "author" : "xcode", 12 | "version" : 1 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-ui-tooling-preview-1.6.10-commonMain--i3iSw.klib: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gustavopeq/CineTracker-KMP/HEAD/.kotlin/metadata/kotlinTransformedMetadataLibraries/org.jetbrains.compose.components-components-ui-tooling-preview-1.6.10-commonMain--i3iSw.klib -------------------------------------------------------------------------------- /iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/util/platform/DateUtils.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import common.util.Constants 4 | 5 | expect object DateUtils { 6 | fun getComingSoonDates( 7 | monthPeriod: Int = Constants.MONTH_PERIOD_COMING_SOON, 8 | ): Pair 9 | fun getCurrentTimeMillis(): Long 10 | } 11 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/core/ImageLoader.kt: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import coil3.ImageLoader 4 | import coil3.PlatformContext 5 | import coil3.request.crossfade 6 | import coil3.util.DebugLogger 7 | 8 | fun getAsyncImageLoader(context: PlatformContext) = 9 | ImageLoader.Builder(context).crossfade(true).logger(DebugLogger()).build() 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/content/BaseMediaContent.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.content 2 | 3 | import common.domain.models.util.MediaType 4 | 5 | interface BaseMediaContent { 6 | val id: Int 7 | val title: String 8 | val overview: String 9 | val poster_path: String 10 | val mediaType: MediaType 11 | } 12 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/di/DatabaseRepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package database.di 2 | 3 | import database.repository.DatabaseRepository 4 | import database.repository.DatabaseRepositoryImpl 5 | import org.koin.dsl.module 6 | 7 | val databaseRepositoryModule = module { 8 | single { DatabaseRepositoryImpl(get(), get()) } 9 | } 10 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/com/projects/cinetracker/core/CoreApplication.kt: -------------------------------------------------------------------------------- 1 | package com.projects.cinetracker.core 2 | 3 | import android.app.Application 4 | import core.di.KoinInitializer 5 | 6 | class CoreApplication : Application() { 7 | override fun onCreate() { 8 | super.onCreate() 9 | KoinInitializer(this@CoreApplication).init() 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/details/state/DetailsSnackbarState.kt: -------------------------------------------------------------------------------- 1 | package features.details.state 2 | 3 | import common.domain.models.util.SnackbarState 4 | import features.watchlist.ui.model.DefaultLists 5 | 6 | data class DetailsSnackbarState( 7 | val listId: Int = DefaultLists.WATCHLIST.listId, 8 | val addedItem: Boolean = false, 9 | ) : SnackbarState() 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_check.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_watchlist_add_list.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/list/ListItem.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.list 2 | 3 | import database.model.ListEntity 4 | 5 | data class ListItem( 6 | val id: Int, 7 | val name: String, 8 | ) 9 | 10 | fun ListEntity.toListItem(): ListItem { 11 | return ListItem( 12 | id = this.listId, 13 | name = this.listName, 14 | ) 15 | } 16 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_chevron_right.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/util/ConversionUtil.kt: -------------------------------------------------------------------------------- 1 | package common.util 2 | 3 | import androidx.compose.ui.unit.Density 4 | import androidx.compose.ui.unit.Dp 5 | 6 | fun pxToDp( 7 | pixels: Int, 8 | density: Density, 9 | ): Dp = with(density) { pixels.toDp() } 10 | 11 | fun dpToPx( 12 | dp: Dp, 13 | density: Density, 14 | ): Int = with(density) { dp.roundToPx() } 15 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/details/events/DetailsEvents.kt: -------------------------------------------------------------------------------- 1 | package features.details.events 2 | 3 | sealed class DetailsEvents { 4 | data object FetchDetails : DetailsEvents() 5 | data object OnError : DetailsEvents() 6 | data object OnSnackbarDismiss : DetailsEvents() 7 | data class ToggleContentFromList( 8 | val listId: Int, 9 | ) : DetailsEvents() 10 | } 11 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_watchlist.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/util/platform/ScreenSizeInfo.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.unit.Dp 5 | 6 | data class ScreenSizeInfo( 7 | val widthPx: Int, 8 | val heightPx: Int, 9 | val widthDp: Dp, 10 | val heightDp: Dp, 11 | ) 12 | 13 | @Composable 14 | expect fun getScreenSizeInfo(): ScreenSizeInfo 15 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/watchlist/ui/state/WatchlistSnackbarState.kt: -------------------------------------------------------------------------------- 1 | package features.watchlist.ui.state 2 | 3 | import common.domain.models.util.SnackbarState 4 | import features.watchlist.ui.model.WatchlistItemAction 5 | 6 | data class WatchlistSnackbarState( 7 | val listId: Int? = null, 8 | val itemAction: WatchlistItemAction = WatchlistItemAction.ITEM_REMOVED, 9 | ) : SnackbarState() 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_sort.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/person/PersonCreditsResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.person 2 | 3 | import kotlinx.serialization.Serializable 4 | import network.models.content.common.CastResponse 5 | 6 | @Serializable 7 | data class PersonCreditsResponse( 8 | val cast: List? = null, 9 | val crew: List? = null, 10 | val id: Int? = null, 11 | ) 12 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_back_arrow.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_close.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/util/Parameters.kt: -------------------------------------------------------------------------------- 1 | package com.projects.moviemanager.network.util 2 | 3 | object Parameters { 4 | const val PAGE_INDEX = "page" 5 | const val LANGUAGE = "language" 6 | const val RELEASE_DATE_GTE = "primary_release_date.gte" 7 | const val RELEASE_DATE_LTE = "primary_release_date.lte" 8 | const val SEARCH_QUERY = "query" 9 | const val MATURE_ENABLED = "include_adult" 10 | } 11 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | kotlin.code.style=official 2 | 3 | #Gradle 4 | org.gradle.jvmargs=-Xmx2048M -Dfile.encoding=UTF-8 -Dkotlin.daemon.jvm.options\="-Xmx2048M" 5 | 6 | #Android 7 | android.nonTransitiveRClass=true 8 | android.useAndroidX=true 9 | 10 | #Kotlin Multiplatform 11 | kotlin.mpp.enableCInteropCommonization=true 12 | kotlin.mpp.androidGradlePluginCompatibility.nowarn=true 13 | 14 | #Room 15 | kotlin.native.disableCompilerDaemon=true -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/ClassicLoadingIndicator.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components 2 | 3 | import androidx.compose.material3.CircularProgressIndicator 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.runtime.Composable 6 | 7 | @Composable 8 | fun ClassicLoadingIndicator() { 9 | CircularProgressIndicator( 10 | color = MaterialTheme.colorScheme.secondary, 11 | ) 12 | } 13 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_nav_watchlist.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_nav_home.xml: -------------------------------------------------------------------------------- 1 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/home/ui/state/HomeState.kt: -------------------------------------------------------------------------------- 1 | package features.home.ui.state 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.mutableStateOf 5 | import common.domain.models.content.GenericContent 6 | import common.domain.models.util.LoadStatusState 7 | 8 | data class HomeState( 9 | val trendingList: MutableState> = mutableStateOf(emptyList()), 10 | ) : LoadStatusState() 11 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/search/ContentPagingResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.search 2 | 3 | import kotlinx.serialization.Serializable 4 | import network.models.content.common.BaseContentResponse 5 | 6 | @Serializable 7 | data class ContentPagingResponse( 8 | val page: Int, 9 | val results: List = emptyList(), 10 | val total_pages: Int, 11 | val total_results: Int, 12 | ) 13 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/util/Either.kt: -------------------------------------------------------------------------------- 1 | package network.util 2 | 3 | sealed class Either { 4 | val isLeft get() = this is Left 5 | val isRight get() = this is Right 6 | companion object 7 | } 8 | 9 | data class Left(val value: L) : Either() 10 | data class Right(val error: R) : Either() 11 | 12 | fun left(value: L) = Left(value) 13 | fun right(error: R) = Right(error) 14 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/content/Videos.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.content 2 | 3 | import network.models.content.common.VideoResponse 4 | 5 | data class Videos( 6 | val name: String, 7 | val key: String, 8 | val publishedAt: String, 9 | ) 10 | 11 | fun VideoResponse.toVideos(): Videos = 12 | Videos( 13 | name = this.name, 14 | key = this.key, 15 | publishedAt = this.published_at, 16 | ) 17 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/person/PersonProfileResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.person 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class PersonProfileResponse( 7 | val aspect_ratio: Double? = null, 8 | val file_path: String? = null, 9 | val height: Int? = null, 10 | val vote_average: Double? = null, 11 | val vote_count: Int? = null, 12 | val width: Int? = null, 13 | ) 14 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/watchlist/ui/state/WatchlistState.kt: -------------------------------------------------------------------------------- 1 | package features.watchlist.ui.state 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.mutableStateOf 5 | import common.domain.models.content.GenericContent 6 | import common.domain.models.util.LoadStatusState 7 | 8 | class WatchlistState( 9 | var listItems: MutableState> = mutableStateOf(mutableListOf()), 10 | ) : LoadStatusState() 11 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/content/StreamProvider.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.content 2 | 3 | import network.models.content.common.ProviderResponse 4 | 5 | data class StreamProvider( 6 | val providerName: String, 7 | val logoPath: String, 8 | ) 9 | 10 | fun ProviderResponse.toStreamProvider(): StreamProvider = 11 | StreamProvider( 12 | providerName = this.provider_name.orEmpty(), 13 | logoPath = this.logo_path.orEmpty(), 14 | ) 15 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/util/ContentListType.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.util 2 | 3 | enum class ContentListType( 4 | val type: String, 5 | ) { 6 | MOVIE_NOW_PLAYING("now_playing"), 7 | MOVIE_POPULAR("popular"), 8 | MOVIE_TOP_RATED("top_rated"), 9 | MOVIE_UPCOMING("upcoming"), 10 | SHOW_AIRING_TODAY("airing_today"), 11 | SHOW_POPULAR("popular"), 12 | SHOW_TOP_RATED("top_rated"), 13 | SHOW_ON_THE_AIR("on_the_air"), 14 | } 15 | -------------------------------------------------------------------------------- /.fleet/receipt.json: -------------------------------------------------------------------------------- 1 | // Project generated by Kotlin Multiplatform Wizard 2 | { 3 | "spec": { 4 | "template_id": "kmt", 5 | "targets": { 6 | "android": { 7 | "ui": [ 8 | "compose" 9 | ] 10 | }, 11 | "ios": { 12 | "ui": [ 13 | "compose" 14 | ] 15 | } 16 | } 17 | }, 18 | "timestamp": "2024-06-22T05:45:36.803742218Z" 19 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/com/projects/cinetracker/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.projects.cinetracker 2 | 3 | import MainAppView 4 | import android.os.Bundle 5 | import androidx.activity.ComponentActivity 6 | import androidx.activity.compose.setContent 7 | 8 | class MainActivity : ComponentActivity() { 9 | override fun onCreate(savedInstanceState: Bundle?) { 10 | super.onCreate(savedInstanceState) 11 | 12 | setContent { 13 | MainAppView() 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/util/SnackbarState.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.util 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.mutableStateOf 5 | 6 | open class SnackbarState { 7 | var displaySnackbar: MutableState = mutableStateOf(false) 8 | 9 | fun setSnackbarVisible() { 10 | displaySnackbar.value = true 11 | } 12 | 13 | fun setSnackbarGone() { 14 | displaySnackbar.value = false 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/search/events/SearchEvent.kt: -------------------------------------------------------------------------------- 1 | package features.search.events 2 | 3 | import features.search.ui.components.SearchTypeFilterItem 4 | 5 | sealed class SearchEvent { 6 | data object ClearSearchBar : SearchEvent() 7 | data object OnError : SearchEvent() 8 | data class SearchQuery( 9 | val query: String, 10 | ) : SearchEvent() 11 | data class FilterTypeSelected( 12 | val searchFilter: SearchTypeFilterItem, 13 | ) : SearchEvent() 14 | } 15 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_more_options.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/SystemUtil.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components 2 | 3 | import androidx.compose.foundation.layout.Spacer 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.unit.dp 8 | import common.util.UiConstants.SYSTEM_BOTTOM_NAV_PADDING 9 | 10 | @Composable 11 | fun SystemNavBarSpacer() { 12 | Spacer(modifier = Modifier.height(SYSTEM_BOTTOM_NAV_PADDING.dp)) 13 | } 14 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/browse/events/BrowseEvent.kt: -------------------------------------------------------------------------------- 1 | package features.browse.events 2 | 3 | import common.domain.models.util.MediaType 4 | import common.domain.models.util.SortTypeItem 5 | 6 | sealed class BrowseEvent { 7 | data class UpdateSortType( 8 | val movieListType: SortTypeItem, 9 | val mediaType: MediaType, 10 | ) : BrowseEvent() 11 | data class UpdateMediaType( 12 | val mediaType: MediaType, 13 | ) : BrowseEvent() 14 | data object OnError : BrowseEvent() 15 | } 16 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/screens/ErrorScreenUI.kt: -------------------------------------------------------------------------------- 1 | package navigation.screens 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.navigation.NavController 5 | import common.ui.screen.ErrorScreen 6 | import navigation.ScreenUI 7 | 8 | class ErrorScreenUI : ScreenUI { 9 | @Composable 10 | override fun UI(navController: NavController) { 11 | ErrorScreen( 12 | onTryAgain = { 13 | navController.popBackStack() 14 | }, 15 | ) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/util/MediaType.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.util 2 | 3 | enum class MediaType { 4 | MOVIE, 5 | SHOW, 6 | PERSON, 7 | UNKNOWN, 8 | ; 9 | 10 | companion object { 11 | fun getType(typeName: String?): MediaType = 12 | when (typeName?.lowercase()) { 13 | "movie" -> MOVIE 14 | "show", "tv" -> SHOW 15 | "person", "people" -> PERSON 16 | else -> UNKNOWN 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/common/util/platform/PlatformUtils.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import java.util.Locale 4 | 5 | actual object PlatformUtils { 6 | actual val isIOS: Boolean = false 7 | actual fun getUserLanguage(): String = Locale.getDefault().language 8 | actual fun getUserCountry(): String = Locale.getDefault().country 9 | actual fun getLocale(): String { 10 | val language = getUserLanguage() 11 | val country = getUserCountry() 12 | 13 | return "$language-$country" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_nav_browse.xml: -------------------------------------------------------------------------------- 1 | 8 | 11 | 12 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/common/VideoResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.common 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class VideoResponse( 7 | val name: String, 8 | val key: String, 9 | val published_at: String, 10 | val id: String? = null, 11 | val iso_3166_1: String? = null, 12 | val iso_639_1: String? = null, 13 | val official: Boolean? = null, 14 | val site: String? = null, 15 | val size: Int? = null, 16 | val type: String? = null, 17 | ) 18 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/MainViewController.kt: -------------------------------------------------------------------------------- 1 | 2 | import androidx.compose.ui.window.ComposeUIViewController 3 | import core.di.KoinInitializer 4 | import dev.gitlive.firebase.Firebase 5 | import dev.gitlive.firebase.crashlytics.crashlytics 6 | import dev.gitlive.firebase.initialize 7 | 8 | fun MainViewController() = ComposeUIViewController( 9 | configure = { 10 | KoinInitializer().init() 11 | Firebase.initialize() 12 | Firebase.crashlytics.setCrashlyticsCollectionEnabled(true) 13 | }, 14 | ) { 15 | MainAppView() 16 | } 17 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_play_video.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/composeResources/drawable/ic_nav_search.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/person/PersonImage.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.person 2 | 3 | import network.models.content.person.PersonProfileResponse 4 | 5 | data class PersonImage( 6 | val aspectRatio: Double?, 7 | val filePath: String?, 8 | val height: Int?, 9 | val width: Int?, 10 | ) 11 | 12 | fun PersonProfileResponse.toPersonImage(): PersonImage = 13 | PersonImage( 14 | aspectRatio = this.aspect_ratio, 15 | filePath = this.file_path, 16 | height = this.height, 17 | width = this.width, 18 | ) 19 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/common/WatchProvidersResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.common 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class WatchProvidersResponse( 7 | val id: Int, 8 | val results: Map? = null, 9 | ) 10 | 11 | @Serializable 12 | data class CountryProviderResponse( 13 | val flatrate: List? = null, 14 | ) 15 | 16 | @Serializable 17 | data class ProviderResponse( 18 | val logo_path: String? = null, 19 | val provider_name: String? = null, 20 | ) 21 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/values/themes.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 17 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/di/DaoModule.kt: -------------------------------------------------------------------------------- 1 | package database.di 2 | 3 | import database.AppDatabase 4 | import database.dao.ContentEntityDao 5 | import database.dao.ListEntityDao 6 | import org.koin.dsl.module 7 | 8 | val daoModule = module { 9 | single { provideContentEntityDao(get()) } 10 | single { provideListEntityDao(get()) } 11 | } 12 | 13 | private fun provideContentEntityDao(appDatabase: AppDatabase): ContentEntityDao { 14 | return appDatabase.contentEntityDao() 15 | } 16 | 17 | private fun provideListEntityDao(appDatabase: AppDatabase): ListEntityDao { 18 | return appDatabase.listEntityDao() 19 | } 20 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/theme/Color.kt: -------------------------------------------------------------------------------- 1 | package common.ui.theme 2 | 3 | import androidx.compose.ui.graphics.Color 4 | 5 | val PrimaryYellowColor = Color(0xFFFA9F26) 6 | val PrimaryYellowColor_90 = Color(0xE6FA9F26) 7 | val PrimaryBlackColor = Color(0xFF000000) 8 | val PrimaryGreyColor = Color(0xFFDCDCDC) 9 | val PrimaryGreyColor_55 = Color(0x8C24252A) 10 | val PrimaryWhiteColor = Color(0xFFFFFFFF) 11 | val SecondaryGreyColor = Color(0xFF9D9D9D) 12 | val MainBarGreyColor = Color(0xFF191A1D) 13 | val PrimaryRedColor = Color(0xFFFA2626) 14 | 15 | val placeholderGrey = Color(0xFFB8B5B5) 16 | val placeholderGrey2 = Color(0xFF8F8B8B) 17 | val DividerGrey = Color(0x592C2E33) 18 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/util/ApiResult.kt: -------------------------------------------------------------------------------- 1 | package network.util 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import kotlinx.coroutines.flow.flow 5 | import network.models.ApiError 6 | 7 | sealed class ApiResult { 8 | class Success(val data: T, val code: Int) : ApiResult() 9 | class Error(val data: ApiError, val code: Int? = null) : ApiResult() 10 | } 11 | 12 | fun ApiResult.asFlow(): Flow> { 13 | val result = this 14 | return flow { 15 | when (result) { 16 | is ApiResult.Success -> emit(left(result.data)) 17 | is ApiResult.Error -> emit(right(result.data)) 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/details/state/DetailsState.kt: -------------------------------------------------------------------------------- 1 | package features.details.state 2 | 3 | import androidx.compose.runtime.MutableState 4 | import androidx.compose.runtime.mutableStateOf 5 | import common.domain.models.content.ContentCast 6 | import common.domain.models.content.DetailedContent 7 | import common.domain.models.content.Videos 8 | import common.domain.models.util.LoadStatusState 9 | 10 | data class DetailsState( 11 | var detailsInfo: MutableState = mutableStateOf(null), 12 | var detailsCast: MutableState> = mutableStateOf(listOf()), 13 | var detailsVideos: MutableState> = mutableStateOf(listOf()), 14 | ) : LoadStatusState() 15 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package database 2 | 3 | import androidx.room.Database 4 | import androidx.room.RoomDatabase 5 | import database.dao.ContentEntityDao 6 | import database.dao.ListEntityDao 7 | import database.model.ContentEntity 8 | import database.model.ListEntity 9 | 10 | @Database( 11 | entities = [ContentEntity::class, ListEntity::class], 12 | version = 5, 13 | ) 14 | abstract class AppDatabase : RoomDatabase(), DB { 15 | abstract fun contentEntityDao(): ContentEntityDao 16 | abstract fun listEntityDao(): ListEntityDao 17 | 18 | override fun clearAllTables() { 19 | super.clearAllTables() 20 | } 21 | } 22 | 23 | interface DB { 24 | fun clearAllTables() {} 25 | } 26 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/content/ContentCast.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.content 2 | 3 | import network.models.content.common.ContentCastResponse 4 | 5 | data class ContentCast( 6 | val id: Int, 7 | val name: String, 8 | val character: String, 9 | val profilePoster: String, 10 | val order: Int?, 11 | ) 12 | 13 | fun ContentCastResponse.toContentCast(): ContentCast = 14 | ContentCast( 15 | id = this.id, 16 | name = this.name, 17 | character = 18 | this.character ?: this.roles 19 | ?.firstOrNull() 20 | ?.character 21 | .orEmpty(), 22 | profilePoster = this.profile_path.orEmpty(), 23 | order = this.order, 24 | ) 25 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/theme/Shape.kt: -------------------------------------------------------------------------------- 1 | package common.ui.theme 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.Shapes 5 | import androidx.compose.ui.unit.dp 6 | import common.util.UiConstants.CARD_ROUND_CORNER 7 | 8 | val RoundCornerShapes = Shapes( 9 | // Extra small = round on bottom 10 | extraSmall = RoundedCornerShape( 11 | bottomStart = CARD_ROUND_CORNER.dp, 12 | bottomEnd = CARD_ROUND_CORNER.dp, 13 | ), 14 | // Small = round on top 15 | small = RoundedCornerShape(topStart = CARD_ROUND_CORNER.dp, topEnd = CARD_ROUND_CORNER.dp), 16 | // Medium = completely round 17 | medium = RoundedCornerShape(6.dp), 18 | large = RoundedCornerShape(8.dp), 19 | ) 20 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/common/util/platform/ScreenSizeInfo.android.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.platform.LocalConfiguration 5 | import androidx.compose.ui.platform.LocalDensity 6 | import androidx.compose.ui.unit.dp 7 | 8 | @Composable 9 | actual fun getScreenSizeInfo(): ScreenSizeInfo { 10 | val density = LocalDensity.current 11 | val config = LocalConfiguration.current 12 | val hDp = config.screenHeightDp.dp 13 | val wDp = config.screenWidthDp.dp 14 | 15 | return ScreenSizeInfo( 16 | widthPx = with(density) { wDp.roundToPx() }, 17 | heightPx = with(density) { hDp.roundToPx() }, 18 | widthDp = wDp, 19 | heightDp = hDp, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/core/di/modules/InteractorModule.kt: -------------------------------------------------------------------------------- 1 | package core.di.modules 2 | 3 | import features.browse.domain.BrowseInteractor 4 | import features.details.domain.DetailsInteractor 5 | import features.home.domain.HomeInteractor 6 | import features.search.domain.SearchInteractor 7 | import features.watchlist.domain.WatchlistInteractor 8 | import org.koin.dsl.module 9 | 10 | val interactorModule = module { 11 | single { BrowseInteractor(get(), get()) } 12 | single { DetailsInteractor(get(), get(), get(), get()) } 13 | single { WatchlistInteractor(get(), get(), get()) } 14 | single { SearchInteractor(get()) } 15 | single { HomeInteractor(get(), get(), get(), get()) } 16 | } 17 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/common/util/platform/ScreenSizeInfo.ios.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.ui.ExperimentalComposeUiApi 5 | import androidx.compose.ui.platform.LocalDensity 6 | import androidx.compose.ui.platform.LocalWindowInfo 7 | 8 | @OptIn(ExperimentalComposeUiApi::class) 9 | @Composable 10 | actual fun getScreenSizeInfo(): ScreenSizeInfo { 11 | val density = LocalDensity.current 12 | val config = LocalWindowInfo.current.containerSize 13 | 14 | return ScreenSizeInfo( 15 | widthPx = config.width, 16 | heightPx = config.height, 17 | widthDp = with(density) { config.width.toDp() }, 18 | heightDp = with(density) { config.height.toDp() }, 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/dao/ListEntityDao.kt: -------------------------------------------------------------------------------- 1 | package database.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import database.model.ListEntity 8 | 9 | @Dao 10 | interface ListEntityDao { 11 | 12 | @Insert(onConflict = OnConflictStrategy.ABORT) 13 | suspend fun insertList(listEntity: ListEntity) 14 | 15 | @Query("DELETE FROM list_entity WHERE listId = :listId") 16 | suspend fun deleteList(listId: Int) 17 | 18 | @Query("SELECT * FROM list_entity") 19 | suspend fun getAllLists(): List 20 | 21 | @Query("SELECT COUNT(*) FROM list_entity WHERE listName = :listName COLLATE NOCASE") 22 | suspend fun getListCountByName(listName: String): Int 23 | } 24 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/common/util/platform/DateUtils.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.Calendar 5 | import java.util.Locale 6 | 7 | actual object DateUtils { 8 | actual fun getComingSoonDates( 9 | monthPeriod: Int, 10 | ): Pair { 11 | val calendar = Calendar.getInstance() 12 | 13 | val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) 14 | val releaseDateGte = dateFormat.format(calendar.time) 15 | calendar.add(Calendar.MONTH, monthPeriod) 16 | val releaseDateLte = dateFormat.format(calendar.time) 17 | return Pair(releaseDateGte, releaseDateLte) 18 | } 19 | 20 | actual fun getCurrentTimeMillis(): Long = System.currentTimeMillis() 21 | } 22 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/model/ContentEntity.kt: -------------------------------------------------------------------------------- 1 | package database.model 2 | 3 | import androidx.room.Entity 4 | import androidx.room.ForeignKey 5 | import androidx.room.PrimaryKey 6 | import common.util.platform.DateUtils 7 | 8 | @Entity( 9 | tableName = "content_entity", 10 | foreignKeys = [ 11 | ForeignKey( 12 | entity = ListEntity::class, 13 | parentColumns = ["listId"], 14 | childColumns = ["listId"], 15 | onDelete = ForeignKey.CASCADE, 16 | ), 17 | ], 18 | ) 19 | data class ContentEntity( 20 | @PrimaryKey(autoGenerate = true) val contentEntityDbId: Int = 0, 21 | val contentId: Int, 22 | val mediaType: String, 23 | val listId: Int, 24 | val createdAt: Long = DateUtils.getCurrentTimeMillis(), 25 | ) 26 | -------------------------------------------------------------------------------- /composeApp/google-services.json: -------------------------------------------------------------------------------- 1 | { 2 | "project_info": { 3 | "project_number": "824837098950", 4 | "project_id": "cinetracker-kmp", 5 | "storage_bucket": "cinetracker-kmp.appspot.com" 6 | }, 7 | "client": [ 8 | { 9 | "client_info": { 10 | "mobilesdk_app_id": "1:824837098950:android:5db2048021a3956ad4b6c7", 11 | "android_client_info": { 12 | "package_name": "gustavo.projects.restapi" 13 | } 14 | }, 15 | "oauth_client": [], 16 | "api_key": [ 17 | { 18 | "current_key": "AIzaSyASQ7FzQT0ETK2f4wniPHxTeoLjUvd7zNQ" 19 | } 20 | ], 21 | "services": { 22 | "appinvite_service": { 23 | "other_platform_oauth_client": [] 24 | } 25 | } 26 | } 27 | ], 28 | "configuration_version": "1" 29 | } -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/common/util/platform/StatusBarUpdate.android.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import android.app.Activity 4 | import androidx.compose.runtime.Composable 5 | import androidx.compose.runtime.DisposableEffect 6 | import androidx.compose.ui.graphics.Color 7 | import androidx.compose.ui.graphics.toArgb 8 | import androidx.compose.ui.platform.LocalContext 9 | import common.ui.theme.PrimaryBlackColor 10 | 11 | @Composable 12 | actual fun SetStatusBarColor(newColor: Color) { 13 | val context = LocalContext.current 14 | val window = (context as? Activity)?.window 15 | 16 | DisposableEffect(Unit) { 17 | window?.statusBarColor = newColor.toArgb() 18 | 19 | onDispose { 20 | window?.statusBarColor = PrimaryBlackColor.toArgb() 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/util/NestedScrollConnection.kt: -------------------------------------------------------------------------------- 1 | package common.util 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.remember 5 | import androidx.compose.ui.geometry.Offset 6 | import androidx.compose.ui.input.nestedscroll.NestedScrollConnection 7 | import androidx.compose.ui.input.nestedscroll.NestedScrollSource 8 | 9 | @Composable 10 | fun rememberNestedScrollConnection(onScroll: () -> Unit): NestedScrollConnection { 11 | return remember { 12 | object : NestedScrollConnection { 13 | override fun onPreScroll( 14 | available: Offset, 15 | source: NestedScrollSource, 16 | ): Offset { 17 | onScroll() 18 | return Offset.Zero 19 | } 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/repository/person/PersonRepository.kt: -------------------------------------------------------------------------------- 1 | package network.repository.person 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import network.models.ApiError 5 | import network.models.content.common.PersonResponse 6 | import network.models.content.person.PersonCreditsResponse 7 | import network.models.content.person.PersonImagesResponse 8 | import network.util.Either 9 | 10 | interface PersonRepository { 11 | suspend fun getPersonDetailsById( 12 | personId: Int, 13 | ): Flow> 14 | 15 | suspend fun getPersonCreditsById( 16 | personId: Int, 17 | ): Flow> 18 | 19 | suspend fun getPersonImagesById( 20 | personId: Int, 21 | ): Flow> 22 | } 23 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/network/NetworkClient.ios.kt: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import common.util.Constants.BASE_URL_MOVIEDB 4 | import io.ktor.client.HttpClient 5 | import io.ktor.client.engine.darwin.Darwin 6 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation 7 | import io.ktor.client.plugins.defaultRequest 8 | import io.ktor.serialization.kotlinx.json.json 9 | import kotlinx.serialization.json.Json 10 | 11 | actual val client: HttpClient 12 | get() = HttpClient(Darwin) { 13 | defaultRequest { 14 | url(BASE_URL_MOVIEDB) 15 | } 16 | install(ContentNegotiation) { 17 | json( 18 | Json { 19 | ignoreUnknownKeys = true 20 | isLenient = true 21 | }, 22 | ) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/network/NetworkClient.android.kt: -------------------------------------------------------------------------------- 1 | package network 2 | 3 | import common.util.Constants.BASE_URL_MOVIEDB 4 | import io.ktor.client.HttpClient 5 | import io.ktor.client.engine.okhttp.OkHttp 6 | import io.ktor.client.plugins.contentnegotiation.ContentNegotiation 7 | import io.ktor.client.plugins.defaultRequest 8 | import io.ktor.serialization.kotlinx.json.json 9 | import kotlinx.serialization.json.Json 10 | 11 | actual val client: HttpClient 12 | get() = HttpClient(OkHttp) { 13 | install(ContentNegotiation) { 14 | json( 15 | Json { 16 | ignoreUnknownKeys = true 17 | isLenient = true 18 | }, 19 | ) 20 | } 21 | defaultRequest { 22 | url(BASE_URL_MOVIEDB) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/browse/ui/components/MediaTypeTabItem.kt: -------------------------------------------------------------------------------- 1 | package features.browse.ui.components 2 | 3 | import cinetracker_kmp.composeapp.generated.resources.Res 4 | import cinetracker_kmp.composeapp.generated.resources.movies_tab 5 | import cinetracker_kmp.composeapp.generated.resources.shows_tab 6 | import common.domain.models.util.MediaType 7 | import org.jetbrains.compose.resources.StringResource 8 | 9 | sealed class MediaTypeTabItem( 10 | val tabResId: StringResource, 11 | val mediaType: MediaType, 12 | ) { 13 | data object Movies : MediaTypeTabItem( 14 | tabResId = Res.string.movies_tab, 15 | mediaType = MediaType.MOVIE, 16 | ) 17 | data object Shows : MediaTypeTabItem( 18 | tabResId = Res.string.shows_tab, 19 | mediaType = MediaType.SHOW, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/common/ContentCastResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.common 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class ContentCastResponse( 7 | val id: Int, 8 | val name: String, 9 | val character: String? = null, 10 | val profile_path: String? = null, 11 | val adult: Boolean? = null, 12 | val credit_id: String? = null, 13 | val gender: Int? = null, 14 | val known_for_department: String? = null, 15 | val order: Int? = null, 16 | val original_name: String? = null, 17 | val popularity: Double? = null, 18 | val roles: List? = null, 19 | ) 20 | 21 | @Serializable 22 | data class CastRoles( 23 | val credit_id: String? = null, 24 | val character: String? = null, 25 | val episode_count: Int? = null, 26 | ) 27 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/watchlist/ui/components/WatchlistSortTypeItem.kt: -------------------------------------------------------------------------------- 1 | package features.watchlist.ui.components 2 | 3 | import cinetracker_kmp.composeapp.generated.resources.Res 4 | import cinetracker_kmp.composeapp.generated.resources.movie_tag 5 | import cinetracker_kmp.composeapp.generated.resources.show_tag 6 | import common.domain.models.util.MediaType 7 | import org.jetbrains.compose.resources.StringResource 8 | 9 | sealed class WatchlistSortTypeItem( 10 | val titleRes: StringResource, 11 | val mediaType: MediaType, 12 | ) { 13 | data object MovieOnly : WatchlistSortTypeItem( 14 | titleRes = Res.string.movie_tag, 15 | mediaType = MediaType.MOVIE, 16 | ) 17 | data object ShowOnly : WatchlistSortTypeItem( 18 | titleRes = Res.string.show_tag, 19 | mediaType = MediaType.SHOW, 20 | ) 21 | } 22 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/util/CardsUtil.kt: -------------------------------------------------------------------------------- 1 | package common.util 2 | 3 | import androidx.compose.ui.unit.Density 4 | import androidx.compose.ui.unit.Dp 5 | import kotlin.math.floor 6 | 7 | fun calculateCardsPerRow( 8 | screenWidth: Int, 9 | minCardSize: Int, 10 | spacing: Int, 11 | density: Density, 12 | ): Pair { 13 | // Calculate the number of cards that can fit in a row 14 | val numCardsPerRow = 15 | floor( 16 | (screenWidth + spacing).toFloat() / 17 | (minCardSize), 18 | ).coerceAtLeast(1f).toInt() 19 | 20 | // Calculate the adjusted size for each card 21 | val totalSpacing = (numCardsPerRow - 1) * spacing 22 | val adjustedCardSize = density.run { ((screenWidth - totalSpacing) / numCardsPerRow).toDp() } 23 | 24 | return Pair(numCardsPerRow, adjustedCardSize) 25 | } 26 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/repository/home/HomeRepository.kt: -------------------------------------------------------------------------------- 1 | package network.repository.home 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import network.models.ApiError 5 | import network.models.content.common.MovieResponse 6 | import network.models.content.common.MultiResponse 7 | import network.models.content.common.PersonResponse 8 | import network.models.content.search.ContentPagingResponse 9 | import network.util.Either 10 | 11 | interface HomeRepository { 12 | suspend fun getTrendingMulti(): Flow, ApiError>> 13 | suspend fun getTrendingPerson(): Flow, ApiError>> 14 | suspend fun getMoviesComingSoon( 15 | releaseDateGte: String, 16 | releaseDateLte: String, 17 | ): Flow, ApiError>> 18 | } 19 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/models/content/person/CrewResponse.kt: -------------------------------------------------------------------------------- 1 | package network.models.content.person 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class CrewResponse( 7 | val adult: Boolean? = null, 8 | val backdrop_path: String? = null, 9 | val credit_id: String? = null, 10 | val department: String? = null, 11 | val genre_ids: List? = null, 12 | val id: Int? = null, 13 | val job: String? = null, 14 | val original_language: String? = null, 15 | val original_title: String? = null, 16 | val overview: String? = null, 17 | val popularity: Double? = null, 18 | val poster_path: String? = null, 19 | val release_date: String? = null, 20 | val title: String? = null, 21 | val video: Boolean? = null, 22 | val vote_average: Double? = null, 23 | val vote_count: Int? = null, 24 | ) 25 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/util/Constants.kt: -------------------------------------------------------------------------------- 1 | package common.util 2 | 3 | object Constants { 4 | const val BASE_URL_MOVIEDB = "https://api.themoviedb.org/3/" 5 | const val BASE_500_IMAGE_URL = "https://image.tmdb.org/t/p/w500" 6 | const val BASE_300_IMAGE_URL = "https://image.tmdb.org/t/p/w300" 7 | const val BASE_ORIGINAL_IMAGE_URL = "https://image.tmdb.org/t/p/original" 8 | const val BASE_YOUTUBE_THUMBAIL_URL = "https://img.youtube.com/vi/" 9 | const val YOUTUBE_THUMBAIL_RESOLUTION = "/maxresdefault.jpg" 10 | 11 | const val BASE_URL_YOUTUBE_VIDEO = "http://www.youtube.com/watch?v=" 12 | 13 | const val PAGE_SIZE = 20 14 | 15 | const val UNSELECTED_OPTION_INDEX = -1 16 | const val ADD_NEW_TAB_ID = -2 17 | const val MAX_WATCHLIST_LIST_NUMBER = 12 18 | const val DEFAULT_LISTS_SIZE = 2 19 | const val MONTH_PERIOD_COMING_SOON = 1 20 | } 21 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/core/di/KoinInitializer.ios.kt: -------------------------------------------------------------------------------- 1 | package core.di 2 | 3 | import core.di.modules.interactorModule 4 | import core.di.modules.viewModelModule 5 | import database.di.daoModule 6 | import database.di.databaseModule 7 | import database.di.databaseRepositoryModule 8 | import network.di.apiModule 9 | import network.di.repositoryModule 10 | import network.di.serviceModule 11 | import org.koin.core.context.startKoin 12 | 13 | actual class KoinInitializer { 14 | actual fun init() { 15 | startKoin { 16 | modules( 17 | interactorModule, 18 | viewModelModule, 19 | databaseModule(), 20 | daoModule, 21 | databaseRepositoryModule, 22 | apiModule, 23 | serviceModule, 24 | repositoryModule, 25 | ) 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/core/di/modules/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package core.di.modules 2 | 3 | import common.domain.models.util.MediaType 4 | import common.ui.MainViewModel 5 | import features.browse.ui.BrowseViewModel 6 | import features.details.ui.DetailsViewModel 7 | import features.home.ui.HomeViewModel 8 | import features.search.ui.SearchViewModel 9 | import features.watchlist.ui.WatchlistViewModel 10 | import org.koin.compose.viewmodel.dsl.viewModel 11 | import org.koin.dsl.module 12 | 13 | val viewModelModule = module { 14 | single { MainViewModel(get()) } 15 | viewModel { HomeViewModel(get()) } 16 | viewModel { BrowseViewModel(get()) } 17 | viewModel { WatchlistViewModel(get()) } 18 | viewModel { SearchViewModel(get()) } 19 | viewModel { 20 | (contentId: Int, mediaType: MediaType) -> 21 | DetailsViewModel(contentId, mediaType, get()) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/services/person/PersonService.kt: -------------------------------------------------------------------------------- 1 | package network.services.person 2 | 3 | import core.LanguageManager.getUserLanguageTag 4 | import network.models.content.common.PersonResponse 5 | import network.models.content.person.PersonCreditsResponse 6 | import network.models.content.person.PersonImagesResponse 7 | import network.util.ApiResult 8 | 9 | interface PersonService { 10 | suspend fun getPersonDetailsById( 11 | personId: Int, 12 | language: String = getUserLanguageTag(), 13 | ): ApiResult 14 | 15 | suspend fun getPersonCreditsById( 16 | personId: Int, 17 | language: String = getUserLanguageTag(), 18 | ): ApiResult 19 | 20 | suspend fun getPersonImagesById( 21 | personId: Int, 22 | language: String = getUserLanguageTag(), 23 | ): ApiResult 24 | } 25 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/di/ServiceModule.kt: -------------------------------------------------------------------------------- 1 | package network.di 2 | 3 | import network.services.home.HomeService 4 | import network.services.home.HomeServiceImpl 5 | import network.services.movie.MovieService 6 | import network.services.movie.MovieServiceImpl 7 | import network.services.person.PersonService 8 | import network.services.person.PersonServiceImpl 9 | import network.services.search.SearchService 10 | import network.services.search.SearchServiceImpl 11 | import network.services.show.ShowService 12 | import network.services.show.ShowServiceImpl 13 | import org.koin.dsl.module 14 | 15 | val serviceModule = module { 16 | single { MovieServiceImpl(get()) } 17 | single { ShowServiceImpl(get()) } 18 | single { PersonServiceImpl(get()) } 19 | single { SearchServiceImpl(get()) } 20 | single { HomeServiceImpl(get()) } 21 | } 22 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/button/SimpleButton.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.button 2 | 3 | import androidx.compose.material3.MaterialTheme 4 | import androidx.compose.material3.Text 5 | import androidx.compose.material3.TextButton 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.graphics.Color 9 | import androidx.compose.ui.text.TextStyle 10 | 11 | @Composable 12 | fun SimpleButton( 13 | modifier: Modifier = Modifier, 14 | text: String, 15 | textColor: Color = MaterialTheme.colorScheme.secondary, 16 | textStyle: TextStyle = MaterialTheme.typography.titleMedium, 17 | onClick: () -> Unit, 18 | ) { 19 | TextButton( 20 | modifier = modifier, 21 | onClick = onClick, 22 | ) { 23 | Text( 24 | text = text, 25 | style = textStyle, 26 | color = textColor, 27 | ) 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/search/domain/SearchInteractor.kt: -------------------------------------------------------------------------------- 1 | package features.search.domain 2 | 3 | import androidx.paging.Pager 4 | import androidx.paging.PagingConfig 5 | import androidx.paging.PagingData 6 | import common.domain.models.content.GenericContent 7 | import common.domain.models.util.MediaType 8 | import common.util.Constants 9 | import features.search.ui.paging.SearchPagingSource 10 | import kotlinx.coroutines.flow.Flow 11 | import network.repository.search.SearchRepository 12 | 13 | class SearchInteractor( 14 | private val searchRepository: SearchRepository, 15 | ) { 16 | fun onSearchQuery( 17 | query: String, 18 | mediaType: MediaType?, 19 | ): Flow> { 20 | return Pager(PagingConfig(pageSize = Constants.PAGE_SIZE)) { 21 | SearchPagingSource( 22 | searchRepository, 23 | query, 24 | mediaType, 25 | ) 26 | }.flow 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/services/home/HomeService.kt: -------------------------------------------------------------------------------- 1 | package network.services.home 2 | 3 | import core.LanguageManager.getUserLanguageTag 4 | import network.models.content.common.MovieResponse 5 | import network.models.content.common.MultiResponse 6 | import network.models.content.common.PersonResponse 7 | import network.models.content.search.ContentPagingResponse 8 | import network.util.ApiResult 9 | 10 | interface HomeService { 11 | suspend fun getDayTrendingMulti( 12 | language: String = getUserLanguageTag(), 13 | ): ApiResult> 14 | 15 | suspend fun getDayTrendingPerson( 16 | language: String = getUserLanguageTag(), 17 | ): ApiResult> 18 | 19 | suspend fun getMoviesComingSoon( 20 | language: String = getUserLanguageTag(), 21 | releaseDateGte: String, 22 | releaseDateLte: String, 23 | ): ApiResult> 24 | } 25 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/di/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package network.di 2 | 3 | import network.repository.home.HomeRepository 4 | import network.repository.home.HomeRepositoryImpl 5 | import network.repository.movie.MovieRepository 6 | import network.repository.movie.MovieRepositoryImpl 7 | import network.repository.person.PersonRepository 8 | import network.repository.person.PersonRepositoryImpl 9 | import network.repository.search.SearchRepository 10 | import network.repository.search.SearchRepositoryImpl 11 | import network.repository.show.ShowRepository 12 | import network.repository.show.ShowRepositoryImpl 13 | import org.koin.dsl.module 14 | 15 | val repositoryModule = module { 16 | single { MovieRepositoryImpl(get()) } 17 | single { ShowRepositoryImpl(get()) } 18 | single { PersonRepositoryImpl(get()) } 19 | single { SearchRepositoryImpl(get()) } 20 | single { HomeRepositoryImpl(get()) } 21 | } 22 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/common/util/platform/PlatformUtils.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import platform.Foundation.NSLocale 4 | import platform.Foundation.countryCode 5 | import platform.Foundation.currentLocale 6 | import platform.Foundation.preferredLanguages 7 | 8 | const val DEFAULT_LANGUAGE = "en" 9 | const val DEFAULT_COUNTRY = "US" 10 | 11 | actual object PlatformUtils { 12 | actual val isIOS: Boolean = true 13 | actual fun getUserLanguage(): String { 14 | val languageTag = (NSLocale.preferredLanguages.firstOrNull() as String?) 15 | return languageTag?.split("-")?.first() ?: DEFAULT_LANGUAGE 16 | } 17 | actual fun getUserCountry(): String = NSLocale.currentLocale.countryCode ?: DEFAULT_COUNTRY 18 | actual fun getLocale(): String { 19 | val locale = (NSLocale.preferredLanguages.firstOrNull() as String?) 20 | val language = getUserLanguage() 21 | val country = getUserCountry() 22 | 23 | return locale ?: "$language-$country" 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/repository/DatabaseRepository.kt: -------------------------------------------------------------------------------- 1 | package database.repository 2 | 3 | import common.domain.models.util.MediaType 4 | import database.model.ContentEntity 5 | import database.model.ListEntity 6 | 7 | interface DatabaseRepository { 8 | suspend fun insertItem(contentId: Int, mediaType: MediaType, listId: Int) 9 | 10 | suspend fun deleteItem(contentId: Int, mediaType: MediaType, listId: Int): ContentEntity? 11 | 12 | suspend fun getAllItemsByListId(listId: Int): List 13 | 14 | suspend fun searchItems(contentId: Int, mediaType: MediaType): List 15 | 16 | suspend fun moveItemToList( 17 | contentId: Int, 18 | mediaType: MediaType, 19 | currentListId: Int, 20 | newListId: Int, 21 | ): ContentEntity? 22 | 23 | suspend fun reinsertItem(contentEntity: ContentEntity) 24 | 25 | suspend fun getAllLists(): List 26 | 27 | suspend fun addNewList(listName: String): Boolean 28 | 29 | suspend fun deleteList(listId: Int) 30 | } 31 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/watchlist/events/WatchlistEvent.kt: -------------------------------------------------------------------------------- 1 | package features.watchlist.events 2 | 3 | import common.domain.models.util.MediaType 4 | import features.watchlist.ui.components.WatchlistTabItem 5 | 6 | sealed class WatchlistEvent { 7 | data object LoadWatchlistData : WatchlistEvent() 8 | data object OnSnackbarDismiss : WatchlistEvent() 9 | data object UndoItemAction : WatchlistEvent() 10 | data class RemoveItem( 11 | val contentId: Int, 12 | val mediaType: MediaType, 13 | ) : WatchlistEvent() 14 | data class SelectList( 15 | val tabItem: WatchlistTabItem, 16 | ) : WatchlistEvent() 17 | data class UpdateSortType( 18 | val mediaType: MediaType?, 19 | ) : WatchlistEvent() 20 | data class UpdateItemListId( 21 | val contentId: Int, 22 | val mediaType: MediaType, 23 | val listId: Int, 24 | ) : WatchlistEvent() 25 | data object LoadAllLists : WatchlistEvent() 26 | data class DeleteList( 27 | val listId: Int, 28 | ) : WatchlistEvent() 29 | } 30 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/core/di/KoinInitializer.android.kt: -------------------------------------------------------------------------------- 1 | package core.di 2 | 3 | import android.content.Context 4 | import core.di.modules.interactorModule 5 | import core.di.modules.viewModelModule 6 | import database.di.daoModule 7 | import database.di.databaseModule 8 | import database.di.databaseRepositoryModule 9 | import network.di.apiModule 10 | import network.di.repositoryModule 11 | import network.di.serviceModule 12 | import org.koin.android.ext.koin.androidContext 13 | import org.koin.core.context.startKoin 14 | 15 | actual class KoinInitializer( 16 | private val context: Context, 17 | ) { 18 | actual fun init() { 19 | startKoin { 20 | androidContext(context) 21 | modules( 22 | interactorModule, 23 | viewModelModule, 24 | databaseModule(), 25 | daoModule, 26 | databaseRepositoryModule, 27 | apiModule, 28 | serviceModule, 29 | repositoryModule, 30 | ) 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/util/DataLoadStatus.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.util 2 | 3 | sealed class DataLoadStatus { 4 | data object Empty : DataLoadStatus() 5 | 6 | data object Loading : DataLoadStatus() 7 | 8 | data object Success : DataLoadStatus() 9 | 10 | data object Failed : DataLoadStatus() 11 | } 12 | 13 | open class LoadStatusState { 14 | var loadStatus: DataLoadStatus = DataLoadStatus.Loading 15 | var errorCode: String? = null 16 | 17 | fun setSuccess() { 18 | this.loadStatus = DataLoadStatus.Success 19 | } 20 | 21 | fun setError(errorCode: String?) { 22 | this.loadStatus = DataLoadStatus.Failed 23 | this.errorCode = errorCode 24 | } 25 | 26 | fun setLoading() { 27 | this.loadStatus = DataLoadStatus.Loading 28 | } 29 | 30 | fun isLoaded(): Boolean = this.loadStatus == DataLoadStatus.Success 31 | 32 | fun isLoading(): Boolean = this.loadStatus == DataLoadStatus.Loading 33 | 34 | fun isFailed(): Boolean = this.loadStatus == DataLoadStatus.Failed 35 | } 36 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/common/util/platform/StringFormat.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import java.text.DecimalFormat 4 | import java.text.DecimalFormatSymbols 5 | import java.text.NumberFormat 6 | import java.util.Locale 7 | 8 | actual object StringFormat { 9 | actual fun formatRating(number: Double): String { 10 | val symbols = DecimalFormatSymbols.getInstance().apply { 11 | decimalSeparator = '.' 12 | } 13 | var formattedRating = DecimalFormat("#.#", symbols).format(number) 14 | if (formattedRating.length == 1) { 15 | formattedRating += "${symbols.decimalSeparator}0" 16 | } 17 | return formattedRating 18 | } 19 | 20 | actual fun Long.toFormattedCurrency(): String { 21 | return try { 22 | val decimalFormat = (NumberFormat.getCurrencyInstance(Locale.US)).apply { 23 | maximumFractionDigits = 0 24 | } 25 | decimalFormat.format(this) 26 | } catch (e: IllegalArgumentException) { 27 | "" 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/domain/models/person/PersonDetails.kt: -------------------------------------------------------------------------------- 1 | package common.domain.models.person 2 | 3 | import common.domain.models.util.MediaType 4 | import network.models.content.common.MultiResponse 5 | import network.models.content.common.PersonResponse 6 | 7 | data class PersonDetails( 8 | val id: Int, 9 | val title: String, 10 | val overview: String, 11 | val posterPath: String, 12 | val mediaType: MediaType, 13 | val birthday: String?, 14 | val deathday: String?, 15 | val placeOfBirth: String?, 16 | val knownForDepartment: String?, 17 | val knownFor: List, 18 | ) 19 | 20 | fun PersonResponse.toPersonDetails(): PersonDetails = 21 | PersonDetails( 22 | id = this.id, 23 | title = this.name.orEmpty(), 24 | overview = this.overview.orEmpty(), 25 | posterPath = this.profile_path.orEmpty(), 26 | mediaType = MediaType.PERSON, 27 | birthday = null, 28 | deathday = null, 29 | placeOfBirth = null, 30 | knownForDepartment = this.known_for_department, 31 | knownFor = this.known_for.orEmpty(), 32 | ) 33 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/details/DetailsScreen.kt: -------------------------------------------------------------------------------- 1 | package features.details 2 | 3 | import androidx.navigation.NamedNavArgument 4 | import androidx.navigation.NavType 5 | import androidx.navigation.navArgument 6 | import navigation.Screen 7 | 8 | object DetailsScreen : Screen { 9 | private const val DETAILS_ROUTE = "details" 10 | const val ARG_CONTENT_ID = "contentId" 11 | const val ARG_MEDIA_TYPE = "mediaType" 12 | private const val FULL_DETAILS_ROUTE = 13 | "$DETAILS_ROUTE/{$ARG_CONTENT_ID}?$ARG_MEDIA_TYPE={$ARG_MEDIA_TYPE}" 14 | override fun route(): String = FULL_DETAILS_ROUTE 15 | 16 | override val arguments: List 17 | get() = listOf( 18 | navArgument(ARG_CONTENT_ID) { 19 | type = NavType.IntType 20 | }, 21 | ) 22 | 23 | fun routeWithArguments( 24 | contentId: Int, 25 | mediaType: String, 26 | ): String { 27 | return buildString { 28 | append(DETAILS_ROUTE) 29 | append("/$contentId?") 30 | append("$ARG_MEDIA_TYPE=$mediaType") 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/details/util/Extensions.kt: -------------------------------------------------------------------------------- 1 | package features.details.util 2 | 3 | import androidx.compose.runtime.Composable 4 | import common.util.platform.StringFormat.toFormattedCurrency 5 | 6 | @Composable 7 | fun Int.formatRuntime(): String { 8 | val runtimeHours: Int = this / 60 9 | val runtimeMinutes = this % 60 10 | 11 | return if (runtimeHours != 0 && runtimeMinutes != 0) { 12 | "${runtimeHours}h ${runtimeMinutes}m" 13 | } else if (runtimeHours != 0) { 14 | "${runtimeHours}h" 15 | } else { 16 | "${runtimeMinutes}m" 17 | } 18 | } 19 | 20 | /** 21 | * Function to convert header position into a value between 0.0 and 1.0. 22 | * Used to define the background image alpha based on the header position. 23 | */ 24 | fun Float.mapValueToRange(initialHeaderPosY: Float): Float { 25 | val maxValue = 0f 26 | val mappedValue = (this - initialHeaderPosY) / (maxValue - initialHeaderPosY) 27 | return (1.0f - mappedValue).coerceIn(0f, 1f) 28 | } 29 | 30 | fun Long?.isValidValue(): Boolean { 31 | return this != null && this > 0 32 | } 33 | 34 | fun Long.toFormattedCurrency(): String { 35 | return this.toFormattedCurrency() 36 | } 37 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/repository/search/SearchRepository.kt: -------------------------------------------------------------------------------- 1 | package network.repository.search 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import network.models.ApiError 5 | import network.models.content.common.MovieResponse 6 | import network.models.content.common.MultiResponse 7 | import network.models.content.common.PersonResponse 8 | import network.models.content.common.ShowResponse 9 | import network.models.content.search.ContentPagingResponse 10 | import network.util.Either 11 | 12 | interface SearchRepository { 13 | suspend fun onSearchMultiByQuery( 14 | query: String, 15 | page: Int, 16 | ): Flow, ApiError>> 17 | 18 | suspend fun onSearchMovieByQuery( 19 | query: String, 20 | page: Int, 21 | ): Flow, ApiError>> 22 | 23 | suspend fun onSearchShowByQuery( 24 | query: String, 25 | page: Int, 26 | ): Flow, ApiError>> 27 | 28 | suspend fun onSearchPersonByQuery( 29 | query: String, 30 | page: Int, 31 | ): Flow, ApiError>> 32 | } 33 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/screens/BrowseScreenUI.kt: -------------------------------------------------------------------------------- 1 | package navigation.screens 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.navigation.NavController 6 | import androidx.navigation.compose.currentBackStackEntryAsState 7 | import common.ui.screen.ErrorScreen 8 | import features.browse.ui.Browse 9 | import features.details.DetailsScreen 10 | import navigation.ScreenUI 11 | 12 | class BrowseScreenUI : ScreenUI { 13 | @Composable 14 | override fun UI(navController: NavController) { 15 | val currentBackStackEntry by navController.currentBackStackEntryAsState() 16 | val currentScreen = currentBackStackEntry?.destination?.route 17 | 18 | Browse( 19 | goToDetails = { contentId, mediaType -> 20 | navController.navigate( 21 | DetailsScreen.routeWithArguments(contentId, mediaType.name), 22 | ) 23 | }, 24 | goToErrorScreen = { 25 | if (currentScreen != ErrorScreen.route()) { 26 | navController.navigate(ErrorScreen.route()) 27 | } 28 | }, 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/screens/SearchScreenUI.kt: -------------------------------------------------------------------------------- 1 | package navigation.screens 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.navigation.NavController 6 | import androidx.navigation.compose.currentBackStackEntryAsState 7 | import common.ui.screen.ErrorScreen 8 | import features.details.DetailsScreen 9 | import features.search.ui.Search 10 | import navigation.ScreenUI 11 | 12 | class SearchScreenUI : ScreenUI { 13 | @Composable 14 | override fun UI(navController: NavController) { 15 | val currentBackStackEntry by navController.currentBackStackEntryAsState() 16 | val currentScreen = currentBackStackEntry?.destination?.route 17 | 18 | Search( 19 | goToDetails = { contentId, mediaType -> 20 | navController.navigate( 21 | DetailsScreen.routeWithArguments(contentId, mediaType.name), 22 | ) 23 | }, 24 | goToErrorScreen = { 25 | if (currentScreen != ErrorScreen.route()) { 26 | navController.navigate(ErrorScreen.route()) 27 | } 28 | }, 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/browse/domain/BrowseInteractor.kt: -------------------------------------------------------------------------------- 1 | package features.browse.domain 2 | 3 | import androidx.paging.Pager 4 | import androidx.paging.PagingConfig 5 | import androidx.paging.PagingData 6 | import common.domain.models.content.GenericContent 7 | import common.domain.models.util.ContentListType 8 | import common.domain.models.util.MediaType 9 | import common.util.Constants.PAGE_SIZE 10 | import features.browse.ui.paging.MediaContentPagingSource 11 | import kotlinx.coroutines.flow.Flow 12 | import network.repository.movie.MovieRepository 13 | import network.repository.show.ShowRepository 14 | 15 | class BrowseInteractor( 16 | private val movieRepository: MovieRepository, 17 | private val showRepository: ShowRepository, 18 | ) { 19 | fun getMediaContentListPager( 20 | contentListType: ContentListType, 21 | mediaType: MediaType, 22 | ): Flow> { 23 | return Pager(PagingConfig(pageSize = PAGE_SIZE)) { 24 | MediaContentPagingSource( 25 | movieRepository, 26 | showRepository, 27 | contentListType, 28 | mediaType, 29 | ) 30 | }.flow 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/screens/WatchlistScreenUI.kt: -------------------------------------------------------------------------------- 1 | package navigation.screens 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.navigation.NavController 6 | import androidx.navigation.compose.currentBackStackEntryAsState 7 | import common.ui.screen.ErrorScreen 8 | import features.details.DetailsScreen 9 | import features.watchlist.ui.Watchlist 10 | import navigation.ScreenUI 11 | 12 | class WatchlistScreenUI : ScreenUI { 13 | @Composable 14 | override fun UI(navController: NavController) { 15 | val currentBackStackEntry by navController.currentBackStackEntryAsState() 16 | val currentScreen = currentBackStackEntry?.destination?.route 17 | 18 | Watchlist( 19 | goToDetails = { contentId, mediaType -> 20 | navController.navigate( 21 | DetailsScreen.routeWithArguments(contentId, mediaType.name), 22 | ) 23 | }, 24 | goToErrorScreen = { 25 | if (currentScreen != ErrorScreen.route()) { 26 | navController.navigate(ErrorScreen.route()) 27 | } 28 | }, 29 | ) 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /composeApp/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle.kts. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | 23 | -dontwarn org.slf4j.impl.StaticLoggerBinder 24 | 25 | # With R8 full mode generic signatures are stripped for classes that are not 26 | # kept. Suspend functions are wrapped in continuations where the type argument 27 | # is used. 28 | -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation 29 | 30 | -keep class com.projects.moviemanager.network.models.** { *; } -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/common/util/platform/DateUtils.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import platform.Foundation.NSCalendar 4 | import platform.Foundation.NSCalendarUnitMonth 5 | import platform.Foundation.NSDate 6 | import platform.Foundation.NSDateFormatter 7 | import platform.Foundation.NSLocale 8 | import platform.Foundation.currentLocale 9 | import platform.Foundation.timeIntervalSince1970 10 | 11 | actual object DateUtils { 12 | actual fun getComingSoonDates( 13 | monthPeriod: Int, 14 | ): Pair { 15 | val calendar = NSCalendar.currentCalendar 16 | 17 | val dateFormatter = NSDateFormatter().apply { 18 | dateFormat = "yyyy-MM-dd" 19 | locale = NSLocale.currentLocale 20 | } 21 | val releaseDateGte = dateFormatter.stringFromDate(NSDate()) 22 | val releaseDateLte = calendar.dateByAddingUnit( 23 | NSCalendarUnitMonth, monthPeriod.toLong(), NSDate(), 0u, 24 | )?.let { 25 | dateFormatter.stringFromDate(it) 26 | } ?: releaseDateGte 27 | return Pair(releaseDateGte, releaseDateLte) 28 | } 29 | 30 | actual fun getCurrentTimeMillis(): Long = (NSDate().timeIntervalSince1970 * 1000).toLong() 31 | } 32 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 14 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/theme/Theme.kt: -------------------------------------------------------------------------------- 1 | package common.ui.theme 2 | 3 | import androidx.compose.foundation.isSystemInDarkTheme 4 | import androidx.compose.material3.MaterialTheme 5 | import androidx.compose.material3.darkColorScheme 6 | import androidx.compose.runtime.Composable 7 | 8 | private val DarkColorPalette = 9 | darkColorScheme( 10 | primary = PrimaryBlackColor, 11 | onPrimary = PrimaryWhiteColor, 12 | secondary = PrimaryYellowColor, 13 | onSecondary = PrimaryBlackColor, 14 | tertiary = PrimaryGreyColor, 15 | surface = SecondaryGreyColor, 16 | onSurface = PrimaryWhiteColor, 17 | onSurfaceVariant = PrimaryYellowColor, 18 | inverseSurface = DividerGrey, 19 | surfaceVariant = PrimaryGreyColor_55, 20 | background = PrimaryBlackColor, 21 | error = PrimaryRedColor, 22 | ) 23 | 24 | @Composable 25 | fun CineTrackerTheme( 26 | darkTheme: Boolean = isSystemInDarkTheme(), 27 | content: 28 | @Composable() 29 | () -> Unit, 30 | ) { 31 | MaterialTheme( 32 | colorScheme = DarkColorPalette, 33 | typography = AppTypography, 34 | shapes = RoundCornerShapes, 35 | content = content, 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/repository/person/PersonRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package network.repository.person 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import network.models.ApiError 5 | import network.models.content.common.PersonResponse 6 | import network.models.content.person.PersonCreditsResponse 7 | import network.models.content.person.PersonImagesResponse 8 | import network.services.person.PersonService 9 | import network.util.Either 10 | import network.util.asFlow 11 | 12 | class PersonRepositoryImpl( 13 | private val personService: PersonService, 14 | ) : PersonRepository { 15 | override suspend fun getPersonDetailsById( 16 | personId: Int, 17 | ): Flow> { 18 | return personService.getPersonDetailsById(personId).asFlow() 19 | } 20 | 21 | override suspend fun getPersonCreditsById( 22 | personId: Int, 23 | ): Flow> { 24 | return personService.getPersonCreditsById(personId).asFlow() 25 | } 26 | 27 | override suspend fun getPersonImagesById( 28 | personId: Int, 29 | ): Flow> { 30 | return personService.getPersonImagesById(personId).asFlow() 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /composeApp/src/iosMain/kotlin/common/util/platform/StringFormat.kt: -------------------------------------------------------------------------------- 1 | package common.util.platform 2 | 3 | import platform.Foundation.NSLocale 4 | import platform.Foundation.NSNumber 5 | import platform.Foundation.NSNumberFormatter 6 | import platform.Foundation.NSNumberFormatterCurrencyStyle 7 | import platform.Foundation.localeWithLocaleIdentifier 8 | 9 | actual object StringFormat { 10 | actual fun formatRating(number: Double): String { 11 | val formatter = NSNumberFormatter() 12 | formatter.minimumFractionDigits = 1u 13 | formatter.maximumFractionDigits = 1u 14 | formatter.numberStyle = 1u 15 | return formatter.stringFromNumber(NSNumber(number))!! 16 | } 17 | 18 | actual fun Long.toFormattedCurrency(): String { 19 | return try { 20 | val numberFormatter = NSNumberFormatter().apply { 21 | numberStyle = NSNumberFormatterCurrencyStyle 22 | locale = NSLocale.localeWithLocaleIdentifier("en_US") 23 | maximumFractionDigits = 0u 24 | } 25 | val nsNumber = NSNumber(this.toDouble()) 26 | numberFormatter.stringFromNumber(nsNumber) ?: "" 27 | } catch (e: Exception) { 28 | "" 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/button/GenericButton.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.button 2 | 3 | import androidx.compose.foundation.shape.RoundedCornerShape 4 | import androidx.compose.material3.Button 5 | import androidx.compose.material3.ButtonDefaults 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.unit.dp 11 | import common.util.UiConstants.CLASSIC_BUTTON_BORDER_SIZE 12 | 13 | @Composable 14 | fun GenericButton( 15 | modifier: Modifier = Modifier, 16 | buttonText: String, 17 | enabled: Boolean = true, 18 | onClick: () -> Unit, 19 | ) { 20 | Button( 21 | modifier = modifier, 22 | onClick = onClick, 23 | colors = ButtonDefaults.buttonColors( 24 | containerColor = MaterialTheme.colorScheme.secondary, 25 | ), 26 | shape = RoundedCornerShape(CLASSIC_BUTTON_BORDER_SIZE.dp), 27 | enabled = enabled, 28 | ) { 29 | Text( 30 | text = buttonText, 31 | style = MaterialTheme.typography.titleMedium, 32 | color = MaterialTheme.colorScheme.primary, 33 | ) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/core/LanguageManager.kt: -------------------------------------------------------------------------------- 1 | package core 2 | 3 | import common.util.platform.PlatformUtils.getLocale 4 | import common.util.platform.PlatformUtils.getUserCountry 5 | import common.util.platform.PlatformUtils.getUserLanguage 6 | 7 | object LanguageManager { 8 | private val supportedLanguages = listOf( 9 | "en-US", 10 | "en-CA", 11 | "pt-BR", 12 | "es-ES", 13 | "es-MX", 14 | "es-BO", 15 | "es-CL", 16 | "es-CO", 17 | "es-EC", 18 | "es-PY", 19 | "es-PE", 20 | "es-PR", 21 | "es-UY", 22 | "es-VE", 23 | "es-CR", 24 | ) 25 | private const val DEFAULT_LANGUAGE = "en-US" 26 | 27 | fun getUserLanguageTag(): String { 28 | val languageTag = getLocale() 29 | val language = getUserLanguage() 30 | 31 | return when { 32 | languageTag.isSupported() -> languageTag 33 | language == "pt" -> "pt-BR" 34 | language == "es" -> "es-ES" 35 | else -> DEFAULT_LANGUAGE 36 | } 37 | } 38 | 39 | private fun String.isSupported(): Boolean { 40 | return supportedLanguages.contains(this) 41 | } 42 | 43 | fun getUserCountryCode(): String = getUserCountry() 44 | } 45 | -------------------------------------------------------------------------------- /iosApp/iosApp/ContentView.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | import SwiftUI 3 | import ComposeApp 4 | 5 | struct ComposeView: UIViewControllerRepresentable { 6 | func makeUIViewController(context: Context) -> UIViewController { 7 | MainViewControllerKt.MainViewController() 8 | } 9 | 10 | func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} 11 | } 12 | 13 | struct ContentView: View { 14 | var statusBarHeight = { 15 | return UIApplication.shared.windows.first?.safeAreaInsets.top 16 | } 17 | var systemNavBarHeight = { 18 | return UIApplication.shared.windows.first?.safeAreaInsets.bottom 19 | } 20 | 21 | var body: some View { 22 | GeometryReader { geometry in 23 | VStack(spacing: 0) { 24 | Color.black 25 | .frame(height: geometry.size.height / 2) 26 | Color(hex: 0x191A1D) 27 | } 28 | .ignoresSafeArea() 29 | 30 | ComposeView() 31 | .padding(.bottom, systemNavBarHeight()) 32 | .padding(.top, statusBarHeight()) 33 | .ignoresSafeArea(edges: .all) 34 | .ignoresSafeArea(.keyboard) // Compose has own keyboard handler 35 | } 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/database/dao/ContentEntityDao.kt: -------------------------------------------------------------------------------- 1 | package database.dao 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.OnConflictStrategy 6 | import androidx.room.Query 7 | import database.model.ContentEntity 8 | 9 | @Dao 10 | interface ContentEntityDao { 11 | @Query("SELECT * FROM content_entity WHERE listId = :listId ORDER BY createdAt DESC") 12 | suspend fun getAllItems(listId: Int): List 13 | 14 | @Insert(onConflict = OnConflictStrategy.IGNORE) 15 | suspend fun insert(contentEntity: ContentEntity) 16 | 17 | @Query( 18 | "DELETE FROM content_entity " + 19 | "WHERE contentId=:contentId AND mediaType = :mediaType AND listId = :listId", 20 | ) 21 | suspend fun delete(contentId: Int, mediaType: String, listId: Int) 22 | 23 | @Query( 24 | "SELECT * FROM content_entity WHERE contentId = :contentId AND mediaType = :mediaType", 25 | ) 26 | suspend fun searchItems(contentId: Int, mediaType: String): List 27 | 28 | @Query( 29 | "SELECT * FROM content_entity WHERE " + 30 | "contentId = :contentId AND mediaType = :mediaType AND listId = :listId", 31 | ) 32 | suspend fun getItem(contentId: Int, mediaType: String, listId: Int): ContentEntity? 33 | } 34 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/search/ui/components/SearchTypeFilterItem.kt: -------------------------------------------------------------------------------- 1 | package features.search.ui.components 2 | 3 | import cinetracker_kmp.composeapp.generated.resources.Res 4 | import cinetracker_kmp.composeapp.generated.resources.search_movies_tab 5 | import cinetracker_kmp.composeapp.generated.resources.search_person_tab 6 | import cinetracker_kmp.composeapp.generated.resources.search_shows_tab 7 | import cinetracker_kmp.composeapp.generated.resources.search_top_results_tab 8 | import common.domain.models.util.MediaType 9 | import org.jetbrains.compose.resources.StringResource 10 | 11 | sealed class SearchTypeFilterItem( 12 | val tabResId: StringResource, 13 | val mediaType: MediaType?, 14 | ) { 15 | data object TopResults : SearchTypeFilterItem( 16 | tabResId = Res.string.search_top_results_tab, 17 | mediaType = null, 18 | ) 19 | data object Movies : SearchTypeFilterItem( 20 | tabResId = Res.string.search_movies_tab, 21 | mediaType = MediaType.MOVIE, 22 | ) 23 | data object Shows : SearchTypeFilterItem( 24 | tabResId = Res.string.search_shows_tab, 25 | mediaType = MediaType.SHOW, 26 | ) 27 | data object Person : SearchTypeFilterItem( 28 | tabResId = Res.string.search_person_tab, 29 | mediaType = MediaType.PERSON, 30 | ) 31 | } 32 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/NetworkImage.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.foundation.layout.size 6 | import androidx.compose.foundation.layout.width 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Modifier 9 | import androidx.compose.ui.graphics.DefaultAlpha 10 | import androidx.compose.ui.layout.ContentScale 11 | import androidx.compose.ui.unit.Dp 12 | import coil3.compose.SubcomposeAsyncImage 13 | 14 | @Composable 15 | fun NetworkImage( 16 | modifier: Modifier = Modifier, 17 | imageUrl: String, 18 | widthDp: Dp = Dp.Unspecified, 19 | heightDp: Dp = Dp.Unspecified, 20 | contentScale: ContentScale = ContentScale.Crop, 21 | alpha: Float = DefaultAlpha, 22 | ) { 23 | Box { 24 | SubcomposeAsyncImage( 25 | modifier = modifier.size(widthDp, heightDp), 26 | model = imageUrl, 27 | contentDescription = null, 28 | contentScale = contentScale, 29 | alpha = alpha, 30 | loading = { 31 | ComponentPlaceholder( 32 | modifier = modifier.width(widthDp).height(heightDp), 33 | ) 34 | }, 35 | ) 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "CineTracker-KMP" 2 | enableFeaturePreview("TYPESAFE_PROJECT_ACCESSORS") 3 | 4 | pluginManagement { 5 | repositories { 6 | google { 7 | mavenContent { 8 | includeGroupAndSubgroups("androidx") 9 | includeGroupAndSubgroups("com.android") 10 | includeGroupAndSubgroups("com.google") 11 | } 12 | } 13 | mavenCentral() 14 | gradlePluginPortal() 15 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 16 | maven("https://androidx.dev/storage/compose-compiler/repository") 17 | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") 18 | } 19 | } 20 | 21 | dependencyResolutionManagement { 22 | repositories { 23 | google { 24 | mavenContent { 25 | includeGroupAndSubgroups("androidx") 26 | includeGroupAndSubgroups("com.android") 27 | includeGroupAndSubgroups("com.google") 28 | } 29 | } 30 | mavenCentral() 31 | maven("https://maven.pkg.jetbrains.space/public/p/compose/dev") 32 | maven("https://androidx.dev/storage/compose-compiler/repository") 33 | maven("https://maven.pkg.jetbrains.space/kotlin/p/kotlin/dev") 34 | } 35 | } 36 | 37 | include(":composeApp") 38 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/repository/home/HomeRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package network.repository.home 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import network.models.ApiError 5 | import network.models.content.common.MovieResponse 6 | import network.models.content.common.MultiResponse 7 | import network.models.content.common.PersonResponse 8 | import network.models.content.search.ContentPagingResponse 9 | import network.services.home.HomeService 10 | import network.util.Either 11 | import network.util.asFlow 12 | 13 | class HomeRepositoryImpl( 14 | private val homeService: HomeService, 15 | ) : HomeRepository { 16 | override suspend fun getTrendingMulti(): Flow, ApiError>> { 17 | return homeService.getDayTrendingMulti().asFlow() 18 | } 19 | 20 | override suspend fun getTrendingPerson(): Flow, ApiError>> { 21 | return homeService.getDayTrendingPerson().asFlow() 22 | } 23 | 24 | override suspend fun getMoviesComingSoon( 25 | releaseDateGte: String, 26 | releaseDateLte: String, 27 | ): Flow, ApiError>> { 28 | return homeService.getMoviesComingSoon( 29 | releaseDateGte = releaseDateGte, 30 | releaseDateLte = releaseDateLte, 31 | ).asFlow() 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /iosApp/iosApp/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CADisableMinimumFrameDurationOnPhone 6 | 7 | CFBundleDevelopmentRegion 8 | $(DEVELOPMENT_LANGUAGE) 9 | CFBundleExecutable 10 | $(EXECUTABLE_NAME) 11 | CFBundleIdentifier 12 | $(PRODUCT_BUNDLE_IDENTIFIER) 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | $(PRODUCT_NAME) 17 | CFBundlePackageType 18 | $(PRODUCT_BUNDLE_PACKAGE_TYPE) 19 | CFBundleShortVersionString 20 | 1.0 21 | CFBundleVersion 22 | 1 23 | LSRequiresIPhoneOS 24 | 25 | UIApplicationSceneManifest 26 | 27 | UIApplicationSupportsMultipleScenes 28 | 29 | 30 | UILaunchScreen 31 | 32 | UIRequiredDeviceCapabilities 33 | 34 | armv7 35 | 36 | UIRequiresFullScreen 37 | 38 | UISupportedInterfaceOrientations 39 | 40 | UIInterfaceOrientationPortrait 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/popup/GenericPopupMenu.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.popup 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.material3.DropdownMenu 5 | import androidx.compose.material3.DropdownMenuItem 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.material3.Text 8 | import androidx.compose.runtime.Composable 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.graphics.Color 11 | 12 | @Composable 13 | fun GenericPopupMenu( 14 | showMenu: Boolean, 15 | backgroundColor: Color = MaterialTheme.colorScheme.primary, 16 | onDismissRequest: () -> Unit, 17 | menuItems: List, 18 | ) { 19 | DropdownMenu( 20 | expanded = showMenu, 21 | onDismissRequest = onDismissRequest, 22 | modifier = Modifier.background(color = backgroundColor), 23 | ) { 24 | menuItems.forEach { menuItem -> 25 | DropdownMenuItem( 26 | text = { 27 | Text( 28 | text = menuItem.title, 29 | color = menuItem.textColor, 30 | style = MaterialTheme.typography.bodySmall, 31 | ) 32 | }, 33 | onClick = { 34 | menuItem.onClick() 35 | onDismissRequest() 36 | }, 37 | ) 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/details/ui/components/moreoptions/MoreOptionsTabItem.kt: -------------------------------------------------------------------------------- 1 | package features.details.ui.components.moreoptions 2 | 3 | import cinetracker_kmp.composeapp.generated.resources.Res 4 | import cinetracker_kmp.composeapp.generated.resources.images_tab 5 | import cinetracker_kmp.composeapp.generated.resources.more_options_similar 6 | import cinetracker_kmp.composeapp.generated.resources.more_options_videos 7 | import cinetracker_kmp.composeapp.generated.resources.movies_tab 8 | import cinetracker_kmp.composeapp.generated.resources.shows_tab 9 | import common.ui.components.tab.TabItem 10 | import common.util.Constants.UNSELECTED_OPTION_INDEX 11 | import org.jetbrains.compose.resources.StringResource 12 | 13 | sealed class MoreOptionsTabItem( 14 | override val tabResId: StringResource, 15 | override val tabName: String? = "", 16 | override var tabIndex: Int = UNSELECTED_OPTION_INDEX, 17 | ) : TabItem { 18 | data object VideosTab : MoreOptionsTabItem( 19 | tabResId = Res.string.more_options_videos, 20 | ) 21 | data object MoreLikeThisTab : MoreOptionsTabItem( 22 | tabResId = Res.string.more_options_similar, 23 | ) 24 | data object MoviesTab : MoreOptionsTabItem( 25 | tabResId = Res.string.movies_tab, 26 | ) 27 | data object ShowsTab : MoreOptionsTabItem( 28 | tabResId = Res.string.shows_tab, 29 | ) 30 | data object ImagesTab : MoreOptionsTabItem( 31 | tabResId = Res.string.images_tab, 32 | ) 33 | } 34 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/watchlist/ui/components/ListRemovePopupMenu.kt: -------------------------------------------------------------------------------- 1 | package features.watchlist.ui.components 2 | 3 | import androidx.compose.foundation.layout.Box 4 | import androidx.compose.foundation.layout.absoluteOffset 5 | import androidx.compose.runtime.Composable 6 | import androidx.compose.ui.Modifier 7 | import androidx.compose.ui.geometry.Offset 8 | import androidx.compose.ui.unit.dp 9 | import cinetracker_kmp.composeapp.generated.resources.Res 10 | import cinetracker_kmp.composeapp.generated.resources.delete_list_pop_up_item 11 | import common.ui.components.popup.GenericPopupMenu 12 | import common.ui.components.popup.PopupMenuItem 13 | import common.ui.theme.MainBarGreyColor 14 | import org.jetbrains.compose.resources.stringResource 15 | 16 | @Composable 17 | fun ListRemovePopUpMenu( 18 | showRemoveMenu: Boolean, 19 | menuOffset: Offset, 20 | onRemoveList: () -> Unit, 21 | onDismiss: () -> Unit, 22 | ) { 23 | val menuItems = listOf( 24 | PopupMenuItem( 25 | stringResource(resource = Res.string.delete_list_pop_up_item), 26 | onClick = onRemoveList, 27 | ), 28 | ) 29 | Box( 30 | modifier = Modifier 31 | .absoluteOffset(x = (menuOffset.x / 2).dp), 32 | ) { 33 | GenericPopupMenu( 34 | showMenu = showRemoveMenu, 35 | backgroundColor = MainBarGreyColor, 36 | onDismissRequest = onDismiss, 37 | menuItems = menuItems, 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/watchlist/ui/model/DefaultLists.kt: -------------------------------------------------------------------------------- 1 | package features.watchlist.ui.model 2 | 3 | import cinetracker_kmp.composeapp.generated.resources.Res 4 | import cinetracker_kmp.composeapp.generated.resources.unknown 5 | import cinetracker_kmp.composeapp.generated.resources.watched_tab 6 | import cinetracker_kmp.composeapp.generated.resources.watchlist_tab 7 | import common.util.Constants.ADD_NEW_TAB_ID 8 | import common.util.capitalized 9 | import org.jetbrains.compose.resources.StringResource 10 | 11 | enum class DefaultLists(val listId: Int) { 12 | WATCHLIST(1), 13 | WATCHED(2), 14 | ADD_NEW(ADD_NEW_TAB_ID), 15 | ; 16 | 17 | override fun toString(): String { 18 | return super.toString().lowercase().capitalized() 19 | } 20 | companion object { 21 | fun getListById(listId: Int): DefaultLists? { 22 | return values().firstOrNull { it.listId == listId } 23 | } 24 | fun getOtherList(listId: Int): DefaultLists { 25 | return when (listId) { 26 | WATCHLIST.listId -> WATCHED 27 | else -> WATCHLIST 28 | } 29 | } 30 | 31 | fun getListLocalizedName( 32 | list: DefaultLists?, 33 | ): StringResource { 34 | return when (list) { 35 | WATCHLIST -> Res.string.watchlist_tab 36 | WATCHED -> Res.string.watched_tab 37 | else -> Res.string.unknown 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/repository/show/ShowRepository.kt: -------------------------------------------------------------------------------- 1 | package network.repository.show 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import network.models.ApiError 5 | import network.models.content.common.ContentCreditsResponse 6 | import network.models.content.common.ShowResponse 7 | import network.models.content.common.VideosByIdResponse 8 | import network.models.content.common.WatchProvidersResponse 9 | import network.models.content.search.ContentPagingResponse 10 | import network.util.Either 11 | 12 | interface ShowRepository { 13 | suspend fun getShowList( 14 | contentListType: String, 15 | pageIndex: Int, 16 | ): Flow, ApiError>> 17 | 18 | suspend fun getShowDetailsById( 19 | showId: Int, 20 | ): Flow> 21 | 22 | suspend fun getShowCreditsById( 23 | showId: Int, 24 | ): Flow> 25 | 26 | suspend fun getShowVideosById( 27 | showId: Int, 28 | ): Flow> 29 | 30 | suspend fun getRecommendationsShowsById( 31 | showId: Int, 32 | ): Flow, ApiError>> 33 | 34 | suspend fun getSimilarShowsById( 35 | showId: Int, 36 | ): Flow, ApiError>> 37 | 38 | suspend fun getStreamingProviders( 39 | showId: Int, 40 | ): Flow> 41 | } 42 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/screens/DetailsScreenUI.kt: -------------------------------------------------------------------------------- 1 | package navigation.screens 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.compose.runtime.remember 6 | import androidx.navigation.NavController 7 | import androidx.navigation.compose.currentBackStackEntryAsState 8 | import common.ui.screen.ErrorScreen 9 | import features.details.DetailsScreen 10 | import features.details.ui.Details 11 | import navigation.ScreenUI 12 | 13 | class DetailsScreenUI : ScreenUI { 14 | @Composable 15 | override fun UI(navController: NavController) { 16 | val currentBackStackEntry by navController.currentBackStackEntryAsState() 17 | val currentScreen = currentBackStackEntry?.destination?.route 18 | 19 | val backStackEntry = remember { 20 | navController.getBackStackEntry(DetailsScreen.route()) 21 | } 22 | 23 | Details( 24 | navBackStackEntry = backStackEntry, 25 | onBackPress = { 26 | navController.popBackStack() 27 | }, 28 | goToDetails = { id, type -> 29 | navController.navigate( 30 | DetailsScreen.routeWithArguments(id, type.name), 31 | ) 32 | }, 33 | goToErrorScreen = { 34 | if (currentScreen != ErrorScreen.route()) { 35 | navController.navigate(ErrorScreen.route()) 36 | } 37 | }, 38 | ) 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled source # 2 | ################### 3 | *.com 4 | *.class 5 | *.dll 6 | *.exe 7 | *.o 8 | *.so 9 | 10 | # Packages # 11 | ############ 12 | # it's better to unpack these files and commit the raw source 13 | # git has its own built in compression methods 14 | *.7z 15 | *.dmg 16 | *.gz 17 | *.iso 18 | *.jar 19 | *.rar 20 | *.tar 21 | *.zip 22 | 23 | # Logs and databases # 24 | ###################### 25 | *.log 26 | *.sql 27 | *.sqlite 28 | 29 | # OS generated files # 30 | ###################### 31 | .DS_Store 32 | .DS_Store? 33 | ._* 34 | .Spotlight-V100 35 | .Trashes 36 | ehthumbs.db 37 | Thumbs.db 38 | 39 | # Built application files 40 | *.apk 41 | *ap_ 42 | app/release 43 | 44 | # Gradle files 45 | .gradle/ 46 | build/ 47 | 48 | # Local configuration file (sdk path, etc) 49 | local.properties 50 | 51 | # Proguard folder generated by Eclipse 52 | proguard/ 53 | 54 | # Log Files 55 | .log 56 | 57 | # Android Studio Navigation editor temp files 58 | .navigation/ 59 | 60 | # Android Studio captures folder 61 | captures/ 62 | 63 | # release 64 | composeApp/release/ 65 | 66 | # IntelliJ 67 | .idea/ 68 | 69 | # External native build folder generated in Android Studio 2.2 and later 70 | .externalNativeBuild 71 | 72 | # KMP 73 | *.iml 74 | .kotlin 75 | **/build/ 76 | xcuserdata 77 | !src/**/build/ 78 | .cxx 79 | *.xcodeproj/* 80 | !*.xcodeproj/project.pbxproj 81 | !*.xcodeproj/xcshareddata/ 82 | !*.xcodeproj/project.xcworkspace/ 83 | !*.xcworkspace/contents.xcworkspacedata 84 | **/xcshareddata/WorkspaceSettings.xcsettings 85 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/services/search/SearchService.kt: -------------------------------------------------------------------------------- 1 | package network.services.search 2 | 3 | import core.LanguageManager 4 | import network.models.content.common.MovieResponse 5 | import network.models.content.common.MultiResponse 6 | import network.models.content.common.PersonResponse 7 | import network.models.content.common.ShowResponse 8 | import network.models.content.search.ContentPagingResponse 9 | import network.util.ApiResult 10 | 11 | interface SearchService { 12 | suspend fun searchMultiByQuery( 13 | query: String, 14 | matureEnabled: Boolean = false, 15 | language: String = LanguageManager.getUserLanguageTag(), 16 | pageIndex: Int, 17 | ): ApiResult> 18 | 19 | suspend fun searchMovieByQuery( 20 | query: String, 21 | matureEnabled: Boolean = false, 22 | language: String = LanguageManager.getUserLanguageTag(), 23 | pageIndex: Int, 24 | ): ApiResult> 25 | 26 | suspend fun searchShowByQuery( 27 | query: String, 28 | matureEnabled: Boolean = false, 29 | language: String = LanguageManager.getUserLanguageTag(), 30 | pageIndex: Int, 31 | ): ApiResult> 32 | 33 | suspend fun searchPersonByQuery( 34 | query: String, 35 | matureEnabled: Boolean = false, 36 | language: String = LanguageManager.getUserLanguageTag(), 37 | pageIndex: Int, 38 | ): ApiResult> 39 | } 40 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/repository/movie/MovieRepository.kt: -------------------------------------------------------------------------------- 1 | package network.repository.movie 2 | 3 | import common.domain.models.util.ContentListType 4 | import kotlinx.coroutines.flow.Flow 5 | import network.models.ApiError 6 | import network.models.content.common.ContentCreditsResponse 7 | import network.models.content.common.MovieResponse 8 | import network.models.content.common.VideosByIdResponse 9 | import network.models.content.common.WatchProvidersResponse 10 | import network.models.content.search.ContentPagingResponse 11 | import network.util.Either 12 | 13 | interface MovieRepository { 14 | suspend fun getMovieList( 15 | contentListType: ContentListType, 16 | pageIndex: Int, 17 | ): Flow, ApiError>> 18 | 19 | suspend fun getMovieDetailsById( 20 | movieId: Int, 21 | ): Flow> 22 | 23 | suspend fun getMovieCreditsById( 24 | movieId: Int, 25 | ): Flow> 26 | 27 | suspend fun getMovieVideosById( 28 | movieId: Int, 29 | ): Flow> 30 | 31 | suspend fun getRecommendationsMoviesById( 32 | movieId: Int, 33 | ): Flow, ApiError>> 34 | 35 | suspend fun getSimilarMoviesById( 36 | movieId: Int, 37 | ): Flow, ApiError>> 38 | 39 | suspend fun getStreamingProviders( 40 | movieId: Int, 41 | ): Flow> 42 | } 43 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/watchlist/ui/components/WatchlistTabItem.kt: -------------------------------------------------------------------------------- 1 | package features.watchlist.ui.components 2 | 3 | import cinetracker_kmp.composeapp.generated.resources.Res 4 | import cinetracker_kmp.composeapp.generated.resources.watched_tab 5 | import cinetracker_kmp.composeapp.generated.resources.watchlist_tab 6 | import common.ui.components.tab.TabItem 7 | import common.util.Constants.ADD_NEW_TAB_ID 8 | import common.util.Constants.UNSELECTED_OPTION_INDEX 9 | import features.watchlist.ui.model.DefaultLists 10 | import org.jetbrains.compose.resources.StringResource 11 | 12 | sealed class WatchlistTabItem( 13 | override val tabResId: StringResource? = null, 14 | override val tabName: String? = "", 15 | override var tabIndex: Int = UNSELECTED_OPTION_INDEX, 16 | open val listId: Int, 17 | ) : TabItem { 18 | data object WatchlistTab : WatchlistTabItem( 19 | tabResId = Res.string.watchlist_tab, 20 | listId = DefaultLists.WATCHLIST.listId, 21 | ) 22 | data object WatchedTab : WatchlistTabItem( 23 | tabResId = Res.string.watched_tab, 24 | listId = DefaultLists.WATCHED.listId, 25 | ) 26 | data object AddNewTab : WatchlistTabItem( 27 | tabResId = null, 28 | listId = DefaultLists.ADD_NEW.listId, 29 | tabIndex = ADD_NEW_TAB_ID, 30 | ) 31 | data class CustomTab( 32 | override val tabResId: StringResource? = null, 33 | override val tabName: String?, 34 | override var tabIndex: Int = UNSELECTED_OPTION_INDEX, 35 | override var listId: Int, 36 | ) : WatchlistTabItem(tabResId, tabName, tabIndex, listId) 37 | } 38 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/ClassicGradientBrush.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.ui.Modifier 5 | import androidx.compose.ui.graphics.Brush 6 | import androidx.compose.ui.graphics.Color 7 | 8 | fun Modifier.classicVerticalGradientBrush( 9 | colorList: List = listOf(Color.Black, Color.Transparent), 10 | direction: GradientDirections = GradientDirections.DOWN, 11 | ): Modifier { 12 | return when (direction) { 13 | GradientDirections.UP -> this.background( 14 | Brush.verticalGradient( 15 | colors = colorList, 16 | startY = Float.POSITIVE_INFINITY, 17 | endY = 0f, 18 | ), 19 | ) 20 | GradientDirections.DOWN -> this.background( 21 | Brush.verticalGradient( 22 | colors = colorList, 23 | startY = 0f, 24 | endY = Float.POSITIVE_INFINITY, 25 | ), 26 | ) 27 | GradientDirections.LEFT -> this.background( 28 | Brush.horizontalGradient( 29 | colors = colorList, 30 | startX = Float.POSITIVE_INFINITY, 31 | endX = 0f, 32 | ), 33 | ) 34 | GradientDirections.RIGHT -> this.background( 35 | Brush.verticalGradient( 36 | colors = colorList, 37 | startY = 0f, 38 | endY = Float.POSITIVE_INFINITY, 39 | ), 40 | ) 41 | } 42 | } 43 | 44 | enum class GradientDirections { 45 | UP, 46 | DOWN, 47 | LEFT, 48 | RIGHT, 49 | } 50 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/button/SortIconButton.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.button 2 | 3 | import androidx.compose.foundation.layout.size 4 | import androidx.compose.material3.Icon 5 | import androidx.compose.material3.IconButton 6 | import androidx.compose.material3.MaterialTheme 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.runtime.collectAsState 9 | import androidx.compose.runtime.getValue 10 | import androidx.compose.ui.Modifier 11 | import androidx.compose.ui.unit.dp 12 | import cinetracker_kmp.composeapp.generated.resources.Res 13 | import cinetracker_kmp.composeapp.generated.resources.ic_sort 14 | import common.ui.MainViewModel 15 | import common.util.UiConstants.BROWSE_SORT_ICON_SIZE 16 | import features.watchlist.WatchlistScreen 17 | import org.jetbrains.compose.resources.painterResource 18 | 19 | @Composable 20 | fun SortIconButton( 21 | mainViewModel: MainViewModel, 22 | currentScreen: String, 23 | displaySortScreen: (Boolean) -> Unit, 24 | ) { 25 | val watchlistSortSelected by mainViewModel.watchlistSort.collectAsState() 26 | val iconColor = if (currentScreen == WatchlistScreen.route() && watchlistSortSelected != null) { 27 | MaterialTheme.colorScheme.secondary 28 | } else { 29 | MaterialTheme.colorScheme.onPrimary 30 | } 31 | IconButton( 32 | onClick = { displaySortScreen(true) }, 33 | ) { 34 | Icon( 35 | modifier = Modifier.size(BROWSE_SORT_ICON_SIZE.dp), 36 | painter = painterResource(resource = Res.drawable.ic_sort), 37 | tint = iconColor, 38 | contentDescription = null, 39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/RatingComponent.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components 2 | 3 | import androidx.compose.foundation.Image 4 | import androidx.compose.foundation.layout.Row 5 | import androidx.compose.foundation.layout.offset 6 | import androidx.compose.foundation.layout.size 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.text.TextStyle 13 | import androidx.compose.ui.unit.dp 14 | import cinetracker_kmp.composeapp.generated.resources.Res 15 | import cinetracker_kmp.composeapp.generated.resources.ic_star 16 | import common.util.UiConstants.RATING_STAR_DEFAULT_SIZE 17 | import common.util.formatRating 18 | import org.jetbrains.compose.resources.painterResource 19 | 20 | @Composable 21 | fun RatingComponent( 22 | modifier: Modifier = Modifier, 23 | rating: Double?, 24 | ratingIconSize: Int? = RATING_STAR_DEFAULT_SIZE, 25 | textStyle: TextStyle = MaterialTheme.typography.titleMedium, 26 | ) { 27 | Row( 28 | verticalAlignment = Alignment.CenterVertically, 29 | modifier = modifier.offset(x = (-0.5).dp), 30 | ) { 31 | Image( 32 | modifier = Modifier.size((ratingIconSize ?: RATING_STAR_DEFAULT_SIZE).dp), 33 | painter = painterResource(resource = Res.drawable.ic_star), 34 | contentDescription = null, 35 | ) 36 | Text( 37 | text = rating.formatRating(), 38 | color = MaterialTheme.colorScheme.onPrimary, 39 | style = textStyle, 40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/services/show/ShowService.kt: -------------------------------------------------------------------------------- 1 | package network.services.show 2 | 3 | import core.LanguageManager.getUserLanguageTag 4 | import network.models.content.common.ContentCreditsResponse 5 | import network.models.content.common.ShowResponse 6 | import network.models.content.common.VideosByIdResponse 7 | import network.models.content.common.WatchProvidersResponse 8 | import network.models.content.search.ContentPagingResponse 9 | import network.util.ApiResult 10 | 11 | interface ShowService { 12 | suspend fun getShowList( 13 | contentListType: String, 14 | pageIndex: Int, 15 | language: String = getUserLanguageTag(), 16 | ): ApiResult> 17 | 18 | suspend fun getShowDetailsById( 19 | showId: Int, 20 | language: String = getUserLanguageTag(), 21 | ): ApiResult 22 | 23 | suspend fun getShowCreditsById( 24 | showId: Int, 25 | language: String = getUserLanguageTag(), 26 | ): ApiResult 27 | 28 | suspend fun getShowVideosById( 29 | showId: Int, 30 | language: String = getUserLanguageTag(), 31 | ): ApiResult 32 | 33 | suspend fun getRecommendationsShowsById( 34 | showId: Int, 35 | language: String = getUserLanguageTag(), 36 | ): ApiResult> 37 | 38 | suspend fun getSimilarShowsById( 39 | showId: Int, 40 | language: String = getUserLanguageTag(), 41 | ): ApiResult> 42 | 43 | suspend fun getStreamingProviders( 44 | showId: Int, 45 | language: String = getUserLanguageTag(), 46 | ): ApiResult 47 | } 48 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/card/MediaTypeTag.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.card 2 | 3 | import androidx.compose.foundation.background 4 | import androidx.compose.foundation.layout.Box 5 | import androidx.compose.foundation.layout.defaultMinSize 6 | import androidx.compose.foundation.layout.padding 7 | import androidx.compose.material3.MaterialTheme 8 | import androidx.compose.material3.Text 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.unit.dp 13 | import cinetracker_kmp.composeapp.generated.resources.Res 14 | import cinetracker_kmp.composeapp.generated.resources.movie_tag 15 | import cinetracker_kmp.composeapp.generated.resources.show_tag 16 | import common.domain.models.util.MediaType 17 | import common.ui.theme.PrimaryYellowColor_90 18 | import org.jetbrains.compose.resources.stringResource 19 | 20 | @Composable 21 | fun MediaTypeTag( 22 | modifier: Modifier = Modifier, 23 | mediaType: MediaType, 24 | ) { 25 | val mediaTypeTag = if (mediaType == MediaType.MOVIE) { 26 | stringResource(resource = Res.string.movie_tag) 27 | } else { 28 | stringResource(resource = Res.string.show_tag) 29 | } 30 | 31 | Box( 32 | modifier = modifier 33 | .defaultMinSize(minWidth = 50.dp) 34 | .background(color = PrimaryYellowColor_90), 35 | contentAlignment = Alignment.Center, 36 | ) { 37 | Text( 38 | text = mediaTypeTag, 39 | color = MaterialTheme.colorScheme.primary, 40 | style = MaterialTheme.typography.titleSmall, 41 | maxLines = 1, 42 | modifier = Modifier.padding(horizontal = 2.dp), 43 | ) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/services/movie/MovieService.kt: -------------------------------------------------------------------------------- 1 | package network.services.movie 2 | 3 | import core.LanguageManager.getUserLanguageTag 4 | import network.models.content.common.ContentCreditsResponse 5 | import network.models.content.common.MovieResponse 6 | import network.models.content.common.VideosByIdResponse 7 | import network.models.content.common.WatchProvidersResponse 8 | import network.models.content.search.ContentPagingResponse 9 | import network.util.ApiResult 10 | 11 | interface MovieService { 12 | suspend fun getMovieList( 13 | movieListType: String, 14 | pageIndex: Int, 15 | language: String = getUserLanguageTag(), 16 | ): ApiResult> 17 | 18 | suspend fun getMovieDetailsById( 19 | movieId: Int, 20 | language: String = getUserLanguageTag(), 21 | ): ApiResult 22 | 23 | suspend fun getMovieCreditsById( 24 | movieId: Int, 25 | language: String = getUserLanguageTag(), 26 | ): ApiResult 27 | 28 | suspend fun getMovieVideosById( 29 | movieId: Int, 30 | language: String = getUserLanguageTag(), 31 | ): ApiResult 32 | 33 | suspend fun getRecommendationsMoviesById( 34 | movieId: Int, 35 | language: String = getUserLanguageTag(), 36 | ): ApiResult> 37 | 38 | suspend fun getSimilarMoviesById( 39 | movieId: Int, 40 | language: String = getUserLanguageTag(), 41 | ): ApiResult> 42 | 43 | suspend fun getStreamingProviders( 44 | movieId: Int, 45 | language: String = getUserLanguageTag(), 46 | ): ApiResult 47 | } 48 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/home/ui/components/carousel/ComingSoonContainer.kt: -------------------------------------------------------------------------------- 1 | package features.home.ui.components.carousel 2 | 3 | import androidx.compose.foundation.layout.Spacer 4 | import androidx.compose.foundation.layout.height 5 | import androidx.compose.foundation.layout.padding 6 | import androidx.compose.runtime.Composable 7 | import androidx.compose.ui.Modifier 8 | import androidx.compose.ui.unit.dp 9 | import common.domain.models.content.GenericContent 10 | import common.domain.models.util.MediaType 11 | import common.ui.components.card.ImageContentCard 12 | import common.util.UiConstants.CAROUSEL_CARDS_WIDTH 13 | import common.util.UiConstants.DEFAULT_PADDING 14 | import org.jetbrains.compose.resources.StringResource 15 | 16 | @Composable 17 | fun ComingSoonCarousel( 18 | carouselHeaderRes: StringResource, 19 | comingSoonList: List, 20 | currentScreenWidth: Float, 21 | goToDetails: (Int, MediaType) -> Unit, 22 | ) { 23 | if (comingSoonList.isNotEmpty()) { 24 | ClassicCarousel( 25 | carouselHeaderRes = carouselHeaderRes, 26 | itemList = comingSoonList, 27 | currentScreenWidth = currentScreenWidth, 28 | goToDetails = goToDetails, 29 | ) { item, goToDetails -> 30 | ImageContentCard( 31 | modifier = Modifier.padding( 32 | top = DEFAULT_PADDING.dp, 33 | bottom = DEFAULT_PADDING.dp, 34 | end = DEFAULT_PADDING.dp, 35 | ), 36 | item = item, 37 | adjustedCardSize = CAROUSEL_CARDS_WIDTH.dp, 38 | goToDetails = goToDetails, 39 | ) 40 | } 41 | Spacer(modifier = Modifier.height(DEFAULT_PADDING.dp)) 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/services/person/PersonServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package network.services.person 2 | 3 | import com.projects.moviemanager.network.util.Parameters 4 | import io.ktor.client.HttpClient 5 | import network.models.content.common.PersonResponse 6 | import network.models.content.person.PersonCreditsResponse 7 | import network.models.content.person.PersonImagesResponse 8 | import network.util.ApiResult 9 | import network.util.buildUrl 10 | import network.util.getResult 11 | 12 | class PersonServiceImpl( 13 | private val client: HttpClient, 14 | ) : PersonService { 15 | override suspend fun getPersonDetailsById( 16 | personId: Int, 17 | language: String, 18 | ): ApiResult { 19 | val path = "person/$personId" 20 | val url = buildUrl(path) { 21 | mapOf( 22 | Parameters.LANGUAGE to language, 23 | ) 24 | } 25 | 26 | return client.getResult(url) 27 | } 28 | 29 | override suspend fun getPersonCreditsById( 30 | personId: Int, 31 | language: String, 32 | ): ApiResult { 33 | val path = "person/$personId/combined_credits" 34 | val url = buildUrl(path) { 35 | mapOf( 36 | Parameters.LANGUAGE to language, 37 | ) 38 | } 39 | 40 | return client.getResult(url) 41 | } 42 | 43 | override suspend fun getPersonImagesById( 44 | personId: Int, 45 | language: String, 46 | ): ApiResult { 47 | val path = "person/$personId/images" 48 | val url = buildUrl(path) { 49 | mapOf( 50 | Parameters.LANGUAGE to language, 51 | ) 52 | } 53 | 54 | return client.getResult(url) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/screens/HomeScreenUI.kt: -------------------------------------------------------------------------------- 1 | package navigation.screens 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.getValue 5 | import androidx.navigation.NavController 6 | import androidx.navigation.compose.currentBackStackEntryAsState 7 | import common.ui.screen.ErrorScreen 8 | import features.browse.BrowseScreen 9 | import features.details.DetailsScreen 10 | import features.home.ui.Home 11 | import features.watchlist.WatchlistScreen 12 | import navigation.ScreenUI 13 | import navigation.components.navigateToTopLevelDestination 14 | 15 | class HomeScreenUI : ScreenUI { 16 | @Composable 17 | override fun UI(navController: NavController) { 18 | val currentBackStackEntry by navController.currentBackStackEntryAsState() 19 | val currentScreen = currentBackStackEntry?.destination?.route 20 | 21 | Home( 22 | goToDetails = { contentId, mediaType -> 23 | navController.navigate( 24 | DetailsScreen.routeWithArguments(contentId, mediaType.name), 25 | ) 26 | }, 27 | goToWatchlist = { 28 | navigateToTopLevelDestination( 29 | navController = navController, 30 | destination = WatchlistScreen.route(), 31 | ) 32 | }, 33 | goToBrowse = { 34 | navigateToTopLevelDestination( 35 | navController = navController, 36 | destination = BrowseScreen.route(), 37 | ) 38 | }, 39 | goToErrorScreen = { 40 | if (currentScreen != ErrorScreen.route()) { 41 | navController.navigate(ErrorScreen.route()) 42 | } 43 | }, 44 | ) 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/kotlin/database/di/DatabaseModule.android.kt: -------------------------------------------------------------------------------- 1 | package database.di 2 | 3 | import android.content.Context 4 | import androidx.room.Room 5 | import androidx.room.RoomDatabase 6 | import androidx.sqlite.db.SupportSQLiteDatabase 7 | import database.AppDatabase 8 | import features.watchlist.ui.model.DefaultLists 9 | import kotlinx.coroutines.Dispatchers 10 | import org.koin.core.module.Module 11 | import org.koin.dsl.module 12 | 13 | actual fun databaseModule(): Module { 14 | return module { 15 | single { createRoomDatabase(get()) } 16 | } 17 | } 18 | 19 | private fun createRoomDatabase( 20 | context: Context, 21 | ): AppDatabase { 22 | val dbFile = context.getDatabasePath("movie_manager_database") 23 | return Room.databaseBuilder( 24 | context, 25 | dbFile.absolutePath, 26 | ) 27 | .setQueryCoroutineContext(Dispatchers.IO) 28 | .addCallback(roomCallback) 29 | .fallbackToDestructiveMigration(true) 30 | .build() 31 | } 32 | 33 | val roomCallback = object : RoomDatabase.Callback() { 34 | override fun onCreate(db: SupportSQLiteDatabase) { 35 | super.onCreate(db) 36 | createDefaultLists(db) 37 | } 38 | 39 | private fun createDefaultLists(db: SupportSQLiteDatabase) { 40 | // Execute the SQL to insert the default lists 41 | val defaultLists = listOf( 42 | DefaultLists.WATCHLIST.toString().lowercase(), 43 | DefaultLists.WATCHED.toString().lowercase(), 44 | ) 45 | defaultLists.forEach { listName -> 46 | db.execSQL( 47 | """ 48 | INSERT INTO list_entity (listName) 49 | VALUES (?) 50 | """, 51 | arrayOf(listName), 52 | ) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /composeApp/src/androidMain/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 15 | 18 | 21 | 22 | 23 | 24 | 30 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/bottomsheet/SortBottomSheetComponents.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.bottomsheet 2 | 3 | import androidx.compose.foundation.layout.PaddingValues 4 | import androidx.compose.foundation.layout.Spacer 5 | import androidx.compose.material3.Button 6 | import androidx.compose.material3.ButtonDefaults 7 | import androidx.compose.material3.Icon 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.material3.Text 10 | import androidx.compose.runtime.Composable 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.graphics.Color 13 | import androidx.compose.ui.unit.dp 14 | import cinetracker_kmp.composeapp.generated.resources.Res 15 | import cinetracker_kmp.composeapp.generated.resources.ic_check 16 | import common.util.UiConstants.SMALL_MARGIN 17 | import org.jetbrains.compose.resources.painterResource 18 | 19 | @Composable 20 | fun SortButton( 21 | text: String, 22 | isSelected: Boolean = false, 23 | textColor: Color, 24 | onClick: () -> Unit, 25 | ) { 26 | Button( 27 | contentPadding = PaddingValues(horizontal = SMALL_MARGIN.dp), 28 | colors = ButtonDefaults.buttonColors( 29 | containerColor = Color.Transparent, 30 | ), 31 | onClick = onClick, 32 | ) { 33 | Text( 34 | text = text, 35 | style = MaterialTheme.typography.bodyMedium, 36 | color = if (isSelected) MaterialTheme.colorScheme.onSurfaceVariant else textColor, 37 | ) 38 | Spacer(modifier = Modifier.weight(1f)) 39 | if (isSelected) { 40 | Icon( 41 | painter = painterResource(resource = Res.drawable.ic_check), 42 | contentDescription = null, 43 | tint = MaterialTheme.colorScheme.secondary, 44 | ) 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/.composeApp-appleMain.cinteropLibraries: -------------------------------------------------------------------------------- 1 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-crashlytics-1.12.0-iosMain-cinterop/dev.gitlive_firebase-crashlytics-cinterop-FirebaseCrashlytics-5CvdJw.klib 2 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.github.ismai117-kottie-1.9.6-alpha02-iosMain-cinterop/Kottie_lib-cinterop-Lottie-jSfdpg.klib 3 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-bundled-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-bundled-cinterop-androidXBundledSqlite-0MuvrA.klib 4 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-app-1.12.0-iosMain-cinterop/dev.gitlive_firebase-app-cinterop-FirebaseCore-Pte9Yg.klib 5 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-nativeMain-cinterop/org.jetbrains.kotlinx_atomicfu-cinterop-interop-yBS35w.klib 6 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.compose.ui-ui-uikit-1.6.11-uikitMain-cinterop/org.jetbrains.compose.ui_ui-uikit-cinterop-utils-oguluQ.klib 7 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-framework-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-framework-cinterop-sqlite3-GE3pgw.klib 8 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.ktor-ktor-utils-2.3.12-iosMain-cinterop/io.ktor_ktor-utils-cinterop-threadUtils-TE4abA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/.composeApp-appleTest.cinteropLibraries: -------------------------------------------------------------------------------- 1 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-crashlytics-1.12.0-iosMain-cinterop/dev.gitlive_firebase-crashlytics-cinterop-FirebaseCrashlytics-5CvdJw.klib 2 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.github.ismai117-kottie-1.9.6-alpha02-iosMain-cinterop/Kottie_lib-cinterop-Lottie-jSfdpg.klib 3 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-bundled-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-bundled-cinterop-androidXBundledSqlite-0MuvrA.klib 4 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-nativeMain-cinterop/org.jetbrains.kotlinx_atomicfu-cinterop-interop-yBS35w.klib 5 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-app-1.12.0-iosMain-cinterop/dev.gitlive_firebase-app-cinterop-FirebaseCore-Pte9Yg.klib 6 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.compose.ui-ui-uikit-1.6.11-uikitMain-cinterop/org.jetbrains.compose.ui_ui-uikit-cinterop-utils-oguluQ.klib 7 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-framework-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-framework-cinterop-sqlite3-GE3pgw.klib 8 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.ktor-ktor-utils-2.3.12-iosMain-cinterop/io.ktor_ktor-utils-cinterop-threadUtils-TE4abA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/.composeApp-iosMain.cinteropLibraries: -------------------------------------------------------------------------------- 1 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-crashlytics-1.12.0-iosMain-cinterop/dev.gitlive_firebase-crashlytics-cinterop-FirebaseCrashlytics-5CvdJw.klib 2 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.github.ismai117-kottie-1.9.6-alpha02-iosMain-cinterop/Kottie_lib-cinterop-Lottie-jSfdpg.klib 3 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-bundled-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-bundled-cinterop-androidXBundledSqlite-0MuvrA.klib 4 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-app-1.12.0-iosMain-cinterop/dev.gitlive_firebase-app-cinterop-FirebaseCore-Pte9Yg.klib 5 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-nativeMain-cinterop/org.jetbrains.kotlinx_atomicfu-cinterop-interop-yBS35w.klib 6 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.compose.ui-ui-uikit-1.6.11-uikitMain-cinterop/org.jetbrains.compose.ui_ui-uikit-cinterop-utils-oguluQ.klib 7 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-framework-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-framework-cinterop-sqlite3-GE3pgw.klib 8 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.ktor-ktor-utils-2.3.12-iosMain-cinterop/io.ktor_ktor-utils-cinterop-threadUtils-TE4abA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/.composeApp-iosTest.cinteropLibraries: -------------------------------------------------------------------------------- 1 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-crashlytics-1.12.0-iosMain-cinterop/dev.gitlive_firebase-crashlytics-cinterop-FirebaseCrashlytics-5CvdJw.klib 2 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.github.ismai117-kottie-1.9.6-alpha02-iosMain-cinterop/Kottie_lib-cinterop-Lottie-jSfdpg.klib 3 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-bundled-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-bundled-cinterop-androidXBundledSqlite-0MuvrA.klib 4 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-nativeMain-cinterop/org.jetbrains.kotlinx_atomicfu-cinterop-interop-yBS35w.klib 5 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-app-1.12.0-iosMain-cinterop/dev.gitlive_firebase-app-cinterop-FirebaseCore-Pte9Yg.klib 6 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.compose.ui-ui-uikit-1.6.11-uikitMain-cinterop/org.jetbrains.compose.ui_ui-uikit-cinterop-utils-oguluQ.klib 7 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-framework-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-framework-cinterop-sqlite3-GE3pgw.klib 8 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.ktor-ktor-utils-2.3.12-iosMain-cinterop/io.ktor_ktor-utils-cinterop-threadUtils-TE4abA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/.composeApp-nativeMain.cinteropLibraries: -------------------------------------------------------------------------------- 1 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-crashlytics-1.12.0-iosMain-cinterop/dev.gitlive_firebase-crashlytics-cinterop-FirebaseCrashlytics-5CvdJw.klib 2 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.github.ismai117-kottie-1.9.6-alpha02-iosMain-cinterop/Kottie_lib-cinterop-Lottie-jSfdpg.klib 3 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-bundled-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-bundled-cinterop-androidXBundledSqlite-0MuvrA.klib 4 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-app-1.12.0-iosMain-cinterop/dev.gitlive_firebase-app-cinterop-FirebaseCore-Pte9Yg.klib 5 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-nativeMain-cinterop/org.jetbrains.kotlinx_atomicfu-cinterop-interop-yBS35w.klib 6 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.compose.ui-ui-uikit-1.6.11-uikitMain-cinterop/org.jetbrains.compose.ui_ui-uikit-cinterop-utils-oguluQ.klib 7 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-framework-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-framework-cinterop-sqlite3-GE3pgw.klib 8 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.ktor-ktor-utils-2.3.12-iosMain-cinterop/io.ktor_ktor-utils-cinterop-threadUtils-TE4abA.klib -------------------------------------------------------------------------------- /.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/.composeApp-nativeTest.cinteropLibraries: -------------------------------------------------------------------------------- 1 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-crashlytics-1.12.0-iosMain-cinterop/dev.gitlive_firebase-crashlytics-cinterop-FirebaseCrashlytics-5CvdJw.klib 2 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.github.ismai117-kottie-1.9.6-alpha02-iosMain-cinterop/Kottie_lib-cinterop-Lottie-jSfdpg.klib 3 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-bundled-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-bundled-cinterop-androidXBundledSqlite-0MuvrA.klib 4 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.kotlinx-atomicfu-0.23.2-nativeMain-cinterop/org.jetbrains.kotlinx_atomicfu-cinterop-interop-yBS35w.klib 5 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/dev.gitlive-firebase-app-1.12.0-iosMain-cinterop/dev.gitlive_firebase-app-cinterop-FirebaseCore-Pte9Yg.klib 6 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/org.jetbrains.compose.ui-ui-uikit-1.6.11-uikitMain-cinterop/org.jetbrains.compose.ui_ui-uikit-cinterop-utils-oguluQ.klib 7 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/androidx.sqlite-sqlite-framework-2.5.0-alpha02-nativeMain-cinterop/androidx.sqlite_sqlite-framework-cinterop-sqlite3-GE3pgw.klib 8 | /Users/gustavop/Documents/Personal/CineTracker-KMP/.kotlin/metadata/kotlinTransformedCInteropMetadataLibraries/io.ktor-ktor-utils-2.3.12-iosMain-cinterop/io.ktor_ktor-utils-cinterop-threadUtils-TE4abA.klib -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/MainNavGraph.kt: -------------------------------------------------------------------------------- 1 | package navigation 2 | 3 | import androidx.compose.animation.EnterTransition 4 | import androidx.compose.animation.ExitTransition 5 | import androidx.compose.runtime.Composable 6 | import androidx.navigation.NavHostController 7 | import androidx.navigation.compose.NavHost 8 | import androidx.navigation.compose.composable 9 | import androidx.navigation.compose.rememberNavController 10 | import common.ui.screen.ErrorScreen 11 | import features.browse.BrowseScreen 12 | import features.details.DetailsScreen 13 | import features.home.HomeScreen 14 | import features.search.SearchScreen 15 | import features.watchlist.WatchlistScreen 16 | import navigation.screens.BrowseScreenUI 17 | import navigation.screens.DetailsScreenUI 18 | import navigation.screens.ErrorScreenUI 19 | import navigation.screens.HomeScreenUI 20 | import navigation.screens.SearchScreenUI 21 | import navigation.screens.WatchlistScreenUI 22 | 23 | private val mainNavDestinations: Map = mapOf( 24 | HomeScreen to HomeScreenUI(), 25 | BrowseScreen to BrowseScreenUI(), 26 | WatchlistScreen to WatchlistScreenUI(), 27 | SearchScreen to SearchScreenUI(), 28 | DetailsScreen to DetailsScreenUI(), 29 | ErrorScreen to ErrorScreenUI(), 30 | ) 31 | 32 | @Composable 33 | fun MainNavGraph( 34 | navController: NavHostController = rememberNavController(), 35 | ) { 36 | NavHost( 37 | navController = navController, 38 | startDestination = HomeScreen.route(), 39 | enterTransition = { EnterTransition.None }, 40 | exitTransition = { ExitTransition.None }, 41 | ) { 42 | mainNavDestinations.forEach { (screen, screenUI) -> 43 | composable(screen.route(), screen.arguments) { 44 | screenUI.UI( 45 | navController = navController, 46 | ) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/bottomsheet/ModalComponents.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.bottomsheet 2 | 3 | import androidx.compose.runtime.Composable 4 | import androidx.compose.runtime.collectAsState 5 | import androidx.compose.runtime.getValue 6 | import common.ui.MainViewModel 7 | import features.browse.BrowseScreen 8 | import features.browse.ui.components.BrowseSortBottomSheet 9 | import features.watchlist.WatchlistScreen 10 | import features.watchlist.ui.components.WatchlistSortBottomSheet 11 | 12 | @Composable 13 | fun ModalComponents( 14 | mainViewModel: MainViewModel, 15 | showSortBottomSheet: Boolean, 16 | displaySortScreen: (Boolean) -> Unit, 17 | ) { 18 | val selectedMovieSortType by mainViewModel.movieSortType.collectAsState() 19 | val selectedShowSortType by mainViewModel.showSortType.collectAsState() 20 | val selectedMediaType by mainViewModel.currentMediaTypeSelected.collectAsState() 21 | val currentScreen by mainViewModel.currentScreen.collectAsState() 22 | val selectedWatchlistSortType by mainViewModel.watchlistSort.collectAsState() 23 | 24 | if (showSortBottomSheet) { 25 | when (currentScreen) { 26 | BrowseScreen.route() -> { 27 | BrowseSortBottomSheet( 28 | mainViewModel = mainViewModel, 29 | selectedMovieSortType = selectedMovieSortType, 30 | selectedShowSortType = selectedShowSortType, 31 | selectedMediaType = selectedMediaType, 32 | displaySortScreen = displaySortScreen, 33 | ) 34 | } 35 | WatchlistScreen.route() -> { 36 | WatchlistSortBottomSheet( 37 | mainViewModel = mainViewModel, 38 | selectedWatchlistSortType = selectedWatchlistSortType, 39 | displaySortScreen = displaySortScreen, 40 | ) 41 | } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/card/ImageContentCard.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.card 2 | 3 | import androidx.compose.foundation.clickable 4 | import androidx.compose.foundation.layout.Column 5 | import androidx.compose.foundation.layout.Spacer 6 | import androidx.compose.foundation.layout.fillMaxSize 7 | import androidx.compose.foundation.layout.height 8 | import androidx.compose.material3.MaterialTheme 9 | import androidx.compose.runtime.Composable 10 | import androidx.compose.ui.Alignment 11 | import androidx.compose.ui.Modifier 12 | import androidx.compose.ui.draw.clip 13 | import androidx.compose.ui.unit.Dp 14 | import androidx.compose.ui.unit.dp 15 | import common.domain.models.content.GenericContent 16 | import common.domain.models.util.MediaType 17 | import common.ui.components.NetworkImage 18 | import common.util.Constants.BASE_300_IMAGE_URL 19 | import common.util.UiConstants.DEFAULT_PADDING 20 | import common.util.UiConstants.POSTER_ASPECT_RATIO_MULTIPLY 21 | 22 | @Composable 23 | fun ImageContentCard( 24 | modifier: Modifier = Modifier, 25 | item: GenericContent, 26 | adjustedCardSize: Dp, 27 | goToDetails: (Int, MediaType) -> Unit, 28 | ) { 29 | val fullImageUrl = BASE_300_IMAGE_URL + item.posterPath 30 | Column( 31 | modifier = modifier 32 | .fillMaxSize() 33 | .clip(MaterialTheme.shapes.medium) 34 | .clickable( 35 | onClick = { 36 | goToDetails(item.id, item.mediaType) 37 | }, 38 | ), 39 | horizontalAlignment = Alignment.CenterHorizontally, 40 | ) { 41 | NetworkImage( 42 | modifier = Modifier 43 | .clip(MaterialTheme.shapes.medium), 44 | imageUrl = fullImageUrl, 45 | widthDp = adjustedCardSize, 46 | heightDp = adjustedCardSize * POSTER_ASPECT_RATIO_MULTIPLY, 47 | ) 48 | Spacer(modifier = Modifier.height(DEFAULT_PADDING.dp)) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/navigation/components/MainNavBarItem.kt: -------------------------------------------------------------------------------- 1 | package navigation.components 2 | 3 | import cinetracker_kmp.composeapp.generated.resources.Res 4 | import cinetracker_kmp.composeapp.generated.resources.ic_nav_browse 5 | import cinetracker_kmp.composeapp.generated.resources.ic_nav_home 6 | import cinetracker_kmp.composeapp.generated.resources.ic_nav_search 7 | import cinetracker_kmp.composeapp.generated.resources.ic_nav_watchlist 8 | import cinetracker_kmp.composeapp.generated.resources.main_nav_browse 9 | import cinetracker_kmp.composeapp.generated.resources.main_nav_home 10 | import cinetracker_kmp.composeapp.generated.resources.main_nav_search 11 | import cinetracker_kmp.composeapp.generated.resources.main_nav_watchlist 12 | import features.browse.BrowseScreen 13 | import features.home.HomeScreen 14 | import features.search.SearchScreen 15 | import features.watchlist.WatchlistScreen 16 | import navigation.Screen 17 | import org.jetbrains.compose.resources.DrawableResource 18 | import org.jetbrains.compose.resources.StringResource 19 | 20 | sealed class MainNavBarItem( 21 | val screen: Screen, 22 | val labelResId: StringResource, 23 | val iconResId: DrawableResource, 24 | ) { 25 | data object Home : MainNavBarItem( 26 | screen = HomeScreen, 27 | labelResId = Res.string.main_nav_home, 28 | iconResId = Res.drawable.ic_nav_home, 29 | ) 30 | data object Browse : MainNavBarItem( 31 | screen = BrowseScreen, 32 | labelResId = Res.string.main_nav_browse, 33 | iconResId = Res.drawable.ic_nav_browse, 34 | ) 35 | data object Watchlist : MainNavBarItem( 36 | screen = WatchlistScreen, 37 | labelResId = Res.string.main_nav_watchlist, 38 | iconResId = Res.drawable.ic_nav_watchlist, 39 | ) 40 | data object Search : MainNavBarItem( 41 | screen = SearchScreen, 42 | labelResId = Res.string.main_nav_search, 43 | iconResId = Res.drawable.ic_nav_search, 44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/network/repository/search/SearchRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package network.repository.search 2 | 3 | import kotlinx.coroutines.flow.Flow 4 | import network.models.ApiError 5 | import network.models.content.common.MovieResponse 6 | import network.models.content.common.MultiResponse 7 | import network.models.content.common.PersonResponse 8 | import network.models.content.common.ShowResponse 9 | import network.models.content.search.ContentPagingResponse 10 | import network.services.search.SearchService 11 | import network.util.Either 12 | import network.util.asFlow 13 | 14 | class SearchRepositoryImpl( 15 | private val searchService: SearchService, 16 | ) : SearchRepository { 17 | override suspend fun onSearchMultiByQuery( 18 | query: String, 19 | page: Int, 20 | ): Flow, ApiError>> { 21 | return searchService.searchMultiByQuery( 22 | query = query, 23 | pageIndex = page, 24 | ).asFlow() 25 | } 26 | 27 | override suspend fun onSearchMovieByQuery( 28 | query: String, 29 | page: Int, 30 | ): Flow, ApiError>> { 31 | return searchService.searchMovieByQuery( 32 | query = query, 33 | pageIndex = page, 34 | ).asFlow() 35 | } 36 | 37 | override suspend fun onSearchShowByQuery( 38 | query: String, 39 | page: Int, 40 | ): Flow, ApiError>> { 41 | return searchService.searchShowByQuery( 42 | query = query, 43 | pageIndex = page, 44 | ).asFlow() 45 | } 46 | 47 | override suspend fun onSearchPersonByQuery( 48 | query: String, 49 | page: Int, 50 | ): Flow, ApiError>> { 51 | return searchService.searchPersonByQuery( 52 | query = query, 53 | pageIndex = page, 54 | ).asFlow() 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/features/details/ui/components/moreoptions/MoreOptionsTab.kt: -------------------------------------------------------------------------------- 1 | package features.details.ui.components.moreoptions 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.runtime.Composable 5 | import common.domain.models.content.GenericContent 6 | import common.domain.models.content.Videos 7 | import common.domain.models.util.MediaType 8 | import common.ui.components.GridContentList 9 | import common.ui.components.tab.GenericTabRow 10 | import common.ui.components.tab.setupGenericTabs 11 | import common.util.UiConstants.MAX_COUNT_MORE_LIKE_THIS_CARDS 12 | import features.details.ui.components.moreoptions.MoreOptionsTabItem.MoreLikeThisTab 13 | import features.details.ui.components.moreoptions.MoreOptionsTabItem.VideosTab 14 | 15 | @Composable 16 | fun MoreOptionsTab( 17 | videoList: List, 18 | contentSimilarList: List, 19 | goToDetails: (Int, MediaType) -> Unit, 20 | ) { 21 | val availableTabs = mutableListOf() 22 | if (contentSimilarList.isNotEmpty()) { 23 | availableTabs.add(MoreLikeThisTab) 24 | } 25 | if (videoList.isNotEmpty()) { 26 | availableTabs.add(VideosTab) 27 | } 28 | 29 | val (tabList, selectedTabIndex, updateSelectedTab) = setupGenericTabs(availableTabs) 30 | 31 | if (tabList.isNotEmpty()) { 32 | Column { 33 | GenericTabRow(selectedTabIndex.value, tabList, updateSelectedTab) 34 | 35 | when (tabList[selectedTabIndex.value].tabIndex) { 36 | VideosTab.tabIndex -> { 37 | VideoList(videoList) 38 | } 39 | 40 | MoreLikeThisTab.tabIndex -> { 41 | GridContentList( 42 | mediaContentList = contentSimilarList, 43 | maxCardsNumber = MAX_COUNT_MORE_LIKE_THIS_CARDS, 44 | openContentDetails = goToDetails, 45 | ) 46 | } 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/card/PersonImages.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components.card 2 | 3 | import androidx.compose.foundation.layout.Column 4 | import androidx.compose.foundation.layout.padding 5 | import androidx.compose.material3.Card 6 | import androidx.compose.material3.CardDefaults 7 | import androidx.compose.runtime.Composable 8 | import androidx.compose.ui.Alignment 9 | import androidx.compose.ui.Modifier 10 | import androidx.compose.ui.draw.clip 11 | import androidx.compose.ui.unit.Dp 12 | import androidx.compose.ui.unit.dp 13 | import common.ui.components.NetworkImage 14 | import common.ui.theme.MainBarGreyColor 15 | import common.ui.theme.RoundCornerShapes 16 | import common.util.Constants.BASE_500_IMAGE_URL 17 | import common.util.UiConstants.BROWSE_CARD_DEFAULT_ELEVATION 18 | import common.util.UiConstants.BROWSE_CARD_PADDING_HORIZONTAL 19 | import common.util.UiConstants.BROWSE_CARD_PADDING_VERTICAL 20 | import common.util.UiConstants.POSTER_ASPECT_RATIO_MULTIPLY 21 | 22 | @Composable 23 | fun PersonImages( 24 | modifier: Modifier = Modifier, 25 | cardWidth: Dp, 26 | imageUrl: String?, 27 | ) { 28 | val fullImageUrl = BASE_500_IMAGE_URL + imageUrl 29 | val imageHeight = cardWidth * POSTER_ASPECT_RATIO_MULTIPLY 30 | 31 | Card( 32 | modifier = modifier.padding( 33 | horizontal = BROWSE_CARD_PADDING_HORIZONTAL.dp, 34 | vertical = BROWSE_CARD_PADDING_VERTICAL.dp, 35 | ), 36 | colors = CardDefaults.cardColors( 37 | containerColor = MainBarGreyColor, 38 | ), 39 | elevation = CardDefaults.elevatedCardElevation( 40 | defaultElevation = BROWSE_CARD_DEFAULT_ELEVATION.dp, 41 | ), 42 | ) { 43 | Column( 44 | horizontalAlignment = Alignment.Start, 45 | ) { 46 | NetworkImage( 47 | imageUrl = fullImageUrl, 48 | modifier = Modifier.clip(RoundCornerShapes.medium), 49 | widthDp = cardWidth, 50 | heightDp = imageHeight, 51 | ) 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /composeApp/src/commonMain/kotlin/common/ui/components/PlaceholderView.kt: -------------------------------------------------------------------------------- 1 | package common.ui.components 2 | 3 | import androidx.compose.animation.core.LinearEasing 4 | import androidx.compose.animation.core.RepeatMode 5 | import androidx.compose.animation.core.animateFloat 6 | import androidx.compose.animation.core.infiniteRepeatable 7 | import androidx.compose.animation.core.rememberInfiniteTransition 8 | import androidx.compose.animation.core.tween 9 | import androidx.compose.foundation.background 10 | import androidx.compose.foundation.layout.Spacer 11 | import androidx.compose.runtime.Composable 12 | import androidx.compose.runtime.getValue 13 | import androidx.compose.ui.Modifier 14 | import androidx.compose.ui.geometry.Offset 15 | import androidx.compose.ui.graphics.Brush 16 | import androidx.compose.ui.graphics.Color 17 | import common.ui.theme.placeholderGrey 18 | import common.ui.theme.placeholderGrey2 19 | 20 | @Composable 21 | fun ComponentPlaceholder( 22 | modifier: Modifier = Modifier, 23 | initialValue: Float = -1000F, 24 | targetValue: Float = 2000F, 25 | durationMillis: Int = 3000, 26 | shimmerColorShades: List = listOf( 27 | placeholderGrey, 28 | placeholderGrey, 29 | placeholderGrey, 30 | placeholderGrey2, 31 | placeholderGrey2, 32 | ), 33 | ) { 34 | val transition = rememberInfiniteTransition(label = "transition") 35 | val translateAnim by transition.animateFloat( 36 | initialValue = initialValue, 37 | targetValue = targetValue, 38 | animationSpec = infiniteRepeatable( 39 | tween( 40 | durationMillis = durationMillis, 41 | easing = LinearEasing, 42 | ), 43 | repeatMode = RepeatMode.Restart, 44 | ), 45 | label = "placeholderAnimation", 46 | ) 47 | 48 | val brush = Brush.linearGradient( 49 | colors = shimmerColorShades, 50 | start = Offset.Zero, 51 | end = Offset(translateAnim, translateAnim), 52 | ) 53 | 54 | Spacer( 55 | modifier = modifier 56 | .background(brush), 57 | ) 58 | } 59 | --------------------------------------------------------------------------------