Skip to content

Fix export duplicate file names and improve progress dialog#674

Merged
Crustack merged 1 commit intomainfrom
fix/export-notes
Oct 19, 2025
Merged

Fix export duplicate file names and improve progress dialog#674
Crustack merged 1 commit intomainfrom
fix/export-notes

Conversation

@Crustack
Copy link
Owner

@Crustack Crustack commented Oct 19, 2025

Fixes #637

Summary by CodeRabbit

  • New Features

    • Enhanced progress tracking with contextual labels for different operations including backup import/export, file addition, deletion, and note export.
    • Improved progress dialog display showing operation-specific messaging.
  • Improvements

    • Streamlined progress indicator implementation across the application for consistent user feedback during long-running tasks.
    • Better handling of export operations with more accurate progress reporting and user notifications.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 19, 2025

Walkthrough

This PR refactors progress tracking by converting the Progress class to abstract with a new titleId property, creating specialized subclasses (AddFilesProgress, BackupProgress, DeleteProgress, etc.), updating UI extensions to derive titles from Progress instances, and adding duplicate filename handling to prevent export collisions.

Changes

Cohort / File(s) Summary
Progress system abstraction
app/src/main/java/com/philkes/notallyx/presentation/view/misc/Progress.kt
Changed from open to abstract class; added titleId: Int as first constructor parameter to enable subclasses to define their own title resources.
Progress subclasses
app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/AddFilesProgress.kt, BackupProgress.kt, DeleteAttachmentProgress.kt, DeleteProgress.kt, ExportNotesProgress.kt
Added five new Progress subclasses, each binding a specific string resource (e.g., R.string.adding_files, R.string.export_backup) and forwarding constructor parameters to the abstract parent.
UI extension refactoring
app/src/main/java/com/philkes/notallyx/presentation/UiExtensions.kt
Removed titleId parameter from public methods setupProgressDialog(activity: Activity), setupProgressDialog(fragment: Fragment), and setupImportProgressDialog(fragment: Fragment). Private implementation now derives title from progress.titleId.
Activity and Fragment updates
app/src/main/java/com/philkes/notallyx/presentation/activity/main/MainActivity.kt, ...EditActivity.kt, ...SettingsFragment.kt
Updated call sites to invoke simplified setupProgressDialog() and setupImportProgressDialog() methods without explicit title resource IDs.
ViewModel consolidation
app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt
Replaced separate exportProgress and deletionProgress trackers with single unified progress: MutableLiveData<Progress>(). Updated export and deletion flows to use ExportNotesProgress and DeleteProgress subclasses.
Progress emission updates
app/src/main/java/com/philkes/notallyx/presentation/viewmodel/NotallyModel.kt
Updated addingFiles progress emissions to use AddFilesProgress instead of generic Progress.
Export utilities
app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt
Updated progress emissions to use BackupProgress; added private helper findFreeDuplicateFileName() to prevent filename collisions during export operations.
Attachment deletion utilities
app/src/main/java/com/philkes/notallyx/utils/IOExtensions.kt
Updated progress emissions in ContextWrapper.deleteAttachments() to use DeleteAttachmentProgress.
String resources
app/src/main/res/values/strings.xml
Added plural resource exported_notes with singular and plural forms for export completion feedback.

Sequence Diagram

sequenceDiagram participant UI as Activity/Fragment participant Progress as Progress LiveData participant Dialog as Progress Dialog participant ViewModel as ViewModel UI->>ViewModel: trigger action (export/delete) ViewModel->>Progress: post(ExportNotesProgress/DeleteProgress/...) Progress->>Dialog: observe titleId from progress Dialog->>Dialog: update title using progress.titleId Dialog->>Dialog: render progress bar ViewModel->>Progress: post(updated progress state) Progress->>Dialog: notify with new titleId Dialog->>Dialog: update display Note over ViewModel: Title now derived from Progress subclass<br/>instead of passed as separate parameter 
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

The changes involve a consistent refactoring pattern across multiple files (abstract class introduction and subclass creation), but include heterogeneous modifications spanning UI extensions, view models, utilities, and resources. The duplicate filename prevention logic in export utilities requires careful verification against the linked issue (#637), and API signature changes across public methods necessitate thorough tracing of call sites.

Possibly related PRs

Poem

🐰 Progress now has titles baked right in,
Each subclass worn like a specialized skin—
No more passing strings from place to place,
The title finds its destined space.
Duplicates prevented, exports now flow true,
A leaps-and-bounds improvement, through and through! 🎉

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Out of Scope Changes Check ⚠️ Warning While the duplicate file name fix directly addresses issue #637, the pull request includes substantial refactoring beyond the scope of the linked issue. The progress dialog improvements involve making Progress abstract, adding a titleId property, creating five new Progress subclasses (AddFilesProgress, BackupProgress, DeleteProgress, ExportNotesProgress, DeleteAttachmentProgress), refactoring UiExtensions.kt setupProgressDialog signatures, and updating multiple activity and fragment files to use the new API. These changes, while potentially beneficial for code quality, are not required to resolve the export collision issue identified in #637 and represent scope creep beyond the specific problem being fixed. Consider separating the duplicate file name fix (which is essential for #637) into a focused pull request, and create a separate PR for the progress dialog refactoring improvements. This would allow each change to be reviewed and merged independently, making it clearer which changes are required to fix the reported issue versus infrastructure improvements. If the progress refactoring is intended as a necessary prerequisite for the export fix, this dependency should be clearly documented in the PR description.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Fix export duplicate file names and improve progress dialog" accurately reflects the main changes in the changeset. The first part directly corresponds to the addition of the findFreeDuplicateFileName helper function in ExportExtensions.kt, which addresses the core issue of missing exports due to file name collisions. The second part aligns with the extensive refactoring of the progress dialog system, including making Progress abstract, adding specialized subclasses, and updating UI extension methods. The title is clear, specific, and concise without unnecessary noise.
Linked Issues Check ✅ Passed The pull request addresses the core requirement of issue #637 regarding missing note exports during batch operations. The key fix is the implementation of findFreeDuplicateFileName in ExportExtensions.kt, which provides proper duplicate file name handling in exportPdfFile and exportPlainTextFile. This directly resolves the suspected cause identified in the issue—that notes were not being exported due to file name collisions when multiple notes share the same title. The addition of the exported_notes plurals resource and updated progress tracking for export operations provide supporting infrastructure for the export functionality.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/export-notes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Crustack Crustack merged commit dbdd537 into main Oct 19, 2025
1 check was pending
@Crustack Crustack deleted the fix/export-notes branch October 19, 2025 13:17
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (6)
app/src/main/res/values/strings.xml (1)

156-159: Plural placeholders and casing consistency

Use %1$d for counts and match casing with existing plurals (“note/notes”). Suggested change:

- <plurals name="exported_notes"> - <item quantity="one">Exported %1$s Note</item> - <item quantity="other">Exported %1$s Notes</item> - </plurals> + <plurals name="exported_notes"> + <item quantity="one">Exported %1$d note</item> + <item quantity="other">Exported %1$d notes</item> + </plurals>
app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/ExportNotesProgress.kt (1)

6-11: Prefer gerund progress title (“Exporting notes”)

To match “Adding files”/“Deleting files”, use a gerund. If acceptable, add a new string and switch:

- ) : Progress(R.string.export, current, total, inProgress, indeterminate) + ) : Progress(R.string.exporting_notes, current, total, inProgress, indeterminate)

Add to strings.xml:

<string name="exporting_notes">Exporting notes</string>
app/src/main/java/com/philkes/notallyx/presentation/view/misc/Progress.kt (1)

3-9: Annotate titleId and (optionally) validate bounds

Add @stringres for compile-time checking; optional guards keep state sane.

 package com.philkes.notallyx.presentation.view.misc -abstract class Progress( - val titleId: Int, +import androidx.annotation.StringRes + +abstract class Progress( + @StringRes val titleId: Int, val current: Int = 0, val total: Int = 0, val inProgress: Boolean = true, val indeterminate: Boolean = false, )

Optional init checks:

init { require(current >= 0) { "current must be >= 0" } require(total >= 0) { "total must be >= 0" } if (!indeterminate) require(current <= total) { "current must be <= total" } }
app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/BackupProgress.kt (1)

6-11: Use “Exporting backup” for in-progress title

Align with gerund style used elsewhere:

-) : Progress(R.string.export_backup, current, total, inProgress, indeterminate) +) : Progress(R.string.exporting_backup, current, total, inProgress, indeterminate)
app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt (1)

624-641: LGTM with optional optimization suggestion.

The duplicate filename resolution logic is correct and handles the recursive export calls properly. The function returns the base filename without extension, which the callers then use appropriately.

Optional optimization

For large folders, listing all files on each duplicate check could be expensive. Consider caching the file list if exporting many notes:

private fun findFreeDuplicateFileName( folder: DocumentFile, fileName: String, fileExtension: String, existingNames: Set<String>? = null, ): String { val names = existingNames ?: folder.listFiles().mapNotNull { it.name }.toSet() if ("$fileName.$fileExtension" !in names) return fileName var index = 0 var newName: String do { index++ newName = "$fileName ($index).$fileExtension" } while (newName in names) return "$fileName ($index)" }

However, the current implementation is acceptable for typical export volumes.

app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/DeleteAttachmentProgress.kt (1)

6-11: Consider whether the open modifier is necessary.

The implementation correctly follows the new Progress pattern with a dedicated titleId. However, the open modifier allows subclassing. If no subclasses of DeleteAttachmentProgress are planned or exist, consider making this class final (removing open) to prevent unintended inheritance.

Apply this diff if no subclasses exist or are planned:

-open class DeleteAttachmentProgress( +class DeleteAttachmentProgress( current: Int = 0, total: Int = 0, inProgress: Boolean = true, indeterminate: Boolean = false, ) : Progress(R.string.deleting_files, current, total, inProgress, indeterminate)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between da7741a and e7902bb.

⛔ Files ignored due to path filters (1)
  • app/translations.xlsx is excluded by !**/*.xlsx
📒 Files selected for processing (16)
  • app/src/main/java/com/philkes/notallyx/data/imports/ImportProgress.kt (2 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/UiExtensions.kt (2 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/activity/main/MainActivity.kt (2 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/view/misc/Progress.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt (7 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/viewmodel/NotallyModel.kt (3 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/AddFilesProgress.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/BackupProgress.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/DeleteAttachmentProgress.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/DeleteProgress.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/ExportNotesProgress.kt (1 hunks)
  • app/src/main/java/com/philkes/notallyx/utils/IOExtensions.kt (3 hunks)
  • app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt (11 hunks)
  • app/src/main/res/values/strings.xml (1 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt (1)
app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt (2)
  • exportPdfFile (509-566)
  • exportPlainTextFile (568-622)
🔇 Additional comments (17)
app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/DeleteProgress.kt (1)

6-11: LGTM — consistent progress title

Using R.string.deleting_files aligns with existing gerund progress strings. No issues.

app/src/main/java/com/philkes/notallyx/presentation/viewmodel/BaseNoteModel.kt (3)

8-8: LGTM!

The new imports are necessary for the unified progress tracking and PDF export functionality.

Also applies to: 56-57


129-129: LGTM!

Consolidating multiple progress trackers into a single unified progress object simplifies the API while maintaining type safety through specialized Progress subclasses.


529-546: LGTM!

The deletion flow properly uses DeleteProgress for progress tracking and correctly passes it through to deleteAttachments.

app/src/main/java/com/philkes/notallyx/utils/backup/ExportExtensions.kt (3)

50-50: LGTM!

Consistent use of BackupProgress throughout export operations provides better type safety and clearer progress semantics.

Also applies to: 268-268, 295-295, 327-329, 341-341, 449-449, 553-553, 619-619


509-566: LGTM with minor TOCTOU consideration.

The duplicate filename handling correctly uses findFreeDuplicateFileName and recursively retries with a new name.

Note: There's a potential time-of-check-time-of-use (TOCTOU) gap between checking for duplicates (line 522) and creating the file (line 537). However, this is acceptable because:

  1. Export operations are typically single-threaded per user
  2. DocumentFile.createFile likely has internal collision handling
  3. The recursive approach will eventually find a free name

If you encounter issues with concurrent exports to the same folder, consider using a synchronized block or checking the result of createFile for null.


775-787: LGTM!

Using else instead of explicitly listing TXT, JSON, HTML makes the code more maintainable and ensures future MIME types are handled correctly.

app/src/main/java/com/philkes/notallyx/presentation/activity/main/MainActivity.kt (1)

50-50: LGTM!

Setting up the progress dialog in onCreate ensures it's ready to display progress for export, delete, and other operations throughout the activity lifecycle.

Also applies to: 132-132

app/src/main/java/com/philkes/notallyx/presentation/activity/note/EditActivity.kt (1)

1205-1205: LGTM!

Removing the titleId parameter is correct since AddFilesProgress now carries its own title resource (R.string.adding_files). This aligns with the broader Progress refactoring.

app/src/main/java/com/philkes/notallyx/data/imports/ImportProgress.kt (1)

3-3: LGTM!

Adding the titleId parameter to the Progress superclass constructor aligns ImportProgress with the unified progress hierarchy.

Also applies to: 12-12

app/src/main/java/com/philkes/notallyx/utils/IOExtensions.kt (1)

17-17: LGTM!

Consistent use of DeleteAttachmentProgress for attachment deletion operations provides better type safety and clearer progress semantics.

Also applies to: 121-121, 138-138, 144-144

app/src/main/java/com/philkes/notallyx/presentation/viewmodel/NotallyModel.kt (1)

42-42: LGTM!

Using AddFilesProgress for file addition operations provides better type safety and progress tracking semantics. The progress values correctly track file addition: initial state (0, total), per-file progress (index+1, total), and completion (inProgress=false).

Also applies to: 160-160, 170-170, 173-173

app/src/main/java/com/philkes/notallyx/presentation/viewmodel/progress/AddFilesProgress.kt (1)

1-11: LGTM!

The AddFilesProgress class is well-defined and follows the pattern of other specialized Progress subclasses. The default parameters are appropriate, and the fixed titleId of R.string.adding_files correctly represents file addition operations.

app/src/main/java/com/philkes/notallyx/presentation/activity/main/fragment/settings/SettingsFragment.kt (1)

403-403: LGTM! Progress dialog setup simplified.

The refactoring correctly removes the explicit title parameter, now deriving it from the Progress instance's titleId property. This improves encapsulation and consistency across the codebase.

app/src/main/java/com/philkes/notallyx/presentation/UiExtensions.kt (3)

414-431: LGTM! Import progress dialog correctly uses specialized rendering.

The setupImportProgressDialog method properly delegates to the base setupProgressDialog with a custom renderProgress lambda that handles the ImportStage enum. The title is correctly derived from progress.titleId in the underlying implementation.


447-485: Verification confirmed: Progress class is abstract with required titleId property.

The Progress class at app/src/main/java/com/philkes/notallyx/presentation/view/misc/Progress.kt is correctly defined as abstract with titleId: Int as a constructor parameter (line 4). The code at line 462 correctly uses progress.titleId to set the dialog title. All assumptions underlying this refactoring are valid.


402-412: All call sites have been correctly updated for the breaking API change.

The verification confirms the refactoring is complete and consistent:

  • External call sites (EditActivity.kt:1205, MainActivity.kt:132) use the new single-parameter signature
  • Internal delegation calls correctly invoke the new 3-parameter private method signature without titleId
  • No old 4-parameter calls with titleId remain in the codebase
  • Title is properly derived from progress.titleId at line 462
Comment on lines +413 to +443
ExportMimeType.PDF -> {
for (note in notes) {
exportPdfFile(
app,
note,
selectedExportMimeType,
DocumentFile.fromTreeUri(app, folderUri)!!,
progress = exportProgress,
progress = progress,
counter = counter,
total = notes.size,
pdfPrintListener =
object : PdfPrintListener {
override fun onSuccess(file: DocumentFile) {
actionMode.close(true)
progress.postValue(ExportNotesProgress(inProgress = false))
val message =
app.getQuantityString(
R.plurals.exported_notes,
counter.get(),
)
app.showToast("$message to '${folderUri.lastPathSegment}'")
}

override fun onFailure(message: CharSequence?) {
app.log(TAG, stackTrace = message as String?)
actionMode.close(true)
progress.postValue(ExportNotesProgress(inProgress = false))
}
},
)
}

ExportMimeType.PDF -> {
exportPdfFile(
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix PDF export completion logic to avoid redundant callbacks.

The completion check at line 555 (if (counter!!.get() == total!!)) is inside the success callback of every PDF export. This means that when the counter reaches the total, the callback will trigger actionMode.close(), post completion progress, and show a toast. However, since this is checked in every file's callback, there's a race condition where multiple files completing simultaneously could all pass this check.

Consider adding synchronization or using compareAndSet:

 override fun onSuccess(file: DocumentFile) { app.contentResolver.openOutputStream(it.uri)?.use { outStream -> app.contentResolver.openInputStream(file.uri)?.copyTo(outStream) } progress?.postValue( BackupProgress(current = counter!!.incrementAndGet(), total = total!!) ) - if (counter!!.get() == total!!) { + if (counter.get() == total) { pdfPrintListener?.onSuccess(file) } }

Also note the redundant null assertions (!!) on lines 553, 555-557 - counter and total are already dereferenced with !! earlier in the function.

Committable suggestion skipped: line range outside the PR's diff.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

1 participant