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()) } 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 } 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") } } } }