Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 30 additions & 30 deletions TRANSLATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,34 +19,34 @@ See [Android Translations Converter](https://github.com/Crustack/android-transla
<!-- translations:start -->
| Language | Coverage |
|----------|----------|
| 🇺🇸 English | 100% (316/316) |
| 🇪🇸 Catalan | 20% (65/316) |
| 🇨🇿 Czech | 99% (313/316) |
| 🇩🇰 Danish | 21% (69/316) |
| 🇩🇪 German | 99% (313/316) |
| 🇬🇷 Greek | 22% (72/316) |
| 🇪🇸 Spanish | 99% (313/316) |
| 🇫🇷 French | 95% (301/316) |
| 🇭🇺 Hungarian | 20% (65/316) |
| 🇮🇩 Indonesian | 23% (75/316) |
| 🇮🇹 Italian | 92% (291/316) |
| 🇯🇵 Japanese | 23% (73/316) |
| 🇲🇲 Burmese | 28% (90/316) |
| 🇳🇴 Norwegian Bokmål | 33% (106/316) |
| 🇳🇱 Dutch | 67% (212/316) |
| 🇳🇴 Norwegian Nynorsk | 33% (106/316) |
| 🇵🇱 Polish | 94% (300/316) |
| 🇧🇷 Portuguese (Brazil) | 98% (312/316) |
| 🇵🇹 Portuguese (Portugal) | 22% (71/316) |
| 🇷🇴 Romanian | 95% (301/316) |
| 🇷🇺 Russian | 96% (305/316) |
| 🇸🇰 Slovak | 20% (65/316) |
| 🇸🇮 Slovenian | 34% (109/316) |
| 🇸🇪 Swedish | 19% (63/316) |
| 🇵🇭 Tagalog | 20% (65/316) |
| 🇹🇷 Turkish | 23% (73/316) |
| 🇺🇦 Ukrainian | 99% (314/316) |
| 🇻🇳 Vietnamese | 33% (107/316) |
| 🇨🇳 Chinese (Simplified) | 98% (312/316) |
| 🇹🇼 Chinese (Traditional) | 93% (294/316) |
| 🇺🇸 English | 100% (320/320) |
| 🇪🇸 Catalan | 20% (65/320) |
| 🇨🇿 Czech | 97% (313/320) |
| 🇩🇰 Danish | 21% (69/320) |
| 🇩🇪 German | 97% (313/320) |
| 🇬🇷 Greek | 22% (72/320) |
| 🇪🇸 Spanish | 97% (313/320) |
| 🇫🇷 French | 94% (301/320) |
| 🇭🇺 Hungarian | 20% (65/320) |
| 🇮🇩 Indonesian | 23% (75/320) |
| 🇮🇹 Italian | 90% (291/320) |
| 🇯🇵 Japanese | 22% (73/320) |
| 🇲🇲 Burmese | 28% (90/320) |
| 🇳🇴 Norwegian Bokmål | 33% (106/320) |
| 🇳🇱 Dutch | 66% (212/320) |
| 🇳🇴 Norwegian Nynorsk | 33% (106/320) |
| 🇵🇱 Polish | 93% (300/320) |
| 🇧🇷 Portuguese (Brazil) | 97% (312/320) |
| 🇵🇹 Portuguese (Portugal) | 22% (71/320) |
| 🇷🇴 Romanian | 94% (301/320) |
| 🇷🇺 Russian | 95% (305/320) |
| 🇸🇰 Slovak | 20% (65/320) |
| 🇸🇮 Slovenian | 34% (109/320) |
| 🇸🇪 Swedish | 19% (63/320) |
| 🇵🇭 Tagalog | 20% (65/320) |
| 🇹🇷 Turkish | 22% (73/320) |
| 🇺🇦 Ukrainian | 98% (314/320) |
| 🇻🇳 Vietnamese | 33% (107/320) |
| 🇨🇳 Chinese (Simplified) | 97% (312/320) |
| 🇹🇼 Chinese (Traditional) | 91% (294/320) |
<!-- translations:end -->
8 changes: 8 additions & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,14 @@
android:exported="false"
android:foregroundServiceType="mediaPlayback" />

<receiver
android:name=".utils.backup.CleanupMissingAttachmentsReceiver"
android:exported="false">
<intent-filter>
<action android:name="com.philkes.notallyx.action.CLEANUP_MISSING_ATTACHMENTS" />
</intent-filter>
</receiver>

</application>

</manifest>
2 changes: 2 additions & 0 deletions app/src/main/java/com/philkes/notallyx/NotallyXApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import com.philkes.notallyx.utils.backup.isEqualTo
import com.philkes.notallyx.utils.backup.modifiedNoteBackupExists
import com.philkes.notallyx.utils.backup.scheduleAutoBackup
import com.philkes.notallyx.utils.backup.updateAutoBackup
import com.philkes.notallyx.utils.checkForMigrations
import com.philkes.notallyx.utils.observeOnce
import com.philkes.notallyx.utils.security.UnlockReceiver
import java.util.concurrent.TimeUnit
Expand All @@ -52,6 +53,7 @@ class NotallyXApplication : Application(), Application.ActivityLifecycleCallback
registerActivityLifecycleCallbacks(this)
if (isTestRunner()) return
preferences = NotallyXPreferences.getInstance(this)
checkForMigrations()
if (preferences.useDynamicColors.value) {
if (DynamicColors.isDynamicColorAvailable()) {
DynamicColors.applyToActivitiesIfAvailable(this)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import com.philkes.notallyx.presentation.view.misc.ItemListener
import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.viewmodel.preference.NotesView
import com.philkes.notallyx.utils.getExternalImagesDirectory
import com.philkes.notallyx.utils.getCurrentImagesDirectory
import java.util.Collections
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
Expand Down Expand Up @@ -59,7 +59,7 @@ open class PickNoteActivity : LockedActivity<ActivityPickNoteBinding>(), ItemLis
labelTagsHiddenInOverview.value,
imagesHiddenInOverview.value,
),
application.getExternalImagesDirectory(),
application.getCurrentImagesDirectory(),
this@PickNoteActivity,
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ import com.philkes.notallyx.presentation.activity.LockedActivity
import com.philkes.notallyx.presentation.add
import com.philkes.notallyx.presentation.dp
import com.philkes.notallyx.presentation.setCancelButton
import com.philkes.notallyx.presentation.view.note.audio.AudioControlView
import com.philkes.notallyx.utils.audio.AudioPlayService
import com.philkes.notallyx.utils.audio.LocalBinder
import com.philkes.notallyx.utils.getExternalAudioDirectory
import com.philkes.notallyx.utils.getCurrentAudioDirectory
import com.philkes.notallyx.utils.getUriForFile
import com.philkes.notallyx.utils.wrapWithChooser
import java.io.File
Expand Down Expand Up @@ -180,7 +179,7 @@ class PlayAudioActivity : LockedActivity<ActivityPlayAudioBinding>() {
}

private fun share() {
val audioRoot = application.getExternalAudioDirectory()
val audioRoot = application.getCurrentAudioDirectory()
val file = if (audioRoot != null) File(audioRoot, audio.name) else null
if (file != null && file.exists()) {
val uri = getUriForFile(file)
Expand Down Expand Up @@ -209,7 +208,7 @@ class PlayAudioActivity : LockedActivity<ActivityPlayAudioBinding>() {
}

private fun saveToDevice() {
val audioRoot = application.getExternalAudioDirectory()
val audioRoot = application.getCurrentAudioDirectory()
val file = if (audioRoot != null) File(audioRoot, audio.name) else null
if (file != null && file.exists()) {
val intent =
Expand All @@ -231,7 +230,7 @@ class PlayAudioActivity : LockedActivity<ActivityPlayAudioBinding>() {
private fun writeAudioToUri(uri: Uri) {
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val audioRoot = application.getExternalAudioDirectory()
val audioRoot = application.getCurrentAudioDirectory()
val file = if (audioRoot != null) File(audioRoot, audio.name) else null
if (file != null && file.exists()) {
val output = contentResolver.openOutputStream(uri) as FileOutputStream
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,10 +27,11 @@ import com.philkes.notallyx.presentation.activity.note.EditActivity.Companion.EX
import com.philkes.notallyx.presentation.add
import com.philkes.notallyx.presentation.setCancelButton
import com.philkes.notallyx.presentation.view.note.image.ImageAdapter
import com.philkes.notallyx.utils.getExternalImagesDirectory
import com.philkes.notallyx.utils.SUBFOLDER_IMAGES
import com.philkes.notallyx.utils.getCurrentImagesDirectory
import com.philkes.notallyx.utils.getUriForFile
import com.philkes.notallyx.utils.resolveAttachmentFile
import com.philkes.notallyx.utils.wrapWithChooser
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -91,7 +92,7 @@ class ViewImageActivity : LockedActivity<ActivityViewImageBinding>() {
val images = ArrayList<FileAttachment>(original.size)
original.filterNotTo(images) { image -> deletedImages.contains(image) }

val mediaRoot = application.getExternalImagesDirectory()
val mediaRoot = application.getCurrentImagesDirectory()
val adapter = ImageAdapter(mediaRoot, images)
binding.MainListView.adapter = adapter
setupToolbar(binding, adapter)
Expand Down Expand Up @@ -192,8 +193,7 @@ class ViewImageActivity : LockedActivity<ActivityViewImageBinding>() {
}

private fun share(image: FileAttachment) {
val mediaRoot = application.getExternalImagesDirectory()
val file = if (mediaRoot != null) File(mediaRoot, image.localName) else null
val file = application.resolveAttachmentFile(SUBFOLDER_IMAGES, image.localName)
if (file != null && file.exists()) {
val uri = getUriForFile(file)
val intent =
Expand All @@ -214,8 +214,7 @@ class ViewImageActivity : LockedActivity<ActivityViewImageBinding>() {
}

private fun saveToDevice(image: FileAttachment) {
val mediaRoot = application.getExternalImagesDirectory()
val file = if (mediaRoot != null) File(mediaRoot, image.localName) else null
val file = application.resolveAttachmentFile(SUBFOLDER_IMAGES, image.localName)
if (file != null && file.exists()) {
val intent =
Intent(Intent.ACTION_CREATE_DOCUMENT)
Expand All @@ -233,14 +232,8 @@ class ViewImageActivity : LockedActivity<ActivityViewImageBinding>() {
private fun writeImageToUri(uri: Uri) {
lifecycleScope.launch {
withContext(Dispatchers.IO) {
val mediaRoot = application.getExternalImagesDirectory()
val file =
if (mediaRoot != null)
File(
mediaRoot,
requireNotNull(currentImage, { "currentImage is null" }).localName,
)
else null
val ci = requireNotNull(currentImage) { "currentImage is null" }
val file = application.resolveAttachmentFile(SUBFOLDER_IMAGES, ci.localName)
if (file != null && file.exists()) {
val output = contentResolver.openOutputStream(uri) as FileOutputStream
output.channel.truncate(0)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,42 +290,45 @@ class BaseNoteVH(
private fun setImages(images: List<FileAttachment>, mediaRoot: File?) {
binding.apply {
if (images.isNotEmpty() && !preferences.hideImages) {
ImageView.visibility = VISIBLE
Message.visibility = GONE

val image = images[0]
val file = if (mediaRoot != null) File(mediaRoot, image.localName) else null
if (file?.exists() == true) {
ImageView.visibility = VISIBLE
Glide.with(ImageView)
.load(file)
.centerCrop()
.transition(DrawableTransitionOptions.withCrossFade())
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(
object : RequestListener<Drawable> {

override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean,
): Boolean {
Message.visibility = VISIBLE
return false
}

Glide.with(ImageView)
.load(file)
.centerCrop()
.transition(DrawableTransitionOptions.withCrossFade())
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(
object : RequestListener<Drawable> {

override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean,
): Boolean {
Message.visibility = VISIBLE
return false
}

override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean,
): Boolean {
return false
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean,
): Boolean {
return false
}
}
}
)
.into(ImageView)
)
.into(ImageView)
} else {
ImageView.visibility = GONE
Message.visibility = VISIBLE
}
Comment on lines +296 to +331
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Prevent stale image leakage on load failure/missing file.

ImageView is made visible before Glide load; if load fails, only Message is shown, which can leave a recycled image visible (privacy/UX risk). Clear + hide the image on failure and when the file is missing.

🛠️ Proposed fix
 override fun onLoadFailed(
     e: GlideException?,
     model: Any?,
     target: Target<Drawable>?,
     isFirstResource: Boolean,
 ): Boolean {
     Message.visibility = VISIBLE
+    ImageView.visibility = GONE
+    Glide.with(ImageView).clear(ImageView)
     return false
 }
...
 } else {
+    Glide.with(ImageView).clear(ImageView)
     ImageView.visibility = GONE
     Message.visibility = VISIBLE
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (file?.exists() == true) {
ImageView.visibility = VISIBLE
Glide.with(ImageView)
.load(file)
.centerCrop()
.transition(DrawableTransitionOptions.withCrossFade())
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(
object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean,
): Boolean {
Message.visibility = VISIBLE
return false
}
Glide.with(ImageView)
.load(file)
.centerCrop()
.transition(DrawableTransitionOptions.withCrossFade())
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(
object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean,
): Boolean {
Message.visibility = VISIBLE
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean,
): Boolean {
return false
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean,
): Boolean {
return false
}
}
}
)
.into(ImageView)
)
.into(ImageView)
} else {
ImageView.visibility = GONE
Message.visibility = VISIBLE
}
if (file?.exists() == true) {
ImageView.visibility = VISIBLE
Glide.with(ImageView)
.load(file)
.centerCrop()
.transition(DrawableTransitionOptions.withCrossFade())
.diskCacheStrategy(DiskCacheStrategy.NONE)
.listener(
object : RequestListener<Drawable> {
override fun onLoadFailed(
e: GlideException?,
model: Any?,
target: Target<Drawable>?,
isFirstResource: Boolean,
): Boolean {
Message.visibility = VISIBLE
ImageView.visibility = GONE
Glide.with(ImageView).clear(ImageView)
return false
}
override fun onResourceReady(
resource: Drawable?,
model: Any?,
target: Target<Drawable>?,
dataSource: DataSource?,
isFirstResource: Boolean,
): Boolean {
return false
}
}
)
.into(ImageView)
} else {
Glide.with(ImageView).clear(ImageView)
ImageView.visibility = GONE
Message.visibility = VISIBLE
}
🤖 Prompt for AI Agents
In `@app/src/main/java/com/philkes/notallyx/presentation/view/main/BaseNoteVH.kt`
around lines 296 - 331, The ImageView is set VISIBLE before Glide loads which
can leave a stale image when a load fails or file is missing; update the Glide
request listener (in BaseNoteVH.kt where ImageView, Message and the
Glide.load(file) block are used) to call Glide.with(ImageView).clear(ImageView)
and set ImageView.visibility = GONE in onLoadFailed, ensure Message.visibility =
VISIBLE there, and in the else branch (file missing) also clear and hide
ImageView before showing Message; additionally, in onResourceReady set
Message.visibility = GONE and ensure ImageView stays VISIBLE so successful loads
and failures consistently update visibility and clear any recycled bitmaps.

if (images.size > 1) {
ImageViewMore.apply {
text = images.size.toString()
Expand All @@ -335,7 +338,7 @@ class BaseNoteVH(
ImageViewMore.visibility = GONE
}
} else {
ImageView.visibility = GONE
ImageLayout.visibility = GONE
Message.visibility = GONE
ImageViewMore.visibility = GONE
Glide.with(ImageView).clear(ImageView)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ImageVH(private val binding: RecyclerImageBinding) : RecyclerView.ViewHold

fun bind(file: File?) {
binding.SSIV.recycle()
if (file != null) {
if (file?.exists() == true) {
binding.Message.visibility = View.GONE
val source = ImageSource.uri(Uri.fromFile(file))
binding.SSIV.setImage(source)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ class PreviewImageVH(
}

fun bind(file: File?) {
if (file?.exists() == false) {
binding.Message.visibility = View.VISIBLE
return
}
binding.Message.visibility = View.GONE

Glide.with(binding.ImageView)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ import com.philkes.notallyx.utils.cancelNoteReminders
import com.philkes.notallyx.utils.copyToLarge
import com.philkes.notallyx.utils.deleteAttachments
import com.philkes.notallyx.utils.getBackupDir
import com.philkes.notallyx.utils.getExternalImagesDirectory
import com.philkes.notallyx.utils.getCurrentImagesDirectory
import com.philkes.notallyx.utils.getExternalMediaDirectory
import com.philkes.notallyx.utils.log
import com.philkes.notallyx.utils.migrateAllAttachments
import com.philkes.notallyx.utils.scheduleNoteReminders
import com.philkes.notallyx.utils.security.DecryptionException
import com.philkes.notallyx.utils.security.EncryptionException
Expand Down Expand Up @@ -136,7 +137,8 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {

val preferences = NotallyXPreferences.getInstance(app)

val imageRoot = app.getExternalImagesDirectory()
val imageRoot
get() = app.getCurrentImagesDirectory()

val importProgress = MutableLiveData<ImportProgress>()
val progress = MutableLiveData<Progress>()
Expand Down Expand Up @@ -275,6 +277,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
"Moving '${internalDatabaseFiles.map { it.name }}' to public '$targetDirectory' folder failed"
)
}
app.migrateAllAttachments(toPrivate = false)
}
savePreference(preferences.dataInPublicFolder, true)
callback?.invoke()
Expand All @@ -297,6 +300,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
"Moving public '${externalDatabaseFiles.map { it.name }}' to internal '$targetDirectory' folder failed"
)
}
app.migrateAllAttachments(toPrivate = true)
}
savePreference(preferences.dataInPublicFolder, false)
callback?.invoke()
Expand Down
Loading