Add search for subscription picker in the feed group dialog
This commit is contained in:
parent
d9100913d5
commit
c24dfc63dc
|
@ -20,6 +20,13 @@ abstract class SubscriptionDAO : BasicDAO<SubscriptionEntity> {
|
|||
@Query("SELECT * FROM subscriptions ORDER BY name COLLATE NOCASE ASC")
|
||||
abstract override fun getAll(): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("""
|
||||
SELECT * FROM subscriptions
|
||||
WHERE name LIKE '%' || :filter || '%'
|
||||
ORDER BY name COLLATE NOCASE ASC
|
||||
""")
|
||||
abstract fun filterByName(filter: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
@Query("SELECT * FROM subscriptions WHERE url LIKE :url AND service_id = :serviceId")
|
||||
abstract fun getSubscriptionFlowable(serviceId: Int, url: String): Flowable<List<SubscriptionEntity>>
|
||||
|
||||
|
|
|
@ -21,6 +21,8 @@ class SubscriptionManager(context: Context) {
|
|||
fun subscriptionTable(): SubscriptionDAO = subscriptionTable
|
||||
fun subscriptions() = subscriptionTable.all
|
||||
|
||||
fun filterByName(filter: String) = subscriptionTable.filterByName(filter)
|
||||
|
||||
fun upsertAll(infoList: List<ChannelInfo>): List<SubscriptionEntity> {
|
||||
val listEntities = subscriptionTable.upsertAll(
|
||||
infoList.map { SubscriptionEntity.from(it) })
|
||||
|
|
|
@ -5,6 +5,7 @@ import android.content.Context
|
|||
import android.os.Bundle
|
||||
import android.os.Parcelable
|
||||
import android.text.Editable
|
||||
import android.text.TextUtils
|
||||
import android.text.TextWatcher
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
|
@ -13,34 +14,22 @@ import android.view.inputmethod.InputMethodManager
|
|||
import android.widget.Toast
|
||||
import androidx.fragment.app.DialogFragment
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProviders
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.recyclerview.widget.GridLayoutManager
|
||||
import androidx.recyclerview.widget.LinearLayoutManager
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import com.xwray.groupie.GroupAdapter
|
||||
import com.xwray.groupie.OnItemClickListener
|
||||
import com.xwray.groupie.Section
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import icepick.Icepick
|
||||
import icepick.State
|
||||
import java.io.Serializable
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.cancel_button
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.confirm_button
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.delete_button
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.delete_screen_message
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.group_name_input
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.group_name_input_container
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.icon_preview
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.icon_selector
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.options_root
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.select_channel_button
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.selected_subscription_count_view
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.separator
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector_header_info
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.subscriptions_selector_list
|
||||
import kotlin.collections.contains
|
||||
import kotlinx.android.synthetic.main.dialog_feed_group_create.*
|
||||
import kotlinx.android.synthetic.main.toolbar_search_layout.*
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.fragments.BackPressable
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.DeleteScreen
|
||||
import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialog.ScreenState.IconPickerScreen
|
||||
|
@ -51,9 +40,10 @@ import org.schabi.newpipe.local.subscription.dialog.FeedGroupDialogViewModel.Dia
|
|||
import org.schabi.newpipe.local.subscription.item.EmptyPlaceholderItem
|
||||
import org.schabi.newpipe.local.subscription.item.PickerIconItem
|
||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||
import org.schabi.newpipe.util.AndroidTvUtils
|
||||
import org.schabi.newpipe.util.ThemeHelper
|
||||
|
||||
class FeedGroupDialog : DialogFragment() {
|
||||
class FeedGroupDialog : DialogFragment(), BackPressable {
|
||||
private lateinit var viewModel: FeedGroupDialogViewModel
|
||||
private var groupId: Long = NO_GROUP_SELECTED
|
||||
private var groupIcon: FeedGroupIcon? = null
|
||||
|
@ -66,22 +56,19 @@ class FeedGroupDialog : DialogFragment() {
|
|||
object DeleteScreen : ScreenState()
|
||||
}
|
||||
|
||||
@State
|
||||
@JvmField
|
||||
var selectedIcon: FeedGroupIcon? = null
|
||||
@State
|
||||
@JvmField
|
||||
var selectedSubscriptions: HashSet<Long> = HashSet()
|
||||
@State
|
||||
@JvmField
|
||||
var currentScreen: ScreenState = InitialScreen
|
||||
@State @JvmField var selectedIcon: FeedGroupIcon? = null
|
||||
@State @JvmField var selectedSubscriptions: HashSet<Long> = HashSet()
|
||||
@State @JvmField var wasSubscriptionSelectionChanged: Boolean = false
|
||||
@State @JvmField var currentScreen: ScreenState = InitialScreen
|
||||
|
||||
@State
|
||||
@JvmField
|
||||
var subscriptionsListState: Parcelable? = null
|
||||
@State
|
||||
@JvmField
|
||||
var iconsListState: Parcelable? = null
|
||||
@State @JvmField var subscriptionsListState: Parcelable? = null
|
||||
@State @JvmField var iconsListState: Parcelable? = null
|
||||
@State @JvmField var wasSearchSubscriptionsVisible = false
|
||||
@State @JvmField var subscriptionsCurrentSearchQuery = ""
|
||||
|
||||
private val subscriptionMainSection = Section()
|
||||
private val subscriptionEmptyFooter = Section()
|
||||
private lateinit var subscriptionGroupAdapter: GroupAdapter<GroupieViewHolder>
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
|
@ -91,22 +78,30 @@ class FeedGroupDialog : DialogFragment() {
|
|||
groupId = arguments?.getLong(KEY_GROUP_ID, NO_GROUP_SELECTED) ?: NO_GROUP_SELECTED
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View? {
|
||||
return inflater.inflate(R.layout.dialog_feed_group_create, container)
|
||||
}
|
||||
|
||||
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
|
||||
return object : Dialog(requireActivity(), theme) {
|
||||
override fun onBackPressed() {
|
||||
if (currentScreen !is InitialScreen) {
|
||||
showScreen(InitialScreen)
|
||||
} else {
|
||||
if (!this@FeedGroupDialog.onBackPressed()) {
|
||||
super.onBackPressed()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
|
||||
wasSearchSubscriptionsVisible = isSearchVisible()
|
||||
}
|
||||
|
||||
override fun onSaveInstanceState(outState: Bundle) {
|
||||
super.onSaveInstanceState(outState)
|
||||
|
||||
|
@ -119,11 +114,15 @@ class FeedGroupDialog : DialogFragment() {
|
|||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
viewModel = ViewModelProviders.of(this, FeedGroupDialogViewModel.Factory(requireContext(), groupId))
|
||||
.get(FeedGroupDialogViewModel::class.java)
|
||||
viewModel = ViewModelProvider(this,
|
||||
FeedGroupDialogViewModel.Factory(requireContext(),
|
||||
groupId, subscriptionsCurrentSearchQuery)
|
||||
).get(FeedGroupDialogViewModel::class.java)
|
||||
|
||||
viewModel.groupLiveData.observe(viewLifecycleOwner, Observer(::handleGroup))
|
||||
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer { setupSubscriptionPicker(it.first, it.second) })
|
||||
viewModel.subscriptionsLiveData.observe(viewLifecycleOwner, Observer {
|
||||
setupSubscriptionPicker(it.first, it.second)
|
||||
})
|
||||
viewModel.dialogEventLiveData.observe(viewLifecycleOwner, Observer {
|
||||
when (it) {
|
||||
ProcessingEvent -> disableInput()
|
||||
|
@ -131,15 +130,54 @@ class FeedGroupDialog : DialogFragment() {
|
|||
}
|
||||
})
|
||||
|
||||
subscriptionGroupAdapter = GroupAdapter<GroupieViewHolder>().apply {
|
||||
add(subscriptionMainSection)
|
||||
add(subscriptionEmptyFooter)
|
||||
spanCount = 4
|
||||
}
|
||||
subscriptions_selector_list.apply {
|
||||
// Disable animations, too distracting.
|
||||
itemAnimator = null
|
||||
adapter = subscriptionGroupAdapter
|
||||
layoutManager = GridLayoutManager(requireContext(), subscriptionGroupAdapter.spanCount,
|
||||
RecyclerView.VERTICAL, false).apply {
|
||||
spanSizeLookup = subscriptionGroupAdapter.spanSizeLookup
|
||||
}
|
||||
}
|
||||
|
||||
setupIconPicker()
|
||||
setupListeners()
|
||||
|
||||
showScreen(currentScreen)
|
||||
|
||||
if (currentScreen == SubscriptionsPickerScreen && wasSearchSubscriptionsVisible) {
|
||||
showSearch()
|
||||
} else if (currentScreen == InitialScreen && groupId == NO_GROUP_SELECTED) {
|
||||
showKeyboard()
|
||||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
subscriptions_selector_list?.adapter = null
|
||||
icon_selector?.adapter = null
|
||||
}
|
||||
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Setup
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
override fun onBackPressed(): Boolean {
|
||||
if (currentScreen is SubscriptionsPickerScreen && isSearchVisible()) {
|
||||
hideSearch()
|
||||
return true
|
||||
} else if (currentScreen !is InitialScreen) {
|
||||
showScreen(InitialScreen)
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private fun setupListeners() {
|
||||
delete_button.setOnClickListener { showScreen(DeleteScreen) }
|
||||
|
@ -163,13 +201,54 @@ class FeedGroupDialog : DialogFragment() {
|
|||
}
|
||||
})
|
||||
|
||||
confirm_button.setOnClickListener {
|
||||
when (currentScreen) {
|
||||
InitialScreen -> handlePositiveButtonInitialScreen()
|
||||
DeleteScreen -> viewModel.deleteGroup()
|
||||
else -> showScreen(InitialScreen)
|
||||
confirm_button.setOnClickListener { handlePositiveButton() }
|
||||
|
||||
select_channel_button.setOnClickListener {
|
||||
subscriptions_selector_list.scrollToPosition(0)
|
||||
showScreen(SubscriptionsPickerScreen)
|
||||
}
|
||||
|
||||
val headerMenu = subscriptions_header_toolbar.menu
|
||||
requireActivity().menuInflater.inflate(R.menu.menu_feed_group_dialog, headerMenu)
|
||||
|
||||
headerMenu.findItem(R.id.action_search).setOnMenuItemClickListener {
|
||||
showSearch()
|
||||
true
|
||||
}
|
||||
|
||||
toolbar_search_clear.setOnClickListener {
|
||||
if (TextUtils.isEmpty(toolbar_search_edit_text.text)) {
|
||||
hideSearch()
|
||||
return@setOnClickListener
|
||||
}
|
||||
resetSearch()
|
||||
showKeyboardSearch()
|
||||
}
|
||||
|
||||
toolbar_search_edit_text.setOnClickListener {
|
||||
if (AndroidTvUtils.isTv(context)) {
|
||||
showKeyboardSearch()
|
||||
}
|
||||
}
|
||||
|
||||
toolbar_search_edit_text.addTextChangedListener(object : TextWatcher {
|
||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
||||
override fun afterTextChanged(s: Editable) = Unit
|
||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
|
||||
val newQuery: String = toolbar_search_edit_text.text.toString()
|
||||
subscriptionsCurrentSearchQuery = newQuery
|
||||
viewModel.filterSubscriptionsBy(newQuery)
|
||||
}
|
||||
})
|
||||
|
||||
subscriptionGroupAdapter?.setOnItemClickListener(subscriptionPickerItemListener)
|
||||
}
|
||||
|
||||
private fun handlePositiveButton() = when {
|
||||
currentScreen is InitialScreen -> handlePositiveButtonInitialScreen()
|
||||
currentScreen is DeleteScreen -> viewModel.deleteGroup()
|
||||
currentScreen is SubscriptionsPickerScreen && isSearchVisible() -> hideSearch()
|
||||
else -> showScreen(InitialScreen)
|
||||
}
|
||||
|
||||
private fun handlePositiveButtonInitialScreen() {
|
||||
|
@ -202,80 +281,73 @@ class FeedGroupDialog : DialogFragment() {
|
|||
groupIcon = feedGroupEntity?.icon
|
||||
groupSortOrder = feedGroupEntity?.sortOrder ?: -1
|
||||
|
||||
icon_preview.setImageResource((if (selectedIcon == null) icon else selectedIcon!!).getDrawableRes(requireContext()))
|
||||
val feedGroupIcon = if (selectedIcon == null) icon else selectedIcon!!
|
||||
icon_preview.setImageResource(feedGroupIcon.getDrawableRes(requireContext()))
|
||||
|
||||
if (group_name_input.text.isNullOrBlank()) {
|
||||
group_name_input.setText(name)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupSubscriptionPicker(subscriptions: List<SubscriptionEntity>, selectedSubscriptions: Set<Long>) {
|
||||
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
||||
val useGridLayout = subscriptions.isNotEmpty()
|
||||
private val subscriptionPickerItemListener = OnItemClickListener { item, view ->
|
||||
if (item is PickerSubscriptionItem) {
|
||||
val subscriptionId = item.subscriptionEntity.uid
|
||||
wasSubscriptionSelectionChanged = true
|
||||
|
||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.spanCount = if (useGridLayout) 4 else 1
|
||||
|
||||
val subscriptionsCount = this.selectedSubscriptions.size
|
||||
val selectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
||||
selected_subscription_count_view.text = selectedCountText
|
||||
subscriptions_selector_header_info.text = selectedCountText
|
||||
|
||||
Section().apply {
|
||||
addAll(subscriptions.map {
|
||||
val isSelected = this@FeedGroupDialog.selectedSubscriptions.contains(it.uid)
|
||||
PickerSubscriptionItem(it, isSelected)
|
||||
})
|
||||
setPlaceholder(EmptyPlaceholderItem())
|
||||
|
||||
groupAdapter.add(this)
|
||||
}
|
||||
|
||||
subscriptions_selector_list.apply {
|
||||
layoutManager = if (useGridLayout) {
|
||||
GridLayoutManager(requireContext(), groupAdapter.spanCount, RecyclerView.VERTICAL, false)
|
||||
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
||||
this.selectedSubscriptions.remove(subscriptionId)
|
||||
false
|
||||
} else {
|
||||
LinearLayoutManager(requireContext(), RecyclerView.VERTICAL, false)
|
||||
this.selectedSubscriptions.add(subscriptionId)
|
||||
true
|
||||
}
|
||||
|
||||
adapter = groupAdapter
|
||||
item.updateSelected(view, isSelected)
|
||||
updateSubscriptionSelectedCount()
|
||||
}
|
||||
}
|
||||
|
||||
if (subscriptionsListState != null) {
|
||||
layoutManager?.onRestoreInstanceState(subscriptionsListState)
|
||||
subscriptionsListState = null
|
||||
}
|
||||
private fun setupSubscriptionPicker(
|
||||
subscriptions: List<PickerSubscriptionItem>,
|
||||
selectedSubscriptions: Set<Long>
|
||||
) {
|
||||
if (!wasSubscriptionSelectionChanged) {
|
||||
this.selectedSubscriptions.addAll(selectedSubscriptions)
|
||||
}
|
||||
|
||||
groupAdapter.setOnItemClickListener { item, _ ->
|
||||
when (item) {
|
||||
is PickerSubscriptionItem -> {
|
||||
val subscriptionId = item.subscriptionEntity.uid
|
||||
updateSubscriptionSelectedCount()
|
||||
|
||||
val isSelected = if (this.selectedSubscriptions.contains(subscriptionId)) {
|
||||
this.selectedSubscriptions.remove(subscriptionId)
|
||||
false
|
||||
} else {
|
||||
this.selectedSubscriptions.add(subscriptionId)
|
||||
true
|
||||
}
|
||||
|
||||
item.isSelected = isSelected
|
||||
item.notifyChanged(PickerSubscriptionItem.UPDATE_SELECTED)
|
||||
|
||||
val subscriptionsCount = this.selectedSubscriptions.size
|
||||
val updateSelectedCountText = resources.getQuantityString(R.plurals.feed_group_dialog_selection_count, subscriptionsCount, subscriptionsCount)
|
||||
selected_subscription_count_view.text = updateSelectedCountText
|
||||
subscriptions_selector_header_info.text = updateSelectedCountText
|
||||
}
|
||||
}
|
||||
if (subscriptions.isEmpty()) {
|
||||
subscriptionEmptyFooter.clear()
|
||||
subscriptionEmptyFooter.add(EmptyPlaceholderItem())
|
||||
} else {
|
||||
subscriptionEmptyFooter.clear()
|
||||
}
|
||||
|
||||
select_channel_button.setOnClickListener {
|
||||
subscriptions.forEach {
|
||||
it.isSelected = this@FeedGroupDialog.selectedSubscriptions
|
||||
.contains(it.subscriptionEntity.uid)
|
||||
}
|
||||
|
||||
subscriptionMainSection.update(subscriptions, false)
|
||||
|
||||
if (subscriptionsListState != null) {
|
||||
subscriptions_selector_list.layoutManager?.onRestoreInstanceState(subscriptionsListState)
|
||||
subscriptionsListState = null
|
||||
} else {
|
||||
subscriptions_selector_list.scrollToPosition(0)
|
||||
showScreen(SubscriptionsPickerScreen)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateSubscriptionSelectedCount() {
|
||||
val selectedCount = this.selectedSubscriptions.size
|
||||
val selectedCountText = resources.getQuantityString(
|
||||
R.plurals.feed_group_dialog_selection_count,
|
||||
selectedCount, selectedCount)
|
||||
selected_subscription_count_view.text = selectedCountText
|
||||
subscriptions_header_info.text = selectedCountText
|
||||
}
|
||||
|
||||
private fun setupIconPicker() {
|
||||
val groupAdapter = GroupAdapter<GroupieViewHolder>()
|
||||
groupAdapter.addAll(FeedGroupIcon.values().map { PickerIconItem(requireContext(), it) })
|
||||
|
@ -311,9 +383,9 @@ class FeedGroupDialog : DialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Screen Selector
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
private fun showScreen(screen: ScreenState) {
|
||||
currentScreen = screen
|
||||
|
@ -337,7 +409,8 @@ class FeedGroupDialog : DialogFragment() {
|
|||
else -> View.VISIBLE
|
||||
}
|
||||
|
||||
if (currentScreen != InitialScreen) hideKeyboard()
|
||||
hideKeyboard()
|
||||
hideSearch()
|
||||
}
|
||||
|
||||
private fun View.onlyVisibleIn(vararg screens: ScreenState) {
|
||||
|
@ -347,13 +420,58 @@ class FeedGroupDialog : DialogFragment() {
|
|||
}
|
||||
}
|
||||
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
/*///////////////////////////////////////////////////////////////////////////
|
||||
// Utils
|
||||
// /////////////////////////////////////////////////////////////////////////
|
||||
////////////////////////////////////////////////////////////////////////// */
|
||||
|
||||
private fun isSearchVisible() = subscriptions_header_search_container?.visibility == View.VISIBLE
|
||||
|
||||
private fun resetSearch() {
|
||||
toolbar_search_edit_text.setText("")
|
||||
subscriptionsCurrentSearchQuery = ""
|
||||
viewModel.clearSubscriptionsFilter()
|
||||
}
|
||||
|
||||
private fun hideSearch() {
|
||||
resetSearch()
|
||||
subscriptions_header_search_container.visibility = View.GONE
|
||||
subscriptions_header_info_container.visibility = View.VISIBLE
|
||||
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = true
|
||||
hideKeyboardSearch()
|
||||
}
|
||||
|
||||
private fun showSearch() {
|
||||
subscriptions_header_search_container.visibility = View.VISIBLE
|
||||
subscriptions_header_info_container.visibility = View.GONE
|
||||
subscriptions_header_toolbar.menu.findItem(R.id.action_search).isVisible = false
|
||||
showKeyboardSearch()
|
||||
}
|
||||
|
||||
private val inputMethodManager by lazy {
|
||||
requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
}
|
||||
|
||||
private fun showKeyboardSearch() {
|
||||
if (toolbar_search_edit_text.requestFocus()) {
|
||||
inputMethodManager.showSoftInput(toolbar_search_edit_text, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboardSearch() {
|
||||
inputMethodManager.hideSoftInputFromWindow(toolbar_search_edit_text.windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
toolbar_search_edit_text.clearFocus()
|
||||
}
|
||||
|
||||
private fun showKeyboard() {
|
||||
if (group_name_input.requestFocus()) {
|
||||
inputMethodManager.showSoftInput(group_name_input, InputMethodManager.SHOW_IMPLICIT)
|
||||
}
|
||||
}
|
||||
|
||||
private fun hideKeyboard() {
|
||||
val inputMethodManager = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
||||
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken, InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
inputMethodManager.hideSoftInputFromWindow(group_name_input.windowToken,
|
||||
InputMethodManager.RESULT_UNCHANGED_SHOWN)
|
||||
group_name_input.clearFocus()
|
||||
}
|
||||
|
||||
|
|
|
@ -9,42 +9,55 @@ import io.reactivex.Completable
|
|||
import io.reactivex.Flowable
|
||||
import io.reactivex.disposables.Disposable
|
||||
import io.reactivex.functions.BiFunction
|
||||
import io.reactivex.processors.BehaviorProcessor
|
||||
import io.reactivex.schedulers.Schedulers
|
||||
import org.schabi.newpipe.database.feed.model.FeedGroupEntity
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.local.feed.FeedDatabaseManager
|
||||
import org.schabi.newpipe.local.subscription.FeedGroupIcon
|
||||
import org.schabi.newpipe.local.subscription.SubscriptionManager
|
||||
import org.schabi.newpipe.local.subscription.item.PickerSubscriptionItem
|
||||
|
||||
class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModel() {
|
||||
class Factory(val context: Context, val groupId: Long = FeedGroupEntity.GROUP_ALL_ID) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedGroupDialogViewModel(context.applicationContext, groupId) as T
|
||||
}
|
||||
}
|
||||
class FeedGroupDialogViewModel(
|
||||
applicationContext: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
initialQuery: String = ""
|
||||
) : ViewModel() {
|
||||
|
||||
private var feedDatabaseManager: FeedDatabaseManager = FeedDatabaseManager(applicationContext)
|
||||
private var subscriptionManager = SubscriptionManager(applicationContext)
|
||||
|
||||
private var filterSubscriptions = BehaviorProcessor.create<String>()
|
||||
private var allSubscriptions = subscriptionManager.subscriptions()
|
||||
|
||||
private var subscriptionsFlowable = filterSubscriptions
|
||||
.startWith(initialQuery)
|
||||
.distinctUntilChanged()
|
||||
.switchMap { query ->
|
||||
if (query.isEmpty()) {
|
||||
allSubscriptions
|
||||
} else {
|
||||
subscriptionManager.filterByName(query)
|
||||
}
|
||||
}.map { list -> list.map { PickerSubscriptionItem(it) } }
|
||||
|
||||
private val mutableGroupLiveData = MutableLiveData<FeedGroupEntity>()
|
||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<SubscriptionEntity>, Set<Long>>>()
|
||||
private val mutableSubscriptionsLiveData = MutableLiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>>()
|
||||
private val mutableDialogEventLiveData = MutableLiveData<DialogEvent>()
|
||||
val groupLiveData: LiveData<FeedGroupEntity> = mutableGroupLiveData
|
||||
val subscriptionsLiveData: LiveData<Pair<List<SubscriptionEntity>, Set<Long>>> = mutableSubscriptionsLiveData
|
||||
val subscriptionsLiveData: LiveData<Pair<List<PickerSubscriptionItem>, Set<Long>>> = mutableSubscriptionsLiveData
|
||||
val dialogEventLiveData: LiveData<DialogEvent> = mutableDialogEventLiveData
|
||||
|
||||
private var actionProcessingDisposable: Disposable? = null
|
||||
|
||||
private var feedGroupDisposable = feedDatabaseManager.getGroup(groupId)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableGroupLiveData::postValue)
|
||||
|
||||
private var subscriptionsDisposable = Flowable
|
||||
.combineLatest(subscriptionManager.subscriptions(), feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<SubscriptionEntity>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
.combineLatest(subscriptionsFlowable, feedDatabaseManager.subscriptionIdsForGroup(groupId),
|
||||
BiFunction { t1: List<PickerSubscriptionItem>, t2: List<Long> -> t1 to t2.toSet() })
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe(mutableSubscriptionsLiveData::postValue)
|
||||
|
||||
override fun onCleared() {
|
||||
super.onCleared()
|
||||
|
@ -55,14 +68,14 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
|||
|
||||
fun createGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>) {
|
||||
doAction(feedDatabaseManager.createGroup(name, selectedIcon)
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
.flatMapCompletable {
|
||||
feedDatabaseManager.updateSubscriptionsForGroup(it, selectedSubscriptions.toList())
|
||||
})
|
||||
}
|
||||
|
||||
fun updateGroup(name: String, selectedIcon: FeedGroupIcon, selectedSubscriptions: Set<Long>, sortOrder: Long) {
|
||||
doAction(feedDatabaseManager.updateSubscriptionsForGroup(groupId, selectedSubscriptions.toList())
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
.andThen(feedDatabaseManager.updateGroup(FeedGroupEntity(groupId, name, selectedIcon, sortOrder))))
|
||||
}
|
||||
|
||||
fun deleteGroup() {
|
||||
|
@ -74,13 +87,33 @@ class FeedGroupDialogViewModel(applicationContext: Context, val groupId: Long =
|
|||
mutableDialogEventLiveData.value = DialogEvent.ProcessingEvent
|
||||
|
||||
actionProcessingDisposable = completable
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||
.subscribeOn(Schedulers.io())
|
||||
.subscribe { mutableDialogEventLiveData.postValue(DialogEvent.SuccessEvent) }
|
||||
}
|
||||
}
|
||||
|
||||
fun filterSubscriptionsBy(query: String) {
|
||||
filterSubscriptions.onNext(query)
|
||||
}
|
||||
|
||||
fun clearSubscriptionsFilter() {
|
||||
filterSubscriptions.onNext("")
|
||||
}
|
||||
|
||||
sealed class DialogEvent {
|
||||
object ProcessingEvent : DialogEvent()
|
||||
object SuccessEvent : DialogEvent()
|
||||
}
|
||||
|
||||
class Factory(
|
||||
private val context: Context,
|
||||
private val groupId: Long = FeedGroupEntity.GROUP_ALL_ID,
|
||||
private val initialQuery: String = ""
|
||||
) : ViewModelProvider.Factory {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
|
||||
return FeedGroupDialogViewModel(context.applicationContext,
|
||||
groupId, initialQuery) as T
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,4 +7,5 @@ import org.schabi.newpipe.R
|
|||
class EmptyPlaceholderItem : Item() {
|
||||
override fun getLayout(): Int = R.layout.list_empty_view
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {}
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int = spanCount
|
||||
}
|
||||
|
|
|
@ -1,39 +1,28 @@
|
|||
package org.schabi.newpipe.local.subscription.item
|
||||
|
||||
import android.view.View
|
||||
import com.nostra13.universalimageloader.core.DisplayImageOptions
|
||||
import com.nostra13.universalimageloader.core.ImageLoader
|
||||
import com.xwray.groupie.kotlinandroidextensions.GroupieViewHolder
|
||||
import com.xwray.groupie.kotlinandroidextensions.Item
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.selected_highlight
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.thumbnail_view
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.title_view
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.*
|
||||
import kotlinx.android.synthetic.main.picker_subscription_item.view.*
|
||||
import org.schabi.newpipe.R
|
||||
import org.schabi.newpipe.database.subscription.SubscriptionEntity
|
||||
import org.schabi.newpipe.util.AnimationUtils
|
||||
import org.schabi.newpipe.util.AnimationUtils.animateView
|
||||
import org.schabi.newpipe.util.ImageDisplayConstants
|
||||
|
||||
data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, var isSelected: Boolean = false) : Item() {
|
||||
companion object {
|
||||
const val UPDATE_SELECTED = 123
|
||||
|
||||
val IMAGE_LOADING_OPTIONS: DisplayImageOptions = ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS
|
||||
}
|
||||
|
||||
data class PickerSubscriptionItem(
|
||||
val subscriptionEntity: SubscriptionEntity,
|
||||
var isSelected: Boolean = false
|
||||
) : Item() {
|
||||
override fun getId(): Long = subscriptionEntity.uid
|
||||
override fun getLayout(): Int = R.layout.picker_subscription_item
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int, payloads: MutableList<Any>) {
|
||||
if (payloads.contains(UPDATE_SELECTED)) {
|
||||
animateView(viewHolder.selected_highlight, AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
|
||||
return
|
||||
}
|
||||
|
||||
super.bind(viewHolder, position, payloads)
|
||||
}
|
||||
override fun getSpanSize(spanCount: Int, position: Int): Int = 1
|
||||
|
||||
override fun bind(viewHolder: GroupieViewHolder, position: Int) {
|
||||
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl, viewHolder.thumbnail_view, IMAGE_LOADING_OPTIONS)
|
||||
ImageLoader.getInstance().displayImage(subscriptionEntity.avatarUrl,
|
||||
viewHolder.thumbnail_view, ImageDisplayConstants.DISPLAY_AVATAR_OPTIONS)
|
||||
|
||||
viewHolder.title_view.text = subscriptionEntity.name
|
||||
viewHolder.selected_highlight.visibility = if (isSelected) View.VISIBLE else View.GONE
|
||||
|
@ -47,7 +36,9 @@ data class PickerSubscriptionItem(val subscriptionEntity: SubscriptionEntity, va
|
|||
viewHolder.selected_highlight.alpha = 1F
|
||||
}
|
||||
|
||||
override fun getId(): Long {
|
||||
return subscriptionEntity.uid
|
||||
fun updateSelected(containerView: View, isSelected: Boolean) {
|
||||
this.isSelected = isSelected
|
||||
animateView(containerView.selected_highlight,
|
||||
AnimationUtils.Type.LIGHT_SCALE_AND_ALPHA, isSelected, 150)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,42 +102,56 @@
|
|||
android:id="@+id/subscriptions_selector"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:visibility="gone">
|
||||
android:orientation="vertical">
|
||||
|
||||
<LinearLayout
|
||||
<androidx.appcompat.widget.Toolbar
|
||||
android:id="@+id/subscriptions_header_toolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingRight="16dp"
|
||||
android:paddingBottom="12dp">
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:gravity="center_vertical"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.ActionBar"
|
||||
app:popupTheme="@style/ThemeOverlay.AppCompat.ActionBar"
|
||||
app:titleTextAppearance="@style/Toolbar.Title">
|
||||
|
||||
<TextView
|
||||
android:layout_width="0dp"
|
||||
<LinearLayout
|
||||
android:id="@+id/subscriptions_header_info_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="start|center_vertical"
|
||||
android:text="@string/tab_subscriptions"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subscriptions_selector_header_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center_vertical"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="12sp"
|
||||
tools:text="1 selected" />
|
||||
</LinearLayout>
|
||||
<TextView
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center_vertical"
|
||||
android:text="@string/tab_subscriptions"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/subscriptions_header_info"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="start|center_vertical"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="12sp"
|
||||
tools:text="1 selected" />
|
||||
</LinearLayout>
|
||||
|
||||
<include
|
||||
android:id="@+id/subscriptions_header_search_container"
|
||||
layout="@layout/toolbar_search_layout"
|
||||
android:visibility="gone"/>
|
||||
|
||||
</androidx.appcompat.widget.Toolbar>
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/subscriptions_selector_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="2dp"
|
||||
android:clipToPadding="false"
|
||||
tools:itemCount="200"
|
||||
tools:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
|
||||
tools:listitem="@layout/picker_subscription_item"
|
||||
|
@ -203,4 +217,4 @@
|
|||
android:layout_alignParentEnd="true"
|
||||
android:text="@string/create" />
|
||||
</RelativeLayout>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
<FrameLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:background="?attr/colorPrimary">
|
||||
tools:background="?attr/colorPrimary">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/toolbar_search_edit_text"
|
||||
|
@ -14,7 +15,7 @@
|
|||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginRight="48dp"
|
||||
android:layout_marginTop="4dp"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:background="@null"
|
||||
android:focusable="true"
|
||||
android:focusableInTouchMode="true"
|
||||
android:nextFocusDown="@+id/suggestions_list"
|
||||
|
@ -29,6 +30,7 @@
|
|||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_gravity="right|center_vertical"
|
||||
android:contentDescription="@string/clear"
|
||||
android:focusable="true"
|
||||
tools:ignore="RtlHardcoded">
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_search"
|
||||
android:icon="?attr/ic_search"
|
||||
android:title="@string/search"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
Loading…
Reference in New Issue