Compare commits

..

No commits in common. "0906b95273635943c72e9ffcfc8de7eb24ca1c7b" and "d1bc545a073058a6bd1ae035ed07de453e76f8db" have entirely different histories.

6 changed files with 51 additions and 225 deletions

View File

@ -8,7 +8,7 @@ on:
jobs:
Build:
runs-on: ubuntu-24.04-slim
runs-on: ubuntu-22.04
steps:
- name: Checkout the repo
uses: actions/checkout@v4

View File

@ -61,6 +61,5 @@ dependencies {
implementation(libs.converter.gson)
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.vico.compose.m3)
}

View File

@ -4,8 +4,8 @@ import com.google.gson.annotations.SerializedName
// Данные
data class GlobalResponse(
@SerializedName("active_currencies") val activeCryptocurrencies: String = "",
@SerializedName("total_market_cap_usd") val totalMarketCapUsd: String = "",
@SerializedName("total_24h_volume_usd") val total24hVolumeUsd: String = "",
@SerializedName("bitcoin_percentage_of_market_cap") val bitcoinPercentageOfMarketCap: String = ""
@SerializedName("active_currencies") val activeCryptocurrencies: String,
@SerializedName("total_market_cap_usd") val totalMarketCapUsd: String,
@SerializedName("total_24h_volume_usd") val total24hVolumeUsd: String,
@SerializedName("bitcoin_percentage_of_market_cap") val bitcoinPercentageOfMarketCap: String
)

View File

@ -1,69 +1,26 @@
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.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.magnifier
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.TextUnit
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.rememberBottom
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberStart
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.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.lineSeries
import com.patrykandpatrick.vico.core.cartesian.decoration.HorizontalLine
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 java.text.DateFormat
import java.time.format.DateTimeFormatter
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
@ -76,181 +33,53 @@ class MainActivity : ComponentActivity() {
}
}
@SuppressLint("MutableCollectionMutableState")
@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) }
// Состояния для хранения результатов запросов
var bitcoinTicker by remember { mutableStateOf("Загрузка Bitcoin Ticker...") }
var globalData by remember { mutableStateOf("Загрузка глобальных данных...") }
var fearGreedData by remember { mutableStateOf("Загрузка индекса страха и жадности...") }
// Запускаем корутину для выполнения сетевых запросов
LaunchedEffect(fearGreedIndexDaysCount) {
LaunchedEffect(Unit) {
// Запрос Bitcoin Ticker
val tickerResponse = cryptoRepository.fetchBitcoinTicker()
bitcoinTicker = tickerResponse!!.first()
bitcoinTicker = tickerResponse?.joinToString(separator = "\n") { data ->
"Название: ${data.name}, Цена: ${data.priceUsd}, Обновлено: ${data.lastUpdated}"
} ?: "Ошибка загрузки Bitcoin Ticker"
// Запрос глобальных данных
val globalResponse = cryptoRepository.fetchGlobalData()
globalData = globalResponse!!
globalData = globalResponse?.let { data ->
"Активных криптовалют: ${data.activeCryptocurrencies}\nРыночная капитализация: ${data.totalMarketCapUsd}\n24h Объем: ${data.total24hVolumeUsd}"
} ?: "Ошибка загрузки глобальных данных"
// Запрос индекса страха и жадности
val fearResponse = cryptoRepository.fetchFearAndGreedData(fearGreedIndexDaysCount)
fearGreedDataList = fearResponse?.dataList as ArrayList<FearAndGreedData>
val fearResponse = cryptoRepository.fetchFearAndGreedData(30)
fearGreedData = fearResponse?.dataList?.joinToString(separator = "\n") { data ->
"Время: ${data.timestamp}, Значение: ${data.value}, Классификация: ${data.valueClassification}"
} ?: "Ошибка загрузки данных страха и жадности"
}
val modelProducer = remember { CartesianChartModelProducer() }
LaunchedEffect(fearGreedDataList) {
modelProducer.runTransaction {
var numberValues = Array<Int>(fearGreedDataList.count()) { index -> fearGreedDataList[index].value.toInt() }
if(numberValues.isEmpty())
numberValues = Array<Int>(1) {0}
lineSeries { series(numberValues.toList()) }
}
}
val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState())
val zoomState = rememberVicoZoomState(initialZoom = Zoom.x(fearGreedIndexDaysCount.toDouble()))
// Отображаем результаты на странице
Scaffold (
// Отображаем результаты в виде простой страницы
Column(
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
.fillMaxSize()
.padding(16.dp)
.verticalScroll(rememberScrollState())
) {
Text(text = "Bitcoin Ticker Data", style = MaterialTheme.typography.bodyMedium)
Text(text = bitcoinTicker)
Spacer(modifier = Modifier.height(16.dp))
topBar = {
MediumTopAppBar(
colors = TopAppBarDefaults.topAppBarColors(
containerColor = MaterialTheme.colorScheme.primaryContainer,
titleContentColor = MaterialTheme.colorScheme.primary,
),
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
.fillMaxSize()
.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(),
startAxis = VerticalAxis.rememberStart(
titleComponent = rememberTextComponent(),
title = "FGI"
),
bottomAxis = HorizontalAxis.rememberBottom(
titleComponent = rememberTextComponent(),
title = "last $fearGreedIndexDaysCount days"
),
decorations = listOf(
remember {
HorizontalLine(
y = { 15.toDouble() },
line = LineComponent(fill(Color.Red), 2f),
labelComponent = TextComponent(
background =
shapeComponent(
fill(Color.Red),
CorneredShape.rounded(
bottomLeft = 4.dp,
bottomRight = 4.dp
)
),
),
label = { "Fear" },
verticalLabelPosition = Position.Vertical.Top
)
},
remember {
HorizontalLine(
y = { 60.toDouble() },
line = LineComponent(fill(Color.Green), 2f),
labelComponent = TextComponent(
background =
shapeComponent(
fill(Color.Green),
CorneredShape.rounded(
bottomLeft = 4.dp,
bottomRight = 4.dp
)
),
),
label = { "Greed" },
verticalLabelPosition = Position.Vertical.Bottom
)
}
),
),
modelProducer = modelProducer,
)
/* Fear Greed Chart End */
HorizontalDivider(thickness = 2.dp)
Text(
"Данные о Биткойне",
modifier = Modifier
.align(alignment = Alignment.CenterHorizontally),
fontSize = 24.sp
)
Text("Текущая цена: \n ${bitcoinTicker.priceUsd}\n")
Text("Суточный оборот: \n ${bitcoinTicker.volume24hUsd}\n")
Text("Капитализация: \n ${bitcoinTicker.marketCapUsd}\n")
Text(
"Изменение курса за: " +
"\n Сутки: ${bitcoinTicker.percentChange24h} " +
"\n Неделю: ${bitcoinTicker.percentChange7d}\n"
)
Text("Дата последнего обновления: \n ${bitcoinTicker.lastUpdated}\n")
Text(text = "Global Data", style = MaterialTheme.typography.bodyMedium)
Text(text = globalData)
Spacer(modifier = Modifier.height(16.dp))
HorizontalDivider(thickness = 2.dp)
Text(
"Глобальные данные",
modifier = Modifier
.align(alignment = Alignment.CenterHorizontally),
fontSize = 24.sp
)
Text("Общая капитализация крипторынка: \n ${globalData.totalMarketCapUsd}\n")
Text("Всего видов криптовалют: \n ${globalData.activeCryptocurrencies}\n")
Text("Суточный оборот других криптовалют: \n ${globalData.total24hVolumeUsd}\n")
Text("Процент доминации Биткоина: \n ${globalData.bitcoinPercentageOfMarketCap}\n")
}
}
Text(text = "Fear & Greed Index", style = MaterialTheme.typography.bodyMedium)
Text(text = fearGreedData)
}
}

