├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── drawable-hdpi │ │ │ │ ├── ic_close.png │ │ │ │ └── ic_search.png │ │ │ ├── drawable-mdpi │ │ │ │ ├── ic_close.png │ │ │ │ └── ic_search.png │ │ │ ├── drawable-xhdpi │ │ │ │ ├── ic_close.png │ │ │ │ └── ic_search.png │ │ │ ├── drawable-xxhdpi │ │ │ │ ├── ic_close.png │ │ │ │ └── ic_search.png │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ ├── ic_launcher_round.png │ │ │ │ └── ic_launcher_foreground.png │ │ │ ├── transition │ │ │ │ ├── transition_main_exit.xml │ │ │ │ ├── transition_main_reenter.xml │ │ │ │ ├── transition_cinema_detail_enter.xml │ │ │ │ └── transition_shared_element.xml │ │ │ ├── transition-v21 │ │ │ │ └── transition_cinema_detail_enter.xml │ │ │ ├── drawable │ │ │ │ ├── bg_indicator_view.xml │ │ │ │ ├── more_info_button_selected.xml │ │ │ │ ├── rating_background_green.xml │ │ │ │ ├── rating_background_orange.xml │ │ │ │ ├── search_background.xml │ │ │ │ ├── ic_big_cinema_in_one_row.xml │ │ │ │ ├── more_info_button.xml │ │ │ │ ├── ic_sort.xml │ │ │ │ ├── ic_download.xml │ │ │ │ ├── ic_add.xml │ │ │ │ ├── ic_home.xml │ │ │ │ ├── ic_arrow_up.xml │ │ │ │ ├── ic_star.xml │ │ │ │ ├── ic_dashboard_black_24dp.xml │ │ │ │ ├── ic_small_cinema_in_one_row.xml │ │ │ │ ├── ic_delete.xml │ │ │ │ ├── ic_star_black_24dp.xml │ │ │ │ ├── rectangle_background.xml │ │ │ │ ├── ic_actor_default_avatar.xml │ │ │ │ ├── ic_cinema.xml │ │ │ │ ├── more_info_button_effect.xml │ │ │ │ ├── ic_date.xml │ │ │ │ ├── ic_watch.xml │ │ │ │ ├── ic_schedule.xml │ │ │ │ ├── ic_share.xml │ │ │ │ ├── ic_action_search.xml │ │ │ │ └── ic_settings.xml │ │ │ ├── values │ │ │ │ ├── attrs.xml │ │ │ │ ├── arrays.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── colors.xml │ │ │ │ └── styles.xml │ │ │ ├── layout │ │ │ │ ├── item_actor_photo.xml │ │ │ │ ├── item_small_cinema_header.xml │ │ │ │ ├── fragment_cinema_detail_content.xml │ │ │ │ ├── fragment_list_cinema.xml │ │ │ │ ├── fragment_actors.xml │ │ │ │ ├── fragment_small_cinemas.xml │ │ │ │ ├── item_slider_poster.xml │ │ │ │ ├── nav_header.xml │ │ │ │ ├── cinema_detail_floating_action_button_menu.xml │ │ │ │ ├── activity_posters_slider.xml │ │ │ │ ├── activity_favourite_list_cinema.xml │ │ │ │ └── activity_popular_actors.xml │ │ │ ├── menu │ │ │ │ ├── menu_main.xml │ │ │ │ ├── menu_navigation.xml │ │ │ │ └── menu_drawer_items.xml │ │ │ ├── drawable-v21 │ │ │ │ └── more_info_button_effect.xml │ │ │ ├── xml │ │ │ │ └── pref_mediateka_settings.xml │ │ │ └── values-v21 │ │ │ │ └── styles.xml │ │ └── java │ │ │ └── com │ │ │ └── ru │ │ │ └── devit │ │ │ └── mediateka │ │ │ ├── presentation │ │ │ ├── base │ │ │ │ ├── BaseView.java │ │ │ │ └── BasePresenter.java │ │ │ ├── main │ │ │ │ ├── SyncConnectionListener.java │ │ │ │ └── MainPresenter.java │ │ │ ├── common │ │ │ │ ├── OnActorClickListener.java │ │ │ │ ├── OnCinemaClickListener.java │ │ │ │ ├── HolderRenderer.java │ │ │ │ ├── CinemaTabSelectorView.java │ │ │ │ ├── OnTabSelectedListener.java │ │ │ │ ├── OnPageScrolled.java │ │ │ │ ├── ViewPagerAdapter.java │ │ │ │ ├── AbstractCinemaListAdapter.java │ │ │ │ └── PhotoLoader.kt │ │ │ ├── settings │ │ │ │ ├── SettingsActivity.java │ │ │ │ └── PreferenceSettingsFragment.java │ │ │ ├── smallcinemalist │ │ │ │ └── SmallCinemaHeaderViewHolder.java │ │ │ ├── cinemadetail │ │ │ │ └── CinemaDetailContentPresenter.java │ │ │ ├── posterslider │ │ │ │ ├── PosterSliderPresenter.java │ │ │ │ └── PosterSliderAdapter.java │ │ │ ├── actordetail │ │ │ │ ├── ActorDetailContentPresenter.java │ │ │ │ └── ActorDetailPresenter.java │ │ │ ├── cinemalist │ │ │ │ ├── CinemaListAdapter.java │ │ │ │ └── CinemaTab.java │ │ │ ├── actorlist │ │ │ │ ├── ActorListAdapter.java │ │ │ │ ├── ActorViewHolder.java │ │ │ │ └── ActorsPresenter.java │ │ │ ├── popularactors │ │ │ │ └── PopularActorsPresenter.java │ │ │ ├── widget │ │ │ │ ├── HideableFABBehaviour.kt │ │ │ │ ├── CinemaHeaderView.java │ │ │ │ └── DateAndTimePicker.java │ │ │ ├── favouritelistcinema │ │ │ │ └── CinemaSortingDialog.java │ │ │ └── search │ │ │ │ └── SearchPresenter.java │ │ │ ├── di │ │ │ ├── ActivityScope.java │ │ │ ├── actor │ │ │ │ ├── ActorScope.java │ │ │ │ ├── ActorListComponent.java │ │ │ │ ├── ActorComponent.java │ │ │ │ ├── ActorDetailComponent.java │ │ │ │ ├── ActorListModule.java │ │ │ │ ├── ActorDetailModule.java │ │ │ │ └── ActorModule.java │ │ │ ├── cinema │ │ │ │ ├── CinemaScope.java │ │ │ │ ├── cinemalist │ │ │ │ │ ├── CinemaListComponent.java │ │ │ │ │ └── CinemaListModule.java │ │ │ │ ├── cinemafavourite │ │ │ │ │ ├── CinemaFavouriteListComponent.java │ │ │ │ │ └── CinemaFavouriteListModule.java │ │ │ │ ├── cinemadetail │ │ │ │ │ └── CinemaDetailComponent.java │ │ │ │ ├── CinemaComponent.java │ │ │ │ └── CinemaModule.java │ │ │ ├── application │ │ │ │ ├── AppComponent.java │ │ │ │ ├── AppModule.java │ │ │ │ └── RetrofitModule.java │ │ │ └── ComponentsManager.java │ │ │ ├── domain │ │ │ ├── SystemTimeCalculator.java │ │ │ ├── UseCaseSubscriber.java │ │ │ ├── ActorRepository.java │ │ │ ├── CinemaRepository.java │ │ │ ├── actorusecases │ │ │ │ ├── GetActorById.java │ │ │ │ ├── GetActors.java │ │ │ │ └── GetActorsByQuery.java │ │ │ ├── Actions.java │ │ │ ├── cinemausecases │ │ │ │ ├── GetCinemas.java │ │ │ │ ├── GetTopRatedCinemas.java │ │ │ │ ├── GetUpComingCinemas.java │ │ │ │ └── GetCinemaById.java │ │ │ └── UseCase.java │ │ │ ├── models │ │ │ ├── network │ │ │ │ ├── Poster.java │ │ │ │ ├── Credits.java │ │ │ │ ├── ImagesResponse.java │ │ │ │ ├── ActorResponse.java │ │ │ │ ├── CinemaResponse.java │ │ │ │ ├── CrewNetwork.java │ │ │ │ ├── ActorNetwork.java │ │ │ │ └── CinemaNetwork.java │ │ │ ├── IntArrayConverter.java │ │ │ ├── mapper │ │ │ │ ├── Mapper.java │ │ │ │ └── CinemaMapper.java │ │ │ ├── model │ │ │ │ └── DateAndTimeInfo.java │ │ │ └── db │ │ │ │ └── CinemaActorJoinEntity.java │ │ │ ├── data │ │ │ ├── datasource │ │ │ │ ├── db │ │ │ │ │ ├── MediatekaDatabase.java │ │ │ │ │ ├── CinemaActorJoinDao.java │ │ │ │ │ ├── ActorDao.java │ │ │ │ │ └── CinemaDao.java │ │ │ │ └── network │ │ │ │ │ └── CinemaApiService.java │ │ │ ├── SharedPreferenceManager.java │ │ │ ├── ConnectionReceiver.java │ │ │ └── repository │ │ │ │ └── actor │ │ │ │ └── ActorLocalRepository.java │ │ │ ├── utils │ │ │ ├── AnimUtils.java │ │ │ ├── UrlImagePathCreator.kt │ │ │ └── pagination │ │ │ │ └── PaginationScrollListener.java │ │ │ └── MediatekaApp.java │ ├── test │ │ └── java │ │ │ └── com │ │ │ └── ru │ │ │ └── devit │ │ │ └── mediateka │ │ │ ├── UnitTest.java │ │ │ ├── presentation │ │ │ ├── posterslider │ │ │ │ └── PosterSliderPresenterTest.java │ │ │ ├── actordetail │ │ │ │ ├── ActorDetailContentPresenterTest.java │ │ │ │ └── ActorDetailPresenterTest.java │ │ │ ├── cinemadetail │ │ │ │ ├── CinemaDetailContentPresenterTest.java │ │ │ │ └── CinemaDetailPresenterTest.java │ │ │ ├── main │ │ │ │ └── MainPresenterTest.java │ │ │ ├── actorlist │ │ │ │ └── ActorsPresenterTest.java │ │ │ ├── search │ │ │ │ └── SearchPresenterTest.java │ │ │ └── smallcinemalist │ │ │ │ └── SmallCinemasPresenterTest.java │ │ │ └── models │ │ │ └── mapper │ │ │ ├── ActorDetailResponseToActorTest.java │ │ │ ├── CinemaEntityToCinemaTest.java │ │ │ └── CinemaResponseToCinemaTest.java │ └── androidTest │ │ └── java │ │ └── com │ │ └── ru │ │ └── devit │ │ └── mediateka │ │ └── ExampleInstrumentedTest.java ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── logo license ├── .gitignore ├── gradle.properties ├── .travis.yml ├── gradle-scripts └── dependencies.gradle └── gradlew.bat /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/drawable-hdpi/ic_close.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-hdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/drawable-hdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/drawable-mdpi/ic_close.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-mdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/drawable-mdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/drawable-xhdpi/ic_close.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/drawable-xhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_close.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/drawable-xxhdpi/ic_close.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable-xxhdpi/ic_search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/drawable-xxhdpi/ic_search.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PopPsyA/Mediateka/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/base/BaseView.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.base; 2 | 3 | public interface BaseView { 4 | void showLoading(); 5 | void hideLoading(); 6 | } 7 | -------------------------------------------------------------------------------- /app/src/main/res/transition/transition_main_exit.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/main/SyncConnectionListener.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.main; 2 | 3 | public interface SyncConnectionListener { 4 | void onNetworkConnectionChanged(boolean connected); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/res/transition/transition_main_reenter.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/transition-v21/transition_cinema_detail_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/transition/transition_cinema_detail_enter.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/common/OnActorClickListener.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.common; 2 | 3 | public interface OnActorClickListener { 4 | void onActorClicked(int actorId , int viewHolderPosition); 5 | } 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/common/OnCinemaClickListener.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.common; 2 | 3 | public interface OnCinemaClickListener { 4 | void onCinemaClicked(int cinemaId , int viewHolderPosition); 5 | } 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat May 30 23:10:07 MSK 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.6.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/common/HolderRenderer.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.common; 2 | 3 | /* 4 | Must implement ALL holders! 5 | */ 6 | 7 | public interface HolderRenderer { 8 | void render(T item , int adapterPosition); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_indicator_view.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/more_info_button_selected.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rating_background_green.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rating_background_orange.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/search_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_big_cinema_in_one_row.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/more_info_button.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/ActivityScope.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | @Scope 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface ActivityScope { 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/actor/ActorScope.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.actor; 2 | 3 | 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | 7 | import javax.inject.Scope; 8 | 9 | @Scope 10 | @Retention(RetentionPolicy.RUNTIME) 11 | @interface ActorScope { 12 | } 13 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/SystemTimeCalculator.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain; 2 | 3 | import com.ru.devit.mediateka.models.model.DateAndTimeInfo; 4 | 5 | public interface SystemTimeCalculator { 6 | long futureTimeInMillisFromDateAndTimeInfo(DateAndTimeInfo dateAndTimeInfo); 7 | long currentTimeInMillis(); 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/cinema/CinemaScope.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.cinema; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | import javax.inject.Scope; 7 | 8 | @Scope 9 | @Retention(RetentionPolicy.RUNTIME) 10 | public @interface CinemaScope { 11 | } 12 | -------------------------------------------------------------------------------- /logo license: -------------------------------------------------------------------------------- 1 | Creative Commons License
This work is licensed under a Creative Commons Attribution 4.0 International License. 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/attrs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/network/Poster.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.network; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class Poster { 6 | @SerializedName("file_path") private String posterUrl; 7 | public String getPosterUrl() { 8 | return posterUrl; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_sort.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_download.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/common/CinemaTabSelectorView.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.common; 2 | 3 | import com.ru.devit.mediateka.presentation.base.BaseView; 4 | 5 | public interface CinemaTabSelectorView extends BaseView { 6 | void onPopularTabSelected(); 7 | void onTopRatedTabSelected(); 8 | void onUpComingTabSelected(); 9 | } 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_add.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_home.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_actor_photo.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_arrow_up.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_dashboard_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_small_cinema_in_one_row.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_delete.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_star_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/actor/ActorListComponent.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.actor; 2 | 3 | 4 | import com.ru.devit.mediateka.di.ActivityScope; 5 | import com.ru.devit.mediateka.presentation.actorlist.ActorsFragment; 6 | 7 | import dagger.Subcomponent; 8 | 9 | @ActivityScope 10 | @Subcomponent(modules = ActorListModule.class) 11 | public interface ActorListComponent { 12 | void inject(ActorsFragment actorsFragment); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/rectangle_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 10 | 11 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_actor_default_avatar.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/transition/transition_shared_element.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/base/BasePresenter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.base; 2 | 3 | public abstract class BasePresenter { 4 | 5 | private View view; 6 | 7 | protected View getView(){ 8 | return view; 9 | } 10 | 11 | public void setView(View view){ 12 | this.view = view; 13 | } 14 | 15 | public abstract void initialize(); 16 | public abstract void onDestroy(); 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/cinema/cinemalist/CinemaListComponent.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.cinema.cinemalist; 2 | 3 | import com.ru.devit.mediateka.di.ActivityScope; 4 | import com.ru.devit.mediateka.presentation.cinemalist.CinemaListFragment; 5 | 6 | import dagger.Subcomponent; 7 | 8 | @ActivityScope 9 | @Subcomponent(modules = CinemaListModule.class) 10 | public interface CinemaListComponent { 11 | 12 | void inject(CinemaListFragment cinemaListFragment); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/UseCaseSubscriber.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain; 2 | 3 | 4 | import io.reactivex.subscribers.DisposableSubscriber; 5 | 6 | public abstract class UseCaseSubscriber extends DisposableSubscriber { 7 | @Override 8 | public void onNext(T t) { 9 | 10 | } 11 | 12 | @Override 13 | public void onError(Throwable e) { 14 | 15 | } 16 | 17 | @Override 18 | public void onComplete() { 19 | 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/network/Credits.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.network; 2 | 3 | import java.util.List; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | public class Credits{ 7 | 8 | @SerializedName("cast") private List casts; 9 | @SerializedName("crew") private List crews; 10 | 11 | public List getCast(){ 12 | return casts; 13 | } 14 | 15 | public List getCrews(){ 16 | return crews; 17 | } 18 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_cinema.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/ActorRepository.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain; 2 | 3 | import com.ru.devit.mediateka.models.model.Actor; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.Flowable; 8 | import io.reactivex.Observable; 9 | import io.reactivex.Single; 10 | 11 | public interface ActorRepository { 12 | 13 | Flowable> searchActors(String query); 14 | Single getActorById(int id); 15 | Single> getPopularActors(int page); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/more_info_button_effect.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/cinema/cinemafavourite/CinemaFavouriteListComponent.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.cinema.cinemafavourite; 2 | 3 | import com.ru.devit.mediateka.di.ActivityScope; 4 | import com.ru.devit.mediateka.presentation.favouritelistcinema.FavouriteListCinemaActivity; 5 | 6 | import dagger.Subcomponent; 7 | 8 | @ActivityScope 9 | @Subcomponent(modules = CinemaFavouriteListModule.class) 10 | public interface CinemaFavouriteListComponent { 11 | 12 | void inject(FavouriteListCinemaActivity activity); 13 | } 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_date.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_watch.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_small_cinema_header.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_schedule.xml: -------------------------------------------------------------------------------- 1 | 7 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/common/OnTabSelectedListener.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.common; 2 | 3 | import android.support.design.widget.TabLayout; 4 | 5 | public abstract class OnTabSelectedListener implements TabLayout.OnTabSelectedListener { 6 | @Override 7 | public void onTabSelected(TabLayout.Tab tab) { 8 | 9 | } 10 | 11 | @Override 12 | public void onTabUnselected(TabLayout.Tab tab) { 13 | 14 | } 15 | 16 | @Override 17 | public void onTabReselected(TabLayout.Tab tab) { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/actor/ActorComponent.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.actor; 2 | 3 | import com.ru.devit.mediateka.presentation.popularactors.PopularActorsActivity; 4 | 5 | import dagger.Subcomponent; 6 | 7 | @ActorScope 8 | @Subcomponent(modules = ActorModule.class) 9 | public interface ActorComponent { 10 | 11 | void inject(PopularActorsActivity popularActors); 12 | 13 | ActorListComponent plusActorListComponent(ActorListModule actorListModule); 14 | ActorDetailComponent plusActorDetailComponent(ActorDetailModule actorDetailModule); 15 | } 16 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/UnitTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka; 2 | 3 | import org.junit.Before; 4 | import org.junit.runner.RunWith; 5 | import org.junit.runners.JUnit4; 6 | import org.mockito.MockitoAnnotations; 7 | 8 | @RunWith(JUnit4.class) 9 | public abstract class UnitTest { 10 | 11 | @Before 12 | public final void setUp(){ 13 | initializeMocks(); 14 | onSetUp(); 15 | } 16 | 17 | private void initializeMocks() { 18 | MockitoAnnotations.initMocks(this); 19 | } 20 | 21 | protected abstract void onSetUp(); 22 | } 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/common/OnPageScrolled.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.common; 2 | 3 | import android.support.v4.view.ViewPager; 4 | 5 | public abstract class OnPageScrolled implements ViewPager.OnPageChangeListener { 6 | @Override 7 | public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { 8 | 9 | } 10 | 11 | @Override 12 | public void onPageSelected(int position) { 13 | 14 | } 15 | 16 | @Override 17 | public void onPageScrollStateChanged(int state) { 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v21/more_info_button_effect.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_cinema_detail_content.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/actor/ActorDetailComponent.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.actor; 2 | 3 | import com.ru.devit.mediateka.di.ActivityScope; 4 | import com.ru.devit.mediateka.presentation.actordetail.ActorDetailActivity; 5 | import com.ru.devit.mediateka.presentation.actordetail.ActorDetailContentFragment; 6 | 7 | import dagger.Subcomponent; 8 | 9 | @ActivityScope 10 | @Subcomponent(modules = ActorDetailModule.class) 11 | public interface ActorDetailComponent { 12 | 13 | void inject(ActorDetailActivity actorDetailActivity); 14 | 15 | void inject(ActorDetailContentFragment actorDetailContentFragment); 16 | } 17 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/CinemaRepository.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain; 2 | 3 | import com.ru.devit.mediateka.models.model.Cinema; 4 | 5 | import java.util.List; 6 | 7 | import io.reactivex.Flowable; 8 | import io.reactivex.Observable; 9 | import io.reactivex.Single; 10 | 11 | public interface CinemaRepository { 12 | 13 | Single> getCinemas(int pageIndex); 14 | Single> getTopRatedCinemas(int pageIndex); 15 | Single> getUpComingCinemas(int pageIndex); 16 | Single getCinemaById(int cinemaId); 17 | Flowable> searchCinemas(String query); 18 | } 19 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Built application files 2 | *.apk 3 | *.ap_ 4 | 5 | # Files for the Dalvik VM 6 | *.dex 7 | 8 | # Java class files 9 | *.class 10 | 11 | # Generated files 12 | bin/ 13 | gen/ 14 | 15 | # Gradle files 16 | .gradle/ 17 | build/ 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 | # Idea 36 | .idea/ 37 | *.iml 38 | 39 | __MACOSX 40 | .DS_Store 41 | /release 42 | -------------------------------------------------------------------------------- /app/src/main/res/values/arrays.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | By cinema date 6 | By cinema title 7 | By cinema genre 8 | 9 | 10 | 11 | 185p 12 | 780p 13 | 1280p 14 | 15 | 16 | 17 | 185 18 | 780 19 | 1280 20 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_list_cinema.xml: -------------------------------------------------------------------------------- 1 | 2 | 8 | 9 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/ru/devit/mediateka/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka; 2 | 3 | import android.content.Context; 4 | 5 | /** 6 | * Instrumented test, which will subscribe on an Android device. 7 | * 8 | * @see Testing documentation 9 | */ 10 | //@RunWith(AndroidJUnit4.class) 11 | public class ExampleInstrumentedTest { 12 | // @Test 13 | // public void useAppContext() throws Exception { 14 | // // Context of the app under test. 15 | // Context appContext = InstrumentationRegistry.getTargetContext(); 16 | // 17 | // assertEquals("com.ru.devit.mediateka", appContext.getPackageName()); 18 | // } 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_share.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/IntArrayConverter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models; 2 | 3 | import android.arch.persistence.room.TypeConverter; 4 | 5 | import com.google.gson.Gson; 6 | import com.google.gson.reflect.TypeToken; 7 | 8 | import java.lang.reflect.Type; 9 | 10 | public class IntArrayConverter { 11 | 12 | @TypeConverter 13 | public static String toJson(final int[] ints){ 14 | Gson gson = new Gson(); 15 | return gson.toJson(ints); 16 | } 17 | 18 | @TypeConverter 19 | public static int[] fromJson(final String val){ 20 | final Type type = new TypeToken(){}.getType(); 21 | return new Gson().fromJson(val , type); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_navigation.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 10 | 11 | 16 | 17 | 21 | 22 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_action_search.xml: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/network/ImagesResponse.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.network; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.List; 6 | 7 | public class ImagesResponse { 8 | @SerializedName("posters") private List cinemaPosters; 9 | @SerializedName("profiles") private List actorPosters; // only for actor !!! 10 | @SerializedName("backdrops") private List cinemaBackgroundPosters; 11 | 12 | public List getCinemaPosters() { 13 | return cinemaPosters; 14 | } 15 | 16 | public List getActorPosters() { 17 | return actorPosters; 18 | } 19 | 20 | public List getCinemaBackgroundPosters() { 21 | return cinemaBackgroundPosters; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/network/ActorResponse.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.network; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | import java.util.List; 6 | 7 | public class ActorResponse { 8 | @SerializedName("page") private int page; 9 | @SerializedName("total_pages") private int totalPages; 10 | @SerializedName("total_results") private int totalResults; 11 | @SerializedName("results") private List actors; 12 | 13 | public int getPage() { 14 | return page; 15 | } 16 | 17 | public int getTotalPages() { 18 | return totalPages; 19 | } 20 | 21 | public int getTotalResults() { 22 | return totalResults; 23 | } 24 | 25 | public List getActors() { 26 | return actors; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/settings/SettingsActivity.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.settings; 2 | 3 | import android.os.Bundle; 4 | import android.view.MenuItem; 5 | 6 | public class SettingsActivity extends AppCompatPreferenceActivity { 7 | 8 | @Override 9 | protected void onCreate(Bundle savedInstanceState) { 10 | super.onCreate(savedInstanceState); 11 | 12 | getSupportActionBar().setDisplayHomeAsUpEnabled(true); 13 | getFragmentManager().beginTransaction().add(android.R.id.content , new PreferenceSettingsFragment()).commit(); 14 | } 15 | 16 | @Override 17 | public boolean onOptionsItemSelected(MenuItem item) { 18 | if (item.getItemId() == android.R.id.home) { 19 | onBackPressed(); 20 | } 21 | return super.onOptionsItemSelected(item); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /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 actorName 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 actorName. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /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=-Xmx4028m 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 | THE_MOVIE_DB_API_KEY="536eef71f9569a963770135812cba068" 19 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_actors.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 20 | 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/mapper/Mapper.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.mapper; 2 | 3 | import java.util.ArrayList; 4 | import java.util.List; 5 | 6 | public abstract class Mapper { 7 | 8 | public abstract T2 map(T1 value); 9 | 10 | public abstract T1 reverseMap(T2 value); 11 | 12 | public List map(List values) { 13 | List returnValues = new ArrayList<>(values.size()); 14 | for (T1 value : values) { 15 | returnValues.add(map(value)); 16 | } 17 | return returnValues; 18 | } 19 | 20 | public List reverseMap(List values) { 21 | List returnValues = new ArrayList<>(values.size()); 22 | for (T2 value : values) { 23 | returnValues.add(reverseMap(value)); 24 | } 25 | return returnValues; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_small_cinemas.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 15 | 16 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/cinema/cinemadetail/CinemaDetailComponent.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.cinema.cinemadetail; 2 | 3 | import com.ru.devit.mediateka.di.ActivityScope; 4 | import com.ru.devit.mediateka.di.cinema.cinemadetail.CinemaDetailModule; 5 | import com.ru.devit.mediateka.presentation.cinemadetail.CinemaDetailContentFragment; 6 | import com.ru.devit.mediateka.presentation.cinemadetail.CinemaDetailsActivity; 7 | import com.ru.devit.mediateka.presentation.smallcinemalist.SmallCinemasFragment; 8 | 9 | import dagger.Subcomponent; 10 | 11 | @ActivityScope 12 | @Subcomponent(modules = CinemaDetailModule.class) 13 | public interface CinemaDetailComponent { 14 | 15 | void inject(CinemaDetailsActivity cinemaDetailsActivity); 16 | void inject(SmallCinemasFragment smallCinemasFragment); 17 | void inject(CinemaDetailContentFragment cinemaDetailContentFragment); 18 | } 19 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/network/CinemaResponse.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.network; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | import java.util.List; 7 | 8 | public class CinemaResponse { 9 | @SerializedName("page") private int page; 10 | @SerializedName("total_pages") private int totalPages; 11 | @SerializedName("total_results") private int totalResults; 12 | @SerializedName("results") private List cinemas; 13 | 14 | public int getPage() { 15 | return page; 16 | } 17 | 18 | public int getTotalPages() { 19 | return totalPages; 20 | } 21 | 22 | public int getTotalResults() { 23 | return totalResults; 24 | } 25 | 26 | public List getCinemas() { 27 | return cinemas; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/res/xml/pref_mediateka_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 7 | 8 | 15 | 16 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/data/datasource/db/MediatekaDatabase.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.data.datasource.db; 2 | 3 | import android.arch.persistence.room.Database; 4 | import android.arch.persistence.room.RoomDatabase; 5 | import android.arch.persistence.room.TypeConverters; 6 | 7 | import com.ru.devit.mediateka.models.IntArrayConverter; 8 | import com.ru.devit.mediateka.models.db.ActorEntity; 9 | import com.ru.devit.mediateka.models.db.CinemaActorJoinEntity; 10 | import com.ru.devit.mediateka.models.db.CinemaEntity; 11 | 12 | @Database(entities = {CinemaEntity.class , ActorEntity.class , CinemaActorJoinEntity.class} , 13 | version = 1 , 14 | exportSchema = false) 15 | @TypeConverters(IntArrayConverter.class) 16 | public abstract class MediatekaDatabase extends RoomDatabase { 17 | public abstract CinemaDao getCinemaDao(); 18 | public abstract ActorDao getActorDao(); 19 | public abstract CinemaActorJoinDao getCinemaActorJoinDao(); 20 | } 21 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/smallcinemalist/SmallCinemaHeaderViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.smallcinemalist; 2 | 3 | import android.content.Context; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.View; 6 | import android.widget.TextView; 7 | 8 | import com.ru.devit.mediateka.R; 9 | 10 | public class SmallCinemaHeaderViewHolder extends RecyclerView.ViewHolder{ 11 | 12 | private TextView mTextViewCinemaCount; 13 | 14 | public SmallCinemaHeaderViewHolder(View itemView) { 15 | super(itemView); 16 | mTextViewCinemaCount = itemView.findViewById(R.id.tv_actor_detail_cinemas_count); 17 | } 18 | 19 | public void render(int cinemaCount){ 20 | mTextViewCinemaCount.setText(getContext() 21 | .getString(R.string.cinemas_count , cinemaCount)); 22 | } 23 | 24 | private Context getContext(){ 25 | return itemView.getContext(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/presentation/posterslider/PosterSliderPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.posterslider; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | 5 | import org.junit.Test; 6 | import org.mockito.Mock; 7 | 8 | import static org.junit.Assert.*; 9 | import static org.mockito.Mockito.times; 10 | import static org.mockito.Mockito.verify; 11 | 12 | public class PosterSliderPresenterTest extends UnitTest { 13 | 14 | @Mock private PosterSliderPresenter.View view; 15 | 16 | private PosterSliderPresenter presenter; 17 | 18 | @Override 19 | protected void onSetUp() { 20 | presenter = new PosterSliderPresenter(); 21 | } 22 | 23 | @Test 24 | public void shouldChangeCurrentPositionWhenPosterSlide(){ 25 | presenter.setView(view); 26 | presenter.countPosters(2 , 10); 27 | 28 | verify(view , times(1)).showCurrentPosition("3/10"); // 3/10 because position start from 0 i.e currentPos += 1 :) 29 | } 30 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/cinemadetail/CinemaDetailContentPresenter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.cinemadetail; 2 | 3 | import com.ru.devit.mediateka.models.model.Cinema; 4 | import com.ru.devit.mediateka.presentation.base.BasePresenter; 5 | import com.ru.devit.mediateka.presentation.base.BaseView; 6 | 7 | public class CinemaDetailContentPresenter extends BasePresenter { 8 | 9 | private Cinema cinema; 10 | 11 | public CinemaDetailContentPresenter(){} 12 | 13 | @Override 14 | public void initialize() { 15 | getView().showLoading(); 16 | } 17 | 18 | public void setCinema(Cinema cinema) { 19 | this.cinema = cinema; 20 | getView().showCinemaContent(cinema); 21 | getView().hideLoading(); 22 | } 23 | 24 | public void onDestroy() { 25 | setView(null); 26 | } 27 | 28 | public interface View extends BaseView { 29 | void showCinemaContent(Cinema cinema); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/posterslider/PosterSliderPresenter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.posterslider; 2 | 3 | import com.ru.devit.mediateka.presentation.base.BasePresenter; 4 | import com.ru.devit.mediateka.presentation.base.BaseView; 5 | 6 | import java.util.Locale; 7 | 8 | import javax.inject.Inject; 9 | 10 | public class PosterSliderPresenter extends BasePresenter { 11 | 12 | @Inject public PosterSliderPresenter() {} 13 | 14 | @Override 15 | public void initialize() { 16 | getView().showLoading(); 17 | } 18 | 19 | @Override 20 | public void onDestroy() { 21 | setView(null); 22 | } 23 | 24 | void countPosters(int currentItem, int count) { 25 | currentItem += 1; 26 | getView().showCurrentPosition(String.format(Locale.getDefault() , "%d/%d" , currentItem , count)); 27 | } 28 | 29 | interface View extends BaseView { 30 | void showCurrentPosition(String position); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/cinema/CinemaComponent.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.cinema; 2 | 3 | import com.ru.devit.mediateka.di.cinema.cinemadetail.CinemaDetailComponent; 4 | import com.ru.devit.mediateka.di.cinema.cinemadetail.CinemaDetailModule; 5 | import com.ru.devit.mediateka.di.cinema.cinemafavourite.CinemaFavouriteListComponent; 6 | import com.ru.devit.mediateka.di.cinema.cinemafavourite.CinemaFavouriteListModule; 7 | import com.ru.devit.mediateka.di.cinema.cinemalist.CinemaListComponent; 8 | import com.ru.devit.mediateka.di.cinema.cinemalist.CinemaListModule; 9 | 10 | import dagger.Subcomponent; 11 | 12 | @CinemaScope 13 | @Subcomponent(modules = CinemaModule.class) 14 | public interface CinemaComponent { 15 | 16 | CinemaListComponent plusCinemaListComponent(CinemaListModule cinemaListModule); 17 | CinemaDetailComponent plusCinemaDetailComponent(CinemaDetailModule cinemaDetailModule); 18 | CinemaFavouriteListComponent plusCinemaFavouriteListComponent(CinemaFavouriteListModule cinemaFavouriteListModule); 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/actorusecases/GetActorById.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain.actorusecases; 2 | 3 | import com.ru.devit.mediateka.domain.ActorRepository; 4 | import com.ru.devit.mediateka.domain.UseCase; 5 | import com.ru.devit.mediateka.models.model.Actor; 6 | 7 | import io.reactivex.Flowable; 8 | import io.reactivex.Scheduler; 9 | 10 | public class GetActorById extends UseCase { 11 | 12 | private final ActorRepository repository; 13 | private int actorId; 14 | 15 | public GetActorById(Scheduler executorThread , 16 | Scheduler uiThread , 17 | ActorRepository repository){ 18 | super(executorThread , uiThread); 19 | this.repository = repository; 20 | } 21 | 22 | public void searchActorById(int actorId) { 23 | this.actorId = actorId; 24 | } 25 | 26 | @Override 27 | protected Flowable createUseCase() { 28 | return repository.getActorById(actorId) 29 | .toFlowable(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/Actions.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain; 2 | 3 | import io.reactivex.functions.Action; 4 | 5 | public class Actions { 6 | 7 | private Action actionOnNext; 8 | private Action actionOnDataLoaded; 9 | private Action actionOnClearAdapter; 10 | 11 | public Actions(Action actionOnNext, Action actionOnDataLoaded, Action actionOnClearAdapter) { 12 | this.actionOnNext = actionOnNext; 13 | this.actionOnDataLoaded = actionOnDataLoaded; 14 | this.actionOnClearAdapter = actionOnClearAdapter; 15 | } 16 | 17 | public void onNext() throws Exception { 18 | actionOnNext.run(); 19 | } 20 | 21 | public void onDataLoaded() throws Exception { 22 | actionOnDataLoaded.run(); 23 | } 24 | 25 | public void onClearAdapter() throws Exception { 26 | actionOnClearAdapter.run(); 27 | } 28 | 29 | public void removeActions(){ 30 | actionOnNext = null; 31 | actionOnDataLoaded = null; 32 | actionOnClearAdapter = null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/actorusecases/GetActors.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain.actorusecases; 2 | 3 | import com.ru.devit.mediateka.domain.ActorRepository; 4 | import com.ru.devit.mediateka.domain.UseCase; 5 | import com.ru.devit.mediateka.models.model.Actor; 6 | 7 | import java.util.List; 8 | 9 | import javax.inject.Inject; 10 | import javax.inject.Named; 11 | 12 | import io.reactivex.Flowable; 13 | import io.reactivex.Scheduler; 14 | 15 | public class GetActors extends UseCase> { 16 | 17 | private final ActorRepository repository; 18 | 19 | @Inject public GetActors(@Named("executor_thread") Scheduler executorThread , 20 | @Named("ui_thread") Scheduler uiThread , 21 | ActorRepository repository) { 22 | super(executorThread, uiThread); 23 | this.repository = repository; 24 | } 25 | 26 | @Override 27 | protected Flowable> createUseCase() { 28 | return repository.getPopularActors(1) 29 | .toFlowable(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/data/SharedPreferenceManager.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.data; 2 | 3 | import android.content.SharedPreferences; 4 | 5 | public class SharedPreferenceManager { 6 | 7 | private final SharedPreferences preferences; 8 | private static final String PREF_CINEMA_SORTING_POSITION = "pref_cinema_sorting_position"; 9 | 10 | public SharedPreferenceManager(SharedPreferences preferences) { 11 | this.preferences = preferences; 12 | } 13 | 14 | public void saveCinemaSortingPosition(int position){ 15 | edit(editor -> editor.putInt(PREF_CINEMA_SORTING_POSITION , position)); 16 | } 17 | 18 | public int getCinemaSortingPosition(){ 19 | return preferences.getInt(PREF_CINEMA_SORTING_POSITION , 0); 20 | } 21 | 22 | private void edit(Consumer consumer){ 23 | SharedPreferences.Editor editor = preferences.edit(); 24 | consumer.apply(editor); 25 | editor.apply(); 26 | } 27 | 28 | @FunctionalInterface 29 | private interface Consumer{ 30 | void apply(T t); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/model/DateAndTimeInfo.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.model; 2 | 3 | import java.io.Serializable; 4 | 5 | public class DateAndTimeInfo implements Serializable { 6 | 7 | private int year; 8 | private int month; 9 | private int day; 10 | private int hour; 11 | private int minute; 12 | 13 | public int getYear() { 14 | return year; 15 | } 16 | 17 | public void setYear(int year) { 18 | this.year = year; 19 | } 20 | 21 | public int getMonth() { 22 | return month; 23 | } 24 | 25 | public void setMonth(int month) { 26 | this.month = month; 27 | } 28 | 29 | public int getDay() { 30 | return day; 31 | } 32 | 33 | public void setDay(int day) { 34 | this.day = day; 35 | } 36 | 37 | public int getHour() { 38 | return hour; 39 | } 40 | 41 | public void setHour(int hour) { 42 | this.hour = hour; 43 | } 44 | 45 | public int getMinute() { 46 | return minute; 47 | } 48 | 49 | public void setMinute(int minute) { 50 | this.minute = minute; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/actor/ActorListModule.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.actor; 2 | 3 | import com.ru.devit.mediateka.domain.ActorRepository; 4 | import com.ru.devit.mediateka.di.ActivityScope; 5 | import com.ru.devit.mediateka.domain.actorusecases.GetActorsByQuery; 6 | import com.ru.devit.mediateka.presentation.actorlist.ActorsPresenter; 7 | 8 | import javax.inject.Named; 9 | 10 | import dagger.Module; 11 | import dagger.Provides; 12 | import io.reactivex.Scheduler; 13 | 14 | @Module 15 | public class ActorListModule { 16 | 17 | @ActivityScope 18 | @Provides 19 | ActorsPresenter provideActorsPresenter(GetActorsByQuery getActorsByQuery){ 20 | return new ActorsPresenter(getActorsByQuery); 21 | } 22 | 23 | @ActivityScope 24 | @Provides 25 | GetActorsByQuery provideGetActorByQuery(@Named("executor_thread") Scheduler executorThread , 26 | @Named("ui_thread") Scheduler uiThread , 27 | ActorRepository repository){ 28 | return new GetActorsByQuery(executorThread , uiThread , repository); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/network/CrewNetwork.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.network; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class CrewNetwork{ 6 | 7 | // @SerializedName("gender") 8 | // private int gender; 9 | // 10 | // @SerializedName("credit_id") 11 | // private String creditId; 12 | 13 | @SerializedName("name") 14 | private String name; 15 | 16 | // @SerializedName("profile_path") 17 | // private String profilePath; 18 | // 19 | // @SerializedName("id") 20 | // private int id; 21 | // 22 | // @SerializedName("department") 23 | // private String department; 24 | 25 | @SerializedName("job") 26 | private String job; 27 | 28 | // public int getGender(){ 29 | // return gender; 30 | // } 31 | // 32 | // public String getCreditId(){ 33 | // return creditId; 34 | // } 35 | 36 | public String getName(){ 37 | return name; 38 | } 39 | 40 | // public String getProfilePath(){ 41 | // return profilePath; 42 | // } 43 | // 44 | // public int getActorId(){ 45 | // return id; 46 | // } 47 | // 48 | // public String getDepartment(){ 49 | // return department; 50 | // } 51 | 52 | public String getJob(){ 53 | return job; 54 | } 55 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_settings.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 100dp 6 | 16dp 7 | 16dp 8 | 30dp 9 | 20dp 10 | 120dp 11 | 30dp 12 | 16dp 13 | 40dp 14 | 10dp 15 | 190dp 16 | 20dp 17 | 150dp 18 | 100dp 19 | 200dp 20 | 8dp 21 | 80dp 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/item_slider_poster.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 18 | 19 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/common/ViewPagerAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.common; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentStatePagerAdapter; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | public class ViewPagerAdapter extends FragmentStatePagerAdapter { 11 | 12 | 13 | private final List mFragmentList = new ArrayList<>(); 14 | private final List mFragmentTitleList = new ArrayList<>(); 15 | 16 | 17 | public ViewPagerAdapter(FragmentManager fm) { 18 | super(fm); 19 | } 20 | 21 | @Override 22 | public Fragment getItem(int position) { 23 | return mFragmentList.get(position); 24 | } 25 | 26 | @Override 27 | public int getCount() { 28 | return mFragmentList.size(); 29 | } 30 | 31 | @Override 32 | public CharSequence getPageTitle(int position) { 33 | return mFragmentTitleList.get(position); 34 | } 35 | 36 | public void addFragment(Fragment fragment, String title) { 37 | mFragmentList.add(fragment); 38 | mFragmentTitleList.add(title); 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/presentation/actordetail/ActorDetailContentPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.actordetail; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | import com.ru.devit.mediateka.models.model.Actor; 5 | 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | 9 | import static org.junit.Assert.*; 10 | import static org.mockito.Matchers.any; 11 | import static org.mockito.Mockito.times; 12 | import static org.mockito.Mockito.verify; 13 | 14 | public class ActorDetailContentPresenterTest extends UnitTest { 15 | 16 | @Mock private ActorDetailContentPresenter.View view; 17 | 18 | private ActorDetailContentPresenter presenter; 19 | 20 | @Override 21 | protected void onSetUp() { 22 | presenter = new ActorDetailContentPresenter(); 23 | } 24 | 25 | @Test 26 | public void shouldShowLoadingWhenInitialize(){ 27 | presenter.setView(view); 28 | presenter.initialize(); 29 | 30 | verify(view , times(1)).showLoading(); 31 | } 32 | 33 | @Test 34 | public void shouldSetActor(){ 35 | presenter.setView(view); 36 | presenter.setActor(any(Actor.class)); 37 | 38 | verify(view , times(1)).showActorInfo(any(Actor.class)); 39 | } 40 | } -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 13 | 14 | 18 | 19 | 23 | 24 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/res/layout/nav_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 11 | 12 | 17 | 18 | 24 | 25 | 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/data/ConnectionReceiver.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.data; 2 | 3 | import android.content.BroadcastReceiver; 4 | import android.content.Context; 5 | import android.content.Intent; 6 | import android.net.ConnectivityManager; 7 | import android.net.NetworkInfo; 8 | 9 | import com.ru.devit.mediateka.presentation.main.SyncConnectionListener; 10 | 11 | public class ConnectionReceiver extends BroadcastReceiver { 12 | 13 | public static SyncConnectionListener connectionListener; 14 | 15 | @Override 16 | public void onReceive(Context context, Intent arg) { 17 | boolean isConnected = checkConnection(context); 18 | 19 | if (connectionListener != null) { 20 | connectionListener.onNetworkConnectionChanged(isConnected); 21 | } 22 | } 23 | 24 | public boolean isInternetConnected(Context context){ 25 | return checkConnection(context); 26 | } 27 | 28 | private boolean checkConnection(Context context) { 29 | ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE); 30 | NetworkInfo activeNetwork = cm.getActiveNetworkInfo(); 31 | 32 | return activeNetwork != null && activeNetwork.isConnectedOrConnecting(); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/actordetail/ActorDetailContentPresenter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.actordetail; 2 | 3 | import com.ru.devit.mediateka.models.model.Actor; 4 | import com.ru.devit.mediateka.presentation.base.BasePresenter; 5 | import com.ru.devit.mediateka.presentation.base.BaseView; 6 | 7 | import java.util.List; 8 | 9 | public class ActorDetailContentPresenter extends BasePresenter { 10 | 11 | private Actor actor; 12 | 13 | public ActorDetailContentPresenter() {} 14 | 15 | @Override 16 | public void initialize() { 17 | getView().showLoading(); 18 | } 19 | 20 | @Override 21 | public void onDestroy() { 22 | setView(null); 23 | } 24 | 25 | public void setActor(Actor actor){ 26 | this.actor = actor; 27 | getView().showActorInfo(this.actor); 28 | } 29 | 30 | public void onPhotoClicked(int position, int viewHolderPos) { 31 | getView().showDetailedPhoto(position , viewHolderPos , actor.getPostersUrl()); 32 | } 33 | 34 | public interface View extends BaseView{ 35 | void showActorInfo(Actor actor); 36 | void showDetailedPhoto(int position , int viewHolderPos , List posterUrls); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/cinemalist/CinemaListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.cinemalist; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.ru.devit.mediateka.R; 10 | import com.ru.devit.mediateka.models.model.Cinema; 11 | import com.ru.devit.mediateka.presentation.common.AbstractCinemaListAdapter; 12 | import com.ru.devit.mediateka.presentation.common.OnCinemaClickListener; 13 | 14 | import java.util.ArrayList; 15 | import java.util.LinkedHashSet; 16 | import java.util.List; 17 | import java.util.Set; 18 | 19 | public class CinemaListAdapter extends AbstractCinemaListAdapter { 20 | 21 | CinemaListAdapter(OnCinemaClickListener onCinemaClickListener) { 22 | super(onCinemaClickListener); 23 | } 24 | 25 | @Override 26 | @NonNull 27 | public CinemaViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 28 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_cinema , parent , false); 29 | return new CinemaViewHolder(view , onCinemaClickListener); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/cinemausecases/GetCinemas.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain.cinemausecases; 2 | 3 | 4 | import com.ru.devit.mediateka.domain.CinemaRepository; 5 | import com.ru.devit.mediateka.domain.UseCase; 6 | import com.ru.devit.mediateka.models.model.Cinema; 7 | 8 | import org.reactivestreams.Publisher; 9 | 10 | import java.util.List; 11 | 12 | import io.reactivex.Flowable; 13 | import io.reactivex.Scheduler; 14 | import io.reactivex.functions.Consumer; 15 | import io.reactivex.functions.Function; 16 | 17 | public class GetCinemas extends UseCase> { 18 | 19 | private final CinemaRepository repository; 20 | 21 | public GetCinemas(Scheduler executorThread , 22 | Scheduler uiThread , 23 | CinemaRepository repository){ 24 | super(executorThread , uiThread); 25 | this.repository = repository; 26 | } 27 | 28 | @Override 29 | public Flowable> createUseCase() { 30 | return repository.getCinemas(pageIndex) 31 | .toFlowable() 32 | .flatMap(Flowable::fromIterable) 33 | .filter(cinema -> !cinema.getDescription().isEmpty()) 34 | .toList() 35 | .toFlowable(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/posterslider/PosterSliderAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.posterslider; 2 | 3 | import android.support.v4.app.Fragment; 4 | import android.support.v4.app.FragmentManager; 5 | import android.support.v4.app.FragmentStatePagerAdapter; 6 | 7 | import java.util.Collections; 8 | import java.util.List; 9 | 10 | public class PosterSliderAdapter extends FragmentStatePagerAdapter { 11 | 12 | private List posterUrls; 13 | private final boolean isForBackgroundPoster; 14 | 15 | public PosterSliderAdapter(FragmentManager fm , List posterUrls , boolean isForBackgroundPoster) { 16 | super(fm); 17 | this.posterUrls = posterUrls; 18 | this.isForBackgroundPoster = isForBackgroundPoster; 19 | checkNotNull(posterUrls); 20 | } 21 | 22 | @Override 23 | public Fragment getItem(int position) { 24 | return PosterSliderFragment.newInstance(posterUrls.get(position) , isForBackgroundPoster); 25 | } 26 | 27 | @Override 28 | public int getCount() { 29 | return posterUrls.size(); 30 | } 31 | 32 | private void checkNotNull(List urls){ 33 | if (urls == null){ 34 | posterUrls = Collections.emptyList(); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/data/datasource/db/CinemaActorJoinDao.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.data.datasource.db; 2 | 3 | import android.arch.persistence.room.Dao; 4 | import android.arch.persistence.room.Insert; 5 | import android.arch.persistence.room.OnConflictStrategy; 6 | import android.arch.persistence.room.Query; 7 | 8 | import com.ru.devit.mediateka.models.db.ActorEntity; 9 | import com.ru.devit.mediateka.models.db.CinemaActorJoinEntity; 10 | import com.ru.devit.mediateka.models.db.CinemaEntity; 11 | 12 | import java.util.List; 13 | 14 | @Dao 15 | public interface CinemaActorJoinDao { 16 | 17 | @Insert(onConflict = OnConflictStrategy.REPLACE) 18 | void insert(CinemaActorJoinEntity cinemaActorJoinEntity); 19 | 20 | @Query("SELECT * FROM ActorTable INNER JOIN CinemaActorJoinTable " + 21 | "ON ActorTable.actorId = CinemaActorJoinTable.actor_id " + 22 | "WHERE CinemaActorJoinTable.cinema_id = :cinemaId ORDER BY `order`" ) 23 | List getActorsForCinema(final int cinemaId); 24 | 25 | @Query("SELECT * FROM CinemaTable INNER JOIN CinemaActorJoinTable " + 26 | "ON CinemaTable.cinemaId = CinemaActorJoinTable.cinema_id " + 27 | "WHERE CinemaActorJoinTable.actor_id = :actorId") 28 | List getCinemasForActor(final int actorId); 29 | } 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/db/CinemaActorJoinEntity.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.db; 2 | 3 | import android.arch.persistence.room.ColumnInfo; 4 | import android.arch.persistence.room.Entity; 5 | import android.arch.persistence.room.ForeignKey; 6 | import android.support.annotation.NonNull; 7 | 8 | @Entity( 9 | tableName = "CinemaActorJoinTable" , 10 | primaryKeys = {"cinema_id" , "actor_id"} , 11 | foreignKeys = { 12 | @ForeignKey(entity = CinemaEntity.class , 13 | parentColumns = "cinemaId" , 14 | childColumns = "cinema_id") , 15 | @ForeignKey(entity = ActorEntity.class , 16 | parentColumns = "actorId", 17 | childColumns = "actor_id") 18 | }) 19 | 20 | public class CinemaActorJoinEntity { 21 | @ColumnInfo(name = "cinema_id") private final int cinemaId; 22 | @ColumnInfo(name = "actor_id") private final int actorId; 23 | 24 | public CinemaActorJoinEntity(final int cinemaId , final int actorId) { 25 | this.cinemaId = cinemaId; 26 | this.actorId = actorId; 27 | } 28 | 29 | public int getCinemaId() { 30 | return cinemaId; 31 | } 32 | 33 | public int getActorId() { 34 | return actorId; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/cinemausecases/GetTopRatedCinemas.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain.cinemausecases; 2 | 3 | import com.ru.devit.mediateka.domain.CinemaRepository; 4 | import com.ru.devit.mediateka.domain.UseCase; 5 | import com.ru.devit.mediateka.models.model.Cinema; 6 | 7 | import java.util.List; 8 | 9 | import io.reactivex.Flowable; 10 | import io.reactivex.Scheduler; 11 | 12 | import static com.ru.devit.mediateka.utils.FormatterUtils.DEFAULT_VALUE; 13 | 14 | public class GetTopRatedCinemas extends UseCase> { 15 | 16 | private final CinemaRepository repository; 17 | 18 | public GetTopRatedCinemas(Scheduler executorThread , 19 | Scheduler uiThread , 20 | CinemaRepository repository) { 21 | super(executorThread, uiThread); 22 | this.repository = repository; 23 | } 24 | 25 | @Override 26 | public Flowable> createUseCase() { 27 | return repository.getTopRatedCinemas(pageIndex) 28 | .toFlowable() 29 | .flatMap(Flowable::fromIterable) 30 | .filter(cinema -> !cinema.getDescription().equals("")) 31 | .filter(cinema -> !cinema.getReleaseDate().equals(DEFAULT_VALUE)) 32 | .toList() 33 | .toFlowable(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/network/ActorNetwork.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.network; 2 | 3 | import com.google.gson.annotations.SerializedName; 4 | 5 | public class ActorNetwork { 6 | 7 | @SerializedName("id") private int actorId; 8 | @SerializedName("cast_id") private int castId; 9 | @SerializedName("character") private String character; 10 | @SerializedName("gender") private int gender; 11 | @SerializedName("credit_id") private String creditId; 12 | @SerializedName("name") private String name; 13 | @SerializedName("profile_path") private String profilePath; 14 | @SerializedName("order") private int order; 15 | @SerializedName("popularity") private double popularity; 16 | 17 | public int getCastId(){ 18 | return castId; 19 | } 20 | 21 | public String getCharacter(){ 22 | return character; 23 | } 24 | 25 | public int getGender(){ 26 | return gender; 27 | } 28 | 29 | public String getCreditId(){ 30 | return creditId; 31 | } 32 | 33 | public String getName(){ 34 | return name; 35 | } 36 | 37 | public String getProfilePath(){ 38 | return profilePath; 39 | } 40 | 41 | public int getActorId(){ 42 | return actorId; 43 | } 44 | 45 | public int getOrder(){ 46 | return order; 47 | } 48 | 49 | public double getPopularity() { 50 | return popularity; 51 | } 52 | 53 | public void setPopularity(double popularity) { 54 | this.popularity = popularity; 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/cinemausecases/GetUpComingCinemas.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain.cinemausecases; 2 | 3 | import com.ru.devit.mediateka.domain.CinemaRepository; 4 | import com.ru.devit.mediateka.domain.UseCase; 5 | import com.ru.devit.mediateka.models.model.Cinema; 6 | 7 | import java.util.List; 8 | 9 | import io.reactivex.Flowable; 10 | import io.reactivex.Scheduler; 11 | 12 | import static com.ru.devit.mediateka.utils.FormatterUtils.DEFAULT_VALUE; 13 | 14 | public class GetUpComingCinemas extends UseCase> { 15 | 16 | private final CinemaRepository repository; 17 | 18 | public GetUpComingCinemas(Scheduler executorThread , 19 | Scheduler uiThread , 20 | CinemaRepository repository) { 21 | super(executorThread, uiThread); 22 | this.repository = repository; 23 | } 24 | 25 | @Override 26 | public Flowable> createUseCase() { 27 | return repository.getUpComingCinemas(pageIndex) 28 | .toFlowable() 29 | .flatMap(Flowable::fromIterable) 30 | .filter(cinema -> !cinema.getDescription().isEmpty()) 31 | .filter(cinema -> !cinema.getReleaseDate().equals(DEFAULT_VALUE) && cinema.getVoteAverage() == 0) 32 | .toList() 33 | .toFlowable(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/application/AppComponent.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.application; 2 | 3 | import com.ru.devit.mediateka.di.actor.ActorComponent; 4 | import com.ru.devit.mediateka.di.cinema.CinemaComponent; 5 | import com.ru.devit.mediateka.di.actor.ActorModule; 6 | import com.ru.devit.mediateka.di.cinema.CinemaModule; 7 | import com.ru.devit.mediateka.presentation.actordetail.ActorDetailActivity; 8 | import com.ru.devit.mediateka.presentation.main.MainActivity; 9 | import com.ru.devit.mediateka.presentation.posterslider.PosterSliderActivity; 10 | import com.ru.devit.mediateka.presentation.search.SearchActivity; 11 | import com.ru.devit.mediateka.presentation.actordetail.ActorDetailContentFragment; 12 | import com.ru.devit.mediateka.presentation.actorlist.ActorsFragment; 13 | import com.ru.devit.mediateka.presentation.cinemadetail.CinemaDetailContentFragment; 14 | 15 | import javax.inject.Singleton; 16 | 17 | import dagger.Component; 18 | 19 | @Singleton 20 | @Component(modules = {RetrofitModule.class , AppModule.class}) 21 | public interface AppComponent { 22 | 23 | void inject(MainActivity mainActivity); 24 | void inject(SearchActivity searchActivity); 25 | void inject(PosterSliderActivity posterSliderActivity); 26 | 27 | CinemaComponent plusCinemaComponent(CinemaModule cinemaModule); 28 | ActorComponent plusActorComponent(ActorModule actorModule); 29 | } 30 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/presentation/cinemadetail/CinemaDetailContentPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.cinemadetail; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | import com.ru.devit.mediateka.models.model.Cinema; 5 | import com.ru.devit.mediateka.presentation.cinemadetail.CinemaDetailContentPresenter; 6 | 7 | import org.junit.Test; 8 | import org.mockito.Mock; 9 | 10 | import static org.junit.Assert.*; 11 | import static org.mockito.Matchers.any; 12 | import static org.mockito.Mockito.times; 13 | import static org.mockito.Mockito.verify; 14 | 15 | public class CinemaDetailContentPresenterTest extends UnitTest { 16 | 17 | @Mock private CinemaDetailContentPresenter.View view; 18 | 19 | private CinemaDetailContentPresenter presenter; 20 | 21 | @Override 22 | protected void onSetUp() { 23 | presenter = new CinemaDetailContentPresenter(); 24 | } 25 | 26 | @Test 27 | public void shouldShowLoadingWhenInitialize(){ 28 | presenter.setView(view); 29 | presenter.initialize(); 30 | 31 | verify(view , times(1)).showLoading(); 32 | } 33 | 34 | @Test 35 | public void shouldSetCinema(){ 36 | presenter.setView(view); 37 | presenter.setCinema(any(Cinema.class)); 38 | 39 | verify(view).showCinemaContent(any(Cinema.class)); 40 | verify(view).hideLoading(); 41 | } 42 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/actor/ActorDetailModule.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.actor; 2 | 3 | import com.ru.devit.mediateka.domain.ActorRepository; 4 | import com.ru.devit.mediateka.di.ActivityScope; 5 | import com.ru.devit.mediateka.domain.actorusecases.GetActorById; 6 | import com.ru.devit.mediateka.presentation.actordetail.ActorDetailContentPresenter; 7 | import com.ru.devit.mediateka.presentation.actordetail.ActorDetailPresenter; 8 | 9 | import javax.inject.Named; 10 | 11 | import dagger.Module; 12 | import dagger.Provides; 13 | import io.reactivex.Scheduler; 14 | 15 | @Module 16 | public class ActorDetailModule { 17 | 18 | @ActivityScope 19 | @Provides 20 | ActorDetailPresenter provideActorDetailPresenter(GetActorById getActorById){ 21 | return new ActorDetailPresenter(getActorById); 22 | } 23 | 24 | @ActivityScope 25 | @Provides 26 | ActorDetailContentPresenter provideActorDetailContentPresenter(){ 27 | return new ActorDetailContentPresenter(); 28 | } 29 | 30 | @ActivityScope 31 | @Provides 32 | GetActorById provideGetActorById(@Named("executor_thread")Scheduler executorThread , 33 | @Named("ui_thread")Scheduler uiThread , 34 | ActorRepository repository){ 35 | return new GetActorById(executorThread , uiThread , repository); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_drawer_items.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 10 | 11 | 15 | 16 | 20 | 21 | 25 | 26 | 30 | 31 | 32 | 33 | 35 | 36 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #333333 4 | #1F1F1F 5 | #1e88e5 6 | #FFFFFF 7 | #FFFFFF 8 | #333333 9 | #18df0a 10 | #effb0d 11 | #FF000000 12 | #FFF40808 13 | #c30000 14 | #FFFFFF 15 | #FFFF9100 16 | #c56200 17 | #009933 18 | #B3613D 19 | #8e24aa 20 | #5c007a 21 | #1FFFFFFF 22 | #ffa6c575 23 | #FF1976D2 24 | #FF424242 25 | #242425 26 | #fa315b 27 | #40c4ff 28 | #00b0ff 29 | 30 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/cinema/cinemafavourite/CinemaFavouriteListModule.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.cinema.cinemafavourite; 2 | 3 | import com.ru.devit.mediateka.data.SharedPreferenceManager; 4 | import com.ru.devit.mediateka.data.repository.cinema.CinemaLocalRepository; 5 | import com.ru.devit.mediateka.di.ActivityScope; 6 | import com.ru.devit.mediateka.domain.cinemausecases.GetFavouriteListCinema; 7 | import com.ru.devit.mediateka.presentation.favouritelistcinema.FavouriteListCinemaPresenter; 8 | 9 | import javax.inject.Named; 10 | 11 | import dagger.Module; 12 | import dagger.Provides; 13 | import io.reactivex.Scheduler; 14 | 15 | @Module 16 | public class CinemaFavouriteListModule { 17 | 18 | @ActivityScope 19 | @Provides 20 | FavouriteListCinemaPresenter provideFavouriteListCinemaPresenter(GetFavouriteListCinema useCase , 21 | SharedPreferenceManager sharedPreferenceManager){ 22 | return new FavouriteListCinemaPresenter(useCase , sharedPreferenceManager); 23 | } 24 | 25 | @ActivityScope 26 | @Provides 27 | GetFavouriteListCinema provideGetFavouriteListCinema(@Named("executor_thread")Scheduler executorThread , 28 | @Named("ui_thread")Scheduler uiThread , 29 | CinemaLocalRepository repository){ 30 | return new GetFavouriteListCinema(executorThread , uiThread , repository); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/presentation/main/MainPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.main; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | 5 | import org.junit.Test; 6 | import org.mockito.Mock; 7 | 8 | import static org.junit.Assert.*; 9 | import static org.mockito.Mockito.times; 10 | import static org.mockito.Mockito.verify; 11 | 12 | public class MainPresenterTest extends UnitTest { 13 | 14 | @Mock private MainPresenter.View view; 15 | 16 | private MainPresenter presenter; 17 | 18 | @Override 19 | protected void onSetUp() { 20 | presenter = new MainPresenter(); 21 | } 22 | 23 | @Test 24 | public void shouldShowErrorWhenInternetNotConnected(){ 25 | presenter.setView(view); 26 | presenter.initialize(); 27 | presenter.onNetworkConnectionChanged(false); 28 | 29 | verify(view , times(1)).startToListenInternetConnection(); 30 | verify(view , times(1)).showNetworkError(); 31 | } 32 | 33 | @Test 34 | public void shouldShowErrorIfInternetNotConnectedWhenRetryButtonClicked(){ 35 | presenter.setView(view); 36 | presenter.onRetryButtonClicked(false); 37 | 38 | verify(view , times(1)).showNetworkError(); 39 | } 40 | 41 | @Test 42 | public void shouldScrollToFirstPositionWhenFABClicked(){ 43 | presenter.setView(view); 44 | presenter.onFABScrollUpClicked(); 45 | 46 | verify(view , times(1)).scrollToFirstPosition(); 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/utils/AnimUtils.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.utils; 2 | 3 | import android.animation.Animator; 4 | import android.support.v4.view.animation.FastOutSlowInInterpolator; 5 | import android.view.View; 6 | import android.view.ViewAnimationUtils; 7 | 8 | 9 | public class AnimUtils { 10 | public static void startRevealAnimation(View view){ 11 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){ 12 | view.post(() -> { 13 | view.setVisibility(View.INVISIBLE); 14 | int cx = view.getWidth() / 2; 15 | int cy = view.getHeight(); 16 | 17 | float endRadius = (float) Math.hypot(cx , cy); 18 | Animator animator = ViewAnimationUtils.createCircularReveal(view , cx , cy , 0 , endRadius); 19 | view.setVisibility(View.VISIBLE); 20 | animator.start(); 21 | }); 22 | } 23 | } 24 | 25 | public static void startRevealAnimationWithOutVisibility(View view){ 26 | if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP){ 27 | view.post(() -> { 28 | int cx = view.getWidth(); 29 | int cy = view.getHeight(); 30 | 31 | float endRadius = (float) Math.hypot(cx , cy); 32 | Animator animator = ViewAnimationUtils.createCircularReveal(view , cx , cy , 0 , endRadius); 33 | animator.setInterpolator(new FastOutSlowInInterpolator()); 34 | animator.start(); 35 | }); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/cinemausecases/GetCinemaById.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain.cinemausecases; 2 | 3 | import com.ru.devit.mediateka.domain.CinemaRepository; 4 | import com.ru.devit.mediateka.domain.SystemTimeCalculator; 5 | import com.ru.devit.mediateka.domain.UseCase; 6 | import com.ru.devit.mediateka.models.model.Cinema; 7 | import com.ru.devit.mediateka.models.model.DateAndTimeInfo; 8 | 9 | import io.reactivex.Flowable; 10 | import io.reactivex.Scheduler; 11 | 12 | public class GetCinemaById extends UseCase { 13 | 14 | private int cinemaId; 15 | private final CinemaRepository repository; 16 | private final SystemTimeCalculator systemTimeCalculator; 17 | 18 | public GetCinemaById(Scheduler executorThread, 19 | Scheduler uiThread, 20 | CinemaRepository repository, 21 | SystemTimeCalculator systemTimeCalculator){ 22 | super(executorThread , uiThread); 23 | this.repository = repository; 24 | this.systemTimeCalculator = systemTimeCalculator; 25 | } 26 | 27 | public void searchCinemaById(int cinemaId){ 28 | this.cinemaId = cinemaId; 29 | } 30 | 31 | @Override 32 | protected Flowable createUseCase() { 33 | return repository.getCinemaById(cinemaId) 34 | .toFlowable(); 35 | } 36 | 37 | public boolean isRetrievedTimeMoreThanCurrentTime(DateAndTimeInfo dateAndTimeInfo){ 38 | return systemTimeCalculator.futureTimeInMillisFromDateAndTimeInfo(dateAndTimeInfo) > systemTimeCalculator.currentTimeInMillis(); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/data/datasource/db/ActorDao.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.data.datasource.db; 2 | 3 | import android.arch.persistence.room.Dao; 4 | import android.arch.persistence.room.Insert; 5 | import android.arch.persistence.room.OnConflictStrategy; 6 | import android.arch.persistence.room.Query; 7 | 8 | import com.ru.devit.mediateka.models.db.ActorEntity; 9 | import com.ru.devit.mediateka.models.db.CinemaEntity; 10 | 11 | 12 | import java.util.List; 13 | 14 | import io.reactivex.Flowable; 15 | import io.reactivex.Single; 16 | 17 | import static android.arch.persistence.room.OnConflictStrategy.*; 18 | 19 | @Dao 20 | public interface ActorDao { 21 | 22 | @Query("SELECT * FROM ActorTable WHERE actorId = :actorId") 23 | Single getActorById(final int actorId); 24 | 25 | @Query("SELECT * FROM ActorTable") 26 | List getAllActors(); 27 | 28 | @Query("SELECT * FROM ActorTable WHERE actorName LIKE :actorName") 29 | Flowable> getAllActorsByName(final String actorName); 30 | 31 | @Insert(onConflict = REPLACE) 32 | void insertActors(List actorEntities); 33 | 34 | @Insert(onConflict = OnConflictStrategy.IGNORE) 35 | void insertCinemas(List cinemaEntities); 36 | 37 | @Query("UPDATE ActorTable SET biography = :biography , birthDay = :birthDay , age = :age , placeOfBirth = :placeOfBirth " + 38 | "WHERE actorId = :actorId") 39 | void updateActor(int actorId , String biography, String birthDay, String age, String placeOfBirth); 40 | 41 | @Query("SELECT * FROM ActorTable ORDER BY popularity DESC") 42 | Single> getPopularActors(); 43 | } 44 | -------------------------------------------------------------------------------- /app/src/main/res/layout/cinema_detail_floating_action_button_menu.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 21 | 22 | 29 | 30 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/mapper/CinemaMapper.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.mapper; 2 | 3 | import com.ru.devit.mediateka.models.db.ActorEntity; 4 | import com.ru.devit.mediateka.models.db.CinemaEntity; 5 | import com.ru.devit.mediateka.models.model.Actor; 6 | import com.ru.devit.mediateka.models.model.Cinema; 7 | import com.ru.devit.mediateka.models.network.CinemaDetailResponse; 8 | import com.ru.devit.mediateka.models.network.CinemaResponse; 9 | 10 | import java.util.List; 11 | 12 | public class CinemaMapper { 13 | 14 | private CinemaResponseToCinema cinemaResponseToCinema; 15 | private CinemaEntityToCinema cinemaEntityToCinema; 16 | 17 | 18 | public CinemaMapper(CinemaResponseToCinema cinemaResponseToCinema, 19 | CinemaEntityToCinema cinemaEntityToCinema) { 20 | this.cinemaResponseToCinema = cinemaResponseToCinema; 21 | this.cinemaEntityToCinema = cinemaEntityToCinema; 22 | } 23 | 24 | public Cinema map(CinemaDetailResponse response){ 25 | return cinemaResponseToCinema.map(response); 26 | } 27 | 28 | public List map(CinemaResponse response){ 29 | return cinemaResponseToCinema.map(response); 30 | } 31 | 32 | public CinemaEntity map(Cinema cinema){ 33 | return cinemaEntityToCinema.map(cinema); 34 | } 35 | 36 | public List map(List cinemas){ 37 | return cinemaEntityToCinema.map(cinemas); 38 | } 39 | 40 | public List mapActors(List actors){ 41 | return cinemaEntityToCinema.mapActors(actors); 42 | } 43 | 44 | public CinemaEntityToCinema getCinemaEntityToCinema() { 45 | return cinemaEntityToCinema; 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/utils/UrlImagePathCreator.kt: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.utils 2 | 3 | 4 | object UrlImagePathCreator { 5 | 6 | private const val IMG_PATH_W1280 = "https://image.tmdb.org/t/p/w1280/" 7 | private const val IMG_PATH_W780 = "https://image.tmdb.org/t/p/w780/" 8 | private const val IMG_PATH_W185 = "https://image.tmdb.org/t/p/w185/" 9 | 10 | fun createPictureUrlFromQuality(qualityType: Quality, imgUrl: String?): String { 11 | for (quality in Quality.values()) { 12 | if (quality == qualityType) { 13 | return qualityType.quality(imgUrl.orEmpty()) 14 | } 15 | } 16 | throw IllegalArgumentException("No such $qualityType") 17 | } 18 | 19 | enum class Quality { 20 | Quality1280 { 21 | override fun quality(imgUrl: String): String { 22 | return create1280pPictureUrl(imgUrl) 23 | } 24 | }, 25 | Quality780 { 26 | override fun quality(imgUrl: String): String { 27 | return create780pPictureUrl(imgUrl) 28 | } 29 | }, 30 | Quality185 { 31 | override fun quality(imgUrl: String): String { 32 | return create185pPictureUrl(imgUrl) 33 | } 34 | }; 35 | 36 | abstract fun quality(imgUrl: String): String 37 | } 38 | 39 | private fun create1280pPictureUrl(imgUrl: String): String { 40 | return IMG_PATH_W1280 + imgUrl 41 | } 42 | 43 | private fun create780pPictureUrl(imgUrl: String): String { 44 | return IMG_PATH_W780 + imgUrl 45 | } 46 | 47 | private fun create185pPictureUrl(imgUrl: String): String { 48 | return IMG_PATH_W185 + imgUrl 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/MediatekaApp.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka; 2 | 3 | import android.app.Application; 4 | import android.arch.persistence.room.Room; 5 | 6 | import com.ru.devit.mediateka.data.ConnectionReceiver; 7 | import com.ru.devit.mediateka.data.datasource.db.MediatekaDatabase; 8 | import com.ru.devit.mediateka.di.ComponentsManager; 9 | import com.ru.devit.mediateka.presentation.main.SyncConnectionListener; 10 | 11 | import io.reactivex.internal.functions.Functions; 12 | import io.reactivex.plugins.RxJavaPlugins; 13 | 14 | 15 | public class MediatekaApp extends Application { 16 | 17 | private static ComponentsManager componentsManager; 18 | private static MediatekaDatabase database; 19 | private static final String DATABASE_NAME = "mediateka_db"; 20 | 21 | @Override 22 | public void onCreate() { 23 | super.onCreate(); 24 | initComponentsManager(); 25 | initAppComponent(); 26 | RxJavaPlugins.setErrorHandler(Functions.emptyConsumer()); 27 | } 28 | 29 | public static ComponentsManager getComponentsManager(){ 30 | return componentsManager; 31 | } 32 | 33 | public void setConnectionListener(SyncConnectionListener connectionListener){ 34 | ConnectionReceiver.connectionListener = connectionListener; 35 | } 36 | 37 | public MediatekaDatabase getDatabaseInstance(){ 38 | if (database == null){ 39 | database = Room.databaseBuilder(this , MediatekaDatabase.class , DATABASE_NAME).build(); 40 | } 41 | return database; 42 | } 43 | 44 | private void initComponentsManager(){ 45 | componentsManager = new ComponentsManager(this); 46 | } 47 | 48 | private void initAppComponent(){ 49 | componentsManager.getAppComponent(); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/main/MainPresenter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.main; 2 | 3 | import com.ru.devit.mediateka.presentation.base.BasePresenter; 4 | import com.ru.devit.mediateka.presentation.cinemalist.CinemaTabPositionPicker; 5 | import com.ru.devit.mediateka.presentation.common.CinemaTabSelectorView; 6 | 7 | import javax.inject.Inject; 8 | 9 | public class MainPresenter extends BasePresenter implements SyncConnectionListener { 10 | 11 | private final CinemaTabPositionPicker cinemaTabPositionPicker = new CinemaTabPositionPicker(); 12 | 13 | @Inject public MainPresenter(){} 14 | 15 | @Override 16 | public void initialize() { 17 | getView().startToListenInternetConnection(); 18 | } 19 | 20 | @Override 21 | public void onDestroy() { 22 | setView(null); 23 | } 24 | 25 | @Override 26 | public void onNetworkConnectionChanged(boolean internetConnected) { 27 | if (!internetConnected){ 28 | getView().showNetworkError(); 29 | } 30 | } 31 | 32 | void onRetryButtonClicked(boolean internetConnected) { 33 | if (!internetConnected){ 34 | getView().showNetworkError(); 35 | } else { 36 | getView().hideNetworkError(); 37 | } 38 | } 39 | 40 | public void onFABScrollUpClicked() { 41 | getView().scrollToFirstPosition(); 42 | } 43 | 44 | public void onTabSelected(int position) { 45 | cinemaTabPositionPicker.loadCinemaFromCinemaPosition(position , getView()); 46 | } 47 | 48 | public interface View extends CinemaTabSelectorView { 49 | void startToListenInternetConnection(); 50 | void showNetworkError(); 51 | void hideNetworkError(); 52 | void scrollToFirstPosition(); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | 15 | 16 | 20 | 21 | 27 | 28 | 31 | 32 | 38 | 39 | 41 | 42 | 44 | 45 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/presentation/actorlist/ActorsPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.actorlist; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | import com.ru.devit.mediateka.domain.actorusecases.GetActorsByQuery; 5 | 6 | import org.junit.Test; 7 | import org.mockito.InOrder; 8 | import org.mockito.Mock; 9 | 10 | import static org.mockito.Mockito.*; 11 | import static org.mockito.Mockito.verify; 12 | 13 | public class ActorsPresenterTest extends UnitTest { 14 | 15 | @Mock private ActorsPresenter.View view; 16 | @Mock private GetActorsByQuery useCaseGetActorsByQueryMock; 17 | 18 | private ActorsPresenter presenter; 19 | 20 | @Override 21 | protected void onSetUp() { 22 | presenter = new ActorsPresenter(useCaseGetActorsByQueryMock); 23 | } 24 | 25 | @Test 26 | public void shouldOpenActorDetailedScreenWhenActorClicked(){ 27 | presenter.setView(view); 28 | presenter.onActorClicked(17890 , 3); 29 | 30 | verify(view , times(1)).openActor(17890 , 3); 31 | } 32 | 33 | @Test 34 | public void shouldUseCaseDisposeWhenPresenterDestroy(){ 35 | presenter.onDestroy(); 36 | 37 | verify(useCaseGetActorsByQueryMock).removeActions(); 38 | verify(useCaseGetActorsByQueryMock).dispose(); 39 | } 40 | 41 | @Test 42 | public void shouldShowLoadingWhenInitialize(){ 43 | presenter.setView(view); 44 | presenter.initialize(); 45 | 46 | InOrder inOrder = inOrder(view); 47 | 48 | inOrder.verify(view).showLoading(); 49 | inOrder.verify(view).hideLoading(); 50 | } 51 | 52 | @Test 53 | public void shouldSetQueryWhenPresenterSetQuery(){ 54 | presenter.onGetTextFromSearchField("Transformers"); 55 | 56 | verify(useCaseGetActorsByQueryMock).setQuery("Transformers"); 57 | } 58 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/actorlist/ActorListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.actorlist; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.LayoutInflater; 6 | import android.view.View; 7 | import android.view.ViewGroup; 8 | 9 | import com.ru.devit.mediateka.R; 10 | import com.ru.devit.mediateka.models.model.Actor; 11 | import com.ru.devit.mediateka.presentation.common.OnActorClickListener; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | public class ActorListAdapter extends RecyclerView.Adapter { 17 | 18 | private final List actors; 19 | private final OnActorClickListener onActorClickListener; 20 | 21 | public ActorListAdapter(OnActorClickListener onActorClickListener) { 22 | actors = new ArrayList<>(); 23 | this.onActorClickListener = onActorClickListener; 24 | } 25 | 26 | @Override 27 | @NonNull 28 | public ActorViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { 29 | View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_actor , parent , false); 30 | return new ActorViewHolder(view , onActorClickListener); 31 | } 32 | 33 | @Override 34 | public void onBindViewHolder(@NonNull ActorViewHolder holder, int position) { 35 | Actor actor = actors.get(position); 36 | holder.render(actor , holder.getAdapterPosition()); 37 | } 38 | 39 | @Override 40 | public int getItemCount() { 41 | return actors.size(); 42 | } 43 | 44 | public void addAll(List actorList){ 45 | actors.clear(); 46 | actors.addAll(actorList); 47 | notifyDataSetChanged(); 48 | } 49 | 50 | public void clear(){ 51 | actors.clear(); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/models/network/CinemaNetwork.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.network; 2 | 3 | import com.google.gson.annotations.Expose; 4 | import com.google.gson.annotations.SerializedName; 5 | 6 | public class CinemaNetwork { 7 | @SerializedName("id") private int id; 8 | @SerializedName("overview") private String description; 9 | @SerializedName("poster_path") private String posterUrl; 10 | @SerializedName("adult") private boolean isAdult; 11 | @SerializedName("title") private String title; 12 | @SerializedName("release_date") private String releaseDate; 13 | @SerializedName("vote_average") private float voteAverage; 14 | @SerializedName("popularity") private float popularity; 15 | @SerializedName("genre_ids") private int[] genreIds; 16 | @SerializedName("character") private String character; 17 | 18 | public int getId() { 19 | return id; 20 | } 21 | 22 | public void setId(int id) { 23 | this.id = id; 24 | } 25 | 26 | public String getDescription() { 27 | return description; 28 | } 29 | 30 | public String getPosterUrl() { 31 | return posterUrl; 32 | } 33 | 34 | public boolean isAdult() { 35 | return isAdult; 36 | } 37 | 38 | public String getTitle() { 39 | return title; 40 | } 41 | 42 | public void setTitle(String title) { 43 | this.title = title; 44 | } 45 | 46 | public String getReleaseDate() { 47 | return releaseDate; 48 | } 49 | 50 | public float getVoteAverage() { 51 | return voteAverage; 52 | } 53 | 54 | public int[] getGenreIds() { 55 | return genreIds; 56 | } 57 | 58 | public float getPopularity() { 59 | return popularity; 60 | } 61 | public String getCharacter() { 62 | return character; 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/UseCase.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain; 2 | 3 | import io.reactivex.CompletableTransformer; 4 | import io.reactivex.Flowable; 5 | import io.reactivex.Scheduler; 6 | import io.reactivex.disposables.CompositeDisposable; 7 | import io.reactivex.subscribers.DisposableSubscriber; 8 | 9 | public abstract class UseCase { 10 | 11 | protected int pageIndex = 1; 12 | 13 | private final Scheduler executorThread; 14 | private final Scheduler uiThread; 15 | private final CompositeDisposable compositeDisposable; 16 | 17 | public UseCase(Scheduler executorThread, 18 | Scheduler uiThread) { 19 | this.executorThread = executorThread; 20 | this.uiThread = uiThread; 21 | compositeDisposable = new CompositeDisposable(); 22 | } 23 | 24 | public void subscribe(DisposableSubscriber disposableSubscriber){ 25 | if (disposableSubscriber == null){ 26 | throw new IllegalArgumentException("subscriber must not be null"); 27 | } 28 | Flowable flowable = createUseCase() 29 | .subscribeOn(executorThread) 30 | .observeOn(uiThread); 31 | 32 | DisposableSubscriber subscriber = flowable.subscribeWith(disposableSubscriber); 33 | compositeDisposable.add(subscriber); 34 | } 35 | 36 | public void setCurrentPage(int pageIndex){ 37 | this.pageIndex = pageIndex; 38 | } 39 | 40 | public void dispose(){ 41 | if (!compositeDisposable.isDisposed()){ 42 | compositeDisposable.dispose(); 43 | } 44 | } 45 | 46 | protected CompletableTransformer applyCompletableSchedulers(){ 47 | return upstream -> upstream.subscribeOn(executorThread) 48 | .observeOn(uiThread); 49 | } 50 | 51 | protected abstract Flowable createUseCase(); 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/utils/pagination/PaginationScrollListener.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.utils.pagination; 2 | 3 | import android.support.v7.widget.GridLayoutManager; 4 | import android.support.v7.widget.LinearLayoutManager; 5 | import android.support.v7.widget.RecyclerView; 6 | 7 | public abstract class PaginationScrollListener extends RecyclerView.OnScrollListener { 8 | 9 | private RecyclerView.LayoutManager layoutManager; 10 | 11 | public PaginationScrollListener(RecyclerView.LayoutManager layoutManager) { 12 | this.layoutManager = layoutManager; 13 | } 14 | 15 | @Override 16 | public void onScrolled(RecyclerView recyclerView, int dx, int dy) { 17 | super.onScrolled(recyclerView, dx, dy); 18 | 19 | int visibleItemCount = layoutManager.getChildCount(); 20 | int totalItemCount = layoutManager.getItemCount(); 21 | int firstVisibleItemPosition = 0; 22 | final int OFFSET = 7; // 7 cinemas before we start loadMoreItems 23 | if (layoutManager instanceof LinearLayoutManager){ 24 | LinearLayoutManager linearLayoutManager = (LinearLayoutManager) layoutManager; 25 | firstVisibleItemPosition = linearLayoutManager.findFirstVisibleItemPosition(); 26 | } else if (layoutManager instanceof GridLayoutManager){ 27 | GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager; 28 | firstVisibleItemPosition = gridLayoutManager.findFirstVisibleItemPosition(); 29 | } 30 | 31 | if (!isLastPage()){ 32 | if (((visibleItemCount + OFFSET) + firstVisibleItemPosition) >= totalItemCount 33 | && firstVisibleItemPosition >= 0){ 34 | loadMoreItems(); 35 | } 36 | } 37 | } 38 | 39 | protected abstract void loadMoreItems(); 40 | 41 | protected abstract boolean isLastPage(); 42 | } 43 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/popularactors/PopularActorsPresenter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.popularactors; 2 | 3 | import com.ru.devit.mediateka.domain.actorusecases.GetActors; 4 | import com.ru.devit.mediateka.models.model.Actor; 5 | import com.ru.devit.mediateka.presentation.base.BasePresenter; 6 | import com.ru.devit.mediateka.presentation.base.BaseView; 7 | 8 | import java.util.List; 9 | 10 | import javax.inject.Inject; 11 | 12 | import io.reactivex.subscribers.DisposableSubscriber; 13 | 14 | public class PopularActorsPresenter extends BasePresenter { 15 | 16 | private final GetActors useCaseGetActors; 17 | 18 | @Inject public PopularActorsPresenter(GetActors useCaseGetActors) { 19 | this.useCaseGetActors = useCaseGetActors; 20 | } 21 | 22 | @Override 23 | public void initialize() { 24 | getView().showLoading(); 25 | useCaseGetActors.subscribe(new DisposableSubscriber>() { 26 | @Override 27 | public void onNext(List actors) { 28 | getView().showPopularActors(actors); 29 | } 30 | 31 | @Override 32 | public void onError(Throwable t) { 33 | getView().showOnError(); 34 | } 35 | 36 | @Override 37 | public void onComplete() { 38 | getView().hideLoading(); 39 | } 40 | }); 41 | } 42 | 43 | @Override 44 | public void onDestroy() { 45 | useCaseGetActors.dispose(); 46 | } 47 | 48 | public void onActorClicked(int actorId, int viewHolderPosition) { 49 | getView().showActorDetail(actorId , viewHolderPosition); 50 | } 51 | 52 | public interface View extends BaseView { 53 | void showPopularActors(List actors); 54 | void showOnError(); 55 | void showActorDetail(int actorId , int viewHolderPos); 56 | } 57 | } 58 | 59 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/ComponentsManager.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di; 2 | 3 | import android.content.Context; 4 | 5 | import com.ru.devit.mediateka.di.actor.ActorComponent; 6 | import com.ru.devit.mediateka.di.actor.ActorModule; 7 | import com.ru.devit.mediateka.di.application.AppComponent; 8 | 9 | 10 | import com.ru.devit.mediateka.di.application.DaggerAppComponent; 11 | import com.ru.devit.mediateka.di.cinema.CinemaComponent; 12 | import com.ru.devit.mediateka.di.cinema.CinemaModule; 13 | 14 | import com.ru.devit.mediateka.di.application.AppModule; 15 | 16 | 17 | public class ComponentsManager { 18 | 19 | private AppComponent appComponent; 20 | private CinemaComponent cinemaComponent; 21 | private ActorComponent actorComponent; 22 | 23 | private final Context context; 24 | 25 | public ComponentsManager(Context context) { 26 | this.context = context.getApplicationContext(); 27 | } 28 | 29 | public AppComponent getAppComponent(){ 30 | if (appComponent == null){ 31 | appComponent = DaggerAppComponent.builder() 32 | .appModule(new AppModule(context)) 33 | .build(); 34 | } 35 | return appComponent; 36 | } 37 | 38 | public CinemaComponent plusCinemaComponent(){ 39 | if (cinemaComponent == null){ 40 | cinemaComponent = appComponent.plusCinemaComponent(new CinemaModule()); 41 | } 42 | return cinemaComponent; 43 | } 44 | 45 | public ActorComponent plusActorComponent(){ 46 | if (actorComponent == null){ 47 | actorComponent = appComponent.plusActorComponent(new ActorModule()); 48 | } 49 | return actorComponent; 50 | } 51 | 52 | public void clearActorComponent(){ 53 | actorComponent = null; 54 | } 55 | 56 | public void clearCinemaComponent(){ 57 | cinemaComponent = null; 58 | } 59 | 60 | 61 | } 62 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/models/mapper/ActorDetailResponseToActorTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.mapper; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | import com.ru.devit.mediateka.models.model.Actor; 5 | import com.ru.devit.mediateka.models.network.ActorDetailResponse; 6 | import com.ru.devit.mediateka.models.network.ActorNetwork; 7 | import com.ru.devit.mediateka.models.network.ActorResponse; 8 | 9 | import org.junit.Test; 10 | import org.mockito.Mock; 11 | 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | import static org.hamcrest.CoreMatchers.is; 16 | import static org.junit.Assert.*; 17 | import static org.mockito.Mockito.*; 18 | 19 | @SuppressWarnings("ResultOfMethodCallIgnored") 20 | public class ActorDetailResponseToActorTest extends UnitTest { 21 | 22 | @Mock private ActorDetailResponse responseDetailMock; 23 | @Mock private ActorResponse responseMock; 24 | @Mock private List actorNetworkListMock; 25 | 26 | private ActorDetailResponseToActor mapper; 27 | 28 | @Override 29 | protected void onSetUp() { 30 | mapper = new ActorDetailResponseToActor(); 31 | } 32 | 33 | @Test 34 | public void shouldMapId() { 35 | when(responseDetailMock.getId()).thenReturn(22); 36 | 37 | Actor actor = mapper.map(responseDetailMock); 38 | assertThat(actor.getActorId() , is(22)); 39 | } 40 | 41 | @Test 42 | public void shouldMapListOfActor() { 43 | List spyListOfActors = spy(new ArrayList<>()); 44 | spyListOfActors.add(new ActorNetwork()); 45 | doReturn(1).when(actorNetworkListMock).size(); 46 | doReturn(spyListOfActors).when(responseMock).getActors(); 47 | 48 | final List actorList = mapper.map(responseMock); 49 | assertNotNull(actorList); 50 | assertFalse(actorList.isEmpty()); 51 | assertEquals(responseMock.getActors().size() , actorList.size()); 52 | } 53 | } -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/presentation/search/SearchPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.search; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | 5 | import org.junit.Test; 6 | import org.mockito.Mock; 7 | 8 | import static org.junit.Assert.*; 9 | import static org.mockito.Mockito.times; 10 | import static org.mockito.Mockito.verify; 11 | 12 | public class SearchPresenterTest extends UnitTest { 13 | 14 | @Mock private SearchPresenter.View view; 15 | 16 | private SearchPresenter presenter; 17 | private static final String TEST_QUERY = "test query"; 18 | private static final int CINEMAS_TAB_POSITION = 0; 19 | private static final int ACTORS_TAB_POSITION = 1; 20 | 21 | 22 | @Override 23 | protected void onSetUp() { 24 | presenter = new SearchPresenter(); 25 | } 26 | 27 | @Test 28 | public void shouldSelectCinemaPositionWhenCinemaTabSelected(){ 29 | presenter.setView(view); 30 | presenter.onTabSelected(CINEMAS_TAB_POSITION); 31 | 32 | verify(view).onCinemaTabSelected(); 33 | } 34 | 35 | @Test 36 | public void shouldSelectActorPositionWhenActorTabSelected(){ 37 | presenter.setView(view); 38 | presenter.onTabSelected(ACTORS_TAB_POSITION); 39 | 40 | verify(view).onActorTabSelected(); 41 | } 42 | 43 | @Test 44 | public void shouldSendQueryToCinemaTabWhenCinemaTabSelected(){ 45 | presenter.setView(view); 46 | presenter.onTabSelected(CINEMAS_TAB_POSITION); 47 | presenter.onTextChanged(TEST_QUERY); 48 | 49 | verify(view , times(1)).textFromCinemaTab(TEST_QUERY); 50 | } 51 | 52 | @Test 53 | public void shouldSendQueryToActorTabWhenActorTabSelected(){ 54 | presenter.setView(view); 55 | presenter.onTabSelected(ACTORS_TAB_POSITION); 56 | presenter.onTextChanged(TEST_QUERY); 57 | 58 | verify(view , times(1)).textFromActorTab(TEST_QUERY); 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_posters_slider.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 18 | 19 | 27 | 28 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/data/datasource/network/CinemaApiService.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.data.datasource.network; 2 | 3 | import com.ru.devit.mediateka.models.network.ActorDetailResponse; 4 | import com.ru.devit.mediateka.models.network.ActorResponse; 5 | import com.ru.devit.mediateka.models.network.CinemaDetailResponse; 6 | import com.ru.devit.mediateka.models.network.CinemaResponse; 7 | import com.ru.devit.mediateka.models.network.ImagesResponse; 8 | 9 | import io.reactivex.Flowable; 10 | import io.reactivex.Single; 11 | import retrofit2.http.GET; 12 | import retrofit2.http.Path; 13 | import retrofit2.http.Query; 14 | 15 | public interface CinemaApiService { 16 | 17 | @GET("movie/popular") 18 | Single getCinemas(@Query("page") int pageIndex); 19 | 20 | @GET("movie/top_rated") 21 | Single getTopRatedCinemas(@Query("page") int pageIndex); 22 | 23 | @GET("movie/upcoming") 24 | Single getUpComingCinemas(@Query("page") int pageIndex); 25 | 26 | @GET("movie/{movie_id}") 27 | Single getCinemaById(@Path("movie_id") int movieId , @Query("append_to_response") String appendToResponse); 28 | 29 | @GET("person/{person_id}") 30 | Single getActorById(@Path("person_id") int actorId , @Query("append_to_response") String appendToResponse); 31 | 32 | @GET("person/popular") 33 | Single getPopularActors(@Query("page") int pageIndex); 34 | 35 | @GET("search/movie") 36 | Flowable searchCinemas(@Query("query") String searchQuery); 37 | 38 | @GET("search/person") 39 | Flowable searchActors(@Query("query") String searchQuery); 40 | 41 | @GET("movie/{movie_id}/images") 42 | Single getImagesForCinema(@Path("movie_id") int cinemaId , @Query("include_image_language") String includedLanguages); 43 | 44 | @GET("person/{person_id}/images") 45 | Single getImagesForActor(@Path("person_id") int actorId); 46 | } 47 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/application/AppModule.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.application; 2 | 3 | import android.content.Context; 4 | import android.content.SharedPreferences; 5 | 6 | import com.ru.devit.mediateka.MediatekaApp; 7 | import com.ru.devit.mediateka.data.SharedPreferenceManager; 8 | import com.ru.devit.mediateka.data.datasource.db.CinemaActorJoinDao; 9 | 10 | 11 | import javax.inject.Named; 12 | import javax.inject.Singleton; 13 | 14 | import dagger.Module; 15 | import dagger.Provides; 16 | import io.reactivex.Scheduler; 17 | import io.reactivex.android.schedulers.AndroidSchedulers; 18 | import io.reactivex.schedulers.Schedulers; 19 | 20 | @Module 21 | public class AppModule { 22 | 23 | private final Context context; 24 | private final String APP_PREFS = "Application_preferences"; 25 | 26 | public AppModule(Context context){ 27 | this.context = context.getApplicationContext(); 28 | } 29 | 30 | @Singleton 31 | @Provides 32 | Context provideContext(){ 33 | return context; 34 | } 35 | 36 | @Singleton 37 | @Provides 38 | SharedPreferences provideSharedPreference(){ 39 | return context.getSharedPreferences(APP_PREFS , Context.MODE_PRIVATE); 40 | } 41 | 42 | @Singleton 43 | @Provides 44 | SharedPreferenceManager provideSharedPrefernceManager(SharedPreferences sharedPreferences){ 45 | return new SharedPreferenceManager(sharedPreferences); 46 | } 47 | 48 | @Singleton 49 | @Provides 50 | CinemaActorJoinDao provideCinemaActorJoinDao(){ 51 | MediatekaApp mediatekaApp = (MediatekaApp) context; 52 | return mediatekaApp.getDatabaseInstance() 53 | .getCinemaActorJoinDao(); 54 | } 55 | 56 | @Named("executor_thread") 57 | @Provides 58 | Scheduler provideExecutorThread(){ 59 | return Schedulers.io(); 60 | } 61 | 62 | @Named("ui_thread") 63 | @Provides 64 | Scheduler provideUiThread(){ 65 | return AndroidSchedulers.mainThread(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/actordetail/ActorDetailPresenter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.actordetail; 2 | 3 | import com.ru.devit.mediateka.domain.actorusecases.GetActorById; 4 | import com.ru.devit.mediateka.domain.UseCaseSubscriber; 5 | import com.ru.devit.mediateka.models.model.Actor; 6 | import com.ru.devit.mediateka.presentation.base.BasePresenter; 7 | import com.ru.devit.mediateka.presentation.base.BaseView; 8 | 9 | import java.util.List; 10 | 11 | public class ActorDetailPresenter extends BasePresenter { 12 | 13 | private final GetActorById useCaseGetCinemaById; 14 | private int actorId; 15 | 16 | public ActorDetailPresenter(GetActorById useCaseGetCinemaById) { 17 | this.useCaseGetCinemaById = useCaseGetCinemaById; 18 | } 19 | 20 | @Override 21 | public void initialize() { 22 | getView().showLoading(); 23 | useCaseGetCinemaById.searchActorById(actorId); 24 | useCaseGetCinemaById.subscribe(new ActorDetailSubscriber()); 25 | } 26 | 27 | @Override 28 | public void onDestroy() { 29 | useCaseGetCinemaById.dispose(); 30 | setView(null); 31 | } 32 | 33 | public void setActorId(int actorId) { 34 | this.actorId = actorId; 35 | } 36 | 37 | public void onAvatarClicked(List posterUrls) { 38 | getView().showPosters(posterUrls); 39 | } 40 | 41 | public interface View extends BaseView{ 42 | void showActorDetail(Actor actor); 43 | void showPosters(List posterUrls); 44 | } 45 | 46 | public class ActorDetailSubscriber extends UseCaseSubscriber{ 47 | @Override 48 | public void onNext(Actor actor) { 49 | getView().showActorDetail(actor); 50 | } 51 | 52 | @Override 53 | public void onError(Throwable e) { 54 | e.printStackTrace(); 55 | getView().hideLoading(); 56 | } 57 | 58 | @Override 59 | public void onComplete() { 60 | getView().hideLoading(); 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/settings/PreferenceSettingsFragment.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.settings; 2 | 3 | import android.os.Bundle; 4 | import android.preference.ListPreference; 5 | import android.preference.Preference; 6 | import android.preference.PreferenceFragment; 7 | import android.preference.PreferenceManager; 8 | import android.support.annotation.Nullable; 9 | 10 | import com.ru.devit.mediateka.R; 11 | 12 | public class PreferenceSettingsFragment extends PreferenceFragment { 13 | 14 | @Override 15 | public void onCreate(@Nullable Bundle savedInstanceState) { 16 | super.onCreate(savedInstanceState); 17 | addPreferencesFromResource(R.xml.pref_mediateka_settings); 18 | 19 | bindPreferenceSummaryToValue(findPreference(getString(R.string.pref_key_background_image_quality_type))); 20 | } 21 | 22 | private void bindPreferenceSummaryToValue(Preference preference) { 23 | preference.setOnPreferenceChangeListener(sBindPreferenceSummaryToValueListener); 24 | 25 | sBindPreferenceSummaryToValueListener.onPreferenceChange(preference, 26 | PreferenceManager 27 | .getDefaultSharedPreferences(preference.getContext()) 28 | .getString(preference.getKey(), "")); 29 | } 30 | 31 | private final Preference.OnPreferenceChangeListener sBindPreferenceSummaryToValueListener = (preference, newValue) -> { 32 | String stringValue = newValue.toString(); 33 | 34 | if (preference instanceof ListPreference) { 35 | 36 | ListPreference listPreference = (ListPreference) preference; 37 | int index = listPreference.findIndexOfValue(stringValue); 38 | 39 | preference.setSummary(index >= 0 ? 40 | qualitySummary(listPreference.getEntries()[index].toString()) 41 | : qualitySummary(listPreference.getEntries()[0].toString())); 42 | } 43 | return true; 44 | }; 45 | 46 | private String qualitySummary(String quality){ 47 | return String.format("%s: %s" , getString(R.string.background_poster_image_quality) , quality); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/common/AbstractCinemaListAdapter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.common; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.v7.widget.RecyclerView; 5 | import android.view.ViewGroup; 6 | 7 | import com.ru.devit.mediateka.models.model.Cinema; 8 | 9 | import java.util.ArrayList; 10 | import java.util.LinkedHashSet; 11 | import java.util.List; 12 | import java.util.Set; 13 | 14 | /* 15 | Must extends all adapters which related with Cinema. 16 | */ 17 | 18 | public abstract class AbstractCinemaListAdapter , VH extends RecyclerView.ViewHolder> extends RecyclerView.Adapter { 19 | 20 | protected final OnCinemaClickListener onCinemaClickListener; 21 | protected final List cinemas; 22 | private final Set cinemaSet; 23 | 24 | public AbstractCinemaListAdapter(OnCinemaClickListener onCinemaClickListener) { 25 | this.onCinemaClickListener = onCinemaClickListener; 26 | cinemas = new ArrayList<>(); 27 | cinemaSet = new LinkedHashSet<>(); 28 | } 29 | 30 | @Override 31 | @NonNull 32 | public abstract VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType); 33 | 34 | @Override 35 | public void onBindViewHolder(@NonNull VH holder, int position) { 36 | Cinema cinema = cinemas.get(position); 37 | HOLDER_RENDERER typedHolder = (HOLDER_RENDERER) holder; 38 | typedHolder.render(cinema , holder.getAdapterPosition()); 39 | } 40 | 41 | @Override 42 | public int getItemCount() { 43 | return cinemas.size(); 44 | } 45 | 46 | public void addAll(List cinemaEntities){ 47 | cinemaSet.addAll(cinemaEntities); 48 | cinemas.clear(); 49 | cinemas.addAll(cinemaSet); 50 | notifyItemInserted(getItemCount()); 51 | } 52 | 53 | public void notifyRemoveEach() { 54 | for (int i = 0; i < cinemas.size(); i++) { 55 | notifyItemRemoved(i); 56 | } 57 | } 58 | 59 | public void notifyAddEach() { 60 | for (int i = 0; i < cinemas.size(); i++) { 61 | notifyItemInserted(i); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | sudo: false 3 | jdk: oraclejdk8 4 | 5 | notifications: 6 | email: false 7 | 8 | before_cache: 9 | 10 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 11 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 12 | 13 | cache: 14 | 15 | directories: 16 | 17 | - $HOME/.gradle/caches/ 18 | - $HOME/.gradle/wrapper/ 19 | - $HOME/.android/build-cache 20 | 21 | env: 22 | 23 | global: 24 | 25 | - ANDROID_API_LEVEL=26 26 | - ANDROID_BUILD_TOOLS_VERSION=28.0.0 27 | - ANDROID_EMU_API_LEVEL=21 28 | - ANDROID_ABI=armeabi-v7a 29 | - ADB_INSTALL_TIMEOUT=5 # minutes 30 | 31 | android: 32 | 33 | components: 34 | 35 | - tools 36 | - platform-tools 37 | - tools 38 | - build-tools-$ANDROID_BUILD_TOOLS 39 | - android-$ANDROID_API_LEVEL 40 | - android-$ANDROID_EMU_API_LEVEL 41 | - extra-google-m2repository 42 | - extra-android-m2repository # for design library 43 | - sys-img-armeabi-v7a-google_apis-$ANDROID_API_LEVEL 44 | - sys-img-armeabi-v7a-google_apis-$ANDROID_EMU_API_LEVEL 45 | 46 | licenses: 47 | 48 | - android-sdk-preview-license-.+ 49 | - android-sdk-license-.+ 50 | - google-gdk-license-.+ 51 | 52 | before_install: 53 | 54 | - yes | sdkmanager "platforms;android-27" 55 | - mkdir "$ANDROID_HOME/licenses" || true 56 | - echo -e "\n8933bad161af4178b1185d1a37fbf41ea5269c55" > "$ANDROID_HOME/licenses/android-sdk-license" 57 | - echo -e "\n84831b9409646a918e30573bab4c9c91346d8abd" > "$ANDROID_HOME/licenses/android-sdk-preview-license" 58 | - chmod +x gradlew 59 | - ./gradlew dependencies || true 60 | 61 | install: 62 | - echo y | android update sdk -u -a -t tools 63 | - echo y | android update sdk -u -a -t platform-tools 64 | - echo y | android update sdk -u -a -t build-tools-28.0.0 65 | - echo y | android update sdk -u -a -t android-26 66 | - echo y | android update sdk -u -a -t extra-google-m2repository 67 | - echo y | android update sdk -u -a -t extra-android-m2repository 68 | 69 | before_script: 70 | 71 | - echo no | android create avd --force -n test -t android-$ANDROID_EMU_API_LEVEL --abi google_apis/$ANDROID_ABI 72 | - emulator -avd test -no-window & 73 | - android-wait-for-emulator 74 | - adb shell input keyevent 82 & 75 | 76 | script: 77 | 78 | - ./gradlew clean test build 79 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/widget/HideableFABBehaviour.kt: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.widget 2 | 3 | import android.content.Context 4 | import android.support.design.widget.CoordinatorLayout 5 | import android.support.design.widget.FloatingActionButton 6 | import android.support.v4.view.ViewCompat 7 | import android.util.AttributeSet 8 | import android.view.View 9 | 10 | class HideableFABBehaviour(context: Context, attrs: AttributeSet) : FloatingActionButton.Behavior(context, attrs) { 11 | 12 | override fun onStartNestedScroll(coordinatorLayout: CoordinatorLayout, 13 | child: FloatingActionButton, 14 | directTargetChild: View, 15 | target: View, 16 | axes: Int, 17 | type: Int): Boolean { 18 | return axes == ViewCompat.SCROLL_AXIS_VERTICAL || super.onStartNestedScroll(coordinatorLayout, child, directTargetChild, target, 19 | axes, type) 20 | } 21 | 22 | override fun onNestedScroll(coordinatorLayout: CoordinatorLayout, 23 | child: FloatingActionButton, 24 | target: View, 25 | dxConsumed: Int, 26 | dyConsumed: Int, 27 | dxUnconsumed: Int, 28 | dyUnconsumed: Int, 29 | type: Int) { 30 | super.onNestedScroll(coordinatorLayout, child, target, dxConsumed, 31 | dyConsumed, dxUnconsumed, dyUnconsumed, type) 32 | 33 | if (dyConsumed > 0 && child.visibility == View.VISIBLE) { 34 | child.hide(object : FloatingActionButton.OnVisibilityChangedListener() { 35 | override fun onHidden(fab: FloatingActionButton?) { 36 | super.onHidden(fab) 37 | fab!!.visibility = View.INVISIBLE 38 | } 39 | }) 40 | } else if (dyConsumed < 0 && child.visibility != View.VISIBLE) { 41 | child.show() 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | 4 | android { 5 | compileSdkVersion 28 6 | buildToolsVersion "28.0.3" 7 | defaultConfig { 8 | applicationId "com.ru.devit.mediateka" 9 | minSdkVersion 19 10 | targetSdkVersion 28 11 | versionCode 2 12 | versionName "1.0" 13 | vectorDrawables.useSupportLibrary= true 14 | } 15 | buildTypes { 16 | release { 17 | minifyEnabled false 18 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 19 | } 20 | } 21 | buildTypes.each { 22 | it.buildConfigField 'String', 'THE_MOVIE_DB_API_KEY', THE_MOVIE_DB_API_KEY 23 | } 24 | compileOptions { 25 | sourceCompatibility JavaVersion.VERSION_1_8 26 | targetCompatibility JavaVersion.VERSION_1_8 27 | } 28 | lintOptions { 29 | abortOnError false 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation supportDependencies.appCompat 35 | implementation supportDependencies.design 36 | implementation supportDependencies.supportAnnotation 37 | implementation supportDependencies.constraintLayout 38 | 39 | implementation daggerDependencies.dagger 40 | annotationProcessor daggerDependencies.daggerCompiler 41 | 42 | implementation rxDependencies.rxJava 43 | implementation rxDependencies.rxAndroid 44 | 45 | implementation cardView 46 | implementation picasso 47 | implementation retrofit 48 | 49 | implementation 'com.squareup.retrofit2:adapter-rxjava2:2.4.0' 50 | implementation gsonConverter 51 | 52 | implementation 'android.arch.persistence.room:runtime:1.1.1' 53 | annotationProcessor 'android.arch.persistence.room:compiler:1.1.1' 54 | implementation 'android.arch.persistence.room:rxjava2:1.1.1' 55 | 56 | implementation 'de.hdodenhof:circleimageview:2.1.0' 57 | implementation 'com.android.support:palette-v7:28.0.0' 58 | 59 | testImplementation testingDependencies.junit 60 | testImplementation testingDependencies.mockito 61 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" 62 | } 63 | repositories { 64 | mavenCentral() 65 | } 66 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/favouritelistcinema/CinemaSortingDialog.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.favouritelistcinema; 2 | 3 | import android.app.Dialog; 4 | import android.content.Context; 5 | import android.os.Bundle; 6 | import android.support.annotation.NonNull; 7 | import android.support.annotation.Nullable; 8 | import android.support.v4.app.DialogFragment; 9 | import android.support.v4.app.Fragment; 10 | import android.support.v7.app.AlertDialog; 11 | import android.util.Log; 12 | 13 | import com.ru.devit.mediateka.R; 14 | 15 | public class CinemaSortingDialog extends DialogFragment { 16 | 17 | private String[] arrayCinemaSortingVariants; 18 | private OnDialogItemClicked onDialogItemClicked; 19 | private int position = 0; 20 | 21 | public static CinemaSortingDialog newInstance(){ 22 | return new CinemaSortingDialog(); 23 | } 24 | 25 | @NonNull 26 | @Override 27 | public Dialog onCreateDialog(Bundle savedInstanceState) { 28 | AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(requireActivity()); 29 | alertDialogBuilder.setTitle(getString(R.string.sort_favourite_list)); 30 | alertDialogBuilder.setSingleChoiceItems(arrayCinemaSortingVariants, position , (dialog, which) -> { 31 | onDialogItemClicked.onDialogItemClicked(which); 32 | dialog.dismiss(); 33 | }); 34 | 35 | return alertDialogBuilder.create(); 36 | } 37 | 38 | public void setPosition(int position){ 39 | this.position = position; 40 | } 41 | 42 | @Override 43 | public void onAttach(Context context) { 44 | super.onAttach(context); 45 | try { 46 | onDialogItemClicked = (OnDialogItemClicked) context; 47 | } catch (ClassCastException exception){ 48 | throw new ClassCastException(context.toString() 49 | + " must implement OnDialogItemClicked"); 50 | } 51 | } 52 | 53 | @Override 54 | public void onCreate(@Nullable Bundle savedInstanceState) { 55 | super.onCreate(savedInstanceState); 56 | arrayCinemaSortingVariants = getResources().getStringArray(R.array.array_sorting_cinema_variants); 57 | } 58 | 59 | public interface OnDialogItemClicked{ 60 | void onDialogItemClicked(int position); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_favourite_list_cinema.xml: -------------------------------------------------------------------------------- 1 | 2 | 10 | 11 | 17 | 18 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 40 | 41 | 46 | 47 | 51 | 52 | 53 | 54 | 55 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/cinema/cinemalist/CinemaListModule.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.cinema.cinemalist; 2 | 3 | import com.ru.devit.mediateka.domain.CinemaRepository; 4 | import com.ru.devit.mediateka.di.ActivityScope; 5 | import com.ru.devit.mediateka.domain.cinemausecases.GetCinemas; 6 | import com.ru.devit.mediateka.domain.cinemausecases.GetTopRatedCinemas; 7 | import com.ru.devit.mediateka.domain.cinemausecases.GetUpComingCinemas; 8 | import com.ru.devit.mediateka.presentation.cinemalist.CinemaListPresenter; 9 | 10 | import javax.inject.Named; 11 | 12 | import dagger.Module; 13 | import dagger.Provides; 14 | import io.reactivex.Scheduler; 15 | 16 | @Module 17 | public class CinemaListModule { 18 | 19 | @ActivityScope 20 | @Provides 21 | CinemaListPresenter provideCinemaListPresenter(GetCinemas getCinemas , 22 | GetTopRatedCinemas getTopRatedCinemas , 23 | GetUpComingCinemas getUpComingCinemas){ 24 | return new CinemaListPresenter(getCinemas , getTopRatedCinemas , getUpComingCinemas); 25 | } 26 | 27 | @ActivityScope 28 | @Provides 29 | GetCinemas provideGetCinemas(@Named("executor_thread")Scheduler executorThread , 30 | @Named("ui_thread")Scheduler uiThread , 31 | CinemaRepository repository){ 32 | return new GetCinemas(executorThread , uiThread , repository); 33 | } 34 | 35 | @ActivityScope 36 | @Provides 37 | GetTopRatedCinemas provideGetTopRatedCinemas(@Named("executor_thread")Scheduler executorThread , 38 | @Named("ui_thread")Scheduler uiThread , 39 | CinemaRepository repository){ 40 | return new GetTopRatedCinemas(executorThread , uiThread , repository); 41 | } 42 | 43 | @ActivityScope 44 | @Provides 45 | GetUpComingCinemas provideGetUpComingCinemas(@Named("executor_thread")Scheduler executorThread , 46 | @Named("ui_thread")Scheduler uiThread , 47 | CinemaRepository repository){ 48 | return new GetUpComingCinemas(executorThread , uiThread , repository); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/models/mapper/CinemaEntityToCinemaTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.mapper; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | import com.ru.devit.mediateka.models.db.CinemaEntity; 5 | import com.ru.devit.mediateka.models.model.Cinema; 6 | 7 | import org.junit.Test; 8 | import org.mockito.Mock; 9 | 10 | import static org.hamcrest.CoreMatchers.is; 11 | import static org.junit.Assert.*; 12 | import static org.mockito.Mockito.doReturn; 13 | 14 | @SuppressWarnings("ResultOfMethodCallIgnored") 15 | public class CinemaEntityToCinemaTest extends UnitTest { 16 | 17 | @Mock private Cinema cinemaMock; 18 | @Mock private CinemaEntity cinemaEntityMock; 19 | 20 | private CinemaEntityToCinema mapper; 21 | 22 | @Override 23 | protected void onSetUp() { 24 | mapper = new CinemaEntityToCinema(); 25 | } 26 | 27 | @Test 28 | public void testMap() { 29 | doReturn(62).when(cinemaMock).getId(); 30 | doReturn("Star wars").when(cinemaMock).getTitle(); 31 | doReturn("Best movie").when(cinemaMock).getDescription(); 32 | doReturn(200000000).when(cinemaMock).getBudget(); 33 | 34 | final CinemaEntity cinemaEntity = mapper.map(cinemaMock); 35 | assertThat(cinemaEntity.getCinemaId() , is(62)); 36 | assertThat(cinemaEntity.getTitle() , is("Star wars")); 37 | assertThat(cinemaEntity.getDescription() , is("Best movie")); 38 | assertThat(cinemaEntity.getBudget() , is(200000000)); 39 | } 40 | 41 | @Test 42 | public void testReverseMap() { 43 | doReturn(20).when(cinemaEntityMock).getCinemaId(); 44 | doReturn("Avengers").when(cinemaEntityMock).getTitle(); 45 | doReturn("Avengers:description").when(cinemaEntityMock).getDescription(); 46 | doReturn("Stan Lee").when(cinemaEntityMock).getDirectorName(); 47 | doReturn("2004.12.03").when(cinemaEntityMock).getReleaseDate(); 48 | 49 | final Cinema cinema = mapper.reverseMap(cinemaEntityMock); 50 | assertThat(cinema.getId() , is(20)); 51 | assertThat(cinema.getTitle() , is("Avengers")); 52 | assertThat(cinema.getDescription() , is("Avengers:description")); 53 | assertThat(cinema.getDirectorName() , is("Stan Lee")); 54 | assertThat(cinema.getReleaseDate() , is("2004.12.03")); 55 | } 56 | } -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/presentation/smallcinemalist/SmallCinemasPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.smallcinemalist; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | import com.ru.devit.mediateka.domain.cinemausecases.GetCinemaByQuery; 5 | import com.ru.devit.mediateka.models.model.Cinema; 6 | 7 | import org.junit.Test; 8 | import org.mockito.Mock; 9 | 10 | import java.util.List; 11 | 12 | import static org.junit.Assert.*; 13 | import static org.mockito.Mockito.times; 14 | import static org.mockito.Mockito.verify; 15 | 16 | public class SmallCinemasPresenterTest extends UnitTest { 17 | 18 | @Mock private SmallCinemasPresenter.View view; 19 | @Mock private GetCinemaByQuery useCaseGetCinemaByQueryMock; 20 | @Mock private List cinemaListMock; 21 | 22 | private static final int TEST_CINEMA_ID = 987; 23 | 24 | private SmallCinemasPresenter presenter; 25 | 26 | @Override 27 | protected void onSetUp() { 28 | presenter = new SmallCinemasPresenter(useCaseGetCinemaByQueryMock); 29 | } 30 | 31 | @Test 32 | public void shouldOpenCinemaDetailScreenWhenCinemaClicked(){ 33 | presenter.setView(view); 34 | presenter.onCinemaClicked(TEST_CINEMA_ID , 6); 35 | 36 | verify(view).openCinema(TEST_CINEMA_ID , 6); 37 | } 38 | 39 | @Test 40 | public void shouldShowLoadingWhenInitialize(){ 41 | presenter.setView(view); 42 | presenter.initialize(); 43 | 44 | verify(view , times(1)).showLoading(); 45 | verify(view , times(1)).hideLoading(); 46 | } 47 | 48 | @Test 49 | public void shouldSetQueryFromSearchFieldWhenCome(){ 50 | presenter.onGetTextFromSearchField("Predator"); 51 | 52 | verify(useCaseGetCinemaByQueryMock).onNextQuery("Predator"); 53 | } 54 | 55 | @Test 56 | public void shouldSortCinemasByDateWhenSetCinemas(){ 57 | presenter.setView(view); 58 | presenter.setCinemas(cinemaListMock); 59 | 60 | verify(view).showCinemas(cinemaListMock); 61 | verify(view).hideLoading(); 62 | } 63 | 64 | @Test 65 | public void shouldUseCaseDisposeWhenPresenterDestroy(){ 66 | presenter.onDestroy(); 67 | 68 | verify(useCaseGetCinemaByQueryMock).removeActions(); 69 | verify(useCaseGetCinemaByQueryMock).dispose(); 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/models/mapper/CinemaResponseToCinemaTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.models.mapper; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | import com.ru.devit.mediateka.models.model.Cinema; 5 | import com.ru.devit.mediateka.models.network.CinemaDetailResponse; 6 | import com.ru.devit.mediateka.models.network.CinemaNetwork; 7 | import com.ru.devit.mediateka.models.network.CinemaResponse; 8 | 9 | import org.junit.Test; 10 | import org.mockito.Mock; 11 | import org.mockito.Mockito; 12 | 13 | import java.util.Iterator; 14 | import java.util.List; 15 | 16 | import static org.hamcrest.CoreMatchers.is; 17 | import static org.junit.Assert.*; 18 | import static org.mockito.Matchers.eq; 19 | import static org.mockito.Mockito.doReturn; 20 | 21 | @SuppressWarnings("ResultOfMethodCallIgnored") 22 | public class CinemaResponseToCinemaTest extends UnitTest { 23 | 24 | @Mock private CinemaDetailResponse cinemaDetailResponseMock; 25 | @Mock private CinemaResponse cinemaResponseMock; 26 | @Mock private List cinemaNetworkListMock; 27 | @Mock private Iterator cinemaNetworkIteratorMock; 28 | 29 | private CinemaResponseToCinema mapper; 30 | 31 | @Override 32 | protected void onSetUp() { 33 | mapper = new CinemaResponseToCinema(); 34 | } 35 | 36 | @Test 37 | public void shouldMapId(){ 38 | doReturn(77).when(cinemaDetailResponseMock).getId(); 39 | 40 | final Cinema cinema = mapper.map(cinemaDetailResponseMock); 41 | assertThat(cinema.getId() , is(cinemaDetailResponseMock.getId())); 42 | } 43 | 44 | @Test 45 | public void shouldMapListOfCinemas(){ 46 | CinemaNetwork cinemaNetwork = Mockito.mock(CinemaNetwork.class); 47 | doReturn(cinemaNetwork).when(cinemaNetworkListMock).get(eq(0)); 48 | doReturn(true).when(cinemaNetworkIteratorMock).hasNext(); 49 | doReturn(1).when(cinemaNetworkListMock).size(); 50 | doReturn(cinemaNetworkIteratorMock).when(cinemaNetworkListMock).iterator(); 51 | doReturn(cinemaNetworkListMock).when(cinemaResponseMock).getCinemas(); 52 | 53 | final List cinemaList = mapper.map(cinemaResponseMock); 54 | assertNotNull(cinemaList); 55 | assertEquals(cinemaNetworkListMock.size() , cinemaList.size()); 56 | assertFalse(cinemaList.isEmpty()); 57 | } 58 | } -------------------------------------------------------------------------------- /gradle-scripts/dependencies.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | 3 | //Version 4 | supportVersion = '28.0.0' 5 | 6 | rxJavaVersion = '2.1.14' 7 | rxAndroidVersion = '2.0.1' 8 | 9 | picassoVersion = '2.5.2' 10 | daggerVersion = '2.16' 11 | 12 | javaxVersion = '1.0' 13 | 14 | junitVersion = '4.12' 15 | mockitoVersion = '1.10.19' 16 | gsonVersion = '2.8.0' 17 | 18 | //Packages 19 | supportPackage = 'com.android.support' 20 | 21 | reactivePackage = 'io.reactivex.rxjava2' 22 | picassoPackage = 'com.squareup.picasso' 23 | daggerPackage = 'com.google.dagger' 24 | javaxPackage = 'javax.annotation' 25 | gsonPackage = 'com.google.code.gson' 26 | 27 | junitPackage = 'junit' 28 | mockitoPackage = 'org.mockito' 29 | 30 | //Support Libraries dependencies 31 | supportDependencies = [ 32 | design : buildDependency(supportPackage, 'design', supportVersion), 33 | appCompat : buildDependency(supportPackage, 'appcompat-v7', supportVersion), 34 | supportAnnotation: buildDependency(supportPackage, 'support-annotations', supportVersion), 35 | constraintLayout : 'com.android.support.constraint:constraint-layout:1.0.2'] 36 | 37 | //RX Libraries dependencies 38 | rxDependencies = [ 39 | rxJava : buildDependency(reactivePackage, 'rxjava', rxJavaVersion), 40 | rxAndroid: buildDependency(reactivePackage, 'rxandroid', rxAndroidVersion)] 41 | 42 | //Dagger Libraries dependencies 43 | daggerDependencies = [ 44 | dagger : buildDependency(daggerPackage, 'dagger', daggerVersion), 45 | daggerCompiler : buildDependency(daggerPackage, 'dagger-compiler', daggerVersion)] 46 | 47 | 48 | 49 | //Elemental Libraries dependencies 50 | picasso = buildDependency(picassoPackage, 'picasso', picassoVersion) 51 | gson = buildDependency(gsonPackage, 'gson', gsonVersion) 52 | gsonConverter = 'com.squareup.retrofit2:converter-gson:2.1.0' 53 | retrofit = 'com.squareup.retrofit2:retrofit:2.3.0' 54 | cardView = 'com.android.support:cardview-v7:28.0.0' 55 | 56 | //Testing 57 | testingDependencies = [ 58 | junit : buildDependency(junitPackage, 'junit', junitVersion), 59 | mockito : buildDependency(mockitoPackage, 'mockito-core', mockitoVersion)] 60 | } 61 | 62 | static String buildDependency(String pack, String dependency, String version) { 63 | return "${pack}:${dependency}:${version}" 64 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_popular_actors.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 11 | 17 | 18 | 22 | 23 | 30 | 31 | 32 | 33 | 34 | 35 | 42 | 43 | 48 | 49 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/data/datasource/db/CinemaDao.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.data.datasource.db; 2 | 3 | import android.arch.persistence.room.Dao; 4 | import android.arch.persistence.room.Insert; 5 | import android.arch.persistence.room.OnConflictStrategy; 6 | import android.arch.persistence.room.Query; 7 | import android.arch.persistence.room.Update; 8 | 9 | import com.ru.devit.mediateka.models.db.ActorEntity; 10 | import com.ru.devit.mediateka.models.db.CinemaEntity; 11 | import com.ru.devit.mediateka.models.model.Cinema; 12 | 13 | import java.util.List; 14 | 15 | import io.reactivex.Flowable; 16 | import io.reactivex.Maybe; 17 | import io.reactivex.Single; 18 | 19 | @Dao 20 | public interface CinemaDao { 21 | 22 | @Query("SELECT * FROM CinemaTable WHERE page = :page ORDER BY popularity DESC") 23 | Single> getCinemas(int page); 24 | 25 | @Query("SELECT * FROM CinemaTable WHERE page = :page AND vote_average > 8 ORDER BY vote_average DESC") 26 | Single> getTopRatedCinemas(int page); 27 | 28 | @Query("SELECT * FROM CinemaTable WHERE page = :page AND vote_average = 0 AND release_date >= :currentYear") 29 | Single> getUpComingCinemas(int page , int currentYear); 30 | 31 | @Query("SELECT * FROM CinemaTable WHERE cinemaId = :id") 32 | Single getCinemaById(final int id); 33 | 34 | @Insert(onConflict = OnConflictStrategy.ROLLBACK) 35 | void insertAll(List cinemaEntities); 36 | 37 | @Query("UPDATE CinemaTable SET budget = :budget , revenue = :revenue , cinema_duration = :cinemaDuration , director_name = :directorName " + 38 | "WHERE cinemaId = :cinemaId") 39 | void updateCinema(int cinemaId , int budget , int revenue , int cinemaDuration , String directorName ); 40 | 41 | @Insert(onConflict = OnConflictStrategy.IGNORE) 42 | void insertActors(List actorEntities); 43 | 44 | @Query("SELECT * FROM CinemaTable WHERE title LIKE :cinemaName") 45 | Flowable> getCinemasByName(String cinemaName); 46 | 47 | @Query("SELECT * FROM CinemaTable WHERE is_favourite") 48 | Maybe> getFavouriteListCinema(); 49 | 50 | @Query("UPDATE CinemaTable SET is_favourite = :isFavourite WHERE cinemaId = :cinemaId") 51 | void insertFavouriteCinema(int cinemaId , boolean isFavourite); 52 | } 53 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/application/RetrofitModule.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.application; 2 | 3 | import com.ru.devit.mediateka.BuildConfig; 4 | import com.ru.devit.mediateka.data.datasource.network.CinemaApiService; 5 | 6 | import java.io.IOException; 7 | import java.util.Locale; 8 | 9 | import javax.inject.Singleton; 10 | 11 | import dagger.Module; 12 | import dagger.Provides; 13 | import io.reactivex.schedulers.Schedulers; 14 | import okhttp3.HttpUrl; 15 | import okhttp3.Interceptor; 16 | import okhttp3.OkHttpClient; 17 | import okhttp3.Request; 18 | import okhttp3.Response; 19 | import retrofit2.Retrofit; 20 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; 21 | import retrofit2.converter.gson.GsonConverterFactory; 22 | 23 | @Module 24 | class RetrofitModule { 25 | 26 | private static final String BASE_URL = "https://api.themoviedb.org/3/"; 27 | private static final String LOCAL_LANGUAGE = Locale.getDefault().getLanguage(); 28 | 29 | @Provides 30 | @Singleton 31 | Retrofit provideRetrofit(OkHttpClient client){ 32 | return new Retrofit.Builder() 33 | .baseUrl(BASE_URL) 34 | .client(client) 35 | .addConverterFactory(GsonConverterFactory.create()) 36 | .addCallAdapterFactory(RxJava2CallAdapterFactory.createWithScheduler(Schedulers.io())) 37 | .build(); 38 | } 39 | 40 | @Provides 41 | @Singleton 42 | OkHttpClient provideOkHttpClient(){ 43 | OkHttpClient.Builder client = new OkHttpClient.Builder(); 44 | client.addInterceptor(chain -> { 45 | Request original = chain.request(); 46 | HttpUrl originalUrl = original.url(); 47 | 48 | HttpUrl url = originalUrl.newBuilder() 49 | .addQueryParameter("api_key" , BuildConfig.THE_MOVIE_DB_API_KEY) 50 | .addQueryParameter("language" , LOCAL_LANGUAGE) 51 | .build(); 52 | Request.Builder request = original.newBuilder() 53 | .url(url); 54 | 55 | return chain.proceed(request.build()); 56 | }); 57 | return client.build(); 58 | } 59 | 60 | @Provides 61 | @Singleton 62 | CinemaApiService provideCinemaApiService(Retrofit retrofit){ 63 | return retrofit.create(CinemaApiService.class); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/widget/CinemaHeaderView.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.widget; 2 | 3 | import android.content.Context; 4 | import android.support.constraint.ConstraintLayout; 5 | import android.util.AttributeSet; 6 | import android.view.View; 7 | import android.widget.ProgressBar; 8 | import android.widget.TextView; 9 | 10 | import com.ru.devit.mediateka.R; 11 | import com.ru.devit.mediateka.models.model.Cinema; 12 | import com.ru.devit.mediateka.utils.FormatterUtils; 13 | 14 | public class CinemaHeaderView extends ConstraintLayout { 15 | 16 | private View rootView; 17 | private TextView releaseDateTextView , durationTextView , titleTextView , genresTextView; 18 | private ProgressBar mProgressBar; 19 | 20 | public CinemaHeaderView(Context context) { 21 | super(context); 22 | init(context); 23 | } 24 | 25 | public CinemaHeaderView(Context context, AttributeSet attrs) { 26 | super(context, attrs); 27 | init(context); 28 | } 29 | 30 | public CinemaHeaderView(Context context, AttributeSet attrs, int defStyleAttr) { 31 | super(context, attrs, defStyleAttr); 32 | init(context); 33 | } 34 | 35 | public void render(Cinema cinema){ 36 | titleTextView.setText(cinema.getTitle()); 37 | releaseDateTextView.setText(FormatterUtils.getYearFromDate(cinema.getReleaseDate())); 38 | durationTextView.setText(FormatterUtils.formatDuration(cinema.getDuration() , getContext())); 39 | genresTextView.setText(FormatterUtils.formatGenres(cinema.getGenres() , getContext())); 40 | } 41 | 42 | public void startProgress(){ 43 | mProgressBar.setVisibility(VISIBLE); 44 | } 45 | 46 | public void hideProgress(){ 47 | mProgressBar.setVisibility(GONE); 48 | } 49 | 50 | private void init(Context context){ 51 | if (rootView == null){ 52 | rootView = inflate(context , R.layout.cinema_detail_header_view, this); 53 | } 54 | releaseDateTextView = rootView.findViewById(R.id.cinema_release_date); 55 | durationTextView = rootView.findViewById(R.id.cinema_duration); 56 | titleTextView = rootView.findViewById(R.id.cinema_title); 57 | genresTextView = rootView.findViewById(R.id.cinema_genres); 58 | mProgressBar = rootView.findViewById(R.id.progressBar); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/actor/ActorModule.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.actor; 2 | 3 | import android.content.Context; 4 | 5 | import com.ru.devit.mediateka.MediatekaApp; 6 | import com.ru.devit.mediateka.data.datasource.db.ActorDao; 7 | import com.ru.devit.mediateka.data.datasource.db.CinemaActorJoinDao; 8 | import com.ru.devit.mediateka.data.datasource.network.CinemaApiService; 9 | import com.ru.devit.mediateka.data.repository.actor.ActorLocalRepository; 10 | import com.ru.devit.mediateka.data.repository.actor.ActorRemoteRepository; 11 | import com.ru.devit.mediateka.domain.ActorRepository; 12 | import com.ru.devit.mediateka.models.mapper.ActorDetailEntityToActor; 13 | import com.ru.devit.mediateka.models.mapper.ActorDetailResponseToActor; 14 | 15 | import dagger.Module; 16 | import dagger.Provides; 17 | 18 | @Module 19 | public class ActorModule { 20 | 21 | @ActorScope 22 | @Provides 23 | ActorRepository provideRepository(CinemaApiService apiService , 24 | ActorDetailResponseToActor networkMapper , 25 | ActorDetailEntityToActor dbMapper , 26 | ActorLocalRepository cache , 27 | CinemaActorJoinDao cinemaActorJoinDao){ 28 | return new ActorRemoteRepository(apiService , networkMapper , dbMapper , cache , cinemaActorJoinDao); 29 | } 30 | 31 | @ActorScope 32 | @Provides 33 | ActorLocalRepository provideActorLocalRepository(ActorDao actorDao , 34 | ActorDetailEntityToActor mapper , 35 | CinemaActorJoinDao cinemaActorJoinDao){ 36 | return new ActorLocalRepository(actorDao , mapper , cinemaActorJoinDao); 37 | } 38 | 39 | @ActorScope 40 | @Provides 41 | ActorDao providesActorDao(Context context){ 42 | MediatekaApp mediatekaApp = (MediatekaApp) context; 43 | return mediatekaApp.getDatabaseInstance() 44 | .getActorDao(); 45 | } 46 | 47 | @ActorScope 48 | @Provides 49 | ActorDetailEntityToActor provideActorDetailEntityToActor(){ 50 | return new ActorDetailEntityToActor(); 51 | } 52 | 53 | @ActorScope 54 | @Provides 55 | ActorDetailResponseToActor provideActorDetailResponseToActor(){ 56 | return new ActorDetailResponseToActor(); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/presentation/actordetail/ActorDetailPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.actordetail; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | import com.ru.devit.mediateka.domain.actorusecases.GetActorById; 5 | 6 | import org.junit.Test; 7 | import org.mockito.Mock; 8 | 9 | import java.util.List; 10 | 11 | import static org.junit.Assert.*; 12 | import static org.mockito.Matchers.any; 13 | import static org.mockito.Mockito.doReturn; 14 | import static org.mockito.Mockito.doThrow; 15 | import static org.mockito.Mockito.times; 16 | import static org.mockito.Mockito.verify; 17 | 18 | public class ActorDetailPresenterTest extends UnitTest { 19 | 20 | @Mock private ActorDetailPresenter.View view; 21 | @Mock private GetActorById useCaseGetActorByIdMock; 22 | @Mock private List posterUrlsMock; 23 | @Mock private ActorDetailPresenter.ActorDetailSubscriber subscriberMock; 24 | 25 | private static final int TEST_USER_ID = 123; 26 | 27 | private ActorDetailPresenter presenter; 28 | 29 | @Override 30 | protected void onSetUp() { 31 | presenter = new ActorDetailPresenter(useCaseGetActorByIdMock); 32 | } 33 | 34 | @Test 35 | public void shouldSearchActorByIdWhenInitialize(){ 36 | presenter.setView(view); 37 | presenter.initialize(); 38 | presenter.setActorId(TEST_USER_ID); 39 | useCaseGetActorByIdMock.searchActorById(TEST_USER_ID); 40 | 41 | verify(view , times(1)).showLoading(); 42 | verify(useCaseGetActorByIdMock).searchActorById(TEST_USER_ID); 43 | } 44 | 45 | @Test 46 | public void shouldHideLoadingWhenRuntimeException(){ 47 | // presenter.setView(view); 48 | // presenter.initialize(); 49 | // doThrow(new RuntimeException()).when(subscriberMock).onError(any(Throwable.class)); 50 | // 51 | // verify(view , times(1)).showLoading(); 52 | // verify(view).hideLoading(); 53 | } 54 | 55 | @Test 56 | public void shouldShowPostersWhenUserAvatarClicked(){ 57 | presenter.setView(view); 58 | presenter.onAvatarClicked(posterUrlsMock); 59 | 60 | verify(view , times(1)).showPosters(posterUrlsMock); 61 | } 62 | 63 | @Test 64 | public void shouldUseCaseDisposeWhenPresenterDestroy(){ 65 | presenter.onDestroy(); 66 | 67 | verify(useCaseGetActorByIdMock , times(1)).dispose(); 68 | } 69 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/data/repository/actor/ActorLocalRepository.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.data.repository.actor; 2 | 3 | import com.ru.devit.mediateka.data.datasource.db.ActorDao; 4 | import com.ru.devit.mediateka.data.datasource.db.CinemaActorJoinDao; 5 | import com.ru.devit.mediateka.domain.ActorRepository; 6 | import com.ru.devit.mediateka.models.db.ActorEntity; 7 | import com.ru.devit.mediateka.models.db.CinemaEntity; 8 | import com.ru.devit.mediateka.models.mapper.ActorDetailEntityToActor; 9 | import com.ru.devit.mediateka.models.model.Actor; 10 | 11 | import java.util.List; 12 | 13 | import io.reactivex.Flowable; 14 | import io.reactivex.Single; 15 | 16 | public class ActorLocalRepository implements ActorRepository { 17 | 18 | private final ActorDao actorDao; 19 | private final ActorDetailEntityToActor mapper; 20 | private final CinemaActorJoinDao cinemaActorJoinDao; 21 | 22 | public ActorLocalRepository(ActorDao actorDao , 23 | ActorDetailEntityToActor mapper , 24 | CinemaActorJoinDao cinemaActorJoinDao) { 25 | this.actorDao = actorDao; 26 | this.mapper = mapper; 27 | this.cinemaActorJoinDao = cinemaActorJoinDao; 28 | } 29 | 30 | @Override 31 | public Single getActorById(final int actorId) { 32 | return actorDao.getActorById(actorId) 33 | .map(actorEntity -> mapper.mapDetailActor(actorEntity , 34 | cinemaActorJoinDao.getCinemasForActor(actorId))); 35 | } 36 | 37 | @Override 38 | public Flowable> searchActors(String query) { 39 | return actorDao.getAllActorsByName(query) 40 | .map(mapper::reverseMap); 41 | 42 | } 43 | 44 | @Override 45 | public Single> getPopularActors(int page){ 46 | return actorDao.getPopularActors() //TODO FIX THIS PAGE 47 | .map(mapper::reverseMap); 48 | } 49 | 50 | public void insertCinemasForActor(List cinemaEntities) { 51 | actorDao.insertCinemas(cinemaEntities); 52 | } 53 | 54 | public void updateActor(int actorId, String biography, String birthDay, String age, String placeOfBirth) { 55 | actorDao.updateActor(actorId , biography , birthDay , age , placeOfBirth); 56 | } 57 | 58 | public void insertActors(List actorEntities){ 59 | actorDao.insertActors(actorEntities); 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/search/SearchPresenter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.search; 2 | 3 | import com.ru.devit.mediateka.presentation.base.BasePresenter; 4 | import com.ru.devit.mediateka.presentation.base.BaseView; 5 | 6 | import javax.inject.Inject; 7 | 8 | 9 | public class SearchPresenter extends BasePresenter { 10 | 11 | private String query = ""; 12 | 13 | private static int currentTabPosition = 0; 14 | 15 | private static final int CINEMAS_TAB_POSITION = 0; 16 | private static final int ACTORS_TAB_POSITION = 1; 17 | 18 | @Inject public SearchPresenter() { 19 | } 20 | 21 | @Override 22 | public void initialize() { 23 | getView().showLoading(); 24 | } 25 | 26 | @Override 27 | public void onDestroy() { 28 | } 29 | 30 | public void onTabSelected(int position) { 31 | switch (position) { 32 | case CINEMAS_TAB_POSITION : { 33 | getView().onCinemaTabSelected(); 34 | currentTabPosition = CINEMAS_TAB_POSITION; 35 | break; 36 | } 37 | case ACTORS_TAB_POSITION : { 38 | getView().onActorTabSelected(); 39 | currentTabPosition = ACTORS_TAB_POSITION; 40 | break; 41 | } 42 | } 43 | } 44 | 45 | public void onTextChanged(String query) { 46 | this.query = query; 47 | checkClearEditTextBtnState(); 48 | switch (currentTabPosition) { 49 | case CINEMAS_TAB_POSITION : { 50 | getView().textFromCinemaTab(query); 51 | break; 52 | } 53 | case ACTORS_TAB_POSITION : { 54 | getView().textFromActorTab(query); 55 | break; 56 | } 57 | } 58 | } 59 | 60 | public void onClearEditTextClicked() { 61 | getView().clearEditText(""); 62 | checkClearEditTextBtnState(); 63 | } 64 | 65 | private void checkClearEditTextBtnState(){ 66 | if (query.isEmpty()){ 67 | getView().hideClearEditTextBtn(); 68 | } else { 69 | getView().showEditTextBtn(); 70 | } 71 | } 72 | 73 | public interface View extends BaseView{ 74 | void onCinemaTabSelected(); 75 | void onActorTabSelected(); 76 | void textFromCinemaTab(String query); 77 | void textFromActorTab(String query); 78 | void hideClearEditTextBtn(); 79 | void showEditTextBtn(); 80 | void clearEditText(String s); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /app/src/test/java/com/ru/devit/mediateka/presentation/cinemadetail/CinemaDetailPresenterTest.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.cinemadetail; 2 | 3 | import com.ru.devit.mediateka.UnitTest; 4 | import com.ru.devit.mediateka.domain.cinemausecases.GetCinemaById; 5 | import com.ru.devit.mediateka.domain.cinemausecases.GetFavouriteListCinema; 6 | 7 | import org.junit.Test; 8 | import org.mockito.Mock; 9 | 10 | import java.util.List; 11 | 12 | import io.reactivex.Completable; 13 | import io.reactivex.functions.Action; 14 | 15 | import static org.junit.Assert.*; 16 | import static org.mockito.Mockito.doReturn; 17 | import static org.mockito.Mockito.verify; 18 | 19 | public class CinemaDetailPresenterTest extends UnitTest { 20 | 21 | @Mock private CinemaDetailPresenter.View view; 22 | @Mock private GetCinemaById useCaseGetCinemaByIdMock; 23 | @Mock private GetFavouriteListCinema useCaseGetFavouriteListCinema; 24 | @Mock private List posterUrlsMock; 25 | 26 | private static final int TEST_CINEMA_ID = 456; 27 | 28 | private CinemaDetailPresenter presenter; 29 | 30 | @Override 31 | protected void onSetUp() { 32 | presenter = new CinemaDetailPresenter(useCaseGetCinemaByIdMock , useCaseGetFavouriteListCinema); 33 | } 34 | 35 | @Test 36 | public void shouldSearchCinemaByIdWhenInitialize(){ 37 | presenter.setView(view); 38 | presenter.initialize(); 39 | presenter.setCinemaId(TEST_CINEMA_ID); 40 | useCaseGetCinemaByIdMock.searchCinemaById(TEST_CINEMA_ID); 41 | 42 | verify(view).showLoading(); 43 | verify(useCaseGetCinemaByIdMock).searchCinemaById(TEST_CINEMA_ID); 44 | } 45 | 46 | @Test 47 | public void shouldSaveCinemaToFavouriteWhenAddToFavouriteClicked(){ 48 | presenter.setView(view); 49 | presenter.setCinemaId(TEST_CINEMA_ID); 50 | doReturn(Completable.complete()).when(useCaseGetFavouriteListCinema).saveFavouriteCinema(TEST_CINEMA_ID); 51 | presenter.onAddFavouriteCinemaClicked(); 52 | 53 | verify(view).hideFABCinemaMenu(); 54 | verify(useCaseGetFavouriteListCinema).saveFavouriteCinema(TEST_CINEMA_ID); 55 | } 56 | 57 | @Test 58 | public void shouldShowPostersWhenPosterClicked(){ 59 | presenter.setView(view); 60 | presenter.onSmallPosterClicked(posterUrlsMock); 61 | 62 | verify(view).showListPosters(posterUrlsMock); 63 | } 64 | 65 | @Test 66 | public void shouldUseCaseDisposeWhenPresenterDestroy(){ 67 | presenter.onDestroy(); 68 | 69 | verify(useCaseGetCinemaByIdMock).dispose(); 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/actorlist/ActorViewHolder.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.actorlist; 2 | 3 | import android.content.Context; 4 | import android.support.graphics.drawable.VectorDrawableCompat; 5 | import android.support.v7.widget.RecyclerView; 6 | import android.view.View; 7 | import android.widget.TextView; 8 | 9 | import com.ru.devit.mediateka.R; 10 | import com.ru.devit.mediateka.models.model.Actor; 11 | import com.ru.devit.mediateka.presentation.common.OnActorClickListener; 12 | import com.ru.devit.mediateka.utils.UrlImagePathCreator; 13 | import com.ru.devit.mediateka.utils.UrlImagePathCreator.Quality; 14 | import com.squareup.picasso.Picasso; 15 | 16 | import de.hdodenhof.circleimageview.CircleImageView; 17 | 18 | class ActorViewHolder extends RecyclerView.ViewHolder{ 19 | 20 | private CircleImageView avatar; 21 | private TextView actorName , actorRole; 22 | private View rootView; 23 | 24 | private final OnActorClickListener onActorClickListener; 25 | 26 | ActorViewHolder(View itemView , OnActorClickListener onActorClickListener) { 27 | super(itemView); 28 | this.onActorClickListener = onActorClickListener; 29 | rootView = itemView; 30 | avatar = itemView.findViewById(R.id.iv_actor_avatar); 31 | actorName = itemView.findViewById(R.id.tv_actor_name); 32 | actorRole = itemView.findViewById(R.id.tv_actor_role); 33 | } 34 | 35 | void render(Actor actor , int viewHolderPosition){ 36 | renderAvatar(UrlImagePathCreator.INSTANCE.createPictureUrlFromQuality(Quality.Quality185 , actor.getProfileUrl())); 37 | onActorClicked(actor.getActorId() , viewHolderPosition); 38 | actorName.setText(actor.getName()); 39 | if (actor.getCharacter() == null){ 40 | return; 41 | } 42 | actorRole.setText(actor.getCharacter().isEmpty() ? "" : getContext().getString(R.string.role , actor.getCharacter())); 43 | } 44 | 45 | private void onActorClicked(int actorId , int viewHolderPosition){ 46 | rootView.setOnClickListener(view -> onActorClickListener.onActorClicked(actorId , viewHolderPosition)); 47 | } 48 | 49 | @SuppressWarnings("ConstantConditions") 50 | private void renderAvatar(String url){ 51 | Picasso.with(getContext()) 52 | .load(url) 53 | .error(VectorDrawableCompat.create(getContext().getResources() , R.drawable.ic_actor_default_avatar , getContext().getTheme())) 54 | .into(avatar); 55 | } 56 | 57 | private Context getContext(){ 58 | return itemView.getContext(); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/di/cinema/CinemaModule.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.di.cinema; 2 | 3 | import android.content.Context; 4 | 5 | import com.ru.devit.mediateka.MediatekaApp; 6 | import com.ru.devit.mediateka.data.datasource.db.CinemaActorJoinDao; 7 | import com.ru.devit.mediateka.data.datasource.db.CinemaDao; 8 | import com.ru.devit.mediateka.data.datasource.network.CinemaApiService; 9 | import com.ru.devit.mediateka.data.repository.cinema.CinemaLocalRepository; 10 | import com.ru.devit.mediateka.data.repository.cinema.CinemaRemoteRepository; 11 | import com.ru.devit.mediateka.domain.CinemaRepository; 12 | import com.ru.devit.mediateka.models.mapper.CinemaEntityToCinema; 13 | import com.ru.devit.mediateka.models.mapper.CinemaMapper; 14 | import com.ru.devit.mediateka.models.mapper.CinemaResponseToCinema; 15 | 16 | import dagger.Module; 17 | import dagger.Provides; 18 | 19 | @Module 20 | public class CinemaModule { 21 | 22 | @CinemaScope 23 | @Provides 24 | CinemaRepository provideRepository(CinemaLocalRepository cache , 25 | CinemaApiService apiService , 26 | CinemaMapper mapper , 27 | CinemaActorJoinDao cinemaActorJoinDao){ 28 | return new CinemaRemoteRepository(cache , apiService , mapper , cinemaActorJoinDao); 29 | } 30 | 31 | @CinemaScope 32 | @Provides 33 | CinemaLocalRepository provideCinemaLocalRepository(CinemaDao cinemaDao , 34 | CinemaMapper cinemaMapper , 35 | CinemaActorJoinDao cinemaActorJoinDao){ 36 | return new CinemaLocalRepository(cinemaDao , cinemaMapper , cinemaActorJoinDao); 37 | } 38 | 39 | @CinemaScope 40 | @Provides 41 | CinemaDao providesCinemaDao(Context context){ 42 | MediatekaApp mediatekaApp = (MediatekaApp) context; 43 | return mediatekaApp.getDatabaseInstance() 44 | .getCinemaDao(); 45 | } 46 | 47 | @CinemaScope 48 | @Provides 49 | CinemaMapper provideCinemaMapper(CinemaResponseToCinema cinemaResponseToCinema , CinemaEntityToCinema cinemaEntityToCinema){ 50 | return new CinemaMapper(cinemaResponseToCinema , cinemaEntityToCinema); 51 | } 52 | 53 | @CinemaScope 54 | @Provides 55 | CinemaResponseToCinema provideCinemaResponseToCinema(){ 56 | return new CinemaResponseToCinema(); 57 | } 58 | 59 | @CinemaScope 60 | @Provides 61 | CinemaEntityToCinema provideCinemaEntityToCinema(){ 62 | return new CinemaEntityToCinema(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/domain/actorusecases/GetActorsByQuery.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.domain.actorusecases; 2 | 3 | import com.ru.devit.mediateka.domain.Actions; 4 | import com.ru.devit.mediateka.domain.ActorRepository; 5 | import com.ru.devit.mediateka.domain.UseCase; 6 | import com.ru.devit.mediateka.models.model.Actor; 7 | 8 | import java.util.List; 9 | import java.util.concurrent.TimeUnit; 10 | 11 | import io.reactivex.Flowable; 12 | import io.reactivex.Scheduler; 13 | import io.reactivex.processors.FlowableProcessor; 14 | import io.reactivex.processors.PublishProcessor; 15 | 16 | public class GetActorsByQuery extends UseCase> { 17 | 18 | private Actions actions; 19 | private boolean isDataLoaded = false; 20 | private final ActorRepository repository; 21 | private final FlowableProcessor processor = PublishProcessor.create(); 22 | 23 | public GetActorsByQuery(Scheduler executorThread , 24 | Scheduler uiThread , 25 | ActorRepository repository) { 26 | super(executorThread, uiThread); 27 | this.repository = repository; 28 | } 29 | 30 | public void setQuery(String query) { 31 | processor.onNext(query); 32 | } 33 | 34 | public void setActions(Actions actions){ 35 | this.actions = actions; 36 | } 37 | 38 | public void removeActions(){ 39 | actions.removeActions(); 40 | } 41 | 42 | @Override 43 | protected Flowable> createUseCase() { 44 | return processor 45 | .flatMap(s -> { 46 | if (!isDataLoaded){ 47 | return Flowable.just(s) 48 | .doOnNext(s1 -> { 49 | if (s1.isEmpty()) actions.onDataLoaded(); 50 | else actions.onNext(); 51 | }); 52 | } else { 53 | actions.onDataLoaded(); 54 | return Flowable.just(s) 55 | .doOnComplete(() -> { 56 | actions.onClearAdapter(); 57 | isDataLoaded = false; 58 | }); 59 | } 60 | }) 61 | .debounce(800 , TimeUnit.MILLISECONDS) 62 | .filter(query -> !query.isEmpty()) 63 | .distinctUntilChanged() 64 | .switchMap(s -> { 65 | isDataLoaded = true; 66 | return repository.searchActors(s); 67 | }); 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /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 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 12 | set DEFAULT_JVM_OPTS= 13 | 14 | set DIRNAME=%~dp0 15 | if "%DIRNAME%" == "" set DIRNAME=. 16 | set APP_BASE_NAME=%~n0 17 | set APP_HOME=%DIRNAME% 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 Windowz variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | if "%@eval[2+2]" == "4" goto 4NT_args 53 | 54 | :win9xME_args 55 | @rem Slurp the command line arguments. 56 | set CMD_LINE_ARGS= 57 | set _SKIP=2 58 | 59 | :win9xME_args_slurp 60 | if "x%~1" == "x" goto execute 61 | 62 | set CMD_LINE_ARGS=%* 63 | goto execute 64 | 65 | :4NT_args 66 | @rem Get arguments from the 4NT Shell from JP Software 67 | set CMD_LINE_ARGS=%$ 68 | 69 | :execute 70 | @rem Setup the command line 71 | 72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 73 | 74 | @rem Execute Gradle 75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 76 | 77 | :end 78 | @rem End local scope for the variables with windows NT shell 79 | if "%ERRORLEVEL%"=="0" goto mainEnd 80 | 81 | :fail 82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 83 | rem the _cmd.exe /c_ return code! 84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 85 | exit /b 1 86 | 87 | :mainEnd 88 | if "%OS%"=="Windows_NT" endlocal 89 | 90 | :omega 91 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/widget/DateAndTimePicker.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.widget; 2 | 3 | import android.app.DatePickerDialog; 4 | import android.app.TimePickerDialog; 5 | import android.content.Context; 6 | 7 | import com.ru.devit.mediateka.models.model.DateAndTimeInfo; 8 | 9 | import java.util.Calendar; 10 | 11 | public class DateAndTimePicker { 12 | 13 | private final Context context; 14 | private final Calendar calendar = Calendar.getInstance(); 15 | private final int year; 16 | private final int month; 17 | private final int day; 18 | private final int hour; 19 | private final int minute; 20 | 21 | public DateAndTimePicker(Context context) { 22 | this.context = context; 23 | year = calendar.get(Calendar.YEAR); 24 | month = calendar.get(Calendar.MONTH); 25 | day = calendar.get(Calendar.DAY_OF_MONTH); 26 | hour = calendar.get(Calendar.HOUR_OF_DAY); 27 | minute = calendar.get(Calendar.MINUTE); 28 | } 29 | 30 | public void showDateAndTimePickerDialog(DateAndTimePickerListener listener){ 31 | DateAndTimeInfo dateAndTimeInfo = new DateAndTimeInfo(); 32 | showDatePickerDialog((year, month, dayOfMonth) -> { 33 | dateAndTimeInfo.setYear(year); 34 | dateAndTimeInfo.setMonth(month); 35 | dateAndTimeInfo.setDay(dayOfMonth); 36 | }, () -> showTimePickerDialog((hour, minute) -> { 37 | dateAndTimeInfo.setHour(hour); 38 | dateAndTimeInfo.setMinute(minute); 39 | listener.onDateAndTimeSet(dateAndTimeInfo); 40 | })); 41 | } 42 | 43 | private void showDatePickerDialog(DataPickerListener listener , Then then){ 44 | DatePickerDialog datePickerDialog = new DatePickerDialog(context, (view, year, month, dayOfMonth) -> { 45 | listener.onDateSet(year , month , dayOfMonth); 46 | then.then(); 47 | }, year , month , day); 48 | datePickerDialog.show(); 49 | } 50 | 51 | private void showTimePickerDialog(TimePickerListener listener){ 52 | TimePickerDialog timePickerDialog = new TimePickerDialog(context, (view, hourOfDay, minute) -> { 53 | listener.onTimeSet(hourOfDay , minute); 54 | }, hour , minute , true); 55 | timePickerDialog.show(); 56 | } 57 | 58 | @FunctionalInterface 59 | interface Then{ 60 | void then(); 61 | } 62 | 63 | public interface DateAndTimePickerListener{ 64 | void onDateAndTimeSet(DateAndTimeInfo dateAndTimeInfo); 65 | } 66 | 67 | interface DataPickerListener { 68 | void onDateSet(int year , int month , int dayOfMonth); 69 | } 70 | 71 | interface TimePickerListener{ 72 | void onTimeSet(int hour , int minute); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/common/PhotoLoader.kt: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.common 2 | 3 | import android.content.Context 4 | import android.content.Intent 5 | import android.graphics.Bitmap 6 | import android.graphics.drawable.Drawable 7 | import android.net.Uri 8 | import android.os.Environment 9 | import android.widget.Toast 10 | import com.ru.devit.mediateka.R 11 | import com.squareup.picasso.Picasso 12 | import com.squareup.picasso.Target 13 | import io.reactivex.Completable 14 | import io.reactivex.android.schedulers.AndroidSchedulers 15 | import io.reactivex.schedulers.Schedulers 16 | import java.io.File 17 | import java.io.FileOutputStream 18 | 19 | class PhotoLoader(private val context: Context , private val pictureName: String): Target { 20 | 21 | companion object { 22 | private const val ALBUM_NAME = "Mediateka" 23 | } 24 | 25 | override fun onPrepareLoad(placeHolderDrawable: Drawable?) { 26 | 27 | } 28 | 29 | override fun onBitmapFailed(errorDrawable: Drawable?) { 30 | } 31 | 32 | override fun onBitmapLoaded(bitmap: Bitmap?, from: Picasso.LoadedFrom?) { 33 | saveBitmapToGallery(bitmap) 34 | .subscribe({ 35 | Toast.makeText(context , context.getString(R.string.message_picture_download_successully) , Toast.LENGTH_LONG).show() 36 | } , { 37 | it.printStackTrace() 38 | }) 39 | } 40 | 41 | private fun saveBitmapToGallery(bitmap: Bitmap?): Completable{ 42 | return Completable.create { 43 | val albumDir = File(Environment.getExternalStorageDirectory() , ALBUM_NAME) 44 | if (!albumDir.exists()){ 45 | albumDir.mkdir() 46 | } 47 | val pathToPicture = albumDir.path.plus("/").plus(pictureName) 48 | val pictureFile = File(pathToPicture) 49 | if (!pictureFile.exists()){ 50 | pictureFile.createNewFile() 51 | val fileOutputStream = FileOutputStream(pictureFile) 52 | fileOutputStream.use { 53 | bitmap?.compress(Bitmap.CompressFormat.JPEG , 80 , fileOutputStream) 54 | fileOutputStream.flush() 55 | } 56 | sendPictureToGallery(pathToPicture) 57 | } 58 | it.onComplete() 59 | } 60 | .subscribeOn(Schedulers.io()) 61 | .observeOn(AndroidSchedulers.mainThread()) 62 | } 63 | 64 | private fun sendPictureToGallery(pathToPicture: String){ 65 | val intent = Intent(Intent.ACTION_MEDIA_SCANNER_SCAN_FILE) 66 | val pictureFile = File(pathToPicture) 67 | val pictureUri = Uri.fromFile(pictureFile) 68 | intent.data = pictureUri 69 | context.sendBroadcast(intent) 70 | } 71 | } -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/actorlist/ActorsPresenter.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.actorlist; 2 | 3 | import com.ru.devit.mediateka.domain.Actions; 4 | import com.ru.devit.mediateka.domain.actorusecases.GetActorsByQuery; 5 | import com.ru.devit.mediateka.domain.UseCaseSubscriber; 6 | import com.ru.devit.mediateka.models.model.Actor; 7 | import com.ru.devit.mediateka.presentation.base.BasePresenter; 8 | import com.ru.devit.mediateka.presentation.base.BaseView; 9 | 10 | import java.util.List; 11 | 12 | public class ActorsPresenter extends BasePresenter { 13 | 14 | private List actors; 15 | private final GetActorsByQuery getActorsByQuery; 16 | 17 | public ActorsPresenter(GetActorsByQuery getActorsByQuery) { 18 | this.getActorsByQuery = getActorsByQuery; 19 | } 20 | 21 | @Override 22 | public void initialize() { 23 | getView().showLoading(); 24 | getActorsByQuery.subscribe(new ActorsSubscriber()); 25 | getActorsByQuery.setActions(new Actions( 26 | () -> getView().showLoading() , 27 | () -> getView().hideLoading() , 28 | () -> getView().clearAdapter() 29 | )); 30 | getView().hideLoading(); 31 | } 32 | 33 | public void setActors(List actors){ 34 | this.actors = actors; 35 | showActors(this.actors); 36 | } 37 | 38 | public void onGetTextFromSearchField(String query) { 39 | getActorsByQuery.setQuery(query); 40 | } 41 | 42 | public void onActorClicked(int actorId , int viewHolderPosition){ 43 | getView().openActor(actorId , viewHolderPosition); 44 | } 45 | 46 | private void showActors(List actors){ 47 | getView().showActors(actors); 48 | getView().hideLoading(); 49 | } 50 | 51 | public void onDestroy() { 52 | getActorsByQuery.removeActions(); 53 | getActorsByQuery.dispose(); 54 | setView(null); 55 | } 56 | 57 | final class ActorsSubscriber extends UseCaseSubscriber>{ 58 | @Override 59 | public void onNext(List actors) { 60 | getView().showActors(actors); 61 | getView().hideLoading(); 62 | } 63 | 64 | @Override 65 | public void onError(Throwable t) { 66 | t.printStackTrace(); 67 | getView().hideLoading(); 68 | } 69 | 70 | @Override 71 | public void onComplete() { 72 | getView().hideLoading(); 73 | } 74 | } 75 | 76 | public interface View extends BaseView{ 77 | void openActor(int actorId , int viewHolderPosition); 78 | void showActors(List actors); 79 | void clearAdapter(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /app/src/main/java/com/ru/devit/mediateka/presentation/cinemalist/CinemaTab.java: -------------------------------------------------------------------------------- 1 | package com.ru.devit.mediateka.presentation.cinemalist; 2 | 3 | import android.support.annotation.NonNull; 4 | import android.support.annotation.Nullable; 5 | import android.util.Log; 6 | 7 | import com.ru.devit.mediateka.domain.UseCase; 8 | import com.ru.devit.mediateka.domain.UseCaseSubscriber; 9 | import com.ru.devit.mediateka.models.model.Cinema; 10 | import com.ru.devit.mediateka.presentation.common.CinemaTabSelectorView; 11 | 12 | import java.util.List; 13 | 14 | import io.reactivex.disposables.CompositeDisposable; 15 | 16 | public enum CinemaTab { 17 | 18 | POPULAR { 19 | @Override 20 | public void loadCinemaList(UseCase> useCase, 21 | UseCaseSubscriber> subscriber, 22 | @NonNull T view) { 23 | super.loadCinemaList(useCase , subscriber , view); 24 | view.onPopularTabSelected(); 25 | } 26 | }, 27 | TOP_RATED { 28 | @Override 29 | public void loadCinemaList(UseCase> useCase, 30 | UseCaseSubscriber> subscriber, 31 | @NonNull T view) { 32 | super.loadCinemaList(useCase , subscriber , view); 33 | view.onTopRatedTabSelected(); 34 | } 35 | }, 36 | UP_COMING { 37 | @Override 38 | public void loadCinemaList(UseCase> useCase, 39 | UseCaseSubscriber> subscriber, 40 | @NonNull T view) { 41 | super.loadCinemaList(useCase , subscriber , view); 42 | view.onUpComingTabSelected(); 43 | } 44 | }; 45 | 46 | private final CompositeDisposable compositeDisposable = new CompositeDisposable(); 47 | 48 | public void dispose(){ 49 | if (!compositeDisposable.isDisposed()){ 50 | compositeDisposable.dispose(); 51 | } 52 | } 53 | 54 | public void loadCinemaList(@Nullable UseCase> useCase, 55 | @Nullable UseCaseSubscriber> subscriber, 56 | @NonNull T view){ 57 | if (useCase != null && subscriber != null){ 58 | useCase.subscribe(subscriber); 59 | compositeDisposable.add(subscriber); 60 | } 61 | } 62 | } 63 | --------------------------------------------------------------------------------