mirror of
				https://github.com/TeamNewPipe/NewPipe
				synced 2025-10-31 15:23:00 +00:00 
			
		
		
		
	Add search for subscription picker in the feed group dialog
This commit is contained in:
		| @@ -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) | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Mauricio Colli
					Mauricio Colli