diff --git a/common/src/main/res/values-ku/strings.xml b/common/src/main/res/values-ku/strings.xml index 78676bf5..27b325e9 100644 --- a/common/src/main/res/values-ku/strings.xml +++ b/common/src/main/res/values-ku/strings.xml @@ -2795,4 +2795,46 @@ Navnîşana serlêder Referralê bipejirîne Referral bi serkeftî hat pejirandin + + + Kurdistana Dîjîtal + Navenda Ekosîstemê + + %d Sepan + %d Sepan + + Paş + Darayî + Rêveberî + Perwerde + Civakî + Berîk + Bank + Danûstandin + Pez-DEX + P2P / Kirîn-Firotin + B2B + Bac/Zekat + Destpêk + Serok + Meclîs + Deng bide + Pejirandox + Dadwerî + Pêşniyar + Rapirsî + Nasnama + Wekîltî + Forum + Zanîngeh + Perwerde + Belge + Lêkolîn + whatsKURD + KurdMedya + KurdAI + Lîstik + Muzîk + Rewşenbîr + Referans diff --git a/common/src/main/res/values-tr/strings.xml b/common/src/main/res/values-tr/strings.xml index 405cc64b..8db479de 100644 --- a/common/src/main/res/values-tr/strings.xml +++ b/common/src/main/res/values-tr/strings.xml @@ -2083,4 +2083,46 @@ Referral kısmına bu adresi yapıştır: Başvuranın adresi Referral Onayla Referral başarıyla onaylandı + + + Dijital Kurdistan + Ekosistem Merkezi + + %d Uygulama + %d Uygulama + + Geri + Finans + Yönetişim + Eğitim + Sosyal + Cüzdan + Banka + Borsa + Pez-DEX + P2P / Al-Sat + B2B + Bac/Zekat + Launchpad + Başkan + Meclis + Oy + Doğrulayıcılar + Adalet + Öneriler + Anketler + Kimlik + Delegasyon + Forum + Üniversite + Perwerde + Sertifikalar + Araştırma + whatsKURD + KurdMedia + KurdAI + Oyunlar + Müzik + Rewshenbir + Davet diff --git a/common/src/main/res/values/strings.xml b/common/src/main/res/values/strings.xml index 10f544d0..849923c8 100644 --- a/common/src/main/res/values/strings.xml +++ b/common/src/main/res/values/strings.xml @@ -2811,4 +2811,46 @@ Applicant\'s address Approve Referral Referral approved successfully + + + Dijital Kurdistan + Ecosystem Hub + + %d App + %d Apps + + Back + Finance + Governance + Education + Social + Wallet + Bank + Exchange + Pez-DEX + P2P / Buy-Sell + B2B + Bac/Zekat + Launchpad + President + Assembly + Vote + Validators + Justice + Proposals + Polls + Identity + Delegation + Forum + University + Perwerde + Certificates + Research + whatsKURD + KurdMedia + KurdAI + Games + Music + Rewshenbir + Referral diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppFragment.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppFragment.kt index bf7b33d1..044dd128 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppFragment.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppFragment.kt @@ -1,6 +1,7 @@ package io.novafoundation.nova.feature_dapp_impl.presentation.main import android.view.View +import androidx.activity.OnBackPressedCallback import androidx.recyclerview.widget.ConcatAdapter import coil.ImageLoader import io.novafoundation.nova.common.base.BaseFragment @@ -11,6 +12,7 @@ import io.novafoundation.nova.common.presentation.LoadingState import io.novafoundation.nova.common.utils.insets.applyStatusBarInsets import io.novafoundation.nova.common.utils.recyclerView.space.SpaceBetween import io.novafoundation.nova.common.utils.recyclerView.space.addSpaceItemDecoration +import io.novafoundation.nova.common.utils.setVisible import io.novafoundation.nova.feature_banners_api.presentation.PromotionBannerAdapter import io.novafoundation.nova.feature_banners_api.presentation.bindWithAdapter import io.novafoundation.nova.feature_dapp_api.di.DAppFeatureApi @@ -21,6 +23,8 @@ import io.novafoundation.nova.feature_dapp_impl.presentation.common.DAppClickHan import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappCategoryListAdapter import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappCategoryViewHolder import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel +import io.novafoundation.nova.feature_dapp_impl.presentation.main.hub.DksHubApp +import io.novafoundation.nova.feature_dapp_impl.presentation.main.hub.DksHubView import javax.inject.Inject class MainDAppFragment : @@ -28,7 +32,8 @@ class MainDAppFragment : DAppClickHandler, DAppHeaderAdapter.Handler, DappCategoriesAdapter.Handler, - MainFavoriteDAppsAdapter.Handler { + MainFavoriteDAppsAdapter.Handler, + DksHubView.Handler { override fun createBinding() = FragmentDappMainBinding.inflate(layoutInflater) @@ -45,6 +50,13 @@ class MainDAppFragment : private val dappCategoriesListAdapter by lazy(LazyThreadSafetyMode.NONE) { DappCategoryListAdapter(this) } + // Back closes the hub before it leaves the tab. + private val closeHubOnBack = object : OnBackPressedCallback(false) { + override fun handleOnBackPressed() { + viewModel.closeDksHub() + } + } + override fun applyInsets(rootView: View) { binder.dappRecyclerViewCatalog.applyStatusBarInsets() } @@ -53,6 +65,10 @@ class MainDAppFragment : binder.dappRecyclerViewCatalog.adapter = ConcatAdapter(headerAdapter, bannerAdapter, favoritesAdapter, dappsShimmering, dappCategoriesListAdapter) binder.dappRecyclerViewCatalog.itemAnimator = null setupRecyclerViewSpacing() + + binder.dappDksHub.hubHandler = this + binder.dappDksHub.loadLogo(imageLoader) + requireActivity().onBackPressedDispatcher.addCallback(viewLifecycleOwner, closeHubOnBack) } override fun inject() { @@ -97,6 +113,19 @@ class MainDAppFragment : favoritesAdapter.show(it.isNotEmpty()) favoritesAdapter.setDApps(it) } + + viewModel.dksHubShown.observe { shown -> + binder.dappDksHub.setVisible(shown) + closeHubOnBack.isEnabled = shown + } + } + + override fun onHubAppClicked(app: DksHubApp) { + viewModel.dksHubAppClicked(app) + } + + override fun onHubBackClicked() { + viewModel.closeDksHub() } override fun onCategoryClicked(id: String) { diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppViewModel.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppViewModel.kt index f0fc2c60..70ca9ca7 100644 --- a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppViewModel.kt +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/MainDAppViewModel.kt @@ -21,7 +21,10 @@ import io.novafoundation.nova.feature_dapp_impl.presentation.common.DappModel import io.novafoundation.nova.feature_dapp_impl.presentation.common.dappCategoryToUi import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapDAppCatalogToDAppCategoryModels import io.novafoundation.nova.feature_dapp_impl.presentation.common.mapFavoriteDappToDappModel +import io.novafoundation.nova.feature_dapp_impl.presentation.main.hub.DksHub +import io.novafoundation.nova.feature_dapp_impl.presentation.main.hub.DksHubApp import io.novafoundation.nova.feature_dapp_impl.presentation.main.model.DAppCategoryState +import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.map import kotlinx.coroutines.launch @@ -76,6 +79,9 @@ class MainDAppViewModel( .withLoading() .share() + /** Whether the Dijital Kurdistan hub is open over the catalog. */ + val dksHubShown = MutableStateFlow(false) + init { syncDApps() } @@ -89,9 +95,22 @@ class MainDAppViewModel( } fun dappClicked(dapp: DappModel) { + // "Dijital Kurdistan" opens its hub first; the user picks where to go from there. + if (DksHub.isEntry(dapp.url)) { + dksHubShown.value = true + return + } router.openDAppBrowser(DAppBrowserPayload.Address(dapp.url)) } + fun dksHubAppClicked(app: DksHubApp) { + router.openDAppBrowser(DAppBrowserPayload.Address(app.url)) + } + + fun closeDksHub() { + dksHubShown.value = false + } + fun searchClicked() { router.openDappSearch() } diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/hub/DksHub.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/hub/DksHub.kt new file mode 100644 index 00000000..f14e8839 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/hub/DksHub.kt @@ -0,0 +1,113 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.main.hub + +import android.net.Uri +import androidx.annotation.StringRes +import io.novafoundation.nova.feature_dapp_impl.R + +/* + * The Dijital Kurdistan Ecosystem Hub: what a user sees after tapping "Dijital Kurdistan" + * in the DApps tab, before choosing where to go. Four sections, each a grid of apps. + * + * Every app opens in the DApp browser. Most are pages of the web app at app.pezkuwichain.io, + * the same routes its own mobile home links to; the rest are their own sites. + */ + +data class DksHubApp( + val id: String, + val emoji: String, + @StringRes val title: Int, + val url: String, +) + +data class DksHubSection( + val id: String, + val emoji: String, + @StringRes val title: Int, + /** The section's colour, ARGB: its border, glow and header band. */ + val color: Long, + val apps: List, +) + +object DksHub { + + /** The DApps list entry that opens the hub instead of a web page (wallet-utils dapps_full.json, "Dijital Kurdistan"). */ + private const val ENTRY_HOST = "pezkuwichain.io" + + const val LOGO_URL = "https://raw.githubusercontent.com/pezkuwichain/pezkuwi-wallet-utils/master/icons/dapps/dapps/DijitalKurdistan.png" + + fun isEntry(url: String): Boolean { + val uri = runCatching { Uri.parse(url) }.getOrNull() ?: return false + val host = uri.host?.removePrefix("www.") ?: return false + val path = uri.path.orEmpty().trim('/') + return host == ENTRY_HOST && path.isEmpty() + } + + private const val WEB_APP = "https://app.pezkuwichain.io" + private const val EXPLORER = "https://explorer.pezkuwichain.io" + + private fun web(route: String) = WEB_APP + route + + val sections: List = listOf( + DksHubSection( + id = "finance", + emoji = "💰", + title = R.string.dks_hub_section_finance, + color = 0xFF22C55E, + apps = listOf( + DksHubApp("wallet", "👛", R.string.dks_hub_app_wallet, EXPLORER), + DksHubApp("bank", "🏦", R.string.dks_hub_app_bank, web("/finance/bank")), + DksHubApp("exchange", "💱", R.string.dks_hub_app_exchange, "https://pex.network"), + DksHubApp("dex", "🔄", R.string.dks_hub_app_dex, web("/dex")), + DksHubApp("p2p", "🤝", R.string.dks_hub_app_p2p, web("/p2p")), + DksHubApp("b2b", "🤖", R.string.dks_hub_app_b2b, web("/bereketli")), + DksHubApp("zekat", "💵", R.string.dks_hub_app_zekat, web("/finance/zekat")), + DksHubApp("launchpad", "🚀", R.string.dks_hub_app_launchpad, web("/launchpad")), + ) + ), + DksHubSection( + id = "governance", + emoji = "🏛️", + title = R.string.dks_hub_section_governance, + color = 0xFFEF4444, + apps = listOf( + DksHubApp("president", "👑", R.string.dks_hub_app_president, web("/elections")), + DksHubApp("assembly", "🏛️", R.string.dks_hub_app_assembly, web("/governance/assembly")), + DksHubApp("vote", "🗳️", R.string.dks_hub_app_vote, web("/elections")), + DksHubApp("validators", "🛡️", R.string.dks_hub_app_validators, EXPLORER), + DksHubApp("justice", "⚖️", R.string.dks_hub_app_justice, web("/governance/justice")), + DksHubApp("proposals", "📜", R.string.dks_hub_app_proposals, web("/citizens/government")), + DksHubApp("polls", "📊", R.string.dks_hub_app_polls, web("/governance/polls")), + DksHubApp("identity", "🪪", R.string.dks_hub_app_identity, web("/identity")), + DksHubApp("delegation", "🔗", R.string.dks_hub_app_delegation, "https://wekil.pex.mom"), + ) + ), + DksHubSection( + id = "education", + emoji = "📚", + title = R.string.dks_hub_section_education, + color = 0xFFF59E0B, + apps = listOf( + DksHubApp("university", "🎓", R.string.dks_hub_app_university, web("/education/university")), + DksHubApp("perwerde", "📖", R.string.dks_hub_app_perwerde, web("/education")), + DksHubApp("certificates", "🏆", R.string.dks_hub_app_certificates, web("/education/certificates")), + DksHubApp("research", "🔬", R.string.dks_hub_app_research, web("/education/research")), + ) + ), + DksHubSection( + id = "social", + emoji = "💬", + title = R.string.dks_hub_section_social, + color = 0xFF3B82F6, + apps = listOf( + DksHubApp("whatskurd", "💬", R.string.dks_hub_app_whatskurd, web("/social/whatskurd")), + DksHubApp("forum", "📰", R.string.dks_hub_app_forum, web("/forum")), + DksHubApp("kurdmedia", "📺", R.string.dks_hub_app_kurdmedia, "https://dks.news"), + DksHubApp("kurdai", "🎙️", R.string.dks_hub_app_kurdai, "https://kurdishtts.dks.news"), + DksHubApp("games", "🎮", R.string.dks_hub_app_games, "https://games.pex.mom"), + DksHubApp("music", "🎵", R.string.dks_hub_app_music, "https://music.dks.news"), + DksHubApp("rewshenbir", "🦅", R.string.dks_hub_app_rewshenbir, "https://rewshenbir.pezkiwi.app"), + DksHubApp("referral", "👥", R.string.dks_hub_app_referral, web("/dashboard")), + ) + ), + ) +} diff --git a/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/hub/DksHubView.kt b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/hub/DksHubView.kt new file mode 100644 index 00000000..d4b539c1 --- /dev/null +++ b/feature-dapp-impl/src/main/java/io/novafoundation/nova/feature_dapp_impl/presentation/main/hub/DksHubView.kt @@ -0,0 +1,289 @@ +package io.novafoundation.nova.feature_dapp_impl.presentation.main.hub + +import android.content.Context +import android.content.res.ColorStateList +import android.graphics.Color +import android.graphics.Typeface +import android.graphics.drawable.GradientDrawable +import android.graphics.drawable.RippleDrawable +import android.text.TextUtils +import android.util.AttributeSet +import android.util.TypedValue +import android.view.Gravity +import android.view.View +import android.widget.FrameLayout +import android.widget.GridLayout +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.core.content.res.ResourcesCompat +import androidx.core.graphics.ColorUtils +import androidx.core.widget.NestedScrollView +import coil.ImageLoader +import coil.load +import coil.transform.CircleCropTransformation +import io.novafoundation.nova.common.utils.insets.applyStatusBarInsets +import io.novafoundation.nova.feature_dapp_impl.R + +/* + * The hub's screen, drawn in code so the whole design sits in one place: a deep navy + * page, the title with the Dijital Kurdistan mark, and one glowing card per section, + * each a four-column grid of apps. It lives inside the DApps tab, so the wallet's own + * bottom bar stays in view. + */ +class DksHubView @JvmOverloads constructor( + context: Context, + attrs: AttributeSet? = null, +) : FrameLayout(context, attrs) { + + interface Handler { + fun onHubAppClicked(app: DksHubApp) + fun onHubBackClicked() + } + + var hubHandler: Handler? = null + + private val content = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + setPadding(dp(16), dp(8), dp(16), dp(24)) + } + + private val logo = ImageView(context) + + init { + // Swallow touches so nothing behind the hub reacts. + isClickable = true + isFocusable = true + background = GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(Color.parseColor("#0B1230"), Color.parseColor("#0A0F24"), Color.parseColor("#070B1A")) + ) + + val scroll = NestedScrollView(context).apply { + isFillViewport = true + overScrollMode = View.OVER_SCROLL_NEVER + clipToPadding = false + addView(content, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)) + } + addView(scroll, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) + content.applyStatusBarInsets(consume = false) + + content.addView(header()) + DksHub.sections.forEach { content.addView(section(it)) } + } + + fun loadLogo(imageLoader: ImageLoader) { + logo.load(DksHub.LOGO_URL, imageLoader) { + transformations(CircleCropTransformation()) + crossfade(true) + } + } + + // ─── header ───────────────────────────────────────────────────────────── + + private fun header(): View { + val row = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(0, dp(4), 0, dp(18)) + } + + val back = ImageView(context).apply { + setImageResource(R.drawable.ic_arrow_back) + imageTintList = ColorStateList.valueOf(Color.parseColor("#B9C6E4")) + contentDescription = context.getString(R.string.dks_hub_back) + scaleType = ImageView.ScaleType.CENTER + background = ripple(Color.TRANSPARENT, radius = dp(20).toFloat()) + setOnClickListener { hubHandler?.onHubBackClicked() } + } + row.addView(back, LinearLayout.LayoutParams(dp(40), dp(40)).apply { marginEnd = dp(6) }) + + val titles = LinearLayout(context).apply { orientation = LinearLayout.VERTICAL } + titles.addView( + text(R.string.dks_hub_title, 28f, "#E4EEFF", bold = true).apply { + // The title's light from the hub's mark: a soft blue glow behind the letters. + setShadowLayer(dp(10).toFloat(), 0f, 0f, Color.parseColor("#803B82F6")) + maxLines = 1 + ellipsize = TextUtils.TruncateAt.END + } + ) + titles.addView(text(R.string.dks_hub_subtitle, 15f, "#8EA1C8")) + row.addView(titles, LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f)) + + // The mark, in a ring of the section colours. + val ring = FrameLayout(context).apply { + background = GradientDrawable().apply { + shape = GradientDrawable.OVAL + colors = intArrayOf( + Color.parseColor("#22C55E"), + Color.parseColor("#F59E0B"), + Color.parseColor("#EF4444"), + Color.parseColor("#3B82F6"), + Color.parseColor("#22C55E") + ) + gradientType = GradientDrawable.SWEEP_GRADIENT + } + setPadding(dp(3), dp(3), dp(3), dp(3)) + } + logo.background = GradientDrawable().apply { shape = GradientDrawable.OVAL; setColor(Color.parseColor("#141C3D")) } + ring.addView(logo, LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)) + row.addView(ring, LinearLayout.LayoutParams(dp(60), dp(60))) + return row + } + + // ─── sections ─────────────────────────────────────────────────────────── + + private fun section(s: DksHubSection): View { + val color = s.color.toInt() + val card = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + background = GradientDrawable( + GradientDrawable.Orientation.TOP_BOTTOM, + intArrayOf(ColorUtils.setAlphaComponent(color, 0x33), ColorUtils.setAlphaComponent(color, 0x14)) + ).apply { + cornerRadius = dp(20).toFloat() + setStroke(dp(2), ColorUtils.setAlphaComponent(color, 0xCC)) + } + clipToOutline = true + elevation = dp(6).toFloat() + if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) { + outlineAmbientShadowColor = color + outlineSpotShadowColor = color + } + } + + // Header band: the section's name in capitals, and how many apps it holds. + val band = LinearLayout(context).apply { + orientation = LinearLayout.HORIZONTAL + gravity = Gravity.CENTER_VERTICAL + setPadding(dp(16), dp(12), dp(12), dp(12)) + background = GradientDrawable( + GradientDrawable.Orientation.LEFT_RIGHT, + intArrayOf(ColorUtils.setAlphaComponent(color, 0x59), ColorUtils.setAlphaComponent(color, 0x1F)) + ).apply { + cornerRadii = floatArrayOf(dp(18).toFloat(), dp(18).toFloat(), dp(18).toFloat(), dp(18).toFloat(), 0f, 0f, 0f, 0f) + } + } + band.addView( + TextView(context).apply { + text = s.emoji + setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f) + }, + LinearLayout.LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT).apply { marginEnd = dp(8) } + ) + band.addView( + TextView(context).apply { + text = context.getString(s.title).uppercase() + setTextSize(TypedValue.COMPLEX_UNIT_SP, 16f) + setTextColor(Color.WHITE) + typeface = font(bold = true) + letterSpacing = 0.04f + }, + LinearLayout.LayoutParams(0, LayoutParams.WRAP_CONTENT, 1f) + ) + band.addView( + TextView(context).apply { + text = resources.getQuantityString(R.plurals.dks_hub_apps_count, s.apps.size, s.apps.size) + setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) + setTextColor(Color.parseColor("#E6ECF7")) + typeface = font(bold = false) + setPadding(dp(10), dp(3), dp(10), dp(3)) + background = GradientDrawable().apply { + cornerRadius = dp(12).toFloat() + setColor(ColorUtils.setAlphaComponent(Color.BLACK, 0x40)) + setStroke(dp(1), ColorUtils.setAlphaComponent(color, 0xB3)) + } + } + ) + card.addView(band) + + // A hairline in the section colour under the band. + card.addView( + View(context).apply { setBackgroundColor(ColorUtils.setAlphaComponent(color, 0x66)) }, + LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, dp(1)) + ) + + val grid = GridLayout(context).apply { + columnCount = COLUMNS + setPadding(dp(6), dp(12), dp(6), dp(10)) + } + s.apps.forEach { grid.addView(tile(it, color), tileParams()) } + card.addView(grid) + + val wrapper = FrameLayout(context).apply { setPadding(0, 0, 0, dp(18)) } + wrapper.addView(card) + return wrapper + } + + private fun tile(app: DksHubApp, color: Int): View { + val tile = LinearLayout(context).apply { + orientation = LinearLayout.VERTICAL + gravity = Gravity.CENTER_HORIZONTAL + setPadding(dp(2), dp(6), dp(2), dp(8)) + background = ripple(Color.TRANSPARENT, radius = dp(14).toFloat(), rippleColor = ColorUtils.setAlphaComponent(color, 0x55)) + contentDescription = context.getString(app.title) + setOnClickListener { hubHandler?.onHubAppClicked(app) } + } + // The icon sits on a soft disc of the section's colour, lit from its centre. + val icon = TextView(context).apply { + text = app.emoji + gravity = Gravity.CENTER + setTextSize(TypedValue.COMPLEX_UNIT_SP, 30f) + background = GradientDrawable().apply { + shape = GradientDrawable.OVAL + gradientType = GradientDrawable.RADIAL_GRADIENT + gradientRadius = dp(30).toFloat() + colors = intArrayOf(ColorUtils.setAlphaComponent(color, 0x55), ColorUtils.setAlphaComponent(color, 0x00)) + } + importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO + } + tile.addView(icon, LinearLayout.LayoutParams(dp(58), dp(58))) + tile.addView( + TextView(context).apply { + setText(app.title) + gravity = Gravity.CENTER + setTextSize(TypedValue.COMPLEX_UNIT_SP, 12f) + setTextColor(Color.parseColor("#F1F5FF")) + typeface = font(bold = true) + maxLines = 1 + ellipsize = TextUtils.TruncateAt.END + setShadowLayer(dp(2).toFloat(), 0f, dp(1).toFloat(), Color.parseColor("#99000000")) + }, + LinearLayout.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT).apply { topMargin = dp(4) } + ) + return tile + } + + private fun tileParams() = GridLayout.LayoutParams( + GridLayout.spec(GridLayout.UNDEFINED), + GridLayout.spec(GridLayout.UNDEFINED, 1f) + ).apply { + width = 0 + height = LayoutParams.WRAP_CONTENT + setMargins(dp(2), dp(2), dp(2), dp(2)) + } + + // ─── helpers ──────────────────────────────────────────────────────────── + + private fun text(res: Int, sp: Float, color: String, bold: Boolean = false) = TextView(context).apply { + setText(res) + setTextSize(TypedValue.COMPLEX_UNIT_SP, sp) + setTextColor(Color.parseColor(color)) + typeface = font(bold) + } + + private fun font(bold: Boolean): Typeface? = + ResourcesCompat.getFont(context, if (bold) R.font.public_sans_bold else R.font.public_sans_regular) + + private fun ripple(fill: Int, radius: Float, rippleColor: Int = Color.parseColor("#33FFFFFF")): RippleDrawable { + val shape = GradientDrawable().apply { cornerRadius = radius; setColor(fill) } + val mask = GradientDrawable().apply { cornerRadius = radius; setColor(Color.WHITE) } + return RippleDrawable(ColorStateList.valueOf(rippleColor), shape, mask) + } + + private fun dp(value: Int): Int = (value * resources.displayMetrics.density).toInt() + + private companion object { + const val COLUMNS = 4 + } +} diff --git a/feature-dapp-impl/src/main/res/layout/fragment_dapp_main.xml b/feature-dapp-impl/src/main/res/layout/fragment_dapp_main.xml index 125087b6..784e2861 100644 --- a/feature-dapp-impl/src/main/res/layout/fragment_dapp_main.xml +++ b/feature-dapp-impl/src/main/res/layout/fragment_dapp_main.xml @@ -1,13 +1,25 @@ - + android:layout_height="match_parent"> + + + + + + +