Compare commits

..

2 Commits
1.3.1 ... main

Author SHA1 Message Date
dbb9dee02b Merge pull request 'final tweaks' (#28) from dev into main
All checks were successful
Gitea Android Builder / Build (push) Successful in 3m44s
Reviewed-on: #28
2025-03-12 15:31:13 +03:00
b3s23
ed305ffeba Added saving data to internal storage for later use.
Added dialog on internal storage usage, and for first start without internet connection
2025-03-12 17:30:23 +05:00
4 changed files with 150 additions and 29 deletions

View File

@ -1,3 +1,4 @@
plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.android)
@ -64,5 +65,6 @@ dependencies {
implementation(libs.kotlinx.coroutines.core)
implementation(libs.vico.compose.m3)
implementation(libs.cicerone)
implementation(libs.androidx.datastore)
}
}

View File

@ -1,21 +1,33 @@
package ru.vendetti.bitcoin_summarizer
import android.annotation.SuppressLint
import android.content.Context
import android.content.pm.PackageManager
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.result.contract.ActivityResultContracts
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.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
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.AlertDialog
import androidx.compose.material3.ButtonColors
import androidx.compose.material3.ButtonDefaults
import androidx.compose.material3.Card
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.DrawerValue
import androidx.compose.material3.ExperimentalMaterial3Api
@ -23,11 +35,10 @@ 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.ModalDrawerSheet
import androidx.compose.material3.ModalNavigationDrawer
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.material3.rememberDrawerState
import androidx.compose.material3.rememberTopAppBarState
@ -42,13 +53,19 @@ 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.graphics.vector.ImageVector
import androidx.compose.ui.input.nestedscroll.nestedScroll
import androidx.compose.ui.platform.ComposeView
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.text.style.TextAlign
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 androidx.compose.ui.window.Dialog
import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment
import com.google.gson.Gson
import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLabelComponent
import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLineComponent
@ -80,6 +97,9 @@ import kotlinx.coroutines.launch
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.io.FileInputStream
import java.io.FileOutputStream
import java.lang.StringBuilder
import java.text.DecimalFormat
import java.time.Instant
import java.time.format.DateTimeFormatter
@ -102,11 +122,51 @@ class CryptoFragment: Fragment() {
}
}
@Composable
fun ShowAlertDialog(
onDismissRequest: () -> Unit,
onConfirmation: () -> Unit,
dialogTitle: String,
dialogText: String,
) {
AlertDialog(
icon = {},
title = {
Text(text = dialogTitle)
},
text = {
Text(text = dialogText)
},
onDismissRequest = {
onDismissRequest()
},
confirmButton = {},
dismissButton = {
TextButton(
onClick = {
onDismissRequest()
}
) {
Text(
text = "Окей",
color = MaterialTheme.colorScheme.onPrimary
)
}
},
containerColor = MaterialTheme.colorScheme.primary,
titleContentColor = MaterialTheme.colorScheme.onPrimary,
textContentColor = MaterialTheme.colorScheme.onPrimary,
)
}
@SuppressLint("MutableCollectionMutableState", "SimpleDateFormat")
@OptIn(ExperimentalMaterial3Api::class)
@Preview
@Composable
fun CryptoComposable() {
val openAlertDialog1 = remember { mutableStateOf(false) }
val openAlertDialog2 = remember { mutableStateOf(false) }
// Создаем репозиторий для работы с API
val cryptoRepository = remember { CryptoRepository(RetrofitClient.apiService) }
// Состояния для хранения результатов запросов
@ -116,8 +176,36 @@ fun CryptoComposable() {
var fearGreedIndexDaysCount by remember { mutableIntStateOf(30) }
val gson = Gson()
val context = LocalContext.current
when {
openAlertDialog1.value -> {
ShowAlertDialog(
onDismissRequest = {openAlertDialog1.value = false},
onConfirmation = {},
dialogTitle = "Внимание!",
dialogText = "Отсутствует интернет-подключение, загружаю сохраненные данные. " +
"" +
"Обратите внимание, что данные могут быть неактуальными.",
)
}
openAlertDialog2.value -> {
ShowAlertDialog(
onDismissRequest = {
openAlertDialog2.value = false
App.INSTANCE.router.exit()
},
onConfirmation = {},
dialogTitle = "Внимание!",
dialogText = "При первом подключении необходимо подключение к интернету!",
)
}
}
// Запускаем корутину для выполнения сетевых запросов
LaunchedEffect(fearGreedIndexDaysCount) {
LaunchedEffect(fearGreedIndexDaysCount, context) {
try {
// Запрос Bitcoin Ticker
val tickerResponse = cryptoRepository.fetchBitcoinTicker()
@ -131,31 +219,53 @@ fun CryptoComposable() {
val fearResponse = cryptoRepository.fetchFearAndGreedData(fearGreedIndexDaysCount)
fearGreedDataList = fearResponse?.dataList as ArrayList<FearAndGreedData>
fearGreedDataList.reverse()
}catch (e: Exception) {
bitcoinTicker = TickerData(
id = "",
name = "",
symbol = "",
rank = "",
priceUsd = "",
priceBtc = "",
volume24hUsd = "",
marketCapUsd = "",
availableSupply = "",
totalSupply = "",
maxSupply = "",
percentChange1h = "",
percentChange24h = "",
percentChange7d = "",
lastUpdated = ""
// *** Сохраняем на диск ***
// 1) Сбор данных в один класс
val fullStatistics = StatisticsFull(
FGI_list = fearGreedDataList,
TickerData = bitcoinTicker,
GlobalData = globalData,
)
globalData = GlobalResponse(
activeCryptocurrencies = "",
totalMarketCapUsd = "",
total24hVolumeUsd = "",
bitcoinPercentageOfMarketCap = ""
)
fearGreedDataList = ArrayList<FearAndGreedData>()
// 2) Перевод класса в json-строку
val statisticsJsonString = gson.toJson(fullStatistics)
// 3) Сохранение в файл во внутреннем хранилище
val fos: FileOutputStream =
context.openFileOutput("statistics.txt", Context.MODE_PRIVATE)
fos.write(statisticsJsonString.toByteArray())
fos.flush()
fos.close()
}
catch (e: Exception) {
try {
// *** Загрузка данных из файла ***
// 1) Получение строки из файла
val fis: FileInputStream =
context.openFileInput("statistics.txt")
var a: Int
val temp = StringBuilder()
while (fis.read().also { a = it } != -1) {
temp.append(a.toChar())
}
// Показ предупреждения.
openAlertDialog1.value = true
val statisticsJsonString = temp.toString()
// 2) Перевод строки в класс
val statisticsFull = gson.fromJson(statisticsJsonString, StatisticsFull::class.java)
// 3) Распределение данных по переменным
bitcoinTicker = statisticsFull.TickerData
globalData = statisticsFull.GlobalData
fearGreedDataList = statisticsFull.FGI_list
}
catch (e: Exception) {
openAlertDialog2.value = true
}
}
}

View File

@ -0,0 +1,7 @@
package ru.vendetti.bitcoin_summarizer
class StatisticsFull (
val FGI_list: ArrayList<FearAndGreedData>,
val TickerData: TickerData,
val GlobalData: GlobalResponse,
)

View File

@ -15,6 +15,7 @@ retrofit = "2.9.0"
vico = "2.0.2"
ciceroneVer = "7.1"
fragmentKtx = "1.8.6"
protoDataStore = "1.1.3"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@ -38,6 +39,7 @@ retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit
vico-compose-m3 = { group = "com.patrykandpatrick.vico", name = "compose-m3", version.ref = "vico" }
cicerone = {module = "com.github.terrakok:cicerone", version.ref = "ciceroneVer"}
androidx-fragment-ktx = { group = "androidx.fragment", name = "fragment-ktx", version.ref = "fragmentKtx" }
androidx-datastore = {group = "androidx.datastore", name = "datastore", version.ref = "protoDataStore"}
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }