From a8975ecd8804ba66cf2e80eccef3ddfb3bcd00a2 Mon Sep 17 00:00:00 2001 From: dsremo Date: Thu, 18 Jun 2026 14:53:03 +0530 Subject: [PATCH] =?UTF-8?q?Settings:=20'Delete=20old=20recordings=E2=80=A6?= =?UTF-8?q?'=20with=20date=20picker,=20routed=20through=20Trash?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replaces the prior auto-cleanup engine on this branch per maintainer feedback. The previous version registered a background sweep on application start and silently removed recordings older than a configured threshold; @Dimowner objected that records could disappear without explicit user action. This rewrite implements the alternative @Dimowner suggested: a manual action under Settings that lets the user pick a cutoff date and review what will happen before anything is deleted. Flow - Settings → 'Delete old recordings…' opens DatePickerDialog (default cutoff: 3 months ago; max date clamped to today). - After a date is picked, a confirmation dialog quotes the formatted date and asks for explicit Yes/No via the project's existing AndroidUtils.showDialogYesNo helper. - On confirmation, SettingsPresenter walks getAllRecords() on the recordingsTasks background queue and calls localRepository .deleteRecord(id) for each record whose added/created timestamp is older than the cutoff. That method routes the recording through the app's existing Trash (markAsTrashRecord + trashDataSource), so the delete is recoverable from the Trash tab — not a permanent removal. - On completion the activity shows a toast with the moved count (plural-aware) or 'No recordings older than that date were found' if zero matched. Why through Trash rather than deleteRecordForever - The Trash flow already exists for individual-record deletes from the records list; reusing it gives this bulk action the same safety net (24h-undo via the Trash tab) without introducing a new code path. - Addresses the 'disappear unexpectedly' concern directly: even after confirmation, the user can still recover anything they didn't mean to delete. Scope - No SharedPreferences, no boot/scheduled work, no background observers. The previous PR's RecordingsRetention.kt and its ARApplication.kt call site are gone. - All strings are pluralised where needed (old_records_moved_to_trash has one/other forms) and use existing dimens/styles for the Settings row so it visually matches Rate/Request. Files touched (5) - SettingsContract.java: +1 user action, +2 view callbacks. - SettingsPresenter.java: deleteRecordsOlderThan implementation on the recordingsTasks queue. - SettingsActivity.java: button wiring, date picker, confirm dialog, view callbacks for moved-count and empty-result. - activity_settings.xml: new 'Delete old recordings…' row above the Settings statistics block, ic_delete_forever icon. - strings.xml: 4 new strings + 1 plurals entry. --- .../app/settings/SettingsActivity.java | 53 +++++++++++++++++++ .../app/settings/SettingsContract.java | 6 +++ .../app/settings/SettingsPresenter.java | 28 ++++++++++ app/src/main/res/layout/activity_settings.xml | 15 ++++++ app/src/main/res/values/strings.xml | 8 +++ 5 files changed, 110 insertions(+) diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsActivity.java b/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsActivity.java index c77fb1b85..a97959f95 100644 --- a/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsActivity.java +++ b/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsActivity.java @@ -18,6 +18,7 @@ import android.annotation.SuppressLint; import android.app.Activity; +import android.app.DatePickerDialog; import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; @@ -50,7 +51,10 @@ import com.dimowner.audiorecorder.util.RippleUtils; import java.io.File; +import java.text.DateFormat; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; import androidx.core.content.ContextCompat; @@ -82,6 +86,7 @@ public class SettingsActivity extends Activity implements SettingsContract.View, private SettingView bitrateSetting; private SettingView channelsSetting; private Button btnReset; + private TextView btnDeleteOldRecordings; private SettingsContract.UserActionsListener presenter; private ColorMap colorMap; @@ -130,6 +135,8 @@ protected void onCreate(Bundle savedInstanceState) { btnView.setOnClickListener(this); btnReset = findViewById(R.id.btnReset); btnReset.setOnClickListener(this); + btnDeleteOldRecordings = findViewById(R.id.btnDeleteOldRecordings); + btnDeleteOldRecordings.setOnClickListener(this); txtSizePerMin = findViewById(R.id.txt_size_per_min); txtInformation = findViewById(R.id.txt_information); txtLocation = findViewById(R.id.txt_records_location); @@ -345,6 +352,8 @@ public void onClick(View v) { } else if (id == R.id.btnReset) { presenter.resetSettings(); presenter.loadSettings(); + } else if (id == R.id.btnDeleteOldRecordings) { + showDeleteOldRecordingsPicker(); } else if (id == R.id.btnRequest) { requestFeature(); } @@ -384,6 +393,39 @@ private void requestFeature() { } } + private void showDeleteOldRecordingsPicker() { + final Calendar initial = Calendar.getInstance(); + initial.add(Calendar.MONTH, -3); + final DatePickerDialog picker = new DatePickerDialog( + this, + (view, year, month, dayOfMonth) -> { + final Calendar cutoff = Calendar.getInstance(); + cutoff.set(year, month, dayOfMonth, 0, 0, 0); + cutoff.set(Calendar.MILLISECOND, 0); + confirmDeleteOldRecordings(cutoff.getTimeInMillis()); + }, + initial.get(Calendar.YEAR), + initial.get(Calendar.MONTH), + initial.get(Calendar.DAY_OF_MONTH) + ); + picker.getDatePicker().setMaxDate(System.currentTimeMillis()); + picker.setTitle(R.string.delete_old_recordings_picker_title); + picker.show(); + } + + private void confirmDeleteOldRecordings(final long cutoffMillis) { + final String formatted = DateFormat.getDateInstance(DateFormat.MEDIUM) + .format(new Date(cutoffMillis)); + final String message = getString(R.string.delete_old_recordings_confirm, formatted); + AndroidUtils.showDialogYesNo( + this, + R.drawable.ic_delete_forever, + getString(R.string.delete_old_recordings), + message, + v -> presenter.deleteRecordsOlderThan(cutoffMillis) + ); + } + private Intent rateIntentForUrl(String url) { Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format("%s?id=%s", url, getApplicationContext().getPackageName()))); int flags = Intent.FLAG_ACTIVITY_NO_HISTORY | Intent.FLAG_ACTIVITY_MULTIPLE_TASK; @@ -465,6 +507,17 @@ public void showFailDeleteAllRecords() { Toast.makeText(getApplicationContext(), R.string.failed_to_delete_all_records, Toast.LENGTH_LONG).show(); } + @Override + public void showOldRecordsMovedToTrash(int count) { + final String msg = getResources().getQuantityString(R.plurals.old_records_moved_to_trash, count, count); + Toast.makeText(getApplicationContext(), msg, Toast.LENGTH_LONG).show(); + } + + @Override + public void showNoOldRecordsFound() { + Toast.makeText(getApplicationContext(), R.string.no_old_records_found, Toast.LENGTH_LONG).show(); + } + @Override public void showTotalRecordsDuration(String duration) { txtTotalDuration.setText(getResources().getString(R.string.total_duration, duration)); diff --git a/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsContract.java b/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsContract.java index 2ecd6ca6c..fb8f4269a 100644 --- a/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsContract.java +++ b/app/src/main/java/com/dimowner/audiorecorder/app/settings/SettingsContract.java @@ -50,6 +50,10 @@ interface View extends Contract.View { void showFailDeleteAllRecords(); + void showOldRecordsMovedToTrash(int count); + + void showNoOldRecordsFound(); + void showTotalRecordsDuration(String duration); void showRecordsCount(int count); void showAvailableSpace(String space); @@ -98,6 +102,8 @@ public interface UserActionsListener extends Contract.UserActionsListener { + final List all = localRepository.getAllRecords(); + int moved = 0; + for (int i = 0; i < all.size(); i++) { + final Record record = all.get(i); + if (record == null) continue; + final long stamp = record.getAdded() > 0 ? record.getAdded() : record.getCreated(); + if (stamp > 0 && stamp < cutoffMillis) { + if (localRepository.deleteRecord(record.getId())) { + moved++; + } + } + } + final int total = moved; + AndroidUtils.runOnUIThread(() -> { + if (view != null) { + if (total == 0) { + view.showNoOldRecordsFound(); + } else { + view.showOldRecordsMovedToTrash(total); + } + } + }); + }); + } + @Override public void deleteAllRecords() { recordingsTasks.postRunnable(() -> { diff --git a/app/src/main/res/layout/activity_settings.xml b/app/src/main/res/layout/activity_settings.xml index ec80da84f..b3a272b8c 100644 --- a/app/src/main/res/layout/activity_settings.xml +++ b/app/src/main/res/layout/activity_settings.xml @@ -367,6 +367,21 @@ android:drawablePadding="@dimen/spacing_normal" /> + + Move to trash %d selected record? Move to trash %d selected records? + + Moved %d record to Trash + Moved %d records to Trash + + Delete old recordings… + Delete recordings older than: + Move all recordings older than %1$s to Trash? + No recordings older than that date were found Copy %d selected record to the \'Downloads\' directory? Copy %d selected records to the \'Downloads\' directory?