Skip to content
GitLab
Menu
Projects
Groups
Snippets
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in / Register
Toggle navigation
Menu
Open sidebar
Geoffrey Métais
VLC-Android
Commits
1a114901
Commit
1a114901
authored
May 05, 2020
by
Geoffrey Métais
Committed by
Geoffrey Métais
May 14, 2020
Browse files
TV: group files & directories
parent
821a1b04
Pipeline
#17380
passed with stage
in 4 minutes and 8 seconds
Changes
5
Pipelines
1
Hide whitespace changes
Inline
Side-by-side
application/television/src/main/java/org/videolan/television/ui/browser/FileBrowserTvFragment.kt
View file @
1a114901
...
...
@@ -94,7 +94,8 @@ class FileBrowserTvFragment : BaseBrowserTvFragment<MediaLibraryItem>(), PathAda
override
fun
onViewCreated
(
view
:
View
,
savedInstanceState
:
Bundle
?)
{
super
.
onViewCreated
(
view
,
savedInstanceState
)
(
viewModel
.
provider
as
BrowserProvider
).
dataset
.
observe
(
viewLifecycleOwner
,
Observer
{
items
->
(
viewModel
as
BrowserModel
).
dataset
.
observe
(
viewLifecycleOwner
,
Observer
{
items
->
if
(
items
==
null
)
return
@Observer
val
lm
=
binding
.
list
.
layoutManager
as
LinearLayoutManager
val
selectedItem
=
lm
.
focusedChild
submitList
(
items
)
...
...
application/vlc-android/src/org/videolan/vlc/providers/BrowserProvider.kt
View file @
1a114901
...
...
@@ -48,15 +48,12 @@ import org.videolan.tools.DependencyProvider
import
org.videolan.tools.Settings
import
org.videolan.tools.livedata.LiveDataset
import
org.videolan.vlc.R
import
org.videolan.vlc.util.ModelsHelper
import
org.videolan.vlc.util.isBrowserMedia
import
org.videolan.vlc.util.isMedia
import
org.videolan.vlc.util.*
const
val
TAG
=
"VLC/BrowserProvider"
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
abstract
class
BrowserProvider
(
val
context
:
Context
,
val
dataset
:
LiveDataset
<
MediaLibraryItem
>,
val
url
:
String
?,
private
var
showHiddenFiles
:
Boolean
)
:
CoroutineScope
,
HeaderProvider
()
{
override
val
coroutineContext
=
Dispatchers
.
Main
.
immediate
+
SupervisorJob
()
...
...
@@ -73,6 +70,13 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
val
descriptionUpdate
=
MutableLiveData
<
Pair
<
Int
,
String
>>()
internal
val
medialibrary
=
Medialibrary
.
getInstance
()
var
desc
:
Boolean
?
=
null
private
val
comparator
:
Comparator
<
MediaLibraryItem
>?
get
()
=
when
(
desc
)
{
true
->
tvDescComp
false
->
tvAscComp
else
->
null
}
init
{
registerCreator
{
CoroutineContextProvider
()
}
...
...
@@ -144,6 +148,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
discoveryJob
=
launch
(
coroutineContextProvider
.
Main
)
{
filesFlow
(
url
).
collect
{
findMedia
(
it
)
?.
let
{
item
->
addMedia
(
item
)
}
}
}
}
else
{
val
files
=
filesFlow
(
url
).
mapNotNull
{
findMedia
(
it
)
}.
onEach
{
addMedia
(
it
)
}.
toList
()
comparator
?.
let
{
files
.
apply
{
(
this
as
MutableList
).
sortWith
(
it
)
}
}
computeHeaders
(
files
)
parseSubDirectories
(
files
)
}
...
...
@@ -180,7 +185,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
loading
.
postValue
(
false
)
}
private
suspend
fun
filesFlow
(
url
:
String
?
=
this
.
url
,
interact
:
Boolean
=
true
)
=
channelFlow
{
private
suspend
fun
filesFlow
(
url
:
String
?
=
this
.
url
,
interact
:
Boolean
=
true
)
=
channelFlow
<
IMedia
>
{
val
listener
=
object
:
EventListener
{
override
fun
onMediaAdded
(
index
:
Int
,
media
:
IMedia
)
{
if
(!
isClosedForSend
)
offer
(
media
.
apply
{
retain
()
})
...
...
@@ -196,7 +201,9 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
awaitClose
{
if
(
url
!=
null
)
mediabrowser
?.
changeEventListener
(
null
)
}
}.
buffer
(
Channel
.
UNLIMITED
)
open
fun
addMedia
(
media
:
MediaLibraryItem
)
=
dataset
.
add
(
media
)
open
fun
addMedia
(
media
:
MediaLibraryItem
)
{
comparator
?.
let
{
dataset
.
add
(
media
,
it
)
}
?:
dataset
.
add
(
media
)
}
open
fun
refresh
()
{
if
(
url
===
null
)
return
...
...
@@ -238,14 +245,14 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
if
(!
isActive
)
break
@loop
//skip media that are not browsable
val
item
=
currentMediaList
[
currentParsedPosition
]
val
current
=
when
{
item
.
itemType
==
MediaLibraryItem
.
TYPE_MEDIA
->
{
val
current
=
when
(
item
.
itemType
)
{
MediaLibraryItem
.
TYPE_MEDIA
->
{
val
mw
=
item
as
MediaWrapper
if
(
mw
.
type
!=
MediaWrapper
.
TYPE_DIR
&&
mw
.
type
!=
MediaWrapper
.
TYPE_PLAYLIST
)
continue
@loop
if
(
mw
.
uri
.
scheme
==
"otg"
||
mw
.
uri
.
scheme
==
"content"
)
continue
@loop
mw
}
item
.
itemType
==
MediaLibraryItem
.
TYPE_STORAGE
->
MediaLibraryItem
.
TYPE_STORAGE
->
MLServiceLocator
.
getAbstractMediaWrapper
((
item
as
Storage
).
uri
).
apply
{
type
=
MediaWrapper
.
TYPE_DIR
}
else
->
continue
@loop
}
...
...
@@ -267,6 +274,7 @@ abstract class BrowserProvider(val context: Context, val dataset: LiveDataset<Me
descriptionUpdate
.
value
=
Pair
(
position
,
it
)
}
directories
.
addAll
(
files
)
comparator
?.
let
{
directories
.
sortWith
(
it
)
}
withContext
(
coroutineContextProvider
.
Main
)
{
foldersContentMap
.
put
(
item
,
directories
.
toMutableList
())
}
}
directories
.
clear
()
...
...
application/vlc-android/src/org/videolan/vlc/util/Kextensions.kt
View file @
1a114901
...
...
@@ -25,6 +25,7 @@ import com.google.android.material.snackbar.Snackbar
import
kotlinx.coroutines.*
import
kotlinx.coroutines.flow.Flow
import
kotlinx.coroutines.flow.collect
import
kotlinx.coroutines.flow.toCollection
import
org.videolan.libvlc.Media
import
org.videolan.libvlc.interfaces.IMedia
import
org.videolan.libvlc.util.AndroidUtil
...
...
@@ -214,4 +215,4 @@ val View.scope : CoroutineScope
fun
<
T
>
Flow
<
T
>.
launchWhenStarted
(
scope
:
LifecycleCoroutineScope
):
Job
=
scope
.
launchWhenStarted
{
collect
()
// tail-call
}
\ No newline at end of file
}
application/vlc-android/src/org/videolan/vlc/util/ModelsHelper.kt
View file @
1a114901
...
...
@@ -236,4 +236,50 @@ interface SortModule {
NbMedia
->
canSortByMediaNumber
()
else
->
false
}
}
val
ascComp
by
lazy
{
Comparator
<
MediaLibraryItem
>
{
item1
,
item2
->
if
(
item1
?.
itemType
==
MediaLibraryItem
.
TYPE_MEDIA
)
{
val
type1
=
(
item1
as
MediaWrapper
).
type
val
type2
=
(
item2
as
MediaWrapper
).
type
if
(
type1
==
MediaWrapper
.
TYPE_DIR
&&
type2
!=
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
-
1
else
if
(
type1
!=
MediaWrapper
.
TYPE_DIR
&&
type2
==
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
1
}
item1
?.
title
?.
toLowerCase
()
?.
compareTo
(
item2
?.
title
?.
toLowerCase
()
?:
""
)
?:
-
1
}
}
val
descComp
by
lazy
{
Comparator
<
MediaLibraryItem
>
{
item1
,
item2
->
if
(
item1
?.
itemType
==
MediaLibraryItem
.
TYPE_MEDIA
)
{
val
type1
=
(
item1
as
MediaWrapper
).
type
val
type2
=
(
item2
as
MediaWrapper
).
type
if
(
type1
==
MediaWrapper
.
TYPE_DIR
&&
type2
!=
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
-
1
else
if
(
type1
!=
MediaWrapper
.
TYPE_DIR
&&
type2
==
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
1
}
item2
?.
title
?.
toLowerCase
()
?.
compareTo
(
item1
?.
title
?.
toLowerCase
()
?:
""
)
?:
-
1
}
}
val
tvAscComp
by
lazy
{
Comparator
<
MediaLibraryItem
>
{
item1
,
item2
->
if
(
item1
?.
title
?.
get
(
0
)
?.
toLowerCase
()
==
item2
?.
title
?.
get
(
0
)
?.
toLowerCase
())
{
val
type1
=
(
item1
as
MediaWrapper
).
type
val
type2
=
(
item2
as
MediaWrapper
).
type
if
(
type1
==
MediaWrapper
.
TYPE_DIR
&&
type2
!=
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
-
1
else
if
(
type1
!=
MediaWrapper
.
TYPE_DIR
&&
type2
==
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
1
}
item1
?.
title
?.
toLowerCase
()
?.
compareTo
(
item2
?.
title
?.
toLowerCase
()
?:
""
)
?:
-
1
}
}
val
tvDescComp
by
lazy
{
Comparator
<
MediaLibraryItem
>
{
item1
,
item2
->
if
(
item1
?.
title
?.
get
(
0
)
?.
toLowerCase
()
==
item2
?.
title
?.
get
(
0
)
?.
toLowerCase
())
{
val
type1
=
(
item1
as
MediaWrapper
).
type
val
type2
=
(
item2
as
MediaWrapper
).
type
if
(
type1
==
MediaWrapper
.
TYPE_DIR
&&
type2
!=
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
-
1
else
if
(
type1
!=
MediaWrapper
.
TYPE_DIR
&&
type2
==
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
1
}
item2
?.
title
?.
toLowerCase
()
?.
compareTo
(
item1
?.
title
?.
toLowerCase
()
?:
""
)
?:
-
1
}
}
\ No newline at end of file
application/vlc-android/src/org/videolan/vlc/viewmodels/browser/BrowserModel.kt
View file @
1a114901
...
...
@@ -25,6 +25,7 @@ import androidx.annotation.MainThread
import
androidx.fragment.app.Fragment
import
androidx.lifecycle.ViewModel
import
androidx.lifecycle.ViewModelProvider
import
androidx.lifecycle.map
import
androidx.lifecycle.viewModelScope
import
kotlinx.coroutines.ExperimentalCoroutinesApi
import
kotlinx.coroutines.ObsoleteCoroutinesApi
...
...
@@ -33,8 +34,10 @@ import kotlinx.coroutines.withContext
import
org.videolan.medialibrary.interfaces.media.MediaWrapper
import
org.videolan.medialibrary.media.MediaLibraryItem
import
org.videolan.tools.CoroutineContextProvider
import
org.videolan.tools.Settings
import
org.videolan.vlc.providers.*
import
org.videolan.vlc.repository.DirectoryRepository
import
org.videolan.vlc.util.*
import
org.videolan.vlc.viewmodels.BaseModel
import
org.videolan.vlc.viewmodels.tv.TvBrowserModel
...
...
@@ -45,15 +48,28 @@ const val TYPE_STORAGE = 3L
@ObsoleteCoroutinesApi
@ExperimentalCoroutinesApi
open
class
BrowserModel
(
context
:
Context
,
val
url
:
String
?,
val
type
:
Long
,
showHiddenFiles
:
Boolean
,
private
val
showDummyCategory
:
Boolean
,
coroutineContextProvider
:
CoroutineContextProvider
=
CoroutineContextProvider
())
:
BaseModel
<
MediaLibraryItem
>(
context
,
coroutineContextProvider
),
TvBrowserModel
<
MediaLibraryItem
>,
IPathOperationDelegate
by
PathOperationDelegate
()
{
open
class
BrowserModel
(
context
:
Context
,
val
url
:
String
?,
val
type
:
Long
,
showHiddenFiles
:
Boolean
,
private
val
showDummyCategory
:
Boolean
,
coroutineContextProvider
:
CoroutineContextProvider
=
CoroutineContextProvider
()
)
:
BaseModel
<
MediaLibraryItem
>(
context
,
coroutineContextProvider
),
TvBrowserModel
<
MediaLibraryItem
>,
IPathOperationDelegate
by
PathOperationDelegate
()
{
override
var
currentItem
:
MediaLibraryItem
?
=
null
override
var
nbColumns
:
Int
=
0
private
val
tv
=
Settings
.
showTvUi
override
val
provider
:
BrowserProvider
=
when
(
type
)
{
TYPE_PICKER
->
FilePickerProvider
(
context
,
dataset
,
url
)
TYPE_NETWORK
->
NetworkProvider
(
context
,
dataset
,
url
,
showHiddenFiles
)
TYPE_PICKER
->
FilePickerProvider
(
context
,
dataset
,
url
)
.
also
{
if
(
tv
)
it
.
desc
=
desc
}
TYPE_NETWORK
->
NetworkProvider
(
context
,
dataset
,
url
,
showHiddenFiles
)
.
also
{
if
(
tv
)
it
.
desc
=
desc
}
TYPE_STORAGE
->
StorageProvider
(
context
,
dataset
,
url
,
showHiddenFiles
)
else
->
FileBrowserProvider
(
context
,
dataset
,
url
,
showHiddenFiles
=
showHiddenFiles
,
showDummyCategory
=
showDummyCategory
)
else
->
FileBrowserProvider
(
context
,
dataset
,
url
,
showHiddenFiles
=
showHiddenFiles
,
showDummyCategory
=
showDummyCategory
)
.
also
{
if
(
tv
)
it
.
desc
=
desc
}
}
override
val
loading
=
provider
.
loading
...
...
@@ -67,7 +83,13 @@ open class BrowserModel(context: Context, val url: String?, val type: Long, show
viewModelScope
.
launch
{
this
@BrowserModel
.
sort
=
sort
desc
=
!
desc
dataset
.
value
=
withContext
(
coroutineContextProvider
.
Default
)
{
dataset
.
value
.
apply
{
sortWith
(
if
(
desc
)
descComp
else
ascComp
)
}.
also
{
provider
.
computeHeaders
(
dataset
.
value
)
}
}
if
(
tv
)
provider
.
desc
=
desc
val
comp
=
if
(
tv
)
{
if
(
desc
)
tvDescComp
else
tvAscComp
}
else
{
if
(
desc
)
descComp
else
ascComp
}
dataset
.
value
=
withContext
(
coroutineContextProvider
.
Default
)
{
dataset
.
value
.
apply
{
sortWith
(
comp
)
}.
also
{
provider
.
computeHeaders
(
dataset
.
value
)
}
}
}
}
...
...
@@ -108,29 +130,6 @@ open class BrowserModel(context: Context, val url: String?, val type: Long, show
override
fun
canSortByFileNameName
():
Boolean
=
true
}
private
val
ascComp
by
lazy
{
Comparator
<
MediaLibraryItem
>
{
item1
,
item2
->
if
(
item1
?.
itemType
==
MediaLibraryItem
.
TYPE_MEDIA
)
{
val
type1
=
(
item1
as
MediaWrapper
).
type
val
type2
=
(
item2
as
MediaWrapper
).
type
if
(
type1
==
MediaWrapper
.
TYPE_DIR
&&
type2
!=
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
-
1
else
if
(
type1
!=
MediaWrapper
.
TYPE_DIR
&&
type2
==
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
1
}
item1
?.
title
?.
toLowerCase
()
?.
compareTo
(
item2
?.
title
?.
toLowerCase
()
?:
""
)
?:
-
1
}
}
private
val
descComp
by
lazy
{
Comparator
<
MediaLibraryItem
>
{
item1
,
item2
->
if
(
item1
?.
itemType
==
MediaLibraryItem
.
TYPE_MEDIA
)
{
val
type1
=
(
item1
as
MediaWrapper
).
type
val
type2
=
(
item2
as
MediaWrapper
).
type
if
(
type1
==
MediaWrapper
.
TYPE_DIR
&&
type2
!=
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
-
1
else
if
(
type1
!=
MediaWrapper
.
TYPE_DIR
&&
type2
==
MediaWrapper
.
TYPE_DIR
)
return
@Comparator
1
}
item2
?.
title
?.
toLowerCase
()
?.
compareTo
(
item1
?.
title
?.
toLowerCase
()
?:
""
)
?:
-
1
}
}
@ExperimentalCoroutinesApi
fun
Fragment
.
getBrowserModel
(
category
:
Long
,
url
:
String
?,
showHiddenFiles
:
Boolean
,
showDummyCategory
:
Boolean
=
false
)
=
if
(
category
==
TYPE_NETWORK
)
ViewModelProvider
(
this
,
NetworkModel
.
Factory
(
requireContext
(),
url
,
showHiddenFiles
)).
get
(
NetworkModel
::
class
.
java
)
...
...
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment