├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── androidApp
├── build.gradle.kts
└── src
│ └── main
│ ├── AndroidManifest.xml
│ ├── java
│ └── com
│ │ └── simform
│ │ └── kmmsample
│ │ └── androidApp
│ │ ├── base
│ │ └── Resource.kt
│ │ ├── ui
│ │ ├── MainActivity.kt
│ │ ├── MainViewModel.kt
│ │ └── MovieListAdapter.kt
│ │ └── utils
│ │ ├── ImageUtils.kt
│ │ ├── MutableLiveDataExtension.kt
│ │ └── binding.kt
│ └── res
│ ├── drawable
│ ├── placeholder.jpg
│ └── tmdb.xml
│ ├── layout
│ ├── activity_main.xml
│ └── movie_list.xml
│ └── values
│ ├── colors.xml
│ ├── strings.xml
│ └── styles.xml
├── build.gradle.kts
├── buildSrc
├── build.gradle.kts
└── src
│ └── main
│ └── java
│ ├── App.kt
│ ├── Extensions.kt
│ ├── Libs.kt
│ ├── Plugins.kt
│ ├── SharedLibs.kt
│ ├── SharedPlugins.kt
│ ├── SharedVersions.kt
│ ├── Urls.kt
│ └── Versions.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── iosApp
├── Podfile
├── iosApp.xcodeproj
│ ├── project.pbxproj
│ └── project.xcworkspace
│ │ ├── contents.xcworkspacedata
│ │ └── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
├── iosApp
│ ├── AppDelegate.swift
│ ├── Assets.xcassets
│ │ ├── AppIcon.appiconset
│ │ │ └── Contents.json
│ │ ├── Contents.json
│ │ └── placeHolderImage.imageset
│ │ │ ├── Contents.json
│ │ │ └── placeholder.jpg
│ ├── Colors.xcassets
│ │ ├── Contents.json
│ │ ├── darkGrey.colorset
│ │ │ └── Contents.json
│ │ ├── mainColor.colorset
│ │ │ └── Contents.json
│ │ └── navigationBarColor.colorset
│ │ │ └── Contents.json
│ ├── Constants
│ │ └── Structures.swift
│ ├── Info.plist
│ ├── Preview Content
│ │ └── Preview Assets.xcassets
│ │ │ └── Contents.json
│ ├── Storyboards
│ │ ├── Base.lproj
│ │ │ └── LaunchScreen.storyboard
│ │ └── Main.storyboard
│ └── ViewControllers
│ │ ├── MainViewController.swift
│ │ ├── MainViewModel.swift
│ │ └── MoviesCell.swift
├── iosAppTests
│ ├── Info.plist
│ └── iosAppTests.swift
└── iosAppUITests
│ ├── Info.plist
│ └── iosAppUITests.swift
├── screenshots
├── KMM Plugin.png
├── Run Configurations.png
├── android.gif
└── iOS.gif
├── settings.gradle.kts
└── shared
├── build.gradle.kts
└── src
├── androidMain
├── AndroidManifest.xml
└── kotlin
│ └── com
│ └── simform
│ └── kmmsample
│ └── shared
│ └── HttpBaseClient.kt
├── commonMain
└── kotlin
│ └── com
│ └── simform
│ └── kmmsample
│ └── shared
│ ├── HttpBaseClient.kt
│ ├── datamodels
│ ├── base
│ │ ├── CustomException.kt
│ │ ├── Either.kt
│ │ └── ErrorResponse.kt
│ └── responsemodels
│ │ ├── MovieEnitity.kt
│ │ └── PopularMoviesResponse.kt
│ ├── remote
│ └── BaseApiClass.kt
│ └── utils
│ ├── ApiEndPoints.kt
│ └── SharedConstants.kt
└── iosMain
└── kotlin
└── com
└── simform
└── kmmsample
└── shared
└── HttpBaseClient.kt
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: bug
6 | assignees: ''
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. iOS or Android]
28 | - Version [e.g. 1.1]
29 |
30 | **Smartphone (please complete the following information):**
31 | - Device: [e.g. iPhone6]
32 | - OS: [e.g. iOS8.1]
33 | - Browser [e.g. stock browser, safari]
34 | - Version [e.g. 1.1]
35 |
36 | **Additional context**
37 | Add any other context about the problem here.
38 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Miscellaneous
2 | *.class
3 | *.log
4 | *.pyc
5 | *.swp
6 | .DS_Store
7 | .atom/
8 | .buildlog/
9 | .history
10 | .svn/
11 |
12 | # IntelliJ related
13 | *.iml
14 | *.ipr
15 | *.iws
16 | .idea/*
17 | **/.idea/*
18 | !.idea/fileTemplates/
19 |
20 | /build/
21 |
22 | # Additional
23 | Gemfile.lock
24 | android/*.jks
25 | android/jks/*.jks
26 | android/keystore.properties
27 | drafts/
28 | logs/
29 |
30 | #Android git ignore
31 | **/**/androidApp/build/
32 | local.properties
33 | .gradle
34 | build/
35 | **/**/androidApp/build
36 | **/**/androidApp/captures
37 | **/**/androidApp/.externalNativeBuild
38 | **/**/androidApp/report.xml
39 | settings/reports
40 | lint/intermediates/
41 | lint/generated/
42 | lint/outputs/
43 | lint/tmp/
44 |
45 |
46 | #Common gitignore
47 | shared/build
48 |
49 | #iOS git ignore
50 | *.perspectivev3
51 | !default.perspectivev3
52 | ## Other
53 | *.moved-aside
54 | *.xccheckout
55 | *.xcscmblueprint
56 | .DS_Store
57 | ## Obj-C/Swift specific
58 | **/**/iosApp/Pods
59 | **/**/iosApp/*.app.dSYM.zip
60 | **/**/iosApp/*.ipa
61 | **/**/iosApp/*.xcuserdata
62 | **/**/iosApp/*.xcworkspace/contents.xcworkspacedata
63 | **/**/iosApp/*DerivedData/
64 | **/**/iosApp/*.moved-aside
65 | **/**/iosApp/*.xccheckout
66 | **/**/iosApp/*.xcscmblueprint
67 | **/**/iosApp/.DS_Store
68 | **/**/iosApp/Podfile.lock
69 | **/**/iosApp/iosApp/LocalizeFile/R.generated.swift
70 | **/**/iosApp/**/xcuserdata/*.*
71 | **/**/iosApp/*.xcworkspace/contents.xcworkspacedata/
72 | **/**/iosApp/iosApp.xcworkspace/xcshareddata
73 |
74 | ## Playgrounds
75 | timeline.xctimeline
76 | playground.xcworkspace
77 | # Swift Package Manager
78 | #
79 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies.
80 | # Packages/
81 | # Package.pins
82 | # Package.resolved
83 | .build/
84 | # CocoaPods
85 | #
86 | # We recommend against adding the Pods directory to your .gitignore. However
87 | # you should judge for yourself, the pros and cons are mentioned at:
88 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
89 | #
90 | **/**/iosApp/Pods/
91 | /.DS_Store
92 | # Carthage
93 | #
94 | # Add this line if you want to avoid checking in source code from Carthage dependencies.
95 | # Carthage/Checkouts
96 | **/**/iosApp/Carthage/Build
97 | # fastlane
98 | #
99 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
100 | # screenshots whenever they are needed.
101 | # For more information about the recommended setup visit:
102 | # https://docs.fastlane.tools/best-practices/source-control/#source-control
103 | fastlane/report.xml
104 | fastlane/Preview.html
105 | fastlane/screenshots
106 | fastlane/test_output
107 | *.
--------------------------------------------------------------------------------
/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 mohammed.h@simformsolutions.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 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # Way to contribute
2 |
3 | 1. Fork the repo and create your branch from `master`.
4 | 2. Clone the project to your own machine. (Please have a look at [**Readme.md**](https://github.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/blob/master/README.md) to understand how to run this project on your machine)
5 | 3. Commit changes to your own branch
6 | 4. Make sure your code lints.
7 | 5. Push your work back up to your fork.
8 | 6. Issue that pull request!
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 Simform Solutions
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # KMM First Look
2 |
3 | ### What is Kotlin Multiplatform (KMP)
4 |
5 | * Kotlin Multiplatform allows you to share your business logic on multiple platforms like android, iOS and native desktop applications and as a result reducing the amount of time required for development.
6 |
7 | ### Now what is Kotlin Multiplatform Mobile (KMM) ?
8 |
9 | * Kotlin Multiplatform (KMP) and Kotlin Multiplatform Mobile (KMM) are not seperate technologies. You can think of Kotlin Multiplatform Mobile (KMM) as a subset of Kotlin Multiplatform (KMP) which primarily focuses on mobile features. Kotlin Multiplatform Mobile (KMM) uses the multiplatform capabilities of kotlin to share bussiness logic of iOS and Android apps.
10 |
11 | ## Why Kotlin Multiplatform Mobile (KMM) ?
12 |
13 | * Using KMM we can write our business logic once and reuse that for both the platforms android and iOS. We only need to write our platform specific code wherever it's necessary for example to implement our native UI. Does that mean we can have a single application which can run on both Android and iOS app using KMM ? No KMM is specifically design to share only business logic and use native UI to have exact look and feel as simple Android or iOS app. So we still need to create seperate applications for both the platforms.
14 |
15 | ### About this project.
16 | This is a sample KMM project which tries to shares as much as code possible between Android and iOS.
17 |
18 | Android App | iOS App
19 | :-------------------------:|:-------------------------:
20 |
|
21 |
22 | ## Pre-requisites
23 | * Android Studio – version 4.2 or higher.
24 | * Xcode – version 11.3 or higher.
25 | * This app displays a list of movies using API from TMDB so you must have a valid api key from TMDB. (If you don't have one you can get it here :- https://www.themoviedb.org/documentation/api)
26 |
27 | ### How to Run Android App :-
28 | * Open Android Studio
29 | * Add KMM Plugin from `Android Studio` -> `Preferences` -> `Plugins` -> `MarketPlace` -> `Search for Kotlin Multiplatform Mobile` -> `Install and Restart Android Studio`
30 |
31 |
32 | * Run app.
33 |
34 | ### How to Run iOS App :-
35 |
36 | * Pretty Simple KMM allows you to run and debug your iOS App from Android Studio itself. Just switch from `androidApp` to `iosApp` in your run configurations menu. (Remember :- This app contains `Pods` so we need to install that from Terminal first before running iOS app from Android Studio or xCode)
37 | * If you are an Android Developer who wants to explore iOS App or a newbie in iOS development and got confused about how to work with pods, we got you covered!
38 | You can find everything about pods here : :- https://cocoapods.org/
39 | *
40 | * Does that mean you can simply run iOS App without xCode or macOS machine ? No You still need a macOS , xCode (and Simulators) and command line tools installed on your machine to run your iOS App.
41 | * Also you can run this project with xCode.
42 | * Open `iosApp` folder from the project install `pods` with terminal open `.xcworkspace` and run your application.
43 |
44 | ## Find this example useful? :heart:
45 | Support it by joining __[stargazers](https://github.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/stargazers)__ for this repository. :star:
46 |
47 | ## 🤝 How to Contribute
48 |
49 | Whether you're helping us fix bugs, improve the docs, or a feature request, we'd love to have you! :muscle:
50 |
51 | Check out our [**Contributing Guide**](https://github.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/blob/master/CONTRIBUTING.md) for ideas on contributing.
52 |
53 | ## Bugs and Feedback
54 |
55 | For bugs, feature requests, and discussion please use [GitHub Issues](https://github.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/issues).
56 |
57 | ### LICENSE
58 | This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details
59 |
--------------------------------------------------------------------------------
/androidApp/build.gradle.kts:
--------------------------------------------------------------------------------
1 | plugins {
2 | id(Plugins.ANDROID_APPLICATION)
3 | kotlin(Plugins.ANDROID)
4 | id(Plugins.KOTLIN_KAPT)
5 | }
6 |
7 | dependencies {
8 | implementation(defaultFileTree())
9 |
10 | // Shared module
11 | implementation(project(Libs.shared))
12 |
13 | //Material Design
14 | implementation(Libs.MATERIAL)
15 |
16 | //SDP SSP
17 | implementation(Libs.SDP)
18 | implementation(Libs.SSP)
19 |
20 | // UI
21 | implementation(Libs.APPCOMPAT)
22 | implementation(Libs.CONSTRAINT_LAYOUT)
23 | implementation(Libs.RECYCLERVIEW)
24 |
25 | // View model
26 | implementation(Libs.VIEW_MODEL)
27 | implementation(Libs.VIEW_MODEL_KTX)
28 |
29 | // Coroutines
30 | implementation(Libs.COROUTINES_CORE)
31 | implementation(Libs.COROUTINES_ANDROID)
32 |
33 | // Glide
34 | implementation(Libs.GLIDE)
35 | }
36 |
37 | android {
38 | compileSdkVersion(Version.COMPILE_SDK)
39 | defaultConfig {
40 | applicationId = App.ID
41 | minSdkVersion(Version.MIN_SDK)
42 | targetSdkVersion(Version.TARGET_SDK)
43 | versionCode = App.Version.CODE
44 | versionName = App.Version.NAME
45 | }
46 | buildTypes {
47 | getByName("release") {
48 | isMinifyEnabled = false
49 | }
50 | }
51 |
52 | compileOptions {
53 | sourceCompatibility = JavaVersion.VERSION_1_8
54 | targetCompatibility = JavaVersion.VERSION_1_8
55 | }
56 |
57 | buildFeatures {
58 | dataBinding = true
59 | }
60 | }
--------------------------------------------------------------------------------
/androidApp/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
13 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/com/simform/kmmsample/androidApp/base/Resource.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.androidApp.base
2 |
3 | import com.simform.kmmsample.shared.datamodels.base.CustomException
4 |
5 | /**
6 | * This class is used to pass api status
7 | */
8 | class Resource(val status: Status, val responseData: T? = null, val throwable: CustomException? = null) {
9 | companion object {
10 | /**
11 | * This fun is used to return success data
12 | */
13 | fun success(data: T?): Resource {
14 | return Resource(Status.SUCCESS, data)
15 | }
16 |
17 | /**
18 | * This fun is used to return error data
19 | */
20 | fun error(exception: CustomException): Resource {
21 | return Resource(Status.ERROR, throwable = exception)
22 | }
23 |
24 | /**
25 | * This fun is used to return loading
26 | */
27 | fun loading(): Resource {
28 | return Resource(Status.LOADING)
29 | }
30 | }
31 |
32 | override fun toString(): String {
33 | return "Resource(status=$status, data=$responseData, throwable=$throwable)"
34 | }
35 | }
36 |
37 | /**
38 | * This is enum class returns the status of apis
39 | */
40 | enum class Status {
41 | SUCCESS,
42 | ERROR,
43 | LOADING
44 | }
--------------------------------------------------------------------------------
/androidApp/src/main/java/com/simform/kmmsample/androidApp/ui/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.androidApp.ui
2 |
3 | import android.app.AlertDialog
4 | import android.os.Bundle
5 | import androidx.appcompat.app.AppCompatActivity
6 | import androidx.databinding.DataBindingUtil
7 | import com.simform.kmmsample.androidApp.R
8 | import com.simform.kmmsample.androidApp.base.Status
9 | import com.simform.kmmsample.androidApp.databinding.ActivityMainBinding
10 | import com.simform.kmmsample.shared.datamodels.responsemodels.PopularMoviesResponse
11 |
12 | class MainActivity : AppCompatActivity() {
13 |
14 | private var viewModel = MainViewModel()
15 | private lateinit var binding: ActivityMainBinding
16 |
17 | override fun onCreate(savedInstanceState: Bundle?) {
18 | super.onCreate(savedInstanceState)
19 | setContentView(R.layout.activity_main)
20 | // SetUp View Binding
21 | setUpBinding()
22 | // Call Movies Api
23 | callApi()
24 |
25 | // Handle Api Response
26 | viewModel.popularMovieResponse.observe(this, { response ->
27 | when (response.status) {
28 | Status.LOADING -> {
29 | viewModel.progressBarVisibility.value = true
30 | }
31 | Status.SUCCESS -> {
32 | viewModel.progressBarVisibility.postValue(false)
33 | response.responseData?.let { movies -> setUpRecyclerView(movies) }
34 | }
35 | Status.ERROR -> {
36 | viewModel.progressBarVisibility.postValue(false)
37 | showAlert(response.throwable?.errorResponse?.statusMessage ?: "")
38 | }
39 | }
40 | })
41 | }
42 |
43 | private fun showAlert(message: String) {
44 | AlertDialog.Builder(this).setTitle(getString(R.string.alertTitle)).setMessage(message)
45 | .setPositiveButton(android.R.string.ok, null).show()
46 | }
47 |
48 | // SetUp Movie List Adapter
49 | private fun setUpRecyclerView(popularMoviesResponse: PopularMoviesResponse) {
50 | val adapter = MovieListAdapter()
51 | adapter.setList(popularMoviesResponse.results)
52 | binding.recyclerViewMovies.adapter = adapter
53 | }
54 |
55 | private fun setUpBinding() {
56 | binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
57 | binding.apply {
58 | lifecycleOwner = this@MainActivity
59 | viewModel = this@MainActivity.viewModel
60 | }
61 | }
62 |
63 | // Call Get Movies Api
64 | private fun callApi() {
65 | viewModel.callMovieList()
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/androidApp/src/main/java/com/simform/kmmsample/androidApp/ui/MainViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.androidApp.ui
2 |
3 | import androidx.lifecycle.MutableLiveData
4 | import androidx.lifecycle.ViewModel
5 | import androidx.lifecycle.viewModelScope
6 | import com.simform.kmmsample.androidApp.base.Resource
7 | import com.simform.kmmsample.androidApp.utils.initWith
8 | import com.simform.kmmsample.shared.datamodels.responsemodels.PopularMoviesResponse
9 | import com.simform.kmmsample.shared.remote.BaseApiClass
10 | import kotlinx.coroutines.launch
11 |
12 | class MainViewModel : ViewModel() {
13 |
14 | var apiService = BaseApiClass()
15 | var popularMovieResponse = MutableLiveData>()
16 | var progressBarVisibility = MutableLiveData().initWith(true)
17 |
18 | fun callMovieList() {
19 | viewModelScope.launch {
20 | popularMovieResponse.postValue(Resource.loading())
21 | apiService.getMovies()?.fold(
22 | failed = {
23 | popularMovieResponse.postValue(Resource.error(it))
24 | },
25 | succeeded = {
26 | popularMovieResponse.postValue(Resource.success(it))
27 | }
28 | )
29 | }
30 | }
31 |
32 | }
--------------------------------------------------------------------------------
/androidApp/src/main/java/com/simform/kmmsample/androidApp/ui/MovieListAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.androidApp.ui
2 |
3 | import android.view.LayoutInflater
4 | import android.view.ViewGroup
5 | import androidx.databinding.DataBindingUtil
6 | import androidx.recyclerview.widget.RecyclerView
7 | import com.simform.kmmsample.androidApp.R
8 | import com.simform.kmmsample.androidApp.databinding.MovieListBinding
9 | import com.simform.kmmsample.shared.datamodels.responsemodels.MovieEntity
10 |
11 | class MovieListAdapter : RecyclerView.Adapter() {
12 |
13 | private var moviesList = arrayListOf()
14 |
15 | fun setList(movies: ArrayList) {
16 | moviesList = movies
17 | notifyDataSetChanged()
18 | }
19 |
20 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
21 | val mBinding: MovieListBinding = DataBindingUtil.inflate(LayoutInflater.from(parent.context), R.layout.movie_list, parent, false)
22 | return ViewHolder(mBinding)
23 | }
24 |
25 | override fun onBindViewHolder(holder: ViewHolder, position: Int) {
26 | holder.bind(moviesList[position])
27 | }
28 |
29 | override fun getItemCount(): Int = moviesList.size
30 |
31 | inner class ViewHolder(private val binding: MovieListBinding) :
32 | RecyclerView.ViewHolder(binding.root) {
33 |
34 | fun bind(movieEntity: MovieEntity) {
35 | binding.movieEntity = movieEntity
36 | }
37 | }
38 | }
--------------------------------------------------------------------------------
/androidApp/src/main/java/com/simform/kmmsample/androidApp/utils/ImageUtils.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.androidApp.utils
2 |
3 | import android.widget.ImageView
4 | import com.bumptech.glide.Glide
5 | import com.bumptech.glide.load.engine.DiskCacheStrategy
6 | import com.bumptech.glide.request.RequestOptions
7 |
8 | fun ImageView.loadUserImage(url: Any?) {
9 | url?.let { image ->
10 | val options = RequestOptions().skipMemoryCache(true).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC)
11 | val requestBuilder = Glide.with(context).load(image).apply(options)
12 | requestBuilder.into(this)
13 | }
14 | }
--------------------------------------------------------------------------------
/androidApp/src/main/java/com/simform/kmmsample/androidApp/utils/MutableLiveDataExtension.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.androidApp.utils
2 |
3 | import androidx.lifecycle.MutableLiveData
4 |
5 | fun MutableLiveData.initWith(data: T): MutableLiveData = this.apply {
6 | value = data
7 | }
--------------------------------------------------------------------------------
/androidApp/src/main/java/com/simform/kmmsample/androidApp/utils/binding.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.androidApp.utils
2 |
3 | import android.graphics.drawable.Drawable
4 | import android.widget.ImageView
5 | import androidx.databinding.BindingAdapter
6 | import com.bumptech.glide.Glide
7 |
8 | @BindingAdapter(value = ["imageUrl", "placeHolder"], requireAll = false)
9 | fun bindImage(view: ImageView, url: String?, placeHolder: Drawable?) {
10 | Glide.with(view.context)
11 | .load(url)
12 | .placeholder(placeHolder)
13 | .into(view)
14 | }
15 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/drawable/placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/androidApp/src/main/res/drawable/placeholder.jpg
--------------------------------------------------------------------------------
/androidApp/src/main/res/drawable/tmdb.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
11 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
12 |
13 |
14 |
18 |
19 |
27 |
28 |
36 |
37 |
38 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/layout/movie_list.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
11 |
12 |
13 |
21 |
22 |
25 |
26 |
38 |
39 |
54 |
55 |
66 |
67 |
79 |
80 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #2D647D
4 | #2D647D
5 | #46a0c6
6 | #1B3C4B
7 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | KMM Sample
4 | %s / 10
5 | TMDB
6 | Alert
7 |
--------------------------------------------------------------------------------
/androidApp/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 |
10 |
--------------------------------------------------------------------------------
/build.gradle.kts:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | gradlePluginPortal()
4 | jcenter()
5 | google()
6 | mavenCentral()
7 | }
8 | dependencies {
9 | classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:${Version.KOTLIN_VERSION}")
10 | classpath("com.android.tools.build:gradle:${Version.ANDROID_GRADLE}")
11 | classpath("org.jetbrains.kotlin:kotlin-serialization:${Version.KOTLIN_VERSION}")
12 | }
13 | }
14 |
15 | allprojects {
16 | repositories {
17 | google()
18 | jcenter()
19 | mavenCentral()
20 | jitpack()
21 | }
22 | }
--------------------------------------------------------------------------------
/buildSrc/build.gradle.kts:
--------------------------------------------------------------------------------
1 | repositories {
2 | jcenter()
3 | }
4 |
5 | plugins {
6 | `kotlin-dsl`
7 | }
8 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/App.kt:
--------------------------------------------------------------------------------
1 | object App {
2 | const val ID = "com.example.kmmsample.androidApp"
3 |
4 | object Version {
5 | const val CODE = 1
6 | const val NAME = "1.1"
7 | }
8 | }
9 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/Extensions.kt:
--------------------------------------------------------------------------------
1 | import org.gradle.api.Project
2 | import org.gradle.api.artifacts.dsl.RepositoryHandler
3 | import java.net.URI
4 |
5 | /**
6 | * Extension to add file tree dependency
7 | */
8 | fun Project.defaultFileTree() = fileTree(mapOf("dir" to "libs", "include" to listOf("*.jar")))
9 |
10 | /**
11 | * Adds preference maven repository.
12 | */
13 | fun RepositoryHandler.jitpack() = maven { url = URI(Urls.JITPACK) }
14 |
--------------------------------------------------------------------------------
/buildSrc/src/main/java/Libs.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Contains all libraries.
3 | */
4 | object Libs {
5 |
6 | //Shared
7 | const val shared = ":shared"
8 |
9 | // Kotlin
10 | const val KOTLIN_STDLIB = "org.jetbrains.kotlin:kotlin-stdlib:${Version.KOTLIN_VERSION}"
11 |
12 | // UI
13 | const val CONSTRAINT_LAYOUT = "androidx.constraintlayout:constraintlayout:${Version.CONSTRAINT_LAYOUT}"
14 | const val RECYCLERVIEW = "androidx.recyclerview:recyclerview:${Version.APPCOMPAT}"
15 |
16 | // ViewModel
17 | const val CORE_KTX = "androidx.core:core-ktx:${Version.CORE_KTX}"
18 | const val VIEW_MODEL = "androidx.lifecycle:lifecycle-viewmodel:${Version.ARCH_VERSION}"
19 | const val VIEW_MODEL_KTX = "androidx.lifecycle:lifecycle-viewmodel-ktx:${Version.ARCH_VERSION}"
20 | const val APPCOMPAT = "androidx.appcompat:appcompat:${Version.APPCOMPAT}"
21 |
22 | // Coroutines
23 | const val COROUTINES_CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${Version.COROUTINES}"
24 | const val COROUTINES_ANDROID = "org.jetbrains.kotlinx:kotlinx-coroutines-android:${Version.COROUTINES}"
25 | const val COROUTINES_TEST = "org.jetbrains.kotlinx:kotlinx-coroutines-test:${Version.COROUTINES}"
26 |
27 | // Glide
28 | const val GLIDE = "com.github.bumptech.glide:glide:${Version.GLIDE}"
29 |
30 | //SDP
31 | const val SDP = "com.intuit.sdp:sdp-android:${Version.SDP}"
32 |
33 | //SSP
34 | const val SSP = "com.intuit.ssp:ssp-android:${Version.SSP}"
35 |
36 | //MATERIAL
37 | const val MATERIAL = "com.google.android.material:material:${Version.MATERIAL}"
38 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/Plugins.kt:
--------------------------------------------------------------------------------
1 | object Plugins {
2 | const val ANDROID_APPLICATION = "com.android.application"
3 | const val ANDROID = "android"
4 | const val KOTLIN_KAPT = "kotlin-kapt"
5 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/SharedLibs.kt:
--------------------------------------------------------------------------------
1 | object SharedLibs {
2 | //Common Libraries
3 |
4 | //Coroutines Core
5 | const val COROUINES_CORE = "org.jetbrains.kotlinx:kotlinx-coroutines-core:${SharedVersions.COROUTINES_VERSION}"
6 |
7 | //Ktor features
8 | const val KTOR = "io.ktor:ktor-client-core:${SharedVersions.KTOR_VERSION}"
9 | const val KTOR_SERIALIZER = "io.ktor:ktor-client-serialization:${SharedVersions.KTOR_VERSION}"
10 |
11 | // KotlinX Serialization
12 | const val KOTLINX_SERIALIZATION= "org.jetbrains.kotlinx:kotlinx-serialization-json:" +
13 | SharedVersions.KOTLINX_SERIALIZATION_VERSION
14 |
15 | //Android Specific Libraries
16 |
17 | //Ktor and it's features
18 | const val KTOR_ANDROID = "io.ktor:ktor-client-android:${SharedVersions.KTOR_VERSION}"
19 | const val KTOR_JSON_ANDROID = "io.ktor:ktor-client-json:${SharedVersions.KTOR_VERSION}"
20 | const val KTOR_SERIALIZER_ANDROID = "io.ktor:ktor-client-serialization-jvm:${SharedVersions.KTOR_VERSION}"
21 |
22 | //Kotlinx Serializer
23 | const val KOTLINX_SERIALIZER_ANDROID = "org.jetbrains.kotlinx:kotlinx-serialization-json:" +
24 | SharedVersions.KOTLINX_SERIALIZATION_VERSION
25 |
26 | //iOS Specific Libraries
27 |
28 | //Ktor for iOS
29 | const val KTOR_IOS = "io.ktor:ktor-client-ios:${SharedVersions.KTOR_VERSION}"
30 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/SharedPlugins.kt:
--------------------------------------------------------------------------------
1 | //Shared native.Plugins
2 | object SharedPlugins {
3 | const val SHARED_MODULE = "com.android.library"
4 | const val MULTIPLATFORM = "multiplatform"
5 | const val SERIALIZATION = "plugin.serialization"
6 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/SharedVersions.kt:
--------------------------------------------------------------------------------
1 | //Shared Versions
2 | object SharedVersions {
3 | const val COROUTINES_VERSION = "1.4.2-native-mt"
4 | const val KTOR_VERSION = "1.6.2"
5 | const val KOTLINX_SERIALIZATION_VERSION = "1.2.2"
6 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/Urls.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Urls for gradle.
3 | */
4 | object Urls {
5 | const val JITPACK = "https://jitpack.io"
6 | }
--------------------------------------------------------------------------------
/buildSrc/src/main/java/Versions.kt:
--------------------------------------------------------------------------------
1 | /**
2 | * Contains versions for [Libs].
3 | */
4 | object Version {
5 | const val COMPILE_SDK = 31
6 | const val MIN_SDK = 21
7 | const val TARGET_SDK = 31
8 |
9 | const val APPCOMPAT = "1.2.0"
10 | const val ANDROID_GRADLE = "7.0.1"
11 | const val CORE_KTX = "1.6.0"
12 | const val ARCH_VERSION = "2.3.1"
13 | const val CONSTRAINT_LAYOUT = "2.1.0"
14 | const val COROUTINES = "1.5.1"
15 | const val GLIDE = "4.12.0"
16 | const val KOTLIN_VERSION = "1.5.21"
17 | const val MATERIAL = "1.2.1"
18 | const val SDP = "1.0.6"
19 | const val SSP = "1.0.6"
20 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | kotlin.code.style=official
2 | xcodeproj=./iosApp
3 | android.useAndroidX=true
4 | kotlin.mpp.enableGranularSourceSetsMetadata=true
5 | kotlin.native.enableDependencyPropagation=false
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Fri Jan 22 12:02:03 IST 2021
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.2-bin.zip
7 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/iosApp/Podfile:
--------------------------------------------------------------------------------
1 | # Uncomment the next line to define a global platform for your project
2 | # platform :ios, '9.0'
3 |
4 | target 'iosApp' do
5 | # Comment the next line if you don't want to use dynamic frameworks
6 | use_frameworks!
7 |
8 | # Pods for iosApp
9 |
10 | pod 'Kingfisher', '5.15.8' # Used for download images
11 |
12 | end
13 |
14 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.pbxproj:
--------------------------------------------------------------------------------
1 | // !$*UTF8*$!
2 | {
3 | archiveVersion = 1;
4 | classes = {
5 | };
6 | objectVersion = 51;
7 | objects = {
8 |
9 | /* Begin PBXBuildFile section */
10 | 1FE6A11B221D69282A6FC03B /* Pods_iosApp.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 583BAA25BEE980389E672006 /* Pods_iosApp.framework */; };
11 | 7555FF7F242A565900829871 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF7E242A565900829871 /* AppDelegate.swift */; };
12 | 7555FF85242A565B00829871 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 7555FF84242A565B00829871 /* Assets.xcassets */; };
13 | 7555FF8B242A565B00829871 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 7555FF89242A565B00829871 /* LaunchScreen.storyboard */; };
14 | 7555FF96242A565B00829871 /* iosAppTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FF95242A565B00829871 /* iosAppTests.swift */; };
15 | 7555FFA1242A565B00829871 /* iosAppUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7555FFA0242A565B00829871 /* iosAppUITests.swift */; };
16 | 7555FFB2242A642300829871 /* shared.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 7555FFB1242A642300829871 /* shared.framework */; };
17 | 7555FFB3242A642300829871 /* shared.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 7555FFB1242A642300829871 /* shared.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
18 | E305F24C25E14589008BA605 /* MoviesCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E305F24B25E14589008BA605 /* MoviesCell.swift */; };
19 | E325AA9D25F20BE300266C70 /* Colors.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = E325AA9C25F20BE300266C70 /* Colors.xcassets */; };
20 | E34F567F25EFECF8003AFD2C /* Structures.swift in Sources */ = {isa = PBXBuildFile; fileRef = E34F567E25EFECF8003AFD2C /* Structures.swift */; };
21 | E3603A2125F1FD40000F381C /* MainViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E3603A2025F1FD40000F381C /* MainViewModel.swift */; };
22 | EB7E684B25BAB4B000DFCBB6 /* MainViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB7E684A25BAB4B000DFCBB6 /* MainViewController.swift */; };
23 | EB7E685025BAB4CE00DFCBB6 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = EB7E684F25BAB4CE00DFCBB6 /* Main.storyboard */; };
24 | /* End PBXBuildFile section */
25 |
26 | /* Begin PBXContainerItemProxy section */
27 | 7555FF92242A565B00829871 /* PBXContainerItemProxy */ = {
28 | isa = PBXContainerItemProxy;
29 | containerPortal = 7555FF73242A565900829871 /* Project object */;
30 | proxyType = 1;
31 | remoteGlobalIDString = 7555FF7A242A565900829871;
32 | remoteInfo = iosApp;
33 | };
34 | 7555FF9D242A565B00829871 /* PBXContainerItemProxy */ = {
35 | isa = PBXContainerItemProxy;
36 | containerPortal = 7555FF73242A565900829871 /* Project object */;
37 | proxyType = 1;
38 | remoteGlobalIDString = 7555FF7A242A565900829871;
39 | remoteInfo = iosApp;
40 | };
41 | /* End PBXContainerItemProxy section */
42 |
43 | /* Begin PBXCopyFilesBuildPhase section */
44 | 7555FFB4242A642300829871 /* Embed Frameworks */ = {
45 | isa = PBXCopyFilesBuildPhase;
46 | buildActionMask = 2147483647;
47 | dstPath = "";
48 | dstSubfolderSpec = 10;
49 | files = (
50 | 7555FFB3242A642300829871 /* shared.framework in Embed Frameworks */,
51 | );
52 | name = "Embed Frameworks";
53 | runOnlyForDeploymentPostprocessing = 0;
54 | };
55 | /* End PBXCopyFilesBuildPhase section */
56 |
57 | /* Begin PBXFileReference section */
58 | 0182BEA8F1606602BF5E3669 /* Pods-iosApp.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.release.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.release.xcconfig"; sourceTree = ""; };
59 | 583BAA25BEE980389E672006 /* Pods_iosApp.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_iosApp.framework; sourceTree = BUILT_PRODUCTS_DIR; };
60 | 7555FF7B242A565900829871 /* iosApp.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iosApp.app; sourceTree = BUILT_PRODUCTS_DIR; };
61 | 7555FF7E242A565900829871 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; };
62 | 7555FF84242A565B00829871 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; };
63 | 7555FF8A242A565B00829871 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; };
64 | 7555FF8C242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
65 | 7555FF91242A565B00829871 /* iosAppTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iosAppTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
66 | 7555FF95242A565B00829871 /* iosAppTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosAppTests.swift; sourceTree = ""; };
67 | 7555FF97242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
68 | 7555FF9C242A565B00829871 /* iosAppUITests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = iosAppUITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
69 | 7555FFA0242A565B00829871 /* iosAppUITests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iosAppUITests.swift; sourceTree = ""; };
70 | 7555FFA2242A565B00829871 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; };
71 | 7555FFB1242A642300829871 /* shared.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = shared.framework; path = "../shared/build/xcode-frameworks/shared.framework"; sourceTree = ""; };
72 | E305F24B25E14589008BA605 /* MoviesCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MoviesCell.swift; sourceTree = ""; };
73 | E305F25925E14DEB008BA605 /* Kingfisher.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Kingfisher.framework; sourceTree = BUILT_PRODUCTS_DIR; };
74 | E325AA9C25F20BE300266C70 /* Colors.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Colors.xcassets; sourceTree = ""; };
75 | E34F567E25EFECF8003AFD2C /* Structures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Structures.swift; sourceTree = ""; };
76 | E3603A2025F1FD40000F381C /* MainViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewModel.swift; sourceTree = ""; };
77 | E8E26546DCD6A90A53C35C5D /* Pods-iosApp.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-iosApp.debug.xcconfig"; path = "Target Support Files/Pods-iosApp/Pods-iosApp.debug.xcconfig"; sourceTree = ""; };
78 | EB7E684A25BAB4B000DFCBB6 /* MainViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainViewController.swift; sourceTree = ""; };
79 | EB7E684F25BAB4CE00DFCBB6 /* Main.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = Main.storyboard; sourceTree = ""; };
80 | /* End PBXFileReference section */
81 |
82 | /* Begin PBXFrameworksBuildPhase section */
83 | 7555FF78242A565900829871 /* Frameworks */ = {
84 | isa = PBXFrameworksBuildPhase;
85 | buildActionMask = 2147483647;
86 | files = (
87 | 7555FFB2242A642300829871 /* shared.framework in Frameworks */,
88 | 1FE6A11B221D69282A6FC03B /* Pods_iosApp.framework in Frameworks */,
89 | );
90 | runOnlyForDeploymentPostprocessing = 0;
91 | };
92 | 7555FF8E242A565B00829871 /* Frameworks */ = {
93 | isa = PBXFrameworksBuildPhase;
94 | buildActionMask = 2147483647;
95 | files = (
96 | );
97 | runOnlyForDeploymentPostprocessing = 0;
98 | };
99 | 7555FF99242A565B00829871 /* Frameworks */ = {
100 | isa = PBXFrameworksBuildPhase;
101 | buildActionMask = 2147483647;
102 | files = (
103 | );
104 | runOnlyForDeploymentPostprocessing = 0;
105 | };
106 | /* End PBXFrameworksBuildPhase section */
107 |
108 | /* Begin PBXGroup section */
109 | 7555FF72242A565900829871 = {
110 | isa = PBXGroup;
111 | children = (
112 | 7555FF7D242A565900829871 /* iosApp */,
113 | 7555FF94242A565B00829871 /* iosAppTests */,
114 | 7555FF9F242A565B00829871 /* iosAppUITests */,
115 | 7555FF7C242A565900829871 /* Products */,
116 | 7555FFB0242A642200829871 /* Frameworks */,
117 | 9C56648237A1BFDD39D50F35 /* Pods */,
118 | );
119 | sourceTree = "";
120 | };
121 | 7555FF7C242A565900829871 /* Products */ = {
122 | isa = PBXGroup;
123 | children = (
124 | 7555FF7B242A565900829871 /* iosApp.app */,
125 | 7555FF91242A565B00829871 /* iosAppTests.xctest */,
126 | 7555FF9C242A565B00829871 /* iosAppUITests.xctest */,
127 | );
128 | name = Products;
129 | sourceTree = "";
130 | };
131 | 7555FF7D242A565900829871 /* iosApp */ = {
132 | isa = PBXGroup;
133 | children = (
134 | E34F567D25EFECE5003AFD2C /* Constants */,
135 | EB7E684E25BAB4C100DFCBB6 /* Storyboards */,
136 | EB7E684525BAB48500DFCBB6 /* ViewControllers */,
137 | 7555FF7E242A565900829871 /* AppDelegate.swift */,
138 | 7555FF84242A565B00829871 /* Assets.xcassets */,
139 | 7555FF8C242A565B00829871 /* Info.plist */,
140 | E325AA9C25F20BE300266C70 /* Colors.xcassets */,
141 | );
142 | path = iosApp;
143 | sourceTree = "";
144 | };
145 | 7555FF94242A565B00829871 /* iosAppTests */ = {
146 | isa = PBXGroup;
147 | children = (
148 | 7555FF95242A565B00829871 /* iosAppTests.swift */,
149 | 7555FF97242A565B00829871 /* Info.plist */,
150 | );
151 | path = iosAppTests;
152 | sourceTree = "";
153 | };
154 | 7555FF9F242A565B00829871 /* iosAppUITests */ = {
155 | isa = PBXGroup;
156 | children = (
157 | 7555FFA0242A565B00829871 /* iosAppUITests.swift */,
158 | 7555FFA2242A565B00829871 /* Info.plist */,
159 | );
160 | path = iosAppUITests;
161 | sourceTree = "";
162 | };
163 | 7555FFB0242A642200829871 /* Frameworks */ = {
164 | isa = PBXGroup;
165 | children = (
166 | E305F25925E14DEB008BA605 /* Kingfisher.framework */,
167 | 7555FFB1242A642300829871 /* shared.framework */,
168 | 583BAA25BEE980389E672006 /* Pods_iosApp.framework */,
169 | );
170 | name = Frameworks;
171 | sourceTree = "";
172 | };
173 | 9C56648237A1BFDD39D50F35 /* Pods */ = {
174 | isa = PBXGroup;
175 | children = (
176 | E8E26546DCD6A90A53C35C5D /* Pods-iosApp.debug.xcconfig */,
177 | 0182BEA8F1606602BF5E3669 /* Pods-iosApp.release.xcconfig */,
178 | );
179 | path = Pods;
180 | sourceTree = "";
181 | };
182 | E34F567D25EFECE5003AFD2C /* Constants */ = {
183 | isa = PBXGroup;
184 | children = (
185 | E34F567E25EFECF8003AFD2C /* Structures.swift */,
186 | );
187 | path = Constants;
188 | sourceTree = "";
189 | };
190 | EB7E684525BAB48500DFCBB6 /* ViewControllers */ = {
191 | isa = PBXGroup;
192 | children = (
193 | EB7E684A25BAB4B000DFCBB6 /* MainViewController.swift */,
194 | E305F24B25E14589008BA605 /* MoviesCell.swift */,
195 | E3603A2025F1FD40000F381C /* MainViewModel.swift */,
196 | );
197 | path = ViewControllers;
198 | sourceTree = "";
199 | };
200 | EB7E684E25BAB4C100DFCBB6 /* Storyboards */ = {
201 | isa = PBXGroup;
202 | children = (
203 | 7555FF89242A565B00829871 /* LaunchScreen.storyboard */,
204 | EB7E684F25BAB4CE00DFCBB6 /* Main.storyboard */,
205 | );
206 | path = Storyboards;
207 | sourceTree = "";
208 | };
209 | /* End PBXGroup section */
210 |
211 | /* Begin PBXNativeTarget section */
212 | 7555FF7A242A565900829871 /* iosApp */ = {
213 | isa = PBXNativeTarget;
214 | buildConfigurationList = 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */;
215 | buildPhases = (
216 | F64F09897066CC5A18C320C1 /* [CP] Check Pods Manifest.lock */,
217 | 7555FFB5242A651A00829871 /* ShellScript */,
218 | 7555FF77242A565900829871 /* Sources */,
219 | 7555FF78242A565900829871 /* Frameworks */,
220 | 7555FF79242A565900829871 /* Resources */,
221 | 7555FFB4242A642300829871 /* Embed Frameworks */,
222 | 3794DCBBB2254BB3B1B04787 /* [CP] Embed Pods Frameworks */,
223 | );
224 | buildRules = (
225 | );
226 | dependencies = (
227 | );
228 | name = iosApp;
229 | productName = iosApp;
230 | productReference = 7555FF7B242A565900829871 /* iosApp.app */;
231 | productType = "com.apple.product-type.application";
232 | };
233 | 7555FF90242A565B00829871 /* iosAppTests */ = {
234 | isa = PBXNativeTarget;
235 | buildConfigurationList = 7555FFA8242A565B00829871 /* Build configuration list for PBXNativeTarget "iosAppTests" */;
236 | buildPhases = (
237 | 7555FF8D242A565B00829871 /* Sources */,
238 | 7555FF8E242A565B00829871 /* Frameworks */,
239 | 7555FF8F242A565B00829871 /* Resources */,
240 | );
241 | buildRules = (
242 | );
243 | dependencies = (
244 | 7555FF93242A565B00829871 /* PBXTargetDependency */,
245 | );
246 | name = iosAppTests;
247 | productName = iosAppTests;
248 | productReference = 7555FF91242A565B00829871 /* iosAppTests.xctest */;
249 | productType = "com.apple.product-type.bundle.unit-test";
250 | };
251 | 7555FF9B242A565B00829871 /* iosAppUITests */ = {
252 | isa = PBXNativeTarget;
253 | buildConfigurationList = 7555FFAB242A565B00829871 /* Build configuration list for PBXNativeTarget "iosAppUITests" */;
254 | buildPhases = (
255 | 7555FF98242A565B00829871 /* Sources */,
256 | 7555FF99242A565B00829871 /* Frameworks */,
257 | 7555FF9A242A565B00829871 /* Resources */,
258 | );
259 | buildRules = (
260 | );
261 | dependencies = (
262 | 7555FF9E242A565B00829871 /* PBXTargetDependency */,
263 | );
264 | name = iosAppUITests;
265 | productName = iosAppUITests;
266 | productReference = 7555FF9C242A565B00829871 /* iosAppUITests.xctest */;
267 | productType = "com.apple.product-type.bundle.ui-testing";
268 | };
269 | /* End PBXNativeTarget section */
270 |
271 | /* Begin PBXProject section */
272 | 7555FF73242A565900829871 /* Project object */ = {
273 | isa = PBXProject;
274 | attributes = {
275 | LastSwiftUpdateCheck = 1130;
276 | LastUpgradeCheck = 1130;
277 | ORGANIZATIONNAME = orgName;
278 | TargetAttributes = {
279 | 7555FF7A242A565900829871 = {
280 | CreatedOnToolsVersion = 11.3.1;
281 | };
282 | 7555FF90242A565B00829871 = {
283 | CreatedOnToolsVersion = 11.3.1;
284 | TestTargetID = 7555FF7A242A565900829871;
285 | };
286 | 7555FF9B242A565B00829871 = {
287 | CreatedOnToolsVersion = 11.3.1;
288 | TestTargetID = 7555FF7A242A565900829871;
289 | };
290 | };
291 | };
292 | buildConfigurationList = 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */;
293 | compatibilityVersion = "Xcode 9.3";
294 | developmentRegion = en;
295 | hasScannedForEncodings = 0;
296 | knownRegions = (
297 | en,
298 | Base,
299 | );
300 | mainGroup = 7555FF72242A565900829871;
301 | productRefGroup = 7555FF7C242A565900829871 /* Products */;
302 | projectDirPath = "";
303 | projectRoot = "";
304 | targets = (
305 | 7555FF7A242A565900829871 /* iosApp */,
306 | 7555FF90242A565B00829871 /* iosAppTests */,
307 | 7555FF9B242A565B00829871 /* iosAppUITests */,
308 | );
309 | };
310 | /* End PBXProject section */
311 |
312 | /* Begin PBXResourcesBuildPhase section */
313 | 7555FF79242A565900829871 /* Resources */ = {
314 | isa = PBXResourcesBuildPhase;
315 | buildActionMask = 2147483647;
316 | files = (
317 | 7555FF8B242A565B00829871 /* LaunchScreen.storyboard in Resources */,
318 | E325AA9D25F20BE300266C70 /* Colors.xcassets in Resources */,
319 | 7555FF85242A565B00829871 /* Assets.xcassets in Resources */,
320 | EB7E685025BAB4CE00DFCBB6 /* Main.storyboard in Resources */,
321 | );
322 | runOnlyForDeploymentPostprocessing = 0;
323 | };
324 | 7555FF8F242A565B00829871 /* Resources */ = {
325 | isa = PBXResourcesBuildPhase;
326 | buildActionMask = 2147483647;
327 | files = (
328 | );
329 | runOnlyForDeploymentPostprocessing = 0;
330 | };
331 | 7555FF9A242A565B00829871 /* Resources */ = {
332 | isa = PBXResourcesBuildPhase;
333 | buildActionMask = 2147483647;
334 | files = (
335 | );
336 | runOnlyForDeploymentPostprocessing = 0;
337 | };
338 | /* End PBXResourcesBuildPhase section */
339 |
340 | /* Begin PBXShellScriptBuildPhase section */
341 | 3794DCBBB2254BB3B1B04787 /* [CP] Embed Pods Frameworks */ = {
342 | isa = PBXShellScriptBuildPhase;
343 | buildActionMask = 2147483647;
344 | files = (
345 | );
346 | inputFileListPaths = (
347 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-input-files.xcfilelist",
348 | );
349 | name = "[CP] Embed Pods Frameworks";
350 | outputFileListPaths = (
351 | "${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks-${CONFIGURATION}-output-files.xcfilelist",
352 | );
353 | runOnlyForDeploymentPostprocessing = 0;
354 | shellPath = /bin/sh;
355 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-iosApp/Pods-iosApp-frameworks.sh\"\n";
356 | showEnvVarsInLog = 0;
357 | };
358 | 7555FFB5242A651A00829871 /* ShellScript */ = {
359 | isa = PBXShellScriptBuildPhase;
360 | buildActionMask = 2147483647;
361 | files = (
362 | );
363 | inputFileListPaths = (
364 | );
365 | inputPaths = (
366 | );
367 | outputFileListPaths = (
368 | );
369 | outputPaths = (
370 | );
371 | runOnlyForDeploymentPostprocessing = 0;
372 | shellPath = /bin/sh;
373 | shellScript = "cd \"$SRCROOT/..\"\n./gradlew :shared:packForXCode -PXCODE_CONFIGURATION=${CONFIGURATION}\n";
374 | };
375 | F64F09897066CC5A18C320C1 /* [CP] Check Pods Manifest.lock */ = {
376 | isa = PBXShellScriptBuildPhase;
377 | buildActionMask = 2147483647;
378 | files = (
379 | );
380 | inputFileListPaths = (
381 | );
382 | inputPaths = (
383 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock",
384 | "${PODS_ROOT}/Manifest.lock",
385 | );
386 | name = "[CP] Check Pods Manifest.lock";
387 | outputFileListPaths = (
388 | );
389 | outputPaths = (
390 | "$(DERIVED_FILE_DIR)/Pods-iosApp-checkManifestLockResult.txt",
391 | );
392 | runOnlyForDeploymentPostprocessing = 0;
393 | shellPath = /bin/sh;
394 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
395 | showEnvVarsInLog = 0;
396 | };
397 | /* End PBXShellScriptBuildPhase section */
398 |
399 | /* Begin PBXSourcesBuildPhase section */
400 | 7555FF77242A565900829871 /* Sources */ = {
401 | isa = PBXSourcesBuildPhase;
402 | buildActionMask = 2147483647;
403 | files = (
404 | E3603A2125F1FD40000F381C /* MainViewModel.swift in Sources */,
405 | 7555FF7F242A565900829871 /* AppDelegate.swift in Sources */,
406 | E34F567F25EFECF8003AFD2C /* Structures.swift in Sources */,
407 | E305F24C25E14589008BA605 /* MoviesCell.swift in Sources */,
408 | EB7E684B25BAB4B000DFCBB6 /* MainViewController.swift in Sources */,
409 | );
410 | runOnlyForDeploymentPostprocessing = 0;
411 | };
412 | 7555FF8D242A565B00829871 /* Sources */ = {
413 | isa = PBXSourcesBuildPhase;
414 | buildActionMask = 2147483647;
415 | files = (
416 | 7555FF96242A565B00829871 /* iosAppTests.swift in Sources */,
417 | );
418 | runOnlyForDeploymentPostprocessing = 0;
419 | };
420 | 7555FF98242A565B00829871 /* Sources */ = {
421 | isa = PBXSourcesBuildPhase;
422 | buildActionMask = 2147483647;
423 | files = (
424 | 7555FFA1242A565B00829871 /* iosAppUITests.swift in Sources */,
425 | );
426 | runOnlyForDeploymentPostprocessing = 0;
427 | };
428 | /* End PBXSourcesBuildPhase section */
429 |
430 | /* Begin PBXTargetDependency section */
431 | 7555FF93242A565B00829871 /* PBXTargetDependency */ = {
432 | isa = PBXTargetDependency;
433 | target = 7555FF7A242A565900829871 /* iosApp */;
434 | targetProxy = 7555FF92242A565B00829871 /* PBXContainerItemProxy */;
435 | };
436 | 7555FF9E242A565B00829871 /* PBXTargetDependency */ = {
437 | isa = PBXTargetDependency;
438 | target = 7555FF7A242A565900829871 /* iosApp */;
439 | targetProxy = 7555FF9D242A565B00829871 /* PBXContainerItemProxy */;
440 | };
441 | /* End PBXTargetDependency section */
442 |
443 | /* Begin PBXVariantGroup section */
444 | 7555FF89242A565B00829871 /* LaunchScreen.storyboard */ = {
445 | isa = PBXVariantGroup;
446 | children = (
447 | 7555FF8A242A565B00829871 /* Base */,
448 | );
449 | name = LaunchScreen.storyboard;
450 | sourceTree = "";
451 | };
452 | /* End PBXVariantGroup section */
453 |
454 | /* Begin XCBuildConfiguration section */
455 | 7555FFA3242A565B00829871 /* Debug */ = {
456 | isa = XCBuildConfiguration;
457 | buildSettings = {
458 | ALWAYS_SEARCH_USER_PATHS = NO;
459 | CLANG_ANALYZER_NONNULL = YES;
460 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
461 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
462 | CLANG_CXX_LIBRARY = "libc++";
463 | CLANG_ENABLE_MODULES = YES;
464 | CLANG_ENABLE_OBJC_ARC = YES;
465 | CLANG_ENABLE_OBJC_WEAK = YES;
466 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
467 | CLANG_WARN_BOOL_CONVERSION = YES;
468 | CLANG_WARN_COMMA = YES;
469 | CLANG_WARN_CONSTANT_CONVERSION = YES;
470 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
471 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
472 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
473 | CLANG_WARN_EMPTY_BODY = YES;
474 | CLANG_WARN_ENUM_CONVERSION = YES;
475 | CLANG_WARN_INFINITE_RECURSION = YES;
476 | CLANG_WARN_INT_CONVERSION = YES;
477 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
478 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
479 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
480 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
481 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
482 | CLANG_WARN_STRICT_PROTOTYPES = YES;
483 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
484 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
485 | CLANG_WARN_UNREACHABLE_CODE = YES;
486 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
487 | COPY_PHASE_STRIP = NO;
488 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
489 | ENABLE_STRICT_OBJC_MSGSEND = YES;
490 | ENABLE_TESTABILITY = YES;
491 | GCC_C_LANGUAGE_STANDARD = gnu11;
492 | GCC_DYNAMIC_NO_PIC = NO;
493 | GCC_NO_COMMON_BLOCKS = YES;
494 | GCC_OPTIMIZATION_LEVEL = 0;
495 | GCC_PREPROCESSOR_DEFINITIONS = (
496 | "DEBUG=1",
497 | "$(inherited)",
498 | );
499 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
500 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
501 | GCC_WARN_UNDECLARED_SELECTOR = YES;
502 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
503 | GCC_WARN_UNUSED_FUNCTION = YES;
504 | GCC_WARN_UNUSED_VARIABLE = YES;
505 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
506 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE;
507 | MTL_FAST_MATH = YES;
508 | ONLY_ACTIVE_ARCH = YES;
509 | SDKROOT = iphoneos;
510 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
511 | SWIFT_OPTIMIZATION_LEVEL = "-Onone";
512 | };
513 | name = Debug;
514 | };
515 | 7555FFA4242A565B00829871 /* Release */ = {
516 | isa = XCBuildConfiguration;
517 | buildSettings = {
518 | ALWAYS_SEARCH_USER_PATHS = NO;
519 | CLANG_ANALYZER_NONNULL = YES;
520 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
521 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14";
522 | CLANG_CXX_LIBRARY = "libc++";
523 | CLANG_ENABLE_MODULES = YES;
524 | CLANG_ENABLE_OBJC_ARC = YES;
525 | CLANG_ENABLE_OBJC_WEAK = YES;
526 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
527 | CLANG_WARN_BOOL_CONVERSION = YES;
528 | CLANG_WARN_COMMA = YES;
529 | CLANG_WARN_CONSTANT_CONVERSION = YES;
530 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
531 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
532 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
533 | CLANG_WARN_EMPTY_BODY = YES;
534 | CLANG_WARN_ENUM_CONVERSION = YES;
535 | CLANG_WARN_INFINITE_RECURSION = YES;
536 | CLANG_WARN_INT_CONVERSION = YES;
537 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
538 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
539 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
540 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
541 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
542 | CLANG_WARN_STRICT_PROTOTYPES = YES;
543 | CLANG_WARN_SUSPICIOUS_MOVE = YES;
544 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
545 | CLANG_WARN_UNREACHABLE_CODE = YES;
546 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
547 | COPY_PHASE_STRIP = NO;
548 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
549 | ENABLE_NS_ASSERTIONS = NO;
550 | ENABLE_STRICT_OBJC_MSGSEND = YES;
551 | GCC_C_LANGUAGE_STANDARD = gnu11;
552 | GCC_NO_COMMON_BLOCKS = YES;
553 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
554 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
555 | GCC_WARN_UNDECLARED_SELECTOR = YES;
556 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
557 | GCC_WARN_UNUSED_FUNCTION = YES;
558 | GCC_WARN_UNUSED_VARIABLE = YES;
559 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
560 | MTL_ENABLE_DEBUG_INFO = NO;
561 | MTL_FAST_MATH = YES;
562 | SDKROOT = iphoneos;
563 | SWIFT_COMPILATION_MODE = wholemodule;
564 | SWIFT_OPTIMIZATION_LEVEL = "-O";
565 | VALIDATE_PRODUCT = YES;
566 | };
567 | name = Release;
568 | };
569 | 7555FFA6242A565B00829871 /* Debug */ = {
570 | isa = XCBuildConfiguration;
571 | baseConfigurationReference = E8E26546DCD6A90A53C35C5D /* Pods-iosApp.debug.xcconfig */;
572 | buildSettings = {
573 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
574 | CODE_SIGN_STYLE = Automatic;
575 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
576 | ENABLE_PREVIEWS = YES;
577 | FRAMEWORK_SEARCH_PATHS = (
578 | "$(SRCROOT)/../shared/build/xcode-frameworks",
579 | "$(inherited)",
580 | );
581 | INFOPLIST_FILE = iosApp/Info.plist;
582 | LD_RUNPATH_SEARCH_PATHS = (
583 | "$(inherited)",
584 | "@executable_path/Frameworks",
585 | );
586 | PRODUCT_BUNDLE_IDENTIFIER = com.simform.kmmsample.iosApp;
587 | PRODUCT_NAME = "$(TARGET_NAME)";
588 | SWIFT_VERSION = 5.0;
589 | TARGETED_DEVICE_FAMILY = "1,2";
590 | };
591 | name = Debug;
592 | };
593 | 7555FFA7242A565B00829871 /* Release */ = {
594 | isa = XCBuildConfiguration;
595 | baseConfigurationReference = 0182BEA8F1606602BF5E3669 /* Pods-iosApp.release.xcconfig */;
596 | buildSettings = {
597 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
598 | CODE_SIGN_STYLE = Automatic;
599 | DEVELOPMENT_ASSET_PATHS = "\"iosApp/Preview Content\"";
600 | ENABLE_PREVIEWS = YES;
601 | FRAMEWORK_SEARCH_PATHS = (
602 | "$(SRCROOT)/../shared/build/xcode-frameworks",
603 | "$(inherited)",
604 | );
605 | INFOPLIST_FILE = iosApp/Info.plist;
606 | LD_RUNPATH_SEARCH_PATHS = (
607 | "$(inherited)",
608 | "@executable_path/Frameworks",
609 | );
610 | PRODUCT_BUNDLE_IDENTIFIER = com.simform.kmmsample.iosApp;
611 | PRODUCT_NAME = "$(TARGET_NAME)";
612 | SWIFT_VERSION = 5.0;
613 | TARGETED_DEVICE_FAMILY = "1,2";
614 | };
615 | name = Release;
616 | };
617 | 7555FFA9242A565B00829871 /* Debug */ = {
618 | isa = XCBuildConfiguration;
619 | buildSettings = {
620 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
621 | BUNDLE_LOADER = "$(TEST_HOST)";
622 | CODE_SIGN_STYLE = Automatic;
623 | INFOPLIST_FILE = iosAppTests/Info.plist;
624 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
625 | LD_RUNPATH_SEARCH_PATHS = (
626 | "$(inherited)",
627 | "@executable_path/Frameworks",
628 | "@loader_path/Frameworks",
629 | );
630 | PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosAppTests;
631 | PRODUCT_NAME = "$(TARGET_NAME)";
632 | SWIFT_VERSION = 5.0;
633 | TARGETED_DEVICE_FAMILY = "1,2";
634 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iosApp.app/iosApp";
635 | };
636 | name = Debug;
637 | };
638 | 7555FFAA242A565B00829871 /* Release */ = {
639 | isa = XCBuildConfiguration;
640 | buildSettings = {
641 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
642 | BUNDLE_LOADER = "$(TEST_HOST)";
643 | CODE_SIGN_STYLE = Automatic;
644 | INFOPLIST_FILE = iosAppTests/Info.plist;
645 | IPHONEOS_DEPLOYMENT_TARGET = 13.2;
646 | LD_RUNPATH_SEARCH_PATHS = (
647 | "$(inherited)",
648 | "@executable_path/Frameworks",
649 | "@loader_path/Frameworks",
650 | );
651 | PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosAppTests;
652 | PRODUCT_NAME = "$(TARGET_NAME)";
653 | SWIFT_VERSION = 5.0;
654 | TARGETED_DEVICE_FAMILY = "1,2";
655 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/iosApp.app/iosApp";
656 | };
657 | name = Release;
658 | };
659 | 7555FFAC242A565B00829871 /* Debug */ = {
660 | isa = XCBuildConfiguration;
661 | buildSettings = {
662 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
663 | CODE_SIGN_STYLE = Automatic;
664 | INFOPLIST_FILE = iosAppUITests/Info.plist;
665 | LD_RUNPATH_SEARCH_PATHS = (
666 | "$(inherited)",
667 | "@executable_path/Frameworks",
668 | "@loader_path/Frameworks",
669 | );
670 | PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosAppUITests;
671 | PRODUCT_NAME = "$(TARGET_NAME)";
672 | SWIFT_VERSION = 5.0;
673 | TARGETED_DEVICE_FAMILY = "1,2";
674 | TEST_TARGET_NAME = iosApp;
675 | };
676 | name = Debug;
677 | };
678 | 7555FFAD242A565B00829871 /* Release */ = {
679 | isa = XCBuildConfiguration;
680 | buildSettings = {
681 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
682 | CODE_SIGN_STYLE = Automatic;
683 | INFOPLIST_FILE = iosAppUITests/Info.plist;
684 | LD_RUNPATH_SEARCH_PATHS = (
685 | "$(inherited)",
686 | "@executable_path/Frameworks",
687 | "@loader_path/Frameworks",
688 | );
689 | PRODUCT_BUNDLE_IDENTIFIER = orgIdentifier.iosAppUITests;
690 | PRODUCT_NAME = "$(TARGET_NAME)";
691 | SWIFT_VERSION = 5.0;
692 | TARGETED_DEVICE_FAMILY = "1,2";
693 | TEST_TARGET_NAME = iosApp;
694 | };
695 | name = Release;
696 | };
697 | /* End XCBuildConfiguration section */
698 |
699 | /* Begin XCConfigurationList section */
700 | 7555FF76242A565900829871 /* Build configuration list for PBXProject "iosApp" */ = {
701 | isa = XCConfigurationList;
702 | buildConfigurations = (
703 | 7555FFA3242A565B00829871 /* Debug */,
704 | 7555FFA4242A565B00829871 /* Release */,
705 | );
706 | defaultConfigurationIsVisible = 0;
707 | defaultConfigurationName = Release;
708 | };
709 | 7555FFA5242A565B00829871 /* Build configuration list for PBXNativeTarget "iosApp" */ = {
710 | isa = XCConfigurationList;
711 | buildConfigurations = (
712 | 7555FFA6242A565B00829871 /* Debug */,
713 | 7555FFA7242A565B00829871 /* Release */,
714 | );
715 | defaultConfigurationIsVisible = 0;
716 | defaultConfigurationName = Release;
717 | };
718 | 7555FFA8242A565B00829871 /* Build configuration list for PBXNativeTarget "iosAppTests" */ = {
719 | isa = XCConfigurationList;
720 | buildConfigurations = (
721 | 7555FFA9242A565B00829871 /* Debug */,
722 | 7555FFAA242A565B00829871 /* Release */,
723 | );
724 | defaultConfigurationIsVisible = 0;
725 | defaultConfigurationName = Release;
726 | };
727 | 7555FFAB242A565B00829871 /* Build configuration list for PBXNativeTarget "iosAppUITests" */ = {
728 | isa = XCConfigurationList;
729 | buildConfigurations = (
730 | 7555FFAC242A565B00829871 /* Debug */,
731 | 7555FFAD242A565B00829871 /* Release */,
732 | );
733 | defaultConfigurationIsVisible = 0;
734 | defaultConfigurationName = Release;
735 | };
736 | /* End XCConfigurationList section */
737 | };
738 | rootObject = 7555FF73242A565900829871 /* Project object */;
739 | }
740 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/iosApp/iosApp.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/iosApp/iosApp/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | import UIKit
2 |
3 | @UIApplicationMain
4 | class AppDelegate: UIResponder, UIApplicationDelegate {
5 |
6 | var window: UIWindow?
7 |
8 | func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
9 | // Override point for customization after application launch.
10 | return true
11 | }
12 | }
13 |
14 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "idiom" : "iphone",
5 | "size" : "20x20",
6 | "scale" : "2x"
7 | },
8 | {
9 | "idiom" : "iphone",
10 | "size" : "20x20",
11 | "scale" : "3x"
12 | },
13 | {
14 | "idiom" : "iphone",
15 | "size" : "29x29",
16 | "scale" : "2x"
17 | },
18 | {
19 | "idiom" : "iphone",
20 | "size" : "29x29",
21 | "scale" : "3x"
22 | },
23 | {
24 | "idiom" : "iphone",
25 | "size" : "40x40",
26 | "scale" : "2x"
27 | },
28 | {
29 | "idiom" : "iphone",
30 | "size" : "40x40",
31 | "scale" : "3x"
32 | },
33 | {
34 | "idiom" : "iphone",
35 | "size" : "60x60",
36 | "scale" : "2x"
37 | },
38 | {
39 | "idiom" : "iphone",
40 | "size" : "60x60",
41 | "scale" : "3x"
42 | },
43 | {
44 | "idiom" : "ipad",
45 | "size" : "20x20",
46 | "scale" : "1x"
47 | },
48 | {
49 | "idiom" : "ipad",
50 | "size" : "20x20",
51 | "scale" : "2x"
52 | },
53 | {
54 | "idiom" : "ipad",
55 | "size" : "29x29",
56 | "scale" : "1x"
57 | },
58 | {
59 | "idiom" : "ipad",
60 | "size" : "29x29",
61 | "scale" : "2x"
62 | },
63 | {
64 | "idiom" : "ipad",
65 | "size" : "40x40",
66 | "scale" : "1x"
67 | },
68 | {
69 | "idiom" : "ipad",
70 | "size" : "40x40",
71 | "scale" : "2x"
72 | },
73 | {
74 | "idiom" : "ipad",
75 | "size" : "76x76",
76 | "scale" : "1x"
77 | },
78 | {
79 | "idiom" : "ipad",
80 | "size" : "76x76",
81 | "scale" : "2x"
82 | },
83 | {
84 | "idiom" : "ipad",
85 | "size" : "83.5x83.5",
86 | "scale" : "2x"
87 | },
88 | {
89 | "idiom" : "ios-marketing",
90 | "size" : "1024x1024",
91 | "scale" : "1x"
92 | }
93 | ],
94 | "info" : {
95 | "version" : 1,
96 | "author" : "xcode"
97 | }
98 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/placeHolderImage.imageset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "filename" : "placeholder.jpg",
5 | "idiom" : "universal",
6 | "scale" : "1x"
7 | },
8 | {
9 | "idiom" : "universal",
10 | "scale" : "2x"
11 | },
12 | {
13 | "idiom" : "universal",
14 | "scale" : "3x"
15 | }
16 | ],
17 | "info" : {
18 | "author" : "xcode",
19 | "version" : 1
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Assets.xcassets/placeHolderImage.imageset/placeholder.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/iosApp/iosApp/Assets.xcassets/placeHolderImage.imageset/placeholder.jpg
--------------------------------------------------------------------------------
/iosApp/iosApp/Colors.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "author" : "xcode",
4 | "version" : 1
5 | }
6 | }
7 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Colors.xcassets/darkGrey.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.776",
9 | "green" : "0.627",
10 | "red" : "0.275"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "idiom" : "universal"
23 | }
24 | ],
25 | "info" : {
26 | "author" : "xcode",
27 | "version" : 1
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Colors.xcassets/mainColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.294",
9 | "green" : "0.235",
10 | "red" : "0.106"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "idiom" : "universal"
23 | }
24 | ],
25 | "info" : {
26 | "author" : "xcode",
27 | "version" : 1
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Colors.xcassets/navigationBarColor.colorset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "colors" : [
3 | {
4 | "color" : {
5 | "color-space" : "srgb",
6 | "components" : {
7 | "alpha" : "1.000",
8 | "blue" : "0.490",
9 | "green" : "0.392",
10 | "red" : "0.176"
11 | }
12 | },
13 | "idiom" : "universal"
14 | },
15 | {
16 | "appearances" : [
17 | {
18 | "appearance" : "luminosity",
19 | "value" : "dark"
20 | }
21 | ],
22 | "color" : {
23 | "color-space" : "srgb",
24 | "components" : {
25 | "alpha" : "1.000",
26 | "blue" : "1.000",
27 | "green" : "1.000",
28 | "red" : "1.000"
29 | }
30 | },
31 | "idiom" : "universal"
32 | }
33 | ],
34 | "info" : {
35 | "author" : "xcode",
36 | "version" : 1
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Constants/Structures.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Constant Strings.swift
3 | // iosApp
4 | //
5 | // Created by Mohammed Hanif on 03/03/21.
6 | // Copyright © 2021 orgName. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct ConstantStrings {
12 | static var movieCell = "moviesCell"
13 | static var votesOutOf = "/ 10"
14 | static var placeHolderImageName = "placeHolderImage"
15 | static var alertTitle = "Alert"
16 | static var ok = "ok"
17 | }
18 |
--------------------------------------------------------------------------------
/iosApp/iosApp/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 | LSRequiresIPhoneOS
22 |
23 | NSAppTransportSecurity
24 |
25 | NSAllowsArbitraryLoads
26 |
27 |
28 | UILaunchStoryboardName
29 | LaunchScreen
30 | UIMainStoryboardFile
31 | Main
32 | UIRequiredDeviceCapabilities
33 |
34 | armv7
35 |
36 | UIStatusBarStyle
37 | UIStatusBarStyleLightContent
38 | UISupportedInterfaceOrientations
39 |
40 | UIInterfaceOrientationPortrait
41 | UIInterfaceOrientationLandscapeLeft
42 | UIInterfaceOrientationLandscapeRight
43 |
44 | UISupportedInterfaceOrientations~ipad
45 |
46 | UIInterfaceOrientationPortrait
47 | UIInterfaceOrientationPortraitUpsideDown
48 | UIInterfaceOrientationLandscapeLeft
49 | UIInterfaceOrientationLandscapeRight
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Preview Content/Preview Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/iosApp/iosApp/Storyboards/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 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/iosApp/iosApp/Storyboards/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 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
48 |
53 |
62 |
63 |
64 |
65 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
165 |
166 |
167 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
176 |
177 |
178 |
179 |
180 |
181 |
182 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 |
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 |
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
--------------------------------------------------------------------------------
/iosApp/iosApp/ViewControllers/MainViewController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewController.swift
3 | // iosApp
4 | //
5 | // Created by Mohammed Hanifbiji on 22/01/21.
6 | // Copyright © 2021 orgName. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared
11 |
12 | class MainViewController: UIViewController {
13 |
14 | // MARK: - Outlets
15 | @IBOutlet weak var tblViewMovies: UITableView!
16 | @IBOutlet weak var progressbar: UIActivityIndicatorView!
17 |
18 | // MARK: - Variables
19 | fileprivate var movieList : [MovieEntity] = []
20 | fileprivate var viewModel: MainViewModel?
21 |
22 | // MARK: - UIViewController Life Cycle
23 | override func viewDidLoad() {
24 | super.viewDidLoad()
25 | viewModel = MainViewModel()
26 | viewModel?.getMovies()
27 | bindData()
28 | }
29 |
30 | /// Bind ViewModel and observe api callback
31 | fileprivate func bindData() {
32 | viewModel?.apiCallback = { [weak self] (movies, error) in
33 | guard let `self` = self else {
34 | return
35 | }
36 | self.progressbar.isHidden = true
37 | if error != nil {
38 | self.showAlert(message: error?.errorResponse?.statusMessage ?? "")
39 | } else {
40 | self.movieList = movies as? [MovieEntity] ?? []
41 | self.tblViewMovies.reloadData()
42 | self.tblViewMovies.isHidden = false
43 | }
44 | }
45 | }
46 |
47 | /// Show Alert
48 | /// - Parameter message: Message received from Api.
49 | fileprivate func showAlert(message: String) {
50 | let alert = UIAlertController(title: ConstantStrings.alertTitle, message: message, preferredStyle: UIAlertController.Style.alert)
51 | alert.addAction(UIAlertAction(title: ConstantStrings.ok, style: UIAlertAction.Style.default, handler: nil))
52 | self.present(alert, animated: true, completion: nil)
53 | }
54 |
55 | }
56 |
57 | // MARK: - UITableViewDataSource
58 | extension MainViewController: UITableViewDataSource {
59 |
60 | /// Use this to set number od rows we want in our tableView
61 | /// - Parameters:
62 | /// - tableView: UITableView
63 | /// - section: TableView Section
64 | /// - Returns: Count of rows present
65 | func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
66 | return movieList.count
67 | }
68 |
69 | /// Use this to set data in tableView Cell
70 | /// - Parameters:
71 | /// - tableView: UITableView
72 | /// - indexPath: indexpath
73 | /// - Returns: Returns our custom cell or UITableViewCell itself
74 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
75 | // TODO: Api call
76 | guard let tableViewCell = tableView.dequeueReusableCell(withIdentifier: ConstantStrings.movieCell, for: indexPath) as? MoviesCell else {
77 | return UITableViewCell()
78 | }
79 | let movie = movieList[indexPath.row]
80 | tableViewCell.setUpMoviesData(movieEntity: movie)
81 | return tableViewCell
82 | }
83 |
84 | }
85 |
--------------------------------------------------------------------------------
/iosApp/iosApp/ViewControllers/MainViewModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MainViewModel.swift
3 | // iosApp
4 | //
5 | // Created by Mohammed Hanif on 05/03/21.
6 | // Copyright © 2021 orgName. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import shared
11 |
12 | class MainViewModel {
13 |
14 | // MARK: - Variables
15 | var apiCallback: ((_ movies: NSMutableArray?, _ error: CustomException?) -> Void)?
16 |
17 | /// Get Movies data from Api
18 | func getMovies() {
19 | BaseApiClass().getMovies { (succes, error) in
20 | if error != nil {
21 | return
22 | }
23 | succes?.fold(failed: { (exception) in
24 | self.apiCallback?(nil, exception)
25 | }, succeeded: { (popularMovies) -> Any? in
26 | self.apiCallback?(popularMovies?.results, nil)
27 | return nil
28 | })
29 | }
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/iosApp/iosApp/ViewControllers/MoviesCell.swift:
--------------------------------------------------------------------------------
1 | //
2 | // MoviesCell.swift
3 | // iosApp
4 | //
5 | // Created by Mohammed Hanif on 20/02/21.
6 | // Copyright © 2021 orgName. All rights reserved.
7 | //
8 |
9 | import UIKit
10 | import shared
11 | import Kingfisher
12 |
13 | class MoviesCell: UITableViewCell {
14 |
15 | // MARK: - Outlets
16 | @IBOutlet weak var lblMovieName: UILabel!
17 | @IBOutlet weak var lblMovieRating: UILabel!
18 | @IBOutlet weak var lblMovieOverView: UILabel!
19 | @IBOutlet weak var imgMoviePoster: UIImageView!
20 |
21 | // MARK: - SetUp Movie Details
22 | func setUpMoviesData(movieEntity: MovieEntity) {
23 | lblMovieName.text = movieEntity.title
24 | lblMovieRating.text = "\(movieEntity.voteAverage) \(ConstantStrings.votesOutOf)"
25 | lblMovieOverView.text = movieEntity.overview
26 | imgMoviePoster.kf.setImage(with: URL(string: movieEntity.picturePoster), placeholder:UIImage(named: ConstantStrings.placeHolderImageName))
27 | }
28 |
29 | }
30 |
--------------------------------------------------------------------------------
/iosApp/iosAppTests/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 |
--------------------------------------------------------------------------------
/iosApp/iosAppTests/iosAppTests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 | @testable import iosApp
3 |
4 | class iosAppTests: XCTestCase {
5 |
6 | override func setUp() {
7 | // Put setup code here. This method is called before the invocation of each test method in the class.
8 | }
9 |
10 | override func tearDown() {
11 | // Put teardown code here. This method is called after the invocation of each test method in the class.
12 | }
13 |
14 | func testExample() {
15 | // This is an example of a functional test case.
16 | // Use XCTAssert and related functions to verify your tests produce the correct results.
17 | }
18 |
19 | func testPerformanceExample() {
20 | // This is an example of a performance test case.
21 | self.measure {
22 | // Put the code you want to measure the time of here.
23 | }
24 | }
25 |
26 | }
27 |
--------------------------------------------------------------------------------
/iosApp/iosAppUITests/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 |
--------------------------------------------------------------------------------
/iosApp/iosAppUITests/iosAppUITests.swift:
--------------------------------------------------------------------------------
1 | import XCTest
2 |
3 | class appNameUITests: XCTestCase {
4 |
5 | override func setUp() {
6 | // Put setup code here. This method is called before the invocation of each test method in the class.
7 |
8 | // In UI tests it is usually best to stop immediately when a failure occurs.
9 | continueAfterFailure = false
10 |
11 | // 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.
12 | }
13 |
14 | override func tearDown() {
15 | // Put teardown code here. This method is called after the invocation of each test method in the class.
16 | }
17 |
18 | func testExample() {
19 | // UI tests must launch the application that they test.
20 | let app = XCUIApplication()
21 | app.launch()
22 |
23 | // Use recording to get started writing UI tests.
24 | // Use XCTAssert and related functions to verify your tests produce the correct results.
25 | }
26 |
27 | func testLaunchPerformance() {
28 | if #available(macOS 10.15, iOS 13.0, tvOS 13.0, *) {
29 | // This measures how long it takes to launch your application.
30 | measure(metrics: [XCTOSSignpostMetric.applicationLaunch]) {
31 | XCUIApplication().launch()
32 | }
33 | }
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/screenshots/KMM Plugin.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/screenshots/KMM Plugin.png
--------------------------------------------------------------------------------
/screenshots/Run Configurations.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/screenshots/Run Configurations.png
--------------------------------------------------------------------------------
/screenshots/android.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/screenshots/android.gif
--------------------------------------------------------------------------------
/screenshots/iOS.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SimformSolutionsPvtLtd/Kotlin-multiplatform-sample/c5b013f28fae3aeea19552921ad2f71215f39375/screenshots/iOS.gif
--------------------------------------------------------------------------------
/settings.gradle.kts:
--------------------------------------------------------------------------------
1 | pluginManagement {
2 | repositories {
3 | google()
4 | jcenter()
5 | gradlePluginPortal()
6 | mavenCentral()
7 | }
8 |
9 | }
10 | rootProject.name = "KMMSample"
11 |
12 |
13 | include(":androidApp")
14 | include(":shared")
15 |
16 |
--------------------------------------------------------------------------------
/shared/build.gradle.kts:
--------------------------------------------------------------------------------
1 | import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget
2 |
3 | plugins {
4 | kotlin(SharedPlugins.MULTIPLATFORM)
5 | kotlin(SharedPlugins.SERIALIZATION)
6 | id(SharedPlugins.SHARED_MODULE)
7 | }
8 |
9 | kotlin {
10 | android()
11 | ios {
12 | binaries {
13 | framework {
14 | baseName = "shared"
15 | }
16 | }
17 | }
18 | sourceSets {
19 | val commonMain by getting {
20 | dependencies {
21 | // Coroutines
22 | implementation(SharedLibs.COROUINES_CORE)
23 | //Ktor features
24 | implementation(SharedLibs.KTOR)
25 | implementation(SharedLibs.KTOR_SERIALIZER)
26 | // KotlinX Serialization
27 | implementation(SharedLibs.KOTLINX_SERIALIZATION)
28 | }
29 | }
30 | val commonTest by getting {
31 | dependencies {
32 | implementation(kotlin("test-common"))
33 | implementation(kotlin("test-annotations-common"))
34 | }
35 | }
36 | val androidMain by getting {
37 | dependencies {
38 | // Ktor and it's features
39 | implementation(SharedLibs.KTOR_ANDROID)
40 | implementation(SharedLibs.KTOR_JSON_ANDROID)
41 | implementation(SharedLibs.KTOR_SERIALIZER_ANDROID)
42 | // Kotlinx serializer
43 | implementation(SharedLibs.KOTLINX_SERIALIZER_ANDROID)
44 | }
45 | }
46 | val androidTest by getting {
47 | dependencies {
48 | implementation(kotlin("test-junit"))
49 | implementation("junit:junit:4.13")
50 | }
51 | }
52 | val iosMain by getting {
53 | dependencies {
54 | //Ktor
55 | implementation(SharedLibs.KTOR_IOS)
56 | }
57 | }
58 | val iosTest by getting
59 | }
60 | }
61 |
62 | android {
63 | compileSdkVersion(Version.COMPILE_SDK)
64 | sourceSets["main"].manifest.srcFile("src/androidMain/AndroidManifest.xml")
65 | defaultConfig {
66 | minSdkVersion(Version.MIN_SDK)
67 | targetSdkVersion(Version.TARGET_SDK)
68 | }
69 | }
70 |
71 | val packForXcode by tasks.creating(Sync::class) {
72 | group = "build"
73 | val mode = System.getenv("CONFIGURATION") ?: "DEBUG"
74 | val sdkName = System.getenv("SDK_NAME") ?: "iphonesimulator"
75 | val targetName = "ios" + if (sdkName.startsWith("iphoneos")) "Arm64" else "X64"
76 | val framework = kotlin.targets.getByName(targetName).binaries.getFramework(mode)
77 | inputs.property("mode", mode)
78 | dependsOn(framework.linkTask)
79 | val targetDir = File(buildDir, "xcode-frameworks")
80 | from({ framework.outputDirectory })
81 | into(targetDir)
82 | }
83 |
84 | tasks.getByName("build").dependsOn(packForXcode)
--------------------------------------------------------------------------------
/shared/src/androidMain/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/shared/src/androidMain/kotlin/com/simform/kmmsample/shared/HttpBaseClient.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared
2 |
3 | import com.simform.kmmsample.shared.datamodels.base.CustomException
4 | import com.simform.kmmsample.shared.datamodels.base.ErrorResponse
5 | import com.simform.kmmsample.shared.utils.ApiEndPoints
6 | import com.simform.kmmsample.shared.utils.CLIENT_REQUEST_EXCEPTION_RANGE
7 | import com.simform.kmmsample.shared.utils.REDIRECT_RESPONSE_EXCEPTION_RANGE
8 | import com.simform.kmmsample.shared.utils.RESPONSE_EXCEPTION_CODE
9 | import com.simform.kmmsample.shared.utils.SERVER_RESPONSE_EXCEPTION_RANGE
10 | import io.ktor.client.HttpClient
11 | import io.ktor.client.call.receive
12 | import io.ktor.client.features.ClientRequestException
13 | import io.ktor.client.features.HttpResponseValidator
14 | import io.ktor.client.features.RedirectResponseException
15 | import io.ktor.client.features.ResponseException
16 | import io.ktor.client.features.ServerResponseException
17 | import io.ktor.client.features.defaultRequest
18 | import io.ktor.client.features.json.JsonFeature
19 | import io.ktor.client.features.json.serializer.KotlinxSerializer
20 | import io.ktor.client.request.host
21 | import io.ktor.http.ContentType
22 | import io.ktor.http.contentType
23 |
24 | actual class HttpBaseClient {
25 | actual val httpClient: HttpClient = HttpClient {
26 | defaultRequest {
27 | host = ApiEndPoints.BASE.url
28 | contentType(ContentType.Application.Json)
29 | }
30 | // Validate Response
31 | expectSuccess = false
32 | // JSON Deserializer
33 | install(JsonFeature) {
34 | val json = kotlinx.serialization.json.Json {
35 | ignoreUnknownKeys = true
36 | coerceInputValues = true
37 | }
38 | serializer = KotlinxSerializer(json)
39 | }
40 | HttpResponseValidator {
41 | validateResponse { response ->
42 | when (response.status.value) {
43 | in REDIRECT_RESPONSE_EXCEPTION_RANGE -> throw RedirectResponseException(response, "")
44 | in CLIENT_REQUEST_EXCEPTION_RANGE -> throw ClientRequestException(response, "")
45 | in SERVER_RESPONSE_EXCEPTION_RANGE -> throw ServerResponseException(response, "")
46 | }
47 | if (response.status.value >= RESPONSE_EXCEPTION_CODE) {
48 | throw ResponseException(response, "")
49 | }
50 | }
51 | handleResponseException { cause ->
52 | var error = ErrorResponse()
53 | when (cause) {
54 | is ResponseException -> {
55 | error = cause.response.receive()
56 | error.statusCode = cause.response.status.value
57 | }
58 | is java.net.UnknownHostException -> {
59 | error = CustomException.getNoInternetError()
60 | }
61 | else -> CustomException.getDefaultError(cause.message)
62 | }
63 | throw CustomException(error)
64 | }
65 | }
66 | }
67 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/com/simform/kmmsample/shared/HttpBaseClient.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared
2 |
3 | import io.ktor.client.HttpClient
4 |
5 | expect class HttpBaseClient() {
6 | val httpClient: HttpClient
7 | }
8 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/base/CustomException.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared.datamodels.base
2 |
3 | @Suppress("TooGenericExceptionCaught")
4 | class CustomException(var errorResponse: ErrorResponse?) : Exception() {
5 |
6 | companion object {
7 |
8 | fun getDefaultError(errorMessage: String?): ErrorResponse {
9 | return ErrorResponse(1, "SOMETHING_WENT_WRONG", false)
10 | }
11 |
12 | fun getNoInternetError(): ErrorResponse {
13 | return ErrorResponse(1, "NO_INTERNET_CONNECTION", false)
14 | }
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/base/Either.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared.datamodels.base
2 |
3 | // This class will be used to handle failure and Success response from the api.
4 | sealed class Either {
5 | // Execute failure block if we receive any response from api else will execute success block in which we can get our data
6 | inline fun fold(failed: (F) -> T, succeeded: (S) -> T): T =
7 | when (this) {
8 | is Failure -> failed(failure)
9 | is Success -> succeeded(success)
10 | }
11 | }
12 |
13 | data class Failure(val failure: F) : Either()
14 |
15 | data class Success(val success: S) : Either()
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/base/ErrorResponse.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared.datamodels.base
2 |
3 | import kotlinx.serialization.SerialName
4 | import kotlinx.serialization.Serializable
5 |
6 | // Custom Error class according to error thrown by api
7 | @Serializable
8 | data class ErrorResponse (
9 | var statusCode: Int? = 0,
10 | @SerialName("status_message")
11 | var statusMessage: String? = "",
12 | var success: Boolean? = false
13 | )
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/responsemodels/MovieEnitity.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared.datamodels.responsemodels
2 |
3 | import com.simform.kmmsample.shared.utils.POSTER_URL
4 | import kotlinx.serialization.SerialName
5 | import kotlinx.serialization.Serializable
6 |
7 | @Serializable
8 | data class MovieEntity(
9 | val popularity: Double,
10 | val id: Int,
11 | val video: Boolean,
12 | @SerialName("vote_count") val voteCount: Int,
13 | @SerialName("vote_average") val voteAverage: Double,
14 | val title: String,
15 | @SerialName("release_date") val releaseDate: String,
16 | @SerialName("original_language") val originalLanguage: String,
17 | @SerialName("original_title") val originalTitle: String,
18 | @SerialName("genre_ids") val genreIds: List,
19 | @SerialName("backdrop_path") val backdropPath: String,
20 | val adult: Boolean,
21 | val overview: String,
22 | @SerialName("poster_path") var posterPath: String
23 | ) {
24 | val picturePoster: String get() = POSTER_URL + posterPath
25 | }
26 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/com/simform/kmmsample/shared/datamodels/responsemodels/PopularMoviesResponse.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared.datamodels.responsemodels
2 |
3 | import kotlinx.serialization.Serializable
4 |
5 | @Serializable
6 | data class PopularMoviesResponse (
7 | val page: Int,
8 | val results: ArrayList,
9 | val total_pages: Int,
10 | val total_results: Int
11 | )
12 |
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/com/simform/kmmsample/shared/remote/BaseApiClass.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared.remote
2 |
3 | import com.simform.kmmsample.shared.HttpBaseClient
4 | import com.simform.kmmsample.shared.datamodels.base.CustomException
5 | import com.simform.kmmsample.shared.datamodels.base.Either
6 | import com.simform.kmmsample.shared.datamodels.base.Failure
7 | import com.simform.kmmsample.shared.datamodels.base.Success
8 | import com.simform.kmmsample.shared.datamodels.responsemodels.PopularMoviesResponse
9 | import com.simform.kmmsample.shared.utils.API_KEY
10 | import com.simform.kmmsample.shared.utils.ApiEndPoints
11 | import com.simform.kmmsample.shared.utils.HEADER_AUTHORIZATION
12 | import io.ktor.client.request.get
13 | import io.ktor.client.request.parameter
14 |
15 | class BaseApiClass {
16 |
17 | //Create Http Client
18 | private var httpClient = HttpBaseClient().httpClient
19 |
20 | @Throws(Exception::class)
21 | suspend fun getMovies() : Either? {
22 | return try {
23 | val response = httpClient.get(ApiEndPoints.POPULAR_MOVIES.url) {
24 | parameter(HEADER_AUTHORIZATION, API_KEY)
25 | }
26 | Success(response)
27 | } catch (e: Exception) {
28 | Failure(e as CustomException)
29 | }
30 | }
31 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/com/simform/kmmsample/shared/utils/ApiEndPoints.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared.utils
2 |
3 | enum class ApiEndPoints(val url: String) {
4 | BASE(url = "api.themoviedb.org"), POPULAR_MOVIES("3/movie/popular")
5 | }
--------------------------------------------------------------------------------
/shared/src/commonMain/kotlin/com/simform/kmmsample/shared/utils/SharedConstants.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared.utils
2 |
3 | //Error response codes
4 | val REDIRECT_RESPONSE_EXCEPTION_RANGE = 300..399
5 | val CLIENT_REQUEST_EXCEPTION_RANGE = 400..499
6 | val SERVER_RESPONSE_EXCEPTION_RANGE = 500..599
7 | const val RESPONSE_EXCEPTION_CODE = 600
8 | const val UNAUTHORIZED_RESPONSE_CODE = 401
9 |
10 | // Movie model
11 | const val POSTER_URL = "https://image.tmdb.org/t/p/original"
12 | const val HEADER_AUTHORIZATION = "api_key"
13 | // Please add your private api key here
14 | const val API_KEY = ""
--------------------------------------------------------------------------------
/shared/src/iosMain/kotlin/com/simform/kmmsample/shared/HttpBaseClient.kt:
--------------------------------------------------------------------------------
1 | package com.simform.kmmsample.shared
2 |
3 | import com.simform.kmmsample.shared.datamodels.base.CustomException
4 | import com.simform.kmmsample.shared.datamodels.base.ErrorResponse
5 | import com.simform.kmmsample.shared.utils.ApiEndPoints
6 | import com.simform.kmmsample.shared.utils.CLIENT_REQUEST_EXCEPTION_RANGE
7 | import com.simform.kmmsample.shared.utils.REDIRECT_RESPONSE_EXCEPTION_RANGE
8 | import com.simform.kmmsample.shared.utils.RESPONSE_EXCEPTION_CODE
9 | import com.simform.kmmsample.shared.utils.SERVER_RESPONSE_EXCEPTION_RANGE
10 | import io.ktor.client.HttpClient
11 | import io.ktor.client.call.receive
12 | import io.ktor.client.engine.ios.Ios
13 | import io.ktor.client.engine.ios.IosHttpRequestException
14 | import io.ktor.client.features.ClientRequestException
15 | import io.ktor.client.features.HttpResponseValidator
16 | import io.ktor.client.features.RedirectResponseException
17 | import io.ktor.client.features.ResponseException
18 | import io.ktor.client.features.ServerResponseException
19 | import io.ktor.client.features.defaultRequest
20 | import io.ktor.client.features.json.JsonFeature
21 | import io.ktor.client.features.json.serializer.KotlinxSerializer
22 | import io.ktor.client.request.host
23 | import io.ktor.http.ContentType
24 | import io.ktor.http.contentType
25 | import platform.Foundation.setHTTPShouldUsePipelining
26 |
27 | actual class HttpBaseClient {
28 | actual val httpClient: HttpClient = HttpClient(Ios) {
29 | defaultRequest {
30 | host = ApiEndPoints.BASE.url
31 | contentType(ContentType.Application.Json)
32 | }
33 |
34 | engine {
35 | // this: IosClientEngineConfig
36 | configureRequest {
37 | setHTTPShouldUsePipelining(true)
38 | }
39 | }
40 |
41 | // Validate Response
42 | expectSuccess = false
43 | // JSON Deserializer
44 | install(JsonFeature) {
45 | val json = kotlinx.serialization.json.Json {
46 | ignoreUnknownKeys = true
47 | coerceInputValues = true
48 | }
49 | serializer = KotlinxSerializer(json)
50 | }
51 |
52 | HttpResponseValidator {
53 | validateResponse { response ->
54 | when (response.status.value) {
55 | in REDIRECT_RESPONSE_EXCEPTION_RANGE -> throw RedirectResponseException(response, "")
56 | in CLIENT_REQUEST_EXCEPTION_RANGE -> throw ClientRequestException(response, "")
57 | in SERVER_RESPONSE_EXCEPTION_RANGE -> throw ServerResponseException(response, "")
58 | }
59 | if (response.status.value >= RESPONSE_EXCEPTION_CODE) {
60 | throw ResponseException(response, "")
61 | }
62 | }
63 | handleResponseException { cause ->
64 | var error = ErrorResponse()
65 | when (cause) {
66 | is ResponseException -> {
67 | error = cause.response.receive()
68 | error.statusCode = cause.response.status.value
69 | }
70 | is IosHttpRequestException -> {
71 | CustomException.getNoInternetError()
72 | }
73 | else -> CustomException.getDefaultError(cause.message)
74 | }
75 | throw CustomException(error)
76 | }
77 | }
78 | }
79 | }
--------------------------------------------------------------------------------