├── app
├── .gitignore
├── src
│ ├── main
│ │ ├── res
│ │ │ ├── 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
│ │ │ ├── values
│ │ │ │ ├── colors.xml
│ │ │ │ ├── styles.xml
│ │ │ │ └── strings.xml
│ │ │ ├── layout
│ │ │ │ ├── empty_view_example.xml
│ │ │ │ ├── fragment_recycler_example.xml
│ │ │ │ ├── item_recycler_example.xml
│ │ │ │ ├── fragment_login.xml
│ │ │ │ ├── fragment_onboarding_page.xml
│ │ │ │ └── fragment_onboarding.xml
│ │ │ └── drawable
│ │ │ │ └── primary_btn_background.xml
│ │ ├── java
│ │ │ └── com
│ │ │ │ └── xmartlabs
│ │ │ │ └── template
│ │ │ │ ├── model
│ │ │ │ ├── Session.kt
│ │ │ │ ├── common
│ │ │ │ │ ├── BuildType.kt
│ │ │ │ │ └── BuildInfo.kt
│ │ │ │ └── AuthResponse.kt
│ │ │ │ ├── ui
│ │ │ │ ├── recyclerfragmentexample
│ │ │ │ │ ├── RecyclerExampleView.kt
│ │ │ │ │ ├── RecyclerExamplePresenter.kt
│ │ │ │ │ ├── RecyclerExampleActivity.kt
│ │ │ │ │ ├── RecyclerExampleAdapter.kt
│ │ │ │ │ └── RecyclerExampleFragment.kt
│ │ │ │ ├── login
│ │ │ │ │ ├── LoginView.kt
│ │ │ │ │ ├── LoginActivity.kt
│ │ │ │ │ ├── LoginPresenter.kt
│ │ │ │ │ └── LoginFragment.kt
│ │ │ │ ├── common
│ │ │ │ │ ├── TemplatePresenter.kt
│ │ │ │ │ ├── TemplateView.kt
│ │ │ │ │ └── TemplateFragment.kt
│ │ │ │ ├── onboarding
│ │ │ │ │ ├── OnboardingActivity.kt
│ │ │ │ │ ├── OnboardingView.kt
│ │ │ │ │ ├── OnboardingPageAdapter.kt
│ │ │ │ │ ├── page
│ │ │ │ │ │ ├── OnboardingPage.kt
│ │ │ │ │ │ └── OnboardingPageFragment.kt
│ │ │ │ │ ├── OnboardingPresenter.kt
│ │ │ │ │ └── OnboardingFragment.kt
│ │ │ │ └── StartActivity.kt
│ │ │ │ ├── database
│ │ │ │ └── AppDataBase.kt
│ │ │ │ ├── di
│ │ │ │ ├── RestServiceModuleApi.kt
│ │ │ │ ├── AppModule.kt
│ │ │ │ ├── OkHttpModule.kt
│ │ │ │ ├── RestServiceModule.kt
│ │ │ │ ├── ServiceGsonModule.kt
│ │ │ │ ├── ControllerModule.kt
│ │ │ │ ├── FragmentBuildersModule.kt
│ │ │ │ ├── ActivityModule.kt
│ │ │ │ └── ApplicationComponent.kt
│ │ │ │ ├── helper
│ │ │ │ ├── EmptyOnPageChangeListener.kt
│ │ │ │ ├── GeneralSingleSubscriber.kt
│ │ │ │ ├── GeneralCompletableSubscriber.kt
│ │ │ │ ├── GeneralObservableSubscriber.kt
│ │ │ │ └── GeneralFlowableSubscriber.kt
│ │ │ │ ├── service
│ │ │ │ └── AuthService.kt
│ │ │ │ ├── controller
│ │ │ │ ├── SessionController.kt
│ │ │ │ └── AuthController.kt
│ │ │ │ └── App.kt
│ │ └── AndroidManifest.xml
│ ├── staging
│ │ └── res
│ │ │ └── values
│ │ │ └── strings.xml
│ ├── androidTest
│ │ ├── AndroidManifest.xml
│ │ └── java
│ │ │ └── com
│ │ │ └── xmartlabs
│ │ │ └── template
│ │ │ ├── TestApplication.kt
│ │ │ ├── common
│ │ │ ├── SleepingAction.kt
│ │ │ ├── BaseInstrumentationTest.kt
│ │ │ ├── ImmediateNewThreadScheduler.kt
│ │ │ └── SingleActivityInstrumentationTest.kt
│ │ │ ├── uitest
│ │ │ └── sign
│ │ │ │ └── LoginInstrumentalTest.kt
│ │ │ ├── InstrumentalTestComponent.kt
│ │ │ └── TestRunner.kt
│ ├── sharedTest
│ │ └── java
│ │ │ └── com
│ │ │ └── xmartlabs
│ │ │ └── template
│ │ │ ├── controller
│ │ │ └── MockAuthController.kt
│ │ │ └── di
│ │ │ ├── MockRestServiceModule.kt
│ │ │ ├── MockClockModule.kt
│ │ │ └── MockControllerModule.kt
│ └── test
│ │ └── java
│ │ └── com
│ │ └── xmartlabs
│ │ └── template
│ │ ├── di
│ │ └── MockAndroidModule.kt
│ │ ├── tests
│ │ └── signin
│ │ │ └── SignInUnitTest.kt
│ │ └── common
│ │ ├── BaseUnitTest.kt
│ │ └── TestComponent.kt
├── proguard-rules.pro
├── detekt-config.yml
└── build.gradle
├── settings.gradle
├── .codebeatignore
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── detekt.gradle
├── README.md
├── gradle.properties
├── .github
└── PULL_REQUEST_TEMPLATE.md
├── .gitignore
├── circle.yml
├── gradlew.bat
└── gradlew
/app/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app'
2 |
--------------------------------------------------------------------------------
/.codebeatignore:
--------------------------------------------------------------------------------
1 | app/src/main/java/com/xmartlabs/template/ApplicationComponent.kt
2 | app/src/sharedTest
3 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xmartlabs/bigbang-template/HEAD/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xmartlabs/bigbang-template/HEAD/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xmartlabs/bigbang-template/HEAD/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xmartlabs/bigbang-template/HEAD/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xmartlabs/bigbang-template/HEAD/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xmartlabs/bigbang-template/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher_round.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/xmartlabs/bigbang-template/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/xmartlabs/bigbang-template/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/xmartlabs/bigbang-template/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/xmartlabs/bigbang-template/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/xmartlabs/bigbang-template/HEAD/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/model/Session.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.model
2 |
3 | import com.xmartlabs.bigbang.core.model.SessionType
4 |
5 | class Session(override var accessToken: String? = null) : SessionType
6 |
--------------------------------------------------------------------------------
/app/src/staging/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Template STA
3 | staging.template.com
4 |
5 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/recyclerfragmentexample/RecyclerExampleView.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.recyclerfragmentexample
2 |
3 | import com.xmartlabs.template.ui.common.TemplateView
4 |
5 | interface RecyclerExampleView : TemplateView
6 |
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Nov 09 12:41:32 UYT 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.10.2-all.zip
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/login/LoginView.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.login
2 |
3 | import com.xmartlabs.template.ui.common.TemplateView
4 |
5 | interface LoginView : TemplateView {
6 | fun setIsLoading(loading: Boolean)
7 | fun gotoRecyclerExampleActivity()
8 | }
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/model/common/BuildType.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.model.common
2 |
3 | import java.util.Locale
4 |
5 | enum class BuildType {
6 | STAGING,
7 | PRODUCTION,
8 | ;
9 |
10 | override fun toString() = name.toLowerCase(Locale.getDefault())
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/androidTest/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #4a90e2
4 | #3F51B5
5 | #303F9F
6 | @android:color/black
7 |
8 |
--------------------------------------------------------------------------------
/detekt.gradle:
--------------------------------------------------------------------------------
1 | detekt {
2 | version = "1.0.0.RC2"
3 | profile("main") {
4 | input = "$projectDir/src/main/"
5 | config = "$projectDir/detekt-config.yml"
6 | filters = ".*test.*,.*/resources/.*,.*/tmp/.*"
7 | output = "$projectDir/build/reports/detekt"
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/empty_view_example.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/recyclerfragmentexample/RecyclerExamplePresenter.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.recyclerfragmentexample
2 |
3 | import com.xmartlabs.template.ui.common.TemplatePresenter
4 | import javax.inject.Inject
5 |
6 | class RecyclerExamplePresenter @Inject constructor() : TemplatePresenter()
7 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/common/TemplatePresenter.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.common
2 |
3 | import com.xmartlabs.bigbang.ui.mvp.BaseMvpPresenter
4 |
5 | abstract class TemplatePresenter : BaseMvpPresenter() {
6 | override fun attachView(view: T) {
7 | super.attachView(view)
8 | view.setup()
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/login/LoginActivity.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.login
2 |
3 | import com.f2prateek.dart.HensonNavigable
4 | import com.xmartlabs.bigbang.ui.SingleFragmentActivity
5 |
6 | @HensonNavigable
7 | class LoginActivity : SingleFragmentActivity() {
8 | override fun createFragment() = LoginFragmentBuilder().build()
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/database/AppDataBase.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.database
2 |
3 | import com.raizlabs.android.dbflow.annotation.Database
4 |
5 | @Database(name = AppDataBase.NAME, version = AppDataBase.VERSION)
6 | object AppDataBase {
7 | //TODO: change db name
8 | const val NAME = "template_database"
9 | const val VERSION = 1
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/model/AuthResponse.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.model
2 |
3 | import org.parceler.Parcel
4 |
5 | //TODO: check service auth response to match app's AuthResponse fields
6 | @Parcel(Parcel.Serialization.BEAN)
7 | class AuthResponse {
8 | var accessToken: String? = null
9 | var scope: String? = null
10 | var tokenType: String? = null
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable/primary_btn_background.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
10 |
11 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/onboarding/OnboardingActivity.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.onboarding
2 |
3 | import com.f2prateek.dart.HensonNavigable
4 | import com.xmartlabs.bigbang.ui.SingleFragmentActivity
5 |
6 | @HensonNavigable
7 | class OnboardingActivity : SingleFragmentActivity() {
8 | override fun createFragment() = OnboardingFragmentBuilder().build()
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/recyclerfragmentexample/RecyclerExampleActivity.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.recyclerfragmentexample
2 |
3 | import com.f2prateek.dart.HensonNavigable
4 | import com.xmartlabs.bigbang.ui.SingleFragmentActivity
5 |
6 | @HensonNavigable
7 | class RecyclerExampleActivity : SingleFragmentActivity() {
8 | override fun createFragment() = RecyclerExampleFragmentBuilder().build()
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/di/RestServiceModuleApi.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import com.xmartlabs.template.service.AuthService
4 | import dagger.Module
5 | import dagger.Provides
6 | import retrofit2.Retrofit
7 | import javax.inject.Singleton
8 |
9 | @Module
10 | class RestServiceModuleApi {
11 | @Provides
12 | @Singleton
13 | internal fun provideAuthService(retrofit: Retrofit) = retrofit.create(AuthService::class.java)
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/helper/EmptyOnPageChangeListener.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.helper
2 |
3 | import android.support.v4.view.ViewPager
4 |
5 | open class EmptyOnPageChangeListener : ViewPager.OnPageChangeListener {
6 | override fun onPageScrollStateChanged(state: Int) = Unit
7 | override fun onPageScrolled(position: Int, positionOffset: Float, positionOffsetPixels: Int) = Unit
8 | override fun onPageSelected(position: Int) = Unit
9 | }
10 |
--------------------------------------------------------------------------------
/app/src/sharedTest/java/com/xmartlabs/template/controller/MockAuthController.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.controller
2 |
3 | import com.xmartlabs.template.service.AuthService
4 | import io.reactivex.Completable
5 | import javax.inject.Inject
6 |
7 | class MockAuthController @Inject constructor(authService: AuthService, sessionController: SessionController)
8 | : AuthController(authService, sessionController) {
9 | override fun signIn() = Completable.complete()
10 | }
11 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/di/AppModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import com.xmartlabs.template.model.common.BuildInfo
4 | import dagger.Module
5 | import dagger.Provides
6 | import javax.inject.Singleton
7 | import com.xmartlabs.bigbang.core.model.BuildInfo as CoreBuildInfo
8 |
9 | @Module
10 | class AppModule {
11 | @Provides
12 | @Singleton
13 | fun provideBuildInformation(coreBuildInfo : BuildInfo) : CoreBuildInfo = coreBuildInfo
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/login/LoginPresenter.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.login
2 |
3 | import com.xmartlabs.template.ui.common.TemplatePresenter
4 | import javax.inject.Inject
5 |
6 | class LoginPresenter @Inject constructor() : TemplatePresenter() {
7 | //TODO: Do something on login button clicked
8 | fun loginButtonClicked() = view?.gotoRecyclerExampleActivity()
9 |
10 | private fun setIsLoading(loading: Boolean) = view?.setIsLoading(loading)
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/sharedTest/java/com/xmartlabs/template/di/MockRestServiceModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import android.content.Context
4 | import com.xmartlabs.bigbang.retrofit.module.RestServiceModule
5 | import io.appflate.restmock.RESTMockServer
6 | import okhttp3.HttpUrl
7 |
8 | class MockRestServiceModule : RestServiceModule() {
9 | @Suppress("UnsafeCallOnNullableType")
10 | override fun provideBaseUrl(context: Context): HttpUrl = HttpUrl.parse(RESTMockServer.getUrl())!!
11 | }
12 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/model/common/BuildInfo.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.model.common
2 |
3 | import com.xmartlabs.template.BuildConfig
4 | import com.xmartlabs.bigbang.core.model.BuildInfo as CoreBuildInfo
5 |
6 | class BuildInfo : CoreBuildInfo {
7 | override val isDebug = BuildConfig.DEBUG
8 | override val isProduction: Boolean
9 | get() = BuildConfig.FLAVOR == BuildType.STAGING.toString()
10 | override val isStaging: Boolean
11 | get() = BuildConfig.FLAVOR == BuildType.PRODUCTION.toString()
12 | }
13 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/xmartlabs/template/TestApplication.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template
2 |
3 | import com.xmartlabs.template.di.ApplicationComponent
4 | import com.xmartlabs.template.di.MockRestServiceModule
5 | import com.xmartlabs.template.model.common.BuildInfo
6 |
7 | class TestApplication : App() {
8 | override fun createComponent(): ApplicationComponent = DaggerInstrumentalTestComponent.builder()
9 | .application(this)
10 | .buildInfo(BuildInfo())
11 | .restServiceModule(MockRestServiceModule())
12 | .build()
13 | }
14 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/common/TemplateView.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.common
2 |
3 | import android.support.annotation.StringRes
4 |
5 | import com.xmartlabs.bigbang.ui.mvp.MvpView
6 | import com.xmartlabs.template.R
7 |
8 | interface TemplateView : MvpView {
9 | fun setup()
10 | fun showError(@StringRes message: Int, @StringRes title: Int = R.string.error,
11 | @StringRes buttonTitle: Int = android.R.string.ok)
12 | fun showError(error: Throwable, @StringRes message: Int?)
13 | val isViewAlive: Boolean
14 | }
15 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/onboarding/OnboardingView.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.onboarding
2 |
3 | import android.content.Intent
4 |
5 | import com.xmartlabs.template.ui.common.TemplateView
6 |
7 | interface OnboardingView : TemplateView {
8 | fun createPageAdapter(): OnboardingPageAdapter
9 | fun setup(pageAdapter: OnboardingPageAdapter)
10 | fun setSkipButtonVisibility(visible: Boolean)
11 | fun moveToPage(page: Int)
12 | fun startActivity(intent: Intent)
13 | fun handleNextButtonVisibility()
14 | fun goToLoginActivity()
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/service/AuthService.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.service
2 |
3 | import com.xmartlabs.template.model.AuthResponse
4 |
5 | import io.reactivex.Single
6 | import retrofit2.http.POST
7 |
8 | interface AuthService {
9 | companion object {
10 | //TODO: replace with url path to get access token
11 | const val URL_ACCESS_TOKEN = "accessToken"
12 | }
13 |
14 | //TODO: change signature to the required one to fetch the access token and check AuthResponse to match response
15 | @get:POST(URL_ACCESS_TOKEN)
16 | val accessToken: Single
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/di/OkHttpModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import com.xmartlabs.bigbang.core.module.SessionInterceptor
4 | import dagger.Module
5 | import dagger.Provides
6 | import okhttp3.Interceptor
7 | import javax.inject.Named
8 | import javax.inject.Singleton
9 | import com.xmartlabs.bigbang.core.module.OkHttpModule as CoreOkHttpModule
10 |
11 | @Module
12 | class OkHttpModule : CoreOkHttpModule() {
13 | @Named(SESSION_INTERCEPTOR)
14 | @Provides
15 | @Singleton
16 | fun provideSessionInterceptor(sessionInterceptor: SessionInterceptor): Interceptor = sessionInterceptor
17 | }
18 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_recycler_example.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/di/RestServiceModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import android.content.Context
4 | import com.xmartlabs.template.App
5 | import com.xmartlabs.template.R
6 | import okhttp3.HttpUrl
7 | import com.xmartlabs.bigbang.retrofit.module.RestServiceModule as CoreRestServiceModule
8 |
9 | class RestServiceModule : CoreRestServiceModule() {
10 | companion object {
11 | private val BASE_URL = App.context.resources.getString(R.string.base_url)
12 | }
13 |
14 | @Suppress("UnsafeCallOnNullableType")
15 | override fun provideBaseUrl(context: Context): HttpUrl = HttpUrl.parse(BASE_URL)!!
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/di/ServiceGsonModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import com.google.gson.FieldNamingPolicy
4 | import com.google.gson.GsonBuilder
5 | import com.xmartlabs.bigbang.core.model.BuildInfo
6 | import com.xmartlabs.bigbang.retrofit.module.ServiceGsonModule as CoreServiceGsonModule
7 |
8 | class ServiceGsonModule : CoreServiceGsonModule() {
9 | override fun modifyGsonBuilder(builder: GsonBuilder, buildInfo: BuildInfo): GsonBuilder {
10 | if (buildInfo.isDebug) {
11 | builder.setPrettyPrinting()
12 | }
13 | builder.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
14 | return builder
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/item_recycler_example.xml:
--------------------------------------------------------------------------------
1 |
2 |
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/onboarding/OnboardingPageAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.onboarding
2 |
3 | import android.support.v4.app.Fragment
4 | import android.support.v4.app.FragmentManager
5 | import android.support.v4.app.FragmentStatePagerAdapter
6 | import com.xmartlabs.template.ui.onboarding.page.OnboardingPage
7 | import com.xmartlabs.template.ui.onboarding.page.OnboardingPageFragmentBuilder
8 |
9 | class OnboardingPageAdapter(fragmentManager: FragmentManager) : FragmentStatePagerAdapter(fragmentManager) {
10 | private val pages = OnboardingPage.values().map { OnboardingPageFragmentBuilder(it).build() }
11 |
12 | override fun getItem(position: Int): Fragment = pages[position]
13 |
14 | override fun getCount() = pages.size
15 | }
16 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/controller/SessionController.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.controller
2 |
3 | import com.xmartlabs.bigbang.core.controller.CoreSessionController
4 | import com.xmartlabs.bigbang.core.controller.SharedPreferencesController
5 | import com.xmartlabs.template.model.Session
6 | import javax.inject.Inject
7 |
8 | class SessionController @Inject constructor(sharedPreferencesController: SharedPreferencesController)
9 | : CoreSessionController(sharedPreferencesController) {
10 | override fun getSessionType() = Session::class.java
11 |
12 | var session
13 | get() = abstractSession as? Session?
14 | set(value) { value?.let { saveSession(it) } ?: deleteSession() }
15 |
16 | fun update(block: (Session?) -> Session) { session = block(session) }
17 | }
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Android Template project
2 |
3 | This is [Xmartlabs](https://xmartlabs.com) Android Template project.
4 | Please read through this README before start working.
5 |
6 | ## Style guide
7 |
8 | Please, remember to always use and follow our [style guide](https://github.com/xmartlabs/Android-Style-Guide)
9 | when making contributions to this project.
10 |
11 | ## Code review process
12 |
13 | Any code that you want to contribute to the project is welcome. However,
14 | before it is accepted, at least one member of the team must validate and approve
15 | the changes, preferably more than one.
16 |
17 | Please request the review by creating a Pull Request. Always include
18 | a clear description of what the changes are. A basic template can be
19 | found [here](.github/PULL_REQUEST_TEMPLATE.md)
20 |
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | org.gradle.jvmargs=-Xmx1536m
13 |
14 | # When configured, Gradle will run in incubating parallel mode.
15 | # This option should only be used with decoupled projects. More details, visit
16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
17 | # org.gradle.parallel=true
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/di/ControllerModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import com.xmartlabs.bigbang.core.controller.CoreSessionController
4 | import com.xmartlabs.bigbang.core.controller.SharedPreferencesController
5 | import com.xmartlabs.template.controller.SessionController
6 | import dagger.Module
7 | import dagger.Provides
8 | import javax.inject.Singleton
9 |
10 | @Module
11 | class ControllerModule {
12 | @Provides
13 | @Singleton
14 | internal fun provideSessionController(sharedPreferencesController: SharedPreferencesController)
15 | = SessionController(sharedPreferencesController)
16 |
17 | @Provides
18 | @Singleton
19 | internal fun provideCoreSessionController(sessionController: SessionController): CoreSessionController
20 | = sessionController
21 | }
22 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/xmartlabs/template/common/SleepingAction.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.common
2 |
3 | import io.reactivex.Scheduler
4 | import java.util.concurrent.TimeUnit
5 |
6 | /** Copied from rx.internal.schedulers.SleepingAction. */
7 | internal class SleepingAction(
8 | private val innerScheduler: Scheduler.Worker,
9 | private val execTime: Long,
10 | private val unit: TimeUnit,
11 | private val underlying: () -> Unit
12 | ) : Runnable {
13 |
14 | override fun run() {
15 | val delay = execTime - innerScheduler.now(unit)
16 | if (delay == 0L) {
17 | return
18 | }
19 |
20 | try {
21 | Thread.sleep(delay)
22 | underlying()
23 | } catch (e: Exception) {
24 | Thread.currentThread().interrupt()
25 | throw RuntimeException(e)
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/.github/PULL_REQUEST_TEMPLATE.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | ### Related Trello cards:
4 |
5 |
6 | ### Additional Info:
7 |
9 |
10 | ### UI preview:
11 |
12 |
13 |
20 |
21 | /cc @xmartlabs/android
22 |
--------------------------------------------------------------------------------
/app/src/test/java/com/xmartlabs/template/di/MockAndroidModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import android.content.Context
4 | import android.content.SharedPreferences
5 | import com.xmartlabs.template.App
6 | import dagger.Module
7 | import dagger.Provides
8 | import org.mockito.Mockito
9 | import javax.inject.Singleton
10 | import com.xmartlabs.template.model.common.BuildInfo as CoreBuildInfo
11 |
12 | @Module
13 | class MockAndroidModule {
14 | companion object {
15 | internal val MOCK_CONTEXT = Mockito.mock(App::class.java)
16 | }
17 |
18 | @Provides
19 | @Singleton
20 | fun provideApplicationContext(app: App): Context = app
21 |
22 | @Provides
23 | @Singleton
24 | fun provideApplication(): App = MOCK_CONTEXT
25 |
26 | @Provides
27 | @Singleton
28 | fun provideSharedPreferences(): SharedPreferences = Mockito.mock(SharedPreferences::class.java)
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/xmartlabs/template/common/BaseInstrumentationTest.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.common
2 |
3 | import android.app.Activity
4 | import android.app.Instrumentation
5 | import android.support.annotation.CallSuper
6 | import android.support.test.InstrumentationRegistry
7 | import android.support.test.runner.AndroidJUnit4
8 | import com.xmartlabs.bigbang.test.extensions.getFirstActivityInstance
9 | import io.appflate.restmock.RESTMockServer
10 | import org.junit.Before
11 | import org.junit.runner.RunWith
12 |
13 | @RunWith(AndroidJUnit4::class)
14 | abstract class BaseInstrumentationTest {
15 | lateinit var instrumentation: Instrumentation
16 |
17 | @Before
18 | @CallSuper
19 | fun setUp() {
20 | instrumentation = InstrumentationRegistry.getInstrumentation()
21 | RESTMockServer.reset()
22 | }
23 |
24 | val activityInstance: Activity?
25 | get() = instrumentation.getFirstActivityInstance()
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/di/FragmentBuildersModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import com.xmartlabs.template.ui.login.LoginFragment
4 | import com.xmartlabs.template.ui.onboarding.OnboardingFragment
5 | import com.xmartlabs.template.ui.onboarding.page.OnboardingPageFragment
6 | import com.xmartlabs.template.ui.recyclerfragmentexample.RecyclerExampleFragment
7 | import dagger.Module
8 | import dagger.android.ContributesAndroidInjector
9 |
10 | @Suppress("unused")
11 | @Module
12 | abstract class FragmentBuildersModule {
13 | @ContributesAndroidInjector
14 | abstract fun contributeLoginFragment(): LoginFragment
15 |
16 | @ContributesAndroidInjector
17 | abstract fun contributeOnboardingFragment(): OnboardingFragment
18 |
19 | @ContributesAndroidInjector
20 | abstract fun contributeOnboardingPageFragment(): OnboardingPageFragment
21 |
22 | @ContributesAndroidInjector
23 | abstract fun contributeRecyclerExampleFragment(): RecyclerExampleFragment
24 | }
25 |
--------------------------------------------------------------------------------
/app/src/sharedTest/java/com/xmartlabs/template/di/MockClockModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import dagger.Module
4 | import dagger.Provides
5 | import org.threeten.bp.*
6 | import java.util.*
7 | import javax.inject.Singleton
8 |
9 | /**
10 | * Created by medina on 22/09/2016.
11 | */
12 | @Module
13 | class MockClockModule {
14 | companion object {
15 | internal val DEFAULT_TIME_ZONE_ID = ZoneId.of("GMT-03:00")
16 | internal val DEFAULT_TIME_ZONE = TimeZone.getTimeZone(DEFAULT_TIME_ZONE_ID.id)
17 | private val DEFAULT_DATE = LocalDate.of(1991, Month.MARCH, 6)
18 | private val DEFAULT_TIME = LocalTime.of(11, 45)
19 | internal val DEFAULT_ZONED_DATE_TIME = ZonedDateTime.of(DEFAULT_DATE, DEFAULT_TIME, DEFAULT_TIME_ZONE_ID)
20 | }
21 |
22 | @Provides
23 | @Singleton
24 | fun provideTimeZone(): TimeZone = DEFAULT_TIME_ZONE
25 |
26 | @Provides
27 | @Singleton
28 | fun provideClock() = Clock.fixed(DEFAULT_ZONED_DATE_TIME.toInstant(), DEFAULT_TIME_ZONE_ID)
29 | }
30 |
--------------------------------------------------------------------------------
/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 /Users/diegomedina24/Library/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 |
19 | # Uncomment this to preserve the line number information for
20 | # debugging stack traces.
21 | #-keepattributes SourceFile,LineNumberTable
22 |
23 | # If you keep the line number information, uncomment this to
24 | # hide the original source file name.
25 | #-renamesourcefileattribute SourceFile
26 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/onboarding/page/OnboardingPage.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.onboarding.page
2 |
3 | import android.support.annotation.DrawableRes
4 | import android.support.annotation.StringRes
5 | import com.xmartlabs.bigbang.ui.common.parcel.ParcelerEnumTypeConverter
6 | import com.xmartlabs.template.R
7 | import org.parceler.Parcel
8 |
9 | @Parcel(converter = OnboardingPageTypeConverter::class, value = Parcel.Serialization.BEAN)
10 | enum class OnboardingPage(@StringRes val title: Int, @StringRes val description: Int, @DrawableRes val image: Int) {
11 | //TODO: Replace texts and images for your onboarding
12 | FIRST(R.string.onboarding_title_1, R.string.onboarding_description_1, R.mipmap.ic_launcher),
13 | SECOND(R.string.onboarding_title_2, R.string.onboarding_description_2, R.mipmap.ic_launcher),
14 | THIRD(R.string.onboarding_title_3, R.string.onboarding_description_3, R.mipmap.ic_launcher),
15 | }
16 |
17 | class OnboardingPageTypeConverter : ParcelerEnumTypeConverter(OnboardingPage::class.java)
18 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/StartActivity.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui
2 |
3 | import android.content.Intent
4 | import android.os.Bundle
5 | import com.f2prateek.dart.HensonNavigable
6 | import com.xmartlabs.bigbang.ui.BaseAppCompatActivity
7 | import com.xmartlabs.template.controller.SessionController
8 | import javax.inject.Inject
9 |
10 | @HensonNavigable
11 | class StartActivity : BaseAppCompatActivity() {
12 | @Inject
13 | internal lateinit var sessionController: SessionController
14 |
15 | override fun onCreate(savedInstanceState: Bundle?) {
16 | super.onCreate(savedInstanceState)
17 |
18 | val session = sessionController.session
19 | if (session == null) {
20 | //TODO: handle on start activity no session
21 | val intent = Henson.with(context)
22 | .gotoOnboardingActivity()
23 | .build()
24 | .setFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION or Intent.FLAG_ACTIVITY_CLEAR_TASK)
25 | startActivity(intent)
26 | } else {
27 | TODO("Handle on start activity with session")
28 | }
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/src/sharedTest/java/com/xmartlabs/template/di/MockControllerModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import com.xmartlabs.bigbang.core.controller.CoreSessionController
4 | import com.xmartlabs.bigbang.core.controller.SharedPreferencesController
5 | import com.xmartlabs.template.controller.AuthController
6 | import com.xmartlabs.template.controller.MockAuthController
7 | import com.xmartlabs.template.controller.SessionController
8 | import dagger.Module
9 | import dagger.Provides
10 | import javax.inject.Singleton
11 |
12 | @Module
13 | class MockControllerModule {
14 | @Provides
15 | @Singleton
16 | internal fun provideAuthController(mockAuthController: MockAuthController): AuthController = mockAuthController
17 |
18 | @Provides
19 | @Singleton
20 | internal fun provideCoreSessionController(sessionController: SessionController) : CoreSessionController = sessionController
21 |
22 | @Provides
23 | @Singleton
24 | internal fun provideSessionController(sharedPreferencesController: SharedPreferencesController) = SessionController(sharedPreferencesController)
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/xmartlabs/template/uitest/sign/LoginInstrumentalTest.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.uitest.sign
2 |
3 | import com.xmartlabs.bigbang.test.extensions.checkHasText
4 | import com.xmartlabs.bigbang.test.extensions.checkIsDisplayed
5 | import com.xmartlabs.bigbang.test.helpers.EspressoUtils
6 | import com.xmartlabs.template.R
7 | import com.xmartlabs.template.common.SingleActivityInstrumentationTest
8 | import com.xmartlabs.template.ui.login.LoginActivity
9 | import org.junit.Test
10 |
11 | class LoginInstrumentalTest : SingleActivityInstrumentationTest() {
12 | override val activityClass: Class
13 | get() = LoginActivity::class.java
14 |
15 | @Test
16 | fun testSignInButtonIsVisible() {
17 | launchActivityWithDefaultIntent()
18 | EspressoUtils.onViewWithId(R.id.loginButton)
19 | .checkIsDisplayed()
20 | }
21 |
22 | @Test
23 | fun testSignInButtonText() {
24 | launchActivityWithDefaultIntent()
25 | EspressoUtils.onViewWithId(R.id.loginButton)
26 | .checkHasText(R.string.login)
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/di/ActivityModule.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import com.xmartlabs.template.ui.StartActivity
4 | import com.xmartlabs.template.ui.login.LoginActivity
5 | import com.xmartlabs.template.ui.onboarding.OnboardingActivity
6 | import com.xmartlabs.template.ui.recyclerfragmentexample.RecyclerExampleActivity
7 | import dagger.Module
8 | import dagger.android.ContributesAndroidInjector
9 |
10 | @Suppress("unused")
11 | @Module
12 | abstract class ActivityModule {
13 | @ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
14 | abstract fun contributeLoginActivity(): LoginActivity
15 |
16 | @ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
17 | abstract fun contributeOnboardingActivity(): OnboardingActivity
18 |
19 | @ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
20 | abstract fun contributeRecyclerExampleActivity(): RecyclerExampleActivity
21 |
22 | @ContributesAndroidInjector(modules = [FragmentBuildersModule::class])
23 | abstract fun contributeStartActivity(): StartActivity
24 | }
25 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Built application files
2 | *.apk
3 | *.ap_
4 |
5 | # Files for the ART/Dalvik VM
6 | *.dex
7 |
8 | # Java class files
9 | *.class
10 |
11 | # Generated files
12 | bin/
13 | gen/
14 | out/
15 |
16 | # Gradle files
17 | .gradle/
18 | build/
19 |
20 | # Local configuration file (sdk path, etc)
21 | local.properties
22 |
23 | # Log Files
24 | *.log
25 |
26 | # Android Studio Navigation editor temp files
27 | .navigation/
28 |
29 | # Android Studio captures folder
30 | captures/
31 |
32 | # Intellij
33 | *.iml
34 | .idea/
35 |
36 | # Keystore files
37 | # Uncomment the following line if you do not want to check your keystore files in.
38 | #*.jks
39 |
40 | # External native build folder generated in Android Studio 2.2 and later
41 | .externalNativeBuild
42 |
43 | # Google Services (e.g. APIs or Firebase)
44 | google-services.json
45 |
46 | # Freeline
47 | freeline.py
48 | freeline/
49 | freeline_project_description.json
50 |
51 | # Java class files
52 | *.class1
53 |
54 | # Local configuration file (sdk path, etc)
55 | release.keystore
56 | build.properties
57 |
58 | # OSX files
59 | .DS_Store
60 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/controller/AuthController.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.controller
2 |
3 | import android.support.annotation.CheckResult
4 | import com.xmartlabs.bigbang.core.controller.Controller
5 | import com.xmartlabs.template.model.Session
6 | import com.xmartlabs.template.service.AuthService
7 | import io.reactivex.Completable
8 | import io.reactivex.Single
9 | import javax.inject.Inject
10 |
11 | open class AuthController @Inject constructor(
12 | private val authService: AuthService,
13 | private val sessionController: SessionController
14 | ) : Controller() {
15 |
16 | //TODO: Change signature and method to match authService request to fetch the Access Token
17 | val accessToken: Single
18 | @CheckResult
19 | get() = authService.accessToken
20 | .applyIoSchedulers()
21 | .filter { authResponse -> authResponse.accessToken != null }
22 | .toSingle()
23 | .map { Session(it.accessToken) }
24 | .doOnSuccess { sessionController.session = it }
25 |
26 | open fun signIn() = Completable.error(NotImplementedError())
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/helper/GeneralSingleSubscriber.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.helper
2 |
3 | import android.support.annotation.CheckResult
4 | import android.support.annotation.StringRes
5 | import com.xmartlabs.template.ui.common.TemplateView
6 | import io.reactivex.SingleObserver
7 | import io.reactivex.disposables.Disposable
8 | import java.lang.ref.WeakReference
9 |
10 | open class GeneralSingleSubscriber constructor(templateView: TemplateView? = null) : SingleObserver {
11 | private val viewReference = WeakReference(templateView)
12 |
13 | override fun onSubscribe(disposable: Disposable) { }
14 |
15 | override fun onError(throwable: Throwable) {
16 | val view = viewReference.get()
17 | if (alertOnError(throwable) && view != null && view.isViewAlive) {
18 | view.showError(throwable, getErrorMessage(throwable))
19 | }
20 | }
21 |
22 | override fun onSuccess(t: T) { }
23 |
24 | @StringRes
25 | protected open fun getErrorMessage(throwable: Throwable): Int? = null
26 |
27 | @CheckResult
28 | protected open fun alertOnError(throwable: Throwable) = true
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/helper/GeneralCompletableSubscriber.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.helper
2 |
3 | import android.support.annotation.CheckResult
4 | import android.support.annotation.StringRes
5 | import com.xmartlabs.template.ui.common.TemplateView
6 | import io.reactivex.CompletableObserver
7 | import io.reactivex.disposables.Disposable
8 | import java.lang.ref.WeakReference
9 |
10 | open class GeneralCompletableSubscriber constructor(templateView: TemplateView? = null) : CompletableObserver {
11 | private val viewReference = WeakReference(templateView)
12 |
13 | override fun onSubscribe(disposable: Disposable) { }
14 |
15 | override fun onComplete() { }
16 |
17 | override fun onError(throwable: Throwable) {
18 | val view = viewReference.get()
19 | if (alertOnError(throwable) && view != null && view.isViewAlive) {
20 | view.showError(throwable, getErrorMessage(throwable))
21 | }
22 | }
23 |
24 | @StringRes
25 | protected open fun getErrorMessage(throwable: Throwable): Int? = null
26 |
27 | @CheckResult
28 | protected open fun alertOnError(throwable: Throwable) = true
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/onboarding/page/OnboardingPageFragment.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.onboarding.page
2 |
3 | import android.os.Bundle
4 | import android.view.View
5 | import com.hannesdorfmann.fragmentargs.annotation.Arg
6 | import com.hannesdorfmann.fragmentargs.annotation.FragmentWithArgs
7 | import com.hannesdorfmann.fragmentargs.bundler.ParcelerArgsBundler
8 | import com.xmartlabs.bigbang.ui.BaseFragment
9 | import com.xmartlabs.template.R
10 | import kotlinx.android.synthetic.main.fragment_onboarding_page.*
11 |
12 | @FragmentWithArgs
13 | class OnboardingPageFragment : BaseFragment() {
14 | @Arg(bundler = ParcelerArgsBundler::class)
15 | internal lateinit var onboardingPage: OnboardingPage
16 |
17 | override val layoutResId = R.layout.fragment_onboarding_page
18 |
19 | override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
20 | super.onViewCreated(view, savedInstanceState)
21 | title.setText(onboardingPage.title)
22 | description.setText(onboardingPage.description)
23 |
24 | @Suppress("DEPRECATION")
25 | image.setImageDrawable(view.context.resources.getDrawable(onboardingPage.image))
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/recyclerfragmentexample/RecyclerExampleAdapter.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.recyclerfragmentexample
2 |
3 | import android.support.v7.widget.RecyclerView
4 | import android.view.LayoutInflater
5 | import android.view.View
6 | import android.view.ViewGroup
7 | import android.widget.TextView
8 |
9 | import com.xmartlabs.template.R
10 |
11 | internal class RecyclerExampleAdapter(private val items: List)
12 | : RecyclerView.Adapter() {
13 |
14 | override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerExampleViewHolder {
15 | val view = LayoutInflater.from(parent.context).inflate(R.layout.item_recycler_example, parent, false)
16 | return RecyclerExampleViewHolder(view)
17 | }
18 |
19 | override fun onBindViewHolder(holder: RecyclerExampleViewHolder, position: Int) = holder.bind(items[position])
20 |
21 | override fun getItemCount() = items.size
22 |
23 | internal class RecyclerExampleViewHolder(view: View) : RecyclerView.ViewHolder(view) {
24 | fun bind(string: String) = (itemView as? TextView)?.let { it.text = string } ?: Unit
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/helper/GeneralObservableSubscriber.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.helper
2 |
3 | import android.support.annotation.CheckResult
4 | import android.support.annotation.StringRes
5 |
6 | import com.xmartlabs.template.ui.common.TemplateView
7 |
8 | import org.reactivestreams.Subscriber
9 | import org.reactivestreams.Subscription
10 |
11 | import java.lang.ref.WeakReference
12 |
13 | open class GeneralObservableSubscriber constructor(templateView: TemplateView? = null) : Subscriber {
14 | private val viewReference = WeakReference(templateView)
15 |
16 | override fun onSubscribe(subscription: Subscription) { }
17 |
18 | override fun onNext(t: T) { }
19 |
20 | override fun onError(throwable: Throwable) {
21 | val view = viewReference.get()
22 | if (alertOnError(throwable) && view != null && view.isViewAlive) {
23 | view.showError(throwable, getErrorMessage(throwable))
24 | }
25 | }
26 |
27 | override fun onComplete() { }
28 |
29 | @StringRes
30 | protected open fun getErrorMessage(throwable: Throwable): Int? = null
31 |
32 | @CheckResult
33 | protected open fun alertOnError(throwable: Throwable) = true
34 | }
35 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/xmartlabs/template/common/ImmediateNewThreadScheduler.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.common
2 |
3 | import io.reactivex.Scheduler
4 | import io.reactivex.annotations.NonNull
5 | import io.reactivex.disposables.Disposable
6 | import io.reactivex.internal.subscriptions.BooleanSubscription
7 | import java.util.concurrent.TimeUnit
8 |
9 | class ImmediateNewThreadScheduler : Scheduler() {
10 | override fun createWorker() = ImmediateNewThreadWorker()
11 |
12 | // Inspired from ImmediateScheduler.InnerImmediateScheduler
13 | class ImmediateNewThreadWorker : Scheduler.Worker() {
14 | internal val innerSubscription = BooleanSubscription()
15 |
16 | override fun schedule(@NonNull run: Runnable, delay: Long, @NonNull unit: TimeUnit): Disposable {
17 | // since we are executing immediately on this thread we must cause this thread to sleep
18 | val execTime = this@ImmediateNewThreadWorker.now(unit) + unit.toMillis(delay)
19 |
20 | return schedule(SleepingAction(this, execTime, unit) { run.run() })
21 | }
22 |
23 | override fun dispose() = innerSubscription.cancel()
24 |
25 | override fun isDisposed() = innerSubscription.isCancelled
26 | }
27 | }
28 |
29 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/recyclerfragmentexample/RecyclerExampleFragment.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.recyclerfragmentexample
2 |
3 | import android.support.annotation.LayoutRes
4 | import android.support.v7.widget.LinearLayoutManager
5 | import com.hannesdorfmann.fragmentargs.annotation.FragmentWithArgs
6 | import com.xmartlabs.template.R
7 | import com.xmartlabs.template.ui.common.TemplateFragment
8 | import kotlinx.android.synthetic.main.fragment_recycler_example.*
9 | import java.util.Locale
10 | import javax.inject.Inject
11 |
12 | @FragmentWithArgs
13 | class RecyclerExampleFragment : TemplateFragment(), RecyclerExampleView {
14 | companion object {
15 | private const val LIST_COUNT = 39
16 | }
17 |
18 | @Inject
19 | override lateinit var presenter: RecyclerExamplePresenter
20 |
21 | @LayoutRes
22 | override val layoutResId = R.layout.fragment_recycler_example
23 |
24 | override fun setup() {
25 | val strings = listOf(1..LIST_COUNT)
26 | .flatten()
27 | .map { String.format(Locale.getDefault(), "Item %d", it) }
28 |
29 | recyclerView.adapter = RecyclerExampleAdapter(strings)
30 | recyclerView.layoutManager = LinearLayoutManager(context)
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | version: 2
2 | jobs:
3 | build:
4 | docker:
5 | - image: circleci/android:api-26-alpha
6 | working_directory: ~/bigbang-template
7 | environment:
8 | JVM_OPTS: -Xmx3g
9 | steps:
10 | - checkout
11 | - restore_cache:
12 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
13 | - run:
14 | name: Download Dependencies
15 | command: ./gradlew androidDependencies
16 | - save_cache:
17 | paths:
18 | - ~/.gradle
19 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
20 | - run:
21 | name: Check Lints
22 | command: ./gradlew clean
23 | detektCheck
24 | detektFormat
25 | lint
26 | --max-workers=2
27 | - run:
28 | name: Assemble
29 | command: ./gradlew
30 | assemble
31 | assembleAndroidTest
32 | --max-workers=2
33 | - run:
34 | name: Run tests and checks
35 | command: ./gradlew build --max-workers=2
36 | - store_artifacts:
37 | path: app/build/reports
38 | destination: reports
39 | - store_test_results:
40 | path: app/build/test-results
41 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/helper/GeneralFlowableSubscriber.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.helper
2 |
3 | import android.support.annotation.CheckResult
4 | import android.support.annotation.StringRes
5 | import com.xmartlabs.template.ui.common.TemplateView
6 | import io.reactivex.FlowableSubscriber
7 | import org.reactivestreams.Subscription
8 | import java.lang.ref.WeakReference
9 |
10 | open class GeneralFlowableSubscriber constructor(templateView: TemplateView? = null) : FlowableSubscriber {
11 | protected val maxNumberOfElements = java.lang.Long.MAX_VALUE
12 | private val viewReference: WeakReference = WeakReference(templateView)
13 |
14 | override fun onSubscribe(subscription: Subscription) = subscription.request(maxNumberOfElements)
15 |
16 | override fun onNext(t: T) { }
17 |
18 | override fun onError(throwable: Throwable) {
19 | val view = viewReference.get()
20 | if (alertOnError(throwable) && view != null && view.isViewAlive) {
21 | view.showError(throwable, getErrorMessage(throwable))
22 | }
23 | }
24 |
25 | override fun onComplete() { }
26 |
27 | @StringRes
28 | protected open fun getErrorMessage(throwable: Throwable): Int? = null
29 |
30 | @CheckResult
31 | protected open fun alertOnError(throwable: Throwable) = true
32 | }
33 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
9 |
13 |
14 |
21 |
22 |
28 |
29 |
30 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/login/LoginFragment.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.login
2 |
3 | import android.content.Intent
4 | import android.view.View
5 | import com.hannesdorfmann.fragmentargs.annotation.FragmentWithArgs
6 | import com.xmartlabs.template.App
7 | import com.xmartlabs.template.R
8 | import com.xmartlabs.template.ui.Henson
9 | import com.xmartlabs.template.ui.common.TemplateFragment
10 | import kotlinx.android.synthetic.main.fragment_login.*
11 | import javax.inject.Inject
12 |
13 | @FragmentWithArgs
14 | class LoginFragment : TemplateFragment(), LoginView {
15 | @Inject
16 | override lateinit var presenter: LoginPresenter
17 |
18 | override val layoutResId = R.layout.fragment_login
19 |
20 | override fun setup() {
21 | loginButton.setOnClickListener { presenter.loginButtonClicked() }
22 | }
23 |
24 | override fun setIsLoading(loading: Boolean) {
25 | loginButton.visibility = if (loading) View.GONE else View.VISIBLE
26 | progressBar.visibility = if (loading) View.VISIBLE else View.GONE
27 | }
28 |
29 | override fun gotoRecyclerExampleActivity() {
30 | val intent = Henson.with(App.context)
31 | .gotoRecyclerExampleActivity()
32 | .build()
33 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
34 | startActivity(intent)
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/onboarding/OnboardingPresenter.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.onboarding
2 |
3 | import com.xmartlabs.template.ui.common.TemplatePresenter
4 | import javax.inject.Inject
5 |
6 | class OnboardingPresenter @Inject constructor() : TemplatePresenter() {
7 | companion object {
8 | private val FIRST_PAGE = 0
9 | }
10 |
11 | val isLastPage: Boolean
12 | get() = currentPage == pageAdapter.count - 1
13 |
14 | @Suppress("LateinitUsage")
15 | private lateinit var pageAdapter: OnboardingPageAdapter
16 | private var currentPage = FIRST_PAGE
17 |
18 | override fun attachView(view: OnboardingView) {
19 | super.attachView(view)
20 | pageAdapter = view.createPageAdapter()
21 | view.setup(pageAdapter)
22 | }
23 |
24 | internal fun nextButtonPressed() {
25 | if (isLastPage) {
26 | skipButtonPressed()
27 | } else {
28 | currentPage++
29 | view?.moveToPage(currentPage)
30 | handleSkipButtonVisibility()
31 | }
32 | }
33 |
34 | private fun handleSkipButtonVisibility() = view?.setSkipButtonVisibility(!isLastPage)
35 |
36 | internal fun pageChanged(newPage: Int) {
37 | currentPage = newPage
38 | view?.handleNextButtonVisibility()
39 | handleSkipButtonVisibility()
40 | }
41 |
42 | internal fun skipButtonPressed() = view?.goToLoginActivity()
43 | }
44 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/xmartlabs/template/common/SingleActivityInstrumentationTest.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.common
2 |
3 | import android.app.Activity
4 | import android.content.Intent
5 | import android.support.test.espresso.intent.rule.IntentsTestRule
6 | import android.support.test.rule.ActivityTestRule
7 | import com.xmartlabs.bigbang.core.extensions.ignoreException
8 | import com.xmartlabs.bigbang.test.extensions.performClick
9 | import com.xmartlabs.bigbang.test.helpers.EspressoUtils
10 | import org.junit.Rule
11 | import org.junit.Test
12 |
13 | /**
14 | * Created by mirland on 10/6/17.
15 | */
16 | abstract class SingleActivityInstrumentationTest : BaseInstrumentationTest() {
17 | @Rule
18 | @JvmField
19 | var activityTestRule = createTestRule()
20 |
21 | protected val defaultIntent: Intent? = null
22 |
23 | protected fun createTestRule(): ActivityTestRule = IntentsTestRule(activityClass, true, false)
24 |
25 | @Test
26 | fun checkActivityOpens() {
27 | launchActivityWithDefaultIntent()
28 | }
29 |
30 | @Test
31 | fun checkUpNavigation() {
32 | launchActivityWithDefaultIntent()
33 | EspressoUtils.onUpButtonView().ignoreException { performClick() }
34 | }
35 |
36 | protected fun launchActivityWithDefaultIntent() {
37 | activityTestRule.launchActivity(defaultIntent)
38 | }
39 |
40 | protected abstract val activityClass: Class
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_login.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
10 |
20 |
21 |
31 |
32 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | Template
3 |
4 | https://xmartlabs.com/
5 |
6 | Skip
7 | Start
8 | Next
9 | Welcome to Template!
10 | Onboarding description 1!
11 | Onboarding Title 2!
12 | Onboarding Description 2!
13 | Onboarding Title 3!
14 | Onboarding Description 3!
15 | Login
16 | Onboarding
17 | Recycler fragment example
18 | Error
19 | Check your internet connection and try again
20 | No internet connection
21 | Could not connect to the server. Please try again.
22 | android_mipmap_image
23 | Empty View Example
24 |
25 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/di/ApplicationComponent.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.di
2 |
3 | import android.app.Application
4 | import com.xmartlabs.bigbang.core.module.AndroidModule
5 | import com.xmartlabs.bigbang.core.module.GsonModule
6 | import com.xmartlabs.bigbang.core.module.PicassoModule
7 | import com.xmartlabs.bigbang.retrofit.module.RestServiceModule
8 | import com.xmartlabs.bigbang.retrofit.module.ServiceGsonModule
9 | import com.xmartlabs.template.App
10 | import com.xmartlabs.template.model.common.BuildInfo
11 | import dagger.BindsInstance
12 | import dagger.Component
13 | import dagger.android.AndroidInjectionModule
14 | import javax.inject.Singleton
15 |
16 | @Component(modules = [
17 | ActivityModule::class,
18 | AndroidInjectionModule::class,
19 | AndroidModule::class,
20 | AppModule::class,
21 | ControllerModule::class,
22 | GsonModule::class,
23 | OkHttpModule::class,
24 | PicassoModule::class,
25 | RestServiceModule::class,
26 | RestServiceModuleApi::class,
27 | ServiceGsonModule::class
28 | ])
29 | @Singleton
30 | interface ApplicationComponent {
31 | @Component.Builder
32 | interface Builder {
33 | @BindsInstance
34 | fun application(application: Application): Builder
35 |
36 | @BindsInstance
37 | fun buildInfo(buildInfo: BuildInfo): Builder
38 |
39 | fun restServiceModule(restService: RestServiceModule): Builder
40 |
41 | fun okHttpModule(okHttpModule: OkHttpModule): Builder
42 |
43 | fun build(): ApplicationComponent
44 | }
45 |
46 | fun inject(app: App)
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/common/TemplateFragment.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.common
2 |
3 | import android.support.annotation.StringRes
4 | import com.afollestad.materialdialogs.MaterialDialog
5 | import com.xmartlabs.bigbang.ui.mvp.BaseMvpFragment
6 | import com.xmartlabs.bigbang.ui.mvp.MvpPresenter
7 | import com.xmartlabs.template.R
8 | import io.reactivex.exceptions.CompositeException
9 | import java.io.IOException
10 | import java.util.concurrent.CancellationException
11 |
12 | abstract class TemplateFragment> : BaseMvpFragment(), TemplateView {
13 | override val isViewAlive: Boolean
14 | get() = isAdded && activity != null
15 |
16 | override fun setup() = Unit
17 |
18 | override fun showError(message: Int, title: Int, buttonTitle: Int) {
19 | if (isViewAlive) {
20 | context?.let { MaterialDialog.Builder(it)
21 | .title(title)
22 | .content(message)
23 | .positiveText(buttonTitle)
24 | .build()
25 | .show()
26 | }
27 | }
28 | }
29 |
30 | override fun showError(error: Throwable, @StringRes message: Int?) {
31 | if (error is CancellationException) {
32 | return
33 | }
34 |
35 | var receiverError = error
36 | if (receiverError is CompositeException) {
37 | receiverError = receiverError.exceptions[0]
38 | }
39 | if (receiverError is IOException) {
40 | showError(R.string.check_your_internet_connection, R.string.no_internet_connection)
41 | } else if (message == null) {
42 | showError(R.string.error_service_call_generic)
43 | } else {
44 | showError(message)
45 | }
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/src/test/java/com/xmartlabs/template/tests/signin/SignInUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.tests.signin
2 |
3 | import com.xmartlabs.template.App
4 | import com.xmartlabs.template.R
5 | import com.xmartlabs.template.common.BaseUnitTest
6 | import com.xmartlabs.template.controller.AuthController
7 | import com.xmartlabs.template.service.AuthService
8 | import io.appflate.restmock.RESTMockServer
9 | import io.appflate.restmock.RequestsVerifier
10 | import io.appflate.restmock.utils.RequestMatchers
11 | import okhttp3.mockwebserver.MockResponse
12 | import org.hamcrest.core.AllOf.allOf
13 | import org.junit.Assert
14 | import org.junit.Test
15 | import javax.inject.Inject
16 |
17 | class SignInUnitTest : BaseUnitTest() {
18 | companion object {
19 | private val POST_ACCESS_TOKEN_MATCHER =
20 | allOf(RequestMatchers.isPOST(), RequestMatchers.pathContains(AuthService.URL_ACCESS_TOKEN))
21 | }
22 |
23 | @Inject
24 | internal lateinit var authController: AuthController
25 |
26 | override fun setUp() {
27 | super.setUp()
28 | testComponent.inject(this)
29 | }
30 |
31 | @Test
32 | fun signInTest() {
33 | authController.signIn().blockingAwait()
34 | }
35 |
36 | @Test
37 | fun testAccessTokenOk() {
38 | RESTMockServer.whenRequested(POST_ACCESS_TOKEN_MATCHER)
39 | .thenReturn(MockResponse().setResponseCode(411))
40 |
41 | authController.accessToken
42 | .ignoreElement()
43 | .onErrorComplete()
44 | .blockingAwait()
45 |
46 | RequestsVerifier.verifyRequest(POST_ACCESS_TOKEN_MATCHER).invoked()
47 | }
48 |
49 | @Test
50 | fun testMockString() {
51 | Assert.assertEquals(DEFAULT_MOCK_STRING, App.context.getString(R.string.app_name))
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
15 |
16 |
22 |
23 |
29 |
30 |
35 |
36 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_onboarding_page.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
21 |
22 |
32 |
33 |
46 |
47 |
48 |
--------------------------------------------------------------------------------
/app/src/test/java/com/xmartlabs/template/common/BaseUnitTest.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.common
2 |
3 | import com.xmartlabs.template.App
4 | import com.xmartlabs.template.di.DaggerApplicationComponent
5 | import com.xmartlabs.template.di.MockAndroidModule
6 | import com.xmartlabs.template.di.MockClockModule
7 | import com.xmartlabs.template.di.MockRestServiceModule
8 | import com.xmartlabs.template.model.common.BuildInfo
9 | import com.xmartlabs.template.tests.signin.SignInUnitTest
10 | import io.appflate.restmock.RESTMockServerStarter
11 | import io.appflate.restmock.android.AndroidLocalFileParser
12 | import io.appflate.restmock.android.AndroidLogger
13 | import org.junit.Before
14 | import org.mockito.ArgumentMatchers
15 | import org.mockito.Mockito.`when`
16 | import org.mockito.MockitoAnnotations
17 | import java.util.*
18 | import javax.inject.Inject
19 |
20 | open class BaseUnitTest {
21 | companion object {
22 | const val DEFAULT_MOCK_STRING = "This is a mock string"
23 | }
24 |
25 | internal lateinit var testComponent: TestComponent
26 |
27 | open fun mockStrings() {
28 | `when`(App.context.getString(ArgumentMatchers.anyInt())).thenReturn(DEFAULT_MOCK_STRING)
29 | }
30 |
31 | open fun mockTime() {
32 | TimeZone.setDefault(MockClockModule.DEFAULT_TIME_ZONE)
33 | }
34 |
35 | @Before
36 | open fun setUp() {
37 | App.context = MockAndroidModule.MOCK_CONTEXT
38 |
39 | MockitoAnnotations.initMocks(this)
40 |
41 | RESTMockServerStarter.startSync(AndroidLocalFileParser(App.context), AndroidLogger())
42 | testComponent = DaggerTestComponent.builder()
43 | .application(App.context)
44 | .buildInfo(BuildInfo())
45 | .restServiceModule(MockRestServiceModule())
46 | .build()
47 |
48 | mockStrings()
49 | mockTime()
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/src/test/java/com/xmartlabs/template/common/TestComponent.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.common
2 |
3 | import android.app.Application
4 | import com.xmartlabs.bigbang.core.module.GsonModule
5 | import com.xmartlabs.bigbang.core.module.PicassoModule
6 | import com.xmartlabs.bigbang.retrofit.module.RestServiceModule
7 | import com.xmartlabs.bigbang.retrofit.module.ServiceGsonModule
8 | import com.xmartlabs.template.di.ActivityModule
9 | import com.xmartlabs.template.di.AppModule
10 | import com.xmartlabs.template.di.ApplicationComponent
11 | import com.xmartlabs.template.di.MockAndroidModule
12 | import com.xmartlabs.template.di.MockClockModule
13 | import com.xmartlabs.template.di.MockControllerModule
14 | import com.xmartlabs.template.di.OkHttpModule
15 | import com.xmartlabs.template.di.RestServiceModuleApi
16 | import com.xmartlabs.template.model.common.BuildInfo
17 | import com.xmartlabs.template.tests.signin.SignInUnitTest
18 | import dagger.BindsInstance
19 | import dagger.Component
20 | import dagger.android.AndroidInjectionModule
21 | import javax.inject.Singleton
22 |
23 | @Singleton
24 | @Component(modules = [
25 | AppModule::class,
26 | ActivityModule::class,
27 | AndroidInjectionModule::class,
28 | GsonModule::class,
29 | MockAndroidModule::class,
30 | MockClockModule::class,
31 | MockControllerModule::class,
32 | OkHttpModule::class,
33 | PicassoModule::class,
34 | RestServiceModule::class,
35 | RestServiceModuleApi::class,
36 | ServiceGsonModule::class
37 | ])
38 | interface TestComponent : ApplicationComponent {
39 | @Component.Builder
40 | interface Builder {
41 | @BindsInstance
42 | fun application(application: Application): Builder
43 |
44 | @BindsInstance
45 | fun buildInfo(buildInfo: BuildInfo): Builder
46 |
47 | fun restServiceModule(restService: RestServiceModule): Builder
48 |
49 | fun okHttpModule(okHttpModule: OkHttpModule): Builder
50 |
51 | fun build(): TestComponent
52 | }
53 |
54 | fun inject(signInUnitTest: SignInUnitTest)
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/xmartlabs/template/InstrumentalTestComponent.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template
2 |
3 | import android.app.Application
4 | import com.xmartlabs.bigbang.core.module.AndroidModule
5 | import com.xmartlabs.bigbang.core.module.GsonModule
6 | import com.xmartlabs.bigbang.core.module.PicassoModule
7 | import com.xmartlabs.bigbang.retrofit.module.RestServiceModule
8 | import com.xmartlabs.bigbang.retrofit.module.ServiceGsonModule
9 | import com.xmartlabs.template.common.BaseInstrumentationTest
10 | import com.xmartlabs.template.di.ActivityModule
11 | import com.xmartlabs.template.di.AppModule
12 | import com.xmartlabs.template.di.ApplicationComponent
13 | import com.xmartlabs.template.di.MockClockModule
14 | import com.xmartlabs.template.di.MockControllerModule
15 | import com.xmartlabs.template.di.OkHttpModule
16 | import com.xmartlabs.template.di.RestServiceModuleApi
17 | import com.xmartlabs.template.model.common.BuildInfo
18 | import dagger.BindsInstance
19 | import dagger.Component
20 | import dagger.android.AndroidInjectionModule
21 | import javax.inject.Singleton
22 |
23 | @Singleton
24 | @Component(modules = [
25 | ActivityModule::class,
26 | AndroidInjectionModule::class,
27 | AndroidModule::class,
28 | AppModule::class,
29 | GsonModule::class,
30 | MockClockModule::class,
31 | MockControllerModule::class,
32 | OkHttpModule::class,
33 | PicassoModule::class,
34 | RestServiceModule::class,
35 | RestServiceModuleApi::class,
36 | ServiceGsonModule::class
37 | ])
38 | interface InstrumentalTestComponent : ApplicationComponent {
39 | @Component.Builder
40 | interface Builder {
41 | @BindsInstance
42 | fun application(application: Application): Builder
43 |
44 | @BindsInstance
45 | fun buildInfo(buildInfo: BuildInfo): Builder
46 |
47 | fun restServiceModule(restService: RestServiceModule): Builder
48 |
49 | fun build(): InstrumentalTestComponent
50 | }
51 |
52 | fun inject(testRunner: TestRunner)
53 |
54 | fun inject(baseInstrumentationTest: BaseInstrumentationTest)
55 | }
56 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_onboarding.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
16 |
17 |
29 |
30 |
43 |
44 |
56 |
57 |
58 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
12 | set DEFAULT_JVM_OPTS=
13 |
14 | set DIRNAME=%~dp0
15 | if "%DIRNAME%" == "" set DIRNAME=.
16 | set APP_BASE_NAME=%~n0
17 | set APP_HOME=%DIRNAME%
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windowz variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 | if "%@eval[2+2]" == "4" goto 4NT_args
53 |
54 | :win9xME_args
55 | @rem Slurp the command line arguments.
56 | set CMD_LINE_ARGS=
57 | set _SKIP=2
58 |
59 | :win9xME_args_slurp
60 | if "x%~1" == "x" goto execute
61 |
62 | set CMD_LINE_ARGS=%*
63 | goto execute
64 |
65 | :4NT_args
66 | @rem Get arguments from the 4NT Shell from JP Software
67 | set CMD_LINE_ARGS=%$
68 |
69 | :execute
70 | @rem Setup the command line
71 |
72 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
73 |
74 | @rem Execute Gradle
75 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
76 |
77 | :end
78 | @rem End local scope for the variables with windows NT shell
79 | if "%ERRORLEVEL%"=="0" goto mainEnd
80 |
81 | :fail
82 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
83 | rem the _cmd.exe /c_ return code!
84 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
85 | exit /b 1
86 |
87 | :mainEnd
88 | if "%OS%"=="Windows_NT" endlocal
89 |
90 | :omega
91 |
--------------------------------------------------------------------------------
/app/src/androidTest/java/com/xmartlabs/template/TestRunner.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template
2 |
3 | import android.app.Application
4 | import android.content.Context
5 | import android.os.Bundle
6 | import android.support.test.espresso.IdlingRegistry
7 | import android.support.test.runner.AndroidJUnitRunner
8 | import com.jakewharton.espresso.OkHttp3IdlingResource
9 | import com.xmartlabs.bigbang.core.module.OkHttpModule
10 | import com.xmartlabs.template.common.ImmediateNewThreadScheduler
11 | import io.appflate.restmock.RESTMockServerStarter
12 | import io.appflate.restmock.android.AndroidAssetsFileParser
13 | import io.appflate.restmock.android.AndroidLogger
14 | import io.reactivex.plugins.RxJavaPlugins
15 | import okhttp3.OkHttpClient
16 | import java.util.*
17 | import javax.inject.Inject
18 | import javax.inject.Named
19 |
20 | class TestRunner : AndroidJUnitRunner() {
21 | companion object {
22 | val picassoClientIdleName = String.format(Locale.US, "%sOkHttp", OkHttpModule.CLIENT_PICASSO)
23 | val serviceClientIdleName = String.format(Locale.US, "%sOkHttp", OkHttpModule.CLIENT_SERVICE)
24 |
25 | @JvmStatic
26 | var androidAssetsFileParser: AndroidAssetsFileParser? = null
27 | private set
28 | @JvmStatic
29 | var testContext: Context? = null
30 | private set
31 | }
32 |
33 | @Inject
34 | @field:[Named (OkHttpModule.CLIENT_PICASSO)]
35 | internal lateinit var picassoOkHttpClient: OkHttpClient
36 | @Inject
37 | @field:[Named (OkHttpModule.CLIENT_SERVICE)]
38 | internal lateinit var serviceOkHttpClient: OkHttpClient
39 | @Inject
40 | lateinit var mockedTimeZone: TimeZone
41 |
42 | override fun onCreate(arguments: Bundle) {
43 | super.onCreate(arguments)
44 |
45 | RxJavaPlugins.setIoSchedulerHandler { ImmediateNewThreadScheduler() }
46 | }
47 |
48 | @Throws(InstantiationException::class, IllegalAccessException::class, ClassNotFoundException::class)
49 | override fun newApplication(classLoader: ClassLoader, className: String, context: Context): Application {
50 | testContext = getContext()
51 | androidAssetsFileParser = AndroidAssetsFileParser(testContext)
52 | RESTMockServerStarter.startSync(androidAssetsFileParser, AndroidLogger())
53 |
54 | return super.newApplication(classLoader, TestApplication::class.java.name, context)
55 | }
56 |
57 | override fun callApplicationOnCreate(app: Application) {
58 | super.callApplicationOnCreate(app)
59 | ((app as TestApplication).applicationComponent as InstrumentalTestComponent)
60 | .inject(this)
61 | TimeZone.setDefault(mockedTimeZone)
62 |
63 | val picassoIdlingResource = OkHttp3IdlingResource.create(picassoClientIdleName, picassoOkHttpClient)
64 | IdlingRegistry.getInstance().register(picassoIdlingResource)
65 |
66 | val serviceIdlingResource = OkHttp3IdlingResource.create(serviceClientIdleName, serviceOkHttpClient)
67 | IdlingRegistry.getInstance().register(serviceIdlingResource)
68 | }
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/ui/onboarding/OnboardingFragment.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template.ui.onboarding
2 |
3 | import android.content.Intent
4 | import android.view.View
5 | import com.hannesdorfmann.fragmentargs.annotation.FragmentWithArgs
6 | import com.xmartlabs.template.App
7 | import com.xmartlabs.template.R
8 | import com.xmartlabs.template.helper.EmptyOnPageChangeListener
9 | import com.xmartlabs.template.ui.Henson
10 | import com.xmartlabs.template.ui.common.TemplateFragment
11 | import kotlinx.android.synthetic.main.fragment_onboarding.*
12 | import javax.inject.Inject
13 |
14 | @FragmentWithArgs
15 | class OnboardingFragment : TemplateFragment(), OnboardingView {
16 | companion object {
17 | private const val ALPHA_INVISIBLE = 0.01f
18 | private const val ALPHA_OPAQUE = 1.0f
19 | private const val ALPHA_START_DELAY_MILLISECONDS: Long = 100
20 | }
21 |
22 | @Inject
23 | override lateinit var presenter: OnboardingPresenter
24 |
25 | override val layoutResId = R.layout.fragment_onboarding
26 |
27 | override fun createPageAdapter() = OnboardingPageAdapter(childFragmentManager)
28 |
29 | override fun setup(pageAdapter: OnboardingPageAdapter) {
30 | viewPager.adapter = pageAdapter
31 | listOf(startButton, nextButton).forEach { it.setOnClickListener { presenter.nextButtonPressed() } }
32 | skipButton.setOnClickListener { presenter.skipButtonPressed() }
33 | viewPager.addOnPageChangeListener(object : EmptyOnPageChangeListener() {
34 | override fun onPageSelected(position: Int) = presenter.pageChanged(position)
35 | })
36 | }
37 |
38 | override fun setSkipButtonVisibility(visible: Boolean) {
39 | @Suppress("ComplexCondition")
40 | if (visible && skipButton.alpha == ALPHA_OPAQUE || !visible && skipButton.alpha == View.INVISIBLE.toFloat()) {
41 | return
42 | }
43 |
44 | skipButton.animate()
45 | .alpha(if (visible) ALPHA_OPAQUE else ALPHA_INVISIBLE)
46 | .start()
47 | }
48 |
49 | override fun moveToPage(page: Int) {
50 | viewPager.setCurrentItem(page, true)
51 | handleNextButtonVisibility()
52 | }
53 |
54 | override fun handleNextButtonVisibility() {
55 | if (presenter.isLastPage) {
56 | swapView(nextButton, startButton)
57 | } else if (nextButton.alpha <= ALPHA_INVISIBLE) {
58 | swapView(startButton, nextButton)
59 | }
60 | }
61 |
62 | private fun swapView(fromView: View, toView: View) {
63 | fromView.animate()
64 | .alpha(ALPHA_INVISIBLE)
65 | .start()
66 | toView.animate()
67 | .alpha(ALPHA_OPAQUE)
68 | .setStartDelay(ALPHA_START_DELAY_MILLISECONDS)
69 | .start()
70 | }
71 |
72 | override fun goToLoginActivity() {
73 | val intent = Henson.with(App.context)
74 | .gotoLoginActivity()
75 | .build()
76 | .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
77 | startActivity(intent)
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/java/com/xmartlabs/template/App.kt:
--------------------------------------------------------------------------------
1 | package com.xmartlabs.template
2 |
3 | import android.app.Activity
4 | import android.app.Application
5 | import android.support.annotation.VisibleForTesting
6 | import com.jakewharton.threetenabp.AndroidThreeTen
7 | import com.raizlabs.android.dbflow.config.FlowConfig
8 | import com.raizlabs.android.dbflow.config.FlowManager
9 | import com.tspoon.traceur.Traceur
10 | import com.tspoon.traceur.TraceurConfig
11 | import com.xmartlabs.bigbang.core.di.AppInjector
12 | import com.xmartlabs.bigbang.core.helper.GeneralErrorHelper
13 | import com.xmartlabs.bigbang.core.log.LoggerTree
14 | import com.xmartlabs.bigbang.log.crashlytics.CrashlyticsLogger
15 | import com.xmartlabs.bigbang.retrofit.helper.ServiceErrorHandler
16 | import com.xmartlabs.template.di.ApplicationComponent
17 | import com.xmartlabs.template.di.DaggerApplicationComponent
18 | import com.xmartlabs.template.di.OkHttpModule
19 | import com.xmartlabs.template.di.RestServiceModule
20 | import com.xmartlabs.template.model.common.BuildInfo
21 | import dagger.android.DispatchingAndroidInjector
22 | import dagger.android.HasActivityInjector
23 | import timber.log.Timber
24 | import javax.inject.Inject
25 |
26 | open class App : Application(), HasActivityInjector {
27 | companion object {
28 | @Suppress("LateinitUsage")
29 | @JvmStatic
30 | lateinit var context: App
31 | @VisibleForTesting internal set
32 | }
33 |
34 | @Inject
35 | internal lateinit var buildInfo: BuildInfo
36 | @Inject
37 | internal lateinit var generalErrorHelper: GeneralErrorHelper
38 | @Inject
39 | internal lateinit var loggerTree: LoggerTree
40 | @Inject
41 | internal lateinit var serviceErrorHandler: ServiceErrorHandler
42 | @Inject
43 | internal lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector
44 | @Suppress("LateinitUsage")
45 | internal lateinit var applicationComponent : ApplicationComponent
46 |
47 | init {
48 | @Suppress("LeakingThis")
49 | context = this
50 | }
51 |
52 | override fun activityInjector() = dispatchingAndroidInjector
53 |
54 | override fun onCreate() {
55 | super.onCreate()
56 | initializeThreeTenABP()
57 | initializeInjections()
58 | initializeDataBase()
59 | initializeRxErrorHandler()
60 | initializeLogging() // Crashlytics initialization should go at the end.
61 | }
62 |
63 | private fun initializeInjections() {
64 | applicationComponent = createComponent()
65 | applicationComponent.inject(this)
66 | AppInjector.init(this)
67 | }
68 |
69 | @VisibleForTesting
70 | protected open fun createComponent(): ApplicationComponent = DaggerApplicationComponent.builder()
71 | .application(this)
72 | .buildInfo(BuildInfo())
73 | .okHttpModule(OkHttpModule())
74 | .restServiceModule(RestServiceModule())
75 | .build()
76 |
77 | private fun initializeDataBase() = FlowManager.init(FlowConfig.Builder(this).build())
78 |
79 | private fun initializeThreeTenABP() = AndroidThreeTen.init(this)
80 |
81 | private fun initializeLogging() {
82 | //TODO: Configure Fabric and add Fabric apiSecret and apiKey properties file in the root folder
83 | loggerTree.addLogger(CrashlyticsLogger().initialize(buildInfo, this))
84 | Timber.plant(loggerTree)
85 | }
86 |
87 | private fun initializeRxErrorHandler() {
88 | serviceErrorHandler.handleServiceErrors()
89 |
90 | val level = if (BuildConfig.DEBUG) Traceur.AssemblyLogLevel.SHOW_ALL else Traceur.AssemblyLogLevel.NONE
91 | val config = TraceurConfig(true, level) { generalErrorHelper.generalErrorAction(it) }
92 | Traceur.enableLogging(config)
93 | }
94 | }
95 |
--------------------------------------------------------------------------------
/app/detekt-config.yml:
--------------------------------------------------------------------------------
1 | autoCorrect: true
2 | failFast: false
3 |
4 | build:
5 | warningThreshold: 1
6 | failThreshold: 1
7 | weights:
8 | complexity: 2
9 | formatting: 1
10 | LongParameterList: 1
11 | comments: 1
12 |
13 | processors:
14 | active: true
15 | exclude:
16 | # - 'FunctionCountProcessor'
17 | # - 'PropertyCountProcessor'
18 | # - 'ClassCountProcessor'
19 | # - 'PackageCountProcessor'
20 | # - 'KtFileCountProcessor'
21 |
22 | console-reports:
23 | active: true
24 | exclude:
25 | # - 'ProjectStatisticsReport'
26 | # - 'ComplexityReport'
27 | # - 'NotificationReport'
28 | # - 'FindingsReport'
29 | # - 'BuildFailureReport'
30 |
31 | output-reports:
32 | active: true
33 | exclude:
34 | # - 'PlainOutputReport'
35 | # - 'XmlOutputReport'
36 |
37 | potential-bugs:
38 | active: true
39 | DuplicateCaseInWhenExpression:
40 | active: true
41 | EqualsWithHashCodeExist:
42 | active: true
43 | ExplicitGarbageCollectionCall:
44 | active: true
45 | UnreachableCode:
46 | active: true
47 | LateinitUsage:
48 | active: true
49 | excludeAnnotatedProperties: 'javax.inject.Inject,com.hannesdorfmann.fragmentargs.annotation.Arg,com.f2prateek.dart.InjectExtra'
50 | UnsafeCallOnNullableType:
51 | active: true
52 | UnsafeCast:
53 | active: true
54 |
55 | performance:
56 | active: true
57 | ForEachOnRange:
58 | active: true
59 | SpreadOperator:
60 | active: true
61 | UnnecessaryTemporaryInstantiation:
62 | active: true
63 |
64 | exceptions:
65 | active: true
66 |
67 | empty-blocks:
68 | active: true
69 |
70 | complexity:
71 | active: true
72 | LongMethod:
73 | threshold: 20
74 | LongParameterList:
75 | threshold: 5
76 | LargeClass:
77 | threshold: 150
78 | ComplexMethod:
79 | threshold: 10
80 | TooManyFunctions:
81 | threshold: 10
82 | ComplexCondition:
83 | threshold: 3
84 | LabeledExpression:
85 | active: false
86 |
87 | code-smell:
88 | active: true
89 | FeatureEnvy:
90 | threshold: 0.5
91 | weight: 0.45
92 | base: 0.5
93 |
94 | formatting:
95 | active: true
96 | useTabs: false
97 | Indentation:
98 | active: true
99 | indentSize: 2
100 | ConsecutiveBlankLines:
101 | active: true
102 | autoCorrect: true
103 | MultipleSpaces:
104 | active: true
105 | autoCorrect: true
106 | SpacingAfterComma:
107 | active: true
108 | autoCorrect: true
109 | SpacingAfterKeyword:
110 | active: true
111 | autoCorrect: true
112 | SpacingAroundColon:
113 | active: true
114 | autoCorrect: true
115 | SpacingAroundCurlyBraces:
116 | active: true
117 | autoCorrect: true
118 | SpacingAroundOperator:
119 | active: true
120 | autoCorrect: true
121 | TrailingSpaces:
122 | active: true
123 | autoCorrect: true
124 | UnusedImports:
125 | active: true
126 | autoCorrect: true
127 | OptionalSemicolon:
128 | active: true
129 | autoCorrect: true
130 | OptionalUnit:
131 | active: true
132 | autoCorrect: true
133 | ExpressionBodySyntax:
134 | active: true
135 | autoCorrect: true
136 | ExpressionBodySyntaxLineBreaks:
137 | active: true
138 | autoCorrect: true
139 | OptionalReturnKeyword:
140 | active: true
141 | autoCorrect: false
142 |
143 | style:
144 | active: true
145 | ReturnCount:
146 | active: true
147 | max: 2
148 | NewLineAtEndOfFile:
149 | active: true
150 | OptionalAbstractKeyword:
151 | active: true
152 | ForbiddenComment:
153 | active: false
154 | values: 'TODO:,FIXME:,STOPSHIP:'
155 | ForbiddenImport:
156 | active: false
157 | imports: ''
158 | ModifierOrder:
159 | active: true
160 | MagicNumber:
161 | active: true
162 | ignoreNumbers: '-1,0,1'
163 | ignoreHashCodeFunction: false
164 | ignorePropertyDeclaration: false
165 | ignoreAnnotation: false
166 | WildcardImport:
167 | active: true
168 | excludeImports: 'kotlinx.android.synthetic.*'
169 | SafeCast:
170 | active: true
171 | MaxLineLength:
172 | active: true
173 | maxLineLength: 120
174 | excludePackageStatements: false
175 | excludeImportStatements: false
176 | NamingConventionViolation:
177 | active: true
178 | variablePattern: '^(_)?[a-z$][a-zA-Z$0-9]*$'
179 | constantPattern: '^([A-Z_]*|serialVersionUID)$'
180 | methodPattern: '^[a-z$][a-zA-Z$0-9]*$'
181 | classPattern: '[A-Z$][a-zA-Z$]*'
182 | enumEntryPattern: '^[A-Z$][a-zA-Z_$]*$'
183 |
184 | comments:
185 | active: true
186 | CommentOverPrivateMethod:
187 | active: true
188 | CommentOverPrivateProperty:
189 | active: true
190 | UndocumentedPublicClass:
191 | active: false
192 | searchInNestedClass: true
193 | searchInInnerClass: true
194 | searchInInnerInterface: true
195 | UndocumentedPublicFunction:
196 | active: false
197 |
198 | # *experimental feature*
199 | # Migration rules can be defined in the same config file or a new one
200 | migration:
201 | active: true
202 | imports:
203 | # your.package.Class: new.package.or.Class
204 | # for example:
205 | # io.gitlab.arturbosch.detekt.api.Rule: io.gitlab.arturbosch.detekt.rule.Rule
206 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
10 | DEFAULT_JVM_OPTS=""
11 |
12 | APP_NAME="Gradle"
13 | APP_BASE_NAME=`basename "$0"`
14 |
15 | # Use the maximum available, or set MAX_FD != -1 to use that value.
16 | MAX_FD="maximum"
17 |
18 | warn ( ) {
19 | echo "$*"
20 | }
21 |
22 | die ( ) {
23 | echo
24 | echo "$*"
25 | echo
26 | exit 1
27 | }
28 |
29 | # OS specific support (must be 'true' or 'false').
30 | cygwin=false
31 | msys=false
32 | darwin=false
33 | case "`uname`" in
34 | CYGWIN* )
35 | cygwin=true
36 | ;;
37 | Darwin* )
38 | darwin=true
39 | ;;
40 | MINGW* )
41 | msys=true
42 | ;;
43 | esac
44 |
45 | # Attempt to set APP_HOME
46 | # Resolve links: $0 may be a link
47 | PRG="$0"
48 | # Need this for relative symlinks.
49 | while [ -h "$PRG" ] ; do
50 | ls=`ls -ld "$PRG"`
51 | link=`expr "$ls" : '.*-> \(.*\)$'`
52 | if expr "$link" : '/.*' > /dev/null; then
53 | PRG="$link"
54 | else
55 | PRG=`dirname "$PRG"`"/$link"
56 | fi
57 | done
58 | SAVED="`pwd`"
59 | cd "`dirname \"$PRG\"`/" >/dev/null
60 | APP_HOME="`pwd -P`"
61 | cd "$SAVED" >/dev/null
62 |
63 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
64 |
65 | # Determine the Java command to use to start the JVM.
66 | if [ -n "$JAVA_HOME" ] ; then
67 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
68 | # IBM's JDK on AIX uses strange locations for the executables
69 | JAVACMD="$JAVA_HOME/jre/sh/java"
70 | else
71 | JAVACMD="$JAVA_HOME/bin/java"
72 | fi
73 | if [ ! -x "$JAVACMD" ] ; then
74 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
75 |
76 | Please set the JAVA_HOME variable in your environment to match the
77 | location of your Java installation."
78 | fi
79 | else
80 | JAVACMD="java"
81 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
82 |
83 | Please set the JAVA_HOME variable in your environment to match the
84 | location of your Java installation."
85 | fi
86 |
87 | # Increase the maximum file descriptors if we can.
88 | if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
89 | MAX_FD_LIMIT=`ulimit -H -n`
90 | if [ $? -eq 0 ] ; then
91 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
92 | MAX_FD="$MAX_FD_LIMIT"
93 | fi
94 | ulimit -n $MAX_FD
95 | if [ $? -ne 0 ] ; then
96 | warn "Could not set maximum file descriptor limit: $MAX_FD"
97 | fi
98 | else
99 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
100 | fi
101 | fi
102 |
103 | # For Darwin, add options to specify how the application appears in the dock
104 | if $darwin; then
105 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
106 | fi
107 |
108 | # For Cygwin, switch paths to Windows format before running java
109 | if $cygwin ; then
110 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
111 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
112 | JAVACMD=`cygpath --unix "$JAVACMD"`
113 |
114 | # We build the pattern for arguments to be converted via cygpath
115 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
116 | SEP=""
117 | for dir in $ROOTDIRSRAW ; do
118 | ROOTDIRS="$ROOTDIRS$SEP$dir"
119 | SEP="|"
120 | done
121 | OURCYGPATTERN="(^($ROOTDIRS))"
122 | # Add a user-defined pattern to the cygpath arguments
123 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
124 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
125 | fi
126 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
127 | i=0
128 | for arg in "$@" ; do
129 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
130 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
131 |
132 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
133 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
134 | else
135 | eval `echo args$i`="\"$arg\""
136 | fi
137 | i=$((i+1))
138 | done
139 | case $i in
140 | (0) set -- ;;
141 | (1) set -- "$args0" ;;
142 | (2) set -- "$args0" "$args1" ;;
143 | (3) set -- "$args0" "$args1" "$args2" ;;
144 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
145 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
146 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
147 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
148 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
149 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
150 | esac
151 | fi
152 |
153 | # Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
154 | function splitJvmOpts() {
155 | JVM_OPTS=("$@")
156 | }
157 | eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
158 | JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
159 |
160 | exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
161 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | google()
4 | jcenter()
5 | mavenCentral()
6 | maven { url 'https://maven.fabric.io/public' }
7 | }
8 | dependencies {
9 | classpath 'com.github.ben-manes:gradle-versions-plugin:0.20.0'
10 | classpath 'com.google.gms:google-services:4.2.0'
11 | classpath 'com.stanfy.spoon:spoon-gradle-plugin:1.2.2'
12 | classpath 'io.fabric.tools:gradle:1.26.1'
13 | }
14 | }
15 |
16 | apply plugin: "io.gitlab.arturbosch.detekt"
17 | apply plugin: 'com.android.application'
18 | apply plugin: 'com.github.ben-manes.versions'
19 | apply plugin: 'kotlin-android'
20 | apply plugin: 'kotlin-android-extensions'
21 | apply plugin: 'kotlin-kapt'
22 | apply plugin: 'project-report'
23 | apply plugin: 'spoon'
24 |
25 | apply from: '../detekt.gradle'
26 |
27 | def getSignInformation = { keystorePath, keystorePropertyPath ->
28 | def keyStore = file(keystorePath)
29 | if (!keyStore.exists()) {
30 | logger.error("Keystore file not found, path=${keyStore.absolutePath}")
31 | return
32 | }
33 | def propertiesFile = file(keystorePropertyPath)
34 | if (!propertiesFile.exists()) {
35 | logger.error("Keystore properties file not found, path=${propertiesFile.absolutePath}")
36 | return
37 | }
38 | Properties properties = new Properties()
39 | properties.load(new FileInputStream(propertiesFile))
40 | return [
41 | storeFile : keyStore,
42 | storePassword: properties["store_password"],
43 | keyAlias : properties["key_alias"],
44 | keyPassword : properties["key_password"]
45 | ]
46 | }
47 |
48 | android {
49 | compileSdkVersion 27
50 | buildToolsVersion '28.0.3'
51 | defaultConfig {
52 | applicationId "com.xmartlabs.template"
53 | minSdkVersion 21
54 | multiDexEnabled true
55 | targetSdkVersion 27
56 | versionCode 1
57 | versionName "1.0"
58 | testInstrumentationRunner "${applicationId}.TestRunner"
59 | }
60 | signingConfigs {
61 | //noinspection GroovyMissingReturnStatement
62 | debug {
63 | def signInformation = getSignInformation("debug.keystore", "keystore.properties")
64 | if (signInformation != null) {
65 | storeFile signInformation.storeFile
66 | storePassword signInformation.storePassword
67 | keyAlias signInformation.keyAlias
68 | keyPassword signInformation.keyPassword
69 | }
70 | }
71 | //noinspection GroovyMissingReturnStatement
72 | release {
73 | def signInformation = getSignInformation("release.keystore", "keystore.properties")
74 | if (signInformation != null) {
75 | storeFile signInformation.storeFile
76 | storePassword signInformation.storePassword
77 | keyAlias signInformation.keyAlias
78 | keyPassword signInformation.keyPassword
79 | }
80 | }
81 | }
82 | buildTypes {
83 | release {
84 | minifyEnabled false
85 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
86 | // The sign process should be enabled to release
87 | // signingConfig signingConfigs.release
88 | }
89 | }
90 | flavorDimensions 'env'
91 | productFlavors {
92 | //noinspection GroovyMissingReturnStatement
93 | production {
94 | dimension 'env'
95 | }
96 | staging {
97 | dimension 'env'
98 | applicationIdSuffix '.staging'
99 | }
100 | }
101 | lintOptions {
102 | warning 'InvalidPackage'
103 | }
104 | packagingOptions {
105 | exclude '.readme'
106 | exclude 'META-INF/LICENSE.txt'
107 | exclude 'META-INF/NOTICE.txt'
108 | exclude 'META-INF/services/javax.annotation.processing.Processor'
109 | }
110 | testOptions {
111 | unitTests.returnDefaultValues = true
112 | }
113 | sourceSets {
114 | String sharedTestDir = 'src/sharedTest/java'
115 | test {
116 | java.srcDirs += sharedTestDir
117 | }
118 | androidTest {
119 | java.srcDirs += sharedTestDir
120 | }
121 | }
122 | }
123 |
124 | final ANDROID_SUPPORT_TEST_VERSION = '1.0.2'
125 | final ANDROID_SUPPORT_VERSION = '27.1.1'
126 | final BIGBANG_VERSION = '7270230d77'
127 | final DAGGER_VERSION = '2.16'
128 | final DART_VERSION = '2.0.3'
129 | final DBFLOW_VERSION = '4.2.4'
130 | final ESPRESSO_VERSION = '3.0.2'
131 | final FRAGMENTARGS_VERSION = '3.0.2'
132 | final GSON_VERSION = '2.8.5'
133 | final JUNIT_VERSION = '4.12'
134 | final KOTLIN_VERSION = '1.2.71'
135 | final LOCALDATE_BACKPORT_VERSION = '1.1.1'
136 | final MATERIAL_DIALOGS_VERSION = '0.9.6.0'
137 | final MOCKITO_VERSION = '2.23.0'
138 | final MULTIDEX_VERSION = '1.0.3'
139 | final OK_HTTP_2_CURL_VERSION = '0.4.5'
140 | final OK_HTTP_IDLING_RESOURCE_VERSION = '1.0.0'
141 | final OK_HTTP_VERSION = '3.11.0'
142 | final OKIO_VERSION = '2.1.0'
143 | final PARCELER_VERSION = '1.1.11'
144 | final PICASSO_VERSION = '8c16e8564e'
145 | final REST_MOCK = '0.3.2'
146 | final RETROFIT_VERSION = '2.4.0'
147 | final RXANDROID_VERSION = '2.1.0'
148 | final RXJAVA_VERSION = '2.2.0'
149 | final RXLIFECYCLE_VERSION = '2.2.2'
150 | final TIMBER_VERSION = '4.7.1'
151 | final TRACEUR_VERSION = '273cc7d582'
152 |
153 | dependencies {
154 | androidTestImplementation "com.github.xmartlabs.bigbang:instrumental-test:${BIGBANG_VERSION}"
155 | androidTestImplementation "com.android.support:appcompat-v7:${ANDROID_SUPPORT_VERSION}"
156 | androidTestImplementation "com.android.support:design:${ANDROID_SUPPORT_VERSION}"
157 | androidTestImplementation "com.android.support:support-v13:${ANDROID_SUPPORT_VERSION}"
158 |
159 | androidTestImplementation "com.android.support:support-annotations:${ANDROID_SUPPORT_VERSION}"
160 | androidTestImplementation "com.android.support.test:rules:${ANDROID_SUPPORT_TEST_VERSION}"
161 | androidTestImplementation("com.android.support.test:runner:${ANDROID_SUPPORT_TEST_VERSION}") {
162 | exclude group: 'com.android.support', module: 'support-annotations'
163 | }
164 | androidTestImplementation("com.android.support.test.espresso:espresso-contrib:${ESPRESSO_VERSION}") {
165 | exclude group: 'com.android.support', module: 'appcompat'
166 | exclude group: 'com.android.support', module: 'appcompat-v7'
167 | exclude group: 'com.android.support', module: 'design'
168 | exclude group: 'com.android.support', module: 'support-v4'
169 | exclude group: 'com.android.support', module: 'support-annotations'
170 | exclude module: 'recyclerview-v7'
171 | }
172 | androidTestImplementation("com.android.support.test.espresso:espresso-core:${ESPRESSO_VERSION}") {
173 | exclude group: 'com.android.support', module: 'support-annotations'
174 | }
175 | androidTestImplementation "com.android.support.test.espresso:espresso-intents:${ESPRESSO_VERSION}"
176 |
177 | androidTestImplementation "com.android.support:support-annotations:${ANDROID_SUPPORT_VERSION}"
178 | androidTestImplementation "com.github.andrzejchm.RESTMock:android:${REST_MOCK}"
179 | androidTestImplementation "com.jakewharton.espresso:okhttp3-idling-resource:${OK_HTTP_IDLING_RESOURCE_VERSION}"
180 | androidTestImplementation "com.squareup.okhttp3:mockwebserver:${OK_HTTP_VERSION}" // Added to prevent dependencies conflict between main and test
181 | androidTestImplementation "com.squareup.okhttp3:okhttp:${OK_HTTP_VERSION}" // Added to prevent dependencies conflict between main and test
182 | androidTestImplementation "com.squareup.okio:okio:${OKIO_VERSION}" // Added to prevent dependencies conflict between main and test
183 |
184 | implementation fileTree(dir: 'libs', include: ['*.jar'])
185 | implementation "com.afollestad.material-dialogs:core:${MATERIAL_DIALOGS_VERSION}"
186 | implementation "com.android.support:appcompat-v7:${ANDROID_SUPPORT_VERSION}"
187 | implementation "com.android.support:design:${ANDROID_SUPPORT_VERSION}"
188 | implementation "com.android.support:support-v13:${ANDROID_SUPPORT_VERSION}"
189 | implementation("com.crashlytics.sdk.android:crashlytics:2.9.6@aar") {
190 | transitive = true
191 | }
192 | implementation "com.f2prateek.dart:dart:${DART_VERSION}"
193 | implementation "com.f2prateek.dart:henson:${DART_VERSION}"
194 | implementation "com.github.mrmike:Ok2Curl:${OK_HTTP_2_CURL_VERSION}"
195 | implementation "com.github.Raizlabs.DBFlow:dbflow-core:${DBFLOW_VERSION}"
196 | implementation "com.github.Raizlabs.DBFlow:dbflow:${DBFLOW_VERSION}"
197 | implementation "com.github.square.picasso:picasso:${PICASSO_VERSION}"
198 | implementation "com.github.xmartlabs.bigbang:core:${BIGBANG_VERSION}"
199 | implementation "com.github.xmartlabs.bigbang:dbflow:${BIGBANG_VERSION}"
200 | implementation "com.github.xmartlabs.bigbang:log-crashlytics:${BIGBANG_VERSION}"
201 | implementation "com.github.xmartlabs.bigbang:retrofit:${BIGBANG_VERSION}"
202 | implementation "com.github.xmartlabs.bigbang:ui:${BIGBANG_VERSION}"
203 | implementation "com.github.xmartlabs:Traceur:${TRACEUR_VERSION}"
204 | implementation "com.google.code.gson:gson:${GSON_VERSION}"
205 | implementation "com.google.dagger:dagger:${DAGGER_VERSION}"
206 | implementation "com.google.dagger:dagger-android-support:${DAGGER_VERSION}"
207 | implementation "com.hannesdorfmann.fragmentargs:annotation:${FRAGMENTARGS_VERSION}"
208 | implementation "com.hannesdorfmann.fragmentargs:bundler-parceler:${FRAGMENTARGS_VERSION}"
209 | implementation "com.jakewharton.threetenabp:threetenabp:${LOCALDATE_BACKPORT_VERSION}"
210 | implementation "com.jakewharton.timber:timber:${TIMBER_VERSION}"
211 | implementation "com.squareup.okhttp3:okhttp:${OK_HTTP_VERSION}"
212 | implementation "com.squareup.okhttp3:logging-interceptor:${OK_HTTP_VERSION}"
213 | implementation "com.squareup.okio:okio:${OKIO_VERSION}"
214 | implementation "com.squareup.retrofit2:adapter-rxjava2:${RETROFIT_VERSION}"
215 | implementation "com.squareup.retrofit2:converter-gson:${RETROFIT_VERSION}"
216 | implementation "com.squareup.retrofit2:retrofit:${RETROFIT_VERSION}"
217 | implementation "com.trello.rxlifecycle2:rxlifecycle-components:${RXLIFECYCLE_VERSION}"
218 | implementation "com.trello.rxlifecycle2:rxlifecycle:${RXLIFECYCLE_VERSION}"
219 | implementation "io.reactivex.rxjava2:rxandroid:${RXANDROID_VERSION}"
220 | implementation "io.reactivex.rxjava2:rxjava:${RXJAVA_VERSION}"
221 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${KOTLIN_VERSION}"
222 | implementation "org.parceler:parceler-api:${PARCELER_VERSION}"
223 |
224 | kapt "com.f2prateek.dart:dart-processor:${DART_VERSION}"
225 | kapt "com.f2prateek.dart:henson-processor:${DART_VERSION}"
226 | kapt "com.github.Raizlabs.DBFlow:dbflow-processor:${DBFLOW_VERSION}"
227 | kapt "com.google.dagger:dagger-android-processor:${DAGGER_VERSION}"
228 | kapt "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"
229 | kapt "com.hannesdorfmann.fragmentargs:processor:${FRAGMENTARGS_VERSION}"
230 | kapt "org.parceler:parceler:${PARCELER_VERSION}"
231 |
232 | kaptAndroidTest "com.google.dagger:dagger-android-processor:${DAGGER_VERSION}"
233 | kaptAndroidTest "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"
234 |
235 | kaptTest "com.google.dagger:dagger-android-processor:${DAGGER_VERSION}"
236 | kaptTest "com.google.dagger:dagger-compiler:${DAGGER_VERSION}"
237 |
238 | implementation 'javax.annotation:jsr250-api:1.0'
239 |
240 | testImplementation "com.android.support:support-annotations:${ANDROID_SUPPORT_VERSION}"
241 | testImplementation "com.github.andrzejchm.RESTMock:android:${REST_MOCK}"
242 | testImplementation "com.squareup.okhttp3:okhttp:${OK_HTTP_VERSION}" // Added to prevent dependencies conflict between main and test
243 | testImplementation "com.squareup.okio:okio:${OKIO_VERSION}" // Added to prevent dependencies conflict between main and test
244 | testImplementation "com.squareup.okhttp3:mockwebserver:${OK_HTTP_VERSION}" // Added to prevent dependencies conflict between main and test
245 | testImplementation "junit:junit:${JUNIT_VERSION}"
246 | testImplementation "org.mockito:mockito-core:${MOCKITO_VERSION}"
247 | }
248 |
249 | spoon {
250 | debug = true
251 | baseOutputDir = file("$projectDir/build/reports/spoon")
252 | }
253 |
--------------------------------------------------------------------------------