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

Commit cfd01053 authored by Hasib Prince's avatar Hasib Prince
Browse files

Merge branch 'install_progressbar_#4946' into epic_176-all-refactorAndGplay

parents 91aa4d11 f52f5a0e
Loading
Loading
Loading
Loading
+209 −109
Original line number Original line Diff line number Diff line
@@ -23,21 +23,26 @@ import android.graphics.Color
import android.graphics.drawable.Drawable
import android.graphics.drawable.Drawable
import android.os.Bundle
import android.os.Bundle
import android.text.Html
import android.text.Html
import android.text.format.Formatter
import android.util.Log
import android.util.Log
import android.view.View
import android.view.View
import android.widget.ImageView
import android.widget.ImageView
import android.widget.RelativeLayout
import androidx.core.content.ContextCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.graphics.BlendModeCompat
import androidx.fragment.app.Fragment
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
import androidx.fragment.app.viewModels
import androidx.lifecycle.lifecycleScope
import androidx.navigation.findNavController
import androidx.navigation.findNavController
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.findNavController
import androidx.navigation.fragment.navArgs
import androidx.navigation.fragment.navArgs
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager
import coil.load
import coil.load
import com.google.android.material.button.MaterialButton
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.snackbar.Snackbar
import com.google.android.material.textview.MaterialTextView
import dagger.hilt.android.AndroidEntryPoint
import dagger.hilt.android.AndroidEntryPoint
import foundation.e.apps.MainActivityViewModel
import foundation.e.apps.MainActivityViewModel
import foundation.e.apps.R
import foundation.e.apps.R
@@ -46,10 +51,13 @@ import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.application.model.ApplicationScreenshotsRVAdapter
import foundation.e.apps.application.model.ApplicationScreenshotsRVAdapter
import foundation.e.apps.application.subFrags.ApplicationDialogFragment
import foundation.e.apps.application.subFrags.ApplicationDialogFragment
import foundation.e.apps.databinding.FragmentApplicationBinding
import foundation.e.apps.databinding.FragmentApplicationBinding
import foundation.e.apps.manager.download.data.DownloadProgress
import foundation.e.apps.manager.pkg.PkgManagerModule
import foundation.e.apps.manager.pkg.PkgManagerModule
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.User
import foundation.e.apps.utils.enums.User
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Inject


