├── .gitignore ├── README.md ├── app ├── .gitignore ├── build.gradle ├── proguard-rules.pro └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── raqun │ │ └── blockchaintracker │ │ ├── HomeFragmentEspressoTest.kt │ │ └── util │ │ ├── UiTestsDataProvider.kt │ │ └── ViewModelUtil.kt │ ├── debug │ ├── AndroidManifest.xml │ └── java │ │ └── com │ │ └── raqun │ │ └── blockchaintracker │ │ └── SingleFragmentActivity.kt │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── raqun │ │ │ └── blockchaintracker │ │ │ ├── Constants.kt │ │ │ ├── TrackerApp.kt │ │ │ ├── api │ │ │ ├── ApiConstants.kt │ │ │ ├── DefaultRequestInterceptor.kt │ │ │ ├── TrackerServices.kt │ │ │ └── response │ │ │ │ └── DefaultResponse.kt │ │ │ ├── binding │ │ │ └── BindingConversions.kt │ │ │ ├── data │ │ │ ├── DataBean.kt │ │ │ ├── DataState.kt │ │ │ ├── Error.kt │ │ │ └── source │ │ │ │ ├── MarketDataSource.kt │ │ │ │ ├── MarketRepository.kt │ │ │ │ └── remote │ │ │ │ └── MarketRemoteDataSource.kt │ │ │ ├── di │ │ │ ├── ActivityModule.kt │ │ │ ├── ActivityScope.kt │ │ │ ├── ApiModule.kt │ │ │ ├── AppComponent.kt │ │ │ ├── AppModule.kt │ │ │ ├── DomainModule.kt │ │ │ ├── FragmentBuildersModule.kt │ │ │ ├── FragmentScope.kt │ │ │ ├── ViewModelKey.kt │ │ │ └── ViewModelModule.kt │ │ │ ├── domain │ │ │ ├── MarketDataUseCasImpl.kt │ │ │ └── MarketDataUseCase.kt │ │ │ ├── extensions │ │ │ ├── ArchComponentExt.kt │ │ │ ├── ContextExt.kt │ │ │ ├── DateExt.kt │ │ │ ├── FragmentManagerExt.kt │ │ │ └── RxExt.kt │ │ │ ├── model │ │ │ ├── MarketVal.kt │ │ │ └── UiDataBean.kt │ │ │ ├── ui │ │ │ ├── BaseActivity.kt │ │ │ ├── BaseFragment.kt │ │ │ ├── BaseView.kt │ │ │ ├── BinderFragment.kt │ │ │ ├── MainActivity.kt │ │ │ ├── NavigationController.kt │ │ │ ├── home │ │ │ │ ├── HomeFragment.kt │ │ │ │ ├── HomeView.kt │ │ │ │ └── HomeViewModel.kt │ │ │ └── view │ │ │ │ └── DefaultLineChart.kt │ │ │ ├── util │ │ │ ├── ChartValueDateFormatter.kt │ │ │ ├── NonFloatValueFormatter.kt │ │ │ └── StableLiveData.kt │ │ │ └── viewmodel │ │ │ └── VMFactory.kt │ └── res │ │ ├── drawable-v24 │ │ └── ic_launcher_foreground.xml │ │ ├── drawable │ │ └── ic_launcher_background.xml │ │ ├── layout │ │ ├── activity_container.xml │ │ └── fragment_home.xml │ │ ├── mipmap-anydpi-v26 │ │ ├── ic_launcher.xml │ │ └── ic_launcher_round.xml │ │ ├── mipmap-hdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ ├── ic_launcher.png │ │ └── ic_launcher_round.png │ │ └── values │ │ ├── colors.xml │ │ ├── strings.xml │ │ └── styles.xml │ └── test │ └── java │ └── com │ └── raqun │ └── blockchaintracker │ ├── MarketDataUseCaseTest.kt │ └── util │ └── UnitTestsDataProvider.kt ├── art └── home_screen.jpg ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat └── settings.gradle /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/workspace.xml 5 | /.idea/libraries 6 | .DS_Store 7 | /build 8 | /captures 9 | .externalNativeBuild 10 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # blockchain-tracker 2 | A blockchain market tracking app. Example implementation of reactive clean architecture and testing. 3 | 4 | ## Tech Stack 5 | - Kotlin
6 | - MVVM
7 | - Clean Architecture
8 | - Dagger2
9 | - Repository
10 | - RxKotlin
11 | - Data Binding
12 | - Arch Components
13 | - Espresso Test
14 | - Unit Test
15 | - Rotation Support 16 | 17 | ## Screenshots 18 | 20 | -------------------------------------------------------------------------------- /app/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | *.iml -------------------------------------------------------------------------------- /app/build.gradle: -------------------------------------------------------------------------------- 1 | apply plugin: 'com.android.application' 2 | apply plugin: 'kotlin-android' 3 | apply plugin: 'kotlin-android-extensions' 4 | apply plugin: 'kotlin-kapt' 5 | 6 | android { 7 | compileSdkVersion 27 8 | defaultConfig { 9 | applicationId "com.raqun.blockchaintracker" 10 | minSdkVersion 17 11 | targetSdkVersion 27 12 | versionCode 1 13 | versionName "1.0" 14 | testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" 15 | } 16 | 17 | flavorDimensions("default") 18 | 19 | productFlavors { 20 | 21 | prod { 22 | dimension("default") 23 | buildConfigField('String', 'BASE_URL', '"https://api.blockchain.info/"') 24 | } 25 | 26 | dev { 27 | dimension("default") 28 | applicationIdSuffix ".dev" 29 | versionNameSuffix ".dev" 30 | buildConfigField('String', 'BASE_URL', '"https://api.blockchain.info/"') 31 | } 32 | } 33 | 34 | buildTypes { 35 | release { 36 | minifyEnabled false 37 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 38 | } 39 | } 40 | 41 | dataBinding { 42 | enabled = true 43 | } 44 | 45 | packagingOptions { 46 | exclude 'META-INF/rxjava.properties' 47 | } 48 | 49 | androidExtensions { 50 | experimental = true 51 | } 52 | } 53 | 54 | kapt { 55 | generateStubs = true 56 | } 57 | 58 | dependencies { 59 | 60 | // DEFAULT 61 | implementation fileTree(dir: 'libs', include: ['*.jar']) 62 | implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" 63 | implementation "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion" 64 | implementation 'com.android.support.constraint:constraint-layout:1.1.2' 65 | 66 | // FOR UNIT TESTING 67 | testImplementation 'junit:junit:4.12' 68 | testImplementation "org.mockito:mockito-core:$rootProject.ext.mockitoVersion" 69 | testImplementation "org.mockito:mockito-android:$rootProject.ext.mockitoVersion" 70 | 71 | // FOR ESPRESSO 72 | androidTestImplementation "com.android.support.test:runner:$rootProject.ext.testRunnerVersion" 73 | androidTestImplementation "com.android.support.test.espresso:espresso-core:$rootProject.ext.espressoVersion" 74 | androidTestImplementation "org.mockito:mockito-core:$rootProject.ext.mockitoVersion" 75 | androidTestImplementation "org.mockito:mockito-android:$rootProject.ext.mockitoVersion" 76 | androidTestImplementation "android.arch.core:core-testing:$rootProject.ext.archCoreTestingVersion" 77 | androidTestImplementation "com.android.support:appcompat-v7:$rootProject.ext.supportLibraryVersion" 78 | androidTestImplementation "com.android.support.test:rules:$rootProject.ext.testRulesVersion" 79 | androidTestImplementation("com.android.support.test.espresso:espresso-core:$espressoVersion", { 80 | exclude group: 'com.android.support', module: 'support-annotations' 81 | exclude group: 'com.google.code.findbugs', module: 'jsr305' 82 | }) 83 | androidTestImplementation("com.android.support.test.espresso:espresso-contrib:$rootProject.ext.espressoVersion", { 84 | exclude group: 'com.android.support', module: 'support-annotations' 85 | exclude group: 'com.google.code.findbugs', module: 'jsr305' 86 | }) 87 | 88 | // RETROFIT 89 | implementation "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofitVersion" 90 | implementation "com.squareup.okhttp3:logging-interceptor:$rootProject.ext.loggingInterceptorVersion" 91 | implementation "com.squareup.retrofit2:converter-gson:$rootProject.ext.gsonVersion" 92 | implementation "com.squareup.retrofit2:adapter-rxjava2:$rootProject.ext.retrofitVersion" 93 | implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion" 94 | 95 | // DAGGER 96 | implementation "com.google.dagger:dagger-android-support:$rootProject.ext.daggerVersion" 97 | kapt "com.google.dagger:dagger-compiler:$rootProject.ext.daggerVersion" 98 | kapt "com.google.dagger:dagger-android-processor:$rootProject.ext.daggerVersion" 99 | 100 | // ARCH COMPONENTS 101 | implementation "android.arch.lifecycle:runtime:$rootProject.ext.archLifecycleVersion" 102 | implementation "android.arch.lifecycle:extensions:$rootProject.ext.archExtensionsVersion" 103 | 104 | // RX 105 | implementation "io.reactivex.rxjava2:rxandroid:2.0.1" 106 | implementation "io.reactivex.rxjava2:rxkotlin:$rootProject.ext.rxKotlinVersion" 107 | 108 | // CHART 109 | implementation "com.github.PhilJay:MPAndroidChart:v$rootProject.ext.mpPieChartVersion" 110 | } 111 | -------------------------------------------------------------------------------- /app/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /app/src/androidTest/java/com/raqun/blockchaintracker/HomeFragmentEspressoTest.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker 2 | 3 | import android.arch.lifecycle.MediatorLiveData 4 | import android.support.test.espresso.Espresso 5 | import android.support.test.rule.ActivityTestRule 6 | import android.support.test.runner.AndroidJUnit4 7 | import com.raqun.blockchaintracker.data.DataBean 8 | import com.raqun.blockchaintracker.model.MarketVal 9 | import com.raqun.blockchaintracker.model.UiDataBean 10 | import com.raqun.blockchaintracker.ui.home.HomeFragment 11 | import com.raqun.blockchaintracker.ui.home.HomeViewModel 12 | import com.raqun.blockchaintracker.util.ViewModelUtil 13 | import org.junit.Before 14 | import org.junit.Rule 15 | import org.junit.Test 16 | import org.junit.runner.RunWith 17 | import org.mockito.Mockito 18 | 19 | import android.support.test.espresso.Espresso.onView 20 | import android.support.test.espresso.assertion.ViewAssertions.matches 21 | import android.support.test.espresso.matcher.ViewMatchers.* 22 | import com.raqun.blockchaintracker.data.Error 23 | import com.raqun.blockchaintracker.util.UiTestsDataProvider 24 | import org.hamcrest.Matchers.not 25 | import android.support.test.espresso.matcher.ViewMatchers.isDisplayed 26 | import android.support.test.espresso.matcher.RootMatchers.withDecorView 27 | import android.support.test.espresso.Espresso.onView 28 | import org.hamcrest.Matchers.`is` 29 | 30 | 31 | @RunWith(AndroidJUnit4::class) 32 | class HomeFragmentEspressoTest { 33 | 34 | lateinit var homeFragment: HomeFragment 35 | lateinit var homeViewModel: HomeViewModel 36 | lateinit var marketDataLiveData: MediatorLiveData>> 37 | 38 | @get:Rule 39 | var activityTestRule: ActivityTestRule = 40 | ActivityTestRule(SingleFragmentActivity::class.java, true, true) 41 | 42 | @Before 43 | fun init() { 44 | homeFragment = HomeFragment.newInstance() 45 | homeViewModel = Mockito.mock(HomeViewModel::class.java) 46 | marketDataLiveData = MediatorLiveData() 47 | 48 | Mockito.`when`(homeViewModel.marketLiveData()).thenReturn(marketDataLiveData) 49 | 50 | homeFragment.vmFactory = ViewModelUtil.createFor(homeViewModel) 51 | activityTestRule.activity.setFragment(homeFragment) 52 | } 53 | 54 | /** 55 | * Test for checking if we show progress bar while fetching market data. 56 | */ 57 | @Test 58 | fun ensureProgressVisiblityWhenFetchingMarketData() { 59 | val fetchingBean = UiDataBean.fetching(null) 60 | marketDataLiveData.postValue(fetchingBean) 61 | onView(withId(R.id.transactionDataProgress)).check(matches(isDisplayed())) 62 | } 63 | 64 | /** 65 | * Test for checking if we disable week spinner to avoid multiple requests while fetching data. 66 | */ 67 | @Test 68 | fun ensureOnlyOneRequestWhenFetchingMarketData() { 69 | val fetchingBean = UiDataBean.fetching(null) 70 | marketDataLiveData.postValue(fetchingBean) 71 | onView(withId(R.id.transactionDataProgress)).check(matches(not(isClickable()))) 72 | } 73 | 74 | /** 75 | * Tests if we hide chart while fetching market data. 76 | */ 77 | @Test 78 | fun ensureChartIsNotVisibleWhenFatchingData() { 79 | val fetchingBean = UiDataBean.fetching(null) 80 | marketDataLiveData.postValue(fetchingBean) 81 | onView(withId(R.id.transactionDataProgress)).check(matches(not(isDisplayed()))) 82 | } 83 | 84 | /** 85 | * Tests if we show chart when we have available market data values. 86 | */ 87 | @Test 88 | fun ensureChartIsVisibleWhenMarketValuesAvailable() { 89 | val marketValCount = 20 90 | val successBean = UiDataBean.success(UiTestsDataProvider.providMarketValues(marketValCount)) 91 | marketDataLiveData.postValue(successBean) 92 | onView(withId(R.id.transactionDataProgress)).check(matches(isDisplayed())) 93 | } 94 | 95 | /** 96 | * Tests if we can show a toast message on an api or business error. 97 | */ 98 | @Test 99 | fun ensureToastMessageIsShownToNotifyUser() { 100 | val errorMessage = "Unknown Error" 101 | val errorBean = UiDataBean.error(null, Error(0, errorMessage)) 102 | marketDataLiveData.postValue(errorBean) 103 | onView(withText(errorMessage)).inRoot(withDecorView(not(`is`(homeFragment.activity?.window?.decorView)))) 104 | .check(matches(isDisplayed())) 105 | } 106 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/raqun/blockchaintracker/util/UiTestsDataProvider.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.util 2 | 3 | import com.raqun.blockchaintracker.model.MarketVal 4 | 5 | class UiTestsDataProvider { 6 | companion object { 7 | private const val timeStampStartVal = 1442534400 8 | private const val transactionStartVal = 188330.0 9 | 10 | private const val timeSampInterval = 100 11 | private const val transactionInterval = 1000 12 | 13 | fun providMarketValues(count: Int = 10): List { 14 | val values = ArrayList() 15 | for (i in 0..count) { 16 | values.add(MarketVal(timeStampStartVal + (i * timeSampInterval).toLong(), 17 | transactionStartVal + (i * transactionInterval).toDouble())) 18 | } 19 | return values 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /app/src/androidTest/java/com/raqun/blockchaintracker/util/ViewModelUtil.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.util 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProvider 5 | 6 | 7 | class ViewModelUtil private constructor() { 8 | companion object { 9 | fun createFor(model: T): ViewModelProvider.Factory { 10 | return object : ViewModelProvider.Factory { 11 | override fun create(modelClass: Class): T { 12 | if (modelClass.isAssignableFrom(model.javaClass)) { 13 | return model as T 14 | } 15 | throw IllegalArgumentException("unexpected model class $modelClass") 16 | } 17 | } 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /app/src/debug/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/debug/java/com/raqun/blockchaintracker/SingleFragmentActivity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker 2 | 3 | import android.view.ViewGroup 4 | import android.widget.FrameLayout 5 | import android.os.Bundle 6 | import android.support.annotation.VisibleForTesting 7 | import android.support.v4.app.Fragment 8 | import android.support.v7.app.AppCompatActivity 9 | 10 | /** 11 | * This is a Single fragment activity to use in Espresso tests for ui testing of Fragments 12 | */ 13 | 14 | @VisibleForTesting 15 | class SingleFragmentActivity : AppCompatActivity() { 16 | 17 | override fun onCreate(savedInstanceState: Bundle?) { 18 | super.onCreate(savedInstanceState) 19 | val content = FrameLayout(this) 20 | content.layoutParams = FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 21 | ViewGroup.LayoutParams.MATCH_PARENT) 22 | content.id = R.id.framelayout_main 23 | setContentView(content) 24 | } 25 | 26 | fun setFragment(fragment: Fragment) { 27 | supportFragmentManager.beginTransaction() 28 | .add(R.id.framelayout_main, fragment, "Test") 29 | .commit() 30 | } 31 | } -------------------------------------------------------------------------------- /app/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/Constants.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker 2 | 3 | /** 4 | * Created by tyln on 29.08.2018. 5 | */ 6 | class Constants private constructor() { 7 | companion object { 8 | const val NO_RES = 0 9 | 10 | const val DEFAULT_DATE_FORMAT = "yyyy MM dd" 11 | } 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/TrackerApp.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker 2 | 3 | import android.app.Activity 4 | import android.app.Application 5 | import com.raqun.blockchaintracker.di.DaggerAppComponent 6 | import dagger.android.AndroidInjector 7 | import dagger.android.DispatchingAndroidInjector 8 | import dagger.android.HasActivityInjector 9 | import javax.inject.Inject 10 | 11 | /** 12 | * Created by tyln on 29.08.2018. 13 | */ 14 | class TrackerApp : Application(), HasActivityInjector { 15 | 16 | @Inject lateinit var dispachingAndroidInjector: DispatchingAndroidInjector 17 | 18 | override fun onCreate() { 19 | super.onCreate() 20 | 21 | DaggerAppComponent.builder() 22 | .application(this) 23 | .build() 24 | .inject(this) 25 | } 26 | 27 | override fun activityInjector(): AndroidInjector = dispachingAndroidInjector 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/api/ApiConstants.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.api 2 | 3 | /** 4 | * Created by tyln on 29.08.2018. 5 | */ 6 | class ApiConstants private constructor() { 7 | 8 | companion object { 9 | const val TIMEOUT_INMILIS = 15000L 10 | 11 | // Date 12 | const val DEFAULT_DATE_FORMAT = "yyyy MM dd" 13 | } 14 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/api/DefaultRequestInterceptor.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.api 2 | 3 | import com.raqun.blockchaintracker.BuildConfig 4 | import okhttp3.Interceptor 5 | import okhttp3.Response 6 | import javax.inject.Inject 7 | 8 | /** 9 | * Created by tyln on 29.08.2018. 10 | */ 11 | class DefaultRequestInterceptor @Inject constructor() 12 | : Interceptor { 13 | 14 | override fun intercept(chain: Interceptor.Chain?): Response? { 15 | chain?.let { 16 | return chain.proceed(with(chain.request().newBuilder()) { 17 | addHeader("Content-Type", "application/json") 18 | addHeader("VersionCode", BuildConfig.VERSION_CODE.toString()) 19 | addHeader("VersionName", BuildConfig.VERSION_NAME) 20 | addHeader("ApplicationId", BuildConfig.APPLICATION_ID) 21 | build() 22 | }) 23 | } 24 | 25 | return null 26 | } 27 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/api/TrackerServices.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.api 2 | 3 | import com.raqun.blockchaintracker.api.response.DefaultResponse 4 | import com.raqun.blockchaintracker.model.MarketVal 5 | import io.reactivex.Single 6 | import retrofit2.http.GET 7 | import retrofit2.http.Query 8 | 9 | /** 10 | * Created by tyln on 29.08.2018. 11 | */ 12 | interface TrackerServices { 13 | @GET("charts/transactions-per-second?rollingAverage=8hours&format=json") 14 | fun getWeeklyBlockchainTransactionData(@Query("timespan") timeSpan: String): Single>> 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/api/response/DefaultResponse.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.api.response 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * Created by tyln on 29.08.2018. 7 | */ 8 | 9 | /* Reference: https://www.blockchain.com/api/charts_api 10 | 11 | "status": "ok", 12 | "name": "Confirmed Transactions Per Day", 13 | "unit": "Transactions", 14 | "period": "day", 15 | "description": "The number of daily confirmed Bitcoin transactions.", 16 | "values": 17 | */ 18 | 19 | data class DefaultResponse(@SerializedName("status") val status: String? = null, 20 | @SerializedName("name") val name: String? = null, 21 | @SerializedName("unit") val unit: String? = null, 22 | @SerializedName("period") val period: String? = null, 23 | @SerializedName("description") val desc: String? = null, 24 | @SerializedName("values") val data: T) 25 | 26 | -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/binding/BindingConversions.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.binding 2 | 3 | import android.databinding.BindingConversion 4 | import android.view.View 5 | import com.raqun.blockchaintracker.data.DataBean 6 | import com.raqun.blockchaintracker.data.DataState 7 | 8 | /** 9 | * Created by tyln on 29.08.2018. 10 | */ 11 | object BindingConversions { 12 | @JvmStatic 13 | @BindingConversion 14 | fun bindBeanToProgress(bean: DataBean?): Int = 15 | if (bean?.getState() == DataState.FETCHING && bean.getData() == null) View.VISIBLE else View.GONE 16 | 17 | @JvmStatic 18 | @BindingConversion 19 | fun bindBooleanToVisiblity(isVisible: Boolean): Int = if (isVisible) View.VISIBLE else View.GONE 20 | 21 | @JvmStatic 22 | @BindingConversion 23 | fun bindBeanToEnableState(bean: DataBean?): Boolean = !(bean != null && bean.getState() != DataState.FETCHING) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/data/DataBean.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.data 2 | 3 | import com.raqun.blockchaintracker.data.Error 4 | 5 | /** 6 | * Created by tyln on 29.08.2018. 7 | */ 8 | interface DataBean { 9 | fun getData(): T? 10 | 11 | fun getState(): DataState 12 | 13 | fun hasError(): Boolean 14 | 15 | fun getError(): Error? 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/data/DataState.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.data 2 | 3 | /** 4 | * Created by tyln on 29.08.2018. 5 | */ 6 | enum class DataState { 7 | FETCHING, 8 | SUCCESS, 9 | ERROR 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/data/Error.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.data 2 | 3 | /** 4 | * Created by tyln on 29.08.2018. 5 | */ 6 | data class Error constructor(val code: Int = 0, val message: String?) -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/data/source/MarketDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.data.source 2 | 3 | import com.raqun.blockchaintracker.api.response.DefaultResponse 4 | import com.raqun.blockchaintracker.model.MarketVal 5 | import io.reactivex.Single 6 | 7 | /** 8 | * Created by tyln on 29.08.2018. 9 | */ 10 | interface MarketDataSource { 11 | fun getWeeklyBlockchainTransactionData(weeks: String): Single>> 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/data/source/MarketRepository.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.data.source 2 | 3 | import com.raqun.blockchaintracker.api.response.DefaultResponse 4 | import com.raqun.blockchaintracker.data.source.remote.MarketRemoteDataSource 5 | import com.raqun.blockchaintracker.model.MarketVal 6 | import io.reactivex.Single 7 | import javax.inject.Inject 8 | import javax.inject.Named 9 | 10 | /** 11 | * Created by tyln on 29.08.2018. 12 | */ 13 | /** 14 | * Created by tyln on 29.08.2018. 15 | */ 16 | open class MarketRepository @Inject constructor(@Named(MarketRemoteDataSource.NAME) private val marketRemoteDataSource: MarketDataSource) 17 | : MarketDataSource { 18 | 19 | override fun getWeeklyBlockchainTransactionData(weeks: String): Single>> = 20 | marketRemoteDataSource.getWeeklyBlockchainTransactionData(weeks) 21 | 22 | companion object { 23 | const val NAME = "marketRepository" 24 | } 25 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/data/source/remote/MarketRemoteDataSource.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.data.source.remote 2 | 3 | import com.raqun.blockchaintracker.api.TrackerServices 4 | import com.raqun.blockchaintracker.api.response.DefaultResponse 5 | import com.raqun.blockchaintracker.data.source.MarketDataSource 6 | import com.raqun.blockchaintracker.model.MarketVal 7 | import io.reactivex.Single 8 | import javax.inject.Inject 9 | 10 | /** 11 | * Created by tyln on 29.08.2018. 12 | */ 13 | class MarketRemoteDataSource @Inject constructor(private val trackerServices: TrackerServices) : MarketDataSource { 14 | 15 | override fun getWeeklyBlockchainTransactionData(weeks: String): Single>> = 16 | trackerServices.getWeeklyBlockchainTransactionData(weeks) 17 | 18 | companion object { 19 | const val NAME = "marketRemoteDataSource" 20 | } 21 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/ActivityModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import com.raqun.blockchaintracker.ui.MainActivity 4 | import dagger.Module 5 | import dagger.android.ContributesAndroidInjector 6 | 7 | /** 8 | * Created by tyln on 29.08.2018. 9 | */ 10 | @Module 11 | internal abstract class ActivityModule { 12 | @ContributesAndroidInjector(modules = [(FragmentBuildersModule::class)]) 13 | @ActivityScope 14 | abstract fun provideMainActivityContributor(): MainActivity 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/ActivityScope.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Created by tyln on 29.08.2018. 7 | */ 8 | @Scope 9 | @Retention(AnnotationRetention.RUNTIME) 10 | internal annotation class ActivityScope -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/ApiModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import com.raqun.blockchaintracker.BuildConfig 4 | import com.raqun.blockchaintracker.api.ApiConstants 5 | import com.raqun.blockchaintracker.api.DefaultRequestInterceptor 6 | import com.raqun.blockchaintracker.api.TrackerServices 7 | import dagger.Module 8 | import dagger.Provides 9 | import okhttp3.Interceptor 10 | import okhttp3.OkHttpClient 11 | import okhttp3.logging.HttpLoggingInterceptor 12 | import retrofit2.Retrofit 13 | import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory 14 | import retrofit2.converter.gson.GsonConverterFactory 15 | import java.util.concurrent.TimeUnit 16 | import javax.inject.Named 17 | import javax.inject.Singleton 18 | 19 | /** 20 | * Created by tyln on 29.08.2018. 21 | */ 22 | @Module 23 | internal class ApiModule { 24 | 25 | @Provides 26 | @Singleton 27 | @Named(NAME_URL) 28 | fun provideBaseUrl(): String = BuildConfig.BASE_URL 29 | 30 | @Provides 31 | @Singleton 32 | fun provideRequestInterceptor(): Interceptor = DefaultRequestInterceptor() 33 | 34 | @Provides 35 | @Singleton 36 | fun provideLoggingInterceptor(): HttpLoggingInterceptor = 37 | HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } 38 | 39 | @Provides 40 | @Singleton 41 | fun provideOkHttpClient(requestInterceptor: DefaultRequestInterceptor, 42 | loggingInterceptor: HttpLoggingInterceptor): OkHttpClient = 43 | with(OkHttpClient.Builder()) { 44 | addInterceptor(requestInterceptor) 45 | if (BuildConfig.DEBUG) addInterceptor(loggingInterceptor) 46 | connectTimeout(ApiConstants.TIMEOUT_INMILIS, TimeUnit.MILLISECONDS) 47 | build() 48 | } 49 | 50 | @Provides 51 | @Singleton 52 | fun provideRetrofit(@Named(NAME_URL) baseUrl: String, client: OkHttpClient): Retrofit = 53 | with(Retrofit.Builder()) { 54 | baseUrl(baseUrl) 55 | client(client) 56 | addConverterFactory(GsonConverterFactory.create()) 57 | addCallAdapterFactory(RxJava2CallAdapterFactory.create()) 58 | build() 59 | } 60 | 61 | @Provides 62 | @Singleton 63 | fun provideApiServices(retrofit: Retrofit): TrackerServices = retrofit.create(TrackerServices::class.java) 64 | 65 | companion object { 66 | private const val NAME_URL = "url" 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/AppComponent.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import com.raqun.blockchaintracker.TrackerApp 4 | import dagger.BindsInstance 5 | import dagger.Component 6 | import dagger.android.AndroidInjectionModule 7 | import javax.inject.Singleton 8 | 9 | /** 10 | * Created by tyln on 29.08.2018. 11 | */ 12 | @Singleton 13 | @Component(modules = [(AndroidInjectionModule::class), 14 | (AppModule::class), 15 | (ApiModule::class), 16 | (ActivityModule::class)]) 17 | interface AppComponent { 18 | 19 | @Component.Builder 20 | interface Builder { 21 | @BindsInstance 22 | fun application(application: TrackerApp): Builder 23 | 24 | fun build(): AppComponent 25 | } 26 | 27 | fun inject(application: TrackerApp) 28 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/AppModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import android.app.Application 4 | import com.raqun.blockchaintracker.TrackerApp 5 | import com.raqun.blockchaintracker.api.TrackerServices 6 | import com.raqun.blockchaintracker.data.source.MarketDataSource 7 | import com.raqun.blockchaintracker.data.source.MarketRepository 8 | import com.raqun.blockchaintracker.data.source.remote.MarketRemoteDataSource 9 | import dagger.Module 10 | import dagger.Provides 11 | import javax.inject.Named 12 | import javax.inject.Singleton 13 | 14 | /** 15 | * Created by tyln on 29.08.2018. 16 | */ 17 | @Module(includes = [(ViewModelModule::class), (DomainModule::class)]) 18 | internal class AppModule { 19 | 20 | /** 21 | * App Scope Items 22 | */ 23 | 24 | @Provides 25 | @Singleton 26 | fun provideApplicationContext(app: Application) = app.applicationContext 27 | 28 | /** 29 | * Market Items 30 | */ 31 | 32 | @Provides 33 | @Singleton 34 | @Named(MarketRemoteDataSource.NAME) 35 | fun provideMarketRemoteDataSource(trackerServices: TrackerServices): MarketDataSource = 36 | MarketRemoteDataSource(trackerServices) 37 | 38 | @Provides 39 | @Singleton 40 | @Named(MarketRepository.NAME) 41 | fun provideMarketRepository(@Named(MarketRemoteDataSource.NAME) marketRemoteDataSource: MarketDataSource): MarketDataSource = 42 | MarketRepository(marketRemoteDataSource) 43 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/DomainModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import com.raqun.blockchaintracker.data.source.MarketDataSource 4 | import com.raqun.blockchaintracker.data.source.MarketRepository 5 | import com.raqun.blockchaintracker.domain.MarketDataUseCasImpl 6 | import com.raqun.blockchaintracker.domain.MarketDataUseCase 7 | import dagger.Module 8 | import dagger.Provides 9 | import javax.inject.Named 10 | 11 | /** 12 | * Created by tyln on 29.08.2018. 13 | */ 14 | @Module 15 | internal class DomainModule { 16 | @Provides 17 | fun provideMarketUseCase(@Named("marketRepository") marketRepository: MarketDataSource): MarketDataUseCase = 18 | MarketDataUseCasImpl(marketRepository) 19 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/FragmentBuildersModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import com.raqun.blockchaintracker.ui.home.HomeFragment 4 | import com.raqun.blockchaintracker.ui.home.HomeViewModel 5 | import dagger.Module 6 | import dagger.android.ContributesAndroidInjector 7 | 8 | /** 9 | * Created by tyln on 29.08.2018. 10 | */ 11 | @Module 12 | internal abstract class FragmentBuildersModule { 13 | @FragmentScope 14 | @ContributesAndroidInjector 15 | abstract fun contributeHomeFragment(): HomeFragment 16 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/FragmentScope.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import javax.inject.Scope 4 | 5 | /** 6 | * Created by tyln on 29.08.2018. 7 | */ 8 | @Scope 9 | @Retention(AnnotationRetention.RUNTIME) 10 | internal annotation class FragmentScope -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/ViewModelKey.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import dagger.MapKey 5 | import kotlin.reflect.KClass 6 | 7 | /** 8 | * Created by tyln on 29.08.2018. 9 | */ 10 | @Retention(AnnotationRetention.RUNTIME) 11 | @Target(AnnotationTarget.FUNCTION, AnnotationTarget.PROPERTY_GETTER, AnnotationTarget.PROPERTY_SETTER) 12 | @MapKey 13 | internal annotation class ViewModelKey(val value: KClass) 14 | -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/di/ViewModelModule.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.di 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProvider 5 | import com.raqun.blockchaintracker.ui.home.HomeViewModel 6 | import com.raqun.blockchaintracker.viewmodel.VMFactory 7 | import dagger.Binds 8 | import dagger.Module 9 | import dagger.multibindings.IntoMap 10 | 11 | /** 12 | * Created by tyln on 29.08.2018. 13 | */ 14 | @Module 15 | internal abstract class ViewModelModule { 16 | @Binds 17 | @IntoMap 18 | @ViewModelKey(HomeViewModel::class) 19 | abstract fun bindHomeViewModel(homeViewModel: HomeViewModel): ViewModel 20 | 21 | // Factory 22 | @Binds 23 | abstract fun bindViewModelFactory(vmFactory: VMFactory): ViewModelProvider.Factory 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/domain/MarketDataUseCasImpl.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.domain 2 | 3 | import com.raqun.blockchaintracker.data.source.MarketDataSource 4 | import com.raqun.blockchaintracker.data.source.MarketRepository 5 | import com.raqun.blockchaintracker.model.MarketVal 6 | import io.reactivex.Observable 7 | import javax.inject.Inject 8 | import javax.inject.Named 9 | 10 | /** 11 | * Created by tyln on 29.08.2018. 12 | */ 13 | class MarketDataUseCasImpl @Inject constructor(@Named(MarketRepository.NAME) private val marketRepository: MarketDataSource) 14 | : MarketDataUseCase { 15 | 16 | override fun fetchWeeklyMarketTransactionData(week: Int): Observable { 17 | 18 | if (week < 0) { 19 | return Observable.error(IllegalArgumentException("Week cannot be lower than 0")) 20 | } 21 | 22 | val weeksQuery = with(StringBuilder()) { 23 | append(week) 24 | append(WEEK_QUERY_PARAM_SUFFIX) 25 | toString() 26 | } 27 | 28 | return marketRepository.getWeeklyBlockchainTransactionData(weeksQuery) 29 | .toObservable() 30 | .map { 31 | it.data.toMutableList() 32 | } 33 | .flatMap { 34 | Observable.fromIterable(it) 35 | } 36 | .filter { 37 | /** 38 | * A simple business logic do demonstrate business impl in a UseCase 39 | * Just show transaction numbers bigger than zero. 40 | */ 41 | it.transactionNumber > 0 42 | } 43 | } 44 | 45 | companion object { 46 | private const val WEEK_QUERY_PARAM_SUFFIX = "weeks" 47 | } 48 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/domain/MarketDataUseCase.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.domain 2 | 3 | import com.raqun.blockchaintracker.model.MarketVal 4 | import io.reactivex.Observable 5 | import java.util.* 6 | 7 | /** 8 | * Created by tyln on 29.08.2018. 9 | */ 10 | interface MarketDataUseCase { 11 | fun fetchWeeklyMarketTransactionData(week: Int): Observable 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/extensions/ArchComponentExt.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.extensions 2 | 3 | import android.arch.lifecycle.LifecycleOwner 4 | import android.arch.lifecycle.LiveData 5 | import android.arch.lifecycle.Observer 6 | import com.raqun.blockchaintracker.data.DataBean 7 | import com.raqun.blockchaintracker.ui.BaseView 8 | import com.raqun.blockchaintracker.data.Error 9 | 10 | /** 11 | * Created by tyln on 29.08.2018. 12 | */ 13 | 14 | inline fun LiveData>.observeApi(lifecycleOwner: LifecycleOwner, 15 | crossinline body: (DataBean?) -> Unit) { 16 | observe(lifecycleOwner, Observer { bean: DataBean? -> 17 | if (bean != null && bean.hasError()) { 18 | if (lifecycleOwner is BaseView) { 19 | lifecycleOwner.onError(bean.getError()) 20 | } 21 | } 22 | body(bean) 23 | }) 24 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/extensions/ContextExt.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.extensions 2 | 3 | import android.content.Context 4 | import android.widget.Toast 5 | 6 | /** 7 | * Created by tyln on 29.08.2018. 8 | */ 9 | fun Context.alert(message: String?) = Toast.makeText(this, message, Toast.LENGTH_SHORT).show() -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/extensions/DateExt.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.extensions 2 | 3 | import java.text.SimpleDateFormat 4 | import java.util.* 5 | 6 | /** 7 | * Created by tyln on 29.08.2018. 8 | */ 9 | fun Long.toDate(targetDateFormat: String): String { 10 | val date = Date(this) 11 | return SimpleDateFormat(targetDateFormat).format(date) 12 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/extensions/FragmentManagerExt.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.extensions 2 | 3 | import android.support.v4.app.FragmentManager 4 | import android.support.v4.app.FragmentTransaction 5 | 6 | /** 7 | * Created by tyln on 29.08.2018. 8 | */ 9 | inline fun FragmentManager.transact(func: FragmentTransaction.() -> FragmentTransaction) { 10 | beginTransaction().func().commit() 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/extensions/RxExt.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.extensions 2 | 3 | import com.raqun.blockchaintracker.data.Error 4 | import io.reactivex.Observable 5 | import io.reactivex.android.schedulers.AndroidSchedulers 6 | import io.reactivex.schedulers.Schedulers 7 | 8 | /** 9 | * Created by tyln on 29.08.2018. 10 | */ 11 | fun Observable.workOnBackground(): Observable = this.subscribeOn(Schedulers.io()) 12 | .observeOn(AndroidSchedulers.mainThread()) 13 | 14 | fun Throwable?.createError(): Error? = com.raqun.blockchaintracker.data.Error(0, this?.message) -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/model/MarketVal.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.model 2 | 3 | import com.google.gson.annotations.SerializedName 4 | 5 | /** 6 | * Created by tyln on 29.08.2018. 7 | */ 8 | 9 | /* Reference: https://www.blockchain.com/api/charts_api 10 | 11 | { 12 | "x": 1442534400, // Unix timestamp (2015-09-18T00:00:00+00:00) 13 | "y": 188330.0 14 | } 15 | 16 | */ 17 | 18 | data class MarketVal(@SerializedName("x") val dateTime: Long, 19 | @SerializedName("y") val transactionNumber: Double) -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/model/UiDataBean.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.model 2 | 3 | import com.raqun.blockchaintracker.data.DataBean 4 | import com.raqun.blockchaintracker.data.DataState 5 | import com.raqun.blockchaintracker.data.Error 6 | 7 | /** 8 | * Created by tyln on 29.08.2018. 9 | */ 10 | class UiDataBean private constructor(private val dataState: DataState, 11 | private val beanData: T?, 12 | private val error: Error?) : DataBean { 13 | 14 | companion object { 15 | fun success(data: T?) = UiDataBean(DataState.SUCCESS, data, null) 16 | 17 | fun error(data: T?, error: Error?) = UiDataBean(DataState.ERROR, data, error) 18 | 19 | fun fetching(data: T?) = UiDataBean(DataState.FETCHING, data, null) 20 | } 21 | 22 | override fun getData(): T? = beanData 23 | 24 | override fun getState(): DataState = dataState 25 | 26 | override fun hasError(): Boolean = error != null 27 | 28 | override fun getError(): Error? = error 29 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/BaseActivity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui 2 | 3 | import android.os.Bundle 4 | import android.support.annotation.LayoutRes 5 | import android.support.annotation.MenuRes 6 | import android.support.annotation.StringRes 7 | import android.support.v4.app.Fragment 8 | import android.support.v7.app.AppCompatActivity 9 | import android.view.Menu 10 | import android.view.MenuItem 11 | import com.raqun.blockchaintracker.Constants 12 | import com.raqun.blockchaintracker.R 13 | import dagger.android.AndroidInjection 14 | import dagger.android.AndroidInjector 15 | import dagger.android.DispatchingAndroidInjector 16 | import dagger.android.support.HasSupportFragmentInjector 17 | import javax.inject.Inject 18 | 19 | /** 20 | * Created by tyln on 29.08.2018. 21 | */ 22 | abstract class BaseActivity : AppCompatActivity(), HasSupportFragmentInjector { 23 | 24 | @Inject 25 | lateinit var dispatchingAndroidInjector: DispatchingAndroidInjector 26 | 27 | @LayoutRes 28 | abstract fun getLayoutRes(): Int 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | AndroidInjection.inject(this) 32 | super.onCreate(savedInstanceState) 33 | setContentView(getLayoutRes()) 34 | setScreenTitle(getString(getScreenTitle())) 35 | } 36 | 37 | override fun onCreateOptionsMenu(menu: Menu?): Boolean { 38 | if (getMenuRes() != Constants.NO_RES) { 39 | menuInflater.inflate(getMenuRes(), menu) 40 | return true 41 | } 42 | 43 | return super.onCreateOptionsMenu(menu) 44 | } 45 | 46 | override fun onOptionsItemSelected(item: MenuItem?): Boolean { 47 | when (item?.itemId) { 48 | android.R.id.home -> onBackPressed() 49 | } 50 | 51 | return super.onOptionsItemSelected(item) 52 | } 53 | 54 | @MenuRes 55 | protected open fun getMenuRes(): Int = Constants.NO_RES 56 | 57 | 58 | override fun supportFragmentInjector(): AndroidInjector = dispatchingAndroidInjector 59 | 60 | @StringRes 61 | protected open fun getScreenTitle() = R.string.app_name 62 | 63 | fun setScreenTitle(title: String?) { 64 | title?.let { 65 | supportActionBar?.title = title 66 | } 67 | } 68 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/BaseFragment.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui 2 | 3 | import android.app.Application 4 | import android.arch.lifecycle.LifecycleRegistry 5 | import android.arch.lifecycle.LifecycleRegistryOwner 6 | import android.content.Context 7 | import android.os.Bundle 8 | import android.support.annotation.LayoutRes 9 | import android.support.annotation.MenuRes 10 | import android.support.annotation.StringRes 11 | import android.support.v4.app.Fragment 12 | import android.view.* 13 | import com.raqun.blockchaintracker.Constants 14 | import com.raqun.blockchaintracker.R 15 | import com.raqun.blockchaintracker.data.Error 16 | import com.raqun.blockchaintracker.extensions.alert 17 | import dagger.android.support.AndroidSupportInjection 18 | import dagger.android.support.HasSupportFragmentInjector 19 | 20 | /** 21 | * Created by tyln on 29.08.2018. 22 | */ 23 | abstract class BaseFragment : Fragment(), BaseView{ 24 | 25 | protected var navigationController: NavigationController? = null 26 | 27 | @LayoutRes 28 | protected abstract fun getLayoutRes(): Int 29 | 30 | override fun onCreate(savedInstanceState: Bundle?) { 31 | if (activity is HasSupportFragmentInjector) { 32 | AndroidSupportInjection.inject(this) 33 | } 34 | super.onCreate(savedInstanceState) 35 | navigationController = NavigationController(activity!!) 36 | if (getMenuRes() != Constants.NO_RES) { 37 | setHasOptionsMenu(true) 38 | } 39 | } 40 | 41 | override fun onCreateOptionsMenu(menu: Menu?, inflater: MenuInflater?) { 42 | super.onCreateOptionsMenu(menu, inflater) 43 | if (getMenuRes() != Constants.NO_RES) { 44 | inflater?.inflate(getMenuRes(), menu) 45 | } 46 | } 47 | 48 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 49 | return inflater.inflate(getLayoutRes(), null, false) 50 | } 51 | 52 | override fun onActivityCreated(savedInstanceState: Bundle?) { 53 | super.onActivityCreated(savedInstanceState) 54 | if (getTitleRes() != Constants.NO_RES) { 55 | setActivityTitle(getString(getTitleRes())) 56 | } 57 | } 58 | 59 | override fun onDestroyView() { 60 | navigationController = null 61 | super.onDestroyView() 62 | } 63 | 64 | override fun onError(e: Error?) { 65 | activity!!.alert(e?.message) 66 | } 67 | 68 | protected fun setActivityTitle(title: String) { 69 | if (activity is BaseActivity) { 70 | (activity as BaseActivity).setScreenTitle(title) 71 | } 72 | } 73 | 74 | @MenuRes 75 | protected open fun getMenuRes(): Int = Constants.NO_RES 76 | 77 | @StringRes 78 | protected open fun getTitleRes(): Int = R.string.app_name 79 | 80 | fun getApplication(): Application = activity!!.application 81 | 82 | fun getApplicationContext(): Context = getApplication().applicationContext 83 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/BaseView.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui 2 | 3 | import com.raqun.blockchaintracker.data.Error 4 | 5 | /** 6 | * Created by tyln on 29.08.2018. 7 | */ 8 | 9 | interface BaseView { 10 | fun onError(e: Error?) 11 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/BinderFragment.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProvider 5 | import android.arch.lifecycle.ViewModelProviders 6 | import android.databinding.DataBindingUtil 7 | import android.databinding.ViewDataBinding 8 | import android.os.Bundle 9 | import android.support.annotation.VisibleForTesting 10 | import android.view.LayoutInflater 11 | import android.view.View 12 | import android.view.ViewGroup 13 | import javax.inject.Inject 14 | 15 | /** 16 | * Created by tyln on 29.08.2018. 17 | */ 18 | abstract class BinderFragment : BaseFragment() { 19 | 20 | @VisibleForTesting 21 | @Inject 22 | lateinit var vmFactory: ViewModelProvider.Factory 23 | 24 | protected lateinit var binding: VB 25 | 26 | @VisibleForTesting 27 | lateinit var viewModel: VM 28 | 29 | abstract fun getModelClass(): Class 30 | 31 | override fun onCreate(savedInstanceState: Bundle?) { 32 | super.onCreate(savedInstanceState) 33 | viewModel = ViewModelProviders.of(this, vmFactory).get(getModelClass()) 34 | } 35 | 36 | override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { 37 | binding = DataBindingUtil.inflate(inflater, getLayoutRes(), container, false) 38 | initView() 39 | return binding.root 40 | } 41 | 42 | open protected fun initView() { 43 | // Can be overridden from subclasses 44 | } 45 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/MainActivity.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui 2 | 3 | import android.support.v7.app.AppCompatActivity 4 | import android.os.Bundle 5 | import com.raqun.blockchaintracker.R 6 | import com.raqun.blockchaintracker.extensions.transact 7 | 8 | class MainActivity : BaseActivity() { 9 | 10 | private var navigationController: NavigationController? = null 11 | 12 | override fun getLayoutRes(): Int = R.layout.activity_container 13 | 14 | override fun onCreate(savedInstanceState: Bundle?) { 15 | super.onCreate(savedInstanceState) 16 | navigationController = NavigationController(this) 17 | if (savedInstanceState == null) { 18 | navigationController?.showHome() 19 | } 20 | } 21 | 22 | override fun onDestroy() { 23 | navigationController = null 24 | super.onDestroy() 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/NavigationController.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui 2 | 3 | import android.support.annotation.IdRes 4 | import android.support.v4.app.Fragment 5 | import android.support.v4.app.FragmentActivity 6 | import com.raqun.blockchaintracker.R 7 | import com.raqun.blockchaintracker.extensions.transact 8 | import com.raqun.blockchaintracker.ui.home.HomeFragment 9 | 10 | /** 11 | * Created by tyln on 29.08.2018. 12 | */ 13 | class NavigationController(private val activity: FragmentActivity, 14 | @IdRes private val containerId: Int = R.id.framelayout_main) { 15 | 16 | enum class NavigationType { 17 | BACK, ROOT 18 | } 19 | 20 | fun showHome() { 21 | navigate(HomeFragment.newInstance(), false) 22 | } 23 | 24 | fun close() { 25 | activity.finish() 26 | } 27 | 28 | private fun navigate(fragment: Fragment, isStackable: Boolean) { 29 | activity.supportFragmentManager.transact { 30 | if (isStackable) { 31 | addToBackStack(null) 32 | } 33 | replace(containerId, fragment) 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/home/HomeFragment.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui.home 2 | 3 | import android.arch.lifecycle.Observer 4 | import android.databinding.BindingAdapter 5 | import android.os.Bundle 6 | import android.util.Log 7 | import android.view.View 8 | import android.widget.AdapterView 9 | import com.raqun.blockchaintracker.R 10 | import com.raqun.blockchaintracker.databinding.FragmentHomeBinding 11 | import com.raqun.blockchaintracker.extensions.alert 12 | import com.raqun.blockchaintracker.extensions.observeApi 13 | import com.raqun.blockchaintracker.ui.BinderFragment 14 | import kotlinx.android.synthetic.main.fragment_home.* 15 | import com.github.mikephil.charting.data.LineData 16 | import android.support.v4.content.ContextCompat 17 | import com.github.mikephil.charting.components.YAxis 18 | import com.github.mikephil.charting.data.LineDataSet 19 | import android.text.format.DateUtils 20 | import com.github.mikephil.charting.charts.LineChart 21 | import com.github.mikephil.charting.components.XAxis 22 | import com.github.mikephil.charting.data.Entry 23 | import com.raqun.blockchaintracker.Constants 24 | import com.raqun.blockchaintracker.model.MarketVal 25 | import com.raqun.blockchaintracker.util.ChartValueDateFormatter 26 | import com.raqun.blockchaintracker.util.NonFloatValueFormatter 27 | 28 | /** 29 | * Created by tyln on 29.08.2018. 30 | */ 31 | open class HomeFragment : BinderFragment(), HomeView { 32 | 33 | override fun getModelClass(): Class = HomeViewModel::class.java 34 | 35 | override fun getLayoutRes(): Int = R.layout.fragment_home 36 | 37 | override fun onActivityCreated(savedInstanceState: Bundle?) { 38 | super.onActivityCreated(savedInstanceState) 39 | 40 | spinnerWeek.onItemSelectedListener = object : AdapterView.OnItemSelectedListener { 41 | override fun onNothingSelected(p0: AdapterView<*>?) { 42 | // no-op 43 | } 44 | 45 | override fun onItemSelected(p0: AdapterView<*>?, p1: View?, p2: Int, p3: Long) { 46 | viewModel.setWeekPeriod(p2 + 1) 47 | } 48 | } 49 | 50 | viewModel.marketLiveData().observeApi(this) { 51 | binding.marketValuesDataBean = it 52 | } 53 | } 54 | 55 | override fun initView() { 56 | binding.view = this 57 | } 58 | 59 | companion object { 60 | private const val MAX_CHAR_Y_AXIS_ADDITION = 1 61 | 62 | fun newInstance() = HomeFragment() 63 | 64 | @JvmStatic 65 | @BindingAdapter(value = ["marketValues", "chartLabel"], requireAll = false) 66 | fun bindDefaultLineChart(chartView: LineChart, 67 | marketValues: List?, 68 | label: String?) { 69 | if (marketValues == null) { 70 | return 71 | } 72 | 73 | val yValues = ArrayList() 74 | var maxCount: Long = 0 75 | for (marketValue in marketValues) { 76 | val entry = Entry(marketValue.dateTime.toFloat(), 77 | marketValue.transactionNumber.toFloat()) 78 | yValues.add(entry) 79 | if (marketValue.transactionNumber > maxCount) { 80 | maxCount = marketValue.transactionNumber.toLong() 81 | } 82 | } 83 | 84 | val xAxis = chartView.xAxis 85 | val xAxisFormatter = ChartValueDateFormatter(Constants.DEFAULT_DATE_FORMAT) 86 | xAxis.valueFormatter = xAxisFormatter 87 | 88 | chartView.axisLeft.apply { 89 | axisMaximum = (maxCount + MAX_CHAR_Y_AXIS_ADDITION).toFloat() 90 | valueFormatter = NonFloatValueFormatter() 91 | } 92 | 93 | val dataSet = LineDataSet(yValues, label).apply { 94 | axisDependency = YAxis.AxisDependency.LEFT 95 | color = ContextCompat.getColor(chartView.context, R.color.colorAccent) 96 | formLineWidth = 2f 97 | setDrawIcons(true) 98 | setDrawValues(false) 99 | } 100 | 101 | val lineData = LineData(dataSet) 102 | chartView.data = lineData 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/home/HomeView.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui.home 2 | 3 | /** 4 | * Created by tyln on 29.08.2018. 5 | * 6 | * An interface for layout to keep SOLID principles with DataBinding 7 | */ 8 | interface HomeView { 9 | // no-op 10 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/home/HomeViewModel.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui.home 2 | 3 | import android.arch.lifecycle.MediatorLiveData 4 | import android.arch.lifecycle.MutableLiveData 5 | import android.arch.lifecycle.ViewModel 6 | import android.support.annotation.VisibleForTesting 7 | import com.raqun.blockchaintracker.data.DataBean 8 | import com.raqun.blockchaintracker.domain.MarketDataUseCase 9 | import com.raqun.blockchaintracker.extensions.createError 10 | import com.raqun.blockchaintracker.extensions.workOnBackground 11 | import com.raqun.blockchaintracker.model.MarketVal 12 | import com.raqun.blockchaintracker.model.UiDataBean 13 | import com.raqun.blockchaintracker.util.StableLiveData 14 | import io.reactivex.android.schedulers.AndroidSchedulers 15 | import io.reactivex.rxkotlin.subscribeBy 16 | import io.reactivex.schedulers.Schedulers 17 | import javax.inject.Inject 18 | 19 | /** 20 | * Created by tyln on 29.08.2018. 21 | */ 22 | open class HomeViewModel @Inject constructor(private val marketUseCase: MarketDataUseCase) : ViewModel() { 23 | 24 | private val marketLiveData = MediatorLiveData>>() 25 | 26 | private val weekPeriodLiveData = StableLiveData() 27 | 28 | private val marketData = ArrayList() 29 | 30 | init { 31 | marketLiveData.addSource(weekPeriodLiveData) { 32 | it?.let { 33 | fetchWeeklyMarketData(it) 34 | } 35 | } 36 | } 37 | 38 | open fun marketLiveData() = marketLiveData 39 | 40 | fun setWeekPeriod(period: Int) { 41 | weekPeriodLiveData?.setValue(period) 42 | } 43 | 44 | private fun fetchWeeklyMarketData(weeks: Int) { 45 | marketLiveData.value = UiDataBean.fetching(null) 46 | marketUseCase.fetchWeeklyMarketTransactionData(weeks) 47 | .workOnBackground() 48 | .subscribeBy( 49 | onNext = { 50 | marketData.add(it) 51 | }, 52 | onError = { 53 | marketLiveData.value = UiDataBean.error(marketData, it.createError()) 54 | }, 55 | onComplete = { 56 | marketLiveData.value = UiDataBean.success(marketData) 57 | } 58 | ) 59 | } 60 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/ui/view/DefaultLineChart.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.ui.view 2 | 3 | import android.content.Context 4 | import android.databinding.adapters.TextViewBindingAdapter.setText 5 | import android.util.AttributeSet 6 | import com.github.mikephil.charting.charts.LineChart 7 | import com.github.mikephil.charting.components.Description 8 | import com.github.mikephil.charting.components.YAxis 9 | import com.github.mikephil.charting.components.XAxis 10 | import com.raqun.blockchaintracker.R 11 | 12 | 13 | /** 14 | * Created by tyln on 29.08.2018. 15 | */ 16 | open class DefaultLineChart @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null, defStyle: Int = 0) 17 | : LineChart(context, attrs, defStyle) { 18 | 19 | init { 20 | setupChart() 21 | } 22 | 23 | private fun setupChart() { 24 | 25 | // Init Description 26 | val description = Description().apply { 27 | isEnabled = false 28 | } 29 | setDescription(description) 30 | 31 | // Init GridBackground 32 | setGridBackgroundColor(android.R.color.white) 33 | setDrawGridBackground(true) 34 | 35 | // Init Borders 36 | setDrawBorders(true) 37 | setBorderColor(android.R.color.black) 38 | setBorderWidth(1f) 39 | 40 | // Init Other Properties 41 | setPinchZoom(false) 42 | isDoubleTapToZoomEnabled = false 43 | isDragEnabled = true 44 | setNoDataText(context.getString(R.string.info_text_no_content)) 45 | setScaleEnabled(true) 46 | 47 | // Init Legend 48 | val legend = legend.apply { 49 | isEnabled = false 50 | } 51 | 52 | // Init xAxis 53 | val xAxis = xAxis.apply { 54 | isEnabled = true 55 | setCenterAxisLabels(false) 56 | gridColor = android.R.color.white 57 | setAvoidFirstLastClipping(false) 58 | setDrawLimitLinesBehindData(true) 59 | position = XAxis.XAxisPosition.BOTTOM 60 | } 61 | 62 | 63 | // Init leftAxis 64 | val leftAxis = axisLeft.apply { 65 | axisMinimum = 0f 66 | setDrawAxisLine(false) 67 | setDrawZeroLine(true) 68 | setDrawGridLines(true) 69 | gridColor = android.R.color.black 70 | axisLineColor = android.R.color.black 71 | } 72 | 73 | val rightAxis = axisRight.apply { 74 | isEnabled = false 75 | } 76 | } 77 | 78 | fun setChartTitle(title: String) { 79 | val description = Description().apply { 80 | text = title 81 | isEnabled = true 82 | } 83 | setDescription(description) 84 | } 85 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/util/ChartValueDateFormatter.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.util 2 | 3 | import android.text.format.DateUtils 4 | import com.github.mikephil.charting.components.AxisBase 5 | import com.github.mikephil.charting.formatter.IndexAxisValueFormatter 6 | import com.raqun.blockchaintracker.extensions.toDate 7 | 8 | 9 | /** 10 | * Created by tyln on 29.08.2018. 11 | */ 12 | class ChartValueDateFormatter(private val targetDateFormat: String) : IndexAxisValueFormatter() { 13 | 14 | override fun getFormattedValue(value: Float, axis: AxisBase?): String { 15 | return value.toLong().toDate(targetDateFormat) 16 | } 17 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/util/NonFloatValueFormatter.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.util 2 | 3 | import com.github.mikephil.charting.components.AxisBase 4 | import com.github.mikephil.charting.formatter.IndexAxisValueFormatter 5 | 6 | 7 | /** 8 | * Created by tyln on 29.08.2018. 9 | */ 10 | class NonFloatValueFormatter : IndexAxisValueFormatter() { 11 | 12 | override fun getFormattedValue(value: Float, axis: AxisBase?): String { 13 | return Math.round(value).toString() 14 | } 15 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/util/StableLiveData.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.util 2 | 3 | import android.arch.lifecycle.MediatorLiveData 4 | import io.reactivex.internal.util.NotificationLite.getValue 5 | 6 | /** 7 | * Created by tyln on 29.08.2018. 8 | */ 9 | class StableLiveData : MediatorLiveData() { 10 | 11 | override fun setValue(value: T) { 12 | if (isValueSame(value)) { 13 | return 14 | } 15 | 16 | super.setValue(value) 17 | } 18 | 19 | override fun postValue(value: T) { 20 | if (isValueSame(value)) { 21 | return 22 | } 23 | 24 | super.postValue(value) 25 | } 26 | 27 | fun forceSetValue(value: T) { 28 | super.setValue(value) 29 | } 30 | 31 | fun forcePostValue(value: T) { 32 | super.postValue(value) 33 | } 34 | 35 | private fun isValueSame(value: T?): Boolean { 36 | val currentValue = getValue() 37 | return currentValue === value || currentValue != null && currentValue == value 38 | } 39 | } -------------------------------------------------------------------------------- /app/src/main/java/com/raqun/blockchaintracker/viewmodel/VMFactory.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.viewmodel 2 | 3 | import android.arch.lifecycle.ViewModel 4 | import android.arch.lifecycle.ViewModelProvider 5 | import javax.inject.Inject 6 | import javax.inject.Provider 7 | import javax.inject.Singleton 8 | 9 | /** 10 | * Created by tyln on 29.08.2018. 11 | */ 12 | @Singleton 13 | class VMFactory @Inject constructor(private val creators: Map, @JvmSuppressWildcards Provider>) 14 | : ViewModelProvider.Factory { 15 | 16 | @SuppressWarnings("Unchecked") 17 | override fun create(modelClass: Class): T { 18 | var creator = creators[modelClass] 19 | 20 | if (creator == null) { 21 | for (entry in creators) { 22 | if (modelClass.isAssignableFrom(entry.key)) { 23 | creator = entry.value 24 | break 25 | } 26 | } 27 | } 28 | 29 | if (creator == null) throw IllegalArgumentException("Unknown model class$modelClass") 30 | 31 | try { 32 | return creator.get() as T 33 | } catch (e: Exception) { 34 | throw RuntimeException(e) 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /app/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /app/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /app/src/main/res/layout/activity_container.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | 10 | 11 | -------------------------------------------------------------------------------- /app/src/main/res/layout/fragment_home.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 20 | 21 | 24 | 25 | 26 | 27 | 31 | 32 | 35 | 36 | 40 | 41 | 45 | 46 | 54 | 55 | 61 | 62 | 63 | 64 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /app/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /app/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | Blockchain Market 3 | 4 | No content available 5 | Weekly Blockchain Transaction Numbers 6 | 7 | 8 | 1 Week 9 | 2 Week 10 | 3 Week 11 | 4 Week 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /app/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /app/src/test/java/com/raqun/blockchaintracker/MarketDataUseCaseTest.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker 2 | 3 | import com.raqun.blockchaintracker.api.response.DefaultResponse 4 | import com.raqun.blockchaintracker.data.source.MarketRepository 5 | import com.raqun.blockchaintracker.domain.MarketDataUseCasImpl 6 | import com.raqun.blockchaintracker.model.MarketVal 7 | import com.raqun.blockchaintracker.util.UnitTestsDataProvider 8 | import io.reactivex.Observable 9 | import io.reactivex.Single 10 | import org.junit.Test 11 | import org.mockito.ArgumentMatchers.anyString 12 | import org.mockito.Mockito 13 | 14 | class MarketDataUseCaseTest { 15 | 16 | private var marketRepository = Mockito.mock(MarketRepository::class.java)!! 17 | 18 | private val marketDataUseCase by lazy { 19 | MarketDataUseCasImpl(marketRepository) 20 | } 21 | 22 | /** 23 | * Test for api success response 24 | */ 25 | @Test 26 | fun testMarketDataUseCase_getWeeklyBlockchainTransactionData_Success() { 27 | val week = 4 28 | val maxTransactionItemCount = 10 29 | val successResponse = Single.just(DefaultResponse(data = UnitTestsDataProvider.providMarketValues(maxTransactionItemCount))) 30 | 31 | Mockito.`when`(marketRepository.getWeeklyBlockchainTransactionData(anyString())) 32 | .thenReturn(successResponse) 33 | 34 | marketDataUseCase.fetchWeeklyMarketTransactionData(week) 35 | .test() 36 | .assertSubscribed() 37 | .assertComplete() 38 | } 39 | 40 | /** 41 | * Tests for api error response 42 | */ 43 | @Test 44 | fun testMarketDataUseCase_getWeeklyBlockchainTransactionData_Error() { 45 | val week = 4 46 | val errorResponse = Throwable("Error..") 47 | 48 | Mockito.`when`(marketRepository.getWeeklyBlockchainTransactionData(anyString())) 49 | .thenReturn(Single.error(errorResponse)) 50 | 51 | marketDataUseCase.fetchWeeklyMarketTransactionData(week) 52 | .test() 53 | .assertSubscribed() 54 | .assertError(errorResponse) 55 | } 56 | 57 | /** 58 | * Test for MarketUseCase business logic which does not accept negative values for week. 59 | */ 60 | @Test 61 | fun testMarketDataUseCase_getWeeklyBlockchainTransactionData_NegativeWeek() { 62 | val week = -1 63 | val error: Observable = Observable.error(IllegalArgumentException("Week cannot be negative number!")) 64 | 65 | assert(marketDataUseCase.fetchWeeklyMarketTransactionData(week) 66 | .test() == error) 67 | } 68 | 69 | /** 70 | * Test for MarketUseCase business logic which filters possible negative transaction responses. 71 | */ 72 | @Test 73 | fun testMarketDataUseCase_getWeeklyBlockchainTransactionData_NegativeTransactionNumbers() { 74 | val week = 4 75 | val maxTransactionItemCount = 10 76 | val suitableValues = UnitTestsDataProvider.providMarketValues(maxTransactionItemCount) 77 | val successResponse = Single.just(DefaultResponse(data = UnitTestsDataProvider.provideMarketValuesWithNegativeTransactionNumbers(suitableValues))) 78 | 79 | Mockito.`when`(marketRepository.getWeeklyBlockchainTransactionData(anyString())) 80 | .thenReturn(successResponse) 81 | 82 | marketDataUseCase.fetchWeeklyMarketTransactionData(week) 83 | .test() 84 | .assertSubscribed() 85 | .assertComplete() 86 | .assertValueSet(suitableValues) 87 | } 88 | } -------------------------------------------------------------------------------- /app/src/test/java/com/raqun/blockchaintracker/util/UnitTestsDataProvider.kt: -------------------------------------------------------------------------------- 1 | package com.raqun.blockchaintracker.util 2 | 3 | import com.raqun.blockchaintracker.model.MarketVal 4 | 5 | class UnitTestsDataProvider private constructor() { 6 | companion object { 7 | private const val timeStampStartVal = 1442534400 8 | private const val transactionStartVal = 188330.0 9 | 10 | private const val timeSampInterval = 100 11 | private const val transactionInterval = 1000 12 | 13 | /** 14 | * Creates suitable market values for app. 15 | */ 16 | fun providMarketValues(count: Int = 10): List { 17 | val values = ArrayList() 18 | for (i in 0..count) { 19 | values.add(MarketVal(timeStampStartVal + (i * timeSampInterval).toLong(), 20 | transactionStartVal + (i * transactionInterval).toDouble())) 21 | } 22 | return values 23 | } 24 | 25 | /** 26 | * Creates suitable + unsuitable market values for app. 27 | * In MarketDataUseCase negative transaction values filtered. 28 | * Thus we add some negative transaction values to see if we can actually filter. 29 | */ 30 | fun provideMarketValuesWithNegativeTransactionNumbers(marketValues: List): List { 31 | val values = ArrayList() 32 | values.addAll(marketValues) 33 | values.add(MarketVal(timeStampStartVal.toLong(), -100.0)) 34 | values.add(MarketVal(timeStampStartVal.toLong(), -110.0)) 35 | values.add(MarketVal(timeStampStartVal.toLong(), -120.0)) 36 | return values 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /art/home_screen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/art/home_screen.jpg -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | // Top-level build file where you can add configuration options common to all sub-projects/modules. 2 | 3 | buildscript { 4 | ext.kotlin_version = '1.2.50' 5 | repositories { 6 | google() 7 | jcenter() 8 | } 9 | dependencies { 10 | classpath 'com.android.tools.build:gradle:3.1.4' 11 | classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" 12 | 13 | // NOTE: Do not place your application dependencies here; they belong 14 | // in the individual module build.gradle files 15 | } 16 | } 17 | 18 | allprojects { 19 | repositories { 20 | google() 21 | jcenter() 22 | maven { url 'https://jitpack.io' } 23 | } 24 | } 25 | 26 | task clean(type: Delete) { 27 | delete rootProject.buildDir 28 | } 29 | 30 | ext { 31 | supportLibraryVersion = '27.1.1' 32 | retrofitVersion = '2.3.0' 33 | loggingInterceptorVersion = '3.8.0' 34 | archExtensionsVersion = '1.1.1' 35 | archLifecycleVersion = '1.1.1' 36 | daggerVersion = '2.11' 37 | gsonVersion = '2.0.2' 38 | rxKotlinVersion = '2.1.0' 39 | dataBindingCompilerVersion = '2.3.1' 40 | okHttpVersion = '3.10.0' 41 | espressoVersion = "2.2.2" 42 | mockitoVersion = '2.18.3' 43 | mpPieChartVersion = '3.0.3' 44 | archCoreTestingVersion = '1.1.1' 45 | testRulesVersion = '1.0.2' 46 | testRunnerVersion = '1.0.2' 47 | } 48 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/savepopulation/blockchain-tracker/b61d3c91348e6ceb215e92c1b3cb9db2408cc2fb/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Thu Aug 30 21:33:16 EET 2018 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.4-all.zip 7 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':app' 2 | --------------------------------------------------------------------------------