├── .gitignore ├── LICENSE ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ └── main │ ├── AndroidManifest.xml │ ├── java │ └── id │ │ └── kotlin │ │ └── training │ │ └── movies │ │ ├── MovieApp.kt │ │ ├── data │ │ ├── Api.kt │ │ ├── local │ │ │ └── Movie.kt │ │ └── remote │ │ │ └── DiscoverMovie.kt │ │ ├── deps │ │ ├── component │ │ │ └── ApplicationComponent.kt │ │ ├── module │ │ │ ├── NetworkModule.kt │ │ │ └── ServiceModule.kt │ │ └── provider │ │ │ └── ApplicationProvider.kt │ │ ├── ext │ │ ├── Commons.kt │ │ ├── Configs.kt │ │ ├── Images.kt │ │ ├── ItemDecoration.kt │ │ ├── Networks.kt │ │ └── Rx.kt │ │ ├── services │ │ ├── DiscoverMovieService.kt │ │ ├── NetworkCallTransformer.kt │ │ └── NetworkCallback.kt │ │ └── view │ │ ├── base │ │ ├── Presenter.kt │ │ └── View.kt │ │ ├── detail │ │ ├── DetailActivity.kt │ │ ├── DetailPresenter.kt │ │ └── DetailView.kt │ │ └── home │ │ ├── MovieActivity.kt │ │ ├── MovieAdapter.kt │ │ ├── MovieListener.kt │ │ ├── MoviePresenter.kt │ │ └── MovieView.kt │ └── res │ ├── drawable │ └── ic_launcher_background.xml │ ├── layout │ ├── activity_detail.xml │ ├── activity_movie.xml │ ├── item_movie.xml │ └── layout_progressbar.xml │ ├── mipmap-anydpi-v26 │ ├── ic_launcher.xml │ └── ic_launcher_round.xml │ ├── mipmap-hdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-mdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ ├── mipmap-xxxhdpi │ ├── ic_launcher.png │ ├── ic_launcher_foreground.png │ └── ic_launcher_round.png │ └── values │ ├── colors.xml │ ├── strings.xml │ └── styles.xml ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the ART/Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | out/ 15 | 16 | # Gradle files 17 | .gradle/ 18 | build/ 19 | 20 | # Local configuration file (sdk path, etc) 21 | local.properties 22 | 23 | # Proguard folder generated by Eclipse 24 | proguard/ 25 | 26 | # Log Files 27 | *.log 28 | 29 | # Android Studio Navigation editor temp files 30 | .navigation/ 31 | 32 | # Android Studio captures folder 33 | captures/ 34 | 35 | # Intellij 36 | *.iml 37 | .idea/ 38 | 39 | # Keystore files 40 | *.jks 41 | 42 | # External native build folder generated in Android Studio 2.2 and later 43 | .externalNativeBuild 44 | 45 | # Google Services (e.g. APIs or Firebase) 46 | google-services.json 47 | 48 | # Freeline 49 | freeline.py 50 | freeline/ 51 | freeline_project_description.json 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 KotlinID 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 | # Android Movie MVP 2 | Android Movie MVP Architecture 3 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion 26 8 | buildToolsVersion "26.0.1" 9 | 10 | defaultConfig { 11 | applicationId "id.kotlin.training.movies" 12 | minSdkVersion 21 13 | targetSdkVersion 26 14 | versionCode 1 15 | versionName "1.0" 16 | multiDexEnabled true 17 | vectorDrawables.useSupportLibrary = true 18 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled false 23 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | dexOptions { 27 | javaMaxHeapSize "5g" 28 | preDexLibraries = false 29 | jumboMode true 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | 36 | implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" 37 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 38 | implementation "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version" 39 | 40 | implementation 'com.android.support:multidex:1.0.2' 41 | implementation 'com.android.support:support-v4:26.0.2' 42 | implementation 'com.android.support:appcompat-v7:26.0.2' 43 | implementation 'com.android.support:recyclerview-v7:26.0.2' 44 | implementation 'com.android.support:design:26.0.2' 45 | implementation 'com.android.support.constraint:constraint-layout:1.0.2' 46 | 47 | implementation 'com.google.dagger:dagger:2.11' 48 | kapt 'com.google.dagger:dagger-compiler:2.11' 49 | 50 | implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' 51 | implementation 'io.reactivex.rxjava2:rxjava:2.1.3' 52 | 53 | implementation 'com.squareup.retrofit2:retrofit:2.3.0' 54 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' 55 | implementation 'com.squareup.retrofit2:converter-gson:2.3.0' 56 | 57 | implementation 'com.squareup.okhttp3:okhttp:3.8.1' 58 | implementation 'com.squareup.okhttp3:logging-interceptor:3.8.1' 59 | 60 | implementation 'org.jetbrains.anko:anko:0.10.1' 61 | implementation 'org.jetbrains.anko:anko-commons:0.10.1' 62 | 63 | implementation 'com.google.code.gson:gson:2.8.1' 64 | 65 | implementation 'com.github.bumptech.glide:glide:4.1.0' 66 | kapt 'com.github.bumptech.glide:compiler:4.1.0' 67 | implementation 'com.github.bumptech.glide:okhttp3-integration:4.1.0@aar' 68 | 69 | implementation 'joda-time:joda-time:2.9.9' 70 | } 71 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 18 | 19 | 22 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/MovieApp.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies 2 | 3 | import android.support.multidex.MultiDexApplication 4 | import id.kotlin.training.movies.deps.component.ApplicationComponent 5 | import id.kotlin.training.movies.deps.component.DaggerApplicationComponent 6 | import id.kotlin.training.movies.deps.module.NetworkModule 7 | import id.kotlin.training.movies.deps.module.ServiceModule 8 | import id.kotlin.training.movies.deps.provider.ApplicationProvider 9 | 10 | class MovieApp : MultiDexApplication(), ApplicationProvider { 11 | 12 | private lateinit var component: ApplicationComponent 13 | 14 | override fun onCreate() { 15 | super.onCreate() 16 | 17 | component = DaggerApplicationComponent.builder() 18 | .networkModule(NetworkModule(this)) 19 | .serviceModule(ServiceModule()) 20 | .build() 21 | } 22 | 23 | override fun providesApplicationComponent(): ApplicationComponent = component 24 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/data/Api.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.data 2 | 3 | import id.kotlin.training.movies.data.remote.DiscoverMovie 4 | import io.reactivex.Flowable 5 | import retrofit2.http.GET 6 | import retrofit2.http.Query 7 | 8 | interface Api { 9 | 10 | @GET("3/discover/movie") 11 | fun discoverMovie(@Query("api_key") key: String, 12 | @Query("sort_by") sortBy: String): Flowable 13 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/data/local/Movie.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.data.local 2 | 3 | import android.os.Parcel 4 | import android.os.Parcelable 5 | import android.os.Parcelable.Creator 6 | 7 | data class Movie(var title: String, 8 | var desc: String, 9 | var date: String, 10 | var image: String, 11 | var vote: Double) : Parcelable { 12 | companion object CREATOR : Creator { 13 | override fun createFromParcel(parcel: Parcel): Movie { 14 | return Movie(parcel) 15 | } 16 | 17 | override fun newArray(size: Int): Array { 18 | return arrayOfNulls(size) 19 | } 20 | } 21 | 22 | constructor(parcel: Parcel) : this( 23 | parcel.readString(), 24 | parcel.readString(), 25 | parcel.readString(), 26 | parcel.readString(), 27 | parcel.readDouble()) 28 | 29 | override fun writeToParcel(parcel: Parcel, flags: Int) { 30 | parcel.writeString(title) 31 | parcel.writeString(desc) 32 | parcel.writeString(date) 33 | parcel.writeString(image) 34 | parcel.writeDouble(vote) 35 | } 36 | 37 | override fun describeContents(): Int { 38 | return 0 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/data/remote/DiscoverMovie.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.data.remote 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | data class DiscoverMovie(@SerializedName("page") val page: Int, 6 | @SerializedName("total_results") val totalResults: Long, 7 | @SerializedName("total_pages") val totalPages: Long, 8 | @SerializedName("results") val results: List) { 9 | 10 | data class Result(@SerializedName("id") val id: Int, 11 | @SerializedName("vote_count") val voteCount: Int, 12 | @SerializedName("video") val video: Boolean, 13 | @SerializedName("vote_average") val voteAverage: Double, 14 | @SerializedName("title") val title: String, 15 | @SerializedName("popularity") val popularity: Double, 16 | @SerializedName("poster_path") val posterPath: String, 17 | @SerializedName("original_language") val originalLanguage: String, 18 | @SerializedName("original_title") val originalTitle: String, 19 | @SerializedName("genre_ids") val genreIds: List, 20 | @SerializedName("backdrop_path") val backdropPath: String, 21 | @SerializedName("adult") val adult: Boolean, 22 | @SerializedName("overview") val overview: String, 23 | @SerializedName("release_date") val releaseDate: String) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/deps/component/ApplicationComponent.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.deps.component 2 | 3 | import dagger.Component 4 | import id.kotlin.training.movies.deps.module.NetworkModule 5 | import id.kotlin.training.movies.deps.module.ServiceModule 6 | import id.kotlin.training.movies.view.detail.DetailActivity 7 | import id.kotlin.training.movies.view.home.MovieActivity 8 | import javax.inject.Singleton 9 | 10 | @Singleton 11 | @Component(modules = arrayOf( 12 | NetworkModule::class, 13 | ServiceModule::class 14 | )) 15 | interface ApplicationComponent { 16 | 17 | fun inject(movieActivity: MovieActivity) 18 | 19 | fun inject(detailActivity: DetailActivity) 20 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/deps/module/NetworkModule.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.deps.module 2 | 3 | import android.content.Context 4 | import dagger.Module 5 | import dagger.Provides 6 | import id.kotlin.training.movies.data.Api 7 | import id.kotlin.training.movies.ext.Config 8 | import id.kotlin.training.movies.ext.Configs 9 | import id.kotlin.training.movies.ext.Network 10 | import id.kotlin.training.movies.ext.Networks 11 | import id.kotlin.training.movies.ext.clazz 12 | import okhttp3.Cache 13 | import okhttp3.ConnectionPool 14 | import okhttp3.Interceptor 15 | import okhttp3.OkHttpClient 16 | import okhttp3.logging.HttpLoggingInterceptor 17 | import retrofit2.Retrofit 18 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 19 | import retrofit2.converter.gson.GsonConverterFactory 20 | import java.util.concurrent.TimeUnit 21 | import javax.inject.Singleton 22 | 23 | @Module 24 | open class NetworkModule(private val context: Context) { 25 | 26 | @Provides 27 | @Singleton 28 | protected fun providesApi(cache: Cache, 29 | connectionPool: ConnectionPool): Api { 30 | 31 | fun getInterceptor(): Interceptor { 32 | return Interceptor { chain -> 33 | val request = chain.request() 34 | val builder = request.newBuilder() 35 | 36 | builder.addHeader("Content-Type", "application/json") 37 | chain.proceed(request) 38 | } 39 | } 40 | 41 | fun getOkHttpClient(cache: Cache, 42 | connectionPool: ConnectionPool): OkHttpClient { 43 | val httpLoggingInterceptor = HttpLoggingInterceptor() 44 | httpLoggingInterceptor.level = HttpLoggingInterceptor.Level.BODY 45 | 46 | return OkHttpClient.Builder() 47 | .cache(cache) 48 | .connectTimeout(30, TimeUnit.SECONDS) 49 | .readTimeout(15, TimeUnit.SECONDS) 50 | .writeTimeout(15, TimeUnit.SECONDS) 51 | .retryOnConnectionFailure(true) 52 | .addInterceptor(getInterceptor()) 53 | .addInterceptor(httpLoggingInterceptor) 54 | .connectionPool(connectionPool) 55 | .build() 56 | } 57 | 58 | @Configs val baseUrl = Config.BASE_URL 59 | val retrofit = Retrofit.Builder() 60 | .client(getOkHttpClient(cache, connectionPool)) 61 | .baseUrl(baseUrl) 62 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 63 | .addConverterFactory(GsonConverterFactory.create()) 64 | .build() 65 | 66 | return retrofit.create(clazz()) 67 | } 68 | 69 | @Provides 70 | @Singleton 71 | protected fun providesCache(): Cache { 72 | @Networks val cacheSize = Network.CACHE_SIZE 73 | return Cache(context.externalCacheDir, cacheSize) 74 | } 75 | 76 | @Provides 77 | @Singleton 78 | protected fun providesConnectionPool(): ConnectionPool { 79 | @Networks val maxIdleConnections = Network.MAX_IDLE_CONNECTIONS.toInt() 80 | @Networks val keepAliveDurations = Network.KEEP_ALIVE_DURATION 81 | 82 | return ConnectionPool(maxIdleConnections, keepAliveDurations, TimeUnit.SECONDS) 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/deps/module/ServiceModule.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.deps.module 2 | 3 | import dagger.Module 4 | import dagger.Provides 5 | import id.kotlin.training.movies.data.Api 6 | import id.kotlin.training.movies.services.DiscoverMovieService 7 | import javax.inject.Singleton 8 | 9 | @Module 10 | open class ServiceModule { 11 | 12 | @Provides 13 | @Singleton 14 | protected fun providesDiscoverMovieService(api: Api) = DiscoverMovieService(api) 15 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/deps/provider/ApplicationProvider.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.deps.provider 2 | 3 | import id.kotlin.training.movies.deps.component.ApplicationComponent 4 | 5 | interface ApplicationProvider { 6 | 7 | fun providesApplicationComponent(): ApplicationComponent 8 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/ext/Commons.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.ext 2 | 3 | import android.content.Context 4 | import android.widget.ImageView 5 | import com.bumptech.glide.Glide 6 | import com.bumptech.glide.MemoryCategory 7 | import com.bumptech.glide.load.engine.DiskCacheStrategy 8 | import org.joda.time.DateTime 9 | import org.joda.time.format.DateTimeFormat 10 | 11 | const val DEFAULT_DATE = "dd MMMM yyyy" 12 | 13 | internal inline fun clazz() = T::class.java 14 | 15 | internal fun getDate(date: String): String { 16 | val format = DateTimeFormat.forPattern(DEFAULT_DATE) 17 | return DateTime(date).toString(format) 18 | } 19 | 20 | internal fun loadImage(context: Context, 21 | url: String, 22 | imageView: ImageView) { 23 | 24 | fun setMemoryCategory(context: Context) { 25 | Glide.get(context).setMemoryCategory(MemoryCategory.NORMAL) 26 | } 27 | 28 | setMemoryCategory(context) 29 | GlideApp.with(context) 30 | .load(url) 31 | .centerCrop() 32 | .diskCacheStrategy(DiskCacheStrategy.RESOURCE) 33 | .into(imageView) 34 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/ext/Configs.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.ext 2 | 3 | import android.support.annotation.StringDef 4 | import kotlin.annotation.AnnotationRetention.SOURCE 5 | 6 | @Retention(SOURCE) 7 | @StringDef( 8 | Config.BASE_URL, 9 | Config.BASE_MOVIE_URL, 10 | Config.API_KEY, 11 | Config.DEFAULT_SORT 12 | ) 13 | annotation class Configs 14 | 15 | object Config { 16 | const val BASE_URL = "https://api.themoviedb.org/" 17 | const val BASE_MOVIE_URL = "https://image.tmdb.org/t/p/w185_and_h278_bestv2" 18 | const val API_KEY = "37dc2d42ed324228aa382f0554ad1b28" 19 | const val DEFAULT_SORT = "popularity.desc" 20 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/ext/Images.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.ext 2 | 3 | import com.bumptech.glide.annotation.GlideModule 4 | import com.bumptech.glide.module.AppGlideModule 5 | 6 | @GlideModule 7 | class Images : AppGlideModule() -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/ext/ItemDecoration.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.ext 2 | 3 | import android.graphics.Rect 4 | import android.support.v7.widget.GridLayoutManager 5 | import android.support.v7.widget.LinearLayoutManager 6 | import android.support.v7.widget.RecyclerView 7 | import android.support.v7.widget.RecyclerView.ItemDecoration 8 | import android.support.v7.widget.RecyclerView.State 9 | import android.support.v7.widget.StaggeredGridLayoutManager 10 | import android.view.View 11 | 12 | class ItemDecoration( 13 | private val horizontalSpacing: Int, 14 | private val verticalSpacing: Int, 15 | private val includeEdge: Boolean 16 | ) : ItemDecoration() { 17 | 18 | override fun getItemOffsets(outRect: Rect?, 19 | view: View?, 20 | parent: RecyclerView?, 21 | state: State?) { 22 | super.getItemOffsets(outRect, view, parent, state) 23 | 24 | fun getGridItemOffsets(outRect: Rect?, 25 | position: Int?, 26 | column: Int?, 27 | spanCount: Int?) { 28 | val spans = spanCount ?: 0 29 | val columns = column ?: 0 30 | 31 | if (includeEdge) { 32 | outRect?.left = horizontalSpacing * (spans - columns) / spans 33 | 34 | if (position ?: 0 < spans) { 35 | outRect?.top = verticalSpacing 36 | } 37 | 38 | outRect?.bottom = verticalSpacing 39 | } else { 40 | outRect?.left = horizontalSpacing * columns / spans 41 | outRect?.right = horizontalSpacing * (spans - 1 - columns) / spans 42 | 43 | if (position ?: 0 >= spans) { 44 | outRect?.top = verticalSpacing 45 | } 46 | } 47 | } 48 | 49 | val position = parent?.getChildAdapterPosition(view) 50 | when (parent?.layoutManager) { 51 | is GridLayoutManager -> { 52 | val layoutManager = parent.layoutManager as GridLayoutManager 53 | val spanCount = layoutManager.spanCount 54 | val column = position?.rem(spanCount) 55 | 56 | getGridItemOffsets(outRect, position, column, spanCount) 57 | } 58 | is StaggeredGridLayoutManager -> { 59 | val layoutManager = parent.layoutManager as StaggeredGridLayoutManager 60 | val spanCount = layoutManager.spanCount 61 | val layoutParams = view?.layoutParams as? StaggeredGridLayoutManager.LayoutParams 62 | val column = layoutParams?.spanIndex 63 | 64 | getGridItemOffsets(outRect, position, column, spanCount) 65 | } 66 | is LinearLayoutManager -> { 67 | outRect?.left = horizontalSpacing 68 | outRect?.right = horizontalSpacing 69 | 70 | if (includeEdge) { 71 | if (position == 0) { 72 | outRect?.top = verticalSpacing 73 | } 74 | 75 | outRect?.bottom = verticalSpacing 76 | } else { 77 | if (position ?: 0 > 0) { 78 | outRect?.top = verticalSpacing 79 | } 80 | } 81 | } 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/ext/Networks.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.ext 2 | 3 | import android.support.annotation.IntDef 4 | import kotlin.annotation.AnnotationRetention.SOURCE 5 | 6 | @Retention(SOURCE) 7 | @IntDef( 8 | Network.MAX_IDLE_CONNECTIONS, 9 | Network.KEEP_ALIVE_DURATION, 10 | Network.CACHE_SIZE 11 | ) 12 | annotation class Networks 13 | 14 | object Network { 15 | const val MAX_IDLE_CONNECTIONS = 15L 16 | const val KEEP_ALIVE_DURATION = 30L * 1000L 17 | const val CACHE_SIZE = 30L * 1024L * 1024L 18 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/ext/Rx.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.ext 2 | 3 | import io.reactivex.disposables.CompositeDisposable 4 | import io.reactivex.subscribers.DisposableSubscriber 5 | 6 | internal fun CompositeDisposable?.safeDispose() { 7 | this?.let { this.clear() } 8 | } 9 | 10 | internal fun disposableSubscriber(next: (T) -> Unit = {}, 11 | error: (Throwable) -> Unit = {}, 12 | complete: () -> Unit = {}): DisposableSubscriber { 13 | return object : DisposableSubscriber() { 14 | override fun onNext(response: T) { 15 | next(response) 16 | } 17 | 18 | override fun onError(e: Throwable) { 19 | error(e) 20 | } 21 | 22 | override fun onComplete() { 23 | complete() 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/services/DiscoverMovieService.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.services 2 | 3 | import id.kotlin.training.movies.data.Api 4 | import id.kotlin.training.movies.data.remote.DiscoverMovie 5 | import id.kotlin.training.movies.ext.disposableSubscriber 6 | import io.reactivex.Flowable 7 | import io.reactivex.disposables.Disposable 8 | import io.reactivex.functions.Function 9 | 10 | class DiscoverMovieService(private val api: Api) { 11 | 12 | fun discoverMovie(key: String, 13 | sortBy: String, 14 | callback: NetworkCallback): Disposable { 15 | return api.discoverMovie(key, sortBy) 16 | .compose(NetworkCallTransformer()) 17 | .onErrorResumeNext(Function { Flowable.error { it } }) 18 | .subscribeWith(disposableSubscriber( 19 | next = { response -> callback.onSuccess(response) }, 20 | error = { exception -> callback.onError(exception) } 21 | )) 22 | } 23 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/services/NetworkCallTransformer.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.services 2 | 3 | import io.reactivex.Flowable 4 | import io.reactivex.FlowableTransformer 5 | import io.reactivex.android.schedulers.AndroidSchedulers 6 | import io.reactivex.schedulers.Schedulers 7 | import org.reactivestreams.Publisher 8 | 9 | class NetworkCallTransformer : FlowableTransformer { 10 | 11 | override fun apply(upstream: Flowable): Publisher = 12 | upstream.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) 13 | .unsubscribeOn(Schedulers.io()) 14 | .cache() 15 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/services/NetworkCallback.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.services 2 | 3 | interface NetworkCallback { 4 | 5 | fun onSuccess(response: T) 6 | 7 | fun onError(e: Throwable) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/base/Presenter.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.base 2 | 3 | interface Presenter { 4 | 5 | fun onAttach(view: T) 6 | 7 | fun onDetach() 8 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/base/View.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.base 2 | 3 | interface View { 4 | 5 | fun onAttach() 6 | 7 | fun onDetach() 8 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/detail/DetailActivity.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.detail 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.app.AppCompatDelegate 6 | import android.view.MenuItem 7 | import id.kotlin.training.movies.R 8 | import id.kotlin.training.movies.data.local.Movie 9 | import id.kotlin.training.movies.deps.provider.ApplicationProvider 10 | import id.kotlin.training.movies.ext.loadImage 11 | import kotlinx.android.synthetic.main.activity_detail.* 12 | 13 | class DetailActivity : AppCompatActivity(), DetailView { 14 | 15 | private var presenter: DetailPresenter? = null 16 | private var movie: Movie? = null 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_detail) 21 | (application as ApplicationProvider).providesApplicationComponent() 22 | .inject(this) 23 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) 24 | 25 | fun initView() { 26 | movie = intent.getParcelableExtra("MOVIE") 27 | supportActionBar?.title = movie?.title 28 | supportActionBar?.setDisplayHomeAsUpEnabled(true) 29 | } 30 | 31 | initView() 32 | } 33 | 34 | override fun onDestroy() { 35 | onDetach() 36 | super.onDestroy() 37 | } 38 | 39 | override fun onResume() { 40 | presenter = DetailPresenter() 41 | onAttach() 42 | super.onResume() 43 | } 44 | 45 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 46 | when (item?.itemId) { 47 | android.R.id.home -> onBackPressed() 48 | } 49 | 50 | return super.onOptionsItemSelected(item) 51 | } 52 | 53 | override fun onAttach() { 54 | presenter?.onAttach(this) 55 | presenter?.setMovieDetail(movie) 56 | } 57 | 58 | override fun onDetach() { 59 | presenter?.onDetach() 60 | } 61 | 62 | override fun onDetailMovie(image: String?, 63 | desc: String?, 64 | date: String?, 65 | vote: Double?) { 66 | loadImage(this, image ?: "", ivDetail) 67 | 68 | tvDetailDescValue.text = desc 69 | tvDetailDateValue.text = date 70 | tvDetailRatingValue.text = vote.toString() 71 | } 72 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/detail/DetailPresenter.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.detail 2 | 3 | import id.kotlin.training.movies.data.local.Movie 4 | import id.kotlin.training.movies.ext.Configs 5 | import id.kotlin.training.movies.view.base.Presenter 6 | 7 | class DetailPresenter : Presenter { 8 | 9 | private var view: DetailView? = null 10 | 11 | override fun onAttach(view: DetailView) { 12 | this.view = view 13 | } 14 | 15 | override fun onDetach() { 16 | view = null 17 | } 18 | 19 | fun setMovieDetail(movie: Movie?) { 20 | @Configs val image = movie?.image 21 | 22 | val desc = movie?.desc 23 | val date = movie?.date 24 | val vote = movie?.vote 25 | 26 | view?.onDetailMovie(image, desc, date, vote) 27 | } 28 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/detail/DetailView.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.detail 2 | 3 | import id.kotlin.training.movies.view.base.View 4 | 5 | interface DetailView : View { 6 | 7 | fun onDetailMovie(image: String?, 8 | desc: String?, 9 | date: String?, 10 | vote: Double?) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/home/MovieActivity.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.home 2 | 3 | import android.os.Bundle 4 | import android.support.v7.app.AppCompatActivity 5 | import android.support.v7.app.AppCompatDelegate 6 | import android.support.v7.widget.GridLayoutManager 7 | import android.support.v7.widget.LinearLayoutManager 8 | import android.view.View 9 | import id.kotlin.training.movies.R 10 | import id.kotlin.training.movies.data.local.Movie 11 | import id.kotlin.training.movies.data.remote.DiscoverMovie 12 | import id.kotlin.training.movies.deps.provider.ApplicationProvider 13 | import id.kotlin.training.movies.ext.Config 14 | import id.kotlin.training.movies.ext.Configs 15 | import id.kotlin.training.movies.ext.ItemDecoration 16 | import id.kotlin.training.movies.ext.getDate 17 | import id.kotlin.training.movies.services.DiscoverMovieService 18 | import id.kotlin.training.movies.view.detail.DetailActivity 19 | import kotlinx.android.synthetic.main.activity_movie.* 20 | import org.jetbrains.anko.startActivity 21 | import javax.inject.Inject 22 | 23 | open class MovieActivity : AppCompatActivity(), MovieView { 24 | 25 | @Inject protected lateinit var service: DiscoverMovieService 26 | 27 | private var presenter: MoviePresenter? = null 28 | 29 | override fun onCreate(savedInstanceState: Bundle?) { 30 | super.onCreate(savedInstanceState) 31 | setContentView(R.layout.activity_movie) 32 | (application as ApplicationProvider).providesApplicationComponent() 33 | .inject(this) 34 | AppCompatDelegate.setCompatVectorFromResourcesEnabled(true) 35 | 36 | fun initView() { 37 | supportActionBar?.title = title 38 | 39 | val layoutManager = GridLayoutManager( 40 | this, 41 | 3, 42 | LinearLayoutManager.VERTICAL, 43 | false 44 | ) 45 | rvMovie.layoutManager = layoutManager 46 | rvMovie.addItemDecoration(ItemDecoration( 47 | 30, 48 | 30, 49 | true 50 | )) 51 | } 52 | 53 | initView() 54 | } 55 | 56 | override fun onDestroy() { 57 | onDetach() 58 | super.onDestroy() 59 | } 60 | 61 | override fun onResume() { 62 | presenter = MoviePresenter() 63 | onAttach() 64 | super.onResume() 65 | } 66 | 67 | override fun onAttach() { 68 | presenter?.onAttach(this) 69 | presenter?.discoverMovie(service) 70 | } 71 | 72 | override fun onDetach() { 73 | presenter?.onDetach() 74 | } 75 | 76 | override fun onProgress() { 77 | pbMovie.visibility = View.VISIBLE 78 | } 79 | 80 | override fun onSuccess(response: DiscoverMovie) { 81 | @Configs val movieBaseUrl = Config.BASE_MOVIE_URL 82 | 83 | val data = response.results 84 | val movies = data.map { 85 | val title = it.title 86 | val desc = it.overview 87 | val date = getDate(it.releaseDate) 88 | val images = movieBaseUrl.plus(it.posterPath) 89 | val vote = it.voteAverage 90 | 91 | Movie(title, desc, date, images, vote) 92 | } 93 | 94 | val adapter = MovieAdapter(this, movies, object : MovieListener { 95 | override fun onClick(movie: Movie) { 96 | presenter?.openMovieDetail(movie) 97 | } 98 | }) 99 | rvMovie.adapter = adapter 100 | adapter.notifyDataSetChanged() 101 | 102 | fun hide() { 103 | pbMovie.visibility = View.GONE 104 | rvMovie.visibility = View.VISIBLE 105 | } 106 | 107 | hide() 108 | } 109 | 110 | override fun onOpenMovieDetail(movie: Movie) { 111 | startActivity( 112 | "MOVIE" to movie 113 | ) 114 | } 115 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/home/MovieAdapter.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.home 2 | 3 | import android.content.Context 4 | import android.support.v7.widget.RecyclerView.Adapter 5 | import android.support.v7.widget.RecyclerView.ViewHolder 6 | import android.view.LayoutInflater 7 | import android.view.View 8 | import android.view.ViewGroup 9 | import id.kotlin.training.movies.R 10 | import id.kotlin.training.movies.data.local.Movie 11 | import id.kotlin.training.movies.ext.loadImage 12 | import id.kotlin.training.movies.view.home.MovieAdapter.MovieHolder 13 | import kotlinx.android.synthetic.main.item_movie.view.* 14 | 15 | class MovieAdapter( 16 | private val context: Context, 17 | private val movies: List, 18 | private val listener: MovieListener 19 | ) : Adapter() { 20 | 21 | override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): MovieHolder = MovieHolder( 22 | LayoutInflater.from(parent?.context).inflate( 23 | R.layout.item_movie, 24 | parent, 25 | false 26 | ) 27 | ) 28 | 29 | override fun onBindViewHolder(holder: MovieHolder?, position: Int) { 30 | val movie = movies[holder?.adapterPosition ?: 0] 31 | holder?.bindView(context, movie, listener) 32 | } 33 | 34 | override fun getItemCount(): Int = movies.size 35 | 36 | class MovieHolder(itemView: View) : ViewHolder(itemView) { 37 | 38 | fun bindView(context: Context, 39 | movie: Movie, 40 | listener: MovieListener) { 41 | with(movie) { 42 | loadImage(context, movie.image, itemView.ivMovie) 43 | itemView.ivMovie.setOnClickListener { listener.onClick(movie) } 44 | } 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/home/MovieListener.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.home 2 | 3 | import id.kotlin.training.movies.data.local.Movie 4 | 5 | interface MovieListener { 6 | 7 | fun onClick(movie: Movie) 8 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/home/MoviePresenter.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.home 2 | 3 | import android.util.Log 4 | import id.kotlin.training.movies.data.local.Movie 5 | import id.kotlin.training.movies.data.remote.DiscoverMovie 6 | import id.kotlin.training.movies.ext.Config 7 | import id.kotlin.training.movies.ext.Configs 8 | import id.kotlin.training.movies.ext.safeDispose 9 | import id.kotlin.training.movies.services.DiscoverMovieService 10 | import id.kotlin.training.movies.services.NetworkCallback 11 | import id.kotlin.training.movies.view.base.Presenter 12 | import io.reactivex.disposables.CompositeDisposable 13 | 14 | class MoviePresenter : Presenter { 15 | 16 | private var view: MovieView? = null 17 | private var disposables: CompositeDisposable? = null 18 | 19 | override fun onAttach(view: MovieView) { 20 | this.view = view 21 | disposables = CompositeDisposable() 22 | } 23 | 24 | override fun onDetach() { 25 | view = null 26 | disposables.safeDispose() 27 | } 28 | 29 | fun discoverMovie(service: DiscoverMovieService) { 30 | view?.onProgress() 31 | 32 | @Configs val apiKey = Config.API_KEY 33 | @Configs val defaultSort = Config.DEFAULT_SORT 34 | 35 | val disposable = service.discoverMovie(apiKey, defaultSort, 36 | object : NetworkCallback { 37 | override fun onSuccess(response: DiscoverMovie) { 38 | view?.onSuccess(response) 39 | } 40 | 41 | override fun onError(e: Throwable) { 42 | Log.e("MOVIE", e.message, e) 43 | } 44 | }) 45 | 46 | disposable.let { disposables?.add(it) } 47 | } 48 | 49 | fun openMovieDetail(movie: Movie) { 50 | view?.onOpenMovieDetail(movie) 51 | } 52 | } -------------------------------------------------------------------------------- /app/src/main/java/id/kotlin/training/movies/view/home/MovieView.kt: -------------------------------------------------------------------------------- 1 | package id.kotlin.training.movies.view.home 2 | 3 | import id.kotlin.training.movies.data.local.Movie 4 | import id.kotlin.training.movies.data.remote.DiscoverMovie 5 | import id.kotlin.training.movies.view.base.View 6 | 7 | interface MovieView : View { 8 | 9 | fun onProgress() 10 | 11 | fun onSuccess(response: DiscoverMovie) 12 | 13 | fun onOpenMovieDetail(movie: Movie) 14 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 18 | 23 | 28 | 33 | 38 | 43 | 48 | 53 | 58 | 63 | 68 | 73 | 78 | 83 | 88 | 93 | 98 | 103 | 108 | 113 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_detail.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 18 | 19 | 22 | 23 | 28 | 29 | 39 | 40 | 49 | 50 | 60 | 61 | 70 | 71 | 81 | 82 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_movie.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 14 | 15 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_movie.xml: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 18 | -------------------------------------------------------------------------------- /app/src/main/res/layout/layout_progressbar.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #FFFFFF 4 | #000000 5 | #000000 6 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | Movies 4 | Description 5 | Date 6 | Rating 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 8 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | ext.kotlin_version = '1.1.4-3' 3 | 4 | repositories { 5 | google() 6 | jcenter() 7 | } 8 | dependencies { 9 | classpath 'com.android.tools.build:gradle:3.0.0-beta3' 10 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 11 | } 12 | } 13 | 14 | allprojects { 15 | repositories { 16 | google() 17 | jcenter() 18 | } 19 | } 20 | 21 | task clean(type: Delete) { 22 | delete rootProject.buildDir 23 | } 24 | 25 | task wrapper(type: Wrapper) { 26 | gradleVersion = '4.1' 27 | } 28 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx5632m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KotlinID/android-movie-mvp/cde2b2a37d8a4cbd880146617af39b827dd55ae4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | zipStoreBase=GRADLE_USER_HOME 4 | zipStorePath=wrapper/dists 5 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip 6 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------