Working subscription groups and image pickers

This commit is contained in:
Kelvin 2023-12-15 19:38:33 +01:00
commit 4930ea8183
24 changed files with 689 additions and 67 deletions

View file

@ -248,20 +248,23 @@ class Settings : FragmentedStorageFileJson() {
return FeedStyle.THUMBNAIL; return FeedStyle.THUMBNAIL;
} }
@FormField(R.string.preview_feed_items, FieldForm.TOGGLE, R.string.preview_feed_items_description, 5) @FormField(R.string.show_subscription_group, FieldForm.TOGGLE, R.string.show_subscription_group_description, 5)
var showSubscriptionGroups: Boolean = true;
@FormField(R.string.preview_feed_items, FieldForm.TOGGLE, R.string.preview_feed_items_description, 6)
var previewFeedItems: Boolean = true; var previewFeedItems: Boolean = true;
@FormField(R.string.progress_bar, FieldForm.TOGGLE, R.string.progress_bar_description, 6) @FormField(R.string.progress_bar, FieldForm.TOGGLE, R.string.progress_bar_description, 7)
var progressBar: Boolean = true; var progressBar: Boolean = true;
@FormField(R.string.fetch_on_app_boot, FieldForm.TOGGLE, R.string.shortly_after_opening_the_app_start_fetching_subscriptions, 7) @FormField(R.string.fetch_on_app_boot, FieldForm.TOGGLE, R.string.shortly_after_opening_the_app_start_fetching_subscriptions, 8)
@Serializable(with = FlexibleBooleanSerializer::class) @Serializable(with = FlexibleBooleanSerializer::class)
var fetchOnAppBoot: Boolean = true; var fetchOnAppBoot: Boolean = true;
@FormField(R.string.fetch_on_tab_opened, FieldForm.TOGGLE, R.string.fetch_on_tab_opened_description, 8) @FormField(R.string.fetch_on_tab_opened, FieldForm.TOGGLE, R.string.fetch_on_tab_opened_description, 9)
var fetchOnTabOpen: Boolean = true; var fetchOnTabOpen: Boolean = true;
@FormField(R.string.background_update, FieldForm.DROPDOWN, R.string.experimental_background_update_for_subscriptions_cache, 9) @FormField(R.string.background_update, FieldForm.DROPDOWN, R.string.experimental_background_update_for_subscriptions_cache, 10)
@DropdownFieldOptionsId(R.array.background_interval) @DropdownFieldOptionsId(R.array.background_interval)
var subscriptionsBackgroundUpdateInterval: Int = 0; var subscriptionsBackgroundUpdateInterval: Int = 0;
@ -277,7 +280,7 @@ class Settings : FragmentedStorageFileJson() {
}; };
@FormField(R.string.subscription_concurrency, FieldForm.DROPDOWN, R.string.specify_how_many_threads_are_used_to_fetch_channels, 10) @FormField(R.string.subscription_concurrency, FieldForm.DROPDOWN, R.string.specify_how_many_threads_are_used_to_fetch_channels, 11)
@DropdownFieldOptionsId(R.array.thread_count) @DropdownFieldOptionsId(R.array.thread_count)
var subscriptionConcurrency: Int = 3; var subscriptionConcurrency: Int = 3;
@ -285,17 +288,17 @@ class Settings : FragmentedStorageFileJson() {
return threadIndexToCount(subscriptionConcurrency); return threadIndexToCount(subscriptionConcurrency);
} }
@FormField(R.string.show_watch_metrics, FieldForm.TOGGLE, R.string.show_watch_metrics_description, 11) @FormField(R.string.show_watch_metrics, FieldForm.TOGGLE, R.string.show_watch_metrics_description, 12)
var showWatchMetrics: Boolean = false; var showWatchMetrics: Boolean = false;
@FormField(R.string.track_playtime_locally, FieldForm.TOGGLE, R.string.track_playtime_locally_description, 12) @FormField(R.string.track_playtime_locally, FieldForm.TOGGLE, R.string.track_playtime_locally_description, 13)
var allowPlaytimeTracking: Boolean = true; var allowPlaytimeTracking: Boolean = true;
@FormField(R.string.always_reload_from_cache, FieldForm.TOGGLE, R.string.always_reload_from_cache_description, 13) @FormField(R.string.always_reload_from_cache, FieldForm.TOGGLE, R.string.always_reload_from_cache_description, 14)
var alwaysReloadFromCache: Boolean = false; var alwaysReloadFromCache: Boolean = false;
@FormField(R.string.clear_channel_cache, FieldForm.BUTTON, R.string.clear_channel_cache_description, 14) @FormField(R.string.clear_channel_cache, FieldForm.BUTTON, R.string.clear_channel_cache_description, 15)
fun clearChannelCache() { fun clearChannelCache() {
UIDialogs.toast(SettingsActivity.getActivity()!!, "Started clearing.."); UIDialogs.toast(SettingsActivity.getActivity()!!, "Started clearing..");
StateCache.instance.clear(); StateCache.instance.clear();

View file

@ -103,14 +103,14 @@ class UISlideOverlays {
}, false) else null, }, false) else null,
if(capabilities.hasType(ResultCapabilities.TYPE_POSTS)) SlideUpMenuItem(container.context, R.drawable.ic_chat, "Posts", "Check for posts", "fetchPosts", { if(capabilities.hasType(ResultCapabilities.TYPE_POSTS)) SlideUpMenuItem(container.context, R.drawable.ic_chat, "Posts", "Check for posts", "fetchPosts", {
subscription.doFetchPosts = menu?.selectOption(null, "fetchPosts", true, true) ?: subscription.doFetchPosts; subscription.doFetchPosts = menu?.selectOption(null, "fetchPosts", true, true) ?: subscription.doFetchPosts;
}, false) else null, }, false) else null/*,,
SlideUpMenuGroup(container.context, "Actions", SlideUpMenuGroup(container.context, "Actions",
"Various things you can do with this subscription", "Various things you can do with this subscription",
-1, listOf()), -1, listOf())
SlideUpMenuItem(container.context, R.drawable.ic_list, "Add to Group", "", "btnAddToGroup", { SlideUpMenuItem(container.context, R.drawable.ic_list, "Add to Group", "", "btnAddToGroup", {
showCreateSubscriptionGroup(container, subscription.channel); showCreateSubscriptionGroup(container, subscription.channel);
}, false) }, false)*/
).filterNotNull()); ).filterNotNull());
menu = SlideUpMenuOverlay(container.context, container, "Subscription Settings", null, true, items); menu = SlideUpMenuOverlay(container.context, container, "Subscription Settings", null, true, items);
@ -531,7 +531,7 @@ class UISlideOverlays {
return overlay; return overlay;
} }
fun showCreateSubscriptionGroup(container: ViewGroup, initialChannel: IPlatformChannel?, onCreate: ((String) -> Unit)? = null): SlideUpMenuOverlay { fun showCreateSubscriptionGroup(container: ViewGroup, initialChannel: IPlatformChannel? = null, onCreate: ((String) -> Unit)? = null): SlideUpMenuOverlay {
val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name)); val nameInput = SlideUpMenuTextInput(container.context, container.context.getString(R.string.name));
val addSubGroupOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_subgroup), container.context.getString(R.string.ok), false, nameInput); val addSubGroupOverlay = SlideUpMenuOverlay(container.context, container, container.context.getString(R.string.create_new_subgroup), container.context.getString(R.string.ok), false, nameInput);

