StorageBrowserFragment.kt 9.16 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90
/*
 * *************************************************************************
 *  StorageBrowserFragment.java
 * **************************************************************************
 *  Copyright © 2015 VLC authors and VideoLAN
 *  Author: Geoffrey Métais
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
 *  ***************************************************************************
 */

package org.videolan.vlc.gui.browser

import android.annotation.TargetApi
import android.arch.lifecycle.ViewModelProviders
import android.content.DialogInterface
import android.databinding.DataBindingUtil
import android.os.Build
import android.os.Bundle
import android.support.design.widget.Snackbar
import android.support.v4.app.Fragment
import android.support.v4.util.SimpleArrayMap
import android.support.v7.app.AlertDialog
import android.support.v7.widget.AppCompatEditText
import android.text.InputType
import android.view.Menu
import android.view.MenuItem
import android.view.View
import android.widget.CheckBox
import kotlinx.coroutines.experimental.CoroutineExceptionHandler
import kotlinx.coroutines.experimental.android.UI
import kotlinx.coroutines.experimental.launch

import org.videolan.libvlc.util.AndroidUtil
import org.videolan.medialibrary.interfaces.EntryPointsEventsCb
import org.videolan.medialibrary.media.MediaLibraryItem
import org.videolan.medialibrary.media.MediaWrapper
import org.videolan.medialibrary.media.Storage
import org.videolan.vlc.R
import org.videolan.vlc.VLCApplication
import org.videolan.vlc.databinding.BrowserItemBinding
import org.videolan.vlc.gui.AudioPlayerContainerActivity
import org.videolan.vlc.gui.dialogs.*
import org.videolan.vlc.gui.helpers.ThreeStatesCheckbox
import org.videolan.vlc.gui.helpers.UiTools
import org.videolan.vlc.util.*
import org.videolan.vlc.viewmodels.browser.BrowserModel
import org.videolan.vlc.viewmodels.browser.*

import java.io.File

const val KEY_IN_MEDIALIB = "key_in_medialib"

class StorageBrowserFragment : FileBrowserFragment(), EntryPointsEventsCb {

    internal var mScannedDirectory = false
    private val mProcessingFolders = SimpleArrayMap<String, CheckBox>()
    private var mSnack: Snackbar? = null
    private var mAlertDialog: AlertDialog? = null

    override val categoryTitle: String
        get() = getString(R.string.directories_summary)

    override fun createFragment(): Fragment {
        return StorageBrowserFragment()
    }

    override fun onCreate(bundle: Bundle?) {
        var bundle = bundle
        super.onCreate(bundle)
        adapter = StorageBrowserAdapter(this)
        if (bundle == null) bundle = arguments
        if (bundle != null) mScannedDirectory = bundle.getBoolean(KEY_IN_MEDIALIB)
    }

