Compare commits

...

15 Commits

Author SHA1 Message Date
82dda95211
Fix move feedback messages 2024-11-15 20:51:47 +01:00
40bed2db21
Match file viewer playlist recursivity with settings 2024-11-14 23:28:32 +01:00
f901495e41
Background file viewer playlist creation 2024-11-14 23:20:41 +01:00
07f5f8b5d9
FileOperationService: remove the correct notification helper on activity destroy 2024-11-13 11:52:42 +01:00
2d5f5a82c9
Wipe exported disk files on background threads 2024-11-11 19:32:18 +01:00
b477272d65
Close open FD after file export to avoid resource leaks 2024-11-11 17:24:35 +01:00
88bd746359
Add .avif extension 2024-10-29 11:58:43 +01:00
9872cab7c2
Add m4v extension 2024-09-24 20:16:34 +02:00
4aa211bca4
Fix gocryptfs_jni.c invalid string access 2024-08-22 23:01:35 +02:00
0a1406769b
Fix ABI versionCode offsets 2024-07-26 13:36:45 +02:00
a62f32e364
Update README.md & TODO.md 2024-07-25 17:18:29 +02:00
f865c864a2
Add changelog & Update screenshots & description 2024-07-25 17:16:57 +02:00
e804059b23
Add Hebrew translation
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2024-07-25 16:59:30 +02:00
solokot
bb821d5f41
Update Russian translation
Signed-off-by: Hardcore Sushi <hardcore.sushi@disroot.org>
2024-07-25 13:15:25 +02:00
6c0e20c68e
Disable usfExpose when disabling usfBackground 2024-07-24 17:57:20 +02:00
29 changed files with 517 additions and 131 deletions

View File

