From 543f92f27836252e162e952411dfece08a3db179 Mon Sep 17 00:00:00 2001 From: abdrasulov Date: Wed, 23 Oct 2024 16:16:23 +0600 Subject: [PATCH 1/3] Update academy structure --- app/build.gradle | 4 +- .../bankwallet/core/managers/GuidesManager.kt | 6 +- .../bankwallet/entities/Guide.kt | 31 +++-- .../modules/settings/guides/GuidesFragment.kt | 112 ++++++++---------- .../settings/guides/GuidesRepository.kt | 11 +- .../settings/guides/GuidesViewModel.kt | 12 +- .../compose/components/cell/CellUniversal.kt | 16 ++- 7 files changed, 100 insertions(+), 92 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index ab8b645485c..487c7374191 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -77,7 +77,7 @@ android { resValue "string", "ftmscanApiKey", "57YQ2GIRAZNV6M5HIJYYG3XQGGNIPVV8MF" resValue "string", "basescanApiKey", "AKEWS351FN9P9E2CFPWRWQVGHYUP7W8SUF" resValue "string", "is_release", "false" - resValue "string", "guidesUrl", "https://raw.githubusercontent.com/horizontalsystems/blockchain-crypto-guides/develop/index.json" + resValue "string", "guidesUrl", "https://raw.githubusercontent.com/horizontalsystems/Unstoppable-Wallet-Website/master/src/edu.json" resValue "string", "faqUrl", "https://raw.githubusercontent.com/horizontalsystems/Unstoppable-Wallet-Website/master/src/faq.json" resValue "string", "coinsJsonUrl", "https://raw.githubusercontent.com/horizontalsystems/cryptocurrencies/master/coins.json" resValue "string", "providerCoinsJsonUrl", "https://raw.githubusercontent.com/horizontalsystems/cryptocurrencies/master/provider.coins.json" @@ -116,7 +116,7 @@ android { resValue "string", "ftmscanApiKey", "JAWRPW27KEMVXMJJ9UKY63CVPH3X5V9SMP" resValue "string", "basescanApiKey", "QU4RJVJXQCW812J3234EW9EV815TA6XC55" resValue "string", "is_release", "true" - resValue "string", "guidesUrl", "https://raw.githubusercontent.com/horizontalsystems/blockchain-crypto-guides/v1.2/index.json" + resValue "string", "guidesUrl", "https://raw.githubusercontent.com/horizontalsystems/Unstoppable-Wallet-Website/master/src/edu.json" resValue "string", "faqUrl", "https://raw.githubusercontent.com/horizontalsystems/Unstoppable-Wallet-Website/v1.3/src/faq.json" resValue "string", "coinsJsonUrl", "https://raw.githubusercontent.com/horizontalsystems/cryptocurrencies/v0.21/coins.json" resValue "string", "providerCoinsJsonUrl", "https://raw.githubusercontent.com/horizontalsystems/cryptocurrencies/v0.21/provider.coins.json" diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/GuidesManager.kt b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/GuidesManager.kt index 020af588bd8..66d48cf05cd 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/GuidesManager.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/core/managers/GuidesManager.kt @@ -42,10 +42,8 @@ object GuidesManager { val jsonObject = json.asJsonObject return Guide( - jsonObject.get("title").asString, - context.deserialize(jsonObject.get("updated_at"), Date::class.java), - jsonObject["image"].asString?.let { absolutify(it) }, - absolutify(jsonObject["file"].asString) + jsonObject.get("title").asString, + absolutify(jsonObject.get("markdown").asString) ) } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/entities/Guide.kt b/app/src/main/java/io/horizontalsystems/bankwallet/entities/Guide.kt index d64b61beeab..e2e66391861 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/entities/Guide.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/entities/Guide.kt @@ -1,21 +1,26 @@ package io.horizontalsystems.bankwallet.entities -import java.util.* - data class Guide( - val title: String, - val updatedAt: Date, - val imageUrl: String?, - val fileUrl: String + val title: String, + val markdown: String, ) data class GuideCategory( - val id: String, - val category: String, - val guides: List) + val category: String, + val sections: List +) data class GuideCategoryMultiLang( - val id: String, - val category: Map){ - var guides = listOf>() -} + val category: Map, + val sections: List +) + +data class GuideSection( + val title: String, + val items: List +) + +data class GuideSectionMultiLang( + val title: Map, + val items: List> +) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt index 4c6c105175e..3a825a3c143 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt @@ -1,31 +1,21 @@ package io.horizontalsystems.bankwallet.modules.settings.guides import androidx.compose.animation.Crossfade -import androidx.compose.foundation.Image import androidx.compose.foundation.background -import androidx.compose.foundation.clickable -import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.PaddingValues -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.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState -import androidx.compose.foundation.lazy.items -import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.clip -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.res.painterResource import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation.NavController -import coil.compose.rememberAsyncImagePainter import io.horizontalsystems.bankwallet.R import io.horizontalsystems.bankwallet.core.BaseComposeFragment import io.horizontalsystems.bankwallet.core.LocalizedException @@ -33,19 +23,19 @@ import io.horizontalsystems.bankwallet.core.slideFromRight import io.horizontalsystems.bankwallet.core.stats.StatEvent import io.horizontalsystems.bankwallet.core.stats.StatPage import io.horizontalsystems.bankwallet.core.stats.stat -import io.horizontalsystems.bankwallet.entities.Guide import io.horizontalsystems.bankwallet.entities.ViewState import io.horizontalsystems.bankwallet.modules.coin.overview.ui.Loading import io.horizontalsystems.bankwallet.modules.markdown.MarkdownFragment import io.horizontalsystems.bankwallet.ui.compose.ComposeAppTheme import io.horizontalsystems.bankwallet.ui.compose.components.AppBar +import io.horizontalsystems.bankwallet.ui.compose.components.HFillSpacer import io.horizontalsystems.bankwallet.ui.compose.components.HsBackButton import io.horizontalsystems.bankwallet.ui.compose.components.ScreenMessageWithAction import io.horizontalsystems.bankwallet.ui.compose.components.ScrollableTabs import io.horizontalsystems.bankwallet.ui.compose.components.TabItem -import io.horizontalsystems.bankwallet.ui.compose.components.caption_grey -import io.horizontalsystems.bankwallet.ui.compose.components.title3_leah -import io.horizontalsystems.core.helpers.DateHelper +import io.horizontalsystems.bankwallet.ui.compose.components.body_leah +import io.horizontalsystems.bankwallet.ui.compose.components.cell.CellUniversal +import io.horizontalsystems.bankwallet.ui.compose.components.headline2_leah import java.net.UnknownHostException class GuidesFragment : BaseComposeFragment() { @@ -64,7 +54,7 @@ fun GuidesScreen(navController: NavController) { val viewState = viewModel.viewState val categories = viewModel.categories val selectedCategory = viewModel.selectedCategory - val guides = viewModel.guides + val expandedSections = viewModel.expandedSections Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { AppBar( @@ -105,18 +95,50 @@ fun GuidesScreen(navController: NavController) { } LazyColumn( state = listState, - contentPadding = PaddingValues(top = 12.dp, bottom = 32.dp) + contentPadding = PaddingValues(bottom = 32.dp) ) { - items(guides) { guide -> - CardsPreviewCardsGuide(guide) { - navController.slideFromRight( - R.id.markdownFragment, - MarkdownFragment.Input(guide.fileUrl, true) - ) + selectedCategory.sections.forEachIndexed { i, section -> + val sectionTitle = section.title + val expanded = expandedSections.contains(sectionTitle) + item { + CellUniversal( + borderTop = i != 0, + color = ComposeAppTheme.colors.lawrence, + onClick = { + viewModel.toggleSection(sectionTitle, expanded) + } + ) { + headline2_leah(sectionTitle) + HFillSpacer(8.dp) + val iconId = if (expanded) { + R.drawable.ic_arrow_big_up_20 + } else { + R.drawable.ic_arrow_big_down_20 + } + Icon( + painter = painterResource(iconId), + contentDescription = null, + tint = ComposeAppTheme.colors.grey + ) + } + } + if (expanded) { + itemsIndexed(section.items) { j, guide -> + CellUniversal( + borderTop = j != 0, + onClick = { + navController.slideFromRight( + R.id.markdownFragment, + MarkdownFragment.Input(guide.markdown, true) + ) - stat(page = StatPage.Academy, event = StatEvent.OpenArticle(guide.fileUrl)) + stat(page = StatPage.Academy, event = StatEvent.OpenArticle(guide.markdown)) + } + ) { + body_leah(guide.title) + } + } } - Spacer(modifier = Modifier.height(8.dp)) } } } @@ -126,39 +148,3 @@ fun GuidesScreen(navController: NavController) { } } } - -@Composable -fun CardsPreviewCardsGuide(guide: Guide, onClick: () -> Unit) { - Column( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 16.dp) - .clip(RoundedCornerShape(16.dp)) - .background(ComposeAppTheme.colors.lawrence) - .clickable(onClick = onClick) - ) { - Box( - modifier = Modifier - .fillMaxWidth() - .height(180.dp) - .background(ComposeAppTheme.colors.raina) - ) { - Image( - modifier = Modifier.fillMaxSize(), - contentScale = ContentScale.Crop, - painter = rememberAsyncImagePainter(model = guide.imageUrl), - contentDescription = null - ) - } - - caption_grey( - modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp), - text = DateHelper.shortDate(guide.updatedAt) - ) - - title3_leah( - modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 8.dp, bottom = 16.dp), - text = guide.title - ) - } -} diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesRepository.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesRepository.kt index 8a0ed8aff5d..3cfb811dc7b 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesRepository.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesRepository.kt @@ -7,6 +7,7 @@ import io.horizontalsystems.bankwallet.core.retryWhen import io.horizontalsystems.bankwallet.entities.DataState import io.horizontalsystems.bankwallet.entities.GuideCategory import io.horizontalsystems.bankwallet.entities.GuideCategoryMultiLang +import io.horizontalsystems.bankwallet.entities.GuideSection import io.reactivex.Observable import io.reactivex.subjects.BehaviorSubject import kotlinx.coroutines.CoroutineScope @@ -68,8 +69,14 @@ class GuidesRepository( private fun getCategoriesByLocalLanguage(categoriesMultiLanguage: Array, language: String, fallbackLanguage: String) = categoriesMultiLanguage.map { categoriesMultiLang -> val categoryTitle = categoriesMultiLang.category[language] ?: categoriesMultiLang.category[fallbackLanguage] ?: "" - val guides = categoriesMultiLang.guides.mapNotNull { it[language] ?: it[fallbackLanguage] } - GuideCategory(categoriesMultiLang.id, categoryTitle, guides.sortedByDescending { it.updatedAt }) + val sections = categoriesMultiLang.sections.map { sectionMultiLang -> + val sectionTitle = sectionMultiLang.title[language] ?: sectionMultiLang.title[fallbackLanguage] ?: "" + val items = sectionMultiLang.items.mapNotNull { + it[language] ?: it[fallbackLanguage] + } + GuideSection(sectionTitle, items) + } + GuideCategory(categoryTitle, sections) } } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesViewModel.kt index c63ae0f85eb..1667b81f8ee 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesViewModel.kt @@ -6,7 +6,6 @@ import androidx.compose.runtime.setValue import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import io.horizontalsystems.bankwallet.entities.DataState -import io.horizontalsystems.bankwallet.entities.Guide import io.horizontalsystems.bankwallet.entities.GuideCategory import io.horizontalsystems.bankwallet.entities.ViewState import kotlinx.coroutines.launch @@ -18,7 +17,7 @@ class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { private set var selectedCategory by mutableStateOf(null) private set - var guides by mutableStateOf>(listOf()) + var expandedSections by mutableStateOf(setOf()) private set var viewState by mutableStateOf(ViewState.Loading) @@ -42,7 +41,6 @@ class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { fun onSelectCategory(category: GuideCategory) { selectedCategory = category - guides = category.guides } override fun onCleared() { @@ -54,4 +52,12 @@ class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { onSelectCategory(guideCategories.first()) } + fun toggleSection(sectionTitle: String, expanded: Boolean) { + expandedSections = if (expanded) { + expandedSections.minus(sectionTitle) + } else { + expandedSections.plus(sectionTitle) + } + } + } diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/components/cell/CellUniversal.kt b/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/components/cell/CellUniversal.kt index 2e649136f35..ace3f8de124 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/components/cell/CellUniversal.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/ui/compose/components/cell/CellUniversal.kt @@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.defaultMinSize +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding import androidx.compose.foundation.shape.RoundedCornerShape @@ -28,15 +29,20 @@ fun CellUniversal( paddingVertical: Dp = 12.dp, paddingHorizontal: Dp = 16.dp, onClick: (() -> Unit)? = null, + color: Color? = null, content: @Composable() (RowScope.() -> Unit), ) { - val modifierClickable = if (onClick != null) { - Modifier.clickable(onClick = onClick) - } else { - Modifier + var modifier: Modifier = Modifier.fillMaxWidth() + + onClick?.let { + modifier = modifier.clickable(onClick = onClick) + } + + color?.let { + modifier = modifier.background(it) } - Box(modifier = modifierClickable) { + Box(modifier = modifier) { if (borderTop) { Divider( thickness = 1.dp, From 184dc0eb69b1e0098f830c2a095d4604866a55f6 Mon Sep 17 00:00:00 2001 From: abdrasulov Date: Wed, 23 Oct 2024 16:41:02 +0600 Subject: [PATCH 2/3] Show border at the bottom --- .../modules/settings/guides/GuidesFragment.kt | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt index 3a825a3c143..544da7b2b61 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt @@ -7,6 +7,7 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyListState import androidx.compose.foundation.lazy.itemsIndexed +import androidx.compose.material.Divider import androidx.compose.material.Icon import androidx.compose.runtime.Composable import androidx.compose.runtime.saveable.rememberSaveable @@ -97,7 +98,11 @@ fun GuidesScreen(navController: NavController) { state = listState, contentPadding = PaddingValues(bottom = 32.dp) ) { - selectedCategory.sections.forEachIndexed { i, section -> + val sections = selectedCategory.sections + val sectionsSize = sections.size + + sections.forEachIndexed { i, section -> + val lastSection = i == sectionsSize - 1 val sectionTitle = section.title val expanded = expandedSections.contains(sectionTitle) item { @@ -138,6 +143,14 @@ fun GuidesScreen(navController: NavController) { body_leah(guide.title) } } + if (lastSection) { + item { + Divider( + thickness = 1.dp, + color = ComposeAppTheme.colors.steel10 + ) + } + } } } } From da8f71a8aa496d8d437da69d42b4f2dd9052a8a1 Mon Sep 17 00:00:00 2001 From: abdrasulov Date: Wed, 23 Oct 2024 16:50:39 +0600 Subject: [PATCH 3/3] Apply ViewModelUiState pattern --- .../modules/settings/guides/GuidesFragment.kt | 10 ++-- .../settings/guides/GuidesViewModel.kt | 56 +++++++++++-------- 2 files changed, 39 insertions(+), 27 deletions(-) diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt index 544da7b2b61..356978dd0ea 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesFragment.kt @@ -52,10 +52,12 @@ class GuidesFragment : BaseComposeFragment() { fun GuidesScreen(navController: NavController) { val viewModel = viewModel(factory = GuidesModule.Factory()) - val viewState = viewModel.viewState - val categories = viewModel.categories - val selectedCategory = viewModel.selectedCategory - val expandedSections = viewModel.expandedSections + val uiState = viewModel.uiState + + val viewState = uiState.viewState + val categories = uiState.categories + val selectedCategory = uiState.selectedCategory + val expandedSections = uiState.expandedSections Column(modifier = Modifier.background(color = ComposeAppTheme.colors.tyler)) { AppBar( diff --git a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesViewModel.kt b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesViewModel.kt index 1667b81f8ee..48ff19a9e3f 100644 --- a/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesViewModel.kt +++ b/app/src/main/java/io/horizontalsystems/bankwallet/modules/settings/guides/GuidesViewModel.kt @@ -1,27 +1,25 @@ package io.horizontalsystems.bankwallet.modules.settings.guides -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue -import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import io.horizontalsystems.bankwallet.core.ViewModelUiState import io.horizontalsystems.bankwallet.entities.DataState import io.horizontalsystems.bankwallet.entities.GuideCategory import io.horizontalsystems.bankwallet.entities.ViewState import kotlinx.coroutines.launch import kotlinx.coroutines.rx2.asFlow -class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { +class GuidesViewModel(private val repository: GuidesRepository) : ViewModelUiState() { + private var viewState: ViewState = ViewState.Loading + private var categories = listOf() + private var selectedCategory: GuideCategory? = null + private var expandedSections = setOf() - var categories by mutableStateOf>(listOf()) - private set - var selectedCategory by mutableStateOf(null) - private set - var expandedSections by mutableStateOf(setOf()) - private set - - var viewState by mutableStateOf(ViewState.Loading) - private set + override fun createState() = GuidesUiState( + viewState = viewState, + categories = categories, + selectedCategory = selectedCategory, + expandedSections = expandedSections + ) init { viewModelScope.launch { @@ -29,6 +27,7 @@ class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { viewModelScope.launch { dataState.viewState?.let { viewState = it + emitState() } if (dataState is DataState.Success) { @@ -41,15 +40,7 @@ class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { fun onSelectCategory(category: GuideCategory) { selectedCategory = category - } - - override fun onCleared() { - repository.clear() - } - - private fun didFetchGuideCategories(guideCategories: List) { - categories = guideCategories - onSelectCategory(guideCategories.first()) + emitState() } fun toggleSection(sectionTitle: String, expanded: Boolean) { @@ -58,6 +49,25 @@ class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { } else { expandedSections.plus(sectionTitle) } + + emitState() } + override fun onCleared() { + repository.clear() + } + + private fun didFetchGuideCategories(guideCategories: List) { + categories = guideCategories + selectedCategory = guideCategories.first() + + emitState() + } } + +data class GuidesUiState( + val viewState: ViewState, + val categories: List, + val selectedCategory: GuideCategory?, + val expandedSections: Set +)