Compare commits

..

9 Commits

10 changed files with 380 additions and 365 deletions

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

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

@ -74,11 +74,12 @@
<string name="usf_screenshot">אפשר צילומי מסך</string> <string name="usf_screenshot">אפשר צילומי מסך</string>
<string name="usf_fingerprint">אפשר שימרת גיבוב של הסיסמא באמצעות טביעת אצבע</string> <string name="usf_fingerprint">אפשר שימרת גיבוב של הסיסמא באמצעות טביעת אצבע</string>
<string name="usf_volume_management">הגדרת אמצעי אחסון</string> <string name="usf_volume_management">הגדרת אמצעי אחסון</string>
<string name="unsafe_features">פיצ'רים לא בטוחים</string> <string name="usf_keep_open">השאר את האמצעי אחסון פתוח כשהאפליקציה רצה ברקע</string>
<string name="manage_unsafe_features">נהל פיצ'רים לא בטוחים</string> <string name="unsafe_features">פיצרים לא בטוחים</string>
<string name="manage_unsafe_features_summary">אפשר/מנע פיצ'רים לא בטוחים</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="usf_home_warning_msg">DroidFS מנסה להיות מאובטח ככל האפשר,עם זאת אבטחה כרוכה לעיתים קרובת בחוסר נוחות. זה הסיבה ש DroidFS מציע פיצרים לא בטוחים שאתה יכול להפעיל /להשבית בהתאם לצרכים שלך .\n\n אזהרה: פיצרים אלה יכולים להיות לא בטוחים. אל תשתמש בהם אלא אם כן אתה יודע בידיוק מה אתה עושה. מומלץ מאוד לקרוא את הקובץ דוקמנטציה לפני שמפעילים אותם.</string>
<string name="see_unsafe_features">הראה פיצ'רים לא בטוחים</string> <string name="see_unsafe_features">הראה פיצרים לא בטוחים</string>
<string name="open_as">פתח כ</string> <string name="open_as">פתח כ</string>
<string name="image">תמונה</string> <string name="image">תמונה</string>
<string name="video">וידאו</string> <string name="video">וידאו</string>
@ -140,7 +141,7 @@
<string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string> <string name="key_permanently_invalidated_exception">KeyPermanentlyInvalidatedException</string>
<string name="key_permanently_invalidated_exception_msg">נראה שהוספת טביעת אצבע חדשה. הגיבוב של סיסמאות שמורות הפך לבלתי שמיש.</string> <string name="key_permanently_invalidated_exception_msg">נראה שהוספת טביעת אצבע חדשה. הגיבוב של סיסמאות שמורות הפך לבלתי שמיש.</string>
<string name="usf_read_doc">עליך לקרוא אותו בעיון לפני הפעלת כל אחת מהאפשרויות הללו.</string> <string name="usf_read_doc">עליך לקרוא אותו בעיון לפני הפעלת כל אחת מהאפשרויות הללו.</string>
<string name="usf_doc">דוקמנטציה של פיצ'רים לא בטוחים</string> <string name="usf_doc">דוקמנטציה של פיצרים לא בטוחים</string>
<string name="error_retrieving_filename">לא ניתן לאחזר את שם הקובץ עבור הקישור: %s</string> <string name="error_retrieving_filename">לא ניתן לאחזר את שם הקובץ עבור הקישור: %s</string>
<string name="hidden_volume">אמצעי אחסון נסתר</string> <string name="hidden_volume">אמצעי אחסון נסתר</string>
<string name="error_slash_in_name">שם האמצעי אחסון לא יכול להכיל סלאשים</string> <string name="error_slash_in_name">שם האמצעי אחסון לא יכול להכיל סלאשים</string>
@ -196,7 +197,7 @@
<string name="password_confirmation_label">חזור שנית על הסיסמא:</string> <string name="password_confirmation_label">חזור שנית על הסיסמא:</string>
<string name="password_confirmation_hint">אישור סיסמא</string> <string name="password_confirmation_hint">אישור סיסמא</string>
<string name="password_hash_saved">הגיבוב של הסיסמא נשמר</string> <string name="password_hash_saved">הגיבוב של הסיסמא נשמר</string>
<string name="no_volumes_text">אין אמצעי אחסון שמורים, הוסף ע\"י לחציה על הסימן +</string> <string name="no_volumes_text">אין אמצעי אחסון שמורים, הוסף ע"י לחציה על הסימן +</string>
<string name="fingerprint_error_msg">לא ניתן להשתמש באימות טביעת אצבע: %s.</string> <string name="fingerprint_error_msg">לא ניתן להשתמש באימות טביעת אצבע: %s.</string>
<string name="keyguard_not_secure">מגן מקשים לא מאובטח</string> <string name="keyguard_not_secure">מגן מקשים לא מאובטח</string>
<string name="no_hardware">לא נמצאה חומרה מתאימה</string> <string name="no_hardware">לא נמצאה חומרה מתאימה</string>
@ -211,7 +212,7 @@
<string name="new_password_label">הזן את הסיסמא החדשה לאמצעי אחסון:</string> <string name="new_password_label">הזן את הסיסמא החדשה לאמצעי אחסון:</string>
<string name="new_password_hint">סיסמא חדשה</string> <string name="new_password_hint">סיסמא חדשה</string>
<string name="new_password_confirmation_label">חזור שנית על הסיסמא החדשה:</string> <string name="new_password_confirmation_label">חזור שנית על הסיסמא החדשה:</string>
<string name="error_marshmallow_required">הפיצ'ר הזה זמין רק למשתמשי אנדרואיד 6 (מרשמלו) ומעלה</string> <string name="error_marshmallow_required">הפיצר הזה זמין רק למשתמשי אנדרואיד 6 (מרשמלו) ומעלה</string>
<string name="copy_hidden_volume">העתק לאחסון משותף</string> <string name="copy_hidden_volume">העתק לאחסון משותף</string>
<string name="copy_external_volume">צור עותק נסתר</string> <string name="copy_external_volume">צור עותק נסתר</string>
<string name="copy_volume_notification">מעתיק אמצעי אחסון…</string> <string name="copy_volume_notification">מעתיק אמצעי אחסון…</string>
@ -230,7 +231,7 @@
<string name="multiple_folders">%d תיקיות</string> <string name="multiple_folders">%d תיקיות</string>
<string name="default_open">פתח אמצעי אחסון זה בעת הפעלת היישום</string> <string name="default_open">פתח אמצעי אחסון זה בעת הפעלת היישום</string>
<string name="remove_default_open">אל תפתח כברירת מחדל</string> <string name="remove_default_open">אל תפתח כברירת מחדל</string>
<string name="elements_selected" formatted="false">%d/%d נבחרו</string> <string name="elements_selected">%d/%d נבחרו</string>
<string name="pin_passwords_title">פריסת מקלדת מספרים</string> <string name="pin_passwords_title">פריסת מקלדת מספרים</string>
<string name="pin_passwords_summary">שימוש בפריסת מקלדת מספרים בעת הזנת סיסמאות אמצעי אחסון</string> <string name="pin_passwords_summary">שימוש בפריסת מקלדת מספרים בעת הזנת סיסמאות אמצעי אחסון</string>
<string name="volume_type_label">סוג אמצעי אחסון:</string> <string name="volume_type_label">סוג אמצעי אחסון:</string>
@ -278,18 +279,4 @@
<string name="debug">דיבאג</string> <string name="debug">דיבאג</string>
<string name="logcat_title">DroidFS Logcat</string> <string name="logcat_title">DroidFS Logcat</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>