~gardenapple/mitch

c4082deb0c7a0157103ade7b64cee8fb2ea236e7 — gardenapple a month ago 651b21d
Adapt to game jam & forum themes
M app/src/main/java/ua/gardenapple/itchupdater/ItchWebsiteUtils.kt => app/src/main/java/ua/gardenapple/itchupdater/ItchWebsiteUtils.kt +39 -11
@@ 18,13 18,15 @@ object ItchWebsiteUtils {
    val STORE_ANDROID_PAGE_URI: Uri = Uri.parse("https://itch.io/games/platform-android")
    val STORE_PAGE_URI: Uri = Uri.parse("https://itch.io/games")

    private val gameBgColorPattern = Regex("root[{]--itchio_ui_bg: (#?\\w+);")
    private val forumJamGameBgColorPattern = Regex("--itchio_ui_bg: (#?\\w+);")
    private val gameButtonColorPattern = Regex("--itchio_button_color: (#?\\w+);")
    private val gameButtonFgColorPattern = Regex("--itchio_button_fg_color: (#?\\w+);")

    private val userBgColorPattern = Regex("--itchio_gray_back: (#?\\w+);")
    private val userFgColorPattern = Regex("--itchio_border_radius: ?\\w+;color:(#?\\w+);")

    private val jamLinkColorPattern = Regex("""\.view_jam_page\s+a[^{]*\{[^}]*color: (#?\w+);""")


    fun isItchWebPage(uri: Uri): Boolean {
        return uri.host != null && (uri.host == "itch.io" ||


@@ 57,6 59,10 @@ object ItchWebsiteUtils {
        }
    }

    fun isJamOrForumPage(htmlDoc: Document): Boolean {
        return htmlDoc.head().getElementById("jam_theme") != null
    }

    fun isGamePage(htmlDoc: Document): Boolean {
        return isDevlogPage(htmlDoc) || isStorePage(htmlDoc) ||
                isPurchasePage(htmlDoc) || isDownloadPage(htmlDoc)


@@ 67,11 73,14 @@ object ItchWebsiteUtils {
    }

    /**
     * @return true if the page is a store page or devlog page, or stylized community profile
     * Should you do day/night handling for this page?
     *
     * Note that most jam pages and forum pages have a few custom colors, but overall they do NOT
     * count as stylized pages, as they follow the standard rules for day/night themes
     * @return true if the page is a store page, devlog page, or stylized community profile
     */
    fun isStylizedPage(htmlDoc: Document): Boolean {
        return htmlDoc.getElementById("game_theme") != null ||
                htmlDoc.getElementById("user_theme") != null
    fun shouldHandleDayNightThemes(htmlDoc: Document): Boolean {
        return htmlDoc.selectFirst("#game_theme, #user_theme, [data-page_name=\"view_jam\"]") == null
    }

    /**


@@ 139,22 148,21 @@ object ItchWebsiteUtils {
     */

    fun getBackgroundUIColor(doc: Document): Int? {
        val gameThemeCSS = doc.getElementById("game_theme")?.html()
        val gameThemeCSS = doc.selectFirst("#game_theme, #jam_theme")?.html()
        if (gameThemeCSS != null) {
            val foundColors = gameBgColorPattern.find(gameThemeCSS)
            val foundColors = forumJamGameBgColorPattern.find(gameThemeCSS)
            if (foundColors != null)
                return Utils.parseCssColor(foundColors.groupValues[1])
        }

        val userThemeCSS = doc.getElementById("user_theme")?.html()
        if (userThemeCSS != null) {
        if (userThemeCSS != null)
            return Utils.parseCssColor("#333333")
        }
        return null
    }

    fun getAccentUIColor(doc: Document): Int? {
        val gameThemeCSS = doc.getElementById("game_theme")?.html()
        val gameThemeCSS = doc.selectFirst("#game_theme, #jam_theme")?.html()
        if (gameThemeCSS != null) {
            val foundColors = gameButtonColorPattern.find(gameThemeCSS)
            if (foundColors != null)


@@ 167,12 175,20 @@ object ItchWebsiteUtils {
            if (foundColors != null)
                return Utils.parseCssColor(foundColors.groupValues[1])
        }

        val jamThemeCSS = doc.selectFirst("#jam_theme")?.html()
        if (jamThemeCSS != null) {
            val foundColors = jamLinkColorPattern.find(jamThemeCSS)
            if (foundColors != null)
                return Utils.parseCssColor(foundColors.groupValues[1])
        }

        return null
    }


    fun getAccentFgUIColor(doc: Document): Int? {
        val gameThemeCSS = doc.getElementById("game_theme")?.html()
        val gameThemeCSS = doc.selectFirst("#game_theme, #jam_theme")?.html()
        if (gameThemeCSS != null) {
            val foundColors = gameButtonFgColorPattern.find(gameThemeCSS)
            if (foundColors != null)


@@ 185,6 201,18 @@ object ItchWebsiteUtils {
            if (foundColors != null)
                return Utils.parseCssColor(foundColors.groupValues[1])
        }

        val jamThemeCSS = doc.selectFirst("#jam_theme")?.html()
        if (jamThemeCSS != null) {
            val foundColors = jamLinkColorPattern.find(jamThemeCSS)
            if (foundColors != null) {
                val color = Utils.parseCssColor(foundColors.groupValues[1])
                return if (Utils.shouldUseLightForeground(color))
                    R.color.colorPrimary
                else
                    R.color.colorPrimaryDark
            }
        }
        return null
    }


M app/src/main/java/ua/gardenapple/itchupdater/Utils.kt => app/src/main/java/ua/gardenapple/itchupdater/Utils.kt +5 -0
@@ 20,6 20,7 @@ import android.util.Log
import androidx.annotation.ColorInt
import androidx.annotation.ColorRes
import androidx.core.content.FileProvider
import androidx.core.graphics.ColorUtils
import com.github.ajalt.colormath.ConvertibleColor
import com.github.ajalt.colormath.fromCss
import kotlinx.coroutines.Dispatchers


@@ 267,4 268,8 @@ object Utils {
    fun getPreferredLocale(context: Context): Locale {
        return getPreferredLocale(context.resources.configuration)
    }

    fun shouldUseLightForeground(@ColorInt bgColor: Int): Boolean {
        return ColorUtils.calculateLuminance(bgColor) < 0.5
    }
}

M app/src/main/java/ua/gardenapple/itchupdater/client/ItchBrowseHandler.kt => app/src/main/java/ua/gardenapple/itchupdater/client/ItchBrowseHandler.kt +1 -1
@@ 99,7 99,7 @@ class ItchBrowseHandler(
        }
        val prefs = PreferenceManager.getDefaultSharedPreferences(context)
        prefs.edit(true) {
            if (!ItchWebsiteUtils.isStylizedPage(doc)) {
            if (ItchWebsiteUtils.shouldHandleDayNightThemes(doc)) {
                if (ItchWebsiteUtils.isDarkTheme(doc))
                    putString("current_site_theme", "dark")
                else

M app/src/main/java/ua/gardenapple/itchupdater/client/ItchWebsiteParser.kt => app/src/main/java/ua/gardenapple/itchupdater/client/ItchWebsiteParser.kt +5 -0
@@ 400,4 400,9 @@ object ItchWebsiteParser {

        throw IllegalArgumentException("Document is not a user page")
    }

    fun getForumOrJamName(doc: Document): String {
        return doc.selectFirst(".jam_title_header, .game_summary h1")?.text()
            ?: throw IllegalArgumentException("Could not find game jam or forum name")
    }
}
\ No newline at end of file

M app/src/main/java/ua/gardenapple/itchupdater/ui/BrowseFragment.kt => app/src/main/java/ua/gardenapple/itchupdater/ui/BrowseFragment.kt +103 -79
@@ 309,93 309,113 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {

        fab.show()

        if (doc != null && ItchWebsiteUtils.isStylizedPage(doc)) {
            if (ItchWebsiteUtils.isGamePage(doc)) {
                //Hide app's navbar after hiding web navbar
                val navBarHideCallback: (String) -> Unit = navBarHide@{
                    if (!isVisible)
                        return@navBarHide

                    navBar.visibility = View.GONE
                    if (info?.purchasedInfo != null) {
                        bottomGameBar.visibility = View.VISIBLE

                        if (info.hasAndroidVersion)
                            gameButton.text = getString(R.string.game_install)
                        else
                            gameButton.text = getString(R.string.game_download)
                        gameButtonInfo.text =
                            Utils.spannedFromHtml(info.purchasedInfo.ownershipReasonHtml)

                        gameButton.setOnClickListener {
                            mainActivity.browseUrl(info.purchasedInfo.downloadPage)
                        }
                    } else if (info?.isFromSpecialBundle == true) {
                        bottomGameBar.visibility = View.VISIBLE

                        gameButton.text = getString(R.string.game_bundle_claim)
                        if (info.isSpecialBundlePalestinian)
                            gameButtonInfo.text = getString(R.string.game_bundle_palestine)
                        else
                            gameButtonInfo.text = getString(R.string.game_bundle_justice)

                        gameButton.setOnClickListener {
                            lifecycleScope.launch {
                                SpecialBundleHandler.claimGame(
                                    info.bundleDownloadLink!!,
                                    info.game!!
                                )
                                webView.reload()
                            }
                        }
                    } else if (info?.paymentInfo != null) {
                        bottomGameBar.visibility = View.VISIBLE

                        if (!info.paymentInfo.isPaymentOptional)
                            gameButton.text = getString(R.string.game_buy)
                        else if (info.hasAndroidVersion)
                            gameButton.text = getString(R.string.game_install)
                        else
                            gameButton.text = getString(R.string.game_download)

                        gameButtonInfo.text =
                            Utils.spannedFromHtml(info.paymentInfo.messageHtml)

                        gameButton.setOnClickListener {
                            val purchaseUri = Uri.parse(info.game!!.storeUrl)
                                .buildUpon()
                                .appendPath("purchase")
                            mainActivity.browseUrl(purchaseUri.toString())
        if (doc?.let { ItchWebsiteUtils.isGamePage(doc) } == true) {
            //Hide app's navbar after hiding web navbar
            val navBarHideCallback: (String) -> Unit = navBarHide@{
                if (!isVisible)
                    return@navBarHide

                navBar.visibility = View.GONE
                if (info?.purchasedInfo != null) {
                    bottomGameBar.visibility = View.VISIBLE

                    if (info.hasAndroidVersion)
                        gameButton.text = getString(R.string.game_install)
                    else
                        gameButton.text = getString(R.string.game_download)
                    gameButtonInfo.text =
                        Utils.spannedFromHtml(info.purchasedInfo.ownershipReasonHtml)

                    gameButton.setOnClickListener {
                        mainActivity.browseUrl(info.purchasedInfo.downloadPage)
                    }
                } else if (info?.isFromSpecialBundle == true) {
                    bottomGameBar.visibility = View.VISIBLE

                    gameButton.text = getString(R.string.game_bundle_claim)
                    if (info.isSpecialBundlePalestinian)
                        gameButtonInfo.text = getString(R.string.game_bundle_palestine)
                    else
                        gameButtonInfo.text = getString(R.string.game_bundle_justice)

                    gameButton.setOnClickListener {
                        lifecycleScope.launch {
                            SpecialBundleHandler.claimGame(
                                info.bundleDownloadLink!!,
                                info.game!!
                            )
                            webView.reload()
                        }
                    } else {
                        bottomGameBar.visibility = View.GONE
                    }
                }
                if (ItchWebsiteUtils.siteHasNavbar(webView, doc)) {
                    setSiteNavbarVisibility(false, navBarHideCallback)
                } else if (info?.paymentInfo != null) {
                    bottomGameBar.visibility = View.VISIBLE

                    if (!info.paymentInfo.isPaymentOptional)
                        gameButton.text = getString(R.string.game_buy)
                    else if (info.hasAndroidVersion)
                        gameButton.text = getString(R.string.game_install)
                    else
                        gameButton.text = getString(R.string.game_download)

                    gameButtonInfo.text =
                        Utils.spannedFromHtml(info.paymentInfo.messageHtml)

                    gameButton.setOnClickListener {
                        val purchaseUri = Uri.parse(info.game!!.storeUrl)
                            .buildUpon()
                            .appendPath("purchase")
                        mainActivity.browseUrl(purchaseUri.toString())
                    }
                } else {
                    setSiteNavbarVisibility(true, navBarHideCallback)
                    bottomGameBar.visibility = View.GONE
                }
            }
            if (ItchWebsiteUtils.siteHasNavbar(webView, doc)) {
                setSiteNavbarVisibility(false, navBarHideCallback)
            } else {
                setSiteNavbarVisibility(true, navBarHideCallback)
            }

                supportAppBar.title =
                    Utils.spannedFromHtml("<b>${Html.escapeHtml(ItchWebsiteParser.getGameName(doc))}</b>")
            supportAppBar.title =
                Utils.spannedFromHtml("<b>${Html.escapeHtml(ItchWebsiteParser.getGameName(doc))}</b>")

                appBar.menu.clear()
                addAppBarActions(appBar, doc)
                addDefaultAppBarActions(appBar)
                supportAppBar.show()
            } else if (ItchWebsiteUtils.isUserPage(doc)) {
                val appBarTitle =
                    "<b>${Html.escapeHtml(ItchWebsiteParser.getUserName(doc))}</b>"
                supportAppBar.title = Utils.spannedFromHtml(appBarTitle)
            appBar.menu.clear()
            addAppBarActions(appBar, doc)
            addDefaultAppBarActions(appBar)
            supportAppBar.show()
        } else if (doc?.let { ItchWebsiteUtils.isUserPage(it) } == true) {
            val appBarTitle =
                "<b>${Html.escapeHtml(ItchWebsiteParser.getUserName(doc))}</b>"
            supportAppBar.title = Utils.spannedFromHtml(appBarTitle)

                appBar.menu.clear()
                addDefaultAppBarActions(appBar)
                supportAppBar.show()
            appBar.menu.clear()
            addDefaultAppBarActions(appBar)
            supportAppBar.show()

                bottomGameBar.visibility = View.GONE
                navBar.visibility = View.GONE
            }
            bottomGameBar.visibility = View.GONE
            navBar.visibility = View.GONE
        } else if (doc?.let { ItchWebsiteUtils.isUserPage(it) } == true) {
            val appBarTitle =
                "<b>${Html.escapeHtml(ItchWebsiteParser.getUserName(doc))}</b>"
            supportAppBar.title = Utils.spannedFromHtml(appBarTitle)

            appBar.menu.clear()
            addDefaultAppBarActions(appBar)
            supportAppBar.show()

            bottomGameBar.visibility = View.GONE
            navBar.visibility = View.GONE
        } else if (doc?.let { ItchWebsiteUtils.isJamOrForumPage(it) } == true) {
            val appBarTitle =
                "<b>${Html.escapeHtml(ItchWebsiteParser.getForumOrJamName(doc))}</b>"
            supportAppBar.title = Utils.spannedFromHtml(appBarTitle)

            appBar.menu.clear()
            addDefaultAppBarActions(appBar)
            supportAppBar.show()

            bottomGameBar.visibility = View.GONE
            navBar.visibility = View.GONE
        } else {
            navBar.visibility = View.VISIBLE
            bottomGameBar.visibility = View.GONE


@@ 415,6 435,10 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {
        val gameThemeButtonColor = doc?.run { ItchWebsiteUtils.getAccentUIColor(doc) }
        val gameThemeButtonFgColor = doc?.run { ItchWebsiteUtils.getAccentFgUIColor(doc) }

//        Log.d(LOGGING_TAG, "game theme bg color: $gameThemeBgColor")
//        Log.d(LOGGING_TAG, "game theme button color: $gameThemeButtonColor")
//        Log.d(LOGGING_TAG, "game theme button fg color: $gameThemeButtonFgColor")

        val accentColor = gameThemeButtonColor ?: defaultAccentColor
        val accentFgColor = gameThemeButtonFgColor ?: defaultWhiteColor