├── .idea
├── .name
├── codeStyles
│ ├── codeStyleConfig.xml
│ └── Project.xml
├── vcs.xml
├── kotlinScripting.xml
├── kotlinc.xml
├── artifacts
│ ├── shared_android.xml
│ └── shared_jvm.xml
├── inspectionProfiles
│ └── Project_Default.xml
├── runConfigurations.xml
├── compiler.xml
├── gradle.xml
├── misc.xml
└── jarRepositories.xml
├── android
├── .gitignore
├── src
│ └── main
│ │ ├── res
│ │ ├── values
│ │ │ ├── strings.xml
│ │ │ ├── colors.xml
│ │ │ └── styles.xml
│ │ ├── mipmap-hdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ │ ├── ic_launcher.png
│ │ │ └── ic_launcher_round.png
│ │ ├── mipmap-anydpi-v26
│ │ │ ├── ic_launcher.xml
│ │ │ └── ic_launcher_round.xml
│ │ ├── drawable
│ │ │ ├── ic_cloud_download.xml
│ │ │ └── ic_launcher_background.xml
│ │ ├── layout
│ │ │ ├── activity_main.xml
│ │ │ └── item_movie.xml
│ │ └── drawable-v24
│ │ │ └── ic_launcher_foreground.xml
│ │ ├── kotlin
│ │ └── app
│ │ │ └── web
│ │ │ └── drjackycv
│ │ │ └── tmdbmultiplatform
│ │ │ └── presentation
│ │ │ ├── base
│ │ │ └── util
│ │ │ │ └── MyAppGlideModule.kt
│ │ │ ├── extension
│ │ │ ├── Extensions.kt
│ │ │ └── ViewExtensions.kt
│ │ │ ├── entity
│ │ │ └── MovieUI.kt
│ │ │ ├── mapper
│ │ │ └── MoviesUIMapper.kt
│ │ │ ├── adapter
│ │ │ └── MoviesAdapter.kt
│ │ │ └── ui
│ │ │ └── MainActivity.kt
│ │ └── AndroidManifest.xml
├── proguard-rules.pro
└── build.gradle.kts
├── .github
├── FUNDING.yml
└── ISSUE_TEMPLATE
│ └── bug_report.md
├── list.png
├── cover.jpg
├── shared
├── src
│ ├── androidMain
│ │ └── AndroidManifest.xml
│ └── commonMain
│ │ └── kotlin
│ │ ├── data
│ │ ├── base
│ │ │ ├── mapper
│ │ │ │ └── Mapper.kt
│ │ │ └── remote
│ │ │ │ └── Api.kt
│ │ ├── repository
│ │ │ └── MoviesRepositoryImpl.kt
│ │ ├── entity
│ │ │ ├── Movie.kt
│ │ │ └── MoviesResponse.kt
│ │ ├── mapper
│ │ │ └── MoviesMapper.kt
│ │ └── remote
│ │ │ └── MoviesApiImpl.kt
│ │ ├── domain
│ │ ├── base
│ │ │ ├── usecase
│ │ │ │ └── UseCase.kt
│ │ │ └── repository
│ │ │ │ └── Repository.kt
│ │ └── usecase
│ │ │ └── GetMoviesUseCaseImpl.kt
│ │ ├── presentation
│ │ ├── base
│ │ │ ├── ListViewModel.kt
│ │ │ └── ViewModelBinding.kt
│ │ └── movies
│ │ │ └── MoviesListViewModel.kt
│ │ └── di
│ │ └── DI.kt
└── build.gradle.kts
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── ios
├── ios
│ ├── Assets.xcassets
│ │ ├── Contents.json
│ │ └── AppIcon.appiconset
│ │ │ └── Contents.json
│ ├── AppDelegate.swift
│ ├── Base.lproj
│ │ ├── Main.storyboard
│ │ └── LaunchScreen.storyboard
│ ├── MovieItem.swift
│ ├── Info.plist
│ ├── SceneDelegate.swift
│ └── ViewController.swift
├── ios.xcodeproj
│ ├── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ ├── xcuserdata
│ │ │ └── drjacky.xcuserdatad
│ │ │ │ ├── UserInterfaceState.xcuserstate
│ │ │ │ ├── IDEFindNavigatorScopes.plist
│ │ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ │ └── xcdebugger
│ │ │ │ └── Expressions.xcexplist
│ │ └── xcshareddata
│ │ │ ├── WorkspaceSettings.xcsettings
│ │ │ ├── IDEWorkspaceChecks.plist
│ │ │ └── swiftpm
│ │ │ └── Package.resolved
│ ├── xcuserdata
│ │ └── drjacky.xcuserdatad
│ │ │ ├── xcschemes
│ │ │ └── xcschememanagement.plist
│ │ │ └── xcdebugger
│ │ │ └── Breakpoints_v2.xcbkptlist
│ └── project.pbxproj
├── iosTests
│ ├── Info.plist
│ └── iosTests.swift
└── iosUITests
│ ├── Info.plist
│ └── iosUITests.swift
├── CONTRIBUTING.md
├── settings.gradle.kts
├── gradle.properties
├── LICENSE
├── README.md
├── gradlew.bat
├── .gitignore
├── CODE_OF_CONDUCT.md
└── gradlew
/.idea/.name:
--------------------------------------------------------------------------------
1 | TMDbMultiplatform
--------------------------------------------------------------------------------
/android/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | github: Drjacky
--------------------------------------------------------------------------------
/list.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/list.png
--------------------------------------------------------------------------------
/cover.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/cover.jpg
--------------------------------------------------------------------------------
/shared/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/ios/ios/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/android/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | TMDbMultiplatform
3 |
4 |
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-hdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-mdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-mdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-xhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/data/base/mapper/Mapper.kt:
--------------------------------------------------------------------------------
1 | package data.base.mapper
2 |
3 | interface Mapper {
4 |
5 | fun mapTo(response: T): E
6 |
7 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/data/base/remote/Api.kt:
--------------------------------------------------------------------------------
1 | package data.base.remote
2 |
3 | interface Api {
4 |
5 | suspend fun execute(request: R?): T
6 |
7 | }
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/android/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/domain/base/usecase/UseCase.kt:
--------------------------------------------------------------------------------
1 | package domain.base.usecase
2 |
3 | interface UseCase {
4 |
5 | suspend fun execute(request: R?): T
6 |
7 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/domain/base/repository/Repository.kt:
--------------------------------------------------------------------------------
1 | package domain.base.repository
2 |
3 | interface Repository {
4 |
5 | suspend fun get(request: R?): T
6 |
7 | }
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/.idea/kotlinScripting.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/project.xcworkspace/xcuserdata/drjacky.xcuserdatad/UserInterfaceState.xcuserstate:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Drjacky/TMDbMultiplatform/HEAD/ios/ios.xcodeproj/project.xcworkspace/xcuserdata/drjacky.xcuserdatad/UserInterfaceState.xcuserstate
--------------------------------------------------------------------------------
/.idea/kotlinc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
--------------------------------------------------------------------------------
/android/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #008577
4 | #00574B
5 | #D81B60
6 |
7 |
--------------------------------------------------------------------------------
/.idea/artifacts/shared_android.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | $PROJECT_DIR$/shared/build/libs
4 |
5 |
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Sun Nov 01 11:48:21 GMT 2020
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-all.zip
7 |
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/project.xcworkspace/xcuserdata/drjacky.xcuserdatad/IDEFindNavigatorScopes.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/.idea/inspectionProfiles/Project_Default.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/project.xcworkspace/xcshareddata/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PreviewsEnabled
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-anydpi-v26/ic_launcher.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/app/web/drjackycv/tmdbmultiplatform/presentation/base/util/MyAppGlideModule.kt:
--------------------------------------------------------------------------------
1 | package app.web.drjackycv.tmdbmultiplatform.presentation.base.util
2 |
3 | import com.bumptech.glide.annotation.GlideModule
4 | import com.bumptech.glide.module.AppGlideModule
5 |
6 | @GlideModule
7 | class MyAppGlideModule : AppGlideModule()
--------------------------------------------------------------------------------
/android/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/.idea/artifacts/shared_jvm.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | $PROJECT_DIR$/shared/build/libs
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/data/repository/MoviesRepositoryImpl.kt:
--------------------------------------------------------------------------------
1 | package data.repository
2 |
3 | import data.base.remote.Api
4 | import data.entity.Movie
5 | import domain.base.repository.Repository
6 |
7 | class MoviesRepositoryImpl(
8 | private val api: Api>
9 | ) : Repository> {
10 |
11 | override suspend fun get(request: R?): List = api.execute(request)
12 |
13 | }
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved:
--------------------------------------------------------------------------------
1 | {
2 | "object": {
3 | "pins": [
4 | {
5 | "package": "Nuke",
6 | "repositoryURL": "https://github.com/kean/Nuke.git",
7 | "state": {
8 | "branch": "master",
9 | "revision": "5558b60564bc25887b4ac1e7189442e8740521ce",
10 | "version": null
11 | }
12 | }
13 | ]
14 | },
15 | "version": 1
16 | }
17 |
--------------------------------------------------------------------------------
/android/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/xcuserdata/drjacky.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | ios.xcscheme_^#shared#^_
8 |
9 | orderHint
10 | 0
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/domain/usecase/GetMoviesUseCaseImpl.kt:
--------------------------------------------------------------------------------
1 | package domain.usecase
2 |
3 | import data.entity.Movie
4 | import domain.base.repository.Repository
5 | import domain.base.usecase.UseCase
6 |
7 | class GetMoviesUseCaseImpl(
8 | private val repository: Repository>
9 | ) : UseCase> {
10 |
11 | override suspend fun execute(request: R?): List {
12 | return repository.get(request)
13 | }
14 |
15 | }
--------------------------------------------------------------------------------
/android/src/main/res/drawable/ic_cloud_download.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/presentation/base/ListViewModel.kt:
--------------------------------------------------------------------------------
1 | package presentation.base
2 |
3 | import com.badoo.reaktive.observable.Observable
4 |
5 | interface ListViewModelInput {
6 |
7 | fun get(request: R)
8 | fun loadMore(request: R)
9 |
10 | }
11 |
12 | interface ListViewModelOutput {
13 |
14 | val loading: Observable
15 | val result: Observable>
16 |
17 | }
18 |
19 | interface ListViewModel {
20 |
21 | val inputs: ListViewModelInput
22 | val outputs: ListViewModelOutput
23 |
24 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/app/web/drjackycv/tmdbmultiplatform/presentation/extension/Extensions.kt:
--------------------------------------------------------------------------------
1 | package app.web.drjackycv.tmdbmultiplatform.presentation.extension
2 |
3 | import androidx.fragment.app.Fragment
4 | import androidx.lifecycle.*
5 |
6 | inline fun Fragment.viewModel(
7 | factory: ViewModelProvider.Factory,
8 | body: T.() -> Unit = {}
9 | ): T {
10 | val vm = ViewModelProvider(this, factory).get(T::class.java)
11 | vm.body()
12 |
13 | return vm
14 | }
15 |
16 | fun LifecycleOwner.observe(liveData: LiveData, action: (t: T) -> Unit) {
17 | liveData.observe(this, Observer { it?.let { t -> action(t) } })
18 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/data/entity/Movie.kt:
--------------------------------------------------------------------------------
1 | package data.entity
2 |
3 | private const val POSTER_PATH_BASE_URL = "https://image.tmdb.org/t/p/original"
4 |
5 | data class Movie(
6 | val popularity: Double,
7 | val voteCount: Int,
8 | val video: Boolean,
9 | val posterPath: String,
10 | val id: Int,
11 | val adult: Boolean,
12 | val backdropPath: String,
13 | val originalLanguage: String,
14 | val originalTitle: String,
15 | val genreIds: List,
16 | val title: String,
17 | val voteAverage: Double,
18 | val overview: String,
19 | val releaseDate: String,
20 | val fullPosterPath: String = POSTER_PATH_BASE_URL + posterPath
21 | )
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/project.xcworkspace/xcuserdata/drjacky.xcuserdatad/WorkspaceSettings.xcsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | BuildLocationStyle
6 | UseAppPreferences
7 | CustomBuildLocationType
8 | RelativeToDerivedData
9 | DerivedDataLocationStyle
10 | Default
11 | IssueFilterStyle
12 | ShowActiveSchemeOnly
13 | LiveSourceIssuesEnabled
14 |
15 | ShowSharedSchemesAutomaticallyEnabled
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/android/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/app/web/drjackycv/tmdbmultiplatform/presentation/entity/MovieUI.kt:
--------------------------------------------------------------------------------
1 | package app.web.drjackycv.tmdbmultiplatform.presentation.entity
2 |
3 | import android.os.Parcelable
4 | import kotlinx.android.parcel.Parcelize
5 |
6 | @Parcelize
7 | data class MovieUI(
8 | val popularity: Double,
9 | val voteCount: Int,
10 | val video: Boolean,
11 | val posterPath: String,
12 | val id: Int,
13 | val adult: Boolean,
14 | val backdropPath: String,
15 | val originalLanguage: String,
16 | val originalTitle: String,
17 | val genreIds: List,
18 | val title: String,
19 | val voteAverage: Double,
20 | val overview: String,
21 | val releaseDate: String,
22 | val fullPosterPath: String
23 | ) : Parcelable
--------------------------------------------------------------------------------
/ios/iosTests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ios/iosUITests/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | 1.0
19 | CFBundleVersion
20 | 1
21 |
22 |
23 |
--------------------------------------------------------------------------------
/android/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Contributing
2 | When contributing to this repository, please first discuss the change you wish to make via issue, email, or any other method with the owners of this repository before making a change.
3 |
4 | Please note we have a code of conduct, please follow it in all your interactions with the project.
5 |
6 | ## Pull Request Process
7 | 1. Ensure you had a clean install and check the result before making the pull request.
8 | 2. Update the README.md with details of changes to the interface, this includes new environment variables, and container parameters.
9 | 3. Increase the version numbers in any examples files and the README.md to the new version that this Pull Request would represent.
10 | 4. You may merge the Pull Request in once you have the sign-off of one developer, or if you do not have permission to do that, you may request the reviewer to merge it for you.
11 |
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | resolutionStrategy {
3 | eachPlugin {
4 | when (requested.id.id) {
5 | "kotlinx-serialization" -> useModule("org.jetbrains.kotlin:kotlin-serialization:1.5.0")
6 | //"kotlinx-serialization" -> useModule("org.jetbrains.kotlin:kotlin-serialization:${requested.version}")
7 | "kotlin-multiplatform" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.0")
8 | //"kotlin-multiplatform" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:${requested.version}")
9 | //"kotlin-platform-js" -> useModule("org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlinVersion")
10 | }
11 | }
12 | }
13 | }
14 |
15 | //enableFeaturePreview("GRADLE_METADATA")
16 |
17 |
18 | include(":android", ":shared")
19 | rootProject.name = "TMDbMultiplatform"
20 |
--------------------------------------------------------------------------------
/android/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/compiler.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: "[BUG]: Title goes here"
5 | labels: bug
6 | assignees: Drjacky
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. macOS]
28 | - Version [e.g. 11]
29 |
30 | **Smartphone (please complete the following information):**
31 | - Device: [e.g. iPhone6]
32 | - OS: [e.g. iOS8.1]
33 | - Version [e.g. 13]
34 |
35 | **Additional context**
36 | Add any other context about the problem here.
37 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/data/mapper/MoviesMapper.kt:
--------------------------------------------------------------------------------
1 | package data.mapper
2 |
3 | import data.base.mapper.Mapper
4 | import data.entity.Movie
5 | import data.entity.MoviesResponse
6 |
7 | class MoviesMapper : Mapper> {
8 |
9 | override fun mapTo(response: MoviesResponse): List = response.results.map {
10 | Movie(
11 | popularity = it.popularity,
12 | voteCount = it.voteCount,
13 | video = it.video,
14 | posterPath = it.posterPath ?: "",
15 | id = it.id,
16 | adult = it.adult,
17 | backdropPath = it.backdropPath ?: "",
18 | originalLanguage = it.originalLanguage,
19 | originalTitle = it.originalTitle,
20 | genreIds = it.genreIds,
21 | title = it.title,
22 | voteAverage = it.voteAverage,
23 | overview = it.overview,
24 | releaseDate = it.releaseDate
25 | )
26 | }
27 |
28 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # gradle
2 | org.gradle.jvmargs=-Xmx1536m
3 | org.gradle.parallel=true
4 | # kotlin
5 | kotlin.code.style=official
6 | kotlinVersion=1.5.0
7 | # 1.3.72
8 | kotlin.incremental.multiplatform=true
9 | kotlin.parallel.tasks.in.project=true
10 | ktorVersion=1.5.4
11 | # 1.3.2
12 | kotlinSerializationVersion=1.0.1
13 | # 0.20.0
14 | reactiveVersion=1.1.22
15 | reactiveNmtcVersion=1.1.22-nmtc
16 | coroutinesVersion=1.4.3-native-mt
17 | # 1.5.0-RC-native-mt
18 | kodeinVersion=7.5.0
19 | # 7.0.0
20 | # android
21 | android.useAndroidX=true
22 | android.enableJetifier=true
23 | systemProp.optimize.conservatively=true
24 | buildToolsVersion=30.0.3
25 | gradleAndroidVersion=4.2.0
26 | androidxBaseVersion=1.1.0
27 | androidxUiVersion=2.2.0
28 | androidxTestVersion=1.2.0
29 | androidxEspressoVersion=3.2.0
30 | androidMaterialVersion=1.3.0
31 | androidxSwipeRefreshLayoutVersion=1.1.0
32 | androidxRecyclerviewVersion=1.2.0
33 | androidConstraintLayoutVersion=1.1.3
34 | androidMultidexVersion=2.0.1
35 | glideVersion=4.12.0
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/ios/iosTests/iosTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // iosTests.swift
3 | // iosTests
4 | //
5 | // Created by Hossein Abbasi on 6/4/20.
6 | // Copyright © 2020 Hossein Abbasi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import ios
11 |
12 | class iosTests: XCTestCase {
13 |
14 | override func setUpWithError() throws {
15 | // Put setup code here. This method is called before the invocation of each test method in the class.
16 | }
17 |
18 | override func tearDownWithError() throws {
19 | // Put teardown code here. This method is called after the invocation of each test method in the class.
20 | }
21 |
22 | func testExample() throws {
23 | // This is an example of a functional test case.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testPerformanceExample() throws {
28 | // This is an example of a performance test case.
29 | self.measure {
30 | // Put the code you want to measure the time of here.
31 | }
32 | }
33 |
34 | }
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2020 Drjacky
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/android/src/main/kotlin/app/web/drjackycv/tmdbmultiplatform/presentation/mapper/MoviesUIMapper.kt:
--------------------------------------------------------------------------------
1 | package app.web.drjackycv.tmdbmultiplatform.presentation.mapper
2 |
3 | import app.web.drjackycv.tmdbmultiplatform.presentation.entity.MovieUI
4 | import data.base.mapper.Mapper
5 | import data.entity.Movie
6 |
7 | class MoviesUIMapper : Mapper, List> {
8 |
9 | override fun mapTo(response: List): List = response.map {
10 | MovieUI(
11 | popularity = it.popularity,
12 | voteCount = it.voteCount,
13 | video = it.video,
14 | posterPath = it.posterPath,
15 | id = it.id,
16 | adult = it.adult,
17 | backdropPath = it.backdropPath,
18 | originalLanguage = it.originalLanguage,
19 | originalTitle = it.originalTitle,
20 | genreIds = it.genreIds,
21 | title = it.title,
22 | voteAverage = it.voteAverage,
23 | overview = it.overview,
24 | releaseDate = it.releaseDate,
25 | fullPosterPath = it.fullPosterPath
26 | )
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/data/entity/MoviesResponse.kt:
--------------------------------------------------------------------------------
1 | package data.entity
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | @Serializable
7 | data class MoviesResponse(
8 | @SerialName("page") val page: Int,
9 | @SerialName("total_results") val totalResults: Int,
10 | @SerialName("total_pages") val totalPages: Int,
11 | @SerialName("results") val results: List
12 | )
13 |
14 | @Serializable
15 | data class MovieResponse(
16 | @SerialName("popularity") val popularity: Double,
17 | @SerialName("vote_count") val voteCount: Int,
18 | @SerialName("video") val video: Boolean,
19 | @SerialName("poster_path") val posterPath: String?,
20 | @SerialName("id") val id: Int,
21 | @SerialName("adult") val adult: Boolean,
22 | @SerialName("backdrop_path") val backdropPath: String?,
23 | @SerialName("original_language") val originalLanguage: String,
24 | @SerialName("original_title") val originalTitle: String,
25 | @SerialName("genre_ids") val genreIds: List,
26 | @SerialName("title") val title: String,
27 | @SerialName("vote_average") val voteAverage: Double,
28 | @SerialName("overview") val overview: String,
29 | @SerialName("release_date") val releaseDate: String
30 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/presentation/base/ViewModelBinding.kt:
--------------------------------------------------------------------------------
1 | package presentation.base
2 |
3 | import com.badoo.reaktive.annotations.ExperimentalReaktiveApi
4 | import com.badoo.reaktive.disposable.Disposable
5 | import com.badoo.reaktive.disposable.scope.DisposableScope
6 | import com.badoo.reaktive.observable.Observable
7 |
8 | @OptIn(ExperimentalReaktiveApi::class)
9 | class ViewModelBinding : DisposableScope by DisposableScope() {
10 |
11 | fun subscribe(
12 | observable: Observable,
13 | isThreadLocal: Boolean = true,
14 | onSubscribe: ((Disposable) -> Unit)? = null,
15 | onError: ((Throwable) -> Unit)? = null,
16 | onComplete: (() -> Unit)? = null,
17 | onNext: ((T) -> Unit)? = null
18 | ) {
19 | observable.subscribeScoped(
20 | isThreadLocal = isThreadLocal,
21 | onSubscribe = onSubscribe,
22 | onError = onError,
23 | onComplete = onComplete,
24 | onNext = onNext
25 | )
26 | }
27 |
28 | fun subscribe(
29 | observable: Observable,
30 | onError: ((Throwable) -> Unit)? = null,
31 | onNext: ((T) -> Unit)? = null
32 | ) {
33 | observable.subscribeScoped(
34 | isThreadLocal = true,
35 | onError = onError,
36 | onNext = onNext
37 | )
38 | }
39 |
40 | fun subscribe(observable: Observable, onNext: ((T) -> Unit)? = null) {
41 | observable.subscribeScoped(isThreadLocal = true, onNext = onNext)
42 | }
43 |
44 | }
--------------------------------------------------------------------------------
/ios/ios/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // ios
4 | //
5 | // Created by Hossein Abbasi on 6/4/20.
6 | // Copyright © 2020 Hossein Abbasi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | @UIApplicationMain
12 | class AppDelegate: UIResponder, UIApplicationDelegate {
13 |
14 |
15 |
16 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
17 | // Override point for customization after application launch.
18 | return true
19 | }
20 |
21 | // MARK: UISceneSession Lifecycle
22 |
23 | func application(_ application: UIApplication, configurationForConnecting connectingSceneSession: UISceneSession, options: UIScene.ConnectionOptions) -> UISceneConfiguration {
24 | // Called when a new scene session is being created.
25 | // Use this method to select a configuration to create the new scene with.
26 | return UISceneConfiguration(name: "Default Configuration", sessionRole: connectingSceneSession.role)
27 | }
28 |
29 | func application(_ application: UIApplication, didDiscardSceneSessions sceneSessions: Set) {
30 | // Called when the user discards a scene session.
31 | // If any sessions were discarded while the application was not running, this will be called shortly after application:didFinishLaunchingWithOptions.
32 | // Use this method to release any resources that were specific to the discarded scenes, as they will not return.
33 | }
34 |
35 |
36 | }
37 |
38 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/app/web/drjackycv/tmdbmultiplatform/presentation/extension/ViewExtensions.kt:
--------------------------------------------------------------------------------
1 | package app.web.drjackycv.tmdbmultiplatform.presentation.extension
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.ImageView
7 | import androidx.annotation.DrawableRes
8 | import androidx.annotation.LayoutRes
9 | import androidx.appcompat.content.res.AppCompatResources
10 | import app.web.drjackycv.tmdbmultiplatform.R
11 | import app.web.drjackycv.tmdbmultiplatform.presentation.base.util.GlideApp
12 | import com.bumptech.glide.request.RequestOptions
13 |
14 | fun View.gone() {
15 | visibility = View.GONE
16 | }
17 |
18 | fun View.visible() {
19 | visibility = View.VISIBLE
20 | }
21 |
22 | fun View.invisible() {
23 | visibility = View.INVISIBLE
24 | }
25 |
26 | fun ViewGroup.inflate(@LayoutRes layoutRes: Int, attachToRoot: Boolean = false): View =
27 | LayoutInflater.from(context).inflate(layoutRes, this, attachToRoot)
28 |
29 | fun ImageView.load(
30 | url: String,
31 | @DrawableRes placeholderRes: Int = R.drawable.ic_cloud_download
32 | ) {
33 | val safePlaceholderDrawable = AppCompatResources.getDrawable(context, placeholderRes)
34 | val requestOptions = RequestOptions().apply {
35 | placeholder(safePlaceholderDrawable)
36 | error(safePlaceholderDrawable)
37 | }
38 | val glideRequest = GlideApp
39 | .with(context)
40 | .setDefaultRequestOptions(requestOptions)
41 | .load(url)
42 |
43 | glideRequest.into(this)
44 | }
45 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 | Android
20 |
21 |
22 | CorrectnessLintAndroid
23 |
24 |
25 | Gradle
26 |
27 |
28 | LintAndroid
29 |
30 |
31 | Probable bugsGradle
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/ios/iosUITests/iosUITests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // iosUITests.swift
3 | // iosUITests
4 | //
5 | // Created by Hossein Abbasi on 6/4/20.
6 | // Copyright © 2020 Hossein Abbasi. All rights reserved.
7 | //
8 |
9 | import XCTest
10 |
11 | class iosUITests: XCTestCase {
12 |
13 | override func setUpWithError() throws {
14 | // Put setup code here. This method is called before the invocation of each test method in the class.
15 |
16 | // In UI tests it is usually best to stop immediately when a failure occurs.
17 | continueAfterFailure = false
18 |
19 | // In UI tests it’s important to set the initial state - such as interface orientation - required for your tests before they run. The setUp method is a good place to do this.
20 | }
21 |
22 | override func tearDownWithError() throws {
23 | // Put teardown code here. This method is called after the invocation of each test method in the class.
24 | }
25 |
26 | func testExample() throws {
27 | // UI tests must launch the application that they test.
28 | let app = XCUIApplication()
29 | app.launch()
30 |
31 | // Use recording to get started writing UI tests.
32 | // Use XCTAssert and related functions to verify your tests produce the correct results.
33 | }
34 |
35 | func testLaunchPerformance() throws {
36 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
37 | // This measures how long it takes to launch your application.
38 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
39 | XCUIApplication().launch()
40 | }
41 | }
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/ios/ios/Base.lproj/Main.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
--------------------------------------------------------------------------------
/android/src/main/kotlin/app/web/drjackycv/tmdbmultiplatform/presentation/adapter/MoviesAdapter.kt:
--------------------------------------------------------------------------------
1 | package app.web.drjackycv.tmdbmultiplatform.presentation.adapter
2 |
3 | import android.view.LayoutInflater
4 | import android.view.View
5 | import android.view.ViewGroup
6 | import android.widget.ImageView
7 | import android.widget.TextView
8 | import androidx.recyclerview.widget.RecyclerView
9 | import app.web.drjackycv.tmdbmultiplatform.R
10 | import app.web.drjackycv.tmdbmultiplatform.presentation.entity.MovieUI
11 | import app.web.drjackycv.tmdbmultiplatform.presentation.extension.load
12 |
13 | class MoviesAdapter : RecyclerView.Adapter() {
14 |
15 | private var mItems = mutableListOf()
16 |
17 | fun setList(list: List) {
18 | mItems.clear()
19 | mItems.addAll(list)
20 | notifyDataSetChanged()
21 | }
22 |
23 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MovieViewHolder {
24 | return MovieViewHolder(
25 | LayoutInflater.from(parent.context).inflate(R.layout.item_movie, parent, false)
26 | )
27 | }
28 |
29 | override fun getItemCount() = mItems.size
30 |
31 | override fun onBindViewHolder(holder: MovieViewHolder, position: Int) {
32 | val movie = mItems[position]
33 |
34 | holder.itemMovieImv.load(movie.fullPosterPath)
35 | holder.itemMovieTitleTxv.text = movie.title
36 | }
37 |
38 | class MovieViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
39 | val itemMovieImv: ImageView = itemView.findViewById(R.id.itemMovieImv)
40 | val itemMovieTitleTxv: TextView = itemView.findViewById(R.id.itemMovieTitleTxv)
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/ios/ios/Base.lproj/LaunchScreen.storyboard:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/project.xcworkspace/xcuserdata/drjacky.xcuserdatad/xcdebugger/Expressions.xcexplist:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
7 |
8 |
10 |
11 |
13 |
14 |
16 |
17 |
19 |
20 |
21 |
22 |
24 |
25 |
27 |
28 |
30 |
31 |
33 |
34 |
35 |
36 |
38 |
39 |
41 |
42 |
43 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/data/remote/MoviesApiImpl.kt:
--------------------------------------------------------------------------------
1 | package data.remote
2 |
3 | import data.base.mapper.Mapper
4 | import data.base.remote.Api
5 | import data.entity.Movie
6 | import data.entity.MoviesResponse
7 | import io.ktor.client.*
8 | import io.ktor.client.request.*
9 | import io.ktor.client.statement.*
10 | import io.ktor.http.*
11 | //import kotlinx.serialization.UnstableDefault
12 | import kotlinx.serialization.json.Json
13 |
14 | class MoviesApiImpl(
15 | private val client: HttpClient,
16 | private val key: String,
17 | private val hostUrl: String,
18 | private val mapper: Mapper>
19 | ) : Api> {
20 |
21 | //@OptIn(UnstableDefault::class)// = deprecated: @UseExperimental(UnstableDefault::class)
22 | override suspend fun execute(request: String?): List {
23 |
24 | val httpStatement = client.get {
25 | apiUrl()
26 | parameter("query", request)
27 | }
28 |
29 | val httpResponse = httpStatement.execute()
30 |
31 | val json = httpResponse.readText()
32 |
33 | //val response = Json.nonstrict.parse(MoviesResponse.serializer(), json)
34 | val response = Json {
35 | ignoreUnknownKeys = true
36 | isLenient = true
37 | }
38 | .decodeFromString(MoviesResponse.serializer(), json)
39 |
40 | return mapper.mapTo(response)
41 |
42 | }
43 |
44 | private fun HttpRequestBuilder.apiUrl(path: String? = null) {
45 | header(HttpHeaders.CacheControl, "no-cache")
46 | url {
47 | takeFrom(hostUrl).parameters.append("api_key", key)
48 | path?.let {
49 | encodedPath = it
50 | }
51 | }
52 | }
53 |
54 | }
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TMDbMultiplatform
2 | 
3 | [](https://app.fossa.com/projects/git%2Bgithub.com%2FDrjacky%2FTMDbMultiplatform?ref=badge_shield)
4 | [](https://androidweekly.net/issues/issue-425)
5 | 
6 | Link to the [article](https://medium.com/@drjacky/kotlin-multiplatform-rx-mvvm-1fb21280a0b5)
7 |
8 | A Multiplatform project to show list of movies on iOS and Android.
9 |
10 | ## Features 🕹
11 |
12 | - Android: 100% Kotlin
13 | - iOS: 100% Swift
14 | - Following Clean Architecture approach
15 | - Following Input/Output MVVM Architectural Design Pattern
16 |
17 | ## Under Development 🚧
18 |
19 | - Improve architecture
20 | - Add search box
21 | - Use Sqldelight to show how to use the database in Multiplatform apps
22 | - Improve Clean Architecture approach and separate modules with the power of gradle
23 | - Categorize dependencies
24 | - Add tests
25 | - ~~Migrate from OMDb API to TMDb API~~
26 | - ~~Use a DI framework or a Service Locator~~
27 | - ~~Migrate to Kotlin 1.4.X. To see changes, click [here](https://github.com/Drjacky/TMDbMultiplatform/pull/3/files).~~
28 |
29 | ## Contributing 🤝
30 |
31 | Feel free to open a issue or submit a pull request for any bugs/improvements.
32 |
33 | ## Result 📺
34 | 
35 |
36 | ## License ⚖️
37 | [](https://app.fossa.com/projects/git%2Bgithub.com%2FDrjacky%2FTMDbMultiplatform?ref=badge_large)
--------------------------------------------------------------------------------
/android/src/main/res/layout/item_movie.xml:
--------------------------------------------------------------------------------
1 |
2 |
12 |
13 |
18 |
19 |
28 |
29 |
40 |
41 |
42 |
--------------------------------------------------------------------------------
/android/src/main/res/drawable-v24/ic_launcher_foreground.xml:
--------------------------------------------------------------------------------
1 |
7 |
12 |
13 |
19 |
22 |
25 |
26 |
27 |
28 |
34 |
35 |
--------------------------------------------------------------------------------
/ios/ios/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "scale" : "2x",
6 | "size" : "20x20"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "scale" : "3x",
11 | "size" : "20x20"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "scale" : "2x",
16 | "size" : "29x29"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "scale" : "3x",
21 | "size" : "29x29"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "scale" : "2x",
26 | "size" : "40x40"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "scale" : "3x",
31 | "size" : "40x40"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "scale" : "2x",
36 | "size" : "60x60"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "scale" : "3x",
41 | "size" : "60x60"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "scale" : "1x",
46 | "size" : "20x20"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "scale" : "2x",
51 | "size" : "20x20"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "scale" : "1x",
56 | "size" : "29x29"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "scale" : "2x",
61 | "size" : "29x29"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "scale" : "1x",
66 | "size" : "40x40"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "scale" : "2x",
71 | "size" : "40x40"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "scale" : "1x",
76 | "size" : "76x76"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "scale" : "2x",
81 | "size" : "76x76"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "scale" : "2x",
86 | "size" : "83.5x83.5"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "scale" : "1x",
91 | "size" : "1024x1024"
92 | }
93 | ],
94 | "info" : {
95 | "author" : "xcode",
96 | "version" : 1
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/di/DI.kt:
--------------------------------------------------------------------------------
1 | package di
2 |
3 | import data.base.mapper.Mapper
4 | import data.base.remote.Api
5 | import data.entity.Movie
6 | import data.entity.MoviesResponse
7 | import data.mapper.MoviesMapper
8 | import data.remote.MoviesApiImpl
9 | import data.repository.MoviesRepositoryImpl
10 | import domain.base.repository.Repository
11 | import domain.base.usecase.UseCase
12 | import domain.usecase.GetMoviesUseCaseImpl
13 | import io.ktor.client.*
14 | import io.ktor.client.features.json.*
15 | import io.ktor.client.features.json.serializer.*
16 | import io.ktor.client.features.logging.*
17 | import org.kodein.di.*
18 | import kotlin.native.concurrent.ThreadLocal
19 |
20 | @ThreadLocal
21 | object Di {
22 |
23 | val di = DI.lazy {
24 | bind>>() with singleton { MoviesMapper() }
25 | bind("ApiHost") with provider { "https://api.themoviedb.org/3/search/movie" }
26 | bind("ApiKey") with provider { "238af7cc48b4305ff3fb75d7af217de4" }
27 | bind() with singleton {
28 | HttpClient {
29 | install(JsonFeature) {
30 | serializer = KotlinxSerializer()
31 | }
32 | install(Logging) {
33 | logger = Logger.DEFAULT
34 | level = LogLevel.ALL
35 | }
36 | }
37 |
38 | }
39 | bind>>("Api") with provider {
40 | val apiKey by di.instance("ApiKey")
41 | val apiHost by di.instance("ApiHost")
42 |
43 | MoviesApiImpl(
44 | client = instance(),
45 | key = apiKey,
46 | hostUrl = apiHost,
47 | mapper = instance()
48 | )
49 | }
50 | bind>>("Repository") with provider {
51 | MoviesRepositoryImpl(instance(tag = "Api"))
52 | }
53 | bind>>("UseCase") with provider {
54 | GetMoviesUseCaseImpl(instance(tag = "Repository"))
55 | }
56 | }
57 |
58 | }
--------------------------------------------------------------------------------
/ios/ios/MovieItem.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MovieItem.swift
3 | // ios
4 | //
5 | // Created by Hossein Abbasi on 7/27/20.
6 | // Copyright © 2020 Hossein Abbasi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared
11 | import Nuke
12 |
13 | class MovieItem: UICollectionViewCell {
14 |
15 | let imgCover: UIImageView = {
16 | let v = UIImageView()
17 |
18 | v.translatesAutoresizingMaskIntoConstraints = false
19 | v.contentMode = .scaleAspectFill
20 | v.clipsToBounds = true
21 |
22 | return v
23 | }()
24 |
25 | let lblTitle: UILabel = {
26 | let v = UILabel()
27 |
28 | v.translatesAutoresizingMaskIntoConstraints = false
29 | v.numberOfLines = 2
30 | v.textAlignment = .center
31 |
32 | return v
33 | }()
34 |
35 | var movie: Movie? {
36 | didSet {
37 | guard let movie = movie else { return }
38 |
39 | if let poster = URL(string: movie.fullPosterPath) {
40 | Nuke.loadImage(with: poster, into: imgCover)
41 | }
42 |
43 | lblTitle.text = movie.title
44 | }
45 | }
46 |
47 | required init(coder aDecoder: NSCoder) {
48 | fatalError("init(coder:)")
49 | }
50 |
51 | override init(frame: CGRect) {
52 | super.init(frame: frame)
53 |
54 | contentView.addSubview(imgCover)
55 | contentView.addSubview(lblTitle)
56 |
57 | NSLayoutConstraint.activate([
58 | imgCover.leadingAnchor.constraint(equalTo: contentView.leadingAnchor),
59 | imgCover.topAnchor.constraint(equalTo: contentView.topAnchor),
60 | imgCover.trailingAnchor.constraint(equalTo: contentView.trailingAnchor),
61 | imgCover.heightAnchor.constraint(equalToConstant: 200),
62 |
63 | lblTitle.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 10),
64 | lblTitle.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -10),
65 | lblTitle.topAnchor.constraint(equalTo: imgCover.bottomAnchor, constant: 10)
66 | ])
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/ios/ios/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | $(DEVELOPMENT_LANGUAGE)
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIdentifier
10 | $(PRODUCT_BUNDLE_IDENTIFIER)
11 | CFBundleInfoDictionaryVersion
12 | 6.0
13 | CFBundleName
14 | $(PRODUCT_NAME)
15 | CFBundlePackageType
16 | $(PRODUCT_BUNDLE_PACKAGE_TYPE)
17 | CFBundleShortVersionString
18 | $(MARKETING_VERSION)
19 | CFBundleVersion
20 | $(CURRENT_PROJECT_VERSION)
21 | LSRequiresIPhoneOS
22 |
23 | UIApplicationSceneManifest
24 |
25 | UIApplicationSupportsMultipleScenes
26 |
27 | UISceneConfigurations
28 |
29 | UIWindowSceneSessionRoleApplication
30 |
31 |
32 | UISceneConfigurationName
33 | Default Configuration
34 | UISceneDelegateClassName
35 | $(PRODUCT_MODULE_NAME).SceneDelegate
36 | UISceneStoryboardFile
37 | Main
38 |
39 |
40 |
41 |
42 | UILaunchStoryboardName
43 | LaunchScreen
44 | UIMainStoryboardFile
45 | Main
46 | UIRequiredDeviceCapabilities
47 |
48 | armv7
49 |
50 | UISupportedInterfaceOrientations
51 |
52 | UIInterfaceOrientationPortrait
53 | UIInterfaceOrientationLandscapeLeft
54 | UIInterfaceOrientationLandscapeRight
55 |
56 | UISupportedInterfaceOrientations~ipad
57 |
58 | UIInterfaceOrientationPortrait
59 | UIInterfaceOrientationPortraitUpsideDown
60 | UIInterfaceOrientationLandscapeLeft
61 | UIInterfaceOrientationLandscapeRight
62 |
63 |
64 |
65 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/.idea/jarRepositories.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
--------------------------------------------------------------------------------
/ios/ios/SceneDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // SceneDelegate.swift
3 | // ios
4 | //
5 | // Created by Hossein Abbasi on 6/4/20.
6 | // Copyright © 2020 Hossein Abbasi. All rights reserved.
7 | //
8 |
9 | import UIKit
10 |
11 | class SceneDelegate: UIResponder, UIWindowSceneDelegate {
12 |
13 | var window: UIWindow?
14 |
15 |
16 | func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
17 | // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
18 | // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
19 | // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).
20 | guard let _ = (scene as? UIWindowScene) else { return }
21 | }
22 |
23 | func sceneDidDisconnect(_ scene: UIScene) {
24 | // Called as the scene is being released by the system.
25 | // This occurs shortly after the scene enters the background, or when its session is discarded.
26 | // Release any resources associated with this scene that can be re-created the next time the scene connects.
27 | // The scene may re-connect later, as its session was not neccessarily discarded (see `application:didDiscardSceneSessions` instead).
28 | }
29 |
30 | func sceneDidBecomeActive(_ scene: UIScene) {
31 | // Called when the scene has moved from an inactive state to an active state.
32 | // Use this method to restart any tasks that were paused (or not yet started) when the scene was inactive.
33 | }
34 |
35 | func sceneWillResignActive(_ scene: UIScene) {
36 | // Called when the scene will move from an active state to an inactive state.
37 | // This may occur due to temporary interruptions (ex. an incoming phone call).
38 | }
39 |
40 | func sceneWillEnterForeground(_ scene: UIScene) {
41 | // Called as the scene transitions from the background to the foreground.
42 | // Use this method to undo the changes made on entering the background.
43 | }
44 |
45 | func sceneDidEnterBackground(_ scene: UIScene) {
46 | // Called as the scene transitions from the foreground to the background.
47 | // Use this method to save data, release shared resources, and store enough scene-specific state information
48 | // to restore the scene back to its current state.
49 | }
50 |
51 |
52 | }
53 |
54 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .gradle
2 | /local.properties
3 | /.idea/caches
4 | /.idea/libraries
5 | /.idea/modules.xml
6 | /.idea/workspace.xml
7 | /.idea/navEditor.xml
8 | /.idea/assetWizardSettings.xml
9 | .DS_Store
10 | /build
11 | /shared/build
12 | /captures
13 | .cxx
14 | /.idea/sonarlint
15 |
16 | # Created by https://www.toptal.com/developers/gitignore/api/android,kotlin
17 | # Edit at https://www.toptal.com/developers/gitignore?templates=android,kotlin
18 |
19 | ### Android ###
20 | # Built application files
21 | *.apk
22 | *.ap_
23 | *.aab
24 |
25 | # Files for the ART/Dalvik VM
26 | *.dex
27 |
28 | # Java class files
29 | *.class
30 |
31 | # Generated files
32 | bin/
33 | gen/
34 | out/
35 |
36 | # Gradle files
37 | .gradle/
38 | build/
39 |
40 | # Local configuration file (sdk path, etc)
41 | local.properties
42 |
43 | # Proguard folder generated by Eclipse
44 | proguard/
45 |
46 | # Log Files
47 | *.log
48 |
49 | # Android Studio Navigation editor temp files
50 | .navigation/
51 |
52 | # Android Studio captures folder
53 | captures/
54 |
55 | # IntelliJ
56 | *.iml
57 | .idea/workspace.xml
58 | .idea/tasks.xml
59 | .idea/gradle.xml
60 | .idea/assetWizardSettings.xml
61 | .idea/dictionaries
62 | .idea/libraries
63 | .idea/caches
64 | # Android Studio 3 in .gitignore file.
65 | .idea/caches/build_file_checksums.ser
66 | .idea/modules.xml
67 |
68 | # Keystore files
69 | # Uncomment the following lines if you do not want to check your keystore files in.
70 | #*.jks
71 | #*.keystore
72 |
73 | # External native build folder generated in Android Studio 2.2 and later
74 | .externalNativeBuild
75 |
76 | # Google Services (e.g. APIs or Firebase)
77 | # google-services.json
78 |
79 | # Freeline
80 | freeline.py
81 | freeline/
82 | freeline_project_description.json
83 |
84 | # fastlane
85 | fastlane/report.xml
86 | fastlane/Preview.html
87 | fastlane/screenshots
88 | fastlane/test_output
89 | fastlane/readme.md
90 |
91 | # Version control
92 | vcs.xml
93 |
94 | # lint
95 | lint/intermediates/
96 | lint/generated/
97 | lint/outputs/
98 | lint/tmp/
99 | # lint/reports/
100 |
101 | ### Android Patch ###
102 | gen-external-apklibs
103 | output.json
104 |
105 | ### Kotlin ###
106 | # Compiled class file
107 |
108 | # Log file
109 |
110 | # BlueJ files
111 | *.ctxt
112 |
113 | # Mobile Tools for Java (J2ME)
114 | .mtj.tmp/
115 |
116 | # Package Files #
117 | *.jar
118 | *.war
119 | *.nar
120 | *.ear
121 | *.zip
122 | *.tar.gz
123 | *.rar
124 |
125 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
126 | hs_err_pid*
127 |
128 | # End of https://www.toptal.com/developers/gitignore/api/android,kotlin
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/presentation/movies/MoviesListViewModel.kt:
--------------------------------------------------------------------------------
1 | package presentation.movies
2 |
3 | import com.badoo.reaktive.coroutinesinterop.singleFromCoroutine
4 | import com.badoo.reaktive.observable.*
5 | import com.badoo.reaktive.subject.publish.PublishSubject
6 | import data.base.mapper.Mapper
7 | import data.entity.Movie
8 | import di.Di
9 | import domain.base.usecase.UseCase
10 | import org.kodein.di.instance
11 | import presentation.base.ListViewModel
12 | import presentation.base.ListViewModelInput
13 | import presentation.base.ListViewModelOutput
14 |
15 | class MoviesListViewModel(
16 | mapper: Mapper, List>?
17 | ) : ListViewModel, ListViewModelInput, ListViewModelOutput {
18 | override val inputs: ListViewModelInput = this
19 | override val outputs: ListViewModelOutput = this
20 | override val loading: Observable
21 | override val result: Observable>
22 |
23 | val useCase: UseCase> by Di.di.instance("UseCase")
24 | private val mListProperty = PublishSubject()// publishSubject()
25 | private val mLoadMoreProperty = PublishSubject()//publishSubject()
26 |
27 | init {
28 | val loadingProperty = PublishSubject()//publishSubject()
29 |
30 | val items = mutableListOf()
31 |
32 | var clearItems = false
33 |
34 | loading = loadingProperty
35 |
36 | val initialRequest = mListProperty
37 | .doOnBeforeNext { loadingProperty.onNext(true) }
38 | .flatMapSingle { request ->
39 | singleFromCoroutine { useCase.execute(request) }
40 | }
41 | .doOnBeforeNext {
42 | loadingProperty.onNext(false)
43 | clearItems = true
44 | }
45 |
46 | val nextRequest = mLoadMoreProperty
47 | .doOnBeforeNext { loadingProperty.onNext(true) }
48 | .flatMapSingle { request ->
49 | singleFromCoroutine { useCase.execute(request) }
50 | }
51 | .doOnBeforeNext {
52 | loadingProperty.onNext(false)
53 | clearItems = false
54 | }
55 |
56 | result = merge(initialRequest, nextRequest).map {
57 | if (clearItems) {
58 | items.clear()
59 | }
60 |
61 | @Suppress("UNCHECKED_CAST")
62 | val list = mapper?.mapTo(it) ?: it as List
63 |
64 | items.addAll(list)
65 |
66 | items
67 | }
68 |
69 | }
70 |
71 | override fun get(request: String) {
72 | mListProperty.onNext(request)
73 | }
74 |
75 | override fun loadMore(request: String) {
76 | mLoadMoreProperty.onNext(request)
77 | }
78 |
79 | }
--------------------------------------------------------------------------------
/android/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
2 |
3 | val buildToolsVersion: String by project
4 | val kotlinVersion: String by project
5 | val androidxBaseVersion: String by project
6 | val androidxUiVersion: String by project
7 | val androidMaterialVersion: String by project
8 | val androidxSwipeRefreshLayoutVersion: String by project
9 | val androidxRecyclerviewVersion: String by project
10 | val androidConstraintLayoutVersion: String by project
11 | val androidMultidexVersion: String by project
12 | val ktorVersion: String by project
13 | val glideVersion: String by project
14 | val reactiveVersion: String by project
15 | val kodeinVersion: String by project
16 |
17 | plugins {
18 | id("com.android.application")
19 | kotlin("android") // = id("kotlin-android")
20 | kotlin("android.extensions")
21 | kotlin("kapt")
22 | }
23 |
24 | androidExtensions {
25 | isExperimental = true
26 | }
27 |
28 | android {
29 | compileSdkVersion(29)
30 | buildToolsVersion = buildToolsVersion
31 | defaultConfig {
32 | applicationId = "app.web.drjackycv.tmdbmultiplatform"
33 | minSdkVersion(21)
34 | targetSdkVersion(29)
35 | multiDexEnabled = true
36 | versionCode = 3
37 | versionName = "1.2.0"
38 | testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
39 | }
40 |
41 | buildTypes {
42 | val debug by getting {}
43 | val release by getting {
44 | isMinifyEnabled = false
45 | proguardFiles(
46 | getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
47 | )
48 | }
49 | }
50 | sourceSets {
51 | val main by getting {
52 | java.srcDirs("src/main/kotlin")
53 | manifest.srcFile("src/main/AndroidManifest.xml")
54 | res.srcDirs("src/main/res")
55 | }
56 | }
57 | packagingOptions {
58 | exclude("META-INF/*.kotlin_module")
59 | }
60 | }
61 |
62 | tasks.withType {
63 | kotlinOptions {
64 | jvmTarget = "1.8"
65 | }
66 | }
67 |
68 | dependencies {
69 | implementation("androidx.appcompat:appcompat:$androidxBaseVersion")
70 | implementation("androidx.core:core-ktx:$androidxBaseVersion")
71 | implementation("com.google.android.material:material:$androidMaterialVersion")
72 | implementation("androidx.swiperefreshlayout:swiperefreshlayout:$androidxSwipeRefreshLayoutVersion")
73 | implementation("androidx.recyclerview:recyclerview:$androidxRecyclerviewVersion")
74 | implementation("androidx.constraintlayout:constraintlayout:$androidConstraintLayoutVersion")
75 | implementation("com.android.support:multidex:$androidMultidexVersion")
76 | implementation("io.ktor:ktor-client-android:$ktorVersion")
77 | implementation("com.github.bumptech.glide:glide:$glideVersion")
78 | kapt("com.github.bumptech.glide:compiler:$glideVersion")
79 | implementation("com.badoo.reaktive:reaktive:$reactiveVersion")
80 | implementation("org.kodein.di:kodein-di-framework-android-x:$kodeinVersion")
81 |
82 | implementation(project(":shared"))
83 | }
--------------------------------------------------------------------------------
/android/src/main/kotlin/app/web/drjackycv/tmdbmultiplatform/presentation/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package app.web.drjackycv.tmdbmultiplatform.presentation.ui
2 |
3 | import android.os.Bundle
4 | import androidx.appcompat.app.AppCompatActivity
5 | import androidx.recyclerview.widget.GridLayoutManager
6 | import androidx.recyclerview.widget.LinearLayoutManager
7 | import androidx.recyclerview.widget.RecyclerView
8 | import app.web.drjackycv.tmdbmultiplatform.R
9 | import app.web.drjackycv.tmdbmultiplatform.presentation.adapter.MoviesAdapter
10 | import app.web.drjackycv.tmdbmultiplatform.presentation.entity.MovieUI
11 | import app.web.drjackycv.tmdbmultiplatform.presentation.mapper.MoviesUIMapper
12 | import com.badoo.reaktive.observable.observeOn
13 | import com.badoo.reaktive.scheduler.mainScheduler
14 | import kotlinx.android.synthetic.main.activity_main.*
15 | import presentation.base.ListViewModel
16 | import presentation.base.ViewModelBinding
17 | import presentation.movies.MoviesListViewModel
18 |
19 | private const val KEYWORD = "avengers"
20 |
21 | class MainActivity : AppCompatActivity() {
22 |
23 | private lateinit var mMoviesAdapter: MoviesAdapter
24 | private var mIsRefreshing = false
25 | private val mBinding = ViewModelBinding()
26 | private val mViewModel: ListViewModel by lazy {
27 | val moviesUiMapper = MoviesUIMapper()
28 |
29 | MoviesListViewModel(moviesUiMapper)
30 | }
31 |
32 | override fun onCreate(savedInstanceState: Bundle?) {
33 | binding()
34 | super.onCreate(savedInstanceState)
35 | setContentView(R.layout.activity_main)
36 | setupUI()
37 | }
38 |
39 | private fun setupUI() {
40 | mMoviesAdapter = MoviesAdapter()
41 | mainList.layoutManager = GridLayoutManager(this, 2)
42 | mainList.adapter = mMoviesAdapter
43 |
44 | mainList.addOnScrollListener(object : RecyclerView.OnScrollListener() {
45 | override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
46 | super.onScrolled(recyclerView, dx, dy)
47 |
48 | val manager = mainList.layoutManager as LinearLayoutManager
49 |
50 | val totalItemCount = manager.itemCount
51 | val lastVisibleItem = manager.findLastVisibleItemPosition()
52 |
53 | if (!mIsRefreshing && totalItemCount <= lastVisibleItem + 2) {
54 | loadMore()
55 | }
56 | }
57 | })
58 |
59 | mainSwipeRefresh.setOnRefreshListener {
60 | mViewModel.inputs.get(KEYWORD)
61 | }
62 |
63 | mViewModel.inputs.get(KEYWORD)
64 | }
65 |
66 | override fun onDestroy() {
67 | mBinding.dispose() //TODO Move it into view model
68 | super.onDestroy()
69 | }
70 |
71 | private fun binding() {
72 | mBinding.subscribe(mViewModel.outputs.loading.observeOn(mainScheduler), onNext = ::loading)
73 | mBinding.subscribe(mViewModel.outputs.result.observeOn(mainScheduler), onNext = ::result)
74 | }
75 |
76 | private fun loading(isLoading: Boolean) {
77 | mIsRefreshing = isLoading
78 |
79 | mainSwipeRefresh.isRefreshing = isLoading
80 | }
81 |
82 | private fun result(movies: List) {
83 | mMoviesAdapter.setList(movies)
84 | }
85 |
86 | private fun loadMore() = mViewModel.inputs.loadMore(KEYWORD)
87 |
88 | }
89 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 | # Contributor Covenant Code of Conduct
2 |
3 | ## Our Pledge
4 |
5 | In the interest of fostering an open and welcoming environment, we as
6 | contributors and maintainers pledge to making participation in our project and
7 | our community a harassment-free experience for everyone, regardless of age, body
8 | size, disability, ethnicity, sex characteristics, gender identity and expression,
9 | level of experience, education, socio-economic status, nationality, personal
10 | appearance, race, religion, or sexual identity and orientation.
11 |
12 | ## Our Standards
13 |
14 | Examples of behavior that contributes to creating a positive environment
15 | include:
16 |
17 | * Using welcoming and inclusive language
18 | * Being respectful of differing viewpoints and experiences
19 | * Gracefully accepting constructive criticism
20 | * Focusing on what is best for the community
21 | * Showing empathy towards other community members
22 |
23 | Examples of unacceptable behavior by participants include:
24 |
25 | * The use of sexualized language or imagery and unwelcome sexual attention or
26 | advances
27 | * Trolling, insulting/derogatory comments, and personal or political attacks
28 | * Public or private harassment
29 | * Publishing others' private information, such as a physical or electronic
30 | address, without explicit permission
31 | * Other conduct which could reasonably be considered inappropriate in a
32 | professional setting
33 |
34 | ## Our Responsibilities
35 |
36 | Project maintainers are responsible for clarifying the standards of acceptable
37 | behavior and are expected to take appropriate and fair corrective action in
38 | response to any instances of unacceptable behavior.
39 |
40 | Project maintainers have the right and responsibility to remove, edit, or
41 | reject comments, commits, code, wiki edits, issues, and other contributions
42 | that are not aligned to this Code of Conduct, or to ban temporarily or
43 | permanently any contributor for other behaviors that they deem inappropriate,
44 | threatening, offensive, or harmful.
45 |
46 | ## Scope
47 |
48 | This Code of Conduct applies both within project spaces and in public spaces
49 | when an individual is representing the project or its community. Examples of
50 | representing a project or community include using an official project e-mail
51 | address, posting via an official social media account, or acting as an appointed
52 | representative at an online or offline event. Representation of a project may be
53 | further defined and clarified by project maintainers.
54 |
55 | ## Enforcement
56 |
57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be
58 | reported by contacting the project team at dr.jacky.2005@gmail.com. All
59 | complaints will be reviewed and investigated and will result in a response that
60 | is deemed necessary and appropriate to the circumstances. The project team is
61 | obligated to maintain confidentiality with regard to the reporter of an incident.
62 | Further details of specific enforcement policies may be posted separately.
63 |
64 | Project maintainers who do not follow or enforce the Code of Conduct in good
65 | faith may face temporary or permanent repercussions as determined by other
66 | members of the project's leadership.
67 |
68 | ## Attribution
69 |
70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
72 |
73 | [homepage]: https://www.contributor-covenant.org
74 |
75 | For answers to common questions about this code of conduct, see
76 | https://www.contributor-covenant.org/faq
77 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 | xmlns:android
17 |
18 | ^$
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 | xmlns:.*
28 |
29 | ^$
30 |
31 |
32 | BY_NAME
33 |
34 |
35 |
36 |
37 |
38 |
39 | .*:id
40 |
41 | http://schemas.android.com/apk/res/android
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | .*:name
51 |
52 | http://schemas.android.com/apk/res/android
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | name
62 |
63 | ^$
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 | style
73 |
74 | ^$
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | .*
84 |
85 | ^$
86 |
87 |
88 | BY_NAME
89 |
90 |
91 |
92 |
93 |
94 |
95 | .*
96 |
97 | http://schemas.android.com/apk/res/android
98 |
99 |
100 | ANDROID_ATTRIBUTE_ORDER
101 |
102 |
103 |
104 |
105 |
106 |
107 | .*
108 |
109 | .*
110 |
111 |
112 | BY_NAME
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
--------------------------------------------------------------------------------
/ios/ios/ViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MovieItem.swift
3 | // ios
4 | //
5 | // Created by Hossein Abbasi on 7/27/20.
6 | // Copyright © 2020 Hossein Abbasi. All rights reserved.
7 | //
8 |
9 |
10 | import UIKit
11 | import shared
12 |
13 | private var KEYWORD = "avengers"
14 |
15 | class ViewController: UIViewController {
16 | private var _movies: [Movie]?
17 | private var _isRefreshing = false
18 |
19 | lazy var uiRefreshControl: UIRefreshControl = {
20 | let view = UIRefreshControl()
21 |
22 | view.addTarget(self, action: #selector(refresh), for: .valueChanged)
23 |
24 | return view
25 | }()
26 |
27 | lazy var uiCollectionView: UICollectionView = {
28 | let layout = UICollectionViewFlowLayout()
29 |
30 | layout.minimumLineSpacing = 15
31 | layout.scrollDirection = .vertical
32 | layout.sectionInset = UIEdgeInsets(top: 15, left: 8, bottom: 8, right: 8)
33 |
34 | let marginsAndInsets = layout.sectionInset.left + layout.sectionInset.right + layout.minimumInteritemSpacing * CGFloat(2 - 1)
35 | let itemWidth = ((UIScreen.main.bounds.size.width - marginsAndInsets) / CGFloat(2)).rounded(.down)
36 | let itemSize = CGSize(width: itemWidth, height: 270)
37 |
38 | layout.itemSize = itemSize
39 |
40 | let v = UICollectionView(frame: .zero, collectionViewLayout: layout)
41 |
42 | v.backgroundColor = UIColor(named: "ListBackground")
43 | v.delegate = self
44 | v.dataSource = self
45 | v.alwaysBounceVertical = true
46 | v.refreshControl = uiRefreshControl
47 | v.translatesAutoresizingMaskIntoConstraints = false
48 | v.register(MovieItem.self, forCellWithReuseIdentifier: "MovieItem")
49 |
50 | return v
51 | }()
52 |
53 | private lazy var _viewModel: MoviesListViewModel = {
54 | let delegate = UIApplication.shared.delegate as! AppDelegate
55 | let viewModel = MoviesListViewModel(mapper: nil)
56 |
57 | return viewModel
58 | }()
59 |
60 | private lazy var _binding: ViewModelBinding = {
61 | return ViewModelBinding()
62 | }()
63 |
64 | deinit {
65 | _binding.dispose()
66 | }
67 |
68 | override func viewDidLoad() {
69 | super.viewDidLoad()
70 |
71 | binding()
72 |
73 | view.addSubview(uiCollectionView)
74 |
75 | NSLayoutConstraint.activate([
76 | uiCollectionView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
77 | uiCollectionView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
78 | uiCollectionView.topAnchor.constraint(equalTo: view.topAnchor),
79 | uiCollectionView.bottomAnchor.constraint(equalTo: view.bottomAnchor)
80 | ])
81 |
82 | _viewModel.inputs.get(request: KEYWORD)
83 | }
84 |
85 | // MARK - Selector
86 | @objc func refresh() {
87 | _viewModel.inputs.get(request: KEYWORD)
88 | }
89 |
90 | // MARK - Private
91 | private func binding() {
92 |
93 | _binding.subscribe(observable: _viewModel.outputs.loading) { [weak self] result in
94 | guard let strongSelf = self, let loading = result as? Bool else { return }
95 |
96 | strongSelf._isRefreshing = loading
97 |
98 | if loading {
99 | strongSelf.uiRefreshControl.beginRefreshing()
100 | } else {
101 | strongSelf.uiRefreshControl.endRefreshing()
102 | }
103 | }
104 |
105 | _binding.subscribe(observable: _viewModel.outputs.result) { [weak self] result in
106 | guard let strongSelf = self, let list = result as? [Movie] else { return }
107 |
108 | strongSelf._movies = list
109 | strongSelf.uiCollectionView.reloadData()
110 | }
111 | }
112 | }
113 |
114 | extension ViewController: UICollectionViewDataSource, UICollectionViewDelegate {
115 | func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
116 | return _movies?.count ?? 0
117 | }
118 |
119 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
120 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "MovieItem", for: indexPath) as! MovieItem
121 |
122 | cell.movie = _movies?[indexPath.row]
123 |
124 | return cell
125 | }
126 |
127 | func scrollViewDidScroll(_ scrollView: UIScrollView) {
128 | let bottomEdge = scrollView.contentOffset.y + scrollView.frame.size.height;
129 |
130 | if (bottomEdge + 200 >= scrollView.contentSize.height && scrollView.contentOffset.y > 0 && !_isRefreshing) {
131 | _viewModel.inputs.loadMore(request: KEYWORD)
132 | }
133 | }
134 | }
135 |
--------------------------------------------------------------------------------
/shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
2 | import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
3 |
4 | val buildToolsVersion: String by project
5 | val kotlinVersion: String by extra
6 | val reactiveVersion: String by extra
7 | val reactiveNmtcVersion: String by extra
8 | val kotlinSerializationVersion: String by extra
9 | val ktorVersion: String by extra
10 | val coroutinesVersion: String by extra
11 | val kodeinVersion: String by extra
12 |
13 | plugins {
14 | id("com.android.library")
15 | kotlin("multiplatform")
16 | kotlin("plugin.serialization") version "1.5.0" //org.jetbrains.kotlin.config.KotlinCompilerVersion.VERSION
17 | }
18 |
19 | android {
20 | compileSdkVersion(29)
21 | buildToolsVersion = buildToolsVersion
22 | defaultConfig {
23 | minSdkVersion(21)
24 | targetSdkVersion(29)
25 | }
26 | sourceSets {
27 | val main by getting {
28 | java.srcDirs("src/androidMain/kotlin")
29 | manifest.srcFile("src/androidMain/AndroidManifest.xml")
30 | res.srcDirs("src/androidMain/res")
31 | }
32 | }
33 | }
34 |
35 | kotlin {
36 | jvm()
37 | //jvm("android")
38 | android()
39 |
40 | //select iOS target platform depending on the Xcode environment variables
41 | val iOSTarget: (String, KotlinNativeTarget.() -> Unit) -> KotlinNativeTarget =
42 | if (System.getenv("SDK_NAME")?.startsWith("iphoneos") == true)
43 | ::iosArm64
44 | else
45 | ::iosX64
46 |
47 | iOSTarget("ios") {
48 | binaries {
49 | framework {
50 | baseName = "shared"
51 | }
52 | }
53 | }
54 |
55 | sourceSets {
56 | val commonMain by getting {
57 | dependencies {
58 | //implementation("org.jetbrains.kotlin:kotlin-stdlib")
59 | //implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-common:$coroutinesVersion")
60 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutinesVersion") {
61 | version {
62 | strictly(coroutinesVersion)
63 | }
64 | }
65 | implementation("com.badoo.reaktive:reaktive:$reactiveVersion")
66 | implementation("com.badoo.reaktive:coroutines-interop:$reactiveNmtcVersion")
67 | //implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-common:$kotlinSerializationVersion")
68 | //implementation("org.jetbrains.kotlinx:kotlinx-serialization-core:$kotlinSerializationVersion")
69 | implementation("io.ktor:ktor-client-core:$ktorVersion")
70 | implementation("io.ktor:ktor-client-serialization:$ktorVersion")
71 | implementation("io.ktor:ktor-client-logging:$ktorVersion")
72 | implementation("org.kodein.di:kodein-di:$kodeinVersion")
73 | }
74 | }
75 |
76 | val mobileMain by creating {
77 | dependsOn(commonMain)
78 | }
79 |
80 | val jvmMain by getting {
81 | dependencies {
82 | //api("org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion")
83 | //api("org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlinVersion")
84 | //api("org.jetbrains.kotlinx:kotlinx-serialization-runtime:$kotlinSerializationVersion")
85 | //api("io.ktor:ktor-client-serialization-jvm:$ktorVersion")
86 | //api("io.ktor:ktor-client-core-jvm:$ktorVersion")
87 | //api("io.ktor:ktor-client-logging-jvm:$ktorVersion")
88 | }
89 | }
90 |
91 | val androidMain by getting {
92 | dependsOn(mobileMain)
93 | dependsOn(jvmMain)
94 | dependencies {
95 | //implementation("io.ktor:ktor-client-android:$ktorVersion")
96 | }
97 | }
98 |
99 | val iosMain by getting {
100 | dependsOn(mobileMain)
101 | dependencies {
102 | //implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-native:$coroutinesVersion")
103 | //implementation("org.jetbrains.kotlinx:kotlinx-serialization-runtime-native:$kotlinSerializationVersion")
104 | implementation("io.ktor:ktor-client-ios:$ktorVersion")
105 | //implementation("io.ktor:ktor-client-serialization-native:$ktorVersion")
106 | //implementation("io.ktor:ktor-client-logging-native:$ktorVersion")
107 | }
108 | }
109 |
110 | all {
111 | languageSettings.useExperimentalAnnotation("kotlin.RequiresOptIn")
112 | }
113 | }
114 |
115 | }
116 |
117 | tasks.withType {
118 | kotlinOptions {
119 | jvmTarget = "1.8"
120 | }
121 | }
122 |
123 | val packForXcode by tasks.creating(Sync::class) {
124 | val targetDir = File(buildDir, "xcode-frameworks")
125 | //selecting the right configuration for the iOS framework depending on the Xcode environment variables
126 | val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
127 | val framework = kotlin.targets.getByName("ios").binaries.getFramework(mode)
128 |
129 | inputs.property("mode", mode)
130 | dependsOn(framework.linkTask)
131 |
132 | from({ framework.outputDirectory })
133 | into(targetDir)
134 |
135 | /// generate a helpful ./gradlew wrapper with embedded Java path
136 | doLast {
137 | val gradlew = File(targetDir, "gradlew")
138 | gradlew.writeText("#!/bin/bash\nexport 'JAVA_HOME=${System.getProperty("java.home")}'\ncd '${rootProject.rootDir}'\n./gradlew \$@\n")
139 | gradlew.setExecutable(true)
140 | }
141 | }
142 |
143 | tasks.getByName("build").dependsOn(packForXcode)
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/android/src/main/res/drawable/ic_launcher_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
10 |
15 |
20 |
25 |
30 |
35 |
40 |
45 |
50 |
55 |
60 |
65 |
70 |
75 |
80 |
85 |
90 |
95 |
100 |
105 |
110 |
115 |
120 |
125 |
130 |
135 |
140 |
145 |
150 |
155 |
160 |
165 |
170 |
171 |
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/xcuserdata/drjacky.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
21 |
22 |
23 |
25 |
37 |
38 |
39 |
41 |
53 |
54 |
55 |
57 |
69 |
70 |
71 |
73 |
85 |
86 |
100 |
101 |
115 |
116 |
117 |
118 |
119 |
121 |
133 |
134 |
135 |
137 |
149 |
150 |
151 |
153 |
165 |
166 |
167 |
169 |
181 |
182 |
183 |
185 |
197 |
198 |
212 |
213 |
227 |
228 |
229 |
230 |
231 |
233 |
245 |
246 |
247 |
249 |
261 |
262 |
263 |
265 |
277 |
278 |
279 |
281 |
293 |
294 |
295 |
297 |
309 |
310 |
311 |
313 |
325 |
326 |
327 |
329 |
341 |
342 |
343 |
345 |
357 |
358 |
359 |
361 |
373 |
374 |
375 |
377 |
389 |
390 |
391 |
393 |
405 |
406 |
407 |
409 |
421 |
422 |
423 |
425 |
437 |
438 |
439 |
441 |
453 |
454 |
455 |
457 |
469 |
470 |
471 |
473 |
485 |
486 |
487 |
489 |
501 |
502 |
503 |
505 |
517 |
518 |
519 |
521 |
533 |
534 |
535 |
537 |
549 |
550 |
551 |
553 |
565 |
566 |
567 |
568 |
569 |
--------------------------------------------------------------------------------
/ios/ios.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 52;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 371C2C6B24898F6500BC04CB /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371C2C6A24898F6500BC04CB /* AppDelegate.swift */; };
11 | 371C2C6D24898F6500BC04CB /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371C2C6C24898F6500BC04CB /* SceneDelegate.swift */; };
12 | 371C2C6F24898F6500BC04CB /* ViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371C2C6E24898F6500BC04CB /* ViewController.swift */; };
13 | 371C2C7224898F6500BC04CB /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 371C2C7024898F6500BC04CB /* Main.storyboard */; };
14 | 371C2C7424898F6700BC04CB /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 371C2C7324898F6700BC04CB /* Assets.xcassets */; };
15 | 371C2C7724898F6700BC04CB /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 371C2C7524898F6700BC04CB /* LaunchScreen.storyboard */; };
16 | 371C2C8224898F6700BC04CB /* iosTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371C2C8124898F6700BC04CB /* iosTests.swift */; };
17 | 371C2C8D24898F6700BC04CB /* iosUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371C2C8C24898F6700BC04CB /* iosUITests.swift */; };
18 | 371C2C9C2489942D00BC04CB /* shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 371C2C9B2489942D00BC04CB /* shared.framework */; };
19 | 371C2C9D2489942D00BC04CB /* shared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 371C2C9B2489942D00BC04CB /* shared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
20 | 37EC695224CED34C005AFB10 /* MovieItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37EC695124CED34C005AFB10 /* MovieItem.swift */; };
21 | 37EC695524CEF684005AFB10 /* Nuke in Frameworks */ = {isa = PBXBuildFile; productRef = 37EC695424CEF684005AFB10 /* Nuke */; };
22 | /* End PBXBuildFile section */
23 |
24 | /* Begin PBXContainerItemProxy section */
25 | 371C2C7E24898F6700BC04CB /* PBXContainerItemProxy */ = {
26 | isa = PBXContainerItemProxy;
27 | containerPortal = 371C2C5F24898F6500BC04CB /* Project object */;
28 | proxyType = 1;
29 | remoteGlobalIDString = 371C2C6624898F6500BC04CB;
30 | remoteInfo = ios;
31 | };
32 | 371C2C8924898F6700BC04CB /* PBXContainerItemProxy */ = {
33 | isa = PBXContainerItemProxy;
34 | containerPortal = 371C2C5F24898F6500BC04CB /* Project object */;
35 | proxyType = 1;
36 | remoteGlobalIDString = 371C2C6624898F6500BC04CB;
37 | remoteInfo = ios;
38 | };
39 | /* End PBXContainerItemProxy section */
40 |
41 | /* Begin PBXCopyFilesBuildPhase section */
42 | 371C2C9E2489942D00BC04CB /* Embed Frameworks */ = {
43 | isa = PBXCopyFilesBuildPhase;
44 | buildActionMask = 2147483647;
45 | dstPath = "";
46 | dstSubfolderSpec = 10;
47 | files = (
48 | 371C2C9D2489942D00BC04CB /* shared.framework in Embed Frameworks */,
49 | );
50 | name = "Embed Frameworks";
51 | runOnlyForDeploymentPostprocessing = 0;
52 | };
53 | /* End PBXCopyFilesBuildPhase section */
54 |
55 | /* Begin PBXFileReference section */
56 | 371C2C6724898F6500BC04CB /* ios.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ios.app; sourceTree = BUILT_PRODUCTS_DIR; };
57 | 371C2C6A24898F6500BC04CB /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
58 | 371C2C6C24898F6500BC04CB /* SceneDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SceneDelegate.swift; sourceTree = ""; };
59 | 371C2C6E24898F6500BC04CB /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; };
60 | 371C2C7124898F6500BC04CB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; };
61 | 371C2C7324898F6700BC04CB /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
62 | 371C2C7624898F6700BC04CB /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
63 | 371C2C7824898F6700BC04CB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
64 | 371C2C7D24898F6700BC04CB /* iosTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iosTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
65 | 371C2C8124898F6700BC04CB /* iosTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosTests.swift; sourceTree = ""; };
66 | 371C2C8324898F6700BC04CB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
67 | 371C2C8824898F6700BC04CB /* iosUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iosUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
68 | 371C2C8C24898F6700BC04CB /* iosUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosUITests.swift; sourceTree = ""; };
69 | 371C2C8E24898F6700BC04CB /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
70 | 371C2C9B2489942D00BC04CB /* shared.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = shared.framework; path = "../shared/build/xcode-frameworks/shared.framework"; sourceTree = ""; };
71 | 37EC695124CED34C005AFB10 /* MovieItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovieItem.swift; sourceTree = ""; };
72 | /* End PBXFileReference section */
73 |
74 | /* Begin PBXFrameworksBuildPhase section */
75 | 371C2C6424898F6500BC04CB /* Frameworks */ = {
76 | isa = PBXFrameworksBuildPhase;
77 | buildActionMask = 2147483647;
78 | files = (
79 | 371C2C9C2489942D00BC04CB /* shared.framework in Frameworks */,
80 | 37EC695524CEF684005AFB10 /* Nuke in Frameworks */,
81 | );
82 | runOnlyForDeploymentPostprocessing = 0;
83 | };
84 | 371C2C7A24898F6700BC04CB /* Frameworks */ = {
85 | isa = PBXFrameworksBuildPhase;
86 | buildActionMask = 2147483647;
87 | files = (
88 | );
89 | runOnlyForDeploymentPostprocessing = 0;
90 | };
91 | 371C2C8524898F6700BC04CB /* Frameworks */ = {
92 | isa = PBXFrameworksBuildPhase;
93 | buildActionMask = 2147483647;
94 | files = (
95 | );
96 | runOnlyForDeploymentPostprocessing = 0;
97 | };
98 | /* End PBXFrameworksBuildPhase section */
99 |
100 | /* Begin PBXGroup section */
101 | 371C2C5E24898F6500BC04CB = {
102 | isa = PBXGroup;
103 | children = (
104 | 371C2C6924898F6500BC04CB /* ios */,
105 | 371C2C8024898F6700BC04CB /* iosTests */,
106 | 371C2C8B24898F6700BC04CB /* iosUITests */,
107 | 371C2C6824898F6500BC04CB /* Products */,
108 | 371C2C9A2489942D00BC04CB /* Frameworks */,
109 | );
110 | sourceTree = "";
111 | };
112 | 371C2C6824898F6500BC04CB /* Products */ = {
113 | isa = PBXGroup;
114 | children = (
115 | 371C2C6724898F6500BC04CB /* ios.app */,
116 | 371C2C7D24898F6700BC04CB /* iosTests.xctest */,
117 | 371C2C8824898F6700BC04CB /* iosUITests.xctest */,
118 | );
119 | name = Products;
120 | sourceTree = "";
121 | };
122 | 371C2C6924898F6500BC04CB /* ios */ = {
123 | isa = PBXGroup;
124 | children = (
125 | 371C2C6A24898F6500BC04CB /* AppDelegate.swift */,
126 | 371C2C6C24898F6500BC04CB /* SceneDelegate.swift */,
127 | 371C2C6E24898F6500BC04CB /* ViewController.swift */,
128 | 371C2C7024898F6500BC04CB /* Main.storyboard */,
129 | 371C2C7324898F6700BC04CB /* Assets.xcassets */,
130 | 371C2C7524898F6700BC04CB /* LaunchScreen.storyboard */,
131 | 371C2C7824898F6700BC04CB /* Info.plist */,
132 | 37EC695124CED34C005AFB10 /* MovieItem.swift */,
133 | );
134 | path = ios;
135 | sourceTree = "";
136 | };
137 | 371C2C8024898F6700BC04CB /* iosTests */ = {
138 | isa = PBXGroup;
139 | children = (
140 | 371C2C8124898F6700BC04CB /* iosTests.swift */,
141 | 371C2C8324898F6700BC04CB /* Info.plist */,
142 | );
143 | path = iosTests;
144 | sourceTree = "";
145 | };
146 | 371C2C8B24898F6700BC04CB /* iosUITests */ = {
147 | isa = PBXGroup;
148 | children = (
149 | 371C2C8C24898F6700BC04CB /* iosUITests.swift */,
150 | 371C2C8E24898F6700BC04CB /* Info.plist */,
151 | );
152 | path = iosUITests;
153 | sourceTree = "";
154 | };
155 | 371C2C9A2489942D00BC04CB /* Frameworks */ = {
156 | isa = PBXGroup;
157 | children = (
158 | 371C2C9B2489942D00BC04CB /* shared.framework */,
159 | );
160 | name = Frameworks;
161 | sourceTree = "";
162 | };
163 | /* End PBXGroup section */
164 |
165 | /* Begin PBXNativeTarget section */
166 | 371C2C6624898F6500BC04CB /* ios */ = {
167 | isa = PBXNativeTarget;
168 | buildConfigurationList = 371C2C9124898F6700BC04CB /* Build configuration list for PBXNativeTarget "ios" */;
169 | buildPhases = (
170 | 371C2C9F2489954A00BC04CB /* ShellScript */,
171 | 371C2C6324898F6500BC04CB /* Sources */,
172 | 371C2C6424898F6500BC04CB /* Frameworks */,
173 | 371C2C6524898F6500BC04CB /* Resources */,
174 | 371C2C9E2489942D00BC04CB /* Embed Frameworks */,
175 | );
176 | buildRules = (
177 | );
178 | dependencies = (
179 | );
180 | name = ios;
181 | packageProductDependencies = (
182 | 37EC695424CEF684005AFB10 /* Nuke */,
183 | );
184 | productName = ios;
185 | productReference = 371C2C6724898F6500BC04CB /* ios.app */;
186 | productType = "com.apple.product-type.application";
187 | };
188 | 371C2C7C24898F6700BC04CB /* iosTests */ = {
189 | isa = PBXNativeTarget;
190 | buildConfigurationList = 371C2C9424898F6700BC04CB /* Build configuration list for PBXNativeTarget "iosTests" */;
191 | buildPhases = (
192 | 371C2C7924898F6700BC04CB /* Sources */,
193 | 371C2C7A24898F6700BC04CB /* Frameworks */,
194 | 371C2C7B24898F6700BC04CB /* Resources */,
195 | );
196 | buildRules = (
197 | );
198 | dependencies = (
199 | 371C2C7F24898F6700BC04CB /* PBXTargetDependency */,
200 | );
201 | name = iosTests;
202 | productName = iosTests;
203 | productReference = 371C2C7D24898F6700BC04CB /* iosTests.xctest */;
204 | productType = "com.apple.product-type.bundle.unit-test";
205 | };
206 | 371C2C8724898F6700BC04CB /* iosUITests */ = {
207 | isa = PBXNativeTarget;
208 | buildConfigurationList = 371C2C9724898F6700BC04CB /* Build configuration list for PBXNativeTarget "iosUITests" */;
209 | buildPhases = (
210 | 371C2C8424898F6700BC04CB /* Sources */,
211 | 371C2C8524898F6700BC04CB /* Frameworks */,
212 | 371C2C8624898F6700BC04CB /* Resources */,
213 | );
214 | buildRules = (
215 | );
216 | dependencies = (
217 | 371C2C8A24898F6700BC04CB /* PBXTargetDependency */,
218 | );
219 | name = iosUITests;
220 | productName = iosUITests;
221 | productReference = 371C2C8824898F6700BC04CB /* iosUITests.xctest */;
222 | productType = "com.apple.product-type.bundle.ui-testing";
223 | };
224 | /* End PBXNativeTarget section */
225 |
226 | /* Begin PBXProject section */
227 | 371C2C5F24898F6500BC04CB /* Project object */ = {
228 | isa = PBXProject;
229 | attributes = {
230 | LastSwiftUpdateCheck = 1150;
231 | LastUpgradeCheck = 1150;
232 | ORGANIZATIONNAME = "Hossein Abbasi";
233 | TargetAttributes = {
234 | 371C2C6624898F6500BC04CB = {
235 | CreatedOnToolsVersion = 11.5;
236 | };
237 | 371C2C7C24898F6700BC04CB = {
238 | CreatedOnToolsVersion = 11.5;
239 | TestTargetID = 371C2C6624898F6500BC04CB;
240 | };
241 | 371C2C8724898F6700BC04CB = {
242 | CreatedOnToolsVersion = 11.5;
243 | TestTargetID = 371C2C6624898F6500BC04CB;
244 | };
245 | };
246 | };
247 | buildConfigurationList = 371C2C6224898F6500BC04CB /* Build configuration list for PBXProject "ios" */;
248 | compatibilityVersion = "Xcode 9.3";
249 | developmentRegion = en;
250 | hasScannedForEncodings = 0;
251 | knownRegions = (
252 | en,
253 | Base,
254 | );
255 | mainGroup = 371C2C5E24898F6500BC04CB;
256 | packageReferences = (
257 | 37EC695324CEF684005AFB10 /* XCRemoteSwiftPackageReference "Nuke" */,
258 | );
259 | productRefGroup = 371C2C6824898F6500BC04CB /* Products */;
260 | projectDirPath = "";
261 | projectRoot = "";
262 | targets = (
263 | 371C2C6624898F6500BC04CB /* ios */,
264 | 371C2C7C24898F6700BC04CB /* iosTests */,
265 | 371C2C8724898F6700BC04CB /* iosUITests */,
266 | );
267 | };
268 | /* End PBXProject section */
269 |
270 | /* Begin PBXResourcesBuildPhase section */
271 | 371C2C6524898F6500BC04CB /* Resources */ = {
272 | isa = PBXResourcesBuildPhase;
273 | buildActionMask = 2147483647;
274 | files = (
275 | 371C2C7724898F6700BC04CB /* LaunchScreen.storyboard in Resources */,
276 | 371C2C7424898F6700BC04CB /* Assets.xcassets in Resources */,
277 | 371C2C7224898F6500BC04CB /* Main.storyboard in Resources */,
278 | );
279 | runOnlyForDeploymentPostprocessing = 0;
280 | };
281 | 371C2C7B24898F6700BC04CB /* Resources */ = {
282 | isa = PBXResourcesBuildPhase;
283 | buildActionMask = 2147483647;
284 | files = (
285 | );
286 | runOnlyForDeploymentPostprocessing = 0;
287 | };
288 | 371C2C8624898F6700BC04CB /* Resources */ = {
289 | isa = PBXResourcesBuildPhase;
290 | buildActionMask = 2147483647;
291 | files = (
292 | );
293 | runOnlyForDeploymentPostprocessing = 0;
294 | };
295 | /* End PBXResourcesBuildPhase section */
296 |
297 | /* Begin PBXShellScriptBuildPhase section */
298 | 371C2C9F2489954A00BC04CB /* ShellScript */ = {
299 | isa = PBXShellScriptBuildPhase;
300 | buildActionMask = 2147483647;
301 | files = (
302 | );
303 | inputFileListPaths = (
304 | );
305 | inputPaths = (
306 | );
307 | outputFileListPaths = (
308 | );
309 | outputPaths = (
310 | );
311 | runOnlyForDeploymentPostprocessing = 0;
312 | shellPath = /bin/sh;
313 | shellScript = "# Type a script or drag a script file from your workspace to insert its path.\ncd \"$SRCROOT/../shared/build/xcode-frameworks\"\n./gradlew :shared:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}\n";
314 | };
315 | /* End PBXShellScriptBuildPhase section */
316 |
317 | /* Begin PBXSourcesBuildPhase section */
318 | 371C2C6324898F6500BC04CB /* Sources */ = {
319 | isa = PBXSourcesBuildPhase;
320 | buildActionMask = 2147483647;
321 | files = (
322 | 371C2C6F24898F6500BC04CB /* ViewController.swift in Sources */,
323 | 371C2C6B24898F6500BC04CB /* AppDelegate.swift in Sources */,
324 | 37EC695224CED34C005AFB10 /* MovieItem.swift in Sources */,
325 | 371C2C6D24898F6500BC04CB /* SceneDelegate.swift in Sources */,
326 | );
327 | runOnlyForDeploymentPostprocessing = 0;
328 | };
329 | 371C2C7924898F6700BC04CB /* Sources */ = {
330 | isa = PBXSourcesBuildPhase;
331 | buildActionMask = 2147483647;
332 | files = (
333 | 371C2C8224898F6700BC04CB /* iosTests.swift in Sources */,
334 | );
335 | runOnlyForDeploymentPostprocessing = 0;
336 | };
337 | 371C2C8424898F6700BC04CB /* Sources */ = {
338 | isa = PBXSourcesBuildPhase;
339 | buildActionMask = 2147483647;
340 | files = (
341 | 371C2C8D24898F6700BC04CB /* iosUITests.swift in Sources */,
342 | );
343 | runOnlyForDeploymentPostprocessing = 0;
344 | };
345 | /* End PBXSourcesBuildPhase section */
346 |
347 | /* Begin PBXTargetDependency section */
348 | 371C2C7F24898F6700BC04CB /* PBXTargetDependency */ = {
349 | isa = PBXTargetDependency;
350 | target = 371C2C6624898F6500BC04CB /* ios */;
351 | targetProxy = 371C2C7E24898F6700BC04CB /* PBXContainerItemProxy */;
352 | };
353 | 371C2C8A24898F6700BC04CB /* PBXTargetDependency */ = {
354 | isa = PBXTargetDependency;
355 | target = 371C2C6624898F6500BC04CB /* ios */;
356 | targetProxy = 371C2C8924898F6700BC04CB /* PBXContainerItemProxy */;
357 | };
358 | /* End PBXTargetDependency section */
359 |
360 | /* Begin PBXVariantGroup section */
361 | 371C2C7024898F6500BC04CB /* Main.storyboard */ = {
362 | isa = PBXVariantGroup;
363 | children = (
364 | 371C2C7124898F6500BC04CB /* Base */,
365 | );
366 | name = Main.storyboard;
367 | sourceTree = "";
368 | };
369 | 371C2C7524898F6700BC04CB /* LaunchScreen.storyboard */ = {
370 | isa = PBXVariantGroup;
371 | children = (
372 | 371C2C7624898F6700BC04CB /* Base */,
373 | );
374 | name = LaunchScreen.storyboard;
375 | sourceTree = "";
376 | };
377 | /* End PBXVariantGroup section */
378 |
379 | /* Begin XCBuildConfiguration section */
380 | 371C2C8F24898F6700BC04CB /* Debug */ = {
381 | isa = XCBuildConfiguration;
382 | buildSettings = {
383 | ALWAYS_SEARCH_USER_PATHS = NO;
384 | CLANG_ANALYZER_NONNULL = YES;
385 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
386 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
387 | CLANG_CXX_LIBRARY = "libc++";
388 | CLANG_ENABLE_MODULES = YES;
389 | CLANG_ENABLE_OBJC_ARC = YES;
390 | CLANG_ENABLE_OBJC_WEAK = YES;
391 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
392 | CLANG_WARN_BOOL_CONVERSION = YES;
393 | CLANG_WARN_COMMA = YES;
394 | CLANG_WARN_CONSTANT_CONVERSION = YES;
395 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
396 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
397 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
398 | CLANG_WARN_EMPTY_BODY = YES;
399 | CLANG_WARN_ENUM_CONVERSION = YES;
400 | CLANG_WARN_INFINITE_RECURSION = YES;
401 | CLANG_WARN_INT_CONVERSION = YES;
402 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
403 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
404 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
405 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
406 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
407 | CLANG_WARN_STRICT_PROTOTYPES = YES;
408 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
409 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
410 | CLANG_WARN_UNREACHABLE_CODE = YES;
411 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
412 | COPY_PHASE_STRIP = NO;
413 | DEBUG_INFORMATION_FORMAT = dwarf;
414 | ENABLE_STRICT_OBJC_MSGSEND = YES;
415 | ENABLE_TESTABILITY = YES;
416 | GCC_C_LANGUAGE_STANDARD = gnu11;
417 | GCC_DYNAMIC_NO_PIC = NO;
418 | GCC_NO_COMMON_BLOCKS = YES;
419 | GCC_OPTIMIZATION_LEVEL = 0;
420 | GCC_PREPROCESSOR_DEFINITIONS = (
421 | "DEBUG=1",
422 | "$(inherited)",
423 | );
424 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
425 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
426 | GCC_WARN_UNDECLARED_SELECTOR = YES;
427 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
428 | GCC_WARN_UNUSED_FUNCTION = YES;
429 | GCC_WARN_UNUSED_VARIABLE = YES;
430 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
431 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
432 | MTL_FAST_MATH = YES;
433 | ONLY_ACTIVE_ARCH = YES;
434 | SDKROOT = iphoneos;
435 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
436 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
437 | };
438 | name = Debug;
439 | };
440 | 371C2C9024898F6700BC04CB /* Release */ = {
441 | isa = XCBuildConfiguration;
442 | buildSettings = {
443 | ALWAYS_SEARCH_USER_PATHS = NO;
444 | CLANG_ANALYZER_NONNULL = YES;
445 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
446 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
447 | CLANG_CXX_LIBRARY = "libc++";
448 | CLANG_ENABLE_MODULES = YES;
449 | CLANG_ENABLE_OBJC_ARC = YES;
450 | CLANG_ENABLE_OBJC_WEAK = YES;
451 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
452 | CLANG_WARN_BOOL_CONVERSION = YES;
453 | CLANG_WARN_COMMA = YES;
454 | CLANG_WARN_CONSTANT_CONVERSION = YES;
455 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
456 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
457 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
458 | CLANG_WARN_EMPTY_BODY = YES;
459 | CLANG_WARN_ENUM_CONVERSION = YES;
460 | CLANG_WARN_INFINITE_RECURSION = YES;
461 | CLANG_WARN_INT_CONVERSION = YES;
462 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
463 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
464 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
465 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
466 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
467 | CLANG_WARN_STRICT_PROTOTYPES = YES;
468 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
469 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
470 | CLANG_WARN_UNREACHABLE_CODE = YES;
471 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
472 | COPY_PHASE_STRIP = NO;
473 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
474 | ENABLE_NS_ASSERTIONS = NO;
475 | ENABLE_STRICT_OBJC_MSGSEND = YES;
476 | GCC_C_LANGUAGE_STANDARD = gnu11;
477 | GCC_NO_COMMON_BLOCKS = YES;
478 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
479 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
480 | GCC_WARN_UNDECLARED_SELECTOR = YES;
481 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
482 | GCC_WARN_UNUSED_FUNCTION = YES;
483 | GCC_WARN_UNUSED_VARIABLE = YES;
484 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
485 | MTL_ENABLE_DEBUG_INFO = NO;
486 | MTL_FAST_MATH = YES;
487 | SDKROOT = iphoneos;
488 | SWIFT_COMPILATION_MODE = wholemodule;
489 | SWIFT_OPTIMIZATION_LEVEL = "-O";
490 | VALIDATE_PRODUCT = YES;
491 | };
492 | name = Release;
493 | };
494 | 371C2C9224898F6700BC04CB /* Debug */ = {
495 | isa = XCBuildConfiguration;
496 | buildSettings = {
497 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
498 | CODE_SIGN_STYLE = Automatic;
499 | CURRENT_PROJECT_VERSION = 3;
500 | DEVELOPMENT_TEAM = G8F77R494D;
501 | "FRAMEWORK_SEARCH_PATHS[arch=*]" = "$(SRCROOT)/../shared/build/xcode-frameworks";
502 | INFOPLIST_FILE = ios/Info.plist;
503 | LD_RUNPATH_SEARCH_PATHS = (
504 | "$(inherited)",
505 | "@executable_path/Frameworks",
506 | );
507 | MARKETING_VERSION = 1.2.0;
508 | PRODUCT_BUNDLE_IDENTIFIER = app.web.drjackycv.ios.debug;
509 | PRODUCT_NAME = "$(TARGET_NAME)";
510 | SWIFT_VERSION = 5.0;
511 | TARGETED_DEVICE_FAMILY = "1,2";
512 | };
513 | name = Debug;
514 | };
515 | 371C2C9324898F6700BC04CB /* Release */ = {
516 | isa = XCBuildConfiguration;
517 | buildSettings = {
518 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
519 | CODE_SIGN_STYLE = Automatic;
520 | CURRENT_PROJECT_VERSION = 3;
521 | DEVELOPMENT_TEAM = G8F77R494D;
522 | "FRAMEWORK_SEARCH_PATHS[arch=*]" = "$(SRCROOT)/../shared/build/xcode-frameworks";
523 | INFOPLIST_FILE = ios/Info.plist;
524 | LD_RUNPATH_SEARCH_PATHS = (
525 | "$(inherited)",
526 | "@executable_path/Frameworks",
527 | );
528 | MARKETING_VERSION = 1.2.0;
529 | PRODUCT_BUNDLE_IDENTIFIER = app.web.drjackycv.ios.debug;
530 | PRODUCT_NAME = "$(TARGET_NAME)";
531 | SWIFT_VERSION = 5.0;
532 | TARGETED_DEVICE_FAMILY = "1,2";
533 | };
534 | name = Release;
535 | };
536 | 371C2C9524898F6700BC04CB /* Debug */ = {
537 | isa = XCBuildConfiguration;
538 | buildSettings = {
539 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
540 | BUNDLE_LOADER = "$(TEST_HOST)";
541 | CODE_SIGN_STYLE = Automatic;
542 | INFOPLIST_FILE = iosTests/Info.plist;
543 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
544 | LD_RUNPATH_SEARCH_PATHS = (
545 | "$(inherited)",
546 | "@executable_path/Frameworks",
547 | "@loader_path/Frameworks",
548 | );
549 | PRODUCT_BUNDLE_IDENTIFIER = app.web.drjackycv.iosTests;
550 | PRODUCT_NAME = "$(TARGET_NAME)";
551 | SWIFT_VERSION = 5.0;
552 | TARGETED_DEVICE_FAMILY = "1,2";
553 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ios.app/ios";
554 | };
555 | name = Debug;
556 | };
557 | 371C2C9624898F6700BC04CB /* Release */ = {
558 | isa = XCBuildConfiguration;
559 | buildSettings = {
560 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
561 | BUNDLE_LOADER = "$(TEST_HOST)";
562 | CODE_SIGN_STYLE = Automatic;
563 | INFOPLIST_FILE = iosTests/Info.plist;
564 | IPHONEOS_DEPLOYMENT_TARGET = 13.5;
565 | LD_RUNPATH_SEARCH_PATHS = (
566 | "$(inherited)",
567 | "@executable_path/Frameworks",
568 | "@loader_path/Frameworks",
569 | );
570 | PRODUCT_BUNDLE_IDENTIFIER = app.web.drjackycv.iosTests;
571 | PRODUCT_NAME = "$(TARGET_NAME)";
572 | SWIFT_VERSION = 5.0;
573 | TARGETED_DEVICE_FAMILY = "1,2";
574 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ios.app/ios";
575 | };
576 | name = Release;
577 | };
578 | 371C2C9824898F6700BC04CB /* Debug */ = {
579 | isa = XCBuildConfiguration;
580 | buildSettings = {
581 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
582 | CODE_SIGN_STYLE = Automatic;
583 | INFOPLIST_FILE = iosUITests/Info.plist;
584 | LD_RUNPATH_SEARCH_PATHS = (
585 | "$(inherited)",
586 | "@executable_path/Frameworks",
587 | "@loader_path/Frameworks",
588 | );
589 | PRODUCT_BUNDLE_IDENTIFIER = app.web.drjackycv.iosUITests;
590 | PRODUCT_NAME = "$(TARGET_NAME)";
591 | SWIFT_VERSION = 5.0;
592 | TARGETED_DEVICE_FAMILY = "1,2";
593 | TEST_TARGET_NAME = ios;
594 | };
595 | name = Debug;
596 | };
597 | 371C2C9924898F6700BC04CB /* Release */ = {
598 | isa = XCBuildConfiguration;
599 | buildSettings = {
600 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
601 | CODE_SIGN_STYLE = Automatic;
602 | INFOPLIST_FILE = iosUITests/Info.plist;
603 | LD_RUNPATH_SEARCH_PATHS = (
604 | "$(inherited)",
605 | "@executable_path/Frameworks",
606 | "@loader_path/Frameworks",
607 | );
608 | PRODUCT_BUNDLE_IDENTIFIER = app.web.drjackycv.iosUITests;
609 | PRODUCT_NAME = "$(TARGET_NAME)";
610 | SWIFT_VERSION = 5.0;
611 | TARGETED_DEVICE_FAMILY = "1,2";
612 | TEST_TARGET_NAME = ios;
613 | };
614 | name = Release;
615 | };
616 | /* End XCBuildConfiguration section */
617 |
618 | /* Begin XCConfigurationList section */
619 | 371C2C6224898F6500BC04CB /* Build configuration list for PBXProject "ios" */ = {
620 | isa = XCConfigurationList;
621 | buildConfigurations = (
622 | 371C2C8F24898F6700BC04CB /* Debug */,
623 | 371C2C9024898F6700BC04CB /* Release */,
624 | );
625 | defaultConfigurationIsVisible = 0;
626 | defaultConfigurationName = Release;
627 | };
628 | 371C2C9124898F6700BC04CB /* Build configuration list for PBXNativeTarget "ios" */ = {
629 | isa = XCConfigurationList;
630 | buildConfigurations = (
631 | 371C2C9224898F6700BC04CB /* Debug */,
632 | 371C2C9324898F6700BC04CB /* Release */,
633 | );
634 | defaultConfigurationIsVisible = 0;
635 | defaultConfigurationName = Release;
636 | };
637 | 371C2C9424898F6700BC04CB /* Build configuration list for PBXNativeTarget "iosTests" */ = {
638 | isa = XCConfigurationList;
639 | buildConfigurations = (
640 | 371C2C9524898F6700BC04CB /* Debug */,
641 | 371C2C9624898F6700BC04CB /* Release */,
642 | );
643 | defaultConfigurationIsVisible = 0;
644 | defaultConfigurationName = Release;
645 | };
646 | 371C2C9724898F6700BC04CB /* Build configuration list for PBXNativeTarget "iosUITests" */ = {
647 | isa = XCConfigurationList;
648 | buildConfigurations = (
649 | 371C2C9824898F6700BC04CB /* Debug */,
650 | 371C2C9924898F6700BC04CB /* Release */,
651 | );
652 | defaultConfigurationIsVisible = 0;
653 | defaultConfigurationName = Release;
654 | };
655 | /* End XCConfigurationList section */
656 |
657 | /* Begin XCRemoteSwiftPackageReference section */
658 | 37EC695324CEF684005AFB10 /* XCRemoteSwiftPackageReference "Nuke" */ = {
659 | isa = XCRemoteSwiftPackageReference;
660 | repositoryURL = "https://github.com/kean/Nuke.git";
661 | requirement = {
662 | branch = master;
663 | kind = branch;
664 | };
665 | };
666 | /* End XCRemoteSwiftPackageReference section */
667 |
668 | /* Begin XCSwiftPackageProductDependency section */
669 | 37EC695424CEF684005AFB10 /* Nuke */ = {
670 | isa = XCSwiftPackageProductDependency;
671 | package = 37EC695324CEF684005AFB10 /* XCRemoteSwiftPackageReference "Nuke" */;
672 | productName = Nuke;
673 | };
674 | /* End XCSwiftPackageProductDependency section */
675 | };
676 | rootObject = 371C2C5F24898F6500BC04CB /* Project object */;
677 | }
678 |
--------------------------------------------------------------------------------