View File

@ -7,19 +7,19 @@ typealias TickerResponse = List<TickerData>
// Сами данные
data class TickerData(
val id: String = "",
val name: String = "",
val symbol: String = "",
val rank: String = "",
@SerializedName("price_usd") val priceUsd: String = "",
@SerializedName("price_btc") val priceBtc: String = "",
@SerializedName("24h_volume_usd") val volume24hUsd: String = "",
@SerializedName("market_cap_usd") val marketCapUsd: String = "",
@SerializedName("available_supply") val availableSupply: String = "",
@SerializedName("total_supply") val totalSupply: String = "",
@SerializedName("max_supply") val maxSupply: String? = "",
@SerializedName("percent_change_1h") val percentChange1h: String = "",
@SerializedName("percent_change_24h") val percentChange24h: String = "",
@SerializedName("percent_change_7d") val percentChange7d: String = "",
@SerializedName("last_updated") val lastUpdated: String = ""
val id: String,
val name: String,
val symbol: String,
val rank: String,
@SerializedName("price_usd") val priceUsd: String,
@SerializedName("price_btc") val priceBtc: String,
@SerializedName("24h_volume_usd") val volume24hUsd: String,
@SerializedName("market_cap_usd") val marketCapUsd: String,
@SerializedName("available_supply") val availableSupply: String,
@SerializedName("total_supply") val totalSupply: String,
@SerializedName("max_supply") val maxSupply: String?,
@SerializedName("percent_change_1h") val percentChange1h: String,
@SerializedName("percent_change_24h") val percentChange24h: String,
@SerializedName("percent_change_7d") val percentChange7d: String,
@SerializedName("last_updated") val lastUpdated: String
)

View File

@ -12,7 +12,6 @@ lifecycleRuntimeKtx = "2.8.7"
activityCompose = "1.10.1"
composeBom = "2025.02.00"
retrofit = "2.9.0"
vico = "2.0.2"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -33,7 +32,6 @@ androidx-material3 = { group = "androidx.compose.material3", name = "material3"
kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "kotlinxCoroutinesAndroid" }
kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "kotlinxCoroutinesCore" }
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
vico-compose-m3 = { group = "com.patrykandpatrick.vico", name = "compose-m3", version.ref = "vico" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }