1
0
Fork 0

Use glide-like image loading function

master
Tiger Oakes 2019-06-26 16:40:20 -04:00 committed by Emily Kager
parent ccfaa3826b
commit 88c05a5f43
16 changed files with 81 additions and 253 deletions

View File

@ -11,20 +11,14 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observer import io.reactivex.Observer
import kotlinx.android.synthetic.main.collection_tab_list_row.view.* import kotlinx.android.synthetic.main.collection_tab_list_row.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.components.browser.icons.IconRequest
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.home.sessioncontrol.Tab import org.mozilla.fenix.home.sessioncontrol.Tab
import org.mozilla.fenix.utils.AdapterWithJob
import kotlin.coroutines.CoroutineContext
class CollectionCreationTabListAdapter( class CollectionCreationTabListAdapter(
val actionEmitter: Observer<CollectionCreationAction> val actionEmitter: Observer<CollectionCreationAction>
) : AdapterWithJob<TabViewHolder>() { ) : RecyclerView.Adapter<TabViewHolder>() {
private var tabs: List<Tab> = listOf() private var tabs: List<Tab> = listOf()
private var selectedTabs: MutableSet<Tab> = mutableSetOf() private var selectedTabs: MutableSet<Tab> = mutableSetOf()
private var hideCheckboxes = false private var hideCheckboxes = false
@ -33,7 +27,7 @@ class CollectionCreationTabListAdapter(
val view = val view =
LayoutInflater.from(parent.context).inflate(TabViewHolder.LAYOUT_ID, parent, false) LayoutInflater.from(parent.context).inflate(TabViewHolder.LAYOUT_ID, parent, false)
return TabViewHolder(view, adapterJob) return TabViewHolder(view)
} }
override fun onBindViewHolder(holder: TabViewHolder, position: Int, payloads: MutableList<Any>) { override fun onBindViewHolder(holder: TabViewHolder, position: Int, payloads: MutableList<Any>) {
@ -44,11 +38,11 @@ class CollectionCreationTabListAdapter(
is CheckChanged -> { is CheckChanged -> {
val checkChanged = payloads[0] as CheckChanged val checkChanged = payloads[0] as CheckChanged
if (checkChanged.shouldBeChecked) { if (checkChanged.shouldBeChecked) {
holder.view.tab_selected_checkbox.isChecked = true holder.itemView.tab_selected_checkbox.isChecked = true
} else if (checkChanged.shouldBeUnchecked) { } else if (checkChanged.shouldBeUnchecked) {
holder.view.tab_selected_checkbox.isChecked = false holder.itemView.tab_selected_checkbox.isChecked = false
} }
holder.view.tab_selected_checkbox.visibility = holder.itemView.tab_selected_checkbox.visibility =
if (checkChanged.shouldHideCheckBox) View.GONE else View.VISIBLE if (checkChanged.shouldHideCheckBox) View.GONE else View.VISIBLE
} }
} }
@ -58,7 +52,7 @@ class CollectionCreationTabListAdapter(
override fun onBindViewHolder(holder: TabViewHolder, position: Int) { override fun onBindViewHolder(holder: TabViewHolder, position: Int) {
val tab = tabs[position] val tab = tabs[position]
val isSelected = selectedTabs.contains(tab) val isSelected = selectedTabs.contains(tab)
holder.view.tab_selected_checkbox.setOnCheckedChangeListener { _, isChecked -> holder.itemView.tab_selected_checkbox.setOnCheckedChangeListener { _, isChecked ->
val action = if (isChecked) { val action = if (isChecked) {
selectedTabs.add(tab) selectedTabs.add(tab)
CollectionCreationAction.AddTabToSelection(tab) CollectionCreationAction.AddTabToSelection(tab)
@ -124,14 +118,7 @@ private class TabDiffUtil(
data class CheckChanged(val shouldBeChecked: Boolean, val shouldBeUnchecked: Boolean, val shouldHideCheckBox: Boolean) data class CheckChanged(val shouldBeChecked: Boolean, val shouldBeUnchecked: Boolean, val shouldHideCheckBox: Boolean)
class TabViewHolder( class TabViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val view: View,
val job: Job
) :
RecyclerView.ViewHolder(view), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
private var tab: Tab? = null private var tab: Tab? = null
private val checkbox = view.tab_selected_checkbox!! private val checkbox = view.tab_selected_checkbox!!
@ -144,21 +131,15 @@ class TabViewHolder(
fun bind(tab: Tab, isSelected: Boolean, shouldHideCheckBox: Boolean) { fun bind(tab: Tab, isSelected: Boolean, shouldHideCheckBox: Boolean) {
this.tab = tab this.tab = tab
view.hostname.text = tab.hostname itemView.hostname.text = tab.hostname
view.tab_title.text = tab.title itemView.tab_title.text = tab.title
checkbox.visibility = if (shouldHideCheckBox) View.INVISIBLE else View.VISIBLE checkbox.visibility = if (shouldHideCheckBox) View.INVISIBLE else View.VISIBLE
view.isClickable = !shouldHideCheckBox itemView.isClickable = !shouldHideCheckBox
if (checkbox.isChecked != isSelected) { if (checkbox.isChecked != isSelected) {
checkbox.isChecked = isSelected checkbox.isChecked = isSelected
} }
launch(Dispatchers.IO) { itemView.context.components.core.icons.loadIntoView(itemView.favicon_image, tab.url)
val bitmap = view.favicon_image.context.components.core.icons
.loadIcon(IconRequest(tab.url)).await().bitmap
launch(Dispatchers.Main) {
view.favicon_image.setImageBitmap(bitmap)
}
}
} }
companion object { companion object {

View File

@ -23,9 +23,6 @@ import io.reactivex.Observer
import io.reactivex.functions.Consumer import io.reactivex.functions.Consumer
import kotlinx.android.synthetic.main.component_collection_creation.* import kotlinx.android.synthetic.main.component_collection_creation.*
import kotlinx.android.synthetic.main.component_collection_creation.view.* import kotlinx.android.synthetic.main.component_collection_creation.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import mozilla.components.support.ktx.android.view.hideKeyboard import mozilla.components.support.ktx.android.view.hideKeyboard
import mozilla.components.support.ktx.android.view.showKeyboard import mozilla.components.support.ktx.android.view.showKeyboard
import org.mozilla.fenix.R import org.mozilla.fenix.R
@ -36,7 +33,6 @@ import org.mozilla.fenix.ext.urlToTrimmedHost
import org.mozilla.fenix.home.sessioncontrol.Tab import org.mozilla.fenix.home.sessioncontrol.Tab
import org.mozilla.fenix.home.sessioncontrol.TabCollection import org.mozilla.fenix.home.sessioncontrol.TabCollection
import org.mozilla.fenix.mvi.UIView import org.mozilla.fenix.mvi.UIView
import kotlin.coroutines.CoroutineContext
class CollectionCreationUIView( class CollectionCreationUIView(
container: ViewGroup, container: ViewGroup,
@ -46,10 +42,7 @@ class CollectionCreationUIView(
container, container,
actionEmitter, actionEmitter,
changesObservable changesObservable
), CoroutineScope { ) {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
override val view = LayoutInflater.from(container.context) override val view = LayoutInflater.from(container.context)
.inflate(R.layout.component_collection_creation, container, true) .inflate(R.layout.component_collection_creation, container, true)
@ -67,7 +60,6 @@ class CollectionCreationUIView(
private val transition = AutoTransition() private val transition = AutoTransition()
init { init {
job = Job()
transition.duration = TRANSITION_DURATION transition.duration = TRANSITION_DURATION
selectTabsConstraints.clone(collection_constraint_layout) selectTabsConstraints.clone(collection_constraint_layout)

View File

@ -10,7 +10,6 @@ import androidx.recyclerview.widget.RecyclerView
import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsDeleteButtonViewHolder
import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsHeaderViewHolder
import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder import org.mozilla.fenix.exceptions.viewholders.ExceptionsListItemViewHolder
import org.mozilla.fenix.utils.AdapterWithJob
private sealed class AdapterItem { private sealed class AdapterItem {
object DeleteButton : AdapterItem() object DeleteButton : AdapterItem()
@ -34,7 +33,7 @@ private class ExceptionsList(val exceptions: List<ExceptionsItem>) {
class ExceptionsAdapter( class ExceptionsAdapter(
private val interactor: ExceptionsInteractor private val interactor: ExceptionsInteractor
) : AdapterWithJob<RecyclerView.ViewHolder>() { ) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var exceptionsList: ExceptionsList = ExceptionsList(emptyList()) private var exceptionsList: ExceptionsList = ExceptionsList(emptyList())
fun updateData(items: List<ExceptionsItem>) { fun updateData(items: List<ExceptionsItem>) {
@ -61,11 +60,7 @@ class ExceptionsAdapter(
interactor interactor
) )
ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view) ExceptionsHeaderViewHolder.LAYOUT_ID -> ExceptionsHeaderViewHolder(view)
ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder( ExceptionsListItemViewHolder.LAYOUT_ID -> ExceptionsListItemViewHolder(view, interactor)
view,
interactor,
adapterJob
)
else -> throw IllegalStateException() else -> throw IllegalStateException()
} }
} }

View File

@ -7,25 +7,16 @@ package org.mozilla.fenix.exceptions.viewholders
import android.view.View import android.view.View
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.exception_item.view.* import kotlinx.android.synthetic.main.exception_item.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.components.browser.icons.IconRequest
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.exceptions.ExceptionsInteractor import org.mozilla.fenix.exceptions.ExceptionsInteractor
import org.mozilla.fenix.exceptions.ExceptionsItem import org.mozilla.fenix.exceptions.ExceptionsItem
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import kotlin.coroutines.CoroutineContext import org.mozilla.fenix.ext.loadIntoView
class ExceptionsListItemViewHolder( class ExceptionsListItemViewHolder(
view: View, view: View,
private val interactor: ExceptionsInteractor, private val interactor: ExceptionsInteractor
val job: Job ) : RecyclerView.ViewHolder(view) {
) : RecyclerView.ViewHolder(view), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
private val favicon = view.favicon_image private val favicon = view.favicon_image
private val url = view.domainView private val url = view.domainView
@ -48,13 +39,7 @@ class ExceptionsListItemViewHolder(
} }
private fun updateFavIcon(url: String) { private fun updateFavIcon(url: String) {
launch(Dispatchers.IO) { favicon.context.components.core.icons.loadIntoView(favicon, url)
val bitmap = favicon.context.components.core.icons
.loadIcon(IconRequest(url)).await().bitmap
launch(Dispatchers.Main) {
favicon.setImageBitmap(bitmap)
}
}
} }
companion object { companion object {

View File

@ -0,0 +1,11 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
package org.mozilla.fenix.ext
import android.widget.ImageView
import mozilla.components.browser.icons.BrowserIcons
import mozilla.components.browser.icons.IconRequest
fun BrowserIcons.loadIntoView(view: ImageView, url: String) = loadIntoView(view, IconRequest(url))

View File

@ -11,6 +11,7 @@ import androidx.annotation.DrawableRes
import androidx.annotation.LayoutRes import androidx.annotation.LayoutRes
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observer import io.reactivex.Observer
import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.CollectionHeaderViewHolder
@ -29,7 +30,6 @@ import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingPr
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingSectionHeaderViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingSectionHeaderViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingThemePickerViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingThemePickerViewHolder
import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTrackingProtectionViewHolder import org.mozilla.fenix.home.sessioncontrol.viewholders.onboarding.OnboardingTrackingProtectionViewHolder
import org.mozilla.fenix.utils.ListAdapterWithJob
import mozilla.components.feature.tab.collections.Tab as ComponentTab import mozilla.components.feature.tab.collections.Tab as ComponentTab
sealed class AdapterItem(@LayoutRes val viewType: Int) { sealed class AdapterItem(@LayoutRes val viewType: Int) {
@ -91,7 +91,7 @@ class AdapterItemDiffCallback : DiffUtil.ItemCallback<AdapterItem>() {
class SessionControlAdapter( class SessionControlAdapter(
private val actionEmitter: Observer<SessionControlAction> private val actionEmitter: Observer<SessionControlAction>
) : ListAdapterWithJob<AdapterItem, RecyclerView.ViewHolder>(AdapterItemDiffCallback()) { ) : ListAdapter<AdapterItem, RecyclerView.ViewHolder>(AdapterItemDiffCallback()) {
// This method triggers the ComplexMethod lint error when in fact it's quite simple. // This method triggers the ComplexMethod lint error when in fact it's quite simple.
@SuppressWarnings("ComplexMethod") @SuppressWarnings("ComplexMethod")
@ -99,13 +99,13 @@ class SessionControlAdapter(
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) { return when (viewType) {
TabHeaderViewHolder.LAYOUT_ID -> TabHeaderViewHolder(view, actionEmitter) TabHeaderViewHolder.LAYOUT_ID -> TabHeaderViewHolder(view, actionEmitter)
TabViewHolder.LAYOUT_ID -> TabViewHolder(view, actionEmitter, adapterJob) TabViewHolder.LAYOUT_ID -> TabViewHolder(view, actionEmitter)
SaveTabGroupViewHolder.LAYOUT_ID -> SaveTabGroupViewHolder(view, actionEmitter) SaveTabGroupViewHolder.LAYOUT_ID -> SaveTabGroupViewHolder(view, actionEmitter)
PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> PrivateBrowsingDescriptionViewHolder(view, actionEmitter) PrivateBrowsingDescriptionViewHolder.LAYOUT_ID -> PrivateBrowsingDescriptionViewHolder(view, actionEmitter)
NoContentMessageViewHolder.LAYOUT_ID -> NoContentMessageViewHolder(view) NoContentMessageViewHolder.LAYOUT_ID -> NoContentMessageViewHolder(view)
CollectionHeaderViewHolder.LAYOUT_ID -> CollectionHeaderViewHolder(view) CollectionHeaderViewHolder.LAYOUT_ID -> CollectionHeaderViewHolder(view)
CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, actionEmitter) CollectionViewHolder.LAYOUT_ID -> CollectionViewHolder(view, actionEmitter)
TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder(view, actionEmitter, adapterJob) TabInCollectionViewHolder.LAYOUT_ID -> TabInCollectionViewHolder(view, actionEmitter)
OnboardingHeaderViewHolder.LAYOUT_ID -> OnboardingHeaderViewHolder(view) OnboardingHeaderViewHolder.LAYOUT_ID -> OnboardingHeaderViewHolder(view)
OnboardingSectionHeaderViewHolder.LAYOUT_ID -> OnboardingSectionHeaderViewHolder(view) OnboardingSectionHeaderViewHolder.LAYOUT_ID -> OnboardingSectionHeaderViewHolder(view)
OnboardingFirefoxAccountViewHolder.LAYOUT_ID -> OnboardingFirefoxAccountViewHolder(view) OnboardingFirefoxAccountViewHolder.LAYOUT_ID -> OnboardingFirefoxAccountViewHolder(view)

View File

@ -12,34 +12,25 @@ import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observer import io.reactivex.Observer
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.tab_in_collection.* import kotlinx.android.synthetic.main.tab_in_collection.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.components.browser.icons.IconRequest
import mozilla.components.support.ktx.android.util.dpToFloat import mozilla.components.support.ktx.android.util.dpToFloat
import org.jetbrains.anko.backgroundColor import org.jetbrains.anko.backgroundColor
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.getColorFromAttr import org.mozilla.fenix.ext.getColorFromAttr
import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.ext.urlToTrimmedHost import org.mozilla.fenix.ext.urlToTrimmedHost
import org.mozilla.fenix.home.sessioncontrol.CollectionAction import org.mozilla.fenix.home.sessioncontrol.CollectionAction
import org.mozilla.fenix.home.sessioncontrol.SessionControlAction import org.mozilla.fenix.home.sessioncontrol.SessionControlAction
import org.mozilla.fenix.home.sessioncontrol.TabCollection import org.mozilla.fenix.home.sessioncontrol.TabCollection
import org.mozilla.fenix.home.sessioncontrol.onNext import org.mozilla.fenix.home.sessioncontrol.onNext
import kotlin.coroutines.CoroutineContext
import mozilla.components.feature.tab.collections.Tab as ComponentTab import mozilla.components.feature.tab.collections.Tab as ComponentTab
class TabInCollectionViewHolder( class TabInCollectionViewHolder(
val view: View, val view: View,
val actionEmitter: Observer<SessionControlAction>, val actionEmitter: Observer<SessionControlAction>,
val job: Job,
override val containerView: View? = view override val containerView: View? = view
) : RecyclerView.ViewHolder(view), LayoutContainer, CoroutineScope { ) : RecyclerView.ViewHolder(view), LayoutContainer {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
lateinit var collection: TabCollection lateinit var collection: TabCollection
private set private set
@ -82,13 +73,7 @@ class TabInCollectionViewHolder(
collection_tab_hostname.text = tab.url.urlToTrimmedHost(view.context) collection_tab_hostname.text = tab.url.urlToTrimmedHost(view.context)
collection_tab_title.text = tab.title collection_tab_title.text = tab.title
launch(Dispatchers.IO) { collection_tab_icon.context.components.core.icons.loadIntoView(collection_tab_icon, tab.url)
val bitmap = collection_tab_icon.context.components.core.icons
.loadIcon(IconRequest(tab.url)).await().bitmap
launch(Dispatchers.Main) {
collection_tab_icon.setImageBitmap(bitmap)
}
}
// If I'm the last one... // If I'm the last one...
if (isLastTab) { if (isLastTab) {

View File

@ -12,33 +12,24 @@ import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observer import io.reactivex.Observer
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.tab_list_row.* import kotlinx.android.synthetic.main.tab_list_row.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.menu.BrowserMenuBuilder import mozilla.components.browser.menu.BrowserMenuBuilder
import mozilla.components.browser.menu.item.SimpleBrowserMenuItem import mozilla.components.browser.menu.item.SimpleBrowserMenuItem
import mozilla.components.support.ktx.android.util.dpToFloat import mozilla.components.support.ktx.android.util.dpToFloat
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.home.sessioncontrol.SessionControlAction import org.mozilla.fenix.home.sessioncontrol.SessionControlAction
import org.mozilla.fenix.home.sessioncontrol.Tab import org.mozilla.fenix.home.sessioncontrol.Tab
import org.mozilla.fenix.home.sessioncontrol.TabAction import org.mozilla.fenix.home.sessioncontrol.TabAction
import org.mozilla.fenix.home.sessioncontrol.onNext import org.mozilla.fenix.home.sessioncontrol.onNext
import kotlin.coroutines.CoroutineContext
class TabViewHolder( class TabViewHolder(
view: View, view: View,
actionEmitter: Observer<SessionControlAction>, actionEmitter: Observer<SessionControlAction>,
private val job: Job,
override val containerView: View? = view override val containerView: View? = view
) : ) :
RecyclerView.ViewHolder(view), LayoutContainer, CoroutineScope { RecyclerView.ViewHolder(view), LayoutContainer {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
var tab: Tab? = null var tab: Tab? = null
private var tabMenu: TabItemMenu private var tabMenu: TabItemMenu
@ -92,13 +83,7 @@ class TabViewHolder(
private fun updateTabUI(tab: Tab) { private fun updateTabUI(tab: Tab) {
hostname.text = tab.hostname hostname.text = tab.hostname
tab_title.text = tab.title tab_title.text = tab.title
launch(Dispatchers.IO) { favicon_image.context.components.core.icons.loadIntoView(favicon_image, tab.url)
val bitmap = favicon_image.context.components.core.icons
.loadIcon(IconRequest(tab.url)).await().bitmap
launch(Dispatchers.Main) {
favicon_image.setImageBitmap(bitmap)
}
}
} }
fun updateSelected(selected: Boolean) { fun updateSelected(selected: Boolean) {

View File

@ -13,12 +13,7 @@ import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.extensions.LayoutContainer import kotlinx.android.extensions.LayoutContainer
import kotlinx.android.synthetic.main.bookmark_row.* import kotlinx.android.synthetic.main.bookmark_row.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.appservices.places.BookmarkRoot import mozilla.appservices.places.BookmarkRoot
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.menu.BrowserMenu import mozilla.components.browser.menu.BrowserMenu
import mozilla.components.concept.storage.BookmarkNode import mozilla.components.concept.storage.BookmarkNode
import mozilla.components.concept.storage.BookmarkNodeType import mozilla.components.concept.storage.BookmarkNodeType
@ -26,11 +21,10 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.ThemeManager import org.mozilla.fenix.ThemeManager
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.increaseTapArea import org.mozilla.fenix.ext.increaseTapArea
import org.mozilla.fenix.utils.AdapterWithJob import org.mozilla.fenix.ext.loadIntoView
import kotlin.coroutines.CoroutineContext
class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteractor) : class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteractor) :
AdapterWithJob<BookmarkAdapter.BookmarkNodeViewHolder>() { RecyclerView.Adapter<BookmarkAdapter.BookmarkNodeViewHolder>() {
private var tree: List<BookmarkNode> = listOf() private var tree: List<BookmarkNode> = listOf()
private var mode: BookmarkState.Mode = BookmarkState.Mode.Normal private var mode: BookmarkState.Mode = BookmarkState.Mode.Normal
@ -86,13 +80,13 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
return when (viewType) { return when (viewType) {
BookmarkItemViewHolder.viewType.ordinal -> BookmarkItemViewHolder( BookmarkItemViewHolder.viewType.ordinal -> BookmarkItemViewHolder(
view, interactor, adapterJob view, interactor
) )
BookmarkFolderViewHolder.viewType.ordinal -> BookmarkFolderViewHolder( BookmarkFolderViewHolder.viewType.ordinal -> BookmarkFolderViewHolder(
view, interactor, adapterJob view, interactor
) )
BookmarkSeparatorViewHolder.viewType.ordinal -> BookmarkSeparatorViewHolder( BookmarkSeparatorViewHolder.viewType.ordinal -> BookmarkSeparatorViewHolder(
view, interactor, adapterJob view, interactor
) )
else -> throw IllegalStateException("ViewType $viewType does not match to a ViewHolder") else -> throw IllegalStateException("ViewType $viewType does not match to a ViewHolder")
} }
@ -120,13 +114,8 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
open class BookmarkNodeViewHolder( open class BookmarkNodeViewHolder(
view: View, view: View,
val interactor: BookmarkViewInteractor, val interactor: BookmarkViewInteractor,
private val job: Job,
override val containerView: View? = view override val containerView: View? = view
) : ) : RecyclerView.ViewHolder(view), LayoutContainer {
RecyclerView.ViewHolder(view), LayoutContainer, CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.Main + job
open fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {} open fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {}
} }
@ -134,10 +123,9 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
class BookmarkItemViewHolder( class BookmarkItemViewHolder(
view: View, view: View,
interactor: BookmarkViewInteractor, interactor: BookmarkViewInteractor,
job: Job,
override val containerView: View? = view override val containerView: View? = view
) : ) :
BookmarkNodeViewHolder(view, interactor, job, containerView) { BookmarkNodeViewHolder(view, interactor, containerView) {
@Suppress("ComplexMethod") @Suppress("ComplexMethod")
override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) { override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {
@ -209,14 +197,9 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
bookmark_favicon.backgroundTintList = backgroundTintList bookmark_favicon.backgroundTintList = backgroundTintList
if (selected) bookmark_favicon.setImageResource(R.drawable.mozac_ic_check) if (selected) bookmark_favicon.setImageResource(R.drawable.mozac_ic_check)
if (!selected && item.url?.startsWith("http") == true) { val url = item.url ?: return
launch(Dispatchers.IO) { if (!selected && url.startsWith("http")) {
val bitmap = bookmark_layout.context.components.core.icons bookmark_layout.context.components.core.icons.loadIntoView(bookmark_favicon, url)
.loadIcon(IconRequest(item.url!!)).await().bitmap
launch(Dispatchers.Main) {
bookmark_favicon.setImageBitmap(bitmap)
}
}
} }
} }
@ -251,10 +234,9 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
class BookmarkFolderViewHolder( class BookmarkFolderViewHolder(
view: View, view: View,
interactor: BookmarkViewInteractor, interactor: BookmarkViewInteractor,
job: Job,
override val containerView: View? = view override val containerView: View? = view
) : ) :
BookmarkNodeViewHolder(view, interactor, job, containerView) { BookmarkNodeViewHolder(view, interactor, containerView) {
override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) { override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {
containerView?.context?.let { containerView?.context?.let {
@ -353,9 +335,8 @@ class BookmarkAdapter(val emptyView: View, val interactor: BookmarkViewInteracto
class BookmarkSeparatorViewHolder( class BookmarkSeparatorViewHolder(
view: View, view: View,
interactor: BookmarkViewInteractor, interactor: BookmarkViewInteractor,
job: Job,
override val containerView: View? = view override val containerView: View? = view
) : BookmarkNodeViewHolder(view, interactor, job, containerView) { ) : BookmarkNodeViewHolder(view, interactor, containerView) {
override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) { override fun bind(item: BookmarkNode, mode: BookmarkState.Mode, selected: Boolean) {

View File

@ -14,7 +14,6 @@ import org.mozilla.fenix.R
import org.mozilla.fenix.library.history.viewholders.HistoryDeleteButtonViewHolder import org.mozilla.fenix.library.history.viewholders.HistoryDeleteButtonViewHolder
import org.mozilla.fenix.library.history.viewholders.HistoryHeaderViewHolder import org.mozilla.fenix.library.history.viewholders.HistoryHeaderViewHolder
import org.mozilla.fenix.library.history.viewholders.HistoryListItemViewHolder import org.mozilla.fenix.library.history.viewholders.HistoryListItemViewHolder
import org.mozilla.fenix.utils.AdapterWithJob
import java.util.Calendar import java.util.Calendar
import java.util.Date import java.util.Date
@ -94,7 +93,7 @@ private class HistoryList(val history: List<HistoryItem>) {
} }
class HistoryAdapter(private val historyInteractor: HistoryInteractor) : class HistoryAdapter(private val historyInteractor: HistoryInteractor) :
AdapterWithJob<RecyclerView.ViewHolder>() { RecyclerView.Adapter<RecyclerView.ViewHolder>() {
private var historyList: HistoryList = HistoryList(emptyList()) private var historyList: HistoryList = HistoryList(emptyList())
private var mode: HistoryState.Mode = HistoryState.Mode.Normal private var mode: HistoryState.Mode = HistoryState.Mode.Normal
var selected = listOf<HistoryItem>() var selected = listOf<HistoryItem>()
@ -158,16 +157,9 @@ class HistoryAdapter(private val historyInteractor: HistoryInteractor) :
val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false) val view = LayoutInflater.from(parent.context).inflate(viewType, parent, false)
return when (viewType) { return when (viewType) {
HistoryDeleteButtonViewHolder.LAYOUT_ID -> HistoryDeleteButtonViewHolder( HistoryDeleteButtonViewHolder.LAYOUT_ID -> HistoryDeleteButtonViewHolder(view, historyInteractor)
view,
historyInteractor
)
HistoryHeaderViewHolder.LAYOUT_ID -> HistoryHeaderViewHolder(view) HistoryHeaderViewHolder.LAYOUT_ID -> HistoryHeaderViewHolder(view)
HistoryListItemViewHolder.LAYOUT_ID -> HistoryListItemViewHolder( HistoryListItemViewHolder.LAYOUT_ID -> HistoryListItemViewHolder(view, historyInteractor)
view,
historyInteractor,
adapterJob
)
else -> throw IllegalStateException() else -> throw IllegalStateException()
} }
} }

View File

@ -9,29 +9,20 @@ import android.widget.CompoundButton
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.android.synthetic.main.history_list_item.view.* import kotlinx.android.synthetic.main.history_list_item.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import mozilla.components.browser.icons.IconRequest
import mozilla.components.browser.menu.BrowserMenu import mozilla.components.browser.menu.BrowserMenu
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ThemeManager import org.mozilla.fenix.ThemeManager
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.library.history.HistoryInteractor import org.mozilla.fenix.library.history.HistoryInteractor
import org.mozilla.fenix.library.history.HistoryItem import org.mozilla.fenix.library.history.HistoryItem
import org.mozilla.fenix.library.history.HistoryItemMenu import org.mozilla.fenix.library.history.HistoryItemMenu
import org.mozilla.fenix.library.history.HistoryState import org.mozilla.fenix.library.history.HistoryState
import kotlin.coroutines.CoroutineContext
class HistoryListItemViewHolder( class HistoryListItemViewHolder(
view: View, view: View,
private val historyInteractor: HistoryInteractor, private val historyInteractor: HistoryInteractor
val job: Job ) : RecyclerView.ViewHolder(view) {
) : RecyclerView.ViewHolder(view), CoroutineScope {
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + job
private val favicon = view.history_favicon private val favicon = view.history_favicon
private val title = view.history_title private val title = view.history_title
@ -124,13 +115,7 @@ class HistoryListItemViewHolder(
} }
private fun updateFavIcon(url: String) { private fun updateFavIcon(url: String) {
launch(Dispatchers.IO) { favicon.context.components.core.icons.loadIntoView(favicon, url)
val bitmap = favicon.context.components.core.icons
.loadIcon(IconRequest(url)).await().bitmap
launch(Dispatchers.Main) {
favicon.setImageBitmap(bitmap)
}
}
} }
private fun setClickListeners( private fun setClickListeners(

View File

@ -17,23 +17,17 @@ import androidx.constraintlayout.widget.ConstraintLayout
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import kotlinx.android.synthetic.main.search_engine_radio_button.view.* import kotlinx.android.synthetic.main.search_engine_radio_button.view.*
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import mozilla.components.browser.search.SearchEngine import mozilla.components.browser.search.SearchEngine
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.utils.Settings import org.mozilla.fenix.utils.Settings
import kotlin.coroutines.CoroutineContext
abstract class SearchEngineListPreference @JvmOverloads constructor( abstract class SearchEngineListPreference @JvmOverloads constructor(
context: Context, context: Context,
attrs: AttributeSet? = null, attrs: AttributeSet? = null,
defStyleAttr: Int = android.R.attr.preferenceStyle defStyleAttr: Int = android.R.attr.preferenceStyle
) : Preference(context, attrs, defStyleAttr), CompoundButton.OnCheckedChangeListener, CoroutineScope { ) : Preference(context, attrs, defStyleAttr), CompoundButton.OnCheckedChangeListener {
private val job = Job()
override val coroutineContext: CoroutineContext
get() = job + Dispatchers.Main
protected var searchEngines: List<SearchEngine> = emptyList() protected var searchEngines: List<SearchEngine> = emptyList()
protected var searchEngineGroup: RadioGroup? = null protected var searchEngineGroup: RadioGroup? = null
@ -54,11 +48,6 @@ abstract class SearchEngineListPreference @JvmOverloads constructor(
refreshSearchEngineViews(context) refreshSearchEngineViews(context)
} }
override fun onDetached() {
job.cancel()
super.onDetached()
}
protected abstract fun onSearchEngineSelected(searchEngine: SearchEngine) protected abstract fun onSearchEngineSelected(searchEngine: SearchEngine)
protected abstract fun updateDefaultItem(defaultButton: CompoundButton) protected abstract fun updateDefaultItem(defaultButton: CompoundButton)

View File

@ -4,7 +4,6 @@
package org.mozilla.fenix.settings package org.mozilla.fenix.settings
import android.graphics.drawable.BitmapDrawable
import android.os.Bundle import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
@ -24,20 +23,17 @@ import androidx.paging.PagedListAdapter
import androidx.recyclerview.widget.DiffUtil import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import mozilla.components.browser.icons.IconRequest
import mozilla.components.feature.sitepermissions.SitePermissions import mozilla.components.feature.sitepermissions.SitePermissions
import org.jetbrains.anko.alert import org.jetbrains.anko.alert
import org.jetbrains.anko.noButton import org.jetbrains.anko.noButton
import org.jetbrains.anko.yesButton import org.jetbrains.anko.yesButton
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.ext.components import org.mozilla.fenix.ext.components
import org.mozilla.fenix.ext.loadIntoView
import org.mozilla.fenix.ext.nav import org.mozilla.fenix.ext.nav
import kotlin.coroutines.CoroutineContext
private const val MAX_ITEMS_PER_PAGE = 50 private const val MAX_ITEMS_PER_PAGE = 50
@ -135,21 +131,7 @@ class SitePermissionsViewHolder(val view: View, val iconView: ImageView, val sit
RecyclerView.ViewHolder(view) RecyclerView.ViewHolder(view)
class ExceptionsAdapter(private val clickListener: View.OnClickListener) : class ExceptionsAdapter(private val clickListener: View.OnClickListener) :
PagedListAdapter<SitePermissions, SitePermissionsViewHolder>(diffCallback), CoroutineScope { PagedListAdapter<SitePermissions, SitePermissionsViewHolder>(diffCallback) {
private lateinit var job: Job
override val coroutineContext: CoroutineContext
get() = Main + job
override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
super.onAttachedToRecyclerView(recyclerView)
job = Job()
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
job.cancel()
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SitePermissionsViewHolder { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SitePermissionsViewHolder {
val context = parent.context val context = parent.context
@ -164,15 +146,7 @@ class ExceptionsAdapter(private val clickListener: View.OnClickListener) :
val sitePermissions = requireNotNull(getItem(position)) val sitePermissions = requireNotNull(getItem(position))
val context = holder.view.context val context = holder.view.context
launch(IO) { context.components.core.icons.loadIntoView(holder.iconView, "https://${sitePermissions.origin}/")
val bitmap = context.components.core.icons
.loadIcon(IconRequest("https://${sitePermissions.origin}/")).await().bitmap
launch(Main) {
val drawable = BitmapDrawable(context.resources, bitmap)
holder.iconView.setImageDrawable(drawable)
}
}
holder.siteTextView.text = sitePermissions.origin holder.siteTextView.text = sitePermissions.origin
holder.view.tag = sitePermissions holder.view.tag = sitePermissions
holder.view.setOnClickListener(clickListener) holder.view.setOnClickListener(clickListener)

View File

@ -19,11 +19,10 @@ import io.reactivex.Observer
import kotlinx.android.synthetic.main.app_share_list_item.view.* import kotlinx.android.synthetic.main.app_share_list_item.view.*
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.mozilla.fenix.R import org.mozilla.fenix.R
import org.mozilla.fenix.utils.AdapterWithJob
import kotlin.coroutines.CoroutineContext
class AppShareRecyclerView @JvmOverloads constructor( class AppShareRecyclerView @JvmOverloads constructor(
context: Context, context: Context,
@ -40,10 +39,10 @@ class AppShareAdapter(
private val context: Context, private val context: Context,
val actionEmitter: Observer<ShareAction>, val actionEmitter: Observer<ShareAction>,
private val intentType: String = "text/plain" private val intentType: String = "text/plain"
) : AdapterWithJob<AppShareItemViewHolder>(), CoroutineScope { ) : RecyclerView.Adapter<AppShareItemViewHolder>() {
private var scope = CoroutineScope(Dispatchers.IO)
override val coroutineContext: CoroutineContext
get() = Dispatchers.IO + adapterJob
private var size: Int = 0 private var size: Int = 0
private val shareItems: MutableList<ShareItem> = mutableListOf() private val shareItems: MutableList<ShareItem> = mutableListOf()
@ -53,7 +52,7 @@ class AppShareAdapter(
flags = FLAG_ACTIVITY_NEW_TASK flags = FLAG_ACTIVITY_NEW_TASK
} }
launch { scope.launch {
val activities = context.packageManager.queryIntentActivities(testIntent, 0) val activities = context.packageManager.queryIntentActivities(testIntent, 0)
val items = activities.map { resolveInfo -> val items = activities.map { resolveInfo ->
@ -86,6 +85,11 @@ class AppShareAdapter(
override fun onBindViewHolder(holder: AppShareItemViewHolder, position: Int) { override fun onBindViewHolder(holder: AppShareItemViewHolder, position: Int) {
holder.bind(shareItems[position]) holder.bind(shareItems[position])
} }
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
scope.cancel()
}
} }
class AppShareItemViewHolder( class AppShareItemViewHolder(

View File

@ -1,38 +0,0 @@
package org.mozilla.fenix.utils
import androidx.recyclerview.widget.DiffUtil
import androidx.recyclerview.widget.ListAdapter
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.Job
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
/**
* [RecyclerView.Adapter] with a [Job] for coroutines.
* The adapterJob is setup when the adapter is initialized to a RecyclerView and canceled when detached.
*/
abstract class AdapterWithJob<VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
protected var adapterJob: Job = Job()
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
adapterJob.cancel()
}
}
/**
* [ListAdapter] with a [Job] for coroutines.
* The adapterJob is setup when the adapter is initialized to a RecyclerView and canceled when detached.
*/
abstract class ListAdapterWithJob<T, VH : RecyclerView.ViewHolder>(
diffCallback: DiffUtil.ItemCallback<T>
) : ListAdapter<T, VH>(diffCallback) {
protected var adapterJob: Job = Job()
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
adapterJob.cancel()
}
}

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This Source Code Form is subject to the terms of the Mozilla Public
- License, v. 2.0. If a copy of the MPL was not distributed with this
- file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
<resources>
<item name="TAG_ICON_JOB" type="id" />
</resources>