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
+ this,
+ R.layout.spinner_item,
+ resources.getStringArray(R.array.depositSpinner)
+ )
+
+ binding.apply {
+ depositSpinner.adapter = spinnerAdapter
+ depositSpinner.setSelection(0, false)
+ depositSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(
+ parent: AdapterView<*>?,
+ view: View?,
+ position: Int,
+ id: Long
+ ) {
+ depositValue = when (position) {
+ 0 -> 1000000.0
+ 1 -> 3000000.0
+ 2 -> 5000000.0
+ 3 -> 10000000.0
+ 4 -> 30000000.0
+ 5 -> 50000000.0
+ else -> 1000000.0
+ }
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
+
+ confirmBtn.setOnClickListener {
+ val intent = Intent()
+ intent.putExtra("krw", depositValue)
+ setResult(RESULT_OK, intent)
+ finish()
+ }
+ }
+ }
+
+ override fun onBackPressed() {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentAsset.kt b/app/src/main/java/com/mobit/mobit/FragmentAsset.kt
new file mode 100644
index 0000000..f5a7326
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentAsset.kt
@@ -0,0 +1,130 @@
+package com.mobit.mobit
+
+import android.graphics.Color
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.mobit.mobit.adapter.FragmentAssetAdapter
+import com.mobit.mobit.data.Asset
+import com.mobit.mobit.data.CoinAsset
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.databinding.FragmentAssetBinding
+import java.text.DecimalFormat
+
+/*
+보유자산 기능이 구현될 Fragment 입니다.
+*/
+class FragmentAsset : Fragment() {
+
+ // UI 변수 시작
+ lateinit var binding: FragmentAssetBinding
+ // UI 변수 끝
+
+ val myViewModel: MyViewModel by activityViewModels()
+ val retainedCoin: ArrayList = ArrayList()
+
+ lateinit var adapter: FragmentAssetAdapter
+
+ val formatter = DecimalFormat("###,###")
+ val changeFormatter = DecimalFormat("###,###.##")
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentAssetBinding.inflate(layoutInflater)
+
+ init()
+
+ return binding.root
+ }
+
+ fun init() {
+ myViewModel.asset.observe(viewLifecycleOwner, Observer { asset ->
+ val krw: Double = asset.krw // 보유 KRW
+ var total: Double = krw // 총 보유자산
+ var buyPrice: Double = 0.0 // 총 매수
+ var totalEvaluation: Double = 0.0 // 총 평가
+ for (coin in asset.coins) {
+ total += coin.amount
+ buyPrice += coin.number * coin.averagePrice
+ totalEvaluation += coin.amount
+ }
+ val gainAndLoss: Double = if (buyPrice > 0.0) totalEvaluation - buyPrice else 0.0
+ val yieldValue: Double = if (buyPrice > 0.0) gainAndLoss / buyPrice * 100 else 0.0
+
+ binding.apply {
+ krwView.text = formatter.format(krw)
+ totalView.text = formatter.format(total)
+ totalBuyView.text = formatter.format(buyPrice)
+
+ if (buyPrice > 0.0) {
+ recyclerView.visibility = View.VISIBLE
+ noAssetView.visibility = View.GONE
+
+ gainAndLossView.text = formatter.format(gainAndLoss)
+ totalEvaluationView.text = formatter.format(totalEvaluation)
+ yieldView.text = changeFormatter.format(yieldValue) + "%"
+
+ val rgb = if (buyPrice > totalEvaluation) Color.rgb(
+ 25,
+ 96,
+ 186
+ ) else if (buyPrice < totalEvaluation) Color.rgb(
+ 207,
+ 80,
+ 71
+ ) else Color.rgb(211, 212, 214)
+ gainAndLossView.setTextColor(rgb)
+ yieldView.setTextColor(rgb)
+ } else {
+ recyclerView.visibility = View.GONE
+ noAssetView.visibility = View.VISIBLE
+
+ gainAndLossView.text = getString(R.string.asset_no_data)
+ totalEvaluationView.text = getString(R.string.asset_no_data)
+ yieldView.text = getString(R.string.asset_no_data)
+
+ val rgb = Color.rgb(211, 212, 214)
+ gainAndLossView.setTextColor(rgb)
+ totalEvaluationView.setTextColor(rgb)
+ yieldView.setTextColor(rgb)
+ }
+ }
+
+ retainedCoin.clear()
+ retainedCoin.addAll(myViewModel.asset.value!!.coins)
+ adapter.notifyDataSetChanged()
+ })
+ myViewModel.coinInfo.observe(viewLifecycleOwner, Observer {
+ val asset2 = Asset(myViewModel.asset.value!!.krw, ArrayList())
+ for (i in myViewModel.asset.value!!.coins.indices) {
+ for (coinInfo in it) {
+ if (myViewModel.asset.value!!.coins[i].code == coinInfo.code) {
+ myViewModel.asset.value!!.coins[i].amount =
+ myViewModel.asset.value!!.coins[i].number * coinInfo.price.realTimePrice
+ asset2.coins.add(myViewModel.asset.value!!.coins[i])
+ break
+ }
+ }
+ }
+ myViewModel.setAsset(asset2)
+
+ retainedCoin.clear()
+ retainedCoin.addAll(myViewModel.asset.value!!.coins)
+ adapter.notifyDataSetChanged()
+ })
+
+ adapter = FragmentAssetAdapter(retainedCoin)
+ binding.apply {
+ recyclerView.layoutManager =
+ LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
+ recyclerView.adapter = adapter
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentBuy.kt b/app/src/main/java/com/mobit/mobit/FragmentBuy.kt
new file mode 100644
index 0000000..b5071ee
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentBuy.kt
@@ -0,0 +1,292 @@
+package com.mobit.mobit
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import com.mobit.mobit.data.CoinInfo
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.data.Transaction
+import com.mobit.mobit.databinding.FragmentBuyBinding
+import java.text.DecimalFormat
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+/*
+코인 매수 기능이 구현될 Fragment 입니다.
+*/
+class FragmentBuy : Fragment() {
+
+ lateinit var getContent: ActivityResultLauncher
+
+ // UI 변수 시작
+ lateinit var binding: FragmentBuyBinding
+ // UI 변수 끝
+
+ val myViewModel: MyViewModel by activityViewModels()
+
+ val formatter = DecimalFormat("###,###")
+ val formatter2 = DecimalFormat("###,###.####")
+ val formatter3 = DecimalFormat("###,###.##")
+ var orderCount: Double = 0.0
+ var orderPrice: Double = 0.0
+
+ var buyIndex: Int = -1
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentBuyBinding.inflate(layoutInflater)
+ getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ binding.orderCount.clearFocus()
+ when (it.resultCode) {
+ Activity.RESULT_OK -> {
+ val code = it.data!!.getStringExtra("code")
+ val name = it.data!!.getStringExtra("name")
+ val price: Double = orderPrice * orderCount
+ val fee: Double = price * 0.0005
+ val time: String = getNowTime()
+ val transaction = Transaction(
+ code,
+ name,
+ time,
+ Transaction.BID,
+ orderCount,
+ orderPrice,
+ price - fee,
+ fee,
+ price
+ )
+
+ buyIndex = myViewModel.bidCoin(
+ code,
+ name,
+ orderPrice,
+ orderCount
+ )
+
+ myViewModel.addTransaction(transaction)
+ val thread = object : Thread() {
+ override fun run() {
+ myViewModel.myDBHelper!!.setKRW(myViewModel.asset.value!!.krw)
+ myViewModel.myDBHelper!!.insertTransaction(transaction)
+
+ if (myViewModel.myDBHelper!!.findCoinAsset(code)) {
+ val ret =
+ myViewModel.myDBHelper!!.updateCoinAsset(myViewModel.asset.value!!.coins[buyIndex])
+ } else {
+ val ret =
+ myViewModel.myDBHelper!!.insertCoinAsset(myViewModel.asset.value!!.coins[buyIndex])
+ }
+ }
+ }
+ thread.start()
+ binding.canOrderPrice.text =
+ "${formatter.format(myViewModel.asset.value!!.krw)}KRW"
+ resetOrderTextView()
+ Toast.makeText(context, "매수주문이 정상 처리되었습니다.", Toast.LENGTH_SHORT).show()
+ }
+ Activity.RESULT_CANCELED -> {
+ Log.i("resultCode", "RESULT_CANCELED")
+ }
+ }
+ }
+
+ init()
+
+ return binding.root
+ }
+
+ override fun onResume() {
+ super.onResume()
+ binding.apply {
+ canOrderPrice.text = "${formatter.format(myViewModel.asset.value!!.krw)}KRW"
+ orderCount.setText("0")
+ orderCountSpinner.setSelection(0)
+ }
+ }
+
+ fun init() {
+ myViewModel.coinInfo.observe(viewLifecycleOwner, Observer {
+ for (coinInfo in myViewModel.coinInfo.value!!) {
+ if (coinInfo.code == myViewModel.selectedCoin.value!!) {
+ orderPrice = coinInfo.price.realTimePrice
+ break
+ }
+ }
+ binding.orderPrice.text =
+ if (orderPrice > 100.0)
+ formatter.format(orderPrice)
+ else
+ formatter3.format(orderPrice)
+ binding.orderTotalPrice.text = "${formatter.format(orderPrice * orderCount)}KRW"
+ })
+
+ // spinner 아이템을 보여주는 view를 커스텀하기 위해서 adapter를 만들어준다
+ val spinnerAdapter = ArrayAdapter(
+ requireContext(),
+ R.layout.spinner_item,
+ resources.getStringArray(R.array.orderCountSpinner)
+ )
+
+ binding.apply {
+ canOrderPrice.text = "${formatter.format(myViewModel.asset.value!!.krw)}KRW"
+ orderCountSpinner.adapter = spinnerAdapter
+ orderCountSpinner.setSelection(0, false)
+ orderCountSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(
+ parent: AdapterView<*>?,
+ view: View?,
+ position: Int,
+ id: Long
+ ) {
+ val krw = myViewModel.asset.value!!.krw
+ var canOrderCount: Double = 0.0
+ when (position) {
+ 0 -> {
+
+ }
+ // 최대
+ 1 -> {
+ canOrderCount = krw / (this@FragmentBuy.orderPrice * 1.0005)
+ }
+ // 50%
+ 2 -> {
+ canOrderCount = (krw / 2) / (this@FragmentBuy.orderPrice * 1.0005)
+ }
+ // 25%
+ 3 -> {
+ canOrderCount = (krw / 4) / (this@FragmentBuy.orderPrice * 1.0005)
+ }
+ // 10%
+ 4 -> {
+ canOrderCount = (krw / 10) / (this@FragmentBuy.orderPrice * 1.0005)
+ }
+ else -> {
+ Log.e("FragmentBuy Spinner", "position is $position")
+ }
+ }
+ orderCount.setText(formatter2.format(canOrderCount))
+ val totalPrice = canOrderCount * this@FragmentBuy.orderPrice
+ orderTotalPrice.text = "${formatter.format(totalPrice)}KRW"
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
+
+ orderCount.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(
+ s: CharSequence?,
+ start: Int,
+ count: Int,
+ after: Int
+ ) {
+ }
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ if (s.isNullOrEmpty()) {
+ this@FragmentBuy.orderCount = 0.0
+ } else {
+ this@FragmentBuy.orderCount = s.toString().replace(",", "").toDouble()
+ if (this@FragmentBuy.orderCount < 0) {
+ this@FragmentBuy.orderCount = 0.0
+ orderCount.setText("0")
+ }
+ }
+ val totalPrice = this@FragmentBuy.orderCount * this@FragmentBuy.orderPrice
+ orderTotalPrice.text = "${formatter.format(totalPrice)}KRW"
+ }
+
+ override fun afterTextChanged(s: Editable?) {}
+ })
+ orderCount.setOnFocusChangeListener(object : View.OnFocusChangeListener {
+ override fun onFocusChange(v: View?, hasFocus: Boolean) {
+ if (v != null) {
+ if (!hasFocus) {
+ val text = orderCount.text.toString()
+ if (text.isNotEmpty()) {
+ val number = text.replace(",", "").toDouble()
+ this@FragmentBuy.orderCount = if (number > 0.0) number else 0.0
+ } else {
+ this@FragmentBuy.orderCount = 0.0
+ orderCount.setText("0")
+ }
+ }
+ }
+ }
+ })
+ // 주문 개수와 주문 가격 초기화
+ resetBtn.setOnClickListener {
+ resetOrderTextView()
+ }
+ // 코인 매수
+ buyBtn.setOnClickListener {
+ orderCount.clearFocus()
+ val nowOrderPrice = this@FragmentBuy.orderPrice
+ if (this@FragmentBuy.orderCount != 0.0 && this@FragmentBuy.orderCount * nowOrderPrice >= 5000.0) {
+ var coin: CoinInfo? = null
+ for (coinInfo in myViewModel.coinInfo.value!!) {
+ if (coinInfo.code == myViewModel.selectedCoin.value!!) {
+ coin = coinInfo
+ break
+ }
+ }
+
+ val flag = myViewModel.asset.value!!.canBidCoin(
+ coin!!.code,
+ coin!!.name,
+ nowOrderPrice,
+ this@FragmentBuy.orderCount
+ )
+ if (flag) {
+ val intent: Intent = Intent(context, PopupBuySellActivity::class.java)
+ intent.putExtra("type", 1)
+ intent.putExtra("code", coin!!.code)
+ intent.putExtra("name", coin!!.name)
+ intent.putExtra("unitPrice", nowOrderPrice)
+ intent.putExtra("count", this@FragmentBuy.orderCount)
+ getContent.launch(intent)
+ } else {
+ Toast.makeText(context, "주문가능 금액이 부족합니다.", Toast.LENGTH_SHORT).show()
+ }
+ } else {
+ Toast.makeText(context, "주문 가능한 최소 금액은 5,000KRW입니다.", Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+ }
+ }
+
+ fun resetOrderTextView() {
+ this@FragmentBuy.orderCount = 0.0
+ binding.orderCount.setText(formatter.format(orderCount))
+ binding.orderPrice.text =
+ if (orderPrice > 100.0)
+ formatter.format(orderPrice)
+ else
+ formatter3.format(orderPrice)
+ binding.orderTotalPrice.text = "0KRW"
+ }
+
+ fun getNowTime(): String {
+ val current = LocalDateTime.now()
+ // yyyy-MM-ddThh:mm:ss 형태로 날짜를 저장한다.
+ // https://developer.android.com/reference/java/time/format/DateTimeFormatter#ISO_LOCAL_DATE_TIME
+ val formatted = current.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
+ return formatted
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentChart.kt b/app/src/main/java/com/mobit/mobit/FragmentChart.kt
new file mode 100644
index 0000000..c587301
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentChart.kt
@@ -0,0 +1,595 @@
+package com.mobit.mobit
+
+import android.graphics.Color
+import android.graphics.Matrix
+import android.graphics.Paint
+import android.os.Bundle
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.MotionEvent
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RadioGroup
+import android.widget.Toast
+import androidx.appcompat.widget.PopupMenu
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import com.github.mikephil.charting.charts.BarChart
+import com.github.mikephil.charting.charts.CandleStickChart
+import com.github.mikephil.charting.components.XAxis
+import com.github.mikephil.charting.components.YAxis
+import com.github.mikephil.charting.data.*
+import com.github.mikephil.charting.listener.ChartTouchListener
+import com.github.mikephil.charting.listener.OnChartGestureListener
+import com.mobit.mobit.data.Candle
+import com.mobit.mobit.data.CoinInfo
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.databinding.FragmentChartBinding
+import com.mobit.mobit.network.UpbitAPICaller
+import java.text.DecimalFormat
+import kotlin.math.abs
+
+/*
+코인 차트 기능이 구현될 Fragment 입니다.
+
+차트 기능 구현할 때 사용할 라이브러리
+-> https://github.com/PhilJay/MPAndroidChart
+ */
+class FragmentChart : Fragment() {
+
+ companion object {
+ const val UNIT_MIN_1: Int = 0
+ const val UNIT_MIN_3: Int = 1
+ const val UNIT_MIN_5: Int = 2
+ const val UNIT_MIN_10: Int = 3
+ const val UNIT_MIN_30: Int = 4
+ const val UNIT_MIN_60: Int = 5
+ const val UNIT_MIN_240: Int = 6
+ const val UNIT_DAY: Int = 7
+ const val UNIT_WEEK: Int = 8
+ const val UNIT_MONTH: Int = 9
+ }
+
+ // UI 변수 시작
+ lateinit var binding: FragmentChartBinding
+ var minuteBtnFlag: Boolean = true
+ // UI 변수 끝
+
+ // 차트 관련 변수 시작
+ var unitFlag: Int = UNIT_MIN_1
+ var unitMinFlag: Int = UNIT_MIN_1
+ // 차트 관련 변수 끝
+
+ val upbitAPICaller: UpbitAPICaller = UpbitAPICaller()
+ val upbitCandleHandler: UpbitCandleHandler = UpbitCandleHandler()
+ lateinit var upbitCandleThread: UpbitCandleThread
+
+ val myViewModel: MyViewModel by activityViewModels()
+
+ var selectedCoin: CoinInfo? = null
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentChartBinding.inflate(layoutInflater)
+
+ upbitCandleThread = UpbitCandleThread()
+ upbitCandleThread.start()
+
+ initChart()
+ init()
+
+ return binding.root
+ }
+
+ override fun onResume() {
+ super.onResume()
+ val thread = object : Thread() {
+ override fun run() {
+ if (!upbitCandleThread.isAlive) {
+ upbitCandleThread = UpbitCandleThread()
+ upbitCandleThread.start()
+ }
+ }
+ }
+ thread.start()
+ }
+
+ override fun onStop() {
+ super.onStop()
+ upbitCandleThread.threadStop(true)
+ }
+
+ fun initChart() {
+ binding.apply {
+ priceChart.description.isEnabled = false
+ priceChart.setMaxVisibleValueCount(200)
+ priceChart.setPinchZoom(false)
+ priceChart.setDrawGridBackground(false)
+ priceChart.xAxis.apply {
+ textColor = Color.TRANSPARENT
+ position = XAxis.XAxisPosition.BOTTOM
+ this.setDrawGridLines(true)
+ }
+ priceChart.axisLeft.apply {
+ textColor = Color.WHITE
+ isEnabled = false
+ }
+ priceChart.axisRight.apply {
+ setLabelCount(7, false)
+ textColor = Color.WHITE
+ setDrawGridLines(true)
+ setDrawAxisLine(false)
+ }
+ priceChart.legend.isEnabled = false
+
+ transactionChart.description.isEnabled = false
+ transactionChart.setMaxVisibleValueCount(200)
+ transactionChart.setPinchZoom(false)
+ transactionChart.setDrawGridBackground(false)
+ transactionChart.xAxis.apply {
+ textColor = Color.TRANSPARENT
+ position = XAxis.XAxisPosition.BOTTOM
+ this.setDrawGridLines(true)
+ }
+ transactionChart.axisLeft.apply {
+ textColor = Color.WHITE
+ isEnabled = false
+ }
+ transactionChart.axisRight.apply {
+ setLabelCount(7, false)
+ textColor = Color.WHITE
+ setDrawGridLines(true)
+ setDrawAxisLine(false)
+ }
+ transactionChart.legend.isEnabled = false
+
+ priceChart.onChartGestureListener = object : OnChartGestureListener {
+ override fun onChartGestureStart(
+ me: MotionEvent?,
+ lastPerformedGesture: ChartTouchListener.ChartGesture?
+ ) {
+ }
+
+ override fun onChartGestureEnd(
+ me: MotionEvent?,
+ lastPerformedGesture: ChartTouchListener.ChartGesture?
+ ) {
+ }
+
+ override fun onChartLongPressed(me: MotionEvent?) {}
+
+ override fun onChartDoubleTapped(me: MotionEvent?) {}
+
+ override fun onChartSingleTapped(me: MotionEvent?) {}
+
+ override fun onChartFling(
+ me1: MotionEvent?,
+ me2: MotionEvent?,
+ velocityX: Float,
+ velocityY: Float
+ ) {
+ }
+
+ override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) {
+ syncCharts(priceChart, transactionChart)
+ }
+
+ override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) {
+ syncCharts(priceChart, transactionChart)
+ }
+ }
+ transactionChart.onChartGestureListener = object : OnChartGestureListener {
+ override fun onChartGestureStart(
+ me: MotionEvent?,
+ lastPerformedGesture: ChartTouchListener.ChartGesture?
+ ) {
+ }
+
+ override fun onChartGestureEnd(
+ me: MotionEvent?,
+ lastPerformedGesture: ChartTouchListener.ChartGesture?
+ ) {
+ }
+
+ override fun onChartLongPressed(me: MotionEvent?) {}
+
+ override fun onChartDoubleTapped(me: MotionEvent?) {}
+
+ override fun onChartSingleTapped(me: MotionEvent?) {}
+
+ override fun onChartFling(
+ me1: MotionEvent?,
+ me2: MotionEvent?,
+ velocityX: Float,
+ velocityY: Float
+ ) {
+ }
+
+ override fun onChartScale(me: MotionEvent?, scaleX: Float, scaleY: Float) {
+ syncCharts(transactionChart, priceChart)
+ }
+
+ override fun onChartTranslate(me: MotionEvent?, dX: Float, dY: Float) {
+ syncCharts(transactionChart, priceChart)
+ }
+ }
+ }
+ }
+
+ fun init() {
+ myViewModel.coinInfo.observe(viewLifecycleOwner, Observer {
+ for (coinInfo in myViewModel.coinInfo.value!!) {
+ if (coinInfo.code == myViewModel.selectedCoin.value!!) {
+ selectedCoin = coinInfo
+ break
+ }
+ }
+ binding.apply {
+ if (selectedCoin != null) {
+ val formatter = DecimalFormat("###,###")
+ val changeFormatter = DecimalFormat("###,###.##")
+ coinName.text = "${selectedCoin!!.name}(${selectedCoin!!.code.split('-')[1]})"
+ coinPrice.text =
+ if (selectedCoin!!.price.realTimePrice > 100.0)
+ formatter.format(selectedCoin!!.price.realTimePrice)
+ else
+ changeFormatter.format(selectedCoin!!.price.realTimePrice)
+ coinRate.text =
+ changeFormatter.format(selectedCoin!!.price.changeRate * 100) + "%"
+ coinDiff.text = when (selectedCoin!!.price.change) {
+ "EVEN" -> ""
+ "RISE" -> "▲ "
+ "FALL" -> "▼ "
+ else -> ""
+ } + changeFormatter.format(abs(selectedCoin!!.price.changePrice))
+
+ setTextViewColor(selectedCoin!!)
+ }
+ }
+ })
+
+ binding.apply {
+ radioGroup.setOnCheckedChangeListener(object : RadioGroup.OnCheckedChangeListener {
+ override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) {
+ when (checkedId) {
+ R.id.minuteBtn -> {
+ unitFlag = unitMinFlag
+ }
+ R.id.dayBtn -> {
+ minuteBtnFlag = false
+ unitFlag = UNIT_DAY
+ }
+ R.id.weekBtn -> {
+ minuteBtnFlag = false
+ unitFlag = UNIT_WEEK
+ }
+ R.id.monthBtn -> {
+ minuteBtnFlag = false
+ unitFlag = UNIT_MONTH
+ }
+ }
+ }
+ })
+ minuteBtn.setOnClickListener { view ->
+ if (minuteBtnFlag && unitFlag in UNIT_MIN_1..UNIT_MIN_240) {
+ setUnitMinBtn(view)
+ } else if (unitFlag in UNIT_MIN_1..UNIT_MIN_240) {
+ minuteBtnFlag = true
+ }
+ }
+
+ for (coinInfo in myViewModel.coinInfo.value!!) {
+ if (coinInfo.code == myViewModel.selectedCoin.value!!) {
+ selectedCoin = coinInfo
+ break
+ }
+ }
+ // 코인이 관심목록에 등록되어 있는 경우에는 ImageButton을 채워진 별로 변경해야 한다.
+ if (myViewModel.favoriteCoinInfo.value!!.contains(selectedCoin)) {
+ favoriteBtn.setImageResource(R.drawable.ic_round_star_24)
+ }
+ favoriteBtn.setOnClickListener(object : View.OnClickListener {
+ override fun onClick(v: View?) {
+ // 즐겨찾기에 이미 추가되어 있는 경우
+ if (myViewModel.favoriteCoinInfo.value!!.contains(selectedCoin)) {
+ if (myViewModel.removeFavoriteCoinInfo(selectedCoin!!)) {
+ val thread = object : Thread() {
+ override fun run() {
+ val flag =
+ myViewModel.myDBHelper!!.deleteFavorite(selectedCoin!!.code)
+ Log.e("favorite delete", flag.toString())
+ }
+ }
+ thread.start()
+ favoriteBtn.setImageResource(R.drawable.ic_round_star_border_24)
+ Toast.makeText(context, "관심코인에서 삭제되었습니다.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ // 즐겨찾기에 추가되어 있지 않은 경우
+ else {
+ if (myViewModel.addFavoriteCoinInfo(selectedCoin!!)) {
+ val thread = object : Thread() {
+ override fun run() {
+ val flag =
+ myViewModel.myDBHelper!!.insertFavoirte(selectedCoin!!.code)
+ Log.e("favorite insert", flag.toString())
+ }
+ }
+ thread.start()
+ favoriteBtn.setImageResource(R.drawable.ic_round_star_24)
+ Toast.makeText(context, "관심코인으로 등록되었습니다.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ })
+ }
+ }
+
+ fun setUnitMinBtn(view: View) {
+ val popupMenu: PopupMenu = PopupMenu(requireContext(), view)
+ popupMenu.menuInflater.inflate(
+ R.menu.menu_chart_unit,
+ popupMenu.menu
+ )
+ popupMenu.setOnMenuItemClickListener { item ->
+ if (item != null) {
+ when (item.itemId) {
+ R.id.minute_1 -> {
+ if (unitFlag != UNIT_MIN_1) {
+ unitFlag = UNIT_MIN_1
+ unitMinFlag = UNIT_MIN_1
+ binding.minuteBtn.text = getString(R.string.chart_unit_minute_1)
+ }
+ }
+ R.id.minute_3 -> {
+ if (unitFlag != UNIT_MIN_3) {
+ unitFlag = UNIT_MIN_3
+ unitMinFlag = UNIT_MIN_3
+ binding.minuteBtn.text = getString(R.string.chart_unit_minute_3)
+ }
+ }
+ R.id.minute_5 -> {
+ if (unitFlag != UNIT_MIN_5) {
+ unitFlag = UNIT_MIN_5
+ unitMinFlag = UNIT_MIN_5
+ binding.minuteBtn.text = getString(R.string.chart_unit_minute_5)
+ }
+ }
+ R.id.minute_10 -> {
+ if (unitFlag != UNIT_MIN_10) {
+ unitFlag = UNIT_MIN_10
+ unitMinFlag = UNIT_MIN_10
+ binding.minuteBtn.text = getString(R.string.chart_unit_minute_10)
+ }
+ }
+ R.id.minute_30 -> {
+ if (unitFlag != UNIT_MIN_30) {
+ unitFlag = UNIT_MIN_30
+ unitMinFlag = UNIT_MIN_30
+ binding.minuteBtn.text = getString(R.string.chart_unit_minute_30)
+ }
+ }
+ R.id.minute_60 -> {
+ if (unitFlag != UNIT_MIN_60) {
+ unitFlag = UNIT_MIN_60
+ unitMinFlag = UNIT_MIN_60
+ binding.minuteBtn.text = getString(R.string.chart_unit_minute_60)
+ }
+ }
+ R.id.minute_240 -> {
+ if (unitFlag != UNIT_MIN_240) {
+ unitFlag = UNIT_MIN_240
+ unitMinFlag = UNIT_MIN_240
+ binding.minuteBtn.text = getString(R.string.chart_unit_minute_240)
+ }
+ }
+ }
+ }
+ true
+ }
+ popupMenu.show()
+ }
+
+ fun setTextViewColor(coinInfo: CoinInfo) {
+ binding.apply {
+ if (coinInfo.price.changeRate > 0) {
+ coinPrice.setTextColor(Color.parseColor("#bd4e3a"))
+ coinRate.setTextColor(Color.parseColor("#bd4e3a"))
+ coinDiff.setTextColor(Color.parseColor("#bd4e3a"))
+ } else if (coinInfo.price.changeRate < 0) {
+ coinPrice.setTextColor(Color.parseColor("#135fc1"))
+ coinRate.setTextColor(Color.parseColor("#135fc1"))
+ coinDiff.setTextColor(Color.parseColor("#135fc1"))
+ } else {
+ coinPrice.setTextColor(Color.parseColor("#FFFFFF"))
+ coinRate.setTextColor(Color.parseColor("#FFFFFF"))
+ coinDiff.setTextColor(Color.parseColor("#FFFFFF"))
+ }
+ }
+ }
+
+ fun syncCharts(mainChart: CandleStickChart, otherChart: BarChart) {
+ val mainMatrix: Matrix
+ val mainVals = FloatArray(9)
+ val otherMatrix: Matrix
+ val otherVals = FloatArray(9)
+ mainMatrix = mainChart.viewPortHandler.matrixTouch
+ mainMatrix.getValues(mainVals)
+
+ otherMatrix = otherChart.viewPortHandler.matrixTouch
+ otherMatrix.getValues(otherVals)
+ otherVals[Matrix.MSCALE_X] = mainVals[Matrix.MSCALE_X]
+ otherVals[Matrix.MTRANS_X] = mainVals[Matrix.MTRANS_X]
+ otherVals[Matrix.MSKEW_X] = mainVals[Matrix.MSKEW_X]
+ otherMatrix.setValues(otherVals)
+ otherChart.viewPortHandler.refresh(otherMatrix, otherChart, true)
+ }
+
+ fun syncCharts(mainChart: BarChart, otherChart: CandleStickChart) {
+ val mainMatrix: Matrix
+ val mainVals = FloatArray(9)
+ val otherMatrix: Matrix
+ val otherVals = FloatArray(9)
+ mainMatrix = mainChart.viewPortHandler.matrixTouch
+ mainMatrix.getValues(mainVals)
+
+ otherMatrix = otherChart.viewPortHandler.matrixTouch
+ otherMatrix.getValues(otherVals)
+ otherVals[Matrix.MSCALE_X] = mainVals[Matrix.MSCALE_X]
+ otherVals[Matrix.MTRANS_X] = mainVals[Matrix.MTRANS_X]
+ otherVals[Matrix.MSKEW_X] = mainVals[Matrix.MSKEW_X]
+ otherMatrix.setValues(otherVals)
+ otherChart.viewPortHandler.refresh(otherMatrix, otherChart, true)
+ }
+
+ inner class UpbitCandleHandler : Handler() {
+ override fun handleMessage(msg: Message) {
+ super.handleMessage(msg)
+
+ val bundle: Bundle = msg.data
+ if (!bundle.isEmpty) {
+ val flag = bundle.getInt("unitFlag")
+ val candles = bundle.getSerializable("candles") as ArrayList
+ val entries1 = ArrayList()
+ val entries2 = ArrayList()
+ val barColor = ArrayList()
+ for (candle in candles) {
+ entries1.add(
+ CandleEntry(
+ candle.createdAt.toFloat(),
+ candle.shadowHigh,
+ candle.shadowLow,
+ candle.open,
+ candle.close
+ )
+ )
+ entries2.add(BarEntry(candle.createdAt.toFloat(), candle.totalTradeVolume))
+ if (candle.close >= candle.open) {
+ barColor.add(Color.rgb(200, 74, 49))
+ } else {
+ barColor.add(Color.rgb(18, 98, 197))
+ }
+ }
+
+ val dataSet1 = CandleDataSet(entries1, "").apply {
+ axisDependency = YAxis.AxisDependency.LEFT
+ // 심지 부분 설정
+ shadowColor = Color.LTGRAY
+ shadowWidth = 0.7F
+ // 음봉
+ decreasingColor = Color.rgb(18, 98, 197)
+ decreasingPaintStyle = Paint.Style.FILL
+ // 양봉
+ increasingColor = Color.rgb(200, 74, 49)
+ increasingPaintStyle = Paint.Style.FILL
+
+ neutralColor = Color.rgb(6, 18, 34)
+ setDrawValues(false)
+ highLightColor = Color.TRANSPARENT
+ }
+ val dataSet2 = BarDataSet(entries2, "").apply {
+ colors = barColor
+ setDrawValues(false)
+ highLightColor = Color.TRANSPARENT
+ }
+ binding.priceChart.apply {
+ this.data = CandleData(dataSet1)
+ invalidate()
+ }
+ binding.transactionChart.apply {
+ this.data = BarData(dataSet2)
+ invalidate()
+ }
+ }
+ }
+ }
+
+ inner class UpbitCandleThread : Thread() {
+
+ var stopFlag = false
+
+ override fun run() {
+ while (!stopFlag) {
+ val message = upbitCandleHandler.obtainMessage()
+ val bundle: Bundle = Bundle()
+
+ when (unitFlag) {
+ UNIT_MIN_1 -> {
+ val candles =
+ upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 1)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ UNIT_MIN_3 -> {
+ val candles =
+ upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 3)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ UNIT_MIN_5 -> {
+ val candles =
+ upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 5)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ UNIT_MIN_10 -> {
+ val candles =
+ upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 10)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ UNIT_MIN_30 -> {
+ val candles =
+ upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 30)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ UNIT_MIN_60 -> {
+ val candles =
+ upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 60)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ UNIT_MIN_240 -> {
+ val candles =
+ upbitAPICaller.getCandleMinute(myViewModel.selectedCoin.value!!, 240)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ UNIT_DAY -> {
+ val candles =
+ upbitAPICaller.getCandleDay(myViewModel.selectedCoin.value!!)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ UNIT_WEEK -> {
+ val candles =
+ upbitAPICaller.getCandleWeek(myViewModel.selectedCoin.value!!)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ UNIT_MONTH -> {
+ val candles =
+ upbitAPICaller.getCandleMonth(myViewModel.selectedCoin.value!!)
+ bundle.putInt("unitFlag", unitFlag)
+ bundle.putSerializable("candles", candles)
+ }
+ }
+
+ message.data = bundle
+ upbitCandleHandler.sendMessage(message)
+ sleep(300)
+ }
+ }
+
+
+ fun threadStop(flag: Boolean) {
+ this.stopFlag = flag
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentCoinInfo.kt b/app/src/main/java/com/mobit/mobit/FragmentCoinInfo.kt
new file mode 100644
index 0000000..9d29c81
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentCoinInfo.kt
@@ -0,0 +1,58 @@
+package com.mobit.mobit
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import com.mobit.mobit.data.CoinInfo
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.databinding.FragmentCoinInfoBinding
+
+class FragmentCoinInfo : Fragment() {
+
+ // UI 변수 시작
+ lateinit var binding: FragmentCoinInfoBinding
+ // UI 변수 끝
+
+ val myViewModel: MyViewModel by activityViewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentCoinInfoBinding.inflate(layoutInflater)
+
+ init()
+
+ return binding.root
+ }
+
+ fun init() {
+ binding.coinInfo.text = when (myViewModel.selectedCoin.value!!) {
+ CoinInfo.BTC_CODE -> getString(R.string.BTC)
+ CoinInfo.ETH_CODE -> getString(R.string.ETH)
+ CoinInfo.ADA_CODE -> getString(R.string.ADA)
+ CoinInfo.DOGE_CODE -> getString(R.string.DOGE)
+ CoinInfo.XRP_CODE -> getString(R.string.XRP)
+ CoinInfo.DOT_CODE -> getString(R.string.DOT)
+ CoinInfo.BCH_CODE -> getString(R.string.BCH)
+ CoinInfo.LTC_CODE -> getString(R.string.LTC)
+ CoinInfo.LINK_CODE -> getString(R.string.LINK)
+ CoinInfo.ETC_CODE -> getString(R.string.ETC)
+ CoinInfo.THETA_CODE -> getString(R.string.THETA)
+ CoinInfo.XLM_CODE -> getString(R.string.XLM)
+ CoinInfo.VET_CODE -> getString(R.string.VET)
+ CoinInfo.EOS_CODE -> getString(R.string.EOS)
+ CoinInfo.TRX_CODE -> getString(R.string.TRX)
+ CoinInfo.NEO_CODE -> getString(R.string.NEO)
+ CoinInfo.IOTA_CODE -> getString(R.string.IOTA)
+ CoinInfo.ATOM_CODE -> getString(R.string.ATOM)
+ CoinInfo.BSV_CODE -> getString(R.string.BSV)
+ CoinInfo.BTT_CODE -> getString(R.string.BTT)
+ else -> ""
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentCoinList.kt b/app/src/main/java/com/mobit/mobit/FragmentCoinList.kt
new file mode 100644
index 0000000..d12246d
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentCoinList.kt
@@ -0,0 +1,143 @@
+package com.mobit.mobit
+
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RadioGroup
+import androidx.appcompat.widget.SearchView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.mobit.mobit.adapter.FragmentCoinListAdapter
+import com.mobit.mobit.data.CoinInfo
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.databinding.FragmentCoinListBinding
+
+/*
+가상화폐 목록과 정보 확인 기능이 구현될 Fragment 입니다.
+*/
+class FragmentCoinList : Fragment() {
+
+ // UI 변수 시작
+ lateinit var binding: FragmentCoinListBinding
+ // UI 변수 끝
+
+ val myViewModel: MyViewModel by activityViewModels()
+
+ // true -> 전체 코인 리스트를 보여주고 있는 상황
+ // false -> 관심 코인 리스트를 보여주고 있는 상황
+ var adapterState: Boolean = true
+
+ val coinInfo: ArrayList = ArrayList() // 전체 코인 리스트
+ lateinit var adapter: FragmentCoinListAdapter // 전체 코인 리스트 adapter
+
+ val favoriteCoinInfo: ArrayList = ArrayList() // 관심 코인 리스트
+ lateinit var favoriteAdapter: FragmentCoinListAdapter // 관심 코인 리스트 adapter
+
+ var listener: OnFragmentInteraction? = null // MainActivity와 통신할 때 사용되는 interface
+
+ interface OnFragmentInteraction {
+ fun showTransaction()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentCoinListBinding.inflate(layoutInflater)
+
+ init()
+
+ return binding.root
+ }
+
+ fun init() {
+ myViewModel.coinInfo.observe(viewLifecycleOwner, androidx.lifecycle.Observer {
+// coinInfo.clear()
+// coinInfo.addAll(myViewModel.coinInfo.value!!)
+ for (index in it.indices) {
+ if (coinInfo.size > index)
+ coinInfo[index] = it[index]
+ else
+ coinInfo.add(it[index])
+ }
+ adapter.notifyDataSetChanged()
+ })
+
+ myViewModel.favoriteCoinInfo.observe(viewLifecycleOwner, Observer {
+ favoriteCoinInfo.clear()
+ favoriteCoinInfo.addAll(myViewModel.favoriteCoinInfo.value!!)
+ favoriteAdapter.notifyDataSetChanged()
+ })
+
+ // 일반 코인 목록을 보여주는 adapter
+ adapter = FragmentCoinListAdapter(coinInfo, coinInfo)
+ adapter.listener = object : FragmentCoinListAdapter.OnItemClickListener {
+ override fun onItemClicked(view: View, coinInfo: CoinInfo) {
+ myViewModel.setSelectedCoin(coinInfo.code)
+ listener?.showTransaction()
+ Log.i("Clicked Coin", coinInfo.code)
+ }
+ }
+ // 관심 목록을 보여주는 adapter
+ favoriteAdapter = FragmentCoinListAdapter(favoriteCoinInfo, favoriteCoinInfo)
+ favoriteAdapter.listener = object : FragmentCoinListAdapter.OnItemClickListener {
+ override fun onItemClicked(view: View, coinInfo: CoinInfo) {
+ myViewModel.setSelectedCoin(coinInfo.code)
+ listener?.showTransaction()
+ Log.i("Clicked Coin", coinInfo.code)
+ }
+ }
+
+ binding.apply {
+ recyclerView.layoutManager =
+ LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
+ recyclerView.adapter = adapter
+
+ radioGroup.setOnCheckedChangeListener(object : RadioGroup.OnCheckedChangeListener {
+ override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) {
+ when (checkedId) {
+ R.id.krwBtn -> {
+ adapterState = true
+ recyclerView.adapter = adapter
+ adapter.notifyDataSetChanged()
+ }
+ R.id.favoriteBtn -> {
+ adapterState = false
+ recyclerView.adapter = favoriteAdapter
+ favoriteAdapter.notifyDataSetChanged()
+ }
+ else -> {
+ Log.e("FragmentCoinList", "Radio Group Error")
+ }
+ }
+ }
+ })
+
+ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ if (adapterState) {
+ adapter.filter.filter(query)
+ } else {
+ favoriteAdapter.filter.filter(query)
+ }
+ return true
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ if (adapterState) {
+ adapter.filter.filter(newText)
+ } else {
+ favoriteAdapter.filter.filter(newText)
+ }
+ return true
+ }
+
+ })
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentInvestment.kt b/app/src/main/java/com/mobit/mobit/FragmentInvestment.kt
new file mode 100644
index 0000000..a0966c5
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentInvestment.kt
@@ -0,0 +1,49 @@
+package com.mobit.mobit
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import com.google.android.material.tabs.TabLayoutMediator
+import com.mobit.mobit.adapter.InvestmentStateAdapter
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.databinding.FragmentInvestmentBinding
+
+/*
+투자내역 기능이 구현될 Fragment 입니다.
+*/
+class FragmentInvestment : Fragment() {
+
+ // UI 변수 시작
+ lateinit var binding: FragmentInvestmentBinding
+ // UI 변수 끝
+
+ val myViewModel: MyViewModel by activityViewModels()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentInvestmentBinding.inflate(layoutInflater)
+
+ init()
+
+ return binding.root
+ }
+
+ fun init() {
+ binding.apply {
+ viewPager.adapter = InvestmentStateAdapter(requireActivity())
+ TabLayoutMediator(tabLayout, viewPager) { tab, position ->
+ tab.text = when (position) {
+ 0 -> getString(R.string.investment_tab1)
+ 1 -> getString(R.string.investment_tab2)
+ else -> ""
+ }
+ }.attach()
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentRecord.kt b/app/src/main/java/com/mobit/mobit/FragmentRecord.kt
new file mode 100644
index 0000000..358ed21
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentRecord.kt
@@ -0,0 +1,79 @@
+package com.mobit.mobit
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.appcompat.widget.SearchView
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.mobit.mobit.adapter.FragmentRecordAdapter
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.data.Transaction
+import com.mobit.mobit.databinding.FragmentRecordBinding
+
+/*
+거래내역 기능이 구현될 Fragment 입니다.
+*/
+class FragmentRecord : Fragment() {
+
+ // UI 변수 시작
+ lateinit var binding: FragmentRecordBinding
+ // UI 변수 끝
+
+ val myViewModel: MyViewModel by activityViewModels()
+
+ lateinit var adapter: FragmentRecordAdapter
+ val transactions: ArrayList = ArrayList()
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentRecordBinding.inflate(layoutInflater)
+
+ init()
+
+ return binding.root
+ }
+
+ fun init() {
+ myViewModel.transaction.observe(viewLifecycleOwner, Observer { transaction ->
+ transactions.clear()
+ transactions.addAll(transaction)
+ adapter.notifyDataSetChanged()
+
+ binding.apply {
+ if (transactions.isEmpty()) {
+ recyclerView.visibility = View.GONE
+ noRecordView.visibility = View.VISIBLE
+ } else {
+ recyclerView.visibility = View.VISIBLE
+ noRecordView.visibility = View.GONE
+ }
+ }
+ })
+
+ adapter = FragmentRecordAdapter(transactions, transactions)
+ binding.apply {
+ recyclerView.layoutManager =
+ LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
+ recyclerView.adapter = adapter
+
+ searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
+ override fun onQueryTextSubmit(query: String?): Boolean {
+ adapter.filter.filter(query)
+ return true
+ }
+
+ override fun onQueryTextChange(newText: String?): Boolean {
+ adapter.filter.filter(newText)
+ return true
+ }
+ })
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentSell.kt b/app/src/main/java/com/mobit/mobit/FragmentSell.kt
new file mode 100644
index 0000000..4816b15
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentSell.kt
@@ -0,0 +1,323 @@
+package com.mobit.mobit
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.text.Editable
+import android.text.TextWatcher
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.AdapterView
+import android.widget.ArrayAdapter
+import android.widget.Toast
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import com.mobit.mobit.data.CoinAsset
+import com.mobit.mobit.data.CoinInfo
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.data.Transaction
+import com.mobit.mobit.databinding.FragmentSellBinding
+import java.text.DecimalFormat
+import java.time.LocalDateTime
+import java.time.format.DateTimeFormatter
+
+/*
+코인 매도 기능이 구현될 Fragment 입니다.
+*/
+class FragmentSell : Fragment() {
+
+ lateinit var getContent: ActivityResultLauncher
+
+ // UI 변수 시작
+ lateinit var binding: FragmentSellBinding
+ // UI 변수 끝
+
+ val myViewModel: MyViewModel by activityViewModels()
+
+ var selectedCoin: CoinAsset? = null
+ val formatter = DecimalFormat("###,###")
+ val formatter2 = DecimalFormat("###,###.####")
+ val formatter3 = DecimalFormat("###,###.##")
+ var orderCount: Double = 0.0
+ var orderPrice: Double = 0.0
+
+ var coinAsset: CoinAsset? = null
+
+ var listener: OnPopupActivityControl? = null
+
+ interface OnPopupActivityControl {
+ fun onPopupActivityShow()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentSellBinding.inflate(layoutInflater)
+ getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ binding.orderCount.clearFocus()
+ when (it.resultCode) {
+ Activity.RESULT_OK -> {
+ val code = it.data!!.getStringExtra("code")
+ val name = it.data!!.getStringExtra("name")
+ val price: Double = orderPrice * orderCount
+ val fee: Double = price * 0.0005
+ val time: String = getNowTime()
+ val transaction = Transaction(
+ code,
+ name,
+ time,
+ Transaction.ASK,
+ orderCount,
+ orderPrice,
+ price,
+ fee,
+ price - fee
+ )
+ coinAsset = myViewModel.askCoin(
+ code,
+ orderPrice,
+ orderCount
+ )
+ myViewModel.addTransaction(transaction)
+ val thread = object : Thread() {
+ override fun run() {
+ myViewModel.myDBHelper!!.setKRW(myViewModel.asset.value!!.krw)
+ myViewModel.myDBHelper!!.insertTransaction(transaction)
+
+ if (myViewModel.asset.value!!.coins.contains(coinAsset!!)) {
+ myViewModel.myDBHelper!!.updateCoinAsset(coinAsset!!)
+ } else {
+ myViewModel.myDBHelper!!.deleteCoinAsset(coinAsset!!)
+ }
+ }
+ }
+ thread.start()
+ binding.canOrderCoin.text = "${formatter.format(coinAsset!!.number)} ${
+ myViewModel.selectedCoin.value!!.split('-')[1]
+ }"
+ resetOrderTextView()
+ Toast.makeText(context, "매도주문이 정상 처리되었습니다.", Toast.LENGTH_SHORT).show()
+ }
+ Activity.RESULT_CANCELED -> {
+ Log.i("resultCode", "RESULT_CANCELED")
+ }
+ }
+ }
+
+ init()
+
+ return binding.root
+ }
+
+ override fun onResume() {
+ super.onResume()
+ binding.apply {
+ orderCount.setText("0")
+ orderCountSpinner.setSelection(0)
+ }
+ }
+
+ fun init() {
+ myViewModel.coinInfo.observe(viewLifecycleOwner, Observer {
+ for (coinInfo in myViewModel.coinInfo.value!!) {
+ if (coinInfo.code == myViewModel.selectedCoin.value!!) {
+ orderPrice = coinInfo.price.realTimePrice
+ break
+ }
+ }
+ binding.orderPrice.text =
+ if (orderPrice > 100.0)
+ formatter.format(orderPrice)
+ else
+ formatter3.format(orderPrice)
+ binding.orderTotalPrice.text = "${formatter.format(orderPrice * orderCount)}KRW"
+ })
+ myViewModel.selectedCoin.observe(viewLifecycleOwner, Observer {
+ var check = false
+ for (coinAsset in myViewModel.asset.value!!.coins) {
+ if (coinAsset.code == myViewModel.selectedCoin.value!!) {
+ check = true
+ selectedCoin = coinAsset
+ binding.canOrderCoin.text =
+ "${formatter2.format(coinAsset.number)} ${coinAsset.code.split('-')[1]}"
+ break
+ }
+ }
+ if (!check) {
+ binding.canOrderCoin.text = "0 ${myViewModel.selectedCoin.value!!.split('-')[1]}"
+ }
+ })
+
+ // spinner 아이템을 보여주는 view를 커스텀하기 위해서 adapter를 만들어준다
+ val spinnerAdapter = ArrayAdapter(
+ requireContext(),
+ R.layout.spinner_item,
+ resources.getStringArray(R.array.orderCountSpinner)
+ )
+
+ binding.apply {
+ for (coinAsset in myViewModel.asset.value!!.coins) {
+ if (coinAsset.code == myViewModel.selectedCoin.value!!) {
+ selectedCoin = coinAsset
+ canOrderCoin.text =
+ "${formatter.format(coinAsset.number)} ${coinAsset.code.split('-')[1]}"
+ break
+ }
+ }
+ if (selectedCoin == null) {
+ canOrderCoin.text = "0 ${myViewModel.selectedCoin.value!!.split('-')[1]}"
+ }
+ orderCountSpinner.adapter = spinnerAdapter
+ orderCountSpinner.setSelection(0, false)
+ orderCountSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
+ override fun onItemSelected(
+ parent: AdapterView<*>?,
+ view: View?,
+ position: Int,
+ id: Long
+ ) {
+ var canOrderCount: Double = 0.0
+ // 선택된 코인이 매수한 적 없는 코인인 경우,
+ // selectedCoin은 null이 되므로 조건문을 추가해야 한다.
+ if (selectedCoin != null) {
+ when (position) {
+ 0 -> {
+
+ }
+ // 최대
+ 1 -> {
+ canOrderCount = selectedCoin!!.number
+ }
+ // 50%
+ 2 -> {
+ canOrderCount = selectedCoin!!.number / 2
+ }
+ // 25%
+ 3 -> {
+ canOrderCount = selectedCoin!!.number / 4
+ }
+ // 10%
+ 4 -> {
+ canOrderCount = selectedCoin!!.number / 10
+ }
+ else -> {
+ Log.e("FragmentSell Spinner", "position is $position")
+ }
+ }
+ }
+ orderCount.setText(formatter2.format(canOrderCount))
+ val totalPrice = canOrderCount * this@FragmentSell.orderPrice
+ orderTotalPrice.text = "${formatter.format(totalPrice)}KRW"
+ }
+
+ override fun onNothingSelected(parent: AdapterView<*>?) {}
+ }
+
+ orderCount.addTextChangedListener(object : TextWatcher {
+ override fun beforeTextChanged(
+ s: CharSequence?,
+ start: Int,
+ count: Int,
+ after: Int
+ ) {
+ }
+
+ override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {
+ if (s.isNullOrEmpty()) {
+ this@FragmentSell.orderCount = 0.0
+ } else {
+ this@FragmentSell.orderCount = s.toString().replace(",", "").toDouble()
+ if (this@FragmentSell.orderCount < 0) {
+ this@FragmentSell.orderCount = 0.0
+ orderCount.setText("0")
+ }
+ }
+ val totalPrice = this@FragmentSell.orderCount * this@FragmentSell.orderPrice
+ orderTotalPrice.text = "${formatter.format(totalPrice)}KRW"
+ }
+
+ override fun afterTextChanged(s: Editable?) {}
+ })
+ orderCount.setOnFocusChangeListener(object : View.OnFocusChangeListener {
+ override fun onFocusChange(v: View?, hasFocus: Boolean) {
+ if (v != null) {
+ if (!hasFocus) {
+ val text = orderCount.text.toString()
+ if (text.isNotEmpty()) {
+ val number = text.replace(",", "").toDouble()
+ this@FragmentSell.orderCount = if (number > 0.0) number else 0.0
+ } else {
+ this@FragmentSell.orderCount = 0.0
+ orderCount.setText("0")
+ }
+ }
+ }
+ }
+ })
+ // 주문 개수와 주문 가격 초기화
+ resetBtn.setOnClickListener {
+ resetOrderTextView()
+ }
+ // 코인 매도
+ sellBtn.setOnClickListener {
+ orderCount.clearFocus()
+ val nowOrderPrice = this@FragmentSell.orderPrice
+ if (this@FragmentSell.orderCount != 0.0 && this@FragmentSell.orderCount * nowOrderPrice >= 5000.0) {
+ var coin: CoinInfo? = null
+ for (coinInfo in myViewModel.coinInfo.value!!) {
+ if (coinInfo.code == myViewModel.selectedCoin.value!!) {
+ coin = coinInfo
+ break
+ }
+ }
+
+ val flag = myViewModel.asset.value!!.canAskCoin(
+ coin!!.code,
+ nowOrderPrice,
+ this@FragmentSell.orderCount
+ )
+ if (flag) {
+ listener?.onPopupActivityShow()
+ val intent: Intent = Intent(context, PopupBuySellActivity::class.java)
+ intent.putExtra("type", 2)
+ intent.putExtra("code", coin!!.code)
+ intent.putExtra("name", coin!!.name)
+ intent.putExtra("unitPrice", nowOrderPrice)
+ intent.putExtra("count", this@FragmentSell.orderCount)
+ getContent.launch(intent)
+ } else {
+ Toast.makeText(context, "주문가능 코인이 부족합니다.", Toast.LENGTH_SHORT).show()
+ }
+ } else {
+ Toast.makeText(context, "주문 가능한 최소 금액은 5,000KRW입니다.", Toast.LENGTH_SHORT)
+ .show()
+ }
+ }
+ }
+ }
+
+ fun resetOrderTextView() {
+ this@FragmentSell.orderCount = 0.0
+ binding.orderCount.setText(formatter.format(orderCount))
+ binding.orderPrice.text =
+ if (orderPrice > 100.0)
+ formatter.format(orderPrice)
+ else
+ formatter3.format(orderPrice)
+ binding.orderTotalPrice.text = "0KRW"
+ }
+
+ fun getNowTime(): String {
+ val current = LocalDateTime.now()
+ // yyyy-MM-ddThh:mm:ss 형태로 날짜를 저장한다.
+ // https://developer.android.com/reference/java/time/format/DateTimeFormatter#ISO_LOCAL_DATE_TIME
+ val formatted = current.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME)
+ return formatted
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentSetting.kt b/app/src/main/java/com/mobit/mobit/FragmentSetting.kt
new file mode 100644
index 0000000..b5b3826
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentSetting.kt
@@ -0,0 +1,94 @@
+package com.mobit.mobit
+
+import android.app.Activity
+import android.content.Intent
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.result.ActivityResultLauncher
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.databinding.FragmentSettingBinding
+
+/*
+설정 기능이 구현될 Fragment 입니다.
+ */
+class FragmentSetting : Fragment() {
+
+ lateinit var binding: FragmentSettingBinding
+
+ val myViewModel: MyViewModel by activityViewModels()
+ val myProgressBar: MyProgressBar = MyProgressBar()
+
+ lateinit var getContent: ActivityResultLauncher
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentSettingBinding.inflate(layoutInflater)
+ getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ if (it.resultCode == Activity.RESULT_OK) {
+ myProgressBar.progressON(activity, "초기화 하는중..")
+ val thread = object : Thread() {
+ override fun run() {
+ myViewModel.myDBHelper!!.clearDB()
+ }
+ }
+ thread.start()
+ try {
+ if (thread.isAlive) {
+ thread.join()
+ }
+ } catch (e: InterruptedException) {
+ Log.e("Setting Reset Error", e.toString())
+ }
+ myProgressBar.progressOFF()
+ val intent = Intent(context, MainActivity::class.java)
+ startActivity(intent)
+ requireActivity().finish()
+ }
+ }
+
+ init()
+
+ return binding.root
+ }
+
+ fun init() {
+ binding.apply {
+ guideTitle.setOnClickListener {
+ when (guideLayout.visibility) {
+ View.VISIBLE -> {
+ guideLayout.visibility = View.GONE
+ guideTitle.setCompoundDrawablesWithIntrinsicBounds(
+ 0,
+ 0,
+ R.drawable.ic_baseline_keyboard_arrow_down_24,
+ 0
+ )
+ }
+ View.GONE -> {
+ guideLayout.visibility = View.VISIBLE
+ guideTitle.setCompoundDrawablesWithIntrinsicBounds(
+ 0,
+ 0,
+ R.drawable.ic_baseline_keyboard_arrow_up_24,
+ 0
+ )
+ }
+ }
+ }
+
+ resetView.setOnClickListener {
+ val intent = Intent(context, PopupResetConfirmActivity::class.java)
+ getContent.launch(intent)
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/FragmentTransaction.kt b/app/src/main/java/com/mobit/mobit/FragmentTransaction.kt
new file mode 100644
index 0000000..fbddf77
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/FragmentTransaction.kt
@@ -0,0 +1,236 @@
+package com.mobit.mobit
+
+import android.graphics.Color
+import android.os.Bundle
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.RadioGroup
+import android.widget.Toast
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.activityViewModels
+import androidx.lifecycle.Observer
+import androidx.recyclerview.widget.LinearLayoutManager
+import com.mobit.mobit.adapter.FragmentTransactionAdapter
+import com.mobit.mobit.data.CoinInfo
+import com.mobit.mobit.data.MyViewModel
+import com.mobit.mobit.data.OrderBook
+import com.mobit.mobit.databinding.FragmentTransactionBinding
+import java.text.DecimalFormat
+import kotlin.math.abs
+
+/*
+코인의 매수/매도 기능이 구현될 Fragment 입니다.
+ */
+class FragmentTransaction : Fragment() {
+
+ // Fragment 변수 시작
+ val fragmentBuy: Fragment = FragmentBuy()
+ val fragmentSell: Fragment = FragmentSell()
+ val fragmentCoinInfo: Fragment = FragmentCoinInfo()
+ // Fragment 변수 끝
+
+ // UI 변수 시작
+ lateinit var binding: FragmentTransactionBinding
+ // UI 변수 끝
+
+ val myViewModel: MyViewModel by activityViewModels()
+
+ var doScrollVertically = true
+ lateinit var adapter: FragmentTransactionAdapter // 호가 정보 리스트 adapter
+ var selectedCoin: CoinInfo? = null // CoinList에서 사용자가 선택한 코인의 정보
+ val orderBook: ArrayList =
+ ArrayList() // selectedCoin의 호가 정보를 갖는다. (내림차순으로 정렬되어 있음)
+ // [0, 14] -> 매도 호가
+ // [15, 29] -> 매수 호가
+
+ var resumeFlag: Boolean = true
+
+ var listener: OnFragmentInteraction? = null // MainActivity와 통신할 때 사용되는 interface
+
+ interface OnFragmentInteraction {
+ fun orderBookThreadStop()
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater, container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ binding = FragmentTransactionBinding.inflate(layoutInflater)
+
+ init()
+
+ return binding.root
+ }
+
+ override fun onResume() {
+ super.onResume()
+ if (resumeFlag) {
+ binding.buyAndSellGroup.check(R.id.coinBuyBtn)
+ replaceFragment(fragmentBuy)
+ } else {
+ resumeFlag = true
+ }
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ listener?.orderBookThreadStop()
+ myViewModel.orderBook.value!!.clear()
+ doScrollVertically = true
+ }
+
+ fun init() {
+ (fragmentSell as FragmentSell).listener = object : FragmentSell.OnPopupActivityControl {
+ override fun onPopupActivityShow() {
+ resumeFlag = false
+ }
+ }
+
+ myViewModel.coinInfo.observe(viewLifecycleOwner, Observer {
+ for (coinInfo in myViewModel.coinInfo.value!!) {
+ if (coinInfo.code == myViewModel.selectedCoin.value!!) {
+ selectedCoin = coinInfo
+ break
+ }
+ }
+ binding.apply {
+ if (selectedCoin != null) {
+ val formatter = DecimalFormat("###,###")
+ val changeFormatter = DecimalFormat("###,###.##")
+ coinName.text = "${selectedCoin!!.name}(${selectedCoin!!.code.split('-')[1]})"
+ coinPrice.text =
+ if (selectedCoin!!.price.realTimePrice > 100.0)
+ formatter.format(selectedCoin!!.price.realTimePrice)
+ else
+ changeFormatter.format(selectedCoin!!.price.realTimePrice)
+ coinRate.text =
+ changeFormatter.format(selectedCoin!!.price.changeRate * 100) + "%"
+ coinDiff.text = when (selectedCoin!!.price.change) {
+ "EVEN" -> ""
+ "RISE" -> "▲ "
+ "FALL" -> "▼ "
+ else -> ""
+ } + changeFormatter.format(abs(selectedCoin!!.price.changePrice))
+
+ setTextViewColor(selectedCoin!!)
+ }
+ }
+ })
+ myViewModel.orderBook.observe(viewLifecycleOwner, Observer {
+ orderBook.clear()
+ orderBook.addAll(myViewModel.orderBook.value!!)
+ adapter.notifyDataSetChanged()
+
+ if (doScrollVertically && orderBook.isNotEmpty()) {
+ Log.i("FragmentTransaction", orderBook.size.toString())
+ binding.apply {
+ // recyclerview에 있는 item들 중에서 가운데에 위치한 아이템이 화면의 중앙에 위치하도록 하고 싶은데 방법을 모르겠다.
+ recyclerView.scrollToPosition(0)
+ recyclerView.scrollToPosition(6)
+ }
+ doScrollVertically = false
+ }
+ })
+
+ for (coinInfo in myViewModel.coinInfo.value!!) {
+ if (coinInfo.code == myViewModel.selectedCoin.value!!) {
+ selectedCoin = coinInfo
+ break
+ }
+ }
+ adapter = FragmentTransactionAdapter(orderBook, selectedCoin!!.price.openPrice)
+
+ binding.apply {
+ recyclerView.layoutManager =
+ LinearLayoutManager(context, LinearLayoutManager.VERTICAL, false)
+ recyclerView.adapter = adapter
+
+ buyAndSellGroup.setOnCheckedChangeListener(object : RadioGroup.OnCheckedChangeListener {
+ override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) {
+ when (checkedId) {
+ R.id.coinBuyBtn -> {
+ replaceFragment(fragmentBuy)
+ }
+ R.id.coinSellBtn -> {
+ replaceFragment(fragmentSell)
+ }
+ R.id.coinInfoBtn -> {
+ replaceFragment(fragmentCoinInfo)
+ }
+ else -> {
+ Log.e("FragmentTransaction", "Radio Group Error")
+ }
+ }
+ }
+ })
+
+ // 코인이 관심목록에 등록되어 있는 경우에는 ImageButton을 채워진 별로 변경해야 한다.
+ if (myViewModel.favoriteCoinInfo.value!!.contains(selectedCoin)) {
+ favoriteBtn.setImageResource(R.drawable.ic_round_star_24)
+ }
+
+ favoriteBtn.setOnClickListener(object : View.OnClickListener {
+ override fun onClick(v: View?) {
+ // 즐겨찾기에 이미 추가되어 있는 경우
+ if (myViewModel.favoriteCoinInfo.value!!.contains(selectedCoin)) {
+ if (myViewModel.removeFavoriteCoinInfo(selectedCoin!!)) {
+ val thread = object : Thread() {
+ override fun run() {
+ val flag =
+ myViewModel.myDBHelper!!.deleteFavorite(selectedCoin!!.code)
+ Log.e("favorite delete", flag.toString())
+ }
+ }
+ thread.start()
+ favoriteBtn.setImageResource(R.drawable.ic_round_star_border_24)
+ Toast.makeText(context, "관심코인에서 삭제되었습니다.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ // 즐겨찾기에 추가되어 있지 않은 경우
+ else {
+ if (myViewModel.addFavoriteCoinInfo(selectedCoin!!)) {
+ val thread = object : Thread() {
+ override fun run() {
+ val flag =
+ myViewModel.myDBHelper!!.insertFavoirte(selectedCoin!!.code)
+ Log.e("favorite insert", flag.toString())
+ }
+ }
+ thread.start()
+ favoriteBtn.setImageResource(R.drawable.ic_round_star_24)
+ Toast.makeText(context, "관심코인으로 등록되었습니다.", Toast.LENGTH_SHORT).show()
+ }
+ }
+ }
+ })
+ }
+ }
+
+ fun replaceFragment(fragment: Fragment) {
+ val fragmentTransaction: androidx.fragment.app.FragmentTransaction =
+ childFragmentManager.beginTransaction()
+ fragmentTransaction.replace(R.id.frameLayout, fragment)
+ fragmentTransaction.commit()
+ }
+
+ fun setTextViewColor(coinInfo: CoinInfo) {
+ binding.apply {
+ if (coinInfo.price.changeRate > 0) {
+ coinPrice.setTextColor(Color.parseColor("#bd4e3a"))
+ coinRate.setTextColor(Color.parseColor("#bd4e3a"))
+ coinDiff.setTextColor(Color.parseColor("#bd4e3a"))
+ } else if (coinInfo.price.changeRate < 0) {
+ coinPrice.setTextColor(Color.parseColor("#135fc1"))
+ coinRate.setTextColor(Color.parseColor("#135fc1"))
+ coinDiff.setTextColor(Color.parseColor("#135fc1"))
+ } else {
+ coinPrice.setTextColor(Color.parseColor("#FFFFFF"))
+ coinRate.setTextColor(Color.parseColor("#FFFFFF"))
+ coinDiff.setTextColor(Color.parseColor("#FFFFFF"))
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/MainActivity.kt b/app/src/main/java/com/mobit/mobit/MainActivity.kt
new file mode 100644
index 0000000..b27f3be
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/MainActivity.kt
@@ -0,0 +1,364 @@
+package com.mobit.mobit
+
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.Bundle
+import android.os.Handler
+import android.os.Message
+import android.util.Log
+import android.widget.Toast
+import androidx.activity.result.contract.ActivityResultContracts
+import androidx.activity.viewModels
+import androidx.appcompat.app.AppCompatActivity
+import androidx.fragment.app.Fragment
+import com.mobit.mobit.data.*
+import com.mobit.mobit.databinding.ActivityMainBinding
+import com.mobit.mobit.db.MyDBHelper
+import com.mobit.mobit.service.UpbitAPIService
+import java.util.*
+import kotlin.collections.ArrayList
+
+class MainActivity : AppCompatActivity() {
+
+ // Fragment 변수 시작
+ val fragmentCoinList: Fragment = FragmentCoinList()
+ val fragmentChart: Fragment = FragmentChart()
+ val fragmentTransaction: Fragment = FragmentTransaction()
+ val fragmentInvestment: Fragment = FragmentInvestment()
+ val fragmentSetting: Fragment = FragmentSetting()
+ // Fragment 변수 끝
+
+ // UI 변수 시작
+ lateinit var binding: ActivityMainBinding
+ // UI 변수 끝
+
+ val myViewModel: MyViewModel by viewModels()
+
+ val dbHandler: DBHandler = DBHandler()
+ lateinit var dbThread: DBThread
+
+ // 뒤로가기 두번 누르면 앱 종료 관련 변수 시작
+ val FINISH_INTERVAL_TIME: Long = 2000
+ var backPressedTime: Long = 0
+ // 뒤로가기 두번 누르면 앱 종료 관련 변수 끝
+
+ val getContent = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
+ if (it.data != null) {
+ val krw: Double = it.data!!.getDoubleExtra("krw", 10000000.0)
+ myViewModel.asset.value!!.krw = krw
+ val thread = object : Thread() {
+ override fun run() {
+ myViewModel.myDBHelper!!.setFlag(true)
+ myViewModel.myDBHelper!!.setKRW(krw)
+ }
+ }
+ thread.start()
+ }
+ }
+
+ val receiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ if (intent != null) {
+ val mode = intent.getStringExtra("mode")
+ when (mode) {
+ "CoinInfo" -> {
+ val isSuccess = intent.getBooleanExtra("isSuccess", false)
+ if (isSuccess) {
+ val coinInfo =
+ intent.getSerializableExtra("coinInfo") as ArrayList
+ val favoriteCoinInfo =
+ intent.getSerializableExtra("favoriteCoinInfo") as ArrayList
+ myViewModel.setCoinInfo(coinInfo)
+ myViewModel.setFavoriteCoinInfo(favoriteCoinInfo)
+ }
+ }
+ "orderBook" -> {
+ val isSuccess = intent.getBooleanExtra("isSuccess", false)
+ if (isSuccess) {
+ val orderBook =
+ intent.getSerializableExtra("orderBook") as ArrayList
+ myViewModel.setOrderBook(orderBook)
+ }
+ }
+ }
+ }
+ }
+ }
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityMainBinding.inflate(layoutInflater)
+ setContentView(binding.root)
+
+ initDB()
+ initData()
+ initService()
+ init()
+ }
+
+ override fun onBackPressed() {
+ val tempTime: Long = System.currentTimeMillis()
+ val intervalTime = tempTime - backPressedTime
+
+ if (0 <= intervalTime && intervalTime <= FINISH_INTERVAL_TIME) {
+ super.onBackPressed()
+ } else {
+ backPressedTime = tempTime
+ val msg: String = "뒤로 가기를 한 번 더 누르면 종료됩니다."
+ Toast.makeText(this, msg, Toast.LENGTH_SHORT).show()
+ }
+ }
+
+ override fun onRestart() {
+ super.onRestart()
+
+ val serviceBRIntent = Intent(this, UpbitAPIService::class.java)
+ serviceBRIntent.putExtra("mode", "START")
+ sendBroadcast(serviceBRIntent)
+ }
+
+ override fun onStop() {
+ super.onStop()
+
+ val serviceBRIntent = Intent(this, UpbitAPIService::class.java)
+ serviceBRIntent.putExtra("mode", "STOP")
+ sendBroadcast(serviceBRIntent)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ unregisterReceiver(receiver)
+ }
+
+ fun initDB() {
+ myViewModel.myDBHelper = MyDBHelper(this)
+ dbThread = DBThread()
+ dbThread.start()
+ }
+
+ fun initData() {
+ myViewModel.setSelectedCoin(CoinInfo.BTC_CODE)
+ myViewModel.setFavoriteCoinInfo(ArrayList())
+ myViewModel.setOrderBook(ArrayList())
+ myViewModel.setAsset(Asset(0.0, ArrayList()))
+ }
+
+ fun initService() {
+ registerReceiver(receiver, IntentFilter("com.mobit.APIRECEIVE"))
+ val intent = Intent(this, UpbitAPIService::class.java)
+ startService(intent)
+ }
+
+ fun init() {
+ myViewModel.selectedCoin.observe(this, androidx.lifecycle.Observer {
+ val serviceBRIntent = Intent("com.mobit.APICALL")
+ serviceBRIntent.putExtra("mode", "SELECTED_COIN_SETTING")
+ serviceBRIntent.putExtra("selectedCoin", myViewModel.selectedCoin.value!!)
+ sendBroadcast(serviceBRIntent)
+ })
+ myViewModel.favoriteCoinInfo.observe(this, androidx.lifecycle.Observer {
+ val serviceBRIntent = Intent("com.mobit.APICALL")
+ serviceBRIntent.putExtra("mode", "FAVORITE_COININFO_SETTING")
+ serviceBRIntent.putExtra("favoriteCoinInfo", myViewModel.favoriteCoinInfo.value!!)
+ sendBroadcast(serviceBRIntent)
+ })
+
+ replaceFragment(fragmentCoinList)
+ binding.apply {
+ bottomNavBar.setOnNavigationItemSelectedListener {
+ when (it.itemId) {
+ R.id.menu_coinlist -> {
+ replaceFragment(fragmentCoinList)
+ return@setOnNavigationItemSelectedListener true
+ }
+ R.id.menu_chart -> {
+ replaceFragment(fragmentChart)
+ return@setOnNavigationItemSelectedListener true
+ }
+ R.id.menu_transaction -> {
+ val serviceBTIntent = Intent("com.mobit.APICALL")
+ serviceBTIntent.putExtra("mode", "START_THREAD2")
+ sendBroadcast(serviceBTIntent)
+
+ replaceFragment(fragmentTransaction)
+ return@setOnNavigationItemSelectedListener true
+ }
+ R.id.menu_investment -> {
+ replaceFragment(fragmentInvestment)
+ return@setOnNavigationItemSelectedListener true
+ }
+ R.id.menu_setting -> {
+ replaceFragment(fragmentSetting)
+ return@setOnNavigationItemSelectedListener true
+ }
+ else -> {
+ return@setOnNavigationItemSelectedListener false
+ }
+ }
+ }
+ }
+
+ (fragmentCoinList as FragmentCoinList).listener =
+ object : FragmentCoinList.OnFragmentInteraction {
+ override fun showTransaction() {
+ binding.bottomNavBar.selectedItemId = R.id.menu_transaction
+ }
+ }
+ (fragmentTransaction as FragmentTransaction).listener =
+ object : FragmentTransaction.OnFragmentInteraction {
+ override fun orderBookThreadStop() {
+ val serviceBTIntent = Intent("com.mobit.APICALL")
+ serviceBTIntent.putExtra("mode", "STOP_THREAD2")
+ sendBroadcast(serviceBTIntent)
+ }
+ }
+ }
+
+ fun replaceFragment(fragment: Fragment) {
+ val fragmentTransaction: androidx.fragment.app.FragmentTransaction =
+ supportFragmentManager.beginTransaction()
+ fragmentTransaction.replace(R.id.frameLayout, fragment)
+ fragmentTransaction.commit()
+ }
+
+ inner class DBHandler : Handler() {
+ override fun handleMessage(msg: Message) {
+ super.handleMessage(msg)
+
+ val bundle: Bundle = msg.data
+ if (!bundle.isEmpty) {
+ val isFavorites = bundle.getBoolean("isFavorites")
+ val isKrw = bundle.getBoolean("isKrw")
+ val isCoinAssets = bundle.getBoolean("isCoinAssets")
+ val isTransaction = bundle.getBoolean("isTransactions")
+
+ if (isFavorites) {
+ val favorites = bundle.getSerializable("favorites") as ArrayList
+ val list = ArrayList()
+ for (code in favorites) {
+ list.add(
+ CoinInfo(
+ code,
+ code,
+ Price(
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ "EVEN",
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ 0.0,
+ "",
+ 0.0,
+ ""
+ )
+ )
+ )
+ }
+ myViewModel.setFavoriteCoinInfo(list)
+ }
+ if (isKrw) {
+ val krw = bundle.getDouble("krw")
+ if (isCoinAssets) {
+ val coinAssets =
+ bundle.getSerializable("coinAssets") as ArrayList
+ val asset = Asset(krw, coinAssets)
+ myViewModel.setAsset(asset)
+ } else {
+ val asset = Asset(krw, ArrayList())
+ myViewModel.setAsset(asset)
+ }
+ Log.i("isKrw True", krw.toString())
+ } else {
+ val asset = Asset(10000000.0, ArrayList())
+ myViewModel.setAsset(asset)
+ Log.i("isKrw False", asset.krw.toString())
+ }
+ if (isTransaction) {
+ val transactions =
+ bundle.getSerializable("transactions") as ArrayList
+ myViewModel.setTransaction(transactions)
+ } else {
+ val transactions = ArrayList()
+ myViewModel.setTransaction(transactions)
+ }
+
+ val flag = bundle.getBoolean("flag")
+ setTheme(R.style.Theme_Mobit)
+ if (!flag) {
+ val intent = Intent(this@MainActivity, FirstSettingActivity::class.java)
+ getContent.launch(intent)
+ }
+
+ val serviceBRIntent = Intent("com.mobit.APICALL")
+ serviceBRIntent.putExtra("mode", "INITIAL_SETTING")
+ serviceBRIntent.putExtra("selectedCoin", myViewModel.selectedCoin.value!!)
+ serviceBRIntent.putExtra("favoriteCoinInfo", myViewModel.favoriteCoinInfo.value!!)
+ serviceBRIntent.putExtra("asset", myViewModel.asset.value!!)
+ sendBroadcast(serviceBRIntent)
+
+ val serviceBRIntent2 = Intent("com.mobit.APICALL")
+ serviceBRIntent2.putExtra("mode", "START_THREAD1")
+ sendBroadcast(serviceBRIntent2)
+ }
+ }
+ }
+
+ inner class DBThread : Thread() {
+ override fun run() {
+ val message: Message = dbHandler.obtainMessage()
+ val bundle: Bundle = Bundle()
+
+ // DB로부터 favorite 데이터 가져오기
+ val favorites = myViewModel.myDBHelper!!.getFavorites()
+ if (favorites.isNotEmpty()) {
+ bundle.putBoolean("isFavorites", true)
+ bundle.putSerializable("favorites", favorites)
+ } else {
+ bundle.putBoolean("isFavorites", false)
+ }
+
+ // DB로부터 KRW 데이터 가져오기
+ val krw = myViewModel.myDBHelper!!.getKRW()
+ if (krw != null) {
+ bundle.putBoolean("isKrw", true)
+ bundle.putDouble("krw", krw!!)
+ } else {
+ bundle.putBoolean("isKrw", false)
+ }
+
+ // DB로부터 CoinAsset 데이터 가져오기
+ val coinAssets = myViewModel.myDBHelper!!.getCoinAssets()
+ if (coinAssets.isNotEmpty()) {
+ bundle.putBoolean("isCoinAssets", true)
+ bundle.putSerializable("coinAssets", coinAssets)
+ } else {
+ bundle.putBoolean("isCoinAssets", false)
+ }
+
+ // DB로부터 Transaction 데이터 가져오기
+ val transactions = myViewModel.myDBHelper!!.getTransactions()
+ if (transactions.isNotEmpty()) {
+ bundle.putBoolean("isTransactions", true)
+ bundle.putSerializable("transactions", transactions)
+ } else {
+ bundle.putBoolean("isTransactions", false)
+ }
+
+ val flag = myViewModel.myDBHelper!!.getFlag()
+ bundle.putBoolean("flag", flag)
+
+ message.data = bundle
+ dbHandler.sendMessage(message)
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/MyProgressBar.kt b/app/src/main/java/com/mobit/mobit/MyProgressBar.kt
new file mode 100644
index 0000000..84836b6
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/MyProgressBar.kt
@@ -0,0 +1,51 @@
+package com.mobit.mobit
+
+import android.app.Activity
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.text.TextUtils
+import android.view.View
+import android.widget.TextView
+import androidx.appcompat.app.AppCompatDialog
+
+class MyProgressBar {
+ private var progressDialog: AppCompatDialog? = null
+
+ fun progressON(activity: Activity?, message: String?) {
+ if (activity == null || activity.isFinishing) {
+ return
+ }
+ if (progressDialog != null && progressDialog!!.isShowing) {
+ progressSET(message)
+ } else {
+ progressDialog = AppCompatDialog(activity)
+ progressDialog!!.setCancelable(false)
+ progressDialog!!.window
+ ?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+ progressDialog!!.setContentView(R.layout.progress_loading)
+ progressDialog!!.show()
+ }
+ val tv_progress_message =
+ progressDialog!!.findViewById(R.id.tv_progress_message) as TextView?
+ if (!TextUtils.isEmpty(message)) {
+ tv_progress_message!!.text = message
+ }
+ }
+
+ fun progressSET(message: String?) {
+ if (progressDialog == null || !progressDialog!!.isShowing) {
+ return
+ }
+ val tv_progress_message =
+ progressDialog!!.findViewById(R.id.tv_progress_message) as TextView?
+ if (!TextUtils.isEmpty(message)) {
+ tv_progress_message!!.text = message
+ }
+ }
+
+ fun progressOFF() {
+ if (progressDialog != null && progressDialog!!.isShowing) {
+ progressDialog!!.dismiss()
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/PopupBuySellActivity.kt b/app/src/main/java/com/mobit/mobit/PopupBuySellActivity.kt
new file mode 100644
index 0000000..26bf432
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/PopupBuySellActivity.kt
@@ -0,0 +1,71 @@
+package com.mobit.mobit
+
+import android.app.Activity
+import android.content.Intent
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.util.Log
+import android.view.Window
+import com.mobit.mobit.databinding.ActivityPopupBuySellBinding
+import java.text.DecimalFormat
+
+class PopupBuySellActivity : Activity() {
+
+ lateinit var binding: ActivityPopupBuySellBinding
+ val formatter = DecimalFormat("###,###")
+ val formatter2 = DecimalFormat("###,###.####")
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityPopupBuySellBinding.inflate(layoutInflater)
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(binding.root)
+
+ window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+
+ // 1-> 매수
+ // 2-> 매도
+ val type: Int = intent.getIntExtra("type", 1)
+ val code: String = intent.getStringExtra("code")
+ val name: String = intent.getStringExtra("name")
+ val unitPrice: Double = intent.getDoubleExtra("unitPrice", 0.0)
+ val count: Double = intent.getDoubleExtra("count", 0.0)
+ val totalPrice: Double = unitPrice * count
+
+ binding.apply {
+ when (type) {
+ 1 -> {
+ title.setTextColor(Color.parseColor("#c34c34"))
+ confirmBtn.setTextColor(Color.parseColor("#c34c34"))
+ title.text = "매수주문 확인"
+ confirmBtn.text = "매수확인"
+ }
+ 2 -> {
+ title.setTextColor(Color.parseColor("#1262c5"))
+ confirmBtn.setTextColor(Color.parseColor("#1262c5"))
+ title.text = "매도주문 확인"
+ confirmBtn.text = "매도확인"
+ }
+ }
+ coin.text = "$name(${code.split('-')[1]}/KRW)"
+ orderPrice.text = formatter.format(unitPrice)
+ orderCount.text = formatter2.format(count)
+ orderTotalPrice.text = formatter.format(totalPrice)
+
+ confirmBtn.setOnClickListener {
+ val intent: Intent = Intent()
+ intent.putExtra("code", code)
+ intent.putExtra("name", name)
+ setResult(RESULT_OK, intent)
+ Log.i("PopupBuySellActivity", "RESULT_OK")
+ finish()
+ }
+ cancelBtn.setOnClickListener {
+ setResult(RESULT_CANCELED)
+ Log.i("PopupBuySellActivity", "RESULT_CANCELED")
+ finish()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/PopupResetConfirmActivity.kt b/app/src/main/java/com/mobit/mobit/PopupResetConfirmActivity.kt
new file mode 100644
index 0000000..9eae887
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/PopupResetConfirmActivity.kt
@@ -0,0 +1,38 @@
+package com.mobit.mobit
+
+import android.app.Activity
+import android.graphics.Color
+import android.graphics.drawable.ColorDrawable
+import android.os.Bundle
+import android.view.Window
+import com.mobit.mobit.databinding.ActivityPopupResetConfirmBinding
+
+class PopupResetConfirmActivity : Activity() {
+
+ lateinit var binding: ActivityPopupResetConfirmBinding
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ binding = ActivityPopupResetConfirmBinding.inflate(layoutInflater)
+ requestWindowFeature(Window.FEATURE_NO_TITLE);
+ setContentView(binding.root)
+
+ window.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
+
+ binding.apply {
+ cancelBtn.setOnClickListener {
+ setResult(RESULT_CANCELED)
+ finish()
+ }
+
+ confirmBtn.setOnClickListener {
+ setResult(RESULT_OK)
+ finish()
+ }
+ }
+ }
+
+ override fun onBackPressed() {
+
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/adapter/FragmentAssetAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/FragmentAssetAdapter.kt
new file mode 100644
index 0000000..d763f0f
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/adapter/FragmentAssetAdapter.kt
@@ -0,0 +1,87 @@
+package com.mobit.mobit.adapter
+
+import android.graphics.Color
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.mobit.mobit.R
+import com.mobit.mobit.data.CoinAsset
+import java.text.DecimalFormat
+
+/*
+FragmentAsset에서 보유 자산을 보여줄 때 사용하는 adapter 입니다.
+*/
+class FragmentAssetAdapter(var items: ArrayList) :
+ RecyclerView.Adapter() {
+
+ val formatter = DecimalFormat("###,###")
+ val formatter2 = DecimalFormat("###,###.##")
+ val formatter3 = DecimalFormat("###,###.####")
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val nameView: TextView
+ val codeView: TextView
+ val gainAndLossView: TextView
+ val yieldView: TextView
+ val retainedNumView: TextView
+ val averagePriceView: TextView
+ val evaluationView: TextView
+ val buyPriceView: TextView
+
+ init {
+ nameView = itemView.findViewById(R.id.nameView)
+ codeView = itemView.findViewById(R.id.codeView)
+ gainAndLossView = itemView.findViewById(R.id.gainAndLossView)
+ yieldView = itemView.findViewById(R.id.yieldView)
+ retainedNumView = itemView.findViewById(R.id.retainedNumView)
+ averagePriceView = itemView.findViewById(R.id.averagePriceView)
+ evaluationView = itemView.findViewById(R.id.evaluationView)
+ buyPriceView = itemView.findViewById(R.id.buyPriceView)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val v = LayoutInflater.from(parent.context)
+ .inflate(R.layout.recyclerview_asset_item, parent, false)
+ return ViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ holder.nameView.text = items[position].name
+ val code = items[position].code.split("-")[1]
+ holder.codeView.text = "($code)"
+ holder.retainedNumView.text =
+ "${formatter3.format(items[position].number)} $code"
+ holder.averagePriceView.text =
+ if (items[position].averagePrice > 100.0)
+ "${formatter.format(items[position].averagePrice)} KRW"
+ else "${formatter2.format(items[position].averagePrice)} KRW"
+ holder.evaluationView.text = "${formatter.format(items[position].amount)} KRW"
+
+ val buyPrice = items[position].averagePrice * items[position].number
+ holder.buyPriceView.text = "${formatter.format(buyPrice)} KRW"
+
+ val gainAndLoss = items[position].amount - buyPrice
+ val yieldValue = gainAndLoss / buyPrice * 100
+ holder.gainAndLossView.text = formatter.format(gainAndLoss)
+ holder.yieldView.text = formatter2.format(yieldValue) + "%"
+
+ val rgb = if (gainAndLoss > 0) Color.rgb(
+ 207,
+ 80,
+ 71
+ ) else if (gainAndLoss < 0) Color.rgb(
+ 25,
+ 96,
+ 186
+ ) else Color.rgb(211, 212, 214)
+ holder.gainAndLossView.setTextColor(rgb)
+ holder.yieldView.setTextColor(rgb)
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/adapter/FragmentCoinListAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/FragmentCoinListAdapter.kt
new file mode 100644
index 0000000..c6ffe16
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/adapter/FragmentCoinListAdapter.kt
@@ -0,0 +1,130 @@
+package com.mobit.mobit.adapter
+
+import android.graphics.Color
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Filter
+import android.widget.Filterable
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.mobit.mobit.R
+import com.mobit.mobit.data.CoinInfo
+import java.text.DecimalFormat
+
+class FragmentCoinListAdapter(
+ var items: ArrayList,
+ var filteredItems: ArrayList
+) :
+ RecyclerView.Adapter(), Filterable {
+
+ var listener: OnItemClickListener? = null
+
+ interface OnItemClickListener {
+ fun onItemClicked(view: View, coinInfo: CoinInfo)
+ }
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val linearLayout: LinearLayout
+ val korCoinName: TextView
+ val engCoinName: TextView
+ val realTimePrice: TextView
+ val changeRate: TextView
+ val totalTradePrice: TextView
+
+ init {
+ linearLayout = itemView.findViewById(R.id.linearLayout)
+ korCoinName = itemView.findViewById(R.id.korCoinName)
+ engCoinName = itemView.findViewById(R.id.engCoinName)
+ realTimePrice = itemView.findViewById(R.id.realTimePrice)
+ changeRate = itemView.findViewById(R.id.changeRate)
+ totalTradePrice = itemView.findViewById(R.id.totalTradePrice)
+
+ val clickListener: View.OnClickListener = object : View.OnClickListener {
+ override fun onClick(v: View?) {
+ if (v != null) {
+ listener?.onItemClicked(v, filteredItems[adapterPosition])
+ }
+ }
+ }
+ linearLayout.setOnClickListener(clickListener)
+ korCoinName.setOnClickListener(clickListener)
+ engCoinName.setOnClickListener(clickListener)
+ realTimePrice.setOnClickListener(clickListener)
+ changeRate.setOnClickListener(clickListener)
+ totalTradePrice.setOnClickListener(clickListener)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val v = LayoutInflater.from(parent.context)
+ .inflate(R.layout.recyclerview_coinlist_item, parent, false)
+ return ViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val formatter = DecimalFormat("###,###")
+ val changeFormatter = DecimalFormat("###,###.##")
+
+ holder.korCoinName.text = filteredItems[position].name
+ holder.engCoinName.text = filteredItems[position].code.split('-')[1] + "/KRW"
+ holder.realTimePrice.text =
+ if (filteredItems[position].price.realTimePrice > 100.0)
+ formatter.format(filteredItems[position].price.realTimePrice)
+ else
+ changeFormatter.format(filteredItems[position].price.realTimePrice)
+ holder.changeRate.text =
+ changeFormatter.format(filteredItems[position].price.changeRate * 100) + "%"
+ var temp = (filteredItems[position].price.totalTradePrice24 / 1000000).toInt()
+ holder.totalTradePrice.text = formatter.format(temp) + "백만"
+
+ if (filteredItems[position].price.changeRate > 0) {
+ holder.realTimePrice.setTextColor(Color.parseColor("#bd4e3a"))
+ holder.changeRate.setTextColor(Color.parseColor("#bd4e3a"))
+ } else if (filteredItems[position].price.changeRate < 0) {
+ holder.realTimePrice.setTextColor(Color.parseColor("#135fc1"))
+ holder.changeRate.setTextColor(Color.parseColor("#135fc1"))
+ } else {
+ holder.realTimePrice.setTextColor(Color.parseColor("#FFFFFF"))
+ holder.changeRate.setTextColor(Color.parseColor("#FFFFFF"))
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return filteredItems.size
+ }
+
+ override fun getFilter(): Filter {
+ return object : Filter() {
+ override fun performFiltering(constraint: CharSequence?): FilterResults {
+ val str: String = constraint.toString()
+
+ if (str.isNullOrBlank()) {
+ filteredItems = items
+ } else {
+ val filteringList: ArrayList = ArrayList()
+ for (coinInfo in items) {
+ val coinName = coinInfo.name
+ if (coinName.contains(str)) {
+ filteringList.add(coinInfo)
+ }
+ }
+ filteredItems = filteringList
+ }
+ val result: FilterResults = FilterResults()
+ result.values = filteredItems
+
+ return result
+ }
+
+ override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
+ if (results != null) {
+ filteredItems = results.values as ArrayList
+ notifyDataSetChanged()
+ }
+ }
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/adapter/FragmentRecordAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/FragmentRecordAdapter.kt
new file mode 100644
index 0000000..abe5d47
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/adapter/FragmentRecordAdapter.kt
@@ -0,0 +1,125 @@
+package com.mobit.mobit.adapter
+
+import android.graphics.Color
+import android.util.Log
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.Filter
+import android.widget.Filterable
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.mobit.mobit.R
+import com.mobit.mobit.data.Transaction
+import java.text.DecimalFormat
+
+// blue -> #1561bf
+// red -> #b35241
+class FragmentRecordAdapter(
+ var items: ArrayList,
+ var filteredList: ArrayList
+) :
+ RecyclerView.Adapter(), Filterable {
+
+ val formatter = DecimalFormat("###,###")
+ val formatter2 = DecimalFormat("###,###.##")
+ val formatter3 = DecimalFormat("###,###.####")
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val nameView: TextView
+ val typeView: TextView
+ val timeView: TextView
+ val tradePriceView: TextView
+ val tradeNumView: TextView
+ val unitPriceView: TextView
+ val feeView: TextView
+ val totalPriceView: TextView
+
+ init {
+ nameView = itemView.findViewById(R.id.nameView)
+ typeView = itemView.findViewById(R.id.typeView)
+ timeView = itemView.findViewById(R.id.timeView)
+ tradePriceView = itemView.findViewById(R.id.tradePriceView)
+ tradeNumView = itemView.findViewById(R.id.tradeNumView)
+ unitPriceView = itemView.findViewById(R.id.unitPriceView)
+ feeView = itemView.findViewById(R.id.feeView)
+ totalPriceView = itemView.findViewById(R.id.totalPriceView)
+ }
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val v = LayoutInflater.from(parent.context)
+ .inflate(R.layout.recyclerview_record_item, parent, false)
+ return ViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ Log.i("FragmentRecordAdapter", filteredList[position].code)
+ val code = filteredList[position].code.split("-")[1]
+// val code = filteredList[position].code
+ holder.nameView.text = "${filteredList[position].name}($code)"
+
+ val tradePrice = filteredList[position].tradePrice
+ val fee = filteredList[position].fee
+ when (filteredList[position].type) {
+ // 매도
+ Transaction.ASK -> {
+ holder.typeView.text = "매도"
+ holder.typeView.setTextColor(Color.rgb(26, 96, 184))
+ holder.totalPriceView.text = "${formatter.format(tradePrice - fee)} KRW"
+ }
+ // 매수
+ Transaction.BID -> {
+ holder.typeView.text = "매수"
+ holder.typeView.setTextColor(Color.rgb(188, 79, 59))
+ holder.totalPriceView.text = "${formatter.format(tradePrice + fee)} KRW"
+ }
+ }
+
+ val times = filteredList[position].time.split("T")
+ holder.timeView.text = "${times[0]} ${times[1].substring(0, 5)}"
+ holder.tradePriceView.text = "${formatter.format(tradePrice)} KRW"
+ holder.tradeNumView.text = "${formatter3.format(filteredList[position].quantity)} $code"
+ holder.unitPriceView.text =
+ if (filteredList[position].unitPrice > 100.0)
+ "${formatter.format(filteredList[position].unitPrice)} KRW"
+ else "${formatter2.format(filteredList[position].unitPrice)} KRW"
+ holder.feeView.text = "${formatter2.format(fee)} KRW"
+ }
+
+ override fun getItemCount(): Int {
+ return filteredList.size
+ }
+
+ override fun getFilter(): Filter {
+ return object : Filter() {
+ override fun performFiltering(constraint: CharSequence?): FilterResults {
+ val str: String = constraint.toString()
+
+ if (str.isBlank()) {
+ filteredList = items
+ } else {
+ val filteringList: ArrayList = ArrayList()
+ for (transaction in items) {
+ if (transaction.name.contains(str)) {
+ filteringList.add(transaction)
+ }
+ }
+ filteredList = filteringList
+ }
+
+ val filterResults: FilterResults = FilterResults()
+ filterResults.values = filteredList
+
+ return filterResults
+ }
+
+ override fun publishResults(constraint: CharSequence?, results: FilterResults?) {
+ if (results != null) {
+ filteredList = results.values as ArrayList
+ notifyDataSetChanged()
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/adapter/FragmentTransactionAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/FragmentTransactionAdapter.kt
new file mode 100644
index 0000000..a34d01c
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/adapter/FragmentTransactionAdapter.kt
@@ -0,0 +1,91 @@
+package com.mobit.mobit.adapter
+
+import android.graphics.Color
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.widget.LinearLayout
+import android.widget.TextView
+import androidx.recyclerview.widget.RecyclerView
+import com.mobit.mobit.R
+import com.mobit.mobit.data.OrderBook
+import java.text.DecimalFormat
+
+class FragmentTransactionAdapter(var items: ArrayList, val openPrice: Double) :
+ RecyclerView.Adapter() {
+
+ var listener: OnItemClickListener? = null
+
+ interface OnItemClickListener {
+ fun onItemClicked(view: View, price: Double)
+ }
+
+ inner class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
+ val linearLayout: LinearLayout
+ val price: TextView
+ val priceRate: TextView
+ val orderSize: TextView
+
+ init {
+ linearLayout = itemView.findViewById(R.id.linearLayout)
+ price = itemView.findViewById(R.id.price)
+ priceRate = itemView.findViewById(R.id.priceRate)
+ orderSize = itemView.findViewById(R.id.orderSize)
+
+ val clickListener: View.OnClickListener = object : View.OnClickListener {
+ override fun onClick(v: View?) {
+ if (v != null) {
+
+ }
+ }
+ }
+ price.setOnClickListener(clickListener)
+ priceRate.setOnClickListener(clickListener)
+ orderSize.setOnClickListener(clickListener)
+ }
+
+ }
+
+ override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
+ val v = LayoutInflater.from(parent.context)
+ .inflate(R.layout.recyclerview_transaction_item, parent, false)
+ return ViewHolder(v)
+ }
+
+ override fun onBindViewHolder(holder: ViewHolder, position: Int) {
+ val formatter = DecimalFormat("###,###")
+ val changeFormatter = DecimalFormat("###,###.###")
+
+ val price = items[position].price
+ val priceRate = (price - openPrice) / openPrice * 100
+ holder.price.text =
+ if (price > 100.0) formatter.format(price) else changeFormatter.format(price)
+ holder.priceRate.text = "${changeFormatter.format(priceRate)}%"
+ holder.orderSize.text = changeFormatter.format(items[position].size)
+
+ if (openPrice > price) {
+ holder.price.setTextColor(Color.parseColor("#135fc1"))
+ holder.priceRate.setTextColor(Color.parseColor("#135fc1"))
+ } else if (openPrice < price) {
+ holder.price.setTextColor(Color.parseColor("#bd4e3a"))
+ holder.priceRate.setTextColor(Color.parseColor("#bd4e3a"))
+ } else {
+ holder.price.setTextColor(Color.parseColor("#FFFFFF"))
+ holder.priceRate.setTextColor(Color.parseColor("#FFFFFF"))
+ }
+
+ if (position < 15) {
+ holder.price.setBackgroundColor(Color.parseColor("#081d3a"))
+ holder.priceRate.setBackgroundColor(Color.parseColor("#081d3a"))
+ holder.orderSize.setBackgroundColor(Color.parseColor("#081d3a"))
+ } else {
+ holder.price.setBackgroundColor(Color.parseColor("#241a23"))
+ holder.priceRate.setBackgroundColor(Color.parseColor("#241a23"))
+ holder.orderSize.setBackgroundColor(Color.parseColor("#241a23"))
+ }
+ }
+
+ override fun getItemCount(): Int {
+ return items.size
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/adapter/InvestmentStateAdapter.kt b/app/src/main/java/com/mobit/mobit/adapter/InvestmentStateAdapter.kt
new file mode 100644
index 0000000..8894c34
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/adapter/InvestmentStateAdapter.kt
@@ -0,0 +1,26 @@
+package com.mobit.mobit.adapter
+
+import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentActivity
+import androidx.viewpager2.adapter.FragmentStateAdapter
+import com.mobit.mobit.FragmentAsset
+import com.mobit.mobit.FragmentRecord
+
+class InvestmentStateAdapter(fragmentActivity: FragmentActivity) :
+ FragmentStateAdapter(fragmentActivity) {
+
+ val fragmentAsset: Fragment = FragmentAsset()
+ val fragmentRecord: Fragment = FragmentRecord()
+
+ override fun getItemCount(): Int {
+ return 2
+ }
+
+ override fun createFragment(position: Int): Fragment {
+ return when (position) {
+ 0 -> fragmentAsset
+ 1 -> fragmentRecord
+ else -> fragmentAsset
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/data/Asset.kt b/app/src/main/java/com/mobit/mobit/data/Asset.kt
new file mode 100644
index 0000000..c72cdfa
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/data/Asset.kt
@@ -0,0 +1,109 @@
+package com.mobit.mobit.data
+
+import android.util.Log
+import java.io.Serializable
+
+class Asset : Serializable {
+
+ constructor(krw: Double, coins: ArrayList) {
+ this.krw = krw
+ this.coins.addAll(coins)
+ }
+
+ var krw: Double = 0.0 // 보유 KRW 금액
+ val coins: ArrayList = ArrayList() // 보유 코인 자산
+
+ fun canBidCoin(code: String, name: String, price: Double, number: Double): Boolean {
+ val orderPrice = price * number
+
+ if (krw < orderPrice)
+ return false
+
+ return true
+ }
+
+ // code에 해당하는 코인을 price 가격으로 number개 만큼 매수한다.
+ // 매수한 코인의 인덱스를 리턴한다.
+ fun bidCoin(code: String, name: String, price: Double, number: Double): Int {
+ val orderPrice = price * number
+ if (krw < orderPrice)
+ return -1
+ krw -= orderPrice
+
+ var index: Int = -1
+ for (i in coins.indices) {
+ if (coins[i].code == code) {
+ index = i
+ break
+ }
+ }
+
+ var ret: Int = 0
+ if (index == -1) {
+ val newCoin = CoinAsset(code, name, number, number * price, price)
+ coins.add(newCoin)
+ ret = coins.indexOf(newCoin)
+ } else {
+ coins[index].averagePrice =
+ (coins[index].number * coins[index].averagePrice + orderPrice) / (coins[index].number + number)
+ coins[index].number += number
+ coins[index].amount += price * number
+ ret = index
+ }
+ Log.i("bidCoin in Asset", coins[ret].number.toString())
+ return ret
+ }
+
+ fun canAskCoin(code: String, price: Double, number: Double): Boolean {
+ var index: Int = -1
+ for (i in coins.indices) {
+ if (coins[i].code == code) {
+ index = i
+ break
+ }
+ }
+ if (index == -1)
+ return false
+
+ val coin = coins[index]
+ if (coin!!.number < number)
+ return false
+
+ return true
+ }
+
+ // code에 해당하는 코인을 price 가격으로 number개 만큼 매도한다.
+ // 매도한 코인의 coinAsset을 리턴한다.
+ fun askCoin(code: String, price: Double, number: Double): CoinAsset? {
+ var index: Int = -1
+ for (i in coins.indices) {
+ if (coins[i].code == code) {
+ index = i
+ break
+ }
+ }
+ if (index == -1)
+ return null
+
+ val coin = coins[index]
+ if (coin!!.number < number)
+ return null
+
+ val orderPrice = price * number
+ val fee = orderPrice * 0.0005
+ krw += (orderPrice - fee)
+
+ var ret: CoinAsset? = null
+ if (coin.number == number) {
+ coins.remove(coin)
+ coin.number = 0.0
+ ret = coin
+ } else {
+ coins[index].number -= number
+ coins[index].amount -= (price * number)
+ ret = coins[index]
+ }
+
+ return ret
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/data/Candle.kt b/app/src/main/java/com/mobit/mobit/data/Candle.kt
new file mode 100644
index 0000000..1673f3f
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/data/Candle.kt
@@ -0,0 +1,12 @@
+package com.mobit.mobit.data
+
+import java.io.Serializable
+
+data class Candle(
+ val createdAt: Long,
+ val open: Float,
+ val close: Float,
+ val shadowHigh: Float,
+ val shadowLow: Float,
+ val totalTradeVolume: Float
+) : Serializable
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/data/CoinAsset.kt b/app/src/main/java/com/mobit/mobit/data/CoinAsset.kt
new file mode 100644
index 0000000..0f370a5
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/data/CoinAsset.kt
@@ -0,0 +1,11 @@
+package com.mobit.mobit.data
+
+import java.io.Serializable
+
+data class CoinAsset(
+ val code: String, // 코인 코드
+ val name: String, // 코인 이름
+ var number: Double, // 코인 보유 개수
+ var amount: Double, // 코인 보유 금액
+ var averagePrice: Double, // 평균 단가
+) : Serializable
diff --git a/app/src/main/java/com/mobit/mobit/data/CoinInfo.kt b/app/src/main/java/com/mobit/mobit/data/CoinInfo.kt
new file mode 100644
index 0000000..ba85a39
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/data/CoinInfo.kt
@@ -0,0 +1,52 @@
+package com.mobit.mobit.data
+
+import java.io.Serializable
+
+data class CoinInfo(
+ val code: String, // 코인 코드
+ val name: String, // 코인 이름
+ val price: Price // 코인 현재가 정보
+) : Serializable {
+ companion object {
+ val BTC_CODE = "KRW-BTC" // 비트코인
+ val BTC_NAME = "비트코인"
+ val ETH_CODE = "KRW-ETH" // 이더리움
+ val ETH_NAME = "이더리움"
+ val ADA_CODE = "KRW-ADA" // 에이다
+ val ADA_NAME = "에이다"
+ val DOGE_CODE = "KRW-DOGE" // 도지코인
+ val DOGE_NAME = "도지코인"
+ val XRP_CODE = "KRW-XRP" // 리플
+ val XRP_NAME = "리플"
+ val DOT_CODE = "KRW-DOT" // 폴카닷
+ val DOT_NAME = "폴카닷"
+ val BCH_CODE = "KRW-BCH" // 비트코인캐시
+ val BCH_NAME = "비트코인캐시"
+ val LTC_CODE = "KRW-LTC" // 라이트코인
+ val LTC_NAME = "라이트코인"
+ val LINK_CODE = "KRW-LINK" // 체인링크
+ val LINK_NAME = "체인링크"
+ val ETC_CODE = "KRW-ETC" // 이더리움클래식
+ val ETC_NAME = "이더리움클래식"
+ val THETA_CODE = "KRW-THETA" // 쎄타토큰
+ val THETA_NAME = "쎄타토큰"
+ val XLM_CODE = "KRW-XLM" // 스텔라루멘
+ val XLM_NAME = "스텔라루멘"
+ val VET_CODE = "KRW-VET" // 비체인
+ val VET_NAME = "비체인"
+ val EOS_CODE = "KRW-EOS" // 이오스
+ val EOS_NAME = "이오스"
+ val TRX_CODE = "KRW-TRX" // 트론
+ val TRX_NAME = "트론"
+ val NEO_CODE = "KRW-NEO" // 네오
+ val NEO_NAME = "네오"
+ val IOTA_CODE = "KRW-IOTA" // 아이오타
+ val IOTA_NAME = "아이오타"
+ val ATOM_CODE = "KRW-ATOM" // 코스모스
+ val ATOM_NAME = "코스모스"
+ val BSV_CODE = "KRW-BSV" // 비트코인에스브이
+ val BSV_NAME = "비트코인에스브이"
+ val BTT_CODE = "KRW-BTT" // 비트토렌트
+ val BTT_NAME = "비트토렌트"
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/data/MyViewModel.kt b/app/src/main/java/com/mobit/mobit/data/MyViewModel.kt
new file mode 100644
index 0000000..a104ef4
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/data/MyViewModel.kt
@@ -0,0 +1,107 @@
+package com.mobit.mobit.data
+
+import android.util.Log
+import androidx.lifecycle.MutableLiveData
+import androidx.lifecycle.ViewModel
+import com.mobit.mobit.db.MyDBHelper
+
+class MyViewModel : ViewModel() {
+
+ // FragmentCoinList에서 선택한 코인을 다른 Fragment에서 참고할 때 사용하는 변수
+ val selectedCoin: MutableLiveData = MutableLiveData()
+
+ // 실시간으로 얻어온 코인 정보를 저장할 변수
+ val coinInfo: MutableLiveData> = MutableLiveData()
+
+ // 사용자가 즐겨찾기에 추가한 코인 정보를 저장할 변수
+ val favoriteCoinInfo: MutableLiveData> = MutableLiveData()
+
+ // 실시간으로 얻어온 호가 정보를 저장할 변수
+ val orderBook: MutableLiveData> = MutableLiveData()
+
+ // 사용자가 보유중인 자산 정보를 저장할 변수
+ val asset: MutableLiveData = MutableLiveData()
+
+ // 사용자가 매수 또는 매도를 진행할 때마다, 거래 내역을 저장할 변수
+ val transaction: MutableLiveData> = MutableLiveData()
+
+ // DB에 데이터를 저장하기 위한 변수
+ var myDBHelper: MyDBHelper? = null
+
+ fun setSelectedCoin(selectedCoin: String) {
+ this.selectedCoin.value = selectedCoin
+ }
+
+ fun setCoinInfo(coinInfo: ArrayList) {
+ this.coinInfo.value = coinInfo
+ }
+
+ fun setFavoriteCoinInfo(favoriteCoinInfo: ArrayList) {
+ this.favoriteCoinInfo.value = favoriteCoinInfo
+ }
+
+ fun setOrderBook(orderBook: ArrayList) {
+ this.orderBook.value = orderBook
+ }
+
+ fun setAsset(asset: Asset) {
+ this.asset.value = asset
+ }
+
+ fun setTransaction(transaction: ArrayList) {
+ this.transaction.value = transaction
+ }
+
+ fun addTransaction(transaction: Transaction) {
+ val temp = ArrayList()
+ temp.add(transaction)
+ temp.addAll(this.transaction.value!!)
+ this.transaction.value = temp
+ }
+
+ fun addFavoriteCoinInfo(coinInfo: CoinInfo): Boolean {
+ if (favoriteCoinInfo.value == null)
+ return false
+
+ if (favoriteCoinInfo.value!!.contains(coinInfo))
+ return false
+
+ val temp = ArrayList()
+ temp.addAll(favoriteCoinInfo.value!!)
+ temp.add(coinInfo)
+ this.favoriteCoinInfo.value = temp
+
+ return true
+ }
+
+ fun removeFavoriteCoinInfo(coinInfo: CoinInfo): Boolean {
+ if (favoriteCoinInfo.value == null)
+ return false
+
+ if (!favoriteCoinInfo.value!!.contains(coinInfo))
+ return false
+
+ val temp = ArrayList()
+ temp.addAll(favoriteCoinInfo.value!!)
+ temp.remove(coinInfo)
+ favoriteCoinInfo.value = temp
+
+ return true
+ }
+
+ fun bidCoin(code: String, name: String, price: Double, number: Double): Int {
+ val temp = Asset(this.asset.value!!.krw, this.asset.value!!.coins)
+ val ret = temp.bidCoin(code, name, price, number)
+ Log.i("FragmentBuy before setValue", temp.coins[ret].number.toString())
+ this.asset.value = temp
+ Log.i("FragmentBuy after setValue", this.asset.value!!.coins[ret].number.toString())
+ return ret
+ }
+
+ fun askCoin(code: String, price: Double, number: Double): CoinAsset? {
+ val temp = Asset(this.asset.value!!.krw, this.asset.value!!.coins)
+ val ret = temp.askCoin(code, price, number)
+ this.asset.value = temp
+ return ret
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/data/OrderBook.kt b/app/src/main/java/com/mobit/mobit/data/OrderBook.kt
new file mode 100644
index 0000000..31a9ce6
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/data/OrderBook.kt
@@ -0,0 +1,12 @@
+package com.mobit.mobit.data
+
+import java.io.Serializable
+
+data class OrderBook(val price: Double, val size: Double) : Serializable, Comparable {
+ override fun compareTo(other: OrderBook): Int {
+ return when (this.price > other.price) {
+ true -> 1
+ false -> -1
+ }
+ }
+}
diff --git a/app/src/main/java/com/mobit/mobit/data/Price.kt b/app/src/main/java/com/mobit/mobit/data/Price.kt
new file mode 100644
index 0000000..4d9fc0d
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/data/Price.kt
@@ -0,0 +1,39 @@
+package com.mobit.mobit.data
+
+import java.io.Serializable
+
+/*
+openPrice : 시가
+highPrice : 고가
+lowPrice : 저가
+endPrice : 종가
+prevEndPrice : 전일 종가
+change : { ("EVEN", 보합), ("RISE", 상승), ("FALL", 하락) }
+changePrice : 부호가 있는 변화액
+changeRate : 부호가 있는 변화율
+totalTradeVolume : 누적 거래량(UTC 0시 기준)
+totalTradePrice : 누적 거래대금(UTC 0시 기준)
+totalTradePrice24: 24시간 누적 거래대금
+highestWeekPrice : 52주 신고가
+highestWeekDate: 52주 신고가 달성일 "yyyy-MM-dd"
+lowestWeekPrice : 52주 신저가
+lowestWeekDate: 52주 신저가 달성일 "yyyy-MM-dd"
+ */
+data class Price(
+ var realTimePrice: Double,
+ var openPrice: Double,
+ var highPrice: Double,
+ var lowPrice: Double,
+ var endPrice: Double,
+ var prevEndPrice: Double,
+ var change: String,
+ var changePrice: Double,
+ var changeRate: Double,
+ var totalTradeVolume: Double,
+ var totalTradePrice: Double,
+ var totalTradePrice24: Double,
+ var highestWeekPrice: Double,
+ var highestWeekDate: String,
+ var lowestWeekPrice: Double,
+ var lowestWeekDate: String
+) : Serializable
diff --git a/app/src/main/java/com/mobit/mobit/data/Transaction.kt b/app/src/main/java/com/mobit/mobit/data/Transaction.kt
new file mode 100644
index 0000000..5dba47e
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/data/Transaction.kt
@@ -0,0 +1,18 @@
+package com.mobit.mobit.data
+
+data class Transaction(
+ val code: String, // 코인 코드
+ val name: String, // 코인 이름
+ val time: String, // 체결시간 "yyyy-MM-ddThh:mm:ss" 형태로 저장함 (https://developer.android.com/reference/java/time/format/DateTimeFormatter#ISO_LOCAL_DATE_TIME)
+ val type: Int, // 매수 매도 여부
+ val quantity: Double, // 거래 수량
+ val unitPrice: Double, // 거래 단가
+ val tradePrice: Double, // 거래 금액
+ val fee: Double, // 수수료
+ val totalPrice: Double // 정산 금액
+) {
+ companion object {
+ const val BID = 100 // 매수
+ const val ASK = 200 // 매도
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/db/MyDBHelper.kt b/app/src/main/java/com/mobit/mobit/db/MyDBHelper.kt
new file mode 100644
index 0000000..3c3debf
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/db/MyDBHelper.kt
@@ -0,0 +1,344 @@
+package com.mobit.mobit.db
+
+import android.content.ContentValues
+import android.content.Context
+import android.database.sqlite.SQLiteDatabase
+import android.database.sqlite.SQLiteOpenHelper
+import android.util.Log
+import com.mobit.mobit.data.CoinAsset
+import com.mobit.mobit.data.Transaction
+import org.json.JSONObject
+
+class MyDBHelper(val context: Context) : SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
+ companion object {
+ val DB_NAME = "mobit.db"
+ val DB_VERSION = 1
+ val TABLE_NAME = arrayOf("favorite", "krw", "coinAsset", "trade", "firstFlag")
+
+ // about favorite
+ val CODE = "code"
+
+ // about krw
+ val KRW = "krw"
+
+ // about coinAsset
+ val NAME = "name"
+ val NUMBER = "number"
+ val AMOUNT = "amount"
+ val AVERAGE_PRICE = "averagePrice"
+
+ // about transaction
+ val TRANSACTION = "trade"
+
+ // about flag
+ val FIRST_SETTING = "firstSetting"
+ }
+
+ // 관심 코인을 DB에 저장
+ fun insertFavoirte(code: String): Boolean {
+ val values = ContentValues()
+ values.put(CODE, code)
+
+ val db = writableDatabase
+ val ret = db.insert(TABLE_NAME[0], null, values) > 0
+ db.close()
+ return ret
+ }
+
+ // 관심 코인을 DB로부터 제거
+ fun deleteFavorite(code: String): Boolean {
+ val strsql = "select * from ${TABLE_NAME[0]} where $CODE='$code';"
+ val db = readableDatabase
+ val cursor = db.rawQuery(strsql, null)
+ val flag = cursor.count != 0
+ if (flag) {
+ cursor.moveToFirst()
+ db.delete(TABLE_NAME[0], "$CODE=?", arrayOf(code))
+ }
+ cursor.close()
+ db.close()
+ return flag
+ }
+
+ // DB에 저장되어 있는 관심 코인들의 code를 반환
+ fun getFavorites(): ArrayList {
+ val ret = ArrayList()
+
+ val strsql = "select * from ${TABLE_NAME[0]};"
+ val db = readableDatabase
+ val cursor = db.rawQuery(strsql, null)
+ cursor.moveToFirst()
+ if (cursor.count != 0) {
+ do {
+ val code = cursor.getString(0)
+ ret.add(code)
+ } while (cursor.moveToNext())
+ }
+ cursor.close()
+ db.close()
+
+ return ret
+ }
+
+ // KRW 테이블에 저장되어 있는 데이터를 krw로 변경
+ fun setKRW(krw: Double): Boolean {
+ clearKRW()
+ return insertKRW(krw)
+ }
+
+ // KRW 테이블에 krw 데이터 추가
+ fun insertKRW(krw: Double): Boolean {
+ val values = ContentValues()
+ values.put(KRW, krw)
+
+ val db = writableDatabase
+ val ret = db.insert(TABLE_NAME[1], null, values) > 0
+ db.close()
+ return ret
+ }
+
+ // KRW 테이블의 데이터를 모두 제거
+ fun clearKRW() {
+ val strsql = "delete from ${TABLE_NAME[1]}"
+ val db = writableDatabase
+ db.execSQL(strsql)
+ }
+
+ // DB에 저장되어 있는 KRW 값을 반환
+ fun getKRW(): Double? {
+ var ret: Double? = null
+
+ val strsql = "select * from ${TABLE_NAME[1]};"
+ val db = readableDatabase
+ val cursor = db.rawQuery(strsql, null)
+ cursor.moveToFirst()
+ if (cursor.count != 0) {
+ ret = cursor.getDouble(0)
+ }
+ cursor.close()
+ db.close()
+ return ret
+ }
+
+ // DB에 coinAsset을 저장
+ fun insertCoinAsset(coinAsset: CoinAsset): Boolean {
+ val values = ContentValues()
+ values.put(CODE, coinAsset.code)
+ values.put(NAME, coinAsset.name)
+ values.put(NUMBER, coinAsset.number)
+ values.put(AMOUNT, coinAsset.amount)
+ values.put(AVERAGE_PRICE, coinAsset.averagePrice)
+
+ val db = writableDatabase
+ val ret = db.insert(TABLE_NAME[2], null, values) > 0
+ db.close()
+ return ret
+ }
+
+ // DB에 있는 코인 자산 데이터 중에서 coinAsset에 해당하는 데이터를 삭제
+ fun deleteCoinAsset(coinAsset: CoinAsset): Boolean {
+ val strsql = "select * from ${TABLE_NAME[2]} where $CODE='${coinAsset.code}';"
+ val db = readableDatabase
+ val cursor = db.rawQuery(strsql, null)
+ val flag = cursor.count != 0
+ if (flag) {
+ cursor.moveToFirst()
+ db.delete(TABLE_NAME[2], "$CODE=?", arrayOf(coinAsset.code))
+ }
+ cursor.close()
+ db.close()
+ return flag
+ }
+
+ // DB에 있는 코인 자산 데이터를 업데이트
+ fun updateCoinAsset(coinAsset: CoinAsset): Boolean {
+ val strsql = "select * from ${TABLE_NAME[2]} where $CODE='${coinAsset.code}';"
+ val db = writableDatabase
+ val cursor = db.rawQuery(strsql, null)
+ val flag = cursor.count != 0
+ if (flag) {
+ cursor.moveToFirst()
+ val values = ContentValues()
+ values.put(NUMBER, coinAsset.number)
+ values.put(AMOUNT, coinAsset.amount)
+ values.put(AVERAGE_PRICE, coinAsset.averagePrice)
+ db.update(TABLE_NAME[2], values, "$CODE=?", arrayOf(coinAsset.code))
+ }
+ cursor.close()
+ db.close()
+ return flag
+ }
+
+ // DB에 저장되어 있는 코인 자산 정보를 반환
+ fun getCoinAssets(): ArrayList {
+ val ret = ArrayList()
+
+ val strsql = "select * from ${TABLE_NAME[2]};"
+ val db = readableDatabase
+ val cursor = db.rawQuery(strsql, null)
+ cursor.moveToFirst()
+ if (cursor.count != 0) {
+ do {
+ val code = cursor.getString(0)
+ val name = cursor.getString(1)
+ val number = cursor.getDouble(2)
+ val amount = cursor.getDouble(3)
+ val averagePrice = cursor.getDouble(4)
+ ret.add(CoinAsset(code, name, number, amount, averagePrice))
+ } while (cursor.moveToNext())
+ }
+ cursor.close()
+ db.close()
+
+ return ret
+ }
+
+ fun findCoinAsset(code: String): Boolean {
+ val strsql = "select * from ${TABLE_NAME[2]} where $CODE='$code';"
+ val db = readableDatabase
+ val cursor = db.rawQuery(strsql, null)
+ val ret = cursor.count != 0
+ cursor.close()
+ db.close()
+ return ret
+ }
+
+ // DB에 trasaction을 JSONObject 형태의 string으로 저장
+ fun insertTransaction(transaction: Transaction): Boolean {
+ val values = ContentValues()
+ val json: String = createJSONObject(transaction)
+ values.put(TRANSACTION, json)
+
+ val db = writableDatabase
+ val ret = db.insert(TABLE_NAME[3], null, values) > 0
+ db.close()
+ return ret
+ }
+
+ // DB에 저장되어 있는 거래내역 정보를 반환
+ fun getTransactions(): ArrayList {
+ val ret = ArrayList()
+
+ val strsql = "select * from ${TABLE_NAME[3]};"
+ val db = readableDatabase
+ val cursor = db.rawQuery(strsql, null)
+ cursor.moveToFirst()
+ if (cursor.count != 0) {
+ do {
+ val jsonObject = cursor.getString(0)
+ val transaction = createTransactionFromJSONObject(jsonObject)
+ ret.add(transaction)
+ } while (cursor.moveToNext())
+ }
+ cursor.close()
+ db.close()
+
+ ret.reverse()
+ return ret
+ }
+
+ // Transaction 객체 하나를 JSONObject 형태로 바꾸고, 이를 String 타입으로 리턴한다.
+ fun createJSONObject(transaction: Transaction): String {
+ val jsonObject: JSONObject = JSONObject()
+ jsonObject.put("code", transaction.code)
+ jsonObject.put("name", transaction.name)
+ jsonObject.put("time", transaction.time)
+ jsonObject.put("type", transaction.type)
+ jsonObject.put("quantity", transaction.quantity)
+ jsonObject.put("unitPrice", transaction.unitPrice)
+ jsonObject.put("tradePrice", transaction.tradePrice)
+ jsonObject.put("fee", transaction.fee)
+ jsonObject.put("totalPrice", transaction.totalPrice)
+ return jsonObject.toString()
+ }
+
+ fun createTransactionFromJSONObject(transaction: String): Transaction {
+ val jsonObject: JSONObject = JSONObject(transaction)
+ val code = jsonObject.getString("code")
+ val name = jsonObject.getString("name")
+ val time = jsonObject.getString("time")
+ val type = jsonObject.getInt("type")
+ val quantity = jsonObject.getDouble("quantity")
+ val unitPrice = jsonObject.getDouble("unitPrice")
+ val tradePrice = jsonObject.getDouble("tradePrice")
+ val fee = jsonObject.getDouble("fee")
+ val totalPrice = jsonObject.getDouble("totalPrice")
+ return Transaction(code, name, time, type, quantity, unitPrice, tradePrice, fee, totalPrice)
+ }
+
+ fun setFlag(flag: Boolean): Boolean {
+ val values = ContentValues()
+ val num = if (flag) 1 else 0
+ values.put(FIRST_SETTING, num)
+
+ val db = writableDatabase
+ val ret = db.insert(TABLE_NAME[4], null, values) > 0
+ db.close()
+ return ret
+ }
+
+ fun getFlag(): Boolean {
+ var ret = false
+
+ val strsql = "select * from ${TABLE_NAME[4]};"
+ val db = readableDatabase
+ val cursor = db.rawQuery(strsql, null)
+ cursor.moveToFirst()
+ if (cursor.count != 0) {
+ val num = cursor.getInt(0)
+ Log.i("getFlag num", num.toString())
+ ret = if (num == 1) true else false
+ }
+ cursor.close()
+ db.close()
+
+ return ret
+ }
+
+ fun clearDB(): Boolean {
+ val strsql1 = "delete from ${TABLE_NAME[0]}"
+ val strsql2 = "delete from ${TABLE_NAME[1]}"
+ val strsql3 = "delete from ${TABLE_NAME[2]}"
+ val strsql4 = "delete from ${TABLE_NAME[3]}"
+ val strsql5 = "delete from ${TABLE_NAME[4]}"
+
+ val db = writableDatabase
+ db.execSQL(strsql1)
+ db.execSQL(strsql2)
+ db.execSQL(strsql3)
+ db.execSQL(strsql4)
+ db.execSQL(strsql5)
+
+ return true
+ }
+
+ override fun onCreate(db: SQLiteDatabase?) {
+ val createTable1 = "create table if not exists ${TABLE_NAME[0]}($CODE text primary key)"
+ val createTable2 =
+ "create table if not exists ${TABLE_NAME[1]}($KRW real)"
+ val createTable3 =
+ "create table if not exists ${TABLE_NAME[2]}($CODE text primary key, $NAME text, $NUMBER real, $AMOUNT real, $AVERAGE_PRICE real)"
+ val createTable4 = "create table if not exists ${TABLE_NAME[3]}($TRANSACTION text)"
+ val createTable5 = "create table if not exists ${TABLE_NAME[4]}($FIRST_SETTING INTEGER)"
+ db?.execSQL(createTable1)
+ db?.execSQL(createTable2)
+ db?.execSQL(createTable3)
+ db?.execSQL(createTable4)
+ db?.execSQL(createTable5)
+ }
+
+ override fun onUpgrade(db: SQLiteDatabase?, oldVersion: Int, newVersion: Int) {
+ val dropTable1 = "drop table if exists ${TABLE_NAME[0]}"
+ val dropTable2 = "drop table if exists ${TABLE_NAME[1]}"
+ val dropTable3 = "drop table if exists ${TABLE_NAME[2]}"
+ val dropTable4 = "drop table if exists ${TABLE_NAME[3]}"
+ val dropTable5 = "drop table if exists ${TABLE_NAME[4]}"
+ db?.execSQL(dropTable1)
+ db?.execSQL(dropTable2)
+ db?.execSQL(dropTable3)
+ db?.execSQL(dropTable4)
+ db?.execSQL(dropTable5)
+ onCreate(db)
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/network/UpbitAPICaller.kt b/app/src/main/java/com/mobit/mobit/network/UpbitAPICaller.kt
new file mode 100644
index 0000000..bd650ad
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/network/UpbitAPICaller.kt
@@ -0,0 +1,249 @@
+package com.mobit.mobit.network
+
+import android.util.Log
+import com.mobit.mobit.data.Candle
+import com.mobit.mobit.data.OrderBook
+import com.mobit.mobit.data.Price
+import org.json.JSONArray
+import java.io.BufferedReader
+import java.io.InputStreamReader
+import java.net.HttpURLConnection
+import java.net.URL
+
+
+/*
+Upbit open API를 사용해서 데이터를 얻어오는 작업을 구현할 클래스입니다.
+*/
+class UpbitAPICaller {
+
+ companion object {
+ val TICK_URL = "https://api.upbit.com/v1/trades/ticks"
+ val TICKER_URL = "https://api.upbit.com/v1/ticker"
+ val ORDERBOOK_URL = "https://api.upbit.com/v1/orderbook"
+ val CANDLE_MINUTE_URL = "https://api.upbit.com/v1/candles/minutes"
+ val CANDLE_DAY_URL = "https://api.upbit.com/v1/candles/days"
+ val CANDLE_WEEK_URL = "https://api.upbit.com/v1/candles/weeks"
+ val CANDLE_MONTH_URL = "https://api.upbit.com/v1/candles/months"
+ }
+
+ fun connect(connUrl: String): String {
+ var ret = ""
+ try {
+ val url = URL(connUrl)
+ val con = url.openConnection() as HttpURLConnection
+ con.requestMethod = "GET"
+ con.setRequestProperty("Accept", "application/json")
+
+ val responseCode = con.responseCode
+ val br: BufferedReader
+ if (responseCode == 200) {
+ br = BufferedReader(InputStreamReader(con.inputStream))
+ } else {
+ br = BufferedReader(InputStreamReader(con.errorStream))
+ }
+ var inputLine: String?
+ val response = StringBuffer()
+ while (br.readLine().also { inputLine = it } != null) {
+ response.append(inputLine)
+ }
+ br.close()
+ ret += response.toString()
+ } catch (e: Exception) {
+ Log.e("connect Error", e.toString())
+ return ret
+ }
+
+ return ret
+ }
+
+ // code에 해당하는 코인 현재가 정보를 가져오는 함수
+ fun getTicker(codes: ArrayList): ArrayList {
+ val ret = ArrayList()
+ var markets = "?markets="
+ for (i in codes.indices) {
+ markets += when (i) {
+ codes.size - 1 -> codes[i]
+ else -> "${codes[i]}, "
+ }
+ }
+ if (markets == "?markets=")
+ return ret
+
+ val url = TICKER_URL + markets
+ val text = connect(url)
+ if (text.isBlank())
+ return ret
+
+ val jsonArray: JSONArray = JSONArray(text)
+ for (i in 0..jsonArray.length() - 1) {
+ val jsonObject = jsonArray.getJSONObject(i)
+ val openPrice = jsonObject.getDouble("opening_price")
+ val highPrice = jsonObject.getDouble("high_price")
+ val lowPrice = jsonObject.getDouble("low_price")
+ val endPrice = jsonObject.getDouble("trade_price")
+ val prevEndPrice = jsonObject.getDouble("prev_closing_price")
+ val change = jsonObject.getString("change")
+ val changePrice = jsonObject.getDouble("signed_change_price")
+ val changeRate = jsonObject.getDouble("signed_change_rate")
+ val totalTradeVolume = jsonObject.getDouble("acc_trade_volume")
+ val totalTradePrice = jsonObject.getDouble("acc_trade_price")
+ val totalTradePrice24 = jsonObject.getDouble("acc_trade_price_24h")
+ val highestWeekPrice = jsonObject.getDouble("highest_52_week_price")
+ val highestWeekDate = jsonObject.getString("highest_52_week_date")
+ val lowestWeekPrice = jsonObject.getDouble("lowest_52_week_price")
+ val lowestWeekDate = jsonObject.getString("lowest_52_week_date")
+ val price = Price(
+ endPrice,
+ openPrice,
+ highPrice,
+ lowPrice,
+ endPrice,
+ prevEndPrice,
+ change,
+ changePrice,
+ changeRate,
+ totalTradeVolume,
+ totalTradePrice,
+ totalTradePrice24,
+ highestWeekPrice,
+ highestWeekDate,
+ lowestWeekPrice,
+ lowestWeekDate
+ )
+ ret.add(price)
+ }
+
+ return ret
+ }
+
+ // code에 해당하는 코인의 호가 정보를 가져오는 함수
+ fun getOrderbook(code: String): ArrayList {
+ val ret = ArrayList()
+
+ val markets = "?markets=$code"
+ val url = ORDERBOOK_URL + markets
+ val text = connect(url)
+ if (text.isBlank())
+ return ret
+
+ val orderbooks = JSONArray(text)
+ val jsonObject = orderbooks!!.getJSONObject(0)
+ val market = jsonObject.getString("market")
+ val orderbook = jsonObject.getJSONArray("orderbook_units")
+ val ask = ArrayList()
+ val bid = ArrayList()
+ // ask(매도)는 가격이 오름차순으로 정렬되어 있고,
+ // bid(매수)는 가격이 내림차순으로 정렬되어 있다.
+ for (j in 0..orderbook.length() - 1) {
+ val temp = orderbook.getJSONObject(j)
+ ask.add(OrderBook(temp.getDouble("ask_price"), temp.getDouble("ask_size")))
+ bid.add(OrderBook(temp.getDouble("bid_price"), temp.getDouble("bid_size")))
+ }
+ ask.reverse()
+ ret.addAll(ask)
+ ret.addAll(bid)
+
+ return ret
+ }
+
+ // code에 해당하는 코인의 unit분 단위의 캔들 차트 정보를 가져오는 함수
+ fun getCandleMinute(code: String, unit: Int): ArrayList {
+ val ret = ArrayList()
+
+ val query = "/$unit?market=$code&count=200"
+ val url = CANDLE_MINUTE_URL + query
+ val text = connect(url)
+ if (text.isBlank())
+ return ret
+
+ val candles = JSONArray(text)
+ for (i in 1..candles.length()) {
+ val candle = candles.getJSONObject(candles.length() - i)
+ val createdAt = i.toLong()
+ val open = candle.getDouble("opening_price").toFloat()
+ val close = candle.getDouble("trade_price").toFloat()
+ val shadowHigh = candle.getDouble("high_price").toFloat()
+ val shadowLow = candle.getDouble("low_price").toFloat()
+ val totalTradeVolume = candle.getDouble("candle_acc_trade_volume").toFloat()
+ ret.add(Candle(createdAt, open, close, shadowHigh, shadowLow, totalTradeVolume))
+ }
+
+ return ret
+ }
+
+ // code에 해당하는 코인의 일 단위의 캔들 차트 정보를 가져오는 함수
+ fun getCandleDay(code: String): ArrayList {
+ val ret = ArrayList()
+
+ val query = "?market=$code&count=200"
+ val url = CANDLE_DAY_URL + query
+ val text = connect(url)
+ if (text.isBlank())
+ return ret
+
+ val candles = JSONArray(text)
+ for (i in 1..candles.length()) {
+ val candle = candles.getJSONObject(candles.length() - i)
+ val createdAt = i.toLong()
+ val open = candle.getDouble("opening_price").toFloat()
+ val close = candle.getDouble("trade_price").toFloat()
+ val shadowHigh = candle.getDouble("high_price").toFloat()
+ val shadowLow = candle.getDouble("low_price").toFloat()
+ val totalTradeVolume = candle.getDouble("candle_acc_trade_volume").toFloat()
+ ret.add(Candle(createdAt, open, close, shadowHigh, shadowLow, totalTradeVolume))
+ }
+
+ return ret
+ }
+
+ // code에 해당하는 코인의 주 단위의 캔들 차트 정보를 가져오는 함수
+ fun getCandleWeek(code: String): ArrayList {
+ val ret = ArrayList()
+
+ val query = "?market=$code&count=200"
+ val url = CANDLE_WEEK_URL + query
+ val text = connect(url)
+ if (text.isBlank())
+ return ret
+
+ val candles = JSONArray(text)
+ for (i in 1..candles.length()) {
+ val candle = candles.getJSONObject(candles.length() - i)
+ val createdAt = i.toLong()
+ val open = candle.getDouble("opening_price").toFloat()
+ val close = candle.getDouble("trade_price").toFloat()
+ val shadowHigh = candle.getDouble("high_price").toFloat()
+ val shadowLow = candle.getDouble("low_price").toFloat()
+ val totalTradeVolume = candle.getDouble("candle_acc_trade_volume").toFloat()
+ ret.add(Candle(createdAt, open, close, shadowHigh, shadowLow, totalTradeVolume))
+ }
+
+ return ret
+ }
+
+ // code에 해당하는 코인의 월 단위의 캔들 차트 정보를 가져오는 함수
+ fun getCandleMonth(code: String): ArrayList {
+ val ret = ArrayList()
+
+ val query = "?market=$code&count=200"
+ val url = CANDLE_MONTH_URL + query
+ val text = connect(url)
+ if (text.isBlank())
+ return ret
+
+ val candles = JSONArray(text)
+ for (i in 1..candles.length()) {
+ val candle = candles.getJSONObject(candles.length() - i)
+ val createdAt = i.toLong()
+ val open = candle.getDouble("opening_price").toFloat()
+ val close = candle.getDouble("trade_price").toFloat()
+ val shadowHigh = candle.getDouble("high_price").toFloat()
+ val shadowLow = candle.getDouble("low_price").toFloat()
+ val totalTradeVolume = candle.getDouble("candle_acc_trade_volume").toFloat()
+ ret.add(Candle(createdAt, open, close, shadowHigh, shadowLow, totalTradeVolume))
+ }
+
+ return ret
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/com/mobit/mobit/service/UpbitAPIService.kt b/app/src/main/java/com/mobit/mobit/service/UpbitAPIService.kt
new file mode 100644
index 0000000..7b849e9
--- /dev/null
+++ b/app/src/main/java/com/mobit/mobit/service/UpbitAPIService.kt
@@ -0,0 +1,233 @@
+package com.mobit.mobit.service
+
+import android.app.Service
+import android.content.BroadcastReceiver
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.os.IBinder
+import android.util.Log
+import com.mobit.mobit.data.CoinInfo
+import com.mobit.mobit.network.UpbitAPICaller
+
+class UpbitAPIService : Service() {
+
+ lateinit var selectedCoin: String
+ var favoriteCoinInfo: ArrayList = ArrayList()
+
+ val upbitAPICaller: UpbitAPICaller = UpbitAPICaller()
+
+ // 코인 정보 가져오는 쓰레드
+ lateinit var upbitAPIThread: UpbitAPIThread
+
+ // 코인 호가 정보 가져오는 쓰레드
+ lateinit var upbitAPIThread2: UpbitAPIThread
+
+ val codes: ArrayList = arrayListOf(
+ CoinInfo.BTC_CODE,
+ CoinInfo.ETH_CODE,
+ CoinInfo.ADA_CODE,
+ CoinInfo.DOGE_CODE,
+ CoinInfo.XRP_CODE,
+ CoinInfo.DOT_CODE,
+ CoinInfo.BCH_CODE,
+ CoinInfo.LTC_CODE,
+ CoinInfo.LINK_CODE,
+ CoinInfo.ETC_CODE,
+ CoinInfo.THETA_CODE,
+ CoinInfo.XLM_CODE,
+ CoinInfo.VET_CODE,
+ CoinInfo.EOS_CODE,
+ CoinInfo.TRX_CODE,
+ CoinInfo.NEO_CODE,
+ CoinInfo.IOTA_CODE,
+ CoinInfo.ATOM_CODE,
+ CoinInfo.BSV_CODE,
+ CoinInfo.BTT_CODE
+ )
+
+ var receiver = object : BroadcastReceiver() {
+ override fun onReceive(context: Context?, intent: Intent?) {
+ APIControl(intent)
+ }
+ }
+
+ override fun onBind(intent: Intent): IBinder? {
+ return null
+ }
+
+ override fun onCreate() {
+ super.onCreate()
+ registerReceiver(receiver, IntentFilter("com.mobit.APICALL"))
+
+ upbitAPIThread = UpbitAPIThread(100, codes)
+ upbitAPIThread2 = UpbitAPIThread(200, codes)
+ }
+
+ override fun onDestroy() {
+ super.onDestroy()
+ unregisterReceiver(receiver)
+ }
+
+ fun APIControl(intent: Intent?) {
+ if (intent != null) {
+ val mode = intent.getStringExtra("mode")
+ when (mode) {
+ "INITIAL_SETTING" -> {
+ selectedCoin = intent.getStringExtra("selectedCoin")
+ favoriteCoinInfo.clear()
+ favoriteCoinInfo.addAll(intent.getSerializableExtra("favoriteCoinInfo") as ArrayList)
+ }
+ "SELECTED_COIN_SETTING" -> {
+ selectedCoin = intent.getStringExtra("selectedCoin")
+ }
+ "FAVORITE_COININFO_SETTING" -> {
+ favoriteCoinInfo.clear()
+ favoriteCoinInfo.addAll(intent.getSerializableExtra("favoriteCoinInfo") as ArrayList)
+ }
+ "START" -> {
+ val thread: Thread = object : Thread() {
+ override fun run() {
+ if (upbitAPIThread.isAlive) {
+ try {
+ upbitAPIThread.join()
+ upbitAPIThread2.join()
+ } catch (e: InterruptedException) {
+ Log.e("OnRestart Error", e.toString())
+ }
+ }
+ upbitAPIThread = UpbitAPIThread(100, codes)
+ upbitAPIThread.start()
+ upbitAPIThread2 = UpbitAPIThread(200, codes)
+ upbitAPIThread2.start()
+ }
+ }
+ thread.start()
+ }
+ "START_THREAD1" -> {
+ val thread: Thread = object : Thread() {
+ override fun run() {
+ if (upbitAPIThread.isAlive) {
+ try {
+ upbitAPIThread.join()
+ } catch (e: InterruptedException) {
+ Log.e("OnRestart Error", e.toString())
+ }
+ }
+ upbitAPIThread = UpbitAPIThread(100, codes)
+ upbitAPIThread.start()
+ }
+ }
+ thread.start()
+ }
+ "START_THREAD2" -> {
+ val thread: Thread = object : Thread() {
+ override fun run() {
+ if (upbitAPIThread2.isAlive) {
+ try {
+ upbitAPIThread2.join()
+ } catch (e: InterruptedException) {
+ Log.e("OnRestart Error", e.toString())
+ }
+ }
+ upbitAPIThread2 = UpbitAPIThread(200, codes)
+ upbitAPIThread2.start()
+ }
+ }
+ thread.start()
+ }
+ "STOP" -> {
+ upbitAPIThread.threadStop(true)
+ upbitAPIThread2.threadStop(true)
+ }
+ "STOP_THREAD1" -> {
+ upbitAPIThread.threadStop(true)
+ }
+ "STOP_THREAD2" -> {
+ upbitAPIThread2.threadStop(true)
+ }
+ }
+ }
+ }
+
+ inner class UpbitAPIThread(var type: Int, val codes: ArrayList) : Thread() {
+
+ var stopFlag: Boolean = false
+
+ override fun run() {
+ while (!stopFlag) {
+ val intent = Intent("com.mobit.APIRECEIVE")
+ // 코인 정보 받아오기
+ if (type == 100) {
+ intent.putExtra("mode", "CoinInfo")
+ val prices = upbitAPICaller.getTicker(codes)
+ if (prices.isNotEmpty()) {
+ val coinInfo = ArrayList()
+ for (i in prices.indices) {
+ coinInfo.add(CoinInfo(codes[i], getCoinName(codes[i]), prices[i]))
+ }
+ intent.putExtra("coinInfo", coinInfo)
+ intent.putExtra("isSuccess", true)
+
+ val favoriteCoinInfo2 = ArrayList()
+ for (favorite in favoriteCoinInfo) {
+ for (coin in coinInfo) {
+ if (favorite.code == coin.code) {
+ favoriteCoinInfo2.add(coin)
+ break
+ }
+ }
+ }
+ intent.putExtra("favoriteCoinInfo", favoriteCoinInfo2)
+ } else {
+ intent.putExtra("isSuccess", false)
+ }
+ }
+ // 호가 정보 받아오기
+ else if (type == 200) {
+ intent.putExtra("mode", "orderBook")
+ val orderBook = upbitAPICaller.getOrderbook(selectedCoin)
+ if (orderBook.isNotEmpty()) {
+ intent.putExtra("orderBook", orderBook)
+ intent.putExtra("isSuccess", true)
+ } else {
+ intent.putExtra("isSuccess", false)
+ }
+ }
+
+ sendBroadcast(intent)
+ sleep(300)
+ }
+ }
+
+ fun getCoinName(code: String): String {
+ return when (code) {
+ CoinInfo.BTC_CODE -> CoinInfo.BTC_NAME
+ CoinInfo.ETH_CODE -> CoinInfo.ETH_NAME
+ CoinInfo.ADA_CODE -> CoinInfo.ADA_NAME
+ CoinInfo.DOGE_CODE -> CoinInfo.DOGE_NAME
+ CoinInfo.XRP_CODE -> CoinInfo.XRP_NAME
+ CoinInfo.DOT_CODE -> CoinInfo.DOT_NAME
+ CoinInfo.BCH_CODE -> CoinInfo.BCH_NAME
+ CoinInfo.LTC_CODE -> CoinInfo.LTC_NAME
+ CoinInfo.LINK_CODE -> CoinInfo.LINK_NAME
+ CoinInfo.ETC_CODE -> CoinInfo.ETC_NAME
+ CoinInfo.THETA_CODE -> CoinInfo.THETA_NAME
+ CoinInfo.XLM_CODE -> CoinInfo.XLM_NAME
+ CoinInfo.VET_CODE -> CoinInfo.VET_NAME
+ CoinInfo.EOS_CODE -> CoinInfo.EOS_NAME
+ CoinInfo.TRX_CODE -> CoinInfo.TRX_NAME
+ CoinInfo.NEO_CODE -> CoinInfo.NEO_NAME
+ CoinInfo.IOTA_CODE -> CoinInfo.IOTA_NAME
+ CoinInfo.ATOM_CODE -> CoinInfo.ATOM_NAME
+ CoinInfo.BSV_CODE -> CoinInfo.BSV_NAME
+ CoinInfo.BTT_CODE -> CoinInfo.BTT_NAME
+ else -> CoinInfo.BTC_NAME
+ }
+ }
+
+ fun threadStop(flag: Boolean) {
+ this.stopFlag = flag
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/color/coin_list_button_selector_color.xml b/app/src/main/res/color/coin_list_button_selector_color.xml
new file mode 100644
index 0000000..1218601
--- /dev/null
+++ b/app/src/main/res/color/coin_list_button_selector_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/navigation_menu_selector_color.xml b/app/src/main/res/color/navigation_menu_selector_color.xml
new file mode 100644
index 0000000..ff8d0d6
--- /dev/null
+++ b/app/src/main/res/color/navigation_menu_selector_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/transaction_buy_button_selector_color.xml b/app/src/main/res/color/transaction_buy_button_selector_color.xml
new file mode 100644
index 0000000..438a8db
--- /dev/null
+++ b/app/src/main/res/color/transaction_buy_button_selector_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/transaction_info_button_selector_color.xml b/app/src/main/res/color/transaction_info_button_selector_color.xml
new file mode 100644
index 0000000..abf4a70
--- /dev/null
+++ b/app/src/main/res/color/transaction_info_button_selector_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/color/transaction_sell_button_selector_color.xml b/app/src/main/res/color/transaction_sell_button_selector_color.xml
new file mode 100644
index 0000000..1218601
--- /dev/null
+++ b/app/src/main/res/color/transaction_sell_button_selector_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ 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..2b068d1
--- /dev/null
+++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/asset_linearlayout_border.xml b/app/src/main/res/drawable/asset_linearlayout_border.xml
new file mode 100644
index 0000000..ec2c3e1
--- /dev/null
+++ b/app/src/main/res/drawable/asset_linearlayout_border.xml
@@ -0,0 +1,22 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/asset_linearlayout_border_2.xml b/app/src/main/res/drawable/asset_linearlayout_border_2.xml
new file mode 100644
index 0000000..1266c7e
--- /dev/null
+++ b/app/src/main/res/drawable/asset_linearlayout_border_2.xml
@@ -0,0 +1,24 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/asset_linearlayout_border_3.xml b/app/src/main/res/drawable/asset_linearlayout_border_3.xml
new file mode 100644
index 0000000..fb9199e
--- /dev/null
+++ b/app/src/main/res/drawable/asset_linearlayout_border_3.xml
@@ -0,0 +1,22 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/coin_list_button_border.xml b/app/src/main/res/drawable/coin_list_button_border.xml
new file mode 100644
index 0000000..5e5213e
--- /dev/null
+++ b/app/src/main/res/drawable/coin_list_button_border.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/coin_list_button_border_checked.xml b/app/src/main/res/drawable/coin_list_button_border_checked.xml
new file mode 100644
index 0000000..14ffc0a
--- /dev/null
+++ b/app/src/main/res/drawable/coin_list_button_border_checked.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/coin_list_button_border_not_checked.xml b/app/src/main/res/drawable/coin_list_button_border_not_checked.xml
new file mode 100644
index 0000000..caa87c3
--- /dev/null
+++ b/app/src/main/res/drawable/coin_list_button_border_not_checked.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/coin_list_linearlayout_border.xml b/app/src/main/res/drawable/coin_list_linearlayout_border.xml
new file mode 100644
index 0000000..ab47af6
--- /dev/null
+++ b/app/src/main/res/drawable/coin_list_linearlayout_border.xml
@@ -0,0 +1,23 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/coin_list_recyclerview_item_background.xml b/app/src/main/res/drawable/coin_list_recyclerview_item_background.xml
new file mode 100644
index 0000000..03bfa00
--- /dev/null
+++ b/app/src/main/res/drawable/coin_list_recyclerview_item_background.xml
@@ -0,0 +1,23 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/coin_list_searchview_border.xml b/app/src/main/res/drawable/coin_list_searchview_border.xml
new file mode 100644
index 0000000..9d00229
--- /dev/null
+++ b/app/src/main/res/drawable/coin_list_searchview_border.xml
@@ -0,0 +1,18 @@
+
+
+
+ -
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/first_setting_button_pressed_background.xml b/app/src/main/res/drawable/first_setting_button_pressed_background.xml
new file mode 100644
index 0000000..b651a92
--- /dev/null
+++ b/app/src/main/res/drawable/first_setting_button_pressed_background.xml
@@ -0,0 +1,14 @@
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_baseline_add_24.xml b/app/src/main/res/drawable/ic_baseline_add_24.xml
new file mode 100644
index 0000000..3c43aeb
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_add_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_description_24.xml b/app/src/main/res/drawable/ic_baseline_description_24.xml
new file mode 100644
index 0000000..e7ef3d4
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_description_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_home_24.xml b/app/src/main/res/drawable/ic_baseline_home_24.xml
new file mode 100644
index 0000000..4c5e854
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_home_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_insert_chart_24.xml b/app/src/main/res/drawable/ic_baseline_insert_chart_24.xml
new file mode 100644
index 0000000..21c76e7
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_insert_chart_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml
new file mode 100644
index 0000000..4e8cf25
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_down_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml
new file mode 100644
index 0000000..7b184de
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_keyboard_arrow_up_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_remove_24.xml b/app/src/main/res/drawable/ic_baseline_remove_24.xml
new file mode 100644
index 0000000..1e51d8b
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_remove_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_search_24.xml b/app/src/main/res/drawable/ic_baseline_search_24.xml
new file mode 100644
index 0000000..182087d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_search_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_settings_24.xml b/app/src/main/res/drawable/ic_baseline_settings_24.xml
new file mode 100644
index 0000000..b240b83
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_settings_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_baseline_sync_alt_24.xml b/app/src/main/res/drawable/ic_baseline_sync_alt_24.xml
new file mode 100644
index 0000000..d4c1bf8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_sync_alt_24.xml
@@ -0,0 +1,6 @@
+
+
+
+
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..07d5da9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_background.xml
@@ -0,0 +1,170 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_launcher_logo_background.xml b/app/src/main/res/drawable/ic_launcher_logo_background.xml
new file mode 100644
index 0000000..ca3826a
--- /dev/null
+++ b/app/src/main/res/drawable/ic_launcher_logo_background.xml
@@ -0,0 +1,74 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_description_24.xml b/app/src/main/res/drawable/ic_outline_description_24.xml
new file mode 100644
index 0000000..7c671fc
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_description_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_home_24.xml b/app/src/main/res/drawable/ic_outline_home_24.xml
new file mode 100644
index 0000000..b6db651
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_home_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_insert_chart_24.xml b/app/src/main/res/drawable/ic_outline_insert_chart_24.xml
new file mode 100644
index 0000000..59a8638
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_insert_chart_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_settings_24.xml b/app/src/main/res/drawable/ic_outline_settings_24.xml
new file mode 100644
index 0000000..c8123a8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_settings_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_outline_sync_alt_24.xml b/app/src/main/res/drawable/ic_outline_sync_alt_24.xml
new file mode 100644
index 0000000..d4c1bf8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_outline_sync_alt_24.xml
@@ -0,0 +1,6 @@
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_round_star_24.xml b/app/src/main/res/drawable/ic_round_star_24.xml
new file mode 100644
index 0000000..19d3bf2
--- /dev/null
+++ b/app/src/main/res/drawable/ic_round_star_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_round_star_border_24.xml b/app/src/main/res/drawable/ic_round_star_border_24.xml
new file mode 100644
index 0000000..a84f4ec
--- /dev/null
+++ b/app/src/main/res/drawable/ic_round_star_border_24.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/menu_chart_selector.xml b/app/src/main/res/drawable/menu_chart_selector.xml
new file mode 100644
index 0000000..bfe29ee
--- /dev/null
+++ b/app/src/main/res/drawable/menu_chart_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/menu_coinlist_selector.xml b/app/src/main/res/drawable/menu_coinlist_selector.xml
new file mode 100644
index 0000000..8ac05a2
--- /dev/null
+++ b/app/src/main/res/drawable/menu_coinlist_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/menu_investment_selector.xml b/app/src/main/res/drawable/menu_investment_selector.xml
new file mode 100644
index 0000000..c795a4b
--- /dev/null
+++ b/app/src/main/res/drawable/menu_investment_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/menu_setting_selector.xml b/app/src/main/res/drawable/menu_setting_selector.xml
new file mode 100644
index 0000000..88939ee
--- /dev/null
+++ b/app/src/main/res/drawable/menu_setting_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/menu_trade_selector.xml b/app/src/main/res/drawable/menu_trade_selector.xml
new file mode 100644
index 0000000..e23f984
--- /dev/null
+++ b/app/src/main/res/drawable/menu_trade_selector.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/navigation_menu_selector_color.xml b/app/src/main/res/drawable/navigation_menu_selector_color.xml
new file mode 100644
index 0000000..ff8d0d6
--- /dev/null
+++ b/app/src/main/res/drawable/navigation_menu_selector_color.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/popup_buy_sell_button_border.xml b/app/src/main/res/drawable/popup_buy_sell_button_border.xml
new file mode 100644
index 0000000..c643e96
--- /dev/null
+++ b/app/src/main/res/drawable/popup_buy_sell_button_border.xml
@@ -0,0 +1,23 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/popup_buy_sell_linearlayout_border.xml b/app/src/main/res/drawable/popup_buy_sell_linearlayout_border.xml
new file mode 100644
index 0000000..5f1c8a8
--- /dev/null
+++ b/app/src/main/res/drawable/popup_buy_sell_linearlayout_border.xml
@@ -0,0 +1,23 @@
+
+
+
+ -
+
+
+
+
+
+
+ -
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/record_linearlayout_border.xml b/app/src/main/res/drawable/record_linearlayout_border.xml
new file mode 100644
index 0000000..6cad8ff
--- /dev/null
+++ b/app/src/main/res/drawable/record_linearlayout_border.xml
@@ -0,0 +1,9 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/splash.xml b/app/src/main/res/drawable/splash.xml
new file mode 100644
index 0000000..cfd1d2f
--- /dev/null
+++ b/app/src/main/res/drawable/splash.xml
@@ -0,0 +1,9 @@
+
+
+
+ -
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/splash_image.png b/app/src/main/res/drawable/splash_image.png
new file mode 100644
index 0000000000000000000000000000000000000000..818042653e9dd04a7e6b4ec80e59b878e8be5754
GIT binary patch
literal 4083
zcmds3WmppcwH>8mZJi
z?)`tC``o|xJnuR0IsZ<)KTfE+ngRvcGcp1K0tzKXSxo{0LeJYgnUwf8a!r^j-x8sV
zrh*JX#V8}@wzy*{ttw4GP#sNvWp^+aiV;@r8o}
z6C3H}qb2aE@T|UXQ7w|6vf;gpE1QQi%e&;FA=HwQ4@5)AM1r46L{N%_J^_YPNk%*p
z4`Y;%d7%*dTp{+EZ1fvYg22c0tQsW0ZW;w3RLi9_B%_B`I*R^7^eg4~f`&;}wIo`Z
zsLZNyF3l9zNQ7;0qkBwiXl8Flf=8>r_
z=Lq=5&QV_dgpeLo>T`~PM`d!!aCBb(!rK1)pM7nYvZkH|PR-qkllQg<~^Ym2ETA%e(!k
zb#AScwQXE;&mxa@s-b7)I|JBT)x{_J$_sZIiIS!5F
z)`4YMhZn9vrX*rFnUwfzDQUfW*yWxF^}2Dou`zJSo+uJ
zH%zn*E<@bPtJ-Gzkn7d$vmf0nCKqS5zWkz0AKT|rU7O|t#TOP4(PT_Fxg!b~5
z>A}g3fYi^>GD*=09q4>rkKkYAk-%
ziJNAL8f65gb^RV*!R(y`LAyIgR=@sg0GefKIF+Q8jTSaeCKV6I<`1Ylmc--@MCSA*
z77gv}pGM{Ozcx?9)WV~0Y136x
zP3u;l|No7^&_4gk?Zo#T74=*Q2q?S$%L)4&ip&WJ$Y+#frM15s??Kzc)|6>_>sN}L
zgbY(i*hu6o!Wb-DX&J#)sgGYL?zI5!V6qx-+2w{PW;%0r6c*kKX~^qBk+oa}?UTwJuN
z#=FcIZzDd926v&WXT$ebM&fPNa9pZeQckphtf0f9*Ni2B6AaeBt`q-z
zdPaXOqW#!Xn}WS6n%-4dHi5>t?hklw_?wIW>PP$>)UodlDCb08k}mq7h`zSZ_|rH#
zWU@zuyH=rtO@)sRYXW6m;K%uwWFwdJv!*%m`c%Z=*4`Tp$Q$L|F*b3*mQalp(^sln
zKSGMs^aUQuN8acxsiU#*>6jSS_Dmx7UgCT%hdh$kl$l9E64twT%b@<+edXr2|dspiA13I`9AzIYF@GUo~nvEl!a)50@$_f5h%DzP%Bb--)^qcrd4zn(1
zkk}_1r^xT`zRU7KdnCZq^XR_y_;+!C^fGB+ALQGs
zr)SuDH6TE&IT`r)FTT!Pj7i(nS5p&Qt`vt}DH+S0h@u;P^c)y4RTx!E9
z;TOK$S2=@_gM_!E=p=2THnxO?ANSk;w+`Rr6yL1UWzUM@#(FKGA?iVH8QE}Xjxq`@pS=OXTr$(hBPr>B0JX|A%(-{I#(GUc1MuDHAsU$;={xa^aevh
zfEMjIPGh|+WhWs|
zBWQ#9^B~Ja!-T@$l$3LRzNRd>+^!v{6PgO~!eM9u&xaXnHxWrGZvWD!FhZTvhHIG&
zD*t`LZP#Q~nc5Ha7&;4fKk2Nu@Ej@cx9}qUD34U7>Av_65;75()n~C^
z{_Tjr@;EqTx5E$<5?b`Ro{9mOyD>;EWWty}>`+3wEvc-uBu;jnDkt;9
z+r_3y-tvkpewpB|RX=)&o6_EatHS=};Ejxp`|UC~;L;EcVo%uijUp`mYP}wCx<@-X
z|3ynKzv6<`x1Thp^`Qsy9;y^@?08&l@i@)eOV$Rm{SO{aX$iWn-f?!DL(C>z&Ecp~
zJx|kL2cF;jk>ugwJAF<5zRi$>2Wxj&A@Uw>)XbmwqCltgWM6w-!nXJhr`x%!5OO#B
zS_dEKmU~^PB>V)y*{~%H&V$Zvnt@Q$R(oH~Ni)MIXN&Vbxy;IVnn0a@f%|Ap)My!7wU5iOa`v_D?vSaB`l{cZZFlt&Y6r9enj8%yJVj
zvfx1T@NrTtI=+$geAf{k7j=xD7c{`=v4-Vbn`lR=hl#|T
z`8Hm(_YP
zG(758)(dAa6|a9w6&Lg-4o9{c@6^hdAfTwQ1ci`A*!O$=jDM$@T({Q4x&zjkt#~EQ
z!Bb0iZDPxeK2wau7p&k><1J@r)oGv@1a=+DIO+gt%YcuN@3fzvy1MZo()vnhdv;Y&N{P9J85mu&V^UTVhIAZ+t1x#nxevhCRx>dcpgo@{u=7!JNd
ztkj8o$~W+K!#UjW3OK#Et6YYA3Xh5@5`~SpBs_V+N)I7Sk11I&ld@L
zfqJ#xozmxlw*%VVl&&ibR;_qzesw>RQmQJnL}(w30@wgHFrqYM(+y5QnUW1Vesk~{
z@263H5J~&w_5M{8T0y{xUHtK1Bk_o~kdxsc{=MuJC!^Eq&}eacX_|)F?VW^}+Ji0S
zdfyu5if`F8rIJHD&dZ=B40))x#Xn!zf?Rpa$ue3j-il`cVZiIe&Ok_?t+rQ#(<-R*J@TWNmV5~(CJRJ;nxuvKWQn`f
zoEE29iIlpj*LU!`QLBWmb+Y7CtSdK_h%|}9|Lv>B2XkFADc=AJJm!`h
RZXbUFB{?)X{{R8qxN`si
literal 0
HcmV?d00001
diff --git a/app/src/main/res/drawable/transaction_button_background.xml b/app/src/main/res/drawable/transaction_button_background.xml
new file mode 100644
index 0000000..71c09a7
--- /dev/null
+++ b/app/src/main/res/drawable/transaction_button_background.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/transaction_button_background_checked.xml b/app/src/main/res/drawable/transaction_button_background_checked.xml
new file mode 100644
index 0000000..24465a0
--- /dev/null
+++ b/app/src/main/res/drawable/transaction_button_background_checked.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/transaction_button_background_not_checked.xml b/app/src/main/res/drawable/transaction_button_background_not_checked.xml
new file mode 100644
index 0000000..17295f8
--- /dev/null
+++ b/app/src/main/res/drawable/transaction_button_background_not_checked.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/drawable/transaction_linearlayout_border.xml b/app/src/main/res/drawable/transaction_linearlayout_border.xml
new file mode 100644
index 0000000..ed7307a
--- /dev/null
+++ b/app/src/main/res/drawable/transaction_linearlayout_border.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_first_setting.xml b/app/src/main/res/layout/activity_first_setting.xml
new file mode 100644
index 0000000..1679d0f
--- /dev/null
+++ b/app/src/main/res/layout/activity_first_setting.xml
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
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..67f3a94
--- /dev/null
+++ b/app/src/main/res/layout/activity_main.xml
@@ -0,0 +1,35 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_popup_buy_sell.xml b/app/src/main/res/layout/activity_popup_buy_sell.xml
new file mode 100644
index 0000000..db7c80a
--- /dev/null
+++ b/app/src/main/res/layout/activity_popup_buy_sell.xml
@@ -0,0 +1,186 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_popup_reset_confirm.xml b/app/src/main/res/layout/activity_popup_reset_confirm.xml
new file mode 100644
index 0000000..6154865
--- /dev/null
+++ b/app/src/main/res/layout/activity_popup_reset_confirm.xml
@@ -0,0 +1,90 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_asset.xml b/app/src/main/res/layout/fragment_asset.xml
new file mode 100644
index 0000000..141284e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_asset.xml
@@ -0,0 +1,215 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_buy.xml b/app/src/main/res/layout/fragment_buy.xml
new file mode 100644
index 0000000..1bbb46f
--- /dev/null
+++ b/app/src/main/res/layout/fragment_buy.xml
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_chart.xml b/app/src/main/res/layout/fragment_chart.xml
new file mode 100644
index 0000000..f42798e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_chart.xml
@@ -0,0 +1,191 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_coin_info.xml b/app/src/main/res/layout/fragment_coin_info.xml
new file mode 100644
index 0000000..956470d
--- /dev/null
+++ b/app/src/main/res/layout/fragment_coin_info.xml
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_coin_list.xml b/app/src/main/res/layout/fragment_coin_list.xml
new file mode 100644
index 0000000..d4bc4df
--- /dev/null
+++ b/app/src/main/res/layout/fragment_coin_list.xml
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_investment.xml b/app/src/main/res/layout/fragment_investment.xml
new file mode 100644
index 0000000..b32c452
--- /dev/null
+++ b/app/src/main/res/layout/fragment_investment.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_record.xml b/app/src/main/res/layout/fragment_record.xml
new file mode 100644
index 0000000..eb0f810
--- /dev/null
+++ b/app/src/main/res/layout/fragment_record.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_sell.xml b/app/src/main/res/layout/fragment_sell.xml
new file mode 100644
index 0000000..d98b6df
--- /dev/null
+++ b/app/src/main/res/layout/fragment_sell.xml
@@ -0,0 +1,287 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_setting.xml b/app/src/main/res/layout/fragment_setting.xml
new file mode 100644
index 0000000..5c2e55c
--- /dev/null
+++ b/app/src/main/res/layout/fragment_setting.xml
@@ -0,0 +1,184 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_transaction.xml b/app/src/main/res/layout/fragment_transaction.xml
new file mode 100644
index 0000000..a5b224e
--- /dev/null
+++ b/app/src/main/res/layout/fragment_transaction.xml
@@ -0,0 +1,190 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/progress_loading.xml b/app/src/main/res/layout/progress_loading.xml
new file mode 100644
index 0000000..5cd65cb
--- /dev/null
+++ b/app/src/main/res/layout/progress_loading.xml
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/recyclerview_asset_item.xml b/app/src/main/res/layout/recyclerview_asset_item.xml
new file mode 100644
index 0000000..1ad1518
--- /dev/null
+++ b/app/src/main/res/layout/recyclerview_asset_item.xml
@@ -0,0 +1,236 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/recyclerview_coinlist_item.xml b/app/src/main/res/layout/recyclerview_coinlist_item.xml
new file mode 100644
index 0000000..61f18b9
--- /dev/null
+++ b/app/src/main/res/layout/recyclerview_coinlist_item.xml
@@ -0,0 +1,70 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/recyclerview_record_item.xml b/app/src/main/res/layout/recyclerview_record_item.xml
new file mode 100644
index 0000000..0b2d959
--- /dev/null
+++ b/app/src/main/res/layout/recyclerview_record_item.xml
@@ -0,0 +1,240 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/recyclerview_transaction_item.xml b/app/src/main/res/layout/recyclerview_transaction_item.xml
new file mode 100644
index 0000000..0748cff
--- /dev/null
+++ b/app/src/main/res/layout/recyclerview_transaction_item.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/spinner_item.xml b/app/src/main/res/layout/spinner_item.xml
new file mode 100644
index 0000000..93a7498
--- /dev/null
+++ b/app/src/main/res/layout/spinner_item.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_bottomnavigationview.xml b/app/src/main/res/menu/menu_bottomnavigationview.xml
new file mode 100644
index 0000000..27e9fe6
--- /dev/null
+++ b/app/src/main/res/menu/menu_bottomnavigationview.xml
@@ -0,0 +1,27 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_chart_unit.xml b/app/src/main/res/menu/menu_chart_unit.xml
new file mode 100644
index 0000000..f40d13d
--- /dev/null
+++ b/app/src/main/res/menu/menu_chart_unit.xml
@@ -0,0 +1,25 @@
+
+
\ No newline at end of file
diff --git a/app/src/main/res/menu/menu_coinlist_item_clicked.xml b/app/src/main/res/menu/menu_coinlist_item_clicked.xml
new file mode 100644
index 0000000..8b7bc63
--- /dev/null
+++ b/app/src/main/res/menu/menu_coinlist_item_clicked.xml
@@ -0,0 +1,9 @@
+
+
\ No newline at end of file
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_logo.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_logo.xml
new file mode 100644
index 0000000..52d540a
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_logo.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_logo_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_logo_round.xml
new file mode 100644
index 0000000..52d540a
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_logo_round.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 0000000000000000000000000000000000000000..a571e60098c92c2baca8a5df62f2929cbff01b52
GIT binary patch
literal 3593
zcmV+k4)*bhP){4Q1@|o^l5vR(0JRNCL<7M6}UD`@%^5zYjRJ-VNC3qn#9n=m>>ACRx!M
zlW3!lO>#0MCAqh6PU7cMP#aQ`+zp##c~|0RJc4JAuaV=qZS|vg8XJ$1pYxc-u~Q5j
z%Ya4ddEvZow!floOU_jrlE84*Kfv6!kMK^%#}A$Bjrna`@pk(TS$jA@P;|iPUR-x)_r4ELtL9aUonVhI31zFsJ96
z|5S{%9|FB-SsuD=#0u1WU!W6fcXF)#63D7tvwg%1l(}|SzXh_Z(5234`w*&@ctO>g
z0Aug~xs*zAjCpNau(Ul@mR~?6dNGx9Ii5MbMvmvUxeqy>$Hrrn;v8G!g*o~UV4mr_
zyWaviS4O6Kb?ksg`)0wj?E@IYiw3az(r1w37|S|7!ODxfW%>6m?!@woyJUIh_!>E$
z+vYyxcpe*%QHt~E*etx=mI~XG8~QJhRar>tNMB;pPOKRfXjGt4fkp)y6=*~XIJC&C!aaha9k7~UP9;`q;1n9prU@a%Kg%gDW+xy9n`kiOj8WIs;+T>HrW
znVTomw_2Yd%+r4at4zQC3*=Z4naYE7H*Dlv4=@IEtH_H;af}t@W7@mE$1xI#XM-`%
z0le3-Q}*@D@ioThJ*cgm>kVSt+=txjd2BpJDbBrpqp-xV9X6Rm?1Mh~?li96xq(IP
z+n(4GTXktSt_z*meC5=$pMzMKGuIn&_IeX6Wd!2$md%l{x(|LXClGVhzqE^Oa@!*!
zN%O7K8^SHD|9aoAoT4QLzF+Uh_V03V;KyQ|__-RTH(F72qnVypVei#KZ2K-7YiPS*
z-4gZd>%uRm<0iGmZH|~KW<>#hP9o@UT@gje_^AR{?p(v|y8`asyNi4G?n#2V+jsBa
z+uJ|m;EyHnA%QR7{z(*%+Z;Ip(Xt5n<`4yZ51n^!%L?*a=)Bt{J_b`;+~$Z7h^x@&
zSBr2>_@&>%7=zp5Ho5H~6-Y@wXkpt{s9Tc+7RnfWuZC|&NO6p{m-gU%=cPw3qyB>1
zto@}!>_e`99vhEQic{;8goXMo1NA`>sch8T3@O44!$uf`IlgBj#c@Ku*!9B`7seRe
z2j?cKG4R-Uj8dFidy25wu#J3>-_u`WT%NfU54JcxsJv;A^i#t!2XXn%zE=O##OXoy
zwR2+M!(O12D_LUsHV)v2&TBZ*di1$c8
z+_~Oo@HcOFV&TasjNRjf*;zVV?|S@-_EXmlIG@&F!WS#yU9<_Ece?sq^L^Jf%(##=
zdTOpA6uXwXx3O|`C-Dbl~`~#9yjlFN>;Yr?Kv68=F`fQLW
z(x40UIAuQRN~Y|fpCi2++qHWrXd&S*NS$z8V+YP
zSX7#fxfebdJfrw~mzZr!thk9BE&_eic@-9C0^nK@0o$T5nAK~CHV4fzY#KJ=^uV!D
z3)jL(DDpL!TDSq`=e0v8(8`Wo_~p*6KHyT!kmCCCU48I?mw-UrBj8=Vg#?O%Z2<|C
z?+4Q&W09VsK<14)vHY^n;Zi3%4Q?s4x^$3;acx76-t*K|3^MUKELf>Jew${&!(