@ -28,41 +28,47 @@ Do not use this app with volumes containing sensitive data unless you know exact
- Unlocking volumes using fingerprint authentication - Unlocking volumes using fingerprint authentication
- Volume auto-locking when the app goes in background - Volume auto-locking when the app goes in background
_For upcoming features, see [TODO.md](https://forge.chapril.org/hardcoresushi/DroidFS/src/branch/master/TODO.md)._ For planned features, see [TODO.md](https://forge.chapril.org/hardcoresushi/DroidFS/src/branch/master/TODO.md).
# Unsafe features # Unsafe features
Some available features are considered risky and are therefore disabled by default. It is strongly recommended that you read the following documentation if you wish to activate one of these options. Some available features are considered risky and are therefore disabled by default. It is strongly recommended that you read the following documentation if you wish to activate one of these options.
<ul> <ul>
<li><h4>Allow screenshots:</h4> <li><b>Allow screenshots:</b>
Disable the secure flag of DroidFS activities. This will allow you to take screenshots from the app, but will also allow other apps to record the screen while using DroidFS. Disable the secure flag of DroidFS activities. This will allow you to take screenshots from the app, but will also allow other apps to record the screen while using DroidFS.
Note: apps with root access don't care about this flag: they can take screenshots or record the screen of any app without any permissions. Note: apps with root access don't care about this flag: they can take screenshots or record the screen of any app without any permissions.</li>
</li> <li><b>Allow exporting files:</b>
<li><h4>Allow exporting files:</h4>
Decrypt and write file to disk (external storage). Any app with storage permissions could access exported files. Decrypt and write file to disk (external storage). Any app with storage permissions could access exported files.</li>
</li> <li><b>Allow sharing files via the android share menu⁽¹⁾:</b>
<li><h4>Allow sharing files via the android share menu*:</h4>
Decrypt and share file with other apps. These apps could save and send the files thus shared. Decrypt and share file with other apps. These apps could save and send the files thus shared.</li>
</li> <li><b>Allow saving password hash using fingerprint:</b>
<li><h4>Allow saving password hash using fingerprint:</h4>
Generate an AES-256 GCM key in the Android Keystore (protected by fingerprint authentication), then use it to encrypt the volume password hash and store it to the DroidFS internal storage. This require Android v6.0+. If your device is not encrypted, extracting the encryption key with physical access may be possible. Generate an AES-256 GCM key in the Android Keystore (protected by fingerprint authentication), then use it to encrypt the volume password hash and store it to the DroidFS internal storage. This require Android v6.0+. If your device is not encrypted, extracting the encryption key with physical access may be possible.</li>
</li> <li><b>Disable volume auto-locking:</b> (previously called <i>"Keep volumes open when the app goes in background"</i>)
<li><h4>Keep volume open when the app goes in background:</h4>
Don't close the volume when you leave the app but keep running it in the background. Anyone going back to the activity could have access to the volume. Don't close open volumes when you leave the app. Anyone going back to the application could have access to open volumes. Cryptographic secrets are kept in memory for an undefined amount of time.</li>
</li> <li><b>Keep volumes open:</b>
<li><h4>Allow opening files with other applications*:</h4> (Different from the old <i>"Keep volumes open when the app goes in background"</i>. Yes it's confusing, sorry)
Decrypt and open file using external apps. These apps could save and send the files thus opened.
</li> Keep the app running as a [foreground service](https://developer.android.com/develop/background-work/services/foreground-services) to maintain volumes open, even when the app is removed from recent tasks.
<li><h4>Expose open volumes*:</h4>
Allow open volumes to be browsed in the system file explorer (<a href="https://developer.android.com/guide/topics/providers/document-provider">DocumentProvider</a> API). Encrypted files can then be selected from other applications, potentially with permanent access. This feature requires <i>"Keep volume open when the app goes in background"</i> to be enabled. This avoid the app from being killed by the system during file operations or while accessing exposed volumes, but this mean cryptographic secrets stay in memory for an undefined amount of time.</li>
</li> <li><b>Allow opening files with other applications⁽¹⁾:</b>
<li><h4>Grant write access:</h4>
Files opened with another applications can be modified by them. This applies to both previous unsafe features. Decrypt and open file using external apps. These apps could save and send the files thus opened.</li>
</li> <li><b>Expose open volumes⁽¹⁾:</b>
Allow open volumes to be browsed in the system file explorer (<a href="https://developer.android.com/guide/topics/providers/document-provider">DocumentProvider</a> API). Encrypted files can then be selected from other applications, potentially with permanent access. This feature requires <i>"Disable volume auto-locking"</i>, and works more reliably when <i>"Keep volumes open"</i> is also enabled.</li>
<li><b>Grant write access:</b>
Files opened with another applications can be modified by them. This applies to both previous unsafe features.</li>
</ul> </ul>
\* These features can work in two ways: temporarily writing the plain file to disk (DroidFS internal storage) or sharing it via memory. By default, DroidFS will choose to keep the file only in memory as it's more secure, but will fallback to disk export if the file is too large to be held in memory. This behavior can be changed with the *"Export method"* parameter in the settings. Please note that some applications require the file to be stored on disk, and therefore do not work with memory-exported files. ⁽¹⁾: These features can work in two ways: temporarily writing the plain file to disk (DroidFS internal storage) or sharing it via memory. By default, DroidFS will choose to keep the file only in memory as it's more secure, but will fallback to disk export if the file is too large to be held in memory. This behavior can be changed with the *"Export method"* parameter in the settings. Please note that some applications require the file to be stored on disk, and therefore do not work with memory-exported files.
# Download # Download
<a href="https://f-droid.org/packages/sushi.hardcore.droidfs"> <a href="https://f-droid.org/packages/sushi.hardcore.droidfs">
@ -90,20 +96,11 @@ F-Droid APKs should be signed with the F-Droid key. More details [here](https://
# Permissions # Permissions
DroidFS needs some permissions for certain features. However, you are free to deny them if you do not wish to use these features. DroidFS needs some permissions for certain features. However, you are free to deny them if you do not wish to use these features.
<ul> - **Read & write access to shared storage**: Required to access volumes located on shared storage.
<li><h4>Read & write access to shared storage:</h4> - **Biometric/Fingerprint hardware**: Required to encrypt/decrypt password hashes using a fingerprint protected key.
Required to access volumes located on shared storage. - **Camera**: Required to take encrypted photos or videos directly from the app.
</li> - **Record audio**: Required if you want sound on video recorded with DroidFS.
<li><h4>Biometric/Fingerprint hardware:</h4> - **Notifications**: Used to report file operations progress and notify about volumes kept open.
Required to encrypt/decrypt password hashes using a fingerprint protected key.
</li>
<li><h4>Camera:</h4>
Required to take encrypted photos or videos directly from the app.
</li>
<li><h4>Record audio:</h4>
Required if you want sound on video recorded with DroidFS.
</li>
</ul>
# Limitations # Limitations
DroidFS works as a wrapper around modified versions of the original encrypted container implementations ([libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) and [libcryfs](https://forge.chapril.org/hardcoresushi/libcryfs)). These programs were designed to run on standard x86 Linux systems: they access the underlying file system with file paths and syscalls. However, on Android, you can't access files from other applications using file paths. Instead, one has to use the [ContentProvider](https://developer.android.com/guide/topics/providers/content-providers) API. Obviously, neither Gocryptfs nor CryFS support this API. As a result, DroidFS cannot open volumes provided by other applications (such as cloud storage clients). If you want to synchronize your volumes on a cloud, the cloud application must synchronize the encrypted directory from disk. DroidFS works as a wrapper around modified versions of the original encrypted container implementations ([libgocryptfs](https://forge.chapril.org/hardcoresushi/libgocryptfs) and [libcryfs](https://forge.chapril.org/hardcoresushi/libcryfs)). These programs were designed to run on standard x86 Linux systems: they access the underlying file system with file paths and syscalls. However, on Android, you can't access files from other applications using file paths. Instead, one has to use the [ContentProvider](https://developer.android.com/guide/topics/providers/content-providers) API. Obviously, neither Gocryptfs nor CryFS support this API. As a result, DroidFS cannot open volumes provided by other applications (such as cloud storage clients). If you want to synchronize your volumes on a cloud, the cloud application must synchronize the encrypted directory from disk.

View File

@ -10,7 +10,7 @@ Here's a list of features that it would be nice to have in DroidFS. As this is a
- File associations editor - File associations editor
- Discovery before exporting - Discovery before exporting
- Making discovery before file operations optional - Making discovery before file operations optional
- Modifiable CryFS scrypt parameters - Modifiable scrypt parameters
- Alert dialog showing details of file operations - Alert dialog showing details of file operations
- Internal file browser to select volumes - Internal file browser to select volumes
@ -19,8 +19,6 @@ Here's a list of features that it would be nice to have in DroidFS. As this is a
- [fscrypt](https://www.kernel.org/doc/html/latest/filesystems/fscrypt.html): filesystem encryption at the kernel level - [fscrypt](https://www.kernel.org/doc/html/latest/filesystems/fscrypt.html): filesystem encryption at the kernel level
## Health ## Health
- F-Droid ABI split
- OpenSSL & FFmpeg as git submodules (useful for F-Droid)
- Remove all android:configChanges from AndroidManifest.xml - Remove all android:configChanges from AndroidManifest.xml
- More efficient thumbnails cache - More efficient thumbnails cache
- Guide for translators - Guide for translators

View File

@ -27,14 +27,14 @@ android {
jvmTarget = "17" jvmTarget = "17"
} }
def abiCodes = [ "arm64-v8a": 1, "armeabi-v7a": 2, "x86_64": 3, "x86": 4] def abiCodes = [ "x86": 1, "x86_64": 2, "armeabi-v7a": 3, "arm64-v8a": 4]
defaultConfig { defaultConfig {
applicationId "sushi.hardcore.droidfs" applicationId "sushi.hardcore.droidfs"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 34 targetSdkVersion 34
versionCode 36 versionCode 37
versionName "2.1.3" versionName "2.2.0"
splits { splits {
abi { abi {

View File

@ -9,6 +9,8 @@ import android.system.Os
import android.util.Log import android.util.Log
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.Compat import sushi.hardcore.droidfs.util.Compat
@ -86,9 +88,11 @@ class EncryptedFileProvider(context: Context) {
} }
override fun free() { override fun free() {
GlobalScope.launch(Dispatchers.IO) {
Wiper.wipe(file) Wiper.wipe(file)
} }
} }
}
class ExportedMemFile private constructor(path: String, private val file: MemFile) : class ExportedMemFile private constructor(path: String, private val file: MemFile) :
ExportedFile(path) { ExportedFile(path) {
@ -160,8 +164,10 @@ class EncryptedFileProvider(context: Context) {
exportedFile: ExportedFile, exportedFile: ExportedFile,
encryptedVolume: EncryptedVolume, encryptedVolume: EncryptedVolume,
): Boolean { ): Boolean {
val fd = exportedFile.open(ParcelFileDescriptor.MODE_WRITE_ONLY, false).fileDescriptor val pfd = exportedFile.open(ParcelFileDescriptor.MODE_WRITE_ONLY, false)
return encryptedVolume.exportFile(exportedFile.path, FileOutputStream(fd)) return encryptedVolume.exportFile(exportedFile.path, FileOutputStream(pfd.fileDescriptor)).also {
pfd.close()
}
} }
enum class Error { enum class Error {

View File

@ -4,8 +4,8 @@ import java.io.File
object FileTypes { object FileTypes {
private val FILE_EXTENSIONS = mapOf( private val FILE_EXTENSIONS = mapOf(
Pair("image", listOf("png", "jpg", "jpeg", "gif", "webp", "bmp", "heic")), Pair("image", listOf("png", "jpg", "jpeg", "gif", "avif", "webp", "bmp", "heic")),
Pair("video", listOf("mp4", "webm", "mkv", "mov")), Pair("video", listOf("mp4", "webm", "mkv", "mov", "m4v")),
Pair("audio", listOf("mp3", "ogg", "m4a", "wav", "flac", "opus")), Pair("audio", listOf("mp3", "ogg", "m4a", "wav", "flac", "opus")),
Pair("pdf", listOf("pdf")), Pair("pdf", listOf("pdf")),
Pair("text", listOf( Pair("text", listOf(

View File

@ -177,25 +177,32 @@ class SettingsActivity : BaseActivity() {
val switchExpose = findPreference<SwitchPreference>("usf_expose")!! val switchExpose = findPreference<SwitchPreference>("usf_expose")!!
val switchSafWrite = findPreference<SwitchPreference>("usf_saf_write")!! val switchSafWrite = findPreference<SwitchPreference>("usf_saf_write")!!
fun updateView(usfOpen: Boolean? = null, usfBackground: Boolean? = null, usfExpose: Boolean? = null) { fun onUsfBackgroundChanged(usfBackground: Boolean) {
val usfBackground = usfBackground ?: switchBackground.isChecked fun updateSwitchPreference(switch: SwitchPreference) = with (switch) {
switchKeepOpen.isEnabled = usfBackground isChecked = isChecked && usfBackground
switchExpose.isEnabled = usfBackground isEnabled = usfBackground
switchSafWrite.isEnabled = usfOpen ?: switchExternalOpen.isChecked || (usfBackground && usfExpose ?: switchExpose.isChecked) onPreferenceChangeListener?.onPreferenceChange(switch, isChecked)
} }
updateSwitchPreference(switchKeepOpen)
updateSwitchPreference(switchExpose)
}
onUsfBackgroundChanged(switchBackground.isChecked)
fun updateSafWrite(usfOpen: Boolean? = null, usfExpose: Boolean? = null) {
switchSafWrite.isEnabled = usfOpen ?: switchExternalOpen.isChecked || usfExpose ?: switchExpose.isChecked
}
updateSafWrite()
updateView()
switchBackground.setOnPreferenceChangeListener { _, checked -> switchBackground.setOnPreferenceChangeListener { _, checked ->
updateView(usfBackground = checked as Boolean) onUsfBackgroundChanged(checked as Boolean)
switchKeepOpen.isChecked = switchKeepOpen.isChecked && checked
true true
} }
switchExternalOpen.setOnPreferenceChangeListener { _, checked -> switchExternalOpen.setOnPreferenceChangeListener { _, checked ->
updateView(usfOpen = checked as Boolean) updateSafWrite(usfOpen = checked as Boolean)
true true
} }
switchExpose.setOnPreferenceChangeListener { _, checked -> switchExpose.setOnPreferenceChangeListener { _, checked ->
updateView(usfExpose = checked as Boolean) updateSafWrite(usfExpose = checked as Boolean)
VolumeProvider.notifyRootsChanged(requireContext()) VolumeProvider.notifyRootsChanged(requireContext())
true true
} }

View File

@ -258,9 +258,8 @@ class SelectPathFragment: Fragment() {
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show() .show()
} else { } else {
binding.editVolumeName.setText(path)
inputViewModel.showEditText = true inputViewModel.showEditText = true
updateUi(path) binding.editVolumeName.setText(path)
} }
} }

View File

@ -328,8 +328,8 @@ class ExplorerActivity : BaseExplorerActivity() {
activityScope.launch { activityScope.launch {
onTaskResult( onTaskResult(
fileOperationService.moveElements(volumeId, toMove, toClean), fileOperationService.moveElements(volumeId, toMove, toClean),
R.string.move_success,
R.string.move_failed, R.string.move_failed,
R.string.move_success,
) )
setCurrentPath(currentDirectoryPath) setCurrentPath(currentDirectoryPath)
} }

View File

@ -104,7 +104,9 @@ class FileOperationService : Service() {
activity.lifecycle.addObserver(object : DefaultLifecycleObserver { activity.lifecycle.addObserver(object : DefaultLifecycleObserver {
override fun onDestroy(owner: LifecycleOwner) { override fun onDestroy(owner: LifecycleOwner) {
activity.unbindService(serviceConnection) activity.unbindService(serviceConnection)
service.notificationPermissionHelpers.removeLast() // Could have been more efficient with a LinkedHashMap but the JDK implementation doesn't allow
// to access the latest element in O(1) unless using reflection
service.notificationPermissionHelpers.removeAll { it.activity == activity }
} }
}) })
activity.bindService( activity.bindService(

View File

@ -14,6 +14,8 @@ import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import sushi.hardcore.droidfs.BaseActivity import sushi.hardcore.droidfs.BaseActivity
import sushi.hardcore.droidfs.FileTypes import sushi.hardcore.droidfs.FileTypes
@ -21,7 +23,6 @@ import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.VolumeManagerApp import sushi.hardcore.droidfs.VolumeManagerApp
import sushi.hardcore.droidfs.explorers.ExplorerElement import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.filesystems.EncryptedVolume import sushi.hardcore.droidfs.filesystems.EncryptedVolume
import sushi.hardcore.droidfs.util.IntentUtils
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.finishOnClose import sushi.hardcore.droidfs.util.finishOnClose
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
@ -32,9 +33,8 @@ abstract class FileViewerActivity: BaseActivity() {
private lateinit var originalParentPath: String private lateinit var originalParentPath: String
private lateinit var windowInsetsController: WindowInsetsControllerCompat private lateinit var windowInsetsController: WindowInsetsControllerCompat
private var windowTypeMask = 0 private var windowTypeMask = 0
private var foldersFirst = true protected val playlist = mutableListOf<ExplorerElement>()
private var wasMapped = false private val playlistMutex = Mutex()
protected val mappedPlaylist = mutableListOf<ExplorerElement>()
protected var currentPlaylistIndex = -1 protected var currentPlaylistIndex = -1
private val isLegacyFullscreen = Build.VERSION.SDK_INT <= Build.VERSION_CODES.R private val isLegacyFullscreen = Build.VERSION.SDK_INT <= Build.VERSION_CODES.R
@ -46,7 +46,6 @@ abstract class FileViewerActivity: BaseActivity() {
intent.getIntExtra("volumeId", -1) intent.getIntExtra("volumeId", -1)
)!! )!!
finishOnClose(encryptedVolume) finishOnClose(encryptedVolume)
foldersFirst = sharedPrefs.getBoolean("folders_first", true)
windowInsetsController = WindowInsetsControllerCompat(window, window.decorView) windowInsetsController = WindowInsetsControllerCompat(window, window.decorView)
windowInsetsController.addOnControllableInsetsChangedListener { _, typeMask -> windowInsetsController.addOnControllableInsetsChangedListener { _, typeMask ->
windowTypeMask = typeMask windowTypeMask = typeMask
@ -131,48 +130,57 @@ abstract class FileViewerActivity: BaseActivity() {
} }
} }
protected fun createPlaylist() { protected suspend fun createPlaylist() {
if (!wasMapped){ playlistMutex.withLock {
encryptedVolume.recursiveMapFiles(originalParentPath)?.let { elements -> if (currentPlaylistIndex != -1) {
for (e in elements) { // playlist already initialized
if (e.isRegularFile) { return
if (FileTypes.isExtensionType(getFileType(), e.name) || filePath == e.fullPath) {
mappedPlaylist.add(e)
}
}
} }
withContext(Dispatchers.IO) {
if (sharedPrefs.getBoolean("map_folders", true)) {
encryptedVolume.recursiveMapFiles(originalParentPath)
} else {
encryptedVolume.readDir(originalParentPath)
}?.filterTo(playlist) { e ->
e.isRegularFile && (FileTypes.isExtensionType(getFileType(), e.name) || filePath == e.fullPath)
} }
val sortOrder = intent.getStringExtra("sortOrder") ?: "name" val sortOrder = intent.getStringExtra("sortOrder") ?: "name"
ExplorerElement.sortBy(sortOrder, foldersFirst, mappedPlaylist) val foldersFirst = sharedPrefs.getBoolean("folders_first", true)
//find current index ExplorerElement.sortBy(sortOrder, foldersFirst, playlist)
for ((i, e) in mappedPlaylist.withIndex()){ currentPlaylistIndex = playlist.indexOfFirst { it.fullPath == filePath }
if (filePath == e.fullPath){
currentPlaylistIndex = i
break
} }
} }
wasMapped = true
}
} }
protected fun playlistNext(forward: Boolean) { private fun updateCurrentItem() {
filePath = playlist[currentPlaylistIndex].fullPath
}
protected suspend fun playlistNext(forward: Boolean) {
createPlaylist() createPlaylist()
currentPlaylistIndex = if (forward) { currentPlaylistIndex = if (forward) {
(currentPlaylistIndex+1)%mappedPlaylist.size (currentPlaylistIndex + 1).mod(playlist.size)
} else { } else {
var x = (currentPlaylistIndex-1)%mappedPlaylist.size (currentPlaylistIndex - 1).mod(playlist.size)
if (x < 0) {
x += mappedPlaylist.size
} }
x updateCurrentItem()
}
filePath = mappedPlaylist[currentPlaylistIndex].fullPath
} }
protected fun refreshPlaylist() { protected suspend fun deleteCurrentFile(): Boolean {
mappedPlaylist.clear() createPlaylist() // ensure we know the current position in the playlist
wasMapped = false return if (encryptedVolume.deleteFile(filePath)) {
createPlaylist() playlist.removeAt(currentPlaylistIndex)
if (playlist.size != 0) {
if (currentPlaylistIndex == playlist.size) {
// deleted the last element of the playlist, go back to the first
currentPlaylistIndex = 0
}
updateCurrentItem()
}
true
} else {
false
}
} }
protected fun goBackToExplorer() { protected fun goBackToExplorer() {

View File

@ -12,10 +12,12 @@ import android.widget.Toast
import androidx.activity.addCallback import androidx.activity.addCallback
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.lifecycleScope
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import com.bumptech.glide.RequestBuilder import com.bumptech.glide.RequestBuilder
import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool import com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool
import com.bumptech.glide.load.resource.bitmap.BitmapTransformation import com.bumptech.glide.load.resource.bitmap.BitmapTransformation
import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.Constants import sushi.hardcore.droidfs.Constants
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.databinding.ActivityImageViewerBinding import sushi.hardcore.droidfs.databinding.ActivityImageViewerBinding
@ -105,17 +107,15 @@ class ImageViewer: FileViewerActivity() {
.keepFullScreen() .keepFullScreen()
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
createPlaylist() //be sure the playlist is created before deleting if there is only one image lifecycleScope.launch {
if (encryptedVolume.deleteFile(filePath)) { if (deleteCurrentFile()) {
playlistNext(true) if (playlist.size == 0) { // no more image left
refreshPlaylist()
if (mappedPlaylist.size == 0) { //deleted all images of the playlist
goBackToExplorer() goBackToExplorer()
} else { } else {
loadImage(true) loadImage(true)
} }
} else { } else {
CustomAlertDialogBuilder(this, theme) CustomAlertDialogBuilder(this@ImageViewer, theme)
.keepFullScreen() .keepFullScreen()
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(getString(R.string.remove_failed, fileName)) .setMessage(getString(R.string.remove_failed, fileName))
@ -123,6 +123,7 @@ class ImageViewer: FileViewerActivity() {
.show() .show()
} }
} }
}
.setNegativeButton(R.string.cancel, null) .setNegativeButton(R.string.cancel, null)
.setMessage(getString(R.string.single_delete_confirm, fileName)) .setMessage(getString(R.string.single_delete_confirm, fileName))
.show() .show()
@ -198,16 +199,18 @@ class ImageViewer: FileViewerActivity() {
rotateImage() rotateImage()
} }
private fun swipeImage(deltaX: Float, slideshowSwipe: Boolean = false){ private fun swipeImage(deltaX: Float, slideshowSwipe: Boolean = false) {
lifecycleScope.launch {
playlistNext(deltaX < 0) playlistNext(deltaX < 0)
loadImage(true) loadImage(true)
if (slideshowActive) { if (slideshowActive) {
if (!slideshowSwipe) { //reset slideshow delay if user swipes if (!slideshowSwipe) { // reset slideshow delay if user swipes
handler.removeCallbacks(slideshowNext) handler.removeCallbacks(slideshowNext)
} }
handler.postDelayed(slideshowNext, Constants.SLIDESHOW_DELAY) handler.postDelayed(slideshowNext, Constants.SLIDESHOW_DELAY)
} }
} }
}
private fun stopSlideshow(){ private fun stopSlideshow(){
slideshowActive = false slideshowActive = false

View File

@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.file_viewers
import android.view.WindowManager import android.view.WindowManager
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.MediaItem import androidx.media3.common.MediaItem
import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackException
import androidx.media3.common.Player import androidx.media3.common.Player
@ -11,6 +12,7 @@ import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.source.MediaSource import androidx.media3.exoplayer.source.MediaSource
import androidx.media3.exoplayer.source.ProgressiveMediaSource import androidx.media3.exoplayer.source.ProgressiveMediaSource
import androidx.media3.extractor.DefaultExtractorsFactory import androidx.media3.extractor.DefaultExtractorsFactory
import kotlinx.coroutines.launch
import sushi.hardcore.droidfs.Constants import sushi.hardcore.droidfs.Constants
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder
@ -39,12 +41,16 @@ abstract class MediaPlayer: FileViewerActivity() {
private fun initializePlayer(){ private fun initializePlayer(){
player = ExoPlayer.Builder(this).setSeekForwardIncrementMs(5000).build() player = ExoPlayer.Builder(this).setSeekForwardIncrementMs(5000).build()
bindPlayer(player) bindPlayer(player)
player.addMediaSource(createMediaSource(filePath))
lifecycleScope.launch {
createPlaylist() createPlaylist()
for (e in mappedPlaylist) { playlist.forEachIndexed { index, e ->
player.addMediaSource(createMediaSource(e.fullPath)) if (index != currentPlaylistIndex) {
player.addMediaSource(index, createMediaSource(e.fullPath))
}
}
} }
player.repeatMode = Player.REPEAT_MODE_ALL player.repeatMode = Player.REPEAT_MODE_ALL
player.seekToDefaultPosition(currentPlaylistIndex)
player.playWhenReady = true player.playWhenReady = true
player.addListener(object : Player.Listener{ player.addListener(object : Player.Listener{
override fun onVideoSizeChanged(videoSize: VideoSize) { override fun onVideoSizeChanged(videoSize: VideoSize) {
@ -67,11 +73,13 @@ abstract class MediaPlayer: FileViewerActivity() {
} }
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) { override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
if (player.repeatMode != Player.REPEAT_MODE_ONE) { if (player.repeatMode != Player.REPEAT_MODE_ONE && currentPlaylistIndex != -1) {
playlistNext(player.currentMediaItemIndex == (currentPlaylistIndex + 1) % mappedPlaylist.size) lifecycleScope.launch {
playlistNext(player.currentMediaItemIndex == (currentPlaylistIndex + 1) % player.mediaItemCount)
refreshFileName() refreshFileName()
} }
} }
}
}) })
player.prepare() player.prepare()
} }

View File

@ -28,7 +28,7 @@ object AndroidUtils {
/** /**
* A [Manifest.permission.POST_NOTIFICATIONS] permission helper. * A [Manifest.permission.POST_NOTIFICATIONS] permission helper.
* *
* Must be initialized before [Activity.onCreate]. * Must be initialized before [Activity.onCreate] finishes.
*/ */
class NotificationPermissionHelper<out A: AppCompatActivity>(val activity: A) { class NotificationPermissionHelper<out A: AppCompatActivity>(val activity: A) {
private var listener: ((Boolean) -> Unit)? = null private var listener: ((Boolean) -> Unit)? = null

View File

@ -196,6 +196,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1list_1dir(JNIEnv
jint sessionID, jstring jplain_dir) { jint sessionID, jstring jplain_dir) {
const char* plain_dir = (*env)->GetStringUTFChars(env, jplain_dir, NULL); const char* plain_dir = (*env)->GetStringUTFChars(env, jplain_dir, NULL);
const size_t plain_dir_len = strlen(plain_dir); const size_t plain_dir_len = strlen(plain_dir);
const char append_slash = plain_dir[plain_dir_len-1] != '/';
GoString go_plain_dir = {plain_dir, plain_dir_len}; GoString go_plain_dir = {plain_dir, plain_dir_len};
struct gcf_list_dir_return elements = gcf_list_dir(sessionID, go_plain_dir); struct gcf_list_dir_return elements = gcf_list_dir(sessionID, go_plain_dir);
@ -216,7 +217,7 @@ Java_sushi_hardcore_droidfs_filesystems_GocryptfsVolume_native_1list_1dir(JNIEnv
char* fullPath = malloc(sizeof(char) * (plain_dir_len + nameLen + 2)); char* fullPath = malloc(sizeof(char) * (plain_dir_len + nameLen + 2));
strcpy(fullPath, plain_dir); strcpy(fullPath, plain_dir);
if (plain_dir[-2] != '/') { if (append_slash) {
strcat(fullPath, "/"); strcat(fullPath, "/");
} }
strcat(fullPath, name); strcat(fullPath, name);

View File

@ -0,0 +1,52 @@
<resources>
<string-array name="sort_orders_entries">
<item>שם</item>
<item>גודל</item>
<item>תאריך</item>
<item>שם (בסדר יורד)</item>
<item>גודל (בסדר יורד)</item>
<item>תאריך (בסדר יורד)</item>
</string-array>
<string-array name="color_names">
<item>ירוק</item>
<item>אדום</item>
<item>כחול</item>
<item>צהוב</item>
<item>כתום</item>
<item>סגול</item>
<item>ורוד</item>
</string-array>
<string-array name="export_methods">
<item>אוטומטי (בהתאם לזיכרון הזמין)</item>
<item>ייצוא זמני לאחסון הפנימי (אמין אך עשוי להשאיר עקבות)</item>
<item>קובץ זיכרון (בטוח יותר אבל לא תמיד עובד)</item>
</string-array>
<!-- don't translate the following otherwise the app will crash -->
<string-array name="sort_orders_values">
<item>name</item>
<item>size</item>
<item>date</item>
<item>name_desc</item>
<item>size_desc</item>
<item>date_desc</item>
</string-array>
<string-array name="color_values">
<item>green</item>
<item>red</item>
<item>blue</item>
<item>yellow</item>
<item>orange</item>
<item>purple</item>
<item>pink</item>
</string-array>
<string-array name="export_methods_values">
<item>auto</item>
<item>disk</item>
<item>memory</item>
</string-array>
</resources>

View File

@ -0,0 +1,282 @@
<resources>
<string name="app_name">DroidFS</string>
<string name="create_volume">צור אמצעי אחסון</string>
<string name="open">פתח</string>
<string name="create">צור</string>
<string name="change_password">שנה סיסמא</string>
<string name="password">סיסמא</string>
<string name="import_files">ייבוא/להצפין קבצים</string>
<string name="import_folder">ייבוא/להצפין תיקיות</string>
<string name="discovering_files">סורק קבצים..</string>
<string name="mkdir">צור תיקייה</string>
<string name="dir_empty">ספריה ריקה</string>
<string name="warning">אזהרה !</string>
<string name="ask_lock_volume">האם אתה בטוח שברצונך לנעול אמצעי אחסון זה?</string>
<string name="ok">בסדר</string>
<string name="cancel">ביטול</string>
<string name="enter_folder_name">שם תיקייה:</string>
<string name="error">שגיאה</string>
<string name="error_filename_empty">.נא להזין שם</string>
<string name="error_mkdir">יצירת תיקייה נכשלה.</string>
<string name="success_import">יובא בהצלחה!</string>
<string name="success_import_msg">הקבצים שנבחרו יובאו בהצלחה.</string>
<string name="import_failed">ייבוא של %s נכשל.</string>
<string name="export_failed">ייצוא של %s נכשל.</string>
<string name="success_export">יוצא בהצלחה!</string>
<string name="remove_failed">המחיקה של %s נכשלה.</string>
<string name="passwords_mismatch">סיסמאות לא תואמות</string>
<string name="dir_not_empty">הספריה שנבחרה אינה ריקה</string>
<string name="create_volume_failed">יצירת אמצעי אחסון נכשלה</string>
<string name="open_volume_failed">פתיחה נכשלה</string>
<string name="share_chooser">שתף קובץ</string>
<string name="storage_perm_denied">הרשאת אחסון נדחתה</string>
<string name="storage_perm_denied_msg">DroidFS לא יכול לעבוד ללא הרשאת אחסון.</string>
<string name="get_size_failed">אחזור גודל הקובץ נכשל.</string>
<string name="parent_folder">תיקיית אב</string>
<string name="empty_volume_path">אנא הזן נתיב לאמצעי אחסון</string>
<string name="empty_volume_name">הזן שם לאמצעי אחסון</string>
<string name="external_open">פתח עם אפליקציה חיצונית</string>
<string name="single_delete_confirm">האם אתה בטוח שברצונך למחוק %s ?</string>
<string name="multiple_delete_confirm">האם אתה בטוח שברצנך למחוק פריטים אלה? %s </string>
<string name="location">מיקום: %s</string>
<string name="total_size">משקל כולל: %s</string>
<string name="import_from_other_volume">ייבא מאמצעי אחסון אחר</string>
<string name="read_file_failed">נכשל לפתוח את הקובץ הזה.</string>
<string name="volume">אמצעי אחסון: %s</string>
<string name="yes">כן</string>
<string name="no">לא</string>
<string name="ask_for_wipe">האם ברצונך למחוק את הקבצים המקוריים?</string>
<string name="wipe_failed">המחיקה נכשלה: %s</string>
<string name="wipe_successful">הקבצים נמחקו בהצלחה!</string>
<string name="rename">שנה שם</string>
<string name="rename_title">שם חדש:</string>
<string name="rename_failed">שינוי השם נכשל</string>
<string name="sort_order">מיין לפי:</string>
<string name="change_password_failed">הפעולה נכשלה. אנא בדוק את הסיסמה הישנה שלך.</string>
<string name="share_menu_label">הצפן עם DroidFS</string>
<string name="share_intent_parsing_failed">בקשת השיתוף נכשלה</string>
<string name="listdir_null_error_msg">אין אפשרות לגשת לספריה זו</string>
<string name="fingerprint_save_checkbox_text">שמור גיבוב של הסיסמא באמצעות טביעת אצבע</string>
<string name="fingerprint_instruction">אנא גע בחיישן טביעת האצבע</string>
<string name="illegal_block_size_exception">הרחבת גודל בלוק לא חוקית</string>
<string name="illegal_block_size_exception_msg">זה יכול לקרות אם הוספת טביעת אצבע חדשה, איפוס אחסון מגובב יכול לפתור את זה.</string>
<string name="reset_hash_storage">אפס אחסון מגובב</string>
<string name="MAC_verification_failed">חתימה/אימות מאק נכשל. חנות המפתחות של אנדרואיד או הקוד המגובב שנשמר השתנו. איפוס אחסון מגובב יכול לפתור את זה.</string>
<string name="hash_storage_reset">אפס אחסון מגובב</string>
<string name="encrypt_action_description">מצפין ושומר גיבוב של הסיסמא</string>
<string name="decrypt_action_description">מפענח גיבוב סיסמא.</string>
<string name="title_activity_settings">DroidFS הגדרות</string>
<string name="explorer">גלה</string>
<string name="settings_title_sort_order">מיון ברירת המחדל</string>
<string name="usf_decrypt">אפשר ייצוא קבצים/פענוח</string>
<string name="usf_share"> אפשר שיתוף קבצים דרך תפריט השיתוף של אנדרואיד </string>
<string name="usf_open">אפשר פתיחת קבצים עם יישומים אחרים </string>
<string name="usf_screenshot">אפשר צילומי מסך</string>
<string name="usf_fingerprint">אפשר שימרת גיבוב של הסיסמא באמצעות טביעת אצבע</string>
<string name="usf_volume_management">הגדרת אמצעי אחסון</string>
<string name="usf_keep_open">השאר את האמצעי אחסון פתוח כשהאפליקציה רצה ברקע</string>
<string name="unsafe_features">פיצרים לא בטוחים</string>
<string name="manage_unsafe_features">נהל פיצרים לא בטוחים</string>
<string name="manage_unsafe_features_summary">אפשר/מנע פיצרים לא בטוחים</string>
<string name="usf_home_warning_msg">DroidFS מנסה להיות מאובטח ככל האפשר,עם זאת אבטחה כרוכה לעיתים קרובת בחוסר נוחות. זה הסיבה ש DroidFS מציע פיצרים לא בטוחים שאתה יכול להפעיל /להשבית בהתאם לצרכים שלך .\n\n אזהרה: פיצרים אלה יכולים להיות לא בטוחים. אל תשתמש בהם אלא אם כן אתה יודע בידיוק מה אתה עושה. מומלץ מאוד לקרוא את הקובץ דוקמנטציה לפני שמפעילים אותם.</string>
<string name="see_unsafe_features">הראה פיצרים לא בטוחים</string>
<string name="open_as">פתח כ</string>
<string name="image">תמונה</string>
<string name="video">וידאו</string>
<string name="audio">שמע</string>
<string name="playing_failed">נכשל לנגן את הקובץ הזה: %s</string>
<string name="text">טקסט</string>
<string name="save_failed">השמירה נכשלה</string>
<string name="file_saved">קובץ נשמר!</string>
<string name="ask_save">הקובץ מכיל שינויים שלא נשמרו. האם ברצונך לשמור אותם לפני היציאה?</string>
<string name="save">שמור</string>
<string name="discard">אל תשמור</string>
<string name="word_wrap">התחל שורה משמאל לשפה האנגלית</string>
<string name="outofmemoryerror_msg">OutOfMemoryError: הקובץ גדול מדי כדי לטעון אותו בזכרון.</string>
<string name="new_file">צור קובץ חדש</string>
<string name="enter_file_name">שם הקובץ:</string>
<string name="file_creation_failed">נכשל ליצור קובץ חדש.</string>
<string name="loading">טוען</string>
<string name="loading_msg_create">יוצר אמצעי אחסון…</string>
<string name="loading_msg_change_password">משנה סיסמא…</string>
<string name="loading_msg_open">פותח אמצעי אחסון…</string>
<string name="loading_msg_export">מייצא קבצים…</string>
<string name="query_cursor_null_error_msg">לא הצלחתי לגשת לקובץ הזה.</string>
<string name="about">אודות</string>
<string name="github">GitHub</string>
<string name="github_summary">מאגר DroidFS ב- GitHub. קוד מקור, תיעוד, מעקב אחר באגים…</string>
<string name="gitea">Gitea</string>
<string name="gitea_summary">מאגר DroidFS בChapril Gitea. שלא כמו GitHub, Gitea היא תוכנה חינמית לחלוטין ומתארחת בעצמה. קוד מקור, תיעוד, מעקב אחר באגים…</string>
<string name="share">שתף</string>
<string name="decrypt_files">ייצא/פענח</string>
<string name="copy_failed">העתקת %s נכשלה.</string>
<string name="copy_success">העתקה בוצע בהצלחה.</string>
<string name="add">הוסף</string>
<string name="camera">מצלמה</string>
<string name="picture_save_success">התמונה נשמרה ל %s</string>
<string name="picture_save_failed">נכשל לשמור את התמונה הזאת.</string>
<string name="video_save_success">הסרטון נשמר ל %s</string>
<string name="file_overwrite_question">%s כבר קיים, האם ברצונך להחליף אותו?</string>
<string name="dir_overwrite_question">%s כבר קיים, האם ברצונך למזג את התוכן שלו ?</string>
<string name="enter_new_name">הזן שם חדש</string>
<string name="copy_menu_title">העתק</string>
<string name="move_failed">העברת %s נכשלה.</string>
<string name="move_success">העברה בוצעה בהצלחה!</string>
<string name="enter_timer_duration">הזן את משך זמן הטיימר (בשניות)</string>
<string name="path_error">לא הצלחתי לגשת לנתיב הזה.</string>
<string name="create_cant_write_error_msg">לDroidFS אין הרשאת כתיבה במיקום הזה, תנסה מיקום אחר</string>
<string name="add_cant_write_warning">ל- DroidFS אין הרשאת כתיבה לנתיב זה. מוסיף אמצעי אחסון עם הרשאת קריאה בלבד.</string>
<string name="sdcard_error_header">DroidFS יכול לכתוב רק לכרטיסי SD נשלפים תחת:</string>
<string name="sdcard_error_add_footer">מוסיף אמצעי אחסון עם הרשאת קריאה בלבד.</string>
<string name="sdcard_error_create_footer">אנא השתמש בספריית משנה של נתיב זה או באחסון פנימי.</string>
<string name="slideshow_stopped">הצגת השקופיות הופסקה</string>
<string name="slideshow_started">התחל מצגת</string>
<string name="ask_save_img_rotated">התמונה סובבה. האם ברצונך לשמור את השינויים הללו ולדרוס את התמונה המקורית?</string>
<string name="image_saved_successfully">השינויים בתמונה נשמרו בהצלחה.</string>
<string name="bitmap_compress_failed">דחיסת מפת הסיביות נכשלה.</string>
<string name="file_write_failed">כתיבת הקובץ נכשלה.</string>
<string name="error_not_a_volume">אמצעי אחסון מוצפן לא זוהה. אנא בדוק את הנתיב שנבחר.</string>
<string name="version">גרסא</string>
<string name="error_cipher_null">שגיאה: הסיסמא ריקה.</string>
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
<string name="key_permanently_invalidated_exception_msg">נראה שהוספת טביעת אצבע חדשה. הגיבוב של סיסמאות שמורות הפך לבלתי שמיש.</string>
<string name="usf_read_doc">עליך לקרוא אותו בעיון לפני הפעלת כל אחת מהאפשרויות הללו.</string>
<string name="usf_doc">דוקמנטציה של פיצרים לא בטוחים</string>
<string name="error_retrieving_filename">לא ניתן לאחזר את שם הקובץ עבור הקישור: %s</string>
<string name="hidden_volume">אמצעי אחסון נסתר</string>
<string name="error_slash_in_name">שם האמצעי אחסון לא יכול להכיל סלאשים</string>
<string name="hidden_volume_warning">אמצעי אחסון נסתרים מאוחסנים באחסון הפנימי של האפליקציה. אפליקציות אחרות לא יכולות לראות את אמצעי האחסון האלה ללא גישת שורש. עם זאת, אם תסיר את ההתקנה של DroidFS או תנקה את נתוני האפליקציה, כל אמצעי האחסון הנסתרים שלך יאבדו. הקפד לבצע גיבויים!</string>
<string name="camera_perm_needed">הרשאת מצלמה נדרשת כדי לצלם תמונות</string>
<string name="choose_resolution">בחר רזולוציה</string>
<string name="file_operations">פעולות קובץ</string>
<string name="file_op_copy_msg">מעתיק קבצים…</string>
<string name="file_op_import_msg">מייבא קבצים…</string>
<string name="file_op_export_msg">מייצא קבצים…</string>
<string name="file_op_move_msg">מעביר קבצים…</string>
<string name="file_op_wiping_msg">מוחק קבצים…</string>
<string name="folders_first">תיקיות קודם</string>
<string name="folders_first_summary">הראה תיקיות בתחילת הרשימה</string>
<string name="auto_fit_title">סיבוב אוטומטי של מסך נגן מדיה</string>
<string name="auto_fit_summary">סובב אוטומטית את המסך כך שיתאים למידות הווידאו</string>
<string name="open_tree_failed">לא נמצא סייר קבצים. אנא התקן אחד ונסה שוב.</string>
<string name="close_volume">סגור אמצעי אחסון</string>
<string name="sort_by">מיין לפי</string>
<string name="cut">גזור</string>
<string name="map_folders">מפה תיקיות</string>
<string name="map_folders_summary">מפה באופן רקורסיבי תיקיות כדי לחשב את הגדלים שלהן (עליך להשבית זאת בעת פתיחת אמצעי אחסון גדולים)</string>
<string name="camera_optimization">אופטימיזציה של מצלמה</string>
<string name="maximize_quality">איכות מקסימלית</string>
<string name="minimize_latency">איכות מינימלית</string>
<string name="auto">אוטומטי</string>
<string name="encryption_cipher_label">הצפנת צופן:</string>
<string name="theme">ערכת נושא</string>
<string name="thumbnails">תמונות ממוזערות</string>
<string name="thumbnails_summary">הצג תמונות וסרטונים ממוזערים</string>
<string name="seek_seconds_forward">+%d שניות</string>
<string name="seek_seconds_backward">-%d שניות</string>
<string name="add_volume">הוסף אמצעי אחסון</string>
<string name="pick_directory">בחר ספרייה</string>
<string name="volume_alread_saved">אמצעי אחסון כבר נשמר</string>
<string name="open_dialog_title">פותח %s:</string>
<string name="remove">הסר</string>
<string name="settings">הגדרות</string>
<string name="select_all">בחר הכל</string>
<string name="remove_fingerprint">הסר טביעת אצבע</string>
<string name="unrecoverable_key_exception_msg">%s. לא מצליח לטעון מפתח הצפנה</string>
<string name="unrecoverable_key_exception">UnrecoverableKeyException</string>
<string name="delete_hidden_volume_question">%s מוסתר, האם אתה רק רוצה לשכוח את הנתיב של האמצעי אחסון או גם למחוק את כל התוכן שלו?</string>
<string name="forget_only">שכח בלבד</string>
<string name="delete_volume">מחק אמצעי אחסון</string>
<string name="hidden_volume_description">אחסן את אמצעי האחסון באחסון הפנימי של DroidFS</string>
<string name="error_is_file">שגיאה: קובץ בשם זה כבר קיים</string>
<string name="volume_path_label">הזן נתיב לאמצעי אחסון:</string>
<string name="volume_name_label">הזן שם לאמצעי אחסון:</string>
<string name="volume_path_hint">נתיב אמצעי אחסון</string>
<string name="volume_name_hint">שם אמצעי אחסון</string>
<string name="password_label">הזן את הסיסמא של האמצעי אחסון:</string>
<string name="password_confirmation_label">חזור שנית על הסיסמא:</string>
<string name="password_confirmation_hint">אישור סיסמא</string>
<string name="password_hash_saved">הגיבוב של הסיסמא נשמר</string>
<string name="no_volumes_text">אין אמצעי אחסון שמורים, הוסף ע"י לחציה על הסימן +</string>
<string name="fingerprint_error_msg">לא ניתן להשתמש באימות טביעת אצבע: %s.</string>
<string name="keyguard_not_secure">מגן מקשים לא מאובטח</string>
<string name="no_hardware">לא נמצאה חומרה מתאימה</string>
<string name="hardware_unavailable">החומרה אינה זמינה</string>
<string name="no_fingerprint">אין טביעת אצבע שמורה</string>
<string name="unknown_error">שגיאה לא ידועה</string>
<string name="biometric_error">שגיאה ביומטרית: %s</string>
<string name="apply_to_all">החל את הבחירה הזו על כל אמצעי האחסון הנסתרים</string>
<string name="select_volume">בחר אמצעי אחסון</string>
<string name="current_password_label">הזן את סיסמאת אמצעי אחסון הנוכחי:</string>
<string name="current_password_hint">סיסמא נוכחית</string>
<string name="new_password_label">הזן את הסיסמא החדשה לאמצעי אחסון:</string>
<string name="new_password_hint">סיסמא חדשה</string>
<string name="new_password_confirmation_label">חזור שנית על הסיסמא החדשה:</string>
<string name="error_marshmallow_required">הפיצר הזה זמין רק למשתמשי אנדרואיד 6 (מרשמלו) ומעלה</string>
<string name="copy_hidden_volume">העתק לאחסון משותף</string>
<string name="copy_external_volume">צור עותק נסתר</string>
<string name="copy_volume_notification">מעתיק אמצעי אחסון…</string>
<string name="hidden_volume_already_exists">אמצעי אחסון נסתר עם אותו שם כבר קיים.</string>
<string name="pdf_document">מסמך PDF</string>
<string name="thumbnail_max_size">גודל מקסימלי עבור תמונות ממוזערות</string>
<string name="thumbnail_max_size_summary">גודל קובץ מקסימלי לטעינת תמונה ממוזערת. ערך נוכחי: %s</string>
<string name="size_hint">גודל (בקילו בייט)</string>
<string name="invalid_number">מספר לא תקין</string>
<string name="new_volume_name">שם אמצעי אחסון חדש:</string>
<string name="volume_rename_failed">שינוי שם אמצעי אחסון נכשל</string>
<string name="switch_display_layout">החלף פריסת תצוגה</string>
<string name="one_file">קובץ 1</string>
<string name="multiple_files">%d קבצים</string>
<string name="one_folder">1 תיקייה</string>
<string name="multiple_folders">%d תיקיות</string>
<string name="default_open">פתח אמצעי אחסון זה בעת הפעלת היישום</string>
<string name="remove_default_open">אל תפתח כברירת מחדל</string>
<string name="elements_selected">%d/%d נבחרו</string>
<string name="pin_passwords_title">פריסת מקלדת מספרים</string>
<string name="pin_passwords_summary">שימוש בפריסת מקלדת מספרים בעת הזנת סיסמאות אמצעי אחסון</string>
<string name="volume_type_label">סוג אמצעי אחסון:</string>
<string name="gocryptfs">Gocryptfs</string>
<string name="cryfs">CryFS</string>
<string name="gocryptfs_disabled">תמיכת Gocryptfs הושבתה</string>
<string name="cryfs_disabled">תמיכת CryFS הושבתה</string>
<string name="file_op_delete_msg">מוחק קבצים…</string>
<string name="volume_type">(%s)</string>
<string name="volume_type_read_only">(%s, קריאה בלבד)</string>
<string name="volume_type_inaccessible">(%s, לא נגיש)</string>
<string name="io_error">שגיאת קלט/פלט.</string>
<string name="use_fingerprint">השתמש בטביעת אצבע במקום בסיסמה הנוכחית</string>
<string name="remember_volume">זכור אמצעי אחסון</string>
<string name="open_volume">פתח אמצעי אחסון</string>
<string name="choose_existing_volume">אנא בחר אמצעי אחסון קיים</string>
<string name="volume_unlocked">אמצעי אחסון פתוח</string>
<string name="lock_volume">נעל אמצעי אחסון</string>
<string name="lock">נעל</string>
<string name="ux">חוויית משתמש</string>
<string name="theme_color">צבע ערכת נושא</string>
<string name="theme_color_summary">שנה את צבע הערכת נושא של היישום</string>
<string name="black_theme">שחור</string>
<string name="password_fallback">חזרה לסיסמא</string>
<string name="password_fallback_summary">בקש סיסמה כאשר אימות טביעת האצבע מבוטל</string>
<string name="unknown_error_code">קוד שגיאה לא ידוע: %d</string>
<string name="config_load_error">לא ניתן לטעון את קובץ התצורה. ודא שיש גישה לאמצעי אחסון.</string>
<string name="wrong_password">לא ניתן לפענח את קובץ התצורה. אנא בדוק את הסיסמה שלך.</string>
<string name="filesystem_id_changed">מזהה מערכת הקבצים בקובץ התצורה שונה מהפעם האחרונה שפתחנו אמצעי אחסון זה. פירוש הדבר יכול להיות מפני שתוקף החליף את מערכת הקבצים במערכת אחרת.</string>
<string name="inaccessible_base_dir">אמצעי האחסון לא קיים או שאינו נגיש.</string>
<string name="task_failed">המשימה נכשלה: %s</string>
<string name="usf_expose">חשוף אמצעי אחסון פתוחים</string>
<string name="usf_expose_summary">אפשר ליישומים אחרים לעיין באמצעי אחסון פתוחים כספקי מסמכים</string>
<string name="usf_saf_write">הענק הרשאת כתיבה</string>
<string name="usf_saf_write_summary">הענק הרשאת כתיבה בעת פתיחת קבצים עם יישומים אחרים</string>
<string name="saf">גישה לאחסון Framework</string>
<string name="tmp_export_failed">הייצוא נכשל: %s</string>
<string name="export_failed_create">לא הצלחתי ליצור קובץ ייצוא</string>
<string name="export_failed_export">ייצוא הקובץ נכשל</string>
<string name="export_mem">מייצא לזיכרון…</string>
<string name="export_disk">מייצא לאחסון פנימי…</string>
<string name="memfd_create_unsupported">הקרנל הנוכחי שלך לא תומכת ב-memfd_create(). תכונה זו דורשת גרסת קרנל מינימלי של %s.</string>
<string name="export_method">שיטת ייצוא</string>
<string name="export_method_summary">שיטת ייצוא קבצים. משמש לשיתוף, פתיחה חיצונית וגישה לקבצים חשופים.</string>
<string name="debug">דיבאג</string>
<string name="logcat_title">DroidFS Logcat</string>
<string name="logcat_saved">Logcat נשמר</string>
</resources>

View File

@ -267,4 +267,17 @@
<string name="debug">Отладка</string> <string name="debug">Отладка</string>
<string name="logcat_title">Журнал logcat DroidFS</string> <string name="logcat_title">Журнал logcat DroidFS</string>
<string name="logcat_saved">Журнал logcat сохранён</string> <string name="logcat_saved">Журнал logcat сохранён</string>
<string name="later">Позже</string>
<string name="notification_denied_msg">Разрешение на отображение уведомления не получено. Фоновые операции с файлами не будут видны. Это можно изменить в настройках разрешений приложения.</string>
<string name="keep_alive_notification_title">Служба поддержки работы</string>
<string name="keep_alive_notification_text">Один или несколько томов остаются открытыми.</string>
<string name="close_all">Закрыть все</string>
<string name="usf_background">Отключить автоблокировку томов</string>
<string name="usf_background_summary">Не блокировать тома автоматически при работе приложения в фоновом режиме</string>
<string name="usf_keep_open">Держать тома открытыми</string>
<string name="usf_keep_open_summary">Поддерживать постоянную работу приложения в фоновом режиме, чтобы тома оставались открытыми</string>
<string name="gocryptfs_details">Быстрый, но не скрывает размеры файлов и структуру папок</string>
<string name="cryfs_details">Медленнее, но защищает метаданные и предотвращает атаки замещения</string>
<string name="or">или</string>
<string name="enter_volume_path">Введите путь к тому</string>
</resources> </resources>

View File

@ -77,7 +77,7 @@
<string name="unsafe_features">Unsafe Features</string> <string name="unsafe_features">Unsafe Features</string>
<string name="manage_unsafe_features">Manage unsafe features</string> <string name="manage_unsafe_features">Manage unsafe features</string>
<string name="manage_unsafe_features_summary">Enable/Disable unsafe features</string> <string name="manage_unsafe_features_summary">Enable/Disable unsafe features</string>
<string name="usf_home_warning_msg">DroidFS try to be as secure as possible. However, security often involves lack of comfort. This is why DroidFS offer you additional unsafe features that you can enable/disable according to your needs.\n\nWarning: this features can be UNSAFE. Do not use them unless you know exactly what you are doing. It is highly recommended to read the documentation before enabling them.</string> <string name="usf_home_warning_msg">DroidFS aims to be as secure as possible. However, security often involves lack of comfort. This is why DroidFS offers you additional unsafe features that you can enable and disable according to your needs.\n\nWarning: this features can be UNSAFE. Do not use them unless you know exactly what you are doing. It is highly recommended to read the documentation before enabling them.</string>
<string name="see_unsafe_features">See unsafe features</string> <string name="see_unsafe_features">See unsafe features</string>
<string name="open_as">Open as</string> <string name="open_as">Open as</string>
<string name="image">Image</string> <string name="image">Image</string>

View File

@ -0,0 +1,8 @@
- Reworked UI for adding volumes
- New unsafe feature to keep the app running as a foreground service
- Allow choosing file export method
- Logcat viewer (for easier debugging)
- New turkish, chinese-simplified, and hebrew translations
- UX improvements
- Bug fixes
- Translations updates

View File

@ -7,6 +7,7 @@ Currently, DroidFS supports the following encrypted containers:
- Compatible with original encrypted volume implementations - Compatible with original encrypted volume implementations
- Internal support for video, audio, images, text and PDF files - Internal support for video, audio, images, text and PDF files
- Built-in camera to take on-the-fly encrypted photos and videos - Built-in camera to take on-the-fly encrypted photos and videos
- Ability to expose volumes to other applications
- Unlocking volumes using fingerprint authentication - Unlocking volumes using fingerprint authentication
- Volume auto-locking when the app goes in background - Volume auto-locking when the app goes in background
@ -15,6 +16,7 @@ Currently, DroidFS supports the following encrypted containers:
<b>Biometric/Fingerprint hardware:</b> needed to encrypt/decrypt password hashes using a fingerprint protected key. <b>Biometric/Fingerprint hardware:</b> needed to encrypt/decrypt password hashes using a fingerprint protected key.
<b>Camera:</b> required to take encrypted photos or videos directly from the app. <b>Camera:</b> required to take encrypted photos or videos directly from the app.
<b>Record audio:</b> required if you want sound on videos recorded with DroidFS. <b>Record audio:</b> required if you want sound on videos recorded with DroidFS.
<b>Notifications:</b> used to report file operations progress and notify about volumes kept open
All of these permissions can be denied if you don't want to use the corresponding feature. All of these permissions can be denied if you don't want to use the corresponding feature.

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 79 KiB

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

After

Width:  |  Height:  |  Size: 100 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 631 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 90 KiB

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.7 MiB

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 147 KiB

After

Width:  |  Height:  |  Size: 232 KiB