diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 6558deb..14c6109 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -49,6 +49,7 @@ 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)
@@ -62,5 +63,6 @@ dependencies {
implementation(libs.kotlinx.coroutines.android)
implementation(libs.kotlinx.coroutines.core)
implementation(libs.vico.compose.m3)
+ implementation(libs.cicerone)
}
\ No newline at end of file
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index dc24fe6..0498372 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,6 +3,7 @@
xmlns:tools="http://schemas.android.com/tools">
(R.id.compose_view).setContent {
+ BitcoinSummarizerTheme {
+ AboutComposable()
+ }
+ }
+ }
+ }
+}
+
+@OptIn(ExperimentalMaterial3Api::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())
+ ) {
+
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ru/vendetti/bitcoin_summarizer/App.kt b/app/src/main/java/ru/vendetti/bitcoin_summarizer/App.kt
new file mode 100644
index 0000000..6f62e95
--- /dev/null
+++ b/app/src/main/java/ru/vendetti/bitcoin_summarizer/App.kt
@@ -0,0 +1,23 @@
+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
+ }
+}
\ 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
new file mode 100644
index 0000000..8246cfb
--- /dev/null
+++ b/app/src/main/java/ru/vendetti/bitcoin_summarizer/CryptoFragment.kt
@@ -0,0 +1,411 @@
+package ru.vendetti.bitcoin_summarizer
+
+import android.annotation.SuppressLint
+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.Spacer
+import androidx.compose.foundation.layout.height
+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.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.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.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.compose.ui.unit.sp
+import androidx.fragment.app.Fragment
+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.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(R.id.compose_view).setContent {
+ BitcoinSummarizerTheme {
+ CryptoComposable()
+ }
+ }
+ }
+ }
+}
+
+@SuppressLint("MutableCollectionMutableState", "SimpleDateFormat")
+@OptIn(ExperimentalMaterial3Api::class)
+@Preview
+@Composable
+fun CryptoComposable() {
+ // Создаем репозиторий для работы с 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()) }
+
+ var fearGreedIndexDaysCount by remember { mutableIntStateOf(30) }
+
+ // Запускаем корутину для выполнения сетевых запросов
+ LaunchedEffect(fearGreedIndexDaysCount) {
+ 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
+ fearGreedDataList.reverse()
+ }catch (e: Exception) {
+ bitcoinTicker = TickerData(
+ id = "",
+ name = "",
+ symbol = "",
+ rank = "",
+ priceUsd = "",
+ priceBtc = "",
+ volume24hUsd = "",
+ marketCapUsd = "",
+ availableSupply = "",
+ totalSupply = "",
+ maxSupply = "",
+ percentChange1h = "",
+ percentChange24h = "",
+ percentChange7d = "",
+ lastUpdated = ""
+ )
+ globalData = GlobalResponse(
+ activeCryptocurrencies = "",
+ totalMarketCapUsd = "",
+ total24hVolumeUsd = "",
+ bitcoinPercentageOfMarketCap = ""
+ )
+ fearGreedDataList = ArrayList()
+ }
+ }
+
+ 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("Загрузка...")
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/vendetti/bitcoin_summarizer/LearnFragment.kt b/app/src/main/java/ru/vendetti/bitcoin_summarizer/LearnFragment.kt
new file mode 100644
index 0000000..3c0d641
--- /dev/null
+++ b/app/src/main/java/ru/vendetti/bitcoin_summarizer/LearnFragment.kt
@@ -0,0 +1,119 @@
+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.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.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(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())
+ ) {
+
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/ru/vendetti/bitcoin_summarizer/MainActivity.kt b/app/src/main/java/ru/vendetti/bitcoin_summarizer/MainActivity.kt
index 0d020b4..df39b3b 100644
--- a/app/src/main/java/ru/vendetti/bitcoin_summarizer/MainActivity.kt
+++ b/app/src/main/java/ru/vendetti/bitcoin_summarizer/MainActivity.kt
@@ -2,6 +2,7 @@ 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
@@ -40,6 +41,8 @@ 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
@@ -72,234 +75,25 @@ import ru.vendetti.bitcoin_summarizer.ui.theme.Green2
import java.text.DecimalFormat
import java.time.format.DateTimeFormatter
-class MainActivity : ComponentActivity() {
+class MainActivity : FragmentActivity() {
+
+ private val navigator = AppNavigator(this, R.id.container)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- 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()) }
-
- 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
- }
-
- 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")
- }
- }
+ 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()
}
}
diff --git a/app/src/main/java/ru/vendetti/bitcoin_summarizer/MyHamburgerModal.kt b/app/src/main/java/ru/vendetti/bitcoin_summarizer/MyHamburgerModal.kt
new file mode 100644
index 0000000..40b5954
--- /dev/null
+++ b/app/src/main/java/ru/vendetti/bitcoin_summarizer/MyHamburgerModal.kt
@@ -0,0 +1,72 @@
+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)
+ }
+ }
+ )
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/vendetti/bitcoin_summarizer/Screens.kt b/app/src/main/java/ru/vendetti/bitcoin_summarizer/Screens.kt
new file mode 100644
index 0000000..8c0cec7
--- /dev/null
+++ b/app/src/main/java/ru/vendetti/bitcoin_summarizer/Screens.kt
@@ -0,0 +1,23 @@
+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
+}
\ No newline at end of file
diff --git a/app/src/main/java/ru/vendetti/bitcoin_summarizer/ui/theme/Theme.kt b/app/src/main/java/ru/vendetti/bitcoin_summarizer/ui/theme/Theme.kt
index cf97fbd..1502de0 100644
--- a/app/src/main/java/ru/vendetti/bitcoin_summarizer/ui/theme/Theme.kt
+++ b/app/src/main/java/ru/vendetti/bitcoin_summarizer/ui/theme/Theme.kt
@@ -15,14 +15,14 @@ import androidx.compose.ui.platform.LocalContext
private val DarkColorScheme = darkColorScheme(
primary = RaisinBlack,
secondary = DarkPurple,
- tertiary = EnglishViolet,
+ tertiary = HunyadiYellow,
// Other default colors to override
background = DarkPurple,
surface = Color(0xFFFFFBFE),
onPrimary = HunyadiYellow,
onSecondary = HunyadiYellow,
- onTertiary = HunyadiYellow,
+ onTertiary = DarkPurple,
onBackground = HunyadiYellow,
onSurface = Color(0xFF1C1B1F),
)
diff --git a/app/src/main/res/layout/fragment_compose_view.xml b/app/src/main/res/layout/fragment_compose_view.xml
new file mode 100644
index 0000000..ab774c5
--- /dev/null
+++ b/app/src/main/res/layout/fragment_compose_view.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_container_main.xml b/app/src/main/res/layout/fragment_container_main.xml
new file mode 100644
index 0000000..d6cc091
--- /dev/null
+++ b/app/src/main/res/layout/fragment_container_main.xml
@@ -0,0 +1,10 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml
new file mode 100644
index 0000000..f76ac19
--- /dev/null
+++ b/app/src/main/res/values/ids.xml
@@ -0,0 +1,4 @@
+
+
+
+
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index cdc58bf..59ea3a6 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -13,6 +13,8 @@ activityCompose = "1.10.1"
composeBom = "2025.02.00"
retrofit = "2.9.0"
vico = "2.0.2"
+ciceroneVer = "7.1"
+fragmentKtx = "1.8.6"
[libraries]
androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" }
@@ -34,6 +36,8 @@ 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" }
[plugins]
android-application = { id = "com.android.application", version.ref = "agp" }