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
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.philkes.notallyx.presentation.activity
import android.app.Activity
import android.app.KeyguardManager
import android.content.Intent
import android.database.sqlite.SQLiteBlobTooBigException
import android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_HW_NOT_PRESENT
import android.hardware.biometrics.BiometricPrompt.BIOMETRIC_ERROR_NO_BIOMETRICS
import android.os.Build
Expand All @@ -15,18 +16,28 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding
import com.google.android.material.color.DynamicColors
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.philkes.notallyx.NotallyXApplication
import com.philkes.notallyx.R
import com.philkes.notallyx.presentation.setupProgressDialog
import com.philkes.notallyx.presentation.showToast
import com.philkes.notallyx.presentation.viewmodel.BaseNoteModel
import com.philkes.notallyx.presentation.viewmodel.preference.NotallyXPreferences
import com.philkes.notallyx.presentation.viewmodel.preference.Theme
import com.philkes.notallyx.presentation.viewmodel.progress.MigrationProgress
import com.philkes.notallyx.utils.log
import com.philkes.notallyx.utils.secondsBetween
import com.philkes.notallyx.utils.security.showBiometricOrPinPrompt
import com.philkes.notallyx.utils.splitOversizedNotes
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext

abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {

Expand All @@ -40,6 +51,8 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setupGlobalExceptionHandler()
initViewModel()
notallyXApplication = (application as NotallyXApplication)
preferences = NotallyXPreferences.getInstance(notallyXApplication)
if (preferences.useDynamicColors.value) {
Expand All @@ -62,6 +75,53 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
}
}

open fun initViewModel() {
baseModel.startObserving()
}