    @TargetApi(Build.VERSION_CODES.LOLLIPOP)
    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
91
        if (isRootDirectory && AndroidDevices.showTvUi(view.context)) {
92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
            mSnack = Snackbar.make(view, R.string.tv_settings_hint, Snackbar.LENGTH_INDEFINITE)
            if (AndroidUtil.isLolliPopOrLater) mSnack?.view?.elevation = view.resources.getDimensionPixelSize(R.dimen.audio_player_elevation).toFloat()
        }
    }

    override fun setupBrowser() {
        viewModel = ViewModelProviders.of(this, BrowserModel.Factory(requireContext(), mrl, TYPE_STORAGE, showHiddenFiles)).get(BrowserModel::class.java)
    }

    override fun onStart() {
        super.onStart()
        VLCApplication.getMLInstance().addEntryPointsEventsCb(this)
        mSnack?.let { it.show() }
    }

    override fun onStop() {
        super.onStop()
        VLCApplication.getMLInstance().removeEntryPointsEventsCb(this)
        mSnack?.let { it.dismiss() }
        mAlertDialog?.let { if (it.isShowing) it.dismiss() }
    }

    override fun onSaveInstanceState(outState: Bundle) {
        super.onSaveInstanceState(outState)
        outState.putBoolean(KEY_IN_MEDIALIB, mScannedDirectory)
    }

    override fun onPrepareOptionsMenu(menu: Menu?) {
        super.onPrepareOptionsMenu(menu)
        menu?.findItem(R.id.ml_menu_custom_dir)?.isVisible = true
        menu?.findItem(R.id.ml_menu_equalizer)?.isVisible = false
123
        menu?.findItem(R.id.ml_menu_refresh)?.isVisible = false
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149
    }

    override fun onOptionsItemSelected(item: MenuItem?): Boolean {
        if (item?.itemId == R.id.ml_menu_custom_dir) {
            showAddDirectoryDialog()
            return true
        }
        return super.onOptionsItemSelected(item)
    }

    fun browse(media: MediaWrapper, position: Int, scanned: Boolean) {
        val ft = activity?.supportFragmentManager?.beginTransaction()
        val next = createFragment()
        val args = Bundle()
        args.putParcelable(KEY_MEDIA, media)
        args.putBoolean(KEY_IN_MEDIALIB, mScannedDirectory || scanned)
        next.arguments = args
        ft?.replace(R.id.fragment_placeholder, next, media.location)
        ft?.addToBackStack(mrl)
        ft?.commit()
    }

    override fun onCtxClick(v: View, position: Int, item: MediaLibraryItem) {
        if (isRootDirectory) {
            val storage = adapter.getItem(position) as Storage
            launch(UI.immediate) {
150
                val isCustom = viewModel.customDirectoryExists(storage.uri.path)
151 152 153 154 155 156 157
                if (isCustom) showContext(requireActivity(), this@StorageBrowserFragment, position, item.title, CTX_CUSTOM_REMOVE)
            }
        }
    }

    override fun onCtxAction(position: Int, option: Int) {
        val storage = adapter.getItem(position) as Storage
158
        viewModel.deleteCustomDirectory(storage.uri.path)
159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182
        viewModel.remove(storage)
        (activity as AudioPlayerContainerActivity).updateLib()
    }

    override fun onClick(v: View, position: Int, item: MediaLibraryItem) {
        val mw = MediaWrapper((item as Storage).uri)
        mw.type = MediaWrapper.TYPE_DIR
        browse(mw, position, (DataBindingUtil.findBinding<BrowserItemBinding>(v))?.browserCheckbox?.state == ThreeStatesCheckbox.STATE_CHECKED)
    }

    internal fun processEvent(cbp: CheckBox, mrl: String) {
        cbp.isEnabled = false
        mProcessingFolders.put(mrl, cbp)
    }

    override fun onEntryPointBanned(entryPoint: String, success: Boolean) {}

    override fun onEntryPointUnbanned(entryPoint: String, success: Boolean) {}

    override fun onEntryPointRemoved(entryPoint: String, success: Boolean) {
        var entryPoint = entryPoint
        if (entryPoint.endsWith("/"))
            entryPoint = entryPoint.substring(0, entryPoint.length - 1)
        if (mProcessingFolders.containsKey(entryPoint)) {
Geoffrey Métais's avatar
Geoffrey Métais committed
183 184 185 186 187 188 189 190 191
            mProcessingFolders.remove(entryPoint)?.let {
                handler.post {
                    it.isEnabled = true
                    if (success) {
                        (adapter as StorageBrowserAdapter).updateMediaDirs(requireContext())
                        adapter.notifyDataSetChanged()
                    } else
                        it.isChecked = true
                }
192 193 194 195 196 197 198 199 200 201 202 203 204
            }
        }
    }

    override fun onDiscoveryStarted(entryPoint: String) {}

    override fun onDiscoveryProgress(entryPoint: String) {}

    override fun onDiscoveryCompleted(entryPoint: String) {
        var path = entryPoint
        if (path.endsWith("/")) path = path.dropLast(1)
        if (mProcessingFolders.containsKey(path)) {
            val finalPath = path
Geoffrey Métais's avatar
Geoffrey Métais committed
205
            handler.post { mProcessingFolders.get(finalPath)?.isEnabled = true }
206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227
            (adapter as StorageBrowserAdapter).updateMediaDirs(requireContext())
        }
    }

    private fun showAddDirectoryDialog() {
        val context = activity
        val builder = AlertDialog.Builder(context!!)
        val input = AppCompatEditText(context)
        input.inputType = InputType.TYPE_TEXT_FLAG_NO_SUGGESTIONS
        builder.setTitle(R.string.add_custom_path)
        builder.setMessage(R.string.add_custom_path_description)
        builder.setView(input)
        builder.setNegativeButton(R.string.cancel) { dialogInterface, which -> }
        builder.setPositiveButton(R.string.ok, DialogInterface.OnClickListener { dialog, which ->
            val path = input.text.toString().trim { it <= ' ' }
            val f = File(path)
            if (!f.exists() || !f.isDirectory) {
                UiTools.snacker(view!!, getString(R.string.directorynotfound, path))
                return@OnClickListener
            }

            launch(UI.immediate + CoroutineExceptionHandler{ _, _ ->}) {
228
                viewModel.addCustomDirectory(f.canonicalPath).join()
229
                viewModel.browserRoot()
230 231 232 233 234
            }
        })
        mAlertDialog = builder.show()
    }
}