306 lines
15 KiB
Kotlin
306 lines
15 KiB
Kotlin
package ru.vendetti.bitcoin_summarizer
|
||
|
||
import android.annotation.SuppressLint
|
||
import android.os.Bundle
|
||
import androidx.activity.ComponentActivity
|
||
import androidx.activity.compose.setContent
|
||
import androidx.compose.foundation.background
|
||
import androidx.compose.foundation.layout.Box
|
||
import androidx.compose.foundation.layout.Column
|
||
import androidx.compose.foundation.layout.Spacer
|
||
import androidx.compose.foundation.layout.height
|
||
import androidx.compose.foundation.layout.padding
|
||
import androidx.compose.foundation.rememberScrollState
|
||
import androidx.compose.foundation.verticalScroll
|
||
import androidx.compose.material.icons.Icons
|
||
import androidx.compose.material.icons.filled.Menu
|
||
import androidx.compose.material.icons.filled.MoreVert
|
||
import androidx.compose.material3.ExperimentalMaterial3Api
|
||
import androidx.compose.material3.HorizontalDivider
|
||
import androidx.compose.material3.Icon
|
||
import androidx.compose.material3.IconButton
|
||
import androidx.compose.material3.MaterialTheme
|
||
import androidx.compose.material3.MediumTopAppBar
|
||
import androidx.compose.material3.Scaffold
|
||
import androidx.compose.material3.Text
|
||
import androidx.compose.material3.TopAppBarDefaults
|
||
import androidx.compose.material3.rememberTopAppBarState
|
||
import androidx.compose.runtime.Composable
|
||
import androidx.compose.runtime.LaunchedEffect
|
||
import androidx.compose.runtime.getValue
|
||
import androidx.compose.runtime.mutableIntStateOf
|
||
import androidx.compose.runtime.mutableStateOf
|
||
import androidx.compose.runtime.remember
|
||
import androidx.compose.runtime.setValue
|
||
import androidx.compose.ui.Alignment
|
||
import androidx.compose.ui.Modifier
|
||
import androidx.compose.ui.graphics.Color
|
||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||
import androidx.compose.ui.text.style.TextOverflow
|
||
import androidx.compose.ui.tooling.preview.Preview
|
||
import androidx.compose.ui.unit.dp
|
||
import androidx.compose.ui.unit.sp
|
||
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
|
||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLabelComponent
|
||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLineComponent
|
||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberBottom
|
||
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberStart
|
||
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLine
|
||
import com.patrykandpatrick.vico.compose.cartesian.layer.rememberLineCartesianLayer
|
||
import com.patrykandpatrick.vico.compose.cartesian.rememberCartesianChart
|
||
import com.patrykandpatrick.vico.compose.cartesian.rememberVicoZoomState
|
||
import com.patrykandpatrick.vico.compose.common.component.rememberTextComponent
|
||
import com.patrykandpatrick.vico.compose.common.component.shapeComponent
|
||
import com.patrykandpatrick.vico.compose.common.fill
|
||
import com.patrykandpatrick.vico.compose.common.shape.rounded
|
||
import com.patrykandpatrick.vico.compose.common.vicoTheme
|
||
import com.patrykandpatrick.vico.core.cartesian.Zoom
|
||
import com.patrykandpatrick.vico.core.cartesian.axis.HorizontalAxis
|
||
import com.patrykandpatrick.vico.core.cartesian.axis.VerticalAxis
|
||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianChartModelProducer
|
||
import com.patrykandpatrick.vico.core.cartesian.data.CartesianLayerRangeProvider
|
||
import com.patrykandpatrick.vico.core.cartesian.data.lineSeries
|
||
import com.patrykandpatrick.vico.core.cartesian.decoration.HorizontalLine
|
||
import com.patrykandpatrick.vico.core.cartesian.layer.LineCartesianLayer
|
||
import com.patrykandpatrick.vico.core.common.Position
|
||
import com.patrykandpatrick.vico.core.common.component.LineComponent
|
||
import com.patrykandpatrick.vico.core.common.component.TextComponent
|
||
import com.patrykandpatrick.vico.core.common.shape.CorneredShape
|
||
import ru.vendetti.bitcoin_summarizer.ui.theme.BitcoinSummarizerTheme
|
||
import ru.vendetti.bitcoin_summarizer.ui.theme.Flame
|
||
import ru.vendetti.bitcoin_summarizer.ui.theme.Green2
|
||
import java.text.DecimalFormat
|
||
import java.time.format.DateTimeFormatter
|
||
|
||
class MainActivity : ComponentActivity() {
|
||
override fun onCreate(savedInstanceState: Bundle?) {
|
||
super.onCreate(savedInstanceState)
|
||
setContent {
|
||
BitcoinSummarizerTheme {
|
||
CryptoScreen()
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
@SuppressLint("MutableCollectionMutableState", "SimpleDateFormat")
|
||
@OptIn(ExperimentalMaterial3Api::class)
|
||
@Preview
|
||
@Composable
|
||
fun CryptoScreen() {
|
||
// Создаем репозиторий для работы с API
|
||
val cryptoRepository = remember { CryptoRepository(RetrofitClient.apiService) }
|
||
// Состояния для хранения результатов запросов
|
||
var bitcoinTicker by remember { mutableStateOf(TickerData()) }
|
||
var globalData by remember { mutableStateOf(GlobalResponse()) }
|
||
var fearGreedDataList by remember { mutableStateOf(ArrayList<FearAndGreedData>()) }
|
||
|
||
var fearGreedIndexDaysCount by remember { mutableIntStateOf(30) }
|
||
|
||
// Запускаем корутину для выполнения сетевых запросов
|
||
LaunchedEffect(fearGreedIndexDaysCount) {
|
||
// Запрос Bitcoin Ticker
|
||
val tickerResponse = cryptoRepository.fetchBitcoinTicker()
|
||
bitcoinTicker = tickerResponse!!.first()
|
||
|
||
// Запрос глобальных данных
|
||
val globalResponse = cryptoRepository.fetchGlobalData()
|
||
globalData = globalResponse!!
|
||
|
||
// Запрос индекса страха и жадности
|
||
val fearResponse = cryptoRepository.fetchFearAndGreedData(fearGreedIndexDaysCount)
|
||
fearGreedDataList = fearResponse?.dataList as ArrayList<FearAndGreedData>
|
||
}
|
||
|
||
val modelProducer = remember { CartesianChartModelProducer() }
|
||
|
||
LaunchedEffect(fearGreedDataList) {
|
||
modelProducer.runTransaction {
|
||
var numberValues = Array(fearGreedDataList.count()) {
|
||
index ->
|
||
fearGreedDataList[fearGreedDataList.count() - index - 1]
|
||
.value.toInt()
|
||
}
|
||
|
||
if(numberValues.isEmpty())
|
||
numberValues = Array(1) {0}
|
||
|
||
lineSeries { series(numberValues.toList()) }
|
||
}
|
||
}
|
||
|
||
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
|
||
val zoomState = rememberVicoZoomState(
|
||
zoomEnabled = false,
|
||
initialZoom = Zoom.x(fearGreedIndexDaysCount.toDouble()))
|
||
|
||
// Отображаем результаты на странице
|
||
Scaffold (
|
||
modifier = Modifier
|
||
.nestedScroll(scrollBehavior.nestedScrollConnection),
|
||
|
||
topBar = {
|
||
MediumTopAppBar(
|
||
colors = TopAppBarDefaults.topAppBarColors(
|
||
containerColor = MaterialTheme.colorScheme.primary,
|
||
scrolledContainerColor = MaterialTheme.colorScheme.primary,
|
||
titleContentColor = MaterialTheme.colorScheme.onPrimary,
|
||
navigationIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||
actionIconContentColor = MaterialTheme.colorScheme.onPrimary,
|
||
),
|
||
title = {
|
||
Text(
|
||
"Bitcoin Summarizer",
|
||
maxLines = 1,
|
||
overflow = TextOverflow.Ellipsis
|
||
)
|
||
},
|
||
navigationIcon = {
|
||
IconButton(onClick = {}) {
|
||
Icon(
|
||
imageVector = Icons.Filled.Menu,
|
||
contentDescription = "Navigation hamburger menu"
|
||
)
|
||
}
|
||
},
|
||
actions = {
|
||
IconButton(onClick = {}) {
|
||
Icon(
|
||
imageVector = Icons.Filled.MoreVert,
|
||
contentDescription = "Actions"
|
||
)
|
||
}
|
||
},
|
||
scrollBehavior = scrollBehavior
|
||
)
|
||
},
|
||
)
|
||
{ innerPadding ->
|
||
Box(
|
||
modifier = Modifier
|
||
.background(Color.Transparent)
|
||
.padding(innerPadding)
|
||
.verticalScroll(rememberScrollState())
|
||
) {
|
||
Column(
|
||
modifier = Modifier
|
||
.padding(16.dp, 16.dp, 16.dp, 36.dp)
|
||
) {
|
||
/* Fear Greed Chart Start */
|
||
CartesianChartHost(
|
||
zoomState = zoomState,
|
||
chart = rememberCartesianChart(
|
||
rememberLineCartesianLayer(
|
||
lineProvider = LineCartesianLayer.LineProvider.series(
|
||
vicoTheme.lineCartesianLayerColors.map{
|
||
_ ->
|
||
LineCartesianLayer
|
||
.rememberLine(
|
||
LineCartesianLayer.LineFill.single(
|
||
fill(MaterialTheme.colorScheme.onPrimary)
|
||
)
|
||
)
|
||
}
|
||
),
|
||
rangeProvider = remember { CartesianLayerRangeProvider.fixed(minY = 0.0, maxY = 100.0)}
|
||
),
|
||
startAxis = VerticalAxis.rememberStart(
|
||
title = "FGI",
|
||
titleComponent = rememberTextComponent(MaterialTheme.colorScheme.onPrimary),
|
||
line = rememberAxisLineComponent(fill(MaterialTheme.colorScheme.onPrimary)),
|
||
label = rememberAxisLabelComponent(MaterialTheme.colorScheme.onPrimary)
|
||
),
|
||
bottomAxis = HorizontalAxis.rememberBottom(
|
||
title = "последние $fearGreedIndexDaysCount дней",
|
||
titleComponent = rememberTextComponent(MaterialTheme.colorScheme.onPrimary),
|
||
line = rememberAxisLineComponent(fill(MaterialTheme.colorScheme.onPrimary)),
|
||
label = rememberAxisLabelComponent(MaterialTheme.colorScheme.onPrimary)
|
||
),
|
||
decorations = listOf(
|
||
remember {
|
||
HorizontalLine(
|
||
y = { 25.toDouble() },
|
||
line = LineComponent(fill(Flame), 1f),
|
||
labelComponent = TextComponent(
|
||
background =
|
||
shapeComponent(
|
||
fill(Flame),
|
||
CorneredShape.rounded(
|
||
topLeft = 4.dp,
|
||
topRight = 4.dp
|
||
)
|
||
),
|
||
),
|
||
label = { "Страх" },
|
||
verticalLabelPosition = Position.Vertical.Top
|
||
)
|
||
},
|
||
remember {
|
||
HorizontalLine(
|
||
y = { 70.toDouble() },
|
||
line = LineComponent(fill(Green2), 1f),
|
||
labelComponent = TextComponent(
|
||
background =
|
||
shapeComponent(
|
||
fill(Green2),
|
||
CorneredShape.rounded(
|
||
bottomLeft = 4.dp,
|
||
bottomRight = 4.dp
|
||
)
|
||
),
|
||
),
|
||
label = { "Жадность" },
|
||
verticalLabelPosition = Position.Vertical.Bottom
|
||
)
|
||
}
|
||
),
|
||
),
|
||
modelProducer = modelProducer,
|
||
)
|
||
/* Fear Greed Chart End */
|
||
Spacer(modifier = Modifier.height(16.dp))
|
||
HorizontalDivider(thickness = 2.dp)
|
||
Spacer(modifier = Modifier.height(16.dp))
|
||
Text(
|
||
"Данные о Биткоине",
|
||
modifier = Modifier
|
||
.align(alignment = Alignment.CenterHorizontally),
|
||
fontSize = 24.sp
|
||
)
|
||
Spacer(modifier = Modifier.height(16.dp))
|
||
val formatter = DecimalFormat("0.00")
|
||
Text("Текущая цена: \n \$ ${formatter.format(bitcoinTicker.priceUsd.toFloat())}\n")
|
||
Text("Суточный оборот: \n \$ ${bitcoinTicker.volume24hUsd}\n")
|
||
Text("Капитализация: \n \$ ${bitcoinTicker.marketCapUsd}\n")
|
||
Text(
|
||
"Изменение курса за: " +
|
||
"\n Сутки: ${formatter.format(bitcoinTicker.percentChange24h.toFloat())}% " +
|
||
"\n Неделю: ${formatter.format(bitcoinTicker.percentChange7d.toFloat())}%\n"
|
||
)
|
||
|
||
var humanDate = ""
|
||
|
||
if(bitcoinTicker.lastUpdated.isNotEmpty())
|
||
humanDate = DateTimeFormatter.ISO_INSTANT
|
||
.format(java.time.Instant.ofEpochSecond(bitcoinTicker.lastUpdated.toLong()))
|
||
|
||
Text("Время последнего обновления: \n ${humanDate}\n")
|
||
|
||
HorizontalDivider(thickness = 2.dp)
|
||
Spacer(modifier = Modifier.height(16.dp))
|
||
Text(
|
||
"Глобальные данные",
|
||
modifier = Modifier
|
||
.align(alignment = Alignment.CenterHorizontally),
|
||
fontSize = 24.sp
|
||
)
|
||
Spacer(modifier = Modifier.height(16.dp))
|
||
Text("Общая капитализация крипторынка: \n \$ ${globalData.totalMarketCapUsd}\n")
|
||
Text("Всего тикеров: \n ${globalData.activeCryptocurrencies}\n")
|
||
Text("Суточный оборот всех криптовалют: \n \$ ${globalData.total24hVolumeUsd}\n")
|
||
Text("Процент доминации Биткоина: \n ${formatter.format(globalData.bitcoinPercentageOfMarketCap.toFloat())}%\n")
|
||
}
|
||
}
|
||
}
|
||
}
|