diff --git a/app/build.gradle b/app/build.gradle index ab8b645485..487c737419 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 020af588bd..66d48cf05c 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 d64b61beea..e2e6639186 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 4c6c105175..356978dd0e 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,22 @@ 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.Divider +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 +24,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() { @@ -61,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 guides = viewModel.guides + 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( @@ -105,18 +98,62 @@ 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) - ) + 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 { + 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) + } + } + if (lastSection) { + item { + Divider( + thickness = 1.dp, + color = ComposeAppTheme.colors.steel10 + ) + } + } } - Spacer(modifier = Modifier.height(8.dp)) } } } @@ -126,39 +163,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 8a0ed8aff5..3cfb811dc7 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 c63ae0f85e..48ff19a9e3 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,28 +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.Guide 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 guides by mutableStateOf>(listOf()) - 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 { @@ -30,6 +27,7 @@ class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { viewModelScope.launch { dataState.viewState?.let { viewState = it + emitState() } if (dataState is DataState.Success) { @@ -42,7 +40,17 @@ class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { fun onSelectCategory(category: GuideCategory) { selectedCategory = category - guides = category.guides + emitState() + } + + fun toggleSection(sectionTitle: String, expanded: Boolean) { + expandedSections = if (expanded) { + expandedSections.minus(sectionTitle) + } else { + expandedSections.plus(sectionTitle) + } + + emitState() } override fun onCleared() { @@ -51,7 +59,15 @@ class GuidesViewModel(private val repository: GuidesRepository) : ViewModel() { private fun didFetchGuideCategories(guideCategories: List) { categories = guideCategories - onSelectCategory(guideCategories.first()) - } + selectedCategory = guideCategories.first() + emitState() + } } + +data class GuidesUiState( + val viewState: ViewState, + val categories: List, + val selectedCategory: GuideCategory?, + val expandedSections: Set +) 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 2e649136f3..ace3f8de12 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,