From 4f02d43b24e4631beeb4b064b36b7e5c9d17617c Mon Sep 17 00:00:00 2001 From: "S. Grimault" Date: Sat, 22 Aug 2020 14:03:39 +0200 Subject: [PATCH] fix: #24 --- .../geonature/sync/auth/AuthLoginViewModel.kt | 5 +- .../fr/geonature/sync/auth/AuthManager.kt | 2 - .../geonature/sync/sync/DataSyncViewModel.kt | 61 +++++----- .../fr/geonature/sync/sync/PackageInfo.kt | 13 +- .../geonature/sync/sync/PackageInfoManager.kt | 49 ++++---- .../sync/sync/PackageInfoViewModel.kt | 10 +- .../fr/geonature/sync/ui/home/HomeActivity.kt | 111 +++++++++++------- .../ui/home/PackageInfoRecyclerViewAdapter.kt | 55 +++------ .../geonature/sync/ui/login/LoginActivity.kt | 1 + sync/src/main/res/layout/list_icon_item_2.xml | 39 ++++-- 10 files changed, 180 insertions(+), 166 deletions(-) diff --git a/sync/src/main/java/fr/geonature/sync/auth/AuthLoginViewModel.kt b/sync/src/main/java/fr/geonature/sync/auth/AuthLoginViewModel.kt index 8acc492a..188db4e2 100644 --- a/sync/src/main/java/fr/geonature/sync/auth/AuthLoginViewModel.kt +++ b/sync/src/main/java/fr/geonature/sync/auth/AuthLoginViewModel.kt @@ -91,8 +91,9 @@ class AuthLoginViewModel(application: Application) : AndroidViewModel(applicatio return@launch } - authManager.setAuthLogin(authLogin) - _loginResult.value = LoginResult(success = authLogin) + authManager.setAuthLogin(authLogin).also { + _loginResult.value = LoginResult(success = authLogin) + } } catch (e: Exception) { _loginResult.value = LoginResult(error = R.string.login_failed) } diff --git a/sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt b/sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt index 107d021e..fa7dbe7f 100644 --- a/sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt +++ b/sync/src/main/java/fr/geonature/sync/auth/AuthManager.kt @@ -85,8 +85,6 @@ class AuthManager private constructor(applicationContext: Context) { return@withContext false } - _authLogin.postValue(authLogin) - preferenceManager.edit() .putString( KEY_PREFERENCE_AUTH_LOGIN, diff --git a/sync/src/main/java/fr/geonature/sync/sync/DataSyncViewModel.kt b/sync/src/main/java/fr/geonature/sync/sync/DataSyncViewModel.kt index aa54541b..b76f6427 100644 --- a/sync/src/main/java/fr/geonature/sync/sync/DataSyncViewModel.kt +++ b/sync/src/main/java/fr/geonature/sync/sync/DataSyncViewModel.kt @@ -4,6 +4,7 @@ import android.app.Application import android.util.Log import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Transformations.map import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModelProvider @@ -33,36 +34,9 @@ class DataSyncViewModel(application: Application) : AndroidViewModel(application it.getLastSynchronizedDate() } - val dataSyncStatus: LiveData = - map(workManager.getWorkInfosByTagLiveData(DataSyncWorker.DATA_SYNC_WORKER_TAG)) { workInfos -> - val workInfo = workInfos.firstOrNull() ?: return@map null - - val serverStatus = ServerStatus.values()[workInfo.progress.getInt( - DataSyncWorker.KEY_SERVER_STATUS, - workInfo.outputData.getInt( - DataSyncWorker.KEY_SERVER_STATUS, - ServerStatus.OK.ordinal - ) - )] - - if (arrayOf( - ServerStatus.FORBIDDEN, - ServerStatus.INTERNAL_SERVER_ERROR - ).contains(serverStatus) - ) { - cancelTasks() - } - - DataSyncStatus( - workInfo.state, - workInfo.progress.getString(DataSyncWorker.KEY_SYNC_MESSAGE) - ?: workInfo.outputData.getString(DataSyncWorker.KEY_SYNC_MESSAGE), - serverStatus - ) - } val lastSynchronizedDate: LiveData = dataSyncManager.lastSynchronizedDate - fun startSync(appSettings: AppSettings) { + fun startSync(appSettings: AppSettings): LiveData { val lastSynchronizedDate = dataSyncManager.lastSynchronizedDate.value if (lastSynchronizedDate?.add( @@ -76,7 +50,7 @@ class DataSyncViewModel(application: Application) : AndroidViewModel(application "data already synchronized at ${lastSynchronizedDate.toIsoDateString()}" ) - return + return MutableLiveData(null) } val constraints = Constraints.Builder() @@ -85,6 +59,7 @@ class DataSyncViewModel(application: Application) : AndroidViewModel(application val dataSyncWorkRequest = OneTimeWorkRequest.Builder(DataSyncWorker::class.java) .addTag(DataSyncWorker.DATA_SYNC_WORKER_TAG) + .setConstraints(constraints) .setInputData( Data.Builder() .putInt( @@ -109,9 +84,6 @@ class DataSyncViewModel(application: Application) : AndroidViewModel(application ) .build() ) - .setConstraints( - constraints - ) .build() val continuation = workManager.beginUniqueWork( @@ -120,8 +92,29 @@ class DataSyncViewModel(application: Application) : AndroidViewModel(application dataSyncWorkRequest ) - // start the work - continuation.enqueue() + return map(workManager.getWorkInfoByIdLiveData(dataSyncWorkRequest.id)) { + if (it == null) { + return@map null + } + + val serverStatus = ServerStatus.values()[it.progress.getInt( + DataSyncWorker.KEY_SERVER_STATUS, + it.outputData.getInt( + DataSyncWorker.KEY_SERVER_STATUS, + ServerStatus.OK.ordinal + ) + )] + + DataSyncStatus( + it.state, + it.progress.getString(DataSyncWorker.KEY_SYNC_MESSAGE) + ?: it.outputData.getString(DataSyncWorker.KEY_SYNC_MESSAGE), + serverStatus + ) + }.also { + // start the work + continuation.enqueue() + } } fun cancelTasks() { diff --git a/sync/src/main/java/fr/geonature/sync/sync/PackageInfo.kt b/sync/src/main/java/fr/geonature/sync/sync/PackageInfo.kt index b4db3f92..3da0d94e 100644 --- a/sync/src/main/java/fr/geonature/sync/sync/PackageInfo.kt +++ b/sync/src/main/java/fr/geonature/sync/sync/PackageInfo.kt @@ -13,16 +13,25 @@ data class PackageInfo( val packageName: String, val label: String, val versionCode: Long, + val localVersionCode: Long = 0, val versionName: String? = null, + val apkUrl: String? = null, val icon: Drawable? = null, val launchIntent: Intent? = null -): Comparable { +) : Comparable { var inputs: Int = 0 var state: WorkInfo.State = WorkInfo.State.ENQUEUED - var apkUrl: String? = null var settings: Any? = null override fun compareTo(other: PackageInfo): Int { return packageName.compareTo(other.packageName) } + + fun isAvailableForInstall(): Boolean { + return localVersionCode == 0L && !apkUrl.isNullOrEmpty() + } + + fun hasNewVersionAvailable(): Boolean { + return versionCode > localVersionCode && !apkUrl.isNullOrEmpty() + } } diff --git a/sync/src/main/java/fr/geonature/sync/sync/PackageInfoManager.kt b/sync/src/main/java/fr/geonature/sync/sync/PackageInfoManager.kt index ec911924..eb7c474e 100644 --- a/sync/src/main/java/fr/geonature/sync/sync/PackageInfoManager.kt +++ b/sync/src/main/java/fr/geonature/sync/sync/PackageInfoManager.kt @@ -43,6 +43,8 @@ class PackageInfoManager private constructor(private val applicationContext: Con */ @SuppressLint("DefaultLocale") suspend fun getAvailableApplications(): List = withContext(IO) { + availablePackageInfos.clear() + val availableAppPackages = try { val geoNatureAPIClient = GeoNatureAPIClient.instance(applicationContext) val response = geoNatureAPIClient?.getApplications() @@ -59,28 +61,24 @@ class PackageInfoManager private constructor(private val applicationContext: Con availableAppPackages.asSequence() .map { - availablePackageInfos[it.packageName]?.copy() - ?.apply { - apkUrl = - if (launchIntent == null || (versionCode < it.versionCode)) it.apkUrl else null - settings = it.settings - } - ?: PackageInfo( - it.packageName, - it.code.toLowerCase() - .capitalize(), - it.versionCode.toLong() - ).apply { - apkUrl = it.apkUrl - settings = it.settings - } + PackageInfo( + it.packageName, + it.code.toLowerCase() + .capitalize(), + it.versionCode.toLong(), + 0, + null, + it.apkUrl + ).apply { + settings = it.settings + } } .onEach { availablePackageInfos[it.packageName] = it } .toList() .also { - packageInfos.postValue(availablePackageInfos.values.toList()) + getInstalledApplications() } } @@ -88,6 +86,9 @@ class PackageInfoManager private constructor(private val applicationContext: Con * Finds all compatible installed applications. */ suspend fun getInstalledApplications(): List = withContext(IO) { + val allPackageInfos = mutableMapOf() + allPackageInfos.putAll(availablePackageInfos) + pm.getInstalledApplications(PackageManager.GET_META_DATA) .asSequence() .filter { it.packageName.startsWith(sharedUserId) } @@ -97,31 +98,29 @@ class PackageInfoManager private constructor(private val applicationContext: Con PackageManager.GET_META_DATA ) + val existingPackageInfo = availablePackageInfos[it.packageName] + @Suppress("DEPRECATION") PackageInfo( it.packageName, pm.getApplicationLabel(it) .toString(), + existingPackageInfo?.versionCode ?: 0, if (isPostPie) packageInfoFromPackageManager.longVersionCode else packageInfoFromPackageManager.versionCode.toLong(), packageInfoFromPackageManager.versionName, + existingPackageInfo?.apkUrl, pm.getApplicationIcon(it.packageName), pm.getLaunchIntentForPackage(it.packageName) ).apply { - val existingPackageInfo = availablePackageInfos[it.packageName] - - if (existingPackageInfo != null) { - apkUrl = - if (existingPackageInfo.versionCode > versionCode) existingPackageInfo.apkUrl else null - settings = existingPackageInfo.settings - } + settings = existingPackageInfo?.settings } } .onEach { - availablePackageInfos[it.packageName] = it + allPackageInfos[it.packageName] = it } .toList() .also { - packageInfos.postValue(availablePackageInfos.values.toList()) + packageInfos.postValue(allPackageInfos.values.toList()) } } diff --git a/sync/src/main/java/fr/geonature/sync/sync/PackageInfoViewModel.kt b/sync/src/main/java/fr/geonature/sync/sync/PackageInfoViewModel.kt index ec509ad0..9cb38686 100644 --- a/sync/src/main/java/fr/geonature/sync/sync/PackageInfoViewModel.kt +++ b/sync/src/main/java/fr/geonature/sync/sync/PackageInfoViewModel.kt @@ -52,8 +52,8 @@ class PackageInfoViewModel(application: Application) : AndroidViewModel(applicat .map { packageInfo -> packageInfo.copy() .apply { - apkUrl = packageInfo.apkUrl settings = packageInfo.settings + val workInfo = workInfos.firstOrNull { workInfo -> workInfo.progress.getString(InputsSyncWorker.KEY_PACKAGE_NAME) == packageName } ?: workInfos.firstOrNull { workInfo -> workInfo.outputData.getString(InputsSyncWorker.KEY_PACKAGE_NAME) == packageName } @@ -91,7 +91,7 @@ class PackageInfoViewModel(application: Application) : AndroidViewModel(applicat _appSettingsUpdated.postValue(true) } - if (!it.apkUrl.isNullOrEmpty()) { + if (it.hasNewVersionAvailable()) { value = it } } @@ -104,16 +104,16 @@ class PackageInfoViewModel(application: Application) : AndroidViewModel(applicat /** * Checks if we can perform an update of existing apps. */ - fun checkAppPackages() { + fun getAvailableApplications() { viewModelScope.launch { packageInfoManager.getAvailableApplications() } } /** - * Gets all compatible installed applications. + * Synchronize all compatible installed applications. */ - fun getInstalledApplicationsToSynchronize() { + fun synchronizeInstalledApplications() { viewModelScope.launch { packageInfoManager.getInstalledApplications() .asSequence() diff --git a/sync/src/main/java/fr/geonature/sync/ui/home/HomeActivity.kt b/sync/src/main/java/fr/geonature/sync/ui/home/HomeActivity.kt index 89952a79..7676a7cb 100644 --- a/sync/src/main/java/fr/geonature/sync/ui/home/HomeActivity.kt +++ b/sync/src/main/java/fr/geonature/sync/ui/home/HomeActivity.kt @@ -2,6 +2,7 @@ package fr.geonature.sync.ui.home import android.Manifest import android.annotation.SuppressLint +import android.app.Activity import android.app.ProgressDialog import android.content.Intent import android.os.Bundle @@ -38,7 +39,6 @@ import fr.geonature.sync.sync.DataSyncViewModel import fr.geonature.sync.sync.PackageInfo import fr.geonature.sync.sync.PackageInfoViewModel import fr.geonature.sync.sync.ServerStatus.FORBIDDEN -import fr.geonature.sync.sync.ServerStatus.INTERNAL_SERVER_ERROR import fr.geonature.sync.ui.login.LoginActivity import fr.geonature.sync.ui.settings.PreferencesActivity import fr.geonature.sync.util.SettingsUtils.getGeoNatureServerUrl @@ -147,12 +147,16 @@ class HomeActivity : AppCompatActivity() { ) addItemDecoration(dividerItemDecoration) } + + checkSelfPermissions() } override fun onResume() { super.onResume() - checkSelfPermissions() + if (appSettings != null) { + packageInfoViewModel.synchronizeInstalledApplications() + } } @SuppressLint("RestrictedApi") @@ -222,7 +226,8 @@ class HomeActivity : AppCompatActivity() { if (requestPermissionsResult) { makeSnackbar(getString(R.string.snackbar_permission_external_storage_available))?.show() - loadAppSettings() + loadAppSettingsAndStartSync() + packageInfoViewModel.getAvailableApplications() } else { makeSnackbar(getString(R.string.snackbar_permissions_not_granted))?.show() } @@ -235,6 +240,24 @@ class HomeActivity : AppCompatActivity() { } } + override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { + super.onActivityResult( + requestCode, + resultCode, + data + ) + + if ((resultCode != Activity.RESULT_OK)) { + return + } + + if (requestCode == PERFORM_LOGIN) { + appSettings?.run { + startSync(this) + } + } + } + private fun configureAppSettingsViewModel(): AppSettingsViewModel { return ViewModelProvider(this, fr.geonature.commons.settings.AppSettingsViewModel.Factory { @@ -270,7 +293,7 @@ class HomeActivity : AppCompatActivity() { "reloading settings after update..." ) - loadAppSettings(true) + loadAppSettingsAndStartSync(true) } vm.packageInfos.observe(this@HomeActivity, @@ -285,38 +308,6 @@ class HomeActivity : AppCompatActivity() { return ViewModelProvider(this, DataSyncViewModel.Factory { DataSyncViewModel(application) }).get(DataSyncViewModel::class.java) .also { vm -> - vm.dataSyncStatus.observe(this@HomeActivity, - Observer { - if (it == null) { - return@Observer - } - - dataSyncView?.setState(if (it.syncMessage.isNullOrBlank()) WorkInfo.State.ENQUEUED else it.state) - - if (!it.syncMessage.isNullOrBlank()) { - dataSyncView?.setMessage(it.syncMessage) - } - - @Suppress("NON_EXHAUSTIVE_WHEN") - when (it.serverStatus) { - INTERNAL_SERVER_ERROR -> packageInfoViewModel.cancelTasks() - FORBIDDEN -> { - packageInfoViewModel.cancelTasks() - - Toast.makeText( - this, - R.string.toast_not_connected, - Toast.LENGTH_SHORT - ) - .show() - - if (appSettings != null) { - startActivity(LoginActivity.newIntent(this)) - } - } - } - }) - vm.lastSynchronizedDate.observe(this@HomeActivity, Observer { dataSyncView?.setLastSynchronizedDate(it) @@ -329,7 +320,8 @@ class HomeActivity : AppCompatActivity() { this@HomeActivity, object : PermissionUtils.OnCheckSelfPermissionListener { override fun onPermissionsGranted() { - loadAppSettings() + loadAppSettingsAndStartSync() + packageInfoViewModel.getAvailableApplications() } override fun onRequestPermissions(vararg permissions: String) { @@ -348,7 +340,7 @@ class HomeActivity : AppCompatActivity() { ) } - private fun loadAppSettings(updated: Boolean = false) { + private fun loadAppSettingsAndStartSync(updated: Boolean = false) { appSettingsViewModel.loadAppSettings() .observeOnce(this@HomeActivity) { if (it == null) { @@ -360,14 +352,11 @@ class HomeActivity : AppCompatActivity() { )?.show() progressBar?.visibility = View.GONE - adapter.clear() if (!checkGeoNatureSettings()) { startActivity(PreferencesActivity.newIntent(this)) return@observeOnce } - - packageInfoViewModel.checkAppPackages() } else { if (updated) { makeSnackbar( @@ -398,17 +387,50 @@ class HomeActivity : AppCompatActivity() { private fun startSync(appSettings: AppSettings) { GlobalScope.launch(Main) { - adapter.clear(false) progressBar?.visibility = View.VISIBLE delay(500) dataSyncViewModel.startSync(appSettings) + .observeUntil(this@HomeActivity, + { + it?.state in arrayListOf( + WorkInfo.State.SUCCEEDED, + WorkInfo.State.FAILED, + WorkInfo.State.CANCELLED + ) + }) { + it?.run { + dataSyncView?.setState(if (it.syncMessage.isNullOrBlank()) WorkInfo.State.ENQUEUED else it.state) + + if (!it.syncMessage.isNullOrBlank()) { + dataSyncView?.setMessage(it.syncMessage) + } + + if (it.serverStatus == FORBIDDEN) { + Log.d( + TAG, + "not connected, redirect to LoginActivity" + ) + + Toast.makeText( + this@HomeActivity, + R.string.toast_not_connected, + Toast.LENGTH_SHORT + ) + .show() + + startActivityForResult( + LoginActivity.newIntent(this@HomeActivity), + PERFORM_LOGIN + ) + } + } + } delay(500) - packageInfoViewModel.getInstalledApplicationsToSynchronize() - packageInfoViewModel.checkAppPackages() + packageInfoViewModel.synchronizeInstalledApplications() } } @@ -526,5 +548,6 @@ class HomeActivity : AppCompatActivity() { private val TAG = HomeActivity::class.java.name private const val REQUEST_STORAGE_PERMISSIONS = 0 + private const val PERFORM_LOGIN = 0 } } diff --git a/sync/src/main/java/fr/geonature/sync/ui/home/PackageInfoRecyclerViewAdapter.kt b/sync/src/main/java/fr/geonature/sync/ui/home/PackageInfoRecyclerViewAdapter.kt index d71ee7fd..83784e6f 100644 --- a/sync/src/main/java/fr/geonature/sync/ui/home/PackageInfoRecyclerViewAdapter.kt +++ b/sync/src/main/java/fr/geonature/sync/ui/home/PackageInfoRecyclerViewAdapter.kt @@ -1,10 +1,9 @@ package fr.geonature.sync.ui.home import android.view.View -import android.view.animation.AlphaAnimation -import android.view.animation.Animation import android.widget.Button import android.widget.ImageView +import android.widget.ProgressBar import android.widget.TextView import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.DrawableCompat @@ -62,31 +61,22 @@ class PackageInfoRecyclerViewAdapter(private val listener: OnPackageInfoRecycler AbstractListItemRecyclerViewAdapter.AbstractViewHolder(itemView) { private val icon: ImageView = itemView.findViewById(android.R.id.icon1) - private val button: Button = itemView.findViewById(android.R.id.button1) private val iconStatus: TextView = itemView.findViewById(android.R.id.icon2) + private val button: Button = itemView.findViewById(android.R.id.button1) + private val progressBar: ProgressBar = itemView.findViewById(R.id.progressBar) private val text1: TextView = itemView.findViewById(android.R.id.text1) private val text2: TextView = itemView.findViewById(android.R.id.text2) - private val stateAnimation = AlphaAnimation( - 0.0f, - 1.0f - ).apply { - duration = 250 - startOffset = 10 - repeatMode = Animation.REVERSE - repeatCount = Animation.INFINITE - } - override fun onBind(item: PackageInfo) { with(button) { visibility = - if (item.apkUrl.isNullOrEmpty()) View.GONE - else View.VISIBLE + if (item.hasNewVersionAvailable()) View.VISIBLE + else View.GONE text = - if (item.versionName.isNullOrEmpty()) itemView.context.getString(R.string.home_app_install) + if (item.isAvailableForInstall()) itemView.context.getString(R.string.home_app_install) else itemView.context.getString(R.string.home_app_upgrade) contentDescription = - if (item.versionName.isNullOrEmpty()) itemView.context.getString( + if (item.isAvailableForInstall()) itemView.context.getString( R.string.home_app_install_desc, item.label ) @@ -99,8 +89,6 @@ class PackageInfoRecyclerViewAdapter(private val listener: OnPackageInfoRecycler } } - iconStatus.visibility = if (item.apkUrl.isNullOrEmpty()) View.VISIBLE else View.GONE - with(icon) { setImageDrawable(item.icon ?: itemView.context.getDrawable(R.drawable.ic_upgrade)) @@ -122,8 +110,8 @@ class PackageInfoRecyclerViewAdapter(private val listener: OnPackageInfoRecycler with(text2) { visibility = - if (item.apkUrl.isNullOrEmpty()) View.VISIBLE - else View.GONE + if (item.isAvailableForInstall()) View.GONE + else View.VISIBLE text = itemView.resources.getQuantityString( R.plurals.home_app_inputs, item.inputs, @@ -137,16 +125,10 @@ class PackageInfoRecyclerViewAdapter(private val listener: OnPackageInfoRecycler private fun setState(state: WorkInfo.State) { when (state) { WorkInfo.State.RUNNING -> { - iconStatus.setTextColor( - ResourcesCompat.getColor( - itemView.resources, - R.color.status_pending, - itemView.context?.theme - ) - ) - iconStatus.startAnimation(stateAnimation) + progressBar.visibility = View.VISIBLE } WorkInfo.State.FAILED -> { + iconStatus.visibility = View.VISIBLE iconStatus.setTextColor( ResourcesCompat.getColor( itemView.resources, @@ -154,9 +136,10 @@ class PackageInfoRecyclerViewAdapter(private val listener: OnPackageInfoRecycler itemView.context?.theme ) ) - iconStatus.clearAnimation() + progressBar.visibility = View.GONE } WorkInfo.State.SUCCEEDED -> { + iconStatus.visibility = View.VISIBLE iconStatus.setTextColor( ResourcesCompat.getColor( itemView.resources, @@ -164,17 +147,11 @@ class PackageInfoRecyclerViewAdapter(private val listener: OnPackageInfoRecycler itemView.context?.theme ) ) - iconStatus.clearAnimation() + progressBar.visibility = View.GONE } else -> { - iconStatus.setTextColor( - ResourcesCompat.getColor( - itemView.resources, - R.color.status_unknown, - itemView.context?.theme - ) - ) - iconStatus.clearAnimation() + iconStatus.visibility = View.GONE + progressBar.visibility = View.GONE } } } diff --git a/sync/src/main/java/fr/geonature/sync/ui/login/LoginActivity.kt b/sync/src/main/java/fr/geonature/sync/ui/login/LoginActivity.kt index 47eda054..422aee0c 100644 --- a/sync/src/main/java/fr/geonature/sync/ui/login/LoginActivity.kt +++ b/sync/src/main/java/fr/geonature/sync/ui/login/LoginActivity.kt @@ -75,6 +75,7 @@ class LoginActivity : AppCompatActivity() { showToast(R.string.login_success) // Complete and destroy login activity once successful + setResult(Activity.RESULT_OK) finish() }) } diff --git a/sync/src/main/res/layout/list_icon_item_2.xml b/sync/src/main/res/layout/list_icon_item_2.xml index 4169abe4..67cd8bb6 100644 --- a/sync/src/main/res/layout/list_icon_item_2.xml +++ b/sync/src/main/res/layout/list_icon_item_2.xml @@ -17,8 +17,32 @@ app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" - tools:srcCompat="@tools:sample/avatars" - tools:ignore="ContentDescription" /> + tools:ignore="ContentDescription" + tools:srcCompat="@tools:sample/avatars" /> + + + +