Compare commits

..

17 Commits

Author SHA1 Message Date
0906b95273
CI/CD image update
Some checks failed
Gitea Android Builder / Build (push) Failing after 20s
2025-03-05 21:11:15 +05:00
07ad6cb59d Merge pull request 'dev' (#16) from dev into main
All checks were successful
Gitea Android Builder / Build (push) Successful in 1m56s
Reviewed-on: #16
2025-03-05 03:51:27 +03:00
fcd27175ac Update app/src/main/java/ru/vendetti/bitcoin_summarizer/TickerResponse.kt 2025-03-05 03:50:25 +03:00
a9c5282bdc Update app/src/main/java/ru/vendetti/bitcoin_summarizer/GlobalResponse.kt 2025-03-05 03:50:08 +03:00
52386c1987 Update app/src/main/java/ru/vendetti/bitcoin_summarizer/MainActivity.kt 2025-03-05 03:49:25 +03:00
fb59407f8b Update gradle/libs.versions.toml 2025-03-05 03:48:52 +03:00
1261b80346 Update app/build.gradle.kts 2025-03-05 03:48:26 +03:00
a1b7569ef0 Merge pull request 'pipeline-test' (#15) from pipeline-test into main
All checks were successful
Gitea Android Builder / Build (push) Successful in 59s
Reviewed-on: #15
2025-03-04 22:06:06 +03:00
85c9bd50cd Merge pull request 'MVP' (#14) from dev into main
All checks were successful
Gitea Android Builder / Build (push) Successful in 1m9s
Reviewed-on: #14
2025-03-03 22:21:51 +03:00
d3d7bd391b Merge pull request 'Dependencies updated and example screen added' (#13) from dev into main
All checks were successful
Gitea Android Builder / Build (push) Successful in 2m20s
Reviewed-on: #13
2025-03-03 19:52:52 +03:00
e6d8a5b7aa Merge pull request 'Data classes and other retrofit logic added' (#12) from dev into main
All checks were successful
Gitea Android Builder / Build (push) Successful in 2m20s
Reviewed-on: #12
2025-03-03 19:28:18 +03:00
fa0905486a Merge pull request 'CI/CD revamp fix' (#11) from dev into main
All checks were successful
Gitea Android Builder / Build (push) Successful in 1m4s
Reviewed-on: #11
2025-02-27 20:36:18 +03:00
d0598853bd Merge pull request 'CI/CD revamp fix' (#10) from dev into main
All checks were successful
Gitea Android Builder / Build (push) Successful in 1m15s
Reviewed-on: #10
2025-02-27 20:34:09 +03:00
9dfdd328db Merge pull request 'CI/CD revamp fix' (#9) from dev into main
All checks were successful
Gitea Android Builder / Build (push) Successful in 3m52s
Reviewed-on: #9
2025-02-27 20:06:45 +03:00
fd77fbd97e Merge pull request 'CI/CD revamp fix' (#8) from dev into main
Some checks failed
Gitea Android Builder / Build (push) Failing after 5m38s
Reviewed-on: #8
2025-02-27 19:55:30 +03:00
08bdb6f481 Merge pull request 'CI/CD revamp fix' (#7) from dev into main
Some checks failed
Gitea Android Builder / Build (push) Failing after 1m50s
Reviewed-on: #7
2025-02-27 19:48:13 +03:00
f33906b965 Merge pull request 'dev' (#6) from dev into main
Some checks failed
Gitea Android Builder / Build (push) Failing after 29s
Reviewed-on: #6
2025-02-27 19:45:38 +03:00
6 changed files with 225 additions and 51 deletions

View File

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

View File

@ -61,5 +61,6 @@ dependencies {
implementation(libs.converter.gson) implementation(libs.converter.gson)
implementation(libs.kotlinx.coroutines.android) implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.core) 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( data class GlobalResponse(
@SerializedName("active_currencies") val activeCryptocurrencies: String, @SerializedName("active_currencies") val activeCryptocurrencies: String = "",
@SerializedName("total_market_cap_usd") val totalMarketCapUsd: String, @SerializedName("total_market_cap_usd") val totalMarketCapUsd: String = "",
@SerializedName("total_24h_volume_usd") val total24hVolumeUsd: String, @SerializedName("total_24h_volume_usd") val total24hVolumeUsd: String = "",
@SerializedName("bitcoin_percentage_of_market_cap") val bitcoinPercentageOfMarketCap: String @SerializedName("bitcoin_percentage_of_market_cap") val bitcoinPercentageOfMarketCap: String = ""
) )

View File

@ -1,26 +1,69 @@
package ru.vendetti.bitcoin_summarizer package ru.vendetti.bitcoin_summarizer
import android.annotation.SuppressLint
import android.os.Bundle import android.os.Bundle
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent import androidx.activity.compose.setContent
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.magnifier
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.verticalScroll 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.MaterialTheme
import androidx.compose.material3.MediumTopAppBar
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberTopAppBarState
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier 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.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 ru.vendetti.bitcoin_summarizer.ui.theme.BitcoinSummarizerTheme
import java.text.DateFormat
import java.time.format.DateTimeFormatter
class MainActivity : ComponentActivity() { class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -33,53 +76,181 @@ class MainActivity : ComponentActivity() {
} }
} }
@SuppressLint("MutableCollectionMutableState")
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable @Composable
fun CryptoScreen() { fun CryptoScreen() {
// Создаем репозиторий для работы с API // Создаем репозиторий для работы с API
val cryptoRepository = remember { CryptoRepository(RetrofitClient.apiService) } val cryptoRepository = remember { CryptoRepository(RetrofitClient.apiService) }
// Состояния для хранения результатов запросов // Состояния для хранения результатов запросов
var bitcoinTicker by remember { mutableStateOf("Загрузка Bitcoin Ticker...") } var bitcoinTicker by remember { mutableStateOf(TickerData()) }
var globalData by remember { mutableStateOf("Загрузка глобальных данных...") } var globalData by remember { mutableStateOf(GlobalResponse()) }
var fearGreedData by remember { mutableStateOf("Загрузка индекса страха и жадности...") } var fearGreedDataList by remember { mutableStateOf(ArrayList<FearAndGreedData>()) }
var fearGreedIndexDaysCount by remember { mutableIntStateOf(30) }
// Запускаем корутину для выполнения сетевых запросов // Запускаем корутину для выполнения сетевых запросов
LaunchedEffect(Unit) { LaunchedEffect(fearGreedIndexDaysCount) {
// Запрос Bitcoin Ticker // Запрос Bitcoin Ticker
val tickerResponse = cryptoRepository.fetchBitcoinTicker() val tickerResponse = cryptoRepository.fetchBitcoinTicker()
bitcoinTicker = tickerResponse?.joinToString(separator = "\n") { data -> bitcoinTicker = tickerResponse!!.first()
"Название: ${data.name}, Цена: ${data.priceUsd}, Обновлено: ${data.lastUpdated}"
} ?: "Ошибка загрузки Bitcoin Ticker"
// Запрос глобальных данных // Запрос глобальных данных
val globalResponse = cryptoRepository.fetchGlobalData() val globalResponse = cryptoRepository.fetchGlobalData()
globalData = globalResponse?.let { data -> globalData = globalResponse!!
"Активных криптовалют: ${data.activeCryptocurrencies}\nРыночная капитализация: ${data.totalMarketCapUsd}\n24h Объем: ${data.total24hVolumeUsd}"
} ?: "Ошибка загрузки глобальных данных"
// Запрос индекса страха и жадности // Запрос индекса страха и жадности
val fearResponse = cryptoRepository.fetchFearAndGreedData(30) val fearResponse = cryptoRepository.fetchFearAndGreedData(fearGreedIndexDaysCount)
fearGreedData = fearResponse?.dataList?.joinToString(separator = "\n") { data -> fearGreedDataList = fearResponse?.dataList as ArrayList<FearAndGreedData>
"Время: ${data.timestamp}, Значение: ${data.value}, Классификация: ${data.valueClassification}"
} ?: "Ошибка загрузки данных страха и жадности"
} }
// Отображаем результаты в виде простой страницы val modelProducer = remember { CartesianChartModelProducer() }
Column(
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 (
modifier = Modifier
.nestedScroll(scrollBehavior.nestedScrollConnection),
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 modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp) .padding(innerPadding)
.verticalScroll(rememberScrollState()) .verticalScroll(rememberScrollState())
) { ) {
Text(text = "Bitcoin Ticker Data", style = MaterialTheme.typography.bodyMedium) Column(
Text(text = bitcoinTicker) modifier = Modifier
Spacer(modifier = Modifier.height(16.dp)) .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) HorizontalDivider(thickness = 2.dp)
Text(text = globalData) Text(
Spacer(modifier = Modifier.height(16.dp)) "Глобальные данные",
modifier = Modifier
Text(text = "Fear & Greed Index", style = MaterialTheme.typography.bodyMedium) .align(alignment = Alignment.CenterHorizontally),
Text(text = fearGreedData) fontSize = 24.sp
)
Text("Общая капитализация крипторынка: \n ${globalData.totalMarketCapUsd}\n")
Text("Всего видов криптовалют: \n ${globalData.activeCryptocurrencies}\n")
Text("Суточный оборот других криптовалют: \n ${globalData.total24hVolumeUsd}\n")
Text("Процент доминации Биткоина: \n ${globalData.bitcoinPercentageOfMarketCap}\n")
}
}
} }
} }

View File

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

View File

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