/* * SPDX-FileCopyrightText: 2020 The Calyx Institute * SPDX-License-Identifier: Apache-2.0 */ package com.stevesoltys.seedvault.settings import android.annotation.SuppressLint import android.content.Intent import android.net.Uri import android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS import android.text.format.Formatter.formatShortFileSize import android.view.LayoutInflater import android.view.View import android.view.View.GONE import android.view.View.INVISIBLE import android.view.View.VISIBLE import android.view.ViewGroup import android.widget.ImageView.ScaleType import android.widget.TextView import android.widget.Toast import android.widget.Toast.LENGTH_SHORT import androidx.core.content.ContextCompat.startActivity import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil.DiffResult import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView.Adapter import androidx.recyclerview.widget.RecyclerView.NO_POSITION import com.stevesoltys.seedvault.R import com.stevesoltys.seedvault.ui.AppBackupState.FAILED_WAS_STOPPED import com.stevesoltys.seedvault.ui.AppBackupState.SUCCEEDED import com.stevesoltys.seedvault.ui.AppViewHolder import com.stevesoltys.seedvault.ui.toRelativeTime internal class AppStatusAdapter(private val toggleListener: AppStatusToggleListener) : Adapter() { private val items = ArrayList() private var editMode = false override fun getItemViewType(position: Int): Int = when (items[position]) { is AppStatus -> 0 is AppSectionTitle -> 1 } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder { return when (viewType) { 0 -> { val v = LayoutInflater.from(parent.context) .inflate(R.layout.list_item_app_status, parent, false) AppStatusViewHolder(v) } 1 -> { val v = LayoutInflater.from(parent.context) .inflate(R.layout.list_item_app_section_title, parent, false) AppSectionTitleViewHolder(v) } else -> throw AssertionError("unknown view type") } } override fun getItemCount() = items.size override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { when (holder) { is AppStatusViewHolder -> holder.bind(items[position] as AppStatus) is AppSectionTitleViewHolder -> holder.bind(items[position] as AppSectionTitle) } } fun setEditMode(enabled: Boolean) { editMode = enabled notifyDataSetChanged() } fun update(newItems: List, diff: DiffResult) { items.clear() items.addAll(newItems) diff.dispatchUpdatesTo(this) } fun onItemChanged(item: AppStatus) { val pos = items.indexOfFirst { it is AppStatus && it.packageName == item.packageName } if (pos != NO_POSITION) notifyItemChanged(pos, item) } class AppSectionTitleViewHolder(v: View) : RecyclerView.ViewHolder(v) { private val titleView: TextView = v as TextView fun bind(item: AppSectionTitle) { titleView.setText(item.titleRes) } } inner class AppStatusViewHolder(v: View) : AppViewHolder(v) { fun bind(item: AppStatus) { appName.text = item.name appIcon.scaleType = if (item.isSpecial) ScaleType.CENTER else ScaleType.FIT_CENTER appIcon.setImageDrawable(item.icon) v.background = clickableBackground if (editMode) { v.setOnClickListener { checkBox.toggle() item.enabled = checkBox.isChecked toggleListener.onAppStatusToggled(item) } appInfo.visibility = GONE appStatus.visibility = INVISIBLE progressBar.visibility = INVISIBLE checkBox.visibility = VISIBLE checkBox.isChecked = item.enabled button.visibility = GONE } else { v.setOnClickListener(null) v.setOnLongClickListener { val intent = Intent(ACTION_APPLICATION_DETAILS_SETTINGS).apply { data = Uri.fromParts("package", item.packageName, null) } startActivity(context, intent, null) true } setState(item.status, false) button.visibility = GONE if (item.status == SUCCEEDED) { appInfo.text = if (item.size == null) { item.time.toRelativeTime(context) } else { item.time.toRelativeTime(context).toString() + " (${formatShortFileSize(v.context, item.size)})" } appInfo.visibility = VISIBLE } else if (item.status == FAILED_WAS_STOPPED && item.time > 0) { @SuppressLint("SetTextI18n") appInfo.text = if (item.size == null) { item.time.toRelativeTime(context).toString() } else { item.time.toRelativeTime(context).toString() + " (${formatShortFileSize(v.context, item.size)})" } + "\n${item.status.getBackupText(context)}" appInfo.visibility = VISIBLE } // setState() above sets appInfo state for other cases already if (item.status == FAILED_WAS_STOPPED) { button.setOnClickListener { val packageManager = context.packageManager packageManager.getLaunchIntentForPackage(item.packageName)?.let { i -> context.startActivity(i) } ?: run { val s = R.string.backup_app_stopped_no_intent Toast.makeText(context, s, LENGTH_SHORT).show() } } button.visibility = VISIBLE } checkBox.visibility = INVISIBLE } // show disabled items differently showEnabled(item.enabled) } private fun showEnabled(enabled: Boolean) { val alpha = if (enabled) 1.0f else 0.5f // setting the alpha on root view v only doesn't work as the ItemAnimator messes with it appIcon.alpha = alpha appName.alpha = alpha appInfo.alpha = alpha appStatus.alpha = alpha } } } internal class AppStatusDiff( private val oldItems: List, private val newItems: List, ) : DiffUtil.Callback() { override fun getOldListSize() = oldItems.size override fun getNewListSize() = newItems.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val old = oldItems[oldItemPosition] val new = newItems[newItemPosition] if (old is AppSectionTitle && new is AppSectionTitle) return old.titleRes == new.titleRes if (old is AppStatus && new is AppStatus) return old.packageName == new.packageName return false } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val old = oldItems[oldItemPosition] val new = newItems[newItemPosition] if (old is AppSectionTitle && new is AppSectionTitle) return old.titleRes == new.titleRes return old == new } } internal class AppStatusResult( val appStatusList: List, val diff: DiffResult, )