~gardenapple/mitch

99e73fd671c53c0423ccc824a001f5fa5a80e5c2 — gardenapple 3 days ago 19b556a main
Add bundle Claim button (close #3 on GitLab issue tracker)
M app/src/main/java/ua/gardenapple/itchupdater/ItchWebsiteUtils.kt => app/src/main/java/ua/gardenapple/itchupdater/ItchWebsiteUtils.kt +11 -0
@@ 21,6 21,7 @@ object ItchWebsiteUtils {
    private val gameBgColorPattern = Regex("root[{]--itchio_ui_bg: (#?\\w+);")
    private val gameButtonColorPattern = Regex("--itchio_button_color: (#?\\w+);")
    private val gameButtonFgColorPattern = Regex("--itchio_button_fg_color: (#?\\w+);")
    private val gameTextColorPattern = Regex("--itchio_text_color: ([^;]+);")

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


@@ 188,6 189,16 @@ object ItchWebsiteUtils {
        return null
    }

    fun getTextColor(doc: Document): Int? {
        val gameThemeCSS = doc.getElementById("game_theme")?.html()
        if (gameThemeCSS != null) {
            val foundColors = gameTextColorPattern.find(gameThemeCSS)
            if (foundColors != null)
                return Utils.parseCssColor(foundColors.groupValues[1])
        }
        return null
    }

    fun isDarkTheme(doc: Document): Boolean {
        return doc.getElementsByClass("main_layout").first()?.hasClass("dark_theme") ?: false
    }

M app/src/main/java/ua/gardenapple/itchupdater/client/ItchBrowseHandler.kt => app/src/main/java/ua/gardenapple/itchupdater/client/ItchBrowseHandler.kt +38 -7
@@ 14,6 14,7 @@ import ua.gardenapple.itchupdater.R
import ua.gardenapple.itchupdater.data.JusticeBundleGameIDs
import ua.gardenapple.itchupdater.data.PalestineBundleGameIDs
import ua.gardenapple.itchupdater.database.AppDatabase
import ua.gardenapple.itchupdater.database.game.Game

class ItchBrowseHandler(
    private val context: Context,


@@ 38,28 39,45 @@ class ItchBrowseHandler(
        private var currentDownloadMimeType: String? = null
    }

    suspend fun onPageVisited(doc: Document, url: String) {
    data class Info(
        val isFromSpecialBundle: Boolean,
        val isSpecialBundlePalestinian: Boolean,
        val bundleDownloadLink: String?,
        val game: Game?
    )

    suspend fun onPageVisited(doc: Document, url: String): Info {
        lastDownloadDoc = null
        lastDownloadPageUrl = null

        var bundleLink: String? = null
        var bundlePalestinian: Boolean? = null
        var game: Game? = null

        if (ItchWebsiteUtils.isStorePage(doc)) {
            withContext(Dispatchers.IO) {
                val db = AppDatabase.getDatabase(context)
                ItchWebsiteParser.getGameInfoForStorePage(doc, url)?.let { game ->
                ItchWebsiteParser.getGameInfoForStorePage(doc, url)?.let { gameInfo ->
                    game = gameInfo
                    Log.d(LOGGING_TAG, "Adding game $game")
                    db.gameDao.upsert(game)
                    db.gameDao.upsert(gameInfo)

                    if (JusticeBundleGameIDs.belongsToJusticeBundle(game.gameId)) {
                    if (JusticeBundleGameIDs.belongsToJusticeBundle(gameInfo.gameId)) {
                        Log.d(LOGGING_TAG, "Belongs to Racial Justice bundle!")
                        val username = ItchWebsiteUtils.getLoggedInUserName(doc)
                        val bundleLink = SpecialBundleHandler.getLinkForUser(context, false, username)

                        bundleLink = SpecialBundleHandler.getLinkForUser(context, false, username)
                        Log.d(LOGGING_TAG, "Bundle link: $bundleLink")
                        bundlePalestinian = false
                    }
                    if (PalestineBundleGameIDs.belongsToPalestineBundle(game.gameId)) {

                    if (PalestineBundleGameIDs.belongsToPalestineBundle(gameInfo.gameId)) {
                        Log.d(LOGGING_TAG, "Belongs to Palestinian Aid bundle!")
                        val username = ItchWebsiteUtils.getLoggedInUserName(doc)
                        val bundleLink = SpecialBundleHandler.getLinkForUser(context, true, username)

                        bundleLink = SpecialBundleHandler.getLinkForUser(context, true, username)
                        Log.d(LOGGING_TAG, "Bundle link: $bundleLink")
                        bundlePalestinian = true
                    }
                }
            }


@@ 82,6 100,19 @@ class ItchBrowseHandler(
        if (SpecialBundleHandler.checkIsBundleLink(context, doc, url)) {
            Log.d(LOGGING_TAG, "Is bundle link! $url")
        }
        return bundleLink?.let { link ->
            Info(
                isFromSpecialBundle = true,
                isSpecialBundlePalestinian = bundlePalestinian!!,
                bundleDownloadLink = link,
                game = game
            )
        } ?: Info(
            isFromSpecialBundle = false,
            isSpecialBundlePalestinian = false,
            bundleDownloadLink = null,
            game = game
        )
    }

    suspend fun setClickedUploadId(uploadId: Int) = withContext(Dispatchers.IO) {

M app/src/main/java/ua/gardenapple/itchupdater/ui/BrowseFragment.kt => app/src/main/java/ua/gardenapple/itchupdater/ui/BrowseFragment.kt +47 -5
@@ 8,6 8,7 @@ import android.net.Uri
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.transition.Visibility
import android.util.Log
import android.view.*
import android.view.inputmethod.EditorInfo


@@ 20,6 21,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ShareCompat
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import androidx.preference.PreferenceManager
import com.google.android.material.textfield.TextInputEditText
import com.leinardi.android.speeddial.SpeedDialActionItem


@@ 31,6 33,7 @@ import ua.gardenapple.itchupdater.R
import ua.gardenapple.itchupdater.Utils
import ua.gardenapple.itchupdater.client.ItchBrowseHandler
import ua.gardenapple.itchupdater.client.ItchWebsiteParser
import ua.gardenapple.itchupdater.client.SpecialBundleHandler
import ua.gardenapple.itchupdater.databinding.BrowseFragmentBinding
import ua.gardenapple.itchupdater.databinding.DialogWebPromptBinding
import java.io.ByteArrayInputStream


@@ 53,6 56,7 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {

    var browseHandler: ItchBrowseHandler? = null
    private var currentDoc: Document? = null
    private var currentInfo: ItchBrowseHandler.Info? = null


    override fun onAttach(context: Context) {


@@ 269,11 273,11 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {
     * Adapts the app's UI to the theme of the current web page.
     */
    fun updateUI() {
        updateUI(currentDoc)
        updateUI(currentDoc, currentInfo)
    }

    fun restoreDefaultUI() {
        updateUI(null)
        updateUI(null, null)
    }

    //TODO: hide stuff on scroll?


@@ 282,8 286,9 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {
     * fragment is selected.
     * Must run on the UI thread!
     * @param doc the parsed DOM of the page the user is currently on. Null if the UI shouldn't adapt to any web page at all
     * @param info metadata about the page
     */
    private fun updateUI(doc: Document?) {
    private fun updateUI(doc: Document?, info: ItchBrowseHandler.Info?) {
        if (!this::chromeClient.isInitialized || isWebFullscreen)
            return



@@ 295,9 300,12 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {
        //Log.d(LOGGING_TAG, "Processing UI...")

        val navBar = mainActivity.binding.bottomNavigationView
        val bottomGameBar = mainActivity.binding.bottomGameBar
        val fab = mainActivity.binding.speedDial
        val supportAppBar = mainActivity.supportActionBar!!
        val appBar = mainActivity.binding.toolbar
        val gameButton = mainActivity.binding.gameButton
        val gameButtonInfo = mainActivity.binding.gameButtonInfo

        fab.show()



@@ 307,8 315,32 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {
            if (ItchWebsiteUtils.isGamePage(doc)) {
                //Hide app's navbar after hiding web navbar
                val navBarHideCallback: (String) -> Unit = {
                    if (isVisible)
                    if (isVisible) {
                        navBar.visibility = View.GONE
                        if (info?.isFromSpecialBundle == true) {
                            bottomGameBar.visibility = View.VISIBLE

                            //Required for marquee animation
                            gameButtonInfo.isSelected = true

                            gameButton.text = resources.getString(R.string.game_justice_bundle_claim)
                            if (info.isSpecialBundlePalestinian)
                                gameButtonInfo.text = "Indie bundle for Palestinian Aid"
                            else
                                gameButtonInfo.text = "Bundle for Racial Justice and Equality"
                            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)


@@ 343,10 375,12 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {
                addDefaultAppBarActions(appBar)
                supportAppBar.show()

                bottomGameBar.visibility = View.GONE
                navBar.visibility = View.GONE
            }
        } else {
            navBar.visibility = View.VISIBLE
            bottomGameBar.visibility = View.GONE
            supportAppBar.hide()
        }



@@ 362,12 396,14 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {
        val gameThemeBgColor = doc?.run { ItchWebsiteUtils.getBackgroundUIColor(doc) }
        val gameThemeButtonColor = doc?.run { ItchWebsiteUtils.getAccentUIColor(doc) }
        val gameThemeButtonFgColor = doc?.run { ItchWebsiteUtils.getAccentFgUIColor(doc) }
        val gameThemeTextColor = doc?.run { ItchWebsiteUtils.getTextColor(doc) }

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

        val bgColor = gameThemeBgColor ?: defaultBgColor
        val fgColor = if (gameThemeBgColor == null) defaultFgColor else defaultWhiteColor
        val miscTextColor = gameThemeTextColor ?: fgColor

        fab.mainFabClosedBackgroundColor = accentColor
        fab.mainFabOpenedBackgroundColor = accentColor


@@ 387,6 423,11 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {
        appBar.setTitleTextColor(fgColor)
        appBar.overflowIcon?.setTint(fgColor)

        bottomGameBar.setBackgroundColor(bgColor)
        gameButtonInfo.setTextColor(miscTextColor)
        gameButton.setTextColor(accentFgColor)
        gameButton.setBackgroundColor(accentColor)

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            mainActivity.window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS)
            mainActivity.window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)


@@ 549,8 590,9 @@ class BrowseFragment : Fragment(), CoroutineScope by MainScope() {

            fragment.launch(Dispatchers.Default) {
                val doc = Jsoup.parse(html)
                fragment.browseHandler?.onPageVisited(doc, url)
                val info = fragment.browseHandler?.onPageVisited(doc, url)
                fragment.currentDoc = doc
                fragment.currentInfo = info
                fragment.activity?.runOnUiThread {
                    fragment.updateUI()
                }

M app/src/main/res/layout/activity_main.xml => app/src/main/res/layout/activity_main.xml +49 -15
@@ 22,35 22,69 @@
            android:id="@+id/fragmentContainer"
            android:layout_width="0dp"
            android:layout_height="0dp"
            app:layout_constraintBottom_toTopOf="@+id/bottomNavigationView"
            app:layout_constraintBottom_toTopOf="@id/bottomView"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toBottomOf="@+id/toolbar" />

    <com.google.android.material.bottomnavigation.BottomNavigationView
            android:id="@+id/bottomNavigationView"
    <FrameLayout
            android:id="@+id/bottomView"
            android:layout_width="0dp"
            android:layout_height="wrap_content"
            android:background="?colorPrimary"
            android:paddingLeft="0dp"
            android:paddingRight="0dp"
            app:itemBackground="?colorPrimary"
            app:itemIconTint="@drawable/bottom_navigation_selector"
            app:itemTextColor="@drawable/bottom_navigation_selector"
            app:labelVisibilityMode="labeled"
            android:elevation="10dp"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintEnd_toEndOf="parent"
            app:layout_constraintStart_toStartOf="parent"
            app:menu="@menu/main" />
            app:layout_constraintStart_toStartOf="parent">

        <com.google.android.material.bottomnavigation.BottomNavigationView
                tools:visibility="gone"
                android:id="@+id/bottomNavigationView"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:background="?colorPrimary"
                android:elevation="10dp"
                android:paddingLeft="0dp"
                android:paddingRight="0dp"
                app:itemBackground="?colorPrimary"
                app:itemIconTint="@drawable/bottom_navigation_selector"
                app:itemTextColor="@drawable/bottom_navigation_selector"
                app:labelVisibilityMode="labeled"
                app:menu="@menu/main" />
        <LinearLayout
                android:visibility="gone"
                tools:visibility="visible"
                android:id="@+id/bottomGameBar"
                android:layout_width="match_parent"
                android:layout_height="wrap_content"
                android:orientation="horizontal"
                android:padding="5dp">
            <com.google.android.material.button.MaterialButton
                    style="@style/FilledButtonStyle"
                    android:id="@+id/game_button"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:text="@string/game_install" />
            <TextView
                    android:id="@+id/game_button_info"
                    android:layout_width="wrap_content"
                    android:layout_height="wrap_content"
                    android:layout_marginHorizontal="10dp"
                    android:text="$5.10 or more"
                    android:ellipsize="marquee"
                    android:focusable="true"
                    android:focusableInTouchMode="true"
                    android:marqueeRepeatLimit="marquee_forever"
                    android:scrollHorizontally="true"
                    android:singleLine="true"/>
        </LinearLayout>
    </FrameLayout>

    <com.leinardi.android.speeddial.SpeedDialView
            android:id="@+id/speedDial"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="bottom|end"
            app:layout_constraintBottom_toBottomOf="@+id/fragmentContainer"
            app:layout_constraintEnd_toEndOf="@+id/fragmentContainer"
            app:layout_constraintBottom_toTopOf="@id/bottomView"
            app:layout_constraintEnd_toEndOf="parent"
            app:sdMainFabClosedSrc="@drawable/ic_baseline_more_vert_24"
            app:sdMainFabClosedIconColor="@color/colorPrimary"/>


M app/src/main/res/layout/browse_fragment.xml => app/src/main/res/layout/browse_fragment.xml +2 -0
@@ 29,4 29,6 @@
            app:layout_constraintStart_toStartOf="parent"
            app:mpb_progressStyle="horizontal"
            app:mpb_useIntrinsicPadding="false" />


</androidx.constraintlayout.widget.ConstraintLayout>
\ No newline at end of file

M app/src/main/res/values-ru-rRU/strings.xml => app/src/main/res/values-ru-rRU/strings.xml +3 -0
@@ 148,4 148,7 @@
    <string name="game_justice_bundle_claim">Присвоить</string>
    <string name="game_buy">Купить</string>
    <string name="popup_handler_app_not_found">Не знаю, как открыть %s</string>
    <string name="game_download">Скачать</string>
    <string name="game_install">Установить</string>
    <string name="game_price">%s или больше</string>
</resources>
\ No newline at end of file

M app/src/main/res/values-uk-rUA/strings.xml => app/src/main/res/values-uk-rUA/strings.xml +3 -0
@@ 148,4 148,7 @@
    <string name="game_justice_bundle_claim">Присвоїти</string>
    <string name="game_buy">Купити</string>
    <string name="popup_handler_app_not_found">Не знаю, як відкрити %s</string>
    <string name="game_download">Завантажити</string>
    <string name="game_install">Встановити</string>
    <string name="game_price">%s чи більше</string>
</resources>
\ No newline at end of file

M app/src/main/res/values/strings.xml => app/src/main/res/values/strings.xml +3 -0
@@ 149,6 149,9 @@
    <string name="popup_handler_app_not_found">Don\'t know how to open %s</string>
    <string name="game_justice_bundle_claim">Claim</string>
    <string name="game_buy">Purchase</string>
    <string name="game_download">Download</string>
    <string name="game_install">Install</string>
    <string name="game_price">%s or more</string>
    <string name="game_justice_bundle_claim_error">Could not access download link from the Racial Justice bundle.</string>
    <string name="settings_justice_link">Bundle for Racial Justice and Equality</string>
    <string name="settings_justice_link_desc">Set custom download URL</string>

M app/src/main/res/values/styles.xml => app/src/main/res/values/styles.xml +1 -0
@@ 95,6 95,7 @@
        <item name="android:textColor">?colorPrimary</item>
        <item name="textAllCaps">false</item>
        <item name="android:letterSpacing">0</item>
        <item name="android:textStyle">bold</item>
    </style>

    <style name="TextInputStyle" parent="Widget.MaterialComponents.TextInputLayout.FilledBox">