├── app ├── .gitignore ├── src │ ├── release │ │ └── kotlin │ │ │ └── com │ │ │ └── hana053 │ │ │ └── micropost │ │ │ └── Application.kt │ ├── main │ │ ├── res │ │ │ ├── mipmap-hdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-mdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── mipmap-xxxhdpi │ │ │ │ └── ic_launcher.png │ │ │ ├── drawable │ │ │ │ ├── ic_create_white_36dp.png │ │ │ │ ├── bg_top.xml │ │ │ │ ├── border_top.xml │ │ │ │ └── border_bottom_on_white.xml │ │ │ ├── anim │ │ │ │ └── slide_in_up.xml │ │ │ ├── values-w820dp │ │ │ │ └── dimens.xml │ │ │ ├── values │ │ │ │ ├── colors.xml │ │ │ │ ├── dimens.xml │ │ │ │ ├── styles.xml │ │ │ │ └── strings.xml │ │ │ ├── menu │ │ │ │ └── menu_main.xml │ │ │ ├── layout │ │ │ │ ├── activity_signup.xml │ │ │ │ ├── _user_posts.xml │ │ │ │ ├── activity_related_user_list.xml │ │ │ │ ├── activity_user_show.xml │ │ │ │ ├── activity_micropost_new.xml │ │ │ │ ├── activity_main.xml │ │ │ │ ├── item_related_users.xml │ │ │ │ ├── item_posts.xml │ │ │ │ ├── fragment_signup_email.xml │ │ │ │ ├── fragment_signup_full_name.xml │ │ │ │ ├── fragment_signup_password.xml │ │ │ │ ├── activity_top.xml │ │ │ │ ├── activity_login.xml │ │ │ │ └── _user_detail.xml │ │ │ └── values-v21 │ │ │ │ └── styles.xml │ │ ├── kotlin │ │ │ └── com │ │ │ │ └── hana053 │ │ │ │ └── micropost │ │ │ │ ├── service │ │ │ │ ├── HttpErrorHandler.kt │ │ │ │ ├── LoginService.kt │ │ │ │ ├── AuthService.kt │ │ │ │ ├── Navigator.kt │ │ │ │ ├── ServiceModule.kt │ │ │ │ ├── AuthServiceImpl.kt │ │ │ │ ├── HttpErrorHandlerImpl.kt │ │ │ │ ├── LoginServiceImpl.kt │ │ │ │ └── NavigatorImpl.kt │ │ │ │ ├── domain │ │ │ │ ├── UserStats.kt │ │ │ │ ├── Micropost.kt │ │ │ │ ├── Avatar.kt │ │ │ │ ├── User.kt │ │ │ │ └── RelatedUser.kt │ │ │ │ ├── Extensions.kt │ │ │ │ ├── repository │ │ │ │ ├── AuthTokenRepository.kt │ │ │ │ ├── RepositoryModule.kt │ │ │ │ └── AuthTokenRepositoryImpl.kt │ │ │ │ ├── pages │ │ │ │ ├── signup │ │ │ │ │ ├── SignupNavigator.kt │ │ │ │ │ ├── SignupService.kt │ │ │ │ │ ├── SignupState.kt │ │ │ │ │ ├── email │ │ │ │ │ │ ├── SignupEmailView.kt │ │ │ │ │ │ ├── SignupFullNameModule.kt │ │ │ │ │ │ ├── SignupEmailFragment.kt │ │ │ │ │ │ └── SignupEmailPresenter.kt │ │ │ │ │ ├── password │ │ │ │ │ │ ├── SignupPasswordView.kt │ │ │ │ │ │ ├── SignupPasswordModule.kt │ │ │ │ │ │ ├── SignupPasswordFragment.kt │ │ │ │ │ │ └── SignupPasswordPresenter.kt │ │ │ │ │ ├── fullname │ │ │ │ │ │ ├── SignupFullNameView.kt │ │ │ │ │ │ ├── SignupFullNameModule.kt │ │ │ │ │ │ ├── SignupFullNameFragment.kt │ │ │ │ │ │ └── SignupFullNamePresenter.kt │ │ │ │ │ ├── SignupModule.kt │ │ │ │ │ ├── SignupNavigatorImpl.kt │ │ │ │ │ ├── SignupActivity.kt │ │ │ │ │ └── SignupServiceImpl.kt │ │ │ │ ├── ViewWrapper.kt │ │ │ │ ├── relateduserlist │ │ │ │ │ ├── RelatedUserListService.kt │ │ │ │ │ ├── RelatedUserListView.kt │ │ │ │ │ ├── RelatedUserListPresenter.kt │ │ │ │ │ ├── followerlist │ │ │ │ │ │ └── FollowerListService.kt │ │ │ │ │ ├── followinglist │ │ │ │ │ │ └── FollowingListService.kt │ │ │ │ │ ├── RelatedUserListActivity.kt │ │ │ │ │ ├── RelatedUserListModule.kt │ │ │ │ │ └── RelatedUserListAdapter.kt │ │ │ │ ├── micropostnew │ │ │ │ │ ├── MicropostNewNavigator.kt │ │ │ │ │ ├── MicropostNewView.kt │ │ │ │ │ ├── MicropostNewService.kt │ │ │ │ │ ├── MicropostNewPresenter.kt │ │ │ │ │ ├── MIcropostNewModule.kt │ │ │ │ │ └── MicropostNewActivity.kt │ │ │ │ ├── top │ │ │ │ │ ├── TopView.kt │ │ │ │ │ ├── TopPresenter.kt │ │ │ │ │ ├── TopModule.kt │ │ │ │ │ └── TopActivity.kt │ │ │ │ ├── usershow │ │ │ │ │ ├── posts │ │ │ │ │ │ ├── UserShowPostsPresenter.kt │ │ │ │ │ │ ├── UserShowPostsView.kt │ │ │ │ │ │ ├── UserShowPostsService.kt │ │ │ │ │ │ └── UserShowPostsModule.kt │ │ │ │ │ ├── detail │ │ │ │ │ │ ├── UserShowDetailService.kt │ │ │ │ │ │ ├── UserShowDetailModule.kt │ │ │ │ │ │ ├── UserShowDetailPresenter.kt │ │ │ │ │ │ └── UserShowDetailView.kt │ │ │ │ │ ├── UserShowModule.kt │ │ │ │ │ └── UserShowActivity.kt │ │ │ │ ├── login │ │ │ │ │ ├── LoginModule.kt │ │ │ │ │ ├── LoginView.kt │ │ │ │ │ ├── LoginActivity.kt │ │ │ │ │ └── LoginPresenter.kt │ │ │ │ ├── main │ │ │ │ │ ├── MainModule.kt │ │ │ │ │ ├── MainView.kt │ │ │ │ │ ├── MainService.kt │ │ │ │ │ ├── MainPresenter.kt │ │ │ │ │ └── MainActivity.kt │ │ │ │ └── Presenter.kt │ │ │ │ ├── interactor │ │ │ │ ├── MicropostInteractor.kt │ │ │ │ ├── LoginInteractor.kt │ │ │ │ ├── UserMicropostInteractor.kt │ │ │ │ ├── FeedInteractor.kt │ │ │ │ ├── RelationshipInteractor.kt │ │ │ │ ├── UserInteractor.kt │ │ │ │ ├── RelatedUserListInteractor.kt │ │ │ │ └── InteractorModule.kt │ │ │ │ ├── shared │ │ │ │ ├── avatar │ │ │ │ │ └── AvatarView.kt │ │ │ │ ├── followbtn │ │ │ │ │ ├── FollowBtnModule.kt │ │ │ │ │ ├── FollowBtnView.kt │ │ │ │ │ └── FollowBtnService.kt │ │ │ │ └── posts │ │ │ │ │ └── PostListAdapter.kt │ │ │ │ ├── AppModule.kt │ │ │ │ ├── system │ │ │ │ └── SystemServicesModule.kt │ │ │ │ └── BaseApplication.kt │ │ └── AndroidManifest.xml │ ├── testShared │ │ └── kotlin │ │ │ └── com │ │ │ └── hana053 │ │ │ └── micropost │ │ │ └── testing │ │ │ ├── EmptyResponseBody.kt │ │ │ ├── InjectableTest.kt │ │ │ ├── TestUtil.kt │ │ │ └── BlockedInteractorModule.kt │ ├── androidTest │ │ └── kotlin │ │ │ └── com │ │ │ └── hana053 │ │ │ └── micropost │ │ │ ├── testing │ │ │ ├── InjectableTestImpl.kt │ │ │ ├── AppTestRunner.kt │ │ │ └── EspressoHelpers.kt │ │ │ └── pages │ │ │ ├── top │ │ │ └── TopActivityTest.kt │ │ │ ├── micropostnew │ │ │ └── MicropostNewActivityTest.kt │ │ │ └── login │ │ │ └── LoginActivityTest.kt │ ├── debug │ │ └── kotlin │ │ │ └── com │ │ │ └── hana053 │ │ │ └── micropost │ │ │ └── Application.kt │ └── test │ │ └── kotlin │ │ └── com │ │ └── hana053 │ │ └── micropost │ │ ├── repository │ │ └── AuthTokenRepositoryTest.kt │ │ ├── testing │ │ ├── TestSchedulerProxy.kt │ │ └── RobolectricBaseTest.kt │ │ ├── pages │ │ ├── micropostnew │ │ │ └── MicropostNewServiceTest.kt │ │ ├── usershow │ │ │ ├── detail │ │ │ │ └── UserShowDetailServiceTest.kt │ │ │ └── posts │ │ │ │ └── UserShowPostsServiceTest.kt │ │ ├── signup │ │ │ └── SignupServiceTest.kt │ │ ├── relateduserlist │ │ │ ├── followerlist │ │ │ │ └── FollowerListServiceTest.kt │ │ │ └── followinglist │ │ │ │ └── FollowingListServiceTest.kt │ │ └── main │ │ │ └── MainServiceTest.kt │ │ ├── service │ │ ├── AuthServiceTest.kt │ │ ├── HttpErrorHandlerTest.kt │ │ ├── LoginServiceTest.kt │ │ └── NavigatorTest.kt │ │ └── shared │ │ └── followbtn │ │ └── FollowBtnServiceTest.kt ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── gradle.properties.ci.example ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradle.properties.example ├── .gitignore ├── LICENSE ├── .travis.yml ├── README.md ├── gradlew.bat └── gradlew /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | -------------------------------------------------------------------------------- /gradle.properties.ci.example: -------------------------------------------------------------------------------- 1 | micropost.apiUrl="http://localhost/api/" 2 | 3 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springboot-angular2-tutorial/android-app/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /app/src/release/kotlin/com/hana053/micropost/Application.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost 2 | 3 | 4 | class Application : BaseApplication() 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springboot-angular2-tutorial/android-app/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springboot-angular2-tutorial/android-app/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springboot-angular2-tutorial/android-app/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springboot-angular2-tutorial/android-app/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springboot-angular2-tutorial/android-app/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_create_white_36dp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/springboot-angular2-tutorial/android-app/HEAD/app/src/main/res/drawable/ic_create_white_36dp.png -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/service/HttpErrorHandler.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.service 2 | 3 | interface HttpErrorHandler { 4 | 5 | fun handleError(throwable: Throwable) 6 | 7 | } 8 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/domain/UserStats.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.domain 2 | 3 | 4 | data class UserStats( 5 | val micropostCnt: Int, 6 | val followingCnt: Int, 7 | val followerCnt: Int 8 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/domain/Micropost.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.domain 2 | 3 | 4 | data class Micropost( 5 | val id: Long, 6 | val content: String, 7 | val createdAt: Long, 8 | val user: User 9 | ) -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/Extensions.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost 2 | 3 | import android.app.Activity 4 | 5 | fun Activity.getOverridingModule() = 6 | (application as BaseApplication).getOverridingModule(javaClass) 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/repository/AuthTokenRepository.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.repository 2 | 3 | 4 | interface AuthTokenRepository { 5 | fun get(): String? 6 | fun set(authToken: String) 7 | fun clear() 8 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/service/LoginService.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.service 2 | 3 | import rx.Observable 4 | 5 | 6 | interface LoginService { 7 | 8 | fun login(email: String, password: String): Observable 9 | 10 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/domain/Avatar.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.domain 2 | 3 | 4 | interface Avatar { 5 | val avatarHash: String 6 | 7 | fun avatarUrl(size: Int = 72) = "https://secure.gravatar.com/avatar/$avatarHash?s=$size" 8 | } 9 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/SignupNavigator.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup 2 | 3 | 4 | interface SignupNavigator { 5 | 6 | fun navigateToEmail() 7 | fun navigateToPassword() 8 | fun navigateToPrev() 9 | 10 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/service/AuthService.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.service 2 | 3 | import com.hana053.micropost.domain.User 4 | 5 | 6 | interface AuthService { 7 | fun isMyself(user: User): Boolean 8 | fun logout() 9 | fun auth(): Boolean 10 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Nov 10 20:27:08 ICT 2016 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-3.4-all.zip 7 | -------------------------------------------------------------------------------- /app/src/main/res/anim/slide_in_up.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/ViewWrapper.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages 2 | 3 | import android.content.Context 4 | import android.view.ViewGroup 5 | 6 | 7 | interface ViewWrapper { 8 | val content: ViewGroup 9 | 10 | fun context(): Context = content.context 11 | } -------------------------------------------------------------------------------- /gradle.properties.example: -------------------------------------------------------------------------------- 1 | org.gradle.daemon=true 2 | org.gradle.jvmargs=-Xmx3072m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8 3 | org.gradle.parallel=true 4 | org.gradle.configureondemand=true 5 | 6 | micropost.apiUrl="http://localhost/api/" 7 | 8 | kotlin.incremental=true 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm 2 | *.iml 3 | 4 | ## Directory-based project format: 5 | .idea/ 6 | 7 | .gradle 8 | /local.properties 9 | .DS_Store 10 | /build 11 | /captures 12 | 13 | # release key 14 | app/myreleasekey.jks 15 | 16 | gradle.properties 17 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/SignupService.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup 2 | 3 | import com.hana053.micropost.interactor.UserInteractor 4 | import rx.Observable 5 | 6 | 7 | interface SignupService { 8 | 9 | fun signup(request: UserInteractor.SignupRequest): Observable 10 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/domain/User.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.domain 2 | 3 | data class User( 4 | val id: Long, 5 | val name: String, 6 | val email: String?, 7 | override val avatarHash: String, 8 | val isFollowedByMe: Boolean? = null, 9 | val userStats: UserStats 10 | ) : Avatar 11 | -------------------------------------------------------------------------------- /app/src/testShared/kotlin/com/hana053/micropost/testing/EmptyResponseBody.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.testing 2 | 3 | import okhttp3.ResponseBody 4 | 5 | class EmptyResponseBody : ResponseBody() { 6 | 7 | override fun contentType() = null 8 | override fun contentLength(): Long = 0 9 | override fun source() = null 10 | 11 | } 12 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/relateduserlist/RelatedUserListService.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.relateduserlist 2 | 3 | import com.hana053.micropost.domain.RelatedUser 4 | import rx.Observable 5 | 6 | 7 | interface RelatedUserListService { 8 | fun listUsers(userId: Long): Observable> 9 | fun title(): String 10 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/micropostnew/MicropostNewNavigator.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.micropostnew 2 | 3 | import android.app.Activity 4 | 5 | 6 | class MicropostNewNavigator( 7 | private val activity: Activity 8 | ) { 9 | fun finishWithPost() { 10 | activity.setResult(Activity.RESULT_OK) 11 | activity.finish() 12 | } 13 | } -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/com/hana053/micropost/testing/InjectableTestImpl.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.testing 2 | 3 | import android.support.test.InstrumentationRegistry 4 | import com.hana053.micropost.BaseApplication 5 | 6 | 7 | class InjectableTestImpl : InjectableTest { 8 | override val app = InstrumentationRegistry.getInstrumentation().targetContext.applicationContext as BaseApplication 9 | } -------------------------------------------------------------------------------- /app/src/main/res/values-w820dp/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 64dp 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/SignupState.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup 2 | 3 | import com.hana053.micropost.interactor.UserInteractor 4 | 5 | 6 | data class SignupState( 7 | var fullName: String = "", 8 | var email: String = "", 9 | var password: String = "" 10 | ) { 11 | 12 | fun toSignupRequest() = UserInteractor.SignupRequest(fullName, email, password) 13 | 14 | } -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | #EEEEEE 7 | 8 | #1D96F2 9 | #1ACF81 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/service/Navigator.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.service 2 | 3 | interface Navigator { 4 | 5 | fun navigateToTop() 6 | fun navigateToMain() 7 | fun navigateToLogin() 8 | fun navigateToSignup() 9 | fun navigateToUserShow(userId: Long) 10 | fun navigateToFollowerList(userId: Long) 11 | fun navigateToFollowingList(userId: Long) 12 | fun navigateToMicropostNew() 13 | 14 | } 15 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/bg_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/main/res/menu/menu_main.xml: -------------------------------------------------------------------------------- 1 | 5 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/domain/RelatedUser.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.domain 2 | 3 | 4 | data class RelatedUser( 5 | val id: Long, 6 | val name: String, 7 | val email: String?, 8 | override val avatarHash: String, 9 | val isFollowedByMe: Boolean, 10 | val userStats: UserStats, 11 | val relationshipId: Long 12 | ) : Avatar { 13 | 14 | fun toUser() = User(id, name, email, avatarHash, isFollowedByMe, userStats) 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/interactor/MicropostInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.interactor 2 | 3 | import com.hana053.micropost.domain.Micropost 4 | import retrofit2.http.Body 5 | import retrofit2.http.POST 6 | import rx.Observable 7 | 8 | 9 | interface MicropostInteractor { 10 | 11 | @POST("microposts") 12 | fun create(@Body request: MicropostRequest): Observable 13 | 14 | data class MicropostRequest(val content: String) 15 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/interactor/LoginInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.interactor 2 | 3 | import retrofit2.http.Body 4 | import retrofit2.http.POST 5 | import rx.Observable 6 | 7 | interface LoginInteractor { 8 | 9 | @POST("auth") 10 | fun login(@Body request: LoginRequest): Observable 11 | 12 | data class LoginRequest(val email: String, val password: String) 13 | data class LoginResponse(val token: String) 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/top/TopView.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.top 2 | 3 | import android.view.ViewGroup 4 | import com.hana053.micropost.pages.ViewWrapper 5 | import com.jakewharton.rxbinding.view.clicks 6 | import kotlinx.android.synthetic.main.activity_top.view.* 7 | 8 | class TopView(override val content: ViewGroup) : ViewWrapper { 9 | 10 | val signupClicks = content.btn_signup.clicks() 11 | val loginClicks = content.btn_login.clicks() 12 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/repository/RepositoryModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.repository 2 | 3 | import com.github.salomonbrys.kodein.Kodein 4 | import com.github.salomonbrys.kodein.bind 5 | import com.github.salomonbrys.kodein.instance 6 | import com.github.salomonbrys.kodein.singleton 7 | 8 | 9 | fun repositoryModule() = Kodein.Module { 10 | 11 | bind() with singleton { 12 | AuthTokenRepositoryImpl(instance()) 13 | } 14 | 15 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/interactor/UserMicropostInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.interactor 2 | 3 | import com.hana053.micropost.domain.Micropost 4 | import retrofit2.http.GET 5 | import retrofit2.http.Path 6 | import retrofit2.http.Query 7 | import rx.Observable 8 | 9 | 10 | interface UserMicropostInteractor { 11 | 12 | @GET("users/{userId}/microposts?count=20") 13 | fun loadPrevPosts(@Path("userId") userId: Long, @Query("maxId") maxId: Long?): Observable> 14 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable/border_top.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/border_bottom_on_white.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/interactor/FeedInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.myapp.interactors 2 | 3 | import com.hana053.micropost.domain.Micropost 4 | import retrofit2.http.GET 5 | import retrofit2.http.Query 6 | import rx.Observable 7 | 8 | interface FeedInteractor { 9 | 10 | @GET("feed?count=20") 11 | fun loadNextFeed(@Query("sinceId") sinceId: Long?): Observable> 12 | 13 | @GET("feed?count=20") 14 | fun loadPrevFeed(@Query("maxId") maxId: Long?): Observable> 15 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/shared/avatar/AvatarView.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.shared.avatar 2 | 3 | import android.widget.ImageView 4 | import com.hana053.micropost.domain.User 5 | import com.squareup.picasso.Picasso 6 | 7 | 8 | class AvatarView( 9 | private val content: ImageView, 10 | private val size: Int = 96 11 | 12 | ) { 13 | 14 | fun render(user: User) { 15 | Picasso.with(content.context) 16 | .load(user.avatarUrl(size)) 17 | .into(content) 18 | } 19 | 20 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/interactor/RelationshipInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.interactor 2 | 3 | import retrofit2.http.DELETE 4 | import retrofit2.http.POST 5 | import retrofit2.http.Path 6 | import rx.Observable 7 | 8 | interface RelationshipInteractor { 9 | 10 | @POST("relationships/to/{followerId}") 11 | fun follow(@Path("followerId") followerId: Long): Observable 12 | 13 | @DELETE("relationships/to/{followerId}") 14 | fun unfollow(@Path("followerId") followerId: Long): Observable 15 | 16 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_signup.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 12 | 13 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost 2 | 3 | import com.github.salomonbrys.kodein.Kodein 4 | import com.hana053.micropost.service.serviceModule 5 | import com.hana053.micropost.interactor.interactorModule 6 | import com.hana053.micropost.repository.repositoryModule 7 | import com.hana053.micropost.system.systemModule 8 | 9 | fun appModule() = Kodein.Module { 10 | 11 | import(systemModule()) 12 | import(repositoryModule()) 13 | import(interactorModule()) 14 | import(serviceModule()) 15 | 16 | } 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/usershow/posts/UserShowPostsPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.usershow.posts 2 | 3 | import com.hana053.micropost.pages.Presenter 4 | 5 | 6 | class UserShowPostsPresenter( 7 | override val view: UserShowPostsView, 8 | private val userId: Long, 9 | private val service: UserShowPostsService 10 | ) : Presenter { 11 | 12 | override fun bind() { 13 | service.loadPosts(userId) 14 | .bindToLifecycle() 15 | .withProgressDialog() 16 | .subscribe() 17 | } 18 | 19 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/system/SystemServicesModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.system 2 | 3 | import android.content.SharedPreferences 4 | import android.preference.PreferenceManager 5 | import com.github.salomonbrys.kodein.Kodein 6 | import com.github.salomonbrys.kodein.bind 7 | import com.github.salomonbrys.kodein.instance 8 | import com.github.salomonbrys.kodein.singleton 9 | 10 | 11 | fun systemModule() = Kodein.Module { 12 | 13 | bind() with singleton { 14 | PreferenceManager.getDefaultSharedPreferences(instance()) 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/shared/followbtn/FollowBtnModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.shared.followbtn 2 | 3 | import com.github.salomonbrys.kodein.Kodein 4 | import com.github.salomonbrys.kodein.android.androidActivityScope 5 | import com.github.salomonbrys.kodein.autoScopedSingleton 6 | import com.github.salomonbrys.kodein.bind 7 | import com.github.salomonbrys.kodein.instance 8 | 9 | fun followBtnModule() = Kodein.Module { 10 | 11 | bind() with autoScopedSingleton(androidActivityScope) { 12 | FollowBtnService(instance(), instance()) 13 | } 14 | 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/top/TopPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.top 2 | 3 | import com.hana053.micropost.pages.Presenter 4 | import com.hana053.micropost.service.Navigator 5 | 6 | 7 | class TopPresenter( 8 | override val view: TopView, 9 | private val navigator: Navigator 10 | ) : Presenter { 11 | 12 | override fun bind() { 13 | view.loginClicks 14 | .bindToLifecycle() 15 | .subscribe { navigator.navigateToLogin() } 16 | 17 | view.signupClicks 18 | .bindToLifecycle() 19 | .subscribe { navigator.navigateToSignup() } 20 | } 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/_user_posts.xml: -------------------------------------------------------------------------------- 1 | 2 | 9 | 10 | 16 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_related_user_list.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/interactor/UserInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.interactor 2 | 3 | import com.hana053.micropost.domain.User 4 | import retrofit2.http.Body 5 | import retrofit2.http.GET 6 | import retrofit2.http.POST 7 | import retrofit2.http.Path 8 | import rx.Observable 9 | 10 | interface UserInteractor { 11 | 12 | @GET("users/{id}") 13 | fun get(@Path("id") userId: Long): Observable 14 | 15 | @POST("users") 16 | fun create(@Body request: SignupRequest): Observable 17 | 18 | data class SignupRequest( 19 | val name: String, 20 | val email: String, 21 | val password: String 22 | ) 23 | } 24 | 25 | -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/com/hana053/micropost/testing/AppTestRunner.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.testing 2 | 3 | import android.os.Bundle 4 | import android.support.test.InstrumentationRegistry 5 | import android.support.test.runner.AndroidJUnitRunner 6 | import com.linkedin.android.testbutler.TestButler 7 | 8 | 9 | class AppTestRunner : AndroidJUnitRunner() { 10 | 11 | override fun onStart() { 12 | TestButler.setup(InstrumentationRegistry.getTargetContext()) 13 | super.onStart() 14 | } 15 | 16 | override fun finish(resultCode: Int, results: Bundle) { 17 | TestButler.teardown(InstrumentationRegistry.getTargetContext()) 18 | super.finish(resultCode, results) 19 | } 20 | 21 | } -------------------------------------------------------------------------------- /app/src/debug/kotlin/com/hana053/micropost/Application.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost 2 | 3 | import android.os.StrictMode 4 | import com.squareup.leakcanary.LeakCanary 5 | import timber.log.Timber 6 | 7 | class Application : BaseApplication() { 8 | 9 | override fun onCreate() { 10 | super.onCreate() 11 | LeakCanary.install(this) 12 | 13 | StrictMode.setThreadPolicy(StrictMode.ThreadPolicy.Builder().detectAll().penaltyLog().penaltyDeathOnNetwork().build()) 14 | StrictMode.setVmPolicy(StrictMode.VmPolicy.Builder().detectLeakedSqlLiteObjects().detectLeakedClosableObjects().detectActivityLeaks().penaltyLog().build()) 15 | 16 | Timber.plant(Timber.DebugTree()) 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/interactor/RelatedUserListInteractor.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.interactor 2 | 3 | import com.hana053.micropost.domain.RelatedUser 4 | import retrofit2.http.GET 5 | import retrofit2.http.Path 6 | import retrofit2.http.Query 7 | import rx.Observable 8 | 9 | 10 | interface RelatedUserListInteractor { 11 | 12 | @GET("users/{id}/followings") 13 | fun listFollowings( 14 | @Path("id") userId: Long, 15 | @Query("maxId") maxId: Long? 16 | ): Observable> 17 | 18 | @GET("users/{id}/followers") 19 | fun listFollowers( 20 | @Path("id") userId: Long, 21 | @Query("maxId") maxId: Long? 22 | ): Observable> 23 | 24 | } -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in /usr/local/opt/android-sdk/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/micropostnew/MicropostNewView.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.micropostnew 2 | 3 | import android.view.ViewGroup 4 | import com.hana053.micropost.pages.ViewWrapper 5 | import com.jakewharton.rxbinding.view.clicks 6 | import com.jakewharton.rxbinding.view.enabled 7 | import com.jakewharton.rxbinding.widget.textChanges 8 | import kotlinx.android.synthetic.main.activity_micropost_new.view.* 9 | 10 | 11 | class MicropostNewView(override val content: ViewGroup) : ViewWrapper { 12 | 13 | // Events 14 | val postTextChanges = content.et_post.textChanges() 15 | val postBtnClicks = content.btn_post.clicks() 16 | 17 | // Props 18 | val postBtnEnabled = content.btn_post.enabled() 19 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/repository/AuthTokenRepositoryImpl.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.repository 2 | 3 | import android.content.SharedPreferences 4 | 5 | class AuthTokenRepositoryImpl( 6 | private val sharedPreferences: SharedPreferences 7 | ) : AuthTokenRepository { 8 | 9 | private val AUTH_TOKEN = "AUTH_TOKEN" 10 | 11 | override fun get(): String? = 12 | sharedPreferences.getString(AUTH_TOKEN, null) 13 | 14 | override fun set(authToken: String) = 15 | sharedPreferences.edit() 16 | .putString(AUTH_TOKEN, authToken) 17 | .apply() 18 | 19 | override fun clear() = 20 | sharedPreferences.edit() 21 | .putString(AUTH_TOKEN, null) 22 | .apply() 23 | 24 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/top/TopModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.top 2 | 3 | import android.app.Activity 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.github.salomonbrys.kodein.android.androidActivityScope 6 | import com.github.salomonbrys.kodein.autoScopedSingleton 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import kotlinx.android.synthetic.main.activity_top.* 10 | 11 | fun topModule() = Kodein.Module { 12 | 13 | bind() with autoScopedSingleton(androidActivityScope) { 14 | TopPresenter(instance(), instance()) 15 | } 16 | 17 | bind() with autoScopedSingleton(androidActivityScope) { 18 | instance().activity_top.let(::TopView) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/usershow/posts/UserShowPostsView.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.usershow.posts 2 | 3 | import android.support.v7.widget.LinearLayoutManager 4 | import android.view.ViewGroup 5 | import com.hana053.micropost.pages.ViewWrapper 6 | import com.hana053.micropost.shared.posts.PostListAdapter 7 | import kotlinx.android.synthetic.main._user_posts.view.* 8 | 9 | 10 | class UserShowPostsView( 11 | override val content: ViewGroup, 12 | postListAdapter: PostListAdapter 13 | ) : ViewWrapper { 14 | 15 | private val listPost = content.list_post 16 | 17 | init { 18 | with(listPost) { 19 | layoutManager = LinearLayoutManager(context()) 20 | adapter = postListAdapter 21 | isNestedScrollingEnabled = false 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/login/LoginModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.login 2 | 3 | import android.app.Activity 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.github.salomonbrys.kodein.android.androidActivityScope 6 | import com.github.salomonbrys.kodein.autoScopedSingleton 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import kotlinx.android.synthetic.main.activity_login.* 10 | 11 | fun loginModule() = Kodein.Module { 12 | 13 | bind() with autoScopedSingleton(androidActivityScope) { 14 | LoginPresenter(instance(), instance(), instance()) 15 | } 16 | 17 | bind() with autoScopedSingleton(androidActivityScope) { 18 | instance().activity_login.let(::LoginView) 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/email/SignupEmailView.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup.email 2 | 3 | import android.view.ViewGroup 4 | import com.hana053.micropost.pages.ViewWrapper 5 | import com.jakewharton.rxbinding.view.clicks 6 | import com.jakewharton.rxbinding.view.enabled 7 | import com.jakewharton.rxbinding.view.visibility 8 | import com.jakewharton.rxbinding.widget.textChanges 9 | import kotlinx.android.synthetic.main.fragment_signup_email.view.* 10 | 11 | 12 | class SignupEmailView(override val content: ViewGroup) : ViewWrapper { 13 | 14 | // Events 15 | val emailChanges = content.et_email.textChanges() 16 | val nextBtnClicks = content.btn_next.clicks() 17 | 18 | // Props 19 | val nextBtnEnabled = content.btn_next.enabled() 20 | val emailInvalidVisibility = content.tv_email_invalid.visibility() 21 | 22 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/password/SignupPasswordView.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup.password 2 | 3 | import android.view.ViewGroup 4 | import com.hana053.micropost.pages.ViewWrapper 5 | import com.jakewharton.rxbinding.view.clicks 6 | import com.jakewharton.rxbinding.view.enabled 7 | import com.jakewharton.rxbinding.view.visibility 8 | import com.jakewharton.rxbinding.widget.textChanges 9 | import kotlinx.android.synthetic.main.fragment_signup_password.view.* 10 | 11 | 12 | class SignupPasswordView(override val content: ViewGroup) : ViewWrapper { 13 | 14 | // Events 15 | val passwordChanges = content.et_password.textChanges() 16 | val nextBtnClicks = content.btn_next.clicks() 17 | 18 | // Props 19 | val nextBtnEnabled = content.btn_next.enabled() 20 | val passwordInvalidVisibility = content.tv_password_invalid.visibility() 21 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/fullname/SignupFullNameView.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup.fullname 2 | 3 | import android.view.ViewGroup 4 | import com.hana053.micropost.pages.ViewWrapper 5 | import com.jakewharton.rxbinding.view.clicks 6 | import com.jakewharton.rxbinding.view.enabled 7 | import com.jakewharton.rxbinding.view.visibility 8 | import com.jakewharton.rxbinding.widget.textChanges 9 | import kotlinx.android.synthetic.main.fragment_signup_full_name.view.* 10 | 11 | 12 | class SignupFullNameView(override val content: ViewGroup) : ViewWrapper { 13 | 14 | // Events 15 | val fullNameChanges = content.et_full_name.textChanges() 16 | val nextBtnClicks = content.btn_next.clicks() 17 | 18 | // Props 19 | val nextBtnEnabled = content.btn_next.enabled() 20 | val fullNameInvalidVisibility = content.tv_full_name_invalid.visibility() 21 | 22 | } -------------------------------------------------------------------------------- /app/src/testShared/kotlin/com/hana053/micropost/testing/InjectableTest.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.testing 2 | 3 | import android.preference.PreferenceManager 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.hana053.micropost.BaseApplication 6 | import com.hana053.micropost.appModule 7 | import org.junit.Before 8 | 9 | 10 | interface InjectableTest { 11 | 12 | val app: BaseApplication 13 | 14 | @Before 15 | fun resetSharedPreferences() { 16 | PreferenceManager.getDefaultSharedPreferences(app).edit() 17 | .clear() 18 | .apply() 19 | 20 | } 21 | 22 | fun overrideAppBindings(init: Kodein.Builder.() -> Unit) { 23 | app.setKodein(Kodein { 24 | import(appModule(), allowOverride = true) 25 | import(blockedInteractorModule(), allowOverride = true) 26 | init() 27 | }) 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/usershow/detail/UserShowDetailService.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.usershow.detail 2 | 3 | import com.hana053.micropost.domain.User 4 | import com.hana053.micropost.interactor.UserInteractor 5 | import com.hana053.micropost.service.HttpErrorHandler 6 | import rx.Observable 7 | import rx.android.schedulers.AndroidSchedulers 8 | import rx.schedulers.Schedulers 9 | 10 | 11 | class UserShowDetailService( 12 | private val userInteractor: UserInteractor, 13 | private val httpErrorHandler: HttpErrorHandler 14 | ) { 15 | 16 | fun getUser(userId: Long): Observable = 17 | userInteractor.get(userId) 18 | .subscribeOn(Schedulers.newThread()) 19 | .observeOn(AndroidSchedulers.mainThread()) 20 | .doOnError { httpErrorHandler.handleError(it) } 21 | .onErrorResumeNext { Observable.empty() } 22 | 23 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/login/LoginView.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.login 2 | 3 | import android.view.ViewGroup 4 | import com.hana053.micropost.pages.ViewWrapper 5 | import com.jakewharton.rxbinding.view.clicks 6 | import com.jakewharton.rxbinding.view.enabled 7 | import com.jakewharton.rxbinding.widget.textChanges 8 | import kotlinx.android.synthetic.main.activity_login.view.* 9 | import rx.Observable 10 | 11 | class LoginView(override val content: ViewGroup) : ViewWrapper { 12 | 13 | // Events 14 | val emailChanges: Observable = content.et_email 15 | .textChanges() 16 | .map { it.toString() } 17 | val passwordChanges: Observable = content.et_password 18 | .textChanges() 19 | .map { it.toString() } 20 | val loginClicks = content.btn_login.clicks() 21 | 22 | // Props 23 | val loginEnabled = content.btn_login.enabled() 24 | } 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/email/SignupFullNameModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup.email 2 | 3 | import android.app.Activity 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.github.salomonbrys.kodein.android.androidActivityScope 6 | import com.github.salomonbrys.kodein.autoScopedSingleton 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import kotlinx.android.synthetic.main.fragment_signup_email.* 10 | 11 | 12 | fun signupEmailModule() = Kodein.Module { 13 | 14 | bind() with autoScopedSingleton(androidActivityScope) { 15 | SignupEmailPresenter(instance(), instance(), instance()) 16 | } 17 | 18 | bind() with autoScopedSingleton(androidActivityScope) { 19 | instance().fragment_signup_email.let(::SignupEmailView) 20 | } 21 | 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/androidTest/kotlin/com/hana053/micropost/testing/EspressoHelpers.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.testing 2 | 3 | import android.support.test.espresso.matcher.BoundedMatcher 4 | import android.support.v7.widget.RecyclerView 5 | import android.view.View 6 | import org.hamcrest.Description 7 | import org.hamcrest.Matcher 8 | 9 | 10 | fun atPositionOnView( 11 | position: Int, 12 | matcher: Matcher, 13 | targetId: Int 14 | ) = object : BoundedMatcher(RecyclerView::class.java) { 15 | 16 | override fun matchesSafely(recyclerView: RecyclerView) = 17 | recyclerView.findViewHolderForAdapterPosition(position) 18 | .itemView 19 | .findViewById(targetId) 20 | .let { matcher.matches(it) } 21 | 22 | override fun describeTo(description: Description?) { 23 | description?.appendText("has view id $matcher at position $position") 24 | } 25 | } 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/fullname/SignupFullNameModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup.fullname 2 | 3 | import android.app.Activity 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.github.salomonbrys.kodein.android.androidActivityScope 6 | import com.github.salomonbrys.kodein.autoScopedSingleton 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import kotlinx.android.synthetic.main.fragment_signup_full_name.* 10 | 11 | 12 | fun signupFullNameModule() = Kodein.Module { 13 | 14 | bind() with autoScopedSingleton(androidActivityScope) { 15 | SignupFullNamePresenter(instance(), instance(), instance()) 16 | } 17 | 18 | bind() with autoScopedSingleton(androidActivityScope) { 19 | instance().fragment_signup_full_name.let(::SignupFullNameView) 20 | } 21 | 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/password/SignupPasswordModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup.password 2 | 3 | import android.app.Activity 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.github.salomonbrys.kodein.android.androidActivityScope 6 | import com.github.salomonbrys.kodein.autoScopedSingleton 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import kotlinx.android.synthetic.main.fragment_signup_password.* 10 | 11 | 12 | fun signupPasswordModule() = Kodein.Module { 13 | 14 | bind() with autoScopedSingleton(androidActivityScope) { 15 | SignupPasswordPresenter(instance(), instance(), instance(), instance()) 16 | } 17 | 18 | bind() with autoScopedSingleton(androidActivityScope) { 19 | instance().fragment_signup_password.let(::SignupPasswordView) 20 | } 21 | 22 | } 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/test/kotlin/com/hana053/micropost/repository/AuthTokenRepositoryTest.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.repository 2 | 3 | import android.preference.PreferenceManager 4 | import com.hana053.micropost.testing.RobolectricBaseTest 5 | import org.assertj.core.api.Assertions.assertThat 6 | import org.junit.Test 7 | 8 | class AuthTokenRepositoryTest : RobolectricBaseTest() { 9 | 10 | private val sharedPreferences = PreferenceManager.getDefaultSharedPreferences(app) 11 | private val authTokenRepository = AuthTokenRepositoryImpl(sharedPreferences) 12 | 13 | @Test 14 | fun shouldSaveAuthToken() { 15 | authTokenRepository.set("my token") 16 | assertThat(authTokenRepository.get()).isEqualTo("my token") 17 | } 18 | 19 | @Test 20 | fun shouldClearAuthToken() { 21 | authTokenRepository.set("my token") 22 | authTokenRepository.clear() 23 | assertThat(authTokenRepository.get()).isNullOrEmpty() 24 | } 25 | 26 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/micropostnew/MicropostNewService.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.micropostnew 2 | 3 | import com.hana053.micropost.domain.Micropost 4 | import com.hana053.micropost.interactor.MicropostInteractor 5 | import com.hana053.micropost.service.HttpErrorHandler 6 | import rx.Observable 7 | import rx.android.schedulers.AndroidSchedulers 8 | import rx.schedulers.Schedulers 9 | 10 | 11 | class MicropostNewService( 12 | private val micropostInteractor: MicropostInteractor, 13 | private val httpErrorHandler: HttpErrorHandler 14 | ) { 15 | 16 | fun createPost(content: String): Observable { 17 | return micropostInteractor 18 | .create(MicropostInteractor.MicropostRequest(content)) 19 | .subscribeOn(Schedulers.newThread()) 20 | .observeOn(AndroidSchedulers.mainThread()) 21 | .doOnError { httpErrorHandler.handleError(it) } 22 | .onErrorResumeNext(Observable.empty()) 23 | } 24 | } -------------------------------------------------------------------------------- /app/src/main/res/values/dimens.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 16dp 4 | 16dp 5 | 6 | 12dp 7 | 16dp 8 | 20dp 9 | 24dp 10 | 11 | 12 | 11sp 13 | 12sp 14 | 14sp 15 | 18sp 16 | 20sp 17 | 24sp 18 | 6sp 19 | 20 | 48dp 21 | 64dp 22 | 23 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_user_show.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 13 | 14 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/service/ServiceModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.service 2 | 3 | import com.github.salomonbrys.kodein.Kodein 4 | import com.github.salomonbrys.kodein.android.androidActivityScope 5 | import com.github.salomonbrys.kodein.autoScopedSingleton 6 | import com.github.salomonbrys.kodein.bind 7 | import com.github.salomonbrys.kodein.instance 8 | 9 | fun serviceModule() = Kodein.Module { 10 | 11 | bind() with autoScopedSingleton(androidActivityScope) { 12 | NavigatorImpl(instance()) 13 | } 14 | 15 | bind() with autoScopedSingleton(androidActivityScope) { 16 | HttpErrorHandlerImpl(instance(), instance()) 17 | } 18 | 19 | bind() with autoScopedSingleton(androidActivityScope) { 20 | AuthServiceImpl(instance(), instance()) 21 | } 22 | 23 | bind() with autoScopedSingleton(androidActivityScope) { 24 | LoginServiceImpl(instance(), instance(), instance(), instance()) 25 | } 26 | 27 | } 28 | 29 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/relateduserlist/RelatedUserListView.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.relateduserlist 2 | 3 | import android.support.v7.widget.LinearLayoutManager 4 | import android.view.ViewGroup 5 | import com.hana053.micropost.pages.ViewWrapper 6 | import com.jakewharton.rxbinding.support.v7.widget.RecyclerViewScrollEvent 7 | import com.jakewharton.rxbinding.support.v7.widget.scrollEvents 8 | import kotlinx.android.synthetic.main.activity_related_user_list.view.* 9 | import rx.Observable 10 | 11 | 12 | class RelatedUserListView( 13 | override val content: ViewGroup, 14 | relatedUserListAdapter: RelatedUserListAdapter 15 | ) : ViewWrapper { 16 | 17 | private val listUser = content.list_user 18 | 19 | // Events 20 | val scrollsToBottom: Observable = listUser.scrollEvents() 21 | .filter { !listUser.canScrollVertically(1) } 22 | 23 | init { 24 | listUser.layoutManager = LinearLayoutManager(context()) 25 | listUser.adapter = relatedUserListAdapter 26 | } 27 | 28 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/usershow/UserShowModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.usershow 2 | 3 | import android.app.Activity 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.github.salomonbrys.kodein.android.androidActivityScope 6 | import com.github.salomonbrys.kodein.autoScopedSingleton 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import com.hana053.micropost.pages.usershow.UserShowActivity.Companion.KEY_USER_ID 10 | import com.hana053.micropost.pages.usershow.detail.userShowDetailModule 11 | import com.hana053.micropost.pages.usershow.posts.userShowPostsModule 12 | import com.hana053.micropost.shared.followbtn.followBtnModule 13 | 14 | fun userShowModule() = Kodein.Module { 15 | 16 | bind(KEY_USER_ID) with autoScopedSingleton(androidActivityScope) { 17 | instance().intent.extras.getLong(KEY_USER_ID) 18 | } 19 | 20 | import(userShowDetailModule()) 21 | import(userShowPostsModule()) 22 | import(followBtnModule()) 23 | 24 | } 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/service/AuthServiceImpl.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.service 2 | 3 | import com.hana053.micropost.domain.User 4 | import com.hana053.micropost.repository.AuthTokenRepository 5 | import com.nimbusds.jose.JWSObject 6 | 7 | 8 | class AuthServiceImpl( 9 | private val authTokenRepository: AuthTokenRepository, 10 | private val navigator: Navigator 11 | ) : AuthService { 12 | 13 | override fun isMyself(user: User): Boolean { 14 | val authToken = authTokenRepository.get() ?: return false 15 | return user.id == JWSObject.parse(authToken) 16 | .payload 17 | .toJSONObject()["sub"] 18 | .toString() 19 | .toLong() 20 | 21 | } 22 | 23 | override fun logout() { 24 | authTokenRepository.clear() 25 | navigator.navigateToTop() 26 | } 27 | 28 | override fun auth(): Boolean { 29 | if (authTokenRepository.get().isNullOrBlank()) { 30 | logout() 31 | return false 32 | } 33 | return true 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/micropostnew/MicropostNewPresenter.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.micropostnew 2 | 3 | import com.hana053.micropost.pages.Presenter 4 | 5 | 6 | class MicropostNewPresenter( 7 | override val view: MicropostNewView, 8 | private val micropostNewService: MicropostNewService, 9 | private val micropostNewNavigator: MicropostNewNavigator 10 | ) : Presenter { 11 | 12 | override fun bind() { 13 | val postTextChanges = view.postTextChanges.share() 14 | 15 | postTextChanges 16 | .bindToLifecycle() 17 | .map { it.isNotBlank() } 18 | .subscribe { view.postBtnEnabled.call(it) } 19 | 20 | view.postBtnClicks 21 | .bindToLifecycle() 22 | .withLatestFrom(postTextChanges, { click, text -> text }) 23 | .flatMap { 24 | micropostNewService.createPost(it.toString()) 25 | .withProgressDialog() 26 | } 27 | .subscribe { micropostNewNavigator.finishWithPost() } 28 | } 29 | 30 | } -------------------------------------------------------------------------------- /app/src/main/res/values-v21/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 18 | 19 | 22 | 23 | 26 | 27 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/login/LoginActivity.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.login 2 | 3 | import android.os.Bundle 4 | import com.github.salomonbrys.kodein.KodeinInjector 5 | import com.github.salomonbrys.kodein.android.AppCompatActivityInjector 6 | import com.github.salomonbrys.kodein.instance 7 | import com.hana053.micropost.R 8 | import com.hana053.micropost.getOverridingModule 9 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity 10 | 11 | class LoginActivity : RxAppCompatActivity(), AppCompatActivityInjector { 12 | 13 | override val injector: KodeinInjector = KodeinInjector() 14 | 15 | private val presenter: LoginPresenter by instance() 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | setContentView(R.layout.activity_login) 20 | initializeInjector() 21 | 22 | presenter.bind() 23 | } 24 | 25 | override fun onDestroy() { 26 | super.onDestroy() 27 | destroyInjector() 28 | } 29 | 30 | override fun provideOverridingModule() = getOverridingModule() 31 | } 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/top/TopActivity.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.top 2 | 3 | import android.os.Bundle 4 | import com.github.salomonbrys.kodein.KodeinInjector 5 | import com.github.salomonbrys.kodein.android.AppCompatActivityInjector 6 | import com.github.salomonbrys.kodein.instance 7 | import com.hana053.micropost.R 8 | import com.hana053.micropost.getOverridingModule 9 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity 10 | 11 | 12 | class TopActivity : RxAppCompatActivity(), AppCompatActivityInjector { 13 | 14 | override val injector: KodeinInjector = KodeinInjector() 15 | 16 | private val presenter: TopPresenter by instance() 17 | 18 | override fun onCreate(savedInstanceState: Bundle?) { 19 | super.onCreate(savedInstanceState) 20 | setContentView(R.layout.activity_top) 21 | initializeInjector() 22 | 23 | presenter.bind() 24 | } 25 | 26 | override fun onDestroy() { 27 | super.onDestroy() 28 | destroyInjector() 29 | } 30 | 31 | override fun provideOverridingModule() = getOverridingModule() 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Akira Sosa 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: android 2 | jdk: 3 | - oraclejdk8 4 | env: 5 | global: 6 | - GRADLE_OPTS="-Xmx512m -XX:MaxPermSize=512m" 7 | - MALLOC_ARENA_MAX=2 8 | android: 9 | components: 10 | - platform-tools 11 | - tools 12 | - build-tools-25.0.2 13 | - android-22 14 | - android-25 15 | - extra-android-support 16 | - extra-google-m2repository 17 | - extra-android-m2repository 18 | - sys-img-armeabi-v7a-android-22 19 | 20 | before_script: 21 | - cp gradle.properties.ci.example gradle.properties 22 | # Emulator Management: Create, Start and Wait 23 | - echo no | android create avd --force -n test -t android-22 --abi armeabi-v7a 24 | - emulator -avd test -no-audio -no-window & 25 | - android-wait-for-emulator 26 | - adb shell input keyevent 82 & 27 | 28 | script: 29 | - ./gradlew clean connectedAndroidTest 30 | - ./gradlew testDebugUnitTest 31 | 32 | before_cache: 33 | - rm -f $HOME/.gradle/caches/modules-2/modules-2.lock 34 | - rm -fr $HOME/.gradle/caches/*/plugin-resolution/ 35 | cache: 36 | directories: 37 | - $HOME/.gradle/caches/ 38 | - $HOME/.gradle/wrapper/ 39 | - $HOME/.android/build-cache -------------------------------------------------------------------------------- /app/src/test/kotlin/com/hana053/micropost/testing/TestSchedulerProxy.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.testing 2 | 3 | import rx.plugins.RxJavaHooks 4 | import rx.schedulers.TestScheduler 5 | import java.util.concurrent.TimeUnit 6 | 7 | internal class TestSchedulerProxy { 8 | 9 | fun advanceBy(delayTime: Long, unit: TimeUnit) { 10 | SCHEDULER.advanceTimeBy(delayTime, unit) 11 | } 12 | 13 | fun advance() { 14 | advanceBy(1, TimeUnit.SECONDS) 15 | } 16 | 17 | companion object { 18 | private val SCHEDULER = TestScheduler() 19 | private val INSTANCE = TestSchedulerProxy() 20 | 21 | init { 22 | try { 23 | RxJavaHooks.setOnIOScheduler { SCHEDULER } 24 | RxJavaHooks.setOnComputationScheduler { SCHEDULER } 25 | RxJavaHooks.setOnNewThreadScheduler { SCHEDULER } 26 | } catch (e: IllegalStateException) { 27 | throw IllegalStateException("Schedulers class already initialized. " + "Ensure you always use the TestSchedulerProxy in unit tests.") 28 | } 29 | } 30 | 31 | fun get() = INSTANCE 32 | } 33 | 34 | } 35 | 36 | -------------------------------------------------------------------------------- /app/src/test/kotlin/com/hana053/micropost/testing/RobolectricBaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.testing 2 | 3 | import android.os.Build 4 | import com.hana053.micropost.Application 5 | import com.hana053.micropost.BaseApplication 6 | import com.hana053.micropost.BuildConfig 7 | import org.junit.runner.RunWith 8 | import org.robolectric.RobolectricTestRunner 9 | import org.robolectric.RuntimeEnvironment 10 | import org.robolectric.annotation.Config 11 | import org.robolectric.shadows.ShadowLog 12 | import org.junit.Before 13 | 14 | 15 | @RunWith(RobolectricTestRunner::class) 16 | @Config( 17 | constants = BuildConfig::class, 18 | packageName = "com.hana053.micropost", 19 | application = Application::class, 20 | sdk = intArrayOf(Build.VERSION_CODES.LOLLIPOP) 21 | ) 22 | abstract class RobolectricBaseTest : InjectableTest { 23 | 24 | @Before 25 | fun setupLog() { 26 | ShadowLog.stream = System.out 27 | } 28 | 29 | override val app = RuntimeEnvironment.application as BaseApplication 30 | 31 | private val testScheduler = TestSchedulerProxy.get() 32 | 33 | fun advance() { 34 | testScheduler.advance() 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/usershow/detail/UserShowDetailModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.usershow.detail 2 | 3 | import android.app.Activity 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.github.salomonbrys.kodein.android.androidActivityScope 6 | import com.github.salomonbrys.kodein.autoScopedSingleton 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import com.hana053.micropost.pages.usershow.UserShowActivity.Companion.KEY_USER_ID 10 | import kotlinx.android.synthetic.main._user_detail.* 11 | 12 | fun userShowDetailModule() = Kodein.Module { 13 | 14 | bind() with autoScopedSingleton(androidActivityScope) { 15 | UserShowDetailService(instance(), instance()) 16 | } 17 | 18 | bind() with autoScopedSingleton(androidActivityScope) { 19 | instance()._user_detail.let(::UserShowDetailView) 20 | } 21 | 22 | bind() with autoScopedSingleton(androidActivityScope) { 23 | UserShowDetailPresenter(instance(), instance(KEY_USER_ID), instance(), instance(), instance()) 24 | } 25 | 26 | } 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/main/MainModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.main 2 | 3 | import android.app.Activity 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.github.salomonbrys.kodein.android.androidActivityScope 6 | import com.github.salomonbrys.kodein.autoScopedSingleton 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import com.hana053.micropost.shared.posts.PostListAdapter 10 | import kotlinx.android.synthetic.main.activity_main.* 11 | 12 | fun mainModule() = Kodein.Module { 13 | 14 | bind() with autoScopedSingleton(androidActivityScope) { 15 | PostListAdapter() 16 | } 17 | 18 | bind() with autoScopedSingleton(androidActivityScope) { 19 | MainPresenter(instance(), instance(), instance(), instance()) 20 | } 21 | 22 | bind() with autoScopedSingleton(androidActivityScope) { 23 | instance().activity_main 24 | .let { MainView(it, instance()) } 25 | } 26 | 27 | bind() with autoScopedSingleton(androidActivityScope) { 28 | MainService(instance(), instance(), instance()) 29 | } 30 | 31 | } 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/micropostnew/MIcropostNewModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.micropostnew 2 | 3 | import android.app.Activity 4 | import com.github.salomonbrys.kodein.Kodein 5 | import com.github.salomonbrys.kodein.android.androidActivityScope 6 | import com.github.salomonbrys.kodein.autoScopedSingleton 7 | import com.github.salomonbrys.kodein.bind 8 | import com.github.salomonbrys.kodein.instance 9 | import kotlinx.android.synthetic.main.activity_micropost_new.* 10 | 11 | 12 | fun micropostNewModule() = Kodein.Module { 13 | 14 | bind() with autoScopedSingleton(androidActivityScope) { 15 | MicropostNewPresenter(instance(), instance(), instance()) 16 | } 17 | 18 | bind() with autoScopedSingleton(androidActivityScope) { 19 | instance().activity_micropost_new.let(::MicropostNewView) 20 | } 21 | 22 | bind() with autoScopedSingleton(androidActivityScope) { 23 | MicropostNewService(instance(), instance()) 24 | } 25 | 26 | bind() with autoScopedSingleton(androidActivityScope) { 27 | MicropostNewNavigator(instance()) 28 | } 29 | 30 | } 31 | 32 | 33 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/SignupModule.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup 2 | 3 | import com.github.salomonbrys.kodein.Kodein 4 | import com.github.salomonbrys.kodein.android.androidActivityScope 5 | import com.github.salomonbrys.kodein.autoScopedSingleton 6 | import com.github.salomonbrys.kodein.bind 7 | import com.github.salomonbrys.kodein.instance 8 | import com.hana053.micropost.pages.signup.email.signupEmailModule 9 | import com.hana053.micropost.pages.signup.fullname.signupFullNameModule 10 | import com.hana053.micropost.pages.signup.password.signupPasswordModule 11 | 12 | fun signupModule() = Kodein.Module { 13 | 14 | bind() with autoScopedSingleton(androidActivityScope) { 15 | SignupState() 16 | } 17 | 18 | bind() with autoScopedSingleton(androidActivityScope) { 19 | SignupNavigatorImpl(instance()) 20 | } 21 | 22 | bind() with autoScopedSingleton(androidActivityScope) { 23 | SignupServiceImpl(instance(), instance(), instance(), instance(), instance()) 24 | } 25 | 26 | import(signupFullNameModule()) 27 | import(signupEmailModule()) 28 | import(signupPasswordModule()) 29 | 30 | } 31 | 32 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 | 8 | 9 | 17 | 18 | 24 | 25 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/usershow/posts/UserShowPostsService.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.usershow.posts 2 | 3 | import com.hana053.micropost.domain.Micropost 4 | import com.hana053.micropost.interactor.UserMicropostInteractor 5 | import com.hana053.micropost.service.HttpErrorHandler 6 | import com.hana053.micropost.shared.posts.PostListAdapter 7 | import rx.Observable 8 | import rx.android.schedulers.AndroidSchedulers 9 | import rx.schedulers.Schedulers 10 | 11 | 12 | class UserShowPostsService( 13 | private val postListAdapter: PostListAdapter, 14 | private val userMicropostInteractor: UserMicropostInteractor, 15 | private val httpErrorHandler: HttpErrorHandler 16 | ) { 17 | 18 | fun loadPosts(userId: Long): Observable> { 19 | val maxId = postListAdapter.getLastItemId() 20 | val itemCount = postListAdapter.itemCount 21 | 22 | return userMicropostInteractor.loadPrevPosts(userId, maxId) 23 | .subscribeOn(Schedulers.newThread()) 24 | .observeOn(AndroidSchedulers.mainThread()) 25 | .doOnNext { postListAdapter.addAll(itemCount, it) } 26 | .doOnError { httpErrorHandler.handleError(it) } 27 | .onErrorResumeNext { Observable.empty() } 28 | } 29 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/email/SignupEmailFragment.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup.email 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.github.salomonbrys.kodein.KodeinInjector 8 | import com.github.salomonbrys.kodein.android.SupportFragmentInjector 9 | import com.github.salomonbrys.kodein.instance 10 | import com.hana053.micropost.R 11 | import com.trello.rxlifecycle.components.support.RxFragment 12 | 13 | 14 | class SignupEmailFragment : RxFragment(), SupportFragmentInjector { 15 | 16 | override val injector: KodeinInjector = KodeinInjector() 17 | 18 | private val presenter: SignupEmailPresenter by instance() 19 | 20 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = 21 | inflater.inflate(R.layout.fragment_signup_email, container, false) 22 | 23 | override fun onActivityCreated(savedInstanceState: Bundle?) { 24 | super.onActivityCreated(savedInstanceState) 25 | initializeInjector() 26 | 27 | presenter.bind() 28 | } 29 | 30 | override fun onDestroyView() { 31 | super.onDestroyView() 32 | destroyInjector() 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/fullname/SignupFullNameFragment.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup.fullname 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.github.salomonbrys.kodein.KodeinInjector 8 | import com.github.salomonbrys.kodein.android.SupportFragmentInjector 9 | import com.github.salomonbrys.kodein.instance 10 | import com.hana053.micropost.R 11 | import com.trello.rxlifecycle.components.support.RxFragment 12 | 13 | 14 | class SignupFullNameFragment : RxFragment(), SupportFragmentInjector { 15 | 16 | override val injector: KodeinInjector = KodeinInjector() 17 | 18 | private val presenter: SignupFullNamePresenter by instance() 19 | 20 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = 21 | inflater.inflate(R.layout.fragment_signup_full_name, container, false) 22 | 23 | override fun onActivityCreated(savedInstanceState: Bundle?) { 24 | super.onActivityCreated(savedInstanceState) 25 | initializeInjector() 26 | 27 | presenter.bind() 28 | } 29 | 30 | override fun onDestroy() { 31 | super.onDestroy() 32 | destroyInjector() 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/Presenter.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages 2 | 3 | import android.view.Gravity 4 | import android.view.View 5 | import android.widget.ProgressBar 6 | import android.widget.RelativeLayout 7 | import com.trello.rxlifecycle.kotlin.bindToLifecycle 8 | import rx.Observable 9 | 10 | 11 | interface Presenter { 12 | 13 | val view: T 14 | 15 | fun bind() 16 | 17 | fun Observable.withProgressDialog(): Observable = Observable.using({ 18 | val progressBar = ProgressBar(view.content.context, null, android.R.attr.progressBarStyle).apply { 19 | isIndeterminate = true 20 | visibility = View.VISIBLE 21 | } 22 | val rl = RelativeLayout(view.content.context).apply { 23 | gravity = Gravity.CENTER 24 | addView(progressBar) 25 | } 26 | val layoutParams = RelativeLayout.LayoutParams( 27 | RelativeLayout.LayoutParams.MATCH_PARENT, 28 | RelativeLayout.LayoutParams.MATCH_PARENT 29 | ) 30 | view.content.addView(rl, layoutParams) 31 | progressBar 32 | }, { this }, { 33 | it.visibility = View.GONE 34 | }) 35 | 36 | fun Observable.bindToLifecycle(): Observable = bindToLifecycle(view.content) 37 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/SignupNavigatorImpl.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup 2 | 3 | import android.support.v4.app.FragmentActivity 4 | import com.hana053.micropost.R 5 | import com.hana053.micropost.pages.signup.email.SignupEmailFragment 6 | import com.hana053.micropost.pages.signup.password.SignupPasswordFragment 7 | 8 | 9 | class SignupNavigatorImpl( 10 | private val activity: FragmentActivity 11 | ) : SignupNavigator { 12 | 13 | override fun navigateToEmail() { 14 | activity.supportFragmentManager 15 | .beginTransaction() 16 | .replace(R.id.container, SignupEmailFragment()) 17 | .addToBackStack(null) 18 | .commit() 19 | } 20 | 21 | override fun navigateToPassword() { 22 | activity.supportFragmentManager 23 | .beginTransaction() 24 | .replace(R.id.container, SignupPasswordFragment()) 25 | .addToBackStack(null) 26 | .commit() 27 | } 28 | 29 | override fun navigateToPrev() { 30 | activity.supportFragmentManager.backStackEntryCount.let { 31 | when (it) { 32 | 0 -> activity.finish() 33 | else -> activity.supportFragmentManager.popBackStack() 34 | } 35 | } 36 | } 37 | 38 | } -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/signup/password/SignupPasswordFragment.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.signup.password 2 | 3 | import android.os.Bundle 4 | import android.view.LayoutInflater 5 | import android.view.View 6 | import android.view.ViewGroup 7 | import com.github.salomonbrys.kodein.KodeinInjector 8 | import com.github.salomonbrys.kodein.android.SupportFragmentInjector 9 | import com.github.salomonbrys.kodein.instance 10 | import com.hana053.micropost.R 11 | import com.trello.rxlifecycle.components.support.RxFragment 12 | 13 | 14 | class SignupPasswordFragment : RxFragment(), SupportFragmentInjector { 15 | 16 | override val injector: KodeinInjector = KodeinInjector() 17 | 18 | private val presenter: SignupPasswordPresenter by instance() 19 | 20 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? = 21 | inflater.inflate(R.layout.fragment_signup_password, container, false) 22 | 23 | override fun onActivityCreated(savedInstanceState: Bundle?) { 24 | super.onActivityCreated(savedInstanceState) 25 | initializeInjector() 26 | 27 | presenter.bind() 28 | } 29 | 30 | override fun onDestroyView() { 31 | super.onDestroyView() 32 | destroyInjector() 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /app/src/testShared/kotlin/com/hana053/micropost/testing/TestUtil.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.testing 2 | 3 | import com.github.salomonbrys.kodein.Kodein 4 | import com.github.salomonbrys.kodein.bind 5 | import com.github.salomonbrys.kodein.instance 6 | import com.hana053.micropost.domain.Micropost 7 | import com.hana053.micropost.domain.RelatedUser 8 | import com.hana053.micropost.domain.User 9 | import com.hana053.micropost.domain.UserStats 10 | import com.hana053.micropost.repository.AuthTokenRepository 11 | import com.nhaarman.mockito_kotlin.doReturn 12 | import com.nhaarman.mockito_kotlin.mock 13 | 14 | val TestUserStats = UserStats(1, 2, 3) 15 | val TestUser = User(1, "John Doe", "test@test.com", "hash", false, TestUserStats) 16 | val TestMicropost = Micropost(1, "content", 0, TestUser) 17 | val TestRelatedUser = RelatedUser(1, "John Doe", "test@test.com", "hash", false, TestUserStats, 1) 18 | 19 | // This token has userId = 1. 20 | val jwtForUserId1 = "eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiIxIiwiZXhwIjoxNDc5NDUwNDY0fQ.Dy33qbg6EnP1bL2DmItMNGDEunrYP7-rzf586wxb2D-wW8WCsFrKdCeCU_ZHq_A7-kg_LxBykyaoG_26z-k9uA" 21 | 22 | fun Kodein.Builder.fakeAuthToken(token: String = jwtForUserId1) { 23 | bind(overrides = true) with instance(mock { 24 | on { get() } doReturn token 25 | }) 26 | } 27 | 28 | -------------------------------------------------------------------------------- /app/src/main/kotlin/com/hana053/micropost/pages/micropostnew/MicropostNewActivity.kt: -------------------------------------------------------------------------------- 1 | package com.hana053.micropost.pages.micropostnew 2 | 3 | import android.os.Bundle 4 | import com.github.salomonbrys.kodein.KodeinInjector 5 | import com.github.salomonbrys.kodein.android.AppCompatActivityInjector 6 | import com.github.salomonbrys.kodein.instance 7 | import com.hana053.micropost.R 8 | import com.hana053.micropost.getOverridingModule 9 | import com.hana053.micropost.service.AuthService 10 | import com.trello.rxlifecycle.components.support.RxAppCompatActivity 11 | 12 | 13 | class MicropostNewActivity : RxAppCompatActivity(), AppCompatActivityInjector { 14 | 15 | override val injector: KodeinInjector = KodeinInjector() 16 | 17 | private val authService: AuthService by instance() 18 | private val presenter: MicropostNewPresenter by instance() 19 | 20 | override fun onCreate(savedInstanceState: Bundle?) { 21 | super.onCreate(savedInstanceState) 22 | setContentView(R.layout.activity_micropost_new) 23 | initializeInjector() 24 | 25 | if (!authService.auth()) return 26 | 27 | presenter.bind() 28 | } 29 | 30 | override fun onDestroy() { 31 | super.onDestroy() 32 | destroyInjector() 33 | } 34 | 35 | override fun provideOverridingModule() = getOverridingModule() 36 | } -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_micropost_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 17 | 18 | 19 | 20 | 21 | 26 | 27 |