forked from hardcoresushi/DroidFS
Allow changing thumbnail max size
This commit is contained in:
parent
72321b8ec5
commit
2ee7a5b871
@ -3,45 +3,44 @@ package sushi.hardcore.droidfs
|
||||
import android.net.Uri
|
||||
import java.io.File
|
||||
|
||||
class ConstValues {
|
||||
companion object {
|
||||
const val creator = "DroidFS"
|
||||
const val gocryptfsConfFilename = "gocryptfs.conf"
|
||||
const val FILE_MODE = 384 //0600
|
||||
const val DIRECTORY_MODE = 448 //0700
|
||||
const val volumeDatabaseName = "SavedVolumes"
|
||||
const val sort_order_key = "sort_order"
|
||||
val fakeUri: Uri = Uri.parse("fakeuri://droidfs")
|
||||
const val MAX_KERNEL_WRITE = 128*1024
|
||||
const val wipe_passes = 2
|
||||
const val slideshow_delay: Long = 4000
|
||||
const val DEFAULT_THEME_VALUE = "dark_green"
|
||||
private val fileExtensions = mapOf(
|
||||
Pair("image", listOf("png", "jpg", "jpeg", "gif", "webp", "bmp")),
|
||||
Pair("video", listOf("mp4", "webm", "mkv", "mov")),
|
||||
Pair("audio", listOf("mp3", "ogg", "m4a", "wav", "flac")),
|
||||
Pair("pdf", listOf("pdf")),
|
||||
Pair("text", listOf("txt", "json", "conf", "log", "xml", "java", "kt", "py", "pl", "rb", "go", "c", "h", "cpp", "hpp", "rs", "sh", "bat", "js", "html", "css", "php", "yml", "yaml", "toml", "ini", "md", "properties"))
|
||||
)
|
||||
object ConstValues {
|
||||
const val CREATOR = "DroidFS"
|
||||
const val FILE_MODE = 384 //0600
|
||||
const val DIRECTORY_MODE = 448 //0700
|
||||
const val VOLUME_DATABASE_NAME = "SavedVolumes"
|
||||
const val SORT_ORDER_KEY = "sort_order"
|
||||
val FAKE_URI: Uri = Uri.parse("fakeuri://droidfs")
|
||||
const val MAX_KERNEL_WRITE = 128*1024
|
||||
const val WIPE_PASSES = 2
|
||||
const val SLIDESHOW_DELAY: Long = 4000
|
||||
const val DEFAULT_THEME_VALUE = "dark_green"
|
||||
const val THUMBNAIL_MAX_SIZE_KEY = "thumbnail_max_size"
|
||||
const val DEFAULT_THUMBNAIL_MAX_SIZE = 10_000L
|
||||
private val FILE_EXTENSIONS = mapOf(
|
||||
Pair("image", listOf("png", "jpg", "jpeg", "gif", "webp", "bmp")),
|
||||
Pair("video", listOf("mp4", "webm", "mkv", "mov")),
|
||||
Pair("audio", listOf("mp3", "ogg", "m4a", "wav", "flac")),
|
||||
Pair("pdf", listOf("pdf")),
|
||||
Pair("text", listOf("txt", "json", "conf", "log", "xml", "java", "kt", "py", "pl", "rb", "go", "c", "h", "cpp", "hpp", "rs", "sh", "bat", "js", "html", "css", "php", "yml", "yaml", "toml", "ini", "md", "properties"))
|
||||
)
|
||||
|
||||
fun isExtensionType(extensionType: String, path: String): Boolean {
|
||||
return fileExtensions[extensionType]?.contains(File(path).extension.lowercase()) ?: false
|
||||
}
|
||||
fun isExtensionType(extensionType: String, path: String): Boolean {
|
||||
return FILE_EXTENSIONS[extensionType]?.contains(File(path).extension.lowercase()) ?: false
|
||||
}
|
||||
|
||||
fun isImage(path: String): Boolean {
|
||||
return isExtensionType("image", path)
|
||||
}
|
||||
fun isVideo(path: String): Boolean {
|
||||
return isExtensionType("video", path)
|
||||
}
|
||||
fun isAudio(path: String): Boolean {
|
||||
return isExtensionType("audio", path)
|
||||
}
|
||||
fun isPDF(path: String): Boolean {
|
||||
return isExtensionType("pdf", path)
|
||||
}
|
||||
fun isText(path: String): Boolean {
|
||||
return isExtensionType("text", path)
|
||||
}
|
||||
fun isImage(path: String): Boolean {
|
||||
return isExtensionType("image", path)
|
||||
}
|
||||
fun isVideo(path: String): Boolean {
|
||||
return isExtensionType("video", path)
|
||||
}
|
||||
fun isAudio(path: String): Boolean {
|
||||
return isExtensionType("audio", path)
|
||||
}
|
||||
fun isPDF(path: String): Boolean {
|
||||
return isExtensionType("pdf", path)
|
||||
}
|
||||
fun isText(path: String): Boolean {
|
||||
return isExtensionType("text", path)
|
||||
}
|
||||
}
|
@ -30,13 +30,14 @@ class GocryptfsVolume(val applicationContext: Context, var sessionID: Int) {
|
||||
const val KeyLen = 32
|
||||
const val ScryptDefaultLogN = 16
|
||||
const val DefaultBS = 4096
|
||||
const val CONFIG_FILE_NAME = "gocryptfs.conf"
|
||||
external fun createVolume(root_cipher_dir: String, password: CharArray, plainTextNames: Boolean, xchacha: Int, logN: Int, creator: String, returnedHash: ByteArray?): Boolean
|
||||
external fun init(root_cipher_dir: String, password: CharArray?, givenHash: ByteArray?, returnedHash: ByteArray?): Int
|
||||
external fun changePassword(root_cipher_dir: String, old_password: CharArray?, givenHash: ByteArray?, new_password: CharArray, returnedHash: ByteArray?): Boolean
|
||||
|
||||
fun isGocryptfsVolume(path: File): Boolean {
|
||||
if (path.isDirectory){
|
||||
return File(path, ConstValues.gocryptfsConfFilename).isFile
|
||||
return File(path, CONFIG_FILE_NAME).isFile
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
@ -1,13 +1,20 @@
|
||||
package sushi.hardcore.droidfs
|
||||
|
||||
import android.content.SharedPreferences
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.view.MenuItem
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import android.widget.Toast
|
||||
import androidx.preference.ListPreference
|
||||
import androidx.preference.Preference
|
||||
import androidx.preference.PreferenceFragmentCompat
|
||||
import androidx.preference.SwitchPreference
|
||||
import sushi.hardcore.droidfs.databinding.ActivitySettingsBinding
|
||||
import sushi.hardcore.droidfs.databinding.DialogEditTextBinding
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
|
||||
import java.lang.NumberFormatException
|
||||
|
||||
class SettingsActivity : BaseActivity() {
|
||||
|
||||
@ -20,7 +27,7 @@ class SettingsActivity : BaseActivity() {
|
||||
val fragment = if (screen == "UnsafeFeaturesSettingsFragment") {
|
||||
UnsafeFeaturesSettingsFragment()
|
||||
} else {
|
||||
MainSettingsFragment()
|
||||
MainSettingsFragment(sharedPrefs)
|
||||
}
|
||||
supportFragmentManager
|
||||
.beginTransaction()
|
||||
@ -38,13 +45,71 @@ class SettingsActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
class MainSettingsFragment : PreferenceFragmentCompat() {
|
||||
class MainSettingsFragment(private val sharedPrefs: SharedPreferences) : PreferenceFragmentCompat() {
|
||||
private lateinit var maxSizePreference: Preference
|
||||
|
||||
private fun setThumbnailMaxSize(input: String) {
|
||||
val value: Long
|
||||
try {
|
||||
value = input.toLong()
|
||||
} catch (e: NumberFormatException) {
|
||||
Toast.makeText(requireContext(), R.string.invalid_number, Toast.LENGTH_SHORT).show()
|
||||
showMaxSizeDialog()
|
||||
return
|
||||
}
|
||||
val size = value*1000
|
||||
if (size < 0) {
|
||||
Toast.makeText(requireContext(), R.string.invalid_number, Toast.LENGTH_SHORT).show()
|
||||
showMaxSizeDialog()
|
||||
} else {
|
||||
with(sharedPrefs.edit()) {
|
||||
putLong(ConstValues.THUMBNAIL_MAX_SIZE_KEY, value)
|
||||
apply()
|
||||
}
|
||||
maxSizePreference.summary = PathUtils.formatSize(size)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showMaxSizeDialog() {
|
||||
val dialogBinding = DialogEditTextBinding.inflate(layoutInflater)
|
||||
with (dialogBinding.dialogEditText) {
|
||||
inputType = EditorInfo.TYPE_CLASS_NUMBER
|
||||
hint = getString(R.string.size_hint)
|
||||
}
|
||||
val dialog = CustomAlertDialogBuilder(requireContext(), (requireActivity() as BaseActivity).themeValue)
|
||||
.setTitle(R.string.thumbnail_max_size)
|
||||
.setView(dialogBinding.root)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
setThumbnailMaxSize(dialogBinding.dialogEditText.text.toString())
|
||||
}
|
||||
.create()
|
||||
dialogBinding.dialogEditText.setOnEditorActionListener { _, _, _ ->
|
||||
dialog.dismiss()
|
||||
setThumbnailMaxSize(dialogBinding.dialogEditText.text.toString())
|
||||
true
|
||||
}
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
|
||||
setPreferencesFromResource(R.xml.root_preferences, rootKey)
|
||||
findPreference<ListPreference>("theme")?.setOnPreferenceChangeListener { _, newValue ->
|
||||
(activity as BaseActivity).onThemeChanged(newValue as String)
|
||||
true
|
||||
}
|
||||
findPreference<Preference>(ConstValues.THUMBNAIL_MAX_SIZE_KEY)?.let {
|
||||
maxSizePreference = it
|
||||
maxSizePreference.summary = getString(
|
||||
R.string.thumbnail_max_size_summary,
|
||||
PathUtils.formatSize(sharedPrefs.getLong(
|
||||
ConstValues.THUMBNAIL_MAX_SIZE_KEY, ConstValues.DEFAULT_THUMBNAIL_MAX_SIZE
|
||||
)*1000)
|
||||
)
|
||||
maxSizePreference.setOnPreferenceClickListener {
|
||||
showMaxSizeDialog()
|
||||
false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import android.database.sqlite.SQLiteDatabase
|
||||
import android.database.sqlite.SQLiteOpenHelper
|
||||
|
||||
class VolumeDatabase(context: Context): SQLiteOpenHelper(context,
|
||||
ConstValues.volumeDatabaseName, null, 3) {
|
||||
ConstValues.VOLUME_DATABASE_NAME, null, 3) {
|
||||
companion object {
|
||||
const val TABLE_NAME = "Volumes"
|
||||
const val COLUMN_NAME = "name"
|
||||
|
@ -23,7 +23,8 @@ class ExplorerElementAdapter(
|
||||
val activity: AppCompatActivity,
|
||||
val gocryptfsVolume: GocryptfsVolume?,
|
||||
val onExplorerElementClick: (Int) -> Unit,
|
||||
val onExplorerElementLongClick: (Int) -> Unit
|
||||
val onExplorerElementLongClick: (Int) -> Unit,
|
||||
val thumbnailMaxSize: Long,
|
||||
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
|
||||
val dateFormat: DateFormat = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.getDefault())
|
||||
var explorerElements = listOf<ExplorerElement>()
|
||||
@ -127,7 +128,7 @@ class ExplorerElementAdapter(
|
||||
adapter.gocryptfsVolume?.let { volume ->
|
||||
displayThumbnail = true
|
||||
Thread {
|
||||
volume.loadWholeFile(fullPath, maxSize = 50_000_000).first?.let {
|
||||
volume.loadWholeFile(fullPath, maxSize = adapter.thumbnailMaxSize).first?.let {
|
||||
if (displayThumbnail) {
|
||||
adapter.activity.runOnUiThread {
|
||||
if (displayThumbnail) {
|
||||
|
@ -127,7 +127,7 @@ class CreateVolumeFragment: Fragment() {
|
||||
var returnedHash: ByteArray? = null
|
||||
if (binding.checkboxSavePassword.isChecked)
|
||||
returnedHash = ByteArray(GocryptfsVolume.KeyLen)
|
||||
if (GocryptfsVolume.createVolume(volumePath, password, false, xchacha, GocryptfsVolume.ScryptDefaultLogN, ConstValues.creator, returnedHash)) {
|
||||
if (GocryptfsVolume.createVolume(volumePath, password, false, xchacha, GocryptfsVolume.ScryptDefaultLogN, ConstValues.CREATOR, returnedHash)) {
|
||||
val volumeName = if (isHiddenVolume) File(volumePath).name else volumePath
|
||||
val volume = Volume(volumeName, isHiddenVolume)
|
||||
volumeDatabase.apply {
|
||||
|
@ -23,11 +23,11 @@ import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
import sushi.hardcore.droidfs.BaseActivity
|
||||
import sushi.hardcore.droidfs.ConstValues
|
||||
import sushi.hardcore.droidfs.ConstValues.Companion.isAudio
|
||||
import sushi.hardcore.droidfs.ConstValues.Companion.isImage
|
||||
import sushi.hardcore.droidfs.ConstValues.Companion.isPDF
|
||||
import sushi.hardcore.droidfs.ConstValues.Companion.isText
|
||||
import sushi.hardcore.droidfs.ConstValues.Companion.isVideo
|
||||
import sushi.hardcore.droidfs.ConstValues.isAudio
|
||||
import sushi.hardcore.droidfs.ConstValues.isImage
|
||||
import sushi.hardcore.droidfs.ConstValues.isPDF
|
||||
import sushi.hardcore.droidfs.ConstValues.isText
|
||||
import sushi.hardcore.droidfs.ConstValues.isVideo
|
||||
import sushi.hardcore.droidfs.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter
|
||||
@ -79,7 +79,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
sortOrderValues = resources.getStringArray(R.array.sort_orders_values)
|
||||
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
|
||||
mapFolders = sharedPrefs.getBoolean("map_folders", true)
|
||||
currentSortOrderIndex = resources.getStringArray(R.array.sort_orders_values).indexOf(sharedPrefs.getString(ConstValues.sort_order_key, "name"))
|
||||
currentSortOrderIndex = resources.getStringArray(R.array.sort_orders_values).indexOf(sharedPrefs.getString(ConstValues.SORT_ORDER_KEY, "name"))
|
||||
init()
|
||||
recycler_view_explorer = findViewById(R.id.recycler_view_explorer)
|
||||
refresher = findViewById(R.id.refresher)
|
||||
@ -101,7 +101,8 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
null
|
||||
},
|
||||
::onExplorerItemClick,
|
||||
::onExplorerItemLongClick
|
||||
::onExplorerItemLongClick,
|
||||
sharedPrefs.getLong(ConstValues.THUMBNAIL_MAX_SIZE_KEY, ConstValues.DEFAULT_THUMBNAIL_MAX_SIZE)*1000,
|
||||
)
|
||||
explorerViewModel= ViewModelProvider(this).get(ExplorerViewModel::class.java)
|
||||
currentDirectoryPath = explorerViewModel.currentDirectoryPath
|
||||
@ -224,7 +225,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
explorerAdapter.explorerElements = explorerElements
|
||||
unselectAll()
|
||||
val sharedPrefsEditor = sharedPrefs.edit()
|
||||
sharedPrefsEditor.putString(ConstValues.sort_order_key, sortOrderValues[currentSortOrderIndex])
|
||||
sharedPrefsEditor.putString(ConstValues.SORT_ORDER_KEY, sortOrderValues[currentSortOrderIndex])
|
||||
sharedPrefsEditor.apply()
|
||||
}
|
||||
|
||||
|
@ -21,7 +21,7 @@ class GocryptfsDataSource(private val gocryptfsVolume: GocryptfsVolume, private
|
||||
}
|
||||
|
||||
override fun getUri(): Uri {
|
||||
return ConstValues.fakeUri
|
||||
return ConstValues.FAKE_URI
|
||||
}
|
||||
|
||||
override fun close() {
|
||||
|
@ -121,7 +121,7 @@ class ImageViewer: FileViewerActivity() {
|
||||
binding.imageButtonSlideshow.setOnClickListener {
|
||||
if (!slideshowActive){
|
||||
slideshowActive = true
|
||||
handler.postDelayed(slideshowNext, ConstValues.slideshow_delay)
|
||||
handler.postDelayed(slideshowNext, ConstValues.SLIDESHOW_DELAY)
|
||||
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
|
||||
hideUI.run()
|
||||
Toast.makeText(this, R.string.slideshow_started, Toast.LENGTH_SHORT).show()
|
||||
@ -204,7 +204,7 @@ class ImageViewer: FileViewerActivity() {
|
||||
if (!slideshowSwipe) { //reset slideshow delay if user swipes
|
||||
handler.removeCallbacks(slideshowNext)
|
||||
}
|
||||
handler.postDelayed(slideshowNext, ConstValues.slideshow_delay)
|
||||
handler.postDelayed(slideshowNext, ConstValues.SLIDESHOW_DELAY)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -27,7 +27,7 @@ abstract class MediaPlayer: FileViewerActivity() {
|
||||
private fun createMediaSource(filePath: String): MediaSource {
|
||||
val dataSourceFactory = GocryptfsDataSource.Factory(gocryptfsVolume, filePath)
|
||||
return ProgressiveMediaSource.Factory(dataSourceFactory, DefaultExtractorsFactory())
|
||||
.createMediaSource(MediaItem.fromUri(ConstValues.fakeUri))
|
||||
.createMediaSource(MediaItem.fromUri(ConstValues.FAKE_URI))
|
||||
}
|
||||
|
||||
private fun initializePlayer(){
|
||||
|
@ -88,7 +88,7 @@ object PathUtils {
|
||||
return result
|
||||
}
|
||||
|
||||
private val units = arrayOf("B", "kB", "MB", "GB", "TB")
|
||||
private val units = arrayOf("B", "kB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB")
|
||||
fun formatSize(size: Long): String {
|
||||
if (size <= 0) {
|
||||
return "0 B"
|
||||
|
@ -25,11 +25,11 @@ object Wiper {
|
||||
val buff = ByteArray(buff_size)
|
||||
Arrays.fill(buff, 0.toByte())
|
||||
val writes = ceil(size.toDouble() / buff_size).toInt()
|
||||
for (i in 0 until ConstValues.wipe_passes) {
|
||||
for (i in 0 until ConstValues.WIPE_PASSES) {
|
||||
for (j in 0 until writes) {
|
||||
os.write(buff)
|
||||
}
|
||||
if (i < ConstValues.wipe_passes - 1) {
|
||||
if (i < ConstValues.WIPE_PASSES - 1) {
|
||||
//reopening to flush and seek
|
||||
os.close()
|
||||
os = context.contentResolver.openOutputStream(uri)!!
|
||||
@ -57,11 +57,11 @@ object Wiper {
|
||||
val buff = ByteArray(buff_size)
|
||||
Arrays.fill(buff, 0.toByte())
|
||||
val writes = ceil(size.toDouble() / buff_size).toInt()
|
||||
for (i in 0 until ConstValues.wipe_passes) {
|
||||
for (i in 0 until ConstValues.WIPE_PASSES) {
|
||||
for (j in 0 until writes) {
|
||||
os.write(buff)
|
||||
}
|
||||
if (i < ConstValues.wipe_passes - 1) {
|
||||
if (i < ConstValues.WIPE_PASSES - 1) {
|
||||
//reopening to flush and seek
|
||||
os.close()
|
||||
os = FileOutputStream(file)
|
||||
|
9
app/src/main/res/drawable/icon_image_crossed_out.xml
Normal file
9
app/src/main/res/drawable/icon_image_crossed_out.xml
Normal file
@ -0,0 +1,9 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24">
|
||||
<path
|
||||
android:fillColor="?attr/colorAccent"
|
||||
android:pathData="M21.9,21.9l-8.49,-8.49l0,0L3.59,3.59l0,0L2.1,2.1L0.69,3.51L3,5.83V19c0,1.1 0.9,2 2,2h13.17l2.31,2.31L21.9,21.9zM5,18l3.5,-4.5l2.5,3.01L12.17,15l3,3H5zM21,18.17L5.83,3H19c1.1,0 2,0.9 2,2V18.17z"/>
|
||||
</vector>
|
@ -228,4 +228,8 @@
|
||||
<string name="copy_volume_notification">Copying volume…</string>
|
||||
<string name="hidden_volume_already_exists">A hidden volume with the same name already exists.</string>
|
||||
<string name="pdf_document">PDF document</string>
|
||||
<string name="thumbnail_max_size">Maximum size for thumbnails</string>
|
||||
<string name="thumbnail_max_size_summary">Maximum file size for which to load a thumbnail. Current value: %s</string>
|
||||
<string name="size_hint">Size (in KB)</string>
|
||||
<string name="invalid_number">Invalid number</string>
|
||||
</resources>
|
||||
|
@ -52,6 +52,11 @@
|
||||
android:title="@string/thumbnails"
|
||||
android:summary="@string/thumbnails_summary"/>
|
||||
|
||||
<Preference
|
||||
android:icon="@drawable/icon_image_crossed_out"
|
||||
android:key="thumbnail_max_size"
|
||||
android:title="@string/thumbnail_max_size"/>
|
||||
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="true"
|
||||
app:icon="@drawable/icon_folder_search"
|
||||
|
Loading…
Reference in New Issue
Block a user