Skip to content
Snippets Groups Projects
Commit f08de412 authored by Geoffrey Métais's avatar Geoffrey Métais
Browse files

TV: Make code more idiomatic

parent a6035872
No related branches found
No related tags found
1 merge request!108Tv media browser refactor
......@@ -19,6 +19,7 @@ import org.videolan.vlc.gui.DiffUtilAdapter
import org.videolan.vlc.gui.helpers.getAudioIconDrawable
import org.videolan.vlc.gui.view.FastScroller
import org.videolan.vlc.interfaces.IEventsHandler
import org.videolan.vlc.util.UPDATE_PAYLOAD
import org.videolan.vlc.util.generateResolutionClass
@ExperimentalCoroutinesApi
......@@ -26,25 +27,23 @@ import org.videolan.vlc.util.generateResolutionClass
class FileTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, var itemSize: Int) : DiffUtilAdapter<MediaLibraryItem, MediaTvItemAdapter.AbstractMediaItemViewHolder<MediaBrowserTvItemBinding>>(), FastScroller.SeparatedAdapter, TvItemAdapter {
override fun submitList(pagedList: Any?) {
if (pagedList is List<*>) {
@Suppress("UNCHECKED_CAST")
update(pagedList as List<MediaLibraryItem>)
}
}
override var focusNext = -1
private val mDefaultCover: BitmapDrawable?
private val defaultCover: BitmapDrawable?
private var focusListener: FocusableRecyclerView.FocusListener? = null
init {
var ctx: Context? = null
if (eventsHandler is Context)
ctx = eventsHandler
else if (eventsHandler is Fragment) ctx = (eventsHandler as Fragment).context
mDefaultCover = if (ctx != null) getAudioIconDrawable(ctx, type) else null
val ctx = when (eventsHandler) {
is Context -> eventsHandler
is Fragment -> eventsHandler.context
else -> null
}
defaultCover = ctx?.let { getAudioIconDrawable(it, type) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MediaTvItemAdapter.AbstractMediaItemViewHolder<MediaBrowserTvItemBinding> {
......@@ -64,11 +63,7 @@ class FileTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, va
}
}
override fun hasSections(): Boolean {
return true
}
override fun hasSections() = true
override fun setOnFocusChangeListener(focusListener: FocusableRecyclerView.FocusListener?) {
this.focusListener = focusListener
......@@ -76,43 +71,29 @@ class FileTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, va
override fun createCB(): DiffCallback<MediaLibraryItem> {
return object : DiffCallback<MediaLibraryItem>() {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return try {
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int) = try {
dataset[oldItemPosition] === dataset[newItemPosition]
} catch (e: IndexOutOfBoundsException) {
false
}
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
return dataset[oldItemPosition].description == dataset[newItemPosition].description
&& dataset[oldItemPosition].title == dataset[newItemPosition].title
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
return arrayListOf(UPDATE_PAYLOAD)
}
override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int) = arrayListOf(UPDATE_PAYLOAD)
}
}
companion object {
private val TAG = "VLC/AudioBrowserAdapter"
private const val UPDATE_PAYLOAD = 1
}
inner class MediaItemTVViewHolder @TargetApi(Build.VERSION_CODES.M)
internal constructor(binding: MediaBrowserTvItemBinding, override val eventsHandler: IEventsHandler) : MediaTvItemAdapter.AbstractMediaItemViewHolder<MediaBrowserTvItemBinding>(binding), View.OnFocusChangeListener {
override fun getItem(layoutPosition: Int): MediaLibraryItem? {
return this@FileTvItemAdapter.getItem(layoutPosition)
}
override fun getItem(layoutPosition: Int) = this@FileTvItemAdapter.getItem(layoutPosition)
init {
binding.holder = this
if (mDefaultCover != null) binding.cover = mDefaultCover
if (defaultCover != null) binding.cover = defaultCover
if (AndroidUtil.isMarshMallowOrLater)
itemView.setOnContextClickListener { v ->
onMoreClick(v)
......@@ -128,10 +109,8 @@ class FileTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, va
val scale = newWidth.toFloat() / itemSize
binding.container.animate().scaleX(scale).scaleY(scale).translationZ(scale)
eventsHandler.onItemFocused(binding.root, getItem(layoutPosition)!!)
if (focusListener != null) {
focusListener!!.onFocusChanged(layoutPosition)
}
eventsHandler.onItemFocused(binding.root, getItem(layoutPosition))
focusListener?.onFocusChanged(layoutPosition)
} else {
binding.container.animate().scaleX(1f).scaleY(1f).translationZ(1f)
......@@ -142,7 +121,7 @@ class FileTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, va
}
override fun recycle() {
if (mDefaultCover != null) binding.cover = mDefaultCover
defaultCover?.let { binding.cover = it }
binding.title.text = ""
binding.subtitle.text = ""
}
......@@ -163,7 +142,6 @@ class FileTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, va
seen = item.seen
var max = 0
if (item.length > 0) {
val lastTime = item.displayTime
if (lastTime > 0) {
......@@ -173,8 +151,6 @@ class FileTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, va
}
binding.max = max
}
}
binding.progress = progress
......@@ -187,11 +163,6 @@ class FileTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, va
}
@ObsoleteCoroutinesApi
override fun setCoverlay(selected: Boolean) {
}
override fun setCoverlay(selected: Boolean) {}
}
}
\ No newline at end of file
}
......@@ -23,7 +23,7 @@ import org.videolan.vlc.gui.helpers.SelectorViewHolder
import org.videolan.vlc.gui.helpers.getAudioIconDrawable
import org.videolan.vlc.gui.view.FastScroller
import org.videolan.vlc.interfaces.IEventsHandler
import org.videolan.vlc.util.UPDATE_SELECTION
import org.videolan.vlc.util.UPDATE_PAYLOAD
import org.videolan.vlc.util.Util
import org.videolan.vlc.util.generateResolutionClass
......@@ -31,21 +31,22 @@ import org.videolan.vlc.util.generateResolutionClass
@ObsoleteCoroutinesApi
class MediaTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, var itemSize: Int) : PagedListAdapter<MediaLibraryItem, MediaTvItemAdapter.AbstractMediaItemViewHolder<ViewDataBinding>>(DIFF_CALLBACK), FastScroller.SeparatedAdapter, TvItemAdapter {
override var focusNext = -1
private val mDefaultCover: BitmapDrawable?
private val defaultCover: BitmapDrawable?
private var focusListener: FocusableRecyclerView.FocusListener? = null
init {
var ctx: Context? = null
if (eventsHandler is Context)
ctx = eventsHandler
else if (eventsHandler is Fragment) ctx = (eventsHandler as Fragment).context
mDefaultCover = if (ctx != null) getAudioIconDrawable(ctx, type) else null
val ctx: Context? = when (eventsHandler) {
is Context -> eventsHandler
is Fragment -> (eventsHandler as Fragment).context
else -> null
}
defaultCover = ctx?.let { getAudioIconDrawable(it, type) }
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbstractMediaItemViewHolder<ViewDataBinding> {
val inflater = parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
val binding = MediaBrowserTvItemBinding.inflate(inflater, parent, false)
@Suppress("UNCHECKED_CAST")
return MediaItemTVViewHolder(binding, eventsHandler) as AbstractMediaItemViewHolder<ViewDataBinding>
}
......@@ -69,37 +70,29 @@ class MediaTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, v
val isSelected = payload.hasStateFlags(MediaLibraryItem.FLAG_SELECTED)
holder.setCoverlay(isSelected)
holder.selectView(isSelected)
} else if (payload is Int) {
if (payload == UPDATE_SELECTION) {
}
}
}
}
override fun hasSections(): Boolean {
return true
}
override fun hasSections() = true
override fun submitList(pagedList: Any?) {
if (pagedList == null) {
this.submitList(null)
}
if (pagedList is PagedList<*>) {
@Suppress("UNCHECKED_CAST")
this.submitList(pagedList as PagedList<MediaLibraryItem>)
}
}
override fun setOnFocusChangeListener(focusListener: FocusableRecyclerView.FocusListener?) {
this.focusListener = focusListener
}
companion object {
private val TAG = "VLC/AudioBrowserAdapter"
private const val UPDATE_PAYLOAD = 1
private const val TAG = "VLC/MediaTvItemAdapter"
/**
* Awful hack to workaround the [PagedListAdapter] not keeping track of notifyItemMoved operations
*/
......@@ -107,16 +100,10 @@ class MediaTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, v
private val DIFF_CALLBACK = object : DiffUtil.ItemCallback<MediaLibraryItem>() {
override fun areItemsTheSame(
oldMedia: MediaLibraryItem, newMedia: MediaLibraryItem): Boolean {
return if (preventNextAnim) {
true
} else oldMedia === newMedia || oldMedia.itemType == newMedia.itemType && oldMedia.equals(newMedia)
}
oldMedia: MediaLibraryItem, newMedia: MediaLibraryItem) = if (preventNextAnim) true
else oldMedia === newMedia || oldMedia.itemType == newMedia.itemType && oldMedia.equals(newMedia)
override fun areContentsTheSame(
oldMedia: MediaLibraryItem, newMedia: MediaLibraryItem): Boolean {
return false
}
override fun areContentsTheSame(oldMedia: MediaLibraryItem, newMedia: MediaLibraryItem) = false
override fun getChangePayload(oldItem: MediaLibraryItem, newItem: MediaLibraryItem): Any? {
preventNextAnim = false
......@@ -129,30 +116,24 @@ class MediaTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, v
abstract class AbstractMediaItemViewHolder<T : ViewDataBinding> @TargetApi(Build.VERSION_CODES.M)
internal constructor(binding: T) : SelectorViewHolder<T>(binding), View.OnFocusChangeListener {
fun onClick(v: View) {
val item = getItem(layoutPosition)
if (item != null) eventsHandler.onClick(v, layoutPosition, item)
getItem(layoutPosition)?.let { eventsHandler.onClick(v, layoutPosition, it) }
}
fun onMoreClick(v: View) {
val item = getItem(layoutPosition)
if (item != null) eventsHandler.onCtxClick(v, layoutPosition, item)
getItem(layoutPosition)?.let { eventsHandler.onCtxClick(v, layoutPosition, it) }
}
fun onLongClick(view: View): Boolean {
val item = getItem(layoutPosition)
return item != null && eventsHandler.onLongClick(view, layoutPosition, item)
return getItem(layoutPosition)?.let { eventsHandler.onLongClick(view, layoutPosition, it) } ?: false
}
fun onImageClick(v: View) {
val item = getItem(layoutPosition)
if (item != null) eventsHandler.onImageClick(v, layoutPosition, item)
getItem(layoutPosition)?.let { eventsHandler.onImageClick(v, layoutPosition, it) }
}
fun onMainActionClick(v: View) {
val item = getItem(layoutPosition)
if (item != null) eventsHandler.onMainActionClick(v, layoutPosition, item)
getItem(layoutPosition)?.let { eventsHandler.onMainActionClick(v, layoutPosition, it) }
}
abstract fun getItem(layoutPosition: Int): MediaLibraryItem?
......@@ -168,13 +149,11 @@ class MediaTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, v
inner class MediaItemTVViewHolder @TargetApi(Build.VERSION_CODES.M)
internal constructor(binding: MediaBrowserTvItemBinding, override val eventsHandler: IEventsHandler) : AbstractMediaItemViewHolder<MediaBrowserTvItemBinding>(binding), View.OnFocusChangeListener {
override fun getItem(layoutPosition: Int): MediaLibraryItem? {
return this@MediaTvItemAdapter.getItem(layoutPosition)
}
override fun getItem(layoutPosition: Int) = this@MediaTvItemAdapter.getItem(layoutPosition)
init {
binding.holder = this
if (mDefaultCover != null) binding.cover = mDefaultCover
if (defaultCover != null) binding.cover = defaultCover
if (AndroidUtil.isMarshMallowOrLater)
itemView.setOnContextClickListener { v ->
onMoreClick(v)
......@@ -200,11 +179,10 @@ class MediaTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, v
}
}
binding.container.clipToOutline = true
}
override fun recycle() {
if (mDefaultCover != null) binding.cover = mDefaultCover
if (defaultCover != null) binding.cover = defaultCover
binding.title.text = ""
binding.subtitle.text = ""
}
......@@ -225,7 +203,6 @@ class MediaTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, v
seen = item.seen
var max = 0
if (item.length > 0) {
val lastTime = item.displayTime
if (lastTime > 0) {
......@@ -235,8 +212,6 @@ class MediaTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, v
}
binding.max = max
}
}
binding.progress = progress
......@@ -249,11 +224,6 @@ class MediaTvItemAdapter(type: Int, private val eventsHandler: IEventsHandler, v
}
@ObsoleteCoroutinesApi
override fun setCoverlay(selected: Boolean) {
}
override fun setCoverlay(selected: Boolean) {}
}
}
\ No newline at end of file
}
......@@ -4,6 +4,5 @@ interface TvItemAdapter {
fun submitList(pagedList: Any?)
fun setOnFocusChangeListener(focusListener: FocusableRecyclerView.FocusListener?)
var focusNext: Int
}
\ No newline at end of file
}
......@@ -77,7 +77,6 @@ abstract class BaseBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEv
abstract fun getColumnNumber(): Int
abstract fun provideAdapter(eventsHandler: IEventsHandler, itemSize: Int): TvItemAdapter
lateinit var viewModel: TvBrowserModel
private var spacing: Int = 0
abstract var adapter: TvItemAdapter
......@@ -87,17 +86,13 @@ abstract class BaseBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEv
private lateinit var backgroundManager: BackgroundManager
internal lateinit var animationDelegate: MediaBrowserAnimatorDelegate
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
return inflater.inflate(R.layout.song_browser, container, false)
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
backgroundManager = BackgroundManager.getInstance(requireActivity())
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
......@@ -107,15 +102,12 @@ abstract class BaseBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEv
val vp = TvUtil.getOverscanVertical(requireContext())
headerList.setPadding(list.paddingLeft + hp, list.paddingTop + vp, list.paddingRight + hp, list.paddingBottom + vp)
val lp = (imageButtonSettings.layoutParams as ConstraintLayout.LayoutParams)
lp.leftMargin += hp
lp.rightMargin += hp
lp.topMargin += vp
lp.bottomMargin += vp
calculateNbColumns()
title.text = viewModel.currentItem?.let {
......@@ -123,7 +115,6 @@ abstract class BaseBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEv
else it.title
} ?: getTitle()
val searchHeaderClick: (View) -> Unit = { animationDelegate.hideFAB() }
val sortClick: (View) -> Unit = { v ->
......@@ -137,7 +128,6 @@ abstract class BaseBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEv
sortButton.setOnClickListener(sortClick)
imageButtonSort.setOnClickListener(sortClick)
gridLayoutManager = object : GridLayoutManager(requireActivity(), viewModel.nbColumns) {
override fun requestChildRectangleOnScreen(parent: RecyclerView, child: View, rect: Rect, immediate: Boolean) = false
......@@ -202,7 +192,7 @@ abstract class BaseBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEv
calculateNbColumns()
gridLayoutManager.spanCount = viewModel.nbColumns
if (BuildConfig.DEBUG) Log.d(TAG, "${viewModel.nbColumns}");
if (BuildConfig.DEBUG) Log.d(TAG, "${viewModel.nbColumns}")
list.layoutManager = gridLayoutManager
}
......@@ -214,22 +204,16 @@ abstract class BaseBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEv
super.onActivityCreated(savedInstanceState)
}
override fun refresh() = (viewModel as RefreshModel).refresh()
override fun onLongClick(v: View, position: Int, item: MediaLibraryItem): Boolean {
if (item is MediaWrapper) {
val intent = Intent(requireActivity(), DetailsActivity::class.java)
// pass the item information
intent.putExtra("media", item)
intent.putExtra("item", MediaItemDetails(item.title, item.artist, item.album, item.location, item.artworkURL))
startActivity(intent)
}
return true
}
......@@ -343,7 +327,7 @@ abstract class BaseBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEv
val now = System.currentTimeMillis()
if (now - lastDpadEventTime > 200) {
lastDpadEventTime = now
if (BuildConfig.DEBUG) Log.d("keydown", "Keydown propagated");
if (BuildConfig.DEBUG) Log.d("keydown", "Keydown propagated")
false
} else true
}
......@@ -356,12 +340,9 @@ abstract class BaseBrowserTvFragment : Fragment(), BrowserFragmentInterface, IEv
animationDelegate.setVisibility(imageButtonHeader, if (viewModel.provider.headers.isEmpty) View.GONE else View.VISIBLE)
animationDelegate.setVisibility(headerButton, if (viewModel.provider.headers.isEmpty) View.GONE else View.VISIBLE)
animationDelegate.setVisibility(headerDescription, if (viewModel.provider.headers.isEmpty) View.GONE else View.VISIBLE)
}
}
@MainThread
@BindingAdapter("constraintRatio")
fun constraintRatio(v: View, isSquare: Boolean) {
......@@ -373,6 +354,4 @@ fun constraintRatio(v: View, isSquare: Boolean) {
constraintLayout.setConstraintSet(constraintSet)
}
}
\ No newline at end of file
}
......@@ -40,8 +40,7 @@ class FileBrowserTvFragment : BaseBrowserTvFragment() {
else -> getString(R.string.video)
}
override fun getColumnNumber(): Int =
resources.getInteger(R.integer.tv_songs_col_count)
override fun getColumnNumber() = resources.getInteger(R.integer.tv_songs_col_count)
companion object {
fun newInstance(type: Int, item: MediaLibraryItem?) =
......@@ -65,7 +64,6 @@ class FileBrowserTvFragment : BaseBrowserTvFragment() {
viewModel.currentItem = item
(viewModel.provider as BrowserProvider).dataset.observe(this, Observer { items ->
submitList(items)
if (BuildConfig.DEBUG) Log.d("FileBrowserTvFragment", "Submit lis of ${items.size} items")
......@@ -89,17 +87,13 @@ class FileBrowserTvFragment : BaseBrowserTvFragment() {
if (BuildConfig.DEBUG) Log.d("FileBrowserTvFragment", "Description update: ${pair.first} ${pair.second}")
if (pair != null) (adapter as RecyclerView.Adapter<*>).notifyItemChanged(pair.first, pair.second)
})
}
override fun onResume() {
super.onResume()
if (item == null)
(viewModel.provider as BrowserProvider).browseRoot()
else
refresh()
if (item == null) (viewModel.provider as BrowserProvider).browseRoot()
else refresh()
}
override fun onSaveInstanceState(outState: Bundle) {
......@@ -114,7 +108,6 @@ class FileBrowserTvFragment : BaseBrowserTvFragment() {
(viewModel as BrowserModel).stop()
}
private fun getCategory() = arguments?.getInt(CATEGORY, TYPE_FILE) ?: TYPE_FILE
override fun onClick(v: View, position: Int, item: MediaLibraryItem) {
......@@ -135,5 +128,4 @@ class FileBrowserTvFragment : BaseBrowserTvFragment() {
ft.replace(R.id.tv_fragment_placeholder, next, media.title)
ft.commit()
}
}
\ No newline at end of file
}
......@@ -31,22 +31,18 @@ class MediaBrowserTvFragment : BaseBrowserTvFragment() {
override lateinit var adapter: TvItemAdapter
override fun getTitle() = when (arguments?.getLong(CATEGORY, CATEGORY_SONGS)) {
CATEGORY_SONGS -> getString(R.string.tracks)
CATEGORY_ALBUMS -> getString(R.string.albums)
CATEGORY_ARTISTS -> getString(R.string.artists)
CATEGORY_GENRES -> getString(R.string.genres)
else -> getString(R.string.video)
}
override fun getTitle(): String =
when (arguments?.getLong(CATEGORY, CATEGORY_SONGS)) {
CATEGORY_SONGS -> getString(R.string.tracks)
CATEGORY_ALBUMS -> getString(R.string.albums)
CATEGORY_ARTISTS -> getString(R.string.artists)
CATEGORY_GENRES -> getString(R.string.genres)
else -> getString(R.string.video)
}
override fun getColumnNumber(): Int =
when (arguments?.getLong(CATEGORY, CATEGORY_SONGS)) {
CATEGORY_VIDEOS -> resources.getInteger(R.integer.tv_videos_col_count)
else -> resources.getInteger(R.integer.tv_songs_col_count)
}
override fun getColumnNumber() = when (arguments?.getLong(CATEGORY, CATEGORY_SONGS)) {
CATEGORY_VIDEOS -> resources.getInteger(R.integer.tv_videos_col_count)
else -> resources.getInteger(R.integer.tv_songs_col_count)
}
companion object {
fun newInstance(type: Long, item: MediaLibraryItem?) =
......@@ -61,9 +57,7 @@ class MediaBrowserTvFragment : BaseBrowserTvFragment() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel = getMediaBrowserModel(arguments?.getLong(CATEGORY, CATEGORY_SONGS)
?: CATEGORY_SONGS)
viewModel = getMediaBrowserModel(arguments?.getLong(CATEGORY, CATEGORY_SONGS) ?: CATEGORY_SONGS)
viewModel.currentItem = if (savedInstanceState != null) savedInstanceState.getParcelable<Parcelable>(ITEM) as? MediaLibraryItem
else requireActivity().intent.getParcelableExtra<Parcelable>(ITEM) as? MediaLibraryItem
......@@ -92,5 +86,4 @@ class MediaBrowserTvFragment : BaseBrowserTvFragment() {
override fun onClick(v: View, position: Int, item: MediaLibraryItem) {
launch { TvUtil.openMediaFromPaged(requireActivity(), item, viewModel.provider as MedialibraryProvider<out MediaLibraryItem>) }
}
}
\ No newline at end of file
}
......@@ -63,7 +63,6 @@ const val REPEAT_ONE = 1
const val REPEAT_ALL = 2
const val MEDIALIBRARY_PAGE_SIZE = 500
// MediaParsingService
const val ACTION_INIT = "medialibrary_init"
const val ACTION_RELOAD = "medialibrary_reload"
......@@ -129,6 +128,7 @@ const val UPDATE_THUMB = 1
const val UPDATE_TIME = 2
const val UPDATE_SEEN = 3
const val UPDATE_DESCRIPTION = 4
const val UPDATE_PAYLOAD = 5
const val KEY_URI = "uri"
const val SELECTED_ITEM = "selected"
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment