Compare commits
	
		
			1 Commits
		
	
	
		
			main
			...
			pipeline-t
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| eeda7031dc | 
| @@ -1,4 +1,3 @@ | ||||
|  | ||||
| plugins { | ||||
|     alias(libs.plugins.android.application) | ||||
|     alias(libs.plugins.kotlin.android) | ||||
| @@ -50,7 +49,6 @@ dependencies { | ||||
|     implementation(libs.androidx.ui.graphics) | ||||
|     implementation(libs.androidx.ui.tooling.preview) | ||||
|     implementation(libs.androidx.material3) | ||||
|     implementation(libs.androidx.fragment.ktx) | ||||
|     testImplementation(libs.junit) | ||||
|     androidTestImplementation(libs.androidx.junit) | ||||
|     androidTestImplementation(libs.androidx.espresso.core) | ||||
| @@ -64,7 +62,5 @@ dependencies { | ||||
|     implementation(libs.kotlinx.coroutines.android) | ||||
|     implementation(libs.kotlinx.coroutines.core) | ||||
|     implementation(libs.vico.compose.m3) | ||||
|     implementation(libs.cicerone) | ||||
|     implementation(libs.androidx.datastore) | ||||
|  | ||||
| } | ||||
| } | ||||
| @@ -3,7 +3,6 @@ | ||||
|     xmlns:tools="http://schemas.android.com/tools"> | ||||
|     <uses-permission android:name="android.permission.INTERNET" /> | ||||
|     <application | ||||
|         android:name=".App" | ||||
|         android:allowBackup="false" | ||||
|         android:dataExtractionRules="@xml/data_extraction_rules" | ||||
|         android:fullBackupContent="@xml/backup_rules" | ||||
|   | ||||
| @@ -1,224 +0,0 @@ | ||||
| package ru.vendetti.bitcoin_summarizer | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.compose.foundation.Image | ||||
| 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.layout.width | ||||
| import androidx.compose.foundation.rememberScrollState | ||||
| import androidx.compose.foundation.text.selection.SelectionContainer | ||||
| 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.CenterAlignedTopAppBar | ||||
| import androidx.compose.material3.DrawerValue | ||||
| 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.ModalNavigationDrawer | ||||
| import androidx.compose.material3.Scaffold | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.material3.TopAppBarDefaults | ||||
| import androidx.compose.material3.rememberDrawerState | ||||
| import androidx.compose.material3.rememberTopAppBarState | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.rememberCoroutineScope | ||||
| 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.platform.ClipboardManager | ||||
| import androidx.compose.ui.platform.ComposeView | ||||
| import androidx.compose.ui.platform.LocalClipboardManager | ||||
| import androidx.compose.ui.res.painterResource | ||||
| import androidx.compose.ui.text.AnnotatedString | ||||
| import androidx.compose.ui.text.ExperimentalTextApi | ||||
| import androidx.compose.ui.text.LinkAnnotation | ||||
| import androidx.compose.ui.text.SpanStyle | ||||
| import androidx.compose.ui.text.TextLinkStyles | ||||
| import androidx.compose.ui.text.buildAnnotatedString | ||||
| import androidx.compose.ui.text.style.TextOverflow | ||||
| import androidx.compose.ui.text.withLink | ||||
| import androidx.compose.ui.text.withStyle | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.unit.sp | ||||
| import androidx.fragment.app.Fragment | ||||
| import kotlinx.coroutines.launch | ||||
| import ru.vendetti.bitcoin_summarizer.ui.theme.BitcoinSummarizerTheme | ||||
| import ru.vendetti.bitcoin_summarizer.ui.theme.Flame | ||||
|  | ||||
| class AboutFragment: Fragment() { | ||||
|     override fun onCreateView( | ||||
|         inflater: LayoutInflater, | ||||
|         container: ViewGroup?, | ||||
|         savedInstanceState: Bundle? | ||||
|     ): View { | ||||
|         return inflater.inflate(R.layout.fragment_compose_view, | ||||
|             container, | ||||
|             false).apply { | ||||
|             findViewById<ComposeView>(R.id.compose_view).setContent { | ||||
|                 BitcoinSummarizerTheme { | ||||
|                     AboutComposable() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @OptIn(ExperimentalMaterial3Api::class, ExperimentalTextApi::class) | ||||
| @Composable | ||||
| @Preview | ||||
| fun AboutComposable() { | ||||
|     val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) | ||||
|     val scope = rememberCoroutineScope() | ||||
|  | ||||
|     ModalNavigationDrawer( | ||||
|         drawerState = drawerState, | ||||
|         drawerContent = { | ||||
|             MyHamburgerModal(ScreenKey.About) | ||||
|         } | ||||
|     ) { | ||||
|         Scaffold( | ||||
|             modifier = Modifier, | ||||
|  | ||||
|             topBar = { | ||||
|                 CenterAlignedTopAppBar( | ||||
|                     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( | ||||
|                             "About", | ||||
|                             maxLines = 1, | ||||
|                             overflow = TextOverflow.Ellipsis | ||||
|                         ) | ||||
|                     }, | ||||
|                     navigationIcon = { | ||||
|                         IconButton(onClick = { | ||||
|                             scope.launch { | ||||
|                                 drawerState.apply { | ||||
|                                     if(isClosed) open() else close() | ||||
|                                 } | ||||
|                             } | ||||
|                         }) { | ||||
|                             Icon( | ||||
|                                 imageVector = Icons.Filled.Menu, | ||||
|                                 contentDescription = "Navigation hamburger menu" | ||||
|                             ) | ||||
|                         } | ||||
|                     }, | ||||
|                     scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) | ||||
|                 ) | ||||
|             }, | ||||
|         ) | ||||
|         { innerPadding -> | ||||
|             Box( | ||||
|                 modifier = Modifier | ||||
|                     .background(Color.Transparent) | ||||
|                     .padding(innerPadding) | ||||
|                     .verticalScroll(rememberScrollState()) | ||||
|             ) { | ||||
|                 Column( | ||||
|                     modifier = Modifier | ||||
|                         .padding(16.dp, 16.dp, 16.dp, 36.dp) | ||||
|                 ) { | ||||
|                     Icon( | ||||
|                         painter = painterResource(id = R.mipmap.ic_launcher_foreground), | ||||
|                         contentDescription = null, | ||||
|                         modifier = Modifier | ||||
|                             .width(160.dp) | ||||
|                             .height(160.dp) | ||||
|                             .align(Alignment.CenterHorizontally) | ||||
|                     ) | ||||
|  | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|                     Text( | ||||
|                         "Репозиторий проекта", | ||||
|                         modifier = Modifier | ||||
|                             .align(alignment = Alignment.CenterHorizontally), | ||||
|                         fontSize = 24.sp | ||||
|                     ) | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|  | ||||
|                     val repositoryLinkString = buildAnnotatedString { | ||||
|                         withLink(LinkAnnotation.Url( | ||||
|                             url = "https://git.vendetti.ru/andy/bitcoin-summarizer/tags", | ||||
|                             styles = TextLinkStyles(style = SpanStyle(color = Flame)) | ||||
|                         ) | ||||
|                         ) { | ||||
|                             append("Ссылка на репозиторий") | ||||
|                         } | ||||
|                     } | ||||
|                     Text(text = repositoryLinkString) | ||||
|  | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|                     HorizontalDivider() | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|  | ||||
|                     Text( | ||||
|                         "Авторы", | ||||
|                         modifier = Modifier | ||||
|                             .align(alignment = Alignment.CenterHorizontally), | ||||
|                         fontSize = 24.sp | ||||
|                     ) | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|  | ||||
|                     Text("Студенты гр. 9ИСП-42-21, ГБПОУ УКРТБ") | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|  | ||||
|                     Text("Васильев Андрей Дмитриевич - Back-end: CI/CD (Continuous Integration), Api interactions, Database interactions") | ||||
|                     Spacer(modifier = Modifier.height(8.dp)) | ||||
|                     SelectionContainer { | ||||
|                         Text("andy@vendetti.ru", color = Flame) | ||||
|                     } | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|  | ||||
|                     Text("Зубарев Артемий Альбертович - Front-end: Charts, App navigation (Cicerone)") | ||||
|  | ||||
|                     Spacer(modifier = Modifier.height(8.dp)) | ||||
|                     SelectionContainer { | ||||
|                         Text("artemiy.work32@gmail.com", color = Flame) | ||||
|                     } | ||||
|  | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|                     HorizontalDivider() | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|  | ||||
|                     Text( | ||||
|                         "Используемый API", | ||||
|                         modifier = Modifier | ||||
|                             .align(alignment = Alignment.CenterHorizontally), | ||||
|                         fontSize = 24.sp | ||||
|                     ) | ||||
|  | ||||
|                     val apiLinkString = buildAnnotatedString { | ||||
|                         withLink(LinkAnnotation.Url( | ||||
|                             url = "https://alternative.me/crypto/api/", | ||||
|                             styles = TextLinkStyles(style = SpanStyle(color = Flame)) | ||||
|                         ) | ||||
|                         ) { | ||||
|                             append("Ссылка на API") | ||||
|                         } | ||||
|                     } | ||||
|                     Text(text = apiLinkString) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| package ru.vendetti.bitcoin_summarizer | ||||
|  | ||||
| import android.app.Application | ||||
| import android.os.Debug | ||||
| import android.util.Log | ||||
| import com.github.terrakok.cicerone.Cicerone | ||||
|  | ||||
| class App : Application() { | ||||
|     private val cicerone = Cicerone.create() | ||||
|     val router get() = cicerone.router | ||||
|     val navigatorHolder get() = cicerone.getNavigatorHolder() | ||||
|  | ||||
|     override fun onCreate() { | ||||
|         super.onCreate() | ||||
|         INSTANCE = this | ||||
|         Log.println(Log.DEBUG, "App", "Instance is $INSTANCE") | ||||
|     } | ||||
|  | ||||
|     companion object { | ||||
|         internal lateinit var INSTANCE: App | ||||
|             private set | ||||
|     } | ||||
| } | ||||
| @@ -1,521 +0,0 @@ | ||||
| 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.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 | ||||
| import androidx.compose.material3.HorizontalDivider | ||||
| import androidx.compose.material3.Icon | ||||
| import androidx.compose.material3.IconButton | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| 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 | ||||
| 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.rememberCoroutineScope | ||||
| 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 | ||||
| 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.rememberVicoScrollState | ||||
| 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 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 | ||||
|  | ||||
| class CryptoFragment: Fragment() { | ||||
|     override fun onCreateView( | ||||
|         inflater: LayoutInflater, | ||||
|         container: ViewGroup?, | ||||
|         savedInstanceState: Bundle? | ||||
|     ): View { | ||||
|         return inflater.inflate(R.layout.fragment_compose_view, | ||||
|             container, | ||||
|             false).apply { | ||||
|             findViewById<ComposeView>(R.id.compose_view).setContent { | ||||
|                 BitcoinSummarizerTheme { | ||||
|                     CryptoComposable() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @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) } | ||||
|     // Состояния для хранения результатов запросов | ||||
|     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) } | ||||
|  | ||||
|     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, context) { | ||||
|         try { | ||||
|             // Запрос 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<FearAndGreedData> | ||||
|             fearGreedDataList.reverse() | ||||
|  | ||||
|             // *** Сохраняем на диск *** | ||||
|             // 1) Сбор данных в один класс | ||||
|             val fullStatistics = StatisticsFull( | ||||
|                 FGI_list = fearGreedDataList, | ||||
|                 TickerData = bitcoinTicker, | ||||
|                 GlobalData = globalData, | ||||
|             ) | ||||
|  | ||||
|             // 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 | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     val modelProducer = remember { CartesianChartModelProducer() } | ||||
|  | ||||
|     LaunchedEffect(fearGreedDataList) { | ||||
|         modelProducer.runTransaction { | ||||
|             var numberValues = Array(fearGreedDataList.count()) { | ||||
|                     index -> | ||||
|                 fearGreedDataList[index] | ||||
|                     .value.toInt() | ||||
|             } | ||||
|  | ||||
|             if(numberValues.isEmpty()) | ||||
|                 numberValues = Array(1) {0} | ||||
|  | ||||
|             lineSeries { series(numberValues.toList()) } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     val scrollBehavior = TopAppBarDefaults.enterAlwaysScrollBehavior(rememberTopAppBarState()) | ||||
|  | ||||
|     // Отображаем результаты на странице | ||||
|  | ||||
|     val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) | ||||
|     val scope = rememberCoroutineScope() | ||||
|  | ||||
|     ModalNavigationDrawer( | ||||
|         drawerState = drawerState, | ||||
|         drawerContent = { | ||||
|             MyHamburgerModal(ScreenKey.Crypto) | ||||
|         } | ||||
|     ) { | ||||
|         Scaffold( | ||||
|             modifier = Modifier | ||||
|                 .nestedScroll(scrollBehavior.nestedScrollConnection), | ||||
|  | ||||
|             topBar = { | ||||
|                 CenterAlignedTopAppBar( | ||||
|                     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( | ||||
|                             "Crypto statistics", | ||||
|                             maxLines = 1, | ||||
|                             overflow = TextOverflow.Ellipsis | ||||
|                         ) | ||||
|                     }, | ||||
|                     navigationIcon = { | ||||
|                         IconButton(onClick = { | ||||
|                             scope.launch { | ||||
|                                 drawerState.apply { | ||||
|                                     if(isClosed) open() else close() | ||||
|                                 } | ||||
|                             } | ||||
|                         }) { | ||||
|                             Icon( | ||||
|                                 imageVector = Icons.Filled.Menu, | ||||
|                                 contentDescription = "Navigation hamburger menu" | ||||
|                             ) | ||||
|                         } | ||||
|                     }, | ||||
|                     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 */ | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|                     Text( | ||||
|                         "Индекс страха/жадности", | ||||
|                         modifier = Modifier | ||||
|                             .align(alignment = Alignment.CenterHorizontally), | ||||
|                         fontSize = 24.sp | ||||
|                     ) | ||||
|                     Spacer(modifier = Modifier.height(16.dp)) | ||||
|                     if (fearGreedDataList.count() > 1) { | ||||
|                         CartesianChartHost( | ||||
|                             zoomState = rememberVicoZoomState( | ||||
|                                 zoomEnabled = true, | ||||
|                                 initialZoom = Zoom.x(fearGreedIndexDaysCount.toDouble()), | ||||
|                             ), | ||||
|                             scrollState = rememberVicoScrollState(scrollEnabled = true), | ||||
|                             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( | ||||
|                                     valueFormatter = { _, value, _ -> | ||||
|                                         val date = DateTimeFormatter.ISO_INSTANT | ||||
|                                             .format(Instant.ofEpochSecond(fearGreedDataList[value.toInt()].timestamp.toLong())) | ||||
|                                         val dateComponents = date.split("T")[0].split("-") | ||||
|                                         // returns dd.mm | ||||
|                                         "${dateComponents[2]}.${dateComponents[1]}" | ||||
|                                     }, | ||||
|                                     title = "last $fearGreedIndexDaysCount days", | ||||
|                                     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, | ||||
|                         ) | ||||
|                     } else { | ||||
|                         Text("Загрузка...") | ||||
|                     } | ||||
|                     /* 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 decimalFormatter = DecimalFormat("0.00") | ||||
|                     val largeNumberFormatter = DecimalFormat("#,###") | ||||
|  | ||||
|                     if (bitcoinTicker.lastUpdated.isNotEmpty()) { | ||||
|                         Text("Текущая цена: \n \$ ${decimalFormatter.format(bitcoinTicker.priceUsd.toFloat())}\n") | ||||
|                         Text("Суточный оборот: \n \$ ${largeNumberFormatter.format(bitcoinTicker.volume24hUsd.toFloat())}\n") | ||||
|                         Text("Капитализация: \n \$ ${largeNumberFormatter.format(bitcoinTicker.marketCapUsd.toFloat())}\n") | ||||
|                         Text( | ||||
|                             "Изменение курса за: " + | ||||
|                                     "\n Сутки: ${decimalFormatter.format(bitcoinTicker.percentChange24h.toFloat())}% " + | ||||
|                                     "\n Неделю: ${decimalFormatter.format(bitcoinTicker.percentChange7d.toFloat())}%\n" | ||||
|                         ) | ||||
|  | ||||
|                         val humanDate = DateTimeFormatter.ISO_INSTANT | ||||
|                             .format(Instant.ofEpochSecond(bitcoinTicker.lastUpdated.toLong())) | ||||
|  | ||||
|                         Text( | ||||
|                             "Время последнего обновления: \n ${humanDate.split("T")[0]}, " + | ||||
|                                     "${ | ||||
|                                         humanDate.split("T")[1].replace( | ||||
|                                             Regex(":[0-9]+[A-Z]"), | ||||
|                                             "" | ||||
|                                         ) | ||||
|                                     }\n" | ||||
|                         ) | ||||
|                     } else { | ||||
|                         Text("Загрузка...") | ||||
|                     } | ||||
|                     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)) | ||||
|                     if (globalData.totalMarketCapUsd.isNotEmpty()) { | ||||
|                         Text( | ||||
|                             "Общая капитализация крипторынка: \n \$ ${ | ||||
|                                 largeNumberFormatter.format( | ||||
|                                     globalData.totalMarketCapUsd.toFloat() | ||||
|                                 ) | ||||
|                             }\n" | ||||
|                         ) | ||||
|                         Text("Всего тикеров: \n ${globalData.activeCryptocurrencies}\n") | ||||
|                         Text( | ||||
|                             "Суточный оборот всех криптовалют: \n \$ ${ | ||||
|                                 largeNumberFormatter.format( | ||||
|                                     globalData.total24hVolumeUsd.toFloat() | ||||
|                                 ) | ||||
|                             }\n" | ||||
|                         ) | ||||
|                         Text("Процент доминации Биткоина: \n ${decimalFormatter.format(globalData.bitcoinPercentageOfMarketCap.toFloat())}%\n") | ||||
|                     } else { | ||||
|                         Text("Загрузка...") | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,126 +0,0 @@ | ||||
| package ru.vendetti.bitcoin_summarizer | ||||
|  | ||||
| import android.os.Bundle | ||||
| import android.view.LayoutInflater | ||||
| import android.view.View | ||||
| import android.view.ViewGroup | ||||
| import androidx.compose.foundation.background | ||||
| import androidx.compose.foundation.layout.Box | ||||
| import androidx.compose.foundation.layout.Column | ||||
| 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.CenterAlignedTopAppBar | ||||
| import androidx.compose.material3.DrawerValue | ||||
| import androidx.compose.material3.ExperimentalMaterial3Api | ||||
| import androidx.compose.material3.Icon | ||||
| import androidx.compose.material3.IconButton | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.MediumTopAppBar | ||||
| import androidx.compose.material3.ModalNavigationDrawer | ||||
| import androidx.compose.material3.Scaffold | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.material3.TopAppBarDefaults | ||||
| import androidx.compose.material3.rememberDrawerState | ||||
| import androidx.compose.material3.rememberTopAppBarState | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.runtime.rememberCoroutineScope | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.graphics.Color | ||||
| import androidx.compose.ui.input.nestedscroll.nestedScroll | ||||
| import androidx.compose.ui.platform.ComposeView | ||||
| import androidx.compose.ui.text.style.TextOverflow | ||||
| import androidx.compose.ui.tooling.preview.Preview | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.fragment.app.Fragment | ||||
| import kotlinx.coroutines.launch | ||||
| import ru.vendetti.bitcoin_summarizer.ui.theme.BitcoinSummarizerTheme | ||||
|  | ||||
| class LearnFragment: Fragment() { | ||||
|     override fun onCreateView( | ||||
|         inflater: LayoutInflater, | ||||
|         container: ViewGroup?, | ||||
|         savedInstanceState: Bundle? | ||||
|     ): View { | ||||
|         return inflater.inflate(R.layout.fragment_compose_view, | ||||
|             container, | ||||
|             false).apply { | ||||
|             findViewById<ComposeView>(R.id.compose_view).setContent { | ||||
|                 BitcoinSummarizerTheme { | ||||
|                     LearnComposable() | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @OptIn(ExperimentalMaterial3Api::class) | ||||
| @Composable | ||||
| @Preview | ||||
| fun LearnComposable() { | ||||
|     val drawerState = rememberDrawerState(initialValue = DrawerValue.Closed) | ||||
|     val scope = rememberCoroutineScope() | ||||
|  | ||||
|     ModalNavigationDrawer( | ||||
|         drawerState = drawerState, | ||||
|         drawerContent = { | ||||
|             MyHamburgerModal(ScreenKey.Learn) | ||||
|         } | ||||
|     ) { | ||||
|         Scaffold( | ||||
|             modifier = Modifier, | ||||
|  | ||||
|             topBar = { | ||||
|                 CenterAlignedTopAppBar( | ||||
|                     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( | ||||
|                             "Learn", | ||||
|                             maxLines = 1, | ||||
|                             overflow = TextOverflow.Ellipsis | ||||
|                         ) | ||||
|                     }, | ||||
|                     navigationIcon = { | ||||
|                         IconButton(onClick = { | ||||
|                             scope.launch { | ||||
|                                 drawerState.apply { | ||||
|                                     if(isClosed) open() else close() | ||||
|                                 } | ||||
|                             } | ||||
|                         }) { | ||||
|                             Icon( | ||||
|                                 imageVector = Icons.Filled.Menu, | ||||
|                                 contentDescription = "Navigation hamburger menu" | ||||
|                             ) | ||||
|                         } | ||||
|                     }, | ||||
|                     scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()) | ||||
|                 ) | ||||
|             }, | ||||
|         ) | ||||
|         { innerPadding -> | ||||
|             Box( | ||||
|                 modifier = Modifier | ||||
|                     .background(Color.Transparent) | ||||
|                     .padding(innerPadding) | ||||
|                     .verticalScroll(rememberScrollState()) | ||||
|             ) { | ||||
|                 Column ( | ||||
|                         modifier = Modifier | ||||
|                             .padding(16.dp, 16.dp, 16.dp, 36.dp) | ||||
|                         ) { | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -2,7 +2,6 @@ package ru.vendetti.bitcoin_summarizer | ||||
|  | ||||
| import android.annotation.SuppressLint | ||||
| import android.os.Bundle | ||||
| import android.widget.FrameLayout | ||||
| import androidx.activity.ComponentActivity | ||||
| import androidx.activity.compose.setContent | ||||
| import androidx.compose.foundation.background | ||||
| @@ -41,8 +40,6 @@ 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.fragment.app.FragmentActivity | ||||
| import com.github.terrakok.cicerone.androidx.AppNavigator | ||||
| import com.patrykandpatrick.vico.compose.cartesian.CartesianChartHost | ||||
| import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLabelComponent | ||||
| import com.patrykandpatrick.vico.compose.cartesian.axis.rememberAxisLineComponent | ||||
| @@ -75,25 +72,234 @@ import ru.vendetti.bitcoin_summarizer.ui.theme.Green2 | ||||
| import java.text.DecimalFormat | ||||
| import java.time.format.DateTimeFormatter | ||||
|  | ||||
| class MainActivity : FragmentActivity() { | ||||
|  | ||||
|     private val navigator = AppNavigator(this, R.id.container) | ||||
|  | ||||
| class MainActivity : ComponentActivity() { | ||||
|     override fun onCreate(savedInstanceState: Bundle?) { | ||||
|         super.onCreate(savedInstanceState) | ||||
|         setContentView(R.layout.fragment_container_main) | ||||
|  | ||||
|         // start the root fragment | ||||
|         App.INSTANCE.router.newRootScreen(Screens.Crypto) | ||||
|     } | ||||
|  | ||||
|     override fun onResumeFragments() { | ||||
|         super.onResumeFragments() | ||||
|         App.INSTANCE.navigatorHolder.setNavigator(navigator) | ||||
|     } | ||||
|  | ||||
|     override fun onPause() { | ||||
|         App.INSTANCE.navigatorHolder.removeNavigator() | ||||
|         super.onPause() | ||||
|         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<FearAndGreedData>()) } | ||||
|  | ||||
|     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<FearAndGreedData> | ||||
|     } | ||||
|  | ||||
|     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") | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,72 +0,0 @@ | ||||
| package ru.vendetti.bitcoin_summarizer | ||||
|  | ||||
| import androidx.compose.foundation.layout.padding | ||||
| import androidx.compose.material3.HorizontalDivider | ||||
| import androidx.compose.material3.MaterialTheme | ||||
| import androidx.compose.material3.ModalDrawerSheet | ||||
| import androidx.compose.material3.NavigationDrawerItem | ||||
| import androidx.compose.material3.NavigationDrawerItemColors | ||||
| import androidx.compose.material3.NavigationDrawerItemDefaults | ||||
| import androidx.compose.material3.Text | ||||
| import androidx.compose.runtime.Composable | ||||
| import androidx.compose.ui.Modifier | ||||
| import androidx.compose.ui.unit.dp | ||||
| import androidx.compose.ui.unit.sp | ||||
| import ru.vendetti.bitcoin_summarizer.ui.theme.BitcoinSummarizerTheme | ||||
|  | ||||
| @Composable | ||||
| fun MyHamburgerModal( | ||||
|     screenKey: ScreenKey | ||||
| ) { | ||||
|     ModalDrawerSheet( | ||||
|         drawerContainerColor = MaterialTheme.colorScheme.primary, | ||||
|         drawerContentColor = MaterialTheme.colorScheme.onPrimary, | ||||
|     ) { | ||||
|         Text("Bitcoin summarizer", | ||||
|             modifier = Modifier.padding(16.dp), | ||||
|             fontSize = 24.sp) | ||||
|         HorizontalDivider() | ||||
|         NavigationDrawerItem( | ||||
|             colors = NavigationDrawerItemDefaults.colors( | ||||
|                 selectedContainerColor = MaterialTheme.colorScheme.tertiary, | ||||
|                 selectedTextColor = MaterialTheme.colorScheme.onTertiary, | ||||
|                 unselectedTextColor = MaterialTheme.colorScheme.onPrimary, | ||||
|             ), | ||||
|             label = { Text("Crypto statistics") }, | ||||
|             selected = (screenKey == ScreenKey.Crypto), | ||||
|             onClick = { | ||||
|                 if (screenKey != ScreenKey.Crypto) { | ||||
|                     App.INSTANCE.router.replaceScreen(Screens.Crypto) | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
|         NavigationDrawerItem( | ||||
|             colors = NavigationDrawerItemDefaults.colors( | ||||
|                 selectedContainerColor = MaterialTheme.colorScheme.tertiary, | ||||
|                 selectedTextColor = MaterialTheme.colorScheme.onTertiary, | ||||
|                 unselectedTextColor = MaterialTheme.colorScheme.onPrimary, | ||||
|             ), | ||||
|             label = { Text("About") }, | ||||
|             selected = (screenKey == ScreenKey.About), | ||||
|             onClick = { | ||||
|                 if (screenKey != ScreenKey.About) { | ||||
|                     App.INSTANCE.router.replaceScreen(Screens.About) | ||||
|                 } | ||||
|             } | ||||
|         ) | ||||
|         /*NavigationDrawerItem( | ||||
|             colors = NavigationDrawerItemDefaults.colors( | ||||
|                 selectedContainerColor = MaterialTheme.colorScheme.tertiary, | ||||
|                 selectedTextColor = MaterialTheme.colorScheme.onTertiary, | ||||
|                 unselectedTextColor = MaterialTheme.colorScheme.onPrimary, | ||||
|             ), | ||||
|             label = { Text("Learn") }, | ||||
|             selected = (screenKey == ScreenKey.Learn), | ||||
|             onClick = { | ||||
|                 if (screenKey != ScreenKey.Learn) { | ||||
|                     App.INSTANCE.router.replaceScreen(Screens.Learn) | ||||
|                 } | ||||
|             } | ||||
|         )*/ | ||||
|     } | ||||
| } | ||||
| @@ -1,23 +0,0 @@ | ||||
| package ru.vendetti.bitcoin_summarizer | ||||
|  | ||||
| import androidx.fragment.app.Fragment | ||||
| import androidx.fragment.app.FragmentFactory | ||||
| import com.github.terrakok.cicerone.androidx.FragmentScreen | ||||
|  | ||||
| class Screens { | ||||
|     object Crypto: FragmentScreen { | ||||
|         override fun createFragment(factory: FragmentFactory) = CryptoFragment() | ||||
|     } | ||||
|     object About: FragmentScreen { | ||||
|         override fun createFragment(factory: FragmentFactory) = AboutFragment() | ||||
|     } | ||||
|     object Learn: FragmentScreen { | ||||
|         override fun createFragment(factory: FragmentFactory) = LearnFragment() | ||||
|     } | ||||
| } | ||||
|  | ||||
| enum class ScreenKey { | ||||
|     Crypto, | ||||
|     About, | ||||
|     Learn | ||||
| } | ||||
| @@ -1,7 +0,0 @@ | ||||
| package ru.vendetti.bitcoin_summarizer | ||||
|  | ||||
| class StatisticsFull ( | ||||
|     val FGI_list: ArrayList<FearAndGreedData>, | ||||
|     val TickerData: TickerData, | ||||
|     val GlobalData: GlobalResponse, | ||||
| ) | ||||
| @@ -15,14 +15,14 @@ import androidx.compose.ui.platform.LocalContext | ||||
| private val DarkColorScheme = darkColorScheme( | ||||
|     primary = RaisinBlack, | ||||
|     secondary = DarkPurple, | ||||
|     tertiary = HunyadiYellow, | ||||
|     tertiary = EnglishViolet, | ||||
|  | ||||
|     // Other default colors to override | ||||
|     background = DarkPurple, | ||||
|     surface = Color(0xFFFFFBFE), | ||||
|     onPrimary = HunyadiYellow, | ||||
|     onSecondary = HunyadiYellow, | ||||
|     onTertiary = DarkPurple, | ||||
|     onTertiary = HunyadiYellow, | ||||
|     onBackground = HunyadiYellow, | ||||
|     onSurface = Color(0xFF1C1B1F), | ||||
| ) | ||||
|   | ||||
| @@ -1,10 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
|     <androidx.compose.ui.platform.ComposeView | ||||
|         android:id="@+id/compose_view" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent" /> | ||||
| </LinearLayout> | ||||
| @@ -1,10 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" | ||||
|     android:orientation="vertical" | ||||
|     android:layout_width="match_parent" | ||||
|     android:layout_height="match_parent"> | ||||
|     <FrameLayout | ||||
|         android:id="@+id/container" | ||||
|         android:layout_width="match_parent" | ||||
|         android:layout_height="match_parent"/> | ||||
| </LinearLayout> | ||||
| @@ -1,6 +1,6 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android"> | ||||
|     <background android:drawable="@color/black2"/> | ||||
|     <background android:drawable="@color/ic_launcher_background"/> | ||||
|     <foreground android:drawable="@mipmap/ic_launcher_foreground"/> | ||||
|     <monochrome android:drawable="@mipmap/ic_launcher_foreground"/> | ||||
| </adaptive-icon> | ||||
| @@ -7,5 +7,4 @@ | ||||
|     <color name="teal_700">#FF018786</color> | ||||
|     <color name="black">#FF000000</color> | ||||
|     <color name="white">#FFFFFFFF</color> | ||||
|     <color name="black2">#221E22</color> | ||||
| </resources> | ||||
| @@ -1,4 +0,0 @@ | ||||
| <?xml version="1.0" encoding="utf-8"?> | ||||
| <resources> | ||||
|     <item name="container" type="id" /> | ||||
| </resources> | ||||
| @@ -13,9 +13,6 @@ activityCompose = "1.10.1" | ||||
| composeBom = "2025.02.00" | ||||
| 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" } | ||||
| @@ -37,9 +34,6 @@ kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutine | ||||
| 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" } | ||||
| 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" } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user