Browse Source

Allow changing thumbnail max size

master
Hardcore Sushi 3 months ago
parent
commit
2ee7a5b871
Signed by: hardcoresushi
GPG Key ID: AFE384344A45E13A
  1. 75
      app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt
  2. 3
      app/src/main/java/sushi/hardcore/droidfs/GocryptfsVolume.kt
  3. 69
      app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt
  4. 2
      app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt
  5. 5
      app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt
  6. 2
      app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt
  7. 17
      app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt
  8. 2
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt
  9. 4
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt
  10. 2
      app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt
  11. 2
      app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt
  12. 8
      app/src/main/java/sushi/hardcore/droidfs/util/Wiper.kt
  13. 9
      app/src/main/res/drawable/icon_image_crossed_out.xml
  14. 4
      app/src/main/res/values/strings.xml
  15. 5
      app/src/main/res/xml/root_preferences.xml

75
app/src/main/java/sushi/hardcore/droidfs/ConstValues.kt

@ -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)
}
}

3
app/src/main/java/sushi/hardcore/droidfs/GocryptfsVolume.kt

@ -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
}

69
app/src/main/java/sushi/hardcore/droidfs/SettingsActivity.kt

@ -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
}
}
}
}

2
app/src/main/java/sushi/hardcore/droidfs/VolumeDatabase.kt

@ -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"

5
app/src/main/java/sushi/hardcore/droidfs/adapters/ExplorerElementAdapter.kt

@ -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) {

2
app/src/main/java/sushi/hardcore/droidfs/add_volume/CreateVolumeFragment.kt

@ -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 {

17
app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt

@ -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()
}

2
app/src/main/java/sushi/hardcore/droidfs/file_viewers/GocryptfsDataSource.kt

@ -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() {

4
app/src/main/java/sushi/hardcore/droidfs/file_viewers/ImageViewer.kt

@ -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)
}
}

2
app/src/main/java/sushi/hardcore/droidfs/file_viewers/MediaPlayer.kt

@ -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(){

2
app/src/main/java/sushi/hardcore/droidfs/util/PathUtils.kt

@ -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"

8
app/src/main/java/sushi/hardcore/droidfs/util/Wiper.kt

@ -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

@ -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>

4
app/src/main/res/values/strings.xml

@ -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>

5
app/src/main/res/xml/root_preferences.xml

@ -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…
Cancel
Save