View file

@ -102,6 +102,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
lateinit var _fragImportPlaylists: ImportPlaylistsFragment; lateinit var _fragImportPlaylists: ImportPlaylistsFragment;
lateinit var _fragBuy: BuyFragment; lateinit var _fragBuy: BuyFragment;
lateinit var _fragSubGroup: SubscriptionGroupFragment; lateinit var _fragSubGroup: SubscriptionGroupFragment;
lateinit var _fragSubGroupList: SubscriptionGroupListFragment;
lateinit var _fragBrowser: BrowserFragment; lateinit var _fragBrowser: BrowserFragment;
@ -238,6 +239,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
_fragImportPlaylists = ImportPlaylistsFragment.newInstance(); _fragImportPlaylists = ImportPlaylistsFragment.newInstance();
_fragBuy = BuyFragment.newInstance(); _fragBuy = BuyFragment.newInstance();
_fragSubGroup = SubscriptionGroupFragment.newInstance(); _fragSubGroup = SubscriptionGroupFragment.newInstance();
_fragSubGroupList = SubscriptionGroupListFragment.newInstance();
_fragBrowser = BrowserFragment.newInstance(); _fragBrowser = BrowserFragment.newInstance();
@ -320,6 +322,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
_fragImportSubscriptions.topBar = _fragTopBarImport; _fragImportSubscriptions.topBar = _fragTopBarImport;
_fragImportPlaylists.topBar = _fragTopBarImport; _fragImportPlaylists.topBar = _fragTopBarImport;
_fragSubGroup.topBar = _fragTopBarNavigation; _fragSubGroup.topBar = _fragTopBarNavigation;
_fragSubGroupList.topBar = _fragTopBarAdd;
_fragBrowser.topBar = _fragTopBarNavigation; _fragBrowser.topBar = _fragTopBarNavigation;
@ -987,6 +990,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher {
BrowserFragment::class -> _fragBrowser as T; BrowserFragment::class -> _fragBrowser as T;
BuyFragment::class -> _fragBuy as T; BuyFragment::class -> _fragBuy as T;
SubscriptionGroupFragment::class -> _fragSubGroup as T; SubscriptionGroupFragment::class -> _fragSubGroup as T;
SubscriptionGroupListFragment::class -> _fragSubGroupList as T;
else -> throw IllegalArgumentException("Fragment type ${T::class.java.name} is not available in MainActivity"); else -> throw IllegalArgumentException("Fragment type ${T::class.java.name} is not available in MainActivity");
} }
} }

View file

@ -55,10 +55,10 @@ class ManageTabsActivity : AppCompatActivity() {
Settings.instance.save() Settings.instance.save()
} }
val items = Settings.instance.tabs.mapNotNull { val items = ArrayList(Settings.instance.tabs.mapNotNull {
val buttonDefinition = MenuBottomBarFragment.buttonDefinitions.find { d -> it.id == d.id } ?: return@mapNotNull null val buttonDefinition = MenuBottomBarFragment.buttonDefinitions.find { d -> it.id == d.id } ?: return@mapNotNull null
TabViewHolderData(buttonDefinition, it.enabled) TabViewHolderData(buttonDefinition, it.enabled)
}; });
_listTabs = _recyclerTabs.asAny(items) { _listTabs = _recyclerTabs.asAny(items) {
it.onDragDrop.subscribe { vh -> it.onDragDrop.subscribe { vh ->

View file

@ -348,6 +348,7 @@ class MenuBottomBarFragment : MainActivityFragment() {
ButtonDefinition(5, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate<HistoryFragment>() }), ButtonDefinition(5, R.drawable.ic_history, R.drawable.ic_history, R.string.history, canToggle = false, { it.currentMain is HistoryFragment }, { it.navigate<HistoryFragment>() }),
ButtonDefinition(6, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate<DownloadsFragment>() }), ButtonDefinition(6, R.drawable.ic_download, R.drawable.ic_download, R.string.downloads, canToggle = false, { it.currentMain is DownloadsFragment }, { it.navigate<DownloadsFragment>() }),
ButtonDefinition(8, R.drawable.ic_chat, R.drawable.ic_chat_filled, R.string.comments, canToggle = true, { it.currentMain is CommentsFragment }, { it.navigate<CommentsFragment>() }), ButtonDefinition(8, R.drawable.ic_chat, R.drawable.ic_chat_filled, R.string.comments, canToggle = true, { it.currentMain is CommentsFragment }, { it.navigate<CommentsFragment>() }),
ButtonDefinition(9, R.drawable.ic_subscriptions, R.drawable.ic_subscriptions_filled, R.string.subscription_group_menu, canToggle = true, { it.currentMain is SubscriptionGroupListFragment }, { it.navigate<SubscriptionGroupListFragment>() }),
ButtonDefinition(7, R.drawable.ic_settings, R.drawable.ic_settings, R.string.settings, canToggle = false, { false }, { ButtonDefinition(7, R.drawable.ic_settings, R.drawable.ic_settings, R.string.settings, canToggle = false, { false }, {
val c = it.context ?: return@ButtonDefinition; val c = it.context ?: return@ButtonDefinition;
Logger.i(TAG, "settings preventPictureInPicture()"); Logger.i(TAG, "settings preventPictureInPicture()");

View file

@ -10,6 +10,7 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
import android.widget.FrameLayout import android.widget.FrameLayout
import android.widget.ImageButton
import android.widget.ImageView import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.TextView import android.widget.TextView
@ -48,7 +49,7 @@ class SubscriptionGroupFragment : MainFragment() {
super.onShownWithView(parameter, isBack); super.onShownWithView(parameter, isBack);
if(parameter is SubscriptionGroup) if(parameter is SubscriptionGroup)
_view?.setGroup(parameter); _view?.setGroup(StateSubscriptionGroups.instance.getSubscriptionGroup(parameter.id) ?: parameter);
else else
_view?.setGroup(null); _view?.setGroup(null);
} }
@ -77,7 +78,8 @@ class SubscriptionGroupFragment : MainFragment() {
private val _textGroupMeta: TextView; private val _textGroupMeta: TextView;
private val _buttonSettings: ImageView; private val _buttonSettings: ImageButton;
private val _buttonDelete: ImageButton;
private val _enabledCreators: ArrayList<IPlatformChannel> = arrayListOf(); private val _enabledCreators: ArrayList<IPlatformChannel> = arrayListOf();
private val _disabledCreators: ArrayList<IPlatformChannel> = arrayListOf(); private val _disabledCreators: ArrayList<IPlatformChannel> = arrayListOf();
@ -107,6 +109,7 @@ class SubscriptionGroupFragment : MainFragment() {
_buttonEditImage = findViewById(R.id.button_edit_image); _buttonEditImage = findViewById(R.id.button_edit_image);
_textGroupMeta = findViewById(R.id.text_group_meta); _textGroupMeta = findViewById(R.id.text_group_meta);
_buttonSettings = findViewById(R.id.button_settings); _buttonSettings = findViewById(R.id.button_settings);
_buttonDelete = findViewById(R.id.button_delete);
_imageGroup.setBackgroundColor(Color.GRAY); _imageGroup.setBackgroundColor(Color.GRAY);
val dp6 = 6.dp(resources); val dp6 = 6.dp(resources);
@ -147,6 +150,16 @@ class SubscriptionGroupFragment : MainFragment() {
_buttonEditImage.setOnClickListener { _buttonEditImage.setOnClickListener {
_group?.let { editImage(it) } _group?.let { editImage(it) }
}; };
_buttonSettings.setOnClickListener {
}
_buttonDelete.setOnClickListener {
_group?.let {
StateSubscriptionGroups.instance.deleteSubscriptionGroup(it.id);
};
fragment.close(true);
}
_buttonSettings.visibility = View.GONE;
_searchBar.onSearchChanged.subscribe { _searchBar.onSearchChanged.subscribe {
filterCreators(); filterCreators();
@ -255,8 +268,10 @@ class SubscriptionGroupFragment : MainFragment() {
_recyclerCreatorsEnabled.adapter.notifyItemInserted(_enabledCreatorsFiltered.size - 1); _recyclerCreatorsEnabled.adapter.notifyItemInserted(_enabledCreatorsFiltered.size - 1);
_group?.let { _group?.let {
it.urls.remove(channel.url); if(!it.urls.contains(channel.url)) {
save(); it.urls.add(channel.url);
save();
}
} }
updateMeta(); updateMeta();
} }

View file

@ -0,0 +1,141 @@
package com.futo.platformplayer.fragment.mainactivity.main
import android.content.Intent
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.webkit.CookieManager
import android.webkit.WebResourceRequest
import android.webkit.WebView
import android.webkit.WebViewClient
import android.widget.FrameLayout
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R
import com.futo.platformplayer.UISlideOverlays
import com.futo.platformplayer.activities.AddSourceOptionsActivity
import com.futo.platformplayer.fragment.mainactivity.topbar.AddTopBarFragment
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.SubscriptionGroup
import com.futo.platformplayer.states.StateSubscriptionGroups
import com.futo.platformplayer.views.AnyAdapterView
import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny
import com.futo.platformplayer.views.adapters.ItemMoveCallback
import com.futo.platformplayer.views.adapters.viewholders.SubscriptionGroupListViewHolder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Collections
class SubscriptionGroupListFragment : MainFragment() {
override val isMainView : Boolean = true;
override val isTab: Boolean = true;
override val hasBottomBar: Boolean get() = true;
private var _touchHelper: ItemTouchHelper? = null;
private var _subs: ArrayList<SubscriptionGroup> = arrayListOf();
private var _list: AnyAdapterView<SubscriptionGroup, SubscriptionGroupListViewHolder>? = null;
private var _overlay: FrameLayout? = null;
override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val view = inflater.inflate(R.layout.fragment_subscriptions_group_list, container, false);
_overlay = view.findViewById(R.id.overlay);
val recycler = view.findViewById<RecyclerView>(R.id.list);
val callback = ItemMoveCallback();
_touchHelper = ItemTouchHelper(callback);
_touchHelper?.attachToRecyclerView(recycler);
_subs.clear();
_subs.addAll(StateSubscriptionGroups.instance.getSubscriptionGroups().sortedBy { it.priority });
_list = recycler.asAny(_subs, RecyclerView.VERTICAL){
it.onClick.subscribe {
navigate<SubscriptionGroupFragment>(it);
};
it.onSettings.subscribe {
};
it.onDelete.subscribe { group ->
val loc = _subs.indexOf(group);
_subs.remove(group);
_list?.adapter?.notifyItemRangeRemoved(loc);
StateSubscriptionGroups.instance.deleteSubscriptionGroup(group.id);
};
it.onDragDrop.subscribe {
_touchHelper?.startDrag(it);
};
};
callback.onRowMoved.subscribe(::groupMoved);
return view;
}
private fun groupMoved(fromPosition: Int, toPosition: Int) {
Logger.i("SubscriptionGroupListFragment", "Moved ${fromPosition} to ${toPosition}");
synchronized(_subs) {
if (fromPosition < toPosition) {
for (i in fromPosition until toPosition) {
Collections.swap(_subs, i, i + 1)
}
} else {
for (i in fromPosition downTo toPosition + 1) {
Collections.swap(_subs, i, i - 1)
}
}
}
_list?.adapter?.notifyItemMoved(fromPosition, toPosition);
synchronized(_subs) {
for(i in 0 until _subs.size) {
val sub = _subs[i];
if(sub.priority != i) {
sub.priority = i;
StateSubscriptionGroups.instance.updateSubscriptionGroup(sub, true);
}
}
}
}
override fun onShownWithView(parameter: Any?, isBack: Boolean) {
super.onShownWithView(parameter, isBack);
updateGroups();
StateSubscriptionGroups.instance.onGroupsChanged.subscribe(this) {
updateGroups();
}
if(topBar is AddTopBarFragment)
(topBar as AddTopBarFragment).onAdd.subscribe {
_overlay?.let {
UISlideOverlays.showCreateSubscriptionGroup(it)
}
};
}
private fun updateGroups() {
lifecycleScope.launch(Dispatchers.Main) {
_subs.clear();
_subs.addAll(StateSubscriptionGroups.instance.getSubscriptionGroups().sortedBy { it.priority });
_list?.adapter?.notifyContentChanged();
}
}
override fun onHide() {
super.onHide();
StateSubscriptionGroups.instance.onGroupsChanged.remove(this);
if(topBar is AddTopBarFragment)
(topBar as AddTopBarFragment).onAdd.remove(this);
}
override fun onBackPressed(): Boolean {
return false;
}
companion object {
fun newInstance() = SubscriptionGroupListFragment().apply {}
}
}

View file

@ -21,6 +21,7 @@ import com.futo.platformplayer.exceptions.ChannelException
import com.futo.platformplayer.exceptions.RateLimitException import com.futo.platformplayer.exceptions.RateLimitException
import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.SearchType import com.futo.platformplayer.models.SearchType
import com.futo.platformplayer.models.SubscriptionGroup
import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.states.StateCache import com.futo.platformplayer.states.StateCache
import com.futo.platformplayer.states.StatePlatform import com.futo.platformplayer.states.StatePlatform
@ -99,6 +100,8 @@ class SubscriptionsFeedFragment : MainFragment() {
class SubscriptionsFeedView : ContentFeedView<SubscriptionsFeedFragment> { class SubscriptionsFeedView : ContentFeedView<SubscriptionsFeedFragment> {
override val shouldShowTimeBar: Boolean get() = Settings.instance.subscriptions.progressBar override val shouldShowTimeBar: Boolean get() = Settings.instance.subscriptions.progressBar
private var _subGroup: SubscriptionGroup? = null;
constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) { constructor(fragment: SubscriptionsFeedFragment, inflater: LayoutInflater, cachedRecyclerData: RecyclerData<InsertedViewAdapterWithLoader<ContentPreviewViewHolder>, LinearLayoutManager, IPager<IPlatformContent>, IPlatformContent, IPlatformContent, InsertedViewHolder<ContentPreviewViewHolder>>? = null) : super(fragment, inflater, cachedRecyclerData) {
Logger.i(TAG, "SubscriptionsFeedFragment constructor()"); Logger.i(TAG, "SubscriptionsFeedFragment constructor()");
StateSubscriptions.instance.onGlobalSubscriptionsUpdateProgress.subscribe(this) { progress, total -> StateSubscriptions.instance.onGlobalSubscriptionsUpdateProgress.subscribe(this) { progress, total ->
@ -254,11 +257,17 @@ class SubscriptionsFeedFragment : MainFragment() {
layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT); layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
}; };
_subscriptionBar?.onClickChannel?.subscribe { c -> fragment.navigate<ChannelFragment>(c); }; _subscriptionBar?.onClickChannel?.subscribe { c -> fragment.navigate<ChannelFragment>(c); };
_subscriptionBar?.onClickGroup?.subscribe { g -> _subscriptionBar?.onToggleGroup?.subscribe { g ->
if(g is SubscriptionGroup.Add)
UISlideOverlays.showCreateSubscriptionGroup(_overlayContainer);
else {
_subGroup = g;
loadCache(); //TODO: Proper subset update
}
}; };
_subscriptionBar?.onHoldGroup?.subscribe { g -> _subscriptionBar?.onHoldGroup?.subscribe { g ->
fragment.navigate<SubscriptionGroupFragment>(g); if(g !is SubscriptionGroup.Add)
fragment.navigate<SubscriptionGroupFragment>(g);
}; };
synchronized(_filterLock) { synchronized(_filterLock) {
@ -294,9 +303,15 @@ class SubscriptionsFeedFragment : MainFragment() {
override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> { override fun filterResults(results: List<IPlatformContent>): List<IPlatformContent> {
val nowSoon = OffsetDateTime.now().plusMinutes(5); val nowSoon = OffsetDateTime.now().plusMinutes(5);
val filterGroup = _subGroup;
return results.filter { return results.filter {
val allowedContentType = _filterSettings.allowContentTypes.contains(if(it.contentType == ContentType.NESTED_VIDEO || it.contentType == ContentType.LOCKED) ContentType.MEDIA else it.contentType); val allowedContentType = _filterSettings.allowContentTypes.contains(if(it.contentType == ContentType.NESTED_VIDEO || it.contentType == ContentType.LOCKED) ContentType.MEDIA else it.contentType);
//TODO: Check against a sub cache
if(filterGroup != null && !filterGroup.urls.contains(it.author.url))
return@filter false;
if(it.datetime?.isAfter(nowSoon) == true) { if(it.datetime?.isAfter(nowSoon) == true) {
if(!_filterSettings.allowPlanned) if(!_filterSettings.allowPlanned)
return@filter false; return@filter false;

View file

@ -3,13 +3,31 @@ package com.futo.platformplayer.models
import java.util.UUID import java.util.UUID
@kotlinx.serialization.Serializable @kotlinx.serialization.Serializable
class SubscriptionGroup { open class SubscriptionGroup {
val id: String = UUID.randomUUID().toString(); var id: String = UUID.randomUUID().toString();
var name: String; var name: String;
var image: ImageVariable? = null; var image: ImageVariable? = null;
var urls: MutableList<String> = mutableListOf(); var urls: MutableList<String> = mutableListOf();
var priority: Int = 99;
constructor(name: String) { constructor(name: String) {
this.name = name; this.name = name;
} }
constructor(parent: SubscriptionGroup) {
this.id = parent.id;
this.name = parent.name;
this.image = parent.image;
this.urls = parent.urls;
this.priority = parent.priority;
}
class Selectable(parent: SubscriptionGroup, isSelected: Boolean = false): SubscriptionGroup(parent) {
var selected: Boolean = isSelected;
}
class Add: SubscriptionGroup("+") {
init {
urls.add("+");
}
}
} }

View file

@ -133,6 +133,7 @@ class StateApp {
//Files //Files
private var _tempDirectory: File? = null; private var _tempDirectory: File? = null;
private var _persistentDirectory: File? = null;
//AutoRotate //AutoRotate
@ -165,6 +166,16 @@ class StateApp {
return File(_tempDirectory, name); return File(_tempDirectory, name);
} }
fun getPersistFile(extension: String? = null): File {
val name = UUID.randomUUID().toString() +
if(extension != null)
".${extension}"
else
"";
return File(_persistentDirectory, name);
}
fun getCurrentSystemAutoRotate(): Boolean { fun getCurrentSystemAutoRotate(): Boolean {
_context?.let { _context?.let {
systemAutoRotate = android.provider.Settings.System.getInt( systemAutoRotate = android.provider.Settings.System.getInt(
@ -290,6 +301,10 @@ class StateApp {
_tempDirectory?.deleteRecursively(); _tempDirectory?.deleteRecursively();
} }
_tempDirectory?.mkdirs(); _tempDirectory?.mkdirs();
_persistentDirectory = File(context.filesDir, "persist");
if(_persistentDirectory?.exists() == false) {
_persistentDirectory?.mkdirs();
}
} }
} }

View file

@ -51,19 +51,25 @@ class StateSubscriptionGroups {
.withUnique { it.id } .withUnique { it.id }
.load(); .load();
val onGroupsChanged = Event0();
fun getSubscriptionGroup(id: String): SubscriptionGroup? { fun getSubscriptionGroup(id: String): SubscriptionGroup? {
return _subGroups.findItem { it.id == id }; return _subGroups.findItem { it.id == id };
} }
fun getSubscriptionGroups(): List<SubscriptionGroup> { fun getSubscriptionGroups(): List<SubscriptionGroup> {
return _subGroups.getItems(); return _subGroups.getItems();
} }
fun updateSubscriptionGroup(subGroup: SubscriptionGroup) { fun updateSubscriptionGroup(subGroup: SubscriptionGroup, preventNotify: Boolean = false) {
_subGroups.save(subGroup); _subGroups.save(subGroup);
if(!preventNotify)
onGroupsChanged.emit();
} }
fun deleteSubscriptionGroup(id: String){ fun deleteSubscriptionGroup(id: String){
val group = getSubscriptionGroup(id); val group = getSubscriptionGroup(id);
if(group != null) if(group != null) {
_subGroups.delete(group); _subGroups.delete(group);
onGroupsChanged.emit();
}
} }

View file

@ -46,9 +46,10 @@ class AnyAdapterView<I, T>(view: RecyclerView, adapter: BaseAnyAdapter<I, T, T>,
where T : AnyAdapter.AnyViewHolder<I>{ where T : AnyAdapter.AnyViewHolder<I>{
companion object { companion object {
/*
inline fun <I, reified T : AnyAdapter.AnyViewHolder<I>> RecyclerView.asAny(list: List<I>, orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null): AnyAdapterView<I, T> { inline fun <I, reified T : AnyAdapter.AnyViewHolder<I>> RecyclerView.asAny(list: List<I>, orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null): AnyAdapterView<I, T> {
return asAny(ArrayList(list), orientation, reversed, onCreate); return asAny(ArrayList(list), orientation, reversed, onCreate);
} }*/
inline fun <I, reified T : AnyAdapter.AnyViewHolder<I>> RecyclerView.asAny(list: ArrayList<I>, orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null): AnyAdapterView<I, T> { inline fun <I, reified T : AnyAdapter.AnyViewHolder<I>> RecyclerView.asAny(list: ArrayList<I>, orientation: Int = RecyclerView.VERTICAL, reversed: Boolean = false, noinline onCreate: ((T)->Unit)? = null): AnyAdapterView<I, T> {
return AnyAdapterView(this, AnyAdapter.create(list, onCreate), orientation, reversed); return AnyAdapterView(this, AnyAdapter.create(list, onCreate), orientation, reversed);
} }

View file

@ -1,5 +1,6 @@
package com.futo.platformplayer.views.adapters package com.futo.platformplayer.views.adapters
import android.annotation.SuppressLint
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ArrayAdapter import android.widget.ArrayAdapter
@ -50,6 +51,7 @@ open class BaseAnyAdapter<I, T : AnyAdapter.AnyViewHolder<I>, IT : ViewHolder> {
cb(item); cb(item);
} }
@SuppressLint("NotifyDataSetChanged")
fun notifyContentChanged() { fun notifyContentChanged() {
adapter.notifyDataSetChanged(); adapter.notifyDataSetChanged();
} }

View file

@ -1,5 +1,6 @@
package com.futo.platformplayer.views.adapters.viewholders package com.futo.platformplayer.views.adapters.viewholders
import android.graphics.Color
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.LinearLayout import android.widget.LinearLayout
@ -41,12 +42,12 @@ class SubscriptionGroupBarViewHolder(private val _viewGroup: ViewGroup) : AnyAda
.setAllCorners(CornerFamily.ROUNDED, dp6.toFloat()) .setAllCorners(CornerFamily.ROUNDED, dp6.toFloat())
.build() .build()
_viewGroup.setOnClickListener { _view.setOnClickListener {
_group?.let { _group?.let {
onClick.emit(it); onClick.emit(it);
} }
} }
_viewGroup.setOnLongClickListener { _view.setOnLongClickListener {
_group?.let { _group?.let {
onClickLong.emit(it); onClickLong.emit(it);
} }
@ -59,9 +60,18 @@ class SubscriptionGroupBarViewHolder(private val _viewGroup: ViewGroup) : AnyAda
val img = value.image; val img = value.image;
if(img != null) if(img != null)
img.setImageView(_image) img.setImageView(_image)
else else {
_image.setImageResource(0); _image.setImageResource(0);
if(value is SubscriptionGroup.Add)
_image.setBackgroundColor(Color.DKGRAY);
}
_textSubGroup.text = value.name; _textSubGroup.text = value.name;
if(value is SubscriptionGroup.Selectable && value.selected)
_view.setBackgroundColor(_view.context.resources.getColor(R.color.colorPrimary, null));
else
_view.setBackgroundColor(_view.context.resources.getColor(R.color.transparent, null));
} }
companion object { companion object {

View file

@ -0,0 +1,109 @@
package com.futo.platformplayer.views.adapters.viewholders
import android.graphics.Color
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.ViewGroup
import android.widget.ImageButton
import android.widget.ImageView
import android.widget.LinearLayout
import android.widget.TextView
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
import com.futo.platformplayer.R
import com.futo.platformplayer.api.media.PlatformID
import com.futo.platformplayer.api.media.models.channels.SerializedChannel
import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.constructs.TaskHandler
import com.futo.platformplayer.dp
import com.futo.platformplayer.logging.Logger
import com.futo.platformplayer.models.Subscription
import com.futo.platformplayer.models.SubscriptionGroup
import com.futo.platformplayer.polycentric.PolycentricCache
import com.futo.platformplayer.selectBestImage
import com.futo.platformplayer.states.StateApp
import com.futo.platformplayer.views.adapters.AnyAdapter
import com.futo.platformplayer.views.adapters.ItemMoveCallback
import com.futo.platformplayer.views.others.CreatorThumbnail
import com.futo.polycentric.core.toURLInfoSystemLinkUrl
import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel
class SubscriptionGroupListViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder<SubscriptionGroup>(
LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_subscription_group, _viewGroup, false)) {
private var _group: SubscriptionGroup? = null;
private val _thumb: ImageView;
private val _image: ShapeableImageView;
private val _textSubGroup: TextView;
private val _textSubGroupMeta: TextView;
private val _buttonSettings: ImageButton;
private val _buttonDelete: ImageButton;
val onClick = Event1<SubscriptionGroup>();
val onSettings = Event1<SubscriptionGroup>();
val onDelete = Event1<SubscriptionGroup>();
val onDragDrop = Event1<RecyclerView.ViewHolder>();
init {
_thumb = _view.findViewById(R.id.thumb);
_image = _view.findViewById(R.id.image);
_textSubGroup = _view.findViewById(R.id.text_sub_group);
_textSubGroupMeta = _view.findViewById(R.id.text_sub_group_meta);
_buttonSettings = _view.findViewById(R.id.button_settings);
_buttonDelete = _view.findViewById(R.id.button_trash);
val dp6 = 6.dp(_view.resources);
_image.shapeAppearanceModel = ShapeAppearanceModel.builder()
.setAllCorners(CornerFamily.ROUNDED, dp6.toFloat())
.build()
_view.setOnClickListener {
_group?.let {
onClick.emit(it);
}
}
_thumb.setOnTouchListener { _, event ->
if (event.action == MotionEvent.ACTION_DOWN) {
onDragDrop.emit(this);
}
false
};
_buttonSettings.setOnClickListener {
_group?.let {
onSettings.emit(it);
};
}
_buttonDelete.setOnClickListener {
_group?.let {
onDelete.emit(it);
};
}
}
override fun bind(value: SubscriptionGroup) {
_group = value;
val img = value.image;
if(img != null)
img.setImageView(_image)
else {
_image.setImageResource(0);
if(value is SubscriptionGroup.Add)
_image.setBackgroundColor(Color.DKGRAY);
}
_textSubGroup.text = value.name;
_textSubGroupMeta.text = "${value.urls.size} subscriptions";
if(value is SubscriptionGroup.Selectable && value.selected)
_view.setBackgroundColor(_view.context.resources.getColor(R.color.colorPrimary, null));
else
_view.setBackgroundColor(_view.context.resources.getColor(R.color.transparent, null));
}
companion object {
private const val TAG = "SubscriptionGroupBarViewHolder";
}
}

View file

@ -1,5 +1,6 @@
package com.futo.platformplayer.views.overlays package com.futo.platformplayer.views.overlays
import android.app.Activity
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.graphics.Color import android.graphics.Color
@ -12,12 +13,16 @@ import android.widget.ImageView
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.result.contract.ActivityResultContracts
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.core.net.toFile
import androidx.core.net.toUri
import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.GridLayoutManager
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.bumptech.glide.Glide
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.UIDialogs
import com.futo.platformplayer.activities.IWithResultLauncher import com.futo.platformplayer.activities.IWithResultLauncher
import com.futo.platformplayer.activities.MainActivity
import com.futo.platformplayer.api.media.models.channels.IPlatformChannel import com.futo.platformplayer.api.media.models.channels.IPlatformChannel
import com.futo.platformplayer.constructs.Event0 import com.futo.platformplayer.constructs.Event0
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
@ -31,13 +36,17 @@ import com.futo.platformplayer.views.adapters.AnyAdapter
import com.futo.platformplayer.views.adapters.viewholders.CreatorBarViewHolder import com.futo.platformplayer.views.adapters.viewholders.CreatorBarViewHolder
import com.futo.platformplayer.views.adapters.viewholders.SelectableCreatorBarViewHolder import com.futo.platformplayer.views.adapters.viewholders.SelectableCreatorBarViewHolder
import com.futo.platformplayer.views.buttons.BigButton import com.futo.platformplayer.views.buttons.BigButton
import com.github.dhaval2404.imagepicker.ImagePicker
import com.google.android.flexbox.FlexboxLayout import com.google.android.flexbox.FlexboxLayout
import com.google.android.material.imageview.ShapeableImageView import com.google.android.material.imageview.ShapeableImageView
import com.google.android.material.shape.CornerFamily import com.google.android.material.shape.CornerFamily
import com.google.android.material.shape.ShapeAppearanceModel import com.google.android.material.shape.ShapeAppearanceModel
import java.io.File
class ImageVariableOverlay: ConstraintLayout { class ImageVariableOverlay: ConstraintLayout {
private val _buttonGallery: BigButton; private val _buttonGallery: BigButton;
private val _imageGallerySelected: ImageView;
private val _imageGallerySelectedContainer: LinearLayout;
private val _buttonSelect: Button; private val _buttonSelect: Button;
private val _topbar: OverlayTopbar; private val _topbar: OverlayTopbar;
private val _recyclerPresets: AnyAdapterView<PresetImage, PresetViewHolder>; private val _recyclerPresets: AnyAdapterView<PresetImage, PresetViewHolder>;
@ -53,6 +62,7 @@ class ImageVariableOverlay: ConstraintLayout {
); );
private var _selected: ImageVariable? = null; private var _selected: ImageVariable? = null;
private var _selectedFile: String? = null;
val onSelected = Event1<ImageVariable>(); val onSelected = Event1<ImageVariable>();
val onClose = Event0(); val onClose = Event0();
@ -74,6 +84,8 @@ class ImageVariableOverlay: ConstraintLayout {
inflate(context, R.layout.overlay_image_variable, this); inflate(context, R.layout.overlay_image_variable, this);
_topbar = findViewById(R.id.topbar); _topbar = findViewById(R.id.topbar);
_buttonGallery = findViewById(R.id.button_gallery); _buttonGallery = findViewById(R.id.button_gallery);
_imageGallerySelected = findViewById(R.id.gallery_selected);
_imageGallerySelectedContainer = findViewById(R.id.gallery_selected_container);
_buttonSelect = findViewById(R.id.button_select); _buttonSelect = findViewById(R.id.button_select);
_recyclerPresets = findViewById<RecyclerView>(R.id.recycler_presets).asAny(_presets, RecyclerView.HORIZONTAL) { _recyclerPresets = findViewById<RecyclerView>(R.id.recycler_presets).asAny(_presets, RecyclerView.HORIZONTAL) {
it.onClick.subscribe { it.onClick.subscribe {
@ -97,23 +109,37 @@ class ImageVariableOverlay: ConstraintLayout {
this.orientation = LinearLayoutManager.VERTICAL; this.orientation = LinearLayoutManager.VERTICAL;
}; };
_buttonGallery.setOnClickListener { _buttonGallery.onClick.subscribe {
val context = StateApp.instance.contextOrNull; val context = StateApp.instance.contextOrNull;
if(context is IWithResultLauncher) { if(context is IWithResultLauncher && context is MainActivity) {
val intent = Intent(); ImagePicker.with(context)
intent.setType("image/*"); .compress(512)
intent.setAction(Intent.ACTION_GET_CONTENT); .maxResultSize(750, 500)
.createIntent {
context.launchForResult(intent, 888) { context.launchForResult(it, 888) {
if(it.resultCode == 888) { if(it.resultCode == Activity.RESULT_OK) {
val url = it.data?.data ?: return@launchForResult; cleanupLastFile();
//TODO: Write to local storage val fileUri = it.data?.data;
_selected = ImageVariable(url.toString()); if(fileUri != null) {
updateSelected(); val file = fileUri.toFile();
} val ext = file.extension;
}; val persistFile = StateApp.instance.getPersistFile(ext);
file.copyTo(persistFile);
_selectedFile = persistFile.toUri().toString();
_selected = ImageVariable(_selectedFile);
updateSelected();
}
}
};
};
} }
}; };
_imageGallerySelectedContainer.setOnClickListener {
if(_selectedFile != null) {
_selected = ImageVariable(_selectedFile);
updateSelected();
}
}
_buttonSelect.setOnClickListener { _buttonSelect.setOnClickListener {
_selected?.let { _selected?.let {
select(it); select(it);
@ -133,13 +159,38 @@ class ImageVariableOverlay: ConstraintLayout {
_creators.forEach { p -> p.active = p.channel.thumbnail == url }; _creators.forEach { p -> p.active = p.channel.thumbnail == url };
_recyclerCreators.notifyContentChanged(); _recyclerCreators.notifyContentChanged();
if(_selectedFile != null) {
_imageGallerySelectedContainer.visibility = View.VISIBLE;
Glide.with(_imageGallerySelected)
.load(_selectedFile)
.into(_imageGallerySelected);
}
else
_imageGallerySelectedContainer.visibility = View.GONE;
if(_selected?.url == _selectedFile)
_imageGallerySelectedContainer.setBackgroundColor(resources.getColor(R.color.colorPrimary, null));
else
_imageGallerySelectedContainer.setBackgroundColor(resources.getColor(R.color.transparent, null));
if(_selected != null) if(_selected != null)
_buttonSelect.alpha = 1f; _buttonSelect.alpha = 1f;
else else
_buttonSelect.alpha = 0.5f; _buttonSelect.alpha = 0.5f;
} }
fun cleanupLastFile() {
_selectedFile?.let {
val file = File(it);
if(file.exists())
file.delete();
_selectedFile = null;
}
}
fun select(variable: ImageVariable) { fun select(variable: ImageVariable) {
if(_selected?.url != _selectedFile)
cleanupLastFile();
onSelected.emit(variable); onSelected.emit(variable);
onClose.emit(); onClose.emit();
} }

View file

@ -3,9 +3,11 @@ package com.futo.platformplayer.views.subscriptions
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.widget.LinearLayout import android.widget.LinearLayout
import androidx.lifecycle.findViewTreeLifecycleOwner
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.RecyclerView.Recycler
import com.futo.platformplayer.R import com.futo.platformplayer.R
import com.futo.platformplayer.Settings
import com.futo.platformplayer.api.media.models.channels.SerializedChannel import com.futo.platformplayer.api.media.models.channels.SerializedChannel
import com.futo.platformplayer.constructs.Event1 import com.futo.platformplayer.constructs.Event1
import com.futo.platformplayer.models.Subscription import com.futo.platformplayer.models.Subscription
@ -17,35 +19,97 @@ import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny
import com.futo.platformplayer.views.others.ToggleTagView import com.futo.platformplayer.views.others.ToggleTagView
import com.futo.platformplayer.views.adapters.viewholders.SubscriptionBarViewHolder import com.futo.platformplayer.views.adapters.viewholders.SubscriptionBarViewHolder
import com.futo.platformplayer.views.adapters.viewholders.SubscriptionGroupBarViewHolder import com.futo.platformplayer.views.adapters.viewholders.SubscriptionGroupBarViewHolder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
class SubscriptionBar : LinearLayout { class SubscriptionBar : LinearLayout {
private var _adapterView: AnyAdapterView<Subscription, SubscriptionBarViewHolder>? = null; private var _adapterView: AnyAdapterView<Subscription, SubscriptionBarViewHolder>? = null;
private var _subGroups: AnyAdapterView<SubscriptionGroup, SubscriptionGroupBarViewHolder> private var _subGroups: AnyAdapterView<SubscriptionGroup, SubscriptionGroupBarViewHolder>
private val _tagsContainer: LinearLayout; private val _tagsContainer: LinearLayout;
private val _groups: ArrayList<SubscriptionGroup>;
private var _group: SubscriptionGroup? = null;
val onClickChannel = Event1<SerializedChannel>(); val onClickChannel = Event1<SerializedChannel>();
val onClickGroup = Event1<SubscriptionGroup>(); val onToggleGroup = Event1<SubscriptionGroup?>();
val onHoldGroup = Event1<SubscriptionGroup>(); val onHoldGroup = Event1<SubscriptionGroup>();
override fun onAttachedToWindow() {
super.onAttachedToWindow();
StateSubscriptionGroups.instance.onGroupsChanged.subscribe(this) {
findViewTreeLifecycleOwner()?.lifecycleScope?.launch(Dispatchers.Main) {
reloadGroups();
}
}
}
override fun onDetachedFromWindow() {
super.onDetachedFromWindow();
StateSubscriptionGroups.instance.onGroupsChanged.remove(this);
}
constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) {
inflate(context, R.layout.view_subscription_bar, this); inflate(context, R.layout.view_subscription_bar, this);
val subscriptions = StateSubscriptions.instance.getSubscriptions().sortedByDescending { it.playbackSeconds }; val subscriptions = ArrayList(StateSubscriptions.instance.getSubscriptions().sortedByDescending { it.playbackSeconds });
_adapterView = findViewById<RecyclerView>(R.id.recycler_creators).asAny(subscriptions, orientation = RecyclerView.HORIZONTAL) { _adapterView = findViewById<RecyclerView>(R.id.recycler_creators).asAny(subscriptions, orientation = RecyclerView.HORIZONTAL) {
it.onClick.subscribe { c -> it.onClick.subscribe { c ->
onClickChannel.emit(c.channel); onClickChannel.emit(c.channel);
}; };
}; };
val subgroups = StateSubscriptionGroups.instance.getSubscriptionGroups(); _groups = ArrayList(getGroups());
_subGroups = findViewById<RecyclerView>(R.id.recycler_subgroups).asAny(subgroups, orientation = RecyclerView.HORIZONTAL) { _subGroups = findViewById<RecyclerView>(R.id.recycler_subgroups).asAny(_groups, orientation = RecyclerView.HORIZONTAL) {
it.onClick.subscribe(onClickGroup::emit); it.onClick.subscribe(::groupClicked);
it.onClickLong.subscribe(onHoldGroup::emit); it.onClickLong.subscribe { g ->
onHoldGroup.emit(g);
}
} }
_tagsContainer = findViewById(R.id.container_tags); _tagsContainer = findViewById(R.id.container_tags);
} }
private fun groupClicked(g: SubscriptionGroup) {
if(g is SubscriptionGroup.Add) {
onToggleGroup.emit(g);
return;
}
val isSame = _group == g;
_group?.let {
if (it is SubscriptionGroup.Selectable) {
it.selected = false;
val index = _groups.indexOf(it);
if (index >= 0)
_subGroups.notifyContentChanged(index);
}
}
if(isSame) {
_group = null;
onToggleGroup.emit(null);
}
else {
_group = g;
if(g is SubscriptionGroup.Selectable)
g.selected = true;
_subGroups.notifyContentChanged(_groups.indexOf(g));
onToggleGroup.emit(g);
}
}
private fun reloadGroups() {
val results = getGroups();
_groups.clear();
_groups.addAll(results);
_subGroups.notifyContentChanged();
}
private fun getGroups(): List<SubscriptionGroup> {
return if(Settings.instance.subscriptions.showSubscriptionGroups)
(StateSubscriptionGroups.instance.getSubscriptionGroups()
.sortedBy { it.priority }
.map { SubscriptionGroup.Selectable(it, it.id == _group?.id) } +
listOf(SubscriptionGroup.Add()));
else listOf();
}
fun setToggles(vararg buttons: Toggle) { fun setToggles(vararg buttons: Toggle) {
_tagsContainer.removeAllViews(); _tagsContainer.removeAllViews();

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android">
<solid android:color="@color/colorPrimary" />
<corners android:radius="1dp" />
<padding android:left="0dp" android:top="0dp" android:right="0dp" android:bottom="0dp" />
</shape>

View file

@ -26,19 +26,37 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="#AA000000"> android:background="#AA000000">
<ImageButton <LinearLayout
android:id="@+id/button_settings" android:layout_width="wrap_content"
android:layout_width="50dp" android:layout_height="wrap_content"
android:layout_height="50dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:src="@drawable/ic_settings"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent" app:layout_constraintRight_toRightOf="parent"
android:padding="10dp" android:orientation="horizontal">
android:background="@color/transparent" <ImageButton
android:visibility="invisible" android:id="@+id/button_delete"
android:scaleType="fitCenter" /> android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="0dp"
android:src="@drawable/ic_trash"
app:tint="#CC0000"
android:padding="10dp"
android:background="@color/transparent"
android:visibility="visible"
android:scaleType="fitCenter" />
<ImageButton
android:id="@+id/button_settings"
android:layout_width="50dp"
android:layout_height="50dp"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:src="@drawable/ic_settings"
android:padding="10dp"
android:background="@color/transparent"
android:visibility="visible"
android:scaleType="fitCenter" />
</LinearLayout>
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image_group" android:id="@+id/image_group"

View file

@ -0,0 +1,21 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.coordinatorlayout.widget.CoordinatorLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</androidx.recyclerview.widget.RecyclerView>
<FrameLayout
android:id="@+id/overlay"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View file

@ -0,0 +1,99 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="70dp"
android:orientation="vertical"
android:gravity="center_horizontal"
android:padding="2dp"
android:layout_margin="2dp"
android:clickable="true"
android:id="@+id/root">
<ImageView
android:id="@+id/thumb"
android:layout_width="50dp"
android:layout_height="match_parent"
android:padding="12dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:src="@drawable/ic_dragdrop_white"
android:background="@color/transparent"
/>
<com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image"
android:layout_width="75dp"
android:layout_height="50dp"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toRightOf="@id/thumb"
android:layout_marginLeft="10dp"
android:scaleType="centerCrop"
android:src="@drawable/xp_book" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="match_parent"
app:layout_constraintLeft_toRightOf="@id/image"
app:layout_constraintRight_toLeftOf="@id/buttons"
android:layout_marginLeft="10dp"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/text_sub_group"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:maxLines="2"
android:ellipsize="end"
android:textSize="12dp"
android:textColor="@color/white"
android:textAlignment="textStart"
android:text="News" />
<TextView
android:id="@+id/text_sub_group_meta"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:maxLines="2"
android:ellipsize="end"
android:textSize="10dp"
android:textColor="@color/gray_ac"
android:textAlignment="textStart"
android:text="News" />
</LinearLayout>
<LinearLayout
android:id="@+id/buttons"
android:layout_width="wrap_content"
android:layout_height="match_parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:orientation="horizontal">
<ImageButton
android:id="@+id/button_trash"
android:layout_width="50dp"
android:layout_height="match_parent"
android:layout_marginRight="10dp"
android:padding="10dp"
android:scaleType="fitCenter"
android:background="@color/transparent"
app:tint="@color/pastel_red"
android:src="@drawable/ic_trash"
/>
<ImageButton
android:id="@+id/button_settings"
android:layout_width="50dp"
android:layout_height="match_parent"
android:padding="10dp"
android:scaleType="fitCenter"
android:background="@color/transparent"
android:src="@drawable/ic_settings"
android:visibility="gone"
/>
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -26,8 +26,24 @@
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical"
android:gravity="center_horizontal">
<LinearLayout
android:id="@+id/gallery_selected_container"
android:layout_width="150dp"
android:layout_height="100dp"
android:gravity="center_horizontal"
android:padding="2dp"
android:layout_marginTop="10dp"
android:layout_marginLeft="10dp"
android:layout_marginRight="10dp">
<ImageView
android:id="@+id/gallery_selected"
android:layout_width="150dp"
android:layout_height="100dp"
android:scaleType="centerCrop" />
</LinearLayout>
<com.futo.platformplayer.views.buttons.BigButton <com.futo.platformplayer.views.buttons.BigButton
android:id="@+id/button_gallery" android:id="@+id/button_gallery"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -41,6 +57,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@ -71,6 +88,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginTop="10dp" android:layout_marginTop="10dp"
android:layout_marginLeft="10dp" android:layout_marginLeft="10dp"
android:layout_marginRight="10dp"
android:layout_marginBottom="10dp" android:layout_marginBottom="10dp"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView

View file

@ -1,17 +1,19 @@
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="75dp" android:layout_width="78dp"
android:layout_height="50dp" android:layout_height="54dp"
android:orientation="vertical" android:orientation="vertical"
android:gravity="center_horizontal" android:gravity="center_horizontal"
android:layout_margin="4dp" android:padding="2dp"
android:layout_margin="2dp"
android:clickable="true" android:clickable="true"
android:id="@+id/root"> android:id="@+id/root">
<com.google.android.material.imageview.ShapeableImageView <com.google.android.material.imageview.ShapeableImageView
android:id="@+id/image" android:id="@+id/image"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:scaleType="centerCrop"
android:src="@drawable/xp_book" /> android:src="@drawable/xp_book" />
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"

View file

@ -346,6 +346,9 @@
<string name="live_chat_webview">Live Chat Webview</string> <string name="live_chat_webview">Live Chat Webview</string>
<string name="background_switch_audio">Switch to Audio in Background</string> <string name="background_switch_audio">Switch to Audio in Background</string>
<string name="background_switch_audio_description">Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter</string> <string name="background_switch_audio_description">Optimize bandwidth usage by switching to audio-only stream in background if available, may cause stutter</string>
<string name="subscription_group_menu">Groups</string>
<string name="show_subscription_group">Show Subscription Groups</string>
<string name="show_subscription_group_description">If subscription groups should be shown above your subscriptions to filter</string>
<string name="preview_feed_items">Preview Feed Items</string> <string name="preview_feed_items">Preview Feed Items</string>
<string name="preview_feed_items_description">When the preview feedstyle is used, if items should auto-preview when scrolling over them</string> <string name="preview_feed_items_description">When the preview feedstyle is used, if items should auto-preview when scrolling over them</string>
<string name="log_level">Log Level</string> <string name="log_level">Log Level</string>