From d5a696289b4bf865e4e7016636bc3b518451405c Mon Sep 17 00:00:00 2001 From: Kelvin Date: Tue, 11 Nov 2025 01:13:35 +0100 Subject: [PATCH] Even more library work --- .../com/futo/platformplayer/Extensions_V8.kt | 12 + .../platformplayer/activities/MainActivity.kt | 6 +- .../fragment/mainactivity/main/FeedView.kt | 21 +- .../mainactivity/main/LibraryAlbumFragment.kt | 10 + .../main/LibraryAlbumsFragment.kt | 6 + .../main/LibraryArtistFragment.kt | 53 ++++- .../main/LibraryArtistsFragment.kt | 1 + .../mainactivity/main/LibraryFilesFragment.kt | 123 +++++----- .../mainactivity/main/LibraryFragment.kt | 14 +- .../main/LibrarySearchFragment.kt | 210 ++++++++++++++++++ .../main/LibraryVideosFragment.kt | 1 + .../topbar/FilesTopBarFragment.kt | 129 +++++++++++ .../futo/platformplayer/states/StateApp.kt | 61 +++-- .../platformplayer/states/StateLibrary.kt | 73 ++++-- .../com/futo/platformplayer/views/PillV2.kt | 22 ++ .../adapters/viewholders/FileViewHolder.kt | 62 ++++++ app/src/main/res/drawable/ic_arrow_up.xml | 10 + app/src/main/res/layout/fragment_artist.xml | 190 ++++++++++++++++ app/src/main/res/layout/fragment_feed.xml | 1 + .../res/layout/fragment_files_top_bar.xml | 44 ++++ .../res/layout/fragview_library_search.xml | 72 ++++++ app/src/main/res/layout/list_file.xml | 51 +++-- .../main/res/layout/view_library_section.xml | 2 +- 23 files changed, 1040 insertions(+), 134 deletions(-) create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibrarySearchFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/FilesTopBarFragment.kt create mode 100644 app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/FileViewHolder.kt create mode 100644 app/src/main/res/drawable/ic_arrow_up.xml create mode 100644 app/src/main/res/layout/fragment_artist.xml create mode 100644 app/src/main/res/layout/fragment_files_top_bar.xml create mode 100644 app/src/main/res/layout/fragview_library_search.xml diff --git a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt index 3166613e..1da4b6fe 100644 --- a/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt +++ b/app/src/main/java/com/futo/platformplayer/Extensions_V8.kt @@ -8,6 +8,7 @@ import com.caoccao.javet.values.reference.V8ValueError import com.caoccao.javet.values.reference.V8ValueObject import com.caoccao.javet.values.reference.V8ValuePromise import com.futo.platformplayer.api.media.platforms.js.JSClient +import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.engine.exceptions.ScriptExecutionException @@ -387,4 +388,15 @@ suspend fun Deferred.awaitCancelConverted(): T { } throw ex; } +} + +fun IPager.toList(): List { + val list = this.getResults().toMutableList(); + + while(this.hasMorePages()) { + this.nextPage(); + list.addAll(this.getResults()); + } + + return list.toList(); } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 4e35e758..aed666ea 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -83,6 +83,7 @@ import com.futo.platformplayer.fragment.mainactivity.main.VideoDetailFragment.St import com.futo.platformplayer.fragment.mainactivity.main.WatchLaterFragment import com.futo.platformplayer.fragment.mainactivity.main.WebDetailFragment import com.futo.platformplayer.fragment.mainactivity.topbar.AddTopBarFragment +import com.futo.platformplayer.fragment.mainactivity.topbar.FilesTopBarFragment import com.futo.platformplayer.fragment.mainactivity.topbar.GeneralTopBarFragment import com.futo.platformplayer.fragment.mainactivity.topbar.ImportTopBarFragment import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment @@ -154,6 +155,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { lateinit var _fragTopBarNavigation: NavigationTopBarFragment; lateinit var _fragTopBarImport: ImportTopBarFragment; lateinit var _fragTopBarAdd: AddTopBarFragment; + lateinit var _fragTopBarFiles: FilesTopBarFragment; //Frags BotBar lateinit var _fragBotBarMenu: MenuBottomBarFragment; @@ -332,6 +334,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { _fragTopBarNavigation = NavigationTopBarFragment.newInstance(); _fragTopBarImport = ImportTopBarFragment.newInstance(); _fragTopBarAdd = AddTopBarFragment.newInstance(); + _fragTopBarFiles = FilesTopBarFragment.newInstance(); //BotBars _fragBotBarMenu = MenuBottomBarFragment.newInstance(); @@ -508,7 +511,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { _fragLibraryArtists.topBar = _fragTopBarNavigation; _fragLibraryArtist.topBar = _fragTopBarNavigation; _fragLibraryVideos.topBar = _fragTopBarNavigation; - _fragLibraryFiles.topBar = _fragTopBarNavigation; + _fragLibraryFiles.topBar = _fragTopBarFiles; _fragBrowser.topBar = _fragTopBarNavigation; @@ -1288,6 +1291,7 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { VideoDetailFragment::class -> _fragVideoDetail as T; MenuBottomBarFragment::class -> _fragBotBarMenu as T; GeneralTopBarFragment::class -> _fragTopBarGeneral as T; + FilesTopBarFragment::class -> _fragTopBarFiles as T; SearchTopBarFragment::class -> _fragTopBarSearch as T; CreatorsFragment::class -> _fragMainSubscriptions as T; CommentsFragment::class -> _fragMainComments as T; diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt index a9ea33b2..2a19fc6e 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/FeedView.kt @@ -39,6 +39,7 @@ import java.time.OffsetDateTime import kotlin.math.max abstract class FeedView : LinearLayout where TPager : IPager, TViewHolder : RecyclerView.ViewHolder, TFragment : MainFragment { + protected val _feedRoot: FrameLayout; protected val _recyclerResults: RecyclerView; protected val _overlayContainer: FrameLayout; protected val _swipeRefresh: SwipeRefreshLayout; @@ -67,7 +68,7 @@ abstract class FeedView : L private var _sortByOptions: List? = null; private var _activeTags: List? = null; - private var _nextPageHandler: TaskHandler>; + private var _nextPageHandler: TaskHandler>>; val recyclerData: RecyclerData, GridLayoutManager, TPager, TResult, TConverted, InsertedViewHolder>; val fragment: TFragment; @@ -80,6 +81,7 @@ abstract class FeedView : L this.fragment = fragment; inflater.inflate(R.layout.fragment_feed, this); + _feedRoot = findViewById(R.id.feed_root); _textCentered = findViewById(R.id.text_centered); _emptyPagerContainer = findViewById(R.id.empty_pager_container); _progressBar = findViewById(R.id.progress_bar); @@ -135,23 +137,27 @@ abstract class FeedView : L _toolbarContentView = findViewById(R.id.container_toolbar_content); - _nextPageHandler = TaskHandler>({fragment.lifecycleScope}, { + _nextPageHandler = TaskHandler>>({fragment.lifecycleScope}, { if (it is IAsyncPager<*>) it.nextPageAsync(); else it.nextPage(); processPagerExceptions(it); - return@TaskHandler it.getResults(); + return@TaskHandler Pair(it, it.getResults()); }).success { + val pager = it.first; + val results = it.second + setLoading(false); val posBefore = recyclerData.results.size; - val filteredResults = filterResults(it); + val filteredResults = filterResults(results); recyclerData.results.addAll(filteredResults); - recyclerData.resultsUnfiltered.addAll(it); + recyclerData.resultsUnfiltered.addAll(results); recyclerData.adapter.notifyItemRangeInserted(recyclerData.adapter.childToParentPosition(posBefore), filteredResults.size); - ensureEnoughContentVisible(filteredResults) + if(pager.hasMorePages()) + ensureEnoughContentVisible(filteredResults) }.exception { Logger.w(TAG, "Failed to load next page.", it); UIDialogs.showGeneralRetryErrorDialog(context, context.getString(R.string.failed_to_load_next_page), it, { @@ -390,6 +396,9 @@ abstract class FeedView : L protected fun finishRefreshLayoutLoader() { _swipeRefresh.isRefreshing = false; } + protected fun disableRefreshLayout() { + _swipeRefresh.isEnabled = false; + } fun clearResults(){ setPager(EmptyPager() as TPager); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryAlbumFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryAlbumFragment.kt index 18eea07e..830e3d08 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryAlbumFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryAlbumFragment.kt @@ -5,6 +5,9 @@ import android.os.Bundle import android.util.TypedValue import android.view.LayoutInflater import android.view.ViewGroup +import android.widget.FrameLayout +import android.widget.LinearLayout +import androidx.core.view.updateLayoutParams import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView import com.futo.platformplayer.UISlideOverlays @@ -12,6 +15,8 @@ import com.futo.platformplayer.api.media.models.video.IPlatformVideo import com.futo.platformplayer.api.media.structures.AdhocPager import com.futo.platformplayer.api.media.structures.EmptyPager import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.dp +import com.futo.platformplayer.fragment.mainactivity.topbar.NavigationTopBarFragment import com.futo.platformplayer.states.Album import com.futo.platformplayer.states.StateLibrary import com.futo.platformplayer.states.StatePlayer @@ -76,6 +81,11 @@ class LibraryAlbumFragment : MainFragment() { StatePlayer.instance.setPlaylist(playlist, focus = true, shuffle = true); } } + + /* + _feedRoot.updateLayoutParams { + this.setMargins(0,-50.dp(resources),0,0) + } */ } fun onShown(parameter: Any?) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryAlbumsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryAlbumsFragment.kt index 68ab873c..5d4e282a 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryAlbumsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryAlbumsFragment.kt @@ -95,6 +95,7 @@ class LibraryAlbumsFragment : MainFragment() { } _toolbarContentView.addView(libraryTypeHeader); + disableRefreshLayout(); } fun onShown() { @@ -105,6 +106,11 @@ class LibraryAlbumsFragment : MainFragment() { setPager(AdhocPager({ listOf(); }, initialAlbums)); } + override fun reload() { + super.reload(); + finishRefreshLayoutLoader(); + } + override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList): InsertedViewAdapterWithLoader { return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(), childCountGetter = { dataset.size }, diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryArtistFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryArtistFragment.kt index 4bce7eea..878cc8a5 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryArtistFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryArtistFragment.kt @@ -52,6 +52,7 @@ import com.futo.platformplayer.states.StateSubscriptions import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader import com.futo.platformplayer.views.adapters.viewholders.AlbumTileViewHolder +import com.futo.platformplayer.views.adapters.viewholders.TrackViewHolder import com.futo.platformplayer.views.others.CreatorThumbnail import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay import com.futo.platformplayer.views.subscriptions.SubscribeButton @@ -125,7 +126,7 @@ class LibraryArtistFragment : MainFragment() { private val _onPageChangeCallback = object : ViewPager2.OnPageChangeCallback() {} init { - inflater.inflate(R.layout.fragment_channel, this) + inflater.inflate(R.layout.fragment_artist, this) val tabs: TabLayout = findViewById(R.id.tabs) val viewPager: ViewPager2 = findViewById(R.id.view_pager) @@ -339,7 +340,7 @@ class LibraryArtistFragment : MainFragment() { _buttonSubscriptionSettings.visibility = if (_buttonSubscribe.isSubscribed) View.VISIBLE else View.GONE _textChannel.text = channel.name - _textChannelSub.text = "" + _textChannelSub.text = "${channel.countTracks} songs, ${channel.countAlbums} albums"; var supportsPlaylists = false; val playlistPosition = 1 @@ -474,20 +475,66 @@ class LibraryArtistFragment : MainFragment() { _lastArtist = artist; } } - class ArtistContentView : ContentFeedView { + class ArtistContentView : FeedView, TrackViewHolder> { override val feedStyle: FeedStyle = FeedStyle.THUMBNAIL; //R.layout.list_creator; + protected var _artist: Artist? = null; + constructor(fragment: LibraryArtistFragment, inflater: LayoutInflater) : super(fragment, inflater) { } fun setArtist(artist: Artist) { + this._artist = artist; val tracks = artist.getAudioTracks(); if(tracks.getResults().isEmpty()) UIDialogs.appToast("No tracks found"); setPager(tracks); } + override fun filterResults(results: List): List { + return results.filter { it is IPlatformVideo }.map { it as IPlatformVideo }; + } + + override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList): InsertedViewAdapterWithLoader { + return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(), + childCountGetter = { dataset.size }, + childViewHolderBinder = { viewHolder, position -> viewHolder.bind(dataset[position]); }, + childViewHolderFactory = { viewGroup, _ -> + val holder = TrackViewHolder(viewGroup); + holder.onClick.subscribe { c -> + + val playlist = _artist?.toPlaylist(); + if (playlist != null) { + val index = playlist.videos.indexOf(c); + if (index == -1) + return@subscribe; + + StatePlayer.instance.setPlaylist(playlist, index, true); + } + }; + holder.onOptions.subscribe { + if(it is IPlatformVideo) + UISlideOverlays.showVideoOptionsOverlay(it, _overlayContainer); + } + return@InsertedViewAdapterWithLoader holder; + } + ); + } + + override fun createLayoutManager(recyclerResults: RecyclerView, context: Context): GridLayoutManager { + val glmResults = GridLayoutManager(context, 1) + + _swipeRefresh.layoutParams = (_swipeRefresh.layoutParams as MarginLayoutParams?)?.apply { + rightMargin = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 8.0f, + context.resources.displayMetrics + ).toInt() + } + return glmResults + } + override fun updateSpanCount(){ } diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryArtistsFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryArtistsFragment.kt index 8b465176..198cd012 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryArtistsFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryArtistsFragment.kt @@ -97,6 +97,7 @@ class LibraryArtistsFragment : MainFragment() { } _toolbarContentView.addView(libraryTypeHeader); + disableRefreshLayout(); } fun onShown() { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFilesFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFilesFragment.kt index fba76049..a50c047d 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFilesFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFilesFragment.kt @@ -18,12 +18,14 @@ import com.futo.platformplayer.api.media.structures.AdhocPager import com.futo.platformplayer.api.media.structures.EmptyPager import com.futo.platformplayer.api.media.structures.IPager import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.fragment.mainactivity.topbar.FilesTopBarFragment import com.futo.platformplayer.states.FileEntry import com.futo.platformplayer.states.StateLibrary import com.futo.platformplayer.views.FeedStyle import com.futo.platformplayer.views.NoResultsView import com.futo.platformplayer.views.adapters.AnyAdapter import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import com.futo.platformplayer.views.adapters.viewholders.FileViewHolder import com.futo.platformplayer.views.buttons.BigButton class LibraryFilesFragment : MainFragment() { @@ -46,7 +48,7 @@ class LibraryFilesFragment : MainFragment() { override fun onShownWithView(parameter: Any?, isBack: Boolean) { super.onShownWithView(parameter, isBack); - view?.onShown(); + view?.onShown(parameter); } override fun onDestroyMainView() { @@ -65,30 +67,51 @@ class LibraryFilesFragment : MainFragment() { var buttonUp: BigButton? = null; var buttonAdd: BigButton? = null; + private var root: FileEntry? = null; + constructor(fragment: LibraryFilesFragment, inflater: LayoutInflater) : super(fragment, inflater) { } - fun onShown() { + fun onShown(parameter: Any? = null) { + this.root = if(parameter is FileEntry) parameter else null; loadTop(); } fun loadTop() { - val initialDirectories = StateLibrary.instance.getFileDirectories(); - if(initialDirectories.size == 0) { - setEmptyPager(true); - setPager(EmptyPager()); + var initialDirectories = listOf(); + if(root == null) { + initialDirectories = StateLibrary.instance.getFileDirectories(); + if (initialDirectories.size == 0) { + setEmptyPager(true); + setPager(EmptyPager()); + buttonAdd?.let { + it.isVisible = false; + } + buttonUp?.let { + it.isVisible = false; + } + return; + } else + setEmptyPager(false); + } + else { buttonAdd?.let { it.isVisible = false; } buttonUp?.let { it.isVisible = false; } - return; + initialDirectories = root?.getSubFiles() ?: listOf(); } - else - setEmptyPager(false); navStack.clear(); - navStack.add(FileStack("", initialDirectories)); + val entry = FileStack("", initialDirectories); + navStack.add(entry); openDirectory(navStack.last()); + fragment.topBar?.let { + if(it is FilesTopBarFragment) { + it.setUpNavigate(null); + it.setTitle(entry); + } + } } fun leaveDirectory() { if(navStack.size > 1) { @@ -101,6 +124,12 @@ class LibraryFilesFragment : MainFragment() { if(addToStack) navStack.add(stack); + fragment.topBar?.let { + if(it is FilesTopBarFragment) { + it.setTitle(stack); + } + } + buttonAdd?.let { it.isVisible = navStack.size < 2 } @@ -109,6 +138,21 @@ class LibraryFilesFragment : MainFragment() { } setPager(AdhocPager({ listOf(); }, stack.files)); setLoading(false); + + fragment.topBar?.let { + if(it is FilesTopBarFragment) { + if(navStack.size > 1) + it.setUpNavigate{ + leaveDirectory(); + }; + else it.setUpNavigate(null); + it.setTitle(stack); + } + } + } + + fun setBack() { + fragment.topBar?.view } override fun getEmptyPagerView(): View? { @@ -116,14 +160,15 @@ class LibraryFilesFragment : MainFragment() { "To see files in Grayjay you have to add directories to view", R.drawable.ic_library, listOf( BigButton(context, "Add Directory", "Select a directory to add", R.drawable.ic_add, { - StateLibrary.instance.addFileDirectory { + StateLibrary.instance.addFileDirectory({ loadTop(); - }; + }, true); }) )) } override fun createAdapter(recyclerResults: RecyclerView, context: Context, dataset: ArrayList): InsertedViewAdapterWithLoader { + /* val buttonUp = BigButton(fragment.requireContext(), "Go up", "Go up a directory", R.drawable.ic_move_up) { if(navStack.size > 1) leaveDirectory(); @@ -133,9 +178,10 @@ class LibraryFilesFragment : MainFragment() { loadTop(); }; } - this.buttonUp = buttonUp; - this.buttonAdd = buttonAdd; - return InsertedViewAdapterWithLoader(context, arrayListOf(buttonUp), arrayListOf(buttonAdd), + */ + //this.buttonUp = buttonUp; + //this.buttonAdd = buttonAdd; + return InsertedViewAdapterWithLoader(context, arrayListOf(), arrayListOf(), childCountGetter = { dataset.size }, childViewHolderBinder = { viewHolder, position -> viewHolder.bind(dataset[position]); }, childViewHolderFactory = { viewGroup, _ -> @@ -185,51 +231,4 @@ class LibraryFilesFragment : MainFragment() { val files: List ) - class FileViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( - LayoutInflater.from(_viewGroup.context).inflate(R.layout.list_file, - _viewGroup, false)) { - - val onClick = Event1(); - val onDelete = Event1(); - - protected var _file: FileEntry? = null; - protected val _imageThumbnail: ImageView - protected val _buttonDelete: ImageButton; - protected val _textName: TextView - protected val _textMetadata: TextView - - init { - _imageThumbnail = _view.findViewById(R.id.image_thumbnail); - _textName = _view.findViewById(R.id.text_name); - _textMetadata = _view.findViewById(R.id.text_metadata); - _buttonDelete = _view.findViewById(R.id.button_delete); - - _view.setOnClickListener { onClick.emit(_file) }; - _buttonDelete.setOnClickListener { onDelete.emit(_file) } - } - - - override fun bind(file: FileEntry) { - _file = file; - _imageThumbnail?.let { - if(file.isDirectory) - it.setImageResource(R.drawable.ic_library); - else { - Glide.with(it) - .load(file.thumbnail) - .placeholder(R.drawable.placeholder_channel_thumbnail) - .into(it) - } - }; - _buttonDelete.isVisible = file.removable; - - _textName.text = file.name; - if(file.isDirectory) - _textMetadata.text = "Directory"; - else - _textMetadata.text = ""; - } - - } - } \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFragment.kt index 62d459be..18723f96 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryFragment.kt @@ -6,6 +6,7 @@ import android.os.Bundle import android.provider.MediaStore import android.util.AttributeSet import android.view.LayoutInflater +import android.view.View import android.view.ViewGroup import android.widget.LinearLayout import android.widget.TextView @@ -37,6 +38,7 @@ import com.futo.platformplayer.views.adapters.AnyAdapter import com.futo.platformplayer.views.adapters.InsertedViewAdapter import com.futo.platformplayer.views.adapters.viewholders.AlbumTileViewHolder import com.futo.platformplayer.views.adapters.viewholders.ArtistTileViewHolder +import com.futo.platformplayer.views.adapters.viewholders.FileViewHolder import com.futo.platformplayer.views.adapters.viewholders.LocalVideoTileViewHolder import com.futo.platformplayer.views.buttons.BigButton @@ -146,7 +148,7 @@ class LibraryFragment : MainFragment() { val recycler: RecyclerView; - val adapterFiles: AnyInsertedAdapterView; + val adapterFiles: AnyInsertedAdapterView; //var metaInfo: TextView; @@ -174,9 +176,9 @@ class LibraryFragment : MainFragment() { this.setMargins(0,0, 0, 0); } sectionFiles.setSection("Directories") { - StateLibrary.instance.addFileDirectory { + StateLibrary.instance.addFileDirectory({ reloadFiles(); - } + }, true) } sectionFiles.setNavIcon(R.drawable.ic_add); //buttonFiles = findViewById(R.id.button_files); @@ -231,13 +233,15 @@ class LibraryFragment : MainFragment() { val videos = StateLibrary.instance.getRecentVideos(null, 20); adapterVideos.setData(videos); - adapterFiles = recycler.asAnyWithViews( + adapterFiles = recycler.asAnyWithViews( arrayListOf( sectionArtists, sectionAlbums, sectionVideos, sectionFiles - ), arrayListOf(), RecyclerView.VERTICAL, false, { + ), + arrayListOf(View(context).apply { this.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 20.dp(resources)) }), + RecyclerView.VERTICAL, false, { it.onClick.subscribe { if(it != null) fragment.navigate(it); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibrarySearchFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibrarySearchFragment.kt new file mode 100644 index 00000000..d50c6ce9 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibrarySearchFragment.kt @@ -0,0 +1,210 @@ +package com.futo.platformplayer.fragment.mainactivity.main + +import android.content.Context +import android.os.Bundle +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.AdapterView +import android.widget.ArrayAdapter +import android.widget.EditText +import android.widget.FrameLayout +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.LinearLayout.GONE +import android.widget.LinearLayout.VISIBLE +import android.widget.Spinner +import android.widget.TextView +import androidx.constraintlayout.widget.ConstraintLayout +import androidx.core.widget.addTextChangedListener +import androidx.recyclerview.widget.GridLayoutManager +import androidx.recyclerview.widget.LinearLayoutManager +import androidx.recyclerview.widget.RecyclerView +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.UISlideOverlays +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.contents.IPlatformContent +import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist +import com.futo.platformplayer.api.media.models.video.IPlatformVideo +import com.futo.platformplayer.api.media.structures.AdhocPager +import com.futo.platformplayer.api.media.structures.IPager +import com.futo.platformplayer.constructs.Event0 +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.dp +import com.futo.platformplayer.fragment.mainactivity.topbar.SearchTopBarFragment +import com.futo.platformplayer.images.GlideHelper.Companion.crossfade +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.states.Album +import com.futo.platformplayer.states.Artist +import com.futo.platformplayer.states.ArtistOrdering +import com.futo.platformplayer.states.FileEntry +import com.futo.platformplayer.states.StateLibrary +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringStorage +import com.futo.platformplayer.views.AnyAdapterView +import com.futo.platformplayer.views.AnyAdapterView.Companion.asAny +import com.futo.platformplayer.views.AnyInsertedAdapterView +import com.futo.platformplayer.views.AnyInsertedAdapterView.Companion.asAnyWithViews +import com.futo.platformplayer.views.FeedStyle +import com.futo.platformplayer.views.LibrarySection +import com.futo.platformplayer.views.LibraryTypeHeaderView +import com.futo.platformplayer.views.LibraryTypeHeaderView.SelectedType +import com.futo.platformplayer.views.PillV2 +import com.futo.platformplayer.views.adapters.AnyAdapter +import com.futo.platformplayer.views.adapters.InsertedViewAdapterWithLoader +import com.futo.platformplayer.views.adapters.SubscriptionAdapter +import com.futo.platformplayer.views.adapters.viewholders.AlbumTileViewHolder +import com.futo.platformplayer.views.adapters.viewholders.ArtistTileViewHolder +import com.futo.platformplayer.views.adapters.viewholders.FileViewHolder +import com.futo.platformplayer.views.adapters.viewholders.LocalVideoTileViewHolder +import com.futo.platformplayer.views.adapters.viewholders.SelectablePlaylist +import com.futo.platformplayer.views.adapters.viewholders.TrackViewHolder +import com.futo.platformplayer.views.others.CreatorThumbnail +import com.futo.platformplayer.views.platform.PlatformIndicator + +class LibrarySearchFragment : MainFragment() { + override val isMainView : Boolean = true; + override val isTab: Boolean = true; + override val hasBottomBar: Boolean get() = true; + + + var view: FragView? = null; + + override fun onCreateMainView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { + val view = FragView(this); + this.view = view; + return view; + } + + override fun onShown(parameter: Any?, isBack: Boolean) { + super.onShown(parameter, isBack) + } + + override fun onShownWithView(parameter: Any?, isBack: Boolean) { + super.onShownWithView(parameter, isBack); + view?.onShown(); + } + + override fun onDestroyMainView() { + view = null; + super.onDestroyMainView(); + } + + companion object { + fun newInstance() = LibrarySearchFragment().apply {} + } + + + class FragView: ConstraintLayout { + val fragment: LibrarySearchFragment; + + val pillArtist: PillV2; + val pillAlbums: PillV2; + val pillSongs: PillV2; + val pills: List; + + val recycler: RecyclerView; + + val adapterArtists: AnyAdapterView; + val adapterSongs: AnyAdapterView; + val adapterAlbums: AnyAdapterView; + + + + constructor(fragment: LibrarySearchFragment) : super(fragment.requireContext()) { + inflate(context, R.layout.fragview_library, this); + this.fragment = fragment; + + pillArtist = findViewById(R.id.pill_artist); + pillAlbums = findViewById(R.id.pill_albums); + pillSongs = findViewById(R.id.pill_songs); + pills = listOf(pillArtist, pillAlbums, pillSongs); + + pillArtist.onClick.subscribe { + pills.forEach { it.setIsEnabled(false) }; + pillArtist.setIsEnabled(true); + loadArtists(); + } + pillAlbums.onClick.subscribe { + pills.forEach { it.setIsEnabled(false) }; + pillAlbums.setIsEnabled(true); + loadAlbums(); + } + pillSongs.onClick.subscribe { + pills.forEach { it.setIsEnabled(false) }; + pillSongs.setIsEnabled(true); + loadSongs(); + } + + recycler = findViewById(R.id.recycler); + adapterArtists = recycler.asAny(RecyclerView.VERTICAL, false, { + it.onClick.subscribe { + if(it != null) + fragment.navigate(it); + } + }); + adapterAlbums = recycler.asAny(RecyclerView.VERTICAL, false, { + it.onClick.subscribe { + if(it != null) + fragment.navigate(it); + } + }); + adapterSongs = recycler.asAny(RecyclerView.VERTICAL, false, { + it.onClick.subscribe { + if(it != null && it is IPlatformVideo) + fragment.navigate(it); + } + }); + + fragment.topBar?.let { + if(it is SearchTopBarFragment) { + it.onSearch.subscribe { + search(it); + } + } + } + + pillArtist.setIsEnabled(true); + loadArtists(); + } + + fun loadArtists(){ + recycler.adapter = adapterArtists.adapter.adapter; + } + fun loadAlbums() { + recycler.adapter = adapterAlbums.adapter.adapter; + } + fun loadSongs() { + recycler.adapter = adapterSongs.adapter.adapter; + } + + fun search(str: String) { + if(recycler.adapter == adapterArtists.adapter.adapter) { + if(str.isNullOrBlank()) + adapterArtists.adapter.setData(listOf()); + else + adapterArtists.setData(StateLibrary.instance.searchArtists(str)); + } + else if(recycler.adapter == adapterAlbums.adapter.adapter) { + if(str.isNullOrBlank()) + adapterAlbums.adapter.setData(listOf()); + else + adapterAlbums.setData(StateLibrary.instance.searchAlbums(str)); + } + else if(recycler.adapter == adapterSongs.adapter.adapter) { + if(str.isNullOrBlank()) + adapterSongs.adapter.setData(listOf()); + else + adapterSongs.setData(StateLibrary.instance.searchTracks(str)); + } + } + + + fun onShown() { + + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryVideosFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryVideosFragment.kt index fb251e9a..43ca3700 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryVideosFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/LibraryVideosFragment.kt @@ -90,6 +90,7 @@ class LibraryVideosFragment : MainFragment() { constructor(fragment: LibraryVideosFragment, inflater: LayoutInflater) : super(fragment, inflater) { initializeToolbarContent(); + disableRefreshLayout(); } fun onShown() { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/FilesTopBarFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/FilesTopBarFragment.kt new file mode 100644 index 00000000..49e76e9e --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/topbar/FilesTopBarFragment.kt @@ -0,0 +1,129 @@ +package com.futo.platformplayer.fragment.mainactivity.topbar + +import android.os.Bundle +import android.util.TypedValue +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.LinearLayout +import android.widget.TextView +import androidx.appcompat.widget.AppCompatImageView +import com.futo.platformplayer.R +import com.futo.platformplayer.api.media.IPlatformClient +import com.futo.platformplayer.api.media.models.PlatformAuthorLink +import com.futo.platformplayer.api.media.models.channels.IPlatformChannel +import com.futo.platformplayer.fragment.mainactivity.main.LibraryFilesFragment +import com.futo.platformplayer.models.Playlist +import com.futo.platformplayer.states.FileEntry +import com.futo.platformplayer.views.casting.CastButton +import com.futo.polycentric.core.PolycentricProfile + +class FilesTopBarFragment : TopFragment() { + private var _buttonBack: ImageButton? = null; + private var _buttonCast: CastButton? = null; + private var _textTitle: TextView? = null; + private var _menuItems: LinearLayout? = null; + + private var _upHandle: (()->Unit)? = null; + + override fun onShown(parameter: Any?) { + setTitle(parameter); + setMenuItems(listOf()); + } + override fun onHide() { + + } + + fun setTitle(parameter: Any? = null) { + if(parameter is IPlatformChannel) { + _textTitle?.text = parameter.name; + } else if(parameter is PlatformAuthorLink) { + _textTitle?.text = parameter.name; + } else if (parameter is Playlist) { + _textTitle?.text = parameter.name; + } else if (parameter is String) { + _textTitle?.text = parameter; + } else if (parameter is IPlatformClient) { + _textTitle?.text = parameter.name; + } else if (parameter is PolycentricProfile) { + _textTitle?.text = parameter.systemState.username; + } else if(parameter is FileEntry) { + val treePrefix = "content://com.android.externalstorage.documents/tree/"; + if(parameter.path.startsWith(treePrefix)) { + _textTitle?.text = parameter.path.substring(treePrefix.length - 1).replace("%3A", " ").replace("%2F", "/"); + } + else if(parameter.path.isNullOrBlank()) + _textTitle?.text = parameter.name; + else + _textTitle?.text = parameter.path; + } + else if(parameter is LibraryFilesFragment.FileStack) { + val treePrefix = "content://com.android.externalstorage.documents/tree/"; + if(parameter.path.startsWith(treePrefix)) { + _textTitle?.text = parameter.path.substring(treePrefix.length - 1).replace("%3A", " ").replace("%2F", "/"); + } + else + _textTitle?.text = parameter.path; + } + } + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_files_top_bar, container, false); + + val buttonBack: ImageButton = view.findViewById(R.id.button_back); + _textTitle = view.findViewById(R.id.text_title); + _menuItems = view.findViewById(R.id.menu_buttons) + + buttonBack.setOnClickListener { + if(_upHandle != null) + _upHandle?.invoke(); + else + closeSegment(); + }; + + _buttonBack = buttonBack; + + return view; + } + + fun setUpNavigate(handle: (()->Unit)? = null) { + _upHandle = handle; + _buttonBack?.setImageResource(if(handle == null) R.drawable.ic_back_nav else R.drawable.ic_arrow_up); + } + + override fun onDestroyView() { + super.onDestroyView() + + _buttonBack?.setOnClickListener(null); + _buttonBack = null; + _buttonCast?.cleanup(); + _buttonCast = null; + _textTitle = null; + } + + fun setMenuItems(items: ListUnit>>) { + _menuItems?.removeAllViews(); + + val dp4 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 4f, resources.displayMetrics).toInt(); + val dp9 = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 9f, resources.displayMetrics).toInt(); + + for(item in items) { + val compatImageItem = AppCompatImageView(requireContext()); + compatImageItem.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.MATCH_PARENT); + compatImageItem.setImageResource(item.first); + compatImageItem.setPadding(dp4, dp9, dp4, dp9); + compatImageItem.scaleType = ImageView.ScaleType.FIT_CENTER; + compatImageItem.setOnClickListener { + item.second.invoke(); + }; + + _menuItems?.addView(compatImageItem); + } + } + + companion object { + fun newInstance() = FilesTopBarFragment().apply { } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt index 70ffb4ba..2e8653d8 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateApp.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateApp.kt @@ -279,29 +279,52 @@ class StateApp { }; } } - fun requestDirectoryAccess(activity: IWithResultLauncher, name: String, purpose: String? = null, path: Uri?, handle: (Uri?)->Unit) + fun requestDirectoryAccess(activity: IWithResultLauncher, name: String, purpose: String? = null, path: Uri?, handle: (Uri?)->Unit) { + return requestDirectoryAccess(activity, name, purpose, path, handle, false); + } + fun requestDirectoryAccess(activity: IWithResultLauncher, name: String, purpose: String? = null, path: Uri?, handle: (Uri?)->Unit, skipDialog: Boolean = false) { if(activity is Context) { - UIDialogs.showDialog(activity, R.drawable.ic_security, "Directory required for\n${name}", "Please select a directory for ${name}.\n${purpose}".trim(), null, 0, - UIDialogs.Action("Cancel", {}), - UIDialogs.Action("Ok", { - val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); - if(path != null) - intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, path); - intent.flags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION - .or(Intent.FLAG_GRANT_READ_URI_PERMISSION) - .or(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) - .or(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + if(skipDialog) { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + if(path != null) + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, path); + intent.flags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION + .or(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .or(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + .or(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + + activity.launchForResult(intent, 99) { + if(it.resultCode == Activity.RESULT_OK) { + handle(it.data?.data); + } + else + UIDialogs.showDialogOk(context, R.drawable.ic_security_pred, "No access granted"); + }; + } + else { + UIDialogs.showDialog(activity, R.drawable.ic_security, "Directory required for\n${name}", "Please select a directory for ${name}.\n${purpose}".trim(), null, 0, + UIDialogs.Action("Cancel", {}), + UIDialogs.Action("Ok", { + val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE); + if(path != null) + intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, path); + intent.flags = Intent.FLAG_GRANT_WRITE_URI_PERMISSION + .or(Intent.FLAG_GRANT_READ_URI_PERMISSION) + .or(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) + .or(Intent.FLAG_GRANT_PREFIX_URI_PERMISSION); + + activity.launchForResult(intent, 99) { + if(it.resultCode == Activity.RESULT_OK) { + handle(it.data?.data); + } + else + UIDialogs.showDialogOk(context, R.drawable.ic_security_pred, "No access granted"); + }; + }, UIDialogs.ActionStyle.PRIMARY)); + } - activity.launchForResult(intent, 99) { - if(it.resultCode == Activity.RESULT_OK) { - handle(it.data?.data); - } - else - UIDialogs.showDialogOk(context, R.drawable.ic_security_pred, "No access granted"); - }; - }, UIDialogs.ActionStyle.PRIMARY)); } } diff --git a/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt b/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt index 8e01f6c0..fb02f6ce 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StateLibrary.kt @@ -29,6 +29,7 @@ import com.futo.platformplayer.models.Playlist import com.futo.platformplayer.states.Album.Companion.TAG import com.futo.platformplayer.stores.FragmentedStorage import com.futo.platformplayer.stores.StringArrayStorage +import com.futo.platformplayer.toList import java.io.File import java.time.Instant import java.time.OffsetDateTime @@ -63,30 +64,50 @@ class StateLibrary { _files.remove(path); _files.save(); } - fun addFileDirectory(onAdded: ((entry: FileEntry) -> Unit)? = null): Boolean { + fun addFileDirectory(onAdded: ((entry: FileEntry) -> Unit)? = null, skipDialog: Boolean = false): Boolean { if(!StateApp.instance.isMainActive) return false; val mainActivity = StateApp.instance.contextOrNull as MainActivity? ?: return false; StateApp.instance.requestDirectoryAccess(mainActivity, "Select Directory", - "Select a directory you would like to make accessible to Grayjay", null, { - if(it != null) { - mainActivity.contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_WRITE_URI_PERMISSION.or(Intent.FLAG_GRANT_READ_URI_PERMISSION)); - try { - val file = DocumentFile.fromTreeUri(mainActivity, it) ?: return@requestDirectoryAccess; - val dir = FileEntry.fromFile(file); - _files.add(dir.path); - _files.save(); - onAdded?.invoke(dir); + "Select a directory you would like to make accessible to Grayjay", null, { + if(it != null) { + mainActivity.contentResolver.takePersistableUriPermission(it, Intent.FLAG_GRANT_WRITE_URI_PERMISSION.or(Intent.FLAG_GRANT_READ_URI_PERMISSION)); + try { + val file = DocumentFile.fromTreeUri(mainActivity, it) ?: return@requestDirectoryAccess; + val dir = FileEntry.fromFile(file); + _files.add(dir.path); + _files.save(); + onAdded?.invoke(dir); + } + catch(ex: Throwable) { + Logger.e(TAG, "Something went wrong converting requested directory", ex); + } } - catch(ex: Throwable) { - Logger.e(TAG, "Something went wrong converting requested directory", ex); - } - } - }); + }, skipDialog); return false; } + + fun searchTracks(str: String): List { + val resolver = StateApp.instance.contextOrNull?.contentResolver; + if(resolver == null) { + Logger.w(TAG, "Album contentResolver not found"); + return listOf(); + } + val cursor = resolver?.query( + MediaStore.Audio.Media.EXTERNAL_CONTENT_URI, StateLibrary.PROJECTION_MEDIA, + "LOWER(" + MediaStore.Audio.Media.DISPLAY_NAME + ") LIKE ? ", arrayOf(str.trim().lowercase()), + null) ?: return listOf(); + cursor.moveToFirst(); + val list = mutableListOf() + while(!cursor.isAfterLast) { + list.add(StateLibrary.audioFromCursor(cursor)); + cursor.moveToNext(); + } + return list; + } + fun getAlbums(): List { return Album.getAlbums(); } @@ -96,6 +117,10 @@ class StateLibrary { return getAlbum(idLong); return null; } + fun searchAlbums(str: String): List { + return Album.getAlbums("LOWER(" + MediaStore.Audio.Albums.ALBUM + ") LIKE ? ", arrayOf(str.trim().lowercase())); + } + fun getAlbum(id: Long): Album? { return Album.getAlbum(id); } @@ -109,6 +134,10 @@ class StateLibrary { return getArtist(idLong); return null; } + fun searchArtists(str: String): List { + return Artist.getArtists(ArtistOrdering.TrackCount, "LOWER(" + MediaStore.Audio.Artists.ARTIST + ") LIKE ? ", arrayOf(str.trim().lowercase())); + } + fun getArtist(id: Long): Artist? { return Artist.getArtist(id); } @@ -401,6 +430,10 @@ class Artist { return Album.getArtistAlbums(id.toLongOrNull() ?: return listOf()); } + fun toPlaylist(tracks: List? = null): Playlist { + return Playlist(name, tracks?.map { SerializedPlatformVideo.fromVideo(it) } ?: getAudioTracks().toList().filter { it is IPlatformVideo }.map { SerializedPlatformVideo.fromVideo(it as IPlatformVideo) }) + } + fun getAudioTracks(): IPager { val idLong = id.toLongOrNull() ?: return EmptyPager(); return AdhocPager({ listOf() }, getTracksPager(idLong)); @@ -441,7 +474,7 @@ class Artist { return null; return Artist.fromCursor(cursor); } - fun getArtists(ordering: ArtistOrdering = ArtistOrdering.Alphabethic): List { + fun getArtists(ordering: ArtistOrdering = ArtistOrdering.Alphabethic, query: String? = null, args: Array? = null): List { val ordering = when(ordering) { ArtistOrdering.Alphabethic -> Artists.ARTIST + " ASC"; ArtistOrdering.AlbumCount -> Artists.NUMBER_OF_ALBUMS + " DESC"; @@ -450,8 +483,8 @@ class Artist { } val cursor = StateApp.instance.contextOrNull?.contentResolver?.query(Artists.EXTERNAL_CONTENT_URI, PROJECTION, - null, - null, + query, + args, ordering) ?: return listOf(); cursor.moveToFirst(); val list = mutableListOf() @@ -557,14 +590,14 @@ class Album { return null; return fromCursor(cursor); } - fun getAlbums(): List { + fun getAlbums(query: String? = null, args: Array? = null): List { val resolver = StateApp.instance.contextOrNull?.contentResolver; if(resolver == null) { Logger.w(TAG, "Album contentResolver not found"); return listOf(); } val cursor = resolver?.query( - MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, PROJECTION, null, null, + MediaStore.Audio.Albums.EXTERNAL_CONTENT_URI, PROJECTION, query, args, MediaStore.Audio.Albums.ALBUM + " ASC") ?: return listOf(); cursor.moveToFirst(); val list = mutableListOf() diff --git a/app/src/main/java/com/futo/platformplayer/views/PillV2.kt b/app/src/main/java/com/futo/platformplayer/views/PillV2.kt index abea7817..ab55ba15 100644 --- a/app/src/main/java/com/futo/platformplayer/views/PillV2.kt +++ b/app/src/main/java/com/futo/platformplayer/views/PillV2.kt @@ -7,6 +7,8 @@ import android.widget.TextView import androidx.constraintlayout.widget.ConstraintLayout import com.futo.platformplayer.R import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.models.ImageVariable +import com.futo.platformplayer.views.others.ToggleTagView class PillV2: FrameLayout { @@ -17,6 +19,26 @@ class PillV2: FrameLayout { val onClick = Event1(); + constructor(context: Context, name: String, isActive: Boolean = false, action: (PillV2, Boolean)->Unit, actionLong: ((PillV2, Boolean)->Unit)? = null): super(context) { + inflate(context, R.layout.view_tag_v2, this); + root = findViewById(R.id.root); + text = findViewById(R.id.text_tag); + + text.text = name; + setIsEnabled(isActive); + + setOnClickListener { + setIsEnabled(!isToggled); + onClick.emit(isToggled); + action(this, isToggled); + } + if(actionLong != null) + setOnLongClickListener { + actionLong(this, this.isToggled); + return@setOnLongClickListener true; + } + } + constructor(context: Context, attr: AttributeSet? = null) : super(context, attr) { inflate(context, R.layout.view_tag_v2, this); root = findViewById(R.id.root); diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/FileViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/FileViewHolder.kt new file mode 100644 index 00000000..04b94d0c --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/viewholders/FileViewHolder.kt @@ -0,0 +1,62 @@ +package com.futo.platformplayer.views.adapters.viewholders + +import android.view.LayoutInflater +import android.view.ViewGroup +import android.widget.ImageButton +import android.widget.ImageView +import android.widget.TextView +import androidx.core.view.isVisible +import com.bumptech.glide.Glide +import com.futo.platformplayer.R +import com.futo.platformplayer.constructs.Event1 +import com.futo.platformplayer.states.FileEntry +import com.futo.platformplayer.views.adapters.AnyAdapter + + +class FileViewHolder(private val _viewGroup: ViewGroup) : AnyAdapter.AnyViewHolder( + LayoutInflater.from(_viewGroup.context).inflate( + R.layout.list_file, + _viewGroup, false)) { + + val onClick = Event1(); + val onDelete = Event1(); + + protected var _file: FileEntry? = null; + protected val _imageThumbnail: ImageView + protected val _buttonDelete: ImageButton; + protected val _textName: TextView + //protected val _textMetadata: TextView + + init { + _imageThumbnail = _view.findViewById(R.id.image_thumbnail); + _textName = _view.findViewById(R.id.text_name); + //_textMetadata = _view.findViewById(R.id.text_metadata); + _buttonDelete = _view.findViewById(R.id.button_delete); + + _view.setOnClickListener { onClick.emit(_file) }; + _buttonDelete.setOnClickListener { onDelete.emit(_file) } + } + + + override fun bind(file: FileEntry) { + _file = file; + _imageThumbnail?.let { + if(file.isDirectory) + it.setImageResource(R.drawable.ic_library); + else { + Glide.with(it) + .load(file.thumbnail) + .placeholder(R.drawable.ic_music) + .into(it) + } + }; + _buttonDelete.isVisible = file.removable; + + _textName.text = file.name; + //if(file.isDirectory) + // _textMetadata.text = "Directory"; + //else + // _textMetadata.text = ""; + } + +} \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_arrow_up.xml b/app/src/main/res/drawable/ic_arrow_up.xml new file mode 100644 index 00000000..f2195ec2 --- /dev/null +++ b/app/src/main/res/drawable/ic_arrow_up.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/fragment_artist.xml b/app/src/main/res/layout/fragment_artist.xml new file mode 100644 index 00000000..8110f62e --- /dev/null +++ b/app/src/main/res/layout/fragment_artist.xml @@ -0,0 +1,190 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_feed.xml b/app/src/main/res/layout/fragment_feed.xml index f1eeed7e..d71385b3 100644 --- a/app/src/main/res/layout/fragment_feed.xml +++ b/app/src/main/res/layout/fragment_feed.xml @@ -1,5 +1,6 @@ diff --git a/app/src/main/res/layout/fragment_files_top_bar.xml b/app/src/main/res/layout/fragment_files_top_bar.xml new file mode 100644 index 00000000..6fb516bf --- /dev/null +++ b/app/src/main/res/layout/fragment_files_top_bar.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragview_library_search.xml b/app/src/main/res/layout/fragview_library_search.xml new file mode 100644 index 00000000..c3eedc02 --- /dev/null +++ b/app/src/main/res/layout/fragview_library_search.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/list_file.xml b/app/src/main/res/layout/list_file.xml index f3e363f1..2107fa1d 100644 --- a/app/src/main/res/layout/list_file.xml +++ b/app/src/main/res/layout/list_file.xml @@ -4,41 +4,58 @@ xmlns:tools="http://schemas.android.com/tools" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" - android:layout_height="wrap_content" + android:layout_height="68dp" android:orientation="vertical" android:layout_marginTop="5dp" android:layout_marginBottom="5dp" + android:layout_marginLeft="10dp" + android:layout_marginRight="10dp" + android:background="@drawable/background_16_round_4dp" android:id="@+id/root" android:clickable="true"> - + app:layout_constraintLeft_toLeftOf="parent" + android:background="@drawable/background_1b_round_6dp"> + + + + app:layout_constraintRight_toLeftOf="@id/button_delete" + app:layout_constraintBottom_toBottomOf="parent" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp"/> +