@AndroidEntryPoint
@AndroidEntryPoint
@@ -70,7 +78,8 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) {
    private var applicationIcon: ImageView? = null
    private var applicationIcon: ImageView? = null


    companion object {
    companion object {
        private const val PRIVACY_SCORE_SOURCE_CODE_URL = "https://gitlab.e.foundation/e/apps/apps/-/blob/main/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt#L131"
        private const val PRIVACY_SCORE_SOURCE_CODE_URL =
            "https://gitlab.e.foundation/e/apps/apps/-/blob/main/app/src/main/java/foundation/e/apps/application/ApplicationViewModel.kt#L131"
        private const val EXODUS_URL = "https://exodus-privacy.eu.org"
        private const val EXODUS_URL = "https://exodus-privacy.eu.org"
        private const val PRIVACY_GUIDELINE_URL = "https://doc.e.foundation/privacy_score"
        private const val PRIVACY_GUIDELINE_URL = "https://doc.e.foundation/privacy_score"
    }
    }
@@ -122,113 +131,6 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) {
            }
            }
        }
        }


        applicationViewModel.appStatus.observe(viewLifecycleOwner) { status ->
            val installButton = binding.downloadInclude.installButton
            val downloadPB = binding.downloadInclude.appInstallPB
            val appSize = binding.downloadInclude.appSize
            val fusedApp = applicationViewModel.fusedApp.value ?: FusedApp()

            when (status) {
                Status.INSTALLED -> {
                    installButton.apply {
                        isEnabled = true
                        text = getString(R.string.open)
                        setTextColor(Color.WHITE)
                        backgroundTintList =
                            ContextCompat.getColorStateList(view.context, R.color.colorAccent)
                        setOnClickListener {
                            startActivity(pkgManagerModule.getLaunchIntent(fusedApp.package_name))
                        }
                    }
                }
                Status.UPDATABLE -> {
                    installButton.apply {
                        text = getString(R.string.update)
                        setTextColor(Color.WHITE)
                        backgroundTintList =
                            ContextCompat.getColorStateList(view.context, R.color.colorAccent)
                        setOnClickListener {
                            applicationIcon?.let {
                                mainActivityViewModel.getApplication(fusedApp, it)
                            }
                        }
                    }
                    downloadPB.visibility = View.GONE
                    appSize.visibility = View.VISIBLE
                }
                Status.UNAVAILABLE -> {
                    installButton.apply {
                        text = getString(R.string.install)
                        setOnClickListener {
                            applicationIcon?.let {
                                mainActivityViewModel.getApplication(fusedApp, it)
                            }
                        }
                    }
                    downloadPB.visibility = View.GONE
                    appSize.visibility = View.VISIBLE
                }
                Status.QUEUED -> {
                    installButton.apply {
                        text = getString(R.string.cancel)
                        setOnClickListener {
                            mainActivityViewModel.cancelDownload(fusedApp)
                        }
                    }
                }
                Status.DOWNLOADING -> {
                    installButton.apply {
                        text = getString(R.string.cancel)
                        setOnClickListener {
                            mainActivityViewModel.cancelDownload(fusedApp)
                        }
                    }
                    downloadPB.visibility = View.VISIBLE
                    appSize.visibility = View.GONE
                    applicationViewModel.downloadProgress.observe(viewLifecycleOwner) {
                        downloadPB.max = it.totalSizeBytes.values.sum().toInt()
                        downloadPB.progress = it.bytesDownloadedSoFar.values.sum().toInt()
                    }
                }
                Status.INSTALLING, Status.UNINSTALLING -> {
                    installButton.isEnabled = false
                    downloadPB.visibility = View.GONE
                    appSize.visibility = View.VISIBLE
                }
                Status.BLOCKED -> {
                    installButton.setOnClickListener {
                        val errorMsg = when (
                            User.valueOf(
                                mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name
                            )
                        ) {
                            User.ANONYMOUS,
                            User.UNAVAILABLE -> getString(R.string.install_blocked_anonymous)
                            User.GOOGLE -> getString(R.string.install_blocked_google)
                        }
                        if (errorMsg.isNotBlank()) {
                            Snackbar.make(view, errorMsg, Snackbar.LENGTH_SHORT).show()
                        }
                    }
                }
                Status.INSTALLATION_ISSUE -> {
                    installButton.apply {
                        text = getString(R.string.retry)
                        setOnClickListener {
                            applicationIcon?.let {
                                mainActivityViewModel.getApplication(fusedApp, it)
                            }
                        }
                    }
                    downloadPB.visibility = View.GONE
                    appSize.visibility = View.VISIBLE
                }
                else -> {
                    Log.d(TAG, "Unknown status: $status")
                }
            }
        }

        applicationViewModel.fusedApp.observe(viewLifecycleOwner) {
        applicationViewModel.fusedApp.observe(viewLifecycleOwner) {
            if (applicationViewModel.appStatus.value == null) {
            if (applicationViewModel.appStatus.value == null) {
                applicationViewModel.appStatus.value = it.status
                applicationViewModel.appStatus.value = it.status
@@ -342,11 +244,209 @@ class ApplicationFragment : Fragment(R.layout.fragment_application) {
                    ).show(childFragmentManager, TAG)
                    ).show(childFragmentManager, TAG)
                }
                }
            }
            }

            observeDownloadStatus(view)
            fetchAppTracker()
            fetchAppTracker()
        }
        }
    }
    }


    private fun observeDownloadStatus(view: View) {
        applicationViewModel.appStatus.observe(viewLifecycleOwner) { status ->
            val installButton = binding.downloadInclude.installButton
            val downloadPB = binding.downloadInclude.progressLayout
            val appSize = binding.downloadInclude.appSize
            val fusedApp = applicationViewModel.fusedApp.value ?: FusedApp()

            when (status) {
                Status.INSTALLED -> handleInstalled(installButton, view, fusedApp)
                Status.UPDATABLE -> handleUpdatable(
                    installButton,
                    view,
                    fusedApp,
                    downloadPB,
                    appSize
                )
                Status.UNAVAILABLE -> handleUnavaiable(installButton, fusedApp, downloadPB, appSize)
                Status.QUEUED -> handleQueued(installButton, fusedApp)
                Status.DOWNLOADING -> handleDownloading(
                    installButton,
                    fusedApp,
                    downloadPB,
                    appSize
                )
                Status.INSTALLING, Status.UNINSTALLING -> handleInstallingUninstalling(
                    installButton,
                    downloadPB,
                    appSize
                )
                Status.BLOCKED -> handleBlocked(installButton, view)
                Status.INSTALLATION_ISSUE -> handleInstallingIssue(
                    installButton,
                    fusedApp,
                    downloadPB,
                    appSize
                )
                else -> {
                    Log.d(TAG, "Unknown status: $status")
                }
            }
        }
    }

    private fun handleInstallingIssue(
        installButton: MaterialButton,
        fusedApp: FusedApp,
        downloadPB: RelativeLayout,
        appSize: MaterialTextView
    ) {
        installButton.apply {
            text = getString(R.string.retry)
            setOnClickListener {
                applicationIcon?.let {
                    mainActivityViewModel.getApplication(fusedApp, it)
                }
            }
        }
        downloadPB.visibility = View.GONE
        appSize.visibility = View.VISIBLE
    }

    private fun handleBlocked(
        installButton: MaterialButton,
        view: View
    ) {
        installButton.setOnClickListener {
            val errorMsg = when (
                User.valueOf(
                    mainActivityViewModel.userType.value ?: User.UNAVAILABLE.name
                )
            ) {
                User.ANONYMOUS,
                User.UNAVAILABLE -> getString(R.string.install_blocked_anonymous)
                User.GOOGLE -> getString(R.string.install_blocked_google)
            }
            if (errorMsg.isNotBlank()) {
                Snackbar.make(view, errorMsg, Snackbar.LENGTH_SHORT).show()
            }
        }
    }

    private fun handleInstallingUninstalling(
        installButton: MaterialButton,
        downloadPB: RelativeLayout,
        appSize: MaterialTextView
    ) {
        installButton.isEnabled = false
        downloadPB.visibility = View.GONE
        appSize.visibility = View.VISIBLE
    }

    private fun handleDownloading(
        installButton: MaterialButton,
        fusedApp: FusedApp,
        downloadPB: RelativeLayout,
        appSize: MaterialTextView
    ) {
        installButton.apply {
            text = getString(R.string.cancel)
            setOnClickListener {
                mainActivityViewModel.cancelDownload(fusedApp)
            }
        }
        downloadPB.visibility = View.VISIBLE
        appSize.visibility = View.GONE
        applicationViewModel.downloadProgress.observe(viewLifecycleOwner) {
            lifecycleScope.launch(Dispatchers.Main) {
                updateProgress(it)
            }
        }
    }

    private fun handleQueued(
        installButton: MaterialButton,
        fusedApp: FusedApp
    ) {
        installButton.apply {
            text = getString(R.string.cancel)
            setOnClickListener {
                mainActivityViewModel.cancelDownload(fusedApp)
            }
        }
    }

    private fun handleUnavaiable(
        installButton: MaterialButton,
        fusedApp: FusedApp,
        downloadPB: RelativeLayout,
        appSize: MaterialTextView
    ) {
        installButton.apply {
            text = getString(R.string.install)
            setOnClickListener {
                applicationIcon?.let {
                    mainActivityViewModel.getApplication(fusedApp, it)
                }
            }
        }
        downloadPB.visibility = View.GONE
        appSize.visibility = View.VISIBLE
    }

    private fun handleUpdatable(
        installButton: MaterialButton,
        view: View,
        fusedApp: FusedApp,
        downloadPB: RelativeLayout,
        appSize: MaterialTextView
    ) {
        installButton.apply {
            text = getString(R.string.update)
            setTextColor(Color.WHITE)
            backgroundTintList =
                ContextCompat.getColorStateList(view.context, R.color.colorAccent)
            setOnClickListener {
                applicationIcon?.let {
                    mainActivityViewModel.getApplication(fusedApp, it)
                }
            }
        }
        downloadPB.visibility = View.GONE
        appSize.visibility = View.VISIBLE
    }

    private fun handleInstalled(
        installButton: MaterialButton,
        view: View,
        fusedApp: FusedApp
    ) {
        installButton.apply {
            isEnabled = true
            text = getString(R.string.open)
            setTextColor(Color.WHITE)
            backgroundTintList =
                ContextCompat.getColorStateList(view.context, R.color.colorAccent)
            setOnClickListener {
                startActivity(pkgManagerModule.getLaunchIntent(fusedApp.package_name))
            }
        }
    }

    private suspend fun updateProgress(
        downloadProgress: DownloadProgress,
    ) {
        val progressResult = applicationViewModel.calculateProgress(downloadProgress)
        if (progressResult.first < 1) {
            return
        }
        val downloadedSize = "${
        Formatter.formatFileSize(requireContext(), progressResult.second).substringBefore(" MB")
        }/${Formatter.formatFileSize(requireContext(), progressResult.first)}"
        val progressPercentage =
            ((progressResult.second / progressResult.first.toDouble()) * 100f).toInt()
        binding.downloadInclude.appInstallPB.progress = progressPercentage
        binding.downloadInclude.percentage.text = "$progressPercentage%"
        binding.downloadInclude.downloadedSize.text = downloadedSize
    }

    private fun getPermissionListString(): String {
    private fun getPermissionListString(): String {
        var permission =
        var permission =
            applicationViewModel.transformPermsToString()
            applicationViewModel.transformPermsToString()
+17 −0
Original line number Original line Diff line number Diff line
@@ -31,7 +31,9 @@ import foundation.e.apps.api.exodus.models.AppPrivacyInfo
import foundation.e.apps.api.exodus.repositories.IAppPrivacyInfoRepository
import foundation.e.apps.api.exodus.repositories.IAppPrivacyInfoRepository
import foundation.e.apps.api.fused.FusedAPIRepository
import foundation.e.apps.api.fused.FusedAPIRepository
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.api.fused.data.FusedApp
import foundation.e.apps.manager.download.data.DownloadProgress
import foundation.e.apps.manager.download.data.DownloadProgressLD
import foundation.e.apps.manager.download.data.DownloadProgressLD
import foundation.e.apps.manager.fused.FusedManagerRepository
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Origin
import foundation.e.apps.utils.enums.Status
import foundation.e.apps.utils.enums.Status
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers
@@ -44,6 +46,7 @@ import kotlin.math.round
class ApplicationViewModel @Inject constructor(
class ApplicationViewModel @Inject constructor(
    downloadProgressLD: DownloadProgressLD,
    downloadProgressLD: DownloadProgressLD,
    private val fusedAPIRepository: FusedAPIRepository,
    private val fusedAPIRepository: FusedAPIRepository,
    private val fusedManagerRepository: FusedManagerRepository,
    private val appPrivacyInfoRepository: IAppPrivacyInfoRepository
    private val appPrivacyInfoRepository: IAppPrivacyInfoRepository
) : ViewModel() {
) : ViewModel() {


@@ -157,4 +160,18 @@ class ApplicationViewModel @Inject constructor(
    private fun calculatePermissionsScore(numberOfPermission: Int): Int {
    private fun calculatePermissionsScore(numberOfPermission: Int): Int {
        return if (numberOfPermission > 9) 0 else round(0.2 * ceil((10 - numberOfPermission) / 2.0)).toInt()
        return if (numberOfPermission > 9) 0 else round(0.2 * ceil((10 - numberOfPermission) / 2.0)).toInt()
    }
    }

    suspend fun calculateProgress(progress: DownloadProgress): Pair<Long, Long> {
        fusedApp.value?.let { app ->
            val appDownload = fusedManagerRepository.getDownloadList().single { it.id.contentEquals(app._id) }
            val downloadingMap = progress.totalSizeBytes.filter { item ->
                appDownload.downloadIdMap.keys.contains(item.key)
            }
            val totalSizeBytes = downloadingMap.values.sum()
            val downloadedSoFar = progress.bytesDownloadedSoFar.filter { item -> appDownload.downloadIdMap.keys.contains(item.key) }.values.sum()

            return Pair(totalSizeBytes, downloadedSoFar)
        }
        return Pair(1, 0)
    }
}
}
+23 −6
Original line number Original line Diff line number Diff line
@@ -2,10 +2,12 @@ package foundation.e.apps.manager.download.data


import android.app.DownloadManager
import android.app.DownloadManager
import androidx.lifecycle.LiveData
import androidx.lifecycle.LiveData
import foundation.e.apps.manager.fused.FusedManagerRepository
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.cancel
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlinx.coroutines.launch
import javax.inject.Inject
import javax.inject.Inject
@@ -14,6 +16,7 @@ import kotlin.coroutines.CoroutineContext
class DownloadProgressLD @Inject constructor(
class DownloadProgressLD @Inject constructor(
    private val downloadManager: DownloadManager,
    private val downloadManager: DownloadManager,
    private val downloadManagerQuery: DownloadManager.Query,
    private val downloadManagerQuery: DownloadManager.Query,
    private val fusedManagerRepository: FusedManagerRepository
) : LiveData<DownloadProgress>(), CoroutineScope {
) : LiveData<DownloadProgress>(), CoroutineScope {


    private val job = Job()
    private val job = Job()
@@ -25,10 +28,20 @@ class DownloadProgressLD @Inject constructor(
    override fun onActive() {
    override fun onActive() {
        super.onActive()
        super.onActive()
        launch {
        launch {
            while (isActive && downloadId.isNotEmpty()) {

                downloadManager.query(downloadManagerQuery.setFilterById(*downloadId.toLongArray()))
            while (isActive) {
                val downloads = fusedManagerRepository.getDownloadList()
                val downloadingList = downloads.map { it.downloadIdMap }.filter { it.values.contains(false) }
                val downloadingIds = mutableListOf<Long>()
                downloadingList.forEach { downloadingIds.addAll(it.keys) }
                if (downloadingIds.isEmpty()) {
                    delay(500)
                    continue
                }
                downloadManager.query(downloadManagerQuery.setFilterById(*downloadingIds.toLongArray()))
                    .use { cursor ->
                    .use { cursor ->
                        if (cursor.moveToFirst()) {
                        cursor.moveToFirst()
                        while (!cursor.isAfterLast) {
                            val id =
                            val id =
                                cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID))
                                cursor.getLong(cursor.getColumnIndexOrThrow(DownloadManager.COLUMN_ID))
                            val status =
                            val status =
@@ -51,16 +64,20 @@ class DownloadProgressLD @Inject constructor(
                            }
                            }


                            downloadProgress.status[id] =
                            downloadProgress.status[id] =
                                status == DownloadManager.STATUS_SUCCESSFUL
                                status == DownloadManager.STATUS_SUCCESSFUL || status == DownloadManager.STATUS_FAILED


                            if (downloadingIds.size == cursor.count) {
                                postValue(downloadProgress)
                                postValue(downloadProgress)
                            }


                            if (downloadProgress.status.all { it.value }) {
                            if (downloadingIds.isEmpty()) {
                                clearDownload()
                                clearDownload()
                                cancel()
                                cancel()
                            }
                            }
                            cursor.moveToNext()
                        }
                        }
                    }
                    }
                delay(1000)
            }
            }
        }
        }
    }
    }
+50 −11
Original line number Original line Diff line number Diff line
@@ -16,23 +16,55 @@
  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
  ~ along with this program.  If not, see <https://www.gnu.org/licenses/>.
  -->
  -->


<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_height="wrap_content"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_marginStart="20dp"
    android:layout_marginStart="20dp"
    android:layout_marginEnd="20dp"
    android:layout_marginEnd="20dp"
    android:gravity="end"
    android:gravity="end"
    android:orientation="horizontal">
    android:orientation="horizontal">


    <RelativeLayout
        android:id="@+id/progressLayout"
        android:layout_width="0dp"
        android:layout_height="wrap_content"
        app:layout_constraintBottom_toBottomOf="@id/installButton"
        app:layout_constraintTop_toTopOf="parent"
        app:layout_constraintLeft_toLeftOf="parent"
        app:layout_constraintRight_toLeftOf="@+id/installButton">
        <com.google.android.material.textview.MaterialTextView
            android:id="@+id/downloadedSize"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/app_info_text_color_grey"
            android:textSize="15sp"
            tools:text="18/23 mib"/>

        <com.google.android.material.textview.MaterialTextView
            android:id="@+id/percentage"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="@color/app_info_text_color_grey"
            android:textSize="15sp"
            android:layout_marginStart="10dp"
            android:layout_toEndOf="@id/downloadedSize"
            tools:text="75%"/>

        <com.google.android.material.progressindicator.LinearProgressIndicator
        <com.google.android.material.progressindicator.LinearProgressIndicator
            android:id="@+id/appInstallPB"
            android:id="@+id/appInstallPB"
            android:layout_width="match_parent"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_height="wrap_content"
            android:max="100"
            android:layout_gravity="center"
            android:layout_gravity="center"
        android:paddingStart="10dp"
            android:paddingEnd="10dp"
            android:paddingEnd="10dp"
        android:visibility="gone" />
            android:visibility="visible"
            android:layout_below="@+id/downloadedSize"

            />
    </RelativeLayout>



    <com.google.android.material.textview.MaterialTextView
    <com.google.android.material.textview.MaterialTextView
        android:id="@+id/appSize"
        android:id="@+id/appSize"
@@ -40,10 +72,16 @@
        android:layout_height="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginEnd="20dp"
        android:layout_marginEnd="20dp"
        android:textColor="@color/app_info_text_color_grey"
        android:textColor="@color/app_info_text_color_grey"
        android:textSize="15sp" />
        android:textSize="15sp"
        app:layout_constraintRight_toLeftOf="@+id/installButton"
        app:layout_constraintTop_toTopOf="@+id/installButton"
        app:layout_constraintBottom_toBottomOf="@+id/installButton"
        />


    <com.google.android.material.button.MaterialButton
    <com.google.android.material.button.MaterialButton
        android:id="@+id/installButton"
        android:id="@+id/installButton"
        app:layout_constraintRight_toRightOf="parent"
        app:layout_constraintTop_toTopOf="parent"
        style="@style/InstallButtonStyle"
        style="@style/InstallButtonStyle"
        android:layout_width="120dp"
        android:layout_width="120dp"
        android:layout_height="43dp"
        android:layout_height="43dp"
@@ -51,6 +89,7 @@
        android:textAllCaps="false"
        android:textAllCaps="false"
        android:textSize="18sp"
        android:textSize="18sp"
        app:autoSizeTextType="uniform"
        app:autoSizeTextType="uniform"
        app:cornerRadius="4dp" />
        app:cornerRadius="4dp"
        />


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