private fun setupGlobalExceptionHandler() {
val previousHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler { thread, throwable ->
if (
throwable is SQLiteBlobTooBigException ||
throwable.cause is SQLiteBlobTooBigException
) {
lifecycleScope.launch {
EXCEPTION_HANDLER_MUTEX.withLock {
val time = System.currentTimeMillis()
if (!isExceptionAlreadyBeingHandled(time)) {
EXCEPTION_HANDLER_MUTEX_LAST_TIMESTAMP = time
val migrationProgress =
MutableLiveData<MigrationProgress>().apply {
setupProgressDialog(this@LockedActivity)
postValue(
MigrationProgress(
R.string.migration_splitting_notes,
indeterminate = true,
)
)
}
log(
TAG,
msg =
"SQLiteBlobTooBigException occurred, trying to fix broken notes...",
)
withContext(Dispatchers.IO) { application.splitOversizedNotes() }
migrationProgress.postValue(
MigrationProgress(R.string.migrating_data, inProgress = false)
)
}
}
}
} else {
previousHandler?.uncaughtException(thread, throwable)
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

private fun isExceptionAlreadyBeingHandled(time: Long): Boolean =
EXCEPTION_HANDLER_MUTEX_LAST_TIMESTAMP?.let { it.secondsBetween(time) < 20 } ?: false

override fun onResume() {
if (preferences.isLockEnabled) {
if (hasToAuthenticateWithBiometric()) {
Expand Down Expand Up @@ -153,4 +213,10 @@ abstract class LockedActivity<T : ViewBinding> : AppCompatActivity() {
}
} ?: false
}

companion object {
private const val TAG = "LockedActivity"
private val EXCEPTION_HANDLER_MUTEX = Mutex()
private var EXCEPTION_HANDLER_MUTEX_LAST_TIMESTAMP: Long? = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -131,9 +131,12 @@ class MainActivity : LockedActivity<ActivityMainBinding>() {
baseModel.progress.setupProgressDialog(this)
}

override fun initViewModel() {}

private fun checkForMigrations(savedInstanceState: Bundle?) {
// Run migrations first (blocking dialog), then proceed with initial navigation
val proceed: () -> Unit = {
baseModel.startObserving()
val fragmentIdToLoad = intent.getIntExtra(EXTRA_FRAGMENT_TO_OPEN, -1)
if (fragmentIdToLoad != -1) {
navController.navigate(fragmentIdToLoad, intent.extras)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,13 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {

lateinit var selectedExportMimeType: ExportMimeType

lateinit var labels: LiveData<List<String>>
lateinit var reminders: LiveData<List<NoteReminder>>
private var allNotes: LiveData<List<BaseNote>>? = null
var labels: LiveData<List<String>> = NotNullLiveData(mutableListOf())
var reminders: LiveData<List<NoteReminder>> = NotNullLiveData(mutableListOf())
private var allNotes: LiveData<List<BaseNote>>? = NotNullLiveData(mutableListOf())
private var allNotesObserver: Observer<List<BaseNote>>? = null
var baseNotes: Content? = null
var deletedNotes: Content? = null
var archivedNotes: Content? = null
var baseNotes: Content? = Content(MutableLiveData(), ::transform)
var deletedNotes: Content? = Content(MutableLiveData(), ::transform)
var archivedNotes: Content? = Content(MutableLiveData(), ::transform)

val folder = NotNullLiveData(Folder.NOTES)

Expand Down Expand Up @@ -149,7 +149,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
internal var showRefreshBackupsFolderAfterThemeChange = false
private var labelsHiddenObserver: Observer<Set<String>>? = null

init {
fun startObserving() {
NotallyDatabase.getDatabase(app).observeForever(::init)
folder.observeForever { newFolder ->
searchResults!!.fetch(keyword, newFolder, currentLabel)
Expand All @@ -166,7 +166,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
// colors = baseNoteDao.getAllColorsAsync()
reminders = baseNoteDao.getAllRemindersAsync()

allNotes?.removeObserver(allNotesObserver!!)
allNotesObserver?.let { allNotes?.removeObserver(it) }
allNotesObserver = Observer { list -> Cache.list = list }
allNotes = baseNoteDao.getAllAsync()
allNotes!!.observeForever(allNotesObserver!!)
Expand Down Expand Up @@ -897,7 +897,7 @@ class BaseNoteModel(private val app: Application) : AndroidViewModel(app) {
askForUriPermissions(backupFolderUri)
}
.show()
} catch (e: Exception) {
} catch (_: Exception) {
showRefreshBackupsFolderAfterThemeChange = false
disableBackups()
}
Comment on lines +900 to 903
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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check for Kotlin version declarations in gradle files and version catalogs
echo "=== Searching for Kotlin version in gradle files ==="
fd -e toml -e gradle -e "gradle.kts" | xargs grep -i "kotlin" 2>/dev/null | grep -iE "version|kotlin-version|kotlinVersion|kotlin-gradle-plugin" | head -40

Repository: Crustack/NotallyX

Length of output: 288


🏁 Script executed:

#!/bin/bash
# More comprehensive search for all Kotlin version configurations
echo "=== All Kotlin version-related declarations ==="
rg -i "kotlin.*version|version.*kotlin" --type gradle --type toml -A 2 -B 2 | head -60

Repository: Crustack/NotallyX

Length of output: 274


🏁 Script executed:

#!/bin/bash
# Broader search for Kotlin version
echo "=== All references to Kotlin 1.9 and 2.0 ==="
rg -i "kotlin.*1\.9|1\.9.*kotlin|kotlin.*2\.0|2\.0.*kotlin" | head -20

echo ""
echo "=== Content of main build files ==="
for file in build.gradle.kts app/build.gradle.kts gradle/libs.versions.toml; do
  if [ -f "$file" ]; then
    echo "File: $file"
    cat "$file" | grep -i "kotlin\|version" | head -15
    echo ""
  fi
done

Repository: Crustack/NotallyX

Length of output: 1295


Use of catch (_: Exception) is incompatible with this project's Kotlin version 1.9.0.

The discard syntax for exception parameters (catch (_: Exception)) was introduced in Kotlin 2.0. This project targets Kotlin 1.9.0, where this syntax will fail to compile. Either update the Kotlin version to 2.0 or later, or use a named parameter instead (e.g., catch (_: Exception)catch (e: Exception)).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt`
around lines 900 - 903, The catch block in BaseNoteModel.kt currently uses the
Kotlin 2.0 discard syntax `catch (_: Exception)` which won't compile under
Kotlin 1.9.0; change the catch to use a named parameter (for example `catch (e:
Exception)`) and keep the existing body that sets
showRefreshBackupsFolderAfterThemeChange = false and calls disableBackups(), or
alternatively bump the project Kotlin version to 2.0+ if you prefer to use
discard syntax.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private fun Application.moveAttachments(preferences: NotallyXPreferences) {
* end of each truncated note that points to the next note. The link text is included in the body
* and must also fit within the size limit.
*/
private suspend fun Application.splitOversizedNotes() {
suspend fun Application.splitOversizedNotes() {
log(
TAG,
"Running migration 2: Splitting notes exceeding the body size limit (limit: $MAX_BODY_CHAR_LENGTH characters)",
Expand All @@ -87,8 +87,14 @@ private suspend fun Application.splitOversizedNotes() {
e,
)
repaired += 1
truncateBodyAndFixSpans(dao, id)
dao.get(id)
try {
truncateBodyAndFixSpans(dao, id)
dao.get(id)
} catch (e: SQLiteBlobTooBigException) {
log(TAG, "Note (id: $id) could not be repaired. Deleting...", e)
dao.delete(id)
null
}
}
if (original == null) return@forEach
if (original.type != Type.NOTE) return@forEach
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.philkes.notallyx.utils
import android.util.Patterns
import java.util.Calendar
import java.util.Locale
import kotlin.math.abs

fun CharSequence.truncate(limit: Int): CharSequence {
return if (length > limit) {
Expand Down Expand Up @@ -110,3 +111,7 @@ fun now(): Calendar =
set(Calendar.SECOND, 0)
set(Calendar.MILLISECOND, 0)
}

typealias TimeMillis = Long

fun TimeMillis.secondsBetween(other: TimeMillis): Long = abs(this - other) / 1000
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.MutableLiveData
import com.philkes.notallyx.R
import com.philkes.notallyx.data.NotallyDatabase
import com.philkes.notallyx.data.dao.BaseNoteDao.Companion.MAX_BODY_CHAR_LENGTH
import com.philkes.notallyx.data.imports.ImportProgress
import com.philkes.notallyx.data.imports.ImportStage
import com.philkes.notallyx.data.model.Audio
Expand Down Expand Up @@ -121,8 +122,28 @@ suspend fun ContextWrapper.importZip(
SQLiteDatabase.openDatabase(dbFile.path, null, SQLiteDatabase.OPEN_READONLY)

val labelCursor = database.query("Label", null, null, null, null, null, null)
val baseNoteCursor = database.query("BaseNote", null, null, null, null, null, null)

val columns =
arrayOf(
"id",
"type",
"folder",
"color",
"title",
"pinned",
"timestamp",
"modifiedTimestamp",
"labels",
"SUBSTR(body, 1, ${MAX_BODY_CHAR_LENGTH}) AS body",
"spans",
"items",
"images",
"files",
"audios",
"reminders",
"viewMode",
)
val baseNoteCursor =
database.query("BaseNote", columns, null, null, null, null, null)
val labels = labelCursor.toList { cursor -> cursor.toLabel() }

var total = baseNoteCursor.count
Expand All @@ -137,7 +158,6 @@ suspend fun ContextWrapper.importZip(
importingBackup?.postValue(ImportProgress(counter++, total))
baseNote
}

delay(1000)

total =
Expand Down