├── .gitignore
├── .idea
├── codeStyles
│ ├── Project.xml
│ └── codeStyleConfig.xml
├── encodings.xml
├── gradle.xml
├── misc.xml
├── runConfigurations.xml
└── vcs.xml
├── README.md
├── build.gradle
├── cache
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── tayfuncesur
│ │ │ └── cache
│ │ │ ├── Constants.kt
│ │ │ ├── ProjectsCacheImpl.kt
│ │ │ ├── dao
│ │ │ └── CachedProjectsDao.kt
│ │ │ ├── db
│ │ │ └── ProjectsDatabase.kt
│ │ │ ├── mapper
│ │ │ ├── CacheMapper.kt
│ │ │ └── CachedProjectMapper.kt
│ │ │ └── model
│ │ │ └── CachedProject.kt
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── tayfuncesur
│ └── cache
│ ├── MockData.kt
│ ├── ProjectsCacheImplTest.kt
│ ├── RunAll.kt
│ ├── dao
│ └── CachedProjectsDaoTest.kt
│ └── mapper
│ └── CachedProjectMapperTest.kt
├── data
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── tayfuncesur
│ │ └── data
│ │ ├── ProjectsDataRepository.kt
│ │ ├── mapper
│ │ ├── Mapper.kt
│ │ └── ProjectMapper.kt
│ │ ├── model
│ │ └── ProjectEntity.kt
│ │ ├── repository
│ │ ├── ProjectsCache.kt
│ │ ├── ProjectsDataStore.kt
│ │ └── ProjectsRemote.kt
│ │ └── store
│ │ ├── ProjectsCacheDataStore.kt
│ │ ├── ProjectsDataStoreFactory.kt
│ │ └── ProjectsRemoteDataStore.kt
│ └── test
│ └── java
│ └── com
│ └── tayfuncesur
│ └── data
│ ├── ProjectDataRepositoryTest.kt
│ ├── RunAll.kt
│ ├── data
│ └── MockData.kt
│ ├── mapper
│ └── ProjectMapperTest.kt
│ └── store
│ ├── ProjectsCacheDataStoreTest.kt
│ └── ProjectsRemoteDataStoreTest.kt
├── dependencies.gradle
├── domain
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── tayfuncesur
│ │ └── domain
│ │ ├── CompletableUseCase.kt
│ │ ├── ObservableUseCase.kt
│ │ ├── bookmark
│ │ ├── BookmarkProject.kt
│ │ ├── GetBookmarkedProjects.kt
│ │ └── UnbookmarkProject.kt
│ │ ├── executor
│ │ └── PostExecutionThread.kt
│ │ ├── getProjects
│ │ └── GetProjects.kt
│ │ ├── model
│ │ └── Project.kt
│ │ └── repository
│ │ └── ProjectsRepository.kt
│ └── test
│ └── java
│ └── com
│ └── tayfuncesur
│ └── data
│ ├── MockData.kt
│ ├── RunAll.kt
│ ├── bookmark
│ ├── BookmarkProjectTest.kt
│ ├── GetBookmarkedProjectsTest.kt
│ └── UnbookmarkProject.kt
│ └── getProjects
│ └── GetProjectsTest.kt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── mobile
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── androidTest
│ └── java
│ │ └── com
│ │ └── tayfuncesur
│ │ └── mobile
│ │ ├── RunAllUITests.kt
│ │ ├── di
│ │ ├── TestAppComponent.kt
│ │ └── module
│ │ │ ├── TestApplicationModule.kt
│ │ │ ├── TestCacheModule.kt
│ │ │ ├── TestDataModule.kt
│ │ │ └── TestRemoteModule.kt
│ │ ├── test
│ │ ├── Helper.kt
│ │ ├── TestApp.kt
│ │ └── TestRunner.kt
│ │ └── ui
│ │ ├── data
│ │ └── MockData.kt
│ │ ├── detail
│ │ └── DetailActivityTest.kt
│ │ └── main
│ │ └── MainActivityTest.kt
│ ├── main
│ ├── AndroidManifest.xml
│ ├── assets
│ │ └── anims
│ │ │ ├── bookmark.json
│ │ │ ├── error.json
│ │ │ ├── loading.json
│ │ │ └── no_connection.json
│ ├── java
│ │ └── com
│ │ │ └── tayfuncesur
│ │ │ └── mobile
│ │ │ ├── App.kt
│ │ │ ├── UIThread.kt
│ │ │ ├── base
│ │ │ ├── ActivityExtensions.kt
│ │ │ ├── BaseDaggerActivity.kt
│ │ │ └── UIConstants.kt
│ │ │ ├── di
│ │ │ ├── AppComponent.kt
│ │ │ ├── ViewModelFactory.kt
│ │ │ └── module
│ │ │ │ ├── ApplicationModule.kt
│ │ │ │ ├── CacheModule.kt
│ │ │ │ ├── DataModule.kt
│ │ │ │ ├── PresentationModule.kt
│ │ │ │ ├── RemoteModule.kt
│ │ │ │ └── UIModule.kt
│ │ │ ├── mapper
│ │ │ ├── ProjectMapper.kt
│ │ │ └── ViewMapper.kt
│ │ │ ├── model
│ │ │ └── Project.kt
│ │ │ └── ui
│ │ │ ├── detail
│ │ │ └── MainDetailActivity.kt
│ │ │ ├── main
│ │ │ ├── MainActivity.kt
│ │ │ └── MainAdapter.kt
│ │ │ ├── noConnection
│ │ │ └── NoConnectionFragment.kt
│ │ │ └── splash
│ │ │ └── SplashActivity.kt
│ └── res
│ │ ├── drawable
│ │ ├── appicon.png
│ │ ├── appiconblack.png
│ │ ├── ic_bookmarked.xml
│ │ ├── ic_favorite.xml
│ │ ├── ic_favorite_outlined.xml
│ │ └── ic_star.xml
│ │ ├── font
│ │ ├── manropemedium.ttf
│ │ └── manroperegular.ttf
│ │ ├── layout
│ │ ├── activity_main.xml
│ │ ├── main_detail.xml
│ │ ├── no_connection.xml
│ │ ├── single_row.xml
│ │ └── splash_screen.xml
│ │ └── values
│ │ ├── colors.xml
│ │ ├── dimens.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ └── test
│ └── java
│ └── com
│ └── tayfuncesur
│ └── mobile
│ ├── MockData.kt
│ └── ProjectMapperTest.kt
├── presentation
├── .gitignore
├── build.gradle
├── proguard-rules.pro
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── java
│ │ └── com
│ │ │ └── tayfuncesur
│ │ │ └── presentation
│ │ │ ├── BookmarkViewModel.kt
│ │ │ ├── ProjectsViewModel.kt
│ │ │ ├── SplashScreenViewModel.kt
│ │ │ ├── mapper
│ │ │ ├── Mapper.kt
│ │ │ └── ProjectViewMapper.kt
│ │ │ ├── model
│ │ │ └── ProjectView.kt
│ │ │ └── state
│ │ │ └── Resource.kt
│ └── res
│ │ └── values
│ │ └── strings.xml
│ └── test
│ └── java
│ └── com
│ └── tayfuncesur
│ └── presentation
│ ├── MockData.kt
│ ├── RunAll.kt
│ ├── mapper
│ └── ProjectViewMapperTest.kt
│ └── viewmodel
│ ├── BookmarkViewModelTest.kt
│ └── ProjectsViewModelTest.kt
├── remote
├── .gitignore
├── build.gradle
└── src
│ ├── main
│ └── java
│ │ └── com
│ │ └── tayfuncesur
│ │ └── remote
│ │ ├── ProjectsRemoteImpl.kt
│ │ ├── mapper
│ │ ├── Mapper.kt
│ │ └── ProjectResponseMapper.kt
│ │ ├── model
│ │ └── ProjectModel.kt
│ │ └── service
│ │ ├── GithubService.kt
│ │ └── GithubServiceFactory.kt
│ └── test
│ └── java
│ └── com
│ └── tayfuncesur
│ └── data
│ ├── mapper
│ └── ProjectModelMapperTest.kt
│ └── remote
│ └── MockData.kt
└── settings.gradle
/.gitignore:
--------------------------------------------------------------------------------
1 | *.iml
2 | .gradle
3 | /local.properties
4 | /.idea/caches
5 | /.idea/libraries
6 | /.idea/modules.xml
7 | /.idea/workspace.xml
8 | /.idea/navEditor.xml
9 | /.idea/assetWizardSettings.xml
10 | .DS_Store
11 | /build
12 | /captures
13 | .externalNativeBuild
14 |
--------------------------------------------------------------------------------
/.idea/codeStyles/Project.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/.idea/codeStyles/codeStyleConfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.idea/encodings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/.idea/gradle.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
22 |
23 |
--------------------------------------------------------------------------------
/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/.idea/runConfigurations.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/.idea/vcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Github Project Browser
2 | This is a sample Android Project that is based on [Uncle Bob's Clean Architecture](https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html) .It contains six different layers but the count of layers may change with respect to your project, you may have more or less layers. All of them have own mission but none of them interests with the others. That is the main idea actually. I will not explain the Clean Architecture because this is already well documented. I will just explain how I implemented. Here is the preview of project for just motivation before diving
3 |
4 |
5 |
6 | ### Layers
7 | When you do an action in your application, the flow will be like below respectively
8 | - **UI Layer** is the layer that displays your data. You may use multi UI layer in your project. (*For instance you have already an app that runs in phones and if you want to expand your targets for new platforms such as watches or TVs. There is no problem for you anymore.*)
9 | - **Presentation Layer** is the layer like a gateway between your UI and Business. You can use any design pattern in here(MVP,MVVM,MVI etc.)
10 | (***MVVM*** is used in this project)
11 | - **Domain Layer,** is the core layer that represents the use cases of project. You can think this layer as a middleman.
12 | - **Data Layer**, is the layer that determines where the data will come from.
13 | It has two sublayers.
14 | - **Remote Layer** is the layer that fetches the data from network. (***Retrofit2*** is used in this project)
15 | - **Cache Layer** is the layer that is responsible for getting data from sqlite
16 | (***Room*** is used in this project)
17 |
18 | ## UI Layer
19 | As I mentioned above, this is the layer that displays your data. It may use your presenters or your ViewModels. (ViewModels are used in this projects.) This layer works as usual way. I mean, observes your livedatas and displays the user.
20 | ## Presentation
21 | This is the layer that is the easiest one in my opinion. It's mission is so simple. It has ViewModels that are just executing your command like below.( *For instance, getProjects*)
22 | ```
23 | fun getProjects() {
24 | remoteProjectsLiveData.postValue(Resource.Loading())
25 | getProjects.execute(ProjectsSubscriber())//Here, the flow navigates through the domain layer
26 | }
27 | ```
28 | The ViewModel's main functions are so simple like above.
29 |
30 | ## Domain
31 | This is the core layer. You should determine your projects use cases. For instance, in this project, I needed four use cases. These are, (*in my **ProjectsRepository** interface*)
32 | - getProjects(): Observable>
33 | - getBookmarkedProjects(): Observable>
34 | - bookmarkProject(projectId: String): Completable
35 | - unbookmarkProject(projectId: String)
36 |
37 | But of course that depends on your projects needs. And you can imagine this interface like a *bridge* that navigates your flow to the data layer. So if you want to display projects, your class *(GetProjects)* calls the getProjects() function of the *ProjectsRepository* and then the flow navigates through the data layer because data layer implements your bridge.
38 |
39 | ## Data
40 | The core class of Data layer is ***ProjectsDataRepository*** class in this project because that class implements your use cases and takes care of them.
41 |
42 | ```
43 | class ProjectsDataRepository @Inject constructor(
44 | private val mapper: ProjectMapper,
45 | private val dataStore: ProjectsDataStoreFactory
46 | ) : ProjectsRepository { // This is the interface that represents your use cases
47 | // You defined it in the Domain Layer
48 | override fun getProjects(): Observable> {
49 | return dataStore.getRemoteDataStore().getProjects().map {
50 | it.map {
51 | mapper.mapFromEntity(it)
52 | }
53 | }
54 | }
55 | }
56 | ```
57 | You can think this layer responsible for getting and serving data through the Domain layer but it doesn't care about where or how the data comes. It just a navigator that navigates your flow through the Remote or Cache.
58 | In this project, Data layer navigates the flow if getProjects() calls, because projects must be fetched from network, but if the other use cases are intended for example *bookmarkProject* or *getBookmarkedProjects*, these data must be fetched from local storage.So data layer navigates your flow though the cache layer.
59 | ## Remote Layer
60 | This layer is also easy layer because it has only one responsibility. It just making a network request via *Retrofit2*
61 | It has only one class that is called by ***ProjectsRemoteImpl***
62 |
63 | ## Cache Layer
64 | This layer is responsible for managing the local storage, getting and serving data through the Data Layer. It uses Room which is an awesome library. It just calls the DAO functions.
65 |
66 | # Keynotes & Bonuses
67 |
68 | ### Mapper
69 | This project have many mapper classes. This was my decision actually not a requirement but this is good way because for this, each layer can work with different model and anyone don't depends on some other model. I have mapper interface like below,(For example CacheMapper, because I can cache some additional data like creationDate etc which doesn't concerns the other layers.)
70 | ```
71 | interface CacheMapper { //Like I: Input, O:Output
72 |
73 | fun mapFromCache(cache: I): O
74 |
75 | fun mapToCache(dataToCache: O): I
76 | }
77 | ```
78 |
79 | ### Dependencies
80 | Many of these layers above uses same dependencies like RX, JavaxAnnotation, or mockito for testing. So it would be really good to manage them all in one file. For this reason, this project uses the dependencies.gradle file and it applies in main build.gradle file
81 | ```
82 | apply from: 'dependencies.gradle'
83 | ```
84 | ### Bonus 1: Tests
85 | This project contains all its units and UI tests and I strongly recommmend you to have a look cache layer unit tests because there are Room and local storage tests.
86 |
87 | ### Bonus 2: [ReactiveNetwork](https://github.com/pwittchen/ReactiveNetwork)
88 | You can learn the implementation of the ReactiveNetwork and how to broadcast the network connection with Rx from this project. It is really useful because there is a behaviour changes between API levels.
89 |
90 | # Core Libraries
91 | - [Android Architecture Components](https://developer.android.com/topic/libraries/architecture/)
92 | - [Dagger2](https://google.github.io/dagger/)
93 | - [Rx](https://github.com/ReactiveX/RxJava)
94 | - [Lottie](https://github.com/airbnb/lottie-android)
95 | - [Room](https://developer.android.com/topic/libraries/architecture/room)
96 | - [Retrofit2](http://square.github.io/retrofit/)
97 | - [Mockito](https://github.com/mockito/mockito)
98 | - [Espresso](https://developer.android.com/training/testing/espresso)
99 |
100 | # Summary
101 | To use Clean Architecture or not, this is not requirement actually this is up to you and your project. But if you want to ***[seperate your concerns](https://www.slideshare.net/outware/a-separation-of-concerns-clean-architecture-on-android)*** for example testing, maintaining. I found it really useful.
102 |
103 | ### Thanks
104 | This project is based on [Joe Birch's Github Trending](https://github.com/hitherejoe/GithubTrending)
105 | If you have any questions, hit me on [Twitter](https://twitter.com/CesurTayfun35)
106 |
107 | ## Licence
108 | ```
109 | Copyright 2019 Tayfun CESUR
110 |
111 | Licensed under the Apache License, Version 2.0 (the "License");
112 | you may not use this file except in compliance with the License.
113 | You may obtain a copy of the License at
114 |
115 | http://www.apache.org/licenses/LICENSE-2.0
116 |
117 | Unless required by applicable law or agreed to in writing, software
118 | distributed under the License is distributed on an "AS IS" BASIS,
119 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
120 | See the License for the specific language governing permissions and
121 | limitations under the License.
122 | ```
123 |
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | // Top-level build file where you can add configuration options common to all sub-projects/modules.
2 |
3 | buildscript {
4 | ext.kotlin_version = '1.3.21'
5 | repositories {
6 | google()
7 | jcenter()
8 |
9 | }
10 | dependencies {
11 | classpath 'com.android.tools.build:gradle:3.3.2'
12 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13 | // NOTE: Do not place your application dependencies here; they belong
14 | // in the individual module build.gradle files
15 | }
16 | }
17 |
18 | apply from: 'dependencies.gradle'
19 |
20 | allprojects {
21 | repositories {
22 | google()
23 | jcenter()
24 |
25 | }
26 | }
27 |
28 | task clean(type: Delete) {
29 | delete rootProject.buildDir
30 | }
31 |
--------------------------------------------------------------------------------
/cache/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/cache/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin:'kotlin-android'
3 | apply plugin:'kotlin-kapt'
4 |
5 | android {
6 | compileSdkVersion 28
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | targetSdkVersion 28
11 | }
12 |
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 |
20 | }
21 |
22 | dependencies {
23 | def cacheDependencies = rootProject.ext.cacheDependencies
24 | def cacheTestDependencies = rootProject.ext.cacheTestDependencies
25 |
26 | compileOnly cacheDependencies.javaxAnnotation
27 |
28 | implementation project(':data')
29 |
30 | implementation cacheDependencies.kotlin
31 | implementation cacheDependencies.javaxInject
32 | implementation cacheDependencies.rxKotlin
33 | implementation cacheDependencies.roomRuntime
34 | implementation cacheDependencies.roomRxJava
35 | kapt cacheDependencies.roomCompiler
36 |
37 | testImplementation cacheTestDependencies.junit
38 | testImplementation cacheTestDependencies.kotlinJUnit
39 | testImplementation cacheTestDependencies.mockito
40 | testImplementation cacheTestDependencies.robolectric
41 | testImplementation cacheTestDependencies.archTesting
42 | testImplementation cacheTestDependencies.roomTesting
43 | androidTestImplementation cacheTestDependencies.supportAnnotations
44 | androidTestImplementation cacheTestDependencies.junit
45 | }
46 |
--------------------------------------------------------------------------------
/cache/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/cache/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/cache/src/main/java/com/tayfuncesur/cache/Constants.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache
2 |
3 | object Constants {
4 |
5 | const val dbName= "TayfunCesurTask"
6 |
7 | const val tableName = "Projects"
8 |
9 | const val selectQuery = "SELECT * from $tableName"
10 |
11 | }
--------------------------------------------------------------------------------
/cache/src/main/java/com/tayfuncesur/cache/ProjectsCacheImpl.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache
2 |
3 | import com.tayfuncesur.cache.db.ProjectsDatabase
4 | import com.tayfuncesur.cache.mapper.CachedProjectMapper
5 | import com.tayfuncesur.cache.model.CachedProject
6 | import com.tayfuncesur.data.repository.ProjectsCache
7 | import io.reactivex.Completable
8 | import io.reactivex.Observable
9 | import javax.inject.Inject
10 |
11 | class ProjectsCacheImpl @Inject constructor(
12 | private val projectsDatabase: ProjectsDatabase,
13 | private val mapper: CachedProjectMapper
14 | ) : ProjectsCache {
15 |
16 | override fun getBookmarkedProjects(): Observable> {
17 | return projectsDatabase.cachedProjectsDao().getBookmarkedProjects().toObservable().map {
18 | it.map {
19 | mapper.mapFromCache(it)
20 | }
21 | }
22 | }
23 |
24 | override fun setProjectBookmarked(projectId: String): Completable {
25 | return Completable.defer {
26 | projectsDatabase.cachedProjectsDao().setBookmarkedProject(CachedProject(projectId, true))
27 | Completable.complete()
28 | }
29 |
30 | }
31 |
32 | override fun setProjectUnbookmarked(projectId: String): Completable {
33 | return Completable.defer {
34 | projectsDatabase.cachedProjectsDao().setUnBookmarkedProject(projectId)
35 | Completable.complete()
36 | }
37 |
38 | }
39 | }
--------------------------------------------------------------------------------
/cache/src/main/java/com/tayfuncesur/cache/dao/CachedProjectsDao.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache.dao
2 |
3 | import android.arch.persistence.room.*
4 | import com.tayfuncesur.cache.Constants
5 | import com.tayfuncesur.cache.model.CachedProject
6 | import io.reactivex.Flowable
7 |
8 | @Dao
9 | interface CachedProjectsDao {
10 |
11 | @Query(Constants.selectQuery)
12 | fun getBookmarkedProjects(): Flowable>
13 |
14 | @Insert(onConflict = OnConflictStrategy.REPLACE)
15 | fun setBookmarkedProject(cachedProject: CachedProject)
16 |
17 | @Query("DELETE FROM ${Constants.tableName} where id = :projectId")
18 | fun setUnBookmarkedProject(projectId: String)
19 |
20 | }
--------------------------------------------------------------------------------
/cache/src/main/java/com/tayfuncesur/cache/db/ProjectsDatabase.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache.db
2 |
3 | import android.arch.persistence.room.Database
4 | import android.arch.persistence.room.Room
5 | import android.arch.persistence.room.RoomDatabase
6 | import android.content.Context
7 | import com.tayfuncesur.cache.Constants
8 | import com.tayfuncesur.cache.dao.CachedProjectsDao
9 | import com.tayfuncesur.cache.model.CachedProject
10 | import javax.inject.Inject
11 |
12 | @Database(version = 1, entities = [CachedProject::class])
13 | abstract class ProjectsDatabase @Inject constructor() : RoomDatabase() {
14 |
15 | abstract fun cachedProjectsDao(): CachedProjectsDao
16 |
17 | companion object {
18 |
19 | @Volatile
20 | private var Instance: ProjectsDatabase? = null
21 |
22 | fun getInstance(context: Context): ProjectsDatabase {
23 | return Instance ?: synchronized(this) {
24 | Instance ?: buildDatabase(context).also { Instance = it }
25 | }
26 | }
27 |
28 | private fun buildDatabase(context: Context): ProjectsDatabase {
29 | return Room.databaseBuilder(context.applicationContext, ProjectsDatabase::class.java, Constants.dbName)
30 | .build()
31 | }
32 |
33 | }
34 | }
--------------------------------------------------------------------------------
/cache/src/main/java/com/tayfuncesur/cache/mapper/CacheMapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache.mapper
2 |
3 | interface CacheMapper {
4 |
5 | fun mapFromCache(cache: I): O
6 |
7 | fun mapToCache(cache: O): I
8 |
9 | }
--------------------------------------------------------------------------------
/cache/src/main/java/com/tayfuncesur/cache/mapper/CachedProjectMapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache.mapper
2 |
3 | import com.tayfuncesur.cache.model.CachedProject
4 |
5 | class CachedProjectMapper : CacheMapper {
6 |
7 | override fun mapFromCache(cache: CachedProject): String {
8 | return cache.id
9 | }
10 |
11 | override fun mapToCache(cache: String): CachedProject {
12 | return CachedProject(cache, true)
13 | }
14 | }
--------------------------------------------------------------------------------
/cache/src/main/java/com/tayfuncesur/cache/model/CachedProject.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache.model
2 |
3 | import android.arch.persistence.room.Entity
4 | import android.arch.persistence.room.PrimaryKey
5 | import com.tayfuncesur.cache.Constants
6 |
7 | @Entity(tableName = Constants.tableName)
8 | class CachedProject(@PrimaryKey var id: String, var isBookmarked: Boolean)
--------------------------------------------------------------------------------
/cache/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | cache
3 |
4 |
--------------------------------------------------------------------------------
/cache/src/test/java/com/tayfuncesur/cache/MockData.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache
2 |
3 | import com.tayfuncesur.cache.model.CachedProject
4 | import com.tayfuncesur.data.model.ProjectEntity
5 | import java.util.*
6 |
7 | object MockData {
8 |
9 | fun generateRandomProjectEntity(isBookmarked : Boolean = false): ProjectEntity {
10 | return ProjectEntity(UUID.randomUUID().toString(), UUID.randomUUID().toString(), Math.random().toInt(), isBookmarked)
11 | }
12 |
13 | fun generateRandomCachedProject(): CachedProject {
14 | return CachedProject(UUID.randomUUID().toString(), Math.random() < 0.5)
15 | }
16 |
17 | fun generateRandomCachedProjectList(count: Int = 20): List {
18 | val list = mutableListOf()
19 | repeat(count) {
20 | list.add(generateRandomCachedProject())
21 | }
22 | return list
23 | }
24 |
25 |
26 | }
--------------------------------------------------------------------------------
/cache/src/test/java/com/tayfuncesur/cache/ProjectsCacheImplTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache
2 |
3 | import android.arch.core.executor.testing.InstantTaskExecutorRule
4 | import android.arch.persistence.room.Room
5 | import com.tayfuncesur.cache.db.ProjectsDatabase
6 | import com.tayfuncesur.cache.mapper.CachedProjectMapper
7 | import org.junit.BeforeClass
8 | import org.junit.Rule
9 | import org.junit.Test
10 | import org.junit.runner.RunWith
11 | import org.robolectric.RobolectricTestRunner
12 | import org.robolectric.RuntimeEnvironment
13 | import kotlin.test.assertEquals
14 |
15 | @RunWith(RobolectricTestRunner::class)
16 | class ProjectsCacheImplTest {
17 |
18 |
19 | @get:Rule
20 | var instantTaskExecutorRule = InstantTaskExecutorRule()
21 |
22 | private val database = Room.inMemoryDatabaseBuilder(
23 | RuntimeEnvironment.application.applicationContext,
24 | ProjectsDatabase::class.java
25 | ).allowMainThreadQueries()
26 | .build()
27 |
28 | private val cacheMapper = CachedProjectMapper()
29 |
30 | private val projectsCacheImpl = ProjectsCacheImpl(database, cacheMapper)
31 |
32 | @Test
33 | fun shouldGetBookmarkedProjectsReturnsData() {
34 | val randomCachedProject = MockData.generateRandomCachedProject()
35 | projectsCacheImpl.setProjectBookmarked(randomCachedProject.id).test()
36 | val result = projectsCacheImpl.getBookmarkedProjects().test()
37 | assertEquals(result.values()[0].isNotEmpty(), true)
38 | }
39 |
40 | @Test
41 | fun shouldSetBookmarkProjectReturnsExpectedData() {
42 | val randomCachedProject = MockData.generateRandomCachedProject()
43 | randomCachedProject.isBookmarked = true
44 | projectsCacheImpl.setProjectBookmarked(randomCachedProject.id).test()
45 | val data = projectsCacheImpl.getBookmarkedProjects().test()
46 | assertEquals(data.values()[0][0], randomCachedProject.id)
47 | }
48 |
49 | @Test
50 | fun shouldUnbookmarkProjectWork() {
51 | val randomCachedProject = MockData.generateRandomCachedProject()
52 | randomCachedProject.isBookmarked = true
53 | projectsCacheImpl.setProjectBookmarked(randomCachedProject.id).test()
54 |
55 |
56 | projectsCacheImpl.setProjectUnbookmarked(randomCachedProject.id).test()
57 |
58 |
59 | val result = projectsCacheImpl.getBookmarkedProjects().test()
60 | assertEquals(result.values()[0].size, 0)
61 | }
62 |
63 | }
--------------------------------------------------------------------------------
/cache/src/test/java/com/tayfuncesur/cache/RunAll.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache
2 |
3 | import com.tayfuncesur.cache.dao.CachedProjectsDaoTest
4 | import com.tayfuncesur.cache.mapper.CachedProjectMapperTest
5 | import org.junit.runner.RunWith
6 | import org.junit.runners.Suite
7 |
8 | @RunWith(Suite::class)
9 | @Suite.SuiteClasses(CachedProjectsDaoTest::class, CachedProjectMapperTest::class, ProjectsCacheImplTest::class)
10 | class RunAll
--------------------------------------------------------------------------------
/cache/src/test/java/com/tayfuncesur/cache/dao/CachedProjectsDaoTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache.dao
2 |
3 | import android.arch.core.executor.testing.InstantTaskExecutorRule
4 | import android.arch.persistence.room.Room
5 | import com.tayfuncesur.cache.MockData
6 | import com.tayfuncesur.cache.db.ProjectsDatabase
7 | import org.junit.*
8 | import org.junit.runner.RunWith
9 | import org.mockito.Mockito
10 | import org.robolectric.RobolectricTestRunner
11 | import org.robolectric.RuntimeEnvironment
12 | import kotlin.test.assertEquals
13 |
14 | @RunWith(RobolectricTestRunner::class)
15 | class CachedProjectsDaoTest {
16 |
17 | @Rule
18 | @JvmField
19 | var instantTaskExecutorRule = InstantTaskExecutorRule()
20 |
21 | lateinit var database: ProjectsDatabase
22 |
23 | @Before
24 | fun setup() {
25 | database = Room.inMemoryDatabaseBuilder(
26 | RuntimeEnvironment.application.applicationContext,
27 | ProjectsDatabase::class.java
28 | ).allowMainThreadQueries()
29 | .build()
30 | }
31 |
32 | @Test
33 | fun shouldGetBookmarkedProjectsReturnsExpectedData() {
34 | val cachedProject = MockData.generateRandomCachedProject()
35 | database.cachedProjectsDao().setBookmarkedProject(cachedProject)
36 |
37 | val list = database.cachedProjectsDao().getBookmarkedProjects().test()
38 | assertEquals(list.values()[0][0].id, cachedProject.id)
39 | assertEquals(list.values()[0][0].isBookmarked, cachedProject.isBookmarked)
40 | }
41 |
42 | @Test
43 | fun shouldSetBookmarkedProjectReturnsExpectedData() {
44 | val cachedProject = MockData.generateRandomCachedProject()
45 | cachedProject.isBookmarked = true
46 | database.cachedProjectsDao().setBookmarkedProject(cachedProject)
47 |
48 | val list = database.cachedProjectsDao().getBookmarkedProjects().test()
49 | assertEquals(list.values()[0][0].id, cachedProject.id)
50 | assertEquals(list.values()[0][0].isBookmarked, cachedProject.isBookmarked)
51 | }
52 |
53 | @Test
54 | fun shouldUnbookmarkedProjectReturnsExpectedData() {
55 | val cachedProject = MockData.generateRandomCachedProject()
56 | cachedProject.isBookmarked = true
57 | database.cachedProjectsDao().setBookmarkedProject(cachedProject)
58 | database.cachedProjectsDao().setUnBookmarkedProject(cachedProject.id)
59 |
60 | val list = database.cachedProjectsDao().getBookmarkedProjects().test()
61 | assertEquals(list.values()[0].size, 0)
62 | }
63 |
64 |
65 | @After
66 | fun close() {
67 | database.close()
68 | }
69 |
70 |
71 | }
--------------------------------------------------------------------------------
/cache/src/test/java/com/tayfuncesur/cache/mapper/CachedProjectMapperTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.cache.mapper
2 |
3 | import com.tayfuncesur.cache.MockData
4 | import org.junit.Test
5 | import org.junit.runner.RunWith
6 | import org.junit.runners.JUnit4
7 | import kotlin.test.assertEquals
8 |
9 | @RunWith(JUnit4::class)
10 | class CachedProjectMapperTest {
11 |
12 | private val mapper = CachedProjectMapper()
13 |
14 | @Test
15 | fun shouldMapFromCache() {
16 | val cached = MockData.generateRandomCachedProject()
17 | val mapped = mapper.mapFromCache(cached)
18 | assertEquals(cached.id, mapped)
19 | }
20 |
21 | @Test
22 | fun shouldMapToCache() {
23 | val entity = MockData.generateRandomProjectEntity(true)
24 | val cached = mapper.mapToCache(entity.id)
25 | assertEquals(cached.id, entity.id)
26 | assertEquals(cached.isBookmarked, entity.isBookmarked)
27 | }
28 |
29 | }
--------------------------------------------------------------------------------
/data/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/data/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | dependencies {
4 | def dataDependencies = rootProject.ext.dataDependencies
5 | def dataTestDependencies = rootProject.ext.dataTestDependencies
6 |
7 | implementation project(':domain')
8 |
9 | implementation dataDependencies.javaxAnnotation
10 | implementation dataDependencies.javaxInject
11 | implementation dataDependencies.rxKotlin
12 |
13 | testImplementation dataTestDependencies.junit
14 | testImplementation dataTestDependencies.kotlinJUnit
15 | testImplementation dataTestDependencies.mockito
16 | }
17 |
18 | sourceCompatibility = "1.6"
19 | targetCompatibility = "1.6"
20 |
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/ProjectsDataRepository.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data
2 |
3 | import com.tayfuncesur.data.mapper.ProjectMapper
4 | import com.tayfuncesur.data.store.ProjectsDataStoreFactory
5 | import com.tayfuncesur.domain.model.Project
6 | import com.tayfuncesur.domain.repository.ProjectsRepository
7 | import io.reactivex.Completable
8 | import io.reactivex.Observable
9 | import javax.inject.Inject
10 |
11 | class ProjectsDataRepository @Inject constructor(
12 | private val mapper: ProjectMapper,
13 | private val dataStore: ProjectsDataStoreFactory
14 | ) : ProjectsRepository {
15 | override fun getProjects(): Observable> {
16 | return dataStore.getRemoteDataStore().getProjects().map {
17 | it.map {
18 | mapper.mapFromEntity(it)
19 | }
20 | }
21 | }
22 |
23 | override fun bookmarkProject(projectId: String): Completable {
24 | return dataStore.getCacheDataStore().setProjectBookmarked(projectId)
25 | }
26 |
27 | override fun unbookmarkProject(projectId: String): Completable {
28 | return dataStore.getCacheDataStore().setProjectUnbookmarked(projectId)
29 | }
30 |
31 | override fun getBookmarkedProjects(): Observable> {
32 | return dataStore.getCacheDataStore().getBookmarkedProjects()
33 | }
34 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/mapper/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.mapper
2 |
3 | interface Mapper {
4 |
5 | fun mapFromEntity(entity: E): D
6 |
7 | fun mapToEntity(domain: D): E
8 |
9 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/mapper/ProjectMapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.mapper
2 |
3 | import com.tayfuncesur.data.model.ProjectEntity
4 | import com.tayfuncesur.domain.model.Project
5 |
6 | class ProjectMapper : Mapper {
7 | override fun mapFromEntity(entity: ProjectEntity): Project {
8 | return Project(entity.id, entity.fullName, entity.starCount, entity.isBookmarked)
9 | }
10 |
11 | override fun mapToEntity(domain: Project): ProjectEntity {
12 | return ProjectEntity(domain.id, domain.fullName, domain.starCount, domain.isBookmarked)
13 | }
14 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/model/ProjectEntity.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.model
2 |
3 | data class ProjectEntity(
4 | val id: String, val fullName: String,
5 | val starCount: Int, val isBookmarked: Boolean
6 | )
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/repository/ProjectsCache.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.repository
2 |
3 | import io.reactivex.Completable
4 | import io.reactivex.Observable
5 |
6 | interface ProjectsCache {
7 |
8 | fun getBookmarkedProjects(): Observable>
9 |
10 | fun setProjectBookmarked(projectId: String): Completable
11 |
12 | fun setProjectUnbookmarked(projectId: String): Completable
13 |
14 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/repository/ProjectsDataStore.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.repository
2 |
3 | import com.tayfuncesur.data.model.ProjectEntity
4 | import io.reactivex.Completable
5 | import io.reactivex.Observable
6 |
7 | interface ProjectsDataStore {
8 |
9 | fun getBookmarkedProjects(): Observable>
10 |
11 | fun setProjectBookmarked(projectId: String): Completable
12 |
13 | fun setProjectUnbookmarked(projectId: String): Completable
14 |
15 | fun getProjects(): Observable>
16 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/repository/ProjectsRemote.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.repository
2 |
3 | import com.tayfuncesur.data.model.ProjectEntity
4 | import io.reactivex.Observable
5 |
6 | interface ProjectsRemote {
7 |
8 | fun getProjects(): Observable>
9 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/store/ProjectsCacheDataStore.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.store
2 |
3 | import com.tayfuncesur.data.model.ProjectEntity
4 | import com.tayfuncesur.data.repository.ProjectsCache
5 | import com.tayfuncesur.data.repository.ProjectsDataStore
6 | import io.reactivex.Completable
7 | import io.reactivex.Observable
8 | import javax.inject.Inject
9 |
10 | class ProjectsCacheDataStore @Inject constructor(
11 | private val projectsCache: ProjectsCache
12 | ) : ProjectsDataStore {
13 |
14 | override fun getBookmarkedProjects(): Observable> {
15 | return projectsCache.getBookmarkedProjects()
16 | }
17 |
18 | override fun setProjectBookmarked(projectId: String): Completable {
19 | return projectsCache.setProjectBookmarked(projectId)
20 | }
21 |
22 | override fun setProjectUnbookmarked(projectId: String): Completable {
23 | return projectsCache.setProjectUnbookmarked(projectId)
24 | }
25 |
26 | override fun getProjects(): Observable> {
27 | throw UnsupportedOperationException("Unsupported operation")
28 | }
29 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/store/ProjectsDataStoreFactory.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.store
2 |
3 | import javax.inject.Inject
4 |
5 | class ProjectsDataStoreFactory @Inject constructor(
6 | private val projectsCacheDataStore: ProjectsCacheDataStore,
7 | private val projectsRemoteDataStore: ProjectsRemoteDataStore
8 | ) {
9 | fun getCacheDataStore(): ProjectsCacheDataStore {
10 | return projectsCacheDataStore
11 | }
12 |
13 | fun getRemoteDataStore(): ProjectsRemoteDataStore {
14 | return projectsRemoteDataStore
15 | }
16 | }
--------------------------------------------------------------------------------
/data/src/main/java/com/tayfuncesur/data/store/ProjectsRemoteDataStore.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.store
2 |
3 | import com.tayfuncesur.data.model.ProjectEntity
4 | import com.tayfuncesur.data.repository.ProjectsDataStore
5 | import com.tayfuncesur.data.repository.ProjectsRemote
6 | import io.reactivex.Completable
7 | import io.reactivex.Observable
8 | import java.lang.UnsupportedOperationException
9 | import javax.inject.Inject
10 |
11 | class ProjectsRemoteDataStore @Inject constructor(
12 | private val projectsRemote: ProjectsRemote
13 | ) : ProjectsDataStore {
14 | override fun getBookmarkedProjects(): Observable> {
15 | throw UnsupportedOperationException("Not supported")
16 | }
17 |
18 | override fun setProjectBookmarked(projectId: String): Completable {
19 | throw UnsupportedOperationException("Not supported")
20 | }
21 |
22 | override fun setProjectUnbookmarked(projectId: String): Completable {
23 | throw UnsupportedOperationException("Not supported")
24 | }
25 |
26 | override fun getProjects(): Observable> {
27 | return projectsRemote.getProjects()
28 | }
29 |
30 | }
--------------------------------------------------------------------------------
/data/src/test/java/com/tayfuncesur/data/ProjectDataRepositoryTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data
2 |
3 | import com.tayfuncesur.data.mapper.ProjectMapper
4 | import com.tayfuncesur.data.store.ProjectsCacheDataStore
5 | import com.tayfuncesur.data.store.ProjectsDataStoreFactory
6 | import com.tayfuncesur.data.store.ProjectsRemoteDataStore
7 | import com.tayfuncesur.data.data.MockData
8 | import io.reactivex.Completable
9 | import io.reactivex.Observable
10 | import org.junit.Before
11 | import org.junit.Test
12 | import org.junit.runner.RunWith
13 | import org.mockito.Mock
14 | import org.mockito.Mockito
15 | import org.mockito.junit.MockitoJUnitRunner
16 | import java.util.*
17 |
18 | @RunWith(MockitoJUnitRunner::class)
19 | class ProjectDataRepositoryTest {
20 |
21 | lateinit var projectsDataRepository: ProjectsDataRepository
22 |
23 | @Mock
24 | lateinit var mapper: ProjectMapper
25 |
26 | @Mock
27 | lateinit var projectsRemoteDataStore: ProjectsRemoteDataStore
28 |
29 | @Mock
30 | lateinit var projectsCacheDataStore: ProjectsCacheDataStore
31 |
32 | lateinit var projectsDataStoreFactory: ProjectsDataStoreFactory
33 |
34 | @Before
35 | fun setup() {
36 | projectsDataStoreFactory = ProjectsDataStoreFactory(projectsCacheDataStore, projectsRemoteDataStore)
37 | projectsDataRepository = ProjectsDataRepository(mapper, projectsDataStoreFactory)
38 | }
39 |
40 | @Test
41 | fun shouldGetProjectsCompletes() {
42 | Mockito.`when`(projectsDataStoreFactory.getRemoteDataStore().getProjects())
43 | .thenReturn(Observable.just(MockData.generateRandomProjectEntityList()))
44 | projectsDataRepository.getProjects().test().assertComplete()
45 | }
46 |
47 | @Test
48 | fun shouldGetProjectsReturnsExpectedData() {
49 | val list = MockData.generateRandomProjectList(1)
50 | val entityList = MockData.generateRandomProjectEntityList(1)
51 |
52 | Mockito.`when`(projectsDataStoreFactory.getRemoteDataStore().getProjects())
53 | .thenReturn(Observable.just(entityList))
54 |
55 | Mockito.`when`(mapper.mapFromEntity(entityList[0]))
56 | .thenReturn(list[0])
57 |
58 | projectsDataRepository.getProjects().test().assertValue(list)
59 | }
60 |
61 | @Test
62 | fun shouldSetProjectBookmarkedCompletes() {
63 | val projectId = UUID.randomUUID().toString()
64 | Mockito.`when`(projectsDataStoreFactory.getCacheDataStore().setProjectBookmarked(projectId)).thenReturn(
65 | Completable.complete()
66 | )
67 | projectsDataRepository.bookmarkProject(projectId).test().assertComplete()
68 | }
69 |
70 | @Test
71 | fun shouldSetProjectUnbookmarkedCompletes() {
72 | val projectId = UUID.randomUUID().toString()
73 | Mockito.`when`(projectsDataStoreFactory.getCacheDataStore().setProjectUnbookmarked(projectId)).thenReturn(
74 | Completable.complete()
75 | )
76 | projectsDataRepository.unbookmarkProject(projectId).test().assertComplete()
77 | }
78 |
79 | @Test
80 | fun shouldGetBookmarkedProjectCompletes(){
81 | Mockito.`when`(projectsDataStoreFactory.getCacheDataStore().getBookmarkedProjects())
82 | .thenReturn(Observable.just(MockData.generateRandomIdList()))
83 | projectsDataRepository.getBookmarkedProjects().test().assertComplete()
84 | }
85 |
86 |
87 |
88 | }
--------------------------------------------------------------------------------
/data/src/test/java/com/tayfuncesur/data/RunAll.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data
2 |
3 | import com.tayfuncesur.data.mapper.ProjectMapperTest
4 | import com.tayfuncesur.data.store.ProjectsCacheDataStoreTest
5 | import com.tayfuncesur.data.store.ProjectsRemoteDataStoreTest
6 | import org.junit.runner.RunWith
7 | import org.junit.runners.Suite
8 |
9 |
10 | @RunWith(Suite::class)
11 | @Suite.SuiteClasses(
12 | ProjectDataRepositoryTest::class,
13 | ProjectMapperTest::class,
14 | ProjectsCacheDataStoreTest::class,
15 | ProjectsRemoteDataStoreTest::class
16 | )
17 | class RunAll
--------------------------------------------------------------------------------
/data/src/test/java/com/tayfuncesur/data/data/MockData.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.data
2 |
3 | import com.tayfuncesur.data.model.ProjectEntity
4 | import com.tayfuncesur.domain.model.Project
5 | import java.util.*
6 |
7 | object MockData {
8 |
9 | fun randomProjectEntity() = ProjectEntity(
10 | UUID.randomUUID().toString(),
11 | UUID.randomUUID().toString(),
12 | Math.random().toInt(),
13 | false
14 | )
15 |
16 |
17 | fun randomProject() = Project(
18 | UUID.randomUUID().toString(),
19 | UUID.randomUUID().toString(),
20 | Math.random().toInt(),
21 | false
22 | )
23 |
24 | fun generateRandomProjectEntity(): ProjectEntity {
25 | return ProjectEntity(UUID.randomUUID().toString(), UUID.randomUUID().toString(), Math.random().toInt(), false)
26 | }
27 |
28 | fun generateRandomProjectEntityList(count : Int = 20): List {
29 | val list = mutableListOf()
30 | repeat(count) {
31 | list.add(generateRandomProjectEntity())
32 | }
33 | return list
34 | }
35 |
36 | fun generateRandomIdList(): List {
37 | val list = mutableListOf()
38 | repeat(20) {
39 | list.add(UUID.randomUUID().toString())
40 | }
41 | return list
42 | }
43 |
44 | fun generateRandomProject(): Project {
45 | return Project(UUID.randomUUID().toString(), UUID.randomUUID().toString(), Math.random().toInt(), false)
46 | }
47 |
48 | fun generateRandomProjectList(count : Int = 20): List {
49 | val list = mutableListOf()
50 | repeat(count) {
51 | list.add(generateRandomProject())
52 | }
53 | return list
54 | }
55 |
56 |
57 | }
--------------------------------------------------------------------------------
/data/src/test/java/com/tayfuncesur/data/mapper/ProjectMapperTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.mapper
2 |
3 | import com.tayfuncesur.data.data.MockData
4 | import org.junit.Test
5 | import org.junit.runner.RunWith
6 | import org.mockito.junit.MockitoJUnitRunner
7 | import kotlin.test.assertEquals
8 |
9 |
10 | @RunWith(MockitoJUnitRunner::class)
11 | class ProjectMapperTest {
12 |
13 | private val projectMapper = ProjectMapper()
14 |
15 | @Test
16 | fun mapFromEntityTest() {
17 | val entity = MockData.randomProjectEntity()
18 | val domain = projectMapper.mapFromEntity(entity)
19 | assertEquals(entity.id, domain.id)
20 | assertEquals(entity.fullName, domain.fullName)
21 | assertEquals(entity.starCount, domain.starCount)
22 | assertEquals(entity.isBookmarked, domain.isBookmarked)
23 | }
24 |
25 | @Test
26 | fun mapToEntityTest() {
27 | val domain = MockData.randomProject()
28 | val entity = projectMapper.mapToEntity(domain)
29 | assertEquals(entity.id, domain.id)
30 | assertEquals(entity.fullName, domain.fullName)
31 | assertEquals(entity.starCount, domain.starCount)
32 | assertEquals(entity.isBookmarked, domain.isBookmarked)
33 | }
34 |
35 |
36 | }
--------------------------------------------------------------------------------
/data/src/test/java/com/tayfuncesur/data/store/ProjectsCacheDataStoreTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.store
2 |
3 | import com.tayfuncesur.data.repository.ProjectsCache
4 | import com.tayfuncesur.data.data.MockData
5 | import io.reactivex.Completable
6 | import io.reactivex.Observable
7 | import org.junit.Before
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 | import org.mockito.ArgumentMatchers.anyString
11 | import org.mockito.Mock
12 | import org.mockito.Mockito
13 | import org.mockito.junit.MockitoJUnitRunner
14 | import java.lang.UnsupportedOperationException
15 | import java.util.*
16 |
17 | @RunWith(MockitoJUnitRunner::class)
18 | class ProjectsCacheDataStoreTest {
19 |
20 | lateinit var projectsCacheDataStore: ProjectsCacheDataStore
21 |
22 | @Mock
23 | lateinit var projectesCache: ProjectsCache
24 |
25 | @Before
26 | fun setup() {
27 | projectsCacheDataStore = ProjectsCacheDataStore(projectesCache)
28 | }
29 |
30 | @Test
31 | fun shouldGetBookmarkedProjectCompletes() {
32 | Mockito.`when`(projectesCache.getBookmarkedProjects())
33 | .thenReturn(Observable.just(MockData.generateRandomIdList()))
34 | projectsCacheDataStore.getBookmarkedProjects().test().assertComplete()
35 | }
36 |
37 | @Test
38 | fun shouldGetBookmarkedProjectReturnsExpectedData() {
39 | val list = MockData.generateRandomIdList()
40 | Mockito.`when`(projectesCache.getBookmarkedProjects()).thenReturn(Observable.just(list))
41 | projectsCacheDataStore.getBookmarkedProjects().test().assertValue(list)
42 | }
43 |
44 | @Test
45 | fun shouldBookmarkProjectsCompletes() {
46 | val projectId = UUID.randomUUID().toString()
47 | Mockito.`when`(projectesCache.setProjectBookmarked(anyString())).thenReturn(Completable.complete())
48 | projectsCacheDataStore.setProjectBookmarked(projectId).test().assertComplete()
49 | }
50 |
51 | @Test
52 | fun shouldBookmarkProjectCallsCacheStore() {
53 | val projectId = UUID.randomUUID().toString()
54 | Mockito.`when`(projectesCache.setProjectBookmarked(anyString())).thenReturn(Completable.complete())
55 | projectsCacheDataStore.setProjectBookmarked(projectId).test()
56 | Mockito.verify(projectesCache).setProjectBookmarked(projectId)
57 | }
58 |
59 | @Test
60 | fun shouldUnbookmarkProjectsCompletes() {
61 | val projectId = UUID.randomUUID().toString()
62 | Mockito.`when`(projectesCache.setProjectUnbookmarked(anyString())).thenReturn(Completable.complete())
63 | projectsCacheDataStore.setProjectUnbookmarked(projectId).test().assertComplete()
64 | }
65 |
66 | @Test
67 | fun shouldUnbookmarkProjectCallsCacheStore() {
68 | val projectId = UUID.randomUUID().toString()
69 | Mockito.`when`(projectesCache.setProjectUnbookmarked(anyString())).thenReturn(Completable.complete())
70 | projectsCacheDataStore.setProjectUnbookmarked(projectId).test()
71 | Mockito.verify(projectesCache).setProjectUnbookmarked(projectId)
72 | }
73 |
74 | @Test(expected = UnsupportedOperationException::class)
75 | fun shouldThrowExceptionForGetProjectsRemote() {
76 | projectsCacheDataStore.getProjects()
77 | }
78 |
79 |
80 | }
--------------------------------------------------------------------------------
/data/src/test/java/com/tayfuncesur/data/store/ProjectsRemoteDataStoreTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.store
2 |
3 | import com.tayfuncesur.data.repository.ProjectsRemote
4 | import com.tayfuncesur.data.data.MockData
5 | import io.reactivex.Observable
6 | import org.junit.Before
7 | import org.junit.Test
8 | import org.junit.runner.RunWith
9 | import org.mockito.Mock
10 | import org.mockito.Mockito
11 | import org.mockito.junit.MockitoJUnitRunner
12 | import java.lang.UnsupportedOperationException
13 | import java.util.*
14 |
15 | @RunWith(MockitoJUnitRunner::class)
16 | class ProjectsRemoteDataStoreTest {
17 |
18 | lateinit var projectsRemoteDataStore: ProjectsRemoteDataStore
19 |
20 | @Mock
21 | lateinit var projectsRemote: ProjectsRemote
22 |
23 | @Before
24 | fun setup() {
25 | projectsRemoteDataStore = ProjectsRemoteDataStore(projectsRemote)
26 | }
27 |
28 | @Test
29 | fun shouldGetProjectsCompletes() {
30 | Mockito.`when`(projectsRemote.getProjects()).thenReturn(Observable.just(MockData.generateRandomProjectEntityList()))
31 | projectsRemoteDataStore.getProjects().test().assertComplete()
32 | }
33 |
34 | @Test
35 | fun shouldGetProjectsReturnsExpectedData() {
36 | val list = MockData.generateRandomProjectEntityList()
37 | Mockito.`when`(projectsRemote.getProjects()).thenReturn(Observable.just(list))
38 | projectsRemoteDataStore.getProjects().test().assertValue(list)
39 | }
40 |
41 | @Test(expected = UnsupportedOperationException::class)
42 | fun shouldThrowExceptionForBookmarkProject() {
43 | projectsRemoteDataStore.setProjectBookmarked(UUID.randomUUID().toString())
44 | }
45 |
46 | @Test(expected = UnsupportedOperationException::class)
47 | fun shouldThrowExceptionForUnbookmarkProject() {
48 | projectsRemoteDataStore.setProjectUnbookmarked(UUID.randomUUID().toString())
49 | }
50 |
51 |
52 | @Test(expected = UnsupportedOperationException::class)
53 | fun shouldThrowExceptionForGetBookmarkedProject() {
54 | projectsRemoteDataStore.getBookmarkedProjects()
55 | }
56 |
57 |
58 | }
--------------------------------------------------------------------------------
/dependencies.gradle:
--------------------------------------------------------------------------------
1 | ext {
2 | //Android
3 | androidBuildToolsVersion = "28.0.0"
4 | androidMinSdkVersion = 16
5 | androidTargetSdkVersion = 28
6 | androidCompileSdkVersion = 28
7 | supportLibraryVersion = '28.0.0'
8 |
9 |
10 | //Layer Dependencies
11 | javaxAnnotationVersion = '1.0'
12 | javaxInjectVersion = '1'
13 | rxKotlinVersion = '2.2.0'
14 |
15 | gsonVersion = '2.8.5'
16 | retrofitVersion = '2.4.0'
17 | okHttpVersion = '3.9.1'
18 |
19 | daggerVersion = '2.19'
20 | kotlinVersion = '1.3.21'
21 | roomVersion = '1.1.1'
22 |
23 | androidAnnotationsVersion = '26.0.0'
24 | rxAndroidVersion = '2.1.1'
25 |
26 | glideVersion = '4.9.0'
27 | glassfishAnnotationVersion = '3.1.1'
28 | espressoVersion = '3.0.2'
29 | runnerVersion = '0.5'
30 | constraintLayoutVersion = '1.1.3'
31 | lottieVersion = '2.7.0'
32 | pushDownAnimVersion = '1.1.1'
33 | rxNetworkVersion = '3.0.1'
34 |
35 | //Test Dependencies
36 | jUnitVersion = '4.12'
37 | mockitoVersion = '2.22.0'
38 | kotlinJUnitVersion = '1.3.21'
39 |
40 | archCompVersion = '1.1.1'
41 | androidSupportRunnerVersion = '1.0.0'
42 | androidSupportRulesVersion = '1.0.0'
43 | androidSupportRulesVersion = '28.0.0'
44 | robolectricVersion = '3.4.2'
45 |
46 | mockitoKotlinVersion = '1.5.0'
47 | mockitoAndroidVersion = '2.8.9'
48 |
49 | // -------------------------------- Domain --------------------------------
50 | domainDependencies = [
51 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
52 | javaxInject : "javax.inject:javax.inject:${javaxInjectVersion}",
53 | rxJava : "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}"
54 | ]
55 |
56 | // -------------------------------- Domain Test --------------------------------
57 | domainTestDependencies = [
58 | junit : "junit:junit:${jUnitVersion}",
59 | mockito: "org.mockito:mockito-inline:${mockitoVersion}",
60 | ]
61 | //-------------------------------------------------------------
62 | //-------------------------------------------------------------
63 | //-------------------------------------------------------------
64 |
65 |
66 | // -------------------------------- Data --------------------------------
67 | dataDependencies = [
68 | rxKotlin : "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
69 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
70 | javaxInject : "javax.inject:javax.inject:${javaxInjectVersion}",
71 | ]
72 |
73 | // -------------------------------- Data Test --------------------------------
74 | dataTestDependencies = [
75 | junit : "junit:junit:${jUnitVersion}",
76 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlinJUnitVersion}",
77 | mockito : "org.mockito:mockito-inline:${mockitoVersion}",
78 | ]
79 | //-------------------------------------------------------------
80 | //-------------------------------------------------------------
81 | //-------------------------------------------------------------
82 |
83 |
84 | // -------------------------------- Remote --------------------------------
85 | remoteDependencies = [
86 | rxKotlin : "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
87 | javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
88 | javaxInject : "javax.inject:javax.inject:${javaxInjectVersion}",
89 | gson : "com.google.code.gson:gson:${gsonVersion}",
90 | okHttp : "com.squareup.okhttp3:okhttp:${okHttpVersion}",
91 | okHttpLogger : "com.squareup.okhttp3:logging-interceptor:${okHttpVersion}",
92 | retrofit : "com.squareup.retrofit2:retrofit:${retrofitVersion}",
93 | retrofitConverter: "com.squareup.retrofit2:converter-gson:${retrofitVersion}",
94 | retrofitAdapter : "com.squareup.retrofit2:adapter-rxjava2:${retrofitVersion}"
95 | ]
96 |
97 | // -------------------------------- Remote Test --------------------------------
98 | remoteTestDependencies = [
99 | junit : "junit:junit:${jUnitVersion}",
100 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlinJUnitVersion}",
101 | mockito : "org.mockito:mockito-inline:${mockitoVersion}",
102 | ]
103 |
104 | //-------------------------------------------------------------
105 | //-------------------------------------------------------------
106 | //-------------------------------------------------------------
107 |
108 |
109 | // -------------------------------- Cache --------------------------------
110 | cacheDependencies = [
111 | rxKotlin : "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
112 | kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}",
113 | javaxAnnotation: "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
114 | javaxInject : "javax.inject:javax.inject:${javaxInjectVersion}",
115 | roomRuntime : "android.arch.persistence.room:runtime:${roomVersion}",
116 | roomCompiler : "android.arch.persistence.room:compiler:${roomVersion}",
117 | roomRxJava : "android.arch.persistence.room:rxjava2:${roomVersion}"
118 | ]
119 |
120 | // -------------------------------- Cache Test --------------------------------
121 | cacheTestDependencies = [
122 | junit : "junit:junit:${jUnitVersion}",
123 | kotlinJUnit : "org.jetbrains.kotlin:kotlin-test-junit:${kotlinJUnitVersion}",
124 | mockito : "org.mockito:mockito-inline:${mockitoVersion}",
125 | roomTesting : "android.arch.persistence.room:testing:${roomVersion}",
126 | archTesting : "android.arch.core:core-testing:${archCompVersion}",
127 | supportRunner : "com.android.support.test:runner:${androidSupportRunnerVersion}",
128 | supportRules : "com.android.support.test:rules:${androidSupportRulesVersion}",
129 | supportAnnotations: "com.android.support:support-annotations:${androidSupportRulesVersion}",
130 | robolectric : "org.robolectric:robolectric:${robolectricVersion}"
131 | ]
132 |
133 | //-------------------------------------------------------------
134 | //-------------------------------------------------------------
135 | //-------------------------------------------------------------
136 |
137 |
138 | // -------------------------------- Presentation --------------------------------
139 | presentationDependencies = [
140 | daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}",
141 | dagger : "com.google.dagger:dagger:${daggerVersion}",
142 | rxKotlin : "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
143 | kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}",
144 | rxAndroid : "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}",
145 | javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
146 | javaxInject : "javax.inject:javax.inject:${javaxInjectVersion}",
147 | androidAnnotations: "com.android.support:support-annotations:${androidAnnotationsVersion}",
148 | archRuntime : "android.arch.lifecycle:runtime:${archCompVersion}",
149 | archExtensions : "android.arch.lifecycle:extensions:${archCompVersion}",
150 | archCompiler : "android.arch.lifecycle:compiler:${archCompVersion}",
151 | ]
152 |
153 | // -------------------------------- Presentation Test --------------------------------
154 | presentationTestDependencies = [
155 | junit : "junit:junit:${jUnitVersion}",
156 | kotlinJUnit: "org.jetbrains.kotlin:kotlin-test-junit:${kotlinJUnitVersion}",
157 | mockito : "com.nhaarman:mockito-kotlin:${mockitoKotlinVersion}",
158 | robolectric: "org.robolectric:robolectric:${robolectricVersion}",
159 | archTesting: "android.arch.core:core-testing:${archCompVersion}",
160 | ]
161 | //-------------------------------------------------------------
162 | //-------------------------------------------------------------
163 | //-------------------------------------------------------------
164 |
165 |
166 | // -------------------------------- Mobile --------------------------------
167 | mobileDependencies = [
168 | daggerCompiler : "com.google.dagger:dagger-compiler:${daggerVersion}",
169 | dagger : "com.google.dagger:dagger:${daggerVersion}",
170 | rxKotlin : "io.reactivex.rxjava2:rxkotlin:${rxKotlinVersion}",
171 | rxAndroid : "io.reactivex.rxjava2:rxandroid:${rxAndroidVersion}",
172 | glide : "com.github.bumptech.glide:glide:${glideVersion}",
173 | kotlin : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${kotlinVersion}",
174 | javaxAnnotation : "javax.annotation:jsr250-api:${javaxAnnotationVersion}",
175 | javaxInject : "javax.inject:javax.inject:${javaxInjectVersion}",
176 | androidAnnotations : "com.android.support:support-annotations:${supportLibraryVersion}",
177 | supportDesign : "com.android.support:design:${supportLibraryVersion}",
178 | supportRecyclerView: "com.android.support:recyclerview-v7:${supportLibraryVersion}",
179 | daggerSupport : "com.google.dagger:dagger-android-support:${daggerVersion}",
180 | daggerProcessor : "com.google.dagger:dagger-android-processor:${daggerVersion}",
181 | glassfishAnnotation: "org.glassfish:javax.annotation:${glassfishAnnotationVersion}",
182 | roomRxJava : "android.arch.persistence.room:rxjava2:${roomVersion}",
183 | constraintLayout : "com.android.support.constraint:constraint-layout:${constraintLayoutVersion}",
184 | lottie : "com.airbnb.android:lottie:${lottieVersion}",
185 | pushDownAnim : "com.github.thekhaeng:pushdown-anim-click:${pushDownAnimVersion}",
186 | reactivenetwork : "com.github.pwittchen:reactivenetwork-rx2:${rxNetworkVersion}"
187 |
188 | ]
189 |
190 |
191 | // -------------------------------- Mobile Test --------------------------------
192 |
193 | mobileUiTestDependencies = [
194 | junit : "junit:junit:${jUnitVersion}",
195 | kotlinJUnit : "org.jetbrains.kotlin:kotlin-test-junit:${kotlin_version}",
196 | supportRunner : "com.android.support.test:runner:${androidSupportRunnerVersion}",
197 | supportRules : "com.android.support.test:rules:${androidSupportRulesVersion}",
198 | mockitoAndroid : "org.mockito:mockito-android:${mockitoAndroidVersion}",
199 | espressoCore : "com.android.support.test.espresso:espresso-core:${espressoVersion}",
200 | espressoContrib: "com.android.support.test.espresso:espresso-contrib:${espressoVersion}",
201 | androidRunner : "com.android.support.test:runner:${runnerVersion}",
202 | androidRules : "com.android.support.test:rules:${runnerVersion}"
203 | ]
204 |
205 |
206 | }
--------------------------------------------------------------------------------
/domain/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/domain/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | dependencies {
4 | testImplementation domainTestDependencies.junit
5 | def domainDependencies = rootProject.ext.domainDependencies
6 | def domainTestDependencies = rootProject.ext.domainTestDependencies
7 | implementation domainDependencies.javaxAnnotation
8 | implementation domainDependencies.javaxInject
9 | implementation domainDependencies.rxJava
10 | testImplementation domainTestDependencies.mockito
11 | }
12 |
13 | sourceCompatibility = "1.6"
14 | targetCompatibility = "1.6"
15 |
16 |
--------------------------------------------------------------------------------
/domain/src/main/java/com/tayfuncesur/domain/CompletableUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.domain
2 |
3 | import com.tayfuncesur.domain.executor.PostExecutionThread
4 | import io.reactivex.Completable
5 | import io.reactivex.disposables.CompositeDisposable
6 | import io.reactivex.disposables.Disposable
7 | import io.reactivex.observers.DisposableCompletableObserver
8 | import io.reactivex.schedulers.Schedulers
9 |
10 | abstract class CompletableUseCase constructor(
11 | private val postExecutionThread: PostExecutionThread
12 | ) {
13 |
14 | private val disposables = CompositeDisposable()
15 |
16 | protected abstract fun doWork(params: Params? = null): Completable
17 |
18 | open fun execute(observer: DisposableCompletableObserver, params: Params? = null) {
19 | val completable = this.doWork(params)
20 | .subscribeOn(Schedulers.io())
21 | .observeOn(postExecutionThread.scheduler)
22 | addDisposable(completable.subscribeWith(observer))
23 | }
24 |
25 | fun addDisposable(disposable: Disposable) {
26 | disposables.add(disposable)
27 | }
28 |
29 | fun clear() {
30 | if (!disposables.isDisposed) disposables.dispose()
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/tayfuncesur/domain/ObservableUseCase.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.domain
2 |
3 | import com.tayfuncesur.domain.executor.PostExecutionThread
4 | import io.reactivex.Observable
5 | import io.reactivex.disposables.CompositeDisposable
6 | import io.reactivex.disposables.Disposable
7 | import io.reactivex.observers.DisposableObserver
8 | import io.reactivex.schedulers.Schedulers
9 |
10 | abstract class ObservableUseCase constructor(
11 | private val postExecutionThread: PostExecutionThread
12 | ) {
13 |
14 | private val disposables = CompositeDisposable()
15 |
16 | protected abstract fun doWork(params: Params? = null): Observable
17 |
18 | open fun execute(singleObserver: DisposableObserver, params: Params? = null) {
19 | val single = this.doWork(params)
20 | .subscribeOn(Schedulers.io())
21 | .observeOn(postExecutionThread.scheduler)
22 | addDisposable(single.subscribeWith(singleObserver))
23 | }
24 |
25 | fun addDisposable(disposable: Disposable) {
26 | disposables.add(disposable)
27 | }
28 |
29 | fun clear() {
30 | if (!disposables.isDisposed) disposables.dispose()
31 | }
32 |
33 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/tayfuncesur/domain/bookmark/BookmarkProject.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.domain.bookmark
2 |
3 | import com.tayfuncesur.domain.CompletableUseCase
4 | import com.tayfuncesur.domain.executor.PostExecutionThread
5 | import com.tayfuncesur.domain.repository.ProjectsRepository
6 | import io.reactivex.Completable
7 | import java.lang.IllegalArgumentException
8 | import javax.inject.Inject
9 |
10 | class BookmarkProject @Inject constructor(
11 | private val projectsRepository: ProjectsRepository,
12 | postExecutionThread: PostExecutionThread
13 | ) :
14 | CompletableUseCase(postExecutionThread) {
15 | public override fun doWork(params: Params?): Completable {
16 | if (params == null) throw IllegalArgumentException("ProjectId is required to bookmark")
17 | return projectsRepository.bookmarkProject(params.projectId)
18 | }
19 |
20 |
21 | data class Params constructor(val projectId: String) {
22 | companion object {
23 | fun projectId(projectId: String): Params = Params(projectId)
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/tayfuncesur/domain/bookmark/GetBookmarkedProjects.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.domain.bookmark
2 |
3 | import com.tayfuncesur.domain.ObservableUseCase
4 | import com.tayfuncesur.domain.executor.PostExecutionThread
5 | import com.tayfuncesur.domain.repository.ProjectsRepository
6 | import io.reactivex.Observable
7 | import javax.inject.Inject
8 |
9 | class GetBookmarkedProjects @Inject constructor(
10 | private val projectsRepository: ProjectsRepository,
11 | postExecutionThread: PostExecutionThread
12 | ) : ObservableUseCase, Nothing?>(postExecutionThread) {
13 | public override fun doWork(params: Nothing?): Observable> {
14 | return projectsRepository.getBookmarkedProjects()
15 | }
16 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/tayfuncesur/domain/bookmark/UnbookmarkProject.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.domain.bookmark
2 |
3 | import com.tayfuncesur.domain.CompletableUseCase
4 | import com.tayfuncesur.domain.executor.PostExecutionThread
5 | import com.tayfuncesur.domain.repository.ProjectsRepository
6 | import io.reactivex.Completable
7 | import java.lang.IllegalArgumentException
8 | import javax.inject.Inject
9 |
10 | class UnbookmarkProject @Inject constructor(
11 | private val projectsRepository: ProjectsRepository,
12 | postExecutionThread: PostExecutionThread
13 | ) :
14 | CompletableUseCase(postExecutionThread) {
15 | public override fun doWork(params: Params?): Completable {
16 | if (params == null) throw IllegalArgumentException("ProjectId is required to unbookmark")
17 | return projectsRepository.unbookmarkProject(params.projectId)
18 | }
19 |
20 |
21 | class Params constructor(val projectId: String) {
22 | companion object {
23 | fun projectId(projectId: String): Params = Params(projectId)
24 | }
25 | }
26 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/tayfuncesur/domain/executor/PostExecutionThread.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.domain.executor
2 |
3 | import io.reactivex.Scheduler
4 |
5 | interface PostExecutionThread {
6 |
7 | val scheduler: Scheduler
8 |
9 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/tayfuncesur/domain/getProjects/GetProjects.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.domain.getProjects
2 |
3 | import com.tayfuncesur.domain.ObservableUseCase
4 | import com.tayfuncesur.domain.executor.PostExecutionThread
5 | import com.tayfuncesur.domain.model.Project
6 | import com.tayfuncesur.domain.repository.ProjectsRepository
7 | import io.reactivex.Observable
8 | import javax.inject.Inject
9 |
10 | open class GetProjects @Inject constructor(
11 | private val projectsRepository: ProjectsRepository,
12 | postExecutionThread: PostExecutionThread
13 | ) : ObservableUseCase, Nothing?>(postExecutionThread) {
14 | public override fun doWork(params: Nothing?): Observable> {
15 | return projectsRepository.getProjects()
16 | }
17 | }
--------------------------------------------------------------------------------
/domain/src/main/java/com/tayfuncesur/domain/model/Project.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.domain.model
2 |
3 | class Project(
4 | val id: String, val fullName: String,
5 | val starCount: Int, val isBookmarked: Boolean
6 | )
--------------------------------------------------------------------------------
/domain/src/main/java/com/tayfuncesur/domain/repository/ProjectsRepository.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.domain.repository
2 |
3 | import com.tayfuncesur.domain.model.Project
4 | import io.reactivex.Completable
5 | import io.reactivex.Observable
6 |
7 | interface ProjectsRepository {
8 |
9 | fun getProjects(): Observable>
10 |
11 | fun bookmarkProject(projectId: String): Completable
12 |
13 | fun unbookmarkProject(projectId: String): Completable
14 |
15 | fun getBookmarkedProjects(): Observable>
16 |
17 |
18 | }
--------------------------------------------------------------------------------
/domain/src/test/java/com/tayfuncesur/data/MockData.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data
2 |
3 | import com.tayfuncesur.domain.model.Project
4 | import java.util.*
5 |
6 | object MockData {
7 |
8 | fun generateRandomProject(): Project {
9 | return Project(UUID.randomUUID().toString(), UUID.randomUUID().toString(), Math.random().toInt(), false)
10 | }
11 |
12 | fun generateRandomProjectList(): List {
13 | val list = mutableListOf()
14 | repeat(20) {
15 | list.add(generateRandomProject())
16 | }
17 | return list
18 | }
19 |
20 | fun generateRandomIdList(): List {
21 | val list = mutableListOf()
22 | repeat(20) {
23 | list.add(UUID.randomUUID().toString())
24 | }
25 | return list
26 | }
27 |
28 |
29 | }
--------------------------------------------------------------------------------
/domain/src/test/java/com/tayfuncesur/data/RunAll.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data
2 |
3 | import com.tayfuncesur.data.bookmark.BookmarkProjectTest
4 | import com.tayfuncesur.data.bookmark.GetBookmarkedProjectsTest
5 | import com.tayfuncesur.data.bookmark.UnbookmarkProjectTest
6 | import com.tayfuncesur.data.getProjects.GetProjectsTest
7 | import org.junit.runner.RunWith
8 | import org.junit.runners.Suite
9 |
10 |
11 | @RunWith(Suite::class)
12 | @Suite.SuiteClasses(
13 | BookmarkProjectTest::class,
14 | GetBookmarkedProjectsTest::class,
15 | UnbookmarkProjectTest::class,
16 | GetProjectsTest::class
17 | )
18 | class RunAll
--------------------------------------------------------------------------------
/domain/src/test/java/com/tayfuncesur/data/bookmark/BookmarkProjectTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.bookmark
2 |
3 | import com.tayfuncesur.domain.bookmark.BookmarkProject
4 | import com.tayfuncesur.domain.executor.PostExecutionThread
5 | import com.tayfuncesur.domain.repository.ProjectsRepository
6 | import io.reactivex.Completable
7 | import org.junit.Before
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 | import org.mockito.ArgumentMatchers.anyString
11 | import org.mockito.Mock
12 | import org.mockito.Mockito
13 | import org.mockito.junit.MockitoJUnitRunner
14 | import java.lang.IllegalArgumentException
15 | import java.util.*
16 |
17 | @RunWith(MockitoJUnitRunner::class)
18 | class BookmarkProjectTest {
19 |
20 | lateinit var bookmarkProject: BookmarkProject
21 |
22 | @Mock
23 | lateinit var projectsRepository: ProjectsRepository
24 | @Mock
25 | lateinit var postExecutionThread: PostExecutionThread
26 |
27 | @Before
28 | fun setup() {
29 | bookmarkProject = BookmarkProject(projectsRepository, postExecutionThread)
30 | }
31 |
32 | @Test
33 | fun shouldBookmarkProjectCompletes() {
34 | Mockito.`when`(projectsRepository.bookmarkProject(anyString())).thenReturn(Completable.complete())
35 | bookmarkProject.doWork(BookmarkProject.Params.projectId(UUID.randomUUID().toString())).test().assertComplete()
36 | }
37 |
38 | @Test(expected = IllegalArgumentException::class)
39 | fun shouldThrowException() {
40 | bookmarkProject.doWork().test()
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/domain/src/test/java/com/tayfuncesur/data/bookmark/GetBookmarkedProjectsTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.bookmark
2 |
3 | import com.tayfuncesur.domain.bookmark.GetBookmarkedProjects
4 | import com.tayfuncesur.domain.executor.PostExecutionThread
5 | import com.tayfuncesur.domain.repository.ProjectsRepository
6 | import com.tayfuncesur.data.MockData
7 | import io.reactivex.Observable
8 | import org.junit.Before
9 | import org.junit.Test
10 | import org.junit.runner.RunWith
11 | import org.mockito.Mock
12 | import org.mockito.Mockito
13 | import org.mockito.junit.MockitoJUnitRunner
14 |
15 | @RunWith(MockitoJUnitRunner::class)
16 | class GetBookmarkedProjectsTest {
17 |
18 | lateinit var getBookmarkedProjects: GetBookmarkedProjects
19 |
20 | @Mock
21 | lateinit var projectsRepository: ProjectsRepository
22 |
23 | @Mock
24 | lateinit var postExecutionThread: PostExecutionThread
25 |
26 | @Before
27 | fun setup() {
28 | getBookmarkedProjects = GetBookmarkedProjects(projectsRepository, postExecutionThread)
29 | }
30 |
31 | @Test
32 | fun shouldGetBookmarkedProjectsCompletes() {
33 | Mockito.`when`(projectsRepository.getBookmarkedProjects())
34 | .thenReturn(Observable.just(MockData.generateRandomIdList()))
35 | getBookmarkedProjects.doWork().test().assertComplete()
36 | }
37 |
38 | @Test
39 | fun shouldGetBookmarkedProjectsReturnsExpectedData(){
40 | val list = MockData.generateRandomIdList()
41 | Mockito.`when`(projectsRepository.getBookmarkedProjects()).thenReturn(Observable.just(list))
42 | getBookmarkedProjects.doWork().test().assertValue(list)
43 | }
44 |
45 |
46 | }
--------------------------------------------------------------------------------
/domain/src/test/java/com/tayfuncesur/data/bookmark/UnbookmarkProject.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.bookmark
2 |
3 | import com.tayfuncesur.domain.bookmark.UnbookmarkProject
4 | import com.tayfuncesur.domain.executor.PostExecutionThread
5 | import com.tayfuncesur.domain.repository.ProjectsRepository
6 | import io.reactivex.Completable
7 | import org.junit.Before
8 | import org.junit.Test
9 | import org.junit.runner.RunWith
10 | import org.mockito.ArgumentMatchers.anyString
11 | import org.mockito.Mock
12 | import org.mockito.Mockito
13 | import org.mockito.junit.MockitoJUnitRunner
14 | import java.util.*
15 |
16 | @RunWith(MockitoJUnitRunner::class)
17 | class UnbookmarkProjectTest {
18 |
19 | lateinit var unbookmarkProject: UnbookmarkProject
20 |
21 | @Mock
22 | lateinit var projectsRepository: ProjectsRepository
23 |
24 | @Mock
25 | lateinit var postExecutionThread: PostExecutionThread
26 |
27 | @Before
28 | fun setup() {
29 | unbookmarkProject = UnbookmarkProject(projectsRepository, postExecutionThread)
30 | }
31 |
32 | @Test
33 | fun shouldUnbookmarkProjectCompletes() {
34 | Mockito.`when`(projectsRepository.unbookmarkProject(anyString())).thenReturn(Completable.complete())
35 | unbookmarkProject.doWork(UnbookmarkProject.Params.projectId(UUID.randomUUID().toString())).test()
36 | .assertComplete()
37 |
38 | }
39 |
40 |
41 | }
--------------------------------------------------------------------------------
/domain/src/test/java/com/tayfuncesur/data/getProjects/GetProjectsTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.getProjects
2 |
3 | import com.tayfuncesur.domain.executor.PostExecutionThread
4 | import com.tayfuncesur.domain.getProjects.GetProjects
5 | import com.tayfuncesur.domain.repository.ProjectsRepository
6 | import com.tayfuncesur.data.MockData
7 | import io.reactivex.Observable
8 | import org.junit.Before
9 | import org.junit.Test
10 | import org.junit.runner.RunWith
11 | import org.mockito.Mock
12 | import org.mockito.Mockito
13 | import org.mockito.Mockito.`when`
14 | import org.mockito.junit.MockitoJUnitRunner
15 |
16 | @RunWith(MockitoJUnitRunner::class)
17 | class GetProjectsTest {
18 |
19 | private lateinit var getProjects: GetProjects
20 | @Mock
21 | lateinit var projectRepository: ProjectsRepository
22 | @Mock
23 | lateinit var postExecutionThread: PostExecutionThread
24 |
25 | @Before
26 | fun setup() {
27 | getProjects = GetProjects(projectRepository, postExecutionThread)
28 | }
29 |
30 | @Test
31 | fun shouldGetProjectsCompletes() {
32 | `when`(projectRepository.getProjects()).thenReturn(Observable.just(MockData.generateRandomProjectList()))
33 |
34 | val testObserver = getProjects.doWork().test()
35 | testObserver.assertComplete()
36 | }
37 |
38 | @Test
39 | fun shouldGetProjectsReturnsData() {
40 | val list = MockData.generateRandomProjectList()
41 | Mockito.`when`(projectRepository.getProjects()).thenReturn(Observable.just(list))
42 | val testObserver = getProjects.doWork().test()
43 | testObserver.assertValue(list)
44 | }
45 |
46 |
47 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 | # IDE (e.g. Android Studio) users:
3 | # Gradle settings configured through the IDE *will override*
4 | # any settings specified in this file.
5 | # For more details on how to configure your build environment visit
6 | # http://www.gradle.org/docs/current/userguide/build_environment.html
7 | # Specifies the JVM arguments used for the daemon process.
8 | # The setting is particularly useful for tweaking memory settings.
9 | org.gradle.jvmargs=-Xmx1536m
10 | # When configured, Gradle will run in incubating parallel mode.
11 | # This option should only be used with decoupled projects. More details, visit
12 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
13 | # org.gradle.parallel=true
14 | # Kotlin code style for this project: "official" or "obsolete":
15 | kotlin.code.style=official
16 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TayfunCesur/GithubProjectBrowser/8aa6705cf42651422ec677c5e48c9cab12e54b9f/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu May 02 18:57:34 EET 2019
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-4.10.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn () {
37 | echo "$*"
38 | }
39 |
40 | die () {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save () {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/mobile/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/mobile/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.application'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-kapt'
4 | apply plugin: 'org.jetbrains.kotlin.android.extensions'
5 |
6 |
7 | android {
8 | compileSdkVersion 28
9 |
10 | defaultConfig {
11 | minSdkVersion 16
12 | targetSdkVersion 28
13 | versionCode 1
14 | versionName "1.0"
15 | testInstrumentationRunner "com.tayfuncesur.mobile.test.TestRunner"
16 | }
17 |
18 | buildTypes {
19 | release {
20 | minifyEnabled false
21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
22 | }
23 | }
24 | compileOptions {
25 | sourceCompatibility JavaVersion.VERSION_1_8
26 | targetCompatibility JavaVersion.VERSION_1_8
27 | }
28 | androidExtensions {
29 | experimental = true
30 | }
31 | }
32 |
33 | dependencies {
34 | def mobileDependencies = rootProject.ext.mobileDependencies
35 | def mobileUiTestDependencies = rootProject.ext.mobileUiTestDependencies
36 |
37 | implementation project(':domain')
38 | implementation project(':data')
39 | implementation project(':cache')
40 | implementation project(':remote')
41 | implementation project(':presentation')
42 |
43 | //Structure
44 | implementation mobileDependencies.javaxAnnotation
45 | implementation mobileDependencies.kotlin
46 | implementation mobileDependencies.javaxInject
47 | implementation presentationDependencies.archRuntime
48 | implementation presentationDependencies.archExtensions
49 | kapt presentationDependencies.archCompiler
50 |
51 | //Design
52 | implementation mobileDependencies.supportRecyclerView
53 | implementation mobileDependencies.supportDesign
54 | implementation mobileDependencies.constraintLayout
55 | implementation mobileDependencies.lottie
56 | implementation (mobileDependencies.pushDownAnim) {
57 | exclude group: 'com.android.support'
58 | }
59 | implementation mobileDependencies.glide
60 | implementation mobileDependencies.reactivenetwork
61 |
62 | //Dagger
63 | implementation mobileDependencies.dagger
64 | implementation mobileDependencies.daggerSupport
65 | implementation mobileDependencies.androidAnnotations
66 | kapt mobileDependencies.daggerCompiler
67 | kapt mobileDependencies.daggerProcessor
68 | compileOnly mobileDependencies.glassfishAnnotation
69 |
70 | //Rx
71 | implementation mobileDependencies.rxKotlin
72 | implementation mobileDependencies.rxAndroid
73 |
74 |
75 | //Room
76 | implementation mobileDependencies.roomRxJava
77 |
78 |
79 | //Testing
80 | testImplementation mobileUiTestDependencies.kotlinJUnit
81 | // Instrumentation test dependencies
82 | androidTestImplementation mobileUiTestDependencies.junit
83 | androidTestImplementation mobileUiTestDependencies.mockitoAndroid
84 | androidTestImplementation(mobileUiTestDependencies.espressoCore) {
85 | exclude group: 'com.android.support', module: 'support-annotations'
86 | }
87 | androidTestImplementation(mobileUiTestDependencies.androidRunner) {
88 | exclude group: 'com.android.support', module: 'support-annotations'
89 | }
90 | androidTestImplementation(mobileUiTestDependencies.androidRules) {
91 | exclude group: 'com.android.support', module: 'support-annotations'
92 | }
93 | androidTestImplementation(mobileUiTestDependencies.espressoContrib) {
94 | exclude module: 'appcompat'
95 | exclude module: 'appcompat-v7'
96 | exclude module: 'support-v4'
97 | exclude module: 'support-v13'
98 | exclude module: 'support-annotations'
99 | exclude module: 'recyclerview-v7'
100 | exclude module: 'design'
101 | }
102 |
103 | kaptTest mobileDependencies.daggerCompiler
104 | kaptAndroidTest mobileDependencies.daggerCompiler
105 | implementation 'com.android.support:cardview-v7:28.0.0'
106 | }
107 |
108 | apply plugin: 'kotlin-android-extensions'
--------------------------------------------------------------------------------
/mobile/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/RunAllUITests.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile
2 |
3 | import com.tayfuncesur.mobile.ui.detail.DetailActivityTest
4 | import com.tayfuncesur.mobile.ui.main.MainActivityTest
5 | import org.junit.runners.Suite
6 | import org.junit.runner.RunWith
7 |
8 |
9 | @RunWith(Suite::class)
10 | @Suite.SuiteClasses(MainActivityTest::class, DetailActivityTest::class)
11 | class RunAllUITests
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/di/TestAppComponent.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di
2 |
3 | import android.app.Application
4 | import com.tayfuncesur.domain.repository.ProjectsRepository
5 | import com.tayfuncesur.mobile.test.TestApp
6 | import com.tayfuncesur.mobile.di.module.*
7 | import dagger.BindsInstance
8 | import dagger.Component
9 | import dagger.android.AndroidInjectionModule
10 | import javax.inject.Singleton
11 |
12 | @Singleton
13 | @Component(
14 | modules = [AndroidInjectionModule::class,
15 | TestApplicationModule::class,
16 | TestCacheModule::class,
17 | TestDataModule::class,
18 | PresentationModule::class,
19 | UIModule::class,
20 | TestRemoteModule::class
21 | ]
22 | )
23 | interface TestAppComponent {
24 |
25 |
26 | fun projectsRepository(): ProjectsRepository
27 |
28 | @Component.Builder
29 | interface Builder {
30 |
31 | @BindsInstance
32 | fun application(application: Application): TestAppComponent.Builder
33 |
34 | fun build(): TestAppComponent
35 | }
36 |
37 | fun inject(app: TestApp)
38 |
39 | }
40 |
41 |
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/di/module/TestApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import dagger.Binds
6 | import dagger.Module
7 |
8 | @Module
9 | abstract class TestApplicationModule {
10 |
11 | @Binds
12 | abstract fun bindContext(application: Application): Context
13 | }
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/di/module/TestCacheModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import android.app.Application
4 | import com.tayfuncesur.cache.ProjectsCacheImpl
5 | import com.tayfuncesur.cache.db.ProjectsDatabase
6 | import com.tayfuncesur.cache.mapper.CachedProjectMapper
7 | import com.tayfuncesur.data.repository.ProjectsCache
8 | import dagger.Binds
9 | import dagger.Module
10 | import dagger.Provides
11 | import org.mockito.Mockito
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | object TestCacheModule {
16 |
17 | @Provides
18 | @Singleton
19 | @JvmStatic
20 | fun providesDatabase(application: Application): ProjectsDatabase {
21 | return ProjectsDatabase.getInstance(application)
22 | }
23 |
24 | @Provides
25 | @Singleton
26 | @JvmStatic
27 | fun providesCachedProjectMapper(): CachedProjectMapper {
28 | return Mockito.mock(CachedProjectMapper::class.java)
29 | }
30 |
31 | @Provides
32 | @Singleton
33 | @JvmStatic
34 | fun providesProjectsCache(): ProjectsCache {
35 | return Mockito.mock(ProjectsCache::class.java)
36 | }
37 |
38 | }
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/di/module/TestDataModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import com.tayfuncesur.data.mapper.ProjectMapper
4 | import com.tayfuncesur.domain.repository.ProjectsRepository
5 | import dagger.Module
6 | import dagger.Provides
7 | import org.mockito.Mockito
8 | import javax.inject.Singleton
9 |
10 | @Module
11 | object TestDataModule {
12 |
13 | @Provides
14 | @Singleton
15 | @JvmStatic
16 | fun providesProjectMapper(): ProjectMapper {
17 | return Mockito.mock(ProjectMapper::class.java)
18 | }
19 |
20 | @Provides
21 | @Singleton
22 | @JvmStatic
23 | fun providesDataRepository(): ProjectsRepository {
24 | return Mockito.mock(ProjectsRepository::class.java)
25 | }
26 | }
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/di/module/TestRemoteModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import com.tayfuncesur.data.repository.ProjectsRemote
4 | import com.tayfuncesur.remote.mapper.ProjectResponseMapper
5 | import com.tayfuncesur.remote.service.GithubService
6 | import dagger.Module
7 | import dagger.Provides
8 | import org.mockito.Mockito
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | object TestRemoteModule {
13 |
14 | @Provides
15 | @Singleton
16 | @JvmStatic
17 | fun providesGithubService(): GithubService {
18 | return Mockito.mock(GithubService::class.java)
19 | }
20 |
21 | @Provides
22 | @Singleton
23 | @JvmStatic
24 | fun providesProjectResponseMapper(): ProjectResponseMapper {
25 | return Mockito.mock(ProjectResponseMapper::class.java)
26 | }
27 |
28 |
29 | @Provides
30 | @Singleton
31 | @JvmStatic
32 | fun providesProjectsRemote(): ProjectsRemote {
33 | return Mockito.mock(ProjectsRemote::class.java)
34 | }
35 |
36 |
37 | }
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/test/Helper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.test
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.support.test.espresso.matcher.BoundedMatcher
5 | import android.view.View
6 | import org.hamcrest.Description
7 | import org.hamcrest.Matcher
8 |
9 |
10 | fun withViewAtPosition(position: Int, itemMatcher: Matcher): Matcher {
11 | return object : BoundedMatcher(RecyclerView::class.java) {
12 | override fun describeTo(description: Description) {
13 | itemMatcher.describeTo(description)
14 | }
15 |
16 | override fun matchesSafely(recyclerView: RecyclerView): Boolean {
17 | val viewHolder = recyclerView.findViewHolderForAdapterPosition(position)
18 | return viewHolder != null && itemMatcher.matches(viewHolder.itemView)
19 | }
20 | }
21 | }
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/test/TestApp.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.test
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.support.test.InstrumentationRegistry
6 | import com.tayfuncesur.mobile.di.DaggerTestAppComponent
7 | import com.tayfuncesur.mobile.di.TestAppComponent
8 | import dagger.android.AndroidInjector
9 | import dagger.android.DispatchingAndroidInjector
10 | import dagger.android.HasActivityInjector
11 | import javax.inject.Inject
12 |
13 | class TestApp : Application(), HasActivityInjector {
14 |
15 | @Inject
16 | lateinit var injector: DispatchingAndroidInjector
17 |
18 | private lateinit var appComponent: TestAppComponent
19 |
20 |
21 | companion object {
22 | fun appComponent(): TestAppComponent {
23 | return (InstrumentationRegistry.getTargetContext().applicationContext as TestApp).appComponent
24 | }
25 | }
26 |
27 | override fun activityInjector(): AndroidInjector {
28 | return injector
29 | }
30 |
31 | override fun onCreate() {
32 | super.onCreate()
33 | appComponent = DaggerTestAppComponent.builder().application(this).build()
34 | appComponent.inject(this)
35 |
36 | }
37 |
38 |
39 | }
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/test/TestRunner.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.test
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.os.Bundle
6 | import android.support.test.runner.AndroidJUnitRunner
7 | import io.reactivex.plugins.RxJavaPlugins
8 | import io.reactivex.schedulers.Schedulers
9 |
10 | class TestRunner : AndroidJUnitRunner() {
11 |
12 | override fun onCreate(arguments: Bundle) {
13 | super.onCreate(arguments)
14 | RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
15 | }
16 |
17 | @Throws(InstantiationException::class, IllegalAccessException::class, ClassNotFoundException::class)
18 | override fun newApplication(cl: ClassLoader, className: String, context: Context): Application {
19 | return super.newApplication(cl, TestApp::class.java.name, context)
20 | }
21 | }
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/ui/data/MockData.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.ui.data
2 |
3 | import com.tayfuncesur.domain.model.Project
4 | import java.util.*
5 |
6 | object MockData {
7 |
8 | fun generateRandomProjectView(isBookmarked: Boolean = false): Project {
9 | return Project(
10 | UUID.randomUUID().toString(),
11 | UUID.randomUUID().toString(),
12 | Math.random().toInt(),
13 | isBookmarked
14 | )
15 | }
16 |
17 | }
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/ui/detail/DetailActivityTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.ui.detail
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import android.support.test.espresso.Espresso.onView
6 | import android.support.test.espresso.action.ViewActions.click
7 | import android.support.test.espresso.assertion.ViewAssertions.matches
8 | import android.support.test.espresso.matcher.ViewMatchers.withId
9 | import android.support.test.espresso.matcher.ViewMatchers.withText
10 | import android.support.test.rule.ActivityTestRule
11 | import android.support.test.runner.AndroidJUnit4
12 | import com.tayfuncesur.mobile.R
13 | import com.tayfuncesur.mobile.base.UIConstants.Selected_Item_Key
14 | import com.tayfuncesur.mobile.mapper.ProjectMapper
15 | import com.tayfuncesur.mobile.model.Project
16 | import com.tayfuncesur.mobile.test.TestApp
17 | import com.tayfuncesur.mobile.ui.data.MockData
18 | import com.tayfuncesur.presentation.mapper.ProjectViewMapper
19 | import io.reactivex.Completable
20 | import kotlinx.android.synthetic.main.main_detail.*
21 | import org.junit.Before
22 | import org.junit.Rule
23 | import org.junit.Test
24 | import org.junit.runner.RunWith
25 | import org.mockito.Mockito
26 | import org.mockito.Mockito.verify
27 |
28 | @RunWith(AndroidJUnit4::class)
29 | class DetailActivityTest {
30 |
31 | @Rule
32 | @JvmField
33 | val activityRule = ActivityTestRule(MainDetailActivity::class.java, false, false)
34 |
35 | @Test
36 | fun shouldStarCountDisplays() {
37 | val randomProject = launchWith(true)
38 | onView(withId(R.id.starCount)).check(matches(withText(randomProject.starCount.toString() + " Stars")))
39 | }
40 |
41 | @Test
42 | fun shouldBookmarkActionCallsCorrectAction() {
43 | val randomProject = launchWith(false)
44 | Mockito.`when`(TestApp.appComponent().projectsRepository().bookmarkProject(randomProject.id)).thenReturn(
45 | Completable.complete()
46 | )
47 | onView(withId(R.id.bookmarkProjectButton)).perform(click())
48 | verify(TestApp.appComponent().projectsRepository()).bookmarkProject(randomProject.id)
49 | }
50 |
51 |
52 | @Test
53 | fun shouldUnBookmarkActionCallsCorrectAction() {
54 | val randomProject = launchWith(true)
55 | Mockito.`when`(TestApp.appComponent().projectsRepository().unbookmarkProject(randomProject.id)).thenReturn(
56 | Completable.complete()
57 | )
58 | onView(withId(R.id.bookmarkProjectButton)).perform(click())
59 | verify(TestApp.appComponent().projectsRepository()).unbookmarkProject(randomProject.id)
60 | }
61 |
62 |
63 | private fun launchWith(isBookmarked: Boolean = true): Project {
64 | val randomProject = MockData.generateRandomProjectView(isBookmarked)
65 | val mapper = ProjectViewMapper()
66 | val project = mapper.mapToView(randomProject)
67 |
68 | val anotherMapper = ProjectMapper()
69 | val realProjec = anotherMapper.mapToView(project)
70 |
71 | val intent = Intent()
72 | val bundle = Bundle(1)
73 | bundle.putParcelable(Selected_Item_Key, realProjec)
74 | intent.putExtras(bundle)
75 |
76 | activityRule.launchActivity(intent)
77 | return realProjec
78 | }
79 | }
--------------------------------------------------------------------------------
/mobile/src/androidTest/java/com/tayfuncesur/mobile/ui/main/MainActivityTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.ui.main
2 |
3 | import android.support.test.espresso.Espresso.onView
4 | import android.support.test.espresso.assertion.ViewAssertions.matches
5 | import android.support.test.espresso.contrib.RecyclerViewActions
6 | import android.support.test.espresso.matcher.ViewMatchers.*
7 | import android.support.test.rule.ActivityTestRule
8 | import android.support.test.runner.AndroidJUnit4
9 | import com.tayfuncesur.mobile.R
10 | import com.tayfuncesur.mobile.test.TestApp
11 | import com.tayfuncesur.mobile.test.withViewAtPosition
12 | import com.tayfuncesur.mobile.ui.data.MockData
13 | import io.reactivex.Observable
14 | import org.hamcrest.Matchers.allOf
15 | import org.hamcrest.Matchers.not
16 | import org.junit.Rule
17 | import org.junit.Test
18 | import org.junit.runner.RunWith
19 | import org.mockito.Mockito
20 | import java.util.*
21 |
22 |
23 | @RunWith(AndroidJUnit4::class)
24 | class MainActivityTest {
25 |
26 | @get:Rule
27 | val activity = ActivityTestRule(MainActivity::class.java, false, false)
28 |
29 | @Test
30 | fun shouldActivityLaunchesSuccessfully() {
31 | Mockito.`when`(TestApp.appComponent().projectsRepository().getProjects()).thenReturn(
32 | Observable.just(listOf(MockData.generateRandomProjectView()))
33 | )
34 | Mockito.`when`(TestApp.appComponent().projectsRepository().getBookmarkedProjects()).thenReturn(
35 | Observable.just(listOf(UUID.randomUUID().toString()))
36 | )
37 | activity.launchActivity(null)
38 | }
39 |
40 | @Test
41 | fun shouldProjectsShow() {
42 | val projects = listOf(
43 | MockData.generateRandomProjectView(),
44 | MockData.generateRandomProjectView(),
45 | MockData.generateRandomProjectView(),
46 | MockData.generateRandomProjectView(),
47 | MockData.generateRandomProjectView()
48 | )
49 | Mockito.`when`(TestApp.appComponent().projectsRepository().getProjects()).thenReturn(
50 | Observable.just(projects)
51 | )
52 | Mockito.`when`(TestApp.appComponent().projectsRepository().getBookmarkedProjects()).thenReturn(
53 | Observable.just(listOf(UUID.randomUUID().toString()))
54 | )
55 | activity.launchActivity(null)
56 |
57 | projects.forEachIndexed { index, project ->
58 | onView(withId(R.id.mainRecycler)).perform(RecyclerViewActions.scrollToPosition(index))
59 |
60 | onView(withId(R.id.mainRecycler)).check(matches(hasDescendant(withText(project.fullName))))
61 | }
62 | }
63 |
64 | @Test
65 | fun shouldBookmarksShow() {
66 | val projects = listOf(MockData.generateRandomProjectView(true), MockData.generateRandomProjectView(false))
67 | Mockito.`when`(TestApp.appComponent().projectsRepository().getProjects()).thenReturn(
68 | Observable.just(projects)
69 | )
70 | Mockito.`when`(TestApp.appComponent().projectsRepository().getBookmarkedProjects()).thenReturn(
71 | Observable.just(listOf(projects[0].id))
72 | )
73 | activity.launchActivity(null)
74 |
75 | projects.forEachIndexed { index, project ->
76 | onView(withId(R.id.mainRecycler)).perform(RecyclerViewActions.scrollToPosition(index))
77 |
78 | onView(withId(R.id.mainRecycler))
79 | .check(
80 | matches(
81 | withViewAtPosition(
82 | index,
83 | hasDescendant(
84 | allOf(
85 | withId(R.id.favImage),
86 | if (project.isBookmarked) isDisplayed() else not(isDisplayed())
87 | )
88 | )
89 | )
90 | )
91 | )
92 | }
93 | }
94 | }
--------------------------------------------------------------------------------
/mobile/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/mobile/src/main/assets/anims/error.json:
--------------------------------------------------------------------------------
1 | {"v":"5.2.1","fr":60,"ip":0,"op":361,"w":500,"h":500,"nm":"Artboard 2","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"?","sr":1,"ks":{"o":{"a":0,"k":80,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[211,193,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[-0.54,0.95],[-1.35,1.22],[-0.36,0.54],[0,0.7],[0.61,0.59],[1,0],[0.67,-0.64],[0.29,-0.95],[0,0],[-1.47,1.26],[-2.21,0],[-1.29,-0.68],[-0.72,-1.18],[0,-1.43],[0.58,-1],[1.13,-1.11],[0.33,-0.63],[0,-0.95],[0,0],[0,0]],"o":[[0,-1.3],[0.54,-0.95],[0.79,-0.73],[0.35,-0.54],[0,-0.84],[-0.61,-0.58],[-1.06,0],[-0.67,0.63],[0,0],[0.5,-1.73],[1.46,-1.26],[1.66,0],[1.29,0.67],[0.73,1.18],[0,1.41],[-0.58,1],[-0.9,0.87],[-0.33,0.62],[0,0],[0,0],[0,0]],"v":[[6.01,18.41],[6.82,15.04],[9.65,11.79],[11.37,9.88],[11.9,8.01],[10.99,5.87],[8.58,5],[5.99,5.96],[4.55,8.33],[0,6.38],[2.95,1.89],[8.46,0],[12.89,1.02],[15.91,3.8],[17,7.72],[16.13,11.34],[13.56,14.51],[11.72,16.75],[11.23,19.11],[11.23,19.76],[6.01,19.76]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0.95,0],[0.67,0.69],[0,0.97],[-0.67,0.67],[-0.95,0],[-0.66,-0.68],[0,-0.98],[0.66,-0.7]],"o":[[-0.95,0],[-0.67,-0.7],[0,-0.98],[0.67,-0.68],[0.95,0],[0.66,0.67],[0,0.97],[-0.66,0.69]],"v":[[3.44,7.03],[1.01,6],[0,3.5],[1.01,1.02],[3.44,0],[5.85,1.02],[6.84,3.5],[5.85,6]],"c":true},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[5.14,22.97],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":1,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933332979679,0.933332979679,0.941175997257,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"?","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"ic_search","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"s":true,"x":{"a":1,"k":[{"i":{"x":[0.631],"y":[0.996]},"o":{"x":[0.289],"y":[-0.007]},"n":["0p631_0p996_0p289_-0p007"],"t":0,"s":[100],"e":[172]},{"i":{"x":[0.654],"y":[1.022]},"o":{"x":[0.437],"y":[-0.003]},"n":["0p654_1p022_0p437_-0p003"],"t":49,"s":[172],"e":[50]},{"i":{"x":[0.89],"y":[0.588]},"o":{"x":[0.24],"y":[0.043]},"n":["0p89_0p588_0p24_0p043"],"t":103,"s":[50],"e":[110.505]},{"i":{"x":[0.563],"y":[-5.665]},"o":{"x":[0.208],"y":[-5.168]},"n":["0p563_-5p665_0p208_-5p168"],"t":177,"s":[110.505],"e":[100.029]},{"i":{"x":[0.661],"y":[61.353]},"o":{"x":[0.199],"y":[1213.655]},"n":["0p661_61p353_0p199_1213p655"],"t":262,"s":[100.029],"e":[100]},{"t":357}],"ix":3},"y":{"a":1,"k":[{"i":{"x":[0.744],"y":[1.016]},"o":{"x":[0.349],"y":[-0.014]},"n":["0p744_1p016_0p349_-0p014"],"t":0,"s":[91],"e":[49]},{"i":{"x":[0.669],"y":[0.846]},"o":{"x":[0.393],"y":[0.013]},"n":["0p669_0p846_0p393_0p013"],"t":49,"s":[49],"e":[135]},{"i":{"x":[0.744],"y":[1.144]},"o":{"x":[0.146],"y":[0.624]},"n":["0p744_1p144_0p146_0p624"],"t":103,"s":[135],"e":[147.777]},{"i":{"x":[0.351],"y":[0.993]},"o":{"x":[0.274],"y":[0.019]},"n":["0p351_0p993_0p274_0p019"],"t":177,"s":[147.777],"e":[25.549]},{"i":{"x":[0.661],"y":[0.973]},"o":{"x":[0.688],"y":[-0.015]},"n":["0p661_0p973_0p688_-0p015"],"t":262,"s":[25.549],"e":[91]},{"t":357}],"ix":4}},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,19.3],[-19.3,0],[0,-19.3],[19.3,0]],"o":[[-19.3,0],[0,-19.3],[19.3,0],[0,19.3],[0,0]],"v":[[35,70],[0,35],[35,0],[70,35],[35,70]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[12,12],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":1,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0],[0,10.78],[25.77,0],[0,-25.78],[-25.78,0],[-7.9,6.14],[0,0],[-1.5,0],[-1.14,1.14],[2.28,2.28]],"o":[[0,0],[6.14,-7.9],[0,-25.78],[-25.78,0],[0,25.77],[10.78,0],[0,0],[1.14,1.14],[1.49,0],[2.28,-2.28],[0,0]],"v":[[103.29,95.04],[83.5,75.25],[93.33,46.67],[46.67,0],[0,46.67],[46.67,93.33],[75.25,83.5],[95.04,103.29],[99.17,105],[103.29,103.29],[103.29,95.04]],"c":false},"ix":2},"nm":"Path 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Path","np":1,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false},{"ty":"mm","mm":1,"nm":"Merge Paths 1","mn":"ADBE Vector Filter - Merge","hd":false},{"ty":"fl","c":{"a":0,"k":[0.933332979679,0.933332979679,0.941175997257,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[300,300],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"ic_search","np":4,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":3600,"st":0,"bm":0}],"markers":[]}
--------------------------------------------------------------------------------
/mobile/src/main/assets/anims/loading.json:
--------------------------------------------------------------------------------
1 | {"v":"5.1.16","fr":29.9700012207031,"ip":0,"op":31.0000012626559,"w":320,"h":320,"nm":"ball stretch","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"right","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[191.25,160,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0"],"t":6,"s":[25.258,25.258],"e":[25.258,25.258]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0"],"t":12,"s":[25.258,25.258],"e":[25.258,70.258]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0"],"t":18,"s":[25.258,70.258],"e":[25.258,25.258]},{"t":24.00000097754}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.086274995991,0.580391977348,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":12,"s":[0.829825008617,0.829825008617,0.829825008617,1],"e":[0.152941182256,0.396078437567,0.811764717102,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":18,"s":[0.152941182256,0.396078437567,0.811764717102,1],"e":[0.829825008617,0.829825008617,0.829825008617,1]},{"t":24.00000097754}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.016,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.0000048877,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"middle","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[160,160,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0"],"t":6,"s":[25.258,25.258],"e":[25.258,70.258]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0"],"t":12,"s":[25.258,70.258],"e":[25.258,25.258]},{"t":18.000000733155}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.086274995991,0.580391977348,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.829825008617,0.829825008617,0.829825008617,1],"e":[0.901960790157,0.007843137719,0.470588237047,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":12,"s":[0.901960790157,0.007843137719,0.470588237047,1],"e":[0.829825008617,0.829825008617,0.829825008617,1]},{"t":18.000000733155}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.016,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"fl","c":{"a":0,"k":[0.901960790157,0.007843137719,0.470588237047,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":1,"cix":2,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.0000048877,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"left","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[129,160,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":1,"k":[{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0"],"t":0,"s":[25.258,25.258],"e":[25.258,70.258]},{"i":{"x":[0.667,0.667],"y":[1,1]},"o":{"x":[0.333,0.333],"y":[0,0]},"n":["0p667_1_0p333_0","0p667_1_0p333_0"],"t":6,"s":[25.258,70.258],"e":[25.258,25.258]},{"t":12.00000048877}],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":20,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"st","c":{"a":0,"k":[0.086274995991,0.580391977348,0,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":0,"ix":5},"lc":1,"lj":1,"ml":4,"nm":"Stroke 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":0,"s":[0.829825008617,0.829825008617,0.829825008617,1],"e":[0.050980392843,0.219607844949,0.501960813999,1]},{"i":{"x":[0.667],"y":[1]},"o":{"x":[0.333],"y":[0]},"n":["0p667_1_0p333_0"],"t":6,"s":[0.050980392843,0.219607844949,0.501960813999,1],"e":[0.829825008617,0.829825008617,0.829825008617,1]},{"t":12.00000048877}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0.016,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Rectangle 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":120.0000048877,"st":0,"bm":0}],"markers":[]}
--------------------------------------------------------------------------------
/mobile/src/main/assets/anims/no_connection.json:
--------------------------------------------------------------------------------
1 | {"v":"5.1.1","fr":60,"ip":0,"op":360,"w":400,"h":400,"nm":"Composição 1","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":4,"nm":"Line","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[217,184.5,0],"ix":2},"a":{"a":0,"k":[127,158.5,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[253.5,0.5],[0.5,316.5]],"c":false},"ix":2},"nm":"Caminho 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"tm","s":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":190,"s":[0],"e":[100]},{"t":210}],"ix":1},"e":{"a":0,"k":0,"ix":2},"o":{"a":0,"k":0,"ix":3},"m":1,"ix":2,"nm":"Aparar caminhos 1","mn":"ADBE Vector Filter - Trim","hd":false},{"ty":"st","c":{"a":0,"k":[0.912683844566,0.301613509655,0.301613509655,1],"ix":3},"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":20,"ix":5},"lc":2,"lj":2,"nm":"Traçado 1","mn":"ADBE Vector Graphic - Stroke","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":3,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":360,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":4,"nm":"XMLID_359_","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[199.9,105.883,0],"ix":2},"a":{"a":0,"k":[194.9,58.173,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[8.75,8.77],[71.15,0],[50.3,-50.32],[-8.76,-8.76],[-5.74,0],[-4.37,4.38],[-59.15,0],[-41.82,-41.84],[-8.77,8.75]],"o":[[-50.3,-50.32],[-71.16,0],[-8.76,8.77],[4.39,4.38],[5.75,0],[41.82,-41.84],[59.15,0],[8.76,8.77],[8.77,-8.77]],"v":[[383.24,78.02],[194.91,0],[6.57,78.02],[6.57,109.77],[22.45,116.34],[38.32,109.77],[194.91,44.9],[351.48,109.77],[383.22,109.78]],"c":true},"ix":2},"nm":"Caminho 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":90,"s":[0.870588243008,0.870588243008,0.870588243008,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":120,"s":[0,0,0,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":150,"s":[0,0,0,1],"e":[0.870588243008,0.870588243008,0.870588243008,1]},{"t":160}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Preenchimento 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":368,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"XMLID_360_","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[199.904,169.12,0],"ix":2},"a":{"a":0,"k":[142.514,47.32,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[51.36,0],[36.31,-36.31],[-8.76,-8.76],[-8.76,8.77],[-39.36,0],[-27.82,-27.83],[-5.72,0],[-4.38,4.39],[8.77,8.77]],"o":[[-51.36,0],[-8.76,8.77],[8.78,8.77],[27.83,-27.83],[39.35,0],[4.39,4.39],[5.76,0],[8.77,-8.76],[-36.31,-36.31]],"v":[[142.52,0],[6.57,56.31],[6.57,88.06],[38.32,88.06],[142.52,44.9],[246.7,88.06],[262.57,94.64],[278.45,88.06],[278.45,56.31]],"c":true},"ix":2},"nm":"Caminho 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":60,"s":[0.870588243008,0.870588243008,0.870588243008,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":90,"s":[0,0,0,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":150,"s":[0,0,0,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":160,"s":[0,0,0,1],"e":[0.870588243008,0.870588243008,0.870588243008,1]},{"t":170}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Preenchimento 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":368,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"XMLID_361_","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[199.914,232.215,0],"ix":2},"a":{"a":0,"k":[90.244,36.495,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[31.6,0],[22.33,-22.35],[-8.77,-8.76],[-8.78,8.77],[-19.62,0],[-13.86,-13.87],[-5.72,0],[-4.38,4.39],[8.78,8.77]],"o":[[-31.6,0],[-8.77,8.77],[8.77,8.77],[13.85,-13.87],[19.62,0],[4.39,4.39],[5.76,0],[8.78,-8.76],[-22.33,-22.35]],"v":[[90.24,0],[6.58,34.66],[6.58,66.41],[38.33,66.41],[90.24,44.9],[142.16,66.41],[158.03,72.99],[173.9,66.41],[173.9,34.66]],"c":true},"ix":2},"nm":"Caminho 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":30,"s":[0.870588243008,0.870588243008,0.870588243008,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":60,"s":[0,0,0,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":150,"s":[0,0,0,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":170,"s":[0,0,0,1],"e":[0.870588243008,0.870588243008,0.870588243008,1]},{"t":180}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Preenchimento 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":368,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":4,"nm":"XMLID_362_","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[199.905,314.064,0],"ix":2},"a":{"a":0,"k":[38.355,38.354,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"shapes":[{"ty":"gr","it":[{"ind":0,"ty":"sh","ix":1,"ks":{"a":0,"k":{"i":[[14.98,-14.97],[-14.97,-14.97],[-14.98,14.98],[14.97,14.98]],"o":[[-14.97,14.98],[14.98,14.98],[14.97,-14.97],[-14.98,-14.97]],"v":[[11.23,11.23],[11.23,65.47],[65.48,65.47],[65.48,11.23]],"c":true},"ix":2},"nm":"Caminho 1","mn":"ADBE Vector Shape - Group","hd":false},{"ty":"fl","c":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":0,"s":[0.870588243008,0.870588243008,0.870588243008,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":30,"s":[0,0,0,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":150,"s":[0,0,0,1],"e":[0,0,0,1]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"n":["0p833_0p833_0p167_0p167"],"t":180,"s":[0,0,0,1],"e":[0.870588243008,0.870588243008,0.870588243008,1]},{"t":190}],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"nm":"Preenchimento 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transformar"}],"nm":"Grupo 1","np":2,"cix":2,"ix":1,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":368,"st":0,"bm":0}],"markers":[]}
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/App.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import com.tayfuncesur.mobile.di.DaggerAppComponent
6 | import dagger.android.AndroidInjector
7 | import dagger.android.DispatchingAndroidInjector
8 | import dagger.android.HasActivityInjector
9 | import javax.inject.Inject
10 |
11 | class App : Application(), HasActivityInjector {
12 |
13 | @Inject
14 | lateinit var androidInjector: DispatchingAndroidInjector
15 |
16 | override fun activityInjector(): AndroidInjector {
17 | return androidInjector
18 | }
19 |
20 |
21 | override fun onCreate() {
22 | super.onCreate()
23 |
24 | DaggerAppComponent
25 | .builder()
26 | .application(this)
27 | .build()
28 | .inject(this)
29 |
30 | }
31 |
32 |
33 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/UIThread.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile
2 |
3 | import com.tayfuncesur.domain.executor.PostExecutionThread
4 | import io.reactivex.Scheduler
5 | import io.reactivex.android.schedulers.AndroidSchedulers
6 | import javax.inject.Inject
7 |
8 | class UIThread @Inject constructor() : PostExecutionThread {
9 | override val scheduler: Scheduler
10 | get() = AndroidSchedulers.mainThread()
11 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/base/ActivityExtensions.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.base
2 |
3 | import android.app.Activity
4 | import android.content.Context
5 | import android.content.Intent
6 | import android.os.Build
7 | import android.os.Bundle
8 |
9 | /**
10 | * Extensions for simpler launching of Activities
11 | */
12 |
13 | inline fun Activity.launchActivity(
14 | requestCode: Int = -1,
15 | options: Bundle? = null,
16 | noinline init: Intent.() -> Unit = {}
17 | ) {
18 |
19 | val intent = newIntent(this)
20 | intent.init()
21 | if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
22 | startActivityForResult(intent, requestCode, options)
23 | } else {
24 | startActivityForResult(intent, requestCode)
25 | }
26 | }
27 |
28 | inline fun Context.launchActivity(
29 | options: Bundle? = null,
30 | noinline init: Intent.() -> Unit = {}
31 | ) {
32 | val intent = newIntent(this)
33 | intent.init()
34 | if (options != null)
35 | intent.putExtras(options)
36 | startActivity(intent)
37 | }
38 |
39 | inline fun newIntent(context: Context): Intent =
40 | Intent(context, T::class.java)
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/base/BaseDaggerActivity.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.base
2 |
3 | import android.os.Bundle
4 | import android.support.v7.app.AppCompatActivity
5 | import com.github.pwittchen.reactivenetwork.library.rx2.ReactiveNetwork
6 | import com.tayfuncesur.mobile.R
7 | import com.tayfuncesur.mobile.ui.noConnection.NoConnectionFragment
8 | import dagger.android.AndroidInjection
9 | import io.reactivex.android.schedulers.AndroidSchedulers
10 | import io.reactivex.disposables.CompositeDisposable
11 | import io.reactivex.schedulers.Schedulers
12 |
13 | open class BaseDaggerActivity : AppCompatActivity() {
14 |
15 |
16 | private var compositeDisposable = CompositeDisposable()
17 |
18 | override fun onCreate(savedInstanceState: Bundle?) {
19 | super.onCreate(savedInstanceState)
20 | AndroidInjection.inject(this)
21 |
22 |
23 | compositeDisposable.add(
24 | ReactiveNetwork
25 | .observeInternetConnectivity()
26 | .subscribeOn(Schedulers.io())
27 | .observeOn(AndroidSchedulers.mainThread())
28 | .subscribe({ isConnectedToInternet ->
29 | val noConnectionFragment = NoConnectionFragment.newInstance()
30 | if (!isConnectedToInternet) {
31 | supportFragmentManager.beginTransaction().setCustomAnimations(
32 | android.R.anim.fade_in,
33 | android.R.anim.fade_out,
34 | android.R.anim.fade_in,
35 | android.R.anim.fade_out
36 | )
37 | .add(android.R.id.content, noConnectionFragment, getString(R.string.noConnectionStr))
38 | .addToBackStack(getString(R.string.noConnectionStr)).commit()
39 | } else {
40 | supportFragmentManager.popBackStack()
41 | supportFragmentManager.executePendingTransactions()
42 | }
43 | }, { })
44 | )
45 | }
46 |
47 | override fun onDestroy() {
48 | compositeDisposable.clear()
49 | super.onDestroy()
50 | }
51 |
52 |
53 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/base/UIConstants.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.base
2 |
3 | object UIConstants {
4 |
5 | const val Selected_Item_Key="SELECTED_ITEM"
6 |
7 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/di/AppComponent.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di
2 |
3 | import android.app.Application
4 | import com.tayfuncesur.mobile.App
5 | import com.tayfuncesur.mobile.di.module.*
6 | import dagger.BindsInstance
7 | import dagger.Component
8 | import dagger.android.AndroidInjectionModule
9 | import javax.inject.Singleton
10 |
11 | @Singleton
12 | @Component(
13 | modules = [AndroidInjectionModule::class,
14 | ApplicationModule::class,
15 | CacheModule::class,
16 | DataModule::class,
17 | PresentationModule::class,
18 | RemoteModule::class,
19 | UIModule::class]
20 | )
21 | interface AppComponent {
22 |
23 |
24 | @Component.Builder
25 | interface Builder {
26 |
27 | @BindsInstance
28 | fun application(application: Application): Builder
29 |
30 | fun build(): AppComponent
31 | }
32 |
33 | fun inject(app: App)
34 |
35 | }
36 |
37 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/di/ViewModelFactory.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di
2 |
3 | import android.arch.lifecycle.ViewModel
4 | import android.arch.lifecycle.ViewModelProvider
5 | import dagger.MapKey
6 | import javax.inject.Inject
7 | import javax.inject.Provider
8 | import javax.inject.Singleton
9 | import kotlin.reflect.KClass
10 |
11 | @Singleton
12 | open class ViewModelFactory : ViewModelProvider.Factory {
13 |
14 | private val creators: Map, Provider>
15 |
16 | @Inject constructor(creators: Map,
17 | @JvmSuppressWildcards Provider>) {
18 | this.creators = creators
19 | }
20 |
21 | override fun create(modelClass: Class): T {
22 | var creator: Provider? = creators[modelClass]
23 | if (creator == null) {
24 | for ((key, value) in creators) {
25 | if (modelClass.isAssignableFrom(key)) {
26 | creator = value
27 | break
28 | }
29 | }
30 | }
31 | if (creator == null) {
32 | throw IllegalStateException("Unknown model class: " + modelClass)
33 | }
34 | try {
35 | return creator.get() as T
36 | } catch (e: Exception) {
37 | throw RuntimeException(e)
38 | }
39 | }
40 |
41 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/di/module/ApplicationModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import dagger.Binds
6 | import dagger.Module
7 |
8 | @Module
9 | abstract class ApplicationModule {
10 |
11 | @Binds
12 | abstract fun bindContext(application: Application): Context
13 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/di/module/CacheModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import android.app.Application
4 | import com.tayfuncesur.cache.ProjectsCacheImpl
5 | import com.tayfuncesur.cache.db.ProjectsDatabase
6 | import com.tayfuncesur.cache.mapper.CachedProjectMapper
7 | import com.tayfuncesur.data.repository.ProjectsCache
8 | import dagger.Binds
9 | import dagger.Module
10 | import dagger.Provides
11 | import javax.inject.Singleton
12 |
13 | @Module
14 | abstract class CacheModule {
15 |
16 | @Module
17 | companion object {
18 | @Provides
19 | @Singleton
20 | @JvmStatic
21 | fun providesDatabase(application: Application): ProjectsDatabase {
22 | return ProjectsDatabase.getInstance(application)
23 | }
24 |
25 | @Provides
26 | @Singleton
27 | @JvmStatic
28 | fun providesCachedProjectMapper(): CachedProjectMapper {
29 | return CachedProjectMapper()
30 | }
31 | }
32 |
33 | @Binds
34 | abstract fun bindProjectsCache(projectsCacheImp: ProjectsCacheImpl): ProjectsCache
35 |
36 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/di/module/DataModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import com.tayfuncesur.data.ProjectsDataRepository
4 | import com.tayfuncesur.data.mapper.ProjectMapper
5 | import com.tayfuncesur.domain.repository.ProjectsRepository
6 | import dagger.Binds
7 | import dagger.Module
8 | import dagger.Provides
9 | import javax.inject.Singleton
10 |
11 | @Module
12 | abstract class DataModule {
13 |
14 | @Module
15 | companion object {
16 | @Provides
17 | @Singleton
18 | @JvmStatic
19 | fun providesProjectMapper(): ProjectMapper {
20 | return ProjectMapper()
21 | }
22 | }
23 |
24 | @Binds
25 | abstract fun bindDataRepository(dataRepository: ProjectsDataRepository): ProjectsRepository
26 |
27 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/di/module/PresentationModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import android.arch.lifecycle.ViewModel
4 | import android.arch.lifecycle.ViewModelProvider
5 | import com.tayfuncesur.mobile.di.ViewModelFactory
6 | import com.tayfuncesur.presentation.BookmarkViewModel
7 | import com.tayfuncesur.presentation.ProjectsViewModel
8 | import com.tayfuncesur.presentation.SplashScreenViewModel
9 | import dagger.Binds
10 | import dagger.MapKey
11 | import dagger.Module
12 | import dagger.multibindings.IntoMap
13 | import kotlin.reflect.KClass
14 |
15 | @Module
16 | abstract class PresentationModule {
17 |
18 | @Binds
19 | @IntoMap
20 | @ViewModelKey(SplashScreenViewModel::class)
21 | abstract fun bindSplashScreenViewModel(
22 | splashViewModel: SplashScreenViewModel
23 | ): ViewModel
24 |
25 | @Binds
26 | @IntoMap
27 | @ViewModelKey(ProjectsViewModel::class)
28 | abstract fun bindProjectsViewModel(
29 | projectsViewModel: ProjectsViewModel
30 | ): ViewModel
31 |
32 | @Binds
33 | @IntoMap
34 | @ViewModelKey(BookmarkViewModel::class)
35 | abstract fun bindBookmarkViewModel(
36 | bookmarkViewModel: BookmarkViewModel
37 | ): ViewModel
38 |
39 |
40 | @Binds
41 | abstract fun bindViewModelFactory(factory: ViewModelFactory): ViewModelProvider.Factory
42 | }
43 |
44 |
45 | @Target(AnnotationTarget.FUNCTION)
46 | @Retention(AnnotationRetention.RUNTIME)
47 | @MapKey
48 | annotation class ViewModelKey(val value: KClass)
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/di/module/RemoteModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import com.tayfuncesur.data.repository.ProjectsRemote
4 | import com.tayfuncesur.mobile.BuildConfig
5 | import com.tayfuncesur.remote.ProjectsRemoteImpl
6 | import com.tayfuncesur.remote.mapper.ProjectResponseMapper
7 | import com.tayfuncesur.remote.service.GithubService
8 | import com.tayfuncesur.remote.service.GithubServiceFactory
9 | import dagger.Binds
10 | import dagger.Module
11 | import dagger.Provides
12 | import javax.inject.Singleton
13 |
14 | @Module
15 | abstract class RemoteModule {
16 |
17 | @Module
18 | companion object {
19 | @Provides
20 | @Singleton
21 | @JvmStatic
22 | fun providesGithubService(): GithubService {
23 | return GithubServiceFactory.create(BuildConfig.DEBUG)
24 | }
25 |
26 | @Provides
27 | @Singleton
28 | @JvmStatic
29 | fun providesProjectResponseMapper(): ProjectResponseMapper {
30 | return ProjectResponseMapper()
31 | }
32 | }
33 |
34 |
35 | @Binds
36 | abstract fun bindProjectsRemote(projectsRemoteImpl: ProjectsRemoteImpl): ProjectsRemote
37 |
38 |
39 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/di/module/UIModule.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.di.module
2 |
3 | import com.tayfuncesur.domain.executor.PostExecutionThread
4 | import com.tayfuncesur.mobile.UIThread
5 | import com.tayfuncesur.mobile.mapper.ProjectMapper
6 | import com.tayfuncesur.mobile.ui.detail.MainDetailActivity
7 | import com.tayfuncesur.mobile.ui.main.MainActivity
8 | import com.tayfuncesur.mobile.ui.splash.SplashActivity
9 | import dagger.Binds
10 | import dagger.Module
11 | import dagger.Provides
12 | import dagger.android.ContributesAndroidInjector
13 | import javax.inject.Singleton
14 |
15 | @Module
16 | abstract class UIModule {
17 |
18 | @Module
19 | companion object{
20 | @Provides
21 | @Singleton
22 | @JvmStatic
23 | fun providesProjectViewMapper() : ProjectMapper{
24 | return ProjectMapper()
25 | }
26 | }
27 |
28 | @Binds
29 | abstract fun bindPostExecutionThread(uiThread: UIThread): PostExecutionThread
30 |
31 | @ContributesAndroidInjector
32 | abstract fun contributesSplashScreenActivity(): SplashActivity
33 |
34 | @ContributesAndroidInjector
35 | abstract fun contributesMainActivity(): MainActivity
36 |
37 | @ContributesAndroidInjector
38 | abstract fun contributesMainDetailActivity(): MainDetailActivity
39 |
40 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/mapper/ProjectMapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.mapper
2 |
3 | import com.tayfuncesur.mobile.model.Project
4 | import com.tayfuncesur.presentation.model.ProjectView
5 |
6 | class ProjectMapper : ViewMapper {
7 |
8 | override fun mapToView(view: ProjectView): Project {
9 | return Project(view.id, view.fullName, view.starCount, view.isBookmarked)
10 | }
11 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/mapper/ViewMapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.mapper
2 |
3 | interface ViewMapper {
4 |
5 | fun mapToView(view: V): P
6 |
7 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/model/Project.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.model
2 |
3 | import android.os.Parcelable
4 | import kotlinx.android.parcel.Parcelize
5 |
6 | @Parcelize
7 | class Project(
8 | val id: String, val fullName: String,
9 | val starCount: Int, var isBookmarked: Boolean
10 | ) : Parcelable
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/ui/detail/MainDetailActivity.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.ui.detail
2 |
3 | import android.animation.Animator
4 | import android.annotation.SuppressLint
5 | import android.arch.lifecycle.ViewModelProviders
6 | import android.os.Bundle
7 | import com.tayfuncesur.mobile.R
8 | import com.tayfuncesur.mobile.base.BaseDaggerActivity
9 | import com.tayfuncesur.mobile.base.UIConstants.Selected_Item_Key
10 | import com.tayfuncesur.mobile.di.ViewModelFactory
11 | import com.tayfuncesur.mobile.model.Project
12 | import com.tayfuncesur.presentation.BookmarkViewModel
13 | import com.thekhaeng.pushdownanim.PushDownAnim
14 | import kotlinx.android.synthetic.main.main_detail.*
15 | import javax.inject.Inject
16 |
17 | class MainDetailActivity : BaseDaggerActivity() {
18 |
19 | @Inject
20 | lateinit var viewModelFactory: ViewModelFactory
21 |
22 | lateinit var bookmarkViewModel: BookmarkViewModel
23 |
24 | @SuppressLint("SetTextI18n")
25 | override fun onCreate(savedInstanceState: Bundle?) {
26 | super.onCreate(savedInstanceState)
27 | setContentView(R.layout.main_detail)
28 | val selectedProject = intent?.extras?.getParcelable(Selected_Item_Key)
29 |
30 | toolbar_detail_title.text = selectedProject?.fullName
31 | starCount.text = "${selectedProject?.starCount} Stars"
32 |
33 | val defaultSpeed = bookmarkAnim.speed
34 |
35 | bookmarkViewModel = ViewModelProviders.of(this, viewModelFactory).get(BookmarkViewModel::class.java)
36 | if (selectedProject?.isBookmarked!!) {
37 | bookmarkAnim.speed = defaultSpeed * 3
38 | bookmarkAnim.playAnimation()
39 | bookmarkLabel.text = getString(R.string.unbookmark_project)
40 | }
41 |
42 |
43 | bookmarkAnim.addAnimatorListener(object : Animator.AnimatorListener {
44 | override fun onAnimationRepeat(animation: Animator?) {}
45 |
46 | override fun onAnimationEnd(animation: Animator?) {
47 | if (!selectedProject.isBookmarked)
48 | bookmarkLabel.text = getString(R.string.bookmark_project)
49 | }
50 |
51 | override fun onAnimationCancel(animation: Animator?) {}
52 |
53 | override fun onAnimationStart(animation: Animator?) {}
54 | })
55 |
56 | PushDownAnim.setPushDownAnimTo(bookmarkProjectButton).setScale(PushDownAnim.MODE_STATIC_DP, 5F).setOnClickListener {
57 | bookmarkAction(selectedProject, defaultSpeed)
58 | }
59 | }
60 |
61 |
62 | private fun bookmarkAction(selectedProject: Project, defaultSpeed: Float) {
63 | if (selectedProject.isBookmarked) {
64 | bookmarkAnim.speed = -2 * defaultSpeed
65 | bookmarkAnim.playAnimation()
66 | bookmarkViewModel.unbookmarkProject(selectedProject.id)
67 | } else {
68 | bookmarkViewModel.bookmarkProject(selectedProject.id)
69 | bookmarkAnim.speed = defaultSpeed
70 | bookmarkAnim.playAnimation()
71 | bookmarkLabel.text = getString(R.string.unbookmark_project)
72 | }
73 | selectedProject.isBookmarked = !selectedProject.isBookmarked
74 | }
75 |
76 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/ui/main/MainActivity.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.ui.main
2 |
3 | import android.arch.lifecycle.Observer
4 | import android.arch.lifecycle.ViewModelProviders
5 | import android.os.Bundle
6 | import android.view.View
7 | import com.tayfuncesur.mobile.R
8 | import com.tayfuncesur.mobile.base.BaseDaggerActivity
9 | import com.tayfuncesur.mobile.base.UIConstants.Selected_Item_Key
10 | import com.tayfuncesur.mobile.base.launchActivity
11 | import com.tayfuncesur.mobile.di.ViewModelFactory
12 | import com.tayfuncesur.mobile.mapper.ProjectMapper
13 | import com.tayfuncesur.mobile.model.Project
14 | import com.tayfuncesur.mobile.ui.detail.MainDetailActivity
15 | import com.tayfuncesur.mobile.ui.noConnection.NoConnectionFragment
16 | import com.tayfuncesur.presentation.ProjectsViewModel
17 | import com.tayfuncesur.presentation.state.Resource
18 | import com.thekhaeng.pushdownanim.PushDownAnim
19 | import kotlinx.android.synthetic.main.activity_main.*
20 | import javax.inject.Inject
21 |
22 | class MainActivity : BaseDaggerActivity() {
23 |
24 | @Inject
25 | lateinit var viewModelFactory: ViewModelFactory
26 | @Inject
27 | lateinit var mapper: ProjectMapper
28 |
29 | lateinit var projectsViewModel: ProjectsViewModel
30 |
31 | private var mainAdapter: MainAdapter? = null
32 |
33 | private var dataSource: List? = emptyList()
34 |
35 | override fun onCreate(savedInstanceState: Bundle?) {
36 | super.onCreate(savedInstanceState)
37 | setContentView(R.layout.activity_main)
38 | projectsViewModel = ViewModelProviders.of(this, viewModelFactory).get(ProjectsViewModel::class.java)
39 |
40 | projectsViewModel.getRemoteProjectsLiveData().observe(this, Observer { it ->
41 | if (it is Resource.Success) {
42 | dataSource = it.data?.map { mapper.mapToView(it) }
43 |
44 | mainAdapter = dataSource?.let { it1 ->
45 | MainAdapter(it1) {
46 | val bundle = Bundle(1)
47 | bundle.putParcelable(Selected_Item_Key, it)
48 | launchActivity(bundle)
49 | }
50 | }
51 | mainRecycler.adapter = mainAdapter
52 | }
53 | loadingLayout.visibility = if (it is Resource.Loading) View.VISIBLE else View.GONE
54 | errorLayout.visibility = if (it is Resource.Failure) View.VISIBLE else View.GONE
55 | })
56 |
57 | projectsViewModel.getBookmarkedProjectsLiveData().observe(this, Observer { it ->
58 | if (it is Resource.Success) {
59 | if (it.data?.isNotEmpty()!!) {
60 | dataSource?.map { project ->
61 | for (id in it.data!!) {
62 | if (project.id == id) {
63 | project.isBookmarked = true
64 | break
65 | } else {
66 | project.isBookmarked = false
67 | }
68 | }
69 | }
70 | } else {
71 | dataSource?.map {
72 | it.isBookmarked = false
73 | }
74 | }
75 | dataSource?.let { it1 -> mainAdapter?.updateData(it1) }
76 | }
77 | })
78 |
79 |
80 | PushDownAnim.setPushDownAnimTo(tryAgainLayout).setScale(PushDownAnim.MODE_STATIC_DP, 5F).setOnClickListener {
81 | projectsViewModel.loadProjects()
82 | }
83 | }
84 |
85 | override fun onBackPressed() {
86 | if (supportFragmentManager.backStackEntryCount > 0) {
87 | var founded = false
88 | for (item in supportFragmentManager.fragments) {
89 | if (item is NoConnectionFragment)
90 | founded = true
91 | }
92 | if (founded) finishAffinity()
93 | else super.onBackPressed()
94 | } else {
95 | super.onBackPressed()
96 | }
97 |
98 | }
99 |
100 | }
101 |
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/ui/main/MainAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.ui.main
2 |
3 | import android.annotation.SuppressLint
4 | import android.support.v7.widget.RecyclerView
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import com.tayfuncesur.mobile.R
9 | import com.tayfuncesur.mobile.model.Project
10 | import com.thekhaeng.pushdownanim.PushDownAnim
11 | import kotlinx.android.synthetic.main.single_row.view.*
12 |
13 | class MainAdapter(private var list: List, private val onClick: (Project) -> Unit) :
14 | RecyclerView.Adapter() {
15 |
16 | fun updateData(_list: List) {
17 | this@MainAdapter.list = _list
18 | notifyDataSetChanged()
19 | }
20 |
21 | override fun onCreateViewHolder(p0: ViewGroup, p1: Int): SingleRow {
22 | return SingleRow(LayoutInflater.from(p0.context).inflate(R.layout.single_row, p0, false))
23 | }
24 |
25 | override fun getItemCount(): Int {
26 | return list.size
27 | }
28 |
29 | override fun onBindViewHolder(p0: SingleRow, p1: Int) {
30 | p0.bind(p1)
31 | }
32 |
33 |
34 | inner class SingleRow(private val view: View) : RecyclerView.ViewHolder(view) {
35 |
36 | @SuppressLint("SetTextI18n")
37 | fun bind(pos: Int) {
38 | view.repoName.text = list[pos].fullName
39 | view.starCount.text = "${list[pos].starCount} Stars"
40 | view.favImage.visibility = if (list[pos].isBookmarked) View.VISIBLE else View.GONE
41 | PushDownAnim.setPushDownAnimTo(view.singleRowItem).setScale(PushDownAnim.MODE_STATIC_DP, 5F)
42 | .setOnClickListener {
43 | onClick.invoke(list[pos])
44 | }
45 | }
46 | }
47 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/ui/noConnection/NoConnectionFragment.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.ui.noConnection
2 |
3 | import android.os.Bundle
4 | import android.support.v4.app.Fragment
5 | import android.view.LayoutInflater
6 | import android.view.View
7 | import android.view.ViewGroup
8 | import com.tayfuncesur.mobile.R
9 |
10 | class NoConnectionFragment : Fragment() {
11 |
12 | companion object {
13 | fun newInstance() = NoConnectionFragment()
14 | }
15 |
16 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
17 | return inflater.inflate(R.layout.no_connection, container, false)
18 | }
19 |
20 | }
--------------------------------------------------------------------------------
/mobile/src/main/java/com/tayfuncesur/mobile/ui/splash/SplashActivity.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile.ui.splash
2 |
3 | import android.arch.lifecycle.Observer
4 | import android.arch.lifecycle.ViewModelProvider
5 | import android.arch.lifecycle.ViewModelProviders
6 | import android.content.Intent
7 | import android.os.Bundle
8 | import android.support.v7.app.AppCompatActivity
9 | import android.view.Window
10 | import android.view.WindowManager
11 | import com.tayfuncesur.mobile.R
12 | import com.tayfuncesur.mobile.base.BaseDaggerActivity
13 | import com.tayfuncesur.mobile.base.launchActivity
14 | import com.tayfuncesur.mobile.di.ViewModelFactory
15 | import com.tayfuncesur.mobile.ui.main.MainActivity
16 | import com.tayfuncesur.presentation.SplashScreenViewModel
17 | import dagger.android.AndroidInjection
18 | import javax.inject.Inject
19 |
20 | class SplashActivity : AppCompatActivity() {
21 |
22 | private lateinit var splashScreenViewModel: SplashScreenViewModel
23 |
24 | @Inject
25 | lateinit var viewModelFactory: ViewModelFactory
26 |
27 | override fun onCreate(savedInstanceState: Bundle?) {
28 | super.onCreate(savedInstanceState)
29 | AndroidInjection.inject(this)
30 | window.requestFeature(Window.FEATURE_NO_TITLE)
31 | window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, WindowManager.LayoutParams.FLAG_FULLSCREEN)
32 | setContentView(R.layout.splash_screen)
33 |
34 | splashScreenViewModel = ViewModelProviders.of(this, viewModelFactory).get(SplashScreenViewModel::class.java)
35 |
36 | splashScreenViewModel.isFinished.observe(this, Observer {
37 | launchActivity { }
38 | finish()
39 | })
40 | }
41 |
42 |
43 | }
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/appicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TayfunCesur/GithubProjectBrowser/8aa6705cf42651422ec677c5e48c9cab12e54b9f/mobile/src/main/res/drawable/appicon.png
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/appiconblack.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TayfunCesur/GithubProjectBrowser/8aa6705cf42651422ec677c5e48c9cab12e54b9f/mobile/src/main/res/drawable/appiconblack.png
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_bookmarked.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_favorite.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_favorite_outlined.xml:
--------------------------------------------------------------------------------
1 |
6 |
9 |
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/drawable/ic_star.xml:
--------------------------------------------------------------------------------
1 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
--------------------------------------------------------------------------------
/mobile/src/main/res/font/manropemedium.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TayfunCesur/GithubProjectBrowser/8aa6705cf42651422ec677c5e48c9cab12e54b9f/mobile/src/main/res/font/manropemedium.ttf
--------------------------------------------------------------------------------
/mobile/src/main/res/font/manroperegular.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/TayfunCesur/GithubProjectBrowser/8aa6705cf42651422ec677c5e48c9cab12e54b9f/mobile/src/main/res/font/manroperegular.ttf
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
21 |
22 |
31 |
32 |
33 |
34 |
35 |
44 |
45 |
46 |
57 |
58 |
67 |
68 |
79 |
80 |
81 |
82 |
95 |
96 |
105 |
106 |
119 |
120 |
136 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/main_detail.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
19 |
20 |
30 |
31 |
32 |
33 |
34 |
43 |
44 |
50 |
51 |
65 |
66 |
67 |
83 |
87 |
88 |
94 |
95 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/no_connection.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
23 |
24 |
36 |
37 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/single_row.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
19 |
20 |
22 |
23 |
24 |
34 |
35 |
36 |
43 |
44 |
45 |
46 |
47 |
50 |
51 |
57 |
69 |
70 |
71 |
72 |
73 |
74 |
77 |
--------------------------------------------------------------------------------
/mobile/src/main/res/layout/splash_screen.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
17 |
18 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #000000
4 | #000000
5 | #D81B60
6 | #222327
7 | #FFFFFF
8 | #f44336
9 | #1f2022
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16sp
4 | 18sp
5 | 14sp
6 | 150dp
7 | 12sp
8 | 8sp
9 | 4sp
10 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Tayfun Cesur Freeletics
3 | Square Projects
4 | Loading...
5 | Bookmark Project
6 | Unbookmark Project
7 | An error occurred while getting repos.
8 | You have no internet connection
9 | NoConnectionFragmetn
10 | Try Again
11 |
12 |
--------------------------------------------------------------------------------
/mobile/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
14 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/mobile/src/test/java/com/tayfuncesur/mobile/MockData.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile
2 |
3 | import com.tayfuncesur.presentation.model.ProjectView
4 | import java.util.*
5 |
6 | object MockData {
7 |
8 | fun generateRandomProjectView(isBookmarked: Boolean = false): ProjectView {
9 | return ProjectView(
10 | UUID.randomUUID().toString(),
11 | UUID.randomUUID().toString(),
12 | Math.random().toInt(),
13 | isBookmarked
14 | )
15 | }
16 |
17 |
18 | }
--------------------------------------------------------------------------------
/mobile/src/test/java/com/tayfuncesur/mobile/ProjectMapperTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.mobile
2 |
3 | import com.tayfuncesur.mobile.mapper.ProjectMapper
4 | import org.junit.Before
5 | import org.junit.Test
6 | import org.junit.runner.RunWith
7 | import org.junit.runners.JUnit4
8 | import kotlin.test.assertEquals
9 |
10 | @RunWith(JUnit4::class)
11 | class ProjectMapperTest {
12 |
13 | lateinit var projectMapper: ProjectMapper
14 |
15 | @Before
16 | fun setup(){
17 | projectMapper = ProjectMapper()
18 | }
19 |
20 | @Test
21 | fun mapToView() {
22 | val randomProjectView = MockData.generateRandomProjectView()
23 | val mapped = projectMapper.mapToView(randomProjectView)
24 | assertEquals(randomProjectView.id, mapped.id)
25 | assertEquals(randomProjectView.fullName, mapped.fullName)
26 | assertEquals(randomProjectView.starCount, mapped.starCount)
27 | assertEquals(randomProjectView.isBookmarked, mapped.isBookmarked)
28 | }
29 |
30 |
31 | }
--------------------------------------------------------------------------------
/presentation/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/presentation/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'com.android.library'
2 | apply plugin: 'kotlin-android'
3 | apply plugin: 'kotlin-kapt'
4 |
5 | android {
6 | compileSdkVersion 28
7 |
8 | defaultConfig {
9 | minSdkVersion 16
10 | targetSdkVersion 28
11 | }
12 |
13 | buildTypes {
14 | release {
15 | minifyEnabled false
16 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
17 | }
18 | }
19 |
20 | compileOptions {
21 | sourceCompatibility JavaVersion.VERSION_1_8
22 | targetCompatibility JavaVersion.VERSION_1_8
23 | }
24 |
25 |
26 | }
27 |
28 | dependencies {
29 | def presentationDependencies = rootProject.ext.presentationDependencies
30 | def presentationTestDependencies = rootProject.ext.presentationTestDependencies
31 |
32 | implementation project(':domain')
33 |
34 | implementation presentationDependencies.kotlin
35 | implementation presentationDependencies.javaxInject
36 | implementation presentationDependencies.rxKotlin
37 | implementation presentationDependencies.archRuntime
38 | implementation presentationDependencies.archExtensions
39 | kapt presentationDependencies.archCompiler
40 |
41 | testImplementation presentationTestDependencies.junit
42 | testImplementation presentationTestDependencies.mockito
43 | testImplementation presentationTestDependencies.robolectric
44 | testImplementation presentationTestDependencies.archTesting
45 | }
46 |
--------------------------------------------------------------------------------
/presentation/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # You can control the set of applied configuration files using the
3 | # proguardFiles setting in build.gradle.
4 | #
5 | # For more details, see
6 | # http://developer.android.com/guide/developing/tools/proguard.html
7 |
8 | # If your project uses WebView with JS, uncomment the following
9 | # and specify the fully qualified class name to the JavaScript interface
10 | # class:
11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12 | # public *;
13 | #}
14 |
15 | # Uncomment this to preserve the line number information for
16 | # debugging stack traces.
17 | #-keepattributes SourceFile,LineNumberTable
18 |
19 | # If you keep the line number information, uncomment this to
20 | # hide the original source file name.
21 | #-renamesourcefileattribute SourceFile
22 |
--------------------------------------------------------------------------------
/presentation/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
3 |
--------------------------------------------------------------------------------
/presentation/src/main/java/com/tayfuncesur/presentation/BookmarkViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation
2 |
3 | import android.arch.lifecycle.MutableLiveData
4 | import android.arch.lifecycle.ViewModel
5 | import com.tayfuncesur.domain.bookmark.BookmarkProject
6 | import com.tayfuncesur.domain.bookmark.UnbookmarkProject
7 | import com.tayfuncesur.domain.model.Project
8 | import com.tayfuncesur.presentation.mapper.ProjectViewMapper
9 | import com.tayfuncesur.presentation.state.Resource
10 | import io.reactivex.observers.DisposableCompletableObserver
11 | import javax.inject.Inject
12 |
13 | class BookmarkViewModel @Inject constructor(
14 | private val bookmarkProject: BookmarkProject,
15 | private val unbookmarkProject: UnbookmarkProject
16 | ) : ViewModel() {
17 |
18 | private val liveData: MutableLiveData> = MutableLiveData()
19 |
20 | fun getBookmarkLiveData(): MutableLiveData> {
21 | return liveData
22 | }
23 |
24 | fun bookmarkProject(projectId: String) {
25 | liveData.postValue(Resource.Loading())
26 | return bookmarkProject.execute(BookmarkSubscriber(), BookmarkProject.Params.projectId(projectId))
27 | }
28 |
29 | fun unbookmarkProject(projectId: String) {
30 | liveData.postValue(Resource.Loading())
31 | return unbookmarkProject.execute(UnbookmarkSubscriber(), UnbookmarkProject.Params.projectId(projectId))
32 | }
33 |
34 | inner class BookmarkSubscriber : DisposableCompletableObserver() {
35 | override fun onComplete() {
36 | liveData.postValue(Resource.Success(null))
37 | }
38 |
39 | override fun onError(e: Throwable) {
40 | liveData.postValue(Resource.Failure(e.localizedMessage))
41 | }
42 |
43 | }
44 |
45 | inner class UnbookmarkSubscriber : DisposableCompletableObserver() {
46 | override fun onComplete() {
47 | liveData.postValue(Resource.Success(null))
48 | }
49 |
50 | override fun onError(e: Throwable) {
51 | liveData.postValue(Resource.Failure(e.localizedMessage))
52 | }
53 |
54 | }
55 |
56 | override fun onCleared() {
57 | bookmarkProject.clear()
58 | unbookmarkProject.clear()
59 | super.onCleared()
60 | }
61 |
62 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/tayfuncesur/presentation/ProjectsViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation
2 |
3 | import android.arch.lifecycle.*
4 | import com.tayfuncesur.domain.bookmark.GetBookmarkedProjects
5 | import com.tayfuncesur.domain.getProjects.GetProjects
6 | import com.tayfuncesur.domain.model.Project
7 | import com.tayfuncesur.presentation.mapper.ProjectViewMapper
8 | import com.tayfuncesur.presentation.model.ProjectView
9 | import com.tayfuncesur.presentation.state.Resource
10 | import io.reactivex.observers.DisposableObserver
11 | import javax.inject.Inject
12 |
13 | class ProjectsViewModel @Inject constructor(
14 | private val getProjects: GetProjects,
15 | private val getBookmarkedProjects: GetBookmarkedProjects,
16 | private val mapper: ProjectViewMapper
17 | ) : ViewModel() {
18 |
19 | private val remoteProjectsLiveData: MutableLiveData>> = MutableLiveData()
20 | private val bookmarkedProjectsLiveData: MutableLiveData>> = MutableLiveData()
21 |
22 | init {
23 | loadProjects()
24 | }
25 |
26 | fun getRemoteProjectsLiveData(): MutableLiveData>> {
27 | return remoteProjectsLiveData
28 | }
29 |
30 | fun getBookmarkedProjectsLiveData(): MutableLiveData>> {
31 | return bookmarkedProjectsLiveData
32 | }
33 |
34 | fun loadProjects() {
35 | remoteProjectsLiveData.postValue(Resource.Loading())
36 | getProjects.execute(ProjectsSubscriber())
37 | }
38 |
39 | fun loadBookmarkedProjects() {
40 | getBookmarkedProjects.execute(BookmarkedProjectsSubscriber())
41 | }
42 |
43 | inner class ProjectsSubscriber : DisposableObserver>() {
44 |
45 | override fun onComplete() {
46 |
47 | }
48 |
49 | override fun onNext(t: List) {
50 | remoteProjectsLiveData.postValue(Resource.Success(t.map {
51 | mapper.mapToView(it)
52 | }))
53 | loadBookmarkedProjects()
54 | }
55 |
56 | override fun onError(e: Throwable) {
57 | remoteProjectsLiveData.postValue(Resource.Failure(e.localizedMessage))
58 | }
59 |
60 | }
61 |
62 | inner class BookmarkedProjectsSubscriber : DisposableObserver>() {
63 | override fun onComplete() {}
64 |
65 | override fun onNext(t: List) {
66 | bookmarkedProjectsLiveData.postValue(Resource.Success(t))
67 | }
68 |
69 | override fun onError(e: Throwable) {
70 | bookmarkedProjectsLiveData.postValue(Resource.Failure(e.localizedMessage))
71 | }
72 |
73 | }
74 |
75 | override fun onCleared() {
76 | getProjects.clear()
77 | super.onCleared()
78 | }
79 |
80 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/tayfuncesur/presentation/SplashScreenViewModel.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation
2 |
3 | import android.arch.lifecycle.MutableLiveData
4 | import android.arch.lifecycle.ViewModel
5 | import android.os.CountDownTimer
6 | import javax.inject.Inject
7 |
8 | class SplashScreenViewModel @Inject constructor() : ViewModel() {
9 |
10 | val isFinished = MutableLiveData()
11 |
12 | init {
13 | object : CountDownTimer(3000, 1000) {
14 |
15 | override fun onTick(millisUntilFinished: Long) {
16 |
17 | }
18 |
19 | override fun onFinish() {
20 | isFinished.postValue(true)
21 | }
22 |
23 | }.start()
24 | }
25 |
26 |
27 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/tayfuncesur/presentation/mapper/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation.mapper
2 |
3 | interface Mapper {
4 |
5 | fun mapToView(project : U) :V
6 |
7 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/tayfuncesur/presentation/mapper/ProjectViewMapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation.mapper
2 |
3 | import com.tayfuncesur.domain.model.Project
4 | import com.tayfuncesur.presentation.model.ProjectView
5 | import javax.inject.Inject
6 |
7 | class ProjectViewMapper @Inject constructor() : Mapper {
8 | override fun mapToView(project: Project): ProjectView {
9 | return ProjectView(project.id, project.fullName, project.starCount, project.isBookmarked)
10 | }
11 | }
--------------------------------------------------------------------------------
/presentation/src/main/java/com/tayfuncesur/presentation/model/ProjectView.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation.model
2 |
3 | class ProjectView(
4 | val id: String, val fullName: String,
5 | val starCount: Int, val isBookmarked: Boolean
6 | )
--------------------------------------------------------------------------------
/presentation/src/main/java/com/tayfuncesur/presentation/state/Resource.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation.state
2 |
3 | sealed class Resource {
4 | class Loading : Resource()
5 | data class Success(val data: T?) : Resource()
6 | data class Failure(val cause: String?) : Resource()
7 | }
--------------------------------------------------------------------------------
/presentation/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | presentation
3 |
4 |
--------------------------------------------------------------------------------
/presentation/src/test/java/com/tayfuncesur/presentation/MockData.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation
2 |
3 | import com.tayfuncesur.domain.model.Project
4 | import com.tayfuncesur.presentation.model.ProjectView
5 | import java.util.*
6 |
7 |
8 | object MockData {
9 |
10 | fun generateRandomProjectView(isBookmarked: Boolean = false): ProjectView {
11 | return ProjectView(
12 | UUID.randomUUID().toString(),
13 | UUID.randomUUID().toString(),
14 | Math.random().toInt(),
15 | isBookmarked
16 | )
17 | }
18 |
19 | fun generateRandomProject(): Project {
20 | return Project(
21 | UUID.randomUUID().toString(),
22 | UUID.randomUUID().toString(),
23 | Math.random().toInt(),
24 | false
25 | )
26 | }
27 |
28 | fun generateRandomProjectViewList(count: Int = 20): List {
29 | val list = mutableListOf()
30 | repeat(count) {
31 | list.add(generateRandomProjectView())
32 | }
33 | return list
34 | }
35 | fun generateRandomProjectList(count: Int = 20): List {
36 | val list = mutableListOf()
37 | repeat(count) {
38 | list.add(generateRandomProject())
39 | }
40 | return list
41 | }
42 |
43 | }
--------------------------------------------------------------------------------
/presentation/src/test/java/com/tayfuncesur/presentation/RunAll.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation
2 |
3 | import com.tayfuncesur.presentation.mapper.ProjectViewMapperTest
4 | import com.tayfuncesur.presentation.viewmodel.BookmarkViewModelTest
5 | import com.tayfuncesur.presentation.viewmodel.ProjectsViewModelTest
6 | import org.junit.runner.RunWith
7 | import org.junit.runners.Suite
8 |
9 |
10 | @RunWith(Suite::class)
11 | @Suite.SuiteClasses(ProjectViewMapperTest::class, ProjectsViewModelTest::class, BookmarkViewModelTest::class)
12 | class RunAll
--------------------------------------------------------------------------------
/presentation/src/test/java/com/tayfuncesur/presentation/mapper/ProjectViewMapperTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation.mapper
2 |
3 | import com.tayfuncesur.presentation.MockData
4 | import junit.framework.Assert.assertEquals
5 | import org.junit.Test
6 | import org.junit.runner.RunWith
7 | import org.junit.runners.JUnit4
8 |
9 | @RunWith(JUnit4::class)
10 | class ProjectViewMapperTest {
11 |
12 | private val mapper = ProjectViewMapper()
13 |
14 | @Test
15 | fun mapToViewTest() {
16 | val project = MockData.generateRandomProject()
17 | val projectView = mapper.mapToView(project)
18 |
19 | assertEquals(project.id, projectView.id)
20 | assertEquals(project.fullName, projectView.fullName)
21 | assertEquals(project.starCount, projectView.starCount)
22 | assertEquals(project.isBookmarked, projectView.isBookmarked)
23 | }
24 |
25 | }
--------------------------------------------------------------------------------
/presentation/src/test/java/com/tayfuncesur/presentation/viewmodel/BookmarkViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation.viewmodel
2 |
3 | import android.arch.core.executor.testing.InstantTaskExecutorRule
4 | import com.nhaarman.mockito_kotlin.*
5 | import com.tayfuncesur.domain.bookmark.BookmarkProject
6 | import com.tayfuncesur.domain.bookmark.UnbookmarkProject
7 | import com.tayfuncesur.presentation.BookmarkViewModel
8 | import org.junit.Before
9 | import org.junit.Rule
10 | import org.junit.Test
11 | import org.junit.runner.RunWith
12 | import org.junit.runners.JUnit4
13 | import java.util.*
14 |
15 | @RunWith(JUnit4::class)
16 | class BookmarkViewModelTest {
17 |
18 | @get:Rule
19 | var instantTaskExecutorRule = InstantTaskExecutorRule()
20 |
21 | private var bookmarkProject = mock()
22 |
23 | private var unbookmarkProject = mock()
24 |
25 | private lateinit var bookmarkViewModel: BookmarkViewModel
26 |
27 | @Before
28 | fun setup() {
29 | bookmarkViewModel = BookmarkViewModel(bookmarkProject, unbookmarkProject)
30 | }
31 |
32 | @Test
33 | fun shouldBookmarkProjectExecutes() {
34 | val id = UUID.randomUUID().toString()
35 | bookmarkViewModel.bookmarkProject(id)
36 | verify(bookmarkProject, times(1)).execute(any(), eq(BookmarkProject.Params(id)))
37 | }
38 |
39 | }
--------------------------------------------------------------------------------
/presentation/src/test/java/com/tayfuncesur/presentation/viewmodel/ProjectsViewModelTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.presentation.viewmodel
2 |
3 | import android.arch.core.executor.testing.InstantTaskExecutorRule
4 | import com.nhaarman.mockito_kotlin.*
5 | import com.tayfuncesur.domain.bookmark.GetBookmarkedProjects
6 | import com.tayfuncesur.domain.getProjects.GetProjects
7 | import com.tayfuncesur.domain.model.Project
8 | import com.tayfuncesur.presentation.ProjectsViewModel
9 | import com.tayfuncesur.presentation.mapper.ProjectViewMapper
10 | import io.reactivex.observers.DisposableObserver
11 | import org.junit.Before
12 | import org.junit.Rule
13 | import org.junit.Test
14 | import org.junit.runner.RunWith
15 | import org.junit.runners.JUnit4
16 | import org.mockito.Captor
17 | import com.tayfuncesur.presentation.MockData
18 | import com.tayfuncesur.presentation.state.Resource
19 | import java.util.*
20 |
21 | @RunWith(JUnit4::class)
22 | class ProjectsViewModelTest {
23 |
24 | @get:Rule
25 | var instantTaskExecutorRule = InstantTaskExecutorRule()
26 |
27 | private var getProjects = mock()
28 |
29 | private var getBookmarkedProjects = mock()
30 |
31 | private val mapper = mock()
32 |
33 | private lateinit var projectsViewModel: ProjectsViewModel
34 |
35 | @Captor
36 | val captor = argumentCaptor>>()
37 |
38 | @Captor
39 | val captorString = argumentCaptor>>()
40 |
41 | @Before
42 | fun setup() {
43 | projectsViewModel = ProjectsViewModel(getProjects, getBookmarkedProjects, mapper)
44 | }
45 |
46 | @Test
47 | fun shouldLoadProjectsExecutes() {
48 | projectsViewModel.loadProjects()
49 |
50 | verify(getProjects, times(2)).execute(any(), eq(null))
51 | }
52 |
53 | @Test
54 | fun shouldLoadProjectsReturnesData() {
55 | val projectList = MockData.generateRandomProjectList(2)
56 | val projectViewList = MockData.generateRandomProjectViewList(2)
57 | whenever(mapper.mapToView(projectList[0])).thenReturn(projectViewList[0])
58 | whenever(mapper.mapToView(projectList[1])).thenReturn(projectViewList[1])
59 |
60 | projectsViewModel.loadProjects()
61 |
62 | verify(getProjects, times(2)).execute(captor.capture(), eq(null))
63 | captor.firstValue.onNext(projectList)
64 | assert(projectsViewModel.getRemoteProjectsLiveData().value is Resource.Success<*>)
65 |
66 | }
67 |
68 | @Test
69 | fun shouldLoadBookmarkedProjectsExecutes() {
70 | projectsViewModel.loadBookmarkedProjects()
71 |
72 | verify(getProjects, times(1)).execute(any(), eq(null))
73 | }
74 |
75 | @Test
76 | fun shouldLoadBookmarkedProjectsReturnesData() {
77 | projectsViewModel.loadBookmarkedProjects()
78 |
79 | verify(getBookmarkedProjects, times(1)).execute(captorString.capture(), eq(null))
80 | captorString.firstValue.onNext(listOf(UUID.randomUUID().toString()))
81 | assert(projectsViewModel.getBookmarkedProjectsLiveData().value is Resource.Success<*>)
82 | }
83 | }
--------------------------------------------------------------------------------
/remote/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/remote/build.gradle:
--------------------------------------------------------------------------------
1 | apply plugin: 'kotlin'
2 |
3 | dependencies {
4 | def remoteDependencies = rootProject.ext.remoteDependencies
5 | def remoteTestDependencies = rootProject.ext.remoteTestDependencies
6 |
7 | implementation project(':data')
8 |
9 | implementation remoteDependencies.javaxAnnotation
10 | implementation remoteDependencies.javaxInject
11 | implementation remoteDependencies.rxKotlin
12 | implementation remoteDependencies.gson
13 | implementation remoteDependencies.okHttp
14 | implementation remoteDependencies.okHttpLogger
15 | implementation remoteDependencies.retrofit
16 | implementation remoteDependencies.retrofitConverter
17 | implementation remoteDependencies.retrofitAdapter
18 |
19 | testImplementation remoteTestDependencies.junit
20 | testImplementation remoteTestDependencies.kotlinJUnit
21 | testImplementation remoteTestDependencies.mockito
22 | }
23 |
24 | sourceCompatibility = "1.6"
25 | targetCompatibility = "1.6"
26 |
--------------------------------------------------------------------------------
/remote/src/main/java/com/tayfuncesur/remote/ProjectsRemoteImpl.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.remote
2 |
3 | import com.tayfuncesur.data.model.ProjectEntity
4 | import com.tayfuncesur.data.repository.ProjectsRemote
5 | import com.tayfuncesur.remote.mapper.ProjectResponseMapper
6 | import com.tayfuncesur.remote.service.GithubService
7 | import io.reactivex.Observable
8 | import javax.inject.Inject
9 |
10 | class ProjectsRemoteImpl @Inject constructor(
11 | private val service: GithubService,
12 | private val mapper: ProjectResponseMapper
13 | ) : ProjectsRemote {
14 | override fun getProjects(): Observable> {
15 | return service.getProjects().map {
16 | it.map {
17 | mapper.mapFromModel(it)
18 | }
19 | }
20 | }
21 |
22 | }
--------------------------------------------------------------------------------
/remote/src/main/java/com/tayfuncesur/remote/mapper/Mapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.remote.mapper
2 |
3 | interface Mapper {
4 |
5 | fun mapFromModel(model : M) :E
6 |
7 | }
--------------------------------------------------------------------------------
/remote/src/main/java/com/tayfuncesur/remote/mapper/ProjectResponseMapper.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.remote.mapper
2 |
3 | import com.tayfuncesur.data.model.ProjectEntity
4 | import com.tayfuncesur.remote.model.ProjectModel
5 |
6 | class ProjectResponseMapper : Mapper {
7 | override fun mapFromModel(model: ProjectModel): ProjectEntity {
8 | return ProjectEntity(model.id, model.fullName, model.starCount, false)
9 | }
10 | }
--------------------------------------------------------------------------------
/remote/src/main/java/com/tayfuncesur/remote/model/ProjectModel.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.remote.model
2 |
3 | import com.google.gson.annotations.SerializedName
4 |
5 | class ProjectModel(
6 | @SerializedName("id") val id: String,
7 | @SerializedName("full_name") val fullName: String,
8 | @SerializedName("stargazers_count") val starCount: Int
9 | )
--------------------------------------------------------------------------------
/remote/src/main/java/com/tayfuncesur/remote/service/GithubService.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.remote.service
2 |
3 | import com.tayfuncesur.remote.model.ProjectModel
4 | import io.reactivex.Observable
5 | import retrofit2.http.GET
6 |
7 | interface GithubService {
8 |
9 | @GET("/orgs/square/repos")
10 | fun getProjects(): Observable>
11 |
12 | }
--------------------------------------------------------------------------------
/remote/src/main/java/com/tayfuncesur/remote/service/GithubServiceFactory.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.remote.service
2 |
3 | import com.google.gson.Gson
4 | import okhttp3.OkHttpClient
5 | import okhttp3.logging.HttpLoggingInterceptor
6 | import retrofit2.Retrofit
7 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory
8 | import retrofit2.converter.gson.GsonConverterFactory
9 | import java.util.concurrent.TimeUnit
10 |
11 | object GithubServiceFactory {
12 |
13 | fun create(isDebugMode: Boolean): GithubService {
14 | val client = createOkHttp(isDebugMode)
15 |
16 | val retrofit = Retrofit.Builder()
17 | .baseUrl("https://api.github.com/")
18 | .client(client)
19 | .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
20 | .addConverterFactory(GsonConverterFactory.create(Gson()))
21 | .build()
22 | return retrofit.create(GithubService::class.java)
23 | }
24 |
25 | private fun createOkHttp(isDebugMode: Boolean): OkHttpClient {
26 | val logging = HttpLoggingInterceptor()
27 | if (isDebugMode) {
28 | logging.level =
29 | HttpLoggingInterceptor.Level.BODY
30 | } else logging.level = HttpLoggingInterceptor.Level.NONE
31 |
32 | return OkHttpClient.Builder()
33 | .addInterceptor(logging)
34 | .connectTimeout(60, TimeUnit.SECONDS)
35 | .readTimeout(60, TimeUnit.SECONDS)
36 | .build()
37 | }
38 |
39 |
40 | }
--------------------------------------------------------------------------------
/remote/src/test/java/com/tayfuncesur/data/mapper/ProjectModelMapperTest.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.mapper
2 |
3 | import com.tayfuncesur.data.remote.MockData
4 | import com.tayfuncesur.remote.mapper.ProjectResponseMapper
5 | import org.junit.Test
6 | import org.junit.runner.RunWith
7 | import org.mockito.junit.MockitoJUnitRunner
8 | import kotlin.test.assertEquals
9 |
10 | @RunWith(MockitoJUnitRunner::class)
11 | class ProjectModelMapperTest {
12 |
13 | private val projectResponseMapper = ProjectResponseMapper()
14 |
15 |
16 | @Test
17 | fun shouldMapFromModelRunsAsExpected() {
18 | val randomModel = MockData.generateRandomProjectModel()
19 | val entity = projectResponseMapper.mapFromModel(randomModel)
20 | assertEquals(randomModel.id, entity.id)
21 | assertEquals(randomModel.fullName, entity.fullName)
22 | assertEquals(randomModel.starCount, entity.starCount)
23 | }
24 |
25 |
26 | }
--------------------------------------------------------------------------------
/remote/src/test/java/com/tayfuncesur/data/remote/MockData.kt:
--------------------------------------------------------------------------------
1 | package com.tayfuncesur.data.remote
2 |
3 | import com.tayfuncesur.data.model.ProjectEntity
4 | import com.tayfuncesur.remote.model.ProjectModel
5 | import java.util.*
6 |
7 | object MockData {
8 |
9 | fun generateRandomProjectEntity(): ProjectEntity {
10 | return ProjectEntity(UUID.randomUUID().toString(), UUID.randomUUID().toString(), Math.random().toInt(), false)
11 | }
12 |
13 | fun generateRandomProjectEntityList(count: Int = 20): List {
14 | val list = mutableListOf()
15 | repeat(count) {
16 | list.add(generateRandomProjectEntity())
17 | }
18 | return list
19 | }
20 |
21 | fun generateRandomIdList(): List {
22 | val list = mutableListOf()
23 | repeat(20) {
24 | list.add(UUID.randomUUID().toString())
25 | }
26 | return list
27 | }
28 |
29 | fun generateRandomProjectModel(): ProjectModel {
30 | return ProjectModel(UUID.randomUUID().toString(), UUID.randomUUID().toString(), Math.random().toInt())
31 | }
32 |
33 | fun generateRandomProjectModelList(count: Int = 20): List {
34 | val list = mutableListOf()
35 | repeat(count) {
36 | list.add(generateRandomProjectModel())
37 | }
38 | return list
39 | }
40 |
41 |
42 | }
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':domain', ':data', ':remote', ':cache', ':presentation', ':mobile'
2 |
--------------------------------------------------------------------------------