Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 76b3c429 authored by Sayantan Roychowdhury's avatar Sayantan Roychowdhury
Browse files

Merge branch '5413-timeout_dialog' into 'main'

[POST V1] Issue 5413 - Display timeout dialog

See merge request !126
parents b1c4d5ff feb40027
Loading
Loading
Loading
Loading
Loading
+7 −0
Original line number Diff line number Diff line
@@ -45,6 +45,7 @@ import foundation.e.apps.setup.signin.SignInViewModel
import foundation.e.apps.updates.UpdatesNotifier
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.User
import foundation.e.apps.utils.parentFragment.TimeoutFragment
import foundation.e.apps.utils.modules.CommonUtilsModule
import kotlinx.coroutines.launch
import java.io.File
@@ -140,6 +141,12 @@ class MainActivity : AppCompatActivity() {
                        generateAuthDataBasedOnUserType(user)
                    } else {
                        Log.d(TAG, "Timeout validating auth data!")
                        val lastFragment = navHostFragment.childFragmentManager.fragments[0]
                        if (lastFragment is TimeoutFragment) {
                            Log.d(TAG, "Displaying timeout from MainActivity on fragment: "
                                    + lastFragment::class.java.name)
                            lastFragment.onTimeout()
                        }
                    }
                }
            } else {
+17 −107
Original line number Diff line number Diff line
@@ -20,13 +20,11 @@ package foundation.e.apps

import android.app.Activity
import android.content.Context
import android.content.DialogInterface
import android.graphics.Bitmap
import android.os.Build
import android.os.SystemClock
import android.util.Base64
import android.util.Log
import android.view.KeyEvent
import android.widget.ImageView
import androidx.annotation.RequiresApi
import androidx.appcompat.app.AlertDialog
@@ -50,7 +48,6 @@ import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.manager.fused.FusedManagerRepository
import foundation.e.apps.manager.pkg.PkgManagerModule
import foundation.e.apps.manager.workmanager.InstallWorkManager
import foundation.e.apps.settings.SettingsFragment
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Type
@@ -95,108 +92,6 @@ class MainActivityViewModel @Inject constructor(
     */
    var firstAuthDataFetchTime = 0L

    /*
     * Alert dialog to show to user if App Lounge times out.
     *
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404
     */
    private lateinit var timeoutAlertDialog: AlertDialog

    /**
     * Display timeout alert dialog.
     *
     * @param activity Activity class. Basically the MainActivity.
     * @param positiveButtonBlock Code block when "Retry" is pressed.
     * @param openSettings Code block when "Open Settings" button is pressed.
     * This should open the [SettingsFragment] fragment.
     * @param applicationTypeFromPreferences Application type string, can be one of
     * [FusedAPIImpl.APP_TYPE_ANY], [FusedAPIImpl.APP_TYPE_OPEN], [FusedAPIImpl.APP_TYPE_PWA]
     */
    fun displayTimeoutAlertDialog(
        activity: Activity,
        positiveButtonBlock: () -> Unit,
        openSettings: () -> Unit,
        applicationTypeFromPreferences: String,
    ) {
        if (!this::timeoutAlertDialog.isInitialized) {
            timeoutAlertDialog = AlertDialog.Builder(activity).apply {
                setTitle(R.string.timeout_title)
                /*
                 * Prevent dismissing the dialog from pressing outside as it will only
                 * show a blank screen below the dialog.
                 */
                setCancelable(false)
                /*
                 * If user presses back button to close the dialog without selecting anything,
                 * close App Lounge.
                 */
                setOnKeyListener { dialog, keyCode, _ ->
                    if (keyCode == KeyEvent.KEYCODE_BACK) {
                        dialog.dismiss()
                        activity.finish()
                    }
                    true
                }
            }.create()
        }

        timeoutAlertDialog.apply {
            /*
             * Set retry button.
             */
            setButton(DialogInterface.BUTTON_POSITIVE, activity.getString(R.string.retry)) { _, _ ->
                positiveButtonBlock()
            }
            /*
             * Set message based on apps from GPlay of cleanapk.
             */
            setMessage(
                activity.getString(
                    when (applicationTypeFromPreferences) {
                        FusedAPIImpl.APP_TYPE_ANY -> R.string.timeout_desc_gplay
                        else -> R.string.timeout_desc_cleanapk
                    }
                )
            )
            /*
             * Show "Open Setting" only for GPlay apps.
             */
            if (applicationTypeFromPreferences == FusedAPIImpl.APP_TYPE_ANY) {
                setButton(
                    DialogInterface.BUTTON_NEUTRAL,
                    activity.getString(R.string.open_settings)
                ) { _, _ ->
                    openSettings()
                }
            }
        }

        timeoutAlertDialog.show()
    }

    /**
     * Returns true if [timeoutAlertDialog] is displaying.
     * Returs false if it is not initialised.
     */
    fun isTimeoutDialogDisplayed(): Boolean {
        return if (this::timeoutAlertDialog.isInitialized) {
            timeoutAlertDialog.isShowing
        } else false
    }

    /**
     * Dismisses the [timeoutAlertDialog] if it is being displayed.
     * Does nothing if it is not being displayed.
     * Caller need not check if the dialog is being displayed.
     */
    fun dismissTimeoutDialog() {
        if (isTimeoutDialogDisplayed()) {
            try {
                timeoutAlertDialog.dismiss()
            } catch (_: Exception) {}
        }
    }

    // Downloads
    val downloadList = fusedManagerRepository.getDownloadLiveList()
    var installInProgress = false
@@ -214,8 +109,10 @@ class MainActivityViewModel @Inject constructor(
    }

    fun setFirstTokenFetchTime() {
        if (firstAuthDataFetchTime == 0L) {
            firstAuthDataFetchTime = SystemClock.uptimeMillis()
        }
    }

    fun isTimeEligibleForTokenRefresh(): Boolean {
        return (SystemClock.uptimeMillis() - firstAuthDataFetchTime) <= timeoutDurationInMillis
@@ -229,6 +126,7 @@ class MainActivityViewModel @Inject constructor(
     * Issue: https://gitlab.e.foundation/e/backlog/-/issues/5404
     */
    fun retryFetchingTokenAfterTimeout() {
        firstAuthDataFetchTime = 0
        setFirstTokenFetchTime()
        authValidity.postValue(false)
    }
@@ -249,7 +147,19 @@ class MainActivityViewModel @Inject constructor(
        if (!authRequestRunning) {
            authRequestRunning = true
            viewModelScope.launch {
                fusedAPIRepository.fetchAuthData()
                /*
                 * If getting auth data failed, try getting again.
                 * Sending false in authValidity, triggers observer in MainActivity,
                 * causing it to destroy credentials and try to regenerate auth data.
                 *
                 * Issue:
                 * https://gitlab.e.foundation/e/backlog/-/issues/5413
                 * https://gitlab.e.foundation/e/backlog/-/issues/5404
                 */
                if (!fusedAPIRepository.fetchAuthData()) {
                    authRequestRunning = false
                    authValidity.postValue(false)
                }
            }
        }
    }
+78 −0
Original line number Diff line number Diff line
package foundation.e.apps.api

import foundation.e.apps.utils.enums.ResultStatus

/**
 * Currently defunct, not being used anywhere.
 * Prototype to merge API request and also get rid of Pair, Triple for timeout related cases.
 */
open class JobResult<T> private constructor(val status: ResultStatus) {

    /*
     * Classes for returning multiple data from a function along with a status
     * in the form of ResultStatus.
     * Use the static overloaded create methods (in companion object) to for easy creation.
     *
     * If needed to just pass a single data element with status for API requests,
     * see the static methods success(), error(), loading() (in companion object).
     */
    class of1<A> (val data1: A, status: ResultStatus): JobResult<A>(status)
    class of2<A,B> (val data1: A, val data2: B, status: ResultStatus): JobResult<A>(status)
    class of3<A,B,C> (val data1: A, val data2: B, val data3: C, status: ResultStatus): JobResult<A>(status)

    var message = ""

    /*
     * This is the primary data, mainly for API requests which might send null data.
     * Other data (type B, C ...) are secondary/optional data.
     *
     * For non-null return type, directly use of1, of2, of3 ... classes
     * and directly access data1, data2, data3 ...
     */
    val data: T? get() = when(this) {
        is of1 -> this.data1
        is of2<T, *> -> this.data1
        is of3<T, *, *> -> this.data1
        else -> null
    }

    fun isSuccess(): Boolean {
        return status == ResultStatus.OK
    }

    companion object {
        fun <A> create(data1: A, status: ResultStatus, message: String? = null): of1<A> {
            return of1(data1, status).apply {
                message?.let { this.message = message }
            }
        }
        fun <A,B> create(data1: A, data2: B, status: ResultStatus, message: String? = null): of2<A,B> {
            return of2(data1, data2, status).apply {
                message?.let { this.message = message }
            }
        }
        fun <A,B,C> create(data1: A, data2: B, data3: C, status: ResultStatus, message: String? = null): of3<A,B,C> {
            return of3(data1, data2, data3, status).apply {
                message?.let { this.message = message }
            }
        }

        /*
         * Methods for API
         */
        fun <T> success(data: T): JobResult<T> {
            return of1(data, ResultStatus.OK)
        }
        fun <T> error(message: String, data: T? = null): JobResult<T> {
            val result = if (data == null) JobResult(ResultStatus.UNKNOWN)
            else of1<T>(data, ResultStatus.UNKNOWN)
            return result.apply {
                this.message = message
            }
        }
        /*fun <T> loading(data: T?): JobResult<T> {
            return if (data == null) JobResult(ResultStatus.LOADING)
            else JobResult.of1(data, ResultStatus.LOADING)
        }*/
    }
}
 No newline at end of file
+310 −124

File changed.

Preview size limit exceeded, changes collapsed.

+15 −10
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.api.fused.data.FusedCategory
import foundation.e.apps.api.fused.data.FusedHome
import foundation.e.apps.manager.database.fusedDownload.FusedDownload
import foundation.e.apps.utils.enums.ResultStatus
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Status
import javax.inject.Inject
@@ -34,7 +35,7 @@ import javax.inject.Singleton
class FusedAPIRepository @Inject constructor(
    private val fusedAPIImpl: FusedAPIImpl
) {
    suspend fun getHomeScreenData(authData: AuthData): Pair<List<FusedHome>, String> {
    suspend fun getHomeScreenData(authData: AuthData): Pair<List<FusedHome>, ResultStatus> {
        return fusedAPIImpl.getHomeScreenData(authData)
    }

@@ -42,6 +43,10 @@ class FusedAPIRepository @Inject constructor(
        return fusedAPIImpl.isFusedHomesEmpty(fusedHomes)
    }

    fun getApplicationCategoryPreference(): String {
        return fusedAPIImpl.getApplicationCategoryPreference()
    }

    suspend fun validateAuthData(authData: AuthData): Boolean {
        return fusedAPIImpl.validateAuthData(authData)
    }
@@ -50,7 +55,7 @@ class FusedAPIRepository @Inject constructor(
        packageNameList: List<String>,
        authData: AuthData,
        origin: Origin
    ): List<FusedApp> {
    ): Pair<List<FusedApp>, ResultStatus> {
        return fusedAPIImpl.getApplicationDetails(packageNameList, authData, origin)
    }

@@ -59,7 +64,7 @@ class FusedAPIRepository @Inject constructor(
        packageName: String,
        authData: AuthData,
        origin: Origin
    ): FusedApp {
    ): Pair<FusedApp, ResultStatus> {
        return fusedAPIImpl.getApplicationDetails(id, packageName, authData, origin)
    }

@@ -75,7 +80,7 @@ class FusedAPIRepository @Inject constructor(
        )
    }

    suspend fun getCategoriesList(type: Category.Type, authData: AuthData): List<FusedCategory> {
    suspend fun getCategoriesList(type: Category.Type, authData: AuthData): Triple<List<FusedCategory>, String, ResultStatus> {
        return fusedAPIImpl.getCategoriesList(type, authData)
    }

@@ -83,7 +88,7 @@ class FusedAPIRepository @Inject constructor(
        return fusedAPIImpl.getSearchSuggestions(query, authData)
    }

    suspend fun fetchAuthData(): Unit? {
    suspend fun fetchAuthData(): Boolean {
        return fusedAPIImpl.fetchAuthData()
    }

@@ -91,7 +96,7 @@ class FusedAPIRepository @Inject constructor(
        return fusedAPIImpl.fetchAuthData(email, aasToken)
    }

    suspend fun getSearchResults(query: String, authData: AuthData): List<FusedApp> {
    suspend fun getSearchResults(query: String, authData: AuthData): Pair<List<FusedApp>, ResultStatus> {
        return fusedAPIImpl.getSearchResults(query, authData)
    }

@@ -103,7 +108,7 @@ class FusedAPIRepository @Inject constructor(
        return fusedAPIImpl.getPlayStoreAppCategoryUrls(browseUrl, authData)
    }

    suspend fun getAppsAndNextClusterUrl(browseUrl: String, authData: AuthData): Pair<List<FusedApp>, String> {
    suspend fun getAppsAndNextClusterUrl(browseUrl: String, authData: AuthData): Triple<List<FusedApp>, String, ResultStatus> {
        return fusedAPIImpl.getAppsAndNextClusterUrl(browseUrl, authData)
    }

@@ -112,10 +117,10 @@ class FusedAPIRepository @Inject constructor(
        browseUrl: String,
        authData: AuthData,
        source: String
    ): List<FusedApp> {
    ): Pair<List<FusedApp>, ResultStatus> {
        return when (source) {
            "Open Source" -> fusedAPIImpl.getOpenSourceApps(category) ?: listOf()
            "PWA" -> fusedAPIImpl.getPWAApps(category) ?: listOf()
            "Open Source" -> fusedAPIImpl.getOpenSourceApps(category)
            "PWA" -> fusedAPIImpl.getPWAApps(category)
            else -> fusedAPIImpl.getPlayStoreApps(browseUrl, authData)
        }
    }
Loading