diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..796b96d --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..8f6b331 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,76 @@ +apply plugin: 'com.android.application' + +apply plugin: 'kotlin-android' + +apply plugin: 'kotlin-android-extensions' + +apply plugin: 'kotlin-kapt' + +android { + compileSdkVersion 29 + buildToolsVersion "29.0.2" + defaultConfig { + applicationId "com.example.sunday" + minSdkVersion 28 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + + dataBinding{ + enabled = true + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation"org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'androidx.core:core-ktx:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.12' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + + // material + implementation "com.google.android.material:material:1.0.0" + + // ViewModel and LiveData + implementation "androidx.lifecycle:lifecycle-extensions:2.2.0-rc01" + implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0-rc01' + + + //Retrofit2 + implementation 'com.squareup.retrofit2:retrofit:2.6.0' + implementation 'com.squareup.retrofit2:converter-gson:2.6.0' + implementation "com.squareup.retrofit2:adapter-rxjava2:2.6.0" + + //Glide + implementation 'com.github.bumptech.glide:glide:4.10.0' + annotationProcessor 'com.github.bumptech.glide:compiler:4.10.0' + + //Browser Helper + implementation 'com.google.androidbrowserhelper:androidbrowserhelper:0.1.0-alpha1' + + // RxJava2 + implementation "io.reactivex.rxjava2:rxandroid:2.1.1" + implementation "io.reactivex.rxjava2:rxjava:2.2.15" + + //Koin lib + implementation 'org.koin:koin-androidx-scope:2.0.1' + implementation 'org.koin:koin-androidx-viewmodel:2.0.1' + implementation 'org.koin:koin-androidx-ext:2.0.1' + + // Coroutines + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.0' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0' + + +} diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..f1b4245 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/app/src/androidTest/java/com/example/sunday/ExampleInstrumentedTest.kt b/app/src/androidTest/java/com/example/sunday/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..54ae364 --- /dev/null +++ b/app/src/androidTest/java/com/example/sunday/ExampleInstrumentedTest.kt @@ -0,0 +1,22 @@ +package com.example.sunday + +import androidx.test.ext.junit.runners.AndroidJUnit4 +import androidx.test.platform.app.InstrumentationRegistry +import org.junit.Assert.assertEquals +import org.junit.Test +import org.junit.runner.RunWith + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("com.example.sunday", appContext.packageName) + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0ec1201 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,28 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/CoinApplication.kt b/app/src/main/java/com/example/sunday/CoinApplication.kt new file mode 100644 index 0000000..3e33e8b --- /dev/null +++ b/app/src/main/java/com/example/sunday/CoinApplication.kt @@ -0,0 +1,21 @@ +package com.example.sunday + + +import android.app.Application +import com.example.sunday.di.networkModuelUpbit +import com.example.sunday.di.viewModelModule +import org.koin.android.ext.koin.androidContext +import org.koin.core.context.startKoin + + +class CoinApplication : Application() { + private val moduleList = listOf(viewModelModule, networkModuelUpbit) + + override fun onCreate() { + super.onCreate() + startKoin { + androidContext(this@CoinApplication) + modules(moduleList) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/base/BaseActivity.kt b/app/src/main/java/com/example/sunday/base/BaseActivity.kt new file mode 100644 index 0000000..6233488 --- /dev/null +++ b/app/src/main/java/com/example/sunday/base/BaseActivity.kt @@ -0,0 +1,34 @@ +package com.example.sunday.base + +import android.content.pm.ActivityInfo +import android.os.Bundle +import android.widget.Toast +import androidx.annotation.LayoutRes +import androidx.appcompat.app.AppCompatActivity +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding + +abstract class BaseActivity(@LayoutRes private val layoutId: Int) : + AppCompatActivity() { + + protected lateinit var binding: B + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + binding = DataBindingUtil.setContentView(this, layoutId) + binding.lifecycleOwner = this + + setOrientationToPortrait() + } + + protected fun showToastMessage(msg: String?) { + if (!msg.isNullOrEmpty()) { + Toast.makeText(this, msg, Toast.LENGTH_SHORT).show() + } + } + + private fun setOrientationToPortrait() { + requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/base/BaseViewHolder.kt b/app/src/main/java/com/example/sunday/base/BaseViewHolder.kt new file mode 100644 index 0000000..d199f53 --- /dev/null +++ b/app/src/main/java/com/example/sunday/base/BaseViewHolder.kt @@ -0,0 +1,16 @@ +package com.example.sunday.base + +import android.view.LayoutInflater +import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.databinding.DataBindingUtil +import androidx.databinding.ViewDataBinding +import androidx.recyclerview.widget.RecyclerView + +abstract class BaseViewHolder(@LayoutRes private val layoutId: Int, parent: ViewGroup) : + RecyclerView.ViewHolder( + LayoutInflater.from(parent.context).inflate(layoutId, parent, false) + ) { + + protected val binding: B = DataBindingUtil.bind(itemView)!! +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/enums/BaseCurrency.kt b/app/src/main/java/com/example/sunday/data/enums/BaseCurrency.kt new file mode 100644 index 0000000..0421216 --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/enums/BaseCurrency.kt @@ -0,0 +1,28 @@ +package com.example.sunday.data.enums + +enum class BaseCurrency { + KRW, + BTC, + ETH, + USDT, + BNB, + USD, + EUR, + GBP, + JPY, + EOS, + HT, + BCH, + EURS, + DAI, + TUSD, + QC, + ZB, + PAX, + LBCN, + QTUM, + ELA, + DAX, + BIX, + GUSD +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/enums/Exchange.kt b/app/src/main/java/com/example/sunday/data/enums/Exchange.kt new file mode 100644 index 0000000..201cd97 --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/enums/Exchange.kt @@ -0,0 +1,7 @@ +package com.example.sunday.data.enums + +enum class Exchange(val exchangeName: String) { + UPBIT("Upbit"), + BITHUMB("Bithumb"), + COINONE("Coinone") +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/model/ExchangeTicker.kt b/app/src/main/java/com/example/sunday/data/model/ExchangeTicker.kt new file mode 100644 index 0000000..e456ffd --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/model/ExchangeTicker.kt @@ -0,0 +1,12 @@ +package com.example.sunday.data.model + +class ExchangeTicker ( + val exchangeName: String, + ticker: Ticker) + : Ticker(currency = ticker.currency, + baseCurrency = ticker.baseCurrency, + last = ticker.last, + high = ticker.high, + low = ticker.low, + volume = ticker.volume, + diff = ticker.diff) \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/model/Ticker.kt b/app/src/main/java/com/example/sunday/data/model/Ticker.kt new file mode 100644 index 0000000..b7a406b --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/model/Ticker.kt @@ -0,0 +1,14 @@ +package com.example.sunday.data.model + +open class Ticker( + var currency: String? = "", + var baseCurrency: String? = "", + var last: Double?, + var high: Double?, + var low: Double?, + var volume: Double?, + var diff: Double? = null) : TickerProvider{ + + override fun toTicker(): Ticker = this + override fun toExchangeTicker(exchangeName: String): ExchangeTicker = ExchangeTicker(exchangeName, this) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/model/TickerProvider.kt b/app/src/main/java/com/example/sunday/data/model/TickerProvider.kt new file mode 100644 index 0000000..3155f1e --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/model/TickerProvider.kt @@ -0,0 +1,6 @@ +package com.example.sunday.data.model + +interface TickerProvider { + fun toTicker(): Ticker + fun toExchangeTicker(exchangeName: String = "empty") : ExchangeTicker +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/repository/ticker/BithumbRepository.kt b/app/src/main/java/com/example/sunday/data/repository/ticker/BithumbRepository.kt new file mode 100644 index 0000000..52f45f3 --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/repository/ticker/BithumbRepository.kt @@ -0,0 +1,9 @@ +package com.example.sunday.data.repository.ticker + +import com.example.sunday.data.soruce.remote.RemoteTickerDataSource + +class BithumbRepository(private val remoteTickerDataSource: RemoteTickerDataSource) : TickerRepository { + override suspend fun getAllTicker():Any = remoteTickerDataSource.getAllTicker() + override suspend fun getTicker(currency: String) = remoteTickerDataSource.getTicker(currency) + override suspend fun getExchangeTicker(currency: String) = remoteTickerDataSource.getExchangeTicker(currency) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/repository/ticker/CoinoneRepository.kt b/app/src/main/java/com/example/sunday/data/repository/ticker/CoinoneRepository.kt new file mode 100644 index 0000000..3c88879 --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/repository/ticker/CoinoneRepository.kt @@ -0,0 +1,9 @@ +package com.example.sunday.data.repository.ticker + +import com.example.sunday.data.soruce.remote.RemoteTickerDataSource + +class CoinoneRepository(private val remoteTickerDataSource: RemoteTickerDataSource) : TickerRepository { + override suspend fun getAllTicker() = remoteTickerDataSource.getAllTicker() + override suspend fun getTicker(currency: String) = remoteTickerDataSource.getTicker(currency) + override suspend fun getExchangeTicker(currency: String) = remoteTickerDataSource.getExchangeTicker(currency) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/repository/ticker/TickerRepository.kt b/app/src/main/java/com/example/sunday/data/repository/ticker/TickerRepository.kt new file mode 100644 index 0000000..e3bc852 --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/repository/ticker/TickerRepository.kt @@ -0,0 +1,7 @@ +package com.example.sunday.data.repository.ticker + +interface TickerRepository { + suspend fun getAllTicker():Any + suspend fun getTicker(currency: String):Any + suspend fun getExchangeTicker(currency: String):Any +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/repository/ticker/UpbitRepository.kt b/app/src/main/java/com/example/sunday/data/repository/ticker/UpbitRepository.kt new file mode 100644 index 0000000..f54b9b1 --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/repository/ticker/UpbitRepository.kt @@ -0,0 +1,9 @@ +package com.example.sunday.data.repository.ticker + +import com.example.sunday.data.soruce.remote.RemoteTickerDataSource + +class UpbitRepository(private val remoteTickerDataSource: RemoteTickerDataSource) : TickerRepository { + override suspend fun getAllTicker(): Any = remoteTickerDataSource.getAllTicker() + override suspend fun getTicker(currency: String): Any = remoteTickerDataSource.getTicker(currency) + override suspend fun getExchangeTicker(currency: String): Any = remoteTickerDataSource.getExchangeTicker(currency) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteBitumbDataSoruceImpl.kt b/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteBitumbDataSoruceImpl.kt new file mode 100644 index 0000000..ceec69b --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteBitumbDataSoruceImpl.kt @@ -0,0 +1,9 @@ +package com.example.sunday.data.soruce.remote + +import com.example.sunday.network.api.BithumbApi + +class RemoteBitumbDataSoruceImpl(private val networkApi: BithumbApi) : RemoteTickerDataSource { + override suspend fun getAllTicker(): Any = networkApi.getTickerList() + override suspend fun getTicker(currency: String): Any = this + override suspend fun getExchangeTicker(currency: String):Any = networkApi.getExchangeTicker(currency) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteCoinoneDataSourceImpl.kt b/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteCoinoneDataSourceImpl.kt new file mode 100644 index 0000000..8d6ae0c --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteCoinoneDataSourceImpl.kt @@ -0,0 +1,9 @@ +package com.example.sunday.data.soruce.remote + +import com.example.sunday.network.api.CoinoneApi + +class RemoteCoinoneDataSourceImpl(private val networkApi: CoinoneApi) : RemoteTickerDataSource { + override suspend fun getAllTicker(): Any = networkApi.getAllTicker() + override suspend fun getTicker(currency: String): Any = networkApi.getTicker(currency) + override suspend fun getExchangeTicker(currency: String): Any = networkApi.getExchangeTicker(currency) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteTickerDataSource.kt b/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteTickerDataSource.kt new file mode 100644 index 0000000..e037fe2 --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteTickerDataSource.kt @@ -0,0 +1,7 @@ +package com.example.sunday.data.soruce.remote + +interface RemoteTickerDataSource { + suspend fun getAllTicker():Any + suspend fun getTicker(currency: String):Any + suspend fun getExchangeTicker(currency: String):Any +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteUpbitDataSourceImpl.kt b/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteUpbitDataSourceImpl.kt new file mode 100644 index 0000000..39b91b6 --- /dev/null +++ b/app/src/main/java/com/example/sunday/data/soruce/remote/RemoteUpbitDataSourceImpl.kt @@ -0,0 +1,9 @@ +package com.example.sunday.data.soruce.remote + +import com.example.sunday.network.api.UpbitApi + +class RemoteUpbitDataSourceImpl(private val networkApi: UpbitApi) : RemoteTickerDataSource { + override suspend fun getAllTicker(): Any = networkApi.getMarket() + override suspend fun getTicker(currency: String): Any = networkApi.getTicker(currency) + override suspend fun getExchangeTicker(currency: String): Any = networkApi.getExchangeTicker(currency) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/di/NetworkModule.kt b/app/src/main/java/com/example/sunday/di/NetworkModule.kt new file mode 100644 index 0000000..743fe20 --- /dev/null +++ b/app/src/main/java/com/example/sunday/di/NetworkModule.kt @@ -0,0 +1,47 @@ +package com.example.sunday.di + +import com.example.sunday.network.api.BithumbApi +import com.example.sunday.network.api.CoinoneApi +import com.example.sunday.network.api.UpbitApi +import org.koin.dsl.module +import retrofit2.CallAdapter +import retrofit2.Converter +import retrofit2.Retrofit +import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory +import retrofit2.converter.gson.GsonConverterFactory + +private const val UPBIT_URL = "https://api.upbit.com" +private const val BITHUMB_URL = "https://api.bithumb.com" +private const val COINONE_URL = "https://api.coinone.co.kr" + +val networkModuelUpbit = module { + single { + Retrofit.Builder() + .addConverterFactory(get()) + .addCallAdapterFactory(get()) + .baseUrl(UPBIT_URL) + .build() + .create(UpbitApi::class.java) + } + + single { + Retrofit.Builder() + .addConverterFactory(get()) + .addCallAdapterFactory(get()) + .baseUrl(BITHUMB_URL) + .build() + .create(BithumbApi::class.java) + } + + single{ + Retrofit.Builder() + .addConverterFactory(get()) + .addCallAdapterFactory(get()) + .baseUrl(COINONE_URL) + .build() + .create(CoinoneApi::class.java) + } + + single { GsonConverterFactory.create() as Converter.Factory } + single { RxJava2CallAdapterFactory.create() as CallAdapter.Factory } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/di/ViewModelModule.kt b/app/src/main/java/com/example/sunday/di/ViewModelModule.kt new file mode 100644 index 0000000..528a0a7 --- /dev/null +++ b/app/src/main/java/com/example/sunday/di/ViewModelModule.kt @@ -0,0 +1,27 @@ +package com.example.sunday.di + +import com.example.sunday.data.enums.Exchange +import com.example.sunday.data.repository.ticker.BithumbRepository +import com.example.sunday.data.repository.ticker.CoinoneRepository +import com.example.sunday.data.repository.ticker.TickerRepository +import com.example.sunday.data.repository.ticker.UpbitRepository +import com.example.sunday.data.soruce.remote.RemoteBitumbDataSoruceImpl +import com.example.sunday.data.soruce.remote.RemoteCoinoneDataSourceImpl +import com.example.sunday.data.soruce.remote.RemoteUpbitDataSourceImpl +import com.example.sunday.viewmodel.ExchangeViewModel +import com.example.sunday.viewmodel.MainViewModel +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val viewModelModule = module { + viewModel { MainViewModel( + mapOf(Exchange.UPBIT.exchangeName to UpbitRepository(RemoteUpbitDataSourceImpl(get())) as TickerRepository, + Exchange.BITHUMB.exchangeName to BithumbRepository(RemoteBitumbDataSoruceImpl(get())) as TickerRepository, + Exchange.COINONE.exchangeName to CoinoneRepository(RemoteCoinoneDataSourceImpl(get())) as TickerRepository)) } + + viewModel { ExchangeViewModel( + mapOf(Exchange.UPBIT.exchangeName to UpbitRepository(RemoteUpbitDataSourceImpl(get())) as TickerRepository, + Exchange.BITHUMB.exchangeName to BithumbRepository(RemoteBitumbDataSoruceImpl(get())) as TickerRepository, + Exchange.COINONE.exchangeName to CoinoneRepository(RemoteCoinoneDataSourceImpl(get())) as TickerRepository)) } +} + diff --git a/app/src/main/java/com/example/sunday/network/api/BithumbApi.kt b/app/src/main/java/com/example/sunday/network/api/BithumbApi.kt new file mode 100644 index 0000000..2825604 --- /dev/null +++ b/app/src/main/java/com/example/sunday/network/api/BithumbApi.kt @@ -0,0 +1,15 @@ +package com.example.sunday.network.api + +import com.example.sunday.network.response.bithumb.BithumbAllResponse +import retrofit2.http.GET +import retrofit2.http.Path + +interface BithumbApi { + + @GET("/public/ticker/all") + suspend fun getTickerList(): BithumbAllResponse + + + @GET("/public/ticker/{exchangeName}") + suspend fun getExchangeTicker(@Path("exchangeName") exchangeName: String) : BithumbAllResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/network/api/CoinoneApi.kt b/app/src/main/java/com/example/sunday/network/api/CoinoneApi.kt new file mode 100644 index 0000000..3f00c77 --- /dev/null +++ b/app/src/main/java/com/example/sunday/network/api/CoinoneApi.kt @@ -0,0 +1,17 @@ +package com.example.sunday.network.api + +import com.example.sunday.network.response.coinone.CoinoneResponse +import retrofit2.http.GET +import retrofit2.http.Query + +interface CoinoneApi { + + @GET("/ticker/?currency=all") + suspend fun getAllTicker(): Map + + @GET("/ticker/?") + suspend fun getTicker(@Query("currency") currency: String): CoinoneResponse + + @GET("/ticker/?") + suspend fun getExchangeTicker(@Query("currency") currency: String): CoinoneResponse +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/network/api/UpbitApi.kt b/app/src/main/java/com/example/sunday/network/api/UpbitApi.kt new file mode 100644 index 0000000..7aad649 --- /dev/null +++ b/app/src/main/java/com/example/sunday/network/api/UpbitApi.kt @@ -0,0 +1,19 @@ +package com.example.sunday.network.api + +import com.example.sunday.network.response.upbit.UpbitMarketResponse +import com.example.sunday.network.response.upbit.UpbitTickerResponse +import retrofit2.http.GET +import retrofit2.http.Query + +interface UpbitApi { + + @GET("/v1/market/all") + suspend fun getMarket(): List + + @GET("/v1/ticker?") + suspend fun getTicker(@Query("markets") markets: String) : List + + @GET("/v1/ticker?") + suspend fun getExchangeTicker(@Query("markets") markets: String) : List + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/network/response/bithumb/BithumbAllResponse.kt b/app/src/main/java/com/example/sunday/network/response/bithumb/BithumbAllResponse.kt new file mode 100644 index 0000000..48e96d7 --- /dev/null +++ b/app/src/main/java/com/example/sunday/network/response/bithumb/BithumbAllResponse.kt @@ -0,0 +1,10 @@ +package com.example.sunday.network.response.bithumb + +import com.google.gson.annotations.SerializedName + +data class BithumbAllResponse( + @SerializedName("status") + val status: String, + @SerializedName("data") + val item: HashMap +) \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/network/response/bithumb/BithumbTickerResponse.kt b/app/src/main/java/com/example/sunday/network/response/bithumb/BithumbTickerResponse.kt new file mode 100644 index 0000000..6936958 --- /dev/null +++ b/app/src/main/java/com/example/sunday/network/response/bithumb/BithumbTickerResponse.kt @@ -0,0 +1,51 @@ +package com.example.sunday.network.response.bithumb + +import com.example.sunday.data.model.ExchangeTicker +import com.example.sunday.data.model.Ticker +import com.example.sunday.data.model.TickerProvider +import com.google.gson.annotations.SerializedName + +data class BithumbTickerResponse( + @SerializedName("acc_trade_value") + val accTradeValue: String, + @SerializedName("acc_trade_value_24H") + val accTradeValue24H: Double, + @SerializedName("closing_price") + val closingPrice: Double, + @SerializedName("fluctate_24H") + val fluctate24H: Double, + @SerializedName("fluctate_rate_24H") + val fluctateRate24H: String, + @SerializedName("max_price") + val maxPrice: Double, + @SerializedName("min_price") + val minPrice: Double, + @SerializedName("opening_price") + val openingPrice: Double, + @SerializedName("prev_closing_price") + val prevClosingPrice: Double, + @SerializedName("units_traded") + val unitsTraded: String, + @SerializedName("units_traded_24H") + val unitsTraded24H: String +) : TickerProvider{ + override fun toTicker() = + Ticker(baseCurrency = "KRW", + last = closingPrice, + high = maxPrice, + low = minPrice, + diff = fluctate24H, + volume = accTradeValue24H) + + fun toTicker(name: String) = + Ticker(currency = name, + baseCurrency = "KRW", + last = closingPrice, + high = maxPrice, + low = minPrice, + diff = fluctate24H, + volume = accTradeValue24H) + + override fun toExchangeTicker(exchangeName: String): ExchangeTicker = + ExchangeTicker(exchangeName, toTicker()) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/network/response/coinone/CoinoneResponse.kt b/app/src/main/java/com/example/sunday/network/response/coinone/CoinoneResponse.kt new file mode 100644 index 0000000..931a7e5 --- /dev/null +++ b/app/src/main/java/com/example/sunday/network/response/coinone/CoinoneResponse.kt @@ -0,0 +1,53 @@ +package com.example.sunday.network.response.coinone + + +import com.example.sunday.data.model.ExchangeTicker +import com.example.sunday.data.model.Ticker +import com.example.sunday.data.model.TickerProvider +import com.google.gson.annotations.SerializedName + +data class CoinoneResponse( + @SerializedName("currency") + val currency: String, + @SerializedName("errorCode") + val errorCode: String, + @SerializedName("first") + val first: Double, + @SerializedName("high") + val high: Double, + @SerializedName("last") + val last: Double, + @SerializedName("low") + val low: Double, + @SerializedName("result") + val result: String, + @SerializedName("timestamp") + val timestamp: String, + @SerializedName("volume") + val volume: Double, + @SerializedName("yesterday_first") + val yesterdayFirst: String, + @SerializedName("yesterday_high") + val yesterdayHigh: String, + @SerializedName("yesterday_last") + val yesterdayLast: String, + @SerializedName("yesterday_low") + val yesterdayLow: String, + @SerializedName("yesterday_volume") + val yesterdayVolume: String +) : TickerProvider{ + override fun toTicker(): Ticker { + val diff = (last - first) / first * 100 + return Ticker(currency, + baseCurrency = "KRW", + last = last, + high = high, + low = low, + diff = diff, + volume = volume * last) + + } + + override fun toExchangeTicker(exchangeName: String): ExchangeTicker = + ExchangeTicker(exchangeName, toTicker()) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/network/response/upbit/UpbitMarketResponse.kt b/app/src/main/java/com/example/sunday/network/response/upbit/UpbitMarketResponse.kt new file mode 100644 index 0000000..0ca6f84 --- /dev/null +++ b/app/src/main/java/com/example/sunday/network/response/upbit/UpbitMarketResponse.kt @@ -0,0 +1,12 @@ +package com.example.sunday.network.response.upbit + +import com.google.gson.annotations.SerializedName + +data class UpbitMarketResponse ( + @SerializedName("english_name") + val englishName: String, + @SerializedName("korean_name") + val koreanName: String, + @SerializedName("market") + val market: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/network/response/upbit/UpbitTickerResponse.kt b/app/src/main/java/com/example/sunday/network/response/upbit/UpbitTickerResponse.kt new file mode 100644 index 0000000..8c165df --- /dev/null +++ b/app/src/main/java/com/example/sunday/network/response/upbit/UpbitTickerResponse.kt @@ -0,0 +1,74 @@ +package com.example.sunday.network.response.upbit + +import com.example.sunday.data.model.ExchangeTicker +import com.example.sunday.data.model.Ticker +import com.example.sunday.data.model.TickerProvider +import com.google.gson.annotations.SerializedName + +data class UpbitTickerResponse ( + @SerializedName("acc_trade_price") + val accTradePrice: Double, + @SerializedName("acc_trade_price_24h") + val accTradePrice24h: Double, + @SerializedName("acc_trade_volume") + val accTradeVolume: Double, + @SerializedName("acc_trade_volume_24h") + val accTradeVolume24h: Double, + @SerializedName("change") + val change: String, + @SerializedName("change_price") + val changePrice: Double, + @SerializedName("change_rate") + val changeRate: Double, + @SerializedName("high_price") + val highPrice: Double, + @SerializedName("highest_52_week_date") + val highest52WeekDate: String, + @SerializedName("highest_52_week_price") + val highest52WeekPrice: Double, + @SerializedName("low_price") + val lowPrice: Double, + @SerializedName("lowest_52_week_date") + val lowest52WeekDate: String, + @SerializedName("lowest_52_week_price") + val lowest52WeekPrice: Double, + @SerializedName("market") + val market: String, + @SerializedName("opening_price") + val openingPrice: Double, + @SerializedName("prev_closing_price") + val prevClosingPrice: Double, + @SerializedName("signed_change_price") + val signedChangePrice: Double, + @SerializedName("signed_change_rate") + val signedChangeRate: Double, + @SerializedName("timestamp") + val timestamp: Long, + @SerializedName("trade_date") + val tradeDate: String, + @SerializedName("trade_date_kst") + val tradeDateKst: String, + @SerializedName("trade_price") + val tradePrice: Double, + @SerializedName("trade_time") + val tradeTime: String, + @SerializedName("trade_time_kst") + val tradeTimeKst: String, + @SerializedName("trade_timestamp") + val tradeTimestamp: Long, + @SerializedName("trade_volume") + val tradeVolume: Double +) : TickerProvider{ + override fun toTicker() = + Ticker( + currency = market.split("-")[1], + baseCurrency = market.split("-")[0], + last = tradePrice, + high = highPrice, + low = lowPrice, + diff = changeRate * if (change == "RISE" ) 100 else -100, + volume = accTradeVolume24h) + + override fun toExchangeTicker(exchangeName: String): ExchangeTicker = + ExchangeTicker(exchangeName, toTicker()) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/ui/CoinAdapter.kt b/app/src/main/java/com/example/sunday/ui/CoinAdapter.kt new file mode 100644 index 0000000..3c4817c --- /dev/null +++ b/app/src/main/java/com/example/sunday/ui/CoinAdapter.kt @@ -0,0 +1,44 @@ +package com.example.sunday.ui + +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.example.sunday.R +import com.example.sunday.base.BaseViewHolder +import com.example.sunday.databinding.ItemCoinBinding +import com.example.sunday.ui.listener.ItemClickListener +import com.example.sunday.ui.model.UTicker + +class CoinAdapter : RecyclerView.Adapter() { + + private val data = mutableListOf() + private lateinit var itemClickListener: ItemClickListener + + fun setData(newData: List?){ + if(newData != null){ + data.clear() + data.addAll(newData) + notifyDataSetChanged() + } + } + + fun setItemClickListner(itemClickListener: ItemClickListener){ + this.itemClickListener = itemClickListener + } + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CoinViewHolder = CoinViewHolder(parent) + + override fun getItemCount(): Int = data.size + + override fun onBindViewHolder(holder: CoinViewHolder, position: Int) { + holder.bindTo(data[position]) + holder.itemView.setOnClickListener { itemClickListener.onClick(it, data[position].name!!) } + } + + + class CoinViewHolder(parent: ViewGroup): BaseViewHolder(R.layout.item_coin, parent){ + fun bindTo(uTicker: UTicker){ + binding.item = uTicker + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/ui/ExchangeActivity.kt b/app/src/main/java/com/example/sunday/ui/ExchangeActivity.kt new file mode 100644 index 0000000..6a3d736 --- /dev/null +++ b/app/src/main/java/com/example/sunday/ui/ExchangeActivity.kt @@ -0,0 +1,33 @@ +package com.example.sunday.ui + +import android.os.Bundle +import com.example.sunday.R +import com.example.sunday.base.BaseActivity +import com.example.sunday.databinding.ActivityExchangeBinding +import com.example.sunday.viewmodel.ExchangeViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel + +class ExchangeActivity : BaseActivity(R.layout.activity_exchange) { + + private val viewModel by viewModel() + private val exchangeAdapter by lazy { ExchangeAdapter() } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initViewModel() + initRecyclerView() + viewModel.getExchangeTicker() + } + + fun initViewModel(){ + viewModel.liveCurrency.value = intent.getStringExtra("currency") + binding.vm = viewModel + } + + fun initRecyclerView(){ + with(binding.recyclerViewExchange){ + adapter = exchangeAdapter + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/ui/ExchangeAdapter.kt b/app/src/main/java/com/example/sunday/ui/ExchangeAdapter.kt new file mode 100644 index 0000000..7b20b52 --- /dev/null +++ b/app/src/main/java/com/example/sunday/ui/ExchangeAdapter.kt @@ -0,0 +1,38 @@ +package com.example.sunday.ui + +import android.view.ViewGroup +import androidx.recyclerview.widget.RecyclerView +import com.example.sunday.R +import com.example.sunday.base.BaseViewHolder +import com.example.sunday.databinding.ItemExchangeBinding +import com.example.sunday.ui.model.ETicker + +class ExchangeAdapter : RecyclerView.Adapter() { + + private val data = mutableListOf() + + fun setData(newData: List?){ + if(newData != null){ + data.clear() + data.addAll(newData) + notifyDataSetChanged() + } + } + + + override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ExchangeViewHolder = ExchangeViewHolder(parent) + + override fun getItemCount(): Int = data.size + + override fun onBindViewHolder(holder: ExchangeViewHolder, position: Int) { + holder.binnTo(data[position]) + } + + + class ExchangeViewHolder(parent: ViewGroup): BaseViewHolder(R.layout.item_exchange, parent) { + fun binnTo(exchangeTicker: ETicker){ + binding.item = exchangeTicker + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/ui/MainActivity.kt b/app/src/main/java/com/example/sunday/ui/MainActivity.kt new file mode 100644 index 0000000..33faf76 --- /dev/null +++ b/app/src/main/java/com/example/sunday/ui/MainActivity.kt @@ -0,0 +1,45 @@ +package com.example.sunday.ui + +import android.content.Intent +import android.os.Bundle +import android.view.View +import com.example.sunday.R +import com.example.sunday.base.BaseActivity +import com.example.sunday.data.enums.BaseCurrency +import com.example.sunday.databinding.ActivityMainBinding +import com.example.sunday.ui.listener.ItemClickListener +import com.example.sunday.viewmodel.MainViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel + +class MainActivity : BaseActivity(R.layout.activity_main) { + + private val viewModel by viewModel() + private val coinAdapter by lazy { CoinAdapter() } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + initViewModel() + initRecyclerView() + viewModel.getTicker(BaseCurrency.KRW.toString()) + } + + fun initViewModel(){ + binding.vm = viewModel + } + + fun initRecyclerView(){ + with(binding.recyclerView){ + coinAdapter.setItemClickListner( object : ItemClickListener { + override fun onClick(view: View, currency: String) { + val intent = Intent(this@MainActivity, ExchangeActivity::class.java).run { + putExtra("currency", currency) + } + startActivity(intent) + } + }) + adapter = coinAdapter + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/ui/listener/ItemClickListener.kt b/app/src/main/java/com/example/sunday/ui/listener/ItemClickListener.kt new file mode 100644 index 0000000..3ee1c68 --- /dev/null +++ b/app/src/main/java/com/example/sunday/ui/listener/ItemClickListener.kt @@ -0,0 +1,7 @@ +package com.example.sunday.ui.listener + +import android.view.View + +interface ItemClickListener { + fun onClick(view: View, currency: String) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/ui/model/ETicker.kt b/app/src/main/java/com/example/sunday/ui/model/ETicker.kt new file mode 100644 index 0000000..1f5b9c2 --- /dev/null +++ b/app/src/main/java/com/example/sunday/ui/model/ETicker.kt @@ -0,0 +1,8 @@ +package com.example.sunday.ui.model + +data class ETicker( + var idx: Int? = 0, + val exchangeName: String?, + val nowPrice: Double?, + val volume: Double? +) \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/ui/model/UTicker.kt b/app/src/main/java/com/example/sunday/ui/model/UTicker.kt new file mode 100644 index 0000000..cbd53a7 --- /dev/null +++ b/app/src/main/java/com/example/sunday/ui/model/UTicker.kt @@ -0,0 +1,9 @@ +package com.example.sunday.ui.model + +data class UTicker( + val name: String?, + val last1: Double?, + val last2: Double?, + val last3: Double?, + val exchange: String +) \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/util/BindingAdapters.kt b/app/src/main/java/com/example/sunday/util/BindingAdapters.kt new file mode 100644 index 0000000..15e46b1 --- /dev/null +++ b/app/src/main/java/com/example/sunday/util/BindingAdapters.kt @@ -0,0 +1,19 @@ +package com.example.sunday.util + +import androidx.databinding.BindingAdapter +import androidx.recyclerview.widget.RecyclerView +import com.example.sunday.ui.CoinAdapter +import com.example.sunday.ui.ExchangeAdapter +import com.example.sunday.ui.model.ETicker +import com.example.sunday.ui.model.UTicker + + +@BindingAdapter("setData") +fun RecyclerView.setData(list: List?) { + (adapter as? CoinAdapter)?.setData(list) +} + +@BindingAdapter("setETicker") +fun RecyclerView.setETicker(list: List?) { + (adapter as? ExchangeAdapter)?.setData(list) +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/util/SchedulerExtension.kt b/app/src/main/java/com/example/sunday/util/SchedulerExtension.kt new file mode 100644 index 0000000..16a21f8 --- /dev/null +++ b/app/src/main/java/com/example/sunday/util/SchedulerExtension.kt @@ -0,0 +1,24 @@ +package com.example.sunday.util + +import io.reactivex.Completable +import io.reactivex.Observable +import io.reactivex.Single +import io.reactivex.android.schedulers.AndroidSchedulers +import io.reactivex.disposables.CompositeDisposable +import io.reactivex.disposables.Disposable +import io.reactivex.schedulers.Schedulers + + +operator fun CompositeDisposable.plusAssign(disposable: Disposable){ + this.add((disposable)) +} + + +fun Single.withSchedulers(): Single = + subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) + +fun Observable.withSchedulers(): Observable = + subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) + +fun Completable.withSchedulers(): Completable = + subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()) \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/util/TextViewExtension.kt b/app/src/main/java/com/example/sunday/util/TextViewExtension.kt new file mode 100644 index 0000000..44e7b29 --- /dev/null +++ b/app/src/main/java/com/example/sunday/util/TextViewExtension.kt @@ -0,0 +1,40 @@ +package com.example.sunday.util + +import android.widget.TextView +import androidx.core.content.res.ResourcesCompat +import androidx.databinding.BindingAdapter +import com.example.sunday.R +import com.example.sunday.network.response.upbit.UpbitTickerResponse +import java.text.DecimalFormat + + +@BindingAdapter("floatNumber") +fun TextView.setFloatNumber(tmp: Double){ + text = DecimalFormat("0.###").format(tmp) +} +@BindingAdapter("intNumber") +fun TextView.setIntNumber(tmp: Int){ + text = tmp.toString() +} + +@BindingAdapter("coinName") +fun TextView.setCoinName(upbitTickerResponse: UpbitTickerResponse){ + text = upbitTickerResponse.market.split("-")[1] +} + +@BindingAdapter("compareRise") +fun TextView.setCompareRise(upbitTickerResponse: UpbitTickerResponse){ + upbitTickerResponse.change.let{ + when{ + it.equals("RISE") -> { + setTextColor(ResourcesCompat.getColor(resources, R.color.colorBlue, null)) + } + it.equals("FALL") -> { + setTextColor(ResourcesCompat.getColor(resources, R.color.colorRed, null)) + } + else -> { + setTextColor(ResourcesCompat.getColor(resources, R.color.colorGray, null)) + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/viewmodel/ExchangeViewModel.kt b/app/src/main/java/com/example/sunday/viewmodel/ExchangeViewModel.kt new file mode 100644 index 0000000..b4403db --- /dev/null +++ b/app/src/main/java/com/example/sunday/viewmodel/ExchangeViewModel.kt @@ -0,0 +1,91 @@ +package com.example.sunday.viewmodel + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.sunday.data.enums.BaseCurrency +import com.example.sunday.data.enums.Exchange +import com.example.sunday.data.model.ExchangeTicker +import com.example.sunday.data.repository.ticker.TickerRepository +import com.example.sunday.network.response.bithumb.BithumbAllResponse +import com.example.sunday.network.response.bithumb.BithumbTickerResponse +import com.example.sunday.network.response.coinone.CoinoneResponse +import com.example.sunday.network.response.upbit.UpbitTickerResponse +import com.example.sunday.ui.model.ETicker +import com.google.gson.Gson +import io.reactivex.disposables.CompositeDisposable +import kotlinx.coroutines.* + + +class ExchangeViewModel(private val repoMap: Map) : ViewModel() { + + private val handler = CoroutineExceptionHandler { _, exception -> Log.e("Coroutines ExchangeViewModel ::","Caught $exception") } + + private val compositeDisposable = CompositeDisposable() + + private val _tickerList = MutableLiveData>() + val tickerList: LiveData> get() = _tickerList + + val liveCurrency = MutableLiveData() + + fun getExchangeTicker(): Job{ + return viewModelScope.launch(handler) { + try{ + + val t1 = async { getUpbitExchangeTickerList() } + val t2 = async { getBithumbExchangeTickerList() } + val t3 = async { getCoinoneExchangeTickerList() } + _tickerList.value = computeResult(t1.await(),t2.await(), t3.await()) + + }catch (error: Exception){ + Log.e("error::", error.toString()) + } + + } + + } + private suspend fun computeResult(t1: List, t2: ExchangeTicker, t3: ExchangeTicker): MutableList { + val list: MutableList = mutableListOf() + withContext(Dispatchers.IO){ + list.add(ETicker(0,t1[0].exchangeName, t1[0].last, t1[0].volume)) + list.add(ETicker(0,t2.exchangeName, t2.last, t2.volume)) + list.add(ETicker(0,t3.exchangeName, t3.last, t3.volume)) + val sortedWith = list.sortedWith(Comparator { a: ETicker, b: ETicker -> + when { + a.nowPrice!! > b.nowPrice!! -> 1 + a.nowPrice!! < b.nowPrice!! -> -1 + else -> 0 + } + }) + sortedWith.forEachIndexed { index, eTicker -> eTicker.idx = index + 1 } + } + return list + } + + private suspend fun getUpbitExchangeTickerList(): List{ + return (repoMap[Exchange.UPBIT.exchangeName]?.getExchangeTicker("${BaseCurrency.KRW}-${liveCurrency.value}") as List) + .map{ + it.toExchangeTicker(Exchange.UPBIT.exchangeName) + } + } + + private suspend fun getBithumbExchangeTickerList(): ExchangeTicker{ + return (repoMap[Exchange.BITHUMB.exchangeName]?.getExchangeTicker(liveCurrency.value!!) as BithumbAllResponse) + .let{ + val gson = Gson() + gson.fromJson(it.item.toString(), BithumbTickerResponse::class.java).toExchangeTicker(Exchange.BITHUMB.exchangeName) + } + } + + private suspend fun getCoinoneExchangeTickerList(): ExchangeTicker { + return (repoMap[Exchange.COINONE.exchangeName]?.getExchangeTicker(liveCurrency.value!!) as CoinoneResponse).toExchangeTicker(Exchange.COINONE.exchangeName) + } + + + override fun onCleared() { + super.onCleared() + compositeDisposable.dispose() + } +} \ No newline at end of file diff --git a/app/src/main/java/com/example/sunday/viewmodel/MainViewModel.kt b/app/src/main/java/com/example/sunday/viewmodel/MainViewModel.kt new file mode 100644 index 0000000..fe50d49 --- /dev/null +++ b/app/src/main/java/com/example/sunday/viewmodel/MainViewModel.kt @@ -0,0 +1,109 @@ +package com.example.sunday.viewmodel + +import android.util.Log +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import androidx.lifecycle.viewModelScope +import com.example.sunday.data.enums.Exchange +import com.example.sunday.data.model.Ticker +import com.example.sunday.data.repository.ticker.TickerRepository +import com.example.sunday.network.response.bithumb.BithumbAllResponse +import com.example.sunday.network.response.bithumb.BithumbTickerResponse +import com.example.sunday.network.response.coinone.CoinoneResponse +import com.example.sunday.network.response.upbit.UpbitMarketResponse +import com.example.sunday.network.response.upbit.UpbitTickerResponse +import com.example.sunday.ui.model.UTicker +import com.google.gson.Gson +import io.reactivex.disposables.CompositeDisposable +import kotlinx.coroutines.* +import kotlin.math.min + +class MainViewModel(private val repoMap: Map) : ViewModel() { + + + private val handler = CoroutineExceptionHandler { _, exception -> Log.e("Coroutines MainViewModel::","Caught $exception") } + + private val compositeDisposable = CompositeDisposable() + + private val _tickerList = MutableLiveData>() + val tickerList: LiveData> get() = _tickerList + + fun getTicker(baseCurrency: String): Job{ + return viewModelScope.launch(handler) { + try{ + val upbitMarketData = getUpbitMarketData(baseCurrency) + val t1 = async { getUpbitTickerData(upbitMarketData) } + val t2 = async { getBithumbTickerList() } + val t3 = async { getCoinoneTickerList() } + _tickerList.value = computeResult(t1.await(), t2.await(), t3.await()) + + }catch (error: Exception){ + Log.e("Coroutine ::", error.toString()) + } + } + } + + private suspend fun computeResult(t1: List, t2: List, t3: List):MutableList { + val list: MutableList = mutableListOf() + withContext(Dispatchers.IO){ + t1.forEach { + val name: String? = it.currency?.toUpperCase() + val last1: Double? = it.last + t2.forEach{ + val last2: Double? = it.last + if(name == it.currency?.toUpperCase()){ + t3.forEach { + val last3: Double? = it.last + if(name == it.currency?.toUpperCase()){ + list.add(UTicker(name,last1,last2,last3, + when(min(min(last1!!, last2!!), last3!!)){ + last1 -> Exchange.UPBIT.exchangeName + last2 -> Exchange.BITHUMB.exchangeName + else -> Exchange.COINONE.exchangeName + })) + + return@forEach + } + } + return@forEach + } + } + } + } + return list + } + + private suspend fun getUpbitMarketData(baseCurrency: String): List{ + return (repoMap[Exchange.UPBIT.exchangeName]?.getAllTicker() as List) + .map { it.market } + .filter { it.split("-")[0] == baseCurrency } + .toList() + } + + private suspend fun getUpbitTickerData(list: List): List { + return (repoMap[Exchange.UPBIT.exchangeName]?.getTicker(list.joinToString()) as List) + .map { it.toTicker()} + } + + private suspend fun getBithumbTickerList(): List{ + val gson = Gson() + return (repoMap[Exchange.BITHUMB.exchangeName]?.getAllTicker() as BithumbAllResponse) + .item + .filter { it.key != "date" } + .map {(name, response) -> gson.fromJson(response.toString(), BithumbTickerResponse::class.java).toTicker(name)} + } + + private suspend fun getCoinoneTickerList(): List{ + val gson = Gson() + return (repoMap[Exchange.COINONE.exchangeName]?.getAllTicker() as Map) + .filter { it.key != "errorCode" && it.key != "timestamp" && it.key != "result" } + .map { gson.fromJson(it.value.toString(), CoinoneResponse::class.java).toTicker() } + } + + + override fun onCleared() { + super.onCleared() + compositeDisposable.dispose() + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..1f6bb29 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..0d025f9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_exchange.xml b/app/src/main/res/layout/activity_exchange.xml new file mode 100644 index 0000000..c1b239b --- /dev/null +++ b/app/src/main/res/layout/activity_exchange.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..462db78 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_coin.xml b/app/src/main/res/layout/item_coin.xml new file mode 100644 index 0000000..f419b98 --- /dev/null +++ b/app/src/main/res/layout/item_coin.xml @@ -0,0 +1,89 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/item_exchange.xml b/app/src/main/res/layout/item_exchange.xml new file mode 100644 index 0000000..6fb1f5e --- /dev/null +++ b/app/src/main/res/layout/item_exchange.xml @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..898f3ed Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..dffca36 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..64ba76f Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..dae5e08 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..e5ed465 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..14ed0af Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..b0907ca Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d8ae031 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..2c18de9 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..beed3cd Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..1fd5cd6 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,9 @@ + + + #008577 + #00574B + #D81B60 + #8e8e8e + #ff0000 + #0000ff + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..a2e5bba --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,14 @@ + + Sunday + 코인명 + 업비트 + 빗썸 + 코인원 + 추천거래소 + + + 순위 + 거래소 + 현재 + 거래대금 + diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml new file mode 100644 index 0000000..5885930 --- /dev/null +++ b/app/src/main/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/app/src/test/java/com/example/sunday/ExampleUnitTest.kt b/app/src/test/java/com/example/sunday/ExampleUnitTest.kt new file mode 100644 index 0000000..ca235b9 --- /dev/null +++ b/app/src/test/java/com/example/sunday/ExampleUnitTest.kt @@ -0,0 +1,16 @@ +package com.example.sunday + +import org.junit.Assert.assertEquals +import org.junit.Test + +/** + * Example local unit test, which will execute on the development machine (host). + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +class ExampleUnitTest { + @Test + fun addition_isCorrect() { + assertEquals(4, 2 + 2) + } +} diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..819d034 --- /dev/null +++ b/build.gradle @@ -0,0 +1,28 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. + +buildscript { + ext.kotlin_version = '1.3.61' + repositories { + google() + jcenter() + + } + dependencies { + classpath 'com.android.tools.build:gradle:3.5.3' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..23339e0 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx1536m +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app's APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..f6b961f Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..702abe8 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Sun Jan 05 12:04:08 KST 2020 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 0000000..cccdd3d --- /dev/null +++ b/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..f955316 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..ab48105 --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +include ':app' +rootProject.name='Sunday'