diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 14c6109..c230651 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -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) -} \ No newline at end of file +} diff --git a/app/src/main/java/ru/vendetti/bitcoin_summarizer/CryptoFragment.kt b/app/src/main/java/ru/vendetti/bitcoin_summarizer/CryptoFragment.kt index 8246cfb..ea2ba10 100644 --- a/app/src/main/java/ru/vendetti/bitcoin_summarizer/CryptoFragment.kt +++ b/app/src/main/java/ru/vendetti/bitcoin_summarizer/CryptoFragment.kt @@ -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 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() + + // 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 + } } } diff --git a/app/src/main/java/ru/vendetti/bitcoin_summarizer/StatisticsFull.kt b/app/src/main/java/ru/vendetti/bitcoin_summarizer/StatisticsFull.kt new file mode 100644 index 0000000..3f86d23 --- /dev/null +++ b/app/src/main/java/ru/vendetti/bitcoin_summarizer/StatisticsFull.kt @@ -0,0 +1,7 @@ +package ru.vendetti.bitcoin_summarizer + +class StatisticsFull ( + val FGI_list: ArrayList, + val TickerData: TickerData, + val GlobalData: GlobalResponse, +) \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 59ea3a6..41c738c 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" }