├── app ├── .gitignore ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ ├── mipmap-hdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-mdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ ├── ic_launcher.png │ │ │ │ └── ic_launcher_round.png │ │ │ ├── mipmap-anydpi-v26 │ │ │ │ ├── ic_launcher.xml │ │ │ │ └── ic_launcher_round.xml │ │ │ ├── drawable-v24 │ │ │ │ └── ic_launcher_foreground.xml │ │ │ └── drawable │ │ │ │ └── ic_launcher_background.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── App.kt │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── io │ │ └── github │ │ └── kobakei │ │ └── katsuo │ │ └── ExampleInstrumentedTest.kt ├── proguard-rules.pro └── build.gradle ├── data ├── api │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── api │ │ │ │ └── ApiClient.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── api │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── api │ │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle ├── entity │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── entity │ │ │ │ ├── Articles.kt │ │ │ │ ├── Ads.kt │ │ │ │ ├── Author.kt │ │ │ │ └── Article.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── entity │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── entity │ │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle ├── database │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── database │ │ │ │ ├── ArticleDao.kt │ │ │ │ └── AppDatabase.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── database │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── database │ │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle └── repository │ ├── .gitignore │ ├── src │ ├── main │ │ ├── res │ │ │ └── values │ │ │ │ └── strings.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── repository │ │ │ ├── AdRepository.kt │ │ │ └── ArticleRepository.kt │ ├── androidTest │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── repository │ │ │ └── ExampleInstrumentedTest.kt │ └── test │ │ └── java │ │ └── io │ │ └── github │ │ └── kobakei │ │ └── katsuo │ │ └── repository │ │ └── ArticleRepositoryTest.kt │ ├── proguard-rules.pro │ └── build.gradle ├── ui ├── author │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ └── strings.xml │ │ │ │ └── layout │ │ │ │ │ ├── author_activity.xml │ │ │ │ │ ├── author_header.xml │ │ │ │ │ └── author_item.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── author │ │ │ │ ├── AuthorRouterImpl.kt │ │ │ │ ├── AuthorViewModel.kt │ │ │ │ ├── AuthorAdapter.kt │ │ │ │ └── AuthorActivity.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── author │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── author │ │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle ├── common │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ ├── strings.xml │ │ │ │ │ ├── colors.xml │ │ │ │ │ └── styles.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── presentation │ │ │ │ └── common │ │ │ │ ├── ImageViewExt.kt │ │ │ │ └── Extra.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── presentation │ │ │ │ └── common │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── presentation │ │ │ └── common │ │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle ├── detail │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ ├── values │ │ │ │ │ └── strings.xml │ │ │ │ ├── drawable │ │ │ │ │ └── ic_share_black_24dp.xml │ │ │ │ └── layout │ │ │ │ │ └── detail_activity.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── detail │ │ │ │ ├── DetailRouterImpl.kt │ │ │ │ ├── DetailViewModel.kt │ │ │ │ └── DetailActivity.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── detail │ │ │ │ ├── ExampleUnitTest.kt │ │ │ │ └── DetailViewModelTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── detail │ │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle ├── router │ ├── .gitignore │ ├── src │ │ ├── main │ │ │ ├── res │ │ │ │ └── values │ │ │ │ │ └── strings.xml │ │ │ ├── AndroidManifest.xml │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── router │ │ │ │ └── Router.kt │ │ ├── test │ │ │ └── java │ │ │ │ └── io │ │ │ │ └── github │ │ │ │ └── kobakei │ │ │ │ └── katsuo │ │ │ │ └── router │ │ │ │ └── ExampleUnitTest.kt │ │ └── androidTest │ │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── router │ │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle └── timeline │ ├── .gitignore │ ├── src │ ├── main │ │ ├── res │ │ │ ├── values │ │ │ │ └── strings.xml │ │ │ └── layout │ │ │ │ ├── timeline_ad_item.xml │ │ │ │ ├── timeline_article_item.xml │ │ │ │ └── timeline_activity.xml │ │ ├── AndroidManifest.xml │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── timeline │ │ │ ├── TimelineActivity.kt │ │ │ ├── TimelineViewModel.kt │ │ │ └── TimelineAdapter.kt │ ├── test │ │ └── java │ │ │ └── io │ │ │ └── github │ │ │ └── kobakei │ │ │ └── katsuo │ │ │ └── timeline │ │ │ └── ExampleUnitTest.kt │ └── androidTest │ │ └── java │ │ └── io │ │ └── github │ │ └── kobakei │ │ └── katsuo │ │ └── timeline │ │ └── ExampleInstrumentedTest.kt │ ├── proguard-rules.pro │ └── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .idea ├── encodings.xml ├── codeStyles │ ├── codeStyleConfig.xml │ └── Project.xml ├── vcs.xml ├── misc.xml ├── runConfigurations.xml └── gradle.xml ├── .gitignore ├── settings.gradle ├── gradle.properties ├── .circleci └── config.yml ├── README.md ├── gradlew.bat ├── versions.gradle ├── gradlew └── LICENSE /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/api/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/entity/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ui/author/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ui/common/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ui/detail/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ui/router/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /ui/timeline/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/database/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /data/repository/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Katsuo 3 | 4 | -------------------------------------------------------------------------------- /data/api/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Api 3 | 4 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /ui/author/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Author 3 | 4 | -------------------------------------------------------------------------------- /ui/common/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Common 3 | 4 | -------------------------------------------------------------------------------- /ui/detail/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Detail 3 | 4 | -------------------------------------------------------------------------------- /ui/router/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Router 3 | 4 | -------------------------------------------------------------------------------- /data/database/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Database 3 | 4 | -------------------------------------------------------------------------------- /data/entity/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Entity 3 | 4 | -------------------------------------------------------------------------------- /ui/timeline/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Timeline 3 | 4 | -------------------------------------------------------------------------------- /data/repository/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Repository 3 | 4 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kobakei/Katsuo/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/encodings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /data/api/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /data/database/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /data/entity/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /ui/router/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /data/repository/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /ui/common/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | -------------------------------------------------------------------------------- /.idea/codeStyles/codeStyleConfig.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /data/entity/src/main/java/io/github/kobakei/katsuo/entity/Articles.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.entity 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Articles( 7 | val articles: List
8 | ) -------------------------------------------------------------------------------- /ui/author/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ui/detail/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /ui/common/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #008577 4 | #00574B 5 | #D81B60 6 | 7 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app', 2 | ':data:entity', 3 | ':data:api', 4 | ':data:database', 5 | ':data:repository', 6 | ':ui:common', 7 | ':ui:router', 8 | ':ui:timeline', 9 | ':ui:detail', 10 | ':ui:author' 11 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Sat Sep 21 02:01:10 JST 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-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /ui/common/src/main/java/io/github/kobakei/katsuo/presentation/common/ImageViewExt.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.timeline 2 | 3 | import android.widget.ImageView 4 | import androidx.databinding.BindingAdapter 5 | import com.bumptech.glide.Glide 6 | 7 | @BindingAdapter("imageUrl") 8 | fun ImageView.setImageUrl(url: String?) { 9 | Glide.with(this).load(url).into(this) 10 | } -------------------------------------------------------------------------------- /data/entity/src/main/java/io/github/kobakei/katsuo/entity/Ads.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.entity 2 | 3 | import kotlinx.serialization.Serializable 4 | 5 | @Serializable 6 | data class Ads( 7 | val timeline: Ad 8 | ) 9 | 10 | @Serializable 11 | data class Ad( 12 | val id: Long, 13 | val description: String, 14 | val image: AdImage 15 | ) 16 | 17 | @Serializable 18 | data class AdImage( 19 | val large: String 20 | ) -------------------------------------------------------------------------------- /data/entity/src/test/java/io/github/kobakei/katsuo/entity/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.entity 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see [Testing documentation](http://d.android.com/tools/testing) 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /data/repository/src/main/java/io/github/kobakei/katsuo/repository/AdRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.repository 2 | 3 | import io.github.kobakei.katsuo.api.ApiClient 4 | import io.github.kobakei.katsuo.entity.Ad 5 | 6 | /** 7 | * 広告データのリポジトリ 8 | * このリポジトリは常にAPIクライアントからデータを取得する 9 | */ 10 | class AdRepository( 11 | private val apiClient: ApiClient 12 | ) { 13 | 14 | suspend fun getTimelineAd(): Ad { 15 | return apiClient.getAdsAsync().timeline 16 | } 17 | 18 | } -------------------------------------------------------------------------------- /ui/router/src/test/java/io/github/kobakei/katsuo/router/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.router 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see [Testing documentation](http://d.android.com/tools/testing) 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /data/api/src/test/java/io/github/kobakei/katsuo/api/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.api 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see [Testing documentation](http://d.android.com/tools/testing) 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, (2 + 2).toLong()) 16 | } 17 | } -------------------------------------------------------------------------------- /ui/author/src/test/java/io/github/kobakei/katsuo/author/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.author 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see [Testing documentation](http://d.android.com/tools/testing) 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, (2 + 2).toLong()) 16 | } 17 | } -------------------------------------------------------------------------------- /ui/detail/src/test/java/io/github/kobakei/katsuo/detail/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.detail 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see [Testing documentation](http://d.android.com/tools/testing) 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, (2 + 2).toLong()) 16 | } 17 | } -------------------------------------------------------------------------------- /ui/timeline/src/test/java/io/github/kobakei/katsuo/timeline/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.timeline 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see [Testing documentation](http://d.android.com/tools/testing) 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, (2 + 2).toLong()) 16 | } 17 | } -------------------------------------------------------------------------------- /data/database/src/test/java/io/github/kobakei/katsuo/database/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.database 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see [Testing documentation](http://d.android.com/tools/testing) 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, (2 + 2).toLong()) 16 | } 17 | } -------------------------------------------------------------------------------- /ui/common/src/test/java/io/github/kobakei/katsuo/presentation/common/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.presentation.common 2 | 3 | import org.junit.Test 4 | 5 | import org.junit.Assert.* 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see [Testing documentation](http://d.android.com/tools/testing) 11 | */ 12 | class ExampleUnitTest { 13 | @Test 14 | fun addition_isCorrect() { 15 | assertEquals(4, 2 + 2) 16 | } 17 | } -------------------------------------------------------------------------------- /ui/common/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /ui/timeline/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ui/author/src/main/java/io/github/kobakei/katsuo/author/AuthorRouterImpl.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.author 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import io.github.kobakei.katsuo.entity.Author 6 | import io.github.kobakei.katsuo.router.AuthorRouter 7 | 8 | class AuthorRouterImpl : AuthorRouter { 9 | override fun navigateToAuthor(activity: Activity, author: Author) { 10 | val intent = AuthorActivity.createIntent(activity, author) 11 | activity.startActivity(intent) 12 | } 13 | } -------------------------------------------------------------------------------- /ui/router/src/main/java/io/github/kobakei/katsuo/router/Router.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.router 2 | 3 | import android.app.Activity 4 | import io.github.kobakei.katsuo.entity.Article 5 | import io.github.kobakei.katsuo.entity.Author 6 | 7 | class Router( 8 | val detail: DetailRouter, 9 | val author: AuthorRouter 10 | ) 11 | 12 | interface DetailRouter { 13 | fun navigateToDetail(activity: Activity, article: Article) 14 | } 15 | 16 | interface AuthorRouter { 17 | fun navigateToAuthor(activity: Activity, author: Author) 18 | } -------------------------------------------------------------------------------- /ui/detail/src/main/java/io/github/kobakei/katsuo/detail/DetailRouterImpl.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.detail 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import io.github.kobakei.katsuo.entity.Article 6 | import io.github.kobakei.katsuo.router.DetailRouter 7 | 8 | class DetailRouterImpl : DetailRouter { 9 | 10 | override fun navigateToDetail(activity: Activity, article: Article) { 11 | val intent = DetailActivity.createIntent(activity, article) 12 | activity.startActivity(intent) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 14 | -------------------------------------------------------------------------------- /app/src/test/java/io/github/kobakei/katsuo/ExampleUnitTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo 2 | 3 | import androidx.test.ext.junit.runners.AndroidJUnit4 4 | import org.junit.Assert.assertEquals 5 | import org.junit.Test 6 | import org.junit.runner.RunWith 7 | 8 | /** 9 | * Example local unit test, which will execute on the development machine (host). 10 | * 11 | * See [testing documentation](http://d.android.com/tools/testing). 12 | */ 13 | @RunWith(AndroidJUnit4::class) 14 | class ExampleUnitTest { 15 | @Test 16 | fun addition_isCorrect() { 17 | assertEquals(4, 2 + 2) 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /data/database/src/main/java/io/github/kobakei/katsuo/database/ArticleDao.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.database 2 | 3 | import androidx.room.Dao 4 | import androidx.room.Insert 5 | import androidx.room.Query 6 | import io.github.kobakei.katsuo.entity.Article 7 | 8 | @Dao 9 | interface ArticleDao { 10 | 11 | @Query("SELECT * FROM article") 12 | suspend fun getAll(): List
13 | 14 | @Query("SELECT * FROM article WHERE author_id = :authorId") 15 | suspend fun getByAuthorId(authorId: Long): List
16 | 17 | @Insert 18 | suspend fun insertAll(vararg articles: Article) 19 | } -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /data/database/src/main/java/io/github/kobakei/katsuo/database/AppDatabase.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.database 2 | 3 | import android.content.Context 4 | import androidx.room.Database 5 | import androidx.room.Room 6 | import androidx.room.RoomDatabase 7 | import io.github.kobakei.katsuo.entity.Article 8 | 9 | @Database(entities = [ 10 | Article::class 11 | ], version = 1, exportSchema = false) 12 | abstract class AppDatabase : RoomDatabase() { 13 | 14 | abstract fun articleDao(): ArticleDao 15 | 16 | } 17 | 18 | fun createDb(context: Context): AppDatabase = 19 | Room.databaseBuilder(context, AppDatabase::class.java, "database-name").build() 20 | -------------------------------------------------------------------------------- /data/entity/src/main/java/io/github/kobakei/katsuo/entity/Author.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.entity 2 | 3 | import android.os.Parcelable 4 | import androidx.room.ColumnInfo 5 | import androidx.room.Embedded 6 | import kotlinx.android.parcel.Parcelize 7 | import kotlinx.serialization.Serializable 8 | 9 | @Parcelize 10 | @Serializable 11 | data class Author( 12 | @ColumnInfo(name = "author_id") val id: Long, 13 | val name: String, 14 | @Embedded val image: AuthorImage? 15 | ): Parcelable 16 | 17 | @Parcelize 18 | @Serializable 19 | data class AuthorImage( 20 | @ColumnInfo(name = "author_image_thumbnail") val thumbnail: String, 21 | @ColumnInfo(name = "author_image_large") val large: String 22 | ): Parcelable -------------------------------------------------------------------------------- /ui/common/src/main/java/io/github/kobakei/katsuo/presentation/common/Extra.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.presentation.common 2 | 3 | import android.app.Activity 4 | import kotlin.properties.ReadOnlyProperty 5 | import kotlin.reflect.KProperty 6 | 7 | class NonNullExtra : ReadOnlyProperty { 8 | @Suppress("UNCHECKED_CAST") 9 | override fun getValue(thisRef: Activity, property: KProperty<*>): T = 10 | requireNotNull(thisRef.intent.extras).get(property.name) as T 11 | } 12 | 13 | class NullableExtra : ReadOnlyProperty { 14 | @Suppress("UNCHECKED_CAST") 15 | override fun getValue(thisRef: Activity, property: KProperty<*>): T? = 16 | thisRef.intent.extras?.get(property.name) as? T 17 | } -------------------------------------------------------------------------------- /data/entity/src/main/java/io/github/kobakei/katsuo/entity/Article.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.entity 2 | 3 | import android.os.Parcelable 4 | import androidx.room.* 5 | import kotlinx.android.parcel.Parcelize 6 | import kotlinx.serialization.Serializable 7 | 8 | @Entity(indices = [Index("author_id")]) 9 | @Parcelize 10 | @Serializable 11 | data class Article( 12 | @PrimaryKey val id: Long, 13 | val title: String, 14 | val body: String, 15 | @Embedded val image: ArticleImage, 16 | @Embedded val author: Author 17 | ): Parcelable 18 | 19 | @Parcelize 20 | @Serializable 21 | data class ArticleImage( 22 | @ColumnInfo(name = "article_image_thumbnail") val thumbnail: String, 23 | @ColumnInfo(name = "article_image_large") val large: String 24 | ): Parcelable -------------------------------------------------------------------------------- /ui/detail/src/main/java/io/github/kobakei/katsuo/detail/DetailViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.detail 2 | 3 | import android.view.View 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import io.github.kobakei.katsuo.entity.Article 7 | import io.github.kobakei.katsuo.entity.Author 8 | 9 | class DetailViewModel : ViewModel() { 10 | 11 | lateinit var article: Article 12 | 13 | val authorClick = MutableLiveData() 14 | val shareClick = MutableLiveData() 15 | 16 | fun onAuthorClick(@Suppress("UNUSED_PARAMETER") view: View) { 17 | authorClick.postValue(article.author) 18 | } 19 | 20 | fun onShareClick(@Suppress("UNUSED_PARAMETER") view: View) { 21 | shareClick.postValue(Unit) 22 | } 23 | } -------------------------------------------------------------------------------- /ui/detail/src/main/res/drawable/ic_share_black_24dp.xml: -------------------------------------------------------------------------------- 1 | 6 | 9 | 10 | -------------------------------------------------------------------------------- /app/src/androidTest/java/io/github/kobakei/katsuo/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * See [testing documentation](http://d.android.com/tools/testing). 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | assertEquals("io.github.kobakei.katsuo", appContext.packageName) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /data/api/src/androidTest/java/io/github/kobakei/katsuo/api/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.api 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see [Testing documentation](http://d.android.com/tools/testing) 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | 23 | assertEquals("io.github.kobakei.katsuo.api.test", appContext.getPackageName()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ui/router/src/androidTest/java/io/github/kobakei/katsuo/router/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.router 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | import org.junit.Test 6 | import org.junit.runner.RunWith 7 | 8 | import org.junit.Assert.* 9 | 10 | /** 11 | * Instrumented test, which will execute on an Android device. 12 | * 13 | * @see [Testing documentation](http://d.android.com/tools/testing) 14 | */ 15 | @RunWith(AndroidJUnit4::class) 16 | class ExampleInstrumentedTest { 17 | @Test 18 | fun useAppContext() { 19 | // Context of the app under test. 20 | val appContext = InstrumentationRegistry.getTargetContext() 21 | 22 | assertEquals("io.github.kobakei.katsuo.router.test", appContext.getPackageName()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ui/author/src/androidTest/java/io/github/kobakei/katsuo/author/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.author 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see [Testing documentation](http://d.android.com/tools/testing) 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | 23 | assertEquals("io.github.kobakei.katsuo.author.test", appContext.getPackageName()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /ui/detail/src/androidTest/java/io/github/kobakei/katsuo/detail/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.detail 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see [Testing documentation](http://d.android.com/tools/testing) 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | 23 | assertEquals("io.github.kobakei.katsuo.detail.test", appContext.getPackageName()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /data/database/src/androidTest/java/io/github/kobakei/katsuo/database/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.database 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see [Testing documentation](http://d.android.com/tools/testing) 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | 23 | assertEquals("io.github.kobakei.katsuo.database.test", appContext.packageName) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/api/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 | -------------------------------------------------------------------------------- /data/entity/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 | -------------------------------------------------------------------------------- /data/repository/src/androidTest/java/io/github/kobakei/katsuo/repository/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.repository 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | import org.junit.Test 6 | import org.junit.runner.RunWith 7 | 8 | import org.junit.Assert.* 9 | 10 | /** 11 | * Instrumented test, which will execute on an Android device. 12 | * 13 | * @see [Testing documentation](http://d.android.com/tools/testing) 14 | */ 15 | @RunWith(AndroidJUnit4::class) 16 | class ExampleInstrumentedTest { 17 | @Test 18 | fun useAppContext() { 19 | // Context of the app under test. 20 | val appContext = InstrumentationRegistry.getTargetContext() 21 | 22 | assertEquals("io.github.kobakei.katsuo.repository.test", appContext.getPackageName()) 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /ui/author/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 | -------------------------------------------------------------------------------- /ui/common/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 | -------------------------------------------------------------------------------- /ui/detail/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 | -------------------------------------------------------------------------------- /ui/router/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 | -------------------------------------------------------------------------------- /ui/timeline/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 | -------------------------------------------------------------------------------- /ui/timeline/src/androidTest/java/io/github/kobakei/katsuo/timeline/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.timeline 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see [Testing documentation](http://d.android.com/tools/testing) 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | 23 | assertEquals("io.github.kobakei.katsuo.timeline.test", appContext.getPackageName()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/database/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 | -------------------------------------------------------------------------------- /data/repository/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 | -------------------------------------------------------------------------------- /ui/common/src/androidTest/java/io/github/kobakei/katsuo/presentation/common/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.presentation.common 2 | 3 | import androidx.test.InstrumentationRegistry 4 | import androidx.test.runner.AndroidJUnit4 5 | 6 | import org.junit.Test 7 | import org.junit.runner.RunWith 8 | 9 | import org.junit.Assert.* 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see [Testing documentation](http://d.android.com/tools/testing) 15 | */ 16 | @RunWith(AndroidJUnit4::class) 17 | class ExampleInstrumentedTest { 18 | @Test 19 | fun useAppContext() { 20 | // Context of the app under test. 21 | val appContext = InstrumentationRegistry.getTargetContext() 22 | 23 | assertEquals("io.github.kobakei.katsuo.presentation.common.test", appContext.getPackageName()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /data/entity/src/androidTest/java/io/github/kobakei/katsuo/entity/ExampleInstrumentedTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.entity 2 | 3 | import android.content.Context 4 | import android.support.test.InstrumentationRegistry 5 | import android.support.test.runner.AndroidJUnit4 6 | 7 | import org.junit.Test 8 | import org.junit.runner.RunWith 9 | 10 | import org.junit.Assert.* 11 | 12 | /** 13 | * Instrumented test, which will execute on an Android device. 14 | * 15 | * @see [Testing documentation](http://d.android.com/tools/testing) 16 | */ 17 | @RunWith(AndroidJUnit4::class) 18 | class ExampleInstrumentedTest { 19 | @Test 20 | fun useAppContext() { 21 | // Context of the app under test. 22 | val appContext = InstrumentationRegistry.getTargetContext() 23 | 24 | assertEquals("io.github.kobakei.katsuo.entity.test", appContext.getPackageName()) 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /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 | android.useAndroidX=true 17 | android.enableJetifier=true 18 | 19 | 20 | # robolectric 4.0 21 | android.enableUnitTestBinaryResources=true -------------------------------------------------------------------------------- /ui/author/src/main/java/io/github/kobakei/katsuo/author/AuthorViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.author 2 | 3 | import androidx.lifecycle.MutableLiveData 4 | import androidx.lifecycle.ViewModel 5 | import androidx.lifecycle.viewModelScope 6 | import io.github.kobakei.katsuo.entity.Article 7 | import io.github.kobakei.katsuo.entity.Author 8 | import io.github.kobakei.katsuo.repository.ArticleRepository 9 | import kotlinx.coroutines.launch 10 | 11 | class AuthorViewModel( 12 | private val articleRepository: ArticleRepository 13 | ) : ViewModel() { 14 | 15 | val author = MutableLiveData() 16 | val articles = MutableLiveData>() 17 | 18 | val articleClick = MutableLiveData
() 19 | 20 | fun loadArticles(author: Author) { 21 | this.author.value = author 22 | author.let { 23 | viewModelScope.launch { 24 | articles.value = articleRepository.getArticlesByAuthor(author) 25 | } 26 | } 27 | } 28 | 29 | fun onArticleClick(article: Article) { 30 | articleClick.postValue(article) 31 | } 32 | } -------------------------------------------------------------------------------- /ui/router/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion build_versions.compile_sdk 8 | 9 | defaultConfig { 10 | minSdkVersion build_versions.min_sdk 11 | targetSdkVersion build_versions.target_sdk 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled true 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | } 27 | 28 | dependencies { 29 | implementation project(':data:entity') 30 | 31 | implementation fileTree(dir: 'libs', include: ['*.jar']) 32 | implementation deps.kotlin.stdlib_jdk7 33 | 34 | testImplementation deps.test.junit 35 | testImplementation deps.test.arch_core_testing 36 | 37 | androidTestImplementation deps.test.runner 38 | androidTestImplementation deps.test.espresso_core 39 | } 40 | -------------------------------------------------------------------------------- /data/repository/src/main/java/io/github/kobakei/katsuo/repository/ArticleRepository.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.repository 2 | 3 | import io.github.kobakei.katsuo.api.ApiClient 4 | import io.github.kobakei.katsuo.database.ArticleDao 5 | import io.github.kobakei.katsuo.entity.Article 6 | import io.github.kobakei.katsuo.entity.Author 7 | 8 | /** 9 | * 記事のリポジトリクラス 10 | * ローカルDBとAPIからデータを取得する。取得の優先度は、room => api。 11 | */ 12 | class ArticleRepository( 13 | private val articleDao: ArticleDao, 14 | private val apiClient: ApiClient 15 | ) { 16 | 17 | /** 18 | * すべての記事を取得する 19 | */ 20 | suspend fun getArticles(): List
{ 21 | val articles = articleDao.getAll() 22 | if (articles.isNotEmpty()) { 23 | return articles 24 | } 25 | val articles2 = apiClient.getArticlesAsync() 26 | articleDao.insertAll(*articles2.articles.toTypedArray()) 27 | return articles2.articles 28 | } 29 | 30 | /** 31 | * 指定した著者のすべての記事を取得する 32 | */ 33 | suspend fun getArticlesByAuthor(author: Author): List
= 34 | articleDao.getByAuthorId(author.id) 35 | } -------------------------------------------------------------------------------- /ui/author/src/main/res/layout/author_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 16 | 17 | 26 | 27 | -------------------------------------------------------------------------------- /ui/timeline/src/main/res/layout/timeline_ad_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 25 | 26 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /ui/common/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion build_versions.compile_sdk 8 | 9 | defaultConfig { 10 | minSdkVersion build_versions.min_sdk 11 | targetSdkVersion build_versions.target_sdk 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled true 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | dataBinding { 27 | enabled = true 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation deps.kotlin.stdlib_jdk7 34 | 35 | implementation deps.androidx.material 36 | 37 | implementation deps.glide.glide 38 | kapt deps.glide.compiler 39 | 40 | testImplementation deps.test.junit 41 | testImplementation deps.test.arch_core_testing 42 | 43 | androidTestImplementation deps.test.runner 44 | androidTestImplementation deps.test.espresso_core 45 | } 46 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | working_directory: ~/code 5 | docker: 6 | - image: circleci/android:api-28-alpha 7 | environment: 8 | JVM_OPTS: -Xmx3200m 9 | steps: 10 | - checkout 11 | - restore_cache: 12 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 13 | # - run: 14 | # name: Chmod permissions #if permission for Gradlew Dependencies fail, use this. 15 | # command: sudo chmod +x ./gradlew 16 | - run: 17 | name: Download Dependencies 18 | command: ./gradlew androidDependencies 19 | - save_cache: 20 | paths: 21 | - ~/.gradle 22 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }} 23 | - run: 24 | name: Run Tests 25 | command: ./gradlew lint test 26 | - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/ 27 | path: app/build/reports 28 | destination: reports 29 | - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/ 30 | path: app/build/test-results 31 | # See https://circleci.com/docs/2.0/deployment-integrations/ for deploy examples 32 | -------------------------------------------------------------------------------- /data/database/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion build_versions.compile_sdk 9 | 10 | defaultConfig { 11 | minSdkVersion build_versions.min_sdk 12 | targetSdkVersion build_versions.target_sdk 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled true 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | } 25 | 26 | dependencies { 27 | implementation project(':data:entity') 28 | 29 | implementation fileTree(dir: 'libs', include: ['*.jar']) 30 | implementation deps.kotlin.stdlib_jdk7 31 | 32 | // Room 33 | implementation deps.room.runtime 34 | implementation deps.room.ktx 35 | kapt deps.room.compiler 36 | 37 | testImplementation deps.test.junit 38 | testImplementation deps.test.arch_core_testing 39 | 40 | androidTestImplementation deps.test.runner 41 | androidTestImplementation deps.test.espresso_core 42 | } 43 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 28 | 29 | -------------------------------------------------------------------------------- /data/api/src/main/java/io/github/kobakei/katsuo/api/ApiClient.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.api 2 | 3 | import com.jakewharton.retrofit2.converter.kotlinx.serialization.serializationConverterFactory 4 | import io.github.kobakei.katsuo.entity.Ads 5 | import io.github.kobakei.katsuo.entity.Articles 6 | import kotlinx.serialization.json.Json 7 | import okhttp3.MediaType 8 | import okhttp3.OkHttpClient 9 | import retrofit2.Retrofit 10 | import retrofit2.http.GET 11 | 12 | /** 13 | * Dummy json 14 | * https://gist.github.com/kobakei/a091214688e4c03e4e582019d16cc778 15 | */ 16 | interface ApiClient { 17 | @GET("kobakei/a091214688e4c03e4e582019d16cc778/raw/b98a91dda49519564dc62bdc34c8c93734a24b82/articles.json") 18 | suspend fun getArticlesAsync(): Articles 19 | 20 | @GET("kobakei/a091214688e4c03e4e582019d16cc778/raw/b98a91dda49519564dc62bdc34c8c93734a24b82/ads.json") 21 | suspend fun getAdsAsync(): Ads 22 | } 23 | 24 | fun okHttpClient(): OkHttpClient { 25 | return OkHttpClient.Builder().build() 26 | } 27 | 28 | fun apiClient(): ApiClient { 29 | val retrofit = Retrofit.Builder() 30 | .addConverterFactory(serializationConverterFactory(MediaType.get("application/json"), Json)) 31 | .client(okHttpClient()) 32 | .baseUrl("https://gist.githubusercontent.com/") 33 | .build() 34 | return retrofit.create(ApiClient::class.java) 35 | } -------------------------------------------------------------------------------- /data/entity/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'kotlinx-serialization' 6 | 7 | android { 8 | compileSdkVersion build_versions.compile_sdk 9 | 10 | defaultConfig { 11 | minSdkVersion build_versions.min_sdk 12 | targetSdkVersion build_versions.target_sdk 13 | versionCode 1 14 | versionName "1.0" 15 | 16 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 17 | 18 | } 19 | 20 | buildTypes { 21 | release { 22 | minifyEnabled true 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | } 27 | 28 | // for Parcelize 29 | androidExtensions { 30 | experimental = true 31 | } 32 | 33 | dependencies { 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation deps.kotlin.stdlib_jdk7 36 | 37 | // JSON 38 | implementation deps.kotlin.serialization_runtime 39 | 40 | // Room 41 | implementation deps.room.runtime 42 | kapt deps.room.compiler 43 | 44 | testImplementation deps.test.junit 45 | testImplementation deps.test.arch_core_testing 46 | 47 | androidTestImplementation deps.test.runner 48 | androidTestImplementation deps.test.espresso_core 49 | } 50 | repositories { 51 | mavenCentral() 52 | } 53 | -------------------------------------------------------------------------------- /data/api/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion build_versions.compile_sdk 7 | 8 | defaultConfig { 9 | minSdkVersion build_versions.min_sdk 10 | targetSdkVersion build_versions.target_sdk 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled true 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | compileOptions { 26 | sourceCompatibility JavaVersion.VERSION_1_8 27 | targetCompatibility JavaVersion.VERSION_1_8 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation project(':data:entity') 33 | 34 | implementation fileTree(dir: 'libs', include: ['*.jar']) 35 | implementation deps.kotlin.stdlib_jdk7 36 | 37 | implementation deps.okhttp 38 | implementation deps.retrofit.retrofit 39 | implementation deps.retrofit.serialization 40 | 41 | testImplementation deps.test.junit 42 | testImplementation deps.test.arch_core_testing 43 | 44 | androidTestImplementation deps.test.runner 45 | androidTestImplementation deps.test.espresso_core 46 | } 47 | repositories { 48 | mavenCentral() 49 | } 50 | -------------------------------------------------------------------------------- /data/repository/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | 5 | android { 6 | compileSdkVersion build_versions.compile_sdk 7 | 8 | defaultConfig { 9 | minSdkVersion build_versions.min_sdk 10 | targetSdkVersion build_versions.target_sdk 11 | versionCode 1 12 | versionName "1.0" 13 | 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled true 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | } 26 | 27 | dependencies { 28 | implementation project(':data:api') 29 | implementation project(':data:database') 30 | implementation project(':data:entity') 31 | 32 | implementation fileTree(dir: 'libs', include: ['*.jar']) 33 | implementation deps.kotlin.stdlib_jdk7 34 | implementation deps.kotlin.coroutines_core 35 | implementation deps.kotlin.coroutines_android 36 | 37 | // Room 38 | implementation deps.room.runtime 39 | 40 | testImplementation deps.test.junit 41 | testImplementation deps.test.mockk 42 | testImplementation deps.test.truth 43 | testImplementation deps.test.ext_truth 44 | 45 | androidTestImplementation deps.test.runner 46 | androidTestImplementation deps.test.espresso_core 47 | } 48 | -------------------------------------------------------------------------------- /ui/detail/src/test/java/io/github/kobakei/katsuo/detail/DetailViewModelTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.detail 2 | 3 | import android.view.View 4 | import androidx.arch.core.executor.testing.InstantTaskExecutorRule 5 | import com.google.common.truth.Truth 6 | import io.github.kobakei.katsuo.entity.Article 7 | import io.github.kobakei.katsuo.entity.ArticleImage 8 | import io.github.kobakei.katsuo.entity.Author 9 | import io.github.kobakei.katsuo.entity.AuthorImage 10 | import io.mockk.MockKAnnotations 11 | import io.mockk.impl.annotations.MockK 12 | import org.junit.Before 13 | import org.junit.Rule 14 | import org.junit.Test 15 | 16 | class DetailViewModelTest { 17 | 18 | @Rule 19 | @JvmField 20 | val instantTaskExecutorRule = InstantTaskExecutorRule() 21 | 22 | @MockK 23 | lateinit var view: View 24 | 25 | @Before 26 | fun setup() = MockKAnnotations.init(this) 27 | 28 | @Test 29 | fun onAuthorClick_isCorrect() { 30 | val author = Author( 31 | id = 123, 32 | name = "Hoge", 33 | image = AuthorImage( 34 | thumbnail = "", 35 | large = "" 36 | ) 37 | ) 38 | val viewModel = DetailViewModel().apply { 39 | article = Article( 40 | id = 1, 41 | title = "Title", 42 | body = "Body", 43 | image = ArticleImage( 44 | thumbnail = "", 45 | large = "" 46 | ), 47 | author = author 48 | ) 49 | } 50 | viewModel.onAuthorClick(view) 51 | Truth.assertThat(viewModel.authorClick.value).isEqualTo(author) 52 | } 53 | } -------------------------------------------------------------------------------- /ui/timeline/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion build_versions.compile_sdk 8 | 9 | defaultConfig { 10 | minSdkVersion build_versions.min_sdk 11 | targetSdkVersion build_versions.target_sdk 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled true 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | dataBinding { 27 | enabled = true 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation project(':data:entity') 33 | implementation project(':data:repository') 34 | implementation project(':ui:common') 35 | implementation project(':ui:router') 36 | 37 | implementation fileTree(dir: 'libs', include: ['*.jar']) 38 | implementation deps.kotlin.stdlib_jdk7 39 | 40 | // UI 41 | implementation deps.androidx.appcompat 42 | implementation deps.androidx.recyclerview 43 | implementation deps.androidx.constraint 44 | 45 | // Lifecycle 46 | implementation deps.androidx.lifecycle 47 | kapt deps.androidx.lifecycle_compiler 48 | 49 | // KTX 50 | implementation deps.androidx.core_ktx 51 | implementation deps.androidx.viewmodel_ktx 52 | 53 | implementation deps.koin_viewmodel 54 | 55 | implementation deps.timber 56 | 57 | testImplementation deps.test.junit 58 | testImplementation deps.test.arch_core_testing 59 | 60 | androidTestImplementation deps.test.runner 61 | androidTestImplementation deps.test.espresso_core 62 | } 63 | -------------------------------------------------------------------------------- /ui/timeline/src/main/java/io/github/kobakei/katsuo/timeline/TimelineActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.timeline 2 | 3 | import android.os.Bundle 4 | import androidx.appcompat.app.AppCompatActivity 5 | import androidx.databinding.DataBindingUtil 6 | import androidx.lifecycle.Observer 7 | import androidx.recyclerview.widget.LinearLayoutManager 8 | import io.github.kobakei.katsuo.router.DetailRouter 9 | import io.github.kobakei.katsuo.router.Router 10 | import io.github.kobakei.katsuo.timeline.databinding.TimelineActivityBinding 11 | import org.koin.android.ext.android.inject 12 | import org.koin.android.viewmodel.ext.android.viewModel 13 | 14 | class TimelineActivity : AppCompatActivity() { 15 | 16 | private lateinit var binding: TimelineActivityBinding 17 | private val timelineViewModel: TimelineViewModel by viewModel() 18 | private val router: Router by inject() 19 | private lateinit var adapter: TimelineAdapter 20 | 21 | override fun onCreate(savedInstanceState: Bundle?) { 22 | super.onCreate(savedInstanceState) 23 | 24 | binding = DataBindingUtil.setContentView(this, R.layout.timeline_activity) 25 | binding.lifecycleOwner = this 26 | 27 | binding.viewModel = timelineViewModel 28 | observeViewModel() 29 | 30 | adapter = TimelineAdapter(this, timelineViewModel) 31 | binding.recyclerView.adapter = adapter 32 | binding.recyclerView.layoutManager = LinearLayoutManager(this) 33 | 34 | timelineViewModel.loadData() 35 | } 36 | 37 | private fun observeViewModel() { 38 | timelineViewModel.timelineData.observe(this, Observer { 39 | adapter.notifyDataSetChanged() 40 | }) 41 | timelineViewModel.articleClick.observe(this, Observer { 42 | router.detail.navigateToDetail(this, it) 43 | }) 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /ui/author/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-kapt' 5 | apply plugin: 'kotlin-android-extensions' 6 | 7 | android { 8 | compileSdkVersion build_versions.compile_sdk 9 | 10 | defaultConfig { 11 | minSdkVersion build_versions.min_sdk 12 | targetSdkVersion build_versions.target_sdk 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | } 17 | 18 | buildTypes { 19 | release { 20 | minifyEnabled true 21 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 22 | } 23 | } 24 | 25 | dataBinding { 26 | enabled = true 27 | } 28 | } 29 | 30 | dependencies { 31 | implementation project(':data:entity') 32 | implementation project(':data:repository') 33 | implementation project(':ui:common') 34 | implementation project(':ui:router') 35 | 36 | implementation fileTree(dir: 'libs', include: ['*.jar']) 37 | implementation deps.kotlin.stdlib_jdk7 38 | 39 | // UI 40 | implementation deps.androidx.appcompat 41 | implementation deps.androidx.recyclerview 42 | implementation deps.androidx.constraint 43 | 44 | // Lifecycle 45 | implementation deps.androidx.lifecycle 46 | kapt deps.androidx.lifecycle_compiler 47 | 48 | // KTX 49 | implementation deps.androidx.core_ktx 50 | implementation deps.androidx.viewmodel_ktx 51 | 52 | implementation deps.koin_viewmodel 53 | 54 | implementation deps.timber 55 | 56 | testImplementation deps.test.junit 57 | testImplementation deps.test.arch_core_testing 58 | 59 | androidTestImplementation deps.test.runner 60 | androidTestImplementation deps.test.espresso_core 61 | } 62 | -------------------------------------------------------------------------------- /ui/author/src/main/res/layout/author_header.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 15 | 16 | 28 | 29 | 42 | 43 | -------------------------------------------------------------------------------- /data/repository/src/test/java/io/github/kobakei/katsuo/repository/ArticleRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.repository 2 | 3 | import com.google.common.truth.Truth 4 | import io.github.kobakei.katsuo.api.ApiClient 5 | import io.github.kobakei.katsuo.database.ArticleDao 6 | import io.github.kobakei.katsuo.entity.Article 7 | import io.github.kobakei.katsuo.entity.Articles 8 | import io.github.kobakei.katsuo.entity.Author 9 | import io.mockk.coEvery 10 | import io.mockk.mockk 11 | import kotlinx.coroutines.runBlocking 12 | import org.junit.Test 13 | 14 | class ArticleRepositoryTest { 15 | 16 | private val articles = listOf
( 17 | mockk(), 18 | mockk(), 19 | mockk() 20 | ) 21 | 22 | @Test 23 | fun getArticles_isCorrectWithoutCache() { 24 | val dao = mockk() 25 | coEvery { dao.getAll() } returns listOf() 26 | coEvery { dao.insertAll(any(), any(), any()) } returns Unit 27 | 28 | val api = mockk() 29 | coEvery { api.getArticlesAsync() } returns Articles( 30 | articles = articles 31 | ) 32 | 33 | val repo = ArticleRepository(dao, api) 34 | runBlocking { 35 | Truth.assertThat(repo.getArticles()) 36 | .containsExactlyElementsIn(articles) 37 | } 38 | } 39 | 40 | @Test 41 | fun getArticlesByAuthor_isCorrect() { 42 | val author = Author( 43 | id = 1, 44 | name = "John", 45 | image = null 46 | ) 47 | val dao = mockk() 48 | coEvery { dao.getByAuthorId(author.id) } returns articles 49 | 50 | val api = mockk() 51 | 52 | runBlocking { 53 | val repo = ArticleRepository(dao, api) 54 | Truth.assertThat(repo.getArticlesByAuthor(author)) 55 | .containsExactlyElementsIn(articles) 56 | } 57 | } 58 | } -------------------------------------------------------------------------------- /ui/detail/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.library' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion build_versions.compile_sdk 8 | 9 | defaultConfig { 10 | minSdkVersion build_versions.min_sdk 11 | targetSdkVersion build_versions.target_sdk 12 | versionCode 1 13 | versionName "1.0" 14 | 15 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 16 | 17 | } 18 | 19 | buildTypes { 20 | release { 21 | minifyEnabled true 22 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 23 | } 24 | } 25 | 26 | dataBinding { 27 | enabled = true 28 | } 29 | } 30 | 31 | dependencies { 32 | implementation project(':data:entity') 33 | implementation project(':ui:common') 34 | implementation project(':ui:router') 35 | 36 | implementation fileTree(dir: 'libs', include: ['*.jar']) 37 | implementation deps.kotlin.stdlib_jdk7 38 | 39 | // UI 40 | implementation deps.androidx.appcompat 41 | implementation deps.androidx.recyclerview 42 | implementation deps.androidx.constraint 43 | implementation deps.androidx.material 44 | 45 | // Lifecycle 46 | implementation deps.androidx.lifecycle 47 | kapt deps.androidx.lifecycle_compiler 48 | 49 | // KTX 50 | implementation deps.androidx.core_ktx 51 | implementation deps.androidx.viewmodel_ktx 52 | 53 | implementation deps.koin_viewmodel 54 | 55 | implementation deps.timber 56 | 57 | testImplementation deps.test.junit 58 | testImplementation deps.test.arch_core_testing 59 | testImplementation deps.test.mockk 60 | testImplementation deps.test.truth 61 | testImplementation deps.test.ext_truth 62 | 63 | androidTestImplementation deps.test.runner 64 | androidTestImplementation deps.test.espresso_core 65 | } 66 | -------------------------------------------------------------------------------- /ui/timeline/src/main/res/layout/timeline_article_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 20 | 28 | 37 | 38 | -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /ui/author/src/main/res/layout/author_item.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 13 | 14 | 15 | 20 | 21 | 34 | 35 | 48 | 49 | -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | 3 | apply plugin: 'kotlin-android' 4 | apply plugin: 'kotlin-android-extensions' 5 | apply plugin: 'kotlin-kapt' 6 | 7 | android { 8 | compileSdkVersion build_versions.compile_sdk 9 | defaultConfig { 10 | applicationId "io.github.kobakei.katsuo" 11 | minSdkVersion build_versions.min_sdk 12 | targetSdkVersion build_versions.target_sdk 13 | versionCode 1 14 | versionName "1.0" 15 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 16 | 17 | // robolectric 18 | testOptions.unitTests.includeAndroidResources = true 19 | } 20 | buildTypes { 21 | release { 22 | minifyEnabled true 23 | proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' 24 | } 25 | } 26 | dataBinding { 27 | enabled = true 28 | } 29 | compileOptions { 30 | sourceCompatibility JavaVersion.VERSION_1_8 31 | targetCompatibility JavaVersion.VERSION_1_8 32 | } 33 | } 34 | 35 | dependencies { 36 | // DIでモジュール間を結合するため、ほぼすべてのモジュールに依存する 37 | implementation project(':ui:timeline') 38 | implementation project(':ui:detail') 39 | implementation project(':ui:author') 40 | implementation project(':ui:router') 41 | implementation project(':data:repository') 42 | implementation project(':data:api') 43 | implementation project(':data:database') 44 | implementation project(':data:entity') 45 | 46 | implementation fileTree(include: ['*.jar'], dir: 'libs') 47 | implementation deps.kotlin.stdlib_jdk7 48 | 49 | implementation deps.androidx.appcompat 50 | 51 | implementation deps.koin_viewmodel 52 | 53 | implementation deps.timber 54 | implementation deps.stetho 55 | 56 | // Room 57 | implementation deps.room.runtime 58 | 59 | testImplementation deps.test.junit 60 | testImplementation deps.test.runner 61 | testImplementation deps.test.rules 62 | testImplementation deps.test.ext_junit_ktx 63 | testImplementation deps.test.arch_core_testing 64 | testImplementation deps.test.robolectric 65 | 66 | androidTestImplementation deps.test.runner 67 | androidTestImplementation deps.test.espresso_core 68 | } 69 | -------------------------------------------------------------------------------- /app/src/main/java/io/github/kobakei/katsuo/App.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo 2 | 3 | import android.app.Application 4 | import com.facebook.stetho.Stetho 5 | import io.github.kobakei.katsuo.api.apiClient 6 | import io.github.kobakei.katsuo.author.AuthorRouterImpl 7 | import io.github.kobakei.katsuo.author.AuthorViewModel 8 | import io.github.kobakei.katsuo.database.createDb 9 | import io.github.kobakei.katsuo.detail.DetailRouterImpl 10 | import io.github.kobakei.katsuo.detail.DetailViewModel 11 | import io.github.kobakei.katsuo.repository.AdRepository 12 | import io.github.kobakei.katsuo.repository.ArticleRepository 13 | import io.github.kobakei.katsuo.router.AuthorRouter 14 | import io.github.kobakei.katsuo.router.DetailRouter 15 | import io.github.kobakei.katsuo.router.Router 16 | import io.github.kobakei.katsuo.timeline.TimelineViewModel 17 | import org.koin.android.ext.koin.androidContext 18 | import org.koin.android.ext.koin.androidLogger 19 | import org.koin.android.viewmodel.dsl.viewModel 20 | import org.koin.core.context.startKoin 21 | import org.koin.dsl.module 22 | import timber.log.Timber 23 | 24 | class App : Application() { 25 | 26 | override fun onCreate() { 27 | super.onCreate() 28 | 29 | if (BuildConfig.DEBUG) { 30 | Timber.plant(Timber.DebugTree()) 31 | Stetho.initializeWithDefaults(this) 32 | } 33 | 34 | startKoin { 35 | androidLogger() 36 | androidContext(this@App) 37 | modules( 38 | listOf( 39 | routerModule, 40 | viewModelModule, 41 | repoModule, 42 | dataModule 43 | ) 44 | ) 45 | } 46 | } 47 | } 48 | 49 | val dataModule = module { 50 | single { apiClient() } 51 | single { createDb(get()).articleDao() } 52 | } 53 | 54 | val repoModule = module { 55 | single { ArticleRepository(get(), get()) } 56 | single { AdRepository(get()) } 57 | } 58 | 59 | val routerModule = module { 60 | single { DetailRouterImpl() } 61 | single { AuthorRouterImpl() } 62 | single { Router(get(), get()) } 63 | } 64 | 65 | val viewModelModule = module { 66 | viewModel { TimelineViewModel(get(), get()) } 67 | viewModel { DetailViewModel() } 68 | viewModel { AuthorViewModel(get()) } 69 | } -------------------------------------------------------------------------------- /ui/timeline/src/main/java/io/github/kobakei/katsuo/timeline/TimelineViewModel.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.timeline 2 | 3 | import android.view.View 4 | import androidx.lifecycle.MutableLiveData 5 | import androidx.lifecycle.ViewModel 6 | import androidx.lifecycle.viewModelScope 7 | import io.github.kobakei.katsuo.entity.Ad 8 | import io.github.kobakei.katsuo.entity.Article 9 | import io.github.kobakei.katsuo.repository.AdRepository 10 | import io.github.kobakei.katsuo.repository.ArticleRepository 11 | import kotlinx.coroutines.* 12 | import timber.log.Timber 13 | 14 | data class TimelineData( 15 | val articles: List
, 16 | val ad: Ad? 17 | ) 18 | 19 | class TimelineViewModel( 20 | private val articleRepo: ArticleRepository, 21 | private val adRepo: AdRepository 22 | ) : ViewModel() { 23 | 24 | val timelineData = MutableLiveData() 25 | 26 | val progressVisibility = MutableLiveData().apply { postValue(View.VISIBLE) } 27 | 28 | val reloadVisibility = MutableLiveData().apply { postValue(View.GONE) } 29 | 30 | val articleClick = MutableLiveData
() 31 | 32 | /** 33 | * 複数のデータを取得して表示するサンプル 34 | * ・並列実行: suspend blockをasyncで囲む 35 | * ・例外処理: supervisorScope.launchで囲む 36 | * ・エラーを無視: asyncの中をさらにrunCatchingで囲み、ResultをgetOrNullで変換 37 | */ 38 | fun loadData() { 39 | viewModelScope.launch { 40 | supervisorScope { 41 | runCatching { 42 | val articlesAsync = async { articleRepo.getArticles() } 43 | val adAsync = async { 44 | runCatching { adRepo.getTimelineAd() }.getOrNull() 45 | } 46 | TimelineData(articlesAsync.await(), adAsync.await()) 47 | }.fold( 48 | onSuccess = { 49 | timelineData.postValue(it) 50 | }, 51 | onFailure = { 52 | reloadVisibility.postValue(View.VISIBLE) 53 | } 54 | ) 55 | progressVisibility.postValue(View.GONE) 56 | } 57 | } 58 | } 59 | 60 | fun onItemClick(article: Article) { 61 | Timber.v("click: ${article.title}") 62 | articleClick.postValue(article) 63 | } 64 | 65 | fun onReloadClick() { 66 | progressVisibility.postValue(View.VISIBLE) 67 | reloadVisibility.postValue(View.GONE) 68 | loadData() 69 | } 70 | } -------------------------------------------------------------------------------- /ui/detail/src/main/java/io/github/kobakei/katsuo/detail/DetailActivity.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.detail 2 | 3 | import android.app.Activity 4 | import android.content.Intent 5 | import androidx.appcompat.app.AppCompatActivity 6 | import android.os.Bundle 7 | import android.view.MenuItem 8 | import android.widget.Toast 9 | import androidx.databinding.DataBindingUtil 10 | import androidx.lifecycle.Observer 11 | import io.github.kobakei.katsuo.detail.databinding.DetailActivityBinding 12 | import io.github.kobakei.katsuo.entity.Article 13 | import io.github.kobakei.katsuo.presentation.common.NonNullExtra 14 | import io.github.kobakei.katsuo.router.Router 15 | import org.koin.android.ext.android.inject 16 | import org.koin.android.viewmodel.ext.android.viewModel 17 | 18 | class DetailActivity : AppCompatActivity() { 19 | 20 | private lateinit var binding: DetailActivityBinding 21 | private val detailViewModel: DetailViewModel by viewModel() 22 | private val router: Router by inject() 23 | 24 | private val article: Article by NonNullExtra() 25 | 26 | override fun onCreate(savedInstanceState: Bundle?) { 27 | super.onCreate(savedInstanceState) 28 | 29 | binding = DataBindingUtil.setContentView(this, R.layout.detail_activity) 30 | binding.lifecycleOwner = this 31 | binding.viewModel = detailViewModel 32 | 33 | detailViewModel.article = article 34 | 35 | observeViewModel() 36 | 37 | supportActionBar?.apply { 38 | setDisplayShowHomeEnabled(true) 39 | } 40 | } 41 | 42 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 43 | return when (item?.itemId) { 44 | android.R.id.home -> { 45 | finish() 46 | true 47 | } 48 | else -> super.onOptionsItemSelected(item) 49 | } 50 | } 51 | 52 | private fun observeViewModel() { 53 | detailViewModel.authorClick.observe(this, Observer { 54 | router.author.navigateToAuthor(this, it) 55 | }) 56 | detailViewModel.shareClick.observe(this, Observer { 57 | Toast.makeText(this, "Not implemented", Toast.LENGTH_SHORT).show() 58 | }) 59 | } 60 | 61 | companion object { 62 | fun createIntent(activity: Activity, article: Article): Intent = 63 | Intent(activity, DetailActivity::class.java).apply { 64 | putExtra(DetailActivity::article.name, article) 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Katsuo 2 | 3 | [![CircleCI](https://circleci.com/gh/kobakei/Katsuo.svg?style=svg)](https://circleci.com/gh/kobakei/Katsuo) 4 | 5 | This is a sample that adopted the latest trend in Android development. 6 | 7 | This app is supposed to be the official application of the news site. This app holds timeline screen, article screen, and author's screen. 8 | 9 | ## Architecture 10 | 11 | The architecture of this application mainly follows the following three items. 12 | 13 | - Multi modules 14 | - MVVM 15 | - Layered architecture 16 | 17 | This application does not adopt Flux or Redux. In applications where state management is not complicated, I think they are over-engineering. 18 | 19 | ### Modules 20 | 21 | - app 22 | - Module with Application class or DI related class 23 | - ui/timeline, ui/author, ui/detail 24 | - Module of each feature 25 | - ui/router 26 | - Module of router interfaces. The implementations of routers are in each feature module. 27 | - ui/common 28 | - Module of classes and extension functions commonly used by other UI modules 29 | - data/repository 30 | - Module of repository classes 31 | - data/api 32 | - Module of API client 33 | - data/database 34 | - Module of database 35 | - data/entity 36 | - Module of entity classes 37 | 38 | ## Libraries 39 | 40 | I am adopting libraries that are standard in Android development and widely used as much as possible. 41 | In addition, I try to use what Kotlin's language function provides as much as possible. 42 | 43 | - Kotlin 44 | - Coroutines 45 | - Serialization 46 | - Parcelize 47 | - Android Jetpack 48 | - Data binding 49 | - Lifecycle & ViewModel 50 | - LiveData 51 | - KTX 52 | - Room 53 | - Koin 54 | - OkHttp 55 | - Retrofit 56 | - KotlinX serialization converter 57 | - Glide 58 | - Timber 59 | - Stetho 60 | - Robolectric 61 | - MockK 62 | - Truth 63 | 64 | ## TODO 65 | 66 | Please check issues. 67 | 68 | ## License 69 | 70 | ``` 71 | Copyright 2019-present Keisuke Kobayashi 72 | 73 | Licensed under the Apache License, Version 2.0 (the "License"); 74 | you may not use this file except in compliance with the License. 75 | You may obtain a copy of the License at 76 | 77 | http://www.apache.org/licenses/LICENSE-2.0 78 | 79 | Unless required by applicable law or agreed to in writing, software 80 | distributed under the License is distributed on an "AS IS" BASIS, 81 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 82 | See the License for the specific language governing permissions and 83 | limitations under the License. 84 | ``` 85 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /ui/author/src/main/java/io/github/kobakei/katsuo/author/AuthorAdapter.kt: -------------------------------------------------------------------------------- 1 | package io.github.kobakei.katsuo.author 2 | 3 | import android.content.Context 4 | import android.view.LayoutInflater 5 | import android.view.ViewGroup 6 | import androidx.databinding.ViewDataBinding 7 | import androidx.recyclerview.widget.RecyclerView 8 | import io.github.kobakei.katsuo.author.databinding.AuthorHeaderBinding 9 | import io.github.kobakei.katsuo.author.databinding.AuthorItemBinding 10 | import io.github.kobakei.katsuo.entity.Article 11 | import io.github.kobakei.katsuo.entity.Author 12 | 13 | class AuthorAdapter(context: Context, val viewModel: AuthorViewModel) : RecyclerView.Adapter() { 14 | 15 | private val inflater = LayoutInflater.from(context) 16 | 17 | var author: Author? = null 18 | var articles = mutableListOf
() 19 | 20 | override fun getItemViewType(position: Int): Int { 21 | return if (position == 0) { 22 | TYPE_HEADER 23 | } else { 24 | TYPE_ITEM 25 | } 26 | } 27 | 28 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AuthorViewHolder { 29 | return when (viewType) { 30 | TYPE_HEADER -> { 31 | val binding = AuthorHeaderBinding.inflate(inflater, parent, false) 32 | AuthorHeaderViewHolder(binding) 33 | } 34 | TYPE_ITEM -> { 35 | val binding = AuthorItemBinding.inflate(inflater, parent, false) 36 | binding.viewModel = viewModel 37 | AuthorItemViewHolder(binding) 38 | } 39 | else -> throw IllegalArgumentException() 40 | } 41 | } 42 | 43 | override fun getItemCount(): Int = articles.size + 1 44 | 45 | override fun onBindViewHolder(holder: AuthorViewHolder, position: Int) { 46 | when (holder) { 47 | is AuthorHeaderViewHolder -> { 48 | holder.binding.author = author 49 | holder.binding.executePendingBindings() 50 | } 51 | is AuthorItemViewHolder -> { 52 | holder.binding.article = articles[position - 1] 53 | holder.binding.executePendingBindings() 54 | } 55 | } 56 | } 57 | 58 | companion object { 59 | private const val TYPE_HEADER = 1 60 | private const val TYPE_ITEM = 2 61 | } 62 | } 63 | 64 | sealed class AuthorViewHolder(binding: ViewDataBinding) : RecyclerView.ViewHolder(binding.root) 65 | class AuthorHeaderViewHolder(val binding: AuthorHeaderBinding) : AuthorViewHolder(binding) 66 | class AuthorItemViewHolder(val binding: AuthorItemBinding) : AuthorViewHolder(binding) 67 | -------------------------------------------------------------------------------- /ui/timeline/src/main/res/layout/timeline_activity.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 7 | 10 | 11 | 12 | 16 | 17 |