From b4a4130c5cf97847059342ff9ec9b60c2e851786 Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 07:44:58 +0300 Subject: [PATCH 01/13] build: build v46, 2.6.46 --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 4eb98dd..9f33270 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -9,8 +9,8 @@ apply from: "$projectDir/gradle/libs-task.gradle" apply from: "$projectDir/gradle/changelog-task.gradle" -def appVersionCode = 45 -def appVersionName = "2.5.${appVersionCode}" +def appVersionCode = 46 +def appVersionName = "2.6.${appVersionCode}" def gitCommitHashProvider = providers.exec { commandLine 'git', 'rev-parse', '--short', 'HEAD' From eb50fc0d24ae2e644ba125d6e79d5f83aae613cd Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 08:58:27 +0300 Subject: [PATCH 02/13] feat: add notification melody picker, volume control, and reminder repeat interval MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Versioned notification channels (reminders_v1+) with system default sound - Melody picker and volume SeekBar in Settings → Media - "Repeat notification" switch with 5/10/15/30/60 min chips in reminder picker - Interval repeat works for both note and task reminders - DB migration 12→13: reminderIntervalMinutes on notes and tasks - New TaskReminderPickerBottomSheet replaces inline date/time pickers - Translations updated for all 11 locales --- CHANGELOG.md | 11 + .../main/java/com/pasich/mynotes/MyApp.java | 21 +- .../cache/NotificationPreferencesCache.java | 95 +++++ .../pasich/mynotes/data/AppDataManager.java | 10 + .../mynotes/data/database/AppDatabase.java | 8 + .../mynotes/data/database/AppDbHelper.java | 14 + .../mynotes/data/database/dao/NoteDao.java | 3 + .../mynotes/data/database/dao/TaskDao.java | 3 + .../data/database/helpers/DbNotesHelper.java | 2 + .../data/database/helpers/DbTasksHelper.java | 1 + .../com/pasich/mynotes/data/model/Note.java | 13 + .../com/pasich/mynotes/data/model/Task.java | 6 + .../pasich/mynotes/di/ApplicationModule.java | 2 +- .../mynotes/ui/receiver/ReminderReceiver.java | 35 +- .../ui/receiver/TaskReminderReceiver.java | 25 +- .../ui/view/activity/TasksActivity.java | 69 +--- .../dialogs/ReminderPickerBottomSheet.java | 54 ++- .../TaskReminderPickerBottomSheet.java | 330 ++++++++++++++++++ .../settings/MediaSettingsFragment.java | 92 ++++- .../utils/constants/DatabaseConstants.java | 2 +- .../constants/settings/PreferencesConfig.java | 6 + .../NotificationChannelManager.java | 81 +++++ .../utils/reminder/ReminderManager.java | 2 + .../utils/reminder/TaskReminderManager.java | 2 + .../res/layout/dialog_reminder_picker.xml | 68 ++++ .../res/layout/fragment_media_settings.xml | 99 ++++++ app/src/main/res/values-be/strings.xml | 12 + app/src/main/res/values-de/strings.xml | 12 + app/src/main/res/values-en-rGB/strings.xml | 12 + app/src/main/res/values-es/strings.xml | 12 + app/src/main/res/values-fr/strings.xml | 12 + app/src/main/res/values-it/strings.xml | 12 + app/src/main/res/values-kk/strings.xml | 12 + app/src/main/res/values-pl/strings.xml | 12 + app/src/main/res/values-ru/strings.xml | 12 + app/src/main/res/values-uk/strings.xml | 12 + app/src/main/res/values/strings.xml | 12 + 37 files changed, 1091 insertions(+), 95 deletions(-) create mode 100644 app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java create mode 100644 app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TaskReminderPickerBottomSheet.java create mode 100644 app/src/main/java/com/pasich/mynotes/utils/notification/NotificationChannelManager.java diff --git a/CHANGELOG.md b/CHANGELOG.md index c83a246..7b6fff3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # CHANGELOG +## [2.6.46] - 18.05.2026 + +**New** + +- **Notification sound:** Changed default notification sound; choose a custom melody in + Settings → Media. +- **Notification volume:** Adjust notification volume directly from the app in Settings → Media. +- **Reminder repeat:** When setting a reminder, toggle "Repeat notification" to receive the + alert again every 5, 10, 15, 30, or 60 minutes until the reminder is cleared. Works for + both note and task reminders. + ## [2.5.45] - 10.05.2026 **New** diff --git a/app/src/main/java/com/pasich/mynotes/MyApp.java b/app/src/main/java/com/pasich/mynotes/MyApp.java index e2d5b8a..847cdd4 100644 --- a/app/src/main/java/com/pasich/mynotes/MyApp.java +++ b/app/src/main/java/com/pasich/mynotes/MyApp.java @@ -1,15 +1,11 @@ package com.pasich.mynotes; import android.app.Application; -import android.app.NotificationChannel; -import android.app.NotificationManager; -import android.content.Context; -import android.os.Build; import android.util.Log; -import com.pasich.mynotes.R; +import com.pasich.mynotes.cache.NotificationPreferencesCache; import com.pasich.mynotes.cache.ThemePreferencesCache; -import com.pasich.mynotes.ui.receiver.ReminderReceiver; +import com.pasich.mynotes.utils.notification.NotificationChannelManager; import java.util.concurrent.Executors; @@ -28,6 +24,8 @@ public class MyApp extends Application { public static ThemePreferencesCache CACHE; @Inject ThemePreferencesCache themePreferencesCache; + @Inject + NotificationPreferencesCache notificationPreferencesCache; @Override public void onCreate() { @@ -35,18 +33,11 @@ public void onCreate() { CACHE = themePreferencesCache; - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { - NotificationChannel channel = new NotificationChannel( - ReminderReceiver.CHANNEL_ID, - getString(R.string.reminder_notification_channel), - NotificationManager.IMPORTANCE_HIGH - ); - NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); - if (nm != null) nm.createNotificationChannel(channel); - } + NotificationChannelManager.ensureChannel(this, notificationPreferencesCache); Executors.newSingleThreadExecutor().execute(() -> { themePreferencesCache.initialize(); + notificationPreferencesCache.initialize(); GLOBAL_FONT_SCALE = themePreferencesCache.getUiFontScale(); CACHE_READY = true; }); diff --git a/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java b/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java new file mode 100644 index 0000000..df94c66 --- /dev/null +++ b/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java @@ -0,0 +1,95 @@ +package com.pasich.mynotes.cache; + +import android.util.Log; + +import com.pasich.mynotes.data.preferences.SafePreferences; +import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; + +import javax.inject.Inject; +import javax.inject.Singleton; + +@Singleton +public class NotificationPreferencesCache { + + private static final String TAG = "NotificationPrefsCache"; + + private final SafePreferences prefs; + private volatile String soundUri; + private volatile String channelId; + private volatile int channelVersion; + private volatile boolean initialized = false; + + @Inject + public NotificationPreferencesCache(SafePreferences prefs) { + this.prefs = prefs; + } + + public synchronized void initialize() { + if (initialized) { + Log.d(TAG, "Cache already initialized"); + return; + } + try { + soundUri = prefs.getString( + PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_SOUND_URI, null); + channelId = prefs.getString( + PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_ID, + PreferencesConfig.ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_ID); + channelVersion = prefs.getInt( + PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_VERSION, + PreferencesConfig.ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_VERSION); + initialized = true; + } catch (Exception e) { + Log.e(TAG, "Failed to initialize cache", e); + setDefaults(); + } + } + + private void setDefaults() { + soundUri = null; + channelId = PreferencesConfig.ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_ID; + channelVersion = PreferencesConfig.ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_VERSION; + initialized = true; + } + + private void ensureInitialized() { + if (!initialized) initialize(); + } + + public String getSoundUri() { + ensureInitialized(); + return soundUri; + } + + public synchronized void setSoundUri(String uri) { + soundUri = uri; + if (uri == null) { + prefs.putString(PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_SOUND_URI, ""); + } else { + prefs.putString(PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_SOUND_URI, uri); + } + } + + public String getChannelId() { + ensureInitialized(); + return channelId; + } + + public synchronized void setChannelId(String id) { + channelId = id; + prefs.putString(PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_ID, id); + } + + public int getChannelVersion() { + ensureInitialized(); + return channelVersion; + } + + public synchronized int incrementAndPersistVersion() { + ensureInitialized(); + channelVersion++; + prefs.putInt(PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_VERSION, channelVersion); + setChannelId("reminders_v" + channelVersion); + return channelVersion; + } +} diff --git a/app/src/main/java/com/pasich/mynotes/data/AppDataManager.java b/app/src/main/java/com/pasich/mynotes/data/AppDataManager.java index 7c0b997..4543335 100644 --- a/app/src/main/java/com/pasich/mynotes/data/AppDataManager.java +++ b/app/src/main/java/com/pasich/mynotes/data/AppDataManager.java @@ -333,6 +333,11 @@ public Completable updateNoteReminder(int noteId, long reminderTime, String repe return dbHelper.updateNoteReminder(noteId, reminderTime, repeat); } + @Override + public Completable updateNoteReminderFull(int noteId, long reminderTime, String repeat, int intervalMinutes) { + return dbHelper.updateNoteReminderFull(noteId, reminderTime, repeat, intervalMinutes); + } + @Override public Completable setPinNote(int noteId, boolean pinned) { return dbHelper.setPinNote(noteId, pinned); @@ -408,6 +413,11 @@ public Completable setTaskReminder(int taskId, long time) { return dbHelper.setTaskReminder(taskId, time); } + @Override + public Completable setTaskReminderFull(int taskId, long time, int intervalMinutes) { + return dbHelper.setTaskReminderFull(taskId, time, intervalMinutes); + } + @Override public Completable clearTaskReminder(int taskId) { return dbHelper.clearTaskReminder(taskId); diff --git a/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java b/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java index 80ccc5f..ed8891a 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java @@ -121,6 +121,14 @@ public void migrate(@NonNull SupportSQLiteDatabase db) { } }; + public static final Migration MIGRATION_12_13 = new Migration(12, 13) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + db.execSQL("ALTER TABLE notes ADD COLUMN reminderIntervalMinutes INTEGER NOT NULL DEFAULT 0"); + db.execSQL("ALTER TABLE tasks ADD COLUMN reminderIntervalMinutes INTEGER NOT NULL DEFAULT 0"); + } + }; + public static final Migration MIGRATION_8_9 = new Migration(8, 9) { @Override public void migrate(@NonNull SupportSQLiteDatabase db) { diff --git a/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java index 698d66b..b84ec50 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java @@ -263,6 +263,13 @@ public Completable updateNoteReminder(int noteId, long reminderTime, String repe ).subscribeOn(io.reactivex.schedulers.Schedulers.io()); } + @Override + public Completable updateNoteReminderFull(int noteId, long reminderTime, String repeat, int intervalMinutes) { + return Completable.fromAction(() -> + appDatabase.noteDao().updateReminderFullSync(noteId, reminderTime, repeat, intervalMinutes) + ).subscribeOn(io.reactivex.schedulers.Schedulers.io()); + } + @Override public Completable setPinNote(int noteId, boolean pinned) { return Completable.fromAction(() -> @@ -342,6 +349,13 @@ public Completable setTaskReminder(int taskId, long time) { return Completable.fromAction(() -> appDatabase.taskDao().setTaskReminder(taskId, time)); } + @Override + public Completable setTaskReminderFull(int taskId, long time, int intervalMinutes) { + return Completable.fromAction(() -> + appDatabase.taskDao().setTaskReminderFullSync(taskId, time, intervalMinutes) + ).subscribeOn(io.reactivex.schedulers.Schedulers.io()); + } + @Override public Completable clearTaskReminder(int taskId) { return Completable.fromAction(() -> appDatabase.taskDao().clearTaskReminder(taskId)); diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java index 2340a75..df48975 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java @@ -94,6 +94,9 @@ public interface NoteDao { @Query("UPDATE notes SET reminderTime = :time, reminderRepeat = :repeat WHERE id = :noteId") void updateReminderSync(int noteId, long time, String repeat); + @Query("UPDATE notes SET reminderTime = :time, reminderRepeat = :repeat, reminderIntervalMinutes = :intervalMinutes WHERE id = :noteId") + void updateReminderFullSync(int noteId, long time, String repeat, int intervalMinutes); + @Query("UPDATE notes SET isPinned = :pinned WHERE id = :noteId") void setPinNoteSync(int noteId, boolean pinned); diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java index 7e56739..7c39a2e 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java @@ -51,6 +51,9 @@ public interface TaskDao { @Query("UPDATE tasks SET reminderTime = :time WHERE id = :taskId") void setTaskReminder(int taskId, long time); + @Query("UPDATE tasks SET reminderTime = :time, reminderIntervalMinutes = :intervalMinutes WHERE id = :taskId") + void setTaskReminderFullSync(int taskId, long time, int intervalMinutes); + @Query("UPDATE tasks SET reminderTime = NULL WHERE id = :taskId") void clearTaskReminder(int taskId); diff --git a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbNotesHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbNotesHelper.java index 1e3f34a..bc0ef49 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbNotesHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbNotesHelper.java @@ -59,6 +59,8 @@ public interface DbNotesHelper { Completable updateNoteReminder(int noteId, long reminderTime, String repeat); + Completable updateNoteReminderFull(int noteId, long reminderTime, String repeat, int intervalMinutes); + Completable setPinNote(int noteId, boolean pinned); } diff --git a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTasksHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTasksHelper.java index c9d627a..5cf9edb 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTasksHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTasksHelper.java @@ -24,6 +24,7 @@ public interface DbTasksHelper { Completable deleteCategory(TaskCategory category); Single getTaskCountForCategory(int categoryId); Completable setTaskReminder(int taskId, long time); + Completable setTaskReminderFull(int taskId, long time, int intervalMinutes); Completable clearTaskReminder(int taskId); Single> getTasksWithReminders(); } diff --git a/app/src/main/java/com/pasich/mynotes/data/model/Note.java b/app/src/main/java/com/pasich/mynotes/data/model/Note.java index 159c5d7..c39b2a4 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/Note.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/Note.java @@ -56,6 +56,10 @@ public class Note { @androidx.room.ColumnInfo(name = "reminderRepeat") private String reminderRepeat = "NONE"; + @SerializedName("m") + @androidx.room.ColumnInfo(name = "reminderIntervalMinutes") + private int reminderIntervalMinutes = 0; + public Note create(String title, String value, long date, String tag) { this.title = title; this.tag = tag; @@ -199,6 +203,14 @@ public void setPinned(boolean pinned) { } + public int getReminderIntervalMinutes() { + return reminderIntervalMinutes; + } + + public void setReminderIntervalMinutes(int reminderIntervalMinutes) { + this.reminderIntervalMinutes = reminderIntervalMinutes; + } + public void copyFrom(Note other) { if (other == null) return; this.title = other.title; @@ -210,6 +222,7 @@ public void copyFrom(Note other) { this.reminderTime = other.reminderTime; this.reminderRepeat = other.reminderRepeat; this.isPinned = other.isPinned; + this.reminderIntervalMinutes = other.reminderIntervalMinutes; } public Note duplicate() { diff --git a/app/src/main/java/com/pasich/mynotes/data/model/Task.java b/app/src/main/java/com/pasich/mynotes/data/model/Task.java index 35d0c9a..4b16c24 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/Task.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/Task.java @@ -23,6 +23,8 @@ public class Task { @ColumnInfo(defaultValue = "0") private int position; private Long reminderTime; + @ColumnInfo(defaultValue = "0") + private int reminderIntervalMinutes = 0; public Task() {} @@ -51,4 +53,8 @@ public Task(String title, int categoryId) { public void setPosition(int position) { this.position = position; } public Long getReminderTime() { return reminderTime; } public void setReminderTime(Long reminderTime) { this.reminderTime = reminderTime; } + public int getReminderIntervalMinutes() { return reminderIntervalMinutes; } + public void setReminderIntervalMinutes(int reminderIntervalMinutes) { + this.reminderIntervalMinutes = reminderIntervalMinutes; + } } diff --git a/app/src/main/java/com/pasich/mynotes/di/ApplicationModule.java b/app/src/main/java/com/pasich/mynotes/di/ApplicationModule.java index abb64df..08937c2 100644 --- a/app/src/main/java/com/pasich/mynotes/di/ApplicationModule.java +++ b/app/src/main/java/com/pasich/mynotes/di/ApplicationModule.java @@ -42,7 +42,7 @@ public class ApplicationModule { AppDatabase providesAppDatabase(@ApplicationContext Context context) { AppDatabase.setContext(context); return Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DatabaseConstants.DB_NAME) - .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11, AppDatabase.MIGRATION_11_12).build(); + .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11, AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13).build(); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java index 3644f72..aae9fdd 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java @@ -11,6 +11,7 @@ import androidx.core.app.TaskStackBuilder; import com.pasich.mynotes.R; +import com.pasich.mynotes.cache.NotificationPreferencesCache; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.ReminderRepeat; @@ -29,38 +30,57 @@ @AndroidEntryPoint public class ReminderReceiver extends BroadcastReceiver { - public static final String CHANNEL_ID = "reminders"; private static final String TAG = "ReminderReceiver"; @Inject DataManager dataManager; + @Inject + NotificationPreferencesCache notificationPreferencesCache; + @Override public void onReceive(Context ctx, Intent intent) { int noteId = intent.getIntExtra(ReminderManager.EXTRA_NOTE_ID, -1); String title = intent.getStringExtra(ReminderManager.EXTRA_NOTE_TITLE); String preview = intent.getStringExtra(ReminderManager.EXTRA_NOTE_PREVIEW); String repeatStr = intent.getStringExtra(ReminderManager.EXTRA_NOTE_REPEAT); + int intervalMinutes = intent.getIntExtra(ReminderManager.EXTRA_NOTE_INTERVAL_MINUTES, 0); if (noteId == -1) return; showNotification(ctx, noteId, title, preview); + if (intervalMinutes > 0) { + long nextTime = System.currentTimeMillis() + intervalMinutes * 60_000L; + dataManager.updateNoteReminderFull(noteId, nextTime, + repeatStr != null ? repeatStr : "NONE", intervalMinutes) + .subscribe(() -> {}, e -> Log.e(TAG, "updateReminderFull failed", e)); + Note tempNote = new Note(); + tempNote.setId(noteId); + tempNote.setTitle(title != null ? title : ""); + tempNote.setValue(preview != null ? preview : ""); + tempNote.setReminderTime(nextTime); + tempNote.setReminderRepeat(repeatStr != null ? repeatStr : "NONE"); + tempNote.setReminderIntervalMinutes(intervalMinutes); + ReminderManager.scheduleReminder(ctx, tempNote); + return; + } + ReminderRepeat repeat = ReminderRepeat.from(repeatStr); if (repeat == ReminderRepeat.NONE) { dataManager.clearReminder(noteId) .subscribe(() -> {}, e -> Log.e(TAG, "clearReminder failed", e)); } else { long nextTime = computeNextTime(System.currentTimeMillis(), repeat); - dataManager.updateNoteReminder(noteId, nextTime, repeat.name()) + dataManager.updateNoteReminderFull(noteId, nextTime, repeat.name(), 0) .subscribe(() -> {}, e -> Log.e(TAG, "updateReminder failed", e)); - Note tempNote = new Note(); tempNote.setId(noteId); tempNote.setTitle(title != null ? title : ""); tempNote.setValue(preview != null ? preview : ""); tempNote.setReminderTime(nextTime); tempNote.setReminderRepeat(repeat.name()); + tempNote.setReminderIntervalMinutes(0); ReminderManager.scheduleReminder(ctx, tempNote); } } @@ -94,15 +114,14 @@ private void showNotification(Context ctx, int noteId, String title, String prev snoozeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent snoozePi = PendingIntent.getActivity( ctx, noteId + 10000, snoozeIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - String notifTitle = (title != null && !title.isEmpty()) - ? title : ctx.getString(R.string.app_name); + String notifTitle = (title != null && !title.isEmpty()) ? title : ctx.getString(R.string.app_name); String notifText = (preview != null && preview.length() > 100) ? preview.substring(0, 100) : (preview != null ? preview : ""); - NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, CHANNEL_ID) + NotificationCompat.Builder builder = new NotificationCompat.Builder( + ctx, notificationPreferencesCache.getChannelId()) .setSmallIcon(R.drawable.ic_bell_small) .setContentTitle(notifTitle) .setContentText(notifText) diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java index 4166a64..5b19879 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java @@ -10,7 +10,9 @@ import androidx.core.app.TaskStackBuilder; import com.pasich.mynotes.R; +import com.pasich.mynotes.cache.NotificationPreferencesCache; import com.pasich.mynotes.data.DataManager; +import com.pasich.mynotes.data.model.Task; import com.pasich.mynotes.ui.view.activity.TasksActivity; import com.pasich.mynotes.utils.reminder.TaskReminderManager; @@ -21,15 +23,17 @@ @AndroidEntryPoint public class TaskReminderReceiver extends BroadcastReceiver { - private static final String CHANNEL_ID = "reminders"; - @Inject DataManager dataManager; + @Inject + NotificationPreferencesCache notificationPreferencesCache; + @Override public void onReceive(Context ctx, Intent intent) { int taskId = intent.getIntExtra(TaskReminderManager.EXTRA_TASK_ID, -1); String title = intent.getStringExtra(TaskReminderManager.EXTRA_TASK_TITLE); + int intervalMinutes = intent.getIntExtra(TaskReminderManager.EXTRA_TASK_INTERVAL_MINUTES, 0); if (taskId == -1) return; Intent openIntent = new Intent(ctx, TasksActivity.class); @@ -38,7 +42,8 @@ public void onReceive(Context ctx, Intent intent) { .getPendingIntent(taskId + 200000, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, CHANNEL_ID) + NotificationCompat.Builder builder = new NotificationCompat.Builder( + ctx, notificationPreferencesCache.getChannelId()) .setSmallIcon(R.drawable.ic_bell_small) .setContentTitle(ctx.getString(R.string.app_name)) .setContentText(title) @@ -50,6 +55,20 @@ public void onReceive(Context ctx, Intent intent) { NotificationManagerCompat.from(ctx).notify(taskId + 100000, builder.build()); } catch (SecurityException ignored) {} + if (intervalMinutes > 0) { + long nextTime = System.currentTimeMillis() + intervalMinutes * 60_000L; + dataManager.setTaskReminderFull(taskId, nextTime, intervalMinutes) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()) + .subscribe(() -> {}, e -> {}); + Task tempTask = new Task(); + tempTask.setId(taskId); + tempTask.setTitle(title != null ? title : ""); + tempTask.setReminderTime(nextTime); + tempTask.setReminderIntervalMinutes(intervalMinutes); + TaskReminderManager.scheduleReminder(ctx, tempTask); + return; + } + dataManager.clearTaskReminder(taskId) .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .subscribe(() -> {}, e -> {}); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java index 9ae0563..79928ea 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java @@ -9,16 +9,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import android.widget.Toast; - -import com.google.android.material.datepicker.CalendarConstraints; -import com.google.android.material.datepicker.DateValidatorPointForward; -import com.google.android.material.datepicker.MaterialDatePicker; -import com.google.android.material.timepicker.MaterialTimePicker; -import com.google.android.material.timepicker.TimeFormat; - -import java.text.DateFormat; -import java.util.Calendar; +import com.pasich.mynotes.ui.view.dialogs.TaskReminderPickerBottomSheet; import com.google.android.material.chip.Chip; import com.google.android.material.dialog.MaterialAlertDialogBuilder; @@ -75,6 +66,10 @@ protected void onCreate(Bundle savedInstanceState) { tasksPresenter.attachView(this); tasksPresenter.viewIsReady(); + + getSupportFragmentManager().setFragmentResultListener( + "taskReminderChanged", this, + (requestKey, result) -> tasksPresenter.onCategorySelected(selectedCategoryId)); } private ItemTouchHelper.SimpleCallback buildTouchCallback() { @@ -166,54 +161,12 @@ private void showDeleteDialog(@StringRes int titleRes, String name, } private void showReminderPicker(Task task) { - if (task.getReminderTime() != null) { - String formatted = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.SHORT) - .format(task.getReminderTime()); - new MaterialAlertDialogBuilder(this) - .setTitle(R.string.tasks_reminder_set) - .setMessage(formatted) - .setPositiveButton(R.string.tasks_reminder_clear, - (d, w) -> tasksPresenter.clearTaskReminder(task)) - .setNegativeButton(R.string.cancel, null) - .show(); - return; - } - - CalendarConstraints constraints = new CalendarConstraints.Builder() - .setValidator(DateValidatorPointForward.from(MaterialDatePicker.todayInUtcMilliseconds())) - .build(); - - MaterialDatePicker datePicker = MaterialDatePicker.Builder.datePicker() - .setTitleText(R.string.tasks_reminder_set) - .setSelection(MaterialDatePicker.todayInUtcMilliseconds()) - .setCalendarConstraints(constraints) - .build(); - - datePicker.addOnPositiveButtonClickListener(dateMs -> { - Calendar now = Calendar.getInstance(); - MaterialTimePicker timePicker = new MaterialTimePicker.Builder() - .setTitleText(R.string.tasks_reminder_set) - .setTimeFormat(TimeFormat.CLOCK_24H) - .setHour(now.get(Calendar.HOUR_OF_DAY)) - .setMinute(now.get(Calendar.MINUTE)) - .build(); - timePicker.addOnPositiveButtonClickListener(v -> { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(dateMs); - cal.set(Calendar.HOUR_OF_DAY, timePicker.getHour()); - cal.set(Calendar.MINUTE, timePicker.getMinute()); - cal.set(Calendar.SECOND, 0); - cal.set(Calendar.MILLISECOND, 0); - if (cal.getTimeInMillis() <= System.currentTimeMillis()) { - Toast.makeText(this, R.string.tasks_reminder_past, Toast.LENGTH_SHORT).show(); - return; - } - tasksPresenter.setTaskReminder(task, cal.getTimeInMillis()); - }); - timePicker.show(getSupportFragmentManager(), "task_time_picker"); - }); - - datePicker.show(getSupportFragmentManager(), "task_date_picker"); + TaskReminderPickerBottomSheet.newInstance( + task.getId(), + task.getTitle(), + task.getReminderTime(), + task.getReminderIntervalMinutes() + ).show(getSupportFragmentManager(), "taskReminderPicker"); } private void showEditDialog(Task task) { diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ReminderPickerBottomSheet.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ReminderPickerBottomSheet.java index 27fb0f9..ec14701 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ReminderPickerBottomSheet.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ReminderPickerBottomSheet.java @@ -64,6 +64,10 @@ public class ReminderPickerBottomSheet extends BottomSheetDialogFragment { private ChipGroup repeatChips; private MaterialButton btnSave; private MaterialButton btnDeleteReminder; + private int selectedIntervalMinutes = 0; + private View intervalDivider; + private com.google.android.material.materialswitch.MaterialSwitch switchRepeatInterval; + private ChipGroup intervalChips; private final ActivityResultLauncher notifPermLauncher = registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { @@ -98,6 +102,25 @@ public View onCreateView(@NonNull LayoutInflater inflater, repeatChips = view.findViewById(R.id.repeatChips); btnSave = view.findViewById(R.id.btnSave); btnDeleteReminder = view.findViewById(R.id.btnDeleteReminder); + intervalDivider = view.findViewById(R.id.intervalDivider); + switchRepeatInterval = view.findViewById(R.id.switchRepeatInterval); + intervalChips = view.findViewById(R.id.intervalChips); + + switchRepeatInterval.setOnCheckedChangeListener((btn, checked) -> { + intervalChips.setVisibility(checked ? View.VISIBLE : View.GONE); + if (!checked) { + selectedIntervalMinutes = 0; + } else { + intervalChips.check(R.id.chipInterval10); + selectedIntervalMinutes = 10; + } + }); + + intervalChips.setOnCheckedStateChangeListener((group, checkedIds) -> { + if (!checkedIds.isEmpty()) { + selectedIntervalMinutes = intervalMinutesFromChipId(checkedIds.get(0)); + } + }); disposables.add( dataManager.getNoteForId(noteId) @@ -128,6 +151,13 @@ private void prefillExistingReminder(Note note) { setRepeatChip(selectedRepeat); btnDeleteReminder.setVisibility(View.VISIBLE); btnSave.setEnabled(true); + int existingInterval = note.getReminderIntervalMinutes(); + if (existingInterval > 0) { + selectedIntervalMinutes = existingInterval; + switchRepeatInterval.setChecked(true); + intervalChips.setVisibility(View.VISIBLE); + setIntervalChip(existingInterval); + } } } @@ -222,6 +252,8 @@ private void showDatePicker() { private void showRepeatSection() { repeatLabel.setVisibility(View.VISIBLE); repeatChips.setVisibility(View.VISIBLE); + intervalDivider.setVisibility(View.VISIBLE); + switchRepeatInterval.setVisibility(View.VISIBLE); } private void updateTimeDisplay() { @@ -249,6 +281,25 @@ private ReminderRepeat getSelectedRepeat() { return ReminderRepeat.NONE; } + private int intervalMinutesFromChipId(int chipId) { + if (chipId == R.id.chipInterval5) return 5; + if (chipId == R.id.chipInterval10) return 10; + if (chipId == R.id.chipInterval15) return 15; + if (chipId == R.id.chipInterval30) return 30; + if (chipId == R.id.chipInterval60) return 60; + return 0; + } + + private void setIntervalChip(int minutes) { + int id; + if (minutes <= 5) id = R.id.chipInterval5; + else if (minutes <= 10) id = R.id.chipInterval10; + else if (minutes <= 15) id = R.id.chipInterval15; + else if (minutes <= 30) id = R.id.chipInterval30; + else id = R.id.chipInterval60; + intervalChips.check(id); + } + private void checkPermissionsAndSave() { if (selectedTime == null || selectedTime <= System.currentTimeMillis()) { Toast.makeText(requireContext(), @@ -283,7 +334,7 @@ private void saveReminder() { final String repeat = selectedRepeat.name(); disposables.add( - dataManager.updateNoteReminder(noteId, time, repeat) + dataManager.updateNoteReminderFull(noteId, time, repeat, selectedIntervalMinutes) .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread()) .subscribe(() -> { @@ -295,6 +346,7 @@ private void saveReminder() { } tempNote.setReminderTime(time); tempNote.setReminderRepeat(repeat); + tempNote.setReminderIntervalMinutes(selectedIntervalMinutes); ReminderManager.scheduleReminder(requireContext(), tempNote); Bundle result = new Bundle(); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TaskReminderPickerBottomSheet.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TaskReminderPickerBottomSheet.java new file mode 100644 index 0000000..e52a86c --- /dev/null +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TaskReminderPickerBottomSheet.java @@ -0,0 +1,330 @@ +package com.pasich.mynotes.ui.view.dialogs; + +import android.Manifest; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.os.Build; +import android.os.Bundle; +import android.provider.Settings; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import android.widget.Toast; + +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; + +import com.google.android.material.bottomsheet.BottomSheetDialogFragment; +import com.google.android.material.button.MaterialButton; +import com.google.android.material.chip.ChipGroup; +import com.google.android.material.datepicker.CalendarConstraints; +import com.google.android.material.datepicker.DateValidatorPointForward; +import com.google.android.material.datepicker.MaterialDatePicker; +import com.google.android.material.materialswitch.MaterialSwitch; +import com.google.android.material.timepicker.MaterialTimePicker; +import com.google.android.material.timepicker.TimeFormat; +import com.pasich.mynotes.R; +import com.pasich.mynotes.data.DataManager; +import com.pasich.mynotes.data.model.Task; +import com.pasich.mynotes.utils.reminder.ReminderManager; +import com.pasich.mynotes.utils.reminder.TaskReminderManager; + +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.Locale; + +import javax.inject.Inject; + +import dagger.hilt.android.AndroidEntryPoint; +import io.reactivex.disposables.CompositeDisposable; + +@AndroidEntryPoint +public class TaskReminderPickerBottomSheet extends BottomSheetDialogFragment { + + private static final String TAG = "TaskReminderPicker"; + private static final String ARG_TASK_ID = "taskId"; + private static final String ARG_TASK_TITLE = "taskTitle"; + private static final String ARG_REMINDER_TIME = "reminderTime"; + private static final String ARG_INTERVAL_MINUTES = "intervalMinutes"; + + @Inject + DataManager dataManager; + + private int taskId; + private String taskTitle; + private Long selectedTime = null; + private int selectedIntervalMinutes = 0; + private final CompositeDisposable disposables = new CompositeDisposable(); + + private View selectedTimeCard; + private TextView selectedTimeDisplay; + private MaterialButton btnSave; + private MaterialButton btnDeleteReminder; + private MaterialSwitch switchRepeatInterval; + private ChipGroup intervalChips; + private View intervalDivider; + + private final ActivityResultLauncher notifPermLauncher = + registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { + if (granted) saveReminder(); + else Toast.makeText(requireContext(), + R.string.reminder_permission_denied, Toast.LENGTH_SHORT).show(); + }); + + public static TaskReminderPickerBottomSheet newInstance(int taskId, String taskTitle, + Long currentReminderTime, int currentIntervalMinutes) { + TaskReminderPickerBottomSheet f = new TaskReminderPickerBottomSheet(); + Bundle args = new Bundle(); + args.putInt(ARG_TASK_ID, taskId); + args.putString(ARG_TASK_TITLE, taskTitle != null ? taskTitle : ""); + if (currentReminderTime != null) args.putLong(ARG_REMINDER_TIME, currentReminderTime); + args.putInt(ARG_INTERVAL_MINUTES, currentIntervalMinutes); + f.setArguments(args); + return f; + } + + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Bundle args = requireArguments(); + taskId = args.getInt(ARG_TASK_ID, -1); + taskTitle = args.getString(ARG_TASK_TITLE, ""); + if (args.containsKey(ARG_REMINDER_TIME)) { + selectedTime = args.getLong(ARG_REMINDER_TIME); + } + } + + @Nullable + @Override + public View onCreateView(@NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.dialog_reminder_picker, container, false); + + selectedTimeCard = view.findViewById(R.id.selectedTimeCard); + selectedTimeDisplay = view.findViewById(R.id.selectedTimeDisplay); + btnSave = view.findViewById(R.id.btnSave); + btnDeleteReminder = view.findViewById(R.id.btnDeleteReminder); + switchRepeatInterval = view.findViewById(R.id.switchRepeatInterval); + intervalChips = view.findViewById(R.id.intervalChips); + intervalDivider = view.findViewById(R.id.intervalDivider); + + // Hide repeat section — tasks don't use DAILY/WEEKLY/MONTHLY + view.findViewById(R.id.repeatLabel).setVisibility(View.GONE); + view.findViewById(R.id.repeatChips).setVisibility(View.GONE); + + // Wire interval switch + switchRepeatInterval.setOnCheckedChangeListener((btn, checked) -> { + intervalChips.setVisibility(checked ? View.VISIBLE : View.GONE); + if (!checked) { + selectedIntervalMinutes = 0; + } else { + intervalChips.check(R.id.chipInterval10); + selectedIntervalMinutes = 10; + } + }); + + intervalChips.setOnCheckedStateChangeListener((group, checkedIds) -> { + if (!checkedIds.isEmpty()) { + selectedIntervalMinutes = intervalMinutesFromChipId(checkedIds.get(0)); + } + }); + + // Pre-fill existing reminder + if (selectedTime != null && selectedTime > System.currentTimeMillis()) { + updateTimeDisplay(); + showIntervalSection(); + int existingInterval = requireArguments().getInt(ARG_INTERVAL_MINUTES, 0); + if (existingInterval > 0) { + selectedIntervalMinutes = existingInterval; + switchRepeatInterval.setChecked(true); + intervalChips.setVisibility(View.VISIBLE); + setIntervalChip(existingInterval); + } + btnDeleteReminder.setVisibility(View.VISIBLE); + btnSave.setEnabled(true); + } + + view.findViewById(R.id.presetToday).setOnClickListener(v -> applyPreset(todayEvening())); + view.findViewById(R.id.presetTomorrow).setOnClickListener(v -> applyPreset(tomorrowMorning())); + view.findViewById(R.id.btnChooseDate).setOnClickListener(v -> showDatePicker()); + btnSave.setOnClickListener(v -> checkPermissionsAndSave()); + view.findViewById(R.id.btnCancel).setOnClickListener(v -> dismiss()); + btnDeleteReminder.setOnClickListener(v -> deleteReminder()); + + return view; + } + + private long todayEvening() { + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.HOUR_OF_DAY, 18); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + if (cal.getTimeInMillis() <= System.currentTimeMillis()) { + cal.add(Calendar.DAY_OF_YEAR, 1); + } + return cal.getTimeInMillis(); + } + + private long tomorrowMorning() { + Calendar cal = Calendar.getInstance(); + cal.add(Calendar.DAY_OF_YEAR, 1); + cal.set(Calendar.HOUR_OF_DAY, 9); + cal.set(Calendar.MINUTE, 0); + cal.set(Calendar.SECOND, 0); + cal.set(Calendar.MILLISECOND, 0); + return cal.getTimeInMillis(); + } + + private void applyPreset(long time) { + selectedTime = time; + updateTimeDisplay(); + showIntervalSection(); + btnSave.setEnabled(true); + } + + private void showDatePicker() { + CalendarConstraints constraints = new CalendarConstraints.Builder() + .setValidator(DateValidatorPointForward.now()) + .build(); + MaterialDatePicker datePicker = MaterialDatePicker.Builder.datePicker() + .setTitleText(getString(R.string.reminder_choose_date)) + .setSelection(MaterialDatePicker.todayInUtcMilliseconds()) + .setCalendarConstraints(constraints) + .build(); + datePicker.addOnPositiveButtonClickListener(dateMs -> { + Calendar dateCal = Calendar.getInstance(); + dateCal.setTimeInMillis(dateMs); + Calendar now = Calendar.getInstance(); + int defaultHour = (dateCal.get(Calendar.DAY_OF_YEAR) == now.get(Calendar.DAY_OF_YEAR) + && dateCal.get(Calendar.YEAR) == now.get(Calendar.YEAR)) + ? now.get(Calendar.HOUR_OF_DAY) : 9; + int defaultMinute = (defaultHour == now.get(Calendar.HOUR_OF_DAY)) + ? now.get(Calendar.MINUTE) + 1 : 0; + MaterialTimePicker timePicker = new MaterialTimePicker.Builder() + .setTimeFormat(TimeFormat.CLOCK_24H) + .setHour(defaultHour) + .setMinute(defaultMinute) + .build(); + timePicker.addOnPositiveButtonClickListener(v -> { + dateCal.set(Calendar.HOUR_OF_DAY, timePicker.getHour()); + dateCal.set(Calendar.MINUTE, timePicker.getMinute()); + dateCal.set(Calendar.SECOND, 0); + dateCal.set(Calendar.MILLISECOND, 0); + if (dateCal.getTimeInMillis() <= System.currentTimeMillis()) { + Toast.makeText(requireContext(), + R.string.reminder_past_time_error, Toast.LENGTH_SHORT).show(); + return; + } + applyPreset(dateCal.getTimeInMillis()); + }); + timePicker.show(getChildFragmentManager(), "timePicker"); + }); + datePicker.show(getChildFragmentManager(), "datePicker"); + } + + private void showIntervalSection() { + intervalDivider.setVisibility(View.VISIBLE); + switchRepeatInterval.setVisibility(View.VISIBLE); + } + + private void updateTimeDisplay() { + if (selectedTime == null) return; + SimpleDateFormat fmt = new SimpleDateFormat("d MMM yyyy, HH:mm", Locale.getDefault()); + selectedTimeDisplay.setText(fmt.format(new Date(selectedTime))); + selectedTimeCard.setVisibility(View.VISIBLE); + } + + private int intervalMinutesFromChipId(int chipId) { + if (chipId == R.id.chipInterval5) return 5; + if (chipId == R.id.chipInterval10) return 10; + if (chipId == R.id.chipInterval15) return 15; + if (chipId == R.id.chipInterval30) return 30; + if (chipId == R.id.chipInterval60) return 60; + return 0; + } + + private void setIntervalChip(int minutes) { + int id; + if (minutes <= 5) id = R.id.chipInterval5; + else if (minutes <= 10) id = R.id.chipInterval10; + else if (minutes <= 15) id = R.id.chipInterval15; + else if (minutes <= 30) id = R.id.chipInterval30; + else id = R.id.chipInterval60; + intervalChips.check(id); + } + + private void checkPermissionsAndSave() { + if (selectedTime == null || selectedTime <= System.currentTimeMillis()) { + Toast.makeText(requireContext(), R.string.reminder_past_time_error, Toast.LENGTH_SHORT).show(); + return; + } + if (!ReminderManager.canScheduleExact(requireContext())) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + startActivity(new Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)); + } + return; + } + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { + if (ActivityCompat.checkSelfPermission(requireContext(), + Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + notifPermLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); + return; + } + } + saveReminder(); + } + + private void saveReminder() { + if (selectedTime == null) return; + final long time = selectedTime; + final int interval = selectedIntervalMinutes; + + disposables.add( + dataManager.setTaskReminderFull(taskId, time, interval) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()) + .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread()) + .subscribe(() -> { + Task tempTask = new Task(); + tempTask.setId(taskId); + tempTask.setTitle(taskTitle); + tempTask.setReminderTime(time); + tempTask.setReminderIntervalMinutes(interval); + TaskReminderManager.scheduleReminder(requireContext(), tempTask); + + Bundle result = new Bundle(); + result.putBoolean("saved", true); + getParentFragmentManager().setFragmentResult("taskReminderChanged", result); + dismiss(); + }, e -> Log.e(TAG, "save failed", e)) + ); + } + + private void deleteReminder() { + disposables.add( + dataManager.clearTaskReminder(taskId) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()) + .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread()) + .subscribe(() -> { + TaskReminderManager.cancelReminder(requireContext(), taskId); + Bundle result = new Bundle(); + result.putBoolean("saved", false); + getParentFragmentManager().setFragmentResult("taskReminderChanged", result); + dismiss(); + }, e -> Log.e(TAG, "delete failed", e)) + ); + } + + @Override + public void onDestroyView() { + super.onDestroyView(); + disposables.clear(); + } +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/MediaSettingsFragment.java b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/MediaSettingsFragment.java index 5214e87..3615458 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/MediaSettingsFragment.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/MediaSettingsFragment.java @@ -1,22 +1,31 @@ package com.pasich.mynotes.ui.view.fragment.settings; import android.annotation.SuppressLint; +import android.content.Intent; +import android.media.AudioManager; +import android.media.RingtoneManager; +import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.widget.SeekBar; +import androidx.activity.result.ActivityResultLauncher; +import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; import com.pasich.mynotes.cache.AppPreferencesCache; +import com.pasich.mynotes.cache.NotificationPreferencesCache; import com.pasich.mynotes.cache.ThemePreferencesCache; import com.pasich.mynotes.databinding.FragmentMediaSettingsBinding; import com.pasich.mynotes.extendedEditor.attach.AttachmentStorage; import com.pasich.mynotes.ui.controllers.mainActivity.RedrawThemeController; +import com.pasich.mynotes.utils.notification.NotificationChannelManager; import javax.inject.Inject; @@ -32,11 +41,26 @@ public class MediaSettingsFragment extends Fragment { @Inject AppPreferencesCache appPreferencesCache; + @Inject + NotificationPreferencesCache notificationPreferencesCache; + private FragmentMediaSettingsBinding binding; + private AudioManager audioManager; + + private final ActivityResultLauncher ringtoneLauncher = + registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { + if (result.getResultCode() != android.app.Activity.RESULT_OK + || result.getData() == null) return; + Uri newUri = result.getData().getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + if (getContext() == null) return; + NotificationChannelManager.changeSound(requireContext(), notificationPreferencesCache, newUri); + updateSoundNameText(newUri); + }); @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = FragmentMediaSettingsBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -44,21 +68,71 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + audioManager = (AudioManager) requireContext().getSystemService(android.content.Context.AUDIO_SERVICE); initViews(); initListeners(); - updateMemoryUsage(); } private void initViews() { binding.imgOptSwitch.setChecked(appPreferencesCache.getImageOpt()); binding.setExtendedDetailsVisible(false); + + // Sound: show current sound name + String savedUri = notificationPreferencesCache.getSoundUri(); + Uri currentUri = (savedUri != null && !savedUri.isEmpty()) + ? Uri.parse(savedUri) + : android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; + updateSoundNameText(currentUri); + + // Volume SeekBar + if (audioManager != null) { + int max = audioManager.getStreamMaxVolume(AudioManager.STREAM_NOTIFICATION); + int cur = audioManager.getStreamVolume(AudioManager.STREAM_NOTIFICATION); + binding.volumeSeekBar.setMax(max); + binding.volumeSeekBar.setProgress(cur); + } } private void initListeners() { binding.imgOptSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> appPreferencesCache.setImageOpt(isChecked)); + binding.soundRow.setOnClickListener(v -> { + String savedUri = notificationPreferencesCache.getSoundUri(); + Uri currentUri = (savedUri != null && !savedUri.isEmpty()) + ? Uri.parse(savedUri) + : android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; + + Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentUri); + ringtoneLauncher.launch(intent); + }); + + binding.volumeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser && audioManager != null) { + audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, progress, 0); + } + } + @Override public void onStartTrackingTouch(SeekBar seekBar) {} + @Override public void onStopTrackingTouch(SeekBar seekBar) {} + }); + } + + private void updateSoundNameText(Uri uri) { + if (binding == null || getContext() == null) return; + String name; + try { + android.media.Ringtone ringtone = RingtoneManager.getRingtone(requireContext(), uri); + name = (ringtone != null) ? ringtone.getTitle(requireContext()) : ""; + } catch (Exception e) { + name = ""; + } + binding.soundNameText.setText(name); } public void updateThemeColors() { @@ -77,17 +151,21 @@ public void updateThemeColors() { null, requireContext() ); + RedrawThemeController.styleCardBlock( + binding.notificationSettings, + binding.notificationSectionTitle, + null, + null, + requireContext() + ); } @SuppressLint("DefaultLocale") private void updateMemoryUsage() { if (getContext() == null) return; - Schedulers.io().scheduleDirect(() -> { long usedBytes = AttachmentStorage.getTotalAttachmentsSize(getContext()); - float usedMB = usedBytes / 1024f / 1024f; - new Handler(Looper.getMainLooper()).post(() -> { if (binding != null) { binding.memoryValue.setText(String.format("%.1f MB", usedMB)); @@ -96,11 +174,9 @@ private void updateMemoryUsage() { }); } - @Override public void onDestroyView() { super.onDestroyView(); binding = null; - } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/constants/DatabaseConstants.java b/app/src/main/java/com/pasich/mynotes/utils/constants/DatabaseConstants.java index 7aaafd4..4c27a25 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/constants/DatabaseConstants.java +++ b/app/src/main/java/com/pasich/mynotes/utils/constants/DatabaseConstants.java @@ -3,6 +3,6 @@ public class DatabaseConstants { public static final String DB_NAME = "MyNotes.db"; - public static final int DB_VERSION = 12; + public static final int DB_VERSION = 13; } diff --git a/app/src/main/java/com/pasich/mynotes/utils/constants/settings/PreferencesConfig.java b/app/src/main/java/com/pasich/mynotes/utils/constants/settings/PreferencesConfig.java index 97589e5..797a310 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/constants/settings/PreferencesConfig.java +++ b/app/src/main/java/com/pasich/mynotes/utils/constants/settings/PreferencesConfig.java @@ -44,4 +44,10 @@ public class PreferencesConfig { //font scaling app public static final String ARGUMENT_PREFERENCE_UI_SCALING = "uiFontScale"; public static final float ARGUMENT_DEFAULT_UI_SCALING_VALUE = 0.0f; + //Notification sound / channel + public static final String ARGUMENT_PREFERENCE_NOTIFICATION_SOUND_URI = "notification_sound_uri"; + public static final String ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_ID = "notification_channel_id"; + public static final String ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_VERSION = "notification_channel_version"; + public static final String ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_ID = "reminders_v1"; + public static final int ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_VERSION = 1; } diff --git a/app/src/main/java/com/pasich/mynotes/utils/notification/NotificationChannelManager.java b/app/src/main/java/com/pasich/mynotes/utils/notification/NotificationChannelManager.java new file mode 100644 index 0000000..114f8dc --- /dev/null +++ b/app/src/main/java/com/pasich/mynotes/utils/notification/NotificationChannelManager.java @@ -0,0 +1,81 @@ +package com.pasich.mynotes.utils.notification; + +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; +import android.media.AudioAttributes; +import android.net.Uri; +import android.os.Build; +import android.provider.Settings; + +import com.pasich.mynotes.R; +import com.pasich.mynotes.cache.NotificationPreferencesCache; + +public class NotificationChannelManager { + + private static final String LEGACY_CHANNEL_ID = "reminders"; + + /** + * Idempotent: creates the current versioned channel if it does not exist. + * On first call after update from old version, deletes the legacy "reminders" channel. + */ + public static void ensureChannel(Context ctx, NotificationPreferencesCache prefs) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; + NotificationManager nm = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); + if (nm == null) return; + + String channelId = prefs.getChannelId(); + if (nm.getNotificationChannel(channelId) != null) return; // already exists + + // Remove legacy channel on first run after update + if (nm.getNotificationChannel(LEGACY_CHANNEL_ID) != null) { + nm.deleteNotificationChannel(LEGACY_CHANNEL_ID); + } + + createChannel(ctx, nm, prefs); + } + + /** + * Deletes the current versioned channel, bumps the version, creates the new channel + * with newSoundUri. Pass null to reset to the system default notification sound. + */ + public static void changeSound(Context ctx, NotificationPreferencesCache prefs, Uri newSoundUri) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { + prefs.setSoundUri(newSoundUri != null ? newSoundUri.toString() : null); + return; + } + NotificationManager nm = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); + if (nm == null) return; + + nm.deleteNotificationChannel(prefs.getChannelId()); + prefs.setSoundUri(newSoundUri != null ? newSoundUri.toString() : null); + prefs.incrementAndPersistVersion(); + createChannel(ctx, nm, prefs); + } + + private static void createChannel(Context ctx, NotificationManager nm, + NotificationPreferencesCache prefs) { + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; + + NotificationChannel channel = new NotificationChannel( + prefs.getChannelId(), + ctx.getString(R.string.reminder_notification_channel), + NotificationManager.IMPORTANCE_HIGH + ); + + String savedUri = prefs.getSoundUri(); + Uri soundUri; + if (savedUri == null || savedUri.isEmpty()) { + soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; + } else { + soundUri = Uri.parse(savedUri); + } + + AudioAttributes attrs = new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build(); + channel.setSound(soundUri, attrs); + nm.createNotificationChannel(channel); + } +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java b/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java index 6943446..e3b023d 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java @@ -17,6 +17,7 @@ public class ReminderManager { public static final String EXTRA_NOTE_TITLE = "noteTitle"; public static final String EXTRA_NOTE_PREVIEW = "notePreview"; public static final String EXTRA_NOTE_REPEAT = "noteRepeat"; + public static final String EXTRA_NOTE_INTERVAL_MINUTES = "intervalMinutes"; public static void scheduleReminder(Context ctx, Note note) { if (note.getReminderTime() == null) return; @@ -67,6 +68,7 @@ private static PendingIntent buildPendingIntent(Context ctx, Note note) { intent.putExtra(EXTRA_NOTE_TITLE, note.getTitle()); intent.putExtra(EXTRA_NOTE_PREVIEW, note.getValuePreview()); intent.putExtra(EXTRA_NOTE_REPEAT, note.getReminderRepeat()); + intent.putExtra(EXTRA_NOTE_INTERVAL_MINUTES, note.getReminderIntervalMinutes()); return PendingIntent.getBroadcast( ctx, note.getId(), intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE diff --git a/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java b/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java index 6f27a24..a2e65f4 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java @@ -15,6 +15,7 @@ public class TaskReminderManager { public static final String EXTRA_TASK_ID = "taskId"; public static final String EXTRA_TASK_TITLE = "taskTitle"; + public static final String EXTRA_TASK_INTERVAL_MINUTES = "intervalMinutes"; // Offset to avoid collision with note reminder PendingIntent request codes private static final int REQUEST_CODE_OFFSET = 100000; @@ -46,6 +47,7 @@ private static PendingIntent buildPendingIntent(Context ctx, Task task) { Intent intent = new Intent(ctx, TaskReminderReceiver.class); intent.putExtra(EXTRA_TASK_ID, task.getId()); intent.putExtra(EXTRA_TASK_TITLE, task.getTitle()); + intent.putExtra(EXTRA_TASK_INTERVAL_MINUTES, task.getReminderIntervalMinutes()); return PendingIntent.getBroadcast(ctx, task.getId() + REQUEST_CODE_OFFSET, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } diff --git a/app/src/main/res/layout/dialog_reminder_picker.xml b/app/src/main/res/layout/dialog_reminder_picker.xml index c4ab66a..fa302c0 100644 --- a/app/src/main/res/layout/dialog_reminder_picker.xml +++ b/app/src/main/res/layout/dialog_reminder_picker.xml @@ -195,6 +195,74 @@ android:textColor="?attr/colorError" android:visibility="gone" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 8bef91f..bed3b47 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -379,4 +379,16 @@ Выдаліць напамін Выберыце будучы час + + Апавяшчэнні нагадванняў + Мелодыя + Гучнасць + + Паўтараць апавяшчэнне + 5 хв + 10 хв + 15 хв + 30 хв + 60 хв + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c2ec545..d725e66 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -381,4 +381,16 @@ Erinnerung löschen Bitte eine zukünftige Zeit wählen + + Erinnerungsbenachrichtigungen + Melodie + Lautstärke + + Benachrichtigung wiederholen + 5 Min. + 10 Min. + 15 Min. + 30 Min. + 60 Min. + \ No newline at end of file diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 27475ac..8660008 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -425,4 +425,16 @@ Clear reminder Please select a future time + + Notification reminders + Melody + Volume + + Repeat notification + 5 min + 10 min + 15 min + 30 min + 60 min + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index e725fb6..59c9de0 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -382,4 +382,16 @@ Eliminar recordatorio Selecciona una hora futura + + Notificaciones de recordatorio + Melodía + Volumen + + Repetir notificación + 5 min + 10 min + 15 min + 30 min + 60 min + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index b5178fb..992ee59 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -377,4 +377,16 @@ Supprimer le rappel Veuillez sélectionner une heure future + + Rappels de notifications + Mélodie + Volume + + Répéter la notification + 5 min + 10 min + 15 min + 30 min + 60 min + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 569f1c1..fcae182 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -379,4 +379,16 @@ Rimuovi promemoria Seleziona un orario futuro + + Notifiche di promemoria + Melodia + Volume + + Ripeti notifica + 5 min + 10 min + 15 min + 30 min + 60 min + \ No newline at end of file diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index df43199..e1c9282 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -378,4 +378,16 @@ Еске салуды өшіру Болашақ уақытты таңдаңыз + + Еске салу хабарландырулары + Әуен + Дыбыс деңгейі + + Хабарландыруды қайталау + 5 мин + 10 мин + 15 мин + 30 мин + 60 мин + \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 5ee5987..8ff56a4 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -380,4 +380,16 @@ Usuń przypomnienie Wybierz przyszły czas + + Powiadomienia przypomnień + Melodia + Głośność + + Powtarzaj powiadomienie + 5 min + 10 min + 15 min + 30 min + 60 min + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 57fbd06..23e15ad 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -384,4 +384,16 @@ Удалить напоминание Выберите будущее время + + Уведомления напоминаний + Мелодия + Громкость + + Повторять уведомление + 5 мин + 10 мин + 15 мин + 30 мин + 60 мин + \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 5d1bf29..4acae40 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -383,4 +383,16 @@ Видалити нагадування Оберіть майбутній час + + Сповіщення нагадувань + Мелодія + Гучність + + Повторювати сповіщення + 5 хв + 10 хв + 15 хв + 30 хв + 60 хв + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2a06a0a..99b24f9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -417,5 +417,17 @@ Clear reminder Please select a future time + + Notification reminders + Melody + Volume + + Repeat notification + 5 min + 10 min + 15 min + 30 min + 60 min + From 148c1bbb0050418f4655dbdb3348739281c2050c Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 09:03:01 +0300 Subject: [PATCH 03/13] fix: NPE in NoteEditorView after release and Slider crash in MoreNoteDialog --- .../com/pasich/mynotes/extendedEditor/NoteEditorView.java | 8 +++++++- app/src/main/res/layout/view_set_note_activity.xml | 1 + 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/NoteEditorView.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/NoteEditorView.java index 84ef34b..959a47e 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/NoteEditorView.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/NoteEditorView.java @@ -85,6 +85,7 @@ protected void onAttachedToWindow() { */ private void loadEditorHtml() { + if (webView == null) return; webView.loadUrl( "file:///android_asset/editor/editor.html?locale=" + Locale.getDefault().getLanguage() ); @@ -159,7 +160,11 @@ public void onEditorReadyFromBridge() { editorIsReady = true; applyTheme(); if (pendingNote != null) { - editorInterface.loadNoteToEditor(pendingNote); + if (editorInterface != null) { + editorInterface.loadNoteToEditor(pendingNote); + } else { + Log.w(TAG, "onEditorReady: editorInterface is null, dropping pending note"); + } pendingNote = null; } handler.postDelayed(this::showEditor, 120); @@ -185,6 +190,7 @@ public void applyTheme() { } void showEditor() { + if (webView == null || loader == null) return; loader.animate().alpha(0f).setDuration(300).withEndAction(() -> loader.setVisibility(View.GONE)).start(); webView.setAlpha(0f); webView.setVisibility(View.VISIBLE); diff --git a/app/src/main/res/layout/view_set_note_activity.xml b/app/src/main/res/layout/view_set_note_activity.xml index e17eac4..6aea48d 100644 --- a/app/src/main/res/layout/view_set_note_activity.xml +++ b/app/src/main/res/layout/view_set_note_activity.xml @@ -34,6 +34,7 @@ android:stepSize="1.0" android:valueFrom="10.0" android:valueTo="32.0" + android:value="16.0" app:tickVisible="false" /> From db32123fd4efd19a91f7c4aca64167d1f24a8326 Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 10:02:06 +0300 Subject: [PATCH 04/13] docs: add Development section with Spotless and git hooks setup && add Tasks and Reminders sections to Help, bump help version to 2.6.46 --- .githooks/pre-commit | 14 + .githooks/pre-push | 19 + README.md | 21 + app/build.gradle | 11 + .../mynotes/data/AttachmentCleanupTest.java | 6 +- .../mynotes/data/AttachmentStorageTest.java | 9 +- .../com/pasich/mynotes/db/MigrationTest.java | 17 +- .../pasich/mynotes/db/NoteRepositoryTest.java | 19 +- .../pasich/mynotes/db/TagsRepositoryTest.java | 27 +- .../main/java/com/pasich/mynotes/MyApp.java | 51 +- .../com/pasich/mynotes/RoutingActivity.java | 9 +- .../mynotes/base/activity/BaseActivity.java | 55 +- .../base/dialog/BaseDialogBottomSheets.java | 21 +- .../mynotes/base/presenter/BasePresenter.java | 11 +- .../base/simplifications/TextWatcher.java | 8 +- .../base/view/BackupOptionsCallback.java | 1 - .../mynotes/base/view/BasePresenter.java | 1 - .../pasich/mynotes/base/view/BaseView.java | 13 +- .../base/view/MoreNoteMainActivityView.java | 2 - .../base/view/MoreNoteNoteActivityView.java | 1 + .../mynotes/base/view/TagsSortView.java | 2 +- .../mynotes/cache/AppPreferencesCache.java | 81 +-- .../cache/NotificationPreferencesCache.java | 24 +- .../mynotes/cache/ThemePreferencesCache.java | 147 +++-- .../pasich/mynotes/data/AppDataManager.java | 77 ++- .../com/pasich/mynotes/data/DataManager.java | 7 +- .../mynotes/data/database/AppDatabase.java | 259 ++++----- .../mynotes/data/database/AppDbHelper.java | 155 +++--- .../mynotes/data/database/DbHelper.java | 5 +- .../mynotes/data/database/dao/NoteDao.java | 13 +- .../mynotes/data/database/dao/TagsDao.java | 11 +- .../data/database/dao/TaskCategoryDao.java | 5 +- .../mynotes/data/database/dao/TaskDao.java | 11 +- .../data/database/dao/Transactions.java | 5 - .../data/database/helpers/DbNotesHelper.java | 11 +- .../data/database/helpers/DbTagsHelper.java | 10 +- .../data/database/helpers/DbTasksHelper.java | 20 +- .../helpers/DbTransactionsHelper.java | 5 +- .../mynotes/data/model/DonationProduct.java | 3 +- .../mynotes/data/model/HelpSection.java | 5 +- .../mynotes/data/model/IndexFilter.java | 2 - .../com/pasich/mynotes/data/model/Note.java | 10 +- .../mynotes/data/model/ReminderRepeat.java | 11 +- .../com/pasich/mynotes/data/model/Tag.java | 14 +- .../com/pasich/mynotes/data/model/Task.java | 95 +++- .../mynotes/data/model/TaskCategory.java | 45 +- .../com/pasich/mynotes/data/model/Theme.java | 1 - .../mynotes/data/model/lib/LibItem.java | 4 +- .../mynotes/data/model/lib/LibSection.java | 3 +- .../preferences/AppPreferencesHelper.java | 75 +-- .../data/preferences/PreferenceHelper.java | 3 - .../data/preferences/SafePreferences.java | 12 +- .../pasich/mynotes/di/ApplicationModule.java | 31 +- .../mynotes/di/activity/ActivityModule.java | 4 - .../mynotes/di/activity/ListUtilsModule.java | 10 +- .../mynotes/di/fragment/FragmentModule.java | 10 +- .../extendedEditor/NoteEditorView.java | 193 +++---- .../attach/AttachmentCleaner.java | 44 +- .../attach/AttachmentStorage.java | 63 +-- .../models/EditorAttachment.java | 6 +- .../extendedEditor/models/ParsedNote.java | 4 - .../models/SettingsEditorJsBridge.java | 2 +- .../utils/EditorAttachmentsWebViewClient.java | 26 +- .../utils/EditorJSInterface.java | 129 ++--- .../extendedEditor/utils/EditorJsonUtils.java | 115 ++-- .../utils/SettingsEditorColors.java | 36 +- .../view/AttachmentActionsDialog.java | 106 ++-- .../extendedEditor/view/CopyTextDialog.java | 57 +- .../mynotes/ui/contract/BackupContract.java | 8 - .../mynotes/ui/contract/MainContract.java | 7 +- .../mynotes/ui/contract/NoteContract.java | 2 - .../mynotes/ui/contract/TagsContract.java | 31 +- .../mynotes/ui/contract/TasksContract.java | 16 +- .../mynotes/ui/contract/TrashContract.java | 2 - .../dialogs/DeleteTagDialogContract.java | 7 +- .../dialogs/MoreNoteDialogContract.java | 2 - .../dialogs/NameTagDialogContract.java | 4 +- .../ui/controllers/SelectionController.java | 73 +-- .../mainActivity/AppUpdateController.java | 64 +-- .../MainRenderListsController.java | 84 ++- .../mainActivity/NavigationController.java | 215 +++++--- .../mainActivity/RedrawThemeController.java | 89 +-- .../mainActivity/SearchController.java | 84 ++- .../mynotes/ui/presenter/BackupPresenter.java | 459 +++++++++------- .../mynotes/ui/presenter/MainPresenter.java | 440 ++++++++------- .../mynotes/ui/presenter/NotePresenter.java | 438 ++++++++------- .../mynotes/ui/presenter/TagsPresenter.java | 133 +++-- .../mynotes/ui/presenter/TasksPresenter.java | 222 ++++---- .../mynotes/ui/presenter/TrashPresenter.java | 75 ++- .../dialogs/DeleteTagDialogPresenter.java | 37 +- .../dialogs/MoreNoteDialogPresenter.java | 160 +++--- .../dialogs/NameTagDialogPresenter.java | 57 +- .../mynotes/ui/receiver/BootReceiver.java | 20 +- .../mynotes/ui/receiver/ReminderReceiver.java | 93 ++-- .../ui/receiver/TaskReminderReceiver.java | 50 +- .../mynotes/ui/state/MainViewState.java | 21 +- .../com/pasich/mynotes/ui/state/UiEvent.java | 11 +- .../pasich/mynotes/ui/tile/NoteQuickTile.java | 5 +- .../ui/view/activity/AboutActivity.java | 39 +- .../ui/view/activity/BackupActivity.java | 423 ++++++++------- .../ui/view/activity/ChangelogActivity.java | 53 +- .../ui/view/activity/HelpActivity.java | 475 +++++++++------- .../ui/view/activity/LibsActivity.java | 35 +- .../ui/view/activity/MainActivity.java | 512 +++++++++--------- .../ui/view/activity/PhotoViewActivity.java | 70 +-- .../ui/view/activity/SettingsActivity.java | 100 ++-- .../ui/view/activity/ShareActivity.java | 92 ++-- .../ui/view/activity/SnoozeActivity.java | 33 +- .../ui/view/activity/SupportActivity.java | 204 ++++--- .../ui/view/activity/TagsActivity.java | 120 ++-- .../ui/view/activity/TasksActivity.java | 171 +++--- .../ui/view/activity/TrashActivity.java | 87 ++- .../noteEditor/BaseNoteEditorActivity.java | 107 ++-- .../activity/noteEditor/NoteActivity.java | 342 ++++++------ .../NoteExtendedEditorActivity.java | 198 ++++--- .../ui/view/dialogs/BackupOptionsDialog.java | 36 +- .../GoogleTakeoutInstructionsBottomSheet.java | 12 +- .../ui/view/dialogs/MoreNoteDialog.java | 269 ++++----- .../ui/view/dialogs/OtherAppImportDialog.java | 27 +- .../dialogs/ReminderPickerBottomSheet.java | 293 +++++----- .../ui/view/dialogs/ShareOptionsDialog.java | 201 ++++--- .../TaskReminderPickerBottomSheet.java | 237 ++++---- .../ui/view/dialogs/ThanksDonatDialog.java | 13 +- .../ui/view/dialogs/TrashInfoDialog.java | 1 - .../view/dialogs/UpdateChangelogDialog.java | 60 +- .../view/dialogs/main/AllTagSelectDialog.java | 18 +- .../ui/view/dialogs/main/DeleteTagDialog.java | 46 +- .../ui/view/dialogs/main/NameTagDialog.java | 93 ++-- .../dialogs/main/SingleTagSelectDialog.java | 63 ++- .../ui/view/dialogs/main/SortDialog.java | 48 +- .../dialogs/main/TagOptionsBottomSheet.java | 56 +- .../main/popupWindowsTag/PopupWindowsTag.java | 82 +-- .../view/dialogs/tasks/AddCategoryDialog.java | 11 +- .../ui/view/dialogs/tasks/AddTaskDialog.java | 17 +- .../view/dialogs/theme/AccentColorDialog.java | 59 +- .../view/dialogs/theme/ThemeModeDialog.java | 58 +- .../fragment/mydata/BackupExportFragment.java | 21 +- .../fragment/mydata/ImportDataFragment.java | 54 +- .../settings/InteractionSettingsFragment.java | 38 +- .../settings/InterfaceSettingsFragment.java | 172 +++--- .../settings/MediaSettingsFragment.java | 152 +++--- .../ui/view/widgets/TwoSideSwitchView.java | 79 ++- .../mynotes/utils/FormattedDataUtil.java | 15 +- .../com/pasich/mynotes/utils/TagsSorter.java | 56 +- .../pasich/mynotes/utils/UpdateChecker.java | 21 +- .../utils/adapters/AccentColorAdapter.java | 21 +- .../utils/adapters/BackupPagerAdapter.java | 3 +- .../adapters/DonationProductAdapter.java | 75 +-- .../utils/adapters/HelpSectionAdapter.java | 26 +- .../utils/adapters/LibsSectionAdapter.java | 13 +- .../utils/adapters/SettingsPagerAdapter.java | 3 +- .../utils/adapters/TagsManagementAdapter.java | 57 +- .../utils/adapters/notes/NoteAdapter.java | 37 +- .../adapters/notes/OnItemClickListener.java | 6 +- .../searchAdapter/SearchNotesAdapter.java | 39 +- .../searchAdapter/SetItemClickListener.java | 1 - .../tagAdapter/OnItemClickListenerTag.java | 2 +- .../adapters/tagAdapter/TagsAdapter.java | 44 +- .../utils/adapters/tasks/TasksAdapter.java | 107 ++-- .../utils/backup/BackupCacheHelper.java | 5 +- .../utils/backup/ScramblerBackupHelper.java | 26 +- .../backup/local/BackupFileValidator.java | 25 +- .../utils/backup/local/LocalBackup.java | 15 +- .../utils/backup/local/LocalBackupHelper.java | 6 +- .../utils/backup/local/ZipBackupHelper.java | 35 +- .../utils/backup/models/JsonBackup.java | 3 - .../backup/models/PreferencesBackup.java | 16 +- .../utils/backup/models/TrashNote.java | 15 +- .../googleKeep/GoogleKeepImportResult.java | 17 +- .../models/googleKeep/GoogleKeepLabel.java | 2 +- .../models/googleKeep/GoogleKeepNote.java | 10 +- .../otherApp/GoogleKeepImportService.java | 24 +- .../utils/changelog/ChangelogManager.java | 21 +- .../mynotes/utils/constants/CloudErrors.java | 1 - .../mynotes/utils/constants/ContactLink.java | 3 +- .../utils/constants/DatabaseConstants.java | 1 - .../constants/settings/PreferencesConfig.java | 42 +- .../utils/constants/settings/SortParam.java | 2 - .../utils/constants/settings/TagSettings.java | 13 +- .../AttachmentsBindingAdapter.java | 4 +- .../databinding/NoteBindingAdapters.java | 92 ++-- .../utils/databinding/TagBindingAdapters.java | 2 - .../pasich/mynotes/utils/enums/SaveState.java | 10 +- .../mynotes/utils/file/FileExportUtils.java | 160 +++--- .../utils/file/HtmlTemplateGenerator.java | 77 ++- .../mynotes/utils/file/ImageOptimizer.java | 62 +-- .../pasich/mynotes/utils/file/OutFormat.java | 10 +- .../mynotes/utils/file/SafeImageLoader.java | 45 +- .../CustomLinkMovementMethod.java | 2 - .../utils/managers/BillingManager.java | 408 ++++++++------ .../utils/managers/SystemTagsManager.java | 40 +- .../utils/navigation/ActivityResultKeys.java | 1 - .../navigation/GoogleTranslateHelper.java | 2 - .../mynotes/utils/navigation/NoteExtras.java | 1 + .../utils/navigation/NoteNavigator.java | 37 +- .../NotificationChannelManager.java | 41 +- .../utils/recycler/SpacesItemDecoration.java | 5 +- .../recycler/SwipeToListNotesCallback.java | 21 +- .../utils/recycler/TagDragCallback.java | 15 +- .../utils/recycler/diffutil/DiffUtilTag.java | 11 +- .../utils/recycler/diffutil/NoteDiff.java | 25 +- .../utils/reminder/ReminderManager.java | 24 +- .../utils/reminder/TaskReminderManager.java | 19 +- .../utils/shareProcessors/ShareProcessor.java | 41 +- .../shareProcessors/SharedNoteCreator.java | 13 +- .../mynotes/utils/themes/ThemesArray.java | 1 - .../mynotes/utils/tool/FormatListTool.java | 7 +- .../mynotes/utils/tool/TextStyleTool.java | 14 +- .../transition/CopyNoteAnimationUtil.java | 36 +- .../utils/transition/TransitionUtil.java | 10 +- app/src/main/res/values-uk/strings.xml | 22 + app/src/main/res/values/strings.xml | 22 + .../mynotes/base/BasePresenterTest.java | 26 +- .../base/RxImmediateSchedulerRule.java | 13 +- .../presenter/BackupPresenterTest.java | 18 +- .../presenter/BasePresenterSafetyTest.java | 18 +- .../mynotes/presenter/MainPresenterTest.java | 36 +- .../presenter/NotePresenterAutoSaveTest.java | 13 +- .../mynotes/presenter/TagsPresenterTest.java | 33 +- .../mynotes/presenter/TrashPresenterTest.java | 37 +- .../mynotes/utils/BackupParserTest.java | 4 +- .../mynotes/utils/SearchFilterTest.java | 24 +- build.gradle | 1 + 223 files changed, 6568 insertions(+), 6212 deletions(-) create mode 100644 .githooks/pre-commit create mode 100644 .githooks/pre-push diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..819486b --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,14 @@ +#!/bin/sh +# Pre-commit: fast format check only (~15s). +# Fix violations: ./gradlew :app:spotlessApply + +echo "[pre-commit] Checking code format..." +./gradlew :app:spotlessCheck -q +if [ $? -ne 0 ]; then + echo "" + echo " Format violations found." + echo " Run: ./gradlew :app:spotlessApply" + echo "" + exit 1 +fi +echo "[pre-commit] Format OK" diff --git a/.githooks/pre-push b/.githooks/pre-push new file mode 100644 index 0000000..a34073d --- /dev/null +++ b/.githooks/pre-push @@ -0,0 +1,19 @@ +#!/bin/sh +# Pre-push: Android Lint check (runs before git push). +# Skip: SKIP_LINT=1 git push + +if [ "$SKIP_LINT" = "1" ]; then + echo "[pre-push] Lint skipped (SKIP_LINT=1)" + exit 0 +fi + +echo "[pre-push] Running Android Lint..." +./gradlew :app:lintDebug -q +if [ $? -ne 0 ]; then + echo "" + echo " Lint errors found. Check: app/build/reports/lint-results-debug.html" + echo " To skip: SKIP_LINT=1 git push" + echo "" + exit 1 +fi +echo "[pre-push] Lint OK" diff --git a/README.md b/README.md index 84161ca..c7b4081 100644 --- a/README.md +++ b/README.md @@ -48,6 +48,27 @@ No tracking, no analytics, no cloud — full privacy by design. If you find bugs, issues, or have ideas to improve the app, please open a new [Issue](https://github.com/pasichDev/My-Notes/issues) in the project repository. +## Development + +**Code style:** [Spotless](https://github.com/diffplug/spotless) + Google Java Format (AOSP, 4-space indent). + +```bash +# Check formatting +./gradlew :app:spotlessCheck + +# Auto-fix formatting +./gradlew :app:spotlessApply +``` + +**Git hooks** — install once after cloning: + +```bash +git config core.hooksPath .githooks +``` + +- **pre-commit** — runs `spotlessCheck` (~15s). Fails fast on format violations. +- **pre-push** — runs `lintDebug` (~2–3 min). Skip with `SKIP_LINT=1 git push`. + ## License This project is licensed under the terms of the [Apache License 2.0](./LICENSE). diff --git a/app/build.gradle b/app/build.gradle index 9f33270..1982465 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,6 +1,7 @@ plugins { id 'com.android.application' id 'com.google.dagger.hilt.android' + id 'com.diffplug.spotless' } apply plugin: 'com.android.application' @@ -189,3 +190,13 @@ dependencies { androidTestImplementation 'com.google.truth:truth:1.4.5' } + +spotless { + java { + target 'src/**/*.java' + googleJavaFormat('1.22.0').aosp() // 4-space indent (AOSP style) + removeUnusedImports() + trimTrailingWhitespace() + endWithNewline() + } +} diff --git a/app/src/androidTest/java/com/pasich/mynotes/data/AttachmentCleanupTest.java b/app/src/androidTest/java/com/pasich/mynotes/data/AttachmentCleanupTest.java index dff6621..90486c9 100644 --- a/app/src/androidTest/java/com/pasich/mynotes/data/AttachmentCleanupTest.java +++ b/app/src/androidTest/java/com/pasich/mynotes/data/AttachmentCleanupTest.java @@ -3,18 +3,14 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; - import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; - import com.pasich.mynotes.extendedEditor.attach.AttachmentCleaner; - +import java.io.File; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.File; - @RunWith(AndroidJUnit4.class) public class AttachmentCleanupTest { diff --git a/app/src/androidTest/java/com/pasich/mynotes/data/AttachmentStorageTest.java b/app/src/androidTest/java/com/pasich/mynotes/data/AttachmentStorageTest.java index 85eabee..85dae77 100644 --- a/app/src/androidTest/java/com/pasich/mynotes/data/AttachmentStorageTest.java +++ b/app/src/androidTest/java/com/pasich/mynotes/data/AttachmentStorageTest.java @@ -3,19 +3,15 @@ import static com.google.common.truth.Truth.assertThat; import android.content.Context; - import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; - import com.pasich.mynotes.extendedEditor.attach.AttachmentCleaner; import com.pasich.mynotes.extendedEditor.attach.AttachmentStorage; - +import java.io.File; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.File; - @RunWith(AndroidJUnit4.class) public class AttachmentStorageTest { @@ -54,7 +50,8 @@ public void cleanup_withNoAttachments_deletesOrphanFiles() throws Exception { orphan.createNewFile(); com.pasich.mynotes.data.model.Note emptyNote = - new com.pasich.mynotes.data.model.Note().create("", "", System.currentTimeMillis(), ""); + new com.pasich.mynotes.data.model.Note() + .create("", "", System.currentTimeMillis(), ""); emptyNote.setId(66666); AttachmentCleaner.cleanup(context, emptyNote); diff --git a/app/src/androidTest/java/com/pasich/mynotes/db/MigrationTest.java b/app/src/androidTest/java/com/pasich/mynotes/db/MigrationTest.java index aa74256..d64a1c5 100644 --- a/app/src/androidTest/java/com/pasich/mynotes/db/MigrationTest.java +++ b/app/src/androidTest/java/com/pasich/mynotes/db/MigrationTest.java @@ -4,25 +4,21 @@ import androidx.sqlite.db.SupportSQLiteDatabase; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; - import com.pasich.mynotes.data.database.AppDatabase; - +import java.io.IOException; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; -import java.io.IOException; - @RunWith(AndroidJUnit4.class) public class MigrationTest { private static final String TEST_DB = "migration-test"; @Rule - public final MigrationTestHelper helper = new MigrationTestHelper( - InstrumentationRegistry.getInstrumentation(), - AppDatabase.class - ); + public final MigrationTestHelper helper = + new MigrationTestHelper( + InstrumentationRegistry.getInstrumentation(), AppDatabase.class); @Test public void migrate2to3_succeeds() throws IOException { @@ -63,7 +59,10 @@ public void migrate6to7_addIsTrashColumn() throws IOException { public void migrateAllStepsChained_succeeds() throws IOException { SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 2); db.close(); - helper.runMigrationsAndValidate(TEST_DB, 7, true, + helper.runMigrationsAndValidate( + TEST_DB, + 7, + true, AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, AppDatabase.MIGRATION_4_5, diff --git a/app/src/androidTest/java/com/pasich/mynotes/db/NoteRepositoryTest.java b/app/src/androidTest/java/com/pasich/mynotes/db/NoteRepositoryTest.java index 6340d22..1a67358 100644 --- a/app/src/androidTest/java/com/pasich/mynotes/db/NoteRepositoryTest.java +++ b/app/src/androidTest/java/com/pasich/mynotes/db/NoteRepositoryTest.java @@ -5,20 +5,17 @@ import androidx.room.Room; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; - import com.pasich.mynotes.data.database.AppDatabase; import com.pasich.mynotes.data.database.dao.NoteDao; import com.pasich.mynotes.data.model.Note; - +import java.util.Collections; +import java.util.Date; +import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.Collections; -import java.util.Date; -import java.util.List; - @RunWith(AndroidJUnit4.class) public class NoteRepositoryTest { @@ -27,10 +24,12 @@ public class NoteRepositoryTest { @Before public void setUp() { - db = Room.inMemoryDatabaseBuilder( - InstrumentationRegistry.getInstrumentation().getTargetContext(), - AppDatabase.class - ).allowMainThreadQueries().build(); + db = + Room.inMemoryDatabaseBuilder( + InstrumentationRegistry.getInstrumentation().getTargetContext(), + AppDatabase.class) + .allowMainThreadQueries() + .build(); noteDao = db.noteDao(); } diff --git a/app/src/androidTest/java/com/pasich/mynotes/db/TagsRepositoryTest.java b/app/src/androidTest/java/com/pasich/mynotes/db/TagsRepositoryTest.java index c7b56f3..1ca090a 100644 --- a/app/src/androidTest/java/com/pasich/mynotes/db/TagsRepositoryTest.java +++ b/app/src/androidTest/java/com/pasich/mynotes/db/TagsRepositoryTest.java @@ -5,19 +5,16 @@ import androidx.room.Room; import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.platform.app.InstrumentationRegistry; - import com.pasich.mynotes.data.database.AppDatabase; import com.pasich.mynotes.data.database.dao.TagsDao; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.utils.managers.SystemTagsManager; - +import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import java.util.List; - @RunWith(AndroidJUnit4.class) public class TagsRepositoryTest { @@ -26,10 +23,12 @@ public class TagsRepositoryTest { @Before public void setUp() { - db = Room.inMemoryDatabaseBuilder( - InstrumentationRegistry.getInstrumentation().getTargetContext(), - AppDatabase.class - ).allowMainThreadQueries().build(); + db = + Room.inMemoryDatabaseBuilder( + InstrumentationRegistry.getInstrumentation().getTargetContext(), + AppDatabase.class) + .allowMainThreadQueries() + .build(); tagsDao = db.tagsDao(); } @@ -54,7 +53,11 @@ public void addTag_appearsInList() { public void deleteTag_removesFromList() { tagsDao.addTag(makeUserTag("Temp")); List before = tagsDao.getTags().blockingFirst(); - Tag inserted = before.stream().filter(t -> "Temp".equals(t.getNameTag())).findFirst().orElseThrow(RuntimeException::new); + Tag inserted = + before.stream() + .filter(t -> "Temp".equals(t.getNameTag())) + .findFirst() + .orElseThrow(RuntimeException::new); tagsDao.deleteTag(inserted); List after = tagsDao.getTags().blockingFirst(); boolean stillExists = after.stream().anyMatch(t -> "Temp".equals(t.getNameTag())); @@ -65,7 +68,11 @@ public void deleteTag_removesFromList() { public void updateTag_changesName() { tagsDao.addTag(makeUserTag("OldName")); List list = tagsDao.getTags().blockingFirst(); - Tag tag = list.stream().filter(t -> "OldName".equals(t.getNameTag())).findFirst().orElseThrow(RuntimeException::new); + Tag tag = + list.stream() + .filter(t -> "OldName".equals(t.getNameTag())) + .findFirst() + .orElseThrow(RuntimeException::new); tag.setNameTag("NewName"); tagsDao.updateTag(tag); List updated = tagsDao.getTags().blockingFirst(); diff --git a/app/src/main/java/com/pasich/mynotes/MyApp.java b/app/src/main/java/com/pasich/mynotes/MyApp.java index 847cdd4..ed67e17 100644 --- a/app/src/main/java/com/pasich/mynotes/MyApp.java +++ b/app/src/main/java/com/pasich/mynotes/MyApp.java @@ -2,18 +2,14 @@ import android.app.Application; import android.util.Log; - import com.pasich.mynotes.cache.NotificationPreferencesCache; import com.pasich.mynotes.cache.ThemePreferencesCache; import com.pasich.mynotes.utils.notification.NotificationChannelManager; - -import java.util.concurrent.Executors; - -import javax.inject.Inject; - import dagger.hilt.android.HiltAndroidApp; import io.reactivex.exceptions.UndeliverableException; import io.reactivex.plugins.RxJavaPlugins; +import java.util.concurrent.Executors; +import javax.inject.Inject; @HiltAndroidApp public class MyApp extends Application { @@ -22,10 +18,8 @@ public class MyApp extends Application { public static volatile float GLOBAL_FONT_SCALE = 1.0f; public static volatile boolean CACHE_READY = false; public static ThemePreferencesCache CACHE; - @Inject - ThemePreferencesCache themePreferencesCache; - @Inject - NotificationPreferencesCache notificationPreferencesCache; + @Inject ThemePreferencesCache themePreferencesCache; + @Inject NotificationPreferencesCache notificationPreferencesCache; @Override public void onCreate() { @@ -35,24 +29,27 @@ public void onCreate() { NotificationChannelManager.ensureChannel(this, notificationPreferencesCache); - Executors.newSingleThreadExecutor().execute(() -> { - themePreferencesCache.initialize(); - notificationPreferencesCache.initialize(); - GLOBAL_FONT_SCALE = themePreferencesCache.getUiFontScale(); - CACHE_READY = true; - }); + Executors.newSingleThreadExecutor() + .execute( + () -> { + themePreferencesCache.initialize(); + notificationPreferencesCache.initialize(); + GLOBAL_FONT_SCALE = themePreferencesCache.getUiFontScale(); + CACHE_READY = true; + }); // Global RxJava ErrorHandler to avoid UndeliverableException - RxJavaPlugins.setErrorHandler(e -> { - if (e instanceof UndeliverableException) { - Throwable cause = e.getCause(); - if (cause instanceof InterruptedException) { - return; - } - Log.w(TAG, "Undeliverable exception received", cause); - } else { - Log.w(TAG, "Unhandled RxJava exception", e); - } - }); + RxJavaPlugins.setErrorHandler( + e -> { + if (e instanceof UndeliverableException) { + Throwable cause = e.getCause(); + if (cause instanceof InterruptedException) { + return; + } + Log.w(TAG, "Undeliverable exception received", cause); + } else { + Log.w(TAG, "Unhandled RxJava exception", e); + } + }); } } diff --git a/app/src/main/java/com/pasich/mynotes/RoutingActivity.java b/app/src/main/java/com/pasich/mynotes/RoutingActivity.java index 8ec451d..fdffc38 100644 --- a/app/src/main/java/com/pasich/mynotes/RoutingActivity.java +++ b/app/src/main/java/com/pasich/mynotes/RoutingActivity.java @@ -2,13 +2,10 @@ import android.content.Intent; import android.os.Bundle; - import androidx.activity.EdgeToEdge; import androidx.core.splashscreen.SplashScreen; - import com.pasich.mynotes.base.activity.BaseActivity; import com.pasich.mynotes.ui.view.activity.MainActivity; - import dagger.hilt.android.AndroidEntryPoint; @AndroidEntryPoint @@ -25,12 +22,10 @@ protected void onCreate(Bundle savedInstanceState) { } @Override - public void initListeners() { - } + public void initListeners() {} private void startNextActivity() { startActivity(new Intent(RoutingActivity.this, MainActivity.class)); finish(); } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/base/activity/BaseActivity.java b/app/src/main/java/com/pasich/mynotes/base/activity/BaseActivity.java index 93a74ca..7908e59 100644 --- a/app/src/main/java/com/pasich/mynotes/base/activity/BaseActivity.java +++ b/app/src/main/java/com/pasich/mynotes/base/activity/BaseActivity.java @@ -12,7 +12,6 @@ import android.view.View; import android.view.WindowManager; import android.widget.TextView; - import androidx.activity.EdgeToEdge; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -20,7 +19,6 @@ import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; - import com.google.android.material.color.MaterialColors; import com.google.android.material.snackbar.Snackbar; import com.pasich.mynotes.MyApp; @@ -29,19 +27,20 @@ import com.pasich.mynotes.cache.ThemePreferencesCache; import com.pasich.mynotes.utils.constants.SnackBarInfo; import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; - import javax.inject.Inject; public abstract class BaseActivity extends AppCompatActivity implements BaseView { - @Inject - ThemePreferencesCache themePreferencesCache; + @Inject ThemePreferencesCache themePreferencesCache; @Override public void selectTheme() { themePreferencesCache.applyCurrentThemeMode(); boolean isDynamicEnabled = themePreferencesCache.isDynamicColorEnabled(); - setTheme(isDynamicEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S ? R.style.AppThemeDynamic : themePreferencesCache.getCurrentThemeStyle()); + setTheme( + isDynamicEnabled && Build.VERSION.SDK_INT >= Build.VERSION_CODES.S + ? R.style.AppThemeDynamic + : themePreferencesCache.getCurrentThemeStyle()); applyScreenProtection(); } @@ -66,9 +65,10 @@ protected void attachBaseContext(Context ctx) { super.attachBaseContext(scaledContext); } - private float readUiFontScale(Context ctx) { - return raw(ctx).getFloat(PreferencesConfig.ARGUMENT_PREFERENCE_UI_SCALING, PreferencesConfig.ARGUMENT_DEFAULT_UI_SCALING_VALUE); + return raw(ctx).getFloat( + PreferencesConfig.ARGUMENT_PREFERENCE_UI_SCALING, + PreferencesConfig.ARGUMENT_DEFAULT_UI_SCALING_VALUE); } @Override @@ -106,9 +106,12 @@ public void onInfoSnack(int resID, View view, int typeInfo, int time) { } public void onInfoSnack(String message, View view, int typeInfo, int time) { - Snackbar snackbar = Snackbar.make(view == null ? findViewById(android.R.id.content) : view, message, time); + Snackbar snackbar = + Snackbar.make( + view == null ? findViewById(android.R.id.content) : view, message, time); - TextView snackbarTextView = snackbar.getView().findViewById(com.google.android.material.R.id.snackbar_text); + TextView snackbarTextView = + snackbar.getView().findViewById(com.google.android.material.R.id.snackbar_text); if (typeInfo != SnackBarInfo.Info) { snackbarTextView.setTypeface(snackbarTextView.getTypeface(), Typeface.BOLD); @@ -116,12 +119,20 @@ public void onInfoSnack(String message, View view, int typeInfo, int time) { switch (typeInfo) { case SnackBarInfo.Success: - snackbar.setBackgroundTint(ContextCompat.getColor(this, R.color.successColorBackground)); - snackbar.setActionTextColor(ContextCompat.getColor(this, R.color.successTextOnColor)); + snackbar.setBackgroundTint( + ContextCompat.getColor(this, R.color.successColorBackground)); + snackbar.setActionTextColor( + ContextCompat.getColor(this, R.color.successTextOnColor)); break; case SnackBarInfo.Error: - snackbar.setBackgroundTint(MaterialColors.getColor(this, com.google.android.material.R.attr.colorErrorContainer, Color.DKGRAY)); - snackbar.setActionTextColor(MaterialColors.getColor(this, com.google.android.material.R.attr.colorOnError, Color.GRAY)); + snackbar.setBackgroundTint( + MaterialColors.getColor( + this, + com.google.android.material.R.attr.colorErrorContainer, + Color.DKGRAY)); + snackbar.setActionTextColor( + MaterialColors.getColor( + this, com.google.android.material.R.attr.colorOnError, Color.GRAY)); break; } @@ -129,10 +140,16 @@ public void onInfoSnack(String message, View view, int typeInfo, int time) { } protected void setupEdgeToEdgeInsets(View rootView) { - ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> { - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(v.getPaddingLeft(), systemBars.top, v.getPaddingRight(), systemBars.bottom); - return insets; - }); + ViewCompat.setOnApplyWindowInsetsListener( + rootView, + (v, insets) -> { + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + v.setPadding( + v.getPaddingLeft(), + systemBars.top, + v.getPaddingRight(), + systemBars.bottom); + return insets; + }); } } diff --git a/app/src/main/java/com/pasich/mynotes/base/dialog/BaseDialogBottomSheets.java b/app/src/main/java/com/pasich/mynotes/base/dialog/BaseDialogBottomSheets.java index 87c045d..47f722e 100644 --- a/app/src/main/java/com/pasich/mynotes/base/dialog/BaseDialogBottomSheets.java +++ b/app/src/main/java/com/pasich/mynotes/base/dialog/BaseDialogBottomSheets.java @@ -4,7 +4,6 @@ import android.os.VibrationEffect; import android.os.Vibrator; import android.view.View; - import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.bottomsheet.BottomSheetDialogFragment; @@ -19,28 +18,22 @@ public void setState(BottomSheetDialog dialog) { } @Override - public void selectTheme() { - - } + public void selectTheme() {} @Override public void vibrateOpenDialog(boolean vibrate) { if (vibrate) { - Vibrator vibrator = (Vibrator) requireActivity().getSystemService(Context.VIBRATOR_SERVICE); + Vibrator vibrator = + (Vibrator) requireActivity().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator != null && vibrator.hasVibrator()) { - VibrationEffect effect = VibrationEffect.createOneShot( - 50, - VibrationEffect.DEFAULT_AMPLITUDE - ); + VibrationEffect effect = + VibrationEffect.createOneShot(50, VibrationEffect.DEFAULT_AMPLITUDE); vibrator.vibrate(effect); } } } - @Override - public void onInfoSnack(int resID, View view, int typeInfo, int time) { - - } -} \ No newline at end of file + public void onInfoSnack(int resID, View view, int typeInfo, int time) {} +} diff --git a/app/src/main/java/com/pasich/mynotes/base/presenter/BasePresenter.java b/app/src/main/java/com/pasich/mynotes/base/presenter/BasePresenter.java index c17e9d9..02455e9 100644 --- a/app/src/main/java/com/pasich/mynotes/base/presenter/BasePresenter.java +++ b/app/src/main/java/com/pasich/mynotes/base/presenter/BasePresenter.java @@ -3,19 +3,21 @@ import com.pasich.mynotes.base.view.BaseView; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.utils.rx.SchedulerProvider; - import io.reactivex.disposables.CompositeDisposable; import io.reactivex.functions.Consumer; -public abstract class BasePresenter implements com.pasich.mynotes.base.view.BasePresenter { +public abstract class BasePresenter + implements com.pasich.mynotes.base.view.BasePresenter { private final SchedulerProvider schedulerProvider; private final CompositeDisposable compositeDisposable; private final DataManager dataManager; private T view; - - public BasePresenter(SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable, DataManager dataManager) { + public BasePresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager) { this.schedulerProvider = schedulerProvider; this.compositeDisposable = compositeDisposable; this.dataManager = dataManager; @@ -64,5 +66,4 @@ protected void runOnView(Consumer action) { } } } - } diff --git a/app/src/main/java/com/pasich/mynotes/base/simplifications/TextWatcher.java b/app/src/main/java/com/pasich/mynotes/base/simplifications/TextWatcher.java index ec5ca23..245ab2a 100644 --- a/app/src/main/java/com/pasich/mynotes/base/simplifications/TextWatcher.java +++ b/app/src/main/java/com/pasich/mynotes/base/simplifications/TextWatcher.java @@ -7,14 +7,10 @@ public abstract class TextWatcher implements android.text.TextWatcher { protected abstract void changeText(Editable s); @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } + public void onTextChanged(CharSequence s, int start, int before, int count) {} @Override public void afterTextChanged(Editable s) { diff --git a/app/src/main/java/com/pasich/mynotes/base/view/BackupOptionsCallback.java b/app/src/main/java/com/pasich/mynotes/base/view/BackupOptionsCallback.java index f27d4db..c1e72d3 100644 --- a/app/src/main/java/com/pasich/mynotes/base/view/BackupOptionsCallback.java +++ b/app/src/main/java/com/pasich/mynotes/base/view/BackupOptionsCallback.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.base.view; - public interface BackupOptionsCallback { void onGoogleDrive(); diff --git a/app/src/main/java/com/pasich/mynotes/base/view/BasePresenter.java b/app/src/main/java/com/pasich/mynotes/base/view/BasePresenter.java index 81e3a2f..784aecf 100644 --- a/app/src/main/java/com/pasich/mynotes/base/view/BasePresenter.java +++ b/app/src/main/java/com/pasich/mynotes/base/view/BasePresenter.java @@ -2,7 +2,6 @@ import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.utils.rx.SchedulerProvider; - import io.reactivex.disposables.CompositeDisposable; public interface BasePresenter { diff --git a/app/src/main/java/com/pasich/mynotes/base/view/BaseView.java b/app/src/main/java/com/pasich/mynotes/base/view/BaseView.java index 6d2bfe0..73e28f3 100644 --- a/app/src/main/java/com/pasich/mynotes/base/view/BaseView.java +++ b/app/src/main/java/com/pasich/mynotes/base/view/BaseView.java @@ -1,9 +1,7 @@ package com.pasich.mynotes.base.view; import android.view.View; - import androidx.annotation.StringRes; - import com.google.android.material.bottomsheet.BottomSheetDialog; public interface BaseView { @@ -14,14 +12,9 @@ public interface BaseView { void onInfoSnack(@StringRes int resID, View view, int typeInfo, int time); - default void redrawActivity() { - } - - default void vibrateOpenDialog(boolean vibrate) { - - } + default void redrawActivity() {} - default void setState(BottomSheetDialog dialog) { + default void vibrateOpenDialog(boolean vibrate) {} - } + default void setState(BottomSheetDialog dialog) {} } diff --git a/app/src/main/java/com/pasich/mynotes/base/view/MoreNoteMainActivityView.java b/app/src/main/java/com/pasich/mynotes/base/view/MoreNoteMainActivityView.java index 7b2651f..c2258a1 100644 --- a/app/src/main/java/com/pasich/mynotes/base/view/MoreNoteMainActivityView.java +++ b/app/src/main/java/com/pasich/mynotes/base/view/MoreNoteMainActivityView.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.base.view; - import com.pasich.mynotes.data.model.Note; public interface MoreNoteMainActivityView { @@ -10,5 +9,4 @@ public interface MoreNoteMainActivityView { void openCopyNote(long idNote); void callbackDeleteNote(Note mNote); - } diff --git a/app/src/main/java/com/pasich/mynotes/base/view/MoreNoteNoteActivityView.java b/app/src/main/java/com/pasich/mynotes/base/view/MoreNoteNoteActivityView.java index e323ae0..19fce25 100644 --- a/app/src/main/java/com/pasich/mynotes/base/view/MoreNoteNoteActivityView.java +++ b/app/src/main/java/com/pasich/mynotes/base/view/MoreNoteNoteActivityView.java @@ -13,5 +13,6 @@ public interface MoreNoteNoteActivityView { void changeTag(String nameTag, boolean change); void openCopyNote(long idNote); + void changeEditor(long idNote); } diff --git a/app/src/main/java/com/pasich/mynotes/base/view/TagsSortView.java b/app/src/main/java/com/pasich/mynotes/base/view/TagsSortView.java index 599e491..66995fc 100644 --- a/app/src/main/java/com/pasich/mynotes/base/view/TagsSortView.java +++ b/app/src/main/java/com/pasich/mynotes/base/view/TagsSortView.java @@ -2,4 +2,4 @@ public interface TagsSortView { void sortTags(String sortParam); -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/cache/AppPreferencesCache.java b/app/src/main/java/com/pasich/mynotes/cache/AppPreferencesCache.java index 64326f0..0eb659f 100644 --- a/app/src/main/java/com/pasich/mynotes/cache/AppPreferencesCache.java +++ b/app/src/main/java/com/pasich/mynotes/cache/AppPreferencesCache.java @@ -1,16 +1,12 @@ package com.pasich.mynotes.cache; import android.util.Log; - import com.pasich.mynotes.data.preferences.SafePreferences; import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; - import javax.inject.Inject; import javax.inject.Singleton; -/** - * Optimized cache for general app preferences (not theme-related). - */ +/** Optimized cache for general app preferences (not theme-related). */ @Singleton public class AppPreferencesCache { @@ -30,36 +26,33 @@ public AppPreferencesCache(SafePreferences prefs) { this.prefs = prefs; } - /** - * Initialize cache by loading values from SharedPreferences. - */ + /** Initialize cache by loading values from SharedPreferences. */ public synchronized void initialize() { if (initialized) return; try { - lastKnownVersion = prefs.getString( - PreferencesConfig.ARGUMENT_PREFERENCE_LAST_KNOWN_VERSION, "0" - ); - - sortPref = prefs.getString( - PreferencesConfig.ARGUMENT_PREFERENCE_SORT, - PreferencesConfig.ARGUMENT_DEFAULT_SORT_PREF - ); - - tagsSortPref = prefs.getString( - PreferencesConfig.ARGUMENT_PREFERENCE_TAGS_SORT, - PreferencesConfig.ARGUMENT_DEFAULT_TAGS_SORT_PREF - ); - - formatPref = prefs.getInt( - PreferencesConfig.ARGUMENT_PREFERENCE_FORMAT, - PreferencesConfig.ARGUMENT_DEFAULT_FORMAT_VALUE - ); - - imageOptEnable = prefs.getBoolean( - PreferencesConfig.ARGUMENT_PREFERENCE_IMAGEOPT, - PreferencesConfig.ARGUMENT_DEFAULT_IMAGEOPT_VALUE - ); + lastKnownVersion = + prefs.getString(PreferencesConfig.ARGUMENT_PREFERENCE_LAST_KNOWN_VERSION, "0"); + + sortPref = + prefs.getString( + PreferencesConfig.ARGUMENT_PREFERENCE_SORT, + PreferencesConfig.ARGUMENT_DEFAULT_SORT_PREF); + + tagsSortPref = + prefs.getString( + PreferencesConfig.ARGUMENT_PREFERENCE_TAGS_SORT, + PreferencesConfig.ARGUMENT_DEFAULT_TAGS_SORT_PREF); + + formatPref = + prefs.getInt( + PreferencesConfig.ARGUMENT_PREFERENCE_FORMAT, + PreferencesConfig.ARGUMENT_DEFAULT_FORMAT_VALUE); + + imageOptEnable = + prefs.getBoolean( + PreferencesConfig.ARGUMENT_PREFERENCE_IMAGEOPT, + PreferencesConfig.ARGUMENT_DEFAULT_IMAGEOPT_VALUE); initialized = true; @@ -88,10 +81,7 @@ public String getLastKnownVersion() { public synchronized void setLastKnownVersion(String version) { try { lastKnownVersion = version; - prefs.putString( - PreferencesConfig.ARGUMENT_PREFERENCE_LAST_KNOWN_VERSION, - version - ); + prefs.putString(PreferencesConfig.ARGUMENT_PREFERENCE_LAST_KNOWN_VERSION, version); } catch (Exception e) { Log.e(TAG, "Failed to save version", e); } @@ -105,10 +95,7 @@ public String getSortPref() { public synchronized void setSortPref(String sort) { try { sortPref = sort; - prefs.putString( - PreferencesConfig.ARGUMENT_PREFERENCE_SORT, - sort - ); + prefs.putString(PreferencesConfig.ARGUMENT_PREFERENCE_SORT, sort); } catch (Exception e) { Log.e(TAG, "Failed to save sort preference", e); } @@ -122,10 +109,7 @@ public boolean getImageOpt() { public synchronized void setImageOpt(boolean mImageOpt) { try { imageOptEnable = mImageOpt; - prefs.putBoolean( - PreferencesConfig.ARGUMENT_PREFERENCE_IMAGEOPT, - mImageOpt - ); + prefs.putBoolean(PreferencesConfig.ARGUMENT_PREFERENCE_IMAGEOPT, mImageOpt); } catch (Exception e) { Log.e(TAG, "Failed to image_opt preference", e); } @@ -139,10 +123,7 @@ public String getTagsSortPref() { public synchronized void setTagsSortPref(String tagsSort) { try { tagsSortPref = tagsSort; - prefs.putString( - PreferencesConfig.ARGUMENT_PREFERENCE_TAGS_SORT, - tagsSort - ); + prefs.putString(PreferencesConfig.ARGUMENT_PREFERENCE_TAGS_SORT, tagsSort); } catch (Exception e) { Log.e(TAG, "Failed to save tags sort preference", e); } @@ -156,10 +137,7 @@ public int getFormatPref() { public synchronized void setFormatPref(int format) { try { formatPref = format; - prefs.putInt( - PreferencesConfig.ARGUMENT_PREFERENCE_FORMAT, - format - ); + prefs.putInt(PreferencesConfig.ARGUMENT_PREFERENCE_FORMAT, format); } catch (Exception e) { Log.e(TAG, "Failed to save format preference", e); } @@ -174,7 +152,6 @@ private void ensureInitialized() { } } - public synchronized void refresh() { initialized = false; initialize(); diff --git a/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java b/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java index df94c66..016d91e 100644 --- a/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java +++ b/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java @@ -1,10 +1,8 @@ package com.pasich.mynotes.cache; import android.util.Log; - import com.pasich.mynotes.data.preferences.SafePreferences; import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; - import javax.inject.Inject; import javax.inject.Singleton; @@ -30,14 +28,17 @@ public synchronized void initialize() { return; } try { - soundUri = prefs.getString( - PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_SOUND_URI, null); - channelId = prefs.getString( - PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_ID, - PreferencesConfig.ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_ID); - channelVersion = prefs.getInt( - PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_VERSION, - PreferencesConfig.ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_VERSION); + soundUri = + prefs.getString( + PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_SOUND_URI, null); + channelId = + prefs.getString( + PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_ID, + PreferencesConfig.ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_ID); + channelVersion = + prefs.getInt( + PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_VERSION, + PreferencesConfig.ARGUMENT_DEFAULT_NOTIFICATION_CHANNEL_VERSION); initialized = true; } catch (Exception e) { Log.e(TAG, "Failed to initialize cache", e); @@ -88,7 +89,8 @@ public int getChannelVersion() { public synchronized int incrementAndPersistVersion() { ensureInitialized(); channelVersion++; - prefs.putInt(PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_VERSION, channelVersion); + prefs.putInt( + PreferencesConfig.ARGUMENT_PREFERENCE_NOTIFICATION_CHANNEL_VERSION, channelVersion); setChannelId("reminders_v" + channelVersion); return channelVersion; } diff --git a/app/src/main/java/com/pasich/mynotes/cache/ThemePreferencesCache.java b/app/src/main/java/com/pasich/mynotes/cache/ThemePreferencesCache.java index f2246fa..a98f5ea 100644 --- a/app/src/main/java/com/pasich/mynotes/cache/ThemePreferencesCache.java +++ b/app/src/main/java/com/pasich/mynotes/cache/ThemePreferencesCache.java @@ -1,25 +1,19 @@ package com.pasich.mynotes.cache; import android.util.Log; - import androidx.appcompat.app.AppCompatDelegate; - import com.pasich.mynotes.data.preferences.SafePreferences; import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; import com.pasich.mynotes.utils.themes.ThemesArray; - import javax.inject.Inject; import javax.inject.Singleton; /** - * Optimized cache for theme preferences to reduce SharedPreferences I/O operations. - * Uses asynchronous PowerPreference methods to avoid UI blocking. - * Performance improvements: - * - Single initialization load from SharedPreferences - * - Memory-based getters for fast access - * - Asynchronous put operations to prevent UI blocking - * - Thread-safe operations with synchronized methods - * - Comprehensive error handling with user feedback + * Optimized cache for theme preferences to reduce SharedPreferences I/O operations. Uses + * asynchronous PowerPreference methods to avoid UI blocking. Performance improvements: - Single + * initialization load from SharedPreferences - Memory-based getters for fast access - Asynchronous + * put operations to prevent UI blocking - Thread-safe operations with synchronized methods - + * Comprehensive error handling with user feedback */ @Singleton public class ThemePreferencesCache { @@ -43,8 +37,8 @@ public ThemePreferencesCache(SafePreferences prefs) { } /** - * Initialize cache by loading values from SharedPreferences. - * Should be called once in Application.onCreate() + * Initialize cache by loading values from SharedPreferences. Should be called once in + * Application.onCreate() */ public synchronized void initialize() { if (initialized) { @@ -54,21 +48,45 @@ public synchronized void initialize() { try { // Load all values from SharedPreferences in one batch - themeMode = prefs.getInt(PreferencesConfig.ARGUMENT_PREFERENCE_THEME_MODE, PreferencesConfig.ARGUMENT_DEFAULT_THEME_MODE_VALUE); - - themeId = prefs.getInt(PreferencesConfig.ARGUMENT_PREFERENCE_THEME, PreferencesConfig.ARGUMENT_DEFAULT_THEME_VALUE); - - dynamicColor = prefs.getBoolean(PreferencesConfig.ARGUMENT_PREFERENCE_DYNAMIC_COLOR, PreferencesConfig.ARGUMENT_DEFAULT_DYNAMIC_COLOR_VALUE); - - screenProtection = prefs.getBoolean(PreferencesConfig.ARGUMENT_PREFERENCE_SCREEN_PROTECTION, PreferencesConfig.ARGUMENT_DEFAULT_SCREEN_PROTECTION_VALUE); - - extendedEditor = prefs.getBoolean(PreferencesConfig.ARGUMENT_PREFERENCE_EXTENDED_EDITOR, PreferencesConfig.ARGUMENT_DEFAULT_EXTENDED_EDITOR_VALUE); - - typeFaceNoteActivity = prefs.getString(PreferencesConfig.ARGUMENT_PREFERENCE_TEXT_STYLE, PreferencesConfig.ARGUMENT_DEFAULT_TEXT_STYLE); - - sizeTextNoteActivity = prefs.getInt(PreferencesConfig.ARGUMENT_PREFERENCE_TEXT_SIZE, PreferencesConfig.ARGUMENT_DEFAULT_TEXT_SIZE); - - uiFontScale = prefs.getFloat(PreferencesConfig.ARGUMENT_PREFERENCE_UI_SCALING, PreferencesConfig.ARGUMENT_DEFAULT_UI_SCALING_VALUE); + themeMode = + prefs.getInt( + PreferencesConfig.ARGUMENT_PREFERENCE_THEME_MODE, + PreferencesConfig.ARGUMENT_DEFAULT_THEME_MODE_VALUE); + + themeId = + prefs.getInt( + PreferencesConfig.ARGUMENT_PREFERENCE_THEME, + PreferencesConfig.ARGUMENT_DEFAULT_THEME_VALUE); + + dynamicColor = + prefs.getBoolean( + PreferencesConfig.ARGUMENT_PREFERENCE_DYNAMIC_COLOR, + PreferencesConfig.ARGUMENT_DEFAULT_DYNAMIC_COLOR_VALUE); + + screenProtection = + prefs.getBoolean( + PreferencesConfig.ARGUMENT_PREFERENCE_SCREEN_PROTECTION, + PreferencesConfig.ARGUMENT_DEFAULT_SCREEN_PROTECTION_VALUE); + + extendedEditor = + prefs.getBoolean( + PreferencesConfig.ARGUMENT_PREFERENCE_EXTENDED_EDITOR, + PreferencesConfig.ARGUMENT_DEFAULT_EXTENDED_EDITOR_VALUE); + + typeFaceNoteActivity = + prefs.getString( + PreferencesConfig.ARGUMENT_PREFERENCE_TEXT_STYLE, + PreferencesConfig.ARGUMENT_DEFAULT_TEXT_STYLE); + + sizeTextNoteActivity = + prefs.getInt( + PreferencesConfig.ARGUMENT_PREFERENCE_TEXT_SIZE, + PreferencesConfig.ARGUMENT_DEFAULT_TEXT_SIZE); + + uiFontScale = + prefs.getFloat( + PreferencesConfig.ARGUMENT_PREFERENCE_UI_SCALING, + PreferencesConfig.ARGUMENT_DEFAULT_UI_SCALING_VALUE); initialized = true; } catch (Exception e) { @@ -77,9 +95,7 @@ public synchronized void initialize() { } } - /** - * Set default values if initialization fails - */ + /** Set default values if initialization fails */ private void setDefaults() { themeMode = PreferencesConfig.ARGUMENT_DEFAULT_THEME_MODE_VALUE; themeId = PreferencesConfig.ARGUMENT_DEFAULT_THEME_VALUE; @@ -93,17 +109,13 @@ private void setDefaults() { Log.d(TAG, "Set default values after initialization failure"); } - /** - * Get cached theme mode (0=System, 1=Light, 2=Dark) - */ + /** Get cached theme mode (0=System, 1=Light, 2=Dark) */ public int getThemeMode() { ensureInitialized(); return themeMode; } - /** - * Set theme mode with asynchronous persistence - */ + /** Set theme mode with asynchronous persistence */ public synchronized void setThemeMode(int themeMode) { try { this.themeMode = themeMode; @@ -114,17 +126,13 @@ public synchronized void setThemeMode(int themeMode) { } } - /** - * Get cached theme ID - */ + /** Get cached theme ID */ public int getThemeId() { ensureInitialized(); return themeId; } - /** - * Set theme ID with asynchronous persistence - */ + /** Set theme ID with asynchronous persistence */ public synchronized void setThemeId(int themeId) { try { this.themeId = themeId; @@ -135,25 +143,19 @@ public synchronized void setThemeId(int themeId) { } } - /** - * Get cached dynamic color setting - */ + /** Get cached dynamic color setting */ public boolean isDynamicColorEnabled() { ensureInitialized(); return dynamicColor; } - /** - * Get cached screen protection setting - */ + /** Get cached screen protection setting */ public boolean isScreenProtectionEnabled() { ensureInitialized(); return screenProtection; } - /** - * Get cached extended editor setting - */ + /** Get cached extended editor setting */ public boolean isExtendedEditorEnabled() { ensureInitialized(); return extendedEditor; @@ -201,10 +203,7 @@ public synchronized void setTypeFaceNoteActivity(String typeFace) { } } - - /** - * Set dynamic color with asynchronous persistence - */ + /** Set dynamic color with asynchronous persistence */ public synchronized void setDynamicColor(boolean enabled) { try { this.dynamicColor = enabled; @@ -214,9 +213,7 @@ public synchronized void setDynamicColor(boolean enabled) { } } - /** - * Set screen protection with asynchronous persistence - */ + /** Set screen protection with asynchronous persistence */ public synchronized void setScreenProtection(boolean enabled) { try { this.screenProtection = enabled; @@ -226,9 +223,7 @@ public synchronized void setScreenProtection(boolean enabled) { } } - /** - * Set extended editor with asynchronous persistence - */ + /** Set extended editor with asynchronous persistence */ public synchronized void setExtendedEditor(boolean enabled) { try { this.extendedEditor = enabled; @@ -238,17 +233,19 @@ public synchronized void setExtendedEditor(boolean enabled) { } } - // Helper methods /** - * Apply current theme mode to AppCompatDelegate - * Safe method with fallback for early calls before DI initialization + * Apply current theme mode to AppCompatDelegate Safe method with fallback for early calls + * before DI initialization */ public void applyCurrentThemeMode() { if (!initialized) { try { - int fallbackThemeMode = prefs.getInt(PreferencesConfig.ARGUMENT_PREFERENCE_THEME_MODE, PreferencesConfig.ARGUMENT_DEFAULT_THEME_MODE_VALUE); + int fallbackThemeMode = + prefs.getInt( + PreferencesConfig.ARGUMENT_PREFERENCE_THEME_MODE, + PreferencesConfig.ARGUMENT_DEFAULT_THEME_MODE_VALUE); applyThemeModeInternal(fallbackThemeMode); Log.d(TAG, "Applied fallback theme mode: " + fallbackThemeMode); } catch (Exception e) { @@ -261,9 +258,7 @@ public void applyCurrentThemeMode() { applyThemeModeInternal(themeMode); } - /** - * Internal method to apply theme mode - */ + /** Internal method to apply theme mode */ private void applyThemeModeInternal(int themeModeValue) { switch (themeModeValue) { case 0: // Follow System @@ -281,32 +276,24 @@ private void applyThemeModeInternal(int themeModeValue) { } } - /** - * Get current theme style resource ID - */ + /** Get current theme style resource ID */ public int getCurrentThemeStyle() { ensureInitialized(); return new ThemesArray().getThemeStyle(themeId); } - /** - * Clear cache and reload from SharedPreferences - */ + /** Clear cache and reload from SharedPreferences */ public synchronized void refresh() { initialized = false; initialize(); Log.d(TAG, "Cache refreshed"); } - /** - * Check if cache is initialized, initialize if needed - */ + /** Check if cache is initialized, initialize if needed */ private void ensureInitialized() { if (!initialized) { Log.w(TAG, "Cache not initialized, initializing now"); initialize(); } } - - } diff --git a/app/src/main/java/com/pasich/mynotes/data/AppDataManager.java b/app/src/main/java/com/pasich/mynotes/data/AppDataManager.java index 4543335..27d7d8f 100644 --- a/app/src/main/java/com/pasich/mynotes/data/AppDataManager.java +++ b/app/src/main/java/com/pasich/mynotes/data/AppDataManager.java @@ -1,9 +1,7 @@ package com.pasich.mynotes.data; - import android.content.Context; import android.net.Uri; - import com.pasich.mynotes.data.database.DbHelper; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.Tag; @@ -15,45 +13,37 @@ import com.pasich.mynotes.utils.backup.local.LocalBackup; import com.pasich.mynotes.utils.backup.models.JsonBackup; import com.pasich.mynotes.utils.backup.models.PreferencesBackup; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - import dagger.hilt.android.qualifiers.ApplicationContext; - import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.Observable; import io.reactivex.Single; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; @Singleton public class AppDataManager implements DataManager { - private final Context context; private final DbHelper dbHelper; private final AppPreferencesHelper preferencesHelper; private final LocalBackup apiBackup; @Inject - AppDataManager(@ApplicationContext Context context, - AppPreferencesHelper preferencesHelper, - DbHelper dbHelper, - LocalBackup apiBackup) { + AppDataManager( + @ApplicationContext Context context, + AppPreferencesHelper preferencesHelper, + DbHelper dbHelper, + LocalBackup apiBackup) { this.context = context; this.dbHelper = dbHelper; this.preferencesHelper = preferencesHelper; this.apiBackup = apiBackup; } - - /** - * PreferencesBackup - */ - + /** PreferencesBackup */ @Override public int getFormatCount() { return preferencesHelper.getFormatCount(); @@ -109,11 +99,7 @@ public String getTypeFaceNoteActivity() { return preferencesHelper.getTypeFaceNoteActivity(); } - - /** - * Tags - */ - + /** Tags */ @Override public Flowable> getTags() { return dbHelper.getTags(); @@ -187,13 +173,15 @@ public Completable transferNotesOutTrash(List ids) { @Override public Completable clearTrash() { return dbHelper.getNotesInTrash() - .firstOrError() - .flatMapCompletable(notes -> { - for (Note note : notes) { - AttachmentCleaner.deleteAttachmentFolderByNoteId(context, note.getId()); - } - return dbHelper.clearTrash(); - }); + .firstOrError() + .flatMapCompletable( + notes -> { + for (Note note : notes) { + AttachmentCleaner.deleteAttachmentFolderByNoteId( + context, note.getId()); + } + return dbHelper.clearTrash(); + }); } @Override @@ -201,7 +189,6 @@ public Completable setTagForNotes(String tag, List noteIds) { return dbHelper.setTagForNotes(tag, noteIds); } - @Override public Completable renameTag(Tag mTag, String newName) { return dbHelper.renameTag(mTag, newName); @@ -212,15 +199,12 @@ public Completable restoreNotesAndFixTags(List ids) { return dbHelper.restoreNotesAndFixTags(ids); } - @Override public Single getCountData() { return dbHelper.getCountData(); } - /** - * Notes - */ + /** Notes */ @Override public Flowable> getNotes() { return dbHelper.getNotes(); @@ -236,7 +220,6 @@ public Completable addNotes(List notes) { return dbHelper.addNotes(notes); } - @Override public Observable> getNotesForTag(String nameTag) { return dbHelper.getNotesForTag(nameTag); @@ -252,13 +235,11 @@ public Single getNoteForId(long idNote) { return dbHelper.getNoteForId(idNote); } - @Override public Single addNote(Note note, boolean copyNote) { return dbHelper.addNote(note, copyNote); } - @Override public Completable deleteNote(Note note) { return dbHelper.deleteNote(note); @@ -266,11 +247,14 @@ public Completable deleteNote(Note note) { @Override public Completable deleteNote(ArrayList notes) { - return Completable.fromAction(() -> { - for (Note note : notes) { - AttachmentCleaner.deleteAttachmentFolderByNoteId(context, note.getId()); - } - }).andThen(dbHelper.deleteNote(notes)); + return Completable.fromAction( + () -> { + for (Note note : notes) { + AttachmentCleaner.deleteAttachmentFolderByNoteId( + context, note.getId()); + } + }) + .andThen(dbHelper.deleteNote(notes)); } @Override @@ -334,7 +318,8 @@ public Completable updateNoteReminder(int noteId, long reminderTime, String repe } @Override - public Completable updateNoteReminderFull(int noteId, long reminderTime, String repeat, int intervalMinutes) { + public Completable updateNoteReminderFull( + int noteId, long reminderTime, String repeat, int intervalMinutes) { return dbHelper.updateNoteReminderFull(noteId, reminderTime, repeat, intervalMinutes); } diff --git a/app/src/main/java/com/pasich/mynotes/data/DataManager.java b/app/src/main/java/com/pasich/mynotes/data/DataManager.java index 59e77a7..52a6368 100644 --- a/app/src/main/java/com/pasich/mynotes/data/DataManager.java +++ b/app/src/main/java/com/pasich/mynotes/data/DataManager.java @@ -1,11 +1,8 @@ package com.pasich.mynotes.data; -import com.pasich.mynotes.utils.backup.local.LocalBackupHelper; import com.pasich.mynotes.data.database.DbHelper; import com.pasich.mynotes.data.database.helpers.DbNotesHelper; import com.pasich.mynotes.data.preferences.PreferenceHelper; +import com.pasich.mynotes.utils.backup.local.LocalBackupHelper; -public interface DataManager extends DbHelper, PreferenceHelper, DbNotesHelper, LocalBackupHelper { - - -} +public interface DataManager extends DbHelper, PreferenceHelper, DbNotesHelper, LocalBackupHelper {} diff --git a/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java b/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java index ed8891a..dd612e0 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java @@ -1,14 +1,12 @@ package com.pasich.mynotes.data.database; import android.content.Context; - import androidx.annotation.NonNull; import androidx.room.AutoMigration; import androidx.room.Database; import androidx.room.RoomDatabase; import androidx.room.migration.Migration; import androidx.sqlite.db.SupportSQLiteDatabase; - import com.pasich.mynotes.data.database.dao.NoteDao; import com.pasich.mynotes.data.database.dao.TagsDao; import com.pasich.mynotes.data.database.dao.TaskCategoryDao; @@ -21,44 +19,37 @@ import com.pasich.mynotes.data.preferences.SafePreferences; import com.pasich.mynotes.utils.constants.DatabaseConstants; import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; - import javax.inject.Singleton; -@Database(version = DatabaseConstants.DB_VERSION, +@Database( + version = DatabaseConstants.DB_VERSION, entities = {Tag.class, Note.class, Task.class, TaskCategory.class}, - autoMigrations = { - @AutoMigration(from = 1, to = 2) - }) + autoMigrations = {@AutoMigration(from = 1, to = 2)}) @Singleton public abstract class AppDatabase extends RoomDatabase { + /** Remove old system tags Add the position field to the tags table */ + public static final Migration MIGRATION_3_4 = + new Migration(3, 4) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL( + "ALTER TABLE tags ADD COLUMN position INTEGER NOT NULL DEFAULT 0"); + } + }; + /** - * Remove old system tags - * Add the position field to the tags table - */ - public static final Migration MIGRATION_3_4 = new Migration(3, 4) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - database.execSQL("ALTER TABLE tags ADD COLUMN position INTEGER NOT NULL DEFAULT 0"); - } - }; - /** - * Додаємо колонку attachments json до таблиці notes - * [ - * { - * «url»: «file:///data/user/0/.../attachments/file1.pdf», - * «name»: «file1.pdf», - * «extension»: «pdf», - * «size»: 12345 - * } - * ] + * Додаємо колонку attachments json до таблиці notes [ { «url»: + * «file:///data/user/0/.../attachments/file1.pdf», «name»: «file1.pdf», «extension»: «pdf», + * «size»: 12345 } ] */ - public static final Migration MIGRATION_5_6 = new Migration(5, 6) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - database.execSQL("ALTER TABLE notes ADD COLUMN attachments TEXT DEFAULT ''"); - } - }; + public static final Migration MIGRATION_5_6 = + new Migration(5, 6) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE notes ADD COLUMN attachments TEXT DEFAULT ''"); + } + }; // MIGRATION 6 → 7 // ------------------------------- @@ -80,104 +71,124 @@ public void migrate(@NonNull SupportSQLiteDatabase database) { // 3) After successful migration, the `trash` table is deleted. // - public static final Migration MIGRATION_6_7 = new Migration(6, 7) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase db) { - db.execSQL("ALTER TABLE notes ADD COLUMN isTrash INTEGER NOT NULL DEFAULT 0"); - db.execSQL(""" + public static final Migration MIGRATION_6_7 = + new Migration(6, 7) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + db.execSQL("ALTER TABLE notes ADD COLUMN isTrash INTEGER NOT NULL DEFAULT 0"); + db.execSQL( + """ INSERT INTO notes (title, value, date, tag, valueJson, attachments, hasRichContent, isTrash) SELECT title, value, date, '', '', '', 0, 1 FROM trash """); - db.execSQL("DROP TABLE IF EXISTS trash"); - } - }; - - public static final Migration MIGRATION_7_8 = new Migration(7, 8) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase db) { - db.execSQL("ALTER TABLE notes ADD COLUMN reminderTime INTEGER DEFAULT NULL"); - db.execSQL("ALTER TABLE notes ADD COLUMN reminderRepeat TEXT NOT NULL DEFAULT 'NONE'"); - } - }; - - public static final Migration MIGRATION_9_10 = new Migration(9, 10) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase db) { - db.execSQL("ALTER TABLE tasks ADD COLUMN description TEXT"); - } - }; - - public static final Migration MIGRATION_10_11 = new Migration(10, 11) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase db) { - db.execSQL("ALTER TABLE tasks ADD COLUMN reminderTime INTEGER DEFAULT NULL"); - } - }; - - public static final Migration MIGRATION_11_12 = new Migration(11, 12) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase db) { - db.execSQL("ALTER TABLE notes ADD COLUMN isPinned INTEGER NOT NULL DEFAULT 0"); - } - }; - - public static final Migration MIGRATION_12_13 = new Migration(12, 13) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase db) { - db.execSQL("ALTER TABLE notes ADD COLUMN reminderIntervalMinutes INTEGER NOT NULL DEFAULT 0"); - db.execSQL("ALTER TABLE tasks ADD COLUMN reminderIntervalMinutes INTEGER NOT NULL DEFAULT 0"); - } - }; - - public static final Migration MIGRATION_8_9 = new Migration(8, 9) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase db) { - db.execSQL("CREATE TABLE IF NOT EXISTS `tasks` (" + - "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + - "`title` TEXT NOT NULL, " + - "`isDone` INTEGER NOT NULL DEFAULT 0, " + - "`categoryId` INTEGER NOT NULL DEFAULT 0, " + - "`createdAt` INTEGER NOT NULL DEFAULT 0, " + - "`position` INTEGER NOT NULL DEFAULT 0)"); - db.execSQL("CREATE TABLE IF NOT EXISTS `task_categories` (" + - "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + - "`name` TEXT NOT NULL, " + - "`colorHex` TEXT NOT NULL DEFAULT '#6750A4', " + - "`position` INTEGER NOT NULL DEFAULT 0)"); - } - }; + db.execSQL("DROP TABLE IF EXISTS trash"); + } + }; + + public static final Migration MIGRATION_7_8 = + new Migration(7, 8) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + db.execSQL("ALTER TABLE notes ADD COLUMN reminderTime INTEGER DEFAULT NULL"); + db.execSQL( + "ALTER TABLE notes ADD COLUMN reminderRepeat TEXT NOT NULL DEFAULT 'NONE'"); + } + }; + + public static final Migration MIGRATION_9_10 = + new Migration(9, 10) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + db.execSQL("ALTER TABLE tasks ADD COLUMN description TEXT"); + } + }; + + public static final Migration MIGRATION_10_11 = + new Migration(10, 11) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + db.execSQL("ALTER TABLE tasks ADD COLUMN reminderTime INTEGER DEFAULT NULL"); + } + }; + + public static final Migration MIGRATION_11_12 = + new Migration(11, 12) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + db.execSQL("ALTER TABLE notes ADD COLUMN isPinned INTEGER NOT NULL DEFAULT 0"); + } + }; + + public static final Migration MIGRATION_12_13 = + new Migration(12, 13) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + db.execSQL( + "ALTER TABLE notes ADD COLUMN reminderIntervalMinutes INTEGER NOT NULL DEFAULT 0"); + db.execSQL( + "ALTER TABLE tasks ADD COLUMN reminderIntervalMinutes INTEGER NOT NULL DEFAULT 0"); + } + }; + + public static final Migration MIGRATION_8_9 = + new Migration(8, 9) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase db) { + db.execSQL( + "CREATE TABLE IF NOT EXISTS `tasks` (" + + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + + "`title` TEXT NOT NULL, " + + "`isDone` INTEGER NOT NULL DEFAULT 0, " + + "`categoryId` INTEGER NOT NULL DEFAULT 0, " + + "`createdAt` INTEGER NOT NULL DEFAULT 0, " + + "`position` INTEGER NOT NULL DEFAULT 0)"); + db.execSQL( + "CREATE TABLE IF NOT EXISTS `task_categories` (" + + "`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, " + + "`name` TEXT NOT NULL, " + + "`colorHex` TEXT NOT NULL DEFAULT '#6750A4', " + + "`position` INTEGER NOT NULL DEFAULT 0)"); + } + }; private static Context appContext; + + /** Remove old system tags Set lastKnownVersion = “2.1.29” for migration */ + public static final Migration MIGRATION_2_3 = + new Migration(2, 3) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL( + "DELETE FROM tags WHERE name = '' AND visibility = 0 AND systemAction = 1"); + database.execSQL( + "DELETE FROM tags WHERE name = 'allNotes' AND visibility = 0 AND systemAction = 2"); + + if (appContext != null) { + prefs().putString( + PreferencesConfig.ARGUMENT_PREFERENCE_LAST_KNOWN_VERSION, + "2.1.29"); + } + } + }; + /** - * Remove old system tags - * Set lastKnownVersion = “2.1.29” for migration - */ - public static final Migration MIGRATION_2_3 = new Migration(2, 3) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - database.execSQL("DELETE FROM tags WHERE name = '' AND visibility = 0 AND systemAction = 1"); - database.execSQL("DELETE FROM tags WHERE name = 'allNotes' AND visibility = 0 AND systemAction = 2"); - - if (appContext != null) { - prefs().putString(PreferencesConfig.ARGUMENT_PREFERENCE_LAST_KNOWN_VERSION, "2.1.29"); - } - } - }; - /** - * Add the valueJson column (nullable) to the notes table. - * Add the hasRichContent column (boolean, NOT NULL, default false). - * Set extendedEditorEnable = false for existing users. + * Add the valueJson column (nullable) to the notes table. Add the hasRichContent column + * (boolean, NOT NULL, default false). Set extendedEditorEnable = false for existing users. */ - public static final Migration MIGRATION_4_5 = new Migration(4, 5) { - @Override - public void migrate(@NonNull SupportSQLiteDatabase database) { - database.execSQL("ALTER TABLE notes ADD COLUMN valueJson TEXT"); - database.execSQL("ALTER TABLE notes ADD COLUMN hasRichContent INTEGER NOT NULL DEFAULT 0"); - - if (appContext != null) { - prefs().putBoolean(PreferencesConfig.ARGUMENT_PREFERENCE_EXTENDED_EDITOR, false); - } - } - }; + public static final Migration MIGRATION_4_5 = + new Migration(4, 5) { + @Override + public void migrate(@NonNull SupportSQLiteDatabase database) { + database.execSQL("ALTER TABLE notes ADD COLUMN valueJson TEXT"); + database.execSQL( + "ALTER TABLE notes ADD COLUMN hasRichContent INTEGER NOT NULL DEFAULT 0"); + + if (appContext != null) { + prefs().putBoolean( + PreferencesConfig.ARGUMENT_PREFERENCE_EXTENDED_EDITOR, + false); + } + } + }; private static SafePreferences prefs() { return new SafePreferences(appContext); diff --git a/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java index b84ec50..a84ef99 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java @@ -1,26 +1,21 @@ package com.pasich.mynotes.data.database; - import android.content.Context; - import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.data.model.Task; import com.pasich.mynotes.data.model.TaskCategory; import com.pasich.mynotes.extendedEditor.attach.AttachmentCleaner; import com.pasich.mynotes.utils.managers.SystemTagsManager; - -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Singleton; - import dagger.hilt.android.qualifiers.ApplicationContext; import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.Observable; import io.reactivex.Single; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; +import javax.inject.Singleton; @Singleton public class AppDbHelper implements DbHelper { @@ -37,10 +32,14 @@ public class AppDbHelper implements DbHelper { @Override public Flowable> getTags() { - return appDatabase.tagsDao().getTags().map(userTags -> { - userTags.addAll(SystemTagsManager.getSystemTags()); - return userTags; - }); + return appDatabase + .tagsDao() + .getTags() + .map( + userTags -> { + userTags.addAll(SystemTagsManager.getSystemTags()); + return userTags; + }); } @Override @@ -78,23 +77,25 @@ public Completable updateTags(List tags) { return Completable.fromAction(() -> appDatabase.tagsDao().updateTags(tags)); } - @Override public Completable clearTagInNotes(Tag tag) { - return Completable.fromAction(() -> appDatabase.transactionsNote().deleteTagButKeepNotes(tag)); + return Completable.fromAction( + () -> appDatabase.transactionsNote().deleteTagButKeepNotes(tag)); } @Override public Completable deleteTagAndMoveNotesToTrash(Tag tag) { - return Completable.fromAction(() -> appDatabase.transactionsNote().deleteTagAndMoveNotesToTrash(tag)); + return Completable.fromAction( + () -> appDatabase.transactionsNote().deleteTagAndMoveNotesToTrash(tag)); } @Override public Completable moveNoteToTrash(int id) { - return Completable.fromCallable(() -> { - appDatabase.noteDao().moveNoteToTrash(id); - return null; - }); + return Completable.fromCallable( + () -> { + appDatabase.noteDao().moveNoteToTrash(id); + return null; + }); } @Override @@ -104,10 +105,11 @@ public Completable moveNotesToTrash(List ids) { @Override public Completable transferNoteOutTrash(int id) { - return Completable.fromCallable(() -> { - appDatabase.noteDao().restoreNoteFromTrash(id); - return null; - }); + return Completable.fromCallable( + () -> { + appDatabase.noteDao().restoreNoteFromTrash(id); + return null; + }); } @Override @@ -117,17 +119,18 @@ public Completable transferNotesOutTrash(List ids) { @Override public Completable clearTrash() { - return Completable.fromAction(() -> { - List trashNotes = appDatabase.noteDao().getTrashNotesSync(); + return Completable.fromAction( + () -> { + List trashNotes = appDatabase.noteDao().getTrashNotesSync(); - // Deleting attachments - for (Note note : trashNotes) { - AttachmentCleaner.deleteAttachmentFolderByNoteId(appContext, note.getId()); - } + // Deleting attachments + for (Note note : trashNotes) { + AttachmentCleaner.deleteAttachmentFolderByNoteId(appContext, note.getId()); + } - // Deleting notes - appDatabase.noteDao().deleteAllTrashNotes(); - }); + // Deleting notes + appDatabase.noteDao().deleteAllTrashNotes(); + }); } @Override @@ -137,24 +140,22 @@ public Completable setTagForNotes(String tag, List noteIds) { @Override public Completable renameTag(Tag mTag, String newName) { - return Completable.fromAction(() -> appDatabase.transactionsNote().renameTag(mTag, newName)); + return Completable.fromAction( + () -> appDatabase.transactionsNote().renameTag(mTag, newName)); } @Override public Completable restoreNotesAndFixTags(List ids) { - return Completable.fromAction(() -> appDatabase.transactionsNote().restoreNotesAndFixTags(ids)); + return Completable.fromAction( + () -> appDatabase.transactionsNote().restoreNotesAndFixTags(ids)); } - @Override public Single getCountData() { return Single.fromCallable(() -> appDatabase.noteDao().getDataCount()); } - - /** - * Notes - */ + /** Notes */ @Override public Flowable> getNotes() { return appDatabase.noteDao().getNotesAll(); @@ -165,7 +166,6 @@ public Flowable> getNotesInTrash() { return appDatabase.noteDao().getTrashNotes(); } - @Override public Observable> getNotesForTag(String nameTag) { return Observable.fromCallable(() -> appDatabase.noteDao().getNotesForTag(nameTag)); @@ -178,20 +178,22 @@ public Single getCountNotesTag(String nameTag) { @Override public Single getNoteForId(long idNote) { - return appDatabase.noteDao().getNoteForId(idNote) - .onErrorReturnItem(new Note()); + return appDatabase.noteDao().getNoteForId(idNote).onErrorReturnItem(new Note()); } @Override public Single addNote(Note note, boolean copyNote) { - return Single.fromCallable(() -> copyNote ? appDatabase.noteDao().addNoteCopy(note) : appDatabase.transactionsNote().addNoteTransaction(note)); + return Single.fromCallable( + () -> + copyNote + ? appDatabase.noteDao().addNoteCopy(note) + : appDatabase.transactionsNote().addNoteTransaction(note)); } public Single copyNote(Note original) { return addNote(original.duplicate(), true); } - @Override public Completable addNotes(List notes) { return Completable.fromAction(() -> appDatabase.noteDao().addNotes(notes)); @@ -204,10 +206,10 @@ public Completable deleteNote(Note note) { @Override public Completable deleteNote(ArrayList notes) { - return Completable.fromAction(() -> { - for (Note note : notes) - appDatabase.noteDao().deleteNote(note); - }); + return Completable.fromAction( + () -> { + for (Note note : notes) appDatabase.noteDao().deleteNote(note); + }); } @Override @@ -228,7 +230,6 @@ public Completable setTagNote(String nameTag, int idNote) { @Override public Flowable getNotesCount() { return appDatabase.noteDao().getNotesCount().map(v -> v == null ? 0 : v); - } @Override @@ -244,37 +245,47 @@ public Flowable getTotalCharacters() { @Override public Single> getNotesWithActiveReminders() { - return Single.fromCallable(() -> - appDatabase.noteDao().getNotesWithActiveRemindersSync(System.currentTimeMillis()) - ).subscribeOn(io.reactivex.schedulers.Schedulers.io()); + return Single.fromCallable( + () -> + appDatabase + .noteDao() + .getNotesWithActiveRemindersSync( + System.currentTimeMillis())) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()); } @Override public Completable clearReminder(int noteId) { - return Completable.fromAction(() -> - appDatabase.noteDao().clearReminderSync(noteId) - ).subscribeOn(io.reactivex.schedulers.Schedulers.io()); + return Completable.fromAction(() -> appDatabase.noteDao().clearReminderSync(noteId)) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()); } @Override public Completable updateNoteReminder(int noteId, long reminderTime, String repeat) { - return Completable.fromAction(() -> - appDatabase.noteDao().updateReminderSync(noteId, reminderTime, repeat) - ).subscribeOn(io.reactivex.schedulers.Schedulers.io()); + return Completable.fromAction( + () -> + appDatabase + .noteDao() + .updateReminderSync(noteId, reminderTime, repeat)) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()); } @Override - public Completable updateNoteReminderFull(int noteId, long reminderTime, String repeat, int intervalMinutes) { - return Completable.fromAction(() -> - appDatabase.noteDao().updateReminderFullSync(noteId, reminderTime, repeat, intervalMinutes) - ).subscribeOn(io.reactivex.schedulers.Schedulers.io()); + public Completable updateNoteReminderFull( + int noteId, long reminderTime, String repeat, int intervalMinutes) { + return Completable.fromAction( + () -> + appDatabase + .noteDao() + .updateReminderFullSync( + noteId, reminderTime, repeat, intervalMinutes)) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()); } @Override public Completable setPinNote(int noteId, boolean pinned) { - return Completable.fromAction(() -> - appDatabase.noteDao().setPinNoteSync(noteId, pinned) - ).subscribeOn(io.reactivex.schedulers.Schedulers.io()); + return Completable.fromAction(() -> appDatabase.noteDao().setPinNoteSync(noteId, pinned)) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()); } // ---- DbTasksHelper ---- @@ -311,7 +322,8 @@ public Completable deleteTask(Task task) { @Override public Completable toggleTask(int taskId, boolean isDone) { - return Completable.fromAction(() -> appDatabase.taskDao().setTaskDone(taskId, isDone ? 1 : 0)); + return Completable.fromAction( + () -> appDatabase.taskDao().setTaskDone(taskId, isDone ? 1 : 0)); } @Override @@ -351,9 +363,12 @@ public Completable setTaskReminder(int taskId, long time) { @Override public Completable setTaskReminderFull(int taskId, long time, int intervalMinutes) { - return Completable.fromAction(() -> - appDatabase.taskDao().setTaskReminderFullSync(taskId, time, intervalMinutes) - ).subscribeOn(io.reactivex.schedulers.Schedulers.io()); + return Completable.fromAction( + () -> + appDatabase + .taskDao() + .setTaskReminderFullSync(taskId, time, intervalMinutes)) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()); } @Override diff --git a/app/src/main/java/com/pasich/mynotes/data/database/DbHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/DbHelper.java index 2cc51c7..239370d 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/DbHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/DbHelper.java @@ -1,11 +1,10 @@ package com.pasich.mynotes.data.database; - import com.pasich.mynotes.data.database.helpers.DbNotesHelper; import com.pasich.mynotes.data.database.helpers.DbTagsHelper; import com.pasich.mynotes.data.database.helpers.DbTasksHelper; import com.pasich.mynotes.data.database.helpers.DbTransactionsHelper; import com.pasich.mynotes.data.database.helpers.StatsHelper; -public interface DbHelper extends DbTagsHelper, DbNotesHelper, DbTransactionsHelper, StatsHelper, DbTasksHelper { -} +public interface DbHelper + extends DbTagsHelper, DbNotesHelper, DbTransactionsHelper, StatsHelper, DbTasksHelper {} diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java index df48975..cc44d91 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java @@ -6,13 +6,10 @@ import androidx.room.OnConflictStrategy; import androidx.room.Query; import androidx.room.Update; - import com.pasich.mynotes.data.model.Note; - -import java.util.List; - import io.reactivex.Flowable; import io.reactivex.Single; +import java.util.List; @Dao public interface NoteDao { @@ -59,7 +56,6 @@ public interface NoteDao { @Query("UPDATE NOTES SET tag=:tag WHERE id=:noteID") void setTagNote(String tag, int noteID); - @Query("UPDATE notes SET isTrash = 1 WHERE id IN (:ids)") void moveNotesToTrash(List ids); @@ -85,7 +81,8 @@ public interface NoteDao { @Query("SELECT SUM(LENGTH(value)) FROM notes WHERE isTrash = 0") Flowable getTotalCharacters(); - @Query("SELECT * FROM notes WHERE reminderTime IS NOT NULL AND reminderTime > :now AND isTrash = 0") + @Query( + "SELECT * FROM notes WHERE reminderTime IS NOT NULL AND reminderTime > :now AND isTrash = 0") List getNotesWithActiveRemindersSync(long now); @Query("UPDATE notes SET reminderTime = NULL, reminderRepeat = 'NONE' WHERE id = :noteId") @@ -94,10 +91,10 @@ public interface NoteDao { @Query("UPDATE notes SET reminderTime = :time, reminderRepeat = :repeat WHERE id = :noteId") void updateReminderSync(int noteId, long time, String repeat); - @Query("UPDATE notes SET reminderTime = :time, reminderRepeat = :repeat, reminderIntervalMinutes = :intervalMinutes WHERE id = :noteId") + @Query( + "UPDATE notes SET reminderTime = :time, reminderRepeat = :repeat, reminderIntervalMinutes = :intervalMinutes WHERE id = :noteId") void updateReminderFullSync(int noteId, long time, String repeat, int intervalMinutes); @Query("UPDATE notes SET isPinned = :pinned WHERE id = :noteId") void setPinNoteSync(int noteId, boolean pinned); - } diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/TagsDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/TagsDao.java index 1604abe..b449f14 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/TagsDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/TagsDao.java @@ -6,13 +6,10 @@ import androidx.room.OnConflictStrategy; import androidx.room.Query; import androidx.room.Update; - import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.utils.managers.SystemTagsManager; - -import java.util.List; - import io.reactivex.Flowable; +import java.util.List; @Dao public interface TagsDao { @@ -31,7 +28,9 @@ public interface TagsDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void addTag(Tag tag); - @Query("SELECT COUNT(name) FROM tags WHERE systemAction = " + SystemTagsManager.SYSTEM_ACTION_USER_TAG) + @Query( + "SELECT COUNT(name) FROM tags WHERE systemAction = " + + SystemTagsManager.SYSTEM_ACTION_USER_TAG) int getCountAllTag(); @Update @@ -39,6 +38,4 @@ public interface TagsDao { @Delete void deleteTag(Tag tag); - - } diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskCategoryDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskCategoryDao.java index 6111f40..12dc8b6 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskCategoryDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskCategoryDao.java @@ -5,12 +5,9 @@ import androidx.room.Insert; import androidx.room.Query; import androidx.room.Update; - import com.pasich.mynotes.data.model.TaskCategory; - -import java.util.List; - import io.reactivex.Flowable; +import java.util.List; @Dao public interface TaskCategoryDao { diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java index 7c39a2e..a6dd9e4 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java @@ -5,12 +5,9 @@ import androidx.room.Insert; import androidx.room.Query; import androidx.room.Update; - import com.pasich.mynotes.data.model.Task; - -import java.util.List; - import io.reactivex.Flowable; +import java.util.List; @Dao public interface TaskDao { @@ -18,7 +15,8 @@ public interface TaskDao { @Query("SELECT * FROM tasks WHERE isDone = 0 ORDER BY position ASC, createdAt ASC") Flowable> getActiveTasks(); - @Query("SELECT * FROM tasks WHERE categoryId = :categoryId AND isDone = 0 ORDER BY position ASC, createdAt ASC") + @Query( + "SELECT * FROM tasks WHERE categoryId = :categoryId AND isDone = 0 ORDER BY position ASC, createdAt ASC") Flowable> getActiveTasksByCategory(int categoryId); @Query("SELECT * FROM tasks WHERE isDone = 1 ORDER BY createdAt DESC") @@ -51,7 +49,8 @@ public interface TaskDao { @Query("UPDATE tasks SET reminderTime = :time WHERE id = :taskId") void setTaskReminder(int taskId, long time); - @Query("UPDATE tasks SET reminderTime = :time, reminderIntervalMinutes = :intervalMinutes WHERE id = :taskId") + @Query( + "UPDATE tasks SET reminderTime = :time, reminderIntervalMinutes = :intervalMinutes WHERE id = :taskId") void setTaskReminderFullSync(int taskId, long time, int intervalMinutes); @Query("UPDATE tasks SET reminderTime = NULL WHERE id = :taskId") diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/Transactions.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/Transactions.java index 233fd2c..93b9282 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/Transactions.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/Transactions.java @@ -6,13 +6,10 @@ import androidx.room.OnConflictStrategy; import androidx.room.Query; import androidx.room.Transaction; - import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.Tag; - import java.util.List; - @Dao public abstract class Transactions { @@ -67,11 +64,9 @@ public void deleteTagAndMoveNotesToTrash(Tag tag) { deleteTag(tag); } - @Transaction public void restoreNotesAndFixTags(List ids) { clearInvalidTags(); restoreNotesInternal(ids); } } - diff --git a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbNotesHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbNotesHelper.java index bc0ef49..5fb3350 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbNotesHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbNotesHelper.java @@ -1,14 +1,12 @@ package com.pasich.mynotes.data.database.helpers; import com.pasich.mynotes.data.model.Note; - -import java.util.ArrayList; -import java.util.List; - import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.Observable; import io.reactivex.Single; +import java.util.ArrayList; +import java.util.List; public interface DbNotesHelper { Single getCountData(); @@ -50,7 +48,6 @@ public interface DbNotesHelper { Completable clearTrash(); - Completable setTagForNotes(String tag, List noteIds); Single> getNotesWithActiveReminders(); @@ -59,8 +56,8 @@ public interface DbNotesHelper { Completable updateNoteReminder(int noteId, long reminderTime, String repeat); - Completable updateNoteReminderFull(int noteId, long reminderTime, String repeat, int intervalMinutes); + Completable updateNoteReminderFull( + int noteId, long reminderTime, String repeat, int intervalMinutes); Completable setPinNote(int noteId, boolean pinned); - } diff --git a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTagsHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTagsHelper.java index d5e0b57..5825424 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTagsHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTagsHelper.java @@ -1,17 +1,13 @@ package com.pasich.mynotes.data.database.helpers; import com.pasich.mynotes.data.model.Tag; - -import java.util.List; - import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.Single; +import java.util.List; public interface DbTagsHelper { - /** - * Tags - */ + /** Tags */ Flowable> getTags(); Flowable> getTagsUser(); @@ -27,6 +23,4 @@ public interface DbTagsHelper { Completable updateTag(Tag tag); Completable updateTags(List tags); - - } diff --git a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTasksHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTasksHelper.java index 5cf9edb..72b413e 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTasksHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTasksHelper.java @@ -2,29 +2,43 @@ import com.pasich.mynotes.data.model.Task; import com.pasich.mynotes.data.model.TaskCategory; - -import java.util.List; - import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.Single; +import java.util.List; public interface DbTasksHelper { Flowable> getActiveTasks(); + Flowable> getActiveTasksByCategory(int categoryId); + Flowable> getCompletedTasks(); + Completable addTask(Task task); + Completable updateTask(Task task); + Completable deleteTask(Task task); + Completable toggleTask(int taskId, boolean isDone); + Completable clearCompletedTasks(); + Flowable> getCategories(); + Completable addCategory(TaskCategory category); + Completable updateCategory(TaskCategory category); + Completable deleteCategory(TaskCategory category); + Single getTaskCountForCategory(int categoryId); + Completable setTaskReminder(int taskId, long time); + Completable setTaskReminderFull(int taskId, long time, int intervalMinutes); + Completable clearTaskReminder(int taskId); + Single> getTasksWithReminders(); } diff --git a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTransactionsHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTransactionsHelper.java index 8a6ce5b..06ef823 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTransactionsHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/helpers/DbTransactionsHelper.java @@ -1,14 +1,11 @@ package com.pasich.mynotes.data.database.helpers; import com.pasich.mynotes.data.model.Tag; - -import java.util.List; - import io.reactivex.Completable; +import java.util.List; public interface DbTransactionsHelper { - Completable clearTagInNotes(Tag tag); Completable deleteTagAndMoveNotesToTrash(Tag tag); diff --git a/app/src/main/java/com/pasich/mynotes/data/model/DonationProduct.java b/app/src/main/java/com/pasich/mynotes/data/model/DonationProduct.java index be0b011..d97a527 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/DonationProduct.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/DonationProduct.java @@ -8,7 +8,8 @@ public class DonationProduct { private String iconResource; private boolean isPurchased; - public DonationProduct(String id, String title, String description, String price, String iconResource) { + public DonationProduct( + String id, String title, String description, String price, String iconResource) { this.id = id; this.title = title; this.description = description; diff --git a/app/src/main/java/com/pasich/mynotes/data/model/HelpSection.java b/app/src/main/java/com/pasich/mynotes/data/model/HelpSection.java index 724d122..68bc220 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/HelpSection.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/HelpSection.java @@ -1,9 +1,8 @@ package com.pasich.mynotes.data.model; -public record HelpSection(int type, String title, String description, Integer iconRes, - String additionalInfo) { +public record HelpSection( + int type, String title, String description, Integer iconRes, String additionalInfo) { public static final int TYPE_HEADER = 0; public static final int TYPE_SECTION_TITLE = 1; public static final int TYPE_FEATURE = 2; - } diff --git a/app/src/main/java/com/pasich/mynotes/data/model/IndexFilter.java b/app/src/main/java/com/pasich/mynotes/data/model/IndexFilter.java index eae6a42..5077577 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/IndexFilter.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/IndexFilter.java @@ -5,12 +5,10 @@ public class IndexFilter { private int indexTitle, indexValue; private long idNote; - public IndexFilter(long idNote, int indexTitle, int indexValue) { this.idNote = idNote; this.indexTitle = indexTitle; this.indexValue = indexValue; - } public long getIdNote() { diff --git a/app/src/main/java/com/pasich/mynotes/data/model/Note.java b/app/src/main/java/com/pasich/mynotes/data/model/Note.java index c39b2a4..7d80c95 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/Note.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/Note.java @@ -2,17 +2,14 @@ import androidx.room.Entity; import androidx.room.PrimaryKey; - import com.google.gson.JsonArray; import com.google.gson.JsonParser; import com.google.gson.annotations.SerializedName; - import java.util.Objects; @Entity(tableName = "notes") public class Note { - @PrimaryKey(autoGenerate = true) @SerializedName("a") public int id; @@ -33,7 +30,7 @@ public class Note { private String valueJson; @SerializedName("g") - private boolean hasRichContent; // no use + private boolean hasRichContent; // no use @SerializedName("h") private String attachments; // JSON attachments @@ -121,12 +118,11 @@ public void setValue(String value) { public String getValuePreview() { if (value == null) { - return ""; // or return some default value if you prefer + return ""; // or return some default value if you prefer } return value.length() > 400 ? value.substring(0, 400) : value; } - public long getDate() { return this.date; } @@ -202,7 +198,6 @@ public void setPinned(boolean pinned) { isPinned = pinned; } - public int getReminderIntervalMinutes() { return reminderIntervalMinutes; } @@ -239,5 +234,4 @@ public Note duplicate() { c.setReminderRepeat("NONE"); return c; } - } diff --git a/app/src/main/java/com/pasich/mynotes/data/model/ReminderRepeat.java b/app/src/main/java/com/pasich/mynotes/data/model/ReminderRepeat.java index bd58c75..dd1b575 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/ReminderRepeat.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/ReminderRepeat.java @@ -1,10 +1,17 @@ package com.pasich.mynotes.data.model; public enum ReminderRepeat { - NONE, DAILY, WEEKLY, MONTHLY; + NONE, + DAILY, + WEEKLY, + MONTHLY; public static ReminderRepeat from(String value) { if (value == null) return NONE; - try { return valueOf(value); } catch (Exception e) { return NONE; } + try { + return valueOf(value); + } catch (Exception e) { + return NONE; + } } } diff --git a/app/src/main/java/com/pasich/mynotes/data/model/Tag.java b/app/src/main/java/com/pasich/mynotes/data/model/Tag.java index 84d4540..3d92dc4 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/Tag.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/Tag.java @@ -5,7 +5,6 @@ import androidx.room.Entity; import androidx.room.Ignore; import androidx.room.PrimaryKey; - import com.google.gson.annotations.SerializedName; @Entity(tableName = "tags") @@ -24,23 +23,17 @@ public class Tag { @SerializedName("c") private int visibility = 0; - /** - * SystemAction - System tag type (1) - add tag (2) - all notes (0) - - * user tag - */ + /** SystemAction - System tag type (1) - add tag (2) - all notes (0) - user tag */ @ColumnInfo(name = "systemAction") @SerializedName("d") private int systemAction = 0; - /** - * Position - tag position for custom sorting - */ + /** Position - tag position for custom sorting */ @ColumnInfo(name = "position") @SerializedName("e") private int position = 0; - @Ignore - private boolean selected = false; + @Ignore private boolean selected = false; public Tag create(String nameTag, int systemAction) { this.nameTag = nameTag; @@ -118,5 +111,4 @@ public Tag copy() { return t; } - } diff --git a/app/src/main/java/com/pasich/mynotes/data/model/Task.java b/app/src/main/java/com/pasich/mynotes/data/model/Task.java index 4b16c24..77d2d00 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/Task.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/Task.java @@ -11,18 +11,24 @@ public class Task { @PrimaryKey(autoGenerate = true) private int id; - @NonNull - private String title = ""; + + @NonNull private String title = ""; private String description; + @ColumnInfo(defaultValue = "0") private boolean isDone; + @ColumnInfo(defaultValue = "0") private int categoryId; + @ColumnInfo(defaultValue = "0") private long createdAt; + @ColumnInfo(defaultValue = "0") private int position; + private Long reminderTime; + @ColumnInfo(defaultValue = "0") private int reminderIntervalMinutes = 0; @@ -37,23 +43,74 @@ public Task(String title, int categoryId) { this.position = 0; } - public int getId() { return id; } - public void setId(int id) { this.id = id; } - public String getTitle() { return title; } - public void setTitle(String title) { this.title = title; } - public String getDescription() { return description; } - public void setDescription(String description) { this.description = description; } - public boolean isDone() { return isDone; } - public void setDone(boolean done) { isDone = done; } - public int getCategoryId() { return categoryId; } - public void setCategoryId(int categoryId) { this.categoryId = categoryId; } - public long getCreatedAt() { return createdAt; } - public void setCreatedAt(long createdAt) { this.createdAt = createdAt; } - public int getPosition() { return position; } - public void setPosition(int position) { this.position = position; } - public Long getReminderTime() { return reminderTime; } - public void setReminderTime(Long reminderTime) { this.reminderTime = reminderTime; } - public int getReminderIntervalMinutes() { return reminderIntervalMinutes; } + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public boolean isDone() { + return isDone; + } + + public void setDone(boolean done) { + isDone = done; + } + + public int getCategoryId() { + return categoryId; + } + + public void setCategoryId(int categoryId) { + this.categoryId = categoryId; + } + + public long getCreatedAt() { + return createdAt; + } + + public void setCreatedAt(long createdAt) { + this.createdAt = createdAt; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } + + public Long getReminderTime() { + return reminderTime; + } + + public void setReminderTime(Long reminderTime) { + this.reminderTime = reminderTime; + } + + public int getReminderIntervalMinutes() { + return reminderIntervalMinutes; + } + public void setReminderIntervalMinutes(int reminderIntervalMinutes) { this.reminderIntervalMinutes = reminderIntervalMinutes; } diff --git a/app/src/main/java/com/pasich/mynotes/data/model/TaskCategory.java b/app/src/main/java/com/pasich/mynotes/data/model/TaskCategory.java index 919bc2a..d429b3c 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/TaskCategory.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/TaskCategory.java @@ -11,11 +11,13 @@ public class TaskCategory { @PrimaryKey(autoGenerate = true) private int id; - @NonNull - private String name = ""; + + @NonNull private String name = ""; + @NonNull @ColumnInfo(defaultValue = "#6750A4") private String colorHex = "#6750A4"; + @ColumnInfo(defaultValue = "0") private int position; @@ -28,12 +30,35 @@ public TaskCategory(String name, String colorHex) { this.position = 0; } - public int getId() { return id; } - public void setId(int id) { this.id = id; } - public String getName() { return name; } - public void setName(String name) { this.name = name; } - public String getColorHex() { return colorHex; } - public void setColorHex(String colorHex) { this.colorHex = colorHex; } - public int getPosition() { return position; } - public void setPosition(int position) { this.position = position; } + public int getId() { + return id; + } + + public void setId(int id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getColorHex() { + return colorHex; + } + + public void setColorHex(String colorHex) { + this.colorHex = colorHex; + } + + public int getPosition() { + return position; + } + + public void setPosition(int position) { + this.position = position; + } } diff --git a/app/src/main/java/com/pasich/mynotes/data/model/Theme.java b/app/src/main/java/com/pasich/mynotes/data/model/Theme.java index 4cd3020..5ad54a5 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/Theme.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/Theme.java @@ -24,7 +24,6 @@ public boolean isCheck() { return check; } - public Theme setCheckReturn(boolean check) { this.check = check; return this; diff --git a/app/src/main/java/com/pasich/mynotes/data/model/lib/LibItem.java b/app/src/main/java/com/pasich/mynotes/data/model/lib/LibItem.java index 78e7cec..6b991bf 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/lib/LibItem.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/lib/LibItem.java @@ -1,5 +1,3 @@ package com.pasich.mynotes.data.model.lib; - -public record LibItem(String id, String version, String source) { -} +public record LibItem(String id, String version, String source) {} diff --git a/app/src/main/java/com/pasich/mynotes/data/model/lib/LibSection.java b/app/src/main/java/com/pasich/mynotes/data/model/lib/LibSection.java index dd7899c..afc06e4 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/lib/LibSection.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/lib/LibSection.java @@ -2,5 +2,4 @@ import java.util.List; -public record LibSection(String title, List items) { -} +public record LibSection(String title, List items) {} diff --git a/app/src/main/java/com/pasich/mynotes/data/preferences/AppPreferencesHelper.java b/app/src/main/java/com/pasich/mynotes/data/preferences/AppPreferencesHelper.java index ab47863..08a456a 100644 --- a/app/src/main/java/com/pasich/mynotes/data/preferences/AppPreferencesHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/preferences/AppPreferencesHelper.java @@ -4,7 +4,6 @@ import com.pasich.mynotes.cache.ThemePreferencesCache; import com.pasich.mynotes.utils.backup.models.PreferencesBackup; import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; - import javax.inject.Inject; import javax.inject.Singleton; @@ -17,7 +16,8 @@ public class AppPreferencesHelper implements PreferenceHelper { private final SafePreferences prefs; @Inject - AppPreferencesHelper(AppPreferencesCache appCache, ThemePreferencesCache themeCache, SafePreferences prefs) { + AppPreferencesHelper( + AppPreferencesCache appCache, ThemePreferencesCache themeCache, SafePreferences prefs) { this.prefs = prefs; this.appCache = appCache; this.themeCache = themeCache; @@ -25,7 +25,6 @@ public class AppPreferencesHelper implements PreferenceHelper { this.themeCache.initialize(); } - @Override public int getFormatCount() { return appCache.getFormatPref(); @@ -51,7 +50,6 @@ public void setSortParamTags(String paramTags) { appCache.setTagsSortPref(paramTags); } - @Override public void editSizeTextNoteActivity(int value) { themeCache.setSizeTextNoteActivity(value); @@ -65,90 +63,70 @@ public PreferencesBackup getListPreferences() { getTypeFaceNoteActivity(), getSortParam(), getSizeTextNoteActivity(), - prefs.getInt( PreferencesConfig.ARGUMENT_PREFERENCE_THEME, - PreferencesConfig.ARGUMENT_DEFAULT_THEME_VALUE - ), - + PreferencesConfig.ARGUMENT_DEFAULT_THEME_VALUE), prefs.getBoolean( PreferencesConfig.ARGUMENT_PREFERENCE_DYNAMIC_COLOR, - PreferencesConfig.ARGUMENT_DEFAULT_DYNAMIC_COLOR_VALUE - ), - + PreferencesConfig.ARGUMENT_DEFAULT_DYNAMIC_COLOR_VALUE), prefs.getInt( PreferencesConfig.ARGUMENT_PREFERENCE_THEME_MODE, - PreferencesConfig.ARGUMENT_DEFAULT_THEME_MODE_VALUE - ), - + PreferencesConfig.ARGUMENT_DEFAULT_THEME_MODE_VALUE), prefs.getBoolean( PreferencesConfig.ARGUMENT_PREFERENCE_IMAGEOPT, - PreferencesConfig.ARGUMENT_DEFAULT_IMAGEOPT_VALUE - ), + PreferencesConfig.ARGUMENT_DEFAULT_IMAGEOPT_VALUE), prefs.getBoolean( PreferencesConfig.ARGUMENT_PREFERENCE_SCREEN_PROTECTION, - PreferencesConfig.ARGUMENT_DEFAULT_SCREEN_PROTECTION_VALUE - ), - + PreferencesConfig.ARGUMENT_DEFAULT_SCREEN_PROTECTION_VALUE), prefs.getBoolean( PreferencesConfig.ARGUMENT_PREFERENCE_EXTENDED_EDITOR, - PreferencesConfig.ARGUMENT_DEFAULT_EXTENDED_EDITOR_VALUE - ), - + PreferencesConfig.ARGUMENT_DEFAULT_EXTENDED_EDITOR_VALUE), prefs.getFloat( PreferencesConfig.ARGUMENT_PREFERENCE_UI_SCALING, - PreferencesConfig.ARGUMENT_DEFAULT_UI_SCALING_VALUE - ) - ); + PreferencesConfig.ARGUMENT_DEFAULT_UI_SCALING_VALUE)); } - @Override public void setListPreferences(PreferencesBackup preferences) { if (preferences.isCreated()) { // OLD FIELDS - prefs.putInt(PreferencesConfig.ARGUMENT_PREFERENCE_FORMAT, - preferences.getFormatCount()); + prefs.putInt( + PreferencesConfig.ARGUMENT_PREFERENCE_FORMAT, preferences.getFormatCount()); - prefs.putString(PreferencesConfig.ARGUMENT_PREFERENCE_TEXT_STYLE, + prefs.putString( + PreferencesConfig.ARGUMENT_PREFERENCE_TEXT_STYLE, preferences.getTypeFaceNoteActivity()); - prefs.putString(PreferencesConfig.ARGUMENT_PREFERENCE_SORT, - preferences.getSortParam()); + prefs.putString(PreferencesConfig.ARGUMENT_PREFERENCE_SORT, preferences.getSortParam()); - prefs.putInt(PreferencesConfig.ARGUMENT_PREFERENCE_TEXT_SIZE, - preferences.getSizeTextNote()); + prefs.putInt( + PreferencesConfig.ARGUMENT_PREFERENCE_TEXT_SIZE, preferences.getSizeTextNote()); - prefs.putInt(PreferencesConfig.ARGUMENT_PREFERENCE_THEME, - preferences.getThemeValue()); + prefs.putInt(PreferencesConfig.ARGUMENT_PREFERENCE_THEME, preferences.getThemeValue()); - prefs.putBoolean(PreferencesConfig.ARGUMENT_PREFERENCE_DYNAMIC_COLOR, + prefs.putBoolean( + PreferencesConfig.ARGUMENT_PREFERENCE_DYNAMIC_COLOR, preferences.isDynamicTheme()); - prefs.putInt(PreferencesConfig.ARGUMENT_PREFERENCE_THEME_MODE, - preferences.getThemeMode()); + prefs.putInt( + PreferencesConfig.ARGUMENT_PREFERENCE_THEME_MODE, preferences.getThemeMode()); prefs.putBoolean( PreferencesConfig.ARGUMENT_PREFERENCE_IMAGEOPT, - preferences.isImageOptimizationEnabled() - ); + preferences.isImageOptimizationEnabled()); prefs.putBoolean( PreferencesConfig.ARGUMENT_PREFERENCE_SCREEN_PROTECTION, - preferences.isScreenProtection() - ); + preferences.isScreenProtection()); prefs.putBoolean( PreferencesConfig.ARGUMENT_PREFERENCE_EXTENDED_EDITOR, - preferences.isExtendedEditor() - ); + preferences.isExtendedEditor()); prefs.putFloat( - PreferencesConfig.ARGUMENT_PREFERENCE_UI_SCALING, - preferences.getUiFontScale() - ); + PreferencesConfig.ARGUMENT_PREFERENCE_UI_SCALING, preferences.getUiFontScale()); // Refresh caches appCache.refresh(); @@ -156,7 +134,6 @@ public void setListPreferences(PreferencesBackup preferences) { } } - @Override public String getTypeFaceNoteActivity() { return themeCache.getTypeFaceNoteActivity(); @@ -171,6 +148,4 @@ public String getLastKnownVersion() { public void setLastKnownVersion(String version) { appCache.setLastKnownVersion(version); } - - } diff --git a/app/src/main/java/com/pasich/mynotes/data/preferences/PreferenceHelper.java b/app/src/main/java/com/pasich/mynotes/data/preferences/PreferenceHelper.java index 2703f65..9330abf 100644 --- a/app/src/main/java/com/pasich/mynotes/data/preferences/PreferenceHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/preferences/PreferenceHelper.java @@ -1,9 +1,7 @@ package com.pasich.mynotes.data.preferences; - import com.pasich.mynotes.utils.backup.models.PreferencesBackup; - public interface PreferenceHelper { int getFormatCount(); @@ -27,5 +25,4 @@ public interface PreferenceHelper { String getLastKnownVersion(); void setLastKnownVersion(String version); - } diff --git a/app/src/main/java/com/pasich/mynotes/data/preferences/SafePreferences.java b/app/src/main/java/com/pasich/mynotes/data/preferences/SafePreferences.java index ffd3c5d..cfde413 100644 --- a/app/src/main/java/com/pasich/mynotes/data/preferences/SafePreferences.java +++ b/app/src/main/java/com/pasich/mynotes/data/preferences/SafePreferences.java @@ -3,27 +3,19 @@ import android.content.Context; import android.content.SharedPreferences; - public class SafePreferences { public static final String SHARED_PREFERENCE_FILE = "com.pasich.mynotes_preferences"; private final SharedPreferences prefs; public SafePreferences(Context ctx) { - this.prefs = ctx.getSharedPreferences( - SHARED_PREFERENCE_FILE, - Context.MODE_PRIVATE - ); + this.prefs = ctx.getSharedPreferences(SHARED_PREFERENCE_FILE, Context.MODE_PRIVATE); } public static SharedPreferences raw(Context ctx) { - return ctx.getSharedPreferences( - SHARED_PREFERENCE_FILE, - Context.MODE_PRIVATE - ); + return ctx.getSharedPreferences(SHARED_PREFERENCE_FILE, Context.MODE_PRIVATE); } - public int getInt(String key, int def) { return prefs.getInt(key, def); } diff --git a/app/src/main/java/com/pasich/mynotes/di/ApplicationModule.java b/app/src/main/java/com/pasich/mynotes/di/ApplicationModule.java index 08937c2..742bf1d 100644 --- a/app/src/main/java/com/pasich/mynotes/di/ApplicationModule.java +++ b/app/src/main/java/com/pasich/mynotes/di/ApplicationModule.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.di; - import static com.pasich.mynotes.data.database.AppDatabase.MIGRATION_4_5; import static com.pasich.mynotes.data.database.AppDatabase.MIGRATION_5_6; import static com.pasich.mynotes.data.database.AppDatabase.MIGRATION_6_7; @@ -9,9 +8,7 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; - import androidx.room.Room; - import com.pasich.mynotes.data.AppDataManager; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.database.AppDatabase; @@ -21,9 +18,6 @@ import com.pasich.mynotes.data.preferences.PreferenceHelper; import com.pasich.mynotes.data.preferences.SafePreferences; import com.pasich.mynotes.utils.constants.DatabaseConstants; - -import javax.inject.Singleton; - import dagger.Module; import dagger.Provides; import dagger.hilt.InstallIn; @@ -32,6 +26,7 @@ import io.noties.markwon.Markwon; import io.noties.markwon.ext.strikethrough.StrikethroughPlugin; import io.noties.markwon.linkify.LinkifyPlugin; +import javax.inject.Singleton; @Module @InstallIn(SingletonComponent.class) @@ -41,18 +36,31 @@ public class ApplicationModule { @Singleton AppDatabase providesAppDatabase(@ApplicationContext Context context) { AppDatabase.setContext(context); - return Room.databaseBuilder(context.getApplicationContext(), AppDatabase.class, DatabaseConstants.DB_NAME) - .addMigrations(AppDatabase.MIGRATION_2_3, AppDatabase.MIGRATION_3_4, MIGRATION_4_5, MIGRATION_5_6, MIGRATION_6_7, MIGRATION_7_8, AppDatabase.MIGRATION_8_9, AppDatabase.MIGRATION_9_10, AppDatabase.MIGRATION_10_11, AppDatabase.MIGRATION_11_12, AppDatabase.MIGRATION_12_13).build(); + return Room.databaseBuilder( + context.getApplicationContext(), + AppDatabase.class, + DatabaseConstants.DB_NAME) + .addMigrations( + AppDatabase.MIGRATION_2_3, + AppDatabase.MIGRATION_3_4, + MIGRATION_4_5, + MIGRATION_5_6, + MIGRATION_6_7, + MIGRATION_7_8, + AppDatabase.MIGRATION_8_9, + AppDatabase.MIGRATION_9_10, + AppDatabase.MIGRATION_10_11, + AppDatabase.MIGRATION_11_12, + AppDatabase.MIGRATION_12_13) + .build(); } - @Provides @Singleton DbHelper providesDbHelper(AppDbHelper appDbHelper) { return appDbHelper; } - @Provides @Singleton DataManager providesDataManager(AppDataManager appDataManager) { @@ -70,7 +78,8 @@ PreferenceHelper providesPreferenceHelper(AppPreferencesHelper appPreferencesHel boolean providerIsPlayStoreInstalled(@ApplicationContext Context context) { boolean flag = false; try { - PackageInfo packageInfo = context.getPackageManager().getPackageInfo("com.android.vending", 0); + PackageInfo packageInfo = + context.getPackageManager().getPackageInfo("com.android.vending", 0); assert packageInfo.applicationInfo != null; flag = packageInfo.applicationInfo.enabled; } catch (PackageManager.NameNotFoundException ignored) { diff --git a/app/src/main/java/com/pasich/mynotes/di/activity/ActivityModule.java b/app/src/main/java/com/pasich/mynotes/di/activity/ActivityModule.java index e23c511..19af278 100644 --- a/app/src/main/java/com/pasich/mynotes/di/activity/ActivityModule.java +++ b/app/src/main/java/com/pasich/mynotes/di/activity/ActivityModule.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.di.activity; - import com.pasich.mynotes.ui.contract.BackupContract; import com.pasich.mynotes.ui.contract.MainContract; import com.pasich.mynotes.ui.contract.NoteContract; @@ -15,7 +14,6 @@ import com.pasich.mynotes.ui.presenter.TrashPresenter; import com.pasich.mynotes.utils.rx.AppSchedulerProvider; import com.pasich.mynotes.utils.rx.SchedulerProvider; - import dagger.Module; import dagger.Provides; import dagger.hilt.InstallIn; @@ -27,7 +25,6 @@ @InstallIn(ActivityComponent.class) public class ActivityModule { - @Provides CompositeDisposable provideCompositeDisposable() { return new CompositeDisposable(); @@ -73,5 +70,4 @@ TagsContract.presenter providesTagsPresenter(TagsPresenter presenter) { TasksContract.presenter providesTasksPresenter(TasksPresenter presenter) { return presenter; } - } diff --git a/app/src/main/java/com/pasich/mynotes/di/activity/ListUtilsModule.java b/app/src/main/java/com/pasich/mynotes/di/activity/ListUtilsModule.java index 022bd5c..2dffa67 100644 --- a/app/src/main/java/com/pasich/mynotes/di/activity/ListUtilsModule.java +++ b/app/src/main/java/com/pasich/mynotes/di/activity/ListUtilsModule.java @@ -1,26 +1,21 @@ package com.pasich.mynotes.di.activity; - import android.content.Context; - import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.StaggeredGridLayoutManager; - import com.pasich.mynotes.cache.AppPreferencesCache; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.utils.recycler.SpacesItemDecoration; import com.pasich.mynotes.utils.recycler.diffutil.DiffUtilTag; - -import javax.inject.Named; - import dagger.Module; import dagger.Provides; import dagger.hilt.InstallIn; import dagger.hilt.android.components.ActivityComponent; import dagger.hilt.android.qualifiers.ApplicationContext; import dagger.hilt.android.scopes.ActivityScoped; +import javax.inject.Named; @Module @InstallIn(ActivityComponent.class) @@ -38,7 +33,6 @@ StaggeredGridLayoutManager providesStaggeredGridLayoutManager(int spanCount) { return new StaggeredGridLayoutManager(spanCount, StaggeredGridLayoutManager.VERTICAL); } - @Named("Tag") @Provides @ActivityScoped @@ -46,7 +40,6 @@ DiffUtil.ItemCallback providesDiffUtilCallbackTag(DiffUtilTag diffUtil) { return diffUtil; } - @Provides @ActivityScoped LinearLayoutManager providesLinearLayoutManager(@ApplicationContext Context context) { @@ -65,5 +58,4 @@ int providesSpanCountStaggerGridLayout(AppPreferencesCache appPreferencesCache) SpacesItemDecoration providerSpaceItemDecorationTags() { return new SpacesItemDecoration(8, 8); } - } diff --git a/app/src/main/java/com/pasich/mynotes/di/fragment/FragmentModule.java b/app/src/main/java/com/pasich/mynotes/di/fragment/FragmentModule.java index d3fb89b..1edced5 100644 --- a/app/src/main/java/com/pasich/mynotes/di/fragment/FragmentModule.java +++ b/app/src/main/java/com/pasich/mynotes/di/fragment/FragmentModule.java @@ -4,7 +4,6 @@ import com.pasich.mynotes.ui.contract.dialogs.NameTagDialogContract; import com.pasich.mynotes.ui.presenter.dialogs.DeleteTagDialogPresenter; import com.pasich.mynotes.ui.presenter.dialogs.NameTagDialogPresenter; - import dagger.Module; import dagger.Provides; import dagger.hilt.InstallIn; @@ -15,18 +14,17 @@ @InstallIn(FragmentComponent.class) public class FragmentModule { - @Provides @FragmentScoped - NameTagDialogContract.presenter providerNewTagDialogPresenter(NameTagDialogPresenter presenter) { + NameTagDialogContract.presenter providerNewTagDialogPresenter( + NameTagDialogPresenter presenter) { return presenter; - } @Provides @FragmentScoped - DeleteTagDialogContract.presenter providerDeleteTagDialogPresenter(DeleteTagDialogPresenter presenter) { + DeleteTagDialogContract.presenter providerDeleteTagDialogPresenter( + DeleteTagDialogPresenter presenter) { return presenter; } - } diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/NoteEditorView.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/NoteEditorView.java index 959a47e..bbd03a3 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/NoteEditorView.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/NoteEditorView.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.extendedEditor; - import android.annotation.SuppressLint; import android.content.Context; import android.content.Intent; @@ -18,16 +17,13 @@ import android.webkit.WebView; import android.widget.FrameLayout; import android.widget.Toast; - import androidx.core.view.ViewCompat; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.extendedEditor.attach.AttachmentStorage; import com.pasich.mynotes.extendedEditor.utils.EditorAttachmentsWebViewClient; import com.pasich.mynotes.extendedEditor.utils.EditorJSInterface; import com.pasich.mynotes.extendedEditor.utils.SettingsEditorColors; - import java.util.Locale; public class NoteEditorView extends FrameLayout { @@ -66,10 +62,9 @@ private void init(Context context) { } /** - * Loads the editor HTML once the view is attached to window. - * This ensures WebView is fully initialized before loading local assets. + * Loads the editor HTML once the view is attached to window. This ensures WebView is fully + * initialized before loading local assets. */ - @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); @@ -80,22 +75,18 @@ protected void onAttachedToWindow() { } } - /** - * Loads the Editor.js HTML page from the app assets with the current locale. - */ - + /** Loads the Editor.js HTML page from the app assets with the current locale. */ private void loadEditorHtml() { if (webView == null) return; webView.loadUrl( - "file:///android_asset/editor/editor.html?locale=" + Locale.getDefault().getLanguage() - ); + "file:///android_asset/editor/editor.html?locale=" + + Locale.getDefault().getLanguage()); } /** - * Configures WebView, JS bridge, security settings and file chooser. - * Called once during initialization. + * Configures WebView, JS bridge, security settings and file chooser. Called once during + * initialization. */ - @SuppressLint("SetJavaScriptEnabled") private void setupWebView() { WebSettings webSettings = webView.getSettings(); @@ -114,61 +105,63 @@ private void setupWebView() { webView.setVerticalScrollBarEnabled(false); webView.setHorizontalScrollBarEnabled(false); webView.setWebViewClient(new EditorAttachmentsWebViewClient(getContext())); - webView.setWebChromeClient(new WebChromeClient() { - @Override - public boolean onShowFileChooser(WebView webView, - ValueCallback filePathCallback, - FileChooserParams fileChooserParams) { - fileCallback = filePathCallback; - Intent intent; - try { - intent = fileChooserParams.createIntent(); - } catch (Exception e) { - fileCallback = null; - return false; - } - - if (fileChooserListener != null) { - fileChooserListener.onOpenFileChooser(intent, FILE_CHOOSER_REQUEST); + webView.setWebChromeClient( + new WebChromeClient() { + @Override + public boolean onShowFileChooser( + WebView webView, + ValueCallback filePathCallback, + FileChooserParams fileChooserParams) { + fileCallback = filePathCallback; + Intent intent; + try { + intent = fileChooserParams.createIntent(); + } catch (Exception e) { + fileCallback = null; + return false; + } + + if (fileChooserListener != null) { + fileChooserListener.onOpenFileChooser(intent, FILE_CHOOSER_REQUEST); + return true; + } + + return false; + } + }); + + webView.setOnLongClickListener( + v -> { + if (onContextDialogListener != null) { + onContextDialogListener.openContextCopy(); + } else { + Log.w(TAG, "OnContextDialogListener is null"); + } return true; - } - - return false; - } - - }); - - webView.setOnLongClickListener(v -> { - if (onContextDialogListener != null) { - onContextDialogListener.openContextCopy(); - } else { - Log.w(TAG, "OnContextDialogListener is null"); - } - return true; - }); - - + }); } - public WebView getWebView() { return webView; } public void onEditorReadyFromBridge() { - handler.post(() -> { - editorIsReady = true; - applyTheme(); - if (pendingNote != null) { - if (editorInterface != null) { - editorInterface.loadNoteToEditor(pendingNote); - } else { - Log.w(TAG, "onEditorReady: editorInterface is null, dropping pending note"); - } - pendingNote = null; - } - handler.postDelayed(this::showEditor, 120); - }); + handler.post( + () -> { + editorIsReady = true; + applyTheme(); + if (pendingNote != null) { + if (editorInterface != null) { + editorInterface.loadNoteToEditor(pendingNote); + } else { + Log.w( + TAG, + "onEditorReady: editorInterface is null, dropping pending note"); + } + pendingNote = null; + } + handler.postDelayed(this::showEditor, 120); + }); } public void setEditorInterface(EditorJSInterface editorInterface) { @@ -178,28 +171,26 @@ public void setEditorInterface(EditorJSInterface editorInterface) { } } - /** - * Applies Android theme colors to the editor via JS bridge. - */ + /** Applies Android theme colors to the editor via JS bridge. */ public void applyTheme() { if (editorInterface != null) { - editorInterface.setThemeColors( - new SettingsEditorColors().getThemeColors(getContext()) - ); + editorInterface.setThemeColors(new SettingsEditorColors().getThemeColors(getContext())); } } void showEditor() { if (webView == null || loader == null) return; - loader.animate().alpha(0f).setDuration(300).withEndAction(() -> loader.setVisibility(View.GONE)).start(); + loader.animate() + .alpha(0f) + .setDuration(300) + .withEndAction(() -> loader.setVisibility(View.GONE)) + .start(); webView.setAlpha(0f); webView.setVisibility(View.VISIBLE); webView.animate().alpha(1f).setDuration(400).setStartDelay(100).start(); } - /** - * Toggles read-only mode inside Editor.js (title and blocks become non-editable). - */ + /** Toggles read-only mode inside Editor.js (title and blocks become non-editable). */ public void actionRead() { if (!editorIsReady) return; editorInterface.toggleReadMode(); @@ -211,8 +202,7 @@ public void deleteBlock(String blockId, String fileUrl) { } /** - * Loads a note into the editor. - * If the editor is not ready yet, the note is stored temporarily. + * Loads a note into the editor. If the editor is not ready yet, the note is stored temporarily. */ public void load(Note mNote) { if (mNote == null) { @@ -228,8 +218,8 @@ public void load(Note mNote) { } /** - * Handles result from WebView file chooser, including validation - * (size limit, free space), before passing the file to JS. + * Handles result from WebView file chooser, including validation (size limit, free space), + * before passing the file to JS. */ public void onFileChooserResult(int resultCode, Intent data) { if (fileCallback == null) return; @@ -268,8 +258,8 @@ protected void onDetachedFromWindow() { } /** - * Fully and safely destroys the WebView instance to prevent memory leaks. - * Must be called from Activity/Fragment onDestroy(). + * Fully and safely destroys the WebView instance to prevent memory leaks. Must be called from + * Activity/Fragment onDestroy(). */ public void release() { try { @@ -305,10 +295,8 @@ public void release() { } /** - * Soft refresh animation: - * - shows loader for 500ms - * - fades out and fades in WebView - * Does NOT reload HTML or reset scroll. + * Soft refresh animation: - shows loader for 500ms - fades out and fades in WebView Does NOT + * reload HTML or reset scroll. */ public void softRefresh() { if (webView == null || loader == null) return; @@ -322,26 +310,24 @@ public void softRefresh() { webView.animate() .alpha(0f) .setDuration(150) - .withEndAction(() -> { - - handler.postDelayed(() -> { - - // Hide loader - loader.animate() - .alpha(0f) - .setDuration(200) - .withEndAction(() -> loader.setVisibility(View.GONE)) - .start(); - - // Show editor again - webView.animate() - .alpha(1f) - .setDuration(200) - .start(); - - }, 500); // loader visible ~0.5 sec - - }) + .withEndAction( + () -> { + handler.postDelayed( + () -> { + + // Hide loader + loader.animate() + .alpha(0f) + .setDuration(200) + .withEndAction( + () -> loader.setVisibility(View.GONE)) + .start(); + + // Show editor again + webView.animate().alpha(1f).setDuration(200).start(); + }, + 500); // loader visible ~0.5 sec + }) .start(); } @@ -357,4 +343,3 @@ public interface OnFileChooserListener { void onOpenFileChooser(Intent intent, int requestCode); } } - diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/attach/AttachmentCleaner.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/attach/AttachmentCleaner.java index 1b18c79..ffd6b33 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/attach/AttachmentCleaner.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/attach/AttachmentCleaner.java @@ -4,13 +4,11 @@ import android.content.Context; import android.util.Log; - import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import com.pasich.mynotes.BuildConfig; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.extendedEditor.models.EditorAttachment; - import java.io.File; import java.lang.reflect.Type; import java.util.ArrayList; @@ -20,15 +18,13 @@ /** * Utility class responsible for maintaining consistency of attachment files. - *

- * This cleaner keeps the filesystem in sync with the note's attachments JSON: - * - Parses the note's attachment metadata. - * - Resolves actual file paths inside internal storage. - * - Deletes orphaned files that are no longer referenced by the JSON. - *

- * Called after successful autosave or manual save of a note. + * + *

This cleaner keeps the filesystem in sync with the note's attachments JSON: - Parses the + * note's attachment metadata. - Resolves actual file paths inside internal storage. - Deletes + * orphaned files that are no longer referenced by the JSON. + * + *

Called after successful autosave or manual save of a note. */ - public class AttachmentCleaner { private static final String TAG = "AttachmentCleaner"; @@ -48,19 +44,16 @@ private static void e(String msg, Throwable t) { /** * Performs a cleanup of attachment files for a given note. - *

- * Logic: - * 1) Parses note.attachments JSON into EditorAttachment models. - * 2) Collects expected filenames referenced by the JSON. - * 3) Locates the actual attachment directory: /files/attachments/note_. - * 4) Deletes all files that are not referenced (orphans). - *

- * Notes: - * - Runs silently in production; detailed logs appear only in debug builds. - * - If the attachment folder does not exist, the method exits safely. - * - Never creates new directories — cleanup must not modify the FS structure. * - * @param ctx Application context. + *

Logic: 1) Parses note.attachments JSON into EditorAttachment models. 2) Collects expected + * filenames referenced by the JSON. 3) Locates the actual attachment directory: + * /files/attachments/note_. 4) Deletes all files that are not referenced (orphans). + * + *

Notes: - Runs silently in production; detailed logs appear only in debug builds. - If the + * attachment folder does not exist, the method exits safely. - Never creates new directories — + * cleanup must not modify the FS structure. + * + * @param ctx Application context. * @param note Source note containing attachments metadata. */ public static void cleanup(Context ctx, Note note) { @@ -71,8 +64,7 @@ public static void cleanup(Context ctx, Note note) { d("Cleanup start"); String json = note.getAttachments(); - Type type = new TypeToken>() { - }.getType(); + Type type = new TypeToken>() {}.getType(); List list = gson.fromJson(json, type); if (list == null) list = new ArrayList<>(); @@ -96,7 +88,8 @@ public static void cleanup(Context ctx, Note note) { } // folder - File folder = new File(new File(ctx.getFilesDir(), ATTACHMENTS_BASE_DIR), "note_" + noteId); + File folder = + new File(new File(ctx.getFilesDir(), ATTACHMENTS_BASE_DIR), "note_" + noteId); if (!folder.exists() || !folder.isDirectory()) { d("No folder → nothing to clean"); return; @@ -153,5 +146,4 @@ private static boolean deleteRecursively(File file) { return file.delete(); } - } diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/attach/AttachmentStorage.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/attach/AttachmentStorage.java index a74e57d..3071425 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/attach/AttachmentStorage.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/attach/AttachmentStorage.java @@ -6,18 +6,16 @@ import android.os.StatFs; import android.provider.OpenableColumns; import android.util.Log; - import com.pasich.mynotes.R; import com.pasich.mynotes.extendedEditor.models.EditorAttachment; import com.pasich.mynotes.utils.file.ImageOptimizer; - import java.io.File; import java.io.FileOutputStream; import java.util.List; /** - * Utility class for managing note attachments stored in the app's internal storage. - * Handles validation, saving, resolving, reading, and deleting attachment files. + * Utility class for managing note attachments stored in the app's internal storage. Handles + * validation, saving, resolving, reading, and deleting attachment files. */ public class AttachmentStorage { @@ -28,11 +26,8 @@ public class AttachmentStorage { /** * Validates whether a file can be attached to a note. - *

- * Performs: - * - File size calculation - * - Maximum size check - * - Free internal storage space check + * + *

Performs: - File size calculation - Maximum size check - Free internal storage space check * * @param ctx application context * @param uri Uri of the selected file @@ -42,7 +37,8 @@ public static AttachmentValidationResult validateBeforeAttach(Context ctx, Uri u long size = getFileSize(ctx, uri); if (size < 0) { - return AttachmentValidationResult.error(ctx.getString(R.string.errorAttachCalculateSize)); + return AttachmentValidationResult.error( + ctx.getString(R.string.errorAttachCalculateSize)); } if (size > MAX_FILE_SIZE) { @@ -77,7 +73,7 @@ public static long getFileSize(Context ctx, Uri uri) { /** * Checks if internal storage has the required amount of free space. * - * @param ctx application context + * @param ctx application context * @param requiredBytes minimum required bytes * @return true if available space ≥ requiredBytes, false otherwise */ @@ -103,10 +99,9 @@ private static File baseDir(Context ctx) { } /** - * Returns (and creates if necessary) the directory for a specific note: - * /attachments/note_ + * Returns (and creates if necessary) the directory for a specific note: /attachments/note_ * - * @param ctx application context + * @param ctx application context * @param noteId ID of the note */ public static File noteFolder(Context ctx, int noteId) { @@ -117,17 +112,21 @@ public static File noteFolder(Context ctx, int noteId) { /** * Saves a file into the note-specific folder. - *

- * Automatically generates unique filename using: - * timestamp + hash(originalName) + extension * - * @param ctx app context - * @param noteId target note id + *

Automatically generates unique filename using: timestamp + hash(originalName) + extension + * + * @param ctx app context + * @param noteId target note id * @param originalName original filename (used for extension extraction) - * @param raw file raw bytes + * @param raw file raw bytes * @return saved File object or null on failure */ - public static File save(Context ctx, int noteId, String originalName, byte[] raw, boolean isExtraOptimizeEnabled) { + public static File save( + Context ctx, + int noteId, + String originalName, + byte[] raw, + boolean isExtraOptimizeEnabled) { try { File folder = noteFolder(ctx, noteId); @@ -135,9 +134,9 @@ public static File save(Context ctx, int noteId, String originalName, byte[] raw ImageOptimizer.OutFormat detectedFormat = ImageOptimizer.detectFormat(raw); boolean isImage = - detectedFormat == ImageOptimizer.OutFormat.JPEG || - detectedFormat == ImageOptimizer.OutFormat.PNG || - detectedFormat == ImageOptimizer.OutFormat.WEBP; + detectedFormat == ImageOptimizer.OutFormat.JPEG + || detectedFormat == ImageOptimizer.OutFormat.PNG + || detectedFormat == ImageOptimizer.OutFormat.WEBP; // If it's an image, let's optimize it if (isImage) { @@ -150,7 +149,8 @@ public static File save(Context ctx, int noteId, String originalName, byte[] raw int dot = originalName.lastIndexOf('.'); if (dot != -1) ext = originalName.substring(dot); - String finalName = System.currentTimeMillis() + "_" + Math.abs(originalName.hashCode()) + ext; + String finalName = + System.currentTimeMillis() + "_" + Math.abs(originalName.hashCode()) + ext; File out = new File(folder, finalName); @@ -166,7 +166,6 @@ public static File save(Context ctx, int noteId, String originalName, byte[] raw } } - /** * Reads an attachment file by resolving its stored internal path. * @@ -184,8 +183,8 @@ public static File read(Context ctx, EditorAttachment att) { /** * Resolves EditorAttachment.url → real File path inside internal storage. - *

- * Expected URL format: file://attachments/note_/filename.ext + * + *

Expected URL format: file://attachments/note_/filename.ext * * @param ctx app context * @param att attachment model @@ -254,13 +253,11 @@ public static long getTotalAttachmentsSize(Context ctx) { return total; } - /** * Represents validation result for attachment checks. - *

- * Contains: - * - ok: validation success flag - * - error: user-readable error message (when ok == false) + * + *

Contains: - ok: validation success flag - error: user-readable error message (when ok == + * false) */ public static class AttachmentValidationResult { diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/models/EditorAttachment.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/models/EditorAttachment.java index 98270e5..728d0ff 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/models/EditorAttachment.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/models/EditorAttachment.java @@ -1,9 +1,7 @@ package com.pasich.mynotes.extendedEditor.models; import android.util.Log; - import com.google.gson.Gson; - import org.json.JSONObject; public class EditorAttachment { @@ -44,8 +42,6 @@ public static EditorAttachment fromJsonObject(JSONObject fileObj) { fileObj.optString("url", ""), fileObj.optString("name", ""), fileObj.optString("extension", ""), - fileObj.optLong("size", 0) - ); + fileObj.optLong("size", 0)); } - } diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/models/ParsedNote.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/models/ParsedNote.java index 3c991ae..cb3181b 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/models/ParsedNote.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/models/ParsedNote.java @@ -1,10 +1,8 @@ package com.pasich.mynotes.extendedEditor.models; import android.util.Log; - import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; - import java.lang.reflect.Type; import java.util.ArrayList; import java.util.List; @@ -36,6 +34,4 @@ public String toAttachmentsJson() { return "[]"; } } - - } diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/models/SettingsEditorJsBridge.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/models/SettingsEditorJsBridge.java index 3fc77e7..d8989fd 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/models/SettingsEditorJsBridge.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/models/SettingsEditorJsBridge.java @@ -1,3 +1,3 @@ package com.pasich.mynotes.extendedEditor.models; -public record SettingsEditorJsBridge(boolean isExtraOptimizeEnabled){} +public record SettingsEditorJsBridge(boolean isExtraOptimizeEnabled) {} diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorAttachmentsWebViewClient.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorAttachmentsWebViewClient.java index 7c436e8..23cf80c 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorAttachmentsWebViewClient.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorAttachmentsWebViewClient.java @@ -10,10 +10,8 @@ import android.webkit.WebResourceResponse; import android.webkit.WebView; import android.webkit.WebViewClient; - import com.pasich.mynotes.extendedEditor.attach.AttachmentStorage; import com.pasich.mynotes.extendedEditor.models.EditorAttachment; - import java.io.File; import java.io.FileInputStream; import java.util.List; @@ -42,8 +40,7 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque try { List segments = uri.getPathSegments(); - if (segments.size() < 2) - return super.shouldInterceptRequest(view, request); + if (segments.size() < 2) return super.shouldInterceptRequest(view, request); String noteStr = segments.get(0); // note_146 String fileName = segments.get(1); // abc.jpg @@ -51,13 +48,14 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque int noteId = Integer.parseInt(noteStr.replace("note_", "")); // Формуємо правильний EditorAttachment URL - String internalUrl = new Uri.Builder() - .scheme(EDITORJS_SCHEME) - .authority("attachments") - .appendPath("note_" + noteId) - .appendPath(fileName) - .build() - .toString(); + String internalUrl = + new Uri.Builder() + .scheme(EDITORJS_SCHEME) + .authority("attachments") + .appendPath("note_" + noteId) + .appendPath(fileName) + .build() + .toString(); EditorAttachment att = new EditorAttachment(internalUrl); @@ -68,11 +66,7 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque String mime = getMimeFromName(fileName); - return new WebResourceResponse( - mime, - null, - new FileInputStream(file) - ); + return new WebResourceResponse(mime, null, new FileInputStream(file)); } catch (Exception e) { Log.e(TAG, "intercept error " + e.getMessage()); diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJSInterface.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJSInterface.java index ccecdde..c7614a1 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJSInterface.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJSInterface.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.extendedEditor.utils; - import static com.pasich.mynotes.extendedEditor.attach.AttachmentStorage.ATTACHMENTS_BASE_DIR; import static com.pasich.mynotes.extendedEditor.utils.EditorJsScheme.EDITORJS_SCHEME; @@ -11,28 +10,23 @@ import android.webkit.JavascriptInterface; import android.webkit.WebView; import android.widget.Toast; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.extendedEditor.attach.AttachmentStorage; import com.pasich.mynotes.extendedEditor.models.EditorAttachment; import com.pasich.mynotes.extendedEditor.models.SettingsEditorJsBridge; - -import org.json.JSONArray; -import org.json.JSONObject; - import java.io.File; import java.util.Map; - +import org.json.JSONArray; +import org.json.JSONObject; /** * JavaScript bridge used to communicate between Android and Editor.js inside the WebView. - *

- * Handles incoming JS callbacks, forwards events to the EditorListener, - * and exposes selected Android-side functionality (file upload, theme sync, etc.) - * to JavaScript through @JavascriptInterface methods. + * + *

Handles incoming JS callbacks, forwards events to the EditorListener, and exposes selected + * Android-side functionality (file upload, theme sync, etc.) to JavaScript + * through @JavascriptInterface methods. */ - public class EditorJSInterface { public static final String nameInterface = "Android"; @@ -43,10 +37,11 @@ public class EditorJSInterface { private final Context appContext; private final SettingsEditorJsBridge settings; - public EditorJSInterface(EditorListener listener, - WebView webView, - Context appContext, - SettingsEditorJsBridge settings) { + public EditorJSInterface( + EditorListener listener, + WebView webView, + Context appContext, + SettingsEditorJsBridge settings) { this.listener = listener; this.webView = webView; @@ -55,10 +50,9 @@ public EditorJSInterface(EditorListener listener, } /** - * Called from JavaScript when Editor.js has fully initialized. - * Forwards the event to the EditorListener. + * Called from JavaScript when Editor.js has fully initialized. Forwards the event to the + * EditorListener. */ - @SuppressWarnings("unused") @JavascriptInterface public void onEditorReady() { @@ -70,17 +64,15 @@ public void onEditorReady() { * * @param jsonData Serialized array of blocks in JSON format. */ - @SuppressWarnings("unused") @JavascriptInterface public void onContentChanged(String jsonData) { if (listener != null) listener.onContentChanged(jsonData); - } /** - * Called from JS whenever the title field inside the editor changes. - * Executed on the main thread to safely update UI listeners. + * Called from JS whenever the title field inside the editor changes. Executed on the main + * thread to safely update UI listeners. */ @SuppressWarnings("unused") @JavascriptInterface @@ -88,9 +80,7 @@ public void onTitleChanged(String title) { webView.post(() -> listener.onTitleChanged(title)); } - /** - * Receives error messages thrown by the JS editor and reports them to listener. - */ + /** Receives error messages thrown by the JS editor and reports them to listener. */ @SuppressWarnings("unused") @JavascriptInterface public void onError(String error) { @@ -98,8 +88,8 @@ public void onError(String error) { } /** - * Sends a map of theme color values to the WebView, applying the current - * Android theme inside Editor.js through custom CSS variables. + * Sends a map of theme color values to the WebView, applying the current Android theme inside + * Editor.js through custom CSS variables. * * @param colors A map of --css-variable -> color value (#RRGGBB) */ @@ -117,12 +107,11 @@ public void setThemeColors(Map colors) { } /** - * Loads a note into Editor.js by passing a JSON structure via evaluateJavascript. - * Handles legacy notes (plain text), optimized rich notes, or empty notes. + * Loads a note into Editor.js by passing a JSON structure via evaluateJavascript. Handles + * legacy notes (plain text), optimized rich notes, or empty notes. * * @param note The note model which will be rendered in the editor. */ - public void loadNoteToEditor(Note note) { if (webView == null || note == null) return; try { @@ -131,7 +120,8 @@ public void loadNoteToEditor(Note note) { boolean isPlainTextFallback = false; - if (!note.isAttachments() && (note.getValueJson() == null || note.getValueJson().isEmpty())) { + if (!note.isAttachments() + && (note.getValueJson() == null || note.getValueJson().isEmpty())) { // old note → transfer plainText isPlainTextFallback = true; json.put("plainText", note.getValue() != null ? note.getValue() : ""); @@ -152,15 +142,13 @@ public void loadNoteToEditor(Note note) { } /** - * Receives a Base64-encoded file from Editor.js, decodes it, - * saves it to secure attachment storage and returns a file:// URL - * that Editor.js stores inside the block metadata. + * Receives a Base64-encoded file from Editor.js, decodes it, saves it to secure attachment + * storage and returns a file:// URL that Editor.js stores inside the block metadata. * - * @param base64 Encoded file content sent by JS. + * @param base64 Encoded file content sent by JS. * @param originalName Name of the file provided by the Editor.js tool. * @return A file://attachments/... URL or empty string on error. */ - @SuppressWarnings("unused") @JavascriptInterface public String uploadFile(String base64, String originalName) { @@ -175,7 +163,9 @@ public String uploadFile(String base64, String originalName) { byte[] raw = Base64.decode(base64, Base64.DEFAULT); if (raw == null || raw.length == 0) return ""; - File saved = AttachmentStorage.save(appContext, noteId, originalName, raw, settings.isExtraOptimizeEnabled()); + File saved = + AttachmentStorage.save( + appContext, noteId, originalName, raw, settings.isExtraOptimizeEnabled()); if (saved == null) return ""; return new Uri.Builder() .scheme(EDITORJS_SCHEME) @@ -187,17 +177,13 @@ public String uploadFile(String base64, String originalName) { } /** - * Accepts a base64 image from Editor.js, stores it in internal storage, - * and returns an Editor.js-compatible response in the following format: - * { - * “success”: 1, - * “file”: { ‘url’: “editorjs://attachments/...” } - * } - *

- * Returns on error: - * { “success”: 0 } - *

- * - uploadFile(...) performs the actual save and returns a URL or “” + * Accepts a base64 image from Editor.js, stores it in internal storage, and returns an + * Editor.js-compatible response in the following format: { “success”: 1, “file”: { ‘url’: + * “editorjs://attachments/...” } } + * + *

Returns on error: { “success”: 0 } + * + *

- uploadFile(...) performs the actual save and returns a URL or “” */ @SuppressWarnings("unused") @JavascriptInterface @@ -224,12 +210,10 @@ public String uploadImage(String base64, String originalName) { } } - /** - * Called when JS requests to open an attachment preview. - * Parses attachment JSON and forwards it to the listener (Activity). + * Called when JS requests to open an attachment preview. Parses attachment JSON and forwards it + * to the listener (Activity). */ - @SuppressWarnings("unused") @JavascriptInterface public void openAttachment(String json) { @@ -241,27 +225,31 @@ public void openAttachment(String json) { * * @param blockId ID of the deleted block * @param fileUrl URL of the file that should be removed (null means file was already missing) - *

- * Deletes the physical attachment file if the URL is not null and shows a toast with the result. + *

Deletes the physical attachment file if the URL is not null and shows a toast with the + * result. */ - @SuppressWarnings("unused") @JavascriptInterface public void onAttachmentBlockDeletedResponse(String blockId, String fileUrl) { if (fileUrl != null) { boolean removed = AttachmentStorage.delete(appContext, new EditorAttachment(fileUrl)); if (!removed) { - Toast.makeText(appContext, appContext.getString(R.string.deleteAttachmentsErrorExits), Toast.LENGTH_SHORT).show(); + Toast.makeText( + appContext, + appContext.getString(R.string.deleteAttachmentsErrorExits), + Toast.LENGTH_SHORT) + .show(); return; } } - Toast.makeText(appContext, appContext.getString(R.string.deleteAttachmentsSuccess), Toast.LENGTH_SHORT).show(); + Toast.makeText( + appContext, + appContext.getString(R.string.deleteAttachmentsSuccess), + Toast.LENGTH_SHORT) + .show(); } - - /** - * Toggles read-only mode inside Editor.js (title and blocks become non-editable). - */ + /** Toggles read-only mode inside Editor.js (title and blocks become non-editable). */ public void toggleReadMode() { webView.post(() -> webView.evaluateJavascript("toggleReadModeFromAndroid();", null)); } @@ -270,21 +258,21 @@ public void toggleReadMode() { * Requests JavaScript to delete a specific Editor.js block. * * @param blockId The ID of the block to remove. - * @param fileUrl URL of the attached file. If null → Android should not delete the physical file. + * @param fileUrl URL of the attached file. If null → Android should not delete the physical + * file. */ public void deleteAttachmentBlockRequest(String blockId, String fileUrl) { // If fileUrl == null → JS should receive “null” instead of “null string” - String jsUrl = (fileUrl == null) - ? "null" - : "'" + fileUrl.replace("'", "\\'") + "'"; + String jsUrl = (fileUrl == null) ? "null" : "'" + fileUrl.replace("'", "\\'") + "'"; - webView.evaluateJavascript("deleteAttachmentBlockFromAndroid('" + blockId + "', " + jsUrl + ");", null); + webView.evaluateJavascript( + "deleteAttachmentBlockFromAndroid('" + blockId + "', " + jsUrl + ");", null); } /** * Called from the JS ImageTool extension when user long-presses an image block. - *

- * Receives the unique blockId generated by Editor.js and delegates + * + *

Receives the unique blockId generated by Editor.js and delegates * * @param blockId Editor.js block unique identifier */ @@ -294,7 +282,6 @@ public void onImageBlockClick(String blockId) { listener.openPhoto(blockId); } - public interface EditorListener { void onEditorReady(); @@ -310,6 +297,4 @@ public interface EditorListener { int getNoteId(); } - - } diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJsonUtils.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJsonUtils.java index 0cf8ce6..3400722 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJsonUtils.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJsonUtils.java @@ -1,35 +1,27 @@ package com.pasich.mynotes.extendedEditor.utils; import android.util.Log; - import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.extendedEditor.models.EditorAttachment; import com.pasich.mynotes.extendedEditor.models.ParsedNote; - +import java.util.Objects; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import java.util.Objects; - public class EditorJsonUtils { private static final String TAG = "EditorJsonUtils"; - /** - * Returns attachment object (EditorAttachment) for a given blockId. - * Supports both formats: - * - data.file - * - data.files[] + * Returns attachment object (EditorAttachment) for a given blockId. Supports both formats: - + * data.file - data.files[] */ public static EditorAttachment findAttachmentByBlockId(Note note, String blockId) { try { - if (note == null || blockId == null || blockId.isEmpty()) - return null; + if (note == null || blockId == null || blockId.isEmpty()) return null; String json = note.getValueJson(); - if (json == null || json.isEmpty()) - return null; + if (json == null || json.isEmpty()) return null; JSONArray blocks = new JSONArray(json); @@ -66,13 +58,13 @@ public static EditorAttachment findAttachmentByBlockId(Note note, String blockId /** * Finds the Editor.js block ID that corresponds to the given attachment. - *

- * This method scans the note's valueJson structure and looks for an "attaches" - * block whose file URL matches the attachment's URL. Supports both - * data.file.url and data.files[].url formats depending on the Editor.js tool. + * + *

This method scans the note's valueJson structure and looks for an "attaches" block whose + * file URL matches the attachment's URL. Supports both data.file.url and data.files[].url + * formats depending on the Editor.js tool. * * @param note The note containing the serialized Editor.js blocks. - * @param att The attachment with the target file URL. + * @param att The attachment with the target file URL. * @return The ID of the matching block, or null if not found. */ public static String findBlockIdByAttachment(Note note, EditorAttachment att) { @@ -118,7 +110,6 @@ public static String findBlockIdByAttachment(Note note, EditorAttachment att) { } catch (Exception e) { Log.e(TAG, "findBlockIdByAttachment() failed", e); - } return null; @@ -126,26 +117,21 @@ public static String findBlockIdByAttachment(Note note, EditorAttachment att) { /** * Converts an extended Editor.js JSON representation into a legacy note format. - *

- * This method parses the array of Editor.js blocks and extracts: - * - Combined plain text content (paragraphs, headers, lists) - * - Attached files (from "attaches" blocks) - *

- * It effectively "flattens" rich structured content into a plain-text fallback - * while also collecting attachment metadata into the ParsedNote model. - *

- * Supported block types: - * - paragraph → appended as cleaned text - * - header → appended as plain text - * - list → appended as list items - * - attaches → extracted into attachment list - *

- * Unsupported blocks are safely ignored. + * + *

This method parses the array of Editor.js blocks and extracts: - Combined plain text + * content (paragraphs, headers, lists) - Attached files (from "attaches" blocks) + * + *

It effectively "flattens" rich structured content into a plain-text fallback while also + * collecting attachment metadata into the ParsedNote model. + * + *

Supported block types: - paragraph → appended as cleaned text - header → appended as plain + * text - list → appended as list items - attaches → extracted into attachment list + * + *

Unsupported blocks are safely ignored. * * @param jsonData Raw Editor.js blocks JSON array (valueJson) * @return ParsedNote containing plain text and parsed attachments */ - public static ParsedNote extendedNoteToOldNote(String jsonData) { ParsedNote result = new ParsedNote(); @@ -165,7 +151,6 @@ public static ParsedNote extendedNoteToOldNote(String jsonData) { if (data == null) continue; switch (type) { - case "paragraph": plainText.append(cleanText(data.optString("text", ""))); break; @@ -206,10 +191,11 @@ public static ParsedNote extendedNoteToOldNote(String jsonData) { /** * Parses a “list” type block and adds its elements to plain text. * - * @param data JSON data of the Editor.js block + * @param data JSON data of the Editor.js block * @param plainText accumulated plain text string */ - private static void handleListBlock(JSONObject data, StringBuilder plainText) throws JSONException { + private static void handleListBlock(JSONObject data, StringBuilder plainText) + throws JSONException { String style = data.optString("style", "unordered"); JSONArray items = data.optJSONArray("items"); @@ -224,7 +210,7 @@ private static void handleListBlock(JSONObject data, StringBuilder plainText) th /** * Parses the “attaches” block and adds attachments to the ParsedNote model. * - * @param data JSON data from the Editor.js block + * @param data JSON data from the Editor.js block * @param result the result where all attachments are collected */ private static void handleAttachBlock(JSONObject data, ParsedNote result) { @@ -242,9 +228,8 @@ private static void handleAttachBlock(JSONObject data, ParsedNote result) { /** * Parses the “image” block (ImageTool) and adds attachments into ParsedNote. - *

- * Expected structure: - * data.file.url → string + * + *

Expected structure: data.file.url → string */ private static void handleImageBlock(JSONObject data, ParsedNote result) { try { @@ -261,16 +246,16 @@ private static void handleImageBlock(JSONObject data, ParsedNote result) { } } - /** * Recursive method for processing nested list items. * - * @param item JSONObject of a single list item. + * @param item JSONObject of a single list item. * @param builder StringBuilder for collecting text. - * @param indent Indentation for nesting. + * @param indent Indentation for nesting. */ - - private static void appendListItem(JSONObject item, StringBuilder builder, String indent, String style, int orderIndex) throws JSONException { + private static void appendListItem( + JSONObject item, StringBuilder builder, String indent, String style, int orderIndex) + throws JSONException { String content = cleanText(item.optString("content", "")); if (!content.isEmpty()) { switch (style) { @@ -278,11 +263,21 @@ private static void appendListItem(JSONObject item, StringBuilder builder, Strin builder.append(indent).append("- ").append(content).append("\n"); break; case "ordered": - builder.append(indent).append(orderIndex).append(". ").append(content).append("\n"); + builder.append(indent) + .append(orderIndex) + .append(". ") + .append(content) + .append("\n"); break; case "checklist": - boolean checked = item.optJSONObject("meta") != null && Objects.requireNonNull(item.optJSONObject("meta")).optBoolean("checked", false); - builder.append(indent).append(checked ? "[x] " : "[ ] ").append(content).append("\n"); + boolean checked = + item.optJSONObject("meta") != null + && Objects.requireNonNull(item.optJSONObject("meta")) + .optBoolean("checked", false); + builder.append(indent) + .append(checked ? "[x] " : "[ ] ") + .append(content) + .append("\n"); break; default: builder.append(indent).append(content).append("\n"); @@ -298,21 +293,18 @@ private static void appendListItem(JSONObject item, StringBuilder builder, Strin } /** - * Cleans HTML-formatted text extracted from Editor.js blocks - * and converts it into plain text. - *

- * Operations performed: - * - Converts
tags (any variant) into newline characters - * - Replaces   with a normal space - * - Removes all remaining HTML tags using a regex - *

- * This method ensures that paragraph and header text from the - * rich-text editor is safely converted into a readable plain-text form. + * Cleans HTML-formatted text extracted from Editor.js blocks and converts it into plain text. + * + *

Operations performed: - Converts
+ * tags (any variant) into newline characters - Replaces   with a normal space - Removes + * all remaining HTML tags using a regex + * + *

This method ensures that paragraph and header text from the rich-text editor is safely + * converted into a readable plain-text form. * * @param text Raw HTML/text content from Editor.js * @return Sanitized plain-text string */ - private static String cleanText(String text) { if (text == null) return ""; text = text.replaceAll("(?i)", "\n"); @@ -320,5 +312,4 @@ private static String cleanText(String text) { text = text.replaceAll("<[^>]+>", ""); return text; } - } diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/SettingsEditorColors.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/SettingsEditorColors.java index 2c79269..aac7324 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/SettingsEditorColors.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/SettingsEditorColors.java @@ -2,9 +2,7 @@ import android.content.Context; import android.util.TypedValue; - import androidx.core.content.ContextCompat; - import java.util.HashMap; import java.util.Map; @@ -12,23 +10,39 @@ public class SettingsEditorColors { public Map getThemeColors(Context context) { Map colors = new HashMap<>(); - colors.put("surface-color", resolveAttrColor(context, com.google.android.material.R.attr.colorSurfaceContainerLow)); - colors.put("on-surface-color", resolveAttrColor(context, com.google.android.material.R.attr.colorOnSurface)); - colors.put("primary-color", resolveAttrColor(context, com.google.android.material.R.attr.colorPrimaryFixed)); - colors.put("on-primary-color", resolveAttrColor(context, com.google.android.material.R.attr.colorOnPrimary)); - colors.put("surface-variant-color", resolveAttrColor(context, com.google.android.material.R.attr.colorSurfaceVariant)); - colors.put("on-surface-variant-color", resolveAttrColor(context, com.google.android.material.R.attr.colorOnSurfaceVariant)); - colors.put("outline-color", resolveAttrColor(context, com.google.android.material.R.attr.colorOutline)); + colors.put( + "surface-color", + resolveAttrColor( + context, com.google.android.material.R.attr.colorSurfaceContainerLow)); + colors.put( + "on-surface-color", + resolveAttrColor(context, com.google.android.material.R.attr.colorOnSurface)); + colors.put( + "primary-color", + resolveAttrColor(context, com.google.android.material.R.attr.colorPrimaryFixed)); + colors.put( + "on-primary-color", + resolveAttrColor(context, com.google.android.material.R.attr.colorOnPrimary)); + colors.put( + "surface-variant-color", + resolveAttrColor(context, com.google.android.material.R.attr.colorSurfaceVariant)); + colors.put( + "on-surface-variant-color", + resolveAttrColor( + context, com.google.android.material.R.attr.colorOnSurfaceVariant)); + colors.put( + "outline-color", + resolveAttrColor(context, com.google.android.material.R.attr.colorOutline)); return colors; } - private String resolveAttrColor(Context context, int attrRes) { TypedValue typedValue = new TypedValue(); if (context.getTheme().resolveAttribute(attrRes, typedValue, true)) { int color; - if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) { + if (typedValue.type >= TypedValue.TYPE_FIRST_COLOR_INT + && typedValue.type <= TypedValue.TYPE_LAST_COLOR_INT) { color = typedValue.data; } else { color = ContextCompat.getColor(context, typedValue.resourceId); diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/view/AttachmentActionsDialog.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/view/AttachmentActionsDialog.java index 11fb901..3d13b19 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/view/AttachmentActionsDialog.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/view/AttachmentActionsDialog.java @@ -11,16 +11,13 @@ import android.view.View; import android.webkit.MimeTypeMap; import android.widget.Toast; - import androidx.core.content.FileProvider; - import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; import com.pasich.mynotes.databinding.BottomSheetAttachmentBinding; import com.pasich.mynotes.extendedEditor.attach.AttachmentStorage; import com.pasich.mynotes.extendedEditor.models.EditorAttachment; - import java.io.File; import java.io.FileInputStream; import java.io.OutputStream; @@ -30,10 +27,7 @@ public class AttachmentActionsDialog { @SuppressLint("UseCompatLoadingForDrawables") public static void show( - Context ctx, - EditorAttachment attachment, - OnAttachmentDeleteListener deleteListener - ) { + Context ctx, EditorAttachment attachment, OnAttachmentDeleteListener deleteListener) { BottomSheetDialog dialog = new BottomSheetDialog(ctx); BottomSheetAttachmentBinding binding = BottomSheetAttachmentBinding.inflate(dialog.getLayoutInflater()); @@ -55,89 +49,97 @@ public static void show( binding.deleteAttach.setBackground(ctx.getDrawable(R.drawable.bg_item_full)); } else { // File exists → show everything - binding.openAction.setOnClickListener(v -> { - dialog.dismiss(); - openWith(ctx, attachment); - }); - - binding.downloadAction.setOnClickListener(v -> { - dialog.dismiss(); - saveToDownloads(ctx, attachment); - }); + binding.openAction.setOnClickListener( + v -> { + dialog.dismiss(); + openWith(ctx, attachment); + }); + + binding.downloadAction.setOnClickListener( + v -> { + dialog.dismiss(); + saveToDownloads(ctx, attachment); + }); } // DELETE — always visible - binding.deleteAttach.setOnClickListener(v -> { - dialog.dismiss(); + binding.deleteAttach.setOnClickListener( + v -> { + dialog.dismiss(); - if (exists) { - // Showing confirmation - showConfirmDelete(ctx, attachment, deleteListener); - } else { - // File does not exist — simply delete without dialog - deleteListener.onDeleteAttachment(attachment, true); - } - }); + if (exists) { + // Showing confirmation + showConfirmDelete(ctx, attachment, deleteListener); + } else { + // File does not exist — simply delete without dialog + deleteListener.onDeleteAttachment(attachment, true); + } + }); dialog.setContentView(binding.getRoot()); dialog.show(); } - - private static void showConfirmDelete(Context ctx, - EditorAttachment att, - OnAttachmentDeleteListener listener) { + private static void showConfirmDelete( + Context ctx, EditorAttachment att, OnAttachmentDeleteListener listener) { new MaterialAlertDialogBuilder( - ctx, - com.google.android.material.R.style.ThemeOverlay_Material3_MaterialAlertDialog - ) + ctx, + com.google.android.material.R.style + .ThemeOverlay_Material3_MaterialAlertDialog) .setTitle(R.string.dialog_delete_title) .setMessage(R.string.dialog_delete_message) - .setPositiveButton(R.string.dialog_delete_confirm, (dialog, which) -> { - listener.onDeleteAttachment(att, false); - dialog.dismiss(); - }) - .setNegativeButton(R.string.dialog_delete_cancel, (dialog, which) -> dialog.dismiss()) + .setPositiveButton( + R.string.dialog_delete_confirm, + (dialog, which) -> { + listener.onDeleteAttachment(att, false); + dialog.dismiss(); + }) + .setNegativeButton( + R.string.dialog_delete_cancel, (dialog, which) -> dialog.dismiss()) .show(); } - private static void openWith(Context ctx, EditorAttachment att) { try { File mFile = AttachmentStorage.read(ctx, att); if (mFile == null) { - Toast.makeText(ctx, ctx.getString(R.string.attachment_load_failed), Toast.LENGTH_SHORT).show(); + Toast.makeText( + ctx, + ctx.getString(R.string.attachment_load_failed), + Toast.LENGTH_SHORT) + .show(); return; } String mime = getMime(att.extension); if (mime == null) mime = "*/*"; - var uri = FileProvider.getUriForFile( - ctx, - ctx.getPackageName() + ".provider", - mFile - ); + var uri = FileProvider.getUriForFile(ctx, ctx.getPackageName() + ".provider", mFile); var intent = new Intent(android.content.Intent.ACTION_VIEW); intent.setDataAndType(uri, mime); intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - ctx.startActivity(Intent.createChooser(intent, ctx.getString(R.string.attachment_open_with))); + ctx.startActivity( + Intent.createChooser(intent, ctx.getString(R.string.attachment_open_with))); } catch (Exception e) { Log.e(TAG, "openWith() aattachments failded", e); - Toast.makeText(ctx, ctx.getString(R.string.attachment_open_failed), Toast.LENGTH_SHORT).show(); + Toast.makeText(ctx, ctx.getString(R.string.attachment_open_failed), Toast.LENGTH_SHORT) + .show(); } } - private static void saveToDownloads(Context ctx, EditorAttachment att) { try { File file = AttachmentStorage.read(ctx, att); if (file == null) { - Toast.makeText(ctx, ctx.getString(R.string.attachment_save_failed), Toast.LENGTH_SHORT).show(); + Toast.makeText( + ctx, + ctx.getString(R.string.attachment_save_failed), + Toast.LENGTH_SHORT) + .show(); return; } @@ -170,7 +172,7 @@ private static void saveToDownloads(Context ctx, EditorAttachment att) { } try (OutputStream out = resolver.openOutputStream(item); - FileInputStream in = new FileInputStream(file)) { + FileInputStream in = new FileInputStream(file)) { byte[] buffer = new byte[8192]; int len; @@ -193,9 +195,7 @@ private static void saveToDownloads(Context ctx, EditorAttachment att) { } } - private static String getMime(String ext) { - return MimeTypeMap.getSingleton() - .getMimeTypeFromExtension(ext.toLowerCase()); + return MimeTypeMap.getSingleton().getMimeTypeFromExtension(ext.toLowerCase()); } } diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/view/CopyTextDialog.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/view/CopyTextDialog.java index 4c7a926..1fd88e4 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/view/CopyTextDialog.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/view/CopyTextDialog.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.extendedEditor.view; - import android.content.ClipData; import android.content.ClipboardManager; import android.content.Context; @@ -8,9 +7,7 @@ import android.view.ViewGroup; import android.widget.ScrollView; import android.widget.TextView; - import androidx.annotation.NonNull; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; @@ -25,34 +22,29 @@ public static void show(Context context, String title, String text) { ScrollView scrollView = getScrollView(context, text); - int verticalInset = (int) TypedValue.applyDimension( - TypedValue.COMPLEX_UNIT_DIP, - 20, - context.getResources().getDisplayMetrics() - ); + int verticalInset = + (int) + TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, + 20, + context.getResources().getDisplayMetrics()); new MaterialAlertDialogBuilder(context) .setTitle(title != null ? title : context.getString(R.string.copy_content)) - .setView( - scrollView, - 0, - verticalInset, - 0, - verticalInset - ) - .setPositiveButton(R.string.copy_all, (d, w) -> { - ClipboardManager clipboard = - (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); + .setView(scrollView, 0, verticalInset, 0, verticalInset) + .setPositiveButton( + R.string.copy_all, + (d, w) -> { + ClipboardManager clipboard = + (ClipboardManager) + context.getSystemService(Context.CLIPBOARD_SERVICE); - if (clipboard != null) { - clipboard.setPrimaryClip( - ClipData.newPlainText("text", text) - ); - } - }) + if (clipboard != null) { + clipboard.setPrimaryClip(ClipData.newPlainText("text", text)); + } + }) .setNegativeButton(R.string.close, null) .show(); - } @NonNull @@ -63,18 +55,15 @@ private static ScrollView getScrollView(Context context, String text) { textView.setTextSize(15); textView.setPadding(32, 32, 32, 32); TypedValue tv = new TypedValue(); - context.getTheme().resolveAttribute( - com.google.android.material.R.attr.colorSurface, - tv, - true - ); + context.getTheme() + .resolveAttribute(com.google.android.material.R.attr.colorSurface, tv, true); textView.setBackgroundColor(tv.data); ScrollView scrollView = new ScrollView(context); - scrollView.setLayoutParams(new ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - (int) (context.getResources().getDisplayMetrics().heightPixels * 0.7f) - )); + scrollView.setLayoutParams( + new ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + (int) (context.getResources().getDisplayMetrics().heightPixels * 0.7f))); scrollView.addView(textView); return scrollView; } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/BackupContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/BackupContract.java index 172692f..b4dd0c1 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/BackupContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/BackupContract.java @@ -1,16 +1,12 @@ package com.pasich.mynotes.ui.contract; - import android.net.Uri; - import androidx.annotation.StringRes; - import com.pasich.mynotes.base.view.BasePresenter; import com.pasich.mynotes.base.view.BaseView; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.utils.backup.models.JsonBackup; import com.pasich.mynotes.utils.backup.models.googleKeep.GoogleKeepImportResult; - import dagger.hilt.android.scopes.ActivityScoped; public interface BackupContract { @@ -44,10 +40,8 @@ interface view extends BaseView { void processSelectedFileOtherApp(Uri fileUri); void onRestoreSuccessFlag(); - } - @ActivityScoped interface presenter extends BasePresenter { @@ -66,7 +60,5 @@ interface presenter extends BasePresenter { void importFromZipOtherApp(Uri fileUri); void startProcessSelectedFileOtherApp(Uri fileUri); - - } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/MainContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/MainContract.java index 41e687f..98f60c0 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/MainContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/MainContract.java @@ -1,7 +1,6 @@ package com.pasich.mynotes.ui.contract; import android.view.View; - import com.pasich.mynotes.base.view.BasePresenter; import com.pasich.mynotes.base.view.BaseView; import com.pasich.mynotes.base.view.MoreNoteMainActivityView; @@ -9,12 +8,10 @@ import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.ui.state.MainViewState; import com.pasich.mynotes.ui.state.StatsData; - +import dagger.hilt.android.scopes.ActivityScoped; import java.util.ArrayList; import java.util.List; -import dagger.hilt.android.scopes.ActivityScoped; - public interface MainContract { interface view extends BaseView, MoreNoteMainActivityView { @@ -40,7 +37,6 @@ interface view extends BaseView, MoreNoteMainActivityView { void multipleTagChangerDialog(List tagsList); } - @ActivityScoped interface presenter extends BasePresenter { void newNotesClick(); @@ -73,5 +69,4 @@ interface presenter extends BasePresenter { void requestTagChangeMultipleNotes(String selectedTag, List notesIds); } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/NoteContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/NoteContract.java index b0bcd55..fedab24 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/NoteContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/NoteContract.java @@ -1,7 +1,6 @@ package com.pasich.mynotes.ui.contract; import android.content.Intent; - import com.pasich.mynotes.base.view.ActionBar; import com.pasich.mynotes.base.view.BasePresenter; import com.pasich.mynotes.base.view.BaseView; @@ -70,7 +69,6 @@ interface presenter extends BasePresenter { boolean hasNote(); void copyNoteRequest(); - } interface AutoSaveCallback { diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/TagsContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/TagsContract.java index f7fdcec..bfa3e31 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/TagsContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/TagsContract.java @@ -1,37 +1,34 @@ package com.pasich.mynotes.ui.contract; import android.view.View; - import androidx.annotation.StringRes; - import com.pasich.mynotes.base.view.ActionBar; import com.pasich.mynotes.base.view.BasePresenter; import com.pasich.mynotes.base.view.BaseView; import com.pasich.mynotes.data.model.Tag; - -import java.util.List; - import dagger.hilt.android.scopes.ActivityScoped; +import java.util.List; public interface TagsContract { interface view extends BaseView, ActionBar { - + void setupRecyclerView(); - + void loadTags(List tags); - + void showCreateTagDialog(int newPosition); - + void showEditTagDialog(Tag tag); - + void showDeleteTagDialog(Tag tag); - + void showTagOptionsDialog(Tag tag, View anchorView); - + void showToastMessage(String message); void showToastMessage(@StringRes int message); + void showToastCheckCountTags(); void showSortDialog(); @@ -39,13 +36,13 @@ interface view extends BaseView, ActionBar { @ActivityScoped interface presenter extends BasePresenter { - + void loadTags(); void toggleTagVisibility(Tag tag); - + void onAddTagClick(); - + void onTagLongClick(Tag tag, View anchorView); void sortTags(String sortParam); @@ -56,8 +53,8 @@ interface presenter extends BasePresenter { void getTagNotesCount(Tag tag, TagNotesCountCallback callback); } - + interface TagNotesCountCallback { void onTagNotesCountReceived(int count); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/TasksContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/TasksContract.java index 7b1c710..a6d0a51 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/TasksContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/TasksContract.java @@ -4,31 +4,41 @@ import com.pasich.mynotes.base.view.BaseView; import com.pasich.mynotes.data.model.Task; import com.pasich.mynotes.data.model.TaskCategory; - -import java.util.List; - import dagger.hilt.android.scopes.ActivityScoped; +import java.util.List; public interface TasksContract { interface view extends BaseView { void renderTasks(List active, List completed); + void renderCategories(List categories); + void initListeners(); } @ActivityScoped interface presenter extends BasePresenter { void onCategorySelected(int categoryId); + void addTask(String title, String description, int categoryId); + void editTask(Task task, String newTitle, String newDescription); + void toggleTask(Task task); + void deleteTask(Task task); + void clearCompleted(); + void addCategory(String name, String colorHex); + void deleteCategory(TaskCategory category); + void updatePositions(List tasks); + void setTaskReminder(Task task, long time); + void clearTaskReminder(Task task); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/TrashContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/TrashContract.java index 4114330..f1c65f5 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/TrashContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/TrashContract.java @@ -4,11 +4,9 @@ import com.pasich.mynotes.base.view.BasePresenter; import com.pasich.mynotes.base.view.BaseView; import com.pasich.mynotes.data.model.Note; - import java.util.ArrayList; import java.util.List; - public interface TrashContract { interface view extends BaseView, ActionBar { diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/DeleteTagDialogContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/DeleteTagDialogContract.java index 7402e7c..d26821b 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/DeleteTagDialogContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/DeleteTagDialogContract.java @@ -4,12 +4,9 @@ import com.pasich.mynotes.base.view.BaseView; import com.pasich.mynotes.data.model.Tag; - public interface DeleteTagDialogContract { - interface view extends BaseView { - - } + interface view extends BaseView {} interface presenter extends BasePresenter { void getLoadCountNotesForTag(String nameTag); @@ -19,7 +16,5 @@ interface presenter extends BasePresenter { void deleteTagAndNotes(Tag tag); int getCountNotesForTag(); - - } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/MoreNoteDialogContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/MoreNoteDialogContract.java index 2f5647e..d0f1b09 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/MoreNoteDialogContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/MoreNoteDialogContract.java @@ -4,10 +4,8 @@ import com.pasich.mynotes.base.view.BaseView; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.Tag; - import java.util.List; - public interface MoreNoteDialogContract { interface view extends BaseView { diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/NameTagDialogContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/NameTagDialogContract.java index ff7cef9..f0b1b30 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/NameTagDialogContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/dialogs/NameTagDialogContract.java @@ -6,9 +6,7 @@ public interface NameTagDialogContract { - interface view extends BaseView { - - } + interface view extends BaseView {} interface presenter extends BasePresenter { diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/SelectionController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/SelectionController.java index 6d2444e..7eac2a0 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/SelectionController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/SelectionController.java @@ -1,14 +1,11 @@ package com.pasich.mynotes.ui.controllers; - import android.view.View; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.databinding.ActionPanelBinding; import com.pasich.mynotes.utils.adapters.notes.NoteAdapter; import com.pasich.mynotes.utils.recycler.payloads.NotePayloads; - import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -35,20 +32,23 @@ public void setListener(Listener l) { private void initActions() { panel.actionClose.setOnClickListener(v -> clearSelection()); - panel.actionDelete.setOnClickListener(v -> { - if (listener != null) listener.onDeleteRequested(); - }); - panel.actionShare.setOnClickListener(v -> { - if (listener != null) listener.onShareRequested(); - }); - panel.actionRestore.setOnClickListener(v -> { - if (listener != null) listener.onRestoreRequested(); - }); - panel.actionTagChange.setOnClickListener(v -> { - if (listener != null) listener.onChangeTagRequested(); - }); + panel.actionDelete.setOnClickListener( + v -> { + if (listener != null) listener.onDeleteRequested(); + }); + panel.actionShare.setOnClickListener( + v -> { + if (listener != null) listener.onShareRequested(); + }); + panel.actionRestore.setOnClickListener( + v -> { + if (listener != null) listener.onRestoreRequested(); + }); + panel.actionTagChange.setOnClickListener( + v -> { + if (listener != null) listener.onChangeTagRequested(); + }); panel.actionClose.setOnClickListener(v -> clearSelection()); - } public boolean isInSelectionMode() { @@ -99,17 +99,13 @@ public void toggle(Note note) { if (panel != null) { int count = selectedIds.size(); panel.selectedCount.setText( - panel.getRoot().getResources().getQuantityString( - R.plurals.selected_count, - count, - count - ) - ); + panel.getRoot() + .getResources() + .getQuantityString(R.plurals.selected_count, count, count)); } notifyListener(); } - public void clearSelection() { if (!selectionMode) return; @@ -148,15 +144,13 @@ private void updateNoteVisualState(int id) { } } - private void showPanel(boolean show) { panel.getRoot().setVisibility(show ? View.VISIBLE : View.GONE); if (listener != null) listener.onSelectionModeChanged(show); } private void notifyListener() { - if (listener != null) - listener.onSelectionCountChanged(selectedIds.size()); + if (listener != null) listener.onSelectionCountChanged(selectedIds.size()); } public void cleanup() { @@ -189,39 +183,26 @@ public boolean isSelected(int id) { return selectedIds.contains(id); } - public HashSet getSelectedIds() { return selectedIds; } - public enum Mode { - NORMAL, // delete + share - RESTORE // restore only + NORMAL, // delete + share + RESTORE // restore only } public interface Listener { - default void onSelectionCountChanged(int ignoredCount) { - - } + default void onSelectionCountChanged(int ignoredCount) {} void onSelectionModeChanged(boolean active); - default void onDeleteRequested() { + default void onDeleteRequested() {} - } - - default void onShareRequested() { + default void onShareRequested() {} - } + default void onRestoreRequested() {} - default void onRestoreRequested() { - - } - - default void onChangeTagRequested() { - - } + default void onChangeTagRequested() {} } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/AppUpdateController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/AppUpdateController.java index e1026c0..9ba5e48 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/AppUpdateController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/AppUpdateController.java @@ -3,10 +3,8 @@ import android.app.Activity; import android.content.Intent; import android.util.Log; - import androidx.activity.result.ActivityResultLauncher; import androidx.appcompat.app.AppCompatActivity; - import com.google.android.play.core.appupdate.AppUpdateInfo; import com.google.android.play.core.appupdate.AppUpdateManager; import com.google.android.play.core.appupdate.AppUpdateManagerFactory; @@ -29,8 +27,7 @@ public class AppUpdateController { public AppUpdateController( Activity activity, UpdateChecker updateChecker, - ActivityResultLauncher changelogLauncher - ) { + ActivityResultLauncher changelogLauncher) { this.activity = activity; this.updateChecker = updateChecker; this.changelogLauncher = changelogLauncher; @@ -45,64 +42,57 @@ private void init() { } public void handleOnResume() { - updateManager.getAppUpdateInfo().addOnSuccessListener(info -> { - if (info.updateAvailability() == - UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) { - startImmediateUpdate(info); - } - }); + updateManager + .getAppUpdateInfo() + .addOnSuccessListener( + info -> { + if (info.updateAvailability() + == UpdateAvailability.DEVELOPER_TRIGGERED_UPDATE_IN_PROGRESS) { + startImmediateUpdate(info); + } + }); } private void checkForUpdate() { - updateManager.getAppUpdateInfo() - .addOnSuccessListener(info -> { - if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE - && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) { - - startImmediateUpdate(info); - } - }) - .addOnFailureListener(e -> - Log.d("AppUpdate", "check error: " + e.getMessage()) - ); + updateManager + .getAppUpdateInfo() + .addOnSuccessListener( + info -> { + if (info.updateAvailability() == UpdateAvailability.UPDATE_AVAILABLE + && info.isUpdateTypeAllowed(AppUpdateType.IMMEDIATE)) { + + startImmediateUpdate(info); + } + }) + .addOnFailureListener(e -> Log.d("AppUpdate", "check error: " + e.getMessage())); } private void startImmediateUpdate(AppUpdateInfo info) { try { updateManager.startUpdateFlowForResult( - info, - AppUpdateType.IMMEDIATE, - activity, - REQUEST_UPDATE - ); + info, AppUpdateType.IMMEDIATE, activity, REQUEST_UPDATE); } catch (Exception e) { Log.d("AppUpdate", "startUpdate error: " + e.getMessage()); } } - /** - * Лише показ changelog-діалогу - */ + /** Лише показ changelog-діалогу */ public void showChangelogIfNeeded() { if (updateChecker.hasNewVersion()) { UpdateChangelogDialog.newInstance() - .show(((AppCompatActivity) activity).getSupportFragmentManager(), + .show( + ((AppCompatActivity) activity).getSupportFragmentManager(), "UpdateChangelogDialog"); } } - /** - * Потрібно NavigationController - */ + /** Потрібно NavigationController */ public boolean hasNewVersion() { return updateChecker.hasNewVersion(); } - /** - * Потрібно NavigationController - */ + /** Потрібно NavigationController */ public void openChangelog() { changelogLauncher.launch(new Intent(activity, ChangelogActivity.class)); } } - diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java index 58aae71..15dd631 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java @@ -5,14 +5,11 @@ import android.view.animation.AccelerateInterpolator; import android.view.animation.DecelerateInterpolator; import android.view.animation.OvershootInterpolator; - import androidx.annotation.Nullable; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.databinding.ActivityMainBinding; import com.pasich.mynotes.utils.managers.SystemTagsManager; - import java.util.List; public class MainRenderListsController { @@ -25,27 +22,29 @@ public MainRenderListsController(ActivityMainBinding binding) { this.res = binding.getRoot().getResources(); } - public void animateNoteListChange() { - binding.listNotes.animate() + binding.listNotes + .animate() .alpha(0f) .scaleY(0.97f) .setDuration(120) - .withEndAction(() -> { - binding.listNotes.scheduleLayoutAnimation(); - binding.listNotes.animate() - .alpha(1f) - .scaleY(1f) - .setInterpolator(new OvershootInterpolator(0.6f)) - .setDuration(220) - .start(); - }) + .withEndAction( + () -> { + binding.listNotes.scheduleLayoutAnimation(); + binding.listNotes + .animate() + .alpha(1f) + .scaleY(1f) + .setInterpolator(new OvershootInterpolator(0.6f)) + .setDuration(220) + .start(); + }) .start(); } /** - * Handles switching between the notes list and the empty state view, - * including fade/scale animations and optional smooth scrolling. + * Handles switching between the notes list and the empty state view, including fade/scale + * animations and optional smooth scrolling. */ public void showStateNoteList(@Nullable Tag selectedTag, int mNotesCount) { if (mNotesCount == 0) { @@ -57,16 +56,13 @@ public void showStateNoteList(@Nullable Tag selectedTag, int mNotesCount) { animateHideEmpty(binding.includeEmpty.emptyViewNote); animateShowList(binding.listNotes); - } public void scrollUpNoteList() { binding.listNotes.post(() -> binding.listNotes.smoothScrollToPosition(0)); } - /** - * Smoothly shows the notes list using fade + scale animation. - */ + /** Smoothly shows the notes list using fade + scale animation. */ private void animateShowList(View list) { if (list.getVisibility() == View.VISIBLE) return; @@ -82,9 +78,7 @@ private void animateShowList(View list) { .start(); } - /** - * Smoothly hides the notes list with fade + scale collapse animation. - */ + /** Smoothly hides the notes list with fade + scale collapse animation. */ private void animateHideList(View list) { if (list.getVisibility() != View.VISIBLE) return; @@ -97,24 +91,17 @@ private void animateHideList(View list) { .start(); } - /** - * Shows the empty state view using a simple fade-in animation. - */ + /** Shows the empty state view using a simple fade-in animation. */ private void animateShowEmpty(View empty) { if (empty.getVisibility() == View.VISIBLE) return; empty.setVisibility(View.VISIBLE); empty.setAlpha(0f); - empty.animate() - .alpha(1f) - .setDuration(200) - .start(); + empty.animate().alpha(1f).setDuration(200).start(); } - /** - * Hides the empty state view using fade-out animation. - */ + /** Hides the empty state view using fade-out animation. */ private void animateHideEmpty(View empty) { if (empty.getVisibility() != View.VISIBLE) return; @@ -126,8 +113,8 @@ private void animateHideEmpty(View empty) { } /** - * Updates the empty state text based on the selected tag and note count. - * Also applies density-specific adjustments for low DPI devices. + * Updates the empty state text based on the selected tag and note count. Also applies + * density-specific adjustments for low DPI devices. */ private void updateEmptyText(String emptyText) { binding.includeEmpty.emptyNotesText.setText(emptyText); @@ -138,9 +125,7 @@ private void updateEmptyText(String emptyText) { } } - /** - * Expands a horizontal list (tags list) using soft scale + fade animation. - */ + /** Expands a horizontal list (tags list) using soft scale + fade animation. */ private void expandHorizontal(View view) { if (view.getVisibility() == View.VISIBLE) return; @@ -157,9 +142,7 @@ private void expandHorizontal(View view) { .start(); } - /** - * Collapses a horizontal list with fade + slight scale-down animation. - */ + /** Collapses a horizontal list with fade + slight scale-down animation. */ private void collapseHorizontal(View view) { if (view.getVisibility() == View.GONE) return; @@ -168,17 +151,18 @@ private void collapseHorizontal(View view) { .alpha(0f) .setDuration(200) .setInterpolator(new DecelerateInterpolator(1.6f)) - .withEndAction(() -> { - view.setVisibility(View.GONE); - view.setScaleY(1f); - view.setAlpha(1f); - }) + .withEndAction( + () -> { + view.setVisibility(View.GONE); + view.setScaleY(1f); + view.setAlpha(1f); + }) .start(); } /** - * Handles rendering of the tags list with animations depending - * on whether user-defined tags exist. + * Handles rendering of the tags list with animations depending on whether user-defined tags + * exist. */ public void renderListTags(List tags) { boolean hasUserTags = false; @@ -196,9 +180,7 @@ public void renderListTags(List tags) { } } - /** - * Helper class for constructing the appropriate empty-state text. - */ + /** Helper class for constructing the appropriate empty-state text. */ private class EmptyStateBuilder { String build(@Nullable Tag selectedTag, int notesCount) { if (notesCount > 0) return ""; diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/NavigationController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/NavigationController.java index bdcfcf3..8d15643 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/NavigationController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/NavigationController.java @@ -5,7 +5,6 @@ import android.os.Looper; import android.view.MenuItem; import android.view.View; - import androidx.activity.OnBackPressedCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.NonNull; @@ -15,7 +14,6 @@ import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.drawerlayout.widget.DrawerLayout; - import com.pasich.mynotes.R; import com.pasich.mynotes.databinding.ActivityMainBinding; import com.pasich.mynotes.databinding.NavHeaderMainBinding; @@ -23,8 +21,8 @@ import com.pasich.mynotes.ui.view.activity.BackupActivity; import com.pasich.mynotes.ui.view.activity.SettingsActivity; import com.pasich.mynotes.ui.view.activity.SupportActivity; -import com.pasich.mynotes.ui.view.activity.TasksActivity; import com.pasich.mynotes.ui.view.activity.TagsActivity; +import com.pasich.mynotes.ui.view.activity.TasksActivity; import com.pasich.mynotes.ui.view.activity.TrashActivity; public class NavigationController { @@ -37,14 +35,14 @@ public class NavigationController { private DrawerLayout drawerLayout; private NavHeaderMainBinding headerBinding; - private int swipeClose = 0; - public NavigationController(AppCompatActivity activity, - ActivityMainBinding binding, - ActivityResultLauncher themeActivityLauncher, - AppUpdateController appUpdateController, - BackHandler backHandler) { + public NavigationController( + AppCompatActivity activity, + ActivityMainBinding binding, + ActivityResultLauncher themeActivityLauncher, + AppUpdateController appUpdateController, + BackHandler backHandler) { this.activity = activity; this.binding = binding; this.themeActivityLauncher = themeActivityLauncher; @@ -67,29 +65,32 @@ public NavHeaderMainBinding getHeaderBinding() { return headerBinding; } - - /** - * Edge-to-Edge for Drawer/Root - */ + /** Edge-to-Edge for Drawer/Root */ private void setupEdgeToEdge() { - ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), (v, insets) -> { - ViewCompat.setOnApplyWindowInsetsListener(binding.activityMain, (mainView, mainInsets) -> { - Insets systemBars = mainInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - mainView.setPadding(0, systemBars.top, 0, systemBars.bottom); - return WindowInsetsCompat.CONSUMED; - }); - return insets; - }); + ViewCompat.setOnApplyWindowInsetsListener( + binding.getRoot(), + (v, insets) -> { + ViewCompat.setOnApplyWindowInsetsListener( + binding.activityMain, + (mainView, mainInsets) -> { + Insets systemBars = + mainInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + mainView.setPadding(0, systemBars.top, 0, systemBars.bottom); + return WindowInsetsCompat.CONSUMED; + }); + return insets; + }); } private void setupDrawerButton() { - binding.actionSearch.setNavigationOnClickListener(v -> { - if (drawerLayout.isDrawerOpen(GravityCompat.START)) { - drawerLayout.closeDrawer(GravityCompat.START); - } else { - drawerLayout.openDrawer(GravityCompat.START); - } - }); + binding.actionSearch.setNavigationOnClickListener( + v -> { + if (drawerLayout.isDrawerOpen(GravityCompat.START)) { + drawerLayout.closeDrawer(GravityCompat.START); + } else { + drawerLayout.openDrawer(GravityCompat.START); + } + }); } private void setupNavigationMenu() { @@ -104,37 +105,77 @@ private boolean onNavigationItemSelected(@NonNull MenuItem item) { private void setupHeaderButtons() { View header = binding.navigationView.getHeaderView(0); - header.findViewById(R.id.nav_tasks).setOnClickListener(v -> - delay(() -> activity.startActivity(new Intent(activity, TasksActivity.class))) - ); - - header.findViewById(R.id.nav_tags).setOnClickListener(v -> - delay(() -> activity.startActivity(new Intent(activity, TagsActivity.class))) - ); - - header.findViewById(R.id.nav_trash).setOnClickListener(v -> - delay(() -> activity.startActivity(new Intent(activity, TrashActivity.class))) - ); - - header.findViewById(R.id.nav_settings).setOnClickListener(v -> - delay(() -> themeActivityLauncher.launch(new Intent(activity, SettingsActivity.class))) - ); - - header.findViewById(R.id.nav_backups).setOnClickListener(v -> - delay(() -> themeActivityLauncher.launch(new Intent(activity, BackupActivity.class))) - ); - - header.findViewById(R.id.nav_about).setOnClickListener(v -> - delay(() -> activity.startActivity(new Intent(activity, AboutActivity.class))) - ); - - header.findViewById(R.id.nav_support).setOnClickListener(v -> - delay(() -> activity.startActivity(new Intent(activity, SupportActivity.class))) - ); - - header.findViewById(R.id.drawerStatsCard).setOnClickListener(v -> - delay(() -> activity.startActivity(new Intent(activity, SupportActivity.class))) - ); + header.findViewById(R.id.nav_tasks) + .setOnClickListener( + v -> + delay( + () -> + activity.startActivity( + new Intent( + activity, TasksActivity.class)))); + + header.findViewById(R.id.nav_tags) + .setOnClickListener( + v -> + delay( + () -> + activity.startActivity( + new Intent(activity, TagsActivity.class)))); + + header.findViewById(R.id.nav_trash) + .setOnClickListener( + v -> + delay( + () -> + activity.startActivity( + new Intent( + activity, TrashActivity.class)))); + + header.findViewById(R.id.nav_settings) + .setOnClickListener( + v -> + delay( + () -> + themeActivityLauncher.launch( + new Intent( + activity, + SettingsActivity.class)))); + + header.findViewById(R.id.nav_backups) + .setOnClickListener( + v -> + delay( + () -> + themeActivityLauncher.launch( + new Intent( + activity, BackupActivity.class)))); + + header.findViewById(R.id.nav_about) + .setOnClickListener( + v -> + delay( + () -> + activity.startActivity( + new Intent( + activity, AboutActivity.class)))); + + header.findViewById(R.id.nav_support) + .setOnClickListener( + v -> + delay( + () -> + activity.startActivity( + new Intent( + activity, SupportActivity.class)))); + + header.findViewById(R.id.drawerStatsCard) + .setOnClickListener( + v -> + delay( + () -> + activity.startActivity( + new Intent( + activity, SupportActivity.class)))); bindHeaderNewVersion(header); } @@ -146,9 +187,7 @@ private void bindHeaderNewVersion(View header) { newVersionView.setVisibility(hasNew ? View.VISIBLE : View.GONE); if (hasNew) { - newVersionView.setOnClickListener(v -> - appUpdateController.openChangelog() - ); + newVersionView.setOnClickListener(v -> appUpdateController.openChangelog()); } } @@ -173,31 +212,32 @@ private void delay(Runnable r) { } private void handleBackPressed() { - activity.getOnBackPressedDispatcher().addCallback(activity, new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - // Спочатку — drawer - if (drawerLayout.isDrawerOpen(GravityCompat.START)) { - drawerLayout.closeDrawer(GravityCompat.START); - return; - } - - // Далі — делегуємо в Activity (finishActivity) - if (backHandler != null) { - boolean shouldFinish = backHandler.onRootBack(); - if (shouldFinish) { - activity.finish(); - } - } else { - activity.finish(); - } - } - }); + activity.getOnBackPressedDispatcher() + .addCallback( + activity, + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + // Спочатку — drawer + if (drawerLayout.isDrawerOpen(GravityCompat.START)) { + drawerLayout.closeDrawer(GravityCompat.START); + return; + } + + // Далі — делегуємо в Activity (finishActivity) + if (backHandler != null) { + boolean shouldFinish = backHandler.onRootBack(); + if (shouldFinish) { + activity.finish(); + } + } else { + activity.finish(); + } + } + }); } - /** - * Processing App Shortcuts (open_search) - */ + /** Processing App Shortcuts (open_search) */ public void handleShortcuts(Intent intent) { if (intent != null && intent.getBooleanExtra("open_search", false)) { new Handler(Looper.getMainLooper()).postDelayed(binding.searchView::show, 100); @@ -224,7 +264,6 @@ public void destroy() { } } - binding.actionSearch.setNavigationOnClickListener(null); ViewCompat.setOnApplyWindowInsetsListener(binding.getRoot(), null); ViewCompat.setOnApplyWindowInsetsListener(binding.activityMain, null); @@ -241,10 +280,10 @@ public int getSwipeClose() { } /** - * true – if the activity needs to be closed, false – if only back has been processed (like closing the panel/snack) + * true – if the activity needs to be closed, false – if only back has been processed (like + * closing the panel/snack) */ public interface BackHandler { boolean onRootBack(); } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/RedrawThemeController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/RedrawThemeController.java index 9bb8e10..977afa7 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/RedrawThemeController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/RedrawThemeController.java @@ -6,10 +6,8 @@ import android.view.View; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.material.card.MaterialCardView; import com.google.android.material.color.MaterialColors; import com.google.android.material.materialswitch.MaterialSwitch; @@ -20,48 +18,60 @@ public class RedrawThemeController { // Card backgrounds public static void styleCard(MaterialCardView card, Context ctx) { - int bg = MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorSurfaceContainer, Color.GRAY); + int bg = + MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorSurfaceContainer, Color.GRAY); card.setCardBackgroundColor(bg); } public static void styleCardSecondary(MaterialCardView card, Context ctx) { - int bg = MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorSecondaryContainer, Color.GRAY); + int bg = + MaterialColors.getColor( + ctx, + com.google.android.material.R.attr.colorSecondaryContainer, + Color.GRAY); card.setCardBackgroundColor(bg); } // Text colors public static void styleText(TextView tv, Context ctx) { - tv.setTextColor(MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorOnSurface, Color.GRAY)); + tv.setTextColor( + MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorOnSurface, Color.GRAY)); } public static void styleTextVariant(TextView tv, Context ctx) { - tv.setTextColor(MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorOnSurfaceVariant, Color.GRAY)); + tv.setTextColor( + MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorOnSurfaceVariant, Color.GRAY)); } // Switch styling public static void styleSwitch(MaterialSwitch sw, Context ctx) { - int active = MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorPrimaryFixed, Color.GRAY); - int inactive = MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorOnSurfaceVariant, Color.GRAY); + int active = + MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorPrimaryFixed, Color.GRAY); + int inactive = + MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorOnSurfaceVariant, Color.GRAY); updateSwitchColors(sw, active, inactive); } // Slider styling public static void styleSlider(@NonNull Slider slider, @NonNull Context ctx) { - int active = MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorPrimaryVariant, Color.GRAY); - - int inactive = MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorSurfaceVariant, - MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorOutlineVariant, - Color.GRAY)); + int active = + MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorPrimaryVariant, Color.GRAY); + + int inactive = + MaterialColors.getColor( + ctx, + com.google.android.material.R.attr.colorSurfaceVariant, + MaterialColors.getColor( + ctx, + com.google.android.material.R.attr.colorOutlineVariant, + Color.GRAY)); slider.setThumbTintList(ColorStateList.valueOf(active)); slider.setHaloTintList(ColorStateList.valueOf(active)); @@ -75,8 +85,7 @@ public static void styleCardBlock( @Nullable TextView title, @Nullable TextView description, @Nullable MaterialSwitch sw, - Context ctx - ) { + Context ctx) { styleCard(card, ctx); if (title != null) styleText(title, ctx); if (description != null) styleTextVariant(description, ctx); @@ -103,14 +112,17 @@ public static void tint(@NonNull View view, @NonNull Context ctx, int colorAttr) // TabLayout styling public static void styleTabs(@NonNull TabLayout tabs, @NonNull Context ctx) { - int primary = MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorPrimaryVariant, Color.GRAY); + int primary = + MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorPrimaryVariant, Color.GRAY); - int textInactive = MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorOnSurfaceVariant, Color.GRAY); + int textInactive = + MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorOnSurfaceVariant, Color.GRAY); - int container = MaterialColors.getColor(ctx, - com.google.android.material.R.attr.colorSurface, Color.GRAY); + int container = + MaterialColors.getColor( + ctx, com.google.android.material.R.attr.colorSurface, Color.GRAY); tabs.setTabTextColors(textInactive, primary); tabs.setSelectedTabIndicatorColor(primary); @@ -120,23 +132,15 @@ public static void styleTabs(@NonNull TabLayout tabs, @NonNull Context ctx) { // Internal switch coloring private static void updateSwitchColors(MaterialSwitch sw, int primary, int surfaceVariant) { int[][] thumbStates = { - new int[]{android.R.attr.state_checked}, - new int[]{-android.R.attr.state_checked} - }; - int[] thumbColors = { - primary, - Color.WHITE + new int[] {android.R.attr.state_checked}, new int[] {-android.R.attr.state_checked} }; + int[] thumbColors = {primary, Color.WHITE}; sw.setThumbTintList(new ColorStateList(thumbStates, thumbColors)); int[][] trackStates = { - new int[]{android.R.attr.state_checked}, - new int[]{-android.R.attr.state_checked} - }; - int[] trackColors = { - adjustAlpha(primary, 0.5f), - adjustAlpha(surfaceVariant, 0.3f) + new int[] {android.R.attr.state_checked}, new int[] {-android.R.attr.state_checked} }; + int[] trackColors = {adjustAlpha(primary, 0.5f), adjustAlpha(surfaceVariant, 0.3f)}; sw.setTrackTintList(new ColorStateList(trackStates, trackColors)); } @@ -145,4 +149,3 @@ private static int adjustAlpha(int color, float alpha) { return Color.argb(a, Color.red(color), Color.green(color), Color.blue(color)); } } - diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/SearchController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/SearchController.java index 61a99bc..70ae9e5 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/SearchController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/SearchController.java @@ -7,10 +7,8 @@ import android.util.Log; import android.view.View; import android.view.ViewGroup; - import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.recyclerview.widget.LinearLayoutManager; - import com.google.android.material.chip.Chip; import com.google.android.material.search.SearchView; import com.pasich.mynotes.R; @@ -19,7 +17,6 @@ import com.pasich.mynotes.databinding.ActivityMainBinding; import com.pasich.mynotes.utils.adapters.searchAdapter.SearchNotesAdapter; import com.pasich.mynotes.utils.recycler.SpacesItemDecoration; - import java.util.List; public class SearchController { @@ -32,9 +29,8 @@ public class SearchController { private final Listener listener; private Runnable searchRunnable; - public SearchController(ActivityMainBinding binding, - SearchNotesAdapter searchAdapter, - Listener listener) { + public SearchController( + ActivityMainBinding binding, SearchNotesAdapter searchAdapter, Listener listener) { this.binding = binding; this.searchAdapter = searchAdapter; this.listener = listener; @@ -50,8 +46,7 @@ private void setup() { private void setupSearchList() { binding.resultsSearchList.setLayoutManager( - new LinearLayoutManager(binding.getRoot().getContext()) - ); + new LinearLayoutManager(binding.getRoot().getContext())); binding.resultsSearchList.addItemDecoration(new SpacesItemDecoration(15, 10)); binding.resultsSearchList.setAdapter(searchAdapter); @@ -59,40 +54,39 @@ private void setupSearchList() { } private void setupSearchBehavior() { - binding.searchView.addTransitionListener((searchView, oldState, newState) -> { - - if (newState == SearchView.TransitionState.SHOWING) { - listener.onSearchOpen(); - ensureFullScreen(); - } - - if (newState == SearchView.TransitionState.HIDDEN) { - listener.onSearchClose(); - clearSearch(); - } - }); - - binding.actionSearch.setOnClickListener(v -> - binding.searchView.show() - ); + binding.searchView.addTransitionListener( + (searchView, oldState, newState) -> { + if (newState == SearchView.TransitionState.SHOWING) { + listener.onSearchOpen(); + ensureFullScreen(); + } + + if (newState == SearchView.TransitionState.HIDDEN) { + listener.onSearchClose(); + clearSearch(); + } + }); + + binding.actionSearch.setOnClickListener(v -> binding.searchView.show()); } private void setupSearchInput() { - binding.searchView.getEditText().addTextChangedListener(new TextWatcher() { - @Override - protected void changeText(Editable s) { + binding.searchView + .getEditText() + .addTextChangedListener( + new TextWatcher() { + @Override + protected void changeText(Editable s) { - if (searchRunnable != null) - handler.removeCallbacks(searchRunnable); + if (searchRunnable != null) handler.removeCallbacks(searchRunnable); - String query = s.toString(); + String query = s.toString(); - searchRunnable = () -> - listener.onSearchQuery(query); + searchRunnable = () -> listener.onSearchQuery(query); - handler.postDelayed(searchRunnable, DEBOUNCE_DELAY); - } - }); + handler.postDelayed(searchRunnable, DEBOUNCE_DELAY); + } + }); } private void ensureFullScreen() { @@ -140,14 +134,15 @@ public void setAvailableTags(List tags) { binding.searchTagChips.addView(chip); } - binding.searchTagChips.setOnCheckedStateChangeListener((group, checkedIds) -> { - if (checkedIds.isEmpty()) return; - View checked = group.findViewById(checkedIds.get(0)); - if (checked != null) { - Object tag = checked.getTag(); - listener.onTagFilterChanged(tag instanceof String ? (String) tag : null); - } - }); + binding.searchTagChips.setOnCheckedStateChangeListener( + (group, checkedIds) -> { + if (checkedIds.isEmpty()) return; + View checked = group.findViewById(checkedIds.get(0)); + if (checked != null) { + Object tag = checked.getTag(); + listener.onTagFilterChanged(tag instanceof String ? (String) tag : null); + } + }); binding.searchTagsScroll.setVisibility(View.VISIBLE); } @@ -164,8 +159,7 @@ public void clearSearch() { } public void destroy() { - if (searchRunnable != null) - handler.removeCallbacks(searchRunnable); + if (searchRunnable != null) handler.removeCallbacks(searchRunnable); searchRunnable = null; } diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/BackupPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/BackupPresenter.java index 1f6a74f..7a8f7b3 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/BackupPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/BackupPresenter.java @@ -2,7 +2,6 @@ import android.net.Uri; import android.util.Log; - import com.pasich.mynotes.R; import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.data.DataManager; @@ -15,13 +14,6 @@ import com.pasich.mynotes.utils.backup.otherApp.GoogleKeepImportService; import com.pasich.mynotes.utils.constants.CloudErrors; import com.pasich.mynotes.utils.rx.SchedulerProvider; - -import java.util.AbstractMap; -import java.util.ArrayList; -import java.util.List; - -import javax.inject.Inject; - import dagger.hilt.android.scopes.ActivityScoped; import io.reactivex.Completable; import io.reactivex.Flowable; @@ -30,17 +22,23 @@ import io.reactivex.disposables.CompositeDisposable; import io.reactivex.observers.DisposableSingleObserver; import io.reactivex.schedulers.Schedulers; +import java.util.AbstractMap; +import java.util.ArrayList; +import java.util.List; +import javax.inject.Inject; @ActivityScoped -public class BackupPresenter extends BasePresenter implements BackupContract.presenter { +public class BackupPresenter extends BasePresenter + implements BackupContract.presenter { - @Inject - public BackupCacheHelper serviceCache; - @Inject - GoogleKeepImportService importService; + @Inject public BackupCacheHelper serviceCache; + @Inject GoogleKeepImportService importService; @Inject - public BackupPresenter(SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable, DataManager dataManager) { + public BackupPresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager) { super(schedulerProvider, compositeDisposable, dataManager); } @@ -62,67 +60,87 @@ public void detachView() { */ @Override public void saveBackupPresenter(boolean local) { - getCompositeDisposable().add(Single.fromCallable(JsonBackup::new) - .flatMap(jsonBackupTemp -> Flowable.zip( - getDataManager().getNotes(), getDataManager().getNotesInTrash(), - getDataManager().getTagsUser(), (noteList, trashNoteList, tagList) -> { - jsonBackupTemp.setNotes(noteList); - jsonBackupTemp.setNewTrashNotes(trashNoteList); - jsonBackupTemp.setTags(tagList); - return noteList.size() + trashNoteList.size() + tagList.size(); - }).firstOrError().map(countData -> { - if (countData != 0) { - jsonBackupTemp.setPreferences(getDataManager().getListPreferences()); - } - return new AbstractMap.SimpleEntry<>(jsonBackupTemp, countData); - })).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(result -> { - JsonBackup jsonBackup = result.getKey(); - Integer countData = result.getValue(); - - if (countData != 0) { - serviceCache.setJsonBackup(jsonBackup); - getView().openIntentSaveBackup(jsonBackup); - } else { - getView().emptyDataToBackup(); - } - }, throwable -> Log.e("RxError", "Error: ", throwable))); + getCompositeDisposable() + .add( + Single.fromCallable(JsonBackup::new) + .flatMap( + jsonBackupTemp -> + Flowable.zip( + getDataManager().getNotes(), + getDataManager().getNotesInTrash(), + getDataManager().getTagsUser(), + (noteList, + trashNoteList, + tagList) -> { + jsonBackupTemp.setNotes( + noteList); + jsonBackupTemp.setNewTrashNotes( + trashNoteList); + jsonBackupTemp.setTags(tagList); + return noteList.size() + + trashNoteList.size() + + tagList.size(); + }) + .firstOrError() + .map( + countData -> { + if (countData != 0) { + jsonBackupTemp + .setPreferences( + getDataManager() + .getListPreferences()); + } + return new AbstractMap + .SimpleEntry<>( + jsonBackupTemp, + countData); + })) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + result -> { + JsonBackup jsonBackup = result.getKey(); + Integer countData = result.getValue(); + + if (countData != 0) { + serviceCache.setJsonBackup(jsonBackup); + getView().openIntentSaveBackup(jsonBackup); + } else { + getView().emptyDataToBackup(); + } + }, + throwable -> Log.e("RxError", "Error: ", throwable))); /* */ } - - /** - * Save local backup (3/3) - write appData to public file - */ + /** Save local backup (3/3) - write appData to public file */ @Override public void writeFileBackupLocal(Uri mUri) { getView().createLocalCopyFinish(getDataManager().writeBackupLocalFile(serviceCache, mUri)); } - - /** - * Restore local backup (3/3) - load public file and write data - */ + /** Restore local backup (3/3) - load public file and write data */ @Override public void readFileBackupLocal(Uri mUri) { getView().showProcessRestoreDialog(); - getCompositeDisposable().add( - Completable.timer(3, java.util.concurrent.TimeUnit.SECONDS) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> { - final JsonBackup jsonBackup = getDataManager().readBackupLocalFile(mUri); - if (jsonBackup.isError()) { - getView().restoreFinish(CloudErrors.BACKUP_DESTROY); - } else { - restoreData(jsonBackup); - } - }) - ); - + getCompositeDisposable() + .add( + Completable.timer(3, java.util.concurrent.TimeUnit.SECONDS) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> { + final JsonBackup jsonBackup = + getDataManager().readBackupLocalFile(mUri); + if (jsonBackup.isError()) { + getView().restoreFinish(CloudErrors.BACKUP_DESTROY); + } else { + restoreData(jsonBackup); + } + })); } - /** * Restore date request rxJava * @@ -138,27 +156,43 @@ private void restoreData(JsonBackup jsonBackup) { } } - getCompositeDisposable().add(Completable.fromAction(() -> - getDataManager() - .setListPreferences(jsonBackup.getPreferences())) - .subscribeOn(getSchedulerProvider().io()) - .andThen(Completable.mergeArray( - getDataManager().addNotes(jsonBackup.getNotes()),// main notes - getDataManager().addTags(jsonBackup.getTags()), // tags - getDataManager().addNotes(jsonBackup.getTrashNotesUnified()) // trash notes - )) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> { - getView().onRestoreSuccessFlag(); - getView().restoreFinish(CloudErrors.OKAY_RESTORE); - }, throwable -> { - Log.e("BackupRestore", "Error restore: " + throwable.getMessage(), throwable); - getView().restoreFinish(CloudErrors.BACKUP_DESTROY); - })); + getCompositeDisposable() + .add( + Completable.fromAction( + () -> + getDataManager() + .setListPreferences( + jsonBackup.getPreferences())) + .subscribeOn(getSchedulerProvider().io()) + .andThen( + Completable.mergeArray( + getDataManager() + .addNotes( + jsonBackup + .getNotes()), // main notes + getDataManager() + .addTags(jsonBackup.getTags()), // tags + getDataManager() + .addNotes( + jsonBackup + .getTrashNotesUnified()) // trash notes + )) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> { + getView().onRestoreSuccessFlag(); + getView().restoreFinish(CloudErrors.OKAY_RESTORE); + }, + throwable -> { + Log.e( + "BackupRestore", + "Error restore: " + throwable.getMessage(), + throwable); + getView().restoreFinish(CloudErrors.BACKUP_DESTROY); + })); } - /** * Restore data algorithm and navigator * @@ -166,133 +200,180 @@ private void restoreData(JsonBackup jsonBackup) { */ @Override public void restoreBackupPresenter(boolean local) { - getDataManager().getCountData().subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(new DisposableSingleObserver<>() { - @Override - public void onSuccess(Integer countData) { - if (countData == 0) { - if (local) { - getView().openIntentReadBackup(); - } else { - return; - } - } else { - getView().dialogRestoreData(local); - } - - dispose(); - } + getDataManager() + .getCountData() + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + new DisposableSingleObserver<>() { + @Override + public void onSuccess(Integer countData) { + if (countData == 0) { + if (local) { + getView().openIntentReadBackup(); + } else { + return; + } + } else { + getView().dialogRestoreData(local); + } + + dispose(); + } - @Override - public void onError(Throwable e) { - Log.e("RxError", "Error: ", e); - } - }); + @Override + public void onError(Throwable e) { + Log.e("RxError", "Error: ", e); + } + }); } - - /** - * Export all notes data - */ + /** Export all notes data */ @Override public void exportAllNotesPresenter() { - getCompositeDisposable().add(getDataManager().getNotes().subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(notes -> { - if (notes != null && !notes.isEmpty()) { - // Sort notes by creation date - newest first - notes.sort((note1, note2) -> Long.compare(note2.getDate(), note1.getDate())); - getView().openShareOptionsDialog(notes, true); - } else { - getView().emptyDataToBackup(); - } - }, throwable -> { - Log.e("BackupPresenter", "Error loading notes for export", throwable); - getView().restoreFinish(CloudErrors.BACKUP_DESTROY); - })); + getCompositeDisposable() + .add( + getDataManager() + .getNotes() + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + notes -> { + if (notes != null && !notes.isEmpty()) { + // Sort notes by creation date - newest first + notes.sort( + (note1, note2) -> + Long.compare( + note2.getDate(), + note1.getDate())); + getView().openShareOptionsDialog(notes, true); + } else { + getView().emptyDataToBackup(); + } + }, + throwable -> { + Log.e( + "BackupPresenter", + "Error loading notes for export", + throwable); + getView().restoreFinish(CloudErrors.BACKUP_DESTROY); + })); } @Override public void importFromZipOtherApp(Uri fileUri) { - getCompositeDisposable().add(Single.fromCallable(() -> importService.importFromZip(fileUri)).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(result -> { - // Після імпорту повідомляємо View про результат - if (getView() != null) { - getView().showImportResultOtherApp(result); - if (!result.hasError()) { - getView().setupImportCallbackOtherApp(result); - } - } - }, throwable -> { - if (getView() != null) { - getView().showImportResultOtherApp(GoogleKeepImportResult.error(throwable.getMessage())); - } - })); + getCompositeDisposable() + .add( + Single.fromCallable(() -> importService.importFromZip(fileUri)) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + result -> { + // Після імпорту повідомляємо View про результат + if (getView() != null) { + getView().showImportResultOtherApp(result); + if (!result.hasError()) { + getView().setupImportCallbackOtherApp(result); + } + } + }, + throwable -> { + if (getView() != null) { + getView() + .showImportResultOtherApp( + GoogleKeepImportResult.error( + throwable.getMessage())); + } + })); } @Override public void importDataOtherApp(GoogleKeepImportResult result) { - getCompositeDisposable().add(Flowable.zip( - getDataManager().getNotes().firstOrError().toFlowable(), - getDataManager().getNotesInTrash().firstOrError().toFlowable(), - getDataManager().getTagsUser().firstOrError().toFlowable(), - (existingNotes, existingTrashNotes, existingTags) -> { - - // Фільтруємо нотатки по вмісту - List newNotes = new ArrayList<>(); - for (Note note : result.toAppNotes()) { - boolean duplicate = false; - for (Note existing : existingNotes) { - if (note.getTitle().equals(existing.getTitle()) && note.getValue().equals(existing.getValue()) && note.getDate() == existing.getDate()) { - duplicate = true; - break; - } - } - if (!duplicate) newNotes.add(note); - } - - // Фільтруємо видалені нотатки по вмісту - List newTrashNotes = new ArrayList<>(); - for (Note trash : result.toAppTrashedNotes()) { - boolean duplicate = false; - for (Note existing : existingTrashNotes) { - if (trash.getTitle().equals(existing.getTitle()) && trash.getValue().equals(existing.getValue()) && trash.getDate() == existing.getDate()) { - duplicate = true; - break; - } - } - if (!duplicate) newTrashNotes.add(trash); - } - - // Фільтруємо теги по назві - List newTags = new ArrayList<>(); - for (Tag tag : result.toAppTags()) { - boolean duplicate = false; - for (Tag existing : existingTags) { - if (tag.getNameTag().equals(existing.getNameTag())) { - duplicate = true; - break; - } - } - if (!duplicate) newTags.add(tag); - } - - // Якщо нічого немає для імпорту — викидаємо помилку - if (newNotes.isEmpty() && newTrashNotes.isEmpty() && newTags.isEmpty()) { - throw new Exception("No new data to import"); - } - - JsonBackup jsonBackup = new JsonBackup(); - jsonBackup.setNotes(newNotes); - jsonBackup.setNewTrashNotes(newTrashNotes); - jsonBackup.setTags(newTags); - - return jsonBackup; - }).subscribeOn(getSchedulerProvider().computation()).observeOn(AndroidSchedulers.mainThread()).subscribe(this::restoreData, throwable -> getView().showErrorsText(0, R.string.empty_data_import))); - - + getCompositeDisposable() + .add( + Flowable.zip( + getDataManager().getNotes().firstOrError().toFlowable(), + getDataManager() + .getNotesInTrash() + .firstOrError() + .toFlowable(), + getDataManager().getTagsUser().firstOrError().toFlowable(), + (existingNotes, existingTrashNotes, existingTags) -> { + + // Фільтруємо нотатки по вмісту + List newNotes = new ArrayList<>(); + for (Note note : result.toAppNotes()) { + boolean duplicate = false; + for (Note existing : existingNotes) { + if (note.getTitle().equals(existing.getTitle()) + && note.getValue() + .equals(existing.getValue()) + && note.getDate() + == existing.getDate()) { + duplicate = true; + break; + } + } + if (!duplicate) newNotes.add(note); + } + + // Фільтруємо видалені нотатки по вмісту + List newTrashNotes = new ArrayList<>(); + for (Note trash : result.toAppTrashedNotes()) { + boolean duplicate = false; + for (Note existing : existingTrashNotes) { + if (trash.getTitle().equals(existing.getTitle()) + && trash.getValue() + .equals(existing.getValue()) + && trash.getDate() + == existing.getDate()) { + duplicate = true; + break; + } + } + if (!duplicate) newTrashNotes.add(trash); + } + + // Фільтруємо теги по назві + List newTags = new ArrayList<>(); + for (Tag tag : result.toAppTags()) { + boolean duplicate = false; + for (Tag existing : existingTags) { + if (tag.getNameTag() + .equals(existing.getNameTag())) { + duplicate = true; + break; + } + } + if (!duplicate) newTags.add(tag); + } + + // Якщо нічого немає для імпорту — викидаємо помилку + if (newNotes.isEmpty() + && newTrashNotes.isEmpty() + && newTags.isEmpty()) { + throw new Exception("No new data to import"); + } + + JsonBackup jsonBackup = new JsonBackup(); + jsonBackup.setNotes(newNotes); + jsonBackup.setNewTrashNotes(newTrashNotes); + jsonBackup.setTags(newTags); + + return jsonBackup; + }) + .subscribeOn(getSchedulerProvider().computation()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe( + this::restoreData, + throwable -> + getView() + .showErrorsText( + 0, R.string.empty_data_import))); } - @Override public void startProcessSelectedFileOtherApp(Uri fileUri) { getView().processSelectedFileOtherApp(fileUri); } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/MainPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/MainPresenter.java index 77289f9..a6c2eb9 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/MainPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/MainPresenter.java @@ -3,7 +3,6 @@ import android.os.Handler; import android.os.Looper; import android.util.Log; - import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Note; @@ -16,7 +15,11 @@ import com.pasich.mynotes.utils.constants.settings.SortParam; import com.pasich.mynotes.utils.managers.SystemTagsManager; import com.pasich.mynotes.utils.rx.SchedulerProvider; - +import dagger.hilt.android.scopes.ActivityScoped; +import io.reactivex.Flowable; +import io.reactivex.Observable; +import io.reactivex.disposables.CompositeDisposable; +import io.reactivex.subjects.BehaviorSubject; import java.util.ArrayList; import java.util.Collections; import java.util.HashSet; @@ -24,17 +27,11 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; - import javax.inject.Inject; -import dagger.hilt.android.scopes.ActivityScoped; -import io.reactivex.Flowable; -import io.reactivex.Observable; -import io.reactivex.disposables.CompositeDisposable; -import io.reactivex.subjects.BehaviorSubject; - @ActivityScoped -public class MainPresenter extends BasePresenter implements MainContract.presenter { +public class MainPresenter extends BasePresenter + implements MainContract.presenter { private static final String TAG = "MainPresenter"; private final Handler uiHandler = new Handler(Looper.getMainLooper()); @@ -42,19 +39,19 @@ public class MainPresenter extends BasePresenter implements M BehaviorSubject.createDefault(SystemTagsManager.createAllNotesTag()); private final BehaviorSubject sortParam = BehaviorSubject.create(); private final BehaviorSubject viewState = BehaviorSubject.create(); - private final BehaviorSubject searchQuery = - BehaviorSubject.createDefault(""); - private final BehaviorSubject searchTagFilter = - BehaviorSubject.createDefault(""); + private final BehaviorSubject searchQuery = BehaviorSubject.createDefault(""); + private final BehaviorSubject searchTagFilter = BehaviorSubject.createDefault(""); private UiEvent lastUiEvent = UiEvent.NONE; private Note backupDeleteNote; private Observable> tagsStream; private Observable> notesStream; - @Inject - public MainPresenter(SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable, DataManager dataManager) { + public MainPresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager) { super(schedulerProvider, compositeDisposable, dataManager); } @@ -71,71 +68,80 @@ public void viewIsReady() { } void startStatsStream() { - getCompositeDisposable().add( - Flowable.combineLatest( - getDataManager().getNotesCount(), - getDataManager().getNotesCreatedLastMonth(), - getDataManager().getTotalCharacters(), - StatsData::new - ) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - stats -> getView().renderDrawerStats(stats), - error -> Log.e(TAG, "drawer stats stream error", error) - ) - ); + getCompositeDisposable() + .add( + Flowable.combineLatest( + getDataManager().getNotesCount(), + getDataManager().getNotesCreatedLastMonth(), + getDataManager().getTotalCharacters(), + StatsData::new) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + stats -> getView().renderDrawerStats(stats), + error -> Log.e(TAG, "drawer stats stream error", error))); } - void starListsRenderStream() { - getCompositeDisposable().add(viewState.hide() - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - state -> getView().render(state), - throwable -> Log.e(TAG, "observeState", throwable) - ) - ); + getCompositeDisposable() + .add( + viewState + .hide() + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + state -> getView().render(state), + throwable -> Log.e(TAG, "observeState", throwable))); } private void initStreams() { - tagsStream = Observable.combineLatest( - getDataManager().getTags() - .toObservable() - .map(tagList -> TagsSorter.sortTags(tagList, getDataManager().getSortParamTags())), - selectedTag, - (tags, selected) -> - tags.stream() - .map(t -> { - Tag copy = t.copy(); - copy.setSelected(selected != null && selected.getId() == t.getId()); - return copy; - }) - .collect(Collectors.toList()) - ); - - notesStream = getDataManager().getNotes() - .toObservable(); + tagsStream = + Observable.combineLatest( + getDataManager() + .getTags() + .toObservable() + .map( + tagList -> + TagsSorter.sortTags( + tagList, + getDataManager().getSortParamTags())), + selectedTag, + (tags, selected) -> + tags.stream() + .map( + t -> { + Tag copy = t.copy(); + copy.setSelected( + selected != null + && selected.getId() + == t.getId()); + return copy; + }) + .collect(Collectors.toList())); + + notesStream = getDataManager().getNotes().toObservable(); } private void startStateCombiner() { - getCompositeDisposable().add( - Observable.combineLatest(tagsStream, notesStream, selectedTag, sortParam, this::buildState) - .debounce(5, TimeUnit.MILLISECONDS) - .distinctUntilChanged() - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(state -> getView().render(state), throwable -> Log.e(TAG, "combineLatest", throwable)) - ); + getCompositeDisposable() + .add( + Observable.combineLatest( + tagsStream, + notesStream, + selectedTag, + sortParam, + this::buildState) + .debounce(5, TimeUnit.MILLISECONDS) + .distinctUntilChanged() + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + state -> getView().render(state), + throwable -> Log.e(TAG, "combineLatest", throwable))); } private MainViewState buildState( - List tags, - List notes, - Tag selectedTag, - String sort - ) { + List tags, List notes, Tag selectedTag, String sort) { // Build hidden tags set Set hidden = new HashSet<>(); @@ -158,7 +164,10 @@ private MainViewState buildState( } // Determine if we show ALL notes - boolean isAllNotes = selectedTag == null || selectedTag.getSystemAction() == SystemTagsManager.SYSTEM_ACTION_ALL_NOTES; + boolean isAllNotes = + selectedTag == null + || selectedTag.getSystemAction() + == SystemTagsManager.SYSTEM_ACTION_ALL_NOTES; // Filter by selected tag List filtered; @@ -179,59 +188,58 @@ private MainViewState buildState( // Sort — pinned always first, then by date boolean sortByNew = SortParam.DataSort.equals(sort); - sorted.sort((a, b) -> { - if (a.isPinned() != b.isPinned()) return a.isPinned() ? -1 : 1; - return sortByNew - ? Long.compare(b.getDate(), a.getDate()) - : Long.compare(a.getDate(), b.getDate()); - }); - + sorted.sort( + (a, b) -> { + if (a.isPinned() != b.isPinned()) return a.isPinned() ? -1 : 1; + return sortByNew + ? Long.compare(b.getDate(), a.getDate()) + : Long.compare(a.getDate(), b.getDate()); + }); return new MainViewState(tags, sorted, selectedTag, lastUiEvent); } private void startSearchStream() { - getCompositeDisposable().add( - Observable.combineLatest( - notesStream, - searchQuery, - searchTagFilter, - this::filterNotes - ) - .debounce(150, TimeUnit.MILLISECONDS) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - filtered -> getView().renderSearch(filtered), - throwable -> Log.e(TAG, "search error", throwable) - ) - ); + getCompositeDisposable() + .add( + Observable.combineLatest( + notesStream, + searchQuery, + searchTagFilter, + this::filterNotes) + .debounce(150, TimeUnit.MILLISECONDS) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + filtered -> getView().renderSearch(filtered), + throwable -> Log.e(TAG, "search error", throwable))); } private List filterNotes(List notes, String query, String tagFilter) { - if (query == null || query.trim().length() < 2) - return Collections.emptyList(); + if (query == null || query.trim().length() < 2) return Collections.emptyList(); String q = query.toLowerCase().trim(); boolean hasTagFilter = tagFilter != null && !tagFilter.isEmpty(); return notes.stream() - .filter(n -> - (n.getTitle().toLowerCase().contains(q) || - n.getValue().toLowerCase().contains(q)) - && (!hasTagFilter || tagFilter.equals(n.getTag())) - ).sorted((n1, n2) -> { - boolean n1Exact = n1.getTitle().equalsIgnoreCase(query); - boolean n2Exact = n2.getTitle().equalsIgnoreCase(query); - - if (n1Exact && !n2Exact) return -1; - if (!n1Exact && n2Exact) return 1; - - return Long.compare(n2.getDate(), n1.getDate()); - }).collect(Collectors.toList()); + .filter( + n -> + (n.getTitle().toLowerCase().contains(q) + || n.getValue().toLowerCase().contains(q)) + && (!hasTagFilter || tagFilter.equals(n.getTag()))) + .sorted( + (n1, n2) -> { + boolean n1Exact = n1.getTitle().equalsIgnoreCase(query); + boolean n2Exact = n2.getTitle().equalsIgnoreCase(query); + + if (n1Exact && !n2Exact) return -1; + if (!n1Exact && n2Exact) return 1; + + return Long.compare(n2.getDate(), n1.getDate()); + }) + .collect(Collectors.toList()); } - public void updateSearchQuery(String query) { searchQuery.onNext(query); } @@ -255,48 +263,48 @@ public void onTagSelected(Tag tag) { @Override public void requestTagSelection(boolean multiple) { - getCompositeDisposable().add( - tagsStream - .take(1) - .map(tags -> { - List filtered = new ArrayList<>(); - for (Tag t : tags) { - if (!SystemTagsManager.isSystemTag(t)) { - filtered.add(t); - } - } - return filtered; - }) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - tags -> { - if (getView() == null) return; - - if (multiple) { - getView().multipleTagChangerDialog(tags); - } else { - getView().allTagSelectDialog(tags); - } - }, - err -> Log.e(TAG, "requestTagSelection()", err) - ) - ); + getCompositeDisposable() + .add( + tagsStream + .take(1) + .map( + tags -> { + List filtered = new ArrayList<>(); + for (Tag t : tags) { + if (!SystemTagsManager.isSystemTag(t)) { + filtered.add(t); + } + } + return filtered; + }) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + tags -> { + if (getView() == null) return; + + if (multiple) { + getView().multipleTagChangerDialog(tags); + } else { + getView().allTagSelectDialog(tags); + } + }, + err -> Log.e(TAG, "requestTagSelection()", err))); } @Override public void requestTagChangeMultipleNotes(String selectedTag, List notesIds) { - getCompositeDisposable().add( - getDataManager() - .setTagForNotes(selectedTag, notesIds) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - () -> { - }, throwable -> Log.e(TAG, "Error restoring notes", throwable)) - ); + getCompositeDisposable() + .add( + getDataManager() + .setTagForNotes(selectedTag, notesIds) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, + throwable -> + Log.e(TAG, "Error restoring notes", throwable))); } - @Override public void clearUiEvent() { lastUiEvent = UiEvent.NONE; @@ -316,83 +324,99 @@ public void newNotesClick() { } Note newNote = new Note().create("", "", System.currentTimeMillis(), tag); - getCompositeDisposable().add( - getDataManager().addNote(newNote, false) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - id -> { - lastUiEvent = UiEvent.NOTE_CREATED; - uiHandler.postDelayed(() -> { - if (isViewAttached()) getView().openNewNoteWithId(id); - }, 80); - }, - throwable -> Log.e(TAG, "Failed to create note", throwable) - ) - ); + getCompositeDisposable() + .add( + getDataManager() + .addNote(newNote, false) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + id -> { + lastUiEvent = UiEvent.NOTE_CREATED; + uiHandler.postDelayed( + () -> { + if (isViewAttached()) + getView().openNewNoteWithId(id); + }, + 80); + }, + throwable -> + Log.e(TAG, "Failed to create note", throwable))); } - @Override public void deleteNotesArray(ArrayList notes) { List ids = new ArrayList<>(); for (Note note : notes) ids.add(note.getId()); - getCompositeDisposable().add( - getDataManager() - .moveNotesToTrash(ids) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - () -> { - }, throwable -> Log.e(TAG, "Error restoring notes", throwable)) - ); + getCompositeDisposable() + .add( + getDataManager() + .moveNotesToTrash(ids) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, + throwable -> + Log.e(TAG, "Error restoring notes", throwable))); } - @Override public void noteMoveToTrash(Note note) { - getCompositeDisposable().add(getDataManager().moveNoteToTrash(note.getId()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()) - .subscribe(() -> { - }, // onComplete - throwable -> Log.e(TAG, "Error deleting note", throwable))); + getCompositeDisposable() + .add( + getDataManager() + .moveNoteToTrash(note.getId()) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, // onComplete + throwable -> Log.e(TAG, "Error deleting note", throwable))); } @Override public void restoreNoteLastMoveToTrash(Note nNote) { - getCompositeDisposable().add(getDataManager() - .transferNoteOutTrash(nNote.getId()) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> { - }, // onComplete - throwable -> Log.e(TAG, "Error restoring note", throwable))); + getCompositeDisposable() + .add( + getDataManager() + .transferNoteOutTrash(nNote.getId()) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, // onComplete + throwable -> + Log.e(TAG, "Error restoring note", throwable))); } private void performDelete(Tag tag) { - getCompositeDisposable().add( - getDataManager().getCountNotesTag(tag.getNameTag()) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(count -> { - if (count == 0) { - deleteTagFromDb(tag); - } else { - getView().startDeleteTagDialog(tag); - } - }, throwable -> Log.e(TAG, "count error", throwable)) - ); + getCompositeDisposable() + .add( + getDataManager() + .getCountNotesTag(tag.getNameTag()) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + count -> { + if (count == 0) { + deleteTagFromDb(tag); + } else { + getView().startDeleteTagDialog(tag); + } + }, + throwable -> Log.e(TAG, "count error", throwable))); } private void deleteTagFromDb(Tag tag) { - getCompositeDisposable().add( - getDataManager().deleteTag(tag) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - () -> { /* ок */ }, - throwable -> Log.e(TAG, "Error deleting tag", throwable) - ) - ); + getCompositeDisposable() + .add( + getDataManager() + .deleteTag(tag) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> { + /* ок */ + }, + throwable -> Log.e(TAG, "Error deleting tag", throwable))); } @Override @@ -403,12 +427,17 @@ public void requestDeleteTag(Tag tag) { uiHandler.postDelayed(() -> performDelete(tag), 300); } - @Override public void editVisibleTag(Tag tag) { - getCompositeDisposable().add(getDataManager().updateTag(tag).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(() -> { - }, // onComplete - throwable -> Log.e(TAG, "Error updating tag", throwable))); + getCompositeDisposable() + .add( + getDataManager() + .updateTag(tag) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, // onComplete + throwable -> Log.e(TAG, "Error updating tag", throwable))); } public Note getBackupDeleteNote() { @@ -425,5 +454,4 @@ public void detachView() { super.detachView(); backupDeleteNote = null; } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/NotePresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/NotePresenter.java index c4ad57a..0a95015 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/NotePresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/NotePresenter.java @@ -1,10 +1,8 @@ package com.pasich.mynotes.ui.presenter; - import android.content.Intent; import android.graphics.Typeface; import android.util.Log; - import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Note; @@ -15,22 +13,18 @@ import com.pasich.mynotes.utils.enums.SaveState; import com.pasich.mynotes.utils.navigation.NoteExtras; import com.pasich.mynotes.utils.rx.SchedulerProvider; - -import java.util.Date; -import java.util.concurrent.TimeUnit; - -import javax.inject.Inject; - import io.reactivex.disposables.CompositeDisposable; import io.reactivex.disposables.SerialDisposable; import io.reactivex.subjects.PublishSubject; +import java.util.Date; +import java.util.concurrent.TimeUnit; +import javax.inject.Inject; - -public class NotePresenter extends BasePresenter implements NoteContract.presenter { +public class NotePresenter extends BasePresenter + implements NoteContract.presenter { private static final String TAG = "NotePresenter"; - private final PublishSubject autoSaveTrigger = PublishSubject.create(); private final SerialDisposable idleTimer = new SerialDisposable(); // Last successfully saved version of the note @@ -45,7 +39,10 @@ public class NotePresenter extends BasePresenter implements N private Note targetNote; @Inject - public NotePresenter(SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable, DataManager dataManager) { + public NotePresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager) { super(schedulerProvider, compositeDisposable, dataManager); } @@ -53,15 +50,19 @@ public NotePresenter(SchedulerProvider schedulerProvider, CompositeDisposable co public void attachView(NoteContract.view v) { super.attachView(v); getCompositeDisposable().add(idleTimer); - getCompositeDisposable().add( - autoSaveTrigger - .debounce(AutoSave.AUTO_SAVE_DELAY, TimeUnit.MILLISECONDS, getSchedulerProvider().computation()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - ignored -> performAutoSave(), - error -> android.util.Log.e(TAG, "autoSave stream error", error) - ) - ); + getCompositeDisposable() + .add( + autoSaveTrigger + .debounce( + AutoSave.AUTO_SAVE_DELAY, + TimeUnit.MILLISECONDS, + getSchedulerProvider().computation()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + ignored -> performAutoSave(), + error -> + android.util.Log.e( + TAG, "autoSave stream error", error))); } public long getIdKey() { @@ -103,21 +104,16 @@ public boolean hasNote() { return targetNote != null; } - /** * Trigger auto-save for any text change. - *

- * Logic: - * 1) If the note is empty, do not save anything (IDLE). - * 2) Cancel the previous auto-save if the user continues to enter text. - * 3) Set status to PENDING — there are unsaved changes. - * 4) After a pause (AUTO_SAVE_DELAY), execute saveNote(). - * - onSuccess → SAVED → after 3 seconds → IDLE - * - onError → ERROR → after 5 seconds → PENDING - *

- * Works as “smart” autosave: saves only after a pause in typing. + * + *

Logic: 1) If the note is empty, do not save anything (IDLE). 2) Cancel the previous + * auto-save if the user continues to enter text. 3) Set status to PENDING — there are unsaved + * changes. 4) After a pause (AUTO_SAVE_DELAY), execute saveNote(). - onSuccess → SAVED → after + * 3 seconds → IDLE - onError → ERROR → after 5 seconds → PENDING + * + *

Works as “smart” autosave: saves only after a pause in typing. */ - @Override public void onNoteChanged() { if (!hasMeaningfulContent(targetNote)) { @@ -130,67 +126,64 @@ public void onNoteChanged() { private void performAutoSave() { if (targetNote != null && !isViewDead()) { - saveNote(targetNote, new NoteContract.AutoSaveCallback() { - @Override - public void onSuccess() { - updateSaveState(SaveState.SAVED); - if (!isViewDead()) { - getView().runAttachmentsCleanup(targetNote); - } - idleTimer.set( - io.reactivex.Observable.timer(3, TimeUnit.SECONDS, getSchedulerProvider().computation()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(ignored -> updateSaveState(SaveState.IDLE)) - ); - } - - @Override - public void onError(Throwable error) { - updateSaveState(SaveState.ERROR); - idleTimer.set( - io.reactivex.Observable.timer(5, TimeUnit.SECONDS, getSchedulerProvider().computation()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(ignored -> updateSaveState(SaveState.PENDING)) - ); - } - }); + saveNote( + targetNote, + new NoteContract.AutoSaveCallback() { + @Override + public void onSuccess() { + updateSaveState(SaveState.SAVED); + if (!isViewDead()) { + getView().runAttachmentsCleanup(targetNote); + } + idleTimer.set( + io.reactivex.Observable.timer( + 3, + TimeUnit.SECONDS, + getSchedulerProvider().computation()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(ignored -> updateSaveState(SaveState.IDLE))); + } + + @Override + public void onError(Throwable error) { + updateSaveState(SaveState.ERROR); + idleTimer.set( + io.reactivex.Observable.timer( + 5, + TimeUnit.SECONDS, + getSchedulerProvider().computation()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + ignored -> updateSaveState(SaveState.PENDING))); + } + }); } } - /** * Saves a note (updates an existing one). - *

- * Main logic: - *

- * 1) Null check: - * - If the note is missing, saving is impossible, so we return an error. - *

- * 2) Activity lifecycle check: - * - If View is destroyed, but there is data in the note — emergency saving is performed, - * which works without UI and without callback. - *

- * 3) Content check: - * - If the note is empty (title/value/valueJson), there is no point in saving. - * We return IDLE and success. - *

- * 4) Checking for changes: - * - If the note has not changed compared to the last saved version — - * we do not perform unnecessary writing to the database. - *

- * 5) Main saving: - * - We update the edit date. - * - Perform updateNote() via RxJava (IO → UI). - * - After successful saving, update lastSavedXXX - * so that the system correctly identifies the changed data next time. - *

- * 6) Error handling: - * - In case of failure — log and call callback.onError(). - *

- * Note: - * The method is only responsible for updating an existing note. + * + *

Main logic: + * + *

1) Null check: - If the note is missing, saving is impossible, so we return an error. + * + *

2) Activity lifecycle check: - If View is destroyed, but there is data in the note — + * emergency saving is performed, which works without UI and without callback. + * + *

3) Content check: - If the note is empty (title/value/valueJson), there is no point in + * saving. We return IDLE and success. + * + *

4) Checking for changes: - If the note has not changed compared to the last saved version + * — we do not perform unnecessary writing to the database. + * + *

5) Main saving: - We update the edit date. - Perform updateNote() via RxJava (IO → UI). - + * After successful saving, update lastSavedXXX so that the system correctly identifies the + * changed data next time. + * + *

6) Error handling: - In case of failure — log and call callback.onError(). + * + *

Note: The method is only responsible for updating an existing note. */ - private void saveNote(Note note, NoteContract.AutoSaveCallback callback) { if (note == null) { callback.onError(new Exception("Note is null")); @@ -222,26 +215,45 @@ private void saveNote(Note note, NoteContract.AutoSaveCallback callback) { updateSaveState(SaveState.SAVING); note.setDate(new Date().getTime()); - getCompositeDisposable().add(getDataManager().updateNote(note).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(() -> { - savedNote.copyFrom(note); - callback.onSuccess(); - }, error -> { - Log.e(TAG, "saveNote() failed", error); - callback.onError(error); - })); + getCompositeDisposable() + .add( + getDataManager() + .updateNote(note) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> { + savedNote.copyFrom(note); + callback.onSuccess(); + }, + error -> { + Log.e(TAG, "saveNote() failed", error); + callback.onError(error); + })); } /** - * Emergency saving when Activity is destroyed but there is unsaved data. - * Performed synchronously to ensure saving. - * Only NoteActivity (SimpleEditor) + * Emergency saving when Activity is destroyed but there is unsaved data. Performed + * synchronously to ensure saving. Only NoteActivity (SimpleEditor) */ private void performEmergencySave(Note note) { try { - boolean hasChanges = !note.getTitle().equals(savedNote.getTitle()) || isContentChanged(); + boolean hasChanges = + !note.getTitle().equals(savedNote.getTitle()) || isContentChanged(); if (hasChanges && hasMeaningfulContent(note)) { note.setDate(new Date().getTime()); - getCompositeDisposable().add(getDataManager().updateNote(note).subscribeOn(getSchedulerProvider().io()).subscribe(() -> savedNote.copyFrom(note), throwable -> Log.e(TAG, "Emergency update failed", throwable))); + getCompositeDisposable() + .add( + getDataManager() + .updateNote(note) + .subscribeOn(getSchedulerProvider().io()) + .subscribe( + () -> savedNote.copyFrom(note), + throwable -> + Log.e( + TAG, + "Emergency update failed", + throwable))); } } catch (Exception e) { Log.e(TAG, "Critical: Emergency save crashed", e); @@ -249,10 +261,9 @@ private void performEmergencySave(Note note) { } /** - * Updates the save status and notifies the UI. - * If the save is complete and closing is expected, closes the screen. + * Updates the save status and notifies the UI. If the save is complete and closing is expected, + * closes the screen. */ - private void updateSaveState(SaveState newState) { isSavingInProgress = (newState == SaveState.SAVING); if (isViewDead()) { @@ -260,7 +271,6 @@ private void updateSaveState(SaveState newState) { } getView().updateSaveStatus(newState); - // If saving is complete and closing is expected if (!isSavingInProgress && pendingClose) { pendingClose = false; @@ -269,10 +279,8 @@ private void updateSaveState(SaveState newState) { } /** - * Handles closing the note screen: - * - deletes empty new notes, - * - saves pending changes, - * - or closes immediately if nothing to save. + * Handles closing the note screen: - deletes empty new notes, - saves pending changes, - or + * closes immediately if nothing to save. */ @Override public void closeActivity() { @@ -280,7 +288,15 @@ public void closeActivity() { // Case: new empty note → delete and exit if (targetNote != null && newNoteKey && !hasMeaningfulContent(targetNote)) { - getCompositeDisposable().add(getDataManager().deleteNote(targetNote).subscribeOn(getSchedulerProvider().io()).subscribe(() -> getView().runAttachmentsCleanup(targetNote), throwable -> Log.e(TAG, "deleteNote(): FAILED", throwable))); + getCompositeDisposable() + .add( + getDataManager() + .deleteNote(targetNote) + .subscribeOn(getSchedulerProvider().io()) + .subscribe( + () -> getView().runAttachmentsCleanup(targetNote), + throwable -> + Log.e(TAG, "deleteNote(): FAILED", throwable))); if (!isViewDead()) { getView().closeNoteActivity(); @@ -309,36 +325,34 @@ public void closeActivity() { return; } - saveNote(targetNote, new NoteContract.AutoSaveCallback() { + saveNote( + targetNote, + new NoteContract.AutoSaveCallback() { - @Override - public void onSuccess() { - updateSaveState(SaveState.SAVED); + @Override + public void onSuccess() { + updateSaveState(SaveState.SAVED); - if (pendingClose && !isViewDead()) { - pendingClose = false; - getView().runAttachmentsCleanup(targetNote); - getView().closeNoteActivity(); - } - } + if (pendingClose && !isViewDead()) { + pendingClose = false; + getView().runAttachmentsCleanup(targetNote); + getView().closeNoteActivity(); + } + } - @Override - public void onError(Throwable error) { - updateSaveState(SaveState.ERROR); - pendingClose = false; - } - }); + @Override + public void onError(Throwable error) { + updateSaveState(SaveState.ERROR); + pendingClose = false; + } + }); return; } if (!isViewDead()) getView().closeNoteActivity(); } - - /** - * Determines if the note requires saving (new or modified). - */ - + /** Determines if the note requires saving (new or modified). */ private boolean needsSave() { if (targetNote == null) return false; @@ -352,11 +366,7 @@ private boolean needsSave() { return isContentChanged(); } - - /** - * Checks whether content differs from the last saved version. - */ - + /** Checks whether content differs from the last saved version. */ private boolean isContentChanged() { if (targetNote == null || savedNote == null) return false; @@ -372,11 +382,7 @@ private boolean isContentChanged() { } } - - /** - * Checks whether the note contains any meaningful data. - */ - + /** Checks whether the note contains any meaningful data. */ private boolean hasMeaningfulContent(Note note) { if (note == null) return false; @@ -394,7 +400,6 @@ private boolean hasMeaningfulContent(Note note) { return json != null && !json.trim().isEmpty() && !json.equals("[]"); } - @Override public void viewIsReady() { getView().initParam(); @@ -406,11 +411,10 @@ public void viewIsReady() { } /** - * Loads parameters passed to the Activity via Intent. - * Initializes presenter fields (id, tag, shared text, creation flag). - * If this is a new note, creates a fresh Note instance with default values. + * Loads parameters passed to the Activity via Intent. Initializes presenter fields (id, tag, + * shared text, creation flag). If this is a new note, creates a fresh Note instance with + * default values. */ - @Override public void getLoadIntentData(Intent mIntent) { setIdKey(mIntent.getLongExtra(NoteExtras.EXTRA_ID_NOTE, 0)); @@ -418,21 +422,29 @@ public void getLoadIntentData(Intent mIntent) { } /** - * Loads a note from the database by its ID. - * Once received, forwards it to the View, updates the internal targetNote, - * synchronizes the savedNote snapshot, and resets the save state to IDLE. + * Loads a note from the database by its ID. Once received, forwards it to the View, updates the + * internal targetNote, synchronizes the savedNote snapshot, and resets the save state to IDLE. */ @Override public void loadingData(long idNote) { - getCompositeDisposable().add(getDataManager().getNoteForId(idNote).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(note -> { - if (note != null && !isViewDead()) { - getView().loadingNote(note); - setNote(note); - // Update saved values on load - savedNote.copyFrom(note); - updateSaveState(SaveState.IDLE); - } - }, throwable -> Log.e(TAG, "loadingData() failed", throwable))); + getCompositeDisposable() + .add( + getDataManager() + .getNoteForId(idNote) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + note -> { + if (note != null && !isViewDead()) { + getView().loadingNote(note); + setNote(note); + // Update saved values on load + savedNote.copyFrom(note); + updateSaveState(SaveState.IDLE); + } + }, + throwable -> + Log.e(TAG, "loadingData() failed", throwable))); } @Override @@ -450,40 +462,30 @@ public int getTypeFace(String textStyle) { }; } - /** - * Clean up Handler and callbacks when destroying Activity - */ + /** Clean up Handler and callbacks when destroying Activity */ public void cleanupHandlers() { // Handler removed; CompositeDisposable.dispose() in detachView() handles cleanup pendingClose = false; isSavingInProgress = false; } - /** * Checks whether the View has already been destroyed. - *

- * Used before any saving operations: - * if the Activity/Fragment no longer exists + * + *

Used before any saving operations: if the Activity/Fragment no longer exists */ private boolean isViewDead() { return getView() == null; } - /** * Updates note content coming from the advanced (Editor.js) editor. * - * @param title Optional updated title. + * @param title Optional updated title. * @param jsonData Optional Editor.js JSON containing blocks. - *

- * If title is provided → update title. - * If jsonData is provided → parse blocks into: - * - plain text → value - * - attachments → attachments JSON - * - raw blocks JSON → valueJson - *

- * Marks the note as rich-content and triggers auto-save. + *

If title is provided → update title. If jsonData is provided → parse blocks into: - + * plain text → value - attachments → attachments JSON - raw blocks JSON → valueJson + *

Marks the note as rich-content and triggers auto-save. */ @Override public void extendedNoteChange(String title, String jsonData) { @@ -504,15 +506,14 @@ public void extendedNoteChange(String title, String jsonData) { onNoteChanged(); } - /** * Updates plain-text note fields (title/value) for the simple editor. - *

- * If title is not null → updates the title. - * If value is not null → updates the plain text and clears valueJson. - *

- * Marks the note as non-rich and triggers auto-save. - * Passing null means "do not modify this field". + * + *

If title is not null → updates the title. If value is not null → updates the plain text + * and clears valueJson. + * + *

Marks the note as non-rich and triggers auto-save. Passing null means "do not modify this + * field". */ @Override public void simpleNoteChange(String title, String value, boolean emergencySave) { @@ -527,10 +528,8 @@ public void simpleNoteChange(String title, String value, boolean emergencySave) if (value != null) { getNote().setValue(value); getNote().setValueJson(""); - } - // emergency save if (emergencySave) { performEmergencySave(targetNote); @@ -544,28 +543,55 @@ public void simpleNoteChange(String title, String value, boolean emergencySave) public void copyNoteRequest() { if (targetNote == null) return; - getCompositeDisposable().add(getDataManager().copyNote(targetNote).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(newId -> { - - // load new note - getCompositeDisposable().add(getDataManager().getNoteForId(newId).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(note -> { - targetNote = note; - savedNote.copyFrom(note); - idKey = newId; - newNoteKey = false; - - if (!isViewDead()) { - if (getExtendedEditor()) { - getView().reloadExtendedEditor(); - } - getView().runCopyAnimation(); - getView().loadingNote(note); - getView().onNoteCopied(newId); - - } - - }, err -> Log.e(TAG, "load failed", err))); - - }, err -> Log.e(TAG, "copy failed", err))); + getCompositeDisposable() + .add( + getDataManager() + .copyNote(targetNote) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + newId -> { + + // load new note + getCompositeDisposable() + .add( + getDataManager() + .getNoteForId(newId) + .subscribeOn( + getSchedulerProvider() + .io()) + .observeOn( + getSchedulerProvider() + .ui()) + .subscribe( + note -> { + targetNote = note; + savedNote.copyFrom( + note); + idKey = newId; + newNoteKey = false; + + if (!isViewDead()) { + if (getExtendedEditor()) { + getView() + .reloadExtendedEditor(); + } + getView() + .runCopyAnimation(); + getView() + .loadingNote( + note); + getView() + .onNoteCopied( + newId); + } + }, + err -> + Log.e( + TAG, + "load failed", + err))); + }, + err -> Log.e(TAG, "copy failed", err))); } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/TagsPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/TagsPresenter.java index 16d9b77..58e8f76 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/TagsPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/TagsPresenter.java @@ -4,7 +4,6 @@ import android.util.Log; import android.view.View; - import com.pasich.mynotes.R; import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.data.DataManager; @@ -13,23 +12,24 @@ import com.pasich.mynotes.utils.TagsSorter; import com.pasich.mynotes.utils.managers.SystemTagsManager; import com.pasich.mynotes.utils.rx.SchedulerProvider; - +import dagger.hilt.android.scopes.ActivityScoped; +import io.reactivex.disposables.CompositeDisposable; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; - import javax.inject.Inject; -import dagger.hilt.android.scopes.ActivityScoped; -import io.reactivex.disposables.CompositeDisposable; - @ActivityScoped -public class TagsPresenter extends BasePresenter implements TagsContract.presenter { +public class TagsPresenter extends BasePresenter + implements TagsContract.presenter { private static final String TAG = "TagsPresenter"; private List cachedTags = new ArrayList<>(); @Inject - public TagsPresenter(SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable, DataManager dataManager) { + public TagsPresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager) { super(schedulerProvider, compositeDisposable, dataManager); } @@ -46,27 +46,40 @@ public void viewIsReady() { public void loadTags() { if (!isViewAttached()) return; - getCompositeDisposable().add(getDataManager().getTagsUser().subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(tagList -> { - if (isViewAttached()) { - // Створюємо спеціальний тег для кнопки "Додати" - tagList.add(0, SystemTagsManager.createAddTag()); - cachedTags = new ArrayList<>(TagsSorter.sortTags(tagList, getDataManager().getSortParamTags())); - displayTags(false); - } - }, throwable -> { - Log.e(TAG, "Error loading tags", throwable); - if (isViewAttached()) { - getView().showToastMessage("Tag loading error"); - } - })); + getCompositeDisposable() + .add( + getDataManager() + .getTagsUser() + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + tagList -> { + if (isViewAttached()) { + // Створюємо спеціальний тег для кнопки "Додати" + tagList.add(0, SystemTagsManager.createAddTag()); + cachedTags = + new ArrayList<>( + TagsSorter.sortTags( + tagList, + getDataManager() + .getSortParamTags())); + displayTags(false); + } + }, + throwable -> { + Log.e(TAG, "Error loading tags", throwable); + if (isViewAttached()) { + getView().showToastMessage("Tag loading error"); + } + })); } - // Сортуємо локальний кеш згідно з налаштуваннями public void displayTags(boolean isSort) { if (!isViewAttached() || cachedTags.isEmpty()) return; if (isSort) { - getView().loadTags(TagsSorter.sortTags(cachedTags, getDataManager().getSortParamTags())); + getView() + .loadTags(TagsSorter.sortTags(cachedTags, getDataManager().getSortParamTags())); } else { getView().loadTags(cachedTags); } @@ -78,13 +91,29 @@ public void toggleTagVisibility(Tag tag) { tag.setVisibility(tag.getVisibility() == 1 ? 0 : 1); - getCompositeDisposable().add(getDataManager().updateTag(tag).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(() -> { - if (isViewAttached()) { - getView().showToastMessage(tag.getVisibility() == 0 ? R.string.toastTagVisible : R.string.toastTagHidde); - updateTagInCache(tag); - displayTags(false); - } - }, throwable -> Log.e(TAG, "Error toggling tag visibility", throwable))); + getCompositeDisposable() + .add( + getDataManager() + .updateTag(tag) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> { + if (isViewAttached()) { + getView() + .showToastMessage( + tag.getVisibility() == 0 + ? R.string.toastTagVisible + : R.string.toastTagHidde); + updateTagInCache(tag); + displayTags(false); + } + }, + throwable -> + Log.e( + TAG, + "Error toggling tag visibility", + throwable))); } @Override @@ -120,19 +149,30 @@ public void onDragCompleted(List currentTagOrders) { getDataManager().setSortParamTags("TagsPositionSort"); } - List userTags = currentTagOrders.stream().filter(tag -> tag.getSystemAction() == 0).collect(Collectors.toList()); + List userTags = + currentTagOrders.stream() + .filter(tag -> tag.getSystemAction() == 0) + .collect(Collectors.toList()); for (int i = 0; i < userTags.size(); i++) { userTags.get(i).setPosition(i); } - updateCacheWithNewPositions(userTags); - - getCompositeDisposable().add(getDataManager().updateTags(userTags).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(() -> { - }, throwable -> Log.e("TagsPresenter", "Error updating tag: ", throwable))); - + getCompositeDisposable() + .add( + getDataManager() + .updateTags(userTags) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, + throwable -> + Log.e( + "TagsPresenter", + "Error updating tag: ", + throwable))); } @Override @@ -146,10 +186,21 @@ public void onSortMenuClick() { public void getTagNotesCount(Tag tag, TagsContract.TagNotesCountCallback callback) { if (tag == null || callback == null) return; - getCompositeDisposable().add(getDataManager().getCountNotesTag(tag.getNameTag()).subscribeOn(getSchedulerProvider().io()).observeOn(getSchedulerProvider().ui()).subscribe(callback::onTagNotesCountReceived, throwable -> { - Log.e(TAG, "Error getting notes count for tag", throwable); - callback.onTagNotesCountReceived(0); - })); + getCompositeDisposable() + .add( + getDataManager() + .getCountNotesTag(tag.getNameTag()) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + callback::onTagNotesCountReceived, + throwable -> { + Log.e( + TAG, + "Error getting notes count for tag", + throwable); + callback.onTagNotesCountReceived(0); + })); } private void updateTagInCache(Tag updatedTag) { @@ -172,4 +223,4 @@ public void detachView() { super.detachView(); cachedTags.clear(); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java index a30d1e8..04a0dd3 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java @@ -2,7 +2,6 @@ import android.content.Context; import android.util.Log; - import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Task; @@ -10,28 +9,27 @@ import com.pasich.mynotes.ui.contract.TasksContract; import com.pasich.mynotes.utils.reminder.TaskReminderManager; import com.pasich.mynotes.utils.rx.SchedulerProvider; - -import java.util.List; - -import javax.inject.Inject; - import dagger.hilt.android.qualifiers.ApplicationContext; import dagger.hilt.android.scopes.ActivityScoped; import io.reactivex.Flowable; import io.reactivex.disposables.CompositeDisposable; +import java.util.List; +import javax.inject.Inject; @ActivityScoped -public class TasksPresenter extends BasePresenter implements TasksContract.presenter { +public class TasksPresenter extends BasePresenter + implements TasksContract.presenter { private static final String TAG = "TasksPresenter"; private int selectedCategoryId = 0; private final Context appContext; @Inject - public TasksPresenter(SchedulerProvider schedulerProvider, - CompositeDisposable compositeDisposable, - DataManager dataManager, - @ApplicationContext Context appContext) { + public TasksPresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager, + @ApplicationContext Context appContext) { super(schedulerProvider, compositeDisposable, dataManager); this.appContext = appContext; } @@ -44,45 +42,50 @@ public void viewIsReady() { } private void loadCategories() { - getCompositeDisposable().add( - getDataManager().getCategories() - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - categories -> { if (isViewAttached()) getView().renderCategories(categories); }, - err -> Log.e(TAG, "loadCategories", err) - ) - ); + getCompositeDisposable() + .add( + getDataManager() + .getCategories() + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + categories -> { + if (isViewAttached()) + getView().renderCategories(categories); + }, + err -> Log.e(TAG, "loadCategories", err))); } private void loadTasks() { getCompositeDisposable().clear(); loadCategories(); - Flowable> activeStream = selectedCategoryId == 0 - ? getDataManager().getActiveTasks() - : getDataManager().getActiveTasksByCategory(selectedCategoryId); + Flowable> activeStream = + selectedCategoryId == 0 + ? getDataManager().getActiveTasks() + : getDataManager().getActiveTasksByCategory(selectedCategoryId); Flowable> completedStream = getDataManager().getCompletedTasks(); - getCompositeDisposable().add( - Flowable.combineLatest(activeStream, completedStream, - (active, completed) -> new Object[]{active, completed}) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - pair -> { - if (isViewAttached()) { - @SuppressWarnings("unchecked") - List active = (List) pair[0]; - @SuppressWarnings("unchecked") - List completed = (List) pair[1]; - getView().renderTasks(active, completed); - } - }, - err -> Log.e(TAG, "loadTasks", err) - ) - ); + getCompositeDisposable() + .add( + Flowable.combineLatest( + activeStream, + completedStream, + (active, completed) -> new Object[] {active, completed}) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + pair -> { + if (isViewAttached()) { + @SuppressWarnings("unchecked") + List active = (List) pair[0]; + @SuppressWarnings("unchecked") + List completed = (List) pair[1]; + getView().renderTasks(active, completed); + } + }, + err -> Log.e(TAG, "loadTasks", err))); } @Override @@ -96,68 +99,76 @@ public void addTask(String title, String description, int categoryId) { if (title == null || title.trim().isEmpty()) return; Task task = new Task(title.trim(), categoryId); task.setDescription(description); - getCompositeDisposable().add( - getDataManager().addTask(task) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "addTask", err)) - ); + getCompositeDisposable() + .add( + getDataManager() + .addTask(task) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "addTask", err))); } @Override public void toggleTask(Task task) { - getCompositeDisposable().add( - getDataManager().toggleTask(task.getId(), !task.isDone()) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "toggleTask", err)) - ); + getCompositeDisposable() + .add( + getDataManager() + .toggleTask(task.getId(), !task.isDone()) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "toggleTask", err))); } @Override public void deleteTask(Task task) { - getCompositeDisposable().add( - getDataManager().deleteTask(task) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "deleteTask", err)) - ); + getCompositeDisposable() + .add( + getDataManager() + .deleteTask(task) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "deleteTask", err))); } @Override public void clearCompleted() { - getCompositeDisposable().add( - getDataManager().clearCompletedTasks() - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "clearCompleted", err)) - ); + getCompositeDisposable() + .add( + getDataManager() + .clearCompletedTasks() + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "clearCompleted", err))); } @Override public void addCategory(String name, String colorHex) { if (name == null || name.trim().isEmpty()) return; TaskCategory cat = new TaskCategory(name.trim(), colorHex); - getCompositeDisposable().add( - getDataManager().addCategory(cat) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "addCategory", err)) - ); + getCompositeDisposable() + .add( + getDataManager() + .addCategory(cat) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "addCategory", err))); } @Override public void editTask(Task task, String newTitle, String newDescription) { if (newTitle == null || newTitle.trim().isEmpty()) return; task.setTitle(newTitle.trim()); - task.setDescription(newDescription == null || newDescription.trim().isEmpty() - ? null : newDescription.trim()); - getCompositeDisposable().add( - getDataManager().updateTask(task) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "editTask", err)) - ); + task.setDescription( + newDescription == null || newDescription.trim().isEmpty() + ? null + : newDescription.trim()); + getCompositeDisposable() + .add( + getDataManager() + .updateTask(task) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "editTask", err))); } @Override @@ -165,46 +176,49 @@ public void updatePositions(List tasks) { for (int i = 0; i < tasks.size(); i++) { tasks.get(i).setPosition(i); } - getCompositeDisposable().add( - io.reactivex.Observable.fromIterable(tasks) - .flatMapCompletable(t -> getDataManager().updateTask(t)) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "updatePositions", err)) - ); + getCompositeDisposable() + .add( + io.reactivex.Observable.fromIterable(tasks) + .flatMapCompletable(t -> getDataManager().updateTask(t)) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "updatePositions", err))); } @Override public void deleteCategory(TaskCategory category) { - getCompositeDisposable().add( - getDataManager().deleteCategory(category) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "deleteCategory", err)) - ); + getCompositeDisposable() + .add( + getDataManager() + .deleteCategory(category) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "deleteCategory", err))); } @Override public void setTaskReminder(Task task, long time) { task.setReminderTime(time); TaskReminderManager.scheduleReminder(appContext, task); - getCompositeDisposable().add( - getDataManager().setTaskReminder(task.getId(), time) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "setTaskReminder", err)) - ); + getCompositeDisposable() + .add( + getDataManager() + .setTaskReminder(task.getId(), time) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "setTaskReminder", err))); } @Override public void clearTaskReminder(Task task) { TaskReminderManager.cancelReminder(appContext, task.getId()); task.setReminderTime(null); - getCompositeDisposable().add( - getDataManager().clearTaskReminder(task.getId()) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(() -> {}, err -> Log.e(TAG, "clearTaskReminder", err)) - ); + getCompositeDisposable() + .add( + getDataManager() + .clearTaskReminder(task.getId()) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe(() -> {}, err -> Log.e(TAG, "clearTaskReminder", err))); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/TrashPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/TrashPresenter.java index 76dc477..239df71 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/TrashPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/TrashPresenter.java @@ -1,31 +1,28 @@ package com.pasich.mynotes.ui.presenter; - import android.util.Log; - import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.ui.contract.TrashContract; import com.pasich.mynotes.utils.rx.SchedulerProvider; - +import io.reactivex.disposables.CompositeDisposable; import java.util.ArrayList; import java.util.List; - import javax.inject.Inject; -import io.reactivex.disposables.CompositeDisposable; - - -public class TrashPresenter extends BasePresenter implements TrashContract.presenter { +public class TrashPresenter extends BasePresenter + implements TrashContract.presenter { private static final String TAG = "TrashPresenter"; @Inject - public TrashPresenter(SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable, DataManager dataManager) { + public TrashPresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager) { super(schedulerProvider, compositeDisposable, dataManager); } - @Override public void viewIsReady() { getView().settingsActionBar(); @@ -34,7 +31,6 @@ public void viewIsReady() { getView().initListeners(); } - @Override public void cleanTrashDialogStart() { if (isViewAttached()) getView().cleanTrashDialogShow(); @@ -42,44 +38,43 @@ public void cleanTrashDialogStart() { @Override public void loadingTrash() { - getCompositeDisposable().add( - getDataManager().getNotesInTrash() - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(trashNoteList -> getView().loadData(trashNoteList), - throwable -> Log.wtf(TAG, "trashLoad", throwable))); + getCompositeDisposable() + .add( + getDataManager() + .getNotesInTrash() + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + trashNoteList -> getView().loadData(trashNoteList), + throwable -> Log.wtf(TAG, "trashLoad", throwable))); } @Override public void restoreNotesArray(ArrayList notes) { List ids = new ArrayList<>(); for (Note n : notes) ids.add(n.getId()); - getCompositeDisposable().add( - getDataManager() - .restoreNotesAndFixTags(ids) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - () -> { - }, - throwable -> Log.e(TAG, "Error restoring notes", throwable) - ) - ); + getCompositeDisposable() + .add( + getDataManager() + .restoreNotesAndFixTags(ids) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, + throwable -> + Log.e(TAG, "Error restoring notes", throwable))); } - @Override public void clearTrash() { - getCompositeDisposable().add( - getDataManager().clearTrash() - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - () -> getView().loadData(new ArrayList<>()), - throwable -> Log.e(TAG, "clearTrash failed", throwable) - ) - ); + getCompositeDisposable() + .add( + getDataManager() + .clearTrash() + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> getView().loadData(new ArrayList<>()), + throwable -> Log.e(TAG, "clearTrash failed", throwable))); } - - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/DeleteTagDialogPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/DeleteTagDialogPresenter.java index a20cb30..98f0ca9 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/DeleteTagDialogPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/DeleteTagDialogPresenter.java @@ -1,22 +1,23 @@ package com.pasich.mynotes.ui.presenter.dialogs; - import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.ui.contract.dialogs.DeleteTagDialogContract; import com.pasich.mynotes.utils.rx.SchedulerProvider; - -import javax.inject.Inject; - import io.reactivex.disposables.CompositeDisposable; +import javax.inject.Inject; -public class DeleteTagDialogPresenter extends BasePresenter implements DeleteTagDialogContract.presenter { +public class DeleteTagDialogPresenter extends BasePresenter + implements DeleteTagDialogContract.presenter { private int countNotesForTag; @Inject - public DeleteTagDialogPresenter(SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable, DataManager dataManager) { + public DeleteTagDialogPresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager) { super(schedulerProvider, compositeDisposable, dataManager); } @@ -25,21 +26,34 @@ public void viewIsReady() { getView().initListeners(); } - @Override public void getLoadCountNotesForTag(String nameTag) { - getCompositeDisposable().add(getDataManager().getCountNotesTag(nameTag).subscribeOn(getSchedulerProvider().io()).subscribe(this::setCountNotesForTag)); - + getCompositeDisposable() + .add( + getDataManager() + .getCountNotesTag(nameTag) + .subscribeOn(getSchedulerProvider().io()) + .subscribe(this::setCountNotesForTag)); } @Override public void deleteTagUnchecked(Tag tag) { - getCompositeDisposable().add(getDataManager().clearTagInNotes(tag).subscribeOn(getSchedulerProvider().io()).subscribe()); + getCompositeDisposable() + .add( + getDataManager() + .clearTagInNotes(tag) + .subscribeOn(getSchedulerProvider().io()) + .subscribe()); } @Override public void deleteTagAndNotes(Tag tag) { - getCompositeDisposable().add(getDataManager().deleteTagAndMoveNotesToTrash(tag).subscribeOn(getSchedulerProvider().io()).subscribe()); + getCompositeDisposable() + .add( + getDataManager() + .deleteTagAndMoveNotesToTrash(tag) + .subscribeOn(getSchedulerProvider().io()) + .subscribe()); } @Override @@ -51,7 +65,6 @@ public void setCountNotesForTag(int count) { this.countNotesForTag = count; } - @Override public void detachView() { super.detachView(); diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/MoreNoteDialogPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/MoreNoteDialogPresenter.java index 4e42764..de6ce17 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/MoreNoteDialogPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/MoreNoteDialogPresenter.java @@ -1,44 +1,40 @@ package com.pasich.mynotes.ui.presenter.dialogs; - import android.util.Log; - import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.ui.contract.dialogs.MoreNoteDialogContract; import com.pasich.mynotes.utils.TagsSorter; import com.pasich.mynotes.utils.rx.SchedulerProvider; - +import io.reactivex.disposables.CompositeDisposable; import java.util.Date; - import javax.inject.Inject; -import io.reactivex.disposables.CompositeDisposable; - -public class MoreNoteDialogPresenter extends BasePresenter implements MoreNoteDialogContract.presenter { +public class MoreNoteDialogPresenter extends BasePresenter + implements MoreNoteDialogContract.presenter { private static final String TAG = "MoreNoteDialogPresenter"; private Note mNote; @Inject - public MoreNoteDialogPresenter(SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable, DataManager dataManager) { + public MoreNoteDialogPresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager) { super(schedulerProvider, compositeDisposable, dataManager); } - @Override public void viewIsReady() { getView().initInterfaces(); getView().setSliderValue(getDataManager().getSizeTextNoteActivity()); } - @Override public Note getNote() { return mNote; } - @Override public void loadNote(int noteId) { if (noteId <= 0) { @@ -46,33 +42,36 @@ public void loadNote(int noteId) { return; } - getCompositeDisposable().add( - getDataManager() - .getNoteForId(noteId) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe(note -> { - if (note != null && isViewAttached()) { - mNote = note; - getView().onNoteLoaded(note); - } - }, error -> { - Log.e(TAG, "loadNote failed", error); - if (isViewAttached()) getView().close(); - }) - ); + getCompositeDisposable() + .add( + getDataManager() + .getNoteForId(noteId) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + note -> { + if (note != null && isViewAttached()) { + mNote = note; + getView().onNoteLoaded(note); + } + }, + error -> { + Log.e(TAG, "loadNote failed", error); + if (isViewAttached()) getView().close(); + })); } @Override public void noteMoveToTrash() { - getCompositeDisposable().add(getDataManager().moveNoteToTrash(mNote.getId()) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - () -> { - }, // onComplete - throwable -> Log.e(TAG, "Error deleting note", throwable) - )); + getCompositeDisposable() + .add( + getDataManager() + .moveNoteToTrash(mNote.getId()) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, // onComplete + throwable -> Log.e(TAG, "Error deleting note", throwable))); } @Override @@ -82,36 +81,45 @@ public void editSizeText(int value) { @Override public void removeTagNote(int idNote) { - getCompositeDisposable().add(getDataManager().setTagNote("", idNote) - .subscribeOn(getSchedulerProvider().io()) - .subscribe( - () -> { - }, // onComplete - throwable -> Log.e(TAG, "Error removing tag", throwable) - )); + getCompositeDisposable() + .add( + getDataManager() + .setTagNote("", idNote) + .subscribeOn(getSchedulerProvider().io()) + .subscribe( + () -> {}, // onComplete + throwable -> Log.e(TAG, "Error removing tag", throwable))); } @Override public void editTagNote(String nameTag, int idNote) { - getCompositeDisposable().add(getDataManager().setTagNote(nameTag, idNote) - .subscribeOn(getSchedulerProvider().io()) - .subscribe( - () -> { - }, // onComplete - throwable -> Log.e(TAG, "Error editing tag", throwable) - )); + getCompositeDisposable() + .add( + getDataManager() + .setTagNote(nameTag, idNote) + .subscribeOn(getSchedulerProvider().io()) + .subscribe( + () -> {}, // onComplete + throwable -> Log.e(TAG, "Error editing tag", throwable))); } @Override public void copyNoteMainActivity() { - getCompositeDisposable().add(getDataManager() - .addNote(new Note().create(mNote.getTitle() + " (copy)", mNote.getValue() + " ", new Date().getTime(), mNote.getTag()), true) - .subscribeOn(getSchedulerProvider().io()) - .subscribe( - aLong -> getView().callableCopyNote(aLong), - throwable -> Log.e(TAG, "Error copying note", throwable) - )); - + getCompositeDisposable() + .add( + getDataManager() + .addNote( + new Note() + .create( + mNote.getTitle() + " (copy)", + mNote.getValue() + " ", + new Date().getTime(), + mNote.getTag()), + true) + .subscribeOn(getSchedulerProvider().io()) + .subscribe( + aLong -> getView().callableCopyNote(aLong), + throwable -> Log.e(TAG, "Error copying note", throwable))); } @Override @@ -119,27 +127,31 @@ public void togglePinNote() { if (mNote == null) return; boolean newPin = !mNote.isPinned(); mNote.setPinned(newPin); - getCompositeDisposable().add( - getDataManager().setPinNote(mNote.getId(), newPin) - .subscribeOn(getSchedulerProvider().io()) - .subscribe(() -> {}, throwable -> Log.e(TAG, "togglePinNote failed", throwable)) - ); + getCompositeDisposable() + .add( + getDataManager() + .setPinNote(mNote.getId(), newPin) + .subscribeOn(getSchedulerProvider().io()) + .subscribe( + () -> {}, + throwable -> + Log.e(TAG, "togglePinNote failed", throwable))); } @Override public void requestTagsOneShot() { - getCompositeDisposable().add( - getDataManager() - .getTagsUser() - .take(1) - .map(tags -> TagsSorter.sortTags(tags, getDataManager().getSortParamTags())) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - sorted -> getView().createChipsTag(sorted), - err -> Log.e(TAG, "requestTagsOneShot()", err) - ) - ); + getCompositeDisposable() + .add( + getDataManager() + .getTagsUser() + .take(1) + .map( + tags -> + TagsSorter.sortTags( + tags, getDataManager().getSortParamTags())) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + sorted -> getView().createChipsTag(sorted), + err -> Log.e(TAG, "requestTagsOneShot()", err))); } - - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/NameTagDialogPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/NameTagDialogPresenter.java index c7b3a93..39ec9c5 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/NameTagDialogPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/dialogs/NameTagDialogPresenter.java @@ -1,21 +1,22 @@ package com.pasich.mynotes.ui.presenter.dialogs; import android.util.Log; - import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.ui.contract.dialogs.NameTagDialogContract; import com.pasich.mynotes.utils.rx.SchedulerProvider; - -import javax.inject.Inject; - import io.reactivex.disposables.CompositeDisposable; +import javax.inject.Inject; -public class NameTagDialogPresenter extends BasePresenter implements NameTagDialogContract.presenter { +public class NameTagDialogPresenter extends BasePresenter + implements NameTagDialogContract.presenter { @Inject - public NameTagDialogPresenter(SchedulerProvider schedulerProvider, CompositeDisposable compositeDisposable, DataManager dataManager) { + public NameTagDialogPresenter( + SchedulerProvider schedulerProvider, + CompositeDisposable compositeDisposable, + DataManager dataManager) { super(schedulerProvider, compositeDisposable, dataManager); } @@ -24,29 +25,37 @@ public void viewIsReady() { getView().initListeners(); } - @Override public void saveTag(Tag nameTag) { - getCompositeDisposable().add(getDataManager().addTag(nameTag) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - () -> {}, // onComplete - throwable -> Log.e("NameTagDialogPresenter", "Error saving tag", throwable) - )); + getCompositeDisposable() + .add( + getDataManager() + .addTag(nameTag) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, // onComplete + throwable -> + Log.e( + "NameTagDialogPresenter", + "Error saving tag", + throwable))); } @Override public void editNameTag(String nameNewTag, Tag mTag) { - getCompositeDisposable().add(getDataManager() - .renameTag(mTag, nameNewTag) - .subscribeOn(getSchedulerProvider().io()) - .observeOn(getSchedulerProvider().ui()) - .subscribe( - () -> {}, // onComplete - throwable -> Log.e("NameTagDialogPresenter", "Error editing tag name", throwable) - )); + getCompositeDisposable() + .add( + getDataManager() + .renameTag(mTag, nameNewTag) + .subscribeOn(getSchedulerProvider().io()) + .observeOn(getSchedulerProvider().ui()) + .subscribe( + () -> {}, // onComplete + throwable -> + Log.e( + "NameTagDialogPresenter", + "Error editing tag name", + throwable))); } - - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java index 3842f19..4c9074e 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java @@ -4,37 +4,33 @@ import android.content.Context; import android.content.Intent; import android.util.Log; - import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.utils.reminder.ReminderManager; import com.pasich.mynotes.utils.reminder.TaskReminderManager; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; +import javax.inject.Inject; @AndroidEntryPoint public class BootReceiver extends BroadcastReceiver { private static final String TAG = "BootReceiver"; - @Inject - DataManager dataManager; + @Inject DataManager dataManager; @Override public void onReceive(Context ctx, Intent intent) { if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) return; - dataManager.getNotesWithActiveReminders() + dataManager + .getNotesWithActiveReminders() .subscribe( notes -> ReminderManager.rescheduleAll(ctx, notes), - e -> Log.e(TAG, "rescheduleAll failed", e) - ); + e -> Log.e(TAG, "rescheduleAll failed", e)); - dataManager.getTasksWithReminders() + dataManager + .getTasksWithReminders() .subscribe( tasks -> TaskReminderManager.rescheduleAll(ctx, tasks), - e -> Log.e(TAG, "rescheduleTasksAll failed", e) - ); + e -> Log.e(TAG, "rescheduleTasksAll failed", e)); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java index aae9fdd..8d1e0b9 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java @@ -5,11 +5,9 @@ import android.content.Context; import android.content.Intent; import android.util.Log; - import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.TaskStackBuilder; - import com.pasich.mynotes.R; import com.pasich.mynotes.cache.NotificationPreferencesCache; import com.pasich.mynotes.data.DataManager; @@ -20,23 +18,18 @@ import com.pasich.mynotes.ui.view.activity.noteEditor.NoteActivity; import com.pasich.mynotes.utils.navigation.NoteExtras; import com.pasich.mynotes.utils.reminder.ReminderManager; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.Calendar; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class ReminderReceiver extends BroadcastReceiver { private static final String TAG = "ReminderReceiver"; - @Inject - DataManager dataManager; + @Inject DataManager dataManager; - @Inject - NotificationPreferencesCache notificationPreferencesCache; + @Inject NotificationPreferencesCache notificationPreferencesCache; @Override public void onReceive(Context ctx, Intent intent) { @@ -52,8 +45,12 @@ public void onReceive(Context ctx, Intent intent) { if (intervalMinutes > 0) { long nextTime = System.currentTimeMillis() + intervalMinutes * 60_000L; - dataManager.updateNoteReminderFull(noteId, nextTime, - repeatStr != null ? repeatStr : "NONE", intervalMinutes) + dataManager + .updateNoteReminderFull( + noteId, + nextTime, + repeatStr != null ? repeatStr : "NONE", + intervalMinutes) .subscribe(() -> {}, e -> Log.e(TAG, "updateReminderFull failed", e)); Note tempNote = new Note(); tempNote.setId(noteId); @@ -68,11 +65,13 @@ public void onReceive(Context ctx, Intent intent) { ReminderRepeat repeat = ReminderRepeat.from(repeatStr); if (repeat == ReminderRepeat.NONE) { - dataManager.clearReminder(noteId) + dataManager + .clearReminder(noteId) .subscribe(() -> {}, e -> Log.e(TAG, "clearReminder failed", e)); } else { long nextTime = computeNextTime(System.currentTimeMillis(), repeat); - dataManager.updateNoteReminderFull(noteId, nextTime, repeat.name(), 0) + dataManager + .updateNoteReminderFull(noteId, nextTime, repeat.name(), 0) .subscribe(() -> {}, e -> Log.e(TAG, "updateReminder failed", e)); Note tempNote = new Note(); tempNote.setId(noteId); @@ -89,10 +88,17 @@ private long computeNextTime(long from, ReminderRepeat repeat) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(from); switch (repeat) { - case DAILY: cal.add(Calendar.DAY_OF_YEAR, 1); break; - case WEEKLY: cal.add(Calendar.WEEK_OF_YEAR, 1); break; - case MONTHLY: cal.add(Calendar.MONTH, 1); break; - default: break; + case DAILY: + cal.add(Calendar.DAY_OF_YEAR, 1); + break; + case WEEKLY: + cal.add(Calendar.WEEK_OF_YEAR, 1); + break; + case MONTHLY: + cal.add(Calendar.MONTH, 1); + break; + default: + break; } return cal.getTimeInMillis(); } @@ -102,33 +108,42 @@ private void showNotification(Context ctx, int noteId, String title, String prev noteIntent.putExtra(NoteExtras.EXTRA_NEW_NOTE, false); noteIntent.putExtra(NoteExtras.EXTRA_ID_NOTE, (long) noteId); - PendingIntent openPi = TaskStackBuilder.create(ctx) - .addNextIntent(new Intent(ctx, MainActivity.class)) - .addNextIntent(noteIntent) - .getPendingIntent(noteId, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + PendingIntent openPi = + TaskStackBuilder.create(ctx) + .addNextIntent(new Intent(ctx, MainActivity.class)) + .addNextIntent(noteIntent) + .getPendingIntent( + noteId, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); Intent snoozeIntent = new Intent(ctx, SnoozeActivity.class); snoozeIntent.putExtra(ReminderManager.EXTRA_NOTE_ID, noteId); snoozeIntent.putExtra(ReminderManager.EXTRA_NOTE_TITLE, title); snoozeIntent.putExtra(ReminderManager.EXTRA_NOTE_PREVIEW, preview); snoozeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - PendingIntent snoozePi = PendingIntent.getActivity( - ctx, noteId + 10000, snoozeIntent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - - String notifTitle = (title != null && !title.isEmpty()) ? title : ctx.getString(R.string.app_name); - String notifText = (preview != null && preview.length() > 100) - ? preview.substring(0, 100) : (preview != null ? preview : ""); - - NotificationCompat.Builder builder = new NotificationCompat.Builder( - ctx, notificationPreferencesCache.getChannelId()) - .setSmallIcon(R.drawable.ic_bell_small) - .setContentTitle(notifTitle) - .setContentText(notifText) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setAutoCancel(true) - .setContentIntent(openPi) - .addAction(0, ctx.getString(R.string.reminder_snooze_label), snoozePi); + PendingIntent snoozePi = + PendingIntent.getActivity( + ctx, + noteId + 10000, + snoozeIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + + String notifTitle = + (title != null && !title.isEmpty()) ? title : ctx.getString(R.string.app_name); + String notifText = + (preview != null && preview.length() > 100) + ? preview.substring(0, 100) + : (preview != null ? preview : ""); + + NotificationCompat.Builder builder = + new NotificationCompat.Builder(ctx, notificationPreferencesCache.getChannelId()) + .setSmallIcon(R.drawable.ic_bell_small) + .setContentTitle(notifTitle) + .setContentText(notifText) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setAutoCancel(true) + .setContentIntent(openPi) + .addAction(0, ctx.getString(R.string.reminder_snooze_label), snoozePi); NotificationManagerCompat nm = NotificationManagerCompat.from(ctx); try { diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java index 5b19879..1dab55d 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java @@ -4,60 +4,59 @@ import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; - import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; import androidx.core.app.TaskStackBuilder; - import com.pasich.mynotes.R; import com.pasich.mynotes.cache.NotificationPreferencesCache; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Task; import com.pasich.mynotes.ui.view.activity.TasksActivity; import com.pasich.mynotes.utils.reminder.TaskReminderManager; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; +import javax.inject.Inject; @AndroidEntryPoint public class TaskReminderReceiver extends BroadcastReceiver { - @Inject - DataManager dataManager; + @Inject DataManager dataManager; - @Inject - NotificationPreferencesCache notificationPreferencesCache; + @Inject NotificationPreferencesCache notificationPreferencesCache; @Override public void onReceive(Context ctx, Intent intent) { int taskId = intent.getIntExtra(TaskReminderManager.EXTRA_TASK_ID, -1); String title = intent.getStringExtra(TaskReminderManager.EXTRA_TASK_TITLE); - int intervalMinutes = intent.getIntExtra(TaskReminderManager.EXTRA_TASK_INTERVAL_MINUTES, 0); + int intervalMinutes = + intent.getIntExtra(TaskReminderManager.EXTRA_TASK_INTERVAL_MINUTES, 0); if (taskId == -1) return; Intent openIntent = new Intent(ctx, TasksActivity.class); - PendingIntent contentIntent = TaskStackBuilder.create(ctx) - .addNextIntentWithParentStack(openIntent) - .getPendingIntent(taskId + 200000, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + PendingIntent contentIntent = + TaskStackBuilder.create(ctx) + .addNextIntentWithParentStack(openIntent) + .getPendingIntent( + taskId + 200000, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - NotificationCompat.Builder builder = new NotificationCompat.Builder( - ctx, notificationPreferencesCache.getChannelId()) - .setSmallIcon(R.drawable.ic_bell_small) - .setContentTitle(ctx.getString(R.string.app_name)) - .setContentText(title) - .setAutoCancel(true) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setContentIntent(contentIntent); + NotificationCompat.Builder builder = + new NotificationCompat.Builder(ctx, notificationPreferencesCache.getChannelId()) + .setSmallIcon(R.drawable.ic_bell_small) + .setContentTitle(ctx.getString(R.string.app_name)) + .setContentText(title) + .setAutoCancel(true) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(contentIntent); try { NotificationManagerCompat.from(ctx).notify(taskId + 100000, builder.build()); - } catch (SecurityException ignored) {} + } catch (SecurityException ignored) { + } if (intervalMinutes > 0) { long nextTime = System.currentTimeMillis() + intervalMinutes * 60_000L; - dataManager.setTaskReminderFull(taskId, nextTime, intervalMinutes) + dataManager + .setTaskReminderFull(taskId, nextTime, intervalMinutes) .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .subscribe(() -> {}, e -> {}); Task tempTask = new Task(); @@ -69,7 +68,8 @@ public void onReceive(Context ctx, Intent intent) { return; } - dataManager.clearTaskReminder(taskId) + dataManager + .clearTaskReminder(taskId) .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .subscribe(() -> {}, e -> {}); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/state/MainViewState.java b/app/src/main/java/com/pasich/mynotes/ui/state/MainViewState.java index ad15d87..e97e954 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/state/MainViewState.java +++ b/app/src/main/java/com/pasich/mynotes/ui/state/MainViewState.java @@ -2,26 +2,14 @@ import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.Tag; - import java.util.List; import java.util.Objects; -public record MainViewState( - List tags, - List notes, - Tag selectedTag, - UiEvent uiEvent -) { +public record MainViewState(List tags, List notes, Tag selectedTag, UiEvent uiEvent) { public static MainViewState empty() { - return new MainViewState( - List.of(), - List.of(), - null, - UiEvent.NONE - ); + return new MainViewState(List.of(), List.of(), null, UiEvent.NONE); } - @Override public boolean equals(Object o) { if (this == o) return true; @@ -30,14 +18,11 @@ public boolean equals(Object o) { return tags.equals(other.tags) && notes.equals(other.notes) && ((selectedTag == null && other.selectedTag == null) - || (selectedTag != null && selectedTag.equals(other.selectedTag))); + || (selectedTag != null && selectedTag.equals(other.selectedTag))); } @Override public int hashCode() { return Objects.hash(tags, notes, selectedTag); } - - } - diff --git a/app/src/main/java/com/pasich/mynotes/ui/state/UiEvent.java b/app/src/main/java/com/pasich/mynotes/ui/state/UiEvent.java index 8a4ca79..e5513b2 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/state/UiEvent.java +++ b/app/src/main/java/com/pasich/mynotes/ui/state/UiEvent.java @@ -1,10 +1,9 @@ package com.pasich.mynotes.ui.state; public enum UiEvent { - - NONE, // No UI animation required; plain list update - SORT_CHANGED, // User changed the sorting order - TAG_CHANGED, // User switched to another tag - NOTE_CREATED, // A new note has been created - NOTES_CHANGED // Notes modified: delete / restore / move / etc. + NONE, // No UI animation required; plain list update + SORT_CHANGED, // User changed the sorting order + TAG_CHANGED, // User switched to another tag + NOTE_CREATED, // A new note has been created + NOTES_CHANGED // Notes modified: delete / restore / move / etc. } diff --git a/app/src/main/java/com/pasich/mynotes/ui/tile/NoteQuickTile.java b/app/src/main/java/com/pasich/mynotes/ui/tile/NoteQuickTile.java index ed7ac24..e0a8680 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/tile/NoteQuickTile.java +++ b/app/src/main/java/com/pasich/mynotes/ui/tile/NoteQuickTile.java @@ -6,7 +6,6 @@ import android.os.Build; import android.service.quicksettings.Tile; import android.service.quicksettings.TileService; - import com.pasich.mynotes.R; import com.pasich.mynotes.ui.view.activity.noteEditor.NoteActivity; import com.pasich.mynotes.utils.navigation.NoteExtras; @@ -30,8 +29,8 @@ public void onClick() { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) { - PendingIntent pi = PendingIntent.getActivity( - this, 0, intent, PendingIntent.FLAG_IMMUTABLE); + PendingIntent pi = + PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_IMMUTABLE); startActivityAndCollapse(pi); } else { startActivityAndCollapse(intent); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/AboutActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/AboutActivity.java index 4797ad9..b074c1e 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/AboutActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/AboutActivity.java @@ -11,36 +11,30 @@ import android.os.Bundle; import android.view.Menu; import android.view.MenuItem; - import androidx.activity.OnBackPressedCallback; import androidx.recyclerview.widget.LinearLayoutManager; - import com.pasich.mynotes.BuildConfig; import com.pasich.mynotes.R; import com.pasich.mynotes.base.activity.BaseActivity; import com.pasich.mynotes.databinding.ActivityAboutBinding; import com.pasich.mynotes.utils.recycler.SpacesItemDecoration; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.Objects; - import javax.inject.Inject; import javax.inject.Named; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class AboutActivity extends BaseActivity { protected ActivityAboutBinding binding; - @Inject - public LinearLayoutManager mLinearLayoutManager; + @Inject public LinearLayoutManager mLinearLayoutManager; + @Named("NotesItemSpaceDecoration") @Inject public SpacesItemDecoration itemDecorationNotes; - @Inject - boolean isPlayMarketInstall; + @Inject boolean isPlayMarketInstall; @Override public void onCreate(Bundle savedInstanceState) { @@ -51,25 +45,22 @@ public void onCreate(Bundle savedInstanceState) { setupEdgeToEdgeInsets(binding.getRoot()); binding.setActivity(this); initActivity(); - getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - setEnabled(finishActivity()); - } - }); + getOnBackPressedDispatcher() + .addCallback( + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + setEnabled(finishActivity()); + } + }); } - private void startLoadingProducts() { initListeners(); } - - @Override - public void initListeners() { - - } + public void initListeners() {} @SuppressLint("SetTextI18n") private void initActivity() { @@ -98,6 +89,7 @@ public void sendFeedback() { public void githubRepo() { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(LINK_GITHUB_REPO))); } + public void openChangelog() { startActivity(new Intent(this, ChangelogActivity.class)); } @@ -125,9 +117,8 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } - private boolean finishActivity() { supportFinishAfterTransition(); return true; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/BackupActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/BackupActivity.java index 2dd9503..72a07ff 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/BackupActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/BackupActivity.java @@ -14,11 +14,9 @@ import android.util.Log; import android.view.Menu; import android.view.MenuItem; - import androidx.activity.OnBackPressedCallback; import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.google.android.material.snackbar.Snackbar; import com.google.android.material.tabs.TabLayoutMediator; @@ -40,79 +38,98 @@ import com.pasich.mynotes.utils.constants.CloudErrors; import com.pasich.mynotes.utils.constants.SnackBarInfo; import com.pasich.mynotes.utils.file.DriveProcess; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.Objects; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class BackupActivity extends BaseActivity implements BackupContract.view { - @Inject - public BackupContract.presenter presenter; - /** - * Save local backup intent - */ - private final ActivityResultLauncher intentExportDevice = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() == Activity.RESULT_OK) { - if (result.getData() != null) { - Uri uri = result.getData().getData(); - presenter.writeFileBackupLocal(uri); - - } - } - }); - /** - * Restore local backup intent - */ - private final ActivityResultLauncher intentImportDevice = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() == Activity.RESULT_OK) { - if (result.getData() != null && result.getData().getData() != null) { - Uri uri = result.getData().getData(); - - BackupFileValidator.isValidBackupFile(this, uri, new BackupFileValidator.BackupValidatorCallback() { - @Override - public void onValid(String fileName) { - presenter.readFileBackupLocal(uri); - } - - @Override - public void onInvalid(String errorMessage) { - onInfoSnack(errorMessage, null, SnackBarInfo.Error, Snackbar.LENGTH_LONG); - } - }); - - } - } - }); + @Inject public BackupContract.presenter presenter; + + /** Save local backup intent */ + private final ActivityResultLauncher intentExportDevice = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + if (result.getData() != null) { + Uri uri = result.getData().getData(); + presenter.writeFileBackupLocal(uri); + } + } + }); + + /** Restore local backup intent */ + private final ActivityResultLauncher intentImportDevice = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + if (result.getData() != null && result.getData().getData() != null) { + Uri uri = result.getData().getData(); + + BackupFileValidator.isValidBackupFile( + this, + uri, + new BackupFileValidator.BackupValidatorCallback() { + @Override + public void onValid(String fileName) { + presenter.readFileBackupLocal(uri); + } + + @Override + public void onInvalid(String errorMessage) { + onInfoSnack( + errorMessage, + null, + SnackBarInfo.Error, + Snackbar.LENGTH_LONG); + } + }); + } + } + }); + public ActivityBackupBinding binding; - ActivityResultLauncher launcherImportDrive = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { - Uri uri = result.getData().getData(); - if (uri != null) { - presenter.readFileBackupLocal(result.getData().getData()); - } else { - onInfoSnack(getString(R.string.file_not_selected), null, SnackBarInfo.Error, Snackbar.LENGTH_LONG); - } - } else { - onInfoSnack(getString(R.string.drive_selected_error), null, SnackBarInfo.Error, Snackbar.LENGTH_LONG); - } - } - ); - ActivityResultLauncher launcherExportDrive = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK) { - createLocalCopyFinish(true); - } else { - onInfoSnack(getString(R.string.export_drive_error), null, SnackBarInfo.Error, Snackbar.LENGTH_LONG); - } - } - ); + ActivityResultLauncher launcherImportDrive = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK + && result.getData() != null) { + Uri uri = result.getData().getData(); + if (uri != null) { + presenter.readFileBackupLocal(result.getData().getData()); + } else { + onInfoSnack( + getString(R.string.file_not_selected), + null, + SnackBarInfo.Error, + Snackbar.LENGTH_LONG); + } + } else { + onInfoSnack( + getString(R.string.drive_selected_error), + null, + SnackBarInfo.Error, + Snackbar.LENGTH_LONG); + } + }); + ActivityResultLauncher launcherExportDrive = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + createLocalCopyFinish(true); + } else { + onInfoSnack( + getString(R.string.export_drive_error), + null, + SnackBarInfo.Error, + Snackbar.LENGTH_LONG); + } + }); private boolean restoreSuccess = false; private OtherAppImportDialog importDialog; private Dialog progressDialog; @@ -137,32 +154,36 @@ public void onCreate(Bundle savedInstanceState) { setupTabs(); - getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - setEnabled(finishActivity()); - } - }); + getOnBackPressedDispatcher() + .addCallback( + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + setEnabled(finishActivity()); + } + }); } private void setupTabs() { BackupPagerAdapter pagerAdapter = new BackupPagerAdapter(this); binding.viewPager.setAdapter(pagerAdapter); - new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> { - switch (position) { - case 0: - tab.setText(R.string.backup_and_export); - break; - case 1: - tab.setText(R.string.import_data); - break; - } - }).attach(); - + new TabLayoutMediator( + binding.tabLayout, + binding.viewPager, + (tab, position) -> { + switch (position) { + case 0: + tab.setText(R.string.backup_and_export); + break; + case 1: + tab.setText(R.string.import_data); + break; + } + }) + .attach(); } - @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_activity_toolbar, menu); @@ -191,7 +212,6 @@ private boolean finishActivity() { return true; } - @Override protected void onDestroy() { super.onDestroy(); @@ -200,100 +220,118 @@ protected void onDestroy() { } } - @Override public void initActivity() { setSupportActionBar(binding.toolbar); Objects.requireNonNull(getSupportActionBar()).setDisplayHomeAsUpEnabled(true); } - /** - * Save local backup (2/3) - start intent save json file - */ + /** Save local backup (2/3) - start intent save json file */ @Override public void openIntentSaveBackup(JsonBackup jsonBackup) { - BackupOptionsDialog.newInstance(new BackupOptionsCallback() { - @Override - public void onGoogleDrive() { - saveBackupToGoogleDrive(BackupActivity.this, - ScramblerBackupHelper.encodeString(jsonBackup), - new DriveProcess() { + BackupOptionsDialog.newInstance( + new BackupOptionsCallback() { @Override - public void onSuccess(Uri uri, String nameFile) { - // Формуємо інтент для Drive - Intent intent = new Intent(Intent.ACTION_SEND); - intent.setType("*/*"); - intent.putExtra(Intent.EXTRA_TITLE, FILE_NAME_BACKUP_MNBKN); - intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{ - "application/zip", - "application/octet-stream" - }); - intent.putExtra(Intent.EXTRA_STREAM, uri); - intent.putExtra(Intent.EXTRA_SUBJECT, FILE_NAME_BACKUP_MNBKN); - intent.setPackage(GOOGLE_DRIVE_PACKAGE); - intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - launcherExportDrive.launch(intent); + public void onGoogleDrive() { + saveBackupToGoogleDrive( + BackupActivity.this, + ScramblerBackupHelper.encodeString(jsonBackup), + new DriveProcess() { + @Override + public void onSuccess(Uri uri, String nameFile) { + // Формуємо інтент для Drive + Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("*/*"); + intent.putExtra( + Intent.EXTRA_TITLE, FILE_NAME_BACKUP_MNBKN); + intent.putExtra( + Intent.EXTRA_MIME_TYPES, + new String[] { + "application/zip", + "application/octet-stream" + }); + intent.putExtra(Intent.EXTRA_STREAM, uri); + intent.putExtra( + Intent.EXTRA_SUBJECT, + FILE_NAME_BACKUP_MNBKN); + intent.setPackage(GOOGLE_DRIVE_PACKAGE); + intent.addFlags( + Intent.FLAG_GRANT_READ_URI_PERMISSION); + launcherExportDrive.launch(intent); + } + + @Override + public void onError(String error) { + onInfoSnack( + error, + null, + SnackBarInfo.Error, + Snackbar.LENGTH_LONG); + } + }); } @Override - public void onError(String error) { - onInfoSnack(error, null, SnackBarInfo.Error, Snackbar.LENGTH_LONG); + public void onDeviceStorage() { + intentExportDevice.launch( + new Intent(Intent.ACTION_CREATE_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .putExtra( + Intent.EXTRA_TITLE, FILE_NAME_BACKUP_MNBKN) + .setType("*/*") + .putExtra( + Intent.EXTRA_MIME_TYPES, + new String[] { + "application/zip", + "application/octet-stream" + })); } - }); - } - - @Override - public void onDeviceStorage() { - intentExportDevice.launch(new Intent(Intent.ACTION_CREATE_DOCUMENT) - .addCategory(Intent.CATEGORY_OPENABLE) - .putExtra(Intent.EXTRA_TITLE, FILE_NAME_BACKUP_MNBKN) - .setType("*/*") - .putExtra(Intent.EXTRA_MIME_TYPES, new String[]{ - "application/zip", - "application/octet-stream" - })); - } - - }, false).show(getSupportFragmentManager(), "ShareOptionsDialog"); - - + }, + false) + .show(getSupportFragmentManager(), "ShareOptionsDialog"); } - /** - * Restore local backup (2/3) - start intent load json file - */ + /** Restore local backup (2/3) - start intent load json file */ @Override public void openIntentReadBackup() { - BackupOptionsDialog.newInstance(new BackupOptionsCallback() { - @Override - public void onGoogleDrive() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.putExtra(Intent.EXTRA_TITLE, FILE_NAME_BACKUP_MNBKN); - intent.setType("*/*"); - intent.putExtra(Intent.EXTRA_MIME_TYPES, new String[]{ - "application/json", - "application/zip", - "application/octet-stream" - }); - intent.addCategory(Intent.CATEGORY_OPENABLE); - intent.setPackage(GOOGLE_DRIVE_PACKAGE); - launcherImportDrive.launch(intent); - } - - @Override - public void onDeviceStorage() { - intentImportDevice.launch(new Intent(Intent.ACTION_OPEN_DOCUMENT).addCategory(Intent.CATEGORY_OPENABLE).setType("*/*") - .putExtra(Intent.EXTRA_TITLE, FILE_NAME_BACKUP_MNBKN) - .putExtra(Intent.EXTRA_MIME_TYPES, new String[]{ - "application/json", - "application/zip", - "application/octet-stream" - })); - } - - }, true).show(getSupportFragmentManager(), "ShareOptionsDialog"); - + BackupOptionsDialog.newInstance( + new BackupOptionsCallback() { + @Override + public void onGoogleDrive() { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.putExtra(Intent.EXTRA_TITLE, FILE_NAME_BACKUP_MNBKN); + intent.setType("*/*"); + intent.putExtra( + Intent.EXTRA_MIME_TYPES, + new String[] { + "application/json", + "application/zip", + "application/octet-stream" + }); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setPackage(GOOGLE_DRIVE_PACKAGE); + launcherImportDrive.launch(intent); + } + @Override + public void onDeviceStorage() { + intentImportDevice.launch( + new Intent(Intent.ACTION_OPEN_DOCUMENT) + .addCategory(Intent.CATEGORY_OPENABLE) + .setType("*/*") + .putExtra( + Intent.EXTRA_TITLE, FILE_NAME_BACKUP_MNBKN) + .putExtra( + Intent.EXTRA_MIME_TYPES, + new String[] { + "application/json", + "application/zip", + "application/octet-stream" + })); + } + }, + true) + .show(getSupportFragmentManager(), "ShareOptionsDialog"); } @Override @@ -308,34 +346,55 @@ public void restoreFinish(int infoCode) { } switch (infoCode) { case CloudErrors.OKAY_RESTORE -> - onInfoSnack(R.string.restoreDataOkay, null, SnackBarInfo.Success, Snackbar.LENGTH_LONG); + onInfoSnack( + R.string.restoreDataOkay, + null, + SnackBarInfo.Success, + Snackbar.LENGTH_LONG); case CloudErrors.BACKUP_DESTROY -> - onInfoSnack(R.string.restoreDataFall, null, SnackBarInfo.Error, Snackbar.LENGTH_LONG); + onInfoSnack( + R.string.restoreDataFall, + null, + SnackBarInfo.Error, + Snackbar.LENGTH_LONG); case CloudErrors.NETWORK_ERROR -> - onInfoSnack(R.string.errorDriveSync, null, SnackBarInfo.Error, Snackbar.LENGTH_LONG); + onInfoSnack( + R.string.errorDriveSync, + null, + SnackBarInfo.Error, + Snackbar.LENGTH_LONG); default -> Log.w("BACKUP_ACTIVITY", "Unknown restore result code: " + infoCode); } } - @Override public void dialogRestoreData(boolean local) { - final MaterialAlertDialogBuilder restoreDialog = new MaterialAlertDialogBuilder(this).setCancelable(false).setTitle(R.string.restoreNotesTitle).setMessage(R.string.restoreNotesMessage).setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()).setPositiveButton(local ? R.string.selectRestore : R.string.nextRestore, (dialog, which) -> { - if (local) { - openIntentReadBackup(); - } - dialog.dismiss(); - }); + final MaterialAlertDialogBuilder restoreDialog = + new MaterialAlertDialogBuilder(this) + .setCancelable(false) + .setTitle(R.string.restoreNotesTitle) + .setMessage(R.string.restoreNotesMessage) + .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) + .setPositiveButton( + local ? R.string.selectRestore : R.string.nextRestore, + (dialog, which) -> { + if (local) { + openIntentReadBackup(); + } + dialog.dismiss(); + }); if (local) { restoreDialog.create().show(); } } - @Override public void showProcessRestoreDialog() { - MaterialAlertDialogBuilder builder = new MaterialAlertDialogBuilder(this, R.style.progressDialogRestore).setCancelable(false).setView(R.layout.view_restore_data); + MaterialAlertDialogBuilder builder = + new MaterialAlertDialogBuilder(this, R.style.progressDialogRestore) + .setCancelable(false) + .setView(R.layout.view_restore_data); progressDialog = builder.create(); progressDialog.show(); } @@ -348,15 +407,16 @@ public void emptyDataToBackup() { @Override public void createLocalCopyFinish(boolean error) { if (error) { - onInfoSnack(R.string.creteLocalCopyOkay, null, SnackBarInfo.Success, Snackbar.LENGTH_LONG); + onInfoSnack( + R.string.creteLocalCopyOkay, null, SnackBarInfo.Success, Snackbar.LENGTH_LONG); } else { - onInfoSnack(R.string.creteLocalCopyFail, null, SnackBarInfo.Error, Snackbar.LENGTH_LONG); + onInfoSnack( + R.string.creteLocalCopyFail, null, SnackBarInfo.Error, Snackbar.LENGTH_LONG); } } @Override - public void initListeners() { - } + public void initListeners() {} @Override public void openShareOptionsDialog(java.util.List notes, boolean isDataExport) { @@ -364,14 +424,12 @@ public void openShareOptionsDialog(java.util.List notes, boolean isDataExp shareOptionsDialog.show(getSupportFragmentManager(), "ShareOptionsDialog"); } - @Override public void processSelectedFileOtherApp(Uri fileUri) { importDialog = OtherAppImportDialog.newInstance(); importDialog.show(getSupportFragmentManager(), "import_progress"); importDialog.updateProgress(true); presenter.importFromZipOtherApp(fileUri); - } @Override @@ -383,5 +441,4 @@ public void showImportResultOtherApp(GoogleKeepImportResult result) { public void setupImportCallbackOtherApp(GoogleKeepImportResult result) { importDialog.setCallback(importResult -> presenter.importDataOtherApp(importResult)); } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/ChangelogActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/ChangelogActivity.java index 22bcb55..454725a 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/ChangelogActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/ChangelogActivity.java @@ -5,34 +5,27 @@ import android.util.Log; import android.view.MenuItem; import android.view.View; - import com.google.android.material.transition.platform.MaterialFade; import com.pasich.mynotes.R; import com.pasich.mynotes.base.activity.BaseActivity; import com.pasich.mynotes.databinding.ActivityChangelogBinding; import com.pasich.mynotes.utils.UpdateChecker; import com.pasich.mynotes.utils.changelog.ChangelogManager; - +import dagger.hilt.android.AndroidEntryPoint; +import io.noties.markwon.Markwon; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; -import io.noties.markwon.Markwon; - @AndroidEntryPoint public class ChangelogActivity extends BaseActivity { public ActivityChangelogBinding binding; - @Inject - Markwon markwon; - @Inject - ChangelogManager changelogManager; - @Inject - UpdateChecker updateChecker; + @Inject Markwon markwon; + @Inject ChangelogManager changelogManager; + @Inject UpdateChecker updateChecker; private ExecutorService executor; @Override @@ -47,16 +40,15 @@ public void onCreate(Bundle savedInstanceState) { initActivity(); initListeners(); loadLocalChangelog(); - } @Override public void initListeners() { - binding.acknowledgeButton.setOnClickListener(v -> { - changelogManager.markChangelogRead(); - finish(); - }); - + binding.acknowledgeButton.setOnClickListener( + v -> { + changelogManager.markChangelogRead(); + finish(); + }); } private void initActivity() { @@ -71,14 +63,12 @@ private void updateAcknowledgeButtonVisibility() { binding.acknowledgeButton.setVisibility(show ? View.VISIBLE : View.GONE); } - private void loadLocalChangelog() { String content = changelogManager.readRawChangelog(); showContent(content); } - private void showContent(String content) { binding.scrollView.setVisibility(View.VISIBLE); @@ -87,19 +77,19 @@ private void showContent(String content) { updateAcknowledgeButtonVisibility(); - executor.execute(() -> { - Spanned markdown = markwon.toMarkdown(content); - - runOnUiThread(() -> { - if (!isFinishing()) { - markwon.setParsedMarkdown(binding.changelogText, markdown); - } - }); - }); - + executor.execute( + () -> { + Spanned markdown = markwon.toMarkdown(content); + + runOnUiThread( + () -> { + if (!isFinishing()) { + markwon.setParsedMarkdown(binding.changelogText, markdown); + } + }); + }); } - @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { @@ -108,7 +98,6 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } - @Override protected void onDestroy() { super.onDestroy(); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/HelpActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/HelpActivity.java index 755ebe9..7cf0094 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/HelpActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/HelpActivity.java @@ -1,32 +1,29 @@ package com.pasich.mynotes.ui.view.activity; import android.os.Bundle; - import androidx.activity.OnBackPressedCallback; import androidx.recyclerview.widget.LinearLayoutManager; - import com.pasich.mynotes.R; import com.pasich.mynotes.base.activity.BaseActivity; import com.pasich.mynotes.data.model.HelpSection; import com.pasich.mynotes.databinding.ActivityHelpBinding; import com.pasich.mynotes.utils.adapters.HelpSectionAdapter; import com.pasich.mynotes.utils.recycler.SpacesItemDecoration; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.ArrayList; import java.util.List; - import javax.inject.Inject; import javax.inject.Named; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class HelpActivity extends BaseActivity { - private final String actualVersionHelp = "2.4.42"; + private final String actualVersionHelp = "2.6.46"; + @Inject @Named("NotesItemSpaceDecoration") public SpacesItemDecoration itemDecoration; + private ActivityHelpBinding binding; @Override @@ -40,12 +37,14 @@ public void onCreate(Bundle savedInstanceState) { setupRecyclerView(); loadHelpContent(); - getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - finish(); - } - }); + getOnBackPressedDispatcher() + .addCallback( + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + finish(); + } + }); } private void setupToolbar() { @@ -55,13 +54,13 @@ private void setupToolbar() { } private void setupRecyclerView() { - LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); + LinearLayoutManager layoutManager = + new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false); binding.recyclerView.setLayoutManager(layoutManager); binding.recyclerView.addItemDecoration(itemDecoration); binding.recyclerView.setNestedScrollingEnabled(false); - } private void loadHelpContent() { @@ -74,234 +73,322 @@ private List createHelpSections() { List sections = new ArrayList<>(); // Заголовок довідки - sections.add(new HelpSection( - HelpSection.TYPE_HEADER, - getString(R.string.help_title), - getString(R.string.help_subtitle), - null, actualVersionHelp - )); + sections.add( + new HelpSection( + HelpSection.TYPE_HEADER, + getString(R.string.help_title), + getString(R.string.help_subtitle), + null, + actualVersionHelp)); // Розділ навігації (перший) - sections.add(new HelpSection( - HelpSection.TYPE_SECTION_TITLE, - getString(R.string.help_navigation_title), - null, - R.drawable.ic_navigation_help, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_SECTION_TITLE, + getString(R.string.help_navigation_title), + null, + R.drawable.ic_navigation_help, + null)); // Головне меню - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_navigation_drawer_title), - getString(R.string.help_navigation_drawer_description), - R.drawable.ic_menu, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_navigation_drawer_title), + getString(R.string.help_navigation_drawer_description), + R.drawable.ic_menu, + null)); // Пошук - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_navigation_search_title), - getString(R.string.help_navigation_search_description), - R.drawable.ic_search, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_navigation_search_title), + getString(R.string.help_navigation_search_description), + R.drawable.ic_search, + null)); // Сортування - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_navigation_sort_title), - getString(R.string.help_navigation_sort_description), - R.drawable.ic_sort, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_navigation_sort_title), + getString(R.string.help_navigation_sort_description), + R.drawable.ic_sort, + null)); // Форматування - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_navigation_format_title), - getString(R.string.help_navigation_format_description), - R.drawable.ic_edit_format_tiles, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_navigation_format_title), + getString(R.string.help_navigation_format_description), + R.drawable.ic_edit_format_tiles, + null)); // Жести свайп - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_navigation_swipe_title), - getString(R.string.help_navigation_swipe_description), - R.drawable.ic_gesture, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_navigation_swipe_title), + getString(R.string.help_navigation_swipe_description), + R.drawable.ic_gesture, + null)); // Розділ про теги (другий) - sections.add(new HelpSection( - HelpSection.TYPE_SECTION_TITLE, - getString(R.string.help_tags_title), - null, - R.drawable.ic_tag_help, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_SECTION_TITLE, + getString(R.string.help_tags_title), + null, + R.drawable.ic_tag_help, + null)); // Створення тегів - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_tags_func_title), - getString(R.string.help_tags_func_description), - R.drawable.ic_tag, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_tags_func_title), + getString(R.string.help_tags_func_description), + R.drawable.ic_tag, + null)); // Редагування тегів - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_tags_edit_title), - getString(R.string.help_tags_edit_description), - R.drawable.ic_edit, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_tags_edit_title), + getString(R.string.help_tags_edit_description), + R.drawable.ic_edit, + null)); // Приховування нотаток - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_tags_hide_title), - getString(R.string.help_tags_hide_description), - R.drawable.ic_visibility_off, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_tags_hide_title), + getString(R.string.help_tags_hide_description), + R.drawable.ic_visibility_off, + null)); // Обмеження тегів - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_tags_limit_title), - getString(R.string.help_tags_limit_description), - R.drawable.ic_info, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_tags_limit_title), + getString(R.string.help_tags_limit_description), + R.drawable.ic_info, + null)); // Огляд тегів - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.helpTitleTagOverview), - getString(R.string.helpDescTagOverview), - R.drawable.ic_overview, - null - )); - + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.helpTitleTagOverview), + getString(R.string.helpDescTagOverview), + R.drawable.ic_overview, + null)); // Розділ "Функції нотаток" - sections.add(new HelpSection( - HelpSection.TYPE_SECTION_TITLE, - getString(R.string.help_notes_title), - null, - R.drawable.ic_notebook, - null - )); - - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_extended_editor_title), - getString(R.string.help_extended_editor_descrpt), - R.drawable.ic_write, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_SECTION_TITLE, + getString(R.string.help_notes_title), + null, + R.drawable.ic_notebook, + null)); + + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_extended_editor_title), + getString(R.string.help_extended_editor_descrpt), + R.drawable.ic_write, + null)); // Автозбереження - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_notes_autosave_title), - getString(R.string.help_notes_autosave_description), - R.drawable.ic_save_success, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_notes_autosave_title), + getString(R.string.help_notes_autosave_description), + R.drawable.ic_save_success, + null)); // Обмеження тексту - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_notes_limit_title), - getString(R.string.help_notes_limit_description), - R.drawable.ic_text_fields, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_notes_limit_title), + getString(R.string.help_notes_limit_description), + R.drawable.ic_text_fields, + null)); // Переклад - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_notes_translate_title), - getString(R.string.help_notes_translate_description), - R.drawable.ic_translate, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_notes_translate_title), + getString(R.string.help_notes_translate_description), + R.drawable.ic_translate, + null)); // Розмір тексту - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_notes_text_size_title), - getString(R.string.help_notes_text_size_description), - R.drawable.ic_text_size, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_notes_text_size_title), + getString(R.string.help_notes_text_size_description), + R.drawable.ic_text_size, + null)); // Редагувати копію - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_notes_copy_title), - getString(R.string.help_notes_copy_description), - R.drawable.ic_copy, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_notes_copy_title), + getString(R.string.help_notes_copy_description), + R.drawable.ic_copy, + null)); // Поділитися та експорт - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_notes_share_title), - getString(R.string.help_notes_share_description), - R.drawable.ic_share_app, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_notes_share_title), + getString(R.string.help_notes_share_description), + R.drawable.ic_share_app, + null)); + + // Розділ завдань + sections.add( + new HelpSection( + HelpSection.TYPE_SECTION_TITLE, + getString(R.string.help_tasks_title), + null, + R.drawable.ic_tasks, + null)); + + // Створення завдань + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_tasks_create_title), + getString(R.string.help_tasks_create_description), + R.drawable.ic_add, + null)); + + // Категорії + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_tasks_categories_title), + getString(R.string.help_tasks_categories_description), + R.drawable.ic_add_category, + null)); + + // Виконання завдань + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_tasks_complete_title), + getString(R.string.help_tasks_complete_description), + R.drawable.ic_check_circle, + null)); + + // Нагадування для завдань + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_tasks_reminder_title), + getString(R.string.help_tasks_reminder_description), + R.drawable.ic_bell, + null)); + + // Розділ нагадувань + sections.add( + new HelpSection( + HelpSection.TYPE_SECTION_TITLE, + getString(R.string.help_reminders_title), + null, + R.drawable.ic_bell, + null)); + + // Нагадування для нотаток + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_reminders_notes_title), + getString(R.string.help_reminders_notes_description), + R.drawable.ic_bell, + null)); + + // Параметри повторення + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_reminders_repeat_title), + getString(R.string.help_reminders_repeat_description), + R.drawable.ic_info, + null)); + + // Інтервальні сповіщення + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_reminders_interval_title), + getString(R.string.help_reminders_interval_description), + R.drawable.ic_bell_small, + null)); + + // Звук і гучність сповіщень + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_reminders_sound_title), + getString(R.string.help_reminders_sound_description), + R.drawable.ic_info, + null)); // Розділ "Інше" - sections.add(new HelpSection( - HelpSection.TYPE_SECTION_TITLE, - getString(R.string.help_other_title), - null, - R.drawable.ic_other, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_SECTION_TITLE, + getString(R.string.help_other_title), + null, + R.drawable.ic_other, + null)); // Захист екрану - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_other_security_title), - getString(R.string.help_other_security_description), - R.drawable.ic_security, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_other_security_title), + getString(R.string.help_other_security_description), + R.drawable.ic_security, + null)); // App Shortcuts - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_other_shortcuts_title), - getString(R.string.help_other_shortcuts_description), - R.drawable.ic_button_help, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_other_shortcuts_title), + getString(R.string.help_other_shortcuts_description), + R.drawable.ic_button_help, + null)); // Синхронізація та експорт даних - sections.add(new HelpSection( - HelpSection.TYPE_FEATURE, - getString(R.string.help_other_backup_export_title), - getString(R.string.help_other_backup_export_description), - R.drawable.ic_export, - null - )); + sections.add( + new HelpSection( + HelpSection.TYPE_FEATURE, + getString(R.string.help_other_backup_export_title), + getString(R.string.help_other_backup_export_description), + R.drawable.ic_export, + null)); return sections; } @Override - public void initListeners() { - } - + public void initListeners() {} } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/LibsActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/LibsActivity.java index 0620623..61a25e6 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/LibsActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/LibsActivity.java @@ -1,20 +1,15 @@ package com.pasich.mynotes.ui.view.activity; import android.os.Bundle; - import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; - import com.pasich.mynotes.R; import com.pasich.mynotes.base.activity.BaseActivity; import com.pasich.mynotes.data.model.lib.LibItem; import com.pasich.mynotes.data.model.lib.LibSection; import com.pasich.mynotes.databinding.ActivityLibsBinding; import com.pasich.mynotes.utils.adapters.LibsSectionAdapter; - -import org.json.JSONArray; -import org.json.JSONObject; - +import dagger.hilt.android.AndroidEntryPoint; import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.util.ArrayList; @@ -24,8 +19,8 @@ import java.util.List; import java.util.Map; import java.util.Objects; - -import dagger.hilt.android.AndroidEntryPoint; +import org.json.JSONArray; +import org.json.JSONObject; @AndroidEntryPoint public class LibsActivity extends BaseActivity { @@ -58,11 +53,7 @@ public boolean onSupportNavigateUp() { return true; } - - /** - * Reads the generated libs.json file from raw/ - * and converts it into a LibItem list. - */ + /** Reads the generated libs.json file from raw/ and converts it into a LibItem list. */ private List loadLibsJson() { try { InputStream is = getResources().openRawResource(R.raw.libs); @@ -74,11 +65,11 @@ private List loadLibsJson() { for (int i = 0; i < arr.length(); i++) { JSONObject o = arr.getJSONObject(i); - list.add(new LibItem( - o.getString("id"), - o.optString("version", ""), - o.getString("source") - )); + list.add( + new LibItem( + o.getString("id"), + o.optString("version", ""), + o.getString("source"))); } return list; @@ -89,9 +80,8 @@ private List loadLibsJson() { } /** - * Builds sections for the UI. - * Groups dependencies by source (gradle, js, js-dev), - * sorts each section by library name. + * Builds sections for the UI. Groups dependencies by source (gradle, js, js-dev), sorts each + * section by library name. */ private List buildSections(List libs) { @@ -113,8 +103,7 @@ private List buildSections(List libs) { if (map.containsKey("gradle")) sections.add(new LibSection("Gradle Dependencies", map.get("gradle"))); - if (map.containsKey("js")) - sections.add(new LibSection("JS Dependencies", map.get("js"))); + if (map.containsKey("js")) sections.add(new LibSection("JS Dependencies", map.get("js"))); if (map.containsKey("js-dev")) sections.add(new LibSection("JS Dev Dependencies", map.get("js-dev"))); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java index cfd47a6..8e7a47b 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java @@ -10,7 +10,6 @@ import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; - import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; @@ -19,7 +18,6 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.StaggeredGridLayoutManager; - import com.google.android.material.snackbar.Snackbar; import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback; import com.pasich.mynotes.R; @@ -60,64 +58,61 @@ import com.pasich.mynotes.utils.recycler.SpacesItemDecoration; import com.pasich.mynotes.utils.recycler.SwipeToListNotesCallback; import com.pasich.mynotes.utils.tool.FormatListTool; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.ArrayList; import java.util.List; - import javax.inject.Inject; import javax.inject.Named; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class MainActivity extends BaseActivity implements MainContract.view { // Update theme listener - final private ActivityResultLauncher themeUpdateListener = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - Intent data = result.getData(); - if (result.getResultCode() == RESULT_CODE_THEME_UPDATE) { - assert data != null; - if (data.hasExtra(EXTRA_UPDATE_THEME_STYLE)) { - this.redrawActivity(); - } - } - }); + private final ActivityResultLauncher themeUpdateListener = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + Intent data = result.getData(); + if (result.getResultCode() == RESULT_CODE_THEME_UPDATE) { + assert data != null; + if (data.hasExtra(EXTRA_UPDATE_THEME_STYLE)) { + this.redrawActivity(); + } + } + }); public ActivityMainBinding mActivityBinding; - @Inject - public MainContract.presenter mainPresenter; - @Inject - public FormatListTool formatList; - @Inject - public TagsAdapter tagsAdapter; - @Inject - public StaggeredGridLayoutManager gridLayoutManager; + @Inject public MainContract.presenter mainPresenter; + @Inject public FormatListTool formatList; + @Inject public TagsAdapter tagsAdapter; + @Inject public StaggeredGridLayoutManager gridLayoutManager; + + @Inject public NoteAdapter mNoteAdapter; - @Inject - public NoteAdapter mNoteAdapter; @Named("TagsItemSpaceDecoration") @Inject public SpacesItemDecoration itemDecorationTags; + @Named("NotesItemSpaceDecoration") @Inject public SpacesItemDecoration itemDecorationNotes; - @Inject - public LinearLayoutManager mLinearLayoutManager; - @Inject - SearchNotesAdapter searchNotesAdapter; - @Inject - UpdateChecker updateChecker; - @Inject - ThemePreferencesCache themePreferencesCache; + + @Inject public LinearLayoutManager mLinearLayoutManager; + @Inject SearchNotesAdapter searchNotesAdapter; + @Inject UpdateChecker updateChecker; + @Inject ThemePreferencesCache themePreferencesCache; private SearchController searchController; private AppUpdateController appUpdateController; private NavigationController navigationController; - private final ActivityResultLauncher changelogLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() == Activity.RESULT_OK) { - boolean hasNewVersion = updateChecker.hasNewVersion(); - if (navigationController != null) - navigationController.updateNewVersionIndicator(hasNewVersion); - } - }); + private final ActivityResultLauncher changelogLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK) { + boolean hasNewVersion = updateChecker.hasNewVersion(); + if (navigationController != null) + navigationController.updateNewVersionIndicator(hasNewVersion); + } + }); private MainRenderListsController mainRenderListsController; private Tag currentSelectedTag = null; private List currentTags = new ArrayList<>(); @@ -138,50 +133,56 @@ public void onCreate(Bundle savedInstanceState) { mNoteAdapter.setSelectionController(selectionController); selectionController.setPanelMode(SelectionController.Mode.NORMAL); - mainPresenter.attachView(this); mainPresenter.viewIsReady(); mActivityBinding.setPresenter((MainPresenter) mainPresenter); - - searchController = new SearchController(mActivityBinding, searchNotesAdapter, new SearchController.Listener() { - @Override - public void onSearchOpen() { - mActivityBinding.listNotes.setNestedScrollingEnabled(false); - List userTags = new ArrayList<>(); - for (Tag t : currentTags) { - if (!SystemTagsManager.isSystemTag(t)) { - userTags.add(t); - } - } - searchController.setAvailableTags(userTags); - } - - @Override - public void onSearchClose() { - mActivityBinding.listNotes.setNestedScrollingEnabled(true); - } - - @Override - public void onSearchQuery(String query) { - mainPresenter.updateSearchQuery(query); - } - - @Override - public void onTagFilterChanged(String tagName) { - mainPresenter.updateSearchTagFilter(tagName); - } - }); + searchController = + new SearchController( + mActivityBinding, + searchNotesAdapter, + new SearchController.Listener() { + @Override + public void onSearchOpen() { + mActivityBinding.listNotes.setNestedScrollingEnabled(false); + List userTags = new ArrayList<>(); + for (Tag t : currentTags) { + if (!SystemTagsManager.isSystemTag(t)) { + userTags.add(t); + } + } + searchController.setAvailableTags(userTags); + } + + @Override + public void onSearchClose() { + mActivityBinding.listNotes.setNestedScrollingEnabled(true); + } + + @Override + public void onSearchQuery(String query) { + mainPresenter.updateSearchQuery(query); + } + + @Override + public void onTagFilterChanged(String tagName) { + mainPresenter.updateSearchTagFilter(tagName); + } + }); appUpdateController = new AppUpdateController(this, updateChecker, changelogLauncher); appUpdateController.showChangelogIfNeeded(); - navigationController = new NavigationController(this, mActivityBinding, themeUpdateListener, appUpdateController, this::finishActivity); + navigationController = + new NavigationController( + this, + mActivityBinding, + themeUpdateListener, + appUpdateController, + this::finishActivity); navigationController.init(); navigationController.handleShortcuts(getIntent()); mainRenderListsController = new MainRenderListsController(mActivityBinding); - - } @Override @@ -197,7 +198,6 @@ public void render(MainViewState state) { renderTags(state.tags()); } - private void renderTags(List tags) { mainRenderListsController.renderListTags(tags); tagsAdapter.submitList(tags); @@ -207,19 +207,25 @@ private void renderTags(List tags) { private void renderNotes(List notes, Tag currentSelectedTag, UiEvent event) { switch (event) { case SORT_CHANGED, TAG_CHANGED -> - mNoteAdapter.submitList(new ArrayList<>(notes), () -> { - mainRenderListsController.showStateNoteList(currentSelectedTag, notes.size()); - mNoteAdapter.notifyDataSetChanged(); - mainRenderListsController.animateNoteListChange(); - }); + mNoteAdapter.submitList( + new ArrayList<>(notes), + () -> { + mainRenderListsController.showStateNoteList( + currentSelectedTag, notes.size()); + mNoteAdapter.notifyDataSetChanged(); + mainRenderListsController.animateNoteListChange(); + }); default -> { boolean shouldScrollUp = event == UiEvent.NOTE_CREATED; - mNoteAdapter.submitList(notes, () -> { - mainRenderListsController.showStateNoteList(currentSelectedTag, notes.size()); - if (shouldScrollUp) { - mainRenderListsController.scrollUpNoteList(); - } - }); + mNoteAdapter.submitList( + notes, + () -> { + mainRenderListsController.showStateNoteList( + currentSelectedTag, notes.size()); + if (shouldScrollUp) { + mainRenderListsController.scrollUpNoteList(); + } + }); } } @@ -229,13 +235,20 @@ private void renderNotes(List notes, Tag currentSelectedTag, UiEvent event @Override public void renderDrawerStats(StatsData stats) { - navigationController.getHeaderBinding().drawerStatsNotesNow.setText(String.valueOf(stats.notesNow())); - navigationController.getHeaderBinding().drawerStatsNotesMonth.setText(String.valueOf(stats.notesMonth())); - navigationController.getHeaderBinding().drawerStatsChars.setText(String.valueOf(stats.chars())); - + navigationController + .getHeaderBinding() + .drawerStatsNotesNow + .setText(String.valueOf(stats.notesNow())); + navigationController + .getHeaderBinding() + .drawerStatsNotesMonth + .setText(String.valueOf(stats.notesMonth())); + navigationController + .getHeaderBinding() + .drawerStatsChars + .setText(String.valueOf(stats.chars())); } - @Override protected void onNewIntent(@NonNull Intent intent) { super.onNewIntent(intent); @@ -243,7 +256,6 @@ protected void onNewIntent(@NonNull Intent intent) { navigationController.handleShortcuts(intent); } - @Override protected void onResume() { super.onResume(); @@ -265,8 +277,7 @@ public void multipleTagChangerDialog(List tagsList) { SingleTagSelectDialog.show( this, tagsList, - new SingleTagSelectDialog.Callback(){ - + new SingleTagSelectDialog.Callback() { @Override public void onTagSelected(String tag) { @@ -279,11 +290,9 @@ public void onTagSelected(String tag) { public void onCancel() { selectionController.clearSelection(); } - } - ); + }); } - @Override public void renderSearch(List filtered) { searchNotesAdapter.submitList(filtered); @@ -296,8 +305,9 @@ public void renderSearch(List filtered) { mActivityBinding.searchEmptyState.setVisibility(hasResults ? View.GONE : View.VISIBLE); if (!hasResults) { mActivityBinding.searchEmptyText.setText( - shortQuery ? R.string.search_hint_keep_typing : R.string.search_empty_no_results - ); + shortQuery + ? R.string.search_hint_keep_typing + : R.string.search_empty_no_results); } } @@ -305,89 +315,89 @@ public void renderSearch(List filtered) { public void initListeners() { mActivityBinding.actionSearch.setOnClickListener(v -> mActivityBinding.searchView.show()); searchNotesAdapter.setItemClickListener(this::openNoteEdit); - mActivityBinding.actionSearch.setOnMenuItemClickListener(menuItem -> { - int idItem = menuItem.getItemId(); - if (idItem == R.id.sort) { - if (!selectionController.isInSelectionMode()) showSortDialog(); - } else if (idItem == R.id.format) { - if (!selectionController.isInSelectionMode()) { - formatList.formatNote(menuItem); - gridLayoutManager.setSpanCount(mainPresenter.getDataManager().getFormatCount()); - } - } - - return true; - }); - - tagsAdapter.setOnItemClickListener(new OnItemClickListenerTag() { - @Override - public void onClick(int position) { - if (!selectionController.isInSelectionMode()) { - Tag clickedTag = tagsAdapter.getCurrentList().get(position); - if (clickedTag.getSelected()) { - shakeTagAt(position); - return; + mActivityBinding.actionSearch.setOnMenuItemClickListener( + menuItem -> { + int idItem = menuItem.getItemId(); + if (idItem == R.id.sort) { + if (!selectionController.isInSelectionMode()) showSortDialog(); + } else if (idItem == R.id.format) { + if (!selectionController.isInSelectionMode()) { + formatList.formatNote(menuItem); + gridLayoutManager.setSpanCount( + mainPresenter.getDataManager().getFormatCount()); + } } - mainPresenter.onTagSelected(clickedTag); - } - } - - @Override - public void onLongClick(int position, View mView) { - if (selectionController.isInSelectionMode()) return; - Tag tag = tagsAdapter.getCurrentList().get(position); - if (SystemTagsManager.isSystemTag(tag)) { - mainPresenter.requestTagSelection(false); - return; - } - - choiceTagDialog(tag, mView); - } - - }); - mNoteAdapter.setOnItemClickListener(new OnItemClickListener<>() { - @Override - public void onClick(int position, Note model) { - if (!selectionController.isInSelectionMode()) { - openNoteEdit(model, gridLayoutManager.findViewByPosition(position)); - } else selectionController.toggle(model); - - } - - @Override - public void onLongClick(int position, Note model) { - if (!selectionController.isInSelectionMode()) choiceNoteDialog(model, position); - } + return true; + }); - }); - selectionController.setListener(new SelectionController.Listener() { - @Override - public void onSelectionModeChanged(boolean active) { - mActivityBinding.newNotesButton.setVisibility(active ? View.GONE : View.VISIBLE); - } + tagsAdapter.setOnItemClickListener( + new OnItemClickListenerTag() { + @Override + public void onClick(int position) { + if (!selectionController.isInSelectionMode()) { + Tag clickedTag = tagsAdapter.getCurrentList().get(position); + if (clickedTag.getSelected()) { + shakeTagAt(position); + return; + } + mainPresenter.onTagSelected(clickedTag); + } + } - @Override - public void onDeleteRequested() { - deleteNotes(); - } + @Override + public void onLongClick(int position, View mView) { + if (selectionController.isInSelectionMode()) return; + Tag tag = tagsAdapter.getCurrentList().get(position); + if (SystemTagsManager.isSystemTag(tag)) { + mainPresenter.requestTagSelection(false); + return; + } + + choiceTagDialog(tag, mView); + } + }); - @Override - public void onChangeTagRequested() { - mainPresenter.requestTagSelection(true); + mNoteAdapter.setOnItemClickListener( + new OnItemClickListener<>() { + @Override + public void onClick(int position, Note model) { + if (!selectionController.isInSelectionMode()) { + openNoteEdit(model, gridLayoutManager.findViewByPosition(position)); + } else selectionController.toggle(model); + } - } + @Override + public void onLongClick(int position, Note model) { + if (!selectionController.isInSelectionMode()) + choiceNoteDialog(model, position); + } + }); + selectionController.setListener( + new SelectionController.Listener() { + @Override + public void onSelectionModeChanged(boolean active) { + mActivityBinding.newNotesButton.setVisibility( + active ? View.GONE : View.VISIBLE); + } - @Override - public void onShareRequested() { - shareNotes(); - } + @Override + public void onDeleteRequested() { + deleteNotes(); + } - }); + @Override + public void onChangeTagRequested() { + mainPresenter.requestTagSelection(true); + } + @Override + public void onShareRequested() { + shareNotes(); + } + }); } - private void shakeTagAt(int position) { assert mActivityBinding.listTags.getLayoutManager() != null; View tagView = mActivityBinding.listTags.getLayoutManager().findViewByPosition(position); @@ -399,18 +409,17 @@ private void shakeTagAt(int position) { void showSortDialog() { SortDialog dialog = SortDialog.newInstance(false); - dialog.setListener(new SortDialog.SortListener() { - @Override - public void onSortSelected(String sortParam) { - mainPresenter.onSortChanged(sortParam); - } + dialog.setListener( + new SortDialog.SortListener() { + @Override + public void onSortSelected(String sortParam) { + mainPresenter.onSortChanged(sortParam); + } - @Override - public void onTagsSortSelected(String tagsSortParam) { - } - }); + @Override + public void onTagsSortSelected(String tagsSortParam) {} + }); dialog.show(getSupportFragmentManager(), "SortDialog"); - } @Override @@ -423,32 +432,45 @@ public void settingsLists() { mActivityBinding.listNotes.setAdapter(mNoteAdapter); mActivityBinding.listNotes.setItemAnimator(new DefaultItemAnimator()); - new ItemTouchHelper(new SwipeToListNotesCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { - @Override - public boolean isItemViewSwipeEnabled() { - return !selectionController.isInSelectionMode() && mainPresenter.getDataManager().getFormatCount() == 1; - } - - @Override - public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) { - int position = viewHolder.getBindingAdapterPosition(); - - if (direction == ItemTouchHelper.LEFT) { - selectItemAction(mNoteAdapter.getCurrentList().get(position)); - mNoteAdapter.notifyItemChanged(position); - } else { - Note sNote = mNoteAdapter.getCurrentList().get(position); - mainPresenter.setBackupDeleteNote(sNote); - mainPresenter.noteMoveToTrash(sNote); - snackBarRestoreNote(); - } - } - }).attachToRecyclerView(mActivityBinding.listNotes); + new ItemTouchHelper( + new SwipeToListNotesCallback( + 0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { + @Override + public boolean isItemViewSwipeEnabled() { + return !selectionController.isInSelectionMode() + && mainPresenter.getDataManager().getFormatCount() == 1; + } + + @Override + public void onSwiped( + @NonNull RecyclerView.ViewHolder viewHolder, int direction) { + int position = viewHolder.getBindingAdapterPosition(); + + if (direction == ItemTouchHelper.LEFT) { + selectItemAction(mNoteAdapter.getCurrentList().get(position)); + mNoteAdapter.notifyItemChanged(position); + } else { + Note sNote = mNoteAdapter.getCurrentList().get(position); + mainPresenter.setBackupDeleteNote(sNote); + mainPresenter.noteMoveToTrash(sNote); + snackBarRestoreNote(); + } + } + }) + .attachToRecyclerView(mActivityBinding.listNotes); } public void snackBarRestoreNote() { - Snackbar snackbar = Snackbar.make(mActivityBinding.drawerLayout, getString(R.string.noteMoveTrashSnackbar), Snackbar.LENGTH_LONG); - snackbar.setAction(getString(R.string.restore), view -> mainPresenter.restoreNoteLastMoveToTrash(mainPresenter.getBackupDeleteNote())); + Snackbar snackbar = + Snackbar.make( + mActivityBinding.drawerLayout, + getString(R.string.noteMoveTrashSnackbar), + Snackbar.LENGTH_LONG); + snackbar.setAction( + getString(R.string.restore), + view -> + mainPresenter.restoreNoteLastMoveToTrash( + mainPresenter.getBackupDeleteNote())); snackbar.setAnchorView(mActivityBinding.newNotesButton); snackbar.show(); } @@ -460,9 +482,8 @@ public void actionStartNote(Note note, int position) { @Override public void openCopyNote(long idNote) { - new NoteNavigator(this, themePreferencesCache).openNote(idNote, false, "", null, String.valueOf(idNote), false); - - + new NoteNavigator(this, themePreferencesCache) + .openNote(idNote, false, "", null, String.valueOf(idNote), false); } @Override @@ -472,52 +493,62 @@ public void callbackDeleteNote(Note mNote) { } public void openNoteEdit(Note note, View view) { - new NoteNavigator(this, themePreferencesCache).openNote(note, false, "", view, String.valueOf(note.getId())); + new NoteNavigator(this, themePreferencesCache) + .openNote(note, false, "", view, String.valueOf(note.getId())); } - @Override public void openNewNoteWithId(long id) { Tag tagSelected = currentSelectedTag; - String tagName = tagSelected == null ? "" : tagSelected.getSystemAction() == 2 ? "" : tagSelected.getNameTag(); - - new NoteNavigator(this, themePreferencesCache).openNote(id, true, tagName, mActivityBinding.newNotesButton, NameTransition.fabTransaction, false); + String tagName = + tagSelected == null + ? "" + : tagSelected.getSystemAction() == 2 ? "" : tagSelected.getNameTag(); + + new NoteNavigator(this, themePreferencesCache) + .openNote( + id, + true, + tagName, + mActivityBinding.newNotesButton, + NameTransition.fabTransaction, + false); } @Override public void choiceTagDialog(Tag tag, View mView) { - new PopupWindowsTag(getLayoutInflater(), mView, tag, new PopupWindowsTagOnClickListener() { - @Override - public void deleteTag() { - mainPresenter.requestDeleteTag(tag); - } - - @Override - public void renameTag() { - new NameTagDialog(tag).show(getSupportFragmentManager(), "RenameTag"); - } + new PopupWindowsTag( + getLayoutInflater(), + mView, + tag, + new PopupWindowsTagOnClickListener() { + @Override + public void deleteTag() { + mainPresenter.requestDeleteTag(tag); + } - @Override - public void visibleEditTag() { - mainPresenter.editVisibleTag(tag.setVisibilityReturn(tag.getVisibility() == 1 ? 0 : 1)); - } - }); + @Override + public void renameTag() { + new NameTagDialog(tag).show(getSupportFragmentManager(), "RenameTag"); + } + @Override + public void visibleEditTag() { + mainPresenter.editVisibleTag( + tag.setVisibilityReturn(tag.getVisibility() == 1 ? 0 : 1)); + } + }); } @Override public void choiceNoteDialog(Note note, int position) { - MoreNoteDialog dialog = MoreNoteDialog.newInstance( - note.getId(), - MoreNoteDialog.RootActivity.MainActivity, - position - ); + MoreNoteDialog dialog = + MoreNoteDialog.newInstance( + note.getId(), MoreNoteDialog.RootActivity.MainActivity, position); dialog.show(getSupportFragmentManager(), "ChoiceDialog"); - } - private boolean finishActivity() { if (mActivityBinding.searchView.isShowing()) { mActivityBinding.searchView.hide(); @@ -531,15 +562,17 @@ private boolean finishActivity() { navigationController.addSwipeClose(1); if (navigationController.getSwipeClose() < 2) { if (!isDestroyed() && mActivityBinding != null) { - onInfoSnack(R.string.exitWhat, mActivityBinding.drawerLayout, SnackBarInfo.Info, Snackbar.LENGTH_LONG); + onInfoSnack( + R.string.exitWhat, + mActivityBinding.drawerLayout, + SnackBarInfo.Info, + Snackbar.LENGTH_LONG); } return false; } finish(); return true; - - } public void deleteNotes() { @@ -556,7 +589,6 @@ public void deleteNotes() { selectionController.clearSelection(); } - public void shareNotes() { List selected = selectionController.getSelectedNotes(); @@ -568,7 +600,6 @@ public void shareNotes() { selectionController.clearSelection(); } - public void selectItemAction(Note note) { if (!selectionController.isInSelectionMode()) { selectionController.startSelection(note); @@ -577,7 +608,6 @@ public void selectItemAction(Note note) { } } - @Override protected void onDestroy() { super.onDestroy(); @@ -617,11 +647,9 @@ private void variablesNull() { searchNotesAdapter = null; } - @Override public void redrawActivity() { super.redrawActivity(); recreate(); } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/PhotoViewActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/PhotoViewActivity.java index 53fc081..192f274 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/PhotoViewActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/PhotoViewActivity.java @@ -8,25 +8,22 @@ import android.view.Menu; import android.view.MenuItem; import android.widget.Toast; - import androidx.activity.OnBackPressedCallback; import androidx.annotation.NonNull; import androidx.core.content.FileProvider; - import com.pasich.mynotes.R; import com.pasich.mynotes.base.activity.BaseActivity; import com.pasich.mynotes.databinding.ActivityImageViewerBinding; import com.pasich.mynotes.extendedEditor.attach.AttachmentStorage; import com.pasich.mynotes.utils.file.SafeImageLoader; - import java.io.File; import java.util.Objects; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** - * Fullscreen viewer for image attachments stored in internal storage. - * Accepts the same URL string as stored in Editor.js/attachments JSON + * Fullscreen viewer for image attachments stored in internal storage. Accepts the same URL string + * as stored in Editor.js/attachments JSON */ public class PhotoViewActivity extends BaseActivity { @@ -36,7 +33,6 @@ public class PhotoViewActivity extends BaseActivity { private final ExecutorService executor = Executors.newSingleThreadExecutor(); private ActivityImageViewerBinding binding; - @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -59,12 +55,14 @@ public void onCreate(Bundle savedInstanceState) { loadImage(file); - getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - finish(); - } - }); + getOnBackPressedDispatcher() + .addCallback( + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + finish(); + } + }); } @Override @@ -89,14 +87,9 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { return super.onOptionsItemSelected(item); } - private void shareImage(File file) { try { - Uri uri = FileProvider.getUriForFile( - this, - getPackageName() + ".provider", - file - ); + Uri uri = FileProvider.getUriForFile(this, getPackageName() + ".provider", file); Intent share = new Intent(Intent.ACTION_SEND); share.setType("image/*"); @@ -111,27 +104,23 @@ private void shareImage(File file) { } private void loadImage(File file) { - executor.execute(() -> { - try { - Bitmap bmp = SafeImageLoader.load( - this, - file, - 1080, 1920 - ); - - if (bmp == null) { - runOnUiThread(this::errorViewImage - ); - return; - } - - runOnUiThread(() -> binding.photoView.setImageBitmap(bmp)); - - } catch (Exception ex) { - Log.e(TAG, "Error loading bitmap", ex); - runOnUiThread(this::errorViewImage); - } - }); + executor.execute( + () -> { + try { + Bitmap bmp = SafeImageLoader.load(this, file, 1080, 1920); + + if (bmp == null) { + runOnUiThread(this::errorViewImage); + return; + } + + runOnUiThread(() -> binding.photoView.setImageBitmap(bmp)); + + } catch (Exception ex) { + Log.e(TAG, "Error loading bitmap", ex); + runOnUiThread(this::errorViewImage); + } + }); } private void errorViewImage() { @@ -145,6 +134,5 @@ protected void onDestroy() { } @Override - public void initListeners() { - } + public void initListeners() {} } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SettingsActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SettingsActivity.java index 9824f83..99c3382 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SettingsActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SettingsActivity.java @@ -10,11 +10,9 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; - import androidx.activity.OnBackPressedCallback; import androidx.fragment.app.Fragment; import androidx.viewpager2.widget.ViewPager2; - import com.google.android.material.color.MaterialColors; import com.google.android.material.tabs.TabLayout; import com.google.android.material.tabs.TabLayoutMediator; @@ -27,19 +25,16 @@ import com.pasich.mynotes.ui.view.fragment.settings.InterfaceSettingsFragment; import com.pasich.mynotes.ui.view.fragment.settings.MediaSettingsFragment; import com.pasich.mynotes.utils.adapters.SettingsPagerAdapter; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.Objects; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint -public class SettingsActivity extends BaseActivity implements InterfaceSettingsFragment.ThemeChangeListener { +public class SettingsActivity extends BaseActivity + implements InterfaceSettingsFragment.ThemeChangeListener { public ActivitySettingsBinding activitySettingsBinding; - @Inject - ThemePreferencesCache themePreferencesCache; + @Inject ThemePreferencesCache themePreferencesCache; private int themeIdStartActivity; private boolean themeDynamicStartActivity; private int themeModeStartActivity; @@ -65,13 +60,14 @@ public void onCreate(Bundle savedInstanceState) { int startIndex = getIntent().getIntExtra("startFragmentIndex", 0); initViewPager(startIndex); - getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - setEnabled(finishActivity()); - } - }); - + getOnBackPressedDispatcher() + .addCallback( + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + setEnabled(finishActivity()); + } + }); } private void initViewPager(int startIndex) { @@ -81,38 +77,42 @@ private void initViewPager(int startIndex) { tabLayout.setTabMode(TabLayout.MODE_SCROLLABLE); // Add margins between tabs - tabLayout.post(() -> { - ViewGroup tabStrip = (ViewGroup) tabLayout.getChildAt(0); - int margin = (int) (12 * getResources().getDisplayMetrics().density); - - for (int i = 0; i < tabStrip.getChildCount(); i++) { - View tabView = tabStrip.getChildAt(i); - ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) tabView.getLayoutParams(); - params.setMargins(margin, 0, margin, 0); - tabView.setLayoutParams(params); - - tabView.requestLayout(); - } - }); + tabLayout.post( + () -> { + ViewGroup tabStrip = (ViewGroup) tabLayout.getChildAt(0); + int margin = (int) (12 * getResources().getDisplayMetrics().density); + + for (int i = 0; i < tabStrip.getChildCount(); i++) { + View tabView = tabStrip.getChildAt(i); + ViewGroup.MarginLayoutParams params = + (ViewGroup.MarginLayoutParams) tabView.getLayoutParams(); + params.setMargins(margin, 0, margin, 0); + tabView.setLayoutParams(params); + + tabView.requestLayout(); + } + }); pagerAdapter = new SettingsPagerAdapter(this); viewPager.setAdapter(pagerAdapter); - new TabLayoutMediator(tabLayout, viewPager, - (tab, position) -> { - switch (position) { - case 0: - tab.setText(getString(R.string.interface_tab)); - break; - case 1: - tab.setText(getString(R.string.interaction_tab)); - break; - case 2: - tab.setText(getString(R.string.mediaTab)); - break; - } - } - ).attach(); + new TabLayoutMediator( + tabLayout, + viewPager, + (tab, position) -> { + switch (position) { + case 0: + tab.setText(getString(R.string.interface_tab)); + break; + case 1: + tab.setText(getString(R.string.interaction_tab)); + break; + case 2: + tab.setText(getString(R.string.mediaTab)); + break; + } + }) + .attach(); if (startIndex == 1) { activitySettingsBinding.viewPager.setCurrentItem(startIndex, true); @@ -164,8 +164,9 @@ private boolean finishActivity() { // If anything changed — send one flag if (hasChanges) { - setResult(RESULT_CODE_THEME_UPDATE, new Intent().putExtra(EXTRA_UPDATE_THEME_STYLE, true)); - + setResult( + RESULT_CODE_THEME_UPDATE, + new Intent().putExtra(EXTRA_UPDATE_THEME_STYLE, true)); } supportFinishAfterTransition(); @@ -187,13 +188,13 @@ public void onFontScaleChanged(float value) { fontScaleWasChanged = value; } - public void redrawSettingsActivity(int themeStyle) { super.redrawActivity(); setTheme(themeStyle); // Background - activitySettingsBinding.activitySettings.setBackgroundColor(MaterialColors.getColor(this, android.R.attr.colorBackground, Color.GRAY)); + activitySettingsBinding.activitySettings.setBackgroundColor( + MaterialColors.getColor(this, android.R.attr.colorBackground, Color.GRAY)); // Apply theme colors to tabs applyTabColors(); @@ -220,5 +221,4 @@ private void updateFragmentThemes() { } } } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/ShareActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/ShareActivity.java index a901694..7e09eb3 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/ShareActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/ShareActivity.java @@ -4,40 +4,30 @@ import android.net.Uri; import android.os.Bundle; import android.widget.Toast; - import androidx.appcompat.app.AppCompatActivity; - import com.pasich.mynotes.R; import com.pasich.mynotes.cache.ThemePreferencesCache; import com.pasich.mynotes.utils.navigation.NoteNavigator; import com.pasich.mynotes.utils.shareProcessors.ShareProcessor; import com.pasich.mynotes.utils.shareProcessors.SharedNoteCreator; - +import dagger.hilt.android.AndroidEntryPoint; import java.io.IOException; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; - - /** - * An activity that is a gateway to save a note via the save button - * or from text selection context menu + * An activity that is a gateway to save a note via the save button or from text selection context + * menu */ - @AndroidEntryPoint public class ShareActivity extends AppCompatActivity { - @Inject - SharedNoteCreator noteCreator; - @Inject - ThemePreferencesCache themePreferencesCache; + @Inject SharedNoteCreator noteCreator; + @Inject ThemePreferencesCache themePreferencesCache; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - Intent intent = getIntent(); try { @@ -65,15 +55,17 @@ public void onSuccess(String content) { @Override public void onError(IOException e) { - runOnUiThread(() -> { - Toast.makeText(ShareActivity.this, - "Error reading file", - Toast.LENGTH_LONG).show(); - finish(); - }); + runOnUiThread( + () -> { + Toast.makeText( + ShareActivity.this, + "Error reading file", + Toast.LENGTH_LONG) + .show(); + finish(); + }); } - } - ); + }); return; } @@ -84,60 +76,48 @@ public void onError(IOException e) { return; } - Toast.makeText(this, - getString(R.string.notSupportedShare), - Toast.LENGTH_LONG).show(); + Toast.makeText(this, getString(R.string.notSupportedShare), Toast.LENGTH_LONG).show(); } catch (Exception e) { - Toast.makeText(this, - "Error processing shared content", - Toast.LENGTH_LONG).show(); + Toast.makeText(this, "Error processing shared content", Toast.LENGTH_LONG).show(); } finish(); } - - /** - * Creates a note in the database and opens the editor - */ + /** Creates a note in the database and opens the editor */ private void createNoteAndOpen(String text) { if (text == null) text = ""; if (ShareProcessor.isTooLarge(text)) { - Toast.makeText(this, - "Text is too large to share", - Toast.LENGTH_LONG).show(); + Toast.makeText(this, "Text is too large to share", Toast.LENGTH_LONG).show(); finish(); return; } // Creating a note using SharedNoteCreator - noteCreator.create(text, new SharedNoteCreator.Callback() { - @Override - public void onCreated(long id) { - openEditor(id); - } - - @Override - public void onError(Throwable error) { - Toast.makeText(ShareActivity.this, - "Failed to save note", - Toast.LENGTH_LONG).show(); - finish(); - } - }); + noteCreator.create( + text, + new SharedNoteCreator.Callback() { + @Override + public void onCreated(long id) { + openEditor(id); + } + + @Override + public void onError(Throwable error) { + Toast.makeText(ShareActivity.this, "Failed to save note", Toast.LENGTH_LONG) + .show(); + finish(); + } + }); } - - /** - * Opens NoteActivity with an already created note - */ + /** Opens NoteActivity with an already created note */ private void openEditor(long noteId) { new NoteNavigator(this, themePreferencesCache) - .openNote(noteId, true, "", - null, null, false); + .openNote(noteId, true, "", null, null, false); finish(); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java index 384c449..f4117ab 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java @@ -3,18 +3,14 @@ import android.content.Intent; import android.os.Bundle; import android.view.View; - import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NotificationManagerCompat; - import com.google.android.material.bottomsheet.BottomSheetDialog; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.ReminderRepeat; import com.pasich.mynotes.utils.reminder.ReminderManager; - -import java.util.Calendar; - import dagger.hilt.android.AndroidEntryPoint; +import java.util.Calendar; @AndroidEntryPoint public class SnoozeActivity extends AppCompatActivity { @@ -28,11 +24,14 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Intent intent = getIntent(); - noteId = intent.getIntExtra(ReminderManager.EXTRA_NOTE_ID, -1); - noteTitle = intent.getStringExtra(ReminderManager.EXTRA_NOTE_TITLE); + noteId = intent.getIntExtra(ReminderManager.EXTRA_NOTE_ID, -1); + noteTitle = intent.getStringExtra(ReminderManager.EXTRA_NOTE_TITLE); notePreview = intent.getStringExtra(ReminderManager.EXTRA_NOTE_PREVIEW); - if (noteId == -1) { finish(); return; } + if (noteId == -1) { + finish(); + return; + } NotificationManagerCompat.from(this).cancel(noteId); showSnoozeSheet(); @@ -45,11 +44,23 @@ private void showSnoozeSheet() { dialog.setOnDismissListener(d -> finish()); view.findViewById(com.pasich.mynotes.R.id.snooze10min) - .setOnClickListener(v -> { snooze(10 * 60 * 1000L); dialog.dismiss(); }); + .setOnClickListener( + v -> { + snooze(10 * 60 * 1000L); + dialog.dismiss(); + }); view.findViewById(com.pasich.mynotes.R.id.snooze1hour) - .setOnClickListener(v -> { snooze(60 * 60 * 1000L); dialog.dismiss(); }); + .setOnClickListener( + v -> { + snooze(60 * 60 * 1000L); + dialog.dismiss(); + }); view.findViewById(com.pasich.mynotes.R.id.snoozeTomorrow) - .setOnClickListener(v -> { snoozeTomorrow(); dialog.dismiss(); }); + .setOnClickListener( + v -> { + snoozeTomorrow(); + dialog.dismiss(); + }); dialog.show(); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SupportActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SupportActivity.java index 6e55b94..ae471e3 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SupportActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SupportActivity.java @@ -9,11 +9,9 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.Toast; - import androidx.activity.OnBackPressedCallback; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; - import com.android.billingclient.api.Purchase; import com.google.android.material.bottomsheet.BottomSheetBehavior; import com.google.android.material.bottomsheet.BottomSheetDialog; @@ -25,12 +23,10 @@ import com.pasich.mynotes.databinding.ActivitySupportBinding; import com.pasich.mynotes.utils.adapters.DonationProductAdapter; import com.pasich.mynotes.utils.managers.BillingManager; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.List; import java.util.Objects; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class SupportActivity extends BaseActivity implements BillingManager.BillingManagerListener { @@ -64,18 +60,23 @@ public void onCreate(Bundle savedInstanceState) { boolean autoOpen = getIntent().getBooleanExtra(PURCHASES_OPEN_EXTRA, false); if (autoOpen) { - binding.getRoot().postDelayed(() -> { - if (billingManager != null) { - showPurchasesBottomSheet(); - } - }, 300); + binding.getRoot() + .postDelayed( + () -> { + if (billingManager != null) { + showPurchasesBottomSheet(); + } + }, + 300); } - getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - setEnabled(finishActivity()); - } - }); + getOnBackPressedDispatcher() + .addCallback( + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + setEnabled(finishActivity()); + } + }); } @Override @@ -91,16 +92,11 @@ public void initListeners() { findViewById(R.id.donation_header), donationContent, donationArrow, - "donation", true - ); + "donation", + true); setupExpandableSection( - findViewById(R.id.contact_header), - contactContent, - contactArrow, - "contact", - false - ); + findViewById(R.id.contact_header), contactContent, contactArrow, "contact", false); } private void initBilling() { @@ -115,7 +111,8 @@ private void setupPurchasesBottomSheet() { purchasesBottomSheet = new BottomSheetDialog(this, R.style.M3BottomSheetAnim); purchasesBottomSheet.setContentView(bottomSheetView); - BottomSheetBehavior behavior = BottomSheetBehavior.from((View) bottomSheetView.getParent()); + BottomSheetBehavior behavior = + BottomSheetBehavior.from((View) bottomSheetView.getParent()); behavior.setState(BottomSheetBehavior.STATE_COLLAPSED); behavior.setSkipCollapsed(false); @@ -123,12 +120,14 @@ private void setupPurchasesBottomSheet() { bottomSheetLoadingView = bottomSheetView.findViewById(R.id.purchases_loading); bottomSheetEmptyView = bottomSheetView.findViewById(R.id.purchases_empty); - donationAdapter = new DonationProductAdapter(product -> { - if (billingManager != null) { - billingManager.launchBillingFlow(this, product.getId()); - purchasesBottomSheet.dismiss(); - } - }); + donationAdapter = + new DonationProductAdapter( + product -> { + if (billingManager != null) { + billingManager.launchBillingFlow(this, product.getId()); + purchasesBottomSheet.dismiss(); + } + }); bottomSheetRecyclerView.setLayoutManager(new LinearLayoutManager(this)); bottomSheetRecyclerView.setAdapter(donationAdapter); @@ -143,7 +142,12 @@ public void showPurchasesBottomSheet() { } } - private void setupExpandableSection(View header, LinearLayout content, ImageView arrow, String sectionId, boolean initiallyExpanded) { + private void setupExpandableSection( + View header, + LinearLayout content, + ImageView arrow, + String sectionId, + boolean initiallyExpanded) { if (header == null || content == null || arrow == null) { return; // Якщо елементи не знайдені, виходимо } @@ -152,22 +156,27 @@ private void setupExpandableSection(View header, LinearLayout content, ImageView content.setVisibility(initiallyExpanded ? View.VISIBLE : View.GONE); arrow.setRotation(initiallyExpanded ? 180f : 0f); - header.setOnClickListener(v -> { - boolean isExpanded = content.getVisibility() == View.VISIBLE; - if (!isExpanded) { - // Закриваємо всі інші спойлери і відкриваємо поточний - closeAllSectionsExcept(sectionId); - openSection(content, arrow); - } - // Якщо спойлер вже відкритий, не закриваємо його - }); + header.setOnClickListener( + v -> { + boolean isExpanded = content.getVisibility() == View.VISIBLE; + if (!isExpanded) { + // Закриваємо всі інші спойлери і відкриваємо поточний + closeAllSectionsExcept(sectionId); + openSection(content, arrow); + } + // Якщо спойлер вже відкритий, не закриваємо його + }); } private void closeAllSectionsExcept(String exceptSectionId) { - if (!"donation".equals(exceptSectionId) && donationContent != null && donationContent.getVisibility() == View.VISIBLE) { + if (!"donation".equals(exceptSectionId) + && donationContent != null + && donationContent.getVisibility() == View.VISIBLE) { closeSection(donationContent, donationArrow); } - if (!"contact".equals(exceptSectionId) && contactContent != null && contactContent.getVisibility() == View.VISIBLE) { + if (!"contact".equals(exceptSectionId) + && contactContent != null + && contactContent.getVisibility() == View.VISIBLE) { closeSection(contactContent, contactArrow); } } @@ -192,19 +201,21 @@ public void sendTelegram() { } public void sendEmail() { - Intent intent = new Intent(Intent.ACTION_SENDTO).setData(Uri.parse("mailto:pasichDev@outlook.com")); + Intent intent = + new Intent(Intent.ACTION_SENDTO).setData(Uri.parse("mailto:pasichDev@outlook.com")); if (intent.resolveActivity(getPackageManager()) != null) { startActivity(intent); } } - public void sendKoFi() { startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://ko-fi.com/pasichdev"))); } public void openPlayStore() { - final Intent rateAppIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("market://details?id=" + getPackageName())); + final Intent rateAppIntent = + new Intent( + Intent.ACTION_VIEW, Uri.parse("market://details?id=" + getPackageName())); if (!getPackageManager().queryIntentActivities(rateAppIntent, 0).isEmpty()) { startActivity(rateAppIntent); } else { @@ -241,30 +252,34 @@ public void onBillingInitialized() { @Override public void onProductsLoaded(List products) { - runOnUiThread(() -> { - if (bottomSheetLoadingView != null) { - bottomSheetLoadingView.setVisibility(View.GONE); - } - - if (products.isEmpty()) { - if (bottomSheetEmptyView != null) { - bottomSheetEmptyView.setVisibility(View.VISIBLE); - } - if (bottomSheetRecyclerView != null && bottomSheetRecyclerView.getParent() != null) { - ((View) bottomSheetRecyclerView.getParent()).setVisibility(View.GONE); - } - } else { - if (bottomSheetEmptyView != null) { - bottomSheetEmptyView.setVisibility(View.GONE); - } - if (bottomSheetRecyclerView != null && bottomSheetRecyclerView.getParent() != null) { - ((View) bottomSheetRecyclerView.getParent()).setVisibility(View.VISIBLE); - } - if (donationAdapter != null) { - donationAdapter.setProducts(products); - } - } - }); + runOnUiThread( + () -> { + if (bottomSheetLoadingView != null) { + bottomSheetLoadingView.setVisibility(View.GONE); + } + + if (products.isEmpty()) { + if (bottomSheetEmptyView != null) { + bottomSheetEmptyView.setVisibility(View.VISIBLE); + } + if (bottomSheetRecyclerView != null + && bottomSheetRecyclerView.getParent() != null) { + ((View) bottomSheetRecyclerView.getParent()).setVisibility(View.GONE); + } + } else { + if (bottomSheetEmptyView != null) { + bottomSheetEmptyView.setVisibility(View.GONE); + } + if (bottomSheetRecyclerView != null + && bottomSheetRecyclerView.getParent() != null) { + ((View) bottomSheetRecyclerView.getParent()) + .setVisibility(View.VISIBLE); + } + if (donationAdapter != null) { + donationAdapter.setProducts(products); + } + } + }); } @Override @@ -274,29 +289,34 @@ public void onPurchaseSuccessful(String productId) { @Override public void onPurchaseFailed(int responseCode, String debugMessage) { - runOnUiThread(() -> { - String errorMessage = BillingManager.getBillingErrorMessage(responseCode); - Snackbar.make(binding.getRoot(), - "Purchase error: " + errorMessage, - Snackbar.LENGTH_LONG).show(); - }); + runOnUiThread( + () -> { + String errorMessage = BillingManager.getBillingErrorMessage(responseCode); + Snackbar.make( + binding.getRoot(), + "Purchase error: " + errorMessage, + Snackbar.LENGTH_LONG) + .show(); + }); } @Override public void onBillingError(String errorMessage) { - runOnUiThread(() -> { - if (bottomSheetLoadingView != null) { - bottomSheetLoadingView.setVisibility(View.GONE); - } - if (bottomSheetEmptyView != null) { - bottomSheetEmptyView.setVisibility(View.VISIBLE); - } - if (bottomSheetRecyclerView != null && bottomSheetRecyclerView.getParent() != null) { - ((View) bottomSheetRecyclerView.getParent()).setVisibility(View.GONE); - } - - Snackbar.make(binding.getRoot(), errorMessage, Snackbar.LENGTH_LONG).show(); - }); + runOnUiThread( + () -> { + if (bottomSheetLoadingView != null) { + bottomSheetLoadingView.setVisibility(View.GONE); + } + if (bottomSheetEmptyView != null) { + bottomSheetEmptyView.setVisibility(View.VISIBLE); + } + if (bottomSheetRecyclerView != null + && bottomSheetRecyclerView.getParent() != null) { + ((View) bottomSheetRecyclerView.getParent()).setVisibility(View.GONE); + } + + Snackbar.make(binding.getRoot(), errorMessage, Snackbar.LENGTH_LONG).show(); + }); } @Override @@ -311,6 +331,10 @@ private void updateProductsWithPurchaseStatus() { } private void showPurchaseSuccessDialog() { - Snackbar.make(binding.getRoot(), "Purchase successful! Thank you for your support!", Snackbar.LENGTH_LONG).show(); + Snackbar.make( + binding.getRoot(), + "Purchase successful! Thank you for your support!", + Snackbar.LENGTH_LONG) + .show(); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TagsActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TagsActivity.java index 19afb0c..688b85e 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TagsActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TagsActivity.java @@ -7,10 +7,8 @@ import android.view.MenuItem; import android.view.View; import android.widget.Toast; - import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; - import com.pasich.mynotes.R; import com.pasich.mynotes.base.activity.BaseActivity; import com.pasich.mynotes.base.view.TagsSortView; @@ -25,20 +23,16 @@ import com.pasich.mynotes.utils.adapters.TagsManagementAdapter; import com.pasich.mynotes.utils.managers.SystemTagsManager; import com.pasich.mynotes.utils.recycler.TagDragCallback; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.List; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class TagsActivity extends BaseActivity implements TagsContract.view, TagsSortView { private ActivityTagsBinding binding; - @Inject - public TagsPresenter presenter; + @Inject public TagsPresenter presenter; private TagsManagementAdapter adapter; @@ -68,8 +62,7 @@ private boolean onMenuItemClick(MenuItem item) { } @Override - public void initListeners() { - } + public void initListeners() {} @Override public void setupRecyclerView() { @@ -84,23 +77,23 @@ public void setupRecyclerView() { ItemTouchHelper itemTouchHelper = new ItemTouchHelper(new TagDragCallback(adapter)); itemTouchHelper.attachToRecyclerView(binding.tagsRecyclerView); - adapter.setOnTagClickListener(new TagsManagementAdapter.OnTagClickListener() { - @Override - public void onTagClick(Tag tag, int position) { - if (SystemTagsManager.isAddTag(tag)) { - presenter.onAddTagClick(); - } - } - - @Override - public void onTagLongClick(Tag tag, View anchorView) { - } - - @Override - public void onOptionsClick(Tag tag, View anchorView) { - presenter.onTagLongClick(tag, anchorView); - } - }); + adapter.setOnTagClickListener( + new TagsManagementAdapter.OnTagClickListener() { + @Override + public void onTagClick(Tag tag, int position) { + if (SystemTagsManager.isAddTag(tag)) { + presenter.onAddTagClick(); + } + } + + @Override + public void onTagLongClick(Tag tag, View anchorView) {} + + @Override + public void onOptionsClick(Tag tag, View anchorView) { + presenter.onTagLongClick(tag, anchorView); + } + }); } @Override @@ -125,25 +118,31 @@ public void showDeleteTagDialog(Tag tag) { @Override public void showTagOptionsDialog(Tag tag, View anchorView) { - presenter.getTagNotesCount(tag, count -> { - TagOptionsBottomSheet bottomSheet = new TagOptionsBottomSheet(tag, count, new TagOptionsBottomSheet.TagOptionsListener() { - @Override - public void onDeleteTagClick(Tag tag) { - showDeleteTagDialog(tag); - } - - @Override - public void onRenameTagClick(Tag tag) { - showEditTagDialog(tag); - } - - @Override - public void onToggleVisibilityClick(Tag tag) { - presenter.toggleTagVisibility(tag); - } - }); - bottomSheet.show(getSupportFragmentManager(), "TagOptionsBottomSheet"); - }); + presenter.getTagNotesCount( + tag, + count -> { + TagOptionsBottomSheet bottomSheet = + new TagOptionsBottomSheet( + tag, + count, + new TagOptionsBottomSheet.TagOptionsListener() { + @Override + public void onDeleteTagClick(Tag tag) { + showDeleteTagDialog(tag); + } + + @Override + public void onRenameTagClick(Tag tag) { + showEditTagDialog(tag); + } + + @Override + public void onToggleVisibilityClick(Tag tag) { + presenter.toggleTagVisibility(tag); + } + }); + bottomSheet.show(getSupportFragmentManager(), "TagOptionsBottomSheet"); + }); } @Override @@ -159,10 +158,10 @@ public void showToastMessage(int message) { @SuppressLint("StringFormatMatches") @Override public void showToastCheckCountTags() { - Toast.makeText(this, getString(R.string.countTagsError, MAX_TAG_COUNT), Toast.LENGTH_LONG).show(); + Toast.makeText(this, getString(R.string.countTagsError, MAX_TAG_COUNT), Toast.LENGTH_LONG) + .show(); } - @Override protected void onDestroy() { super.onDestroy(); @@ -179,19 +178,16 @@ public void sortTags(String sortParam) { @Override public void showSortDialog() { SortDialog dialog = SortDialog.newInstance(true); - dialog.setListener(new SortDialog.SortListener() { - @Override - public void onSortSelected(String sortParam) { - } - - @Override - public void onTagsSortSelected(String tagsSortParam) { - sortTags(tagsSortParam); - } - }); + dialog.setListener( + new SortDialog.SortListener() { + @Override + public void onSortSelected(String sortParam) {} + + @Override + public void onTagsSortSelected(String tagsSortParam) { + sortTags(tagsSortParam); + } + }); dialog.show(getSupportFragmentManager(), "SortDialog"); - - } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java index 79928ea..809c0f0 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java @@ -2,15 +2,11 @@ import android.os.Bundle; import android.view.View; - import androidx.annotation.NonNull; import androidx.annotation.StringRes; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; - -import com.pasich.mynotes.ui.view.dialogs.TaskReminderPickerBottomSheet; - import com.google.android.material.chip.Chip; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; @@ -20,21 +16,18 @@ import com.pasich.mynotes.databinding.ActivityTasksBinding; import com.pasich.mynotes.databinding.DialogDeleteConfirmBinding; import com.pasich.mynotes.ui.contract.TasksContract; +import com.pasich.mynotes.ui.view.dialogs.TaskReminderPickerBottomSheet; import com.pasich.mynotes.ui.view.dialogs.tasks.AddCategoryDialog; import com.pasich.mynotes.ui.view.dialogs.tasks.AddTaskDialog; import com.pasich.mynotes.utils.adapters.tasks.TasksAdapter; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.List; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class TasksActivity extends BaseActivity implements TasksContract.view { - @Inject - public TasksContract.presenter tasksPresenter; + @Inject public TasksContract.presenter tasksPresenter; private ActivityTasksBinding binding; private TasksAdapter tasksAdapter; @@ -49,14 +42,19 @@ protected void onCreate(Bundle savedInstanceState) { binding = ActivityTasksBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - tasksAdapter = new TasksAdapter( - task -> tasksPresenter.toggleTask(task), - task -> showDeleteTaskDialog(task), - task -> showEditDialog(task), - () -> AddTaskDialog.show(this, selectedCategoryId, - (title, desc, catId) -> tasksPresenter.addTask(title, desc, catId)), - holder -> itemTouchHelper.startDrag(holder), - task -> showReminderPicker(task)); + tasksAdapter = + new TasksAdapter( + task -> tasksPresenter.toggleTask(task), + task -> showDeleteTaskDialog(task), + task -> showEditDialog(task), + () -> + AddTaskDialog.show( + this, + selectedCategoryId, + (title, desc, catId) -> + tasksPresenter.addTask(title, desc, catId)), + holder -> itemTouchHelper.startDrag(holder), + task -> showReminderPicker(task)); binding.tasksList.setLayoutManager(new LinearLayoutManager(this)); binding.tasksList.setAdapter(tasksAdapter); @@ -67,9 +65,12 @@ protected void onCreate(Bundle savedInstanceState) { tasksPresenter.attachView(this); tasksPresenter.viewIsReady(); - getSupportFragmentManager().setFragmentResultListener( - "taskReminderChanged", this, - (requestKey, result) -> tasksPresenter.onCategorySelected(selectedCategoryId)); + getSupportFragmentManager() + .setFragmentResultListener( + "taskReminderChanged", + this, + (requestKey, result) -> + tasksPresenter.onCategorySelected(selectedCategoryId)); } private ItemTouchHelper.SimpleCallback buildTouchCallback() { @@ -78,28 +79,29 @@ private ItemTouchHelper.SimpleCallback buildTouchCallback() { ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) { @Override - public boolean isLongPressDragEnabled() { return false; } + public boolean isLongPressDragEnabled() { + return false; + } @Override - public int getDragDirs(@NonNull RecyclerView rv, - @NonNull RecyclerView.ViewHolder vh) { + public int getDragDirs(@NonNull RecyclerView rv, @NonNull RecyclerView.ViewHolder vh) { if (!tasksAdapter.isActiveTask(vh.getAdapterPosition())) return 0; return super.getDragDirs(rv, vh); } @Override - public int getSwipeDirs(@NonNull RecyclerView rv, - @NonNull RecyclerView.ViewHolder vh) { + public int getSwipeDirs(@NonNull RecyclerView rv, @NonNull RecyclerView.ViewHolder vh) { if (!tasksAdapter.isActiveTask(vh.getAdapterPosition())) return 0; return super.getSwipeDirs(rv, vh); } @Override - public boolean onMove(@NonNull RecyclerView rv, - @NonNull RecyclerView.ViewHolder from, - @NonNull RecyclerView.ViewHolder to) { - boolean moved = tasksAdapter.moveItem( - from.getAdapterPosition(), to.getAdapterPosition()); + public boolean onMove( + @NonNull RecyclerView rv, + @NonNull RecyclerView.ViewHolder from, + @NonNull RecyclerView.ViewHolder to) { + boolean moved = + tasksAdapter.moveItem(from.getAdapterPosition(), to.getAdapterPosition()); if (moved) dragMoved = true; return moved; } @@ -120,8 +122,7 @@ public void onSwiped(@NonNull RecyclerView.ViewHolder vh, int direction) { } @Override - public void clearView(@NonNull RecyclerView rv, - @NonNull RecyclerView.ViewHolder vh) { + public void clearView(@NonNull RecyclerView rv, @NonNull RecyclerView.ViewHolder vh) { super.clearView(rv, vh); if (dragMoved) { dragMoved = false; @@ -139,8 +140,8 @@ private void showDeleteTaskDialog(Task task) { null); } - private void showDeleteDialog(@StringRes int titleRes, String name, - Runnable onDelete, Runnable onCancel) { + private void showDeleteDialog( + @StringRes int titleRes, String name, Runnable onDelete, Runnable onCancel) { DialogDeleteConfirmBinding dialogBinding = DialogDeleteConfirmBinding.inflate(getLayoutInflater()); dialogBinding.deleteConfirmTitle.setText(titleRes); @@ -148,25 +149,30 @@ private void showDeleteDialog(@StringRes int titleRes, String name, new MaterialAlertDialogBuilder(this) .setView(dialogBinding.getRoot()) - .setPositiveButton(R.string.delete, (d, w) -> { - if (onDelete != null) onDelete.run(); - }) - .setNegativeButton(R.string.cancel, (d, w) -> { - if (onCancel != null) onCancel.run(); - }) - .setOnCancelListener(d -> { - if (onCancel != null) onCancel.run(); - }) + .setPositiveButton( + R.string.delete, + (d, w) -> { + if (onDelete != null) onDelete.run(); + }) + .setNegativeButton( + R.string.cancel, + (d, w) -> { + if (onCancel != null) onCancel.run(); + }) + .setOnCancelListener( + d -> { + if (onCancel != null) onCancel.run(); + }) .show(); } private void showReminderPicker(Task task) { TaskReminderPickerBottomSheet.newInstance( - task.getId(), - task.getTitle(), - task.getReminderTime(), - task.getReminderIntervalMinutes() - ).show(getSupportFragmentManager(), "taskReminderPicker"); + task.getId(), + task.getTitle(), + task.getReminderTime(), + task.getReminderIntervalMinutes()) + .show(getSupportFragmentManager(), "taskReminderPicker"); } private void showEditDialog(Task task) { @@ -180,12 +186,14 @@ private void showEditDialog(Task task) { new MaterialAlertDialogBuilder(this) .setTitle(R.string.tasks_edit) .setView(view) - .setPositiveButton(android.R.string.ok, (d, w) -> { - String text = input.getText().toString().trim(); - String desc = descInput.getText().toString().trim(); - if (!text.isEmpty()) - tasksPresenter.editTask(task, text, desc.isEmpty() ? null : desc); - }) + .setPositiveButton( + android.R.string.ok, + (d, w) -> { + String text = input.getText().toString().trim(); + String desc = descInput.getText().toString().trim(); + if (!text.isEmpty()) + tasksPresenter.editTask(task, text, desc.isEmpty() ? null : desc); + }) .setNegativeButton(R.string.cancel, null) .show(); input.requestFocus(); @@ -194,14 +202,16 @@ private void showEditDialog(Task task) { @Override public void initListeners() { binding.tasksToolbar.setNavigationOnClickListener(v -> finish()); - binding.tasksToolbar.setOnMenuItemClickListener(item -> { - if (item.getItemId() == R.id.tasks_add_category) { - AddCategoryDialog.show(this, (name, colorHex) -> - tasksPresenter.addCategory(name, colorHex)); - return true; - } - return false; - }); + binding.tasksToolbar.setOnMenuItemClickListener( + item -> { + if (item.getItemId() == R.id.tasks_add_category) { + AddCategoryDialog.show( + this, + (name, colorHex) -> tasksPresenter.addCategory(name, colorHex)); + return true; + } + return false; + }); } @Override @@ -225,10 +235,11 @@ public void renderCategories(List categories) { allChip.setText(getString(R.string.search_filter_all)); allChip.setCheckable(true); allChip.setChecked(selectedCategoryId == 0); - allChip.setOnClickListener(v -> { - selectedCategoryId = 0; - tasksPresenter.onCategorySelected(0); - }); + allChip.setOnClickListener( + v -> { + selectedCategoryId = 0; + tasksPresenter.onCategorySelected(0); + }); binding.tasksCategoryChips.addView(allChip); for (TaskCategory cat : categories) { @@ -236,18 +247,20 @@ public void renderCategories(List categories) { chip.setText(cat.getName()); chip.setCheckable(true); chip.setChecked(selectedCategoryId == cat.getId()); - chip.setOnLongClickListener(v -> { - showDeleteDialog( - R.string.tasks_category_delete_confirm, - cat.getName(), - () -> tasksPresenter.deleteCategory(cat), - null); - return true; - }); - chip.setOnClickListener(v -> { - selectedCategoryId = cat.getId(); - tasksPresenter.onCategorySelected(cat.getId()); - }); + chip.setOnLongClickListener( + v -> { + showDeleteDialog( + R.string.tasks_category_delete_confirm, + cat.getName(), + () -> tasksPresenter.deleteCategory(cat), + null); + return true; + }); + chip.setOnClickListener( + v -> { + selectedCategoryId = cat.getId(); + tasksPresenter.onCategorySelected(cat.getId()); + }); binding.tasksCategoryChips.addView(chip); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TrashActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TrashActivity.java index 5194e12..41f4da3 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TrashActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TrashActivity.java @@ -4,9 +4,7 @@ import android.view.Menu; import android.view.MenuItem; import android.view.View; - import androidx.activity.OnBackPressedCallback; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; import com.pasich.mynotes.base.activity.BaseActivity; @@ -18,27 +16,24 @@ import com.pasich.mynotes.ui.view.dialogs.TrashInfoDialog; import com.pasich.mynotes.utils.adapters.notes.NoteAdapter; import com.pasich.mynotes.utils.recycler.SpacesItemDecoration; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.ArrayList; import java.util.List; import java.util.Objects; - import javax.inject.Inject; import javax.inject.Named; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class TrashActivity extends BaseActivity implements TrashContract.view { - @Inject - public TrashPresenter trashPresenter; + @Inject public TrashPresenter trashPresenter; public ActivityTrashBinding binding; - @Inject - public NoteAdapter mNotesTrashAdapter; + @Inject public NoteAdapter mNotesTrashAdapter; + @Named("NotesItemSpaceDecoration") @Inject public SpacesItemDecoration itemDecorationNotes; + private SelectionController selectionController; @Override @@ -52,23 +47,23 @@ public void onCreate(Bundle savedInstanceState) { mNotesTrashAdapter.setSelectionController(selectionController); selectionController.setPanelMode(SelectionController.Mode.RESTORE); - setupEdgeToEdgeInsets(binding.getRoot()); trashPresenter.attachView(this); trashPresenter.viewIsReady(); binding.setPresenter(trashPresenter); - getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - if (selectionController.isInSelectionMode()) { - selectionController.clearSelection(); - } else { - finishActivity(); - } - } - }); - + getOnBackPressedDispatcher() + .addCallback( + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + if (selectionController.isInSelectionMode()) { + selectionController.clearSelection(); + } else { + finishActivity(); + } + } + }); } @Override @@ -84,7 +79,6 @@ protected void onPause() { @Override public void initListeners() { mNotesTrashAdapter.setOnItemClickListener((position, model) -> selectItemAction(model)); - } @Override @@ -102,7 +96,6 @@ protected void onDestroy() { } } - @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(R.menu.menu_trash_toolbar, menu); @@ -125,7 +118,6 @@ public boolean onOptionsItemSelected(MenuItem item) { return true; } - private void finishActivity() { supportFinishAfterTransition(); } @@ -134,19 +126,18 @@ private void finishActivity() { public void settingsNotesList() { binding.ListTrash.addItemDecoration(itemDecorationNotes); binding.ListTrash.setAdapter(mNotesTrashAdapter); - selectionController.setListener(new SelectionController.Listener() { - @Override - public void onSelectionModeChanged(boolean active) { - binding.cleanTrash.setVisibility(active ? View.GONE : View.VISIBLE); - } - - @Override - public void onRestoreRequested() { - restoreNotes(); - } - - }); - + selectionController.setListener( + new SelectionController.Listener() { + @Override + public void onSelectionModeChanged(boolean active) { + binding.cleanTrash.setVisibility(active ? View.GONE : View.VISIBLE); + } + + @Override + public void onRestoreRequested() { + restoreNotes(); + } + }); } @Override @@ -155,7 +146,6 @@ public void loadData(List trashList) { if (trashList.isEmpty()) showEmptyTrash(); } - private void showEmptyTrash() { binding.setEmptyNotesTrash(true); if (getResources().getDisplayMetrics().density < 2.2) @@ -166,21 +156,25 @@ private void showEmptyTrash() { @Override public void cleanTrashDialogShow() { - new MaterialAlertDialogBuilder(this).setTitle(R.string.trashClean).setMessage(R.string.cleanTrashMessage).setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()).setPositiveButton(R.string.yesCleanTrash, (dialog, which) -> { - trashPresenter.clearTrash(); - dialog.dismiss(); - }).show(); - + new MaterialAlertDialogBuilder(this) + .setTitle(R.string.trashClean) + .setMessage(R.string.cleanTrashMessage) + .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) + .setPositiveButton( + R.string.yesCleanTrash, + (dialog, which) -> { + trashPresenter.clearTrash(); + dialog.dismiss(); + }) + .show(); } - public void restoreNotes() { trashPresenter.restoreNotesArray(new ArrayList<>(selectionController.getSelectedNotes())); selectionController.clearSelection(); } - public void selectItemAction(Note note) { if (!selectionController.isInSelectionMode()) { selectionController.startSelection(note); @@ -188,5 +182,4 @@ public void selectItemAction(Note note) { selectionController.toggle(note); } } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/BaseNoteEditorActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/BaseNoteEditorActivity.java index f70d4e8..f1c6eac 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/BaseNoteEditorActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/BaseNoteEditorActivity.java @@ -15,7 +15,6 @@ import android.view.MenuItem; import android.view.View; import android.view.Window; - import androidx.activity.OnBackPressedCallback; import androidx.annotation.MenuRes; import androidx.annotation.NonNull; @@ -23,7 +22,6 @@ import androidx.core.view.WindowCompat; import androidx.core.view.WindowInsetsControllerCompat; import androidx.viewbinding.ViewBinding; - import com.google.android.material.transition.platform.MaterialContainerTransformSharedElementCallback; import com.pasich.mynotes.R; import com.pasich.mynotes.base.activity.BaseActivity; @@ -36,15 +34,13 @@ import com.pasich.mynotes.utils.enums.SaveState; import com.pasich.mynotes.utils.navigation.NoteExtras; import com.pasich.mynotes.utils.transition.CopyNoteAnimationUtil; - import java.util.Objects; - import javax.inject.Inject; -public abstract class BaseNoteEditorActivity extends BaseActivity implements NoteContract.view { +public abstract class BaseNoteEditorActivity extends BaseActivity + implements NoteContract.view { - @Inject - protected NoteContract.presenter notePresenter; + @Inject protected NoteContract.presenter notePresenter; // Menu for the save status indicator protected MenuItem saveStatusMenuItem; @@ -53,16 +49,19 @@ public abstract class BaseNoteEditorActivity extends Base protected long idNote; protected abstract @MenuRes int getMenuResId(); + // Returns toolbar menu layout protected abstract Toolbar getToolbar(); - // Returns toolbar instance + // Returns toolbar instance protected abstract T inflateBinding(LayoutInflater inflater); + // Inflate and return view binding protected abstract void bindingSetPresenter(T binding); + // Bind presenter to layout (if required) protected void onAfterPresenterReady() { @@ -70,15 +69,16 @@ protected void onAfterPresenterReady() { } protected abstract void applyEdgeToEdgeInsets(View rootView); + // Apply window insets (IME/system bars) protected abstract void onNewNoteInit(Note note); - // Load a new/empty note into the editor + // Load a new/empty note into the editor protected abstract void setNewNoteTitle(); - // Set title for a new note + // Set title for a new note @Override public void onCreate(Bundle savedInstanceState) { @@ -102,19 +102,28 @@ public void onCreate(Bundle savedInstanceState) { onAfterPresenterReady(); - getSupportFragmentManager().setFragmentResultListener("reminderChanged", this, (key, result) -> { - if (notePresenter == null || !notePresenter.hasNote()) return; - boolean hasReminder = result.getBoolean("hasReminder", false); - notePresenter.getNote().setReminderTime(hasReminder ? result.getLong("reminderTime") : null); - updateReminderIcon(notePresenter.getNote()); - }); - - getOnBackPressedDispatcher().addCallback(new OnBackPressedCallback(true) { - @Override - public void handleOnBackPressed() { - notePresenter.closeActivity(); - } - }); + getSupportFragmentManager() + .setFragmentResultListener( + "reminderChanged", + this, + (key, result) -> { + if (notePresenter == null || !notePresenter.hasNote()) return; + boolean hasReminder = result.getBoolean("hasReminder", false); + notePresenter + .getNote() + .setReminderTime( + hasReminder ? result.getLong("reminderTime") : null); + updateReminderIcon(notePresenter.getNote()); + }); + + getOnBackPressedDispatcher() + .addCallback( + new OnBackPressedCallback(true) { + @Override + public void handleOnBackPressed() { + notePresenter.closeActivity(); + } + }); } @Override @@ -139,7 +148,6 @@ public void initTypeActivity() { } } - private void setupSharedTransition(View layout, long id) { layout.setTransitionName(String.valueOf(id)); setEnterSharedElementCallback(new MaterialContainerTransformSharedElementCallback()); @@ -147,7 +155,6 @@ private void setupSharedTransition(View layout, long id) { getWindow().setSharedElementReturnTransition(buildContainerTransform(layout)); } - @Override public boolean onCreateOptionsMenu(Menu menu) { getMenuInflater().inflate(getMenuResId(), menu); @@ -168,10 +175,13 @@ protected void updateReminderIcon(Note note) { if (reminderMenuItem == null || note == null) return; boolean hasReminder = note.hasReminder(); TypedValue tv = new TypedValue(); - getTheme().resolveAttribute( - hasReminder ? android.R.attr.colorPrimary - : com.google.android.material.R.attr.colorOnBackground, - tv, true); + getTheme() + .resolveAttribute( + hasReminder + ? android.R.attr.colorPrimary + : com.google.android.material.R.attr.colorOnBackground, + tv, + true); reminderMenuItem.setIconTintList(ColorStateList.valueOf(tv.data)); } @@ -180,9 +190,12 @@ protected void settingsStatusBar(Window window) { WindowCompat.setDecorFitsSystemWindows(window, false); // Control the color of status bar icons depending on night mode - final int currentNightMode = getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; - WindowInsetsControllerCompat insetsController = new WindowInsetsControllerCompat(window, window.getDecorView()); - insetsController.setAppearanceLightStatusBars(currentNightMode == Configuration.UI_MODE_NIGHT_NO); + final int currentNightMode = + getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; + WindowInsetsControllerCompat insetsController = + new WindowInsetsControllerCompat(window, window.getDecorView()); + insetsController.setAppearanceLightStatusBars( + currentNightMode == Configuration.UI_MODE_NIGHT_NO); // Transparent status bar window.setStatusBarColor(Color.TRANSPARENT); @@ -202,7 +215,6 @@ public void settingsActionBar() { public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == android.R.id.home) { notePresenter.closeActivity(); - } if (item.getItemId() == R.id.reminderBut) { @@ -214,11 +226,13 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == R.id.moreBut) { if (notePresenter.hasNote()) { - MoreNoteDialog dialog = MoreNoteDialog.newInstance( - notePresenter.getNote().getId(), - isExtendedEditor() ? MoreNoteDialog.RootActivity.ExtendedActivity : MoreNoteDialog.RootActivity.NoteActivity, - 0 - ); + MoreNoteDialog dialog = + MoreNoteDialog.newInstance( + notePresenter.getNote().getId(), + isExtendedEditor() + ? MoreNoteDialog.RootActivity.ExtendedActivity + : MoreNoteDialog.RootActivity.NoteActivity, + 0); dialog.show(getSupportFragmentManager(), "MoreNote"); } } @@ -226,13 +240,11 @@ public boolean onOptionsItemSelected(@NonNull MenuItem item) { return super.onOptionsItemSelected(item); } - /** - * Determines whether the current note editor screen is using - * the Extended Editor layout (ActivityNoteExtendedEditorBinding). - *

- * This method is defensive: - * - safely handles null binding + * Determines whether the current note editor screen is using the Extended Editor layout + * (ActivityNoteExtendedEditorBinding). + * + *

This method is defensive: - safely handles null binding * * @return true if this Activity is using the Extended Editor, false otherwise. */ @@ -287,7 +299,6 @@ public void onDestroy() { ((NotePresenter) notePresenter).cleanupHandlers(); notePresenter.detachView(); } - } @Override @@ -302,7 +313,10 @@ public void openCopyNote(long idNote) { @Override public void changeEditor(long idNote) { - Intent intent = new Intent(this, isExtendedEditor() ? NoteActivity.class : NoteExtendedEditorActivity.class); + Intent intent = + new Intent( + this, + isExtendedEditor() ? NoteActivity.class : NoteExtendedEditorActivity.class); intent.putExtra(NoteExtras.EXTRA_NEW_NOTE, false); intent.putExtra(EXTRA_ID_NOTE, idNote); @@ -339,5 +353,4 @@ public void changeTextSizeOffline() { public void initParam() { // Not implemented extended } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java index 0a72060..f4bfc8c 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java @@ -9,27 +9,23 @@ import android.view.LayoutInflater; import android.view.View; import android.view.inputmethod.InputMethodManager; - import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; import androidx.core.widget.NestedScrollView; - import com.pasich.mynotes.R; import com.pasich.mynotes.base.simplifications.TextWatcher; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.databinding.ActivityNoteBinding; import com.pasich.mynotes.ui.presenter.NotePresenter; import com.pasich.mynotes.utils.linkMovement.CustomLinkMovementMethod; - import dagger.hilt.android.AndroidEntryPoint; @AndroidEntryPoint public class NoteActivity extends BaseNoteEditorActivity { - private TextWatcher titleWatcher; private TextWatcher valueWatcher; @@ -83,88 +79,83 @@ protected void onAfterPresenterReady() { setupAppBarScrollListener(); } - - /** - * Scrolls the view to keep the cursor visible when the keyboard is open. - */ + /** Scrolls the view to keep the cursor visible when the keyboard is open. */ private void scrollToCursor() { if (!binding.valueNote.isFocused()) return; - binding.valueNote.post(() -> { - WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(binding.getRoot()); - if (insets == null || !insets.isVisible(WindowInsetsCompat.Type.ime())) { - return; - } + binding.valueNote.post( + () -> { + WindowInsetsCompat insets = ViewCompat.getRootWindowInsets(binding.getRoot()); + if (insets == null || !insets.isVisible(WindowInsetsCompat.Type.ime())) { + return; + } - Layout layout = binding.valueNote.getLayout(); - if (layout == null) { - return; - } + Layout layout = binding.valueNote.getLayout(); + if (layout == null) { + return; + } - int cursorPosition = binding.valueNote.getSelectionStart(); - int line = layout.getLineForOffset(cursorPosition); + int cursorPosition = binding.valueNote.getSelectionStart(); + int line = layout.getLineForOffset(cursorPosition); - if (cursorPosition == lastCursorPosition && line == lastCursorLine) { - return; - } + if (cursorPosition == lastCursorPosition && line == lastCursorLine) { + return; + } - int lineTop = layout.getLineTop(line); - int editTextTop = binding.valueNote.getTop(); - int absoluteLineTop = editTextTop + lineTop; + int lineTop = layout.getLineTop(line); + int editTextTop = binding.valueNote.getTop(); + int absoluteLineTop = editTextTop + lineTop; - int currentScrollY = binding.scrollView.getScrollY(); + int currentScrollY = binding.scrollView.getScrollY(); - Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); - Insets systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); + Insets systemInsets = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - int visibleHeight = binding.getRoot().getHeight() - imeInsets.bottom - systemInsets.top; + int visibleHeight = + binding.getRoot().getHeight() - imeInsets.bottom - systemInsets.top; - int lineHeight = layout.getLineBottom(line) - layout.getLineTop(line); - int lineVisibleTop = absoluteLineTop - currentScrollY; - int lineVisibleBottom = lineVisibleTop + lineHeight; + int lineHeight = layout.getLineBottom(line) - layout.getLineTop(line); + int lineVisibleTop = absoluteLineTop - currentScrollY; + int lineVisibleBottom = lineVisibleTop + lineHeight; - float ratio = (float) lineVisibleBottom / (float) visibleHeight; + float ratio = (float) lineVisibleBottom / (float) visibleHeight; - if (ratio > 0.9f) { - int targetScrollY = absoluteLineTop - (int) (visibleHeight * 0.7f); - binding.scrollView.smoothScrollTo(0, Math.max(0, targetScrollY)); - } - lastCursorPosition = cursorPosition; - lastCursorLine = line; - }); + if (ratio > 0.9f) { + int targetScrollY = absoluteLineTop - (int) (visibleHeight * 0.7f); + binding.scrollView.smoothScrollTo(0, Math.max(0, targetScrollY)); + } + lastCursorPosition = cursorPosition; + lastCursorLine = line; + }); } - - /** - * Sets up a scroll listener for AppBar - */ + /** Sets up a scroll listener for AppBar */ private void setupAppBarScrollListener() { - binding.scrollView.setOnScrollChangeListener((NestedScrollView.OnScrollChangeListener) (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { - - int titleTop = binding.notesTitle.getTop(); - - boolean shouldShowCollapsed = scrollY > titleTop; - - if (shouldShowCollapsed) { - if (binding.centerContent.getVisibility() == View.VISIBLE) { - binding.centerContent.setVisibility(View.GONE); - binding.endContent.setVisibility(View.VISIBLE); - binding.scrollProgressIndicator.setVisibility(View.VISIBLE); - } - updateScrollProgress(scrollY); - } else { - if (binding.centerContent.getVisibility() == View.GONE) { - binding.centerContent.setVisibility(View.VISIBLE); - binding.endContent.setVisibility(View.GONE); - binding.scrollProgressIndicator.setVisibility(View.GONE); - } - } - }); + binding.scrollView.setOnScrollChangeListener( + (NestedScrollView.OnScrollChangeListener) + (v, scrollX, scrollY, oldScrollX, oldScrollY) -> { + int titleTop = binding.notesTitle.getTop(); + + boolean shouldShowCollapsed = scrollY > titleTop; + + if (shouldShowCollapsed) { + if (binding.centerContent.getVisibility() == View.VISIBLE) { + binding.centerContent.setVisibility(View.GONE); + binding.endContent.setVisibility(View.VISIBLE); + binding.scrollProgressIndicator.setVisibility(View.VISIBLE); + } + updateScrollProgress(scrollY); + } else { + if (binding.centerContent.getVisibility() == View.GONE) { + binding.centerContent.setVisibility(View.VISIBLE); + binding.endContent.setVisibility(View.GONE); + binding.scrollProgressIndicator.setVisibility(View.GONE); + } + } + }); } - /** - * Updates the scroll progress indicator - */ + /** Updates the scroll progress indicator */ private void updateScrollProgress(int scrollY) { View child = binding.scrollView.getChildAt(0); if (child != null) { @@ -179,69 +170,88 @@ private void updateScrollProgress(int scrollY) { } } - /** - * Handles system indents and keyboard appearance/disappearance: - * - correctly sets top inset for root layout - * - raises FAB above the navbar - * - adapts the bottom margin of ScrollView to the keyboard or system navbar - * - saves and restores the scroll position when opening/closing the keyboard - * - automatically scrolls to the cursor when the keyboard appears + * Handles system indents and keyboard appearance/disappearance: - correctly sets top inset for + * root layout - raises FAB above the navbar - adapts the bottom margin of ScrollView to the + * keyboard or system navbar - saves and restores the scroll position when opening/closing the + * keyboard - automatically scrolls to the cursor when the keyboard appears */ @Override protected void applyEdgeToEdgeInsets(View rootView) { - ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> { - - Insets navBarInsets = insets.getInsets(WindowInsetsCompat.Type.navigationBars()); - - CoordinatorLayout.LayoutParams fab = (CoordinatorLayout.LayoutParams) binding.editActive.getLayoutParams(); - fab.setMargins(fab.leftMargin, fab.topMargin, fab.rightMargin, 25 + navBarInsets.bottom // додаємо висоту нижньої панелі - ); - binding.editActive.setLayoutParams(fab); - - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - - Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); - - boolean keyboardWasVisible = isKeyboardVisible; - boolean keyboardWillBeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); - - if (!keyboardWasVisible && keyboardWillBeVisible) { - savedScrollPosition = binding.scrollView.getScrollY(); - } - - isKeyboardVisible = keyboardWillBeVisible; - - v.setPadding(v.getPaddingLeft(), systemBars.top, v.getPaddingRight(), 0); - - int bottomMargin = Math.max(imeInsets.bottom, systemBars.bottom); + ViewCompat.setOnApplyWindowInsetsListener( + rootView, + (v, insets) -> { + Insets navBarInsets = + insets.getInsets(WindowInsetsCompat.Type.navigationBars()); + + CoordinatorLayout.LayoutParams fab = + (CoordinatorLayout.LayoutParams) binding.editActive.getLayoutParams(); + fab.setMargins( + fab.leftMargin, + fab.topMargin, + fab.rightMargin, + 25 + navBarInsets.bottom // додаємо висоту нижньої панелі + ); + binding.editActive.setLayoutParams(fab); + + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + + Insets imeInsets = insets.getInsets(WindowInsetsCompat.Type.ime()); + + boolean keyboardWasVisible = isKeyboardVisible; + boolean keyboardWillBeVisible = insets.isVisible(WindowInsetsCompat.Type.ime()); + + if (!keyboardWasVisible && keyboardWillBeVisible) { + savedScrollPosition = binding.scrollView.getScrollY(); + } - android.widget.LinearLayout.LayoutParams params = (android.widget.LinearLayout.LayoutParams) binding.scrollView.getLayoutParams(); + isKeyboardVisible = keyboardWillBeVisible; - if (keyboardWasVisible && !keyboardWillBeVisible && savedScrollPosition >= 0) { - binding.scrollView.scrollTo(0, savedScrollPosition); - } + v.setPadding(v.getPaddingLeft(), systemBars.top, v.getPaddingRight(), 0); - params.setMargins(params.leftMargin, params.topMargin, params.rightMargin, bottomMargin); - binding.scrollView.setLayoutParams(params); + int bottomMargin = Math.max(imeInsets.bottom, systemBars.bottom); - binding.scrollView.setPadding(binding.scrollView.getPaddingLeft(), binding.scrollView.getPaddingTop(), binding.scrollView.getPaddingRight(), getResources().getDimensionPixelSize(R.dimen.scroll_view_bottom_margin)); + android.widget.LinearLayout.LayoutParams params = + (android.widget.LinearLayout.LayoutParams) + binding.scrollView.getLayoutParams(); - if (!keyboardWasVisible && keyboardWillBeVisible && binding.valueNote.isFocused()) { - lastCursorPosition = -1; - binding.valueNote.postDelayed(this::scrollToCursor, 200); - } else if (keyboardWasVisible && !keyboardWillBeVisible && savedScrollPosition >= 0) { - binding.scrollView.post(() -> { - if (Math.abs(binding.scrollView.getScrollY() - savedScrollPosition) > 5) { + if (keyboardWasVisible && !keyboardWillBeVisible && savedScrollPosition >= 0) { binding.scrollView.scrollTo(0, savedScrollPosition); } - }); - } - return insets; - }); - } + params.setMargins( + params.leftMargin, params.topMargin, params.rightMargin, bottomMargin); + binding.scrollView.setLayoutParams(params); + + binding.scrollView.setPadding( + binding.scrollView.getPaddingLeft(), + binding.scrollView.getPaddingTop(), + binding.scrollView.getPaddingRight(), + getResources() + .getDimensionPixelSize(R.dimen.scroll_view_bottom_margin)); + + if (!keyboardWasVisible + && keyboardWillBeVisible + && binding.valueNote.isFocused()) { + lastCursorPosition = -1; + binding.valueNote.postDelayed(this::scrollToCursor, 200); + } else if (keyboardWasVisible + && !keyboardWillBeVisible + && savedScrollPosition >= 0) { + binding.scrollView.post( + () -> { + if (Math.abs( + binding.scrollView.getScrollY() + - savedScrollPosition) + > 5) { + binding.scrollView.scrollTo(0, savedScrollPosition); + } + }); + } + return insets; + }); + } @Override public void onStop() { @@ -257,41 +267,44 @@ public void onStop() { @Override public void initListeners() { - titleWatcher = new TextWatcher() { - @Override - protected void changeText(Editable s) { - if (!notePresenter.hasNote()) return; - if (s.toString().contains("\n")) { - binding.notesTitle.setText(s.toString().replace('\n', ' ').trim()); - binding.valueNote.requestFocus(); - } - String title = s.toString().trim(); - binding.titleToolbarCollapsed.setText(!title.isEmpty() ? title : getString(R.string.noteTitle)); - notePresenter.simpleNoteChange(title, null, false); - } - }; + titleWatcher = + new TextWatcher() { + @Override + protected void changeText(Editable s) { + if (!notePresenter.hasNote()) return; + if (s.toString().contains("\n")) { + binding.notesTitle.setText(s.toString().replace('\n', ' ').trim()); + binding.valueNote.requestFocus(); + } + String title = s.toString().trim(); + binding.titleToolbarCollapsed.setText( + !title.isEmpty() ? title : getString(R.string.noteTitle)); + notePresenter.simpleNoteChange(title, null, false); + } + }; binding.notesTitle.addTextChangedListener(titleWatcher); - valueWatcher = new TextWatcher() { - @Override - protected void changeText(Editable s) { - if (!notePresenter.hasNote()) return; - String newValue = s.toString(); - updateWordCount(newValue); - if (!newValue.equals(notePresenter.getNote().getValue())) { - notePresenter.simpleNoteChange(null, newValue, false); - } - } - }; + valueWatcher = + new TextWatcher() { + @Override + protected void changeText(Editable s) { + if (!notePresenter.hasNote()) return; + String newValue = s.toString(); + updateWordCount(newValue); + if (!newValue.equals(notePresenter.getNote().getValue())) { + notePresenter.simpleNoteChange(null, newValue, false); + } + } + }; binding.valueNote.addTextChangedListener(valueWatcher); // Add a click handler for the input field - only for cursor movement processing - binding.valueNote.setOnClickListener(v -> { - if (binding.valueNote.isFocused() && scrollProgress < 95) { - binding.valueNote.postDelayed(this::scrollToCursor, 50); - } - }); - + binding.valueNote.setOnClickListener( + v -> { + if (binding.valueNote.isFocused() && scrollProgress < 95) { + binding.valueNote.postDelayed(this::scrollToCursor, 50); + } + }); } @Override @@ -305,21 +318,23 @@ public void activatedActivity() { binding.valueNote.requestFocus(); if (notePresenter.getNewNotesKey()) { - ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)).toggleSoftInputFromWindow(binding.valueNote.getApplicationWindowToken(), InputMethodManager.SHOW_IMPLICIT, 0); + ((InputMethodManager) getSystemService(INPUT_METHOD_SERVICE)) + .toggleSoftInputFromWindow( + binding.valueNote.getApplicationWindowToken(), + InputMethodManager.SHOW_IMPLICIT, + 0); lastCursorPosition = -1; binding.valueNote.postDelayed(this::scrollToCursor, 300); } else { if (binding.valueNote.requestFocus()) { - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager imm = + (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(binding.valueNote, InputMethodManager.SHOW_IMPLICIT); } } - - } - @Override public void onDestroy() { super.onDestroy(); @@ -337,7 +352,6 @@ public void onDestroy() { isKeyboardVisible = false; } - @Override public void loadingNote(Note note) { if (note == null) { @@ -353,20 +367,14 @@ public void loadingNote(Note note) { binding.valueNote.setText(value != null ? value : ""); binding.valueNote.setMovementMethod(CustomLinkMovementMethod.getInstance()); - - String formattedDate = getString( - R.string.lastDateEditNote, - lastDayEditNote(note.getDate()) - ); + String formattedDate = + getString(R.string.lastDateEditNote, lastDayEditNote(note.getDate())); binding.titleToolbarDataCenter.setText(formattedDate); // Collapsed title binding.titleToolbarCollapsed.setText( - title != null && !title.isEmpty() - ? title - : getString(R.string.noteTitle) - ); + title != null && !title.isEmpty() ? title : getString(R.string.noteTitle)); binding.titleToolbarDataCollapsed.setText(formattedDate); // Tag @@ -398,7 +406,8 @@ public void closeNoteActivity() { } binding.getRoot().clearFocus(); - InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); + InputMethodManager imm = + (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); if (binding != null) { imm.hideSoftInputFromWindow(binding.valueNote.getWindowToken(), 0); } @@ -425,7 +434,10 @@ public void changeTag(String nameTag, boolean change) { @Override public void changeTextStyle() { - binding.valueNote.setTypeface(null, notePresenter.getTypeFace(notePresenter.getDataManager().getTypeFaceNoteActivity())); + binding.valueNote.setTypeface( + null, + notePresenter.getTypeFace( + notePresenter.getDataManager().getTypeFaceNoteActivity())); } @Override diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteExtendedEditorActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteExtendedEditorActivity.java index 6f1a1b9..7bfe2e5 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteExtendedEditorActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteExtendedEditorActivity.java @@ -12,7 +12,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; - import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; @@ -20,7 +19,6 @@ import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; import androidx.core.view.WindowInsetsCompat; - import com.pasich.mynotes.R; import com.pasich.mynotes.cache.AppPreferencesCache; import com.pasich.mynotes.data.model.Note; @@ -34,25 +32,23 @@ import com.pasich.mynotes.extendedEditor.view.CopyTextDialog; import com.pasich.mynotes.ui.presenter.NotePresenter; import com.pasich.mynotes.ui.view.activity.PhotoViewActivity; - import dagger.hilt.android.AndroidEntryPoint; import jakarta.inject.Inject; @AndroidEntryPoint -public class NoteExtendedEditorActivity extends BaseNoteEditorActivity implements NoteEditorView.OnFileChooserListener { - private final ActivityResultLauncher fileChooserLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (binding != null) { - binding.noteEditor.onFileChooserResult( - result.getResultCode(), - result.getData() - ); - } - } - ); - @Inject - AppPreferencesCache appPreferencesCache; +public class NoteExtendedEditorActivity + extends BaseNoteEditorActivity + implements NoteEditorView.OnFileChooserListener { + private final ActivityResultLauncher fileChooserLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (binding != null) { + binding.noteEditor.onFileChooserResult( + result.getResultCode(), result.getData()); + } + }); + @Inject AppPreferencesCache appPreferencesCache; private boolean isReadMode = false; @Override @@ -79,55 +75,54 @@ protected void bindingSetPresenter(ActivityNoteExtendedEditorBinding binding) { protected void onAfterPresenterReady() { notePresenter.setExtendedEditor(true); - EditorJSInterface bridge = new EditorJSInterface( - new EditorJSInterface.EditorListener() { - - @Override - public void onEditorReady() { - binding.noteEditor.onEditorReadyFromBridge(); - } - - @Override - public void onContentChanged(String json) { - runOnUiThread(() -> processTextChange(json)); - } - - @Override - public void onTitleChanged(String title) { - runOnUiThread(() -> processTitleChange(title)); - - } - - @Override - public void openPhoto(String blockId) { - handleImageOpen(blockId); - } - - @Override - public void openFile(EditorAttachment att) { - runOnUiThread(() -> AttachmentActionsDialog.show( - NoteExtendedEditorActivity.this, - att, - (at, ls) -> handleAttachmentDelete(at, ls) - )); - - - } - - @Override - public int getNoteId() { - return notePresenter.getNote().getId(); - } - - @Override - public void onError(String error) { - Log.e("ExtendedEditor", "NoteEditorView error:" + error); - } - }, - binding.noteEditor.getWebView(), - this, - new SettingsEditorJsBridge(appPreferencesCache.getImageOpt()) - ); + EditorJSInterface bridge = + new EditorJSInterface( + new EditorJSInterface.EditorListener() { + + @Override + public void onEditorReady() { + binding.noteEditor.onEditorReadyFromBridge(); + } + + @Override + public void onContentChanged(String json) { + runOnUiThread(() -> processTextChange(json)); + } + + @Override + public void onTitleChanged(String title) { + runOnUiThread(() -> processTitleChange(title)); + } + + @Override + public void openPhoto(String blockId) { + handleImageOpen(blockId); + } + + @Override + public void openFile(EditorAttachment att) { + runOnUiThread( + () -> + AttachmentActionsDialog.show( + NoteExtendedEditorActivity.this, + att, + (at, ls) -> + handleAttachmentDelete(at, ls))); + } + + @Override + public int getNoteId() { + return notePresenter.getNote().getId(); + } + + @Override + public void onError(String error) { + Log.e("ExtendedEditor", "NoteEditorView error:" + error); + } + }, + binding.noteEditor.getWebView(), + this, + new SettingsEditorJsBridge(appPreferencesCache.getImageOpt())); binding.noteEditor.setEditorInterface(bridge); } @@ -142,32 +137,32 @@ protected void setNewNoteTitle() { binding.titleToolbarDataCollapsed.setText(getString(R.string.new_note)); } - /** - * Configures indents taking into account the keyboard for NoteActivity - */ + /** Configures indents taking into account the keyboard for NoteActivity */ @Override protected void applyEdgeToEdgeInsets(View rootView) { - ViewCompat.setOnApplyWindowInsetsListener(rootView, (v, insets) -> { - // Get indents for system bars - Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); + ViewCompat.setOnApplyWindowInsetsListener( + rootView, + (v, insets) -> { + // Get indents for system bars + Insets systemBars = insets.getInsets(WindowInsetsCompat.Type.systemBars()); - // Set padding at the top for system bars only for the root view - v.setPadding(v.getPaddingLeft(), systemBars.top, v.getPaddingRight(), 0); + // Set padding at the top for system bars only for the root view + v.setPadding(v.getPaddingLeft(), systemBars.top, v.getPaddingRight(), 0); - return insets; - }); + return insets; + }); } @Override public void initListeners() { binding.noteEditor.setOnFileChooserListener(this); - binding.noteEditor.setOnContextDialogListener(() -> CopyTextDialog.show( - this, - null, // title - notePresenter.getNote().getValue() // plain text - )); - - + binding.noteEditor.setOnContextDialogListener( + () -> + CopyTextDialog.show( + this, + null, // title + notePresenter.getNote().getValue() // plain text + )); } /** @@ -181,7 +176,11 @@ private void handleImageOpen(String blockId) { // Try to find the matching EditorAttachment for this block EditorAttachment image = findAttachmentByBlockId(notePresenter.getNote(), blockId); - if (image == null || image.url == null || image.url.isEmpty() || blockId == null || blockId.trim().isEmpty()) { + if (image == null + || image.url == null + || image.url.isEmpty() + || blockId == null + || blockId.trim().isEmpty()) { Toast.makeText(this, getString(R.string.openImageError), Toast.LENGTH_SHORT).show(); return; } @@ -199,15 +198,13 @@ private void handleImageOpen(String blockId) { } } - /** - * Handles attachment deletion flow: - * 1. Locates the block ID inside Editor.js JSON. - * 2. Passes the deletion request to the editor. - * 3. Sends either the real file URL or null (if file is already missing). + * Handles attachment deletion flow: 1. Locates the block ID inside Editor.js JSON. 2. Passes + * the deletion request to the editor. 3. Sends either the real file URL or null (if file is + * already missing). * * @param attach The attachment metadata. - * @param lost True if file does not exist on disk. + * @param lost True if file does not exist on disk. */ private void handleAttachmentDelete(EditorAttachment attach, boolean lost) { @@ -224,17 +221,12 @@ private void handleAttachmentDelete(EditorAttachment attach, boolean lost) { binding.noteEditor.deleteBlock(blockId, urlOrNull); } - - /** - * Process title changes with enhanced features - */ + /** Process title changes with enhanced features */ private void processTitleChange(String title) { notePresenter.extendedNoteChange(title, null); } - /** - * Process text changes with enhanced features - */ + /** Process text changes with enhanced features */ private void processTextChange(String jsonData) { notePresenter.extendedNoteChange(null, jsonData); } @@ -244,7 +236,6 @@ public void activatedActivity() { binding.setActivateEdit(true); } - @Override public boolean onOptionsItemSelected(@NonNull MenuItem item) { if (item.getItemId() == R.id.actionRead) { @@ -276,11 +267,11 @@ public void loadingNote(Note note) { } changeTag(note.getTag() != null ? note.getTag() : "", false); - binding.titleToolbarDataCollapsed.setText(getString(R.string.lastDateEditNote, lastDayEditNote(note.getDate()))); + binding.titleToolbarDataCollapsed.setText( + getString(R.string.lastDateEditNote, lastDayEditNote(note.getDate()))); binding.noteEditor.load(note); } - @Override public void closeNoteActivity() { if (binding == null || notePresenter == null) { @@ -291,7 +282,6 @@ public void closeNoteActivity() { supportFinishAfterTransition(); } - @Override public void changeTag(String nameTag, boolean change) { if (change) { @@ -311,7 +301,6 @@ public void onOpenFileChooser(Intent intent, int requestCode) { fileChooserLauncher.launch(intent); } - @Override public void runAttachmentsCleanup(Note note) { new Thread(() -> AttachmentCleaner.cleanup(getApplicationContext(), note)).start(); @@ -325,8 +314,7 @@ public void reloadExtendedEditor() { @Override public void onNoteCopied(long newNoteId) { binding.duplicateTag.setText( - getString(R.string.tagHastag, getString(R.string.duplicateTag)) - ); + getString(R.string.tagHastag, getString(R.string.duplicateTag))); binding.duplicateTag.setVisibility(VISIBLE); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/BackupOptionsDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/BackupOptionsDialog.java index 33fb511..216db24 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/BackupOptionsDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/BackupOptionsDialog.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.ui.view.dialogs; - import static android.view.View.GONE; import static android.view.View.VISIBLE; @@ -8,10 +7,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.material.bottomsheet.BottomSheetDialog; import com.pasich.mynotes.R; import com.pasich.mynotes.base.dialog.BaseDialogBottomSheets; @@ -30,13 +27,17 @@ public BackupOptionsDialog(BackupOptionsCallback callback, boolean isRestore) { this.isRestore = isRestore; } - public static BackupOptionsDialog newInstance(BackupOptionsCallback callback, boolean isRestore) { + public static BackupOptionsDialog newInstance( + BackupOptionsCallback callback, boolean isRestore) { return new BackupOptionsDialog(callback, isRestore); } @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { vibrateOpenDialog(true); setState((BottomSheetDialog) requireDialog()); binding = DialogBackupOptionsBinding.inflate(inflater, container, false); @@ -49,23 +50,24 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c binding.backupOptions.setText(R.string.save_backup); } - binding.googleDrive.setVisibility(FileExportUtils.isAppInstalled(getContext()) ? VISIBLE : GONE); + binding.googleDrive.setVisibility( + FileExportUtils.isAppInstalled(getContext()) ? VISIBLE : GONE); return binding.getRoot(); } @Override public void initListeners() { - binding.googleDrive.setOnClickListener(v -> { - if (callback != null) callback.onGoogleDrive(); - dismiss(); - }); - - binding.deviceStorage.setOnClickListener(v -> { - if (callback != null) callback.onDeviceStorage(); - dismiss(); - }); - - + binding.googleDrive.setOnClickListener( + v -> { + if (callback != null) callback.onGoogleDrive(); + dismiss(); + }); + + binding.deviceStorage.setOnClickListener( + v -> { + if (callback != null) callback.onDeviceStorage(); + dismiss(); + }); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/GoogleTakeoutInstructionsBottomSheet.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/GoogleTakeoutInstructionsBottomSheet.java index 9af073b..1313bfe 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/GoogleTakeoutInstructionsBottomSheet.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/GoogleTakeoutInstructionsBottomSheet.java @@ -4,10 +4,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.pasich.mynotes.databinding.BottomSheetGoogleTakeoutInstructionsBinding; @@ -15,12 +13,14 @@ public class GoogleTakeoutInstructionsBottomSheet extends BottomSheetDialogFragm private BottomSheetGoogleTakeoutInstructionsBinding binding; - public GoogleTakeoutInstructionsBottomSheet() { - } + public GoogleTakeoutInstructionsBottomSheet() {} @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = BottomSheetGoogleTakeoutInstructionsBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -41,4 +41,4 @@ public void onDestroyView() { public static GoogleTakeoutInstructionsBottomSheet newInstance() { return new GoogleTakeoutInstructionsBottomSheet(); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/MoreNoteDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/MoreNoteDialog.java index 51c58e7..e0fca4a 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/MoreNoteDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/MoreNoteDialog.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.ui.view.dialogs; - import static android.view.View.GONE; import android.app.Activity; @@ -11,14 +10,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Toast; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.chip.Chip; import com.google.android.material.slider.Slider; @@ -34,35 +27,29 @@ import com.pasich.mynotes.ui.view.widgets.TwoSideSwitchView; import com.pasich.mynotes.utils.navigation.GoogleTranslateHelper; import com.pasich.mynotes.utils.tool.TextStyleTool; -import com.pasich.mynotes.ui.view.dialogs.ReminderPickerBottomSheet; - +import dagger.hilt.android.AndroidEntryPoint; +import java.text.SimpleDateFormat; +import java.util.Date; import java.util.List; - +import java.util.Locale; import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; - - @AndroidEntryPoint public class MoreNoteDialog extends BaseDialogBottomSheets implements MoreNoteDialogContract.view { - @Inject - public MoreNoteDialogPresenter mPresenter; - @Inject - public TextStyleTool textStylePreferences; + @Inject public MoreNoteDialogPresenter mPresenter; + @Inject public TextStyleTool textStylePreferences; // private Note mNote; private RootActivity rootActivity; private int positionItem; private DialogMoreNoteBinding binding; - /** - * Interfaces - */ + + /** Interfaces */ private MoreNoteNoteActivityView noteActivity; - private MoreNoteMainActivityView mainActivity; - public MoreNoteDialog() { - } + private MoreNoteMainActivityView mainActivity; + public MoreNoteDialog() {} public static MoreNoteDialog newInstance(int noteId, RootActivity root, int position) { MoreNoteDialog dialog = new MoreNoteDialog(); @@ -76,7 +63,6 @@ public static MoreNoteDialog newInstance(int noteId, RootActivity root, int posi return dialog; } - @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -92,7 +78,10 @@ public void onCreate(@Nullable Bundle savedInstanceState) { } @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { vibrateOpenDialog(rootActivity == RootActivity.MainActivity); setState((BottomSheetDialog) requireDialog()); binding = DialogMoreNoteBinding.inflate(getLayoutInflater(), container, false); @@ -135,7 +124,8 @@ public void onNoteLoaded(Note mNote) { private void updatePinState(Note note) { if (note == null) return; - binding.pinNoteText.setText(note.isPinned() ? getString(R.string.unpinNote) : getString(R.string.pinNote)); + binding.pinNoteText.setText( + note.isPinned() ? getString(R.string.unpinNote) : getString(R.string.pinNote)); } private void updateReminderMenuState(Note note) { @@ -149,9 +139,11 @@ private void updateReminderMenuState(Note note) { } } - public void setHideTextSize() { - binding.settingsActivity.getRoot().setVisibility(rootActivity == RootActivity.NoteActivity ? View.VISIBLE : View.GONE); + binding.settingsActivity + .getRoot() + .setVisibility( + rootActivity == RootActivity.NoteActivity ? View.VISIBLE : View.GONE); } public void setChangeTypeEditor() { @@ -160,11 +152,15 @@ public void setChangeTypeEditor() { binding.changeTypeEditor.setVisibility(GONE); return; } - binding.changeTypeEditor.setMode(rootActivity == RootActivity.ExtendedActivity ? TwoSideSwitchView.Mode.EXTENDED : TwoSideSwitchView.Mode.SIMPLE); + binding.changeTypeEditor.setMode( + rootActivity == RootActivity.ExtendedActivity + ? TwoSideSwitchView.Mode.EXTENDED + : TwoSideSwitchView.Mode.SIMPLE); } public void goneCopyNotesExtended() { - binding.copyNote.setVisibility(mPresenter.getNote().isAttachments() ? View.GONE : View.VISIBLE); + binding.copyNote.setVisibility( + mPresenter.getNote().isAttachments() ? View.GONE : View.VISIBLE); } @Override @@ -190,16 +186,12 @@ public void initInterfaces() { return; } - Toast.makeText( - requireContext(), - R.string.error_dialog_wrong_context, - Toast.LENGTH_SHORT - ).show(); + Toast.makeText(requireContext(), R.string.error_dialog_wrong_context, Toast.LENGTH_SHORT) + .show(); dismiss(); } - @Override public void callableCopyNote(long newNoteId) { if (mainActivity != null) { @@ -207,7 +199,6 @@ public void callableCopyNote(long newNoteId) { } } - @Override public void initListeners() { if (mPresenter.getNote() == null) { @@ -216,100 +207,110 @@ public void initListeners() { if (rootActivity != RootActivity.MainActivity) { binding.noSave.setOnClickListener(v -> noteActivity.closeActivityNotSaved()); - binding.settingsActivity.textSize.addOnSliderTouchListener(new Slider.OnSliderTouchListener() { - @Override - public void onStartTrackingTouch(@NonNull Slider slider) { - } - - @Override - public void onStopTrackingTouch(@NonNull Slider slider) { - mPresenter.editSizeText(Math.round(slider.getValue())); - } - }); - - binding.settingsActivity.textSize.addOnChangeListener((slider, value, fromUser) -> { - if (fromUser) noteActivity.changeTextSizeOnline(Math.round(value)); - }); - - binding.settingsActivity.textStyleItem.setOnClickListener(v -> { - textStylePreferences.changeArgument(); - noteActivity.changeTextStyle(); - }); - - binding.changeTypeEditor.setOnModeChangedListener(mode -> - binding.changeTypeEditor.postDelayed(() -> { - if (!isAdded() || noteActivity == null || mPresenter.getNote() == null) - return; - - switch (mode) { - case SIMPLE: - case EXTENDED: - noteActivity.changeEditor(mPresenter.getNote().getId()); - break; - case INACTIVE: - break; + binding.settingsActivity.textSize.addOnSliderTouchListener( + new Slider.OnSliderTouchListener() { + @Override + public void onStartTrackingTouch(@NonNull Slider slider) {} + + @Override + public void onStopTrackingTouch(@NonNull Slider slider) { + mPresenter.editSizeText(Math.round(slider.getValue())); } - dismiss(); - }, 300) - ); - + }); + + binding.settingsActivity.textSize.addOnChangeListener( + (slider, value, fromUser) -> { + if (fromUser) noteActivity.changeTextSizeOnline(Math.round(value)); + }); + + binding.settingsActivity.textStyleItem.setOnClickListener( + v -> { + textStylePreferences.changeArgument(); + noteActivity.changeTextStyle(); + }); + + binding.changeTypeEditor.setOnModeChangedListener( + mode -> + binding.changeTypeEditor.postDelayed( + () -> { + if (!isAdded() + || noteActivity == null + || mPresenter.getNote() == null) return; + + switch (mode) { + case SIMPLE: + case EXTENDED: + noteActivity.changeEditor( + mPresenter.getNote().getId()); + break; + case INACTIVE: + break; + } + dismiss(); + }, + 300)); } else { - binding.actionPanelActivate.setOnClickListener(view -> { - assert mainActivity != null; - mainActivity.actionStartNote(mPresenter.getNote(), positionItem); - dismiss(); - }); + binding.actionPanelActivate.setOnClickListener( + view -> { + assert mainActivity != null; + mainActivity.actionStartNote(mPresenter.getNote(), positionItem); + dismiss(); + }); } - binding.share.setVisibility(View.VISIBLE); - binding.share.setOnClickListener(v -> { - // Open share options dialog - ShareOptionsDialog shareDialog = new ShareOptionsDialog(mPresenter.getNote()); - shareDialog.show(getParentFragmentManager(), "ShareOptionsDialog"); - dismiss(); - }); + binding.share.setOnClickListener( + v -> { + // Open share options dialog + ShareOptionsDialog shareDialog = new ShareOptionsDialog(mPresenter.getNote()); + shareDialog.show(getParentFragmentManager(), "ShareOptionsDialog"); + dismiss(); + }); binding.translateNote.setVisibility(View.VISIBLE); - binding.translateNote.setOnClickListener(v -> { - GoogleTranslateHelper.startTranslation(requireActivity(), mPresenter.getNote().getValue()); - dismiss(); - }); - binding.setReminder.setOnClickListener(v -> { - if (mPresenter.getNote() == null) return; - ReminderPickerBottomSheet.newInstance(mPresenter.getNote().getId()) - .show(getParentFragmentManager(), "ReminderPicker"); - dismiss(); - }); - - binding.pinNote.setOnClickListener(v -> { - mPresenter.togglePinNote(); - dismiss(); - }); - - binding.moveToTrash.setOnClickListener(v -> { - mPresenter.noteMoveToTrash(); - if (rootActivity == RootActivity.MainActivity) { - mainActivity.callbackDeleteNote(mPresenter.getNote()); - dismiss(); - } else { - noteActivity.closeActivityNotSaved(); - } - - }); - - binding.copyNote.setOnClickListener(v -> { - if (mPresenter.getNote().isAttachments()) return; - if (rootActivity != RootActivity.MainActivity) { - noteActivity.openCopyNote(mPresenter.getNote().getId()); - } else { - mPresenter.copyNoteMainActivity(); - } - - dismiss(); - }); - + binding.translateNote.setOnClickListener( + v -> { + GoogleTranslateHelper.startTranslation( + requireActivity(), mPresenter.getNote().getValue()); + dismiss(); + }); + binding.setReminder.setOnClickListener( + v -> { + if (mPresenter.getNote() == null) return; + ReminderPickerBottomSheet.newInstance(mPresenter.getNote().getId()) + .show(getParentFragmentManager(), "ReminderPicker"); + dismiss(); + }); + + binding.pinNote.setOnClickListener( + v -> { + mPresenter.togglePinNote(); + dismiss(); + }); + + binding.moveToTrash.setOnClickListener( + v -> { + mPresenter.noteMoveToTrash(); + if (rootActivity == RootActivity.MainActivity) { + mainActivity.callbackDeleteNote(mPresenter.getNote()); + dismiss(); + } else { + noteActivity.closeActivityNotSaved(); + } + }); + + binding.copyNote.setOnClickListener( + v -> { + if (mPresenter.getNote().isAttachments()) return; + if (rootActivity != RootActivity.MainActivity) { + noteActivity.openCopyNote(mPresenter.getNote().getId()); + } else { + mPresenter.copyNoteMainActivity(); + } + + dismiss(); + }); if (mPresenter.getNote().getValue().isEmpty()) { binding.translateNote.setVisibility(GONE); @@ -318,7 +319,6 @@ public void onStopTrackingTouch(@NonNull Slider slider) { } } - @Override public void onDismiss(@NonNull DialogInterface dialog) { super.onDismiss(dialog); @@ -335,7 +335,6 @@ public void onDismiss(@NonNull DialogInterface dialog) { positionItem = 0; } - binding.setReminder.setOnClickListener(null); binding.pinNote.setOnClickListener(null); binding.moveToTrash.setOnClickListener(null); @@ -343,7 +342,6 @@ public void onDismiss(@NonNull DialogInterface dialog) { binding.share.setOnClickListener(null); } - @Override public void createChipsTag(List tags) { binding.chipGroupSystem.removeAllViews(); @@ -358,24 +356,25 @@ public void createChipsTag(List tags) { String noteTag = mPresenter.getNote().getTag(); // може бути "" for (Tag tag : tags) { - Chip chip = (Chip) getLayoutInflater() - .inflate(R.layout.layout_chip_entry, binding.chipGroupSystem, false); + Chip chip = + (Chip) + getLayoutInflater() + .inflate( + R.layout.layout_chip_entry, + binding.chipGroupSystem, + false); chip.setText(getString(R.string.tagHastag, tag.getNameTag())); boolean checked = TextUtils.equals(noteTag, tag.getNameTag()); chip.setChecked(checked); - chip.setOnCheckedChangeListener( - (buttonView, isChecked) -> selectedTag(tag, isChecked) - ); + chip.setOnCheckedChangeListener((buttonView, isChecked) -> selectedTag(tag, isChecked)); binding.chipGroupSystem.addView(chip); } } - - private void selectedTag(Tag tag, boolean checked) { final int noteId = mPresenter.getNote().getId(); if (checked) { @@ -393,6 +392,8 @@ private void selectedTag(Tag tag, boolean checked) { } public enum RootActivity { - MainActivity, NoteActivity, ExtendedActivity + MainActivity, + NoteActivity, + ExtendedActivity } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/OtherAppImportDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/OtherAppImportDialog.java index c5a9a77..3af79cd 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/OtherAppImportDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/OtherAppImportDialog.java @@ -4,20 +4,17 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.material.bottomsheet.BottomSheetDialogFragment; -import com.pasich.mynotes.utils.backup.models.googleKeep.GoogleKeepImportResult; import com.pasich.mynotes.databinding.DialogOtherAppImportBinding; +import com.pasich.mynotes.utils.backup.models.googleKeep.GoogleKeepImportResult; public class OtherAppImportDialog extends BottomSheetDialogFragment { private DialogOtherAppImportBinding binding; private ImportCallback callback; - public OtherAppImportDialog() { - } + public OtherAppImportDialog() {} public interface ImportCallback { void onImportConfirmed(GoogleKeepImportResult result); @@ -32,7 +29,8 @@ public void setCallback(ImportCallback callback) { } @Override - public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { binding = DialogOtherAppImportBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -63,17 +61,18 @@ public void showResult(GoogleKeepImportResult result) { binding.errorText.setVisibility(View.GONE); binding.resultContainer.setVisibility(View.VISIBLE); binding.importButton.setVisibility(View.VISIBLE); - + binding.notesCount.setText(String.valueOf(result.getTotalNotesCount())); binding.trashedNotesCount.setText(String.valueOf(result.getTrashedNotes().size())); binding.tagsCount.setText(String.valueOf(result.getLabels().size())); - binding.importButton.setOnClickListener(v -> { - if (callback != null) { - callback.onImportConfirmed(result); - } - dismiss(); - }); + binding.importButton.setOnClickListener( + v -> { + if (callback != null) { + callback.onImportConfirmed(result); + } + dismiss(); + }); } binding.cancel.setOnClickListener(v -> dismiss()); @@ -85,4 +84,4 @@ public void onDestroyView() { super.onDestroyView(); binding = null; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ReminderPickerBottomSheet.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ReminderPickerBottomSheet.java index ec14701..c0133da 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ReminderPickerBottomSheet.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ReminderPickerBottomSheet.java @@ -12,13 +12,11 @@ import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; - import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; - import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.button.MaterialButton; import com.google.android.material.chip.ChipGroup; @@ -32,25 +30,21 @@ import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.ReminderRepeat; import com.pasich.mynotes.utils.reminder.ReminderManager; - +import dagger.hilt.android.AndroidEntryPoint; +import io.reactivex.disposables.CompositeDisposable; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.disposables.CompositeDisposable; - @AndroidEntryPoint public class ReminderPickerBottomSheet extends BottomSheetDialogFragment { private static final String TAG = "ReminderPicker"; private static final String ARG_NOTE_ID = "noteId"; - @Inject - DataManager dataManager; + @Inject DataManager dataManager; private int noteId; private Long selectedTime = null; @@ -70,10 +64,12 @@ public class ReminderPickerBottomSheet extends BottomSheetDialogFragment { private ChipGroup intervalChips; private final ActivityResultLauncher notifPermLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - if (granted) saveReminder(); - else showPermissionDenied(); - }); + registerForActivityResult( + new ActivityResultContracts.RequestPermission(), + granted -> { + if (granted) saveReminder(); + else showPermissionDenied(); + }); public static ReminderPickerBottomSheet newInstance(int noteId) { ReminderPickerBottomSheet f = new ReminderPickerBottomSheet(); @@ -91,49 +87,55 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.dialog_reminder_picker, container, false); - selectedTimeCard = view.findViewById(R.id.selectedTimeCard); + selectedTimeCard = view.findViewById(R.id.selectedTimeCard); selectedTimeDisplay = view.findViewById(R.id.selectedTimeDisplay); - repeatLabel = view.findViewById(R.id.repeatLabel); - repeatChips = view.findViewById(R.id.repeatChips); - btnSave = view.findViewById(R.id.btnSave); - btnDeleteReminder = view.findViewById(R.id.btnDeleteReminder); - intervalDivider = view.findViewById(R.id.intervalDivider); + repeatLabel = view.findViewById(R.id.repeatLabel); + repeatChips = view.findViewById(R.id.repeatChips); + btnSave = view.findViewById(R.id.btnSave); + btnDeleteReminder = view.findViewById(R.id.btnDeleteReminder); + intervalDivider = view.findViewById(R.id.intervalDivider); switchRepeatInterval = view.findViewById(R.id.switchRepeatInterval); - intervalChips = view.findViewById(R.id.intervalChips); - - switchRepeatInterval.setOnCheckedChangeListener((btn, checked) -> { - intervalChips.setVisibility(checked ? View.VISIBLE : View.GONE); - if (!checked) { - selectedIntervalMinutes = 0; - } else { - intervalChips.check(R.id.chipInterval10); - selectedIntervalMinutes = 10; - } - }); - - intervalChips.setOnCheckedStateChangeListener((group, checkedIds) -> { - if (!checkedIds.isEmpty()) { - selectedIntervalMinutes = intervalMinutesFromChipId(checkedIds.get(0)); - } - }); + intervalChips = view.findViewById(R.id.intervalChips); + + switchRepeatInterval.setOnCheckedChangeListener( + (btn, checked) -> { + intervalChips.setVisibility(checked ? View.VISIBLE : View.GONE); + if (!checked) { + selectedIntervalMinutes = 0; + } else { + intervalChips.check(R.id.chipInterval10); + selectedIntervalMinutes = 10; + } + }); + + intervalChips.setOnCheckedStateChangeListener( + (group, checkedIds) -> { + if (!checkedIds.isEmpty()) { + selectedIntervalMinutes = intervalMinutesFromChipId(checkedIds.get(0)); + } + }); disposables.add( - dataManager.getNoteForId(noteId) + dataManager + .getNoteForId(noteId) .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread()) - .subscribe(note -> { - currentNote = note; - prefillExistingReminder(note); - }, e -> Log.e(TAG, "load note failed", e)) - ); + .subscribe( + note -> { + currentNote = note; + prefillExistingReminder(note); + }, + e -> Log.e(TAG, "load note failed", e))); view.findViewById(R.id.presetToday).setOnClickListener(v -> applyPreset(todayEvening())); - view.findViewById(R.id.presetTomorrow).setOnClickListener(v -> applyPreset(tomorrowMorning())); + view.findViewById(R.id.presetTomorrow) + .setOnClickListener(v -> applyPreset(tomorrowMorning())); view.findViewById(R.id.btnChooseDate).setOnClickListener(v -> showDatePicker()); btnSave.setOnClickListener(v -> checkPermissionsAndSave()); view.findViewById(R.id.btnCancel).setOnClickListener(v -> dismiss()); @@ -202,49 +204,60 @@ private void applyPreset(long time) { } private void showDatePicker() { - CalendarConstraints constraints = new CalendarConstraints.Builder() - .setValidator(DateValidatorPointForward.now()) - .build(); - - MaterialDatePicker datePicker = MaterialDatePicker.Builder - .datePicker() - .setTitleText(getString(R.string.reminder_choose_date)) - .setSelection(MaterialDatePicker.todayInUtcMilliseconds()) - .setCalendarConstraints(constraints) - .build(); - - datePicker.addOnPositiveButtonClickListener(dateMs -> { - Calendar dateCal = Calendar.getInstance(); - dateCal.setTimeInMillis(dateMs); - - Calendar now = Calendar.getInstance(); - int defaultHour = (dateCal.get(Calendar.DAY_OF_YEAR) == now.get(Calendar.DAY_OF_YEAR) - && dateCal.get(Calendar.YEAR) == now.get(Calendar.YEAR)) - ? now.get(Calendar.HOUR_OF_DAY) : 9; - int defaultMinute = (defaultHour == now.get(Calendar.HOUR_OF_DAY)) - ? now.get(Calendar.MINUTE) + 1 : 0; - - MaterialTimePicker timePicker = new MaterialTimePicker.Builder() - .setTimeFormat(TimeFormat.CLOCK_24H) - .setHour(defaultHour) - .setMinute(defaultMinute) - .build(); - - timePicker.addOnPositiveButtonClickListener(v -> { - dateCal.set(Calendar.HOUR_OF_DAY, timePicker.getHour()); - dateCal.set(Calendar.MINUTE, timePicker.getMinute()); - dateCal.set(Calendar.SECOND, 0); - dateCal.set(Calendar.MILLISECOND, 0); - if (dateCal.getTimeInMillis() <= System.currentTimeMillis()) { - Toast.makeText(requireContext(), - R.string.reminder_past_time_error, Toast.LENGTH_SHORT).show(); - return; - } - applyPreset(dateCal.getTimeInMillis()); - }); - - timePicker.show(getChildFragmentManager(), "timePicker"); - }); + CalendarConstraints constraints = + new CalendarConstraints.Builder() + .setValidator(DateValidatorPointForward.now()) + .build(); + + MaterialDatePicker datePicker = + MaterialDatePicker.Builder.datePicker() + .setTitleText(getString(R.string.reminder_choose_date)) + .setSelection(MaterialDatePicker.todayInUtcMilliseconds()) + .setCalendarConstraints(constraints) + .build(); + + datePicker.addOnPositiveButtonClickListener( + dateMs -> { + Calendar dateCal = Calendar.getInstance(); + dateCal.setTimeInMillis(dateMs); + + Calendar now = Calendar.getInstance(); + int defaultHour = + (dateCal.get(Calendar.DAY_OF_YEAR) == now.get(Calendar.DAY_OF_YEAR) + && dateCal.get(Calendar.YEAR) == now.get(Calendar.YEAR)) + ? now.get(Calendar.HOUR_OF_DAY) + : 9; + int defaultMinute = + (defaultHour == now.get(Calendar.HOUR_OF_DAY)) + ? now.get(Calendar.MINUTE) + 1 + : 0; + + MaterialTimePicker timePicker = + new MaterialTimePicker.Builder() + .setTimeFormat(TimeFormat.CLOCK_24H) + .setHour(defaultHour) + .setMinute(defaultMinute) + .build(); + + timePicker.addOnPositiveButtonClickListener( + v -> { + dateCal.set(Calendar.HOUR_OF_DAY, timePicker.getHour()); + dateCal.set(Calendar.MINUTE, timePicker.getMinute()); + dateCal.set(Calendar.SECOND, 0); + dateCal.set(Calendar.MILLISECOND, 0); + if (dateCal.getTimeInMillis() <= System.currentTimeMillis()) { + Toast.makeText( + requireContext(), + R.string.reminder_past_time_error, + Toast.LENGTH_SHORT) + .show(); + return; + } + applyPreset(dateCal.getTimeInMillis()); + }); + + timePicker.show(getChildFragmentManager(), "timePicker"); + }); datePicker.show(getChildFragmentManager(), "datePicker"); } @@ -264,25 +277,26 @@ private void updateTimeDisplay() { } private void setRepeatChip(ReminderRepeat repeat) { - int chipId = switch (repeat) { - case DAILY -> R.id.chipDaily; - case WEEKLY -> R.id.chipWeekly; - case MONTHLY -> R.id.chipMonthly; - default -> R.id.chipNone; - }; + int chipId = + switch (repeat) { + case DAILY -> R.id.chipDaily; + case WEEKLY -> R.id.chipWeekly; + case MONTHLY -> R.id.chipMonthly; + default -> R.id.chipNone; + }; repeatChips.check(chipId); } private ReminderRepeat getSelectedRepeat() { int checkedId = repeatChips.getCheckedChipId(); - if (checkedId == R.id.chipDaily) return ReminderRepeat.DAILY; - if (checkedId == R.id.chipWeekly) return ReminderRepeat.WEEKLY; + if (checkedId == R.id.chipDaily) return ReminderRepeat.DAILY; + if (checkedId == R.id.chipWeekly) return ReminderRepeat.WEEKLY; if (checkedId == R.id.chipMonthly) return ReminderRepeat.MONTHLY; return ReminderRepeat.NONE; } private int intervalMinutesFromChipId(int chipId) { - if (chipId == R.id.chipInterval5) return 5; + if (chipId == R.id.chipInterval5) return 5; if (chipId == R.id.chipInterval10) return 10; if (chipId == R.id.chipInterval15) return 15; if (chipId == R.id.chipInterval30) return 30; @@ -302,8 +316,8 @@ private void setIntervalChip(int minutes) { private void checkPermissionsAndSave() { if (selectedTime == null || selectedTime <= System.currentTimeMillis()) { - Toast.makeText(requireContext(), - R.string.reminder_past_time_error, Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), R.string.reminder_past_time_error, Toast.LENGTH_SHORT) + .show(); return; } @@ -316,8 +330,9 @@ private void checkPermissionsAndSave() { } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ActivityCompat.checkSelfPermission(requireContext(), - Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + if (ActivityCompat.checkSelfPermission( + requireContext(), Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) { notifPermLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); return; } @@ -334,51 +349,57 @@ private void saveReminder() { final String repeat = selectedRepeat.name(); disposables.add( - dataManager.updateNoteReminderFull(noteId, time, repeat, selectedIntervalMinutes) + dataManager + .updateNoteReminderFull(noteId, time, repeat, selectedIntervalMinutes) .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread()) - .subscribe(() -> { - Note tempNote = new Note(); - tempNote.setId(noteId); - if (currentNote != null) { - tempNote.setTitle(currentNote.getTitle()); - tempNote.setValue(currentNote.getValue()); - } - tempNote.setReminderTime(time); - tempNote.setReminderRepeat(repeat); - tempNote.setReminderIntervalMinutes(selectedIntervalMinutes); - ReminderManager.scheduleReminder(requireContext(), tempNote); - - Bundle result = new Bundle(); - result.putBoolean("hasReminder", true); - result.putLong("reminderTime", time); - getParentFragmentManager().setFragmentResult("reminderChanged", result); - - dismiss(); - }, e -> Log.e(TAG, "save failed", e)) - ); + .subscribe( + () -> { + Note tempNote = new Note(); + tempNote.setId(noteId); + if (currentNote != null) { + tempNote.setTitle(currentNote.getTitle()); + tempNote.setValue(currentNote.getValue()); + } + tempNote.setReminderTime(time); + tempNote.setReminderRepeat(repeat); + tempNote.setReminderIntervalMinutes(selectedIntervalMinutes); + ReminderManager.scheduleReminder(requireContext(), tempNote); + + Bundle result = new Bundle(); + result.putBoolean("hasReminder", true); + result.putLong("reminderTime", time); + getParentFragmentManager() + .setFragmentResult("reminderChanged", result); + + dismiss(); + }, + e -> Log.e(TAG, "save failed", e))); } private void deleteReminder() { disposables.add( - dataManager.clearReminder(noteId) + dataManager + .clearReminder(noteId) .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread()) - .subscribe(() -> { - ReminderManager.cancelReminder(requireContext(), noteId); - - Bundle result = new Bundle(); - result.putBoolean("hasReminder", false); - getParentFragmentManager().setFragmentResult("reminderChanged", result); - - dismiss(); - }, e -> Log.e(TAG, "delete failed", e)) - ); + .subscribe( + () -> { + ReminderManager.cancelReminder(requireContext(), noteId); + + Bundle result = new Bundle(); + result.putBoolean("hasReminder", false); + getParentFragmentManager() + .setFragmentResult("reminderChanged", result); + + dismiss(); + }, + e -> Log.e(TAG, "delete failed", e))); } private void showPermissionDenied() { - Toast.makeText(requireContext(), - R.string.reminder_permission_denied, Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), R.string.reminder_permission_denied, Toast.LENGTH_SHORT) + .show(); } @Override diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ShareOptionsDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ShareOptionsDialog.java index 00b38ab..303bb6d 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ShareOptionsDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ShareOptionsDialog.java @@ -8,28 +8,21 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.material.bottomsheet.BottomSheetDialog; import com.pasich.mynotes.base.dialog.BaseDialogBottomSheets; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.databinding.DialogShareOptionsBinding; import com.pasich.mynotes.utils.file.FileExportUtils; - -import java.util.List; - import io.reactivex.schedulers.Schedulers; +import java.util.List; -/** - * Dialog for advanced sharing options - */ +/** Dialog for advanced sharing options */ public class ShareOptionsDialog extends BaseDialogBottomSheets { - private Note mNote; private List mSelectedNotes; private boolean isDataExport; @@ -42,8 +35,7 @@ public class ShareOptionsDialog extends BaseDialogBottomSheets { private String currentNoteTitle; private String currentNoteContent; - public ShareOptionsDialog() { - } + public ShareOptionsDialog() {} public ShareOptionsDialog(Note note) { this(); @@ -95,49 +87,73 @@ public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Initialize activity result launchers - saveTxtLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { - Uri uri = result.getData().getData(); - - if (uri != null && currentNoteTitle != null && currentNoteContent != null) { - Schedulers.io().scheduleDirect(() -> FileExportUtils.saveTxtToUri(requireContext(), uri, currentNoteTitle, currentNoteContent)); - } - } - dismiss(); // Dismiss after handling result - } - ); - - savePdfLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { - Uri uri = result.getData().getData(); - if (uri != null) { - FileExportUtils.savePdfToUri(requireContext(), uri, currentNoteTitle, currentNoteContent); - } - } - dismiss(); // Dismiss after handling result - } - ); - - saveHtmlLauncher = registerForActivityResult( - new ActivityResultContracts.StartActivityForResult(), - result -> { - if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { - Uri uri = result.getData().getData(); - if (uri != null) { - FileExportUtils.saveHtmlToUri(requireContext(), uri, currentNoteTitle, currentNoteContent, mSelectedNotes); - } - } - dismiss(); // Dismiss after handling result - } - ); + saveTxtLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK + && result.getData() != null) { + Uri uri = result.getData().getData(); + + if (uri != null + && currentNoteTitle != null + && currentNoteContent != null) { + Schedulers.io() + .scheduleDirect( + () -> + FileExportUtils.saveTxtToUri( + requireContext(), + uri, + currentNoteTitle, + currentNoteContent)); + } + } + dismiss(); // Dismiss after handling result + }); + + savePdfLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK + && result.getData() != null) { + Uri uri = result.getData().getData(); + if (uri != null) { + FileExportUtils.savePdfToUri( + requireContext(), + uri, + currentNoteTitle, + currentNoteContent); + } + } + dismiss(); // Dismiss after handling result + }); + + saveHtmlLauncher = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK + && result.getData() != null) { + Uri uri = result.getData().getData(); + if (uri != null) { + FileExportUtils.saveHtmlToUri( + requireContext(), + uri, + currentNoteTitle, + currentNoteContent, + mSelectedNotes); + } + } + dismiss(); // Dismiss after handling result + }); } @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { vibrateOpenDialog(true); setState((BottomSheetDialog) requireDialog()); binding = DialogShareOptionsBinding.inflate(getLayoutInflater(), container, false); @@ -153,37 +169,46 @@ public void setState(BottomSheetDialog dialog) { @Override public void initListeners() { - binding.saveToGoogleDrive.setOnClickListener(v -> { - prepareNoteData(); - String formattedContent = FileExportUtils.formatNoteContent(currentNoteTitle, currentNoteContent); - FileExportUtils.saveToGoogleDrive(requireContext(), currentNoteTitle, formattedContent); - dismiss(); - }); - - binding.saveAsTxt.setOnClickListener(v -> { - prepareNoteData(); - Intent intent = FileExportUtils.createSaveTxtIntent(currentNoteTitle); - saveTxtLauncher.launch(intent); - }); - - binding.saveAsPdf.setOnClickListener(v -> { - prepareNoteData(); - Intent intent = FileExportUtils.createSavePdfIntent(currentNoteTitle); - savePdfLauncher.launch(intent); - }); - - binding.saveAsHtml.setOnClickListener(v -> { - prepareNoteData(); - Intent intent = FileExportUtils.createSaveHtmlIntent(currentNoteTitle); - saveHtmlLauncher.launch(intent); - }); - - binding.shareViaOtherApps.setOnClickListener(v -> { - prepareNoteData(); - String formattedContent = FileExportUtils.formatNoteContent(currentNoteTitle, currentNoteContent); - FileExportUtils.shareViaOtherApps(requireActivity(), currentNoteTitle, formattedContent); - dismiss(); - }); + binding.saveToGoogleDrive.setOnClickListener( + v -> { + prepareNoteData(); + String formattedContent = + FileExportUtils.formatNoteContent(currentNoteTitle, currentNoteContent); + FileExportUtils.saveToGoogleDrive( + requireContext(), currentNoteTitle, formattedContent); + dismiss(); + }); + + binding.saveAsTxt.setOnClickListener( + v -> { + prepareNoteData(); + Intent intent = FileExportUtils.createSaveTxtIntent(currentNoteTitle); + saveTxtLauncher.launch(intent); + }); + + binding.saveAsPdf.setOnClickListener( + v -> { + prepareNoteData(); + Intent intent = FileExportUtils.createSavePdfIntent(currentNoteTitle); + savePdfLauncher.launch(intent); + }); + + binding.saveAsHtml.setOnClickListener( + v -> { + prepareNoteData(); + Intent intent = FileExportUtils.createSaveHtmlIntent(currentNoteTitle); + saveHtmlLauncher.launch(intent); + }); + + binding.shareViaOtherApps.setOnClickListener( + v -> { + prepareNoteData(); + String formattedContent = + FileExportUtils.formatNoteContent(currentNoteTitle, currentNoteContent); + FileExportUtils.shareViaOtherApps( + requireActivity(), currentNoteTitle, formattedContent); + dismiss(); + }); } @Override @@ -202,7 +227,10 @@ private void clearListeners() { private void prepareNoteData() { if (mNote != null) { - currentNoteTitle = (mNote.getTitle() == null || mNote.getTitle().isEmpty()) ? "***" : mNote.getTitle(); + currentNoteTitle = + (mNote.getTitle() == null || mNote.getTitle().isEmpty()) + ? "***" + : mNote.getTitle(); currentNoteContent = mNote.getValue(); } else if (mSelectedNotes != null && !mSelectedNotes.isEmpty()) { @@ -218,8 +246,10 @@ private void prepareNoteData() { for (int i = 0; i < mSelectedNotes.size(); i++) { Note note = mSelectedNotes.get(i); - String noteTitle = (note.getTitle() == null || note.getTitle().isEmpty()) ? "***" : note.getTitle(); - + String noteTitle = + (note.getTitle() == null || note.getTitle().isEmpty()) + ? "***" + : note.getTitle(); combinedContent.append(noteTitle).append("\n\n"); if (note.getValue() != null) { @@ -236,5 +266,4 @@ private void prepareNoteData() { currentNoteContent = ""; } } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TaskReminderPickerBottomSheet.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TaskReminderPickerBottomSheet.java index e52a86c..8efeec9 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TaskReminderPickerBottomSheet.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TaskReminderPickerBottomSheet.java @@ -12,13 +12,11 @@ import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; - import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.ActivityCompat; - import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import com.google.android.material.button.MaterialButton; import com.google.android.material.chip.ChipGroup; @@ -33,17 +31,14 @@ import com.pasich.mynotes.data.model.Task; import com.pasich.mynotes.utils.reminder.ReminderManager; import com.pasich.mynotes.utils.reminder.TaskReminderManager; - +import dagger.hilt.android.AndroidEntryPoint; +import io.reactivex.disposables.CompositeDisposable; import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.Date; import java.util.Locale; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.disposables.CompositeDisposable; - @AndroidEntryPoint public class TaskReminderPickerBottomSheet extends BottomSheetDialogFragment { @@ -53,8 +48,7 @@ public class TaskReminderPickerBottomSheet extends BottomSheetDialogFragment { private static final String ARG_REMINDER_TIME = "reminderTime"; private static final String ARG_INTERVAL_MINUTES = "intervalMinutes"; - @Inject - DataManager dataManager; + @Inject DataManager dataManager; private int taskId; private String taskTitle; @@ -71,14 +65,20 @@ public class TaskReminderPickerBottomSheet extends BottomSheetDialogFragment { private View intervalDivider; private final ActivityResultLauncher notifPermLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - if (granted) saveReminder(); - else Toast.makeText(requireContext(), - R.string.reminder_permission_denied, Toast.LENGTH_SHORT).show(); - }); - - public static TaskReminderPickerBottomSheet newInstance(int taskId, String taskTitle, - Long currentReminderTime, int currentIntervalMinutes) { + registerForActivityResult( + new ActivityResultContracts.RequestPermission(), + granted -> { + if (granted) saveReminder(); + else + Toast.makeText( + requireContext(), + R.string.reminder_permission_denied, + Toast.LENGTH_SHORT) + .show(); + }); + + public static TaskReminderPickerBottomSheet newInstance( + int taskId, String taskTitle, Long currentReminderTime, int currentIntervalMinutes) { TaskReminderPickerBottomSheet f = new TaskReminderPickerBottomSheet(); Bundle args = new Bundle(); args.putInt(ARG_TASK_ID, taskId); @@ -102,39 +102,42 @@ public void onCreate(@Nullable Bundle savedInstanceState) { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, - @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { View view = inflater.inflate(R.layout.dialog_reminder_picker, container, false); - selectedTimeCard = view.findViewById(R.id.selectedTimeCard); - selectedTimeDisplay = view.findViewById(R.id.selectedTimeDisplay); - btnSave = view.findViewById(R.id.btnSave); - btnDeleteReminder = view.findViewById(R.id.btnDeleteReminder); + selectedTimeCard = view.findViewById(R.id.selectedTimeCard); + selectedTimeDisplay = view.findViewById(R.id.selectedTimeDisplay); + btnSave = view.findViewById(R.id.btnSave); + btnDeleteReminder = view.findViewById(R.id.btnDeleteReminder); switchRepeatInterval = view.findViewById(R.id.switchRepeatInterval); - intervalChips = view.findViewById(R.id.intervalChips); - intervalDivider = view.findViewById(R.id.intervalDivider); + intervalChips = view.findViewById(R.id.intervalChips); + intervalDivider = view.findViewById(R.id.intervalDivider); // Hide repeat section — tasks don't use DAILY/WEEKLY/MONTHLY view.findViewById(R.id.repeatLabel).setVisibility(View.GONE); view.findViewById(R.id.repeatChips).setVisibility(View.GONE); // Wire interval switch - switchRepeatInterval.setOnCheckedChangeListener((btn, checked) -> { - intervalChips.setVisibility(checked ? View.VISIBLE : View.GONE); - if (!checked) { - selectedIntervalMinutes = 0; - } else { - intervalChips.check(R.id.chipInterval10); - selectedIntervalMinutes = 10; - } - }); - - intervalChips.setOnCheckedStateChangeListener((group, checkedIds) -> { - if (!checkedIds.isEmpty()) { - selectedIntervalMinutes = intervalMinutesFromChipId(checkedIds.get(0)); - } - }); + switchRepeatInterval.setOnCheckedChangeListener( + (btn, checked) -> { + intervalChips.setVisibility(checked ? View.VISIBLE : View.GONE); + if (!checked) { + selectedIntervalMinutes = 0; + } else { + intervalChips.check(R.id.chipInterval10); + selectedIntervalMinutes = 10; + } + }); + + intervalChips.setOnCheckedStateChangeListener( + (group, checkedIds) -> { + if (!checkedIds.isEmpty()) { + selectedIntervalMinutes = intervalMinutesFromChipId(checkedIds.get(0)); + } + }); // Pre-fill existing reminder if (selectedTime != null && selectedTime > System.currentTimeMillis()) { @@ -152,7 +155,8 @@ public View onCreateView(@NonNull LayoutInflater inflater, } view.findViewById(R.id.presetToday).setOnClickListener(v -> applyPreset(todayEvening())); - view.findViewById(R.id.presetTomorrow).setOnClickListener(v -> applyPreset(tomorrowMorning())); + view.findViewById(R.id.presetTomorrow) + .setOnClickListener(v -> applyPreset(tomorrowMorning())); view.findViewById(R.id.btnChooseDate).setOnClickListener(v -> showDatePicker()); btnSave.setOnClickListener(v -> checkPermissionsAndSave()); view.findViewById(R.id.btnCancel).setOnClickListener(v -> dismiss()); @@ -191,42 +195,54 @@ private void applyPreset(long time) { } private void showDatePicker() { - CalendarConstraints constraints = new CalendarConstraints.Builder() - .setValidator(DateValidatorPointForward.now()) - .build(); - MaterialDatePicker datePicker = MaterialDatePicker.Builder.datePicker() - .setTitleText(getString(R.string.reminder_choose_date)) - .setSelection(MaterialDatePicker.todayInUtcMilliseconds()) - .setCalendarConstraints(constraints) - .build(); - datePicker.addOnPositiveButtonClickListener(dateMs -> { - Calendar dateCal = Calendar.getInstance(); - dateCal.setTimeInMillis(dateMs); - Calendar now = Calendar.getInstance(); - int defaultHour = (dateCal.get(Calendar.DAY_OF_YEAR) == now.get(Calendar.DAY_OF_YEAR) - && dateCal.get(Calendar.YEAR) == now.get(Calendar.YEAR)) - ? now.get(Calendar.HOUR_OF_DAY) : 9; - int defaultMinute = (defaultHour == now.get(Calendar.HOUR_OF_DAY)) - ? now.get(Calendar.MINUTE) + 1 : 0; - MaterialTimePicker timePicker = new MaterialTimePicker.Builder() - .setTimeFormat(TimeFormat.CLOCK_24H) - .setHour(defaultHour) - .setMinute(defaultMinute) - .build(); - timePicker.addOnPositiveButtonClickListener(v -> { - dateCal.set(Calendar.HOUR_OF_DAY, timePicker.getHour()); - dateCal.set(Calendar.MINUTE, timePicker.getMinute()); - dateCal.set(Calendar.SECOND, 0); - dateCal.set(Calendar.MILLISECOND, 0); - if (dateCal.getTimeInMillis() <= System.currentTimeMillis()) { - Toast.makeText(requireContext(), - R.string.reminder_past_time_error, Toast.LENGTH_SHORT).show(); - return; - } - applyPreset(dateCal.getTimeInMillis()); - }); - timePicker.show(getChildFragmentManager(), "timePicker"); - }); + CalendarConstraints constraints = + new CalendarConstraints.Builder() + .setValidator(DateValidatorPointForward.now()) + .build(); + MaterialDatePicker datePicker = + MaterialDatePicker.Builder.datePicker() + .setTitleText(getString(R.string.reminder_choose_date)) + .setSelection(MaterialDatePicker.todayInUtcMilliseconds()) + .setCalendarConstraints(constraints) + .build(); + datePicker.addOnPositiveButtonClickListener( + dateMs -> { + Calendar dateCal = Calendar.getInstance(); + dateCal.setTimeInMillis(dateMs); + Calendar now = Calendar.getInstance(); + int defaultHour = + (dateCal.get(Calendar.DAY_OF_YEAR) == now.get(Calendar.DAY_OF_YEAR) + && dateCal.get(Calendar.YEAR) == now.get(Calendar.YEAR)) + ? now.get(Calendar.HOUR_OF_DAY) + : 9; + int defaultMinute = + (defaultHour == now.get(Calendar.HOUR_OF_DAY)) + ? now.get(Calendar.MINUTE) + 1 + : 0; + MaterialTimePicker timePicker = + new MaterialTimePicker.Builder() + .setTimeFormat(TimeFormat.CLOCK_24H) + .setHour(defaultHour) + .setMinute(defaultMinute) + .build(); + timePicker.addOnPositiveButtonClickListener( + v -> { + dateCal.set(Calendar.HOUR_OF_DAY, timePicker.getHour()); + dateCal.set(Calendar.MINUTE, timePicker.getMinute()); + dateCal.set(Calendar.SECOND, 0); + dateCal.set(Calendar.MILLISECOND, 0); + if (dateCal.getTimeInMillis() <= System.currentTimeMillis()) { + Toast.makeText( + requireContext(), + R.string.reminder_past_time_error, + Toast.LENGTH_SHORT) + .show(); + return; + } + applyPreset(dateCal.getTimeInMillis()); + }); + timePicker.show(getChildFragmentManager(), "timePicker"); + }); datePicker.show(getChildFragmentManager(), "datePicker"); } @@ -243,7 +259,7 @@ private void updateTimeDisplay() { } private int intervalMinutesFromChipId(int chipId) { - if (chipId == R.id.chipInterval5) return 5; + if (chipId == R.id.chipInterval5) return 5; if (chipId == R.id.chipInterval10) return 10; if (chipId == R.id.chipInterval15) return 15; if (chipId == R.id.chipInterval30) return 30; @@ -263,7 +279,8 @@ private void setIntervalChip(int minutes) { private void checkPermissionsAndSave() { if (selectedTime == null || selectedTime <= System.currentTimeMillis()) { - Toast.makeText(requireContext(), R.string.reminder_past_time_error, Toast.LENGTH_SHORT).show(); + Toast.makeText(requireContext(), R.string.reminder_past_time_error, Toast.LENGTH_SHORT) + .show(); return; } if (!ReminderManager.canScheduleExact(requireContext())) { @@ -273,8 +290,9 @@ private void checkPermissionsAndSave() { return; } if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { - if (ActivityCompat.checkSelfPermission(requireContext(), - Manifest.permission.POST_NOTIFICATIONS) != PackageManager.PERMISSION_GRANTED) { + if (ActivityCompat.checkSelfPermission( + requireContext(), Manifest.permission.POST_NOTIFICATIONS) + != PackageManager.PERMISSION_GRANTED) { notifPermLauncher.launch(Manifest.permission.POST_NOTIFICATIONS); return; } @@ -288,38 +306,45 @@ private void saveReminder() { final int interval = selectedIntervalMinutes; disposables.add( - dataManager.setTaskReminderFull(taskId, time, interval) + dataManager + .setTaskReminderFull(taskId, time, interval) .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread()) - .subscribe(() -> { - Task tempTask = new Task(); - tempTask.setId(taskId); - tempTask.setTitle(taskTitle); - tempTask.setReminderTime(time); - tempTask.setReminderIntervalMinutes(interval); - TaskReminderManager.scheduleReminder(requireContext(), tempTask); - - Bundle result = new Bundle(); - result.putBoolean("saved", true); - getParentFragmentManager().setFragmentResult("taskReminderChanged", result); - dismiss(); - }, e -> Log.e(TAG, "save failed", e)) - ); + .subscribe( + () -> { + Task tempTask = new Task(); + tempTask.setId(taskId); + tempTask.setTitle(taskTitle); + tempTask.setReminderTime(time); + tempTask.setReminderIntervalMinutes(interval); + TaskReminderManager.scheduleReminder( + requireContext(), tempTask); + + Bundle result = new Bundle(); + result.putBoolean("saved", true); + getParentFragmentManager() + .setFragmentResult("taskReminderChanged", result); + dismiss(); + }, + e -> Log.e(TAG, "save failed", e))); } private void deleteReminder() { disposables.add( - dataManager.clearTaskReminder(taskId) + dataManager + .clearTaskReminder(taskId) .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .observeOn(io.reactivex.android.schedulers.AndroidSchedulers.mainThread()) - .subscribe(() -> { - TaskReminderManager.cancelReminder(requireContext(), taskId); - Bundle result = new Bundle(); - result.putBoolean("saved", false); - getParentFragmentManager().setFragmentResult("taskReminderChanged", result); - dismiss(); - }, e -> Log.e(TAG, "delete failed", e)) - ); + .subscribe( + () -> { + TaskReminderManager.cancelReminder(requireContext(), taskId); + Bundle result = new Bundle(); + result.putBoolean("saved", false); + getParentFragmentManager() + .setFragmentResult("taskReminderChanged", result); + dismiss(); + }, + e -> Log.e(TAG, "delete failed", e))); } @Override diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ThanksDonatDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ThanksDonatDialog.java index 47b020b..f70bfe1 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ThanksDonatDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/ThanksDonatDialog.java @@ -5,10 +5,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.pasich.mynotes.base.dialog.BaseDialogBottomSheets; import com.pasich.mynotes.databinding.DialogThanksDonatBinding; @@ -16,26 +14,25 @@ public class ThanksDonatDialog extends BaseDialogBottomSheets { public DialogThanksDonatBinding binding; - public ThanksDonatDialog() { - } + public ThanksDonatDialog() {} @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = DialogThanksDonatBinding.inflate(getLayoutInflater()); initListeners(); return binding.getRoot(); } - public void initListeners() { binding.closeDialog.setOnClickListener(v -> dismiss()); } - @Override public void onDismiss(@NonNull DialogInterface dialog) { super.onDismiss(dialog); - } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TrashInfoDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TrashInfoDialog.java index effc161..89e685f 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TrashInfoDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/TrashInfoDialog.java @@ -1,7 +1,6 @@ package com.pasich.mynotes.ui.view.dialogs; import android.content.Context; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/UpdateChangelogDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/UpdateChangelogDialog.java index 5b823a3..24a0b9f 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/UpdateChangelogDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/UpdateChangelogDialog.java @@ -7,35 +7,28 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.material.bottomsheet.BottomSheetDialog; import com.pasich.mynotes.base.dialog.BaseDialogBottomSheets; import com.pasich.mynotes.databinding.DialogUpdateChangelogBinding; import com.pasich.mynotes.ui.view.activity.SupportActivity; import com.pasich.mynotes.utils.changelog.ChangelogManager; import com.pasich.mynotes.utils.navigation.GoogleTranslateHelper; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; import io.noties.markwon.Markwon; +import javax.inject.Inject; @AndroidEntryPoint public class UpdateChangelogDialog extends BaseDialogBottomSheets { - @Inject - ChangelogManager changelogManager; + @Inject ChangelogManager changelogManager; - @Inject - Markwon markwon; + @Inject Markwon markwon; private DialogUpdateChangelogBinding binding; - public UpdateChangelogDialog() { - } + public UpdateChangelogDialog() {} public static UpdateChangelogDialog newInstance() { return new UpdateChangelogDialog(); @@ -45,8 +38,7 @@ public static UpdateChangelogDialog newInstance() { public View onCreateView( @NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState - ) { + @Nullable Bundle savedInstanceState) { setState((BottomSheetDialog) requireDialog()); binding = DialogUpdateChangelogBinding.inflate(inflater, container, false); @@ -76,28 +68,30 @@ private void initChangelogContent() { public void initListeners() { // Close button - binding.okButton.setOnClickListener(v -> { - changelogManager.markChangelogRead(); - dismiss(); - }); + binding.okButton.setOnClickListener( + v -> { + changelogManager.markChangelogRead(); + dismiss(); + }); // Support button - binding.supportButton.setOnClickListener(v -> { - changelogManager.markChangelogRead(); - Intent intent = new Intent(requireActivity(), SupportActivity.class); - intent.putExtra(PURCHASES_OPEN_EXTRA, true); - startActivity(intent); - - dismiss(); - }); - - binding.translateIcon.setOnClickListener(v -> { - String text = binding.changelogText.getText().toString(); - if (!text.isEmpty()) { - GoogleTranslateHelper.startTranslation(requireActivity(), text); - } - }); - + binding.supportButton.setOnClickListener( + v -> { + changelogManager.markChangelogRead(); + Intent intent = new Intent(requireActivity(), SupportActivity.class); + intent.putExtra(PURCHASES_OPEN_EXTRA, true); + startActivity(intent); + + dismiss(); + }); + + binding.translateIcon.setOnClickListener( + v -> { + String text = binding.changelogText.getText().toString(); + if (!text.isEmpty()) { + GoogleTranslateHelper.startTranslation(requireActivity(), text); + } + }); } @Override diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/AllTagSelectDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/AllTagSelectDialog.java index 3308821..c9e62e0 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/AllTagSelectDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/AllTagSelectDialog.java @@ -2,26 +2,21 @@ import android.content.Context; import android.view.LayoutInflater; -import android.view.View; import android.widget.ScrollView; -import android.widget.TextView; - import androidx.appcompat.app.AlertDialog; - import com.google.android.material.chip.Chip; import com.google.android.material.chip.ChipGroup; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Tag; - import java.util.List; public class AllTagSelectDialog { public static void show(Context ctx, List tags, Callback callback) { - ScrollView root = (ScrollView) LayoutInflater.from(ctx) - .inflate(R.layout.dialog_tag_select, null); + ScrollView root = + (ScrollView) LayoutInflater.from(ctx).inflate(R.layout.dialog_tag_select, null); ChipGroup chipGroup = root.findViewById(R.id.chipGroupTags); @@ -39,10 +34,11 @@ public static void show(Context ctx, List tags, Callback callback) { chip.setCheckable(false); chip.setClickable(true); - chip.setOnClickListener(v -> { - callback.onTagSelected(tag); - dialog.dismiss(); - }); + chip.setOnClickListener( + v -> { + callback.onTagSelected(tag); + dialog.dismiss(); + }); chipGroup.addView(chip); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/DeleteTagDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/DeleteTagDialog.java index 46cf4a5..0c5a061 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/DeleteTagDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/DeleteTagDialog.java @@ -1,43 +1,39 @@ package com.pasich.mynotes.ui.view.dialogs.main; - import android.content.DialogInterface; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.pasich.mynotes.R; import com.pasich.mynotes.base.dialog.BaseDialogBottomSheets; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.databinding.DialogDeleteTagBinding; import com.pasich.mynotes.ui.contract.dialogs.DeleteTagDialogContract; import com.pasich.mynotes.ui.presenter.dialogs.DeleteTagDialogPresenter; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; - +import javax.inject.Inject; @AndroidEntryPoint -public class DeleteTagDialog extends BaseDialogBottomSheets implements DeleteTagDialogContract.view { +public class DeleteTagDialog extends BaseDialogBottomSheets + implements DeleteTagDialogContract.view { - @Inject - public DeleteTagDialogPresenter mPresenter; + @Inject public DeleteTagDialogPresenter mPresenter; private Tag tag; private DialogDeleteTagBinding binding; - public DeleteTagDialog(Tag tag) { this.tag = tag; } @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = DialogDeleteTagBinding.inflate(getLayoutInflater()); mPresenter.attachView(this); mPresenter.viewIsReady(); @@ -49,25 +45,25 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c @Override public void onStart() { super.onStart(); - binding.deleteTagAndNotes.setVisibility(mPresenter.getCountNotesForTag() > 0 ? View.VISIBLE : View.GONE); + binding.deleteTagAndNotes.setVisibility( + mPresenter.getCountNotesForTag() > 0 ? View.VISIBLE : View.GONE); } @Override public void initListeners() { - binding.deleteTag.setOnClickListener(v -> { - mPresenter.deleteTagUnchecked(tag); - dismiss(); - }); - - - binding.deleteTagAndNotes.setOnClickListener(v -> { - mPresenter.deleteTagAndNotes(tag); - dismiss(); - }); - + binding.deleteTag.setOnClickListener( + v -> { + mPresenter.deleteTagUnchecked(tag); + dismiss(); + }); + + binding.deleteTagAndNotes.setOnClickListener( + v -> { + mPresenter.deleteTagAndNotes(tag); + dismiss(); + }); binding.cancel.setOnClickListener(v -> dismiss()); - } @Override diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/NameTagDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/NameTagDialog.java index 1aef675..63115cb 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/NameTagDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/NameTagDialog.java @@ -11,11 +11,9 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.inputmethod.EditorInfo; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.content.res.AppCompatResources; - import com.google.android.material.bottomsheet.BottomSheetDialog; import com.pasich.mynotes.R; import com.pasich.mynotes.base.dialog.BaseDialogBottomSheets; @@ -23,29 +21,22 @@ import com.pasich.mynotes.databinding.DialogNameTagBinding; import com.pasich.mynotes.ui.contract.dialogs.NameTagDialogContract; import com.pasich.mynotes.ui.presenter.dialogs.NameTagDialogPresenter; - +import dagger.hilt.android.AndroidEntryPoint; import java.util.Objects; - import javax.inject.Inject; -import dagger.hilt.android.AndroidEntryPoint; - @AndroidEntryPoint public class NameTagDialog extends BaseDialogBottomSheets implements NameTagDialogContract.view { - private final Tag mTag; - @Inject - public NameTagDialogPresenter mPresenter; + @Inject public NameTagDialogPresenter mPresenter; private DialogNameTagBinding binding; private boolean errorText = true; private int newTagPosition = -1; - public NameTagDialog(int newPosition) { this.mTag = null; this.newTagPosition = newPosition; - } public NameTagDialog(Tag tag) { @@ -54,17 +45,21 @@ public NameTagDialog(Tag tag) { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = DialogNameTagBinding.inflate(getLayoutInflater()); mPresenter.attachView(this); mPresenter.viewIsReady(); - if (getTag() != null && getTag().equals("RenameTag") && mTag != null) { binding.nameTag.setText(mTag.getNameTag()); - binding.outlinedTextField.setEndIconDrawable(AppCompatResources.getDrawable(requireContext(), R.drawable.ic_rename)); - binding.nameTag.setSelection(Objects.requireNonNull(binding.nameTag.getText()).length()); + binding.outlinedTextField.setEndIconDrawable( + AppCompatResources.getDrawable(requireContext(), R.drawable.ic_rename)); + binding.nameTag.setSelection( + Objects.requireNonNull(binding.nameTag.getText()).length()); } binding.outlinedTextField.requestFocus(); @@ -85,37 +80,32 @@ public void setState(BottomSheetDialog dialog) { public void initListeners() { binding.outlinedTextField.setEndIconOnClickListener(v -> saveTag()); - - binding.nameTag.setOnEditorActionListener((v, actionId, event) -> { - if (actionId == EditorInfo.IME_ACTION_DONE) { - saveTag(); - return true; - } else return false; - }); - binding.nameTag.addTextChangedListener(new TextWatcher() { - - @Override - public void beforeTextChanged(CharSequence s, int start, int count, int after) { - - } - - @Override - public void onTextChanged(CharSequence s, int start, int before, int count) { - - } - - @Override - public void afterTextChanged(Editable s) { - validateText(s.toString().trim().length()); - } - }); + binding.nameTag.setOnEditorActionListener( + (v, actionId, event) -> { + if (actionId == EditorInfo.IME_ACTION_DONE) { + saveTag(); + return true; + } else return false; + }); + binding.nameTag.addTextChangedListener( + new TextWatcher() { + + @Override + public void beforeTextChanged( + CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) {} + + @Override + public void afterTextChanged(Editable s) { + validateText(s.toString().trim().length()); + } + }); } @Override - public void onInfoSnack(int resID, View view, int typeInfo, int time) { - - } - + public void onInfoSnack(int resID, View view, int typeInfo, int time) {} private void validateText(int length) { if (length >= MAX_NAME_TAG + 1) { @@ -129,13 +119,17 @@ private void validateText(int length) { else if (length < (MAX_NAME_TAG + 1) - 1) errorText = false; } - private void saveTag() { if (!errorText) { if (getTag() != null && getTag().equals("RenameTag") && mTag != null) { - mPresenter.editNameTag(Objects.requireNonNull(binding.nameTag.getText()).toString(), mTag); + mPresenter.editNameTag( + Objects.requireNonNull(binding.nameTag.getText()).toString(), mTag); } else { - Tag newTag = new Tag().create(Objects.requireNonNull(binding.nameTag.getText()).toString()); + Tag newTag = + new Tag() + .create( + Objects.requireNonNull(binding.nameTag.getText()) + .toString()); newTag.setPosition(newTagPosition); mPresenter.saveTag(newTag); } @@ -143,14 +137,13 @@ private void saveTag() { } } - @Override public void onDismiss(@NonNull DialogInterface dialog) { super.onDismiss(dialog); mPresenter.detachView(); binding.nameTag.addTextChangedListener(null); - requireActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); + requireActivity() + .getWindow() + .setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN); } - - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/SingleTagSelectDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/SingleTagSelectDialog.java index 3a3bd23..05414af 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/SingleTagSelectDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/SingleTagSelectDialog.java @@ -5,27 +5,20 @@ import android.view.View; import android.widget.ScrollView; import android.widget.TextView; - import androidx.appcompat.app.AlertDialog; - import com.google.android.material.chip.Chip; import com.google.android.material.chip.ChipGroup; import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Tag; - import java.util.List; public class SingleTagSelectDialog { - public static void show( - Context ctx, - List allTags, - Callback callback - ) { + public static void show(Context ctx, List allTags, Callback callback) { - ScrollView root = (ScrollView) LayoutInflater.from(ctx) - .inflate(R.layout.dialog_tag_select, null); + ScrollView root = + (ScrollView) LayoutInflater.from(ctx).inflate(R.layout.dialog_tag_select, null); TextView desc = root.findViewById(R.id.tagDialogDescription); desc.setVisibility(View.VISIBLE); @@ -41,12 +34,12 @@ public static void show( clearChip.setCheckable(true); clearChip.setClickable(true); - - clearChip.setOnCheckedChangeListener((v, checked) -> { - if (checked) { - selectedTag[0] = ""; - } - }); + clearChip.setOnCheckedChangeListener( + (v, checked) -> { + if (checked) { + selectedTag[0] = ""; + } + }); chipGroup.addView(clearChip); @@ -60,26 +53,32 @@ public static void show( chip.setChecked(true); } - chip.setOnCheckedChangeListener((v, checked) -> { - if (checked) { - selectedTag[0] = tag.getNameTag(); - } - }); + chip.setOnCheckedChangeListener( + (v, checked) -> { + if (checked) { + selectedTag[0] = tag.getNameTag(); + } + }); chipGroup.addView(chip); } - AlertDialog dialog = new MaterialAlertDialogBuilder(ctx, R.style.Theme_MyNotes_Dialog) - .setView(root) - .setNegativeButton(R.string.cancel, (d, w) -> { - callback.onCancel(); - d.dismiss(); - }) - .setPositiveButton(R.string.apply, (d, w) -> { - callback.onTagSelected(selectedTag[0]); - d.dismiss(); - }) - .create(); + AlertDialog dialog = + new MaterialAlertDialogBuilder(ctx, R.style.Theme_MyNotes_Dialog) + .setView(root) + .setNegativeButton( + R.string.cancel, + (d, w) -> { + callback.onCancel(); + d.dismiss(); + }) + .setPositiveButton( + R.string.apply, + (d, w) -> { + callback.onTagSelected(selectedTag[0]); + d.dismiss(); + }) + .create(); dialog.show(); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/SortDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/SortDialog.java index e5f78f6..e8a6b75 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/SortDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/SortDialog.java @@ -6,10 +6,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.material.bottomsheet.BottomSheetDialog; import com.google.android.material.color.MaterialColors; import com.pasich.mynotes.R; @@ -17,16 +15,13 @@ import com.pasich.mynotes.cache.AppPreferencesCache; import com.pasich.mynotes.databinding.DialogChooseSortBinding; import com.pasich.mynotes.utils.constants.settings.SortParam; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; +import javax.inject.Inject; @AndroidEntryPoint public class SortDialog extends BaseDialogBottomSheets { - @Inject - AppPreferencesCache cache; + @Inject AppPreferencesCache cache; private boolean isTagsSort; private SortListener listener; private DialogChooseSortBinding binding; @@ -34,8 +29,7 @@ public class SortDialog extends BaseDialogBottomSheets { private String tagsSortParam; @Inject - public SortDialog() { - } + public SortDialog() {} public static SortDialog newInstance(boolean isTagsSort) { SortDialog dialog = new SortDialog(); @@ -51,7 +45,10 @@ public void setListener(SortListener listener) { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = DialogChooseSortBinding.inflate(inflater, container, false); isTagsSort = getArguments() != null && getArguments().getBoolean("isTagsSort", false); @@ -105,8 +102,16 @@ private void setupTagsView() { } private void selectedAutoItem(String param) { - int colorBackground = MaterialColors.getColor(requireContext(), com.google.android.material.R.attr.colorSurfaceVariant, Color.GRAY); - int colorText = MaterialColors.getColor(requireContext(), com.google.android.material.R.attr.colorPrimaryFixed, Color.BLACK); + int colorBackground = + MaterialColors.getColor( + requireContext(), + com.google.android.material.R.attr.colorSurfaceVariant, + Color.GRAY); + int colorText = + MaterialColors.getColor( + requireContext(), + com.google.android.material.R.attr.colorPrimaryFixed, + Color.BLACK); binding.DataSortCheck.setVisibility(View.GONE); binding.DataReserveCheck.setVisibility(View.GONE); @@ -126,8 +131,16 @@ private void selectedAutoItem(String param) { } private void selectedAutoItemTags(String param) { - int colorBackground = MaterialColors.getColor(requireContext(), com.google.android.material.R.attr.colorSurfaceVariant, Color.GRAY); - int colorText = MaterialColors.getColor(requireContext(), com.google.android.material.R.attr.colorPrimaryFixed, Color.BLACK); + int colorBackground = + MaterialColors.getColor( + requireContext(), + com.google.android.material.R.attr.colorSurfaceVariant, + Color.GRAY); + int colorText = + MaterialColors.getColor( + requireContext(), + com.google.android.material.R.attr.colorPrimaryFixed, + Color.BLACK); binding.TagsPositionSortCheck.setVisibility(View.GONE); binding.TagsCreationDateSortCheck.setVisibility(View.GONE); @@ -149,8 +162,10 @@ private void selectedAutoItemTags(String param) { @Override public void initListeners() { if (isTagsSort) { - binding.TagsPositionSort.setOnClickListener(v -> selectedTagsSort(SortParam.TagsPositionSort)); - binding.TagsCreationDateSort.setOnClickListener(v -> selectedTagsSort(SortParam.TagsCreationDateSort)); + binding.TagsPositionSort.setOnClickListener( + v -> selectedTagsSort(SortParam.TagsPositionSort)); + binding.TagsCreationDateSort.setOnClickListener( + v -> selectedTagsSort(SortParam.TagsCreationDateSort)); } else { binding.DataSort.setOnClickListener(v -> selectedSort(SortParam.DataSort)); binding.DataReserve.setOnClickListener(v -> selectedSort(SortParam.DataReserve)); @@ -185,5 +200,4 @@ public interface SortListener { void onTagsSortSelected(String tagsSortParam); } - } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/TagOptionsBottomSheet.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/TagOptionsBottomSheet.java index 7e23df3..0c4518e 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/TagOptionsBottomSheet.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/TagOptionsBottomSheet.java @@ -4,10 +4,8 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; - import com.google.android.material.bottomsheet.BottomSheetDialog; import com.pasich.mynotes.R; import com.pasich.mynotes.base.dialog.BaseDialogBottomSheets; @@ -29,7 +27,10 @@ public TagOptionsBottomSheet(Tag tag, int notesCount, TagOptionsListener listene @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = BottomSheetTagOptionsBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -48,29 +49,34 @@ private void setupView() { binding.tagTitle.setText(tag.getNameTag()); // Отображаем количество заметок - binding.tagNotesCount.setText(getResources().getQuantityString( - R.plurals.notes_count, notesCount, notesCount)); + binding.tagNotesCount.setText( + getResources().getQuantityString(R.plurals.notes_count, notesCount, notesCount)); // Настраиваем видимость тега - binding.imageTagVisible.setImageResource(tag.getVisibility() == 1 ? R.drawable.ic_show : R.drawable.ic_hide); - binding.textVisibilityTag.setText(tag.getVisibility() == 1 ? R.string.visibleTag : R.string.hiddeTag); + binding.imageTagVisible.setImageResource( + tag.getVisibility() == 1 ? R.drawable.ic_show : R.drawable.ic_hide); + binding.textVisibilityTag.setText( + tag.getVisibility() == 1 ? R.string.visibleTag : R.string.hiddeTag); } private void setupListeners() { - binding.deleteTag.setOnClickListener(v -> { - listener.onDeleteTagClick(tag); - dismiss(); - }); - - binding.renameTag.setOnClickListener(v -> { - listener.onRenameTagClick(tag); - dismiss(); - }); - - binding.visibleTag.setOnClickListener(v -> { - listener.onToggleVisibilityClick(tag); - dismiss(); - }); + binding.deleteTag.setOnClickListener( + v -> { + listener.onDeleteTagClick(tag); + dismiss(); + }); + + binding.renameTag.setOnClickListener( + v -> { + listener.onRenameTagClick(tag); + dismiss(); + }); + + binding.visibleTag.setOnClickListener( + v -> { + listener.onToggleVisibilityClick(tag); + dismiss(); + }); } @Override @@ -80,13 +86,13 @@ public void onDestroyView() { } @Override - public void initListeners() { - - } + public void initListeners() {} public interface TagOptionsListener { void onDeleteTagClick(Tag tag); + void onRenameTagClick(Tag tag); + void onToggleVisibilityClick(Tag tag); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/popupWindowsTag/PopupWindowsTag.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/popupWindowsTag/PopupWindowsTag.java index 494fa0e..b0d69a0 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/popupWindowsTag/PopupWindowsTag.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/main/popupWindowsTag/PopupWindowsTag.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.ui.view.dialogs.main.popupWindowsTag; - import android.content.Context; import android.content.res.Resources; import android.os.Vibrator; @@ -8,9 +7,7 @@ import android.view.View; import android.widget.PopupWindow; import android.widget.RelativeLayout; - import androidx.core.content.ContextCompat; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.databinding.ViewPopupTagBinding; @@ -25,27 +22,46 @@ public class PopupWindowsTag { private View mAnchor; private PopupWindowsTagOnClickListener mListener; - public PopupWindowsTag(LayoutInflater layoutInflater, View anchor, Tag tag, PopupWindowsTagOnClickListener listener) { + public PopupWindowsTag( + LayoutInflater layoutInflater, + View anchor, + Tag tag, + PopupWindowsTagOnClickListener listener) { this.mTag = tag; this.mListener = listener; this.mBinding = ViewPopupTagBinding.inflate(layoutInflater); this.mAnchor = anchor; this.widthAnchor = anchor.getWidth(); - this.mPopupWindows = new PopupWindow(mBinding.getRoot(), RelativeLayout.LayoutParams.WRAP_CONTENT, RelativeLayout.LayoutParams.WRAP_CONTENT, true); - - mBinding.getRoot().measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); + this.mPopupWindows = + new PopupWindow( + mBinding.getRoot(), + RelativeLayout.LayoutParams.WRAP_CONTENT, + RelativeLayout.LayoutParams.WRAP_CONTENT, + true); + + mBinding.getRoot() + .measure( + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), + View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); onVibrate(); onSettingsView(); } - private void onSettingsView() { int xof, widthDisplayCenter = widthDisplay / 2; if (mAnchor.getX() > widthDisplayCenter) { - mBinding.getRoot().setBackground(ContextCompat.getDrawable(mBinding.getRoot().getContext(), R.drawable.background_popup_tag_right)); + mBinding.getRoot() + .setBackground( + ContextCompat.getDrawable( + mBinding.getRoot().getContext(), + R.drawable.background_popup_tag_right)); xof = (int) (-mBinding.getRoot().getMeasuredWidth() * 0.9); } else { - mBinding.getRoot().setBackground(ContextCompat.getDrawable(mBinding.getRoot().getContext(), R.drawable.background_popup_tag_left)); + mBinding.getRoot() + .setBackground( + ContextCompat.getDrawable( + mBinding.getRoot().getContext(), + R.drawable.background_popup_tag_left)); xof = widthAnchor / 3; } getPopupWindows().setElevation(20); @@ -55,42 +71,41 @@ private void onSettingsView() { getPopupWindows().showAsDropDown(mAnchor, xof, 30); } - private void editVisibleConfiguration() { - mBinding.imageTagVisible.setImageResource(mTag.getVisibility() == 1 ? R.drawable.ic_show : R.drawable.ic_hide); - mBinding.textVisibilityTag.setText(mTag.getVisibility() == 1 ? R.string.visibleTag : R.string.hiddeTag); - - + mBinding.imageTagVisible.setImageResource( + mTag.getVisibility() == 1 ? R.drawable.ic_show : R.drawable.ic_hide); + mBinding.textVisibilityTag.setText( + mTag.getVisibility() == 1 ? R.string.visibleTag : R.string.hiddeTag); } - private void onVibrate() { - Vibrator vibrator = (Vibrator) mBinding.getRoot().getContext().getSystemService(Context.VIBRATOR_SERVICE); + Vibrator vibrator = + (Vibrator) + mBinding.getRoot().getContext().getSystemService(Context.VIBRATOR_SERVICE); if (vibrator.hasVibrator()) { vibrator.vibrate(100L); } } - private void initListeners() { - mBinding.deleteTag.setOnClickListener(v -> { - mListener.deleteTag(); - getPopupWindows().dismiss(); - - }); - mBinding.renameTag.setOnClickListener(v -> { - mListener.renameTag(); - getPopupWindows().dismiss(); - }); - mBinding.visibleTag.setOnClickListener(v -> { - mListener.visibleEditTag(); - getPopupWindows().dismiss(); - }); - + mBinding.deleteTag.setOnClickListener( + v -> { + mListener.deleteTag(); + getPopupWindows().dismiss(); + }); + mBinding.renameTag.setOnClickListener( + v -> { + mListener.renameTag(); + getPopupWindows().dismiss(); + }); + mBinding.visibleTag.setOnClickListener( + v -> { + mListener.visibleEditTag(); + getPopupWindows().dismiss(); + }); } - public PopupWindow getPopupWindows() { return mPopupWindows; } @@ -101,6 +116,5 @@ public void setOnDismissListener() { mBinding.deleteTag.setOnClickListener(null); mBinding.renameTag.setOnClickListener(null); mBinding.visibleTag.setOnClickListener(null); - } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/tasks/AddCategoryDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/tasks/AddCategoryDialog.java index 0932e08..50bb32b 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/tasks/AddCategoryDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/tasks/AddCategoryDialog.java @@ -4,7 +4,6 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; @@ -29,10 +28,12 @@ public static void show(Context context, Callback callback) { new MaterialAlertDialogBuilder(context) .setTitle(context.getString(R.string.tasks_add_category)) .setView(view) - .setPositiveButton(android.R.string.ok, (d, w) -> { - String name = input.getText().toString().trim(); - if (!name.isEmpty()) callback.onAdd(name, selectedColor[0]); - }) + .setPositiveButton( + android.R.string.ok, + (d, w) -> { + String name = input.getText().toString().trim(); + if (!name.isEmpty()) callback.onAdd(name, selectedColor[0]); + }) .setNegativeButton(R.string.cancel, null) .show(); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/tasks/AddTaskDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/tasks/AddTaskDialog.java index 8cde503..5e3f812 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/tasks/AddTaskDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/tasks/AddTaskDialog.java @@ -4,7 +4,6 @@ import android.view.LayoutInflater; import android.view.View; import android.widget.EditText; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; @@ -22,13 +21,15 @@ public static void show(Context context, int categoryId, Callback callback) { new MaterialAlertDialogBuilder(context) .setTitle(context.getString(R.string.tasks_add)) .setView(view) - .setPositiveButton(android.R.string.ok, (d, w) -> { - String text = input.getText().toString().trim(); - if (!text.isEmpty()) { - String desc = descInput.getText().toString().trim(); - callback.onAdd(text, desc.isEmpty() ? null : desc, categoryId); - } - }) + .setPositiveButton( + android.R.string.ok, + (d, w) -> { + String text = input.getText().toString().trim(); + if (!text.isEmpty()) { + String desc = descInput.getText().toString().trim(); + callback.onAdd(text, desc.isEmpty() ? null : desc, categoryId); + } + }) .setNegativeButton(R.string.cancel, null) .show(); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/theme/AccentColorDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/theme/AccentColorDialog.java index 1b799fb..d2f2bdc 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/theme/AccentColorDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/theme/AccentColorDialog.java @@ -1,22 +1,18 @@ package com.pasich.mynotes.ui.view.dialogs.theme; - import android.content.Context; import android.content.res.Configuration; import android.view.LayoutInflater; import android.view.View; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.RecyclerView; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; import com.pasich.mynotes.cache.ThemePreferencesCache; import com.pasich.mynotes.data.model.Theme; import com.pasich.mynotes.utils.adapters.AccentColorAdapter; import com.pasich.mynotes.utils.themes.ThemesArray; - import java.util.ArrayList; public class AccentColorDialog { @@ -27,53 +23,50 @@ public static void show(Context ctx, ThemePreferencesCache cache, Callback callb ArrayList themes = new ThemesArray().getThemes(); if (themes.isEmpty()) return; - int mode = ctx.getResources().getConfiguration().uiMode - & Configuration.UI_MODE_NIGHT_MASK; + int mode = ctx.getResources().getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK; int[] colorResources = getAccentColors(mode); - View dialogView = LayoutInflater.from(ctx) - .inflate(R.layout.dialog_accent_picker, null); + View dialogView = LayoutInflater.from(ctx).inflate(R.layout.dialog_accent_picker, null); RecyclerView rv = dialogView.findViewById(R.id.accentGrid); rv.setLayoutManager(new GridLayoutManager(ctx, 4)); - rv.setAdapter(new AccentColorAdapter( - ctx, - themes, - colorResources, - cache.getThemeId(), - selected -> { - cache.setThemeId(selected.getId()); - callback.onSelected(selected); - } - )); + rv.setAdapter( + new AccentColorAdapter( + ctx, + themes, + colorResources, + cache.getThemeId(), + selected -> { + cache.setThemeId(selected.getId()); + callback.onSelected(selected); + })); new MaterialAlertDialogBuilder(ctx, R.style.Theme_MyNotes_Dialog) .setTitle(ctx.getString(R.string.selectAccentColor)) .setView(dialogView) .setNegativeButton(R.string.close, null) .show(); - } private static int[] getAccentColors(int mode) { if (mode == Configuration.UI_MODE_NIGHT_YES) { - return new int[]{ - R.color.default_theme_dark_primary, - R.color.green_theme_dark_theme_primary, - R.color.red_pale_theme_dark_primary, - R.color.yellow_theme_dark_primary, - R.color.purple_theme_dark_primary, - R.color.silver_theme_dark_primary + return new int[] { + R.color.default_theme_dark_primary, + R.color.green_theme_dark_theme_primary, + R.color.red_pale_theme_dark_primary, + R.color.yellow_theme_dark_primary, + R.color.purple_theme_dark_primary, + R.color.silver_theme_dark_primary }; } else { - return new int[]{ - R.color.default_theme_light_primary, - R.color.green_theme_light_theme_primary, - R.color.red_pale_theme_light_primary, - R.color.yellow_theme_light_primary, - R.color.purple_theme_light_primary, - R.color.silver_theme_primary + return new int[] { + R.color.default_theme_light_primary, + R.color.green_theme_light_theme_primary, + R.color.red_pale_theme_light_primary, + R.color.yellow_theme_light_primary, + R.color.purple_theme_light_primary, + R.color.silver_theme_primary }; } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/theme/ThemeModeDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/theme/ThemeModeDialog.java index a470d84..68635ce 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/theme/ThemeModeDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/theme/ThemeModeDialog.java @@ -7,9 +7,7 @@ import android.widget.ArrayAdapter; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; - import com.google.android.material.dialog.MaterialAlertDialogBuilder; import com.pasich.mynotes.R; import com.pasich.mynotes.cache.ThemePreferencesCache; @@ -22,48 +20,46 @@ public interface Callback { public static void show(Context ctx, ThemePreferencesCache cache, Callback callback) { String[] names = { - ctx.getString(R.string.themeModeFollowSystem), - ctx.getString(R.string.themeModeLight), - ctx.getString(R.string.themeModeDark) + ctx.getString(R.string.themeModeFollowSystem), + ctx.getString(R.string.themeModeLight), + ctx.getString(R.string.themeModeDark) }; - int[] icons = { - R.drawable.ic_auto_mode, - R.drawable.ic_light_mode, - R.drawable.ic_dark_mode - }; + int[] icons = {R.drawable.ic_auto_mode, R.drawable.ic_light_mode, R.drawable.ic_dark_mode}; int current = cache.getThemeMode(); - ArrayAdapter adapter = new ArrayAdapter<>(ctx, - R.layout.item_theme_mode_dialog, R.id.themeModeName, names) { + ArrayAdapter adapter = + new ArrayAdapter<>( + ctx, R.layout.item_theme_mode_dialog, R.id.themeModeName, names) { - @NonNull - @Override - public View getView(int position, View convertView, - @NonNull ViewGroup parent) { + @NonNull + @Override + public View getView(int position, View convertView, @NonNull ViewGroup parent) { - View view = super.getView(position, convertView, parent); - ImageView icon = view.findViewById(R.id.themeModeIcon); - ImageView selected = view.findViewById(R.id.selectedModeIndicator); - TextView title = view.findViewById(R.id.themeModeName); + View view = super.getView(position, convertView, parent); + ImageView icon = view.findViewById(R.id.themeModeIcon); + ImageView selected = view.findViewById(R.id.selectedModeIndicator); + TextView title = view.findViewById(R.id.themeModeName); - icon.setImageResource(icons[position]); + icon.setImageResource(icons[position]); - boolean isSelected = position == current; - selected.setVisibility(isSelected ? View.VISIBLE : View.GONE); - title.setTypeface(null, isSelected ? Typeface.BOLD : Typeface.NORMAL); + boolean isSelected = position == current; + selected.setVisibility(isSelected ? View.VISIBLE : View.GONE); + title.setTypeface(null, isSelected ? Typeface.BOLD : Typeface.NORMAL); - return view; - } - }; + return view; + } + }; new MaterialAlertDialogBuilder(ctx, R.style.Theme_MyNotes_Dialog) .setTitle(ctx.getString(R.string.selectThemeMode)) - .setAdapter(adapter, (d, which) -> { - cache.setThemeMode(which); - callback.onSelected(which); - }) + .setAdapter( + adapter, + (d, which) -> { + cache.setThemeMode(which); + callback.onSelected(which); + }) .setNegativeButton(R.string.cancel, null) .show(); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/mydata/BackupExportFragment.java b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/mydata/BackupExportFragment.java index 8da6b3f..f5f91b5 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/mydata/BackupExportFragment.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/mydata/BackupExportFragment.java @@ -4,29 +4,23 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; - import com.pasich.mynotes.databinding.FragmentBackupExportBinding; import com.pasich.mynotes.ui.contract.BackupContract; import com.pasich.mynotes.ui.presenter.BackupPresenter; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; +import javax.inject.Inject; @AndroidEntryPoint public class BackupExportFragment extends Fragment { private FragmentBackupExportBinding binding; - - @Inject - BackupContract.presenter presenter; - public BackupExportFragment() { - } + @Inject BackupContract.presenter presenter; + + public BackupExportFragment() {} public static BackupExportFragment newInstance() { return new BackupExportFragment(); @@ -34,7 +28,10 @@ public static BackupExportFragment newInstance() { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = FragmentBackupExportBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -56,4 +53,4 @@ public void onDestroyView() { public FragmentBackupExportBinding getBinding() { return binding; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/mydata/ImportDataFragment.java b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/mydata/ImportDataFragment.java index 26ad714..da1583f 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/mydata/ImportDataFragment.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/mydata/ImportDataFragment.java @@ -6,34 +6,27 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; - import com.pasich.mynotes.databinding.FragmentImportDataBinding; import com.pasich.mynotes.ui.contract.BackupContract; import com.pasich.mynotes.ui.view.dialogs.GoogleTakeoutInstructionsBottomSheet; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; - +import javax.inject.Inject; @AndroidEntryPoint public class ImportDataFragment extends Fragment { private FragmentImportDataBinding binding; - @Inject - BackupContract.presenter presenter; + @Inject BackupContract.presenter presenter; private ActivityResultLauncher zipFilePicker; - public ImportDataFragment() { - } + public ImportDataFragment() {} public static ImportDataFragment newInstance() { return new ImportDataFragment(); @@ -41,7 +34,10 @@ public static ImportDataFragment newInstance() { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = FragmentImportDataBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -50,24 +46,32 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - zipFilePicker = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() == Activity.RESULT_OK && result.getData() != null) { - presenter.startProcessSelectedFileOtherApp(result.getData().getData()); - } - }); - binding.btnGoogleKeepInstructions.setOnClickListener(v -> GoogleTakeoutInstructionsBottomSheet.newInstance().show(getChildFragmentManager(), "google_takeout_instructions")); - - binding.importFromGoogleKeep.setOnClickListener(v -> { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); - intent.setType("application/zip"); - zipFilePicker.launch(intent); - }); + zipFilePicker = + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() == Activity.RESULT_OK + && result.getData() != null) { + presenter.startProcessSelectedFileOtherApp( + result.getData().getData()); + } + }); + binding.btnGoogleKeepInstructions.setOnClickListener( + v -> + GoogleTakeoutInstructionsBottomSheet.newInstance() + .show(getChildFragmentManager(), "google_takeout_instructions")); + + binding.importFromGoogleKeep.setOnClickListener( + v -> { + Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + intent.setType("application/zip"); + zipFilePicker.launch(intent); + }); } - @Override public void onDestroyView() { super.onDestroyView(); binding = null; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/InteractionSettingsFragment.java b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/InteractionSettingsFragment.java index 5373952..47592da 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/InteractionSettingsFragment.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/InteractionSettingsFragment.java @@ -8,30 +8,28 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; - import com.pasich.mynotes.cache.ThemePreferencesCache; import com.pasich.mynotes.databinding.FragmentInteractionSettingsBinding; import com.pasich.mynotes.ui.controllers.mainActivity.RedrawThemeController; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; +import javax.inject.Inject; @AndroidEntryPoint public class InteractionSettingsFragment extends Fragment { - @Inject - ThemePreferencesCache themePreferencesCache; + @Inject ThemePreferencesCache themePreferencesCache; private FragmentInteractionSettingsBinding binding; @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = FragmentInteractionSettingsBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -50,12 +48,15 @@ private void initViews() { } private void initListeners() { - binding.screenProtection.setOnCheckedChangeListener((buttonView, isChecked) -> - themePreferencesCache.setScreenProtection(isChecked)); - - binding.extendedEditor.setOnCheckedChangeListener((buttonView, isChecked) -> - themePreferencesCache.setExtendedEditor(isChecked)); - binding.feedbackNewEditor.setOnClickListener(v -> startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(SEND_FEEDBACK_EDITOR)))); + binding.screenProtection.setOnCheckedChangeListener( + (buttonView, isChecked) -> themePreferencesCache.setScreenProtection(isChecked)); + + binding.extendedEditor.setOnCheckedChangeListener( + (buttonView, isChecked) -> themePreferencesCache.setExtendedEditor(isChecked)); + binding.feedbackNewEditor.setOnClickListener( + v -> + startActivity( + new Intent(Intent.ACTION_VIEW, Uri.parse(SEND_FEEDBACK_EDITOR)))); binding.detailsExtended.setOnClickListener(v -> toggleDetails()); } @@ -70,21 +71,18 @@ public void updateThemeColors() { null, binding.screenProtectionDescription, binding.screenProtection, - requireContext() - ); + requireContext()); RedrawThemeController.styleCardBlock( binding.extendedEditorCard, null, binding.extendedEditorDescription, binding.extendedEditor, - requireContext() - ); + requireContext()); } @Override public void onDestroyView() { super.onDestroyView(); binding = null; - } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/InterfaceSettingsFragment.java b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/InterfaceSettingsFragment.java index 11e8c70..4d35170 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/InterfaceSettingsFragment.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/InterfaceSettingsFragment.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.ui.view.fragment.settings; - import android.content.res.ColorStateList; import android.graphics.Color; import android.os.Build; @@ -9,11 +8,9 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; - import com.google.android.material.color.MaterialColors; import com.google.android.material.slider.Slider; import com.pasich.mynotes.R; @@ -23,34 +20,31 @@ import com.pasich.mynotes.ui.view.dialogs.theme.AccentColorDialog; import com.pasich.mynotes.ui.view.dialogs.theme.ThemeModeDialog; import com.pasich.mynotes.utils.themes.ThemesArray; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; +import javax.inject.Inject; @AndroidEntryPoint public class InterfaceSettingsFragment extends Fragment { private static final float PREVIEW_BASE_SP = 14f; private final float[] FONT_SCALES = { - 0.0f, // Auto (system default) - 0.55f, // XS - 0.85f, // S - 1.0f, // Default - 1.10f, // L - 1.25f // XL + 0.0f, // Auto (system default) + 0.55f, // XS + 0.85f, // S + 1.0f, // Default + 1.10f, // L + 1.25f // XL }; private final int[] FONT_LABELS = { - R.string.font_size_auto, - R.string.font_size_xs, - R.string.font_size_s, - R.string.font_size_default, - R.string.font_size_l, - R.string.font_size_xl + R.string.font_size_auto, + R.string.font_size_xs, + R.string.font_size_s, + R.string.font_size_default, + R.string.font_size_l, + R.string.font_size_xl }; private final Handler fontScaleDebounceHandler = new Handler(); - @Inject - ThemePreferencesCache themePreferencesCache; + @Inject ThemePreferencesCache themePreferencesCache; private FragmentInterfaceSettingsBinding binding; private boolean enableDynamic; private Runnable fontScaleDebounceRunnable; @@ -68,13 +62,14 @@ public class InterfaceSettingsFragment extends Fragment { if (fontScaleDebounceRunnable != null) { fontScaleDebounceHandler.removeCallbacks(fontScaleDebounceRunnable); } - fontScaleDebounceRunnable = () -> { - themePreferencesCache.setUiFontScale(scale); + fontScaleDebounceRunnable = + () -> { + themePreferencesCache.setUiFontScale(scale); - if (getActivity() instanceof ThemeChangeListener) { - ((ThemeChangeListener) getActivity()).onFontScaleChanged(scale); - } - }; + if (getActivity() instanceof ThemeChangeListener) { + ((ThemeChangeListener) getActivity()).onFontScaleChanged(scale); + } + }; fontScaleDebounceHandler.postDelayed(fontScaleDebounceRunnable, 350); }; @@ -113,7 +108,10 @@ private void loadFontScaleState() { @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = FragmentInterfaceSettingsBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -145,47 +143,55 @@ private void initViews() { private void initListeners() { // Theme Mode Card Click Listener - binding.themeModeCard.setOnClickListener(v -> - ThemeModeDialog.show(requireContext(), themePreferencesCache, mode -> { - updateThemeModeDisplay(); - themePreferencesCache.applyCurrentThemeMode(); - }) - ); + binding.themeModeCard.setOnClickListener( + v -> + ThemeModeDialog.show( + requireContext(), + themePreferencesCache, + mode -> { + updateThemeModeDisplay(); + themePreferencesCache.applyCurrentThemeMode(); + })); // Accent Color Card Click Listener - binding.accentColorCard.setOnClickListener(v -> - AccentColorDialog.show( - requireContext(), - themePreferencesCache, - selectedTheme -> { - if (getActivity() instanceof ThemeChangeListener) { - ((ThemeChangeListener) getActivity()).onThemeChanged(selectedTheme.getTHEME_STYLE()); + binding.accentColorCard.setOnClickListener( + v -> + AccentColorDialog.show( + requireContext(), + themePreferencesCache, + selectedTheme -> { + if (getActivity() instanceof ThemeChangeListener) { + ((ThemeChangeListener) getActivity()) + .onThemeChanged(selectedTheme.getTHEME_STYLE()); + } + })); + + binding.dynamicColor.setOnCheckedChangeListener( + (buttonView, isChecked) -> { + if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + if (isChecked) { + // Notify activity to change theme + if (getActivity() != null + && getActivity() instanceof ThemeChangeListener) { + ((ThemeChangeListener) getActivity()) + .onThemeChanged(R.style.AppThemeDynamic); + } + } else { + int themeStyle = + new ThemesArray() + .getThemeStyle(themePreferencesCache.getThemeId()); + if (getActivity() != null + && getActivity() instanceof ThemeChangeListener) { + ((ThemeChangeListener) getActivity()).onThemeChanged(themeStyle); } } - ) - ); - - binding.dynamicColor.setOnCheckedChangeListener((buttonView, isChecked) -> { - if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { - if (isChecked) { - // Notify activity to change theme - if (getActivity() != null && getActivity() instanceof ThemeChangeListener) { - ((ThemeChangeListener) getActivity()).onThemeChanged(R.style.AppThemeDynamic); - } - } else { - int themeStyle = new ThemesArray().getThemeStyle(themePreferencesCache.getThemeId()); - if (getActivity() != null && getActivity() instanceof ThemeChangeListener) { - ((ThemeChangeListener) getActivity()).onThemeChanged(themeStyle); + themePreferencesCache.setDynamicColor(isChecked); + enableDynamic = isChecked; + updateAccentCardState(isChecked); } - } - themePreferencesCache.setDynamicColor(isChecked); - enableDynamic = isChecked; - updateAccentCardState(isChecked); - } - }); + }); binding.fontSizeSlider.addOnChangeListener(fontScaleListener); - } private void applyThemeColors() { @@ -197,8 +203,7 @@ private void applyThemeColors() { null, binding.dynamicColorDescription, binding.dynamicColor, - requireContext() - ); + requireContext()); // Accent Color Card RedrawThemeController.styleCardBlock( @@ -206,18 +211,11 @@ private void applyThemeColors() { null, binding.accentColorDescription, null, - requireContext() - ); + requireContext()); // Theme Mode Card RedrawThemeController.styleCardBlock( - binding.themeModeCard, - null, - binding.currentThemeModeText, - null, - requireContext() - ); - + binding.themeModeCard, null, binding.currentThemeModeText, null, requireContext()); // font scale RedrawThemeController.styleCardBlock( @@ -225,16 +223,19 @@ private void applyThemeColors() { binding.fontSizeTitle, binding.fontSizeDescription, null, - requireContext() - ); + requireContext()); RedrawThemeController.styleCardSecondary(binding.fontPreviewCard, requireContext()); RedrawThemeController.styleTextVariant(binding.fontPreviewText, requireContext()); RedrawThemeController.styleTextVariant(binding.fontSizeValue, requireContext()); // colorPreview - RedrawThemeController.tint(binding.colorPreview, requireContext(), + RedrawThemeController.tint( + binding.colorPreview, + requireContext(), com.google.android.material.R.attr.colorPrimaryVariant); // themeModeIcon - RedrawThemeController.tint(binding.themeModeIcon, requireContext(), + RedrawThemeController.tint( + binding.themeModeIcon, + requireContext(), com.google.android.material.R.attr.colorPrimaryVariant); // font scale slider RedrawThemeController.styleSlider(binding.fontSizeSlider, requireContext()); @@ -252,8 +253,14 @@ private void updateAccentCardState(boolean isDynamicEnabled) { private void updateThemeModeDisplay() { int currentThemeMode = themePreferencesCache.getThemeMode(); - String[] themeModeNames = {getString(R.string.themeModeFollowSystem), getString(R.string.themeModeLight), getString(R.string.themeModeDark)}; - int[] themeModeIcons = {R.drawable.ic_auto_mode, R.drawable.ic_light_mode, R.drawable.ic_dark_mode}; + String[] themeModeNames = { + getString(R.string.themeModeFollowSystem), + getString(R.string.themeModeLight), + getString(R.string.themeModeDark) + }; + int[] themeModeIcons = { + R.drawable.ic_auto_mode, R.drawable.ic_light_mode, R.drawable.ic_dark_mode + }; if (currentThemeMode >= 0 && currentThemeMode < themeModeNames.length) { binding.currentThemeModeText.setText(themeModeNames[currentThemeMode]); @@ -261,7 +268,11 @@ private void updateThemeModeDisplay() { // Update icon color to match current theme if (getContext() != null) { - int colorPrimary = MaterialColors.getColor(getContext(), com.google.android.material.R.attr.colorPrimaryVariant, Color.GRAY); + int colorPrimary = + MaterialColors.getColor( + getContext(), + com.google.android.material.R.attr.colorPrimaryVariant, + Color.GRAY); binding.themeModeIcon.setImageTintList(ColorStateList.valueOf(colorPrimary)); } } @@ -281,10 +292,9 @@ public void onDestroyView() { super.onDestroyView(); } - public interface ThemeChangeListener { void onThemeChanged(int themeStyle); void onFontScaleChanged(float value); } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/MediaSettingsFragment.java b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/MediaSettingsFragment.java index 3615458..94c6ab4 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/MediaSettingsFragment.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/fragment/settings/MediaSettingsFragment.java @@ -12,13 +12,11 @@ import android.view.View; import android.view.ViewGroup; import android.widget.SeekBar; - import androidx.activity.result.ActivityResultLauncher; import androidx.activity.result.contract.ActivityResultContracts; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.Fragment; - import com.pasich.mynotes.cache.AppPreferencesCache; import com.pasich.mynotes.cache.NotificationPreferencesCache; import com.pasich.mynotes.cache.ThemePreferencesCache; @@ -26,41 +24,44 @@ import com.pasich.mynotes.extendedEditor.attach.AttachmentStorage; import com.pasich.mynotes.ui.controllers.mainActivity.RedrawThemeController; import com.pasich.mynotes.utils.notification.NotificationChannelManager; - -import javax.inject.Inject; - import dagger.hilt.android.AndroidEntryPoint; import io.reactivex.schedulers.Schedulers; +import javax.inject.Inject; @AndroidEntryPoint public class MediaSettingsFragment extends Fragment { - @Inject - ThemePreferencesCache themePreferencesCache; + @Inject ThemePreferencesCache themePreferencesCache; - @Inject - AppPreferencesCache appPreferencesCache; + @Inject AppPreferencesCache appPreferencesCache; - @Inject - NotificationPreferencesCache notificationPreferencesCache; + @Inject NotificationPreferencesCache notificationPreferencesCache; private FragmentMediaSettingsBinding binding; private AudioManager audioManager; private final ActivityResultLauncher ringtoneLauncher = - registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), result -> { - if (result.getResultCode() != android.app.Activity.RESULT_OK - || result.getData() == null) return; - Uri newUri = result.getData().getParcelableExtra(RingtoneManager.EXTRA_RINGTONE_PICKED_URI); - if (getContext() == null) return; - NotificationChannelManager.changeSound(requireContext(), notificationPreferencesCache, newUri); - updateSoundNameText(newUri); - }); + registerForActivityResult( + new ActivityResultContracts.StartActivityForResult(), + result -> { + if (result.getResultCode() != android.app.Activity.RESULT_OK + || result.getData() == null) return; + Uri newUri = + result.getData() + .getParcelableExtra( + RingtoneManager.EXTRA_RINGTONE_PICKED_URI); + if (getContext() == null) return; + NotificationChannelManager.changeSound( + requireContext(), notificationPreferencesCache, newUri); + updateSoundNameText(newUri); + }); @Nullable @Override - public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, - @Nullable Bundle savedInstanceState) { + public View onCreateView( + @NonNull LayoutInflater inflater, + @Nullable ViewGroup container, + @Nullable Bundle savedInstanceState) { binding = FragmentMediaSettingsBinding.inflate(inflater, container, false); return binding.getRoot(); } @@ -68,7 +69,9 @@ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup c @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); - audioManager = (AudioManager) requireContext().getSystemService(android.content.Context.AUDIO_SERVICE); + audioManager = + (AudioManager) + requireContext().getSystemService(android.content.Context.AUDIO_SERVICE); initViews(); initListeners(); updateMemoryUsage(); @@ -80,9 +83,10 @@ private void initViews() { // Sound: show current sound name String savedUri = notificationPreferencesCache.getSoundUri(); - Uri currentUri = (savedUri != null && !savedUri.isEmpty()) - ? Uri.parse(savedUri) - : android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; + Uri currentUri = + (savedUri != null && !savedUri.isEmpty()) + ? Uri.parse(savedUri) + : android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; updateSoundNameText(currentUri); // Volume SeekBar @@ -95,32 +99,41 @@ private void initViews() { } private void initListeners() { - binding.imgOptSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> - appPreferencesCache.setImageOpt(isChecked)); - - binding.soundRow.setOnClickListener(v -> { - String savedUri = notificationPreferencesCache.getSoundUri(); - Uri currentUri = (savedUri != null && !savedUri.isEmpty()) - ? Uri.parse(savedUri) - : android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; - - Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false); - intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentUri); - ringtoneLauncher.launch(intent); - }); - - binding.volumeSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - if (fromUser && audioManager != null) { - audioManager.setStreamVolume(AudioManager.STREAM_NOTIFICATION, progress, 0); - } - } - @Override public void onStartTrackingTouch(SeekBar seekBar) {} - @Override public void onStopTrackingTouch(SeekBar seekBar) {} - }); + binding.imgOptSwitch.setOnCheckedChangeListener( + (buttonView, isChecked) -> appPreferencesCache.setImageOpt(isChecked)); + + binding.soundRow.setOnClickListener( + v -> { + String savedUri = notificationPreferencesCache.getSoundUri(); + Uri currentUri = + (savedUri != null && !savedUri.isEmpty()) + ? Uri.parse(savedUri) + : android.provider.Settings.System.DEFAULT_NOTIFICATION_URI; + + Intent intent = new Intent(RingtoneManager.ACTION_RINGTONE_PICKER); + intent.putExtra( + RingtoneManager.EXTRA_RINGTONE_TYPE, RingtoneManager.TYPE_NOTIFICATION); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_SHOW_SILENT, false); + intent.putExtra(RingtoneManager.EXTRA_RINGTONE_EXISTING_URI, currentUri); + ringtoneLauncher.launch(intent); + }); + + binding.volumeSeekBar.setOnSeekBarChangeListener( + new SeekBar.OnSeekBarChangeListener() { + @Override + public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { + if (fromUser && audioManager != null) { + audioManager.setStreamVolume( + AudioManager.STREAM_NOTIFICATION, progress, 0); + } + } + + @Override + public void onStartTrackingTouch(SeekBar seekBar) {} + + @Override + public void onStopTrackingTouch(SeekBar seekBar) {} + }); } private void updateSoundNameText(Uri uri) { @@ -142,36 +155,35 @@ public void updateThemeColors() { null, binding.imgOptDescription, binding.imgOptSwitch, - requireContext() - ); + requireContext()); RedrawThemeController.styleCardBlock( - binding.memory, - binding.memoryTitle, - binding.memoryValue, - null, - requireContext() - ); + binding.memory, binding.memoryTitle, binding.memoryValue, null, requireContext()); RedrawThemeController.styleCardBlock( binding.notificationSettings, binding.notificationSectionTitle, null, null, - requireContext() - ); + requireContext()); } @SuppressLint("DefaultLocale") private void updateMemoryUsage() { if (getContext() == null) return; - Schedulers.io().scheduleDirect(() -> { - long usedBytes = AttachmentStorage.getTotalAttachmentsSize(getContext()); - float usedMB = usedBytes / 1024f / 1024f; - new Handler(Looper.getMainLooper()).post(() -> { - if (binding != null) { - binding.memoryValue.setText(String.format("%.1f MB", usedMB)); - } - }); - }); + Schedulers.io() + .scheduleDirect( + () -> { + long usedBytes = + AttachmentStorage.getTotalAttachmentsSize(getContext()); + float usedMB = usedBytes / 1024f / 1024f; + new Handler(Looper.getMainLooper()) + .post( + () -> { + if (binding != null) { + binding.memoryValue.setText( + String.format("%.1f MB", usedMB)); + } + }); + }); } @Override diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/widgets/TwoSideSwitchView.java b/app/src/main/java/com/pasich/mynotes/ui/view/widgets/TwoSideSwitchView.java index 8aa6089..0a8fd6f 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/widgets/TwoSideSwitchView.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/widgets/TwoSideSwitchView.java @@ -8,11 +8,9 @@ import android.util.AttributeSet; import android.widget.ImageView; import android.widget.LinearLayout; - import androidx.annotation.ColorInt; import androidx.annotation.Nullable; import androidx.appcompat.widget.SwitchCompat; - import com.pasich.mynotes.R; public class TwoSideSwitchView extends LinearLayout { @@ -20,18 +18,14 @@ public class TwoSideSwitchView extends LinearLayout { private ImageView leftIcon; private ImageView rightIcon; private SwitchCompat switchView; - @ColorInt - private int leftActiveColor; - @ColorInt - private int rightActiveColor; - @ColorInt - private int inactiveColor; + @ColorInt private int leftActiveColor; + @ColorInt private int rightActiveColor; + @ColorInt private int inactiveColor; private int currentThumbColor; private Mode currentMode = Mode.INACTIVE; private OnModeChangedListener modeChangedListener; private boolean suppressCallback = false; - public TwoSideSwitchView(Context context) { super(context); init(context, null); @@ -82,8 +76,10 @@ private void init(Context context, @Nullable AttributeSet attrs) { rightIcon.setImageDrawable(a.getDrawable(R.styleable.TwoSideSwitchView_rightIcon)); } - leftActiveColor = a.getColor(R.styleable.TwoSideSwitchView_leftActiveColor, leftActiveColor); - rightActiveColor = a.getColor(R.styleable.TwoSideSwitchView_rightActiveColor, rightActiveColor); + leftActiveColor = + a.getColor(R.styleable.TwoSideSwitchView_leftActiveColor, leftActiveColor); + rightActiveColor = + a.getColor(R.styleable.TwoSideSwitchView_rightActiveColor, rightActiveColor); inactiveColor = a.getColor(R.styleable.TwoSideSwitchView_inactiveColor, inactiveColor); int modeValue = a.getInt(R.styleable.TwoSideSwitchView_modeEditor, 0); @@ -125,28 +121,31 @@ private void init(Context context, @Nullable AttributeSet attrs) { post(() -> applyCurrentState(true)); // ---------- LISTENERS ---------- - switchView.setOnCheckedChangeListener((btn, checked) -> { - if (currentMode == Mode.INACTIVE) return; - currentMode = checked ? Mode.EXTENDED : Mode.SIMPLE; - applyCurrentState(false); - notifyModeChanged(); - }); - - leftIcon.setOnClickListener(v -> { - if (currentMode == Mode.INACTIVE) return; - currentMode = Mode.SIMPLE; - switchView.setChecked(false); - applyCurrentState(false); - notifyModeChanged(); - }); - - rightIcon.setOnClickListener(v -> { - if (currentMode == Mode.INACTIVE) return; - currentMode = Mode.EXTENDED; - switchView.setChecked(true); - applyCurrentState(false); - notifyModeChanged(); - }); + switchView.setOnCheckedChangeListener( + (btn, checked) -> { + if (currentMode == Mode.INACTIVE) return; + currentMode = checked ? Mode.EXTENDED : Mode.SIMPLE; + applyCurrentState(false); + notifyModeChanged(); + }); + + leftIcon.setOnClickListener( + v -> { + if (currentMode == Mode.INACTIVE) return; + currentMode = Mode.SIMPLE; + switchView.setChecked(false); + applyCurrentState(false); + notifyModeChanged(); + }); + + rightIcon.setOnClickListener( + v -> { + if (currentMode == Mode.INACTIVE) return; + currentMode = Mode.EXTENDED; + switchView.setChecked(true); + applyCurrentState(false); + notifyModeChanged(); + }); applyInteractionState(); } @@ -164,7 +163,6 @@ private void applyInteractionState() { private void applyCurrentState(boolean instant) { switch (currentMode) { - case EXTENDED: animateIconTint(leftIcon, getIconTint(leftIcon), inactiveColor, instant); animateIconTint(rightIcon, getIconTint(rightIcon), rightActiveColor, instant); @@ -189,16 +187,16 @@ private void applyCurrentState(boolean instant) { } } - private void animateIconTint(ImageView v, @ColorInt int from, @ColorInt int to, boolean instant) { + private void animateIconTint( + ImageView v, @ColorInt int from, @ColorInt int to, boolean instant) { if (instant) { v.setImageTintList(ColorStateList.valueOf(to)); return; } ValueAnimator animator = ValueAnimator.ofArgb(from, to); animator.setDuration(200); - animator.addUpdateListener(a -> - v.setImageTintList(ColorStateList.valueOf((int) a.getAnimatedValue())) - ); + animator.addUpdateListener( + a -> v.setImageTintList(ColorStateList.valueOf((int) a.getAnimatedValue()))); animator.start(); } @@ -214,9 +212,8 @@ private void animateThumbTint(@ColorInt int from, @ColorInt int to, boolean inst } ValueAnimator animator = ValueAnimator.ofArgb(from, to); animator.setDuration(200); - animator.addUpdateListener(a -> - switchView.getThumbDrawable().setTint((int) a.getAnimatedValue()) - ); + animator.addUpdateListener( + a -> switchView.getThumbDrawable().setTint((int) a.getAnimatedValue())); animator.start(); } diff --git a/app/src/main/java/com/pasich/mynotes/utils/FormattedDataUtil.java b/app/src/main/java/com/pasich/mynotes/utils/FormattedDataUtil.java index d1677e5..b3618dc 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/FormattedDataUtil.java +++ b/app/src/main/java/com/pasich/mynotes/utils/FormattedDataUtil.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.utils; - import java.text.SimpleDateFormat; import java.util.Calendar; import java.util.GregorianCalendar; @@ -14,17 +13,17 @@ public class FormattedDataUtil { * @param date - last modified date * @return - formatted date */ - public static String lastDayEditNote(long date) { final GregorianCalendar newDate = new GregorianCalendar(); - final int dayNote = Integer.parseInt(new SimpleDateFormat("dd", Locale.getDefault()).format(date)); - final int montNote = Integer.parseInt(new SimpleDateFormat("MM", Locale.getDefault()).format(date)); + final int dayNote = + Integer.parseInt(new SimpleDateFormat("dd", Locale.getDefault()).format(date)); + final int montNote = + Integer.parseInt(new SimpleDateFormat("MM", Locale.getDefault()).format(date)); - if (newDate.get(Calendar.DAY_OF_MONTH) == dayNote && newDate.get(Calendar.MONTH) + 1 == montNote) + if (newDate.get(Calendar.DAY_OF_MONTH) == dayNote + && newDate.get(Calendar.MONTH) + 1 == montNote) return new SimpleDateFormat("HH:mm", Locale.getDefault()).format(date); else return new SimpleDateFormat("d MMM", Locale.getDefault()).format(date).replace(".", ""); - } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/TagsSorter.java b/app/src/main/java/com/pasich/mynotes/utils/TagsSorter.java index 3b8880b..35684bd 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/TagsSorter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/TagsSorter.java @@ -1,17 +1,16 @@ package com.pasich.mynotes.utils; - import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.utils.constants.settings.SortParam; import com.pasich.mynotes.utils.managers.SystemTagsManager; - import java.util.ArrayList; import java.util.Collections; import java.util.List; public class TagsSorter { - // Unified sorting logic for system tags (by weight) and user tags (by position or creation date) + // Unified sorting logic for system tags (by weight) and user tags (by position or creation + // date) public static List sortTags(List tags, String sortParam) { if (tags == null || tags.isEmpty()) { return Collections.emptyList(); @@ -19,32 +18,33 @@ public static List sortTags(List tags, String sortParam) { List sortedList = new ArrayList<>(tags); - sortedList.sort((o1, o2) -> { - int x1 = o1.getSystemAction(); - int x2 = o2.getSystemAction(); - - if (o1.getSystemAction() == SystemTagsManager.SYSTEM_ACTION_ALL_NOTES) { - x1 = 98; // allNotes 1 - } - if (o2.getSystemAction() == SystemTagsManager.SYSTEM_ACTION_ALL_NOTES) { - x2 = 98; - } - - int sComp = Integer.compare(x2, x1); - if (sComp != 0) { - return sComp; - } - - if (o1.getSystemAction() == 0 && o2.getSystemAction() == 0) { - if (SortParam.TagsPositionSort.equals(sortParam)) { - return Integer.compare(o1.getPosition(), o2.getPosition()); - } else { - return Long.compare(o2.getId(), o1.getId()); - } - } + sortedList.sort( + (o1, o2) -> { + int x1 = o1.getSystemAction(); + int x2 = o2.getSystemAction(); + + if (o1.getSystemAction() == SystemTagsManager.SYSTEM_ACTION_ALL_NOTES) { + x1 = 98; // allNotes 1 + } + if (o2.getSystemAction() == SystemTagsManager.SYSTEM_ACTION_ALL_NOTES) { + x2 = 98; + } + + int sComp = Integer.compare(x2, x1); + if (sComp != 0) { + return sComp; + } + + if (o1.getSystemAction() == 0 && o2.getSystemAction() == 0) { + if (SortParam.TagsPositionSort.equals(sortParam)) { + return Integer.compare(o1.getPosition(), o2.getPosition()); + } else { + return Long.compare(o2.getId(), o1.getId()); + } + } - return Long.compare(o2.getId(), o1.getId()); - }); + return Long.compare(o2.getId(), o1.getId()); + }); return sortedList; } diff --git a/app/src/main/java/com/pasich/mynotes/utils/UpdateChecker.java b/app/src/main/java/com/pasich/mynotes/utils/UpdateChecker.java index 3451b28..2264dc8 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/UpdateChecker.java +++ b/app/src/main/java/com/pasich/mynotes/utils/UpdateChecker.java @@ -3,18 +3,12 @@ import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; - import com.pasich.mynotes.cache.AppPreferencesCache; - +import dagger.hilt.android.qualifiers.ApplicationContext; import javax.inject.Inject; import javax.inject.Singleton; -import dagger.hilt.android.qualifiers.ApplicationContext; - - -/** - * Class for checking app updates - */ +/** Class for checking app updates */ @Singleton public class UpdateChecker { @@ -53,24 +47,21 @@ public boolean hasNewVersion() { */ public String getCurrentAppVersion() { try { - PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0); + PackageInfo packageInfo = + context.getPackageManager().getPackageInfo(context.getPackageName(), 0); return packageInfo.versionName; } catch (PackageManager.NameNotFoundException e) { return "1.0.0"; } } - /** - * Mark the current version as read/acknowledged by the user - */ + /** Mark the current version as read/acknowledged by the user */ public void markVersionAsRead() { String currentVersion = getCurrentAppVersion(); cache.setLastKnownVersion(currentVersion); } - /** - * Initialize version check (should be called on the first launch) - */ + /** Initialize version check (should be called on the first launch) */ public void initializeVersionCheck() { String lastKnownVersion = cache.getLastKnownVersion(); if (lastKnownVersion == null || lastKnownVersion.isEmpty()) { diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/AccentColorAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/AccentColorAdapter.java index 66a368d..c77d573 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/AccentColorAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/AccentColorAdapter.java @@ -5,14 +5,11 @@ import android.view.View; import android.view.ViewGroup; import android.widget.ImageView; - import androidx.annotation.NonNull; import androidx.core.content.ContextCompat; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Theme; - import java.util.ArrayList; public class AccentColorAdapter extends RecyclerView.Adapter { @@ -27,8 +24,8 @@ public interface Listener { void onSelect(Theme theme); } - public AccentColorAdapter(Context ctx, ArrayList themes, int[] colors, - int selectedId, Listener listener) { + public AccentColorAdapter( + Context ctx, ArrayList themes, int[] colors, int selectedId, Listener listener) { this.ctx = ctx; this.themes = themes; this.colors = colors; @@ -39,8 +36,7 @@ public AccentColorAdapter(Context ctx, ArrayList themes, int[] colors, @NonNull @Override public VH onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - return new VH(LayoutInflater.from(ctx) - .inflate(R.layout.item_accent_circle, parent, false)); + return new VH(LayoutInflater.from(ctx).inflate(R.layout.item_accent_circle, parent, false)); } @Override @@ -53,11 +49,12 @@ public void onBindViewHolder(@NonNull VH h, int position) { h.checkmark.setVisibility(isSelected ? View.VISIBLE : View.GONE); - h.itemView.setOnClickListener(v -> { - selectedId = themes.get(position).getId(); - listener.onSelect(themes.get(position)); - notifyDataSetChanged(); - }); + h.itemView.setOnClickListener( + v -> { + selectedId = themes.get(position).getId(); + listener.onSelect(themes.get(position)); + notifyDataSetChanged(); + }); } @Override diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/BackupPagerAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/BackupPagerAdapter.java index af2a7fe..bf1454c 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/BackupPagerAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/BackupPagerAdapter.java @@ -4,7 +4,6 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.viewpager2.adapter.FragmentStateAdapter; - import com.pasich.mynotes.ui.view.fragment.mydata.BackupExportFragment; import com.pasich.mynotes.ui.view.fragment.mydata.ImportDataFragment; @@ -27,4 +26,4 @@ public Fragment createFragment(int position) { public int getItemCount() { return 2; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/DonationProductAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/DonationProductAdapter.java index 156ce1d..7279a74 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/DonationProductAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/DonationProductAdapter.java @@ -5,49 +5,54 @@ import android.view.ViewGroup; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.DonationProduct; - import java.util.ArrayList; import java.util.List; -public class DonationProductAdapter extends RecyclerView.Adapter { +public class DonationProductAdapter + extends RecyclerView.Adapter { private final OnProductClickListener listener; private List products = new ArrayList<>(); + public DonationProductAdapter(OnProductClickListener listener) { this.listener = listener; } public void setProducts(List newProducts) { - DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new DiffUtil.Callback() { - @Override - public int getOldListSize() { - return products.size(); - } - - @Override - public int getNewListSize() { - return newProducts.size(); - } - - @Override - public boolean areItemsTheSame(int oldItemPosition, int newItemPosition) { - return products.get(oldItemPosition).getId().equals(newProducts.get(newItemPosition).getId()); - } - - @Override - public boolean areContentsTheSame(int oldItemPosition, int newItemPosition) { - DonationProduct oldProduct = products.get(oldItemPosition); - DonationProduct newProduct = newProducts.get(newItemPosition); - return oldProduct.equals(newProduct); - } - }); + DiffUtil.DiffResult diffResult = + DiffUtil.calculateDiff( + new DiffUtil.Callback() { + @Override + public int getOldListSize() { + return products.size(); + } + + @Override + public int getNewListSize() { + return newProducts.size(); + } + + @Override + public boolean areItemsTheSame( + int oldItemPosition, int newItemPosition) { + return products.get(oldItemPosition) + .getId() + .equals(newProducts.get(newItemPosition).getId()); + } + + @Override + public boolean areContentsTheSame( + int oldItemPosition, int newItemPosition) { + DonationProduct oldProduct = products.get(oldItemPosition); + DonationProduct newProduct = newProducts.get(newItemPosition); + return oldProduct.equals(newProduct); + } + }); this.products = new ArrayList<>(newProducts); diffResult.dispatchUpdatesTo(this); @@ -60,8 +65,9 @@ public void updatePurchasedProducts() { @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - View view = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_donation_product, parent, false); + View view = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_donation_product, parent, false); return new ViewHolder(view); } @@ -112,11 +118,12 @@ public void bind(DonationProduct product, OnProductClickListener listener) { } else { // Normal state itemView.setAlpha(1.0f); - itemView.setOnClickListener(v -> { - if (listener != null) { - listener.onProductClick(product); - } - }); + itemView.setOnClickListener( + v -> { + if (listener != null) { + listener.onProductClick(product); + } + }); } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/HelpSectionAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/HelpSectionAdapter.java index 89999da..38b80af 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/HelpSectionAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/HelpSectionAdapter.java @@ -3,16 +3,13 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.HelpSection; import com.pasich.mynotes.databinding.ItemHelpFeatureBinding; import com.pasich.mynotes.databinding.ItemHelpHeaderBinding; import com.pasich.mynotes.databinding.ItemHelpSectionTitleBinding; - import java.util.List; public class HelpSectionAdapter extends RecyclerView.Adapter { @@ -34,15 +31,13 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int LayoutInflater inflater = LayoutInflater.from(parent.getContext()); return switch (viewType) { - case HelpSection.TYPE_HEADER -> new HeaderViewHolder( - ItemHelpHeaderBinding.inflate(inflater, parent, false) - ); - case HelpSection.TYPE_SECTION_TITLE -> new SectionTitleViewHolder( - ItemHelpSectionTitleBinding.inflate(inflater, parent, false) - ); - default -> new FeatureViewHolder( - ItemHelpFeatureBinding.inflate(inflater, parent, false) - ); + case HelpSection.TYPE_HEADER -> + new HeaderViewHolder(ItemHelpHeaderBinding.inflate(inflater, parent, false)); + case HelpSection.TYPE_SECTION_TITLE -> + new SectionTitleViewHolder( + ItemHelpSectionTitleBinding.inflate(inflater, parent, false)); + default -> + new FeatureViewHolder(ItemHelpFeatureBinding.inflate(inflater, parent, false)); }; } @@ -77,10 +72,9 @@ static class HeaderViewHolder extends RecyclerView.ViewHolder { } void bind(HelpSection section) { - binding.versionActual.setText(itemView.getContext().getString( - R.string.actual_version, - section.additionalInfo() - )); + binding.versionActual.setText( + itemView.getContext() + .getString(R.string.actual_version, section.additionalInfo())); if (section.description() != null) { binding.subtitleText.setText(section.description()); binding.subtitleText.setVisibility(View.VISIBLE); diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/LibsSectionAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/LibsSectionAdapter.java index 98bbaea..2fe6ca2 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/LibsSectionAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/LibsSectionAdapter.java @@ -5,14 +5,11 @@ import android.view.View; import android.view.ViewGroup; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.lib.LibItem; import com.pasich.mynotes.data.model.lib.LibSection; - import java.util.List; public class LibsSectionAdapter extends RecyclerView.Adapter { @@ -57,12 +54,14 @@ public int getItemCount() { public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int type) { if (type == TYPE_HEADER) { - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_lib_header, parent, false); + View v = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_lib_header, parent, false); return new HeaderHolder(v); } else { - View v = LayoutInflater.from(parent.getContext()) - .inflate(R.layout.item_lib, parent, false); + View v = + LayoutInflater.from(parent.getContext()) + .inflate(R.layout.item_lib, parent, false); return new ItemHolder(v); } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/SettingsPagerAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/SettingsPagerAdapter.java index 1ecef7d..9063438 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/SettingsPagerAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/SettingsPagerAdapter.java @@ -4,7 +4,6 @@ import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentActivity; import androidx.viewpager2.adapter.FragmentStateAdapter; - import com.pasich.mynotes.ui.view.fragment.settings.InteractionSettingsFragment; import com.pasich.mynotes.ui.view.fragment.settings.InterfaceSettingsFragment; import com.pasich.mynotes.ui.view.fragment.settings.MediaSettingsFragment; @@ -29,4 +28,4 @@ public Fragment createFragment(int position) { public int getItemCount() { return 3; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/TagsManagementAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/TagsManagementAdapter.java index 54f53ca..9e5cdf8 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/TagsManagementAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/TagsManagementAdapter.java @@ -4,16 +4,13 @@ import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.databinding.ItemTagManagementBinding; import com.pasich.mynotes.utils.managers.SystemTagsManager; - import java.util.ArrayList; import java.util.List; @@ -46,7 +43,6 @@ public void setOnTagMoveListener(OnTagMoveListener listener) { this.moveListener = listener; } - public void moveItemUI(int fromPosition, int toPosition) { if (fromPosition == 0 || toPosition == 0) return; @@ -70,7 +66,8 @@ public void saveDragChangesToDatabase() { @Override public TagViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { LayoutInflater inflater = LayoutInflater.from(parent.getContext()); - ItemTagManagementBinding binding = ItemTagManagementBinding.inflate(inflater, parent, false); + ItemTagManagementBinding binding = + ItemTagManagementBinding.inflate(inflater, parent, false); return new TagViewHolder(binding); } @@ -92,27 +89,32 @@ public void bind(Tag tag, int position) { binding.executePendingBindings(); // Set click listeners - binding.getRoot().setOnClickListener(v -> { - if (clickListener != null) { - clickListener.onTagClick(tag, position); - } - }); - - binding.getRoot().setOnLongClickListener(v -> { - if (clickListener != null && !SystemTagsManager.isAddTag(tag)) { - clickListener.onTagLongClick(tag, v); - return true; - } - return false; - }); + binding.getRoot() + .setOnClickListener( + v -> { + if (clickListener != null) { + clickListener.onTagClick(tag, position); + } + }); + + binding.getRoot() + .setOnLongClickListener( + v -> { + if (clickListener != null && !SystemTagsManager.isAddTag(tag)) { + clickListener.onTagLongClick(tag, v); + return true; + } + return false; + }); // Options button listener (only for regular tags) if (!SystemTagsManager.isAddTag(tag)) { - binding.optionsButton.setOnClickListener(v -> { - if (clickListener != null) { - clickListener.onOptionsClick(tag, v); - } - }); + binding.optionsButton.setOnClickListener( + v -> { + if (clickListener != null) { + clickListener.onOptionsClick(tag, v); + } + }); } } } @@ -125,9 +127,10 @@ public boolean areItemsTheSame(@NonNull Tag oldItem, @NonNull Tag newItem) { @Override public boolean areContentsTheSame(@NonNull Tag oldItem, @NonNull Tag newItem) { - return oldItem.getNameTag().equals(newItem.getNameTag()) && - oldItem.getVisibility() == newItem.getVisibility() && - oldItem.getSystemAction() == newItem.getSystemAction() && oldItem.getPosition() == newItem.getPosition(); + return oldItem.getNameTag().equals(newItem.getNameTag()) + && oldItem.getVisibility() == newItem.getVisibility() + && oldItem.getSystemAction() == newItem.getSystemAction() + && oldItem.getPosition() == newItem.getPosition(); } } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java index 413a109..096996f 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java @@ -2,23 +2,18 @@ import android.view.LayoutInflater; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.databinding.ItemNoteBinding; import com.pasich.mynotes.ui.controllers.SelectionController; import com.pasich.mynotes.utils.recycler.diffutil.NoteDiff; import com.pasich.mynotes.utils.recycler.payloads.NotePayloads; - +import dagger.hilt.android.scopes.ActivityScoped; import java.util.List; - import javax.inject.Inject; -import dagger.hilt.android.scopes.ActivityScoped; - @ActivityScoped public class NoteAdapter extends ListAdapter { @@ -43,20 +38,14 @@ public long getItemId(int position) { @NonNull @Override public NoteHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - ItemNoteBinding binding = ItemNoteBinding.inflate( - LayoutInflater.from(parent.getContext()), - parent, - false - ); + ItemNoteBinding binding = + ItemNoteBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); return new NoteHolder(binding); } @Override public void onBindViewHolder( - @NonNull NoteHolder holder, - int position, - @NonNull List payloads - ) { + @NonNull NoteHolder holder, int position, @NonNull List payloads) { if (!payloads.isEmpty() && payloads.contains(NotePayloads.PAYLOAD_SELECTION)) { holder.bindSelectionState(getItem(position)); } else { @@ -88,14 +77,15 @@ void bind(Note note) { bindSelectionState(note); - binding.getRoot().setOnClickListener(v -> - listener.onClick(getBindingAdapterPosition(), note) - ); + binding.getRoot() + .setOnClickListener(v -> listener.onClick(getBindingAdapterPosition(), note)); - binding.getRoot().setOnLongClickListener(v -> { - listener.onLongClick(getBindingAdapterPosition(), note); - return true; - }); + binding.getRoot() + .setOnLongClickListener( + v -> { + listener.onLongClick(getBindingAdapterPosition(), note); + return true; + }); binding.getRoot().setTransitionName("note_" + note.getId()); } @@ -103,8 +93,7 @@ void bind(Note note) { void bindSelectionState(Note note) { boolean isSelected = - selectionController != null && - selectionController.isSelected(note.getId()); + selectionController != null && selectionController.isSelected(note.getId()); binding.setActivated(isSelected); binding.itemTop.setActivated(isSelected); diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/OnItemClickListener.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/OnItemClickListener.java index b7e42e0..3822bea 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/OnItemClickListener.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/OnItemClickListener.java @@ -1,11 +1,7 @@ package com.pasich.mynotes.utils.adapters.notes; - public interface OnItemClickListener { void onClick(int position, T model); - default void onLongClick(int position, T model) { - - } - + default void onLongClick(int position, T model) {} } diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SearchNotesAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SearchNotesAdapter.java index f907da5..2b4da9a 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SearchNotesAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SearchNotesAdapter.java @@ -3,35 +3,31 @@ import android.annotation.SuppressLint; import android.view.LayoutInflater; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.databinding.ItemResultBinding; - -import javax.inject.Inject; - import dagger.hilt.android.scopes.ActivityScoped; - +import javax.inject.Inject; @ActivityScoped public class SearchNotesAdapter extends ListAdapter { - private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback<>() { - @Override - public boolean areItemsTheSame(@NonNull Note oldItem, @NonNull Note newItem) { - return oldItem.getId() == newItem.getId(); - } + private static final DiffUtil.ItemCallback DIFF_CALLBACK = + new DiffUtil.ItemCallback<>() { + @Override + public boolean areItemsTheSame(@NonNull Note oldItem, @NonNull Note newItem) { + return oldItem.getId() == newItem.getId(); + } - @SuppressLint("DiffUtilEquals") - @Override - public boolean areContentsTheSame(@NonNull Note oldItem, @NonNull Note newItem) { - return oldItem.equals(newItem); - } - }; + @SuppressLint("DiffUtilEquals") + @Override + public boolean areContentsTheSame(@NonNull Note oldItem, @NonNull Note newItem) { + return oldItem.equals(newItem); + } + }; private SetItemClickListener onItemClickListener; @@ -47,9 +43,8 @@ public void setItemClickListener(SetItemClickListener onItemClickListener) { @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - ItemResultBinding binding = ItemResultBinding.inflate( - LayoutInflater.from(parent.getContext()), parent, false - ); + ItemResultBinding binding = + ItemResultBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); return new ViewHolder(binding, onItemClickListener); } @@ -74,9 +69,7 @@ public void bind(Note note) { binding.executePendingBindings(); if (clickListener != null) { - itemView.setOnClickListener(v -> - clickListener.onClick(note, binding.itemNote) - ); + itemView.setOnClickListener(v -> clickListener.onClick(note, binding.itemNote)); } } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SetItemClickListener.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SetItemClickListener.java index 514a32e..f7a4385 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SetItemClickListener.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SetItemClickListener.java @@ -1,7 +1,6 @@ package com.pasich.mynotes.utils.adapters.searchAdapter; import android.view.View; - import com.pasich.mynotes.data.model.Note; public interface SetItemClickListener { diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/OnItemClickListenerTag.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/OnItemClickListenerTag.java index 8d3c539..b6cf7b9 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/OnItemClickListenerTag.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/OnItemClickListenerTag.java @@ -6,4 +6,4 @@ public interface OnItemClickListenerTag { void onClick(int position); void onLongClick(int position, View mView); -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/TagsAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/TagsAdapter.java index d81116e..eaa15d7 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/TagsAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/TagsAdapter.java @@ -2,18 +2,14 @@ import android.view.LayoutInflater; import android.view.ViewGroup; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListAdapter; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.databinding.ItemTagBinding; import com.pasich.mynotes.utils.recycler.payloads.TagPayloads; - import java.util.List; - import javax.inject.Inject; import javax.inject.Named; @@ -39,28 +35,27 @@ public void setOnItemClickListener(OnItemClickListenerTag listener) { @NonNull @Override public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { - ItemTagBinding binding = ItemTagBinding.inflate( - LayoutInflater.from(parent.getContext()), - parent, - false - ); + ItemTagBinding binding = + ItemTagBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); ViewHolder holder = new ViewHolder(binding); - holder.itemView.setOnClickListener(v -> { - int pos = holder.getBindingAdapterPosition(); - if (pos != RecyclerView.NO_POSITION && clickListener != null) { - clickListener.onClick(pos); - } - }); + holder.itemView.setOnClickListener( + v -> { + int pos = holder.getBindingAdapterPosition(); + if (pos != RecyclerView.NO_POSITION && clickListener != null) { + clickListener.onClick(pos); + } + }); - holder.itemView.setOnLongClickListener(v -> { - int pos = holder.getBindingAdapterPosition(); - if (pos != RecyclerView.NO_POSITION && clickListener != null) { - clickListener.onLongClick(pos, holder.itemView); - } - return true; - }); + holder.itemView.setOnLongClickListener( + v -> { + int pos = holder.getBindingAdapterPosition(); + if (pos != RecyclerView.NO_POSITION && clickListener != null) { + clickListener.onLongClick(pos, holder.itemView); + } + return true; + }); return holder; } @@ -72,9 +67,8 @@ public void onBindViewHolder(@NonNull ViewHolder holder, int position) { } @Override - public void onBindViewHolder(@NonNull ViewHolder holder, - int position, - @NonNull List payloads) { + public void onBindViewHolder( + @NonNull ViewHolder holder, int position, @NonNull List payloads) { if (!payloads.isEmpty()) { Tag tag = getItem(position); diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/tasks/TasksAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/tasks/TasksAdapter.java index f07ff91..d1441ae 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/tasks/TasksAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/tasks/TasksAdapter.java @@ -11,14 +11,11 @@ import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Task; import com.pasich.mynotes.data.model.TaskCategory; - import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; @@ -35,12 +32,29 @@ public class TasksAdapter extends RecyclerView.Adapter private static final String SENTINEL_HEADER = "HEADER"; private static final String SENTINEL_ADD = "ADD"; - public interface OnToggleListener { void onToggle(Task task); } - public interface OnDeleteListener { void onDelete(Task task); } - public interface OnEditListener { void onEdit(Task task); } - public interface OnAddListener { void onAdd(); } - public interface OnStartDragListener { void onStartDrag(RecyclerView.ViewHolder holder); } - public interface OnReminderListener { void onReminder(Task task); } + public interface OnToggleListener { + void onToggle(Task task); + } + + public interface OnDeleteListener { + void onDelete(Task task); + } + + public interface OnEditListener { + void onEdit(Task task); + } + + public interface OnAddListener { + void onAdd(); + } + + public interface OnStartDragListener { + void onStartDrag(RecyclerView.ViewHolder holder); + } + + public interface OnReminderListener { + void onReminder(Task task); + } private List activeTasks = new ArrayList<>(); private List completedTasks = new ArrayList<>(); @@ -56,9 +70,13 @@ public interface OnReminderListener { void onReminder(Task task); } private List displayList = new ArrayList<>(); - public TasksAdapter(OnToggleListener toggle, OnDeleteListener delete, - OnEditListener edit, OnAddListener add, - OnStartDragListener drag, OnReminderListener reminder) { + public TasksAdapter( + OnToggleListener toggle, + OnDeleteListener delete, + OnEditListener edit, + OnAddListener add, + OnStartDragListener drag, + OnReminderListener reminder) { this.toggleListener = toggle; this.deleteListener = delete; this.editListener = edit; @@ -124,13 +142,15 @@ public boolean isActiveTask(int position) { public int getItemViewType(int position) { Object item = displayList.get(position); if (SENTINEL_HEADER.equals(item)) return TYPE_HEADER; - if (SENTINEL_ADD.equals(item)) return TYPE_ADD_FOOTER; + if (SENTINEL_ADD.equals(item)) return TYPE_ADD_FOOTER; Task t = (Task) item; return t.isDone() ? TYPE_COMPLETED : TYPE_ACTIVE; } @Override - public int getItemCount() { return displayList.size(); } + public int getItemCount() { + return displayList.size(); + } @NonNull @Override @@ -148,15 +168,20 @@ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int @Override public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) { if (holder instanceof HeaderHolder) { - ((HeaderHolder) holder).bind(completedTasks.size(), completedExpanded, () -> { - completedExpanded = !completedExpanded; - rebuildDisplayList(); - }); + ((HeaderHolder) holder) + .bind( + completedTasks.size(), + completedExpanded, + () -> { + completedExpanded = !completedExpanded; + rebuildDisplayList(); + }); } else if (holder instanceof AddFooterHolder) { ((AddFooterHolder) holder).bind(addListener); } else { Task task = (Task) displayList.get(position); - ((TaskHolder) holder).bind(task, toggleListener, editListener, dragListener, reminderListener); + ((TaskHolder) holder) + .bind(task, toggleListener, editListener, dragListener, reminderListener); } } @@ -179,8 +204,12 @@ class TaskHolder extends RecyclerView.ViewHolder { } @SuppressLint("ClickableViewAccessibility") - void bind(Task task, OnToggleListener toggle, OnEditListener edit, - OnStartDragListener drag, OnReminderListener reminder) { + void bind( + Task task, + OnToggleListener toggle, + OnEditListener edit, + OnStartDragListener drag, + OnReminderListener reminder) { checkBox.setOnCheckedChangeListener(null); checkBox.setChecked(task.isDone()); title.setText(task.getTitle()); @@ -212,8 +241,8 @@ void bind(Task task, OnToggleListener toggle, OnEditListener edit, description.setVisibility(View.GONE); } - TaskCategory cat = task.getCategoryId() != 0 - ? categoryMap.get(task.getCategoryId()) : null; + TaskCategory cat = + task.getCategoryId() != 0 ? categoryMap.get(task.getCategoryId()) : null; if (cat != null) { categoryLabel.setText(cat.getName()); categoryLabel.setVisibility(View.VISIBLE); @@ -224,7 +253,8 @@ void bind(Task task, OnToggleListener toggle, OnEditListener edit, bg.setCornerRadius(8f); bg.setColor(color); categoryLabel.setBackground(bg); - } catch (IllegalArgumentException ignored) {} + } catch (IllegalArgumentException ignored) { + } if (done) categoryLabel.setAlpha(0.45f); else categoryLabel.setAlpha(1f); } else { @@ -233,22 +263,27 @@ void bind(Task task, OnToggleListener toggle, OnEditListener edit, checkBox.setOnCheckedChangeListener((btn, checked) -> toggle.onToggle(task)); - itemView.setOnLongClickListener(v -> { - edit.onEdit(task); - return true; - }); - - dragHandle.setOnTouchListener((v, e) -> { - if (e.getAction() == MotionEvent.ACTION_DOWN) { - drag.onStartDrag(this); - } - return false; - }); + itemView.setOnLongClickListener( + v -> { + edit.onEdit(task); + return true; + }); + + dragHandle.setOnTouchListener( + (v, e) -> { + if (e.getAction() == MotionEvent.ACTION_DOWN) { + drag.onStartDrag(this); + } + return false; + }); } } static class AddFooterHolder extends RecyclerView.ViewHolder { - AddFooterHolder(View v) { super(v); } + AddFooterHolder(View v) { + super(v); + } + void bind(OnAddListener listener) { itemView.setOnClickListener(v -> listener.onAdd()); } diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/BackupCacheHelper.java b/app/src/main/java/com/pasich/mynotes/utils/backup/BackupCacheHelper.java index f36b0b9..dfcfcb5 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/BackupCacheHelper.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/BackupCacheHelper.java @@ -1,8 +1,6 @@ package com.pasich.mynotes.utils.backup; - import com.pasich.mynotes.utils.backup.models.JsonBackup; - import javax.inject.Inject; public class BackupCacheHelper { @@ -10,8 +8,7 @@ public class BackupCacheHelper { private JsonBackup jsonBackup; @Inject - public BackupCacheHelper() { - } + public BackupCacheHelper() {} public JsonBackup getJsonBackup() { return jsonBackup; diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/ScramblerBackupHelper.java b/app/src/main/java/com/pasich/mynotes/utils/backup/ScramblerBackupHelper.java index 17f5d9d..cbcc851 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/ScramblerBackupHelper.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/ScramblerBackupHelper.java @@ -1,36 +1,34 @@ package com.pasich.mynotes.utils.backup; - import android.util.Base64; - import com.google.gson.Gson; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.utils.backup.models.JsonBackup; - import java.nio.charset.StandardCharsets; /** * Helper class responsible for encoding and decoding backup data. * - *

The backup payload is serialized to JSON, encoded in Base64, - * and stored as a single string for portability and corruption safety.

+ *

The backup payload is serialized to JSON, encoded in Base64, and stored as a single string for + * portability and corruption safety. * - *

Decoding includes backward compatibility handling for older - * JSON formats that may not contain certain fields.

+ *

Decoding includes backward compatibility handling for older JSON formats that may not contain + * certain fields. */ public class ScramblerBackupHelper { - /** * Serializes a {@link JsonBackup} object into JSON and encodes it into Base64. * * @param jsonBackup The full backup model containing notes, tags, preferences etc. - * @return Base64-encoded string representing the backup. Returns empty string if encoding fails. + * @return Base64-encoded string representing the backup. Returns empty string if encoding + * fails. */ public static String encodeString(JsonBackup jsonBackup) { try { String jsonString = new Gson().toJson(jsonBackup); - return Base64.encodeToString(jsonString.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT); + return Base64.encodeToString( + jsonString.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT); } catch (Exception e) { return ""; } @@ -39,9 +37,9 @@ public static String encodeString(JsonBackup jsonBackup) { /** * Decodes a Base64 backup string and restores it into a {@link JsonBackup} object. * - *

This method includes compatibility logic for old backup formats: - * If the tag model did not contain the "position" field in older backups, - * the method assigns a default position (-1) for tags that represent normal user tags.

+ *

This method includes compatibility logic for old backup formats: If the tag model did not + * contain the "position" field in older backups, the method assigns a default position (-1) for + * tags that represent normal user tags. * * @param string Base64-encoded backup string * @return Decoded {@link JsonBackup} object, or backup object with error flag if corrupted @@ -77,6 +75,4 @@ public static JsonBackup decodeString(String string) { return new JsonBackup().error(); } } - - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/local/BackupFileValidator.java b/app/src/main/java/com/pasich/mynotes/utils/backup/local/BackupFileValidator.java index e13b927..74e8d6b 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/local/BackupFileValidator.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/local/BackupFileValidator.java @@ -5,28 +5,26 @@ import android.net.Uri; import android.provider.OpenableColumns; import android.util.Log; - import com.pasich.mynotes.R; /** - * Small helper class for validating selected backup files. - * Ensures that the file exists and has a supported extension. + * Small helper class for validating selected backup files. Ensures that the file exists and has a + * supported extension. */ public class BackupFileValidator { private static final String TAG = "BackupFileValidator"; private static final String EXT_JSON = ".json"; - private static final String EXT_ZIP = ".zip"; + private static final String EXT_ZIP = ".zip"; private static final String EXT_MNBK = ".mnbkn"; /** * Validate backup file based on its filename and extension. - *

- * - If user cancels selection → return silently (no errors shown). - * - If filename cannot be determined → callback.onInvalid(...) - * - If extension unsupported → callback.onInvalid(...) - * - If valid → callback.onValid(filename) + * + *

- If user cancels selection → return silently (no errors shown). - If filename cannot be + * determined → callback.onInvalid(...) - If extension unsupported → callback.onInvalid(...) - + * If valid → callback.onValid(filename) */ public static void isValidBackupFile(Context ctx, Uri uri, BackupValidatorCallback callback) { @@ -42,9 +40,7 @@ public static void isValidBackupFile(Context ctx, Uri uri, BackupValidatorCallba String lower = name.toLowerCase(); boolean ok = - lower.endsWith(EXT_JSON) || - lower.endsWith(EXT_ZIP) || - lower.endsWith(EXT_MNBK); + lower.endsWith(EXT_JSON) || lower.endsWith(EXT_ZIP) || lower.endsWith(EXT_MNBK); if (!ok) { callback.onInvalid(ctx.getString(R.string.file_wrong_format)); @@ -81,11 +77,10 @@ public static String getFileName(Context ctx, Uri uri) { return null; } - /** - * Result callback for validation. - */ + /** Result callback for validation. */ public interface BackupValidatorCallback { void onValid(String fileName); + void onInvalid(String errorMessage); } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/local/LocalBackup.java b/app/src/main/java/com/pasich/mynotes/utils/backup/local/LocalBackup.java index 2149f63..8507849 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/local/LocalBackup.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/local/LocalBackup.java @@ -4,11 +4,10 @@ import android.net.Uri; import android.os.ParcelFileDescriptor; import android.util.Log; - import com.pasich.mynotes.utils.backup.BackupCacheHelper; import com.pasich.mynotes.utils.backup.ScramblerBackupHelper; import com.pasich.mynotes.utils.backup.models.JsonBackup; - +import dagger.hilt.android.qualifiers.ApplicationContext; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -16,12 +15,9 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; import java.nio.file.Files; - import javax.inject.Inject; import javax.inject.Singleton; -import dagger.hilt.android.qualifiers.ApplicationContext; - @Singleton public class LocalBackup implements LocalBackupHelper { @@ -41,7 +37,8 @@ public boolean writeBackupLocalFile(BackupCacheHelper serviceCache, Uri uriLocal File zip = ZipBackupHelper.writeZipBackup(mContext, serviceCache.getJsonBackup()); - try (ParcelFileDescriptor desc = mContext.getContentResolver().openFileDescriptor(uriLocalFile, "w")) { + try (ParcelFileDescriptor desc = + mContext.getContentResolver().openFileDescriptor(uriLocalFile, "w")) { assert desc != null; try (FileOutputStream fos = new FileOutputStream(desc.getFileDescriptor())) { @@ -56,7 +53,6 @@ public boolean writeBackupLocalFile(BackupCacheHelper serviceCache, Uri uriLocal } } - @Override public JsonBackup readBackupLocalFile(Uri uriLocalFile) { try { @@ -76,7 +72,7 @@ public JsonBackup readBackupLocalFile(Uri uriLocalFile) { private byte[] readBytes(Uri uri) throws IOException { try (InputStream is = mContext.getContentResolver().openInputStream(uri); - ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { + ByteArrayOutputStream buffer = new ByteArrayOutputStream()) { if (is == null) return new byte[0]; @@ -90,10 +86,7 @@ private byte[] readBytes(Uri uri) throws IOException { } } - private boolean checkServiceCache(BackupCacheHelper serviceCache) { return serviceCache != null && serviceCache.getJsonBackup() != null; } - - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/local/LocalBackupHelper.java b/app/src/main/java/com/pasich/mynotes/utils/backup/local/LocalBackupHelper.java index 2c9cb59..001e0b6 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/local/LocalBackupHelper.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/local/LocalBackupHelper.java @@ -1,16 +1,12 @@ package com.pasich.mynotes.utils.backup.local; import android.net.Uri; - -import com.pasich.mynotes.utils.backup.models.JsonBackup; import com.pasich.mynotes.utils.backup.BackupCacheHelper; - -import java.io.File; +import com.pasich.mynotes.utils.backup.models.JsonBackup; public interface LocalBackupHelper { boolean writeBackupLocalFile(BackupCacheHelper serviceCache, Uri uriLocalFile); JsonBackup readBackupLocalFile(Uri uriLocalFile); - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/local/ZipBackupHelper.java b/app/src/main/java/com/pasich/mynotes/utils/backup/local/ZipBackupHelper.java index d2d3b60..2489e5b 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/local/ZipBackupHelper.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/local/ZipBackupHelper.java @@ -1,16 +1,13 @@ package com.pasich.mynotes.utils.backup.local; - import static com.pasich.mynotes.extendedEditor.attach.AttachmentStorage.ATTACHMENTS_BASE_DIR; import static com.pasich.mynotes.utils.constants.Backup.FILE_NAME_BACKUP; import static com.pasich.mynotes.utils.constants.Backup.FILE_NAME_BACKUP_MNBKN; import android.content.Context; import android.net.Uri; - import com.google.gson.Gson; import com.pasich.mynotes.utils.backup.models.JsonBackup; - import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; @@ -21,25 +18,15 @@ import java.util.zip.ZipInputStream; import java.util.zip.ZipOutputStream; -/** - * New ZIP-based backup format. - * Structure: - * My_Notes_Backup.json - * attachments/note_/file.ext - */ +/** New ZIP-based backup format. Structure: My_Notes_Backup.json attachments/note_/file.ext */ public class ZipBackupHelper { - - /** - * Detect ZIP by magic header "PK" - */ + /** Detect ZIP by magic header "PK" */ public static boolean isZip(byte[] data) { return data.length > 2 && data[0] == 0x50 && data[1] == 0x4B; } - /** - * Create ZIP backup - */ + /** Create ZIP backup */ public static File writeZipBackup(Context ctx, JsonBackup backup) throws Exception { File zipFile = new File(ctx.getCacheDir(), FILE_NAME_BACKUP_MNBKN); @@ -66,7 +53,12 @@ public static File writeZipBackup(Context ctx, JsonBackup backup) throws Excepti if (files == null) continue; for (File attachment : files) { - String entryName = ATTACHMENTS_BASE_DIR + "/" + noteDir.getName() + "/" + attachment.getName(); + String entryName = + ATTACHMENTS_BASE_DIR + + "/" + + noteDir.getName() + + "/" + + attachment.getName(); zos.putNextEntry(new ZipEntry(entryName)); zos.write(Files.readAllBytes(attachment.toPath())); @@ -80,15 +72,13 @@ public static File writeZipBackup(Context ctx, JsonBackup backup) throws Excepti return zipFile; } - /** - * Parse ZIP backup - */ + /** Parse ZIP backup */ public static JsonBackup readZipBackup(Context ctx, Uri uri) throws Exception { JsonBackup backup = null; try (InputStream is = ctx.getContentResolver().openInputStream(uri); - ZipInputStream zis = new ZipInputStream(is)) { + ZipInputStream zis = new ZipInputStream(is)) { ZipEntry entry; @@ -109,7 +99,6 @@ public static JsonBackup readZipBackup(Context ctx, Uri uri) throws Exception { backup = new Gson().fromJson(json, JsonBackup.class); } - // ================== attachments/... ================== else if (entry.getName().startsWith(ATTACHMENTS_BASE_DIR)) { @@ -134,6 +123,4 @@ else if (entry.getName().startsWith(ATTACHMENTS_BASE_DIR)) { return backup != null ? backup : new JsonBackup().error(); } - - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/models/JsonBackup.java b/app/src/main/java/com/pasich/mynotes/utils/backup/models/JsonBackup.java index 4c2a98d..5a6347d 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/models/JsonBackup.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/models/JsonBackup.java @@ -1,10 +1,8 @@ package com.pasich.mynotes.utils.backup.models; - import com.google.gson.annotations.SerializedName; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.Tag; - import java.util.ArrayList; import java.util.List; @@ -111,5 +109,4 @@ public List getTrashNotesUnified() { // Обидва пусті return new ArrayList<>(); } - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/models/PreferencesBackup.java b/app/src/main/java/com/pasich/mynotes/utils/backup/models/PreferencesBackup.java index d814914..8be0a1b 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/models/PreferencesBackup.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/models/PreferencesBackup.java @@ -3,15 +3,13 @@ import com.google.gson.annotations.SerializedName; import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; - /** * Backup model for user preferences. * - *

This class stores all app + theme preferences required - * to fully restore user configuration after importing a backup.

+ *

This class stores all app + theme preferences required to fully restore user configuration + * after importing a backup. * - *

Backward-compatible: new fields are optional and have - * safe default values for old backups.

+ *

Backward-compatible: new fields are optional and have safe default values for old backups. */ public class PreferencesBackup { @@ -44,7 +42,6 @@ public class PreferencesBackup { @SerializedName("k") private final boolean imageOptimizationEnabled; - @SerializedName("m") private final boolean screenProtection; @@ -54,7 +51,6 @@ public class PreferencesBackup { @SerializedName("o") private final float uiFontScale; - public PreferencesBackup( int formatCount, String typeFace, @@ -66,8 +62,7 @@ public PreferencesBackup( boolean imageOptEnabled, boolean screenProtection, boolean extendedEditor, - float uiFontScale - ) { + float uiFontScale) { this.formatCount = formatCount; this.typeFaceNoteActivity = typeFace; this.sortParam = sortParam; @@ -83,7 +78,6 @@ public PreferencesBackup( this.isCreated = true; } - // ================= DEFAULT CONSTRUCTOR (IMPORT OLD BACKUP) ================= public PreferencesBackup() { @@ -103,7 +97,6 @@ public PreferencesBackup() { this.isCreated = false; } - // ================= GETTERS ================= public int getFormatCount() { @@ -138,7 +131,6 @@ public int getThemeMode() { return themeMode; } - public boolean isImageOptimizationEnabled() { return imageOptimizationEnabled; } diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/models/TrashNote.java b/app/src/main/java/com/pasich/mynotes/utils/backup/models/TrashNote.java index d83dcfb..950e67a 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/models/TrashNote.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/models/TrashNote.java @@ -1,18 +1,15 @@ package com.pasich.mynotes.utils.backup.models; - import com.google.gson.annotations.SerializedName; import com.pasich.mynotes.data.model.Note; /** * Legacy TrashNote model. * - *

This class is kept only for backward compatibility with - * old backup files created before the introduction of "isTrash" - * field inside the Note model.

+ *

This class is kept only for backward compatibility with old backup files created before the + * introduction of "isTrash" field inside the Note model. * - *

Once support for old backups is no longer needed, - * this class can be safely removed.

+ *

Once support for old backups is no longer needed, this class can be safely removed. */ @Deprecated public class TrashNote { @@ -39,7 +36,6 @@ public TrashNote create(String title, String value, long date) { return this; } - public int getId() { return this.id; } @@ -77,8 +73,8 @@ public void setDate(long date) { } /** - * Converts this legacy TrashNote into a modern Note object - * using the new unified "isTrash" flag. + * Converts this legacy TrashNote into a modern Note object using the new unified "isTrash" + * flag. */ public Note toNote() { Note note = new Note(); @@ -97,5 +93,4 @@ public Note toNote() { return note; } - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepImportResult.java b/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepImportResult.java index 58ce65b..ceca817 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepImportResult.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepImportResult.java @@ -2,7 +2,6 @@ import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.Tag; - import java.util.ArrayList; import java.util.List; @@ -12,19 +11,27 @@ public class GoogleKeepImportResult { private final List labels; private final String error; - private GoogleKeepImportResult(List notes, List trashedNotes, List labels, String error) { + private GoogleKeepImportResult( + List notes, + List trashedNotes, + List labels, + String error) { this.notes = notes; this.trashedNotes = trashedNotes; this.labels = labels; this.error = error; } - public static GoogleKeepImportResult success(List notes, List trashedNotes, List labels) { + public static GoogleKeepImportResult success( + List notes, + List trashedNotes, + List labels) { return new GoogleKeepImportResult(notes, trashedNotes, labels, null); } public static GoogleKeepImportResult error(String error) { - return new GoogleKeepImportResult(new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), error); + return new GoogleKeepImportResult( + new ArrayList<>(), new ArrayList<>(), new ArrayList<>(), error); } public List getNotes() { @@ -77,4 +84,4 @@ public List toAppTags() { } return appTags; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepLabel.java b/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepLabel.java index 94cf9bf..73c1b04 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepLabel.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepLabel.java @@ -15,6 +15,7 @@ public GoogleKeepLabel() {} public String getName() { return name; } + public void setName(String name) { this.name = name; } @@ -32,4 +33,3 @@ public int hashCode() { return name != null ? name.hashCode() : 0; } } - diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepNote.java b/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepNote.java index 540137b..edafcb9 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepNote.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/models/googleKeep/GoogleKeepNote.java @@ -2,7 +2,6 @@ import com.google.gson.annotations.SerializedName; import com.pasich.mynotes.data.model.Note; - import java.util.List; public class GoogleKeepNote { @@ -42,9 +41,7 @@ public List getLabels() { return labels; } - /** - * Отримати перший label (або null, якщо їх нема) - */ + /** Отримати перший label (або null, якщо їх нема) */ public GoogleKeepLabel getFirstLabel() { if (labels != null && !labels.isEmpty()) { return labels.get(0); @@ -52,9 +49,7 @@ public GoogleKeepLabel getFirstLabel() { return new GoogleKeepLabel(""); } - /** - * Конвертує нотатку з Google Keep у формат застосунку - */ + /** Конвертує нотатку з Google Keep у формат застосунку */ public Note toAppNotes() { Note note = new Note(); long timestamp = userEditedTimestampUsec / 1000; @@ -68,4 +63,3 @@ public Note toAppNotesTrash() { return note.create(title, textContent, timestamp, getFirstLabel().getName()); } } - diff --git a/app/src/main/java/com/pasich/mynotes/utils/backup/otherApp/GoogleKeepImportService.java b/app/src/main/java/com/pasich/mynotes/utils/backup/otherApp/GoogleKeepImportService.java index 5f76753..7d78aa5 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/backup/otherApp/GoogleKeepImportService.java +++ b/app/src/main/java/com/pasich/mynotes/utils/backup/otherApp/GoogleKeepImportService.java @@ -3,13 +3,12 @@ import android.content.Context; import android.net.Uri; import android.util.Log; - import com.google.gson.Gson; import com.google.gson.JsonSyntaxException; import com.pasich.mynotes.utils.backup.models.googleKeep.GoogleKeepImportResult; import com.pasich.mynotes.utils.backup.models.googleKeep.GoogleKeepLabel; import com.pasich.mynotes.utils.backup.models.googleKeep.GoogleKeepNote; - +import dagger.hilt.android.qualifiers.ApplicationContext; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -18,12 +17,9 @@ import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipInputStream; - import javax.inject.Inject; import javax.inject.Singleton; -import dagger.hilt.android.qualifiers.ApplicationContext; - @Singleton public class GoogleKeepImportService { private static final String TAG = "GoogleKeepImportService"; @@ -44,7 +40,8 @@ public GoogleKeepImportResult importFromZip(Uri zipUri) { List trashedNotes = new ArrayList<>(); List labels = new ArrayList<>(); - try (InputStream inputStream = context.getContentResolver().openInputStream(zipUri); ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { + try (InputStream inputStream = context.getContentResolver().openInputStream(zipUri); + ZipInputStream zipInputStream = new ZipInputStream(inputStream)) { ZipEntry entry; boolean foundKeepFolder = false; @@ -65,7 +62,8 @@ public GoogleKeepImportResult importFromZip(Uri zipUri) { } if (!foundKeepFolder) { - return GoogleKeepImportResult.error("The archive does not match the Google Takeout/Keep export format"); + return GoogleKeepImportResult.error( + "The archive does not match the Google Takeout/Keep export format"); } if (notes.isEmpty() && trashedNotes.isEmpty()) { @@ -76,11 +74,17 @@ public GoogleKeepImportResult importFromZip(Uri zipUri) { } catch (IOException e) { Log.e(TAG, "Error while processing the ZIP archive", e); - return GoogleKeepImportResult.error("Error while processing the ZIP archive: " + e.getMessage()); + return GoogleKeepImportResult.error( + "Error while processing the ZIP archive: " + e.getMessage()); } } - private void processJsonEntry(ZipInputStream zipInputStream, List notes, List trashedNotes, List labels) throws IOException { + private void processJsonEntry( + ZipInputStream zipInputStream, + List notes, + List trashedNotes, + List labels) + throws IOException { BufferedReader reader = new BufferedReader(new InputStreamReader(zipInputStream)); StringBuilder jsonContent = new StringBuilder(); String line; @@ -107,4 +111,4 @@ private void processJsonEntry(ZipInputStream zipInputStream, List 0) { textView.setText( - textView.getContext().getString(R.string.lastDateEditNote, FormattedDataUtil.lastDayEditNote(note.getDate())) - ); + textView.getContext() + .getString( + R.string.lastDateEditNote, + FormattedDataUtil.lastDayEditNote(note.getDate()))); textView.setVisibility(View.VISIBLE); } else { textView.setText(""); @@ -47,9 +46,7 @@ public static void setDataNote(TextView textView, Note note) { public static void setNoteMediaCount(TextView textView, Note note) { int countMedia = ParsedNote.parseAttachmentsJson(note.getAttachments()).size(); if (countMedia > 0) { - textView.setText( - textView.getContext().getString(R.string.countMediaNote, countMedia) - ); + textView.setText(textView.getContext().getString(R.string.countMediaNote, countMedia)); textView.setVisibility(View.VISIBLE); } else { textView.setText(""); @@ -57,7 +54,6 @@ public static void setNoteMediaCount(TextView textView, Note note) { } } - @BindingAdapter("noteStrokeColor") public static void setNoteStrokeColor(MaterialCardView card, Note note) { if (note == null) return; @@ -73,7 +69,6 @@ public static void setNoteStrokeColor(MaterialCardView card, Note note) { color = ctx.getColor(R.color.item_bindig_note_surface_variant); } - card.setStrokeColor(color); } @@ -83,35 +78,34 @@ public static void setNoteBottomPadding(View view, Note note) { boolean hasExtras = !note.getTag().isEmpty() || note.isAttachments() || note.hasReminder(); - int padding = view.getContext().getResources().getDimensionPixelSize( - hasExtras ? R.dimen.marginItemsNoteChip : R.dimen.marginItemsNote - ); + int padding = + view.getContext() + .getResources() + .getDimensionPixelSize( + hasExtras ? R.dimen.marginItemsNoteChip : R.dimen.marginItemsNote); view.setPadding( - view.getPaddingLeft(), - view.getPaddingTop(), - view.getPaddingRight(), - padding - ); + view.getPaddingLeft(), view.getPaddingTop(), view.getPaddingRight(), padding); } @BindingAdapter("dynamicShapeTop") public static void setDynamicShapeTop(MaterialCardView card, boolean needTop) { - int style = needTop - ? R.style.ShapeAppearance_SettingsCard_Top - : R.style.ShapeAppearance_SettingsCard_Base; + int style = + needTop + ? R.style.ShapeAppearance_SettingsCard_Top + : R.style.ShapeAppearance_SettingsCard_Base; card.setShapeAppearanceModel( - ShapeAppearanceModel.builder(card.getContext(), style, 0).build() - ); + ShapeAppearanceModel.builder(card.getContext(), style, 0).build()); } @BindingAdapter("dynamicShapeBottom") public static void setDynamicShapeBottom(MaterialCardView card, boolean needBottom) { - int style = needBottom - ? R.style.ShapeAppearance_SettingsCard_Base : R.style.ShapeAppearance_SettingsCard_Bottom; + int style = + needBottom + ? R.style.ShapeAppearance_SettingsCard_Base + : R.style.ShapeAppearance_SettingsCard_Bottom; card.setShapeAppearanceModel( - ShapeAppearanceModel.builder(card.getContext(), style, 0).build() - ); + ShapeAppearanceModel.builder(card.getContext(), style, 0).build()); } @BindingAdapter("paddingVerticalDynamic") @@ -121,17 +115,16 @@ public static void setDynamicVerticalPadding(View view, Note note) { int paddingValue; if (note.getTitle().isEmpty() && note.getValuePreview().isEmpty()) { - paddingValue = view.getContext().getResources().getDimensionPixelSize(R.dimen.marginItemsNote); + paddingValue = + view.getContext().getResources().getDimensionPixelSize(R.dimen.marginItemsNote); } else { - paddingValue = view.getContext().getResources().getDimensionPixelSize(R.dimen.marginItemsNoteBottomCard); + paddingValue = + view.getContext() + .getResources() + .getDimensionPixelSize(R.dimen.marginItemsNoteBottomCard); } - view.setPadding( - view.getPaddingLeft(), - paddingValue, - view.getPaddingRight(), - paddingValue - ); + view.setPadding(view.getPaddingLeft(), paddingValue, view.getPaddingRight(), paddingValue); } @BindingAdapter("reminderText") @@ -146,15 +139,17 @@ public static void setReminderText(TextView textView, Note note) { Calendar rem = Calendar.getInstance(); rem.setTimeInMillis(note.getReminderTime()); - boolean isToday = now.get(Calendar.DATE) == rem.get(Calendar.DATE) - && now.get(Calendar.MONTH) == rem.get(Calendar.MONTH) - && now.get(Calendar.YEAR) == rem.get(Calendar.YEAR); + boolean isToday = + now.get(Calendar.DATE) == rem.get(Calendar.DATE) + && now.get(Calendar.MONTH) == rem.get(Calendar.MONTH) + && now.get(Calendar.YEAR) == rem.get(Calendar.YEAR); Calendar tomorrowCal = Calendar.getInstance(); tomorrowCal.add(Calendar.DATE, 1); - boolean isTomorrow = tomorrowCal.get(Calendar.DATE) == rem.get(Calendar.DATE) - && tomorrowCal.get(Calendar.MONTH) == rem.get(Calendar.MONTH) - && tomorrowCal.get(Calendar.YEAR) == rem.get(Calendar.YEAR); + boolean isTomorrow = + tomorrowCal.get(Calendar.DATE) == rem.get(Calendar.DATE) + && tomorrowCal.get(Calendar.MONTH) == rem.get(Calendar.MONTH) + && tomorrowCal.get(Calendar.YEAR) == rem.get(Calendar.YEAR); Calendar weekEnd = Calendar.getInstance(); weekEnd.add(Calendar.DATE, 7); @@ -196,5 +191,4 @@ public static void bindNoteItemTop(MaterialCardView tv, Note note) { tv.setVisibility(View.VISIBLE); } } - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/databinding/TagBindingAdapters.java b/app/src/main/java/com/pasich/mynotes/utils/databinding/TagBindingAdapters.java index fd05b67..ee503e5 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/databinding/TagBindingAdapters.java +++ b/app/src/main/java/com/pasich/mynotes/utils/databinding/TagBindingAdapters.java @@ -2,9 +2,7 @@ import android.view.View; import android.widget.TextView; - import androidx.databinding.BindingAdapter; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.utils.managers.SystemTagsManager; diff --git a/app/src/main/java/com/pasich/mynotes/utils/enums/SaveState.java b/app/src/main/java/com/pasich/mynotes/utils/enums/SaveState.java index 4c51e1d..19b1161 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/enums/SaveState.java +++ b/app/src/main/java/com/pasich/mynotes/utils/enums/SaveState.java @@ -1,9 +1,9 @@ package com.pasich.mynotes.utils.enums; public enum SaveState { - IDLE, // Без змін - PENDING, // Є незбережені зміни - SAVING, // Процес збереження - SAVED, // Успішно збережено - ERROR // Помилка збереження + IDLE, // Без змін + PENDING, // Є незбережені зміни + SAVING, // Процес збереження + SAVED, // Успішно збережено + ERROR // Помилка збереження } diff --git a/app/src/main/java/com/pasich/mynotes/utils/file/FileExportUtils.java b/app/src/main/java/com/pasich/mynotes/utils/file/FileExportUtils.java index 065262a..29949f2 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/file/FileExportUtils.java +++ b/app/src/main/java/com/pasich/mynotes/utils/file/FileExportUtils.java @@ -9,12 +9,9 @@ import android.net.Uri; import android.util.Log; import android.widget.Toast; - import androidx.core.content.FileProvider; - import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Note; - import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; @@ -25,18 +22,13 @@ import java.util.Locale; import java.util.Objects; - -/** - * Utility class for exporting notes to different file formats - */ +/** Utility class for exporting notes to different file formats */ public class FileExportUtils { private static final String TAG = "FileExportUtils"; public static final String GOOGLE_DRIVE_PACKAGE = "com.google.android.apps.docs"; - /** - * Create intent for saving TXT file with system file picker - */ + /** Create intent for saving TXT file with system file picker */ public static Intent createSaveTxtIntent(String noteTitle) { String fileName = generateFileName(noteTitle, "txt"); @@ -49,9 +41,7 @@ public static Intent createSaveTxtIntent(String noteTitle) { return intent; } - /** - * Create intent for saving PDF file with system file picker - */ + /** Create intent for saving PDF file with system file picker */ public static Intent createSavePdfIntent(String noteTitle) { String fileName = generateFileName(noteTitle, "pdf"); @@ -63,9 +53,7 @@ public static Intent createSavePdfIntent(String noteTitle) { return intent; } - /** - * Create intent for saving HTML file with system file picker - */ + /** Create intent for saving HTML file with system file picker */ public static Intent createSaveHtmlIntent(String noteTitle) { String fileName = generateFileName(noteTitle, "html"); @@ -77,14 +65,16 @@ public static Intent createSaveHtmlIntent(String noteTitle) { return intent; } - /** - * Open Google Drive intent to save file - */ + /** Open Google Drive intent to save file */ public static void saveToGoogleDrive(Context context, String noteTitle, String noteContent) { try { // Check if Google Drive is installed if (!isAppInstalled(context)) { - Toast.makeText(context, context.getString(R.string.googleDriveNotInstalled), Toast.LENGTH_LONG).show(); + Toast.makeText( + context, + context.getString(R.string.googleDriveNotInstalled), + Toast.LENGTH_LONG) + .show(); return; } @@ -98,16 +88,22 @@ public static void saveToGoogleDrive(Context context, String noteTitle, String n } catch (Exception e) { Log.e(TAG, "Error saving to Google Drive", e); - Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT).show(); + Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT) + .show(); } } - public static void saveBackupToGoogleDrive(Context context, String jsonContent, DriveProcess callback) { + public static void saveBackupToGoogleDrive( + Context context, String jsonContent, DriveProcess callback) { String fileName = generateBackupFileName(); try { // Перевірка, чи є Google Drive if (!isAppInstalled(context)) { - Toast.makeText(context, context.getString(R.string.googleDriveNotInstalled), Toast.LENGTH_LONG).show(); + Toast.makeText( + context, + context.getString(R.string.googleDriveNotInstalled), + Toast.LENGTH_LONG) + .show(); return; } @@ -119,7 +115,6 @@ public static void saveBackupToGoogleDrive(Context context, String jsonContent, } } - // Створюємо тимчасовий файл у кеші File file = new File(context.getCacheDir(), fileName + ".json"); try (FileOutputStream fos = new FileOutputStream(file)) { @@ -128,36 +123,34 @@ public static void saveBackupToGoogleDrive(Context context, String jsonContent, } // Робимо Uri через FileProvider - Uri fileUri = FileProvider.getUriForFile(context, context.getPackageName() + ".provider", // authorities - file); + Uri fileUri = + FileProvider.getUriForFile( + context, + context.getPackageName() + ".provider", // authorities + file); callback.onSuccess(fileUri, fileName); - - } catch (Exception e) { callback.onError(context.getString(R.string.errorSavingFile)); Log.e("DriveExport", "Error saving to Google Drive", e); } } - - /** - * Share note through other apps - */ + /** Share note through other apps */ public static void shareViaOtherApps(Context context, String noteTitle, String noteContent) { Intent shareIntent = new Intent(Intent.ACTION_SEND); shareIntent.setType("text/plain"); shareIntent.putExtra(Intent.EXTRA_TEXT, noteContent); shareIntent.putExtra(Intent.EXTRA_SUBJECT, noteTitle); - Intent chooser = Intent.createChooser(shareIntent, context.getString(R.string.shareViaApps)); + Intent chooser = + Intent.createChooser(shareIntent, context.getString(R.string.shareViaApps)); context.startActivity(chooser); } - /** - * Save TXT content to selected URI - */ - public static void saveTxtToUri(Context context, Uri uri, String noteTitle, String noteContent) { + /** Save TXT content to selected URI */ + public static void saveTxtToUri( + Context context, Uri uri, String noteTitle, String noteContent) { try { String formattedContent = formatNoteContent(noteTitle, noteContent); @@ -165,7 +158,7 @@ public static void saveTxtToUri(Context context, Uri uri, String noteTitle, Stri if (outputStream != null) { // Add UTF-8 BOM for better compatibility - byte[] bom = new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}; + byte[] bom = new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}; outputStream.write(bom); byte[] contentBytes = formattedContent.getBytes(StandardCharsets.UTF_8); @@ -174,24 +167,33 @@ public static void saveTxtToUri(Context context, Uri uri, String noteTitle, Stri outputStream.flush(); outputStream.close(); - Toast.makeText(context, context.getString(R.string.fileSavedSuccessfully), Toast.LENGTH_SHORT).show(); + Toast.makeText( + context, + context.getString(R.string.fileSavedSuccessfully), + Toast.LENGTH_SHORT) + .show(); } else { - Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT).show(); + Toast.makeText( + context, + context.getString(R.string.errorSavingFile), + Toast.LENGTH_SHORT) + .show(); } } catch (Exception e) { Log.e(TAG, "Error saving TXT file", e); - Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT).show(); + Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT) + .show(); } } - /** - * Save PDF content to selected URI - */ - public static void savePdfToUri(Context context, Uri uri, String noteTitle, String noteContent) { + /** Save PDF content to selected URI */ + public static void savePdfToUri( + Context context, Uri uri, String noteTitle, String noteContent) { try { // Create PDF document PdfDocument pdfDocument = new PdfDocument(); - PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(595, 842, 1).create(); // A4 size + PdfDocument.PageInfo pageInfo = + new PdfDocument.PageInfo.Builder(595, 842, 1).create(); // A4 size PdfDocument.Page page = pdfDocument.startPage(pageInfo); Canvas canvas = page.getCanvas(); @@ -205,7 +207,8 @@ public static void savePdfToUri(Context context, Uri uri, String noteTitle, Stri int maxWidth = 500; // Title - String title = (noteTitle == null || noteTitle.trim().isEmpty()) ? "***" : noteTitle.trim(); + String title = + (noteTitle == null || noteTitle.trim().isEmpty()) ? "***" : noteTitle.trim(); paint.setTextSize(16); paint.setFakeBoldText(true); @@ -222,7 +225,10 @@ public static void savePdfToUri(Context context, Uri uri, String noteTitle, Stri for (String line : lines) { if (y > 800) { // Start new page if needed pdfDocument.finishPage(page); - pageInfo = new PdfDocument.PageInfo.Builder(595, 842, pdfDocument.getPages().size() + 1).create(); + pageInfo = + new PdfDocument.PageInfo.Builder( + 595, 842, pdfDocument.getPages().size() + 1) + .create(); page = pdfDocument.startPage(pageInfo); canvas = page.getCanvas(); y = 50; @@ -270,31 +276,41 @@ public static void savePdfToUri(Context context, Uri uri, String noteTitle, Stri outputStream.close(); Log.d(TAG, "PDF written successfully"); - Toast.makeText(context, context.getString(R.string.fileSavedSuccessfully), Toast.LENGTH_SHORT).show(); + Toast.makeText( + context, + context.getString(R.string.fileSavedSuccessfully), + Toast.LENGTH_SHORT) + .show(); } else { Log.e(TAG, "PDF OutputStream is null"); - Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT).show(); + Toast.makeText( + context, + context.getString(R.string.errorSavingFile), + Toast.LENGTH_SHORT) + .show(); } pdfDocument.close(); } catch (Exception e) { - Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT).show(); + Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT) + .show(); } } - /** - * Save HTML content to selected URI - */ - public static void saveHtmlToUri(Context context, Uri uri, String noteTitle, String noteContent, List notes) { + /** Save HTML content to selected URI */ + public static void saveHtmlToUri( + Context context, Uri uri, String noteTitle, String noteContent, List notes) { try { - String htmlContent = HtmlTemplateGenerator.generateHtmlContent(context, noteTitle, noteContent, notes); + String htmlContent = + HtmlTemplateGenerator.generateHtmlContent( + context, noteTitle, noteContent, notes); OutputStream outputStream = context.getContentResolver().openOutputStream(uri); if (outputStream != null) { // Add UTF-8 BOM for better compatibility - byte[] bom = new byte[]{(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}; + byte[] bom = new byte[] {(byte) 0xEF, (byte) 0xBB, (byte) 0xBF}; outputStream.write(bom); byte[] contentBytes = htmlContent.getBytes(StandardCharsets.UTF_8); @@ -303,19 +319,26 @@ public static void saveHtmlToUri(Context context, Uri uri, String noteTitle, Str outputStream.flush(); outputStream.close(); - Toast.makeText(context, context.getString(R.string.fileSavedSuccessfully), Toast.LENGTH_SHORT).show(); + Toast.makeText( + context, + context.getString(R.string.fileSavedSuccessfully), + Toast.LENGTH_SHORT) + .show(); } else { - Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT).show(); + Toast.makeText( + context, + context.getString(R.string.errorSavingFile), + Toast.LENGTH_SHORT) + .show(); } } catch (Exception e) { Log.e(TAG, "Error saving HTML file", e); - Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT).show(); + Toast.makeText(context, context.getString(R.string.errorSavingFile), Toast.LENGTH_SHORT) + .show(); } } - /** - * Generate file name with timestamp and handle empty titles - */ + /** Generate file name with timestamp and handle empty titles */ private static String generateFileName(String noteTitle, String extension) { // Handle empty or null title String title = (noteTitle == null || noteTitle.trim().isEmpty()) ? "***" : noteTitle.trim(); @@ -325,13 +348,12 @@ private static String generateFileName(String noteTitle, String extension) { sanitizedTitle = sanitizedTitle.substring(0, 50); } - String timestamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); + String timestamp = + new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(new Date()); return sanitizedTitle + "_" + timestamp + "." + extension; } - /** - * Format note content with title and text properly - */ + /** Format note content with title and text properly */ public static String formatNoteContent(String noteTitle, String noteContent) { StringBuilder formattedContent = new StringBuilder(); @@ -347,9 +369,7 @@ public static String formatNoteContent(String noteTitle, String noteContent) { return formattedContent.toString(); } - /** - * Check if app is installed - */ + /** Check if app is installed */ public static boolean isAppInstalled(Context context) { try { context.getPackageManager().getApplicationInfo(FileExportUtils.GOOGLE_DRIVE_PACKAGE, 0); diff --git a/app/src/main/java/com/pasich/mynotes/utils/file/HtmlTemplateGenerator.java b/app/src/main/java/com/pasich/mynotes/utils/file/HtmlTemplateGenerator.java index 8feb3be..a70c42e 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/file/HtmlTemplateGenerator.java +++ b/app/src/main/java/com/pasich/mynotes/utils/file/HtmlTemplateGenerator.java @@ -1,10 +1,7 @@ package com.pasich.mynotes.utils.file; - import android.content.Context; - import com.pasich.mynotes.data.model.Note; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; @@ -14,19 +11,17 @@ import java.util.List; import java.util.Locale; -/** - * Utility class for generating HTML templates and content - */ +/** Utility class for generating HTML templates and content */ public class HtmlTemplateGenerator { - /** - * Generate complete HTML content from notes - */ - public static String generateHtmlContent(Context context, String noteTitle, String noteContent, List notes) { + /** Generate complete HTML content from notes */ + public static String generateHtmlContent( + Context context, String noteTitle, String noteContent, List notes) { StringBuilder html = new StringBuilder(); // Get current date - SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", getCurrentLocale(context)); + SimpleDateFormat dateFormat = + new SimpleDateFormat("dd MMMM yyyy", getCurrentLocale(context)); String currentDate = dateFormat.format(new Date()); // Get localized strings @@ -35,19 +30,30 @@ public static String generateHtmlContent(Context context, String noteTitle, Stri // HTML template start html.append("\n") - .append("\n") + .append("\n") .append("\n") .append(" \n") - .append(" \n") - .append(" ").append(escapeHtml(notesTitle)).append("\n") + .append( + " \n") + .append(" ") + .append(escapeHtml(notesTitle)) + .append("\n") .append(" \n") .append("\n") .append("\n") .append("

\n") - .append("

").append(escapeHtml(notesTitle)).append("

\n") - .append("
").append(escapeHtml(exportFromText)).append(" ").append(escapeHtml(currentDate)).append("
\n") + .append("

") + .append(escapeHtml(notesTitle)) + .append("

\n") + .append("
") + .append(escapeHtml(exportFromText)) + .append(" ") + .append(escapeHtml(currentDate)) + .append("
\n") .append("
\n\n"); // Add notes content @@ -62,28 +68,27 @@ public static String generateHtmlContent(Context context, String noteTitle, Stri } // HTML template end - html.append("\n") - .append(""); + html.append("\n").append(""); return html.toString(); } - /** - * Add single note to HTML - */ + /** Add single note to HTML */ private static void addNoteToHtml(StringBuilder html, String title, String content) { String noteTitle = (title == null || title.trim().isEmpty()) ? "***" : title.trim(); String noteContent = (content == null) ? "" : content.trim(); html.append("
\n") - .append("
").append(escapeHtml(noteTitle)).append("
\n") - .append("
").append(escapeHtml(noteContent)).append("
\n") + .append("
") + .append(escapeHtml(noteTitle)) + .append("
\n") + .append("
") + .append(escapeHtml(noteContent)) + .append("
\n") .append("
\n\n"); } - /** - * Escape HTML special characters - */ + /** Escape HTML special characters */ private static String escapeHtml(String text) { if (text == null) return ""; return text.replace("&", "&") @@ -94,23 +99,17 @@ private static String escapeHtml(String text) { .replace("\n", "
"); } - /** - * Get current locale - */ + /** Get current locale */ private static Locale getCurrentLocale(Context context) { return context.getResources().getConfiguration().getLocales().get(0); } - /** - * Get current language code - */ + /** Get current language code */ private static String getCurrentLanguageCode(Context context) { return getCurrentLocale(context).getLanguage(); } - /** - * Get localized "My Notes" title - */ + /** Get localized "My Notes" title */ private static String getLocalizedNotesTitle(Context context) { String language = getCurrentLanguageCode(context); return switch (language) { @@ -127,9 +126,7 @@ private static String getLocalizedNotesTitle(Context context) { }; } - /** - * Get localized "Export from" text - */ + /** Get localized "Export from" text */ private static String getLocalizedExportFromText(Context context) { String language = getCurrentLanguageCode(context); return switch (language) { @@ -150,7 +147,7 @@ private static String loadCssFromAssets(Context context) { try { assert context != null; try (InputStream is = context.getAssets().open("notes_styles.css"); - BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { + BufferedReader reader = new BufferedReader(new InputStreamReader(is))) { StringBuilder sb = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { @@ -160,9 +157,7 @@ private static String loadCssFromAssets(Context context) { } } catch (IOException e) { - return ""; // fallback } } - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/file/ImageOptimizer.java b/app/src/main/java/com/pasich/mynotes/utils/file/ImageOptimizer.java index 0cff1be..31178d0 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/file/ImageOptimizer.java +++ b/app/src/main/java/com/pasich/mynotes/utils/file/ImageOptimizer.java @@ -3,39 +3,30 @@ import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.Build; - import java.io.ByteArrayOutputStream; /** * Ultra-lightweight image optimizer for Editor.js attachments. - *

- * Designed specifically for: - * - byte[] → byte[] processing (no I/O inside) - * - very fast path (no resizing, no EXIF parsing, no metadata work) - *

- * Behavior: - * - Detects input type by magic bytes (JPEG / PNG / WEBP) - * - PNG with transparency → stays PNG - * - PNG without transparency → converted to JPEG (smaller) - * - JPEG stays JPEG - * - WEBP stays WEBP (WEBP_LOSSY on Android R+) - * - UNKNOWN formats (HEIC, GIF, PDF, TXT, ZIP, etc.) → returned unchanged - *

- * Quality: - * - extraOptimize = true → strong compression (~60) - * - extraOptimize = false → normal compression (~85) + * + *

Designed specifically for: - byte[] → byte[] processing (no I/O inside) - very fast path (no + * resizing, no EXIF parsing, no metadata work) + * + *

Behavior: - Detects input type by magic bytes (JPEG / PNG / WEBP) - PNG with transparency → + * stays PNG - PNG without transparency → converted to JPEG (smaller) - JPEG stays JPEG - WEBP stays + * WEBP (WEBP_LOSSY on Android R+) - UNKNOWN formats (HEIC, GIF, PDF, TXT, ZIP, etc.) → returned + * unchanged + * + *

Quality: - extraOptimize = true → strong compression (~60) - extraOptimize = false → normal + * compression (~85) */ public class ImageOptimizer { - /** - * Detect format by magic bytes. - */ + /** Detect format by magic bytes. */ public static OutFormat detectFormat(byte[] data) { if (data == null || data.length < 12) return OutFormat.UNKNOWN; // JPEG: FF D8 - if ((data[0] & 0xFF) == 0xFF && (data[1] & 0xFF) == 0xD8) - return OutFormat.JPEG; + if ((data[0] & 0xFF) == 0xFF && (data[1] & 0xFF) == 0xD8) return OutFormat.JPEG; // PNG if ((data[0] & 0xFF) == 0x89 && data[1] == 0x50 && data[2] == 0x4E && data[3] == 0x47) @@ -50,22 +41,17 @@ public static OutFormat detectFormat(byte[] data) { /** * Optimizes raw byte[] image data. - *

- * - extraOptimize = true → lower quality (≈60) - * - extraOptimize = false → higher quality (≈85) - *

- * PNG with alpha → stays PNG. - * PNG without alpha → JPEG. - * JPEG stays JPEG. - * WEBP stays WEBP (lossy if Android R+). + * + *

- extraOptimize = true → lower quality (≈60) - extraOptimize = false → higher quality + * (≈85) + * + *

PNG with alpha → stays PNG. PNG without alpha → JPEG. JPEG stays JPEG. WEBP stays WEBP + * (lossy if Android R+). * * @return optimized image bytes or original bytes on error. */ public static byte[] optimizeRawImage( - byte[] raw, - OutFormat inputFormat, - boolean extraOptimize - ) { + byte[] raw, OutFormat inputFormat, boolean extraOptimize) { try { // We skip HEIC, GIF, PDF, DOC, ZIP — everything UNKNOWN if (inputFormat == OutFormat.UNKNOWN) { @@ -125,17 +111,11 @@ public static byte[] optimizeRawImage( } } - - /** - * Allowed output formats - */ + /** Allowed output formats */ public enum OutFormat { JPEG, PNG, WEBP, UNKNOWN } - } - - diff --git a/app/src/main/java/com/pasich/mynotes/utils/file/OutFormat.java b/app/src/main/java/com/pasich/mynotes/utils/file/OutFormat.java index c527c4a..b720ef8 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/file/OutFormat.java +++ b/app/src/main/java/com/pasich/mynotes/utils/file/OutFormat.java @@ -1,8 +1,8 @@ package com.pasich.mynotes.utils.file; public enum OutFormat { - JPEG, - PNG, - WEBP, - UNKNOWN - } + JPEG, + PNG, + WEBP, + UNKNOWN +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/file/SafeImageLoader.java b/app/src/main/java/com/pasich/mynotes/utils/file/SafeImageLoader.java index 5d46f49..2284463 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/file/SafeImageLoader.java +++ b/app/src/main/java/com/pasich/mynotes/utils/file/SafeImageLoader.java @@ -5,20 +5,15 @@ import android.graphics.BitmapFactory; import android.graphics.Matrix; import android.net.Uri; - import androidx.exifinterface.media.ExifInterface; - import java.io.File; import java.io.InputStream; /** * SafeImageLoader - *

- * Safely load large images from disk/URI without OutOfMemory. - * Can: - * • read bounds without decoding (to determine size) - * • scale via inSampleSize - * • correct EXIF rotations + * + *

Safely load large images from disk/URI without OutOfMemory. Can: • read bounds without + * decoding (to determine size) • scale via inSampleSize • correct EXIF rotations */ public class SafeImageLoader { public static Bitmap load(Context ctx, File file, int targetW, int targetH) throws Exception { @@ -28,15 +23,14 @@ public static Bitmap load(Context ctx, File file, int targetW, int targetH) thro /** * Main method for loading an image. - *

- * Steps: - * 1) Read only the dimensions (inJustDecodeBounds=true) to determine the optimal scale. - * 2) Calculate inSampleSize so as not to load the photo in full size (saving RAM). - * 3) Decode the actual image with the desired scaling. - * 4) Read EXIF and return the image in the correct orientation. * - * @param ctx context - * @param uri file Uri + *

Steps: 1) Read only the dimensions (inJustDecodeBounds=true) to determine the optimal + * scale. 2) Calculate inSampleSize so as not to load the photo in full size (saving RAM). 3) + * Decode the actual image with the desired scaling. 4) Read EXIF and return the image in the + * correct orientation. + * + * @param ctx context + * @param uri file Uri * @param targetW desired width (for scaling) * @param targetH desired height (for scaling) */ @@ -69,10 +63,9 @@ public static Bitmap load(Context ctx, Uri uri, int targetW, int targetH) throws try (InputStream is = ctx.getContentResolver().openInputStream(uri)) { assert is != null; ExifInterface exif = new ExifInterface(is); - int orientation = exif.getAttributeInt( - ExifInterface.TAG_ORIENTATION, - ExifInterface.ORIENTATION_NORMAL - ); + int orientation = + exif.getAttributeInt( + ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); bitmap = rotateFromExif(bitmap, orientation); } @@ -81,10 +74,9 @@ public static Bitmap load(Context ctx, Uri uri, int targetW, int targetH) throws /** * Calculates the optimal inSampleSize. - *

- * Algorithm: - * • Divide the dimensions by 2 until they fit into targetW/targetH. - * • inSampleSize is always a power of two (Android requirement). + * + *

Algorithm: • Divide the dimensions by 2 until they fit into targetW/targetH. • + * inSampleSize is always a power of two (Android requirement). */ private static int calculateInSampleSize(int srcW, int srcH, int reqW, int reqH) { int inSampleSize = 1; @@ -100,10 +92,7 @@ private static int calculateInSampleSize(int srcW, int srcH, int reqW, int reqH) return inSampleSize; } - /** - * Applies EXIF rotation to Bitmap. - * Supports 90 / 180 / 270 degrees. - */ + /** Applies EXIF rotation to Bitmap. Supports 90 / 180 / 270 degrees. */ private static Bitmap rotateFromExif(Bitmap bmp, int orientation) { Matrix matrix = new Matrix(); diff --git a/app/src/main/java/com/pasich/mynotes/utils/linkMovement/CustomLinkMovementMethod.java b/app/src/main/java/com/pasich/mynotes/utils/linkMovement/CustomLinkMovementMethod.java index d93c9a8..0d0659c 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/linkMovement/CustomLinkMovementMethod.java +++ b/app/src/main/java/com/pasich/mynotes/utils/linkMovement/CustomLinkMovementMethod.java @@ -50,7 +50,5 @@ public boolean onTouchEvent(TextView widget, Spannable buffer, MotionEvent event Selection.removeSelection(buffer); return true; - } - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/managers/BillingManager.java b/app/src/main/java/com/pasich/mynotes/utils/managers/BillingManager.java index 9f7282b..0b54c22 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/managers/BillingManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/managers/BillingManager.java @@ -3,10 +3,8 @@ import android.app.Activity; import android.content.Context; import android.util.Log; - import com.android.billingclient.api.*; import com.pasich.mynotes.data.model.DonationProduct; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -14,13 +12,18 @@ public class BillingManager implements PurchasesUpdatedListener, BillingClientStateListener { private static final String TAG = "BillingManager"; - + public interface BillingManagerListener { void onBillingInitialized(); + void onProductsLoaded(List products); + void onPurchaseSuccessful(String productId); + void onPurchaseFailed(int responseCode, String debugMessage); + void onBillingError(String errorMessage); + void onPurchasesLoaded(List purchases); } @@ -29,21 +32,23 @@ public interface BillingManagerListener { private boolean isServiceConnected; // Product IDs for donations - private static final List DONATION_PRODUCT_IDS = Arrays.asList( - "donate_seed_of_ideas", - "donate_spark_of_inspiration", - "donate_midnight_notebook", - "donate_wave_of_support", - "donate_universe_of_inspiration" - ); + private static final List DONATION_PRODUCT_IDS = + Arrays.asList( + "donate_seed_of_ideas", + "donate_spark_of_inspiration", + "donate_midnight_notebook", + "donate_wave_of_support", + "donate_universe_of_inspiration"); public BillingManager(Context context, BillingManagerListener listener) { this.listener = listener; - billingClient = BillingClient.newBuilder(context) - .setListener(this) - .enablePendingPurchases(PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) - .build(); - + billingClient = + BillingClient.newBuilder(context) + .setListener(this) + .enablePendingPurchases( + PendingPurchasesParams.newBuilder().enableOneTimeProducts().build()) + .build(); + startServiceConnection(); } @@ -56,13 +61,14 @@ public void onBillingSetupFinished(BillingResult billingResult) { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { isServiceConnected = true; Log.d(TAG, "Billing service connected successfully"); - + // Спочатку споживаємо всі наявні покупки - consumeAllExistingPurchases(() -> { - listener.onBillingInitialized(); - queryProductDetails(); - queryPurchases(); - }); + consumeAllExistingPurchases( + () -> { + listener.onBillingInitialized(); + queryProductDetails(); + queryPurchases(); + }); } else { Log.e(TAG, "Failed to connect to billing service: " + billingResult.getDebugMessage()); listener.onBillingError("Failed to connect to billing service"); @@ -70,41 +76,53 @@ public void onBillingSetupFinished(BillingResult billingResult) { } private void consumeAllExistingPurchases(Runnable onComplete) { - QueryPurchasesParams params = QueryPurchasesParams.newBuilder() - .setProductType(BillingClient.ProductType.INAPP) - .build(); - - billingClient.queryPurchasesAsync(params, (billingResult, purchasesList) -> { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { - if (purchasesList.isEmpty()) { - Log.d(TAG, "No existing purchases to consume"); - onComplete.run(); - return; - } - - Log.d(TAG, "Found " + purchasesList.size() + " existing purchases, consuming them"); - int[] remainingPurchases = {purchasesList.size()}; - - for (Purchase purchase : purchasesList) { - if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { - consumePurchase(purchase, () -> { - remainingPurchases[0]--; - if (remainingPurchases[0] <= 0) { - onComplete.run(); - } - }); - } else { - remainingPurchases[0]--; - if (remainingPurchases[0] <= 0) { + QueryPurchasesParams params = + QueryPurchasesParams.newBuilder() + .setProductType(BillingClient.ProductType.INAPP) + .build(); + + billingClient.queryPurchasesAsync( + params, + (billingResult, purchasesList) -> { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + if (purchasesList.isEmpty()) { + Log.d(TAG, "No existing purchases to consume"); onComplete.run(); + return; + } + + Log.d( + TAG, + "Found " + + purchasesList.size() + + " existing purchases, consuming them"); + int[] remainingPurchases = {purchasesList.size()}; + + for (Purchase purchase : purchasesList) { + if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { + consumePurchase( + purchase, + () -> { + remainingPurchases[0]--; + if (remainingPurchases[0] <= 0) { + onComplete.run(); + } + }); + } else { + remainingPurchases[0]--; + if (remainingPurchases[0] <= 0) { + onComplete.run(); + } + } } + } else { + Log.e( + TAG, + "Failed to query existing purchases for consumption: " + + billingResult.getDebugMessage()); + onComplete.run(); } - } - } else { - Log.e(TAG, "Failed to query existing purchases for consumption: " + billingResult.getDebugMessage()); - onComplete.run(); - } - }); + }); } @Override @@ -122,42 +140,46 @@ private void queryProductDetails() { List productList = new ArrayList<>(); for (String productId : DONATION_PRODUCT_IDS) { productList.add( - QueryProductDetailsParams.Product.newBuilder() - .setProductId(productId) - .setProductType(BillingClient.ProductType.INAPP) - .build() - ); + QueryProductDetailsParams.Product.newBuilder() + .setProductId(productId) + .setProductType(BillingClient.ProductType.INAPP) + .build()); } - QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder() - .setProductList(productList) - .build(); + QueryProductDetailsParams queryProductDetailsParams = + QueryProductDetailsParams.newBuilder().setProductList(productList).build(); - billingClient.queryProductDetailsAsync(queryProductDetailsParams, + billingClient.queryProductDetailsAsync( + queryProductDetailsParams, (billingResult, productDetailsList) -> { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { List products = new ArrayList<>(); - for (ProductDetails productDetails : productDetailsList.getProductDetailsList()) { + for (ProductDetails productDetails : + productDetailsList.getProductDetailsList()) { DonationProduct product = createDonationProduct(productDetails); products.add(product); } // Сортуємо товари за ціною (від меншої до більшої) - products.sort((p1, p2) -> { - try { - // Витягуємо числові значення з цін - double price1 = extractPrice(p1.getPrice()); - double price2 = extractPrice(p2.getPrice()); - return Double.compare(price1, price2); - } catch (Exception e) { - Log.w(TAG, "Error sorting prices: " + e.getMessage()); - return 0; - } - }); + products.sort( + (p1, p2) -> { + try { + // Витягуємо числові значення з цін + double price1 = extractPrice(p1.getPrice()); + double price2 = extractPrice(p2.getPrice()); + return Double.compare(price1, price2); + } catch (Exception e) { + Log.w(TAG, "Error sorting prices: " + e.getMessage()); + return 0; + } + }); listener.onProductsLoaded(products); } else { - Log.e(TAG, "Failed to query product details: " + billingResult.getDebugMessage()); + Log.e( + TAG, + "Failed to query product details: " + + billingResult.getDebugMessage()); listener.onBillingError("Failed to load products"); } }); @@ -165,16 +187,16 @@ private void queryProductDetails() { private DonationProduct createDonationProduct(ProductDetails productDetails) { String iconResource = getIconForProduct(productDetails.getProductId()); - ProductDetails.OneTimePurchaseOfferDetails offerDetails = productDetails.getOneTimePurchaseOfferDetails(); + ProductDetails.OneTimePurchaseOfferDetails offerDetails = + productDetails.getOneTimePurchaseOfferDetails(); String price = offerDetails != null ? offerDetails.getFormattedPrice() : "N/A"; - + return new DonationProduct( productDetails.getProductId(), productDetails.getTitle(), productDetails.getDescription(), price, - iconResource - ); + iconResource); } private String getIconForProduct(String productId) { @@ -195,39 +217,52 @@ public void launchBillingFlow(Activity activity, String productId) { } // Спочатку перевіряємо і споживаємо наявні покупки цього товару - queryAndConsumeExistingPurchases(productId, () -> { - // Після споживання запускаємо новий billing flow - proceedWithBillingFlow(activity, productId); - }); + queryAndConsumeExistingPurchases( + productId, + () -> { + // Після споживання запускаємо новий billing flow + proceedWithBillingFlow(activity, productId); + }); } private void queryAndConsumeExistingPurchases(String productId, Runnable onComplete) { - QueryPurchasesParams params = QueryPurchasesParams.newBuilder() - .setProductType(BillingClient.ProductType.INAPP) - .build(); - - billingClient.queryPurchasesAsync(params, (billingResult, purchasesList) -> { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { - boolean foundExisting = false; - for (Purchase purchase : purchasesList) { - if (purchase.getProducts().contains(productId) && - purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { - foundExisting = true; - Log.d(TAG, "Found existing purchase for " + productId + ", consuming it first"); - consumePurchase(purchase, onComplete); - break; + QueryPurchasesParams params = + QueryPurchasesParams.newBuilder() + .setProductType(BillingClient.ProductType.INAPP) + .build(); + + billingClient.queryPurchasesAsync( + params, + (billingResult, purchasesList) -> { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + boolean foundExisting = false; + for (Purchase purchase : purchasesList) { + if (purchase.getProducts().contains(productId) + && purchase.getPurchaseState() + == Purchase.PurchaseState.PURCHASED) { + foundExisting = true; + Log.d( + TAG, + "Found existing purchase for " + + productId + + ", consuming it first"); + consumePurchase(purchase, onComplete); + break; + } + } + if (!foundExisting) { + // Немає наявних покупок, можемо продовжувати + onComplete.run(); + } + } else { + Log.e( + TAG, + "Failed to query existing purchases: " + + billingResult.getDebugMessage()); + // Пробуємо продовжити навіть при помилці + onComplete.run(); } - } - if (!foundExisting) { - // Немає наявних покупок, можемо продовжувати - onComplete.run(); - } - } else { - Log.e(TAG, "Failed to query existing purchases: " + billingResult.getDebugMessage()); - // Пробуємо продовжити навіть при помилці - onComplete.run(); - } - }); + }); } private void proceedWithBillingFlow(Activity activity, String productId) { @@ -236,36 +271,43 @@ private void proceedWithBillingFlow(Activity activity, String productId) { return; } - List productList = List.of( - QueryProductDetailsParams.Product.newBuilder() - .setProductId(productId) - .setProductType(BillingClient.ProductType.INAPP) - .build() - ); + List productList = + List.of( + QueryProductDetailsParams.Product.newBuilder() + .setProductId(productId) + .setProductType(BillingClient.ProductType.INAPP) + .build()); - QueryProductDetailsParams queryProductDetailsParams = QueryProductDetailsParams.newBuilder() - .setProductList(productList) - .build(); + QueryProductDetailsParams queryProductDetailsParams = + QueryProductDetailsParams.newBuilder().setProductList(productList).build(); - billingClient.queryProductDetailsAsync(queryProductDetailsParams, + billingClient.queryProductDetailsAsync( + queryProductDetailsParams, (billingResult, productDetailsList) -> { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && !productDetailsList.getProductDetailsList().isEmpty()) { - - ProductDetails productDetails = productDetailsList.getProductDetailsList().get(0); - List productDetailsParamsList = List.of( - BillingFlowParams.ProductDetailsParams.newBuilder() - .setProductDetails(productDetails) - .build() - ); - - BillingFlowParams billingFlowParams = BillingFlowParams.newBuilder() - .setProductDetailsParamsList(productDetailsParamsList) - .build(); - - BillingResult result = billingClient.launchBillingFlow(activity, billingFlowParams); + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK + && !productDetailsList.getProductDetailsList().isEmpty()) { + + ProductDetails productDetails = + productDetailsList.getProductDetailsList().get(0); + List productDetailsParamsList = + List.of( + BillingFlowParams.ProductDetailsParams.newBuilder() + .setProductDetails(productDetails) + .build()); + + BillingFlowParams billingFlowParams = + BillingFlowParams.newBuilder() + .setProductDetailsParamsList(productDetailsParamsList) + .build(); + + BillingResult result = + billingClient.launchBillingFlow(activity, billingFlowParams); if (result.getResponseCode() != BillingClient.BillingResponseCode.OK) { - Log.e(TAG, "Failed to launch billing flow: " + result.getDebugMessage()); - listener.onPurchaseFailed(result.getResponseCode(), result.getDebugMessage()); + Log.e( + TAG, + "Failed to launch billing flow: " + result.getDebugMessage()); + listener.onPurchaseFailed( + result.getResponseCode(), result.getDebugMessage()); } } else { Log.e(TAG, "Product not found: " + productId); @@ -276,38 +318,45 @@ private void proceedWithBillingFlow(Activity activity, String productId) { @Override public void onPurchasesUpdated(BillingResult billingResult, List purchases) { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK && purchases != null) { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK + && purchases != null) { for (Purchase purchase : purchases) { handlePurchase(purchase); } - } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.USER_CANCELED) { + } else if (billingResult.getResponseCode() + == BillingClient.BillingResponseCode.USER_CANCELED) { Log.d(TAG, "User canceled the purchase"); listener.onPurchaseFailed(billingResult.getResponseCode(), "Purchase canceled by user"); - } else if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) { + } else if (billingResult.getResponseCode() + == BillingClient.BillingResponseCode.ITEM_ALREADY_OWNED) { Log.d(TAG, "Item already owned, attempting to consume existing purchase"); // Спробуємо знайти і спожити наявну покупку queryAndConsumeAllPurchases(); listener.onBillingError("Item already owned. Please try again in a moment."); } else { Log.e(TAG, "Purchase failed: " + billingResult.getDebugMessage()); - listener.onPurchaseFailed(billingResult.getResponseCode(), billingResult.getDebugMessage()); + listener.onPurchaseFailed( + billingResult.getResponseCode(), billingResult.getDebugMessage()); } } private void queryAndConsumeAllPurchases() { - QueryPurchasesParams params = QueryPurchasesParams.newBuilder() - .setProductType(BillingClient.ProductType.INAPP) - .build(); - - billingClient.queryPurchasesAsync(params, (billingResult, purchasesList) -> { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { - for (Purchase purchase : purchasesList) { - if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { - consumePurchase(purchase); + QueryPurchasesParams params = + QueryPurchasesParams.newBuilder() + .setProductType(BillingClient.ProductType.INAPP) + .build(); + + billingClient.queryPurchasesAsync( + params, + (billingResult, purchasesList) -> { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + for (Purchase purchase : purchasesList) { + if (purchase.getPurchaseState() == Purchase.PurchaseState.PURCHASED) { + consumePurchase(purchase); + } + } } - } - } - }); + }); } private void handlePurchase(Purchase purchase) { @@ -317,7 +366,7 @@ private void handlePurchase(Purchase purchase) { if (!products.isEmpty()) { String productId = products.get(0); listener.onPurchaseSuccessful(productId); - + // Consume the purchase immediately to allow repeated purchases consumePurchase(purchase); } @@ -329,22 +378,25 @@ private void consumePurchase(Purchase purchase) { } private void consumePurchase(Purchase purchase, Runnable onComplete) { - ConsumeParams consumeParams = ConsumeParams.newBuilder() - .setPurchaseToken(purchase.getPurchaseToken()) - .build(); - - billingClient.consumeAsync(consumeParams, (billingResult, purchaseToken) -> { - if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { - Log.d(TAG, "Purchase consumed successfully, can be purchased again"); - } else { - Log.e(TAG, "Failed to consume purchase: " + billingResult.getDebugMessage()); - } - - // Викликаємо callback незалежно від результату - if (onComplete != null) { - onComplete.run(); - } - }); + ConsumeParams consumeParams = + ConsumeParams.newBuilder().setPurchaseToken(purchase.getPurchaseToken()).build(); + + billingClient.consumeAsync( + consumeParams, + (billingResult, purchaseToken) -> { + if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { + Log.d(TAG, "Purchase consumed successfully, can be purchased again"); + } else { + Log.e( + TAG, + "Failed to consume purchase: " + billingResult.getDebugMessage()); + } + + // Викликаємо callback незалежно від результату + if (onComplete != null) { + onComplete.run(); + } + }); } public void destroy() { @@ -379,14 +431,14 @@ public static String getBillingErrorMessage(int responseCode) { } /** - * Extracts the numerical value of the price from the price string. - * For example: “₴40.00” -> 40.0, “200,00 UAH” -> 200.0 + * Extracts the numerical value of the price from the price string. For example: “₴40.00” -> + * 40.0, “200,00 UAH” -> 200.0 */ private double extractPrice(String priceString) { if (priceString == null || priceString.isEmpty()) { return 0.0; } - + try { String cleanPrice = priceString.replaceAll("[^\\d.,]", ""); @@ -394,8 +446,9 @@ private double extractPrice(String priceString) { int lastDotIndex = cleanPrice.lastIndexOf("."); if (lastDotIndex != -1 && lastDotIndex != cleanPrice.indexOf(".")) { - cleanPrice = cleanPrice.substring(0, lastDotIndex).replace(".", "") + - cleanPrice.substring(lastDotIndex); + cleanPrice = + cleanPrice.substring(0, lastDotIndex).replace(".", "") + + cleanPrice.substring(lastDotIndex); } return Double.parseDouble(cleanPrice); @@ -411,11 +464,13 @@ public void queryPurchases() { return; } - QueryPurchasesParams params = QueryPurchasesParams.newBuilder() - .setProductType(BillingClient.ProductType.INAPP) - .build(); + QueryPurchasesParams params = + QueryPurchasesParams.newBuilder() + .setProductType(BillingClient.ProductType.INAPP) + .build(); - billingClient.queryPurchasesAsync(params, + billingClient.queryPurchasesAsync( + params, (billingResult, purchasesList) -> { if (billingResult.getResponseCode() == BillingClient.BillingResponseCode.OK) { listener.onPurchasesLoaded(purchasesList); @@ -425,5 +480,4 @@ public void queryPurchases() { } }); } - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/managers/SystemTagsManager.java b/app/src/main/java/com/pasich/mynotes/utils/managers/SystemTagsManager.java index 58da57c..1767030 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/managers/SystemTagsManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/managers/SystemTagsManager.java @@ -1,25 +1,18 @@ package com.pasich.mynotes.utils.managers; import com.pasich.mynotes.data.model.Tag; - import java.util.ArrayList; import java.util.List; -/** - * Manager for system-level tags. - * Contains helper methods to identify and create system tags. - */ +/** Manager for system-level tags. Contains helper methods to identify and create system tags. */ public class SystemTagsManager { // System tag types - public static final int SYSTEM_ACTION_ADD_TAG = 1; // "Add tag" button - public static final int SYSTEM_ACTION_ALL_NOTES = 2; // "All notes" - public static final int SYSTEM_ACTION_USER_TAG = 0; // Regular user tag - + public static final int SYSTEM_ACTION_ADD_TAG = 1; // "Add tag" button + public static final int SYSTEM_ACTION_ALL_NOTES = 2; // "All notes" + public static final int SYSTEM_ACTION_USER_TAG = 0; // Regular user tag - /** - * Returns the built-in system tags. - */ + /** Returns the built-in system tags. */ public static List getSystemTags() { List systemTags = new ArrayList<>(); @@ -32,39 +25,30 @@ public static List getSystemTags() { return systemTags; } - /** - * Checks if a tag is system-defined. - */ + /** Checks if a tag is system-defined. */ public static boolean isSystemTag(Tag tag) { - return tag.getSystemAction() == SYSTEM_ACTION_ADD_TAG || tag.getSystemAction() == SYSTEM_ACTION_ALL_NOTES; + return tag.getSystemAction() == SYSTEM_ACTION_ADD_TAG + || tag.getSystemAction() == SYSTEM_ACTION_ALL_NOTES; } - /** - * Checks if the tag represents the "Add tag" action. - */ + /** Checks if the tag represents the "Add tag" action. */ public static boolean isAddTag(Tag tag) { return tag.getSystemAction() == SYSTEM_ACTION_ADD_TAG; } - /** - * Checks if the tag represents "All notes". - */ + /** Checks if the tag represents "All notes". */ public static boolean isAllNotesTag(Tag tag) { return tag.getSystemAction() == SYSTEM_ACTION_ALL_NOTES; } - /** - * Creates the "Add tag" system entry. - */ + /** Creates the "Add tag" system entry. */ public static Tag createAddTag() { Tag tag = new Tag(); tag.setSystemAction(SYSTEM_ACTION_ADD_TAG); return tag; } - /** - * Creates the "All notes" system entry - */ + /** Creates the "All notes" system entry */ public static Tag createAllNotesTag() { Tag tag = new Tag(); tag.setNameTag("allNotes"); diff --git a/app/src/main/java/com/pasich/mynotes/utils/navigation/ActivityResultKeys.java b/app/src/main/java/com/pasich/mynotes/utils/navigation/ActivityResultKeys.java index 02763c1..3267c3a 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/navigation/ActivityResultKeys.java +++ b/app/src/main/java/com/pasich/mynotes/utils/navigation/ActivityResultKeys.java @@ -5,5 +5,4 @@ public class ActivityResultKeys { public static final int RESULT_CODE_THEME_UPDATE = 11; public static final String EXTRA_UPDATE_THEME_STYLE = "updateThemeStyle"; - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/navigation/GoogleTranslateHelper.java b/app/src/main/java/com/pasich/mynotes/utils/navigation/GoogleTranslateHelper.java index a4eb1a8..5d2e6d7 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/navigation/GoogleTranslateHelper.java +++ b/app/src/main/java/com/pasich/mynotes/utils/navigation/GoogleTranslateHelper.java @@ -4,7 +4,6 @@ import android.content.ActivityNotFoundException; import android.content.Intent; import android.widget.Toast; - import com.pasich.mynotes.R; public class GoogleTranslateHelper { @@ -28,7 +27,6 @@ public static void startTranslation(Activity activity, String text) { } catch (ActivityNotFoundException e) { // If Google Translate not installed Toast.makeText(activity, R.string.notInstaledAppTranslate, Toast.LENGTH_SHORT).show(); - } } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteExtras.java b/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteExtras.java index e6dd4e7..6323b11 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteExtras.java +++ b/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteExtras.java @@ -1,4 +1,5 @@ package com.pasich.mynotes.utils.navigation; + public final class NoteExtras { private NoteExtras() {} // no instances diff --git a/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteNavigator.java b/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteNavigator.java index 3991bba..026e858 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteNavigator.java +++ b/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteNavigator.java @@ -1,13 +1,10 @@ package com.pasich.mynotes.utils.navigation; - import android.app.Activity; import android.content.Intent; import android.view.View; - import androidx.annotation.NonNull; import androidx.core.app.ActivityOptionsCompat; - import com.pasich.mynotes.cache.ThemePreferencesCache; import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.ui.view.activity.noteEditor.NoteActivity; @@ -20,16 +17,8 @@ public void openNote( boolean isNew, String tag, View transitionView, - String transitionName - ) { - openNote( - note.id, - isNew, - tag, - transitionView, - transitionName, - note.isAttachments() - ); + String transitionName) { + openNote(note.id, isNew, tag, transitionView, transitionName, note.isAttachments()); } public void openNote( @@ -38,15 +27,14 @@ public void openNote( String tag, View transitionView, String transitionName, - boolean isAttachesNote - ) { + boolean isAttachesNote) { - Intent intent = new Intent( - activity, - prefs.isExtendedEditorEnabled() || isAttachesNote - ? NoteExtendedEditorActivity.class - : NoteActivity.class - ); + Intent intent = + new Intent( + activity, + prefs.isExtendedEditorEnabled() || isAttachesNote + ? NoteExtendedEditorActivity.class + : NoteActivity.class); intent.putExtra(NoteExtras.EXTRA_NEW_NOTE, isNew); intent.putExtra(NoteExtras.EXTRA_ID_NOTE, noteId); @@ -55,15 +43,10 @@ public void openNote( if (transitionView != null) { ActivityOptionsCompat options = ActivityOptionsCompat.makeSceneTransitionAnimation( - activity, - transitionView, - transitionName - ); + activity, transitionView, transitionName); activity.startActivity(intent, options.toBundle()); } else { activity.startActivity(intent); } - } - } diff --git a/app/src/main/java/com/pasich/mynotes/utils/notification/NotificationChannelManager.java b/app/src/main/java/com/pasich/mynotes/utils/notification/NotificationChannelManager.java index 114f8dc..53bb212 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/notification/NotificationChannelManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/notification/NotificationChannelManager.java @@ -7,7 +7,6 @@ import android.net.Uri; import android.os.Build; import android.provider.Settings; - import com.pasich.mynotes.R; import com.pasich.mynotes.cache.NotificationPreferencesCache; @@ -16,12 +15,13 @@ public class NotificationChannelManager { private static final String LEGACY_CHANNEL_ID = "reminders"; /** - * Idempotent: creates the current versioned channel if it does not exist. - * On first call after update from old version, deletes the legacy "reminders" channel. + * Idempotent: creates the current versioned channel if it does not exist. On first call after + * update from old version, deletes the legacy "reminders" channel. */ public static void ensureChannel(Context ctx, NotificationPreferencesCache prefs) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; - NotificationManager nm = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); + NotificationManager nm = + (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); if (nm == null) return; String channelId = prefs.getChannelId(); @@ -36,15 +36,17 @@ public static void ensureChannel(Context ctx, NotificationPreferencesCache prefs } /** - * Deletes the current versioned channel, bumps the version, creates the new channel - * with newSoundUri. Pass null to reset to the system default notification sound. + * Deletes the current versioned channel, bumps the version, creates the new channel with + * newSoundUri. Pass null to reset to the system default notification sound. */ - public static void changeSound(Context ctx, NotificationPreferencesCache prefs, Uri newSoundUri) { + public static void changeSound( + Context ctx, NotificationPreferencesCache prefs, Uri newSoundUri) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) { prefs.setSoundUri(newSoundUri != null ? newSoundUri.toString() : null); return; } - NotificationManager nm = (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); + NotificationManager nm = + (NotificationManager) ctx.getSystemService(Context.NOTIFICATION_SERVICE); if (nm == null) return; nm.deleteNotificationChannel(prefs.getChannelId()); @@ -53,15 +55,15 @@ public static void changeSound(Context ctx, NotificationPreferencesCache prefs, createChannel(ctx, nm, prefs); } - private static void createChannel(Context ctx, NotificationManager nm, - NotificationPreferencesCache prefs) { + private static void createChannel( + Context ctx, NotificationManager nm, NotificationPreferencesCache prefs) { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return; - NotificationChannel channel = new NotificationChannel( - prefs.getChannelId(), - ctx.getString(R.string.reminder_notification_channel), - NotificationManager.IMPORTANCE_HIGH - ); + NotificationChannel channel = + new NotificationChannel( + prefs.getChannelId(), + ctx.getString(R.string.reminder_notification_channel), + NotificationManager.IMPORTANCE_HIGH); String savedUri = prefs.getSoundUri(); Uri soundUri; @@ -71,10 +73,11 @@ private static void createChannel(Context ctx, NotificationManager nm, soundUri = Uri.parse(savedUri); } - AudioAttributes attrs = new AudioAttributes.Builder() - .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) - .setUsage(AudioAttributes.USAGE_NOTIFICATION) - .build(); + AudioAttributes attrs = + new AudioAttributes.Builder() + .setContentType(AudioAttributes.CONTENT_TYPE_SONIFICATION) + .setUsage(AudioAttributes.USAGE_NOTIFICATION) + .build(); channel.setSound(soundUri, attrs); nm.createNotificationChannel(channel); } diff --git a/app/src/main/java/com/pasich/mynotes/utils/recycler/SpacesItemDecoration.java b/app/src/main/java/com/pasich/mynotes/utils/recycler/SpacesItemDecoration.java index 9785566..4c77e77 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/recycler/SpacesItemDecoration.java +++ b/app/src/main/java/com/pasich/mynotes/utils/recycler/SpacesItemDecoration.java @@ -2,13 +2,10 @@ import android.graphics.Rect; import android.view.View; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; - -import javax.inject.Inject; - import dagger.hilt.android.scopes.ActivityScoped; +import javax.inject.Inject; @ActivityScoped public class SpacesItemDecoration extends RecyclerView.ItemDecoration { diff --git a/app/src/main/java/com/pasich/mynotes/utils/recycler/SwipeToListNotesCallback.java b/app/src/main/java/com/pasich/mynotes/utils/recycler/SwipeToListNotesCallback.java index d179701..f87cbe5 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/recycler/SwipeToListNotesCallback.java +++ b/app/src/main/java/com/pasich/mynotes/utils/recycler/SwipeToListNotesCallback.java @@ -1,32 +1,37 @@ package com.pasich.mynotes.utils.recycler; import android.graphics.Canvas; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; - -abstract public class SwipeToListNotesCallback extends ItemTouchHelper.SimpleCallback { - +public abstract class SwipeToListNotesCallback extends ItemTouchHelper.SimpleCallback { public SwipeToListNotesCallback(int dragDirs, int swipeDirs) { super(dragDirs, swipeDirs); } - @Override - public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { + public boolean onMove( + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { return false; } @Override - public void onChildDraw(@NonNull Canvas c, @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive) { + public void onChildDraw( + @NonNull Canvas c, + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + float dX, + float dY, + int actionState, + boolean isCurrentlyActive) { final float alpha = 1.0f - Math.abs(dX) / (float) viewHolder.itemView.getWidth(); viewHolder.itemView.setAlpha(alpha); - super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/recycler/TagDragCallback.java b/app/src/main/java/com/pasich/mynotes/utils/recycler/TagDragCallback.java index e67ed64..ea2ee67 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/recycler/TagDragCallback.java +++ b/app/src/main/java/com/pasich/mynotes/utils/recycler/TagDragCallback.java @@ -3,7 +3,6 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.RecyclerView; - import com.pasich.mynotes.utils.adapters.TagsManagementAdapter; public class TagDragCallback extends ItemTouchHelper.Callback { @@ -16,7 +15,8 @@ public TagDragCallback(TagsManagementAdapter adapter) { } @Override - public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + public int getMovementFlags( + @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { int position = viewHolder.getAbsoluteAdapterPosition(); // Disable drag for first element (Add button) @@ -31,7 +31,10 @@ public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull Recycle } @Override - public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) { + public boolean onMove( + @NonNull RecyclerView recyclerView, + @NonNull RecyclerView.ViewHolder viewHolder, + @NonNull RecyclerView.ViewHolder target) { int fromPosition = viewHolder.getAbsoluteAdapterPosition(); int toPosition = target.getAbsoluteAdapterPosition(); @@ -46,9 +49,9 @@ public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView. return true; } - @Override - public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { + public void clearView( + @NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) { super.clearView(recyclerView, viewHolder); // If we were dragging, save changes to database if (isDragging) { @@ -72,4 +75,4 @@ public boolean isLongPressDragEnabled() { public boolean isItemViewSwipeEnabled() { return false; } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/DiffUtilTag.java b/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/DiffUtilTag.java index 1e42fa1..094516d 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/DiffUtilTag.java +++ b/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/DiffUtilTag.java @@ -1,26 +1,19 @@ package com.pasich.mynotes.utils.recycler.diffutil; - import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; - import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.utils.recycler.payloads.TagPayloads; - - +import dagger.hilt.android.scopes.ActivityScoped; import java.util.ArrayList; import java.util.List; - import javax.inject.Inject; -import dagger.hilt.android.scopes.ActivityScoped; - @ActivityScoped public class DiffUtilTag extends DiffUtil.ItemCallback { @Inject - public DiffUtilTag() { - } + public DiffUtilTag() {} @Override public boolean areItemsTheSame(@NonNull Tag oldItem, @NonNull Tag newItem) { diff --git a/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/NoteDiff.java b/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/NoteDiff.java index 225758b..fb2a43d 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/NoteDiff.java +++ b/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/NoteDiff.java @@ -2,12 +2,9 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.DiffUtil; - import com.pasich.mynotes.data.model.Note; - -import java.util.Objects; - import dagger.hilt.android.scopes.ActivityScoped; +import java.util.Objects; @ActivityScoped public class NoteDiff extends DiffUtil.ItemCallback { @@ -15,21 +12,21 @@ public class NoteDiff extends DiffUtil.ItemCallback { @Override public boolean areItemsTheSame(@NonNull Note oldItem, @NonNull Note newItem) { if (oldItem.getId() == 0 || newItem.getId() == 0) { - return oldItem.getDate() == newItem.getDate() && - oldItem.getTitle().equals(newItem.getTitle()) && - oldItem.getValue().equals(newItem.getValue()); + return oldItem.getDate() == newItem.getDate() + && oldItem.getTitle().equals(newItem.getTitle()) + && oldItem.getValue().equals(newItem.getValue()); } return oldItem.getId() == newItem.getId(); } @Override public boolean areContentsTheSame(@NonNull Note oldItem, @NonNull Note newItem) { - return oldItem.getId() == newItem.getId() && - oldItem.getTitle().equals(newItem.getTitle()) && - oldItem.getValue().equals(newItem.getValue()) && - oldItem.getTag().equals(newItem.getTag()) && - oldItem.getDate() == newItem.getDate() && - oldItem.isPinned() == newItem.isPinned() && - Objects.equals(oldItem.getReminderTime(), newItem.getReminderTime()); + return oldItem.getId() == newItem.getId() + && oldItem.getTitle().equals(newItem.getTitle()) + && oldItem.getValue().equals(newItem.getValue()) + && oldItem.getTag().equals(newItem.getTag()) + && oldItem.getDate() == newItem.getDate() + && oldItem.isPinned() == newItem.isPinned() + && Objects.equals(oldItem.getReminderTime(), newItem.getReminderTime()); } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java b/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java index e3b023d..45697ba 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java @@ -5,10 +5,8 @@ import android.content.Context; import android.content.Intent; import android.os.Build; - import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.ui.receiver.ReminderReceiver; - import java.util.List; public class ReminderManager { @@ -30,20 +28,19 @@ public static void scheduleReminder(Context ctx, Note note) { } alarmManager.setExactAndAllowWhileIdle( - AlarmManager.RTC_WAKEUP, - note.getReminderTime(), - buildPendingIntent(ctx, note) - ); + AlarmManager.RTC_WAKEUP, note.getReminderTime(), buildPendingIntent(ctx, note)); } public static void cancelReminder(Context ctx, int noteId) { AlarmManager alarmManager = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); if (alarmManager == null) return; Intent intent = new Intent(ctx, ReminderReceiver.class); - PendingIntent pi = PendingIntent.getBroadcast( - ctx, noteId, intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); + PendingIntent pi = + PendingIntent.getBroadcast( + ctx, + noteId, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); alarmManager.cancel(pi); pi.cancel(); } @@ -70,8 +67,9 @@ private static PendingIntent buildPendingIntent(Context ctx, Note note) { intent.putExtra(EXTRA_NOTE_REPEAT, note.getReminderRepeat()); intent.putExtra(EXTRA_NOTE_INTERVAL_MINUTES, note.getReminderIntervalMinutes()); return PendingIntent.getBroadcast( - ctx, note.getId(), intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE - ); + ctx, + note.getId(), + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java b/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java index a2e65f4..691a23e 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java @@ -5,10 +5,8 @@ import android.content.Context; import android.content.Intent; import android.os.Build; - import com.pasich.mynotes.data.model.Task; import com.pasich.mynotes.ui.receiver.TaskReminderReceiver; - import java.util.List; public class TaskReminderManager { @@ -25,16 +23,20 @@ public static void scheduleReminder(Context ctx, Task task) { AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); if (am == null) return; if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && !am.canScheduleExactAlarms()) return; - am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, task.getReminderTime(), - buildPendingIntent(ctx, task)); + am.setExactAndAllowWhileIdle( + AlarmManager.RTC_WAKEUP, task.getReminderTime(), buildPendingIntent(ctx, task)); } public static void cancelReminder(Context ctx, int taskId) { AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); if (am == null) return; Intent intent = new Intent(ctx, TaskReminderReceiver.class); - PendingIntent pi = PendingIntent.getBroadcast(ctx, taskId + REQUEST_CODE_OFFSET, intent, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + PendingIntent pi = + PendingIntent.getBroadcast( + ctx, + taskId + REQUEST_CODE_OFFSET, + intent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); am.cancel(pi); pi.cancel(); } @@ -48,7 +50,10 @@ private static PendingIntent buildPendingIntent(Context ctx, Task task) { intent.putExtra(EXTRA_TASK_ID, task.getId()); intent.putExtra(EXTRA_TASK_TITLE, task.getTitle()); intent.putExtra(EXTRA_TASK_INTERVAL_MINUTES, task.getReminderIntervalMinutes()); - return PendingIntent.getBroadcast(ctx, task.getId() + REQUEST_CODE_OFFSET, intent, + return PendingIntent.getBroadcast( + ctx, + task.getId() + REQUEST_CODE_OFFSET, + intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/shareProcessors/ShareProcessor.java b/app/src/main/java/com/pasich/mynotes/utils/shareProcessors/ShareProcessor.java index 6d26a68..b6c9b1b 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/shareProcessors/ShareProcessor.java +++ b/app/src/main/java/com/pasich/mynotes/utils/shareProcessors/ShareProcessor.java @@ -1,10 +1,8 @@ package com.pasich.mynotes.utils.shareProcessors; - import android.content.ContentResolver; import android.content.Intent; import android.net.Uri; - import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -28,24 +26,27 @@ public static String extractSharedText(Intent intent) { } public static void readFileAsync(ContentResolver resolver, Uri uri, FileReadCallback callback) { - new Thread(() -> { - try { - StringBuilder sb = new StringBuilder(); - try (BufferedReader reader = new BufferedReader( - new InputStreamReader(resolver.openInputStream(uri))) - ) { - String line; - while ((line = reader.readLine()) != null) { - sb.append(line).append("\n"); - - if (sb.length() > MAX_SHARE_SIZE) break; - } - } - callback.onSuccess(sb.toString()); - } catch (IOException e) { - callback.onError(e); - } - }).start(); + new Thread( + () -> { + try { + StringBuilder sb = new StringBuilder(); + try (BufferedReader reader = + new BufferedReader( + new InputStreamReader( + resolver.openInputStream(uri)))) { + String line; + while ((line = reader.readLine()) != null) { + sb.append(line).append("\n"); + + if (sb.length() > MAX_SHARE_SIZE) break; + } + } + callback.onSuccess(sb.toString()); + } catch (IOException e) { + callback.onError(e); + } + }) + .start(); } public static boolean isTooLarge(String text) { diff --git a/app/src/main/java/com/pasich/mynotes/utils/shareProcessors/SharedNoteCreator.java b/app/src/main/java/com/pasich/mynotes/utils/shareProcessors/SharedNoteCreator.java index 3d5730d..eea7473 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/shareProcessors/SharedNoteCreator.java +++ b/app/src/main/java/com/pasich/mynotes/utils/shareProcessors/SharedNoteCreator.java @@ -1,14 +1,11 @@ package com.pasich.mynotes.utils.shareProcessors; - import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Note; - -import javax.inject.Inject; - import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.disposables.CompositeDisposable; import io.reactivex.schedulers.Schedulers; +import javax.inject.Inject; public class SharedNoteCreator { @@ -21,14 +18,18 @@ public SharedNoteCreator(DataManager dataManager) { } public void create(String text, Callback callback) { - disposables.add(dataManager.addNote(new Note().create("", text, System.currentTimeMillis(), ""), false).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread()).subscribe(callback::onCreated, callback::onError)); + disposables.add( + dataManager + .addNote(new Note().create("", text, System.currentTimeMillis(), ""), false) + .subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(callback::onCreated, callback::onError)); } public void clear() { disposables.clear(); } - public interface Callback { void onCreated(long id); diff --git a/app/src/main/java/com/pasich/mynotes/utils/themes/ThemesArray.java b/app/src/main/java/com/pasich/mynotes/utils/themes/ThemesArray.java index 67e1e41..dff6eed 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/themes/ThemesArray.java +++ b/app/src/main/java/com/pasich/mynotes/utils/themes/ThemesArray.java @@ -3,7 +3,6 @@ import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Theme; import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; - import java.util.ArrayList; public class ThemesArray { diff --git a/app/src/main/java/com/pasich/mynotes/utils/tool/FormatListTool.java b/app/src/main/java/com/pasich/mynotes/utils/tool/FormatListTool.java index 798bbbe..352060e 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/tool/FormatListTool.java +++ b/app/src/main/java/com/pasich/mynotes/utils/tool/FormatListTool.java @@ -1,10 +1,8 @@ package com.pasich.mynotes.utils.tool; import android.view.MenuItem; - import com.pasich.mynotes.R; import com.pasich.mynotes.cache.AppPreferencesCache; - import javax.inject.Inject; public class FormatListTool { @@ -24,9 +22,7 @@ private int getParamFormatValue() { return cache.getFormatPref(); } - /** - * The switch itself, which switches the operating mode depending on the selected parameter - */ + /** The switch itself, which switches the operating mode depending on the selected parameter */ public void formatNote(MenuItem menuItem) { switch (getParamFormatValue()) { case 1 -> { @@ -51,6 +47,5 @@ private int getParamIco(int param) { return R.drawable.ic_edit_format_list; } return R.drawable.ic_edit_format_tiles; - } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/tool/TextStyleTool.java b/app/src/main/java/com/pasich/mynotes/utils/tool/TextStyleTool.java index 274716d..1f4752d 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/tool/TextStyleTool.java +++ b/app/src/main/java/com/pasich/mynotes/utils/tool/TextStyleTool.java @@ -3,13 +3,10 @@ import static com.pasich.mynotes.utils.constants.settings.PreferencesConfig.ARGUMENT_DEFAULT_TEXT_STYLE; import android.widget.ImageButton; - import com.pasich.mynotes.R; import com.pasich.mynotes.cache.ThemePreferencesCache; - -import javax.inject.Inject; - import dagger.hilt.android.scopes.FragmentScoped; +import javax.inject.Inject; @FragmentScoped public class TextStyleTool { @@ -33,22 +30,21 @@ private String getArgPreference() { return cache.getTypeFaceNoteActivity(); } - public void changeArgument() { if (mButton != null) { switch (getArgPreference()) { case ARGUMENT_DEFAULT_TEXT_STYLE -> { - //selected italic + // selected italic mButton.setImageResource(getLoadSrcDrawable("italic")); cache.setTypeFaceNoteActivity("italic"); } case "italic" -> { - //selected bold + // selected bold mButton.setImageResource(getLoadSrcDrawable("bold")); cache.setTypeFaceNoteActivity("bold"); } case "bold" -> { - //selected normal + // selected normal mButton.setImageResource(getLoadSrcDrawable("normal")); cache.setTypeFaceNoteActivity("normal"); } @@ -56,7 +52,6 @@ public void changeArgument() { } } - private int getLoadSrcDrawable(String param) { int NORMAL_ICON = R.drawable.ic_style_normal; int ITALIC_ICON = R.drawable.ic_style_italic; @@ -66,6 +61,5 @@ private int getLoadSrcDrawable(String param) { case "bold" -> BOLD_ICON; default -> NORMAL_ICON; }; - } } diff --git a/app/src/main/java/com/pasich/mynotes/utils/transition/CopyNoteAnimationUtil.java b/app/src/main/java/com/pasich/mynotes/utils/transition/CopyNoteAnimationUtil.java index 3a59156..34a4481 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/transition/CopyNoteAnimationUtil.java +++ b/app/src/main/java/com/pasich/mynotes/utils/transition/CopyNoteAnimationUtil.java @@ -1,6 +1,5 @@ package com.pasich.mynotes.utils.transition; - import android.animation.ValueAnimator; import android.content.Context; import android.graphics.drawable.Drawable; @@ -9,12 +8,9 @@ import android.view.Gravity; import android.view.View; import android.view.animation.DecelerateInterpolator; - import androidx.core.content.res.ResourcesCompat; - import com.pasich.mynotes.R; - public class CopyNoteAnimationUtil { public static void runCopyAnimation(View root) { @@ -22,11 +18,11 @@ public static void runCopyAnimation(View root) { Context context = root.getContext(); - Drawable pulse = ResourcesCompat.getDrawable( - context.getResources(), - R.drawable.note_copy_pulse_base, - context.getTheme() - ); + Drawable pulse = + ResourcesCompat.getDrawable( + context.getResources(), + R.drawable.note_copy_pulse_base, + context.getTheme()); if (pulse == null) return; @@ -37,11 +33,12 @@ public static void runCopyAnimation(View root) { ValueAnimator alphaAnimator = ValueAnimator.ofInt(0, 180, 0); alphaAnimator.setDuration(750); alphaAnimator.setInterpolator(new DecelerateInterpolator()); - alphaAnimator.addUpdateListener(a -> { - int alpha = (int) a.getAnimatedValue(); - pulse.setAlpha(alpha); - root.invalidate(); - }); + alphaAnimator.addUpdateListener( + a -> { + int alpha = (int) a.getAnimatedValue(); + pulse.setAlpha(alpha); + root.invalidate(); + }); alphaAnimator.start(); // Bounce @@ -49,16 +46,9 @@ public static void runCopyAnimation(View root) { .scaleX(0.95f) .scaleY(0.95f) .setDuration(120) - .withEndAction(() -> - root.animate() - .scaleX(1f) - .scaleY(1f) - .setDuration(150) - .start() - ) + .withEndAction(() -> root.animate().scaleX(1f).scaleY(1f).setDuration(150).start()) .start(); new Handler(Looper.getMainLooper()).postDelayed(() -> root.setForeground(null), 820); } - -} \ No newline at end of file +} diff --git a/app/src/main/java/com/pasich/mynotes/utils/transition/TransitionUtil.java b/app/src/main/java/com/pasich/mynotes/utils/transition/TransitionUtil.java index 3f97a17..ccc3585 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/transition/TransitionUtil.java +++ b/app/src/main/java/com/pasich/mynotes/utils/transition/TransitionUtil.java @@ -2,9 +2,7 @@ import android.transition.Transition; import android.view.View; - import androidx.interpolator.view.animation.FastOutSlowInInterpolator; - import com.google.android.material.color.MaterialColors; import com.google.android.material.transition.platform.MaterialArcMotion; import com.google.android.material.transition.platform.MaterialContainerTransform; @@ -13,14 +11,18 @@ public class TransitionUtil { /** * Transition to activity Notes + * * @param container - coordinationLayout */ public static Transition buildContainerTransform(View container) { MaterialContainerTransform materialContainerTransform = new MaterialContainerTransform(); - materialContainerTransform.addTarget(container) + materialContainerTransform + .addTarget(container) .setDuration(300) .setInterpolator(new FastOutSlowInInterpolator()); - materialContainerTransform.setAllContainerColors(MaterialColors.getColor(container, com.google.android.material.R.attr.colorSurface)); + materialContainerTransform.setAllContainerColors( + MaterialColors.getColor( + container, com.google.android.material.R.attr.colorSurface)); materialContainerTransform.setPathMotion(new MaterialArcMotion()); materialContainerTransform.setFadeMode(MaterialContainerTransform.FADE_MODE_CROSS); return materialContainerTransform; diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 4acae40..7e3275f 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -222,6 +222,28 @@ Автозбереження Нотатки автоматично зберігаються через 2 секунди після завершення вводу. У верхній панелі відображається індикатор: синя крапка — незбережені зміни, анімована іконка — процес збереження, зелена галочка — успішно збережено. Навіть якщо ви закриєте додаток або він аварійно завершиться, останні зміни не буде втрачено. + + Завдання + Створення завдань + Відкрийте Завдання з головного меню. Натисніть «+», щоб додати нове завдання. Можна вказати назву, опис і призначити категорію. + Категорії + Організовуйте завдання за власними категоріями. Натисніть «Додати категорію», щоб створити нову. Завдання категорії групуються разом для зручного керування. + Виконання завдань + Натисніть на прапорець біля завдання, щоб позначити його як виконане. Виконані завдання групуються внизу списку. Натисніть ще раз, щоб скасувати позначення. + Нагадування для завдань + Встановіть нагадування для будь-якого завдання через іконку дзвіночка. Ви отримаєте сповіщення у вказаний час. Натисніть на сповіщення, щоб відкрити завдання. + + + Нагадування + Нагадування для нотаток + Відкрийте будь-яку нотатку та натисніть іконку дзвіночка, щоб встановити нагадування. Виберіть час за допомогою швидких варіантів або вручну. Сповіщення з\'явиться у вказаний час. + Параметри повторення + При встановленні нагадування для нотатки увімкніть перемикач «Повторення», щоб вибрати цикл: щодня, щотижня або щомісяця. Нагадування автоматично переноситься після кожного спрацювання. + Інтервальні сповіщення + Для повторюваних нагадувань можна увімкнути інтервальне повторення кожні 5, 10, 15, 30 або 60 хвилин. Сповіщення повторюватиметься через вибраний інтервал до наступного запланованого часу. + Звук і гучність сповіщень + Налаштуйте мелодію та гучність нагадувань у Налаштуваннях → Медіа. Зміни застосовуються до всіх майбутніх сповіщень. + Теги Натисніть цю кнопку, щоб створити тег. Налаштування тегу diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 99b24f9..7f69326 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -223,6 +223,28 @@ Auto-save Notes are automatically saved 2 seconds after you stop typing. A status indicator in the top bar shows: blue dot for unsaved changes, animated icon for saving in progress, green checkmark for successfully saved. Even if you close the app or it crashes, your latest changes won\'t be lost. + + Tasks + Creating Tasks + Open Tasks from the main menu. Tap the "+" button to add a new task. You can set a title, description, and assign it to a category. + Categories + Organize tasks into custom categories. Tap "Add category" to create one. Tasks within a category are grouped together for easy management. + Completing Tasks + Tap the checkbox next to a task to mark it as done. Completed tasks are grouped at the bottom of the list. Tap again to unmark. + Task Reminders + Set a reminder for any task by tapping the bell icon. You will receive a notification at the scheduled time. Tap the notification to open the task directly. + + + Reminders + Note Reminders + Open any note and tap the bell icon to set a reminder. Pick a time using quick options or the date and time picker. A notification will appear at the scheduled time. + Repeat Options + When setting a note reminder, enable the Repeat toggle to choose a repeat cycle: Daily, Weekly, or Monthly. The reminder reschedules automatically after each notification. + Interval Notifications + For repeating reminders, you can enable interval re-notifications every 5, 10, 15, 30, or 60 minutes. The notification will repeat at the chosen interval until the next scheduled occurrence. + Notification Sound and Volume + Customize the reminder melody and volume in Settings → Media. Changes apply to all future reminder notifications. + Up to date for version %1$s diff --git a/app/src/test/java/com/pasich/mynotes/base/BasePresenterTest.java b/app/src/test/java/com/pasich/mynotes/base/BasePresenterTest.java index 6ba5855..f39309a 100644 --- a/app/src/test/java/com/pasich/mynotes/base/BasePresenterTest.java +++ b/app/src/test/java/com/pasich/mynotes/base/BasePresenterTest.java @@ -1,23 +1,31 @@ package com.pasich.mynotes.base; -import org.junit.Rule; -import org.mockito.MockitoAnnotations; - import com.pasich.mynotes.utils.rx.SchedulerProvider; - import io.reactivex.Scheduler; import io.reactivex.schedulers.Schedulers; +import org.junit.Rule; +import org.mockito.MockitoAnnotations; public abstract class BasePresenterTest { - @Rule - public RxImmediateSchedulerRule schedulers = new RxImmediateSchedulerRule(); + @Rule public RxImmediateSchedulerRule schedulers = new RxImmediateSchedulerRule(); protected SchedulerProvider testSchedulerProvider() { return new SchedulerProvider() { - @Override public Scheduler ui() { return Schedulers.trampoline(); } - @Override public Scheduler computation() { return Schedulers.trampoline(); } - @Override public Scheduler io() { return Schedulers.trampoline(); } + @Override + public Scheduler ui() { + return Schedulers.trampoline(); + } + + @Override + public Scheduler computation() { + return Schedulers.trampoline(); + } + + @Override + public Scheduler io() { + return Schedulers.trampoline(); + } }; } diff --git a/app/src/test/java/com/pasich/mynotes/base/RxImmediateSchedulerRule.java b/app/src/test/java/com/pasich/mynotes/base/RxImmediateSchedulerRule.java index f5da088..1681b34 100644 --- a/app/src/test/java/com/pasich/mynotes/base/RxImmediateSchedulerRule.java +++ b/app/src/test/java/com/pasich/mynotes/base/RxImmediateSchedulerRule.java @@ -1,12 +1,11 @@ package com.pasich.mynotes.base; -import org.junit.rules.TestRule; -import org.junit.runner.Description; -import org.junit.runners.model.Statement; - import io.reactivex.android.plugins.RxAndroidPlugins; import io.reactivex.plugins.RxJavaPlugins; import io.reactivex.schedulers.Schedulers; +import org.junit.rules.TestRule; +import org.junit.runner.Description; +import org.junit.runners.model.Statement; public class RxImmediateSchedulerRule implements TestRule { @@ -18,8 +17,10 @@ public void evaluate() throws Throwable { RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline()); RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline()); RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline()); - RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline()); - RxAndroidPlugins.setMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline()); + RxAndroidPlugins.setInitMainThreadSchedulerHandler( + scheduler -> Schedulers.trampoline()); + RxAndroidPlugins.setMainThreadSchedulerHandler( + scheduler -> Schedulers.trampoline()); try { base.evaluate(); } finally { diff --git a/app/src/test/java/com/pasich/mynotes/presenter/BackupPresenterTest.java b/app/src/test/java/com/pasich/mynotes/presenter/BackupPresenterTest.java index c08bbca..2db2b8b 100644 --- a/app/src/test/java/com/pasich/mynotes/presenter/BackupPresenterTest.java +++ b/app/src/test/java/com/pasich/mynotes/presenter/BackupPresenterTest.java @@ -6,20 +6,16 @@ import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.ui.contract.BackupContract; import com.pasich.mynotes.ui.presenter.BackupPresenter; - +import io.reactivex.disposables.CompositeDisposable; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import io.reactivex.disposables.CompositeDisposable; - public class BackupPresenterTest extends BasePresenterTest { - @Mock - DataManager mockDataManager; + @Mock DataManager mockDataManager; - @Mock - BackupContract.view mockView; + @Mock BackupContract.view mockView; private BackupPresenter presenter; @@ -27,11 +23,9 @@ public class BackupPresenterTest extends BasePresenterTest { public void setUp() { initMocks(this); - presenter = new BackupPresenter( - testSchedulerProvider(), - new CompositeDisposable(), - mockDataManager - ); + presenter = + new BackupPresenter( + testSchedulerProvider(), new CompositeDisposable(), mockDataManager); presenter.attachView(mockView); } diff --git a/app/src/test/java/com/pasich/mynotes/presenter/BasePresenterSafetyTest.java b/app/src/test/java/com/pasich/mynotes/presenter/BasePresenterSafetyTest.java index 91594e3..bfa4e5a 100644 --- a/app/src/test/java/com/pasich/mynotes/presenter/BasePresenterSafetyTest.java +++ b/app/src/test/java/com/pasich/mynotes/presenter/BasePresenterSafetyTest.java @@ -6,25 +6,25 @@ import com.pasich.mynotes.base.presenter.BasePresenter; import com.pasich.mynotes.base.view.BaseView; import com.pasich.mynotes.data.DataManager; - +import io.reactivex.disposables.CompositeDisposable; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import io.reactivex.disposables.CompositeDisposable; - public class BasePresenterSafetyTest extends BasePresenterTest { static class TestPresenter extends BasePresenter { boolean actionWasCalled = false; - TestPresenter(com.pasich.mynotes.utils.rx.SchedulerProvider sp, - CompositeDisposable cd, - DataManager dm) { + TestPresenter( + com.pasich.mynotes.utils.rx.SchedulerProvider sp, + CompositeDisposable cd, + DataManager dm) { super(sp, cd, dm); } - @Override public void viewIsReady() {} + @Override + public void viewIsReady() {} public void triggerViewAction() { runOnView(view -> actionWasCalled = true); @@ -37,7 +37,9 @@ public void triggerViewAction() { @Before public void setUp() { initMocks(this); - presenter = new TestPresenter(testSchedulerProvider(), new CompositeDisposable(), mockDataManager); + presenter = + new TestPresenter( + testSchedulerProvider(), new CompositeDisposable(), mockDataManager); } @Test diff --git a/app/src/test/java/com/pasich/mynotes/presenter/MainPresenterTest.java b/app/src/test/java/com/pasich/mynotes/presenter/MainPresenterTest.java index c54d1ba..af74a29 100644 --- a/app/src/test/java/com/pasich/mynotes/presenter/MainPresenterTest.java +++ b/app/src/test/java/com/pasich/mynotes/presenter/MainPresenterTest.java @@ -7,29 +7,21 @@ import com.pasich.mynotes.base.BasePresenterTest; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Note; -import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.ui.contract.MainContract; import com.pasich.mynotes.ui.presenter.MainPresenter; - +import io.reactivex.Flowable; +import io.reactivex.disposables.CompositeDisposable; +import java.util.Collections; +import java.util.List; import org.junit.Before; -import org.junit.Rule; import org.junit.Test; import org.mockito.Mock; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; - -import io.reactivex.Flowable; -import io.reactivex.disposables.CompositeDisposable; - public class MainPresenterTest extends BasePresenterTest { - @Mock - DataManager mockDataManager; + @Mock DataManager mockDataManager; - @Mock - MainContract.view mockView; + @Mock MainContract.view mockView; private MainPresenter presenter; @@ -46,11 +38,9 @@ public void setUp() { when(mockDataManager.getNotesCreatedLastMonth()).thenReturn(Flowable.just(0)); when(mockDataManager.getTotalCharacters()).thenReturn(Flowable.just(0L)); - presenter = new MainPresenter( - testSchedulerProvider(), - new CompositeDisposable(), - mockDataManager - ); + presenter = + new MainPresenter( + testSchedulerProvider(), new CompositeDisposable(), mockDataManager); } @Test @@ -78,11 +68,9 @@ public void attachView_withNotes_rendersState() { when(mockDataManager.getNotes()).thenReturn(Flowable.just(notes)); // Re-create presenter with the updated mock - presenter = new MainPresenter( - testSchedulerProvider(), - new CompositeDisposable(), - mockDataManager - ); + presenter = + new MainPresenter( + testSchedulerProvider(), new CompositeDisposable(), mockDataManager); presenter.attachView(mockView); presenter.viewIsReady(); diff --git a/app/src/test/java/com/pasich/mynotes/presenter/NotePresenterAutoSaveTest.java b/app/src/test/java/com/pasich/mynotes/presenter/NotePresenterAutoSaveTest.java index 69cf679..2210232 100644 --- a/app/src/test/java/com/pasich/mynotes/presenter/NotePresenterAutoSaveTest.java +++ b/app/src/test/java/com/pasich/mynotes/presenter/NotePresenterAutoSaveTest.java @@ -11,16 +11,13 @@ import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.ui.contract.NoteContract; import com.pasich.mynotes.ui.presenter.NotePresenter; - +import io.reactivex.Completable; +import io.reactivex.disposables.CompositeDisposable; +import java.util.Date; import org.junit.Before; import org.junit.Test; import org.mockito.Mock; -import java.util.Date; - -import io.reactivex.Completable; -import io.reactivex.disposables.CompositeDisposable; - public class NotePresenterAutoSaveTest extends BasePresenterTest { @Mock DataManager mockDataManager; @@ -32,7 +29,9 @@ public class NotePresenterAutoSaveTest extends BasePresenterTest { public void setUp() { initMocks(this); when(mockDataManager.updateNote(any())).thenReturn(Completable.complete()); - presenter = new NotePresenter(testSchedulerProvider(), new CompositeDisposable(), mockDataManager); + presenter = + new NotePresenter( + testSchedulerProvider(), new CompositeDisposable(), mockDataManager); presenter.attachView(mockView); testNote = new Note().create("Test Title", "Test content", new Date().getTime(), ""); diff --git a/app/src/test/java/com/pasich/mynotes/presenter/TagsPresenterTest.java b/app/src/test/java/com/pasich/mynotes/presenter/TagsPresenterTest.java index 5d0e07b..09d3e3c 100644 --- a/app/src/test/java/com/pasich/mynotes/presenter/TagsPresenterTest.java +++ b/app/src/test/java/com/pasich/mynotes/presenter/TagsPresenterTest.java @@ -9,24 +9,19 @@ import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.ui.contract.TagsContract; import com.pasich.mynotes.ui.presenter.TagsPresenter; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; - -import java.util.ArrayList; - import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.disposables.CompositeDisposable; +import java.util.ArrayList; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; public class TagsPresenterTest extends BasePresenterTest { - @Mock - DataManager mockDataManager; + @Mock DataManager mockDataManager; - @Mock - TagsContract.view mockView; + @Mock TagsContract.view mockView; private TagsPresenter presenter; @@ -36,16 +31,12 @@ public void setUp() { // Stub methods called during loadTags() — use a mutable list because // TagsPresenter calls tagList.add(0, createAddTag()) on the returned list - when(mockDataManager.getTagsUser()) - .thenReturn(Flowable.just(new ArrayList<>())); - when(mockDataManager.getSortParamTags()) - .thenReturn("TagsCreationDateSort"); - - presenter = new TagsPresenter( - testSchedulerProvider(), - new CompositeDisposable(), - mockDataManager - ); + when(mockDataManager.getTagsUser()).thenReturn(Flowable.just(new ArrayList<>())); + when(mockDataManager.getSortParamTags()).thenReturn("TagsCreationDateSort"); + + presenter = + new TagsPresenter( + testSchedulerProvider(), new CompositeDisposable(), mockDataManager); presenter.attachView(mockView); } diff --git a/app/src/test/java/com/pasich/mynotes/presenter/TrashPresenterTest.java b/app/src/test/java/com/pasich/mynotes/presenter/TrashPresenterTest.java index a097867..5054d90 100644 --- a/app/src/test/java/com/pasich/mynotes/presenter/TrashPresenterTest.java +++ b/app/src/test/java/com/pasich/mynotes/presenter/TrashPresenterTest.java @@ -9,26 +9,21 @@ import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.ui.contract.TrashContract; import com.pasich.mynotes.ui.presenter.TrashPresenter; - -import org.junit.Before; -import org.junit.Test; -import org.mockito.Mock; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - import io.reactivex.Completable; import io.reactivex.Flowable; import io.reactivex.disposables.CompositeDisposable; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; public class TrashPresenterTest extends BasePresenterTest { - @Mock - DataManager mockDataManager; + @Mock DataManager mockDataManager; - @Mock - TrashContract.view mockView; + @Mock TrashContract.view mockView; private TrashPresenter presenter; @@ -37,14 +32,11 @@ public void setUp() { initMocks(this); // Default stub for loadingTrash() called inside viewIsReady() - when(mockDataManager.getNotesInTrash()) - .thenReturn(Flowable.just(Collections.emptyList())); - - presenter = new TrashPresenter( - testSchedulerProvider(), - new CompositeDisposable(), - mockDataManager - ); + when(mockDataManager.getNotesInTrash()).thenReturn(Flowable.just(Collections.emptyList())); + + presenter = + new TrashPresenter( + testSchedulerProvider(), new CompositeDisposable(), mockDataManager); presenter.attachView(mockView); } @@ -76,8 +68,7 @@ public void clearTrash_onError_doesNotCrash() { @Test public void restoreNotesArray_callsRestoreNotesAndFixTags() { - when(mockDataManager.restoreNotesAndFixTags(any())) - .thenReturn(Completable.complete()); + when(mockDataManager.restoreNotesAndFixTags(any())).thenReturn(Completable.complete()); Note note = new Note(); note.setId(1); diff --git a/app/src/test/java/com/pasich/mynotes/utils/BackupParserTest.java b/app/src/test/java/com/pasich/mynotes/utils/BackupParserTest.java index 7ffed73..d3afc66 100644 --- a/app/src/test/java/com/pasich/mynotes/utils/BackupParserTest.java +++ b/app/src/test/java/com/pasich/mynotes/utils/BackupParserTest.java @@ -6,11 +6,9 @@ import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.utils.backup.models.JsonBackup; - -import org.junit.Test; - import java.util.Arrays; import java.util.Collections; +import org.junit.Test; public class BackupParserTest { diff --git a/app/src/test/java/com/pasich/mynotes/utils/SearchFilterTest.java b/app/src/test/java/com/pasich/mynotes/utils/SearchFilterTest.java index 62989db..774dc44 100644 --- a/app/src/test/java/com/pasich/mynotes/utils/SearchFilterTest.java +++ b/app/src/test/java/com/pasich/mynotes/utils/SearchFilterTest.java @@ -3,14 +3,11 @@ import static com.google.common.truth.Truth.assertThat; import com.pasich.mynotes.data.model.Note; - -import org.junit.Test; - import java.util.Arrays; -import java.util.Collections; import java.util.Date; import java.util.List; import java.util.stream.Collectors; +import org.junit.Test; public class SearchFilterTest { @@ -22,8 +19,10 @@ private List filter(List notes, String query) { if (query == null || query.trim().isEmpty()) return notes; String q = query.toLowerCase().trim(); return notes.stream() - .filter(n -> n.getTitle().toLowerCase().contains(q) - || n.getValue().toLowerCase().contains(q)) + .filter( + n -> + n.getTitle().toLowerCase().contains(q) + || n.getValue().toLowerCase().contains(q)) .collect(Collectors.toList()); } @@ -35,25 +34,30 @@ public void emptyQuery_returnsAllNotes() { @Test public void exactTitleMatch_returnsNote() { - List notes = Arrays.asList(makeNote("Shopping List", "milk"), makeNote("Work", "tasks")); + List notes = + Arrays.asList(makeNote("Shopping List", "milk"), makeNote("Work", "tasks")); assertThat(filter(notes, "Shopping List")).hasSize(1); } @Test public void partialTitleMatch_returnsNote() { - List notes = Arrays.asList(makeNote("My Shopping", "items"), makeNote("Work", "tasks")); + List notes = + Arrays.asList(makeNote("My Shopping", "items"), makeNote("Work", "tasks")); assertThat(filter(notes, "Shop")).hasSize(1); } @Test public void caseInsensitiveSearch_works() { - List notes = Arrays.asList(makeNote("UPPERCASE", "content"), makeNote("Other", "stuff")); + List notes = + Arrays.asList(makeNote("UPPERCASE", "content"), makeNote("Other", "stuff")); assertThat(filter(notes, "uppercase")).hasSize(1); } @Test public void contentSearch_matchesValue() { - List notes = Arrays.asList(makeNote("Title", "buy groceries"), makeNote("Title2", "meeting notes")); + List notes = + Arrays.asList( + makeNote("Title", "buy groceries"), makeNote("Title2", "meeting notes")); assertThat(filter(notes, "groceries")).hasSize(1); } diff --git a/build.gradle b/build.gradle index ba02efd..97ea753 100644 --- a/build.gradle +++ b/build.gradle @@ -16,6 +16,7 @@ buildscript { } plugins { id 'com.google.dagger.hilt.android' version "$daggerHilt" apply false + id 'com.diffplug.spotless' version '6.25.0' apply false } tasks.register('clean', Delete) { From 5951dca89d0513ab34449163a2a65239b45ec458 Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 10:24:38 +0300 Subject: [PATCH 05/13] docs: update changelog --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7b6fff3..c9725f2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,11 @@ alert again every 5, 10, 15, 30, or 60 minutes until the reminder is cleared. Works for both note and task reminders. +**Improvements** + +- **Help:** Updated the in-app guide — added dedicated sections for Tasks and Reminders with + step-by-step descriptions of all key features. + ## [2.5.45] - 10.05.2026 **New** From 1128b5a3b847a709041058868c5b71b5baf97d3e Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 10:31:04 +0300 Subject: [PATCH 06/13] docs: db migration --- .../13.json | 246 ++++++++++++++++++ 1 file changed, 246 insertions(+) create mode 100644 app/schemas/com.pasich.mynotes.data.database.AppDatabase/13.json diff --git a/app/schemas/com.pasich.mynotes.data.database.AppDatabase/13.json b/app/schemas/com.pasich.mynotes.data.database.AppDatabase/13.json new file mode 100644 index 0000000..d48d602 --- /dev/null +++ b/app/schemas/com.pasich.mynotes.data.database.AppDatabase/13.json @@ -0,0 +1,246 @@ +{ + "formatVersion": 1, + "database": { + "version": 13, + "identityHash": "fdfbf201d5a00f67c494499b4e320136", + "entities": [ + { + "tableName": "tags", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `visibility` INTEGER NOT NULL, `systemAction` INTEGER NOT NULL, `position` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "nameTag", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "visibility", + "columnName": "visibility", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "systemAction", + "columnName": "systemAction", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "notes", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT, `value` TEXT, `date` INTEGER NOT NULL, `tag` TEXT, `valueJson` TEXT, `hasRichContent` INTEGER NOT NULL, `attachments` TEXT, `isTrash` INTEGER NOT NULL, `reminderTime` INTEGER, `isPinned` INTEGER NOT NULL, `reminderRepeat` TEXT NOT NULL, `reminderIntervalMinutes` INTEGER NOT NULL)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT" + }, + { + "fieldPath": "value", + "columnName": "value", + "affinity": "TEXT" + }, + { + "fieldPath": "date", + "columnName": "date", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "tag", + "columnName": "tag", + "affinity": "TEXT" + }, + { + "fieldPath": "valueJson", + "columnName": "valueJson", + "affinity": "TEXT" + }, + { + "fieldPath": "hasRichContent", + "columnName": "hasRichContent", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "attachments", + "columnName": "attachments", + "affinity": "TEXT" + }, + { + "fieldPath": "isTrash", + "columnName": "isTrash", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reminderTime", + "columnName": "reminderTime", + "affinity": "INTEGER" + }, + { + "fieldPath": "isPinned", + "columnName": "isPinned", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "reminderRepeat", + "columnName": "reminderRepeat", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "reminderIntervalMinutes", + "columnName": "reminderIntervalMinutes", + "affinity": "INTEGER", + "notNull": true + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "tasks", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `title` TEXT NOT NULL, `description` TEXT, `isDone` INTEGER NOT NULL DEFAULT 0, `categoryId` INTEGER NOT NULL DEFAULT 0, `createdAt` INTEGER NOT NULL DEFAULT 0, `position` INTEGER NOT NULL DEFAULT 0, `reminderTime` INTEGER, `reminderIntervalMinutes` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "title", + "columnName": "title", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "description", + "columnName": "description", + "affinity": "TEXT" + }, + { + "fieldPath": "isDone", + "columnName": "isDone", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "categoryId", + "columnName": "categoryId", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "createdAt", + "columnName": "createdAt", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + }, + { + "fieldPath": "reminderTime", + "columnName": "reminderTime", + "affinity": "INTEGER" + }, + { + "fieldPath": "reminderIntervalMinutes", + "columnName": "reminderIntervalMinutes", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + }, + { + "tableName": "task_categories", + "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `colorHex` TEXT NOT NULL DEFAULT '#6750A4', `position` INTEGER NOT NULL DEFAULT 0)", + "fields": [ + { + "fieldPath": "id", + "columnName": "id", + "affinity": "INTEGER", + "notNull": true + }, + { + "fieldPath": "name", + "columnName": "name", + "affinity": "TEXT", + "notNull": true + }, + { + "fieldPath": "colorHex", + "columnName": "colorHex", + "affinity": "TEXT", + "notNull": true, + "defaultValue": "'#6750A4'" + }, + { + "fieldPath": "position", + "columnName": "position", + "affinity": "INTEGER", + "notNull": true, + "defaultValue": "0" + } + ], + "primaryKey": { + "autoGenerate": true, + "columnNames": [ + "id" + ] + } + } + ], + "setupQueries": [ + "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", + "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fdfbf201d5a00f67c494499b4e320136')" + ] + } +} \ No newline at end of file From 006ae77843eb4af1983210927ebacda7e1b8bd6c Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 17:48:42 +0300 Subject: [PATCH 07/13] fix: pluralize word count for all locales && small fixes --- .../mynotes/data/database/dao/NoteDao.java | 3 +- .../mynotes/data/database/dao/TaskDao.java | 2 +- .../mynotes/ui/presenter/TasksPresenter.java | 6 +++- .../mynotes/ui/receiver/ReminderReceiver.java | 34 ++++++++++++++++--- .../activity/noteEditor/NoteActivity.java | 3 +- app/src/main/res/values-be/strings.xml | 8 +++++ app/src/main/res/values-de/strings.xml | 6 ++++ app/src/main/res/values-en-rGB/strings.xml | 6 ++++ app/src/main/res/values-es/strings.xml | 6 ++++ app/src/main/res/values-fr/strings.xml | 6 ++++ app/src/main/res/values-it/strings.xml | 6 ++++ app/src/main/res/values-kk/strings.xml | 5 +++ app/src/main/res/values-pl/strings.xml | 8 +++++ app/src/main/res/values-ru/strings.xml | 8 +++++ app/src/main/res/values-uk/strings.xml | 8 +++++ app/src/main/res/values/strings.xml | 6 +++- 16 files changed, 112 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java index cc44d91..cd42d66 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java @@ -85,7 +85,8 @@ public interface NoteDao { "SELECT * FROM notes WHERE reminderTime IS NOT NULL AND reminderTime > :now AND isTrash = 0") List getNotesWithActiveRemindersSync(long now); - @Query("UPDATE notes SET reminderTime = NULL, reminderRepeat = 'NONE' WHERE id = :noteId") + @Query( + "UPDATE notes SET reminderTime = NULL, reminderRepeat = 'NONE', reminderIntervalMinutes = 0 WHERE id = :noteId") void clearReminderSync(int noteId); @Query("UPDATE notes SET reminderTime = :time, reminderRepeat = :repeat WHERE id = :noteId") diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java index a6dd9e4..631aaa9 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java @@ -53,7 +53,7 @@ public interface TaskDao { "UPDATE tasks SET reminderTime = :time, reminderIntervalMinutes = :intervalMinutes WHERE id = :taskId") void setTaskReminderFullSync(int taskId, long time, int intervalMinutes); - @Query("UPDATE tasks SET reminderTime = NULL WHERE id = :taskId") + @Query("UPDATE tasks SET reminderTime = NULL, reminderIntervalMinutes = 0 WHERE id = :taskId") void clearTaskReminder(int taskId); @Query("SELECT * FROM tasks WHERE reminderTime IS NOT NULL AND isDone = 0") diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java index 04a0dd3..4a8e0b0 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java @@ -110,10 +110,14 @@ public void addTask(String title, String description, int categoryId) { @Override public void toggleTask(Task task) { + boolean markingDone = !task.isDone(); + if (markingDone && task.getReminderTime() != null) { + clearTaskReminder(task); + } getCompositeDisposable() .add( getDataManager() - .toggleTask(task.getId(), !task.isDone()) + .toggleTask(task.getId(), markingDone) .subscribeOn(getSchedulerProvider().io()) .observeOn(getSchedulerProvider().ui()) .subscribe(() -> {}, err -> Log.e(TAG, "toggleTask", err))); diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java index 8d1e0b9..9c4681d 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java @@ -31,17 +31,28 @@ public class ReminderReceiver extends BroadcastReceiver { @Inject NotificationPreferencesCache notificationPreferencesCache; + public static final String ACTION_DISMISS = "com.pasich.mynotes.ACTION_DISMISS_REMINDER"; + @Override public void onReceive(Context ctx, Intent intent) { int noteId = intent.getIntExtra(ReminderManager.EXTRA_NOTE_ID, -1); + if (noteId == -1) return; + + if (ACTION_DISMISS.equals(intent.getAction())) { + ReminderManager.cancelReminder(ctx, noteId); + dataManager + .clearReminder(noteId) + .subscribe(() -> {}, e -> Log.e(TAG, "dismiss clearReminder failed", e)); + NotificationManagerCompat.from(ctx).cancel(noteId); + return; + } + String title = intent.getStringExtra(ReminderManager.EXTRA_NOTE_TITLE); String preview = intent.getStringExtra(ReminderManager.EXTRA_NOTE_PREVIEW); String repeatStr = intent.getStringExtra(ReminderManager.EXTRA_NOTE_REPEAT); int intervalMinutes = intent.getIntExtra(ReminderManager.EXTRA_NOTE_INTERVAL_MINUTES, 0); - if (noteId == -1) return; - - showNotification(ctx, noteId, title, preview); + showNotification(ctx, noteId, title, preview, intervalMinutes); if (intervalMinutes > 0) { long nextTime = System.currentTimeMillis() + intervalMinutes * 60_000L; @@ -103,7 +114,8 @@ private long computeNextTime(long from, ReminderRepeat repeat) { return cal.getTimeInMillis(); } - private void showNotification(Context ctx, int noteId, String title, String preview) { + private void showNotification( + Context ctx, int noteId, String title, String preview, int intervalMinutes) { Intent noteIntent = new Intent(ctx, NoteActivity.class); noteIntent.putExtra(NoteExtras.EXTRA_NEW_NOTE, false); noteIntent.putExtra(NoteExtras.EXTRA_ID_NOTE, (long) noteId); @@ -135,6 +147,16 @@ private void showNotification(Context ctx, int noteId, String title, String prev ? preview.substring(0, 100) : (preview != null ? preview : ""); + Intent dismissIntent = new Intent(ctx, ReminderReceiver.class); + dismissIntent.setAction(ACTION_DISMISS); + dismissIntent.putExtra(ReminderManager.EXTRA_NOTE_ID, noteId); + PendingIntent dismissPi = + PendingIntent.getBroadcast( + ctx, + noteId + 30000, + dismissIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, notificationPreferencesCache.getChannelId()) .setSmallIcon(R.drawable.ic_bell_small) @@ -145,6 +167,10 @@ private void showNotification(Context ctx, int noteId, String title, String prev .setContentIntent(openPi) .addAction(0, ctx.getString(R.string.reminder_snooze_label), snoozePi); + if (intervalMinutes > 0) { + builder.addAction(0, ctx.getString(R.string.reminder_dismiss_label), dismissPi); + } + NotificationManagerCompat nm = NotificationManagerCompat.from(ctx); try { nm.notify(noteId, builder.build()); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java index f4bfc8c..3b83584 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java @@ -394,7 +394,8 @@ private void updateWordCount(String text) { return; } int count = text.trim().split("\\s+").length; - binding.wordCountCenter.setText(getString(R.string.wordCount, count)); + binding.wordCountCenter.setText( + getResources().getQuantityString(R.plurals.wordCount, count, count)); binding.wordCountCenter.setVisibility(View.VISIBLE); } diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index bed3b47..3c2ad20 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -353,6 +353,7 @@ 1 гадзіна Заўтра раніцай Адкласці + Закрыць Патрабуецца дазвол для нагадванняў Нагадванні Нагадванне @@ -391,4 +392,11 @@ 30 хв 60 хв + + %d слова + %d словы + %d слоў + %d слова + + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index d725e66..c7826c2 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -355,6 +355,7 @@ 1 Stunde Morgen früh Schlummern + Schließen Berechtigung für Erinnerungen erforderlich Erinnerungen Erinnerung @@ -393,4 +394,9 @@ 30 Min. 60 Min. + + %d Wort + %d Wörter + + \ No newline at end of file diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 8660008..8cd93c6 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -399,6 +399,7 @@ 1 hour Tomorrow morning Snooze + Dismiss Permission required for reminders Reminders Reminder @@ -437,4 +438,9 @@ 30 min 60 min + + %d word + %d words + + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 59c9de0..ed27f68 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -356,6 +356,7 @@ 1 hora Mañana por la mañana Posponer + Descartar Permiso requerido para recordatorios Recordatorios Recordatorio @@ -394,4 +395,9 @@ 30 min 60 min + + %d palabra + %d palabras + + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 992ee59..206dc7c 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -351,6 +351,7 @@ 1 heure Demain matin Reporter + Ignorer Permission requise pour les rappels Rappels Rappel @@ -389,4 +390,9 @@ 30 min 60 min + + %d mot + %d mots + + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index fcae182..65f1b4e 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -353,6 +353,7 @@ 1 ora Domani mattina Posponi + Ignora Autorizzazione richiesta per i promemoria Promemoria Promemoria @@ -391,4 +392,9 @@ 30 min 60 min + + %d parola + %d parole + + \ No newline at end of file diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index e1c9282..47e9eb0 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -352,6 +352,7 @@ 1 сағат Ертең таңертең Кейінге қалдыру + Жабу Еске салулар үшін рұқсат қажет Еске салулар Еске салу @@ -390,4 +391,8 @@ 30 мин 60 мин + + %d сөз + + \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 8ff56a4..aa85af8 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -354,6 +354,7 @@ 1 godzina Jutro rano Drzemka + Odrzuć Wymagane uprawnienie do przypomnień Przypomnienia Przypomnienie @@ -392,4 +393,11 @@ 30 min 60 min + + %d słowo + %d słowa + %d słów + %d słowa + + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 23e15ad..504db67 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -358,6 +358,7 @@ 1 час Завтра утром Отложить + Закрыть Нужно разрешение для напоминаний Напоминания Напоминание @@ -396,4 +397,11 @@ 30 мин 60 мин + + %d слово + %d слова + %d слов + %d слова + + \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 7e3275f..f80625b 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -379,6 +379,7 @@ 1 година Завтра вранці Відкласти + Закрити Дозвіл потрібен для нагадувань Нагадування Нагадування @@ -417,4 +418,11 @@ 30 хв 60 хв + + %d слово + %d слова + %d слів + %d слова + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7f69326..76ee5d0 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -60,7 +60,10 @@ Edit copy Pin note Unpin note - %d words + + %d word + %d words + Note moved to trash @@ -413,6 +416,7 @@ 1 hour Tomorrow morning Snooze + Dismiss Permission required for reminders Reminders Reminder From 74e9d2d6f0b5d96f42ed1e36be1e4af02c892b62 Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 20:10:48 +0300 Subject: [PATCH 08/13] fix: small error --- .../mynotes/data/database/AppDbHelper.java | 3 ++- .../mynotes/ui/receiver/BootReceiver.java | 24 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java index a84ef99..7439c2b 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java @@ -373,7 +373,8 @@ public Completable setTaskReminderFull(int taskId, long time, int intervalMinute @Override public Completable clearTaskReminder(int taskId) { - return Completable.fromAction(() -> appDatabase.taskDao().clearTaskReminder(taskId)); + return Completable.fromAction(() -> appDatabase.taskDao().clearTaskReminder(taskId)) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()); } @Override diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java index 4c9074e..6e98b7b 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java @@ -8,6 +8,7 @@ import com.pasich.mynotes.utils.reminder.ReminderManager; import com.pasich.mynotes.utils.reminder.TaskReminderManager; import dagger.hilt.android.AndroidEntryPoint; +import io.reactivex.Single; import javax.inject.Inject; @AndroidEntryPoint @@ -21,16 +22,21 @@ public class BootReceiver extends BroadcastReceiver { public void onReceive(Context ctx, Intent intent) { if (!Intent.ACTION_BOOT_COMPLETED.equals(intent.getAction())) return; - dataManager - .getNotesWithActiveReminders() - .subscribe( - notes -> ReminderManager.rescheduleAll(ctx, notes), - e -> Log.e(TAG, "rescheduleAll failed", e)); + final PendingResult pendingResult = goAsync(); - dataManager - .getTasksWithReminders() + Single.zip( + dataManager.getNotesWithActiveReminders(), + dataManager.getTasksWithReminders(), + (notes, tasks) -> { + ReminderManager.rescheduleAll(ctx, notes); + TaskReminderManager.rescheduleAll(ctx, tasks); + return true; + }) .subscribe( - tasks -> TaskReminderManager.rescheduleAll(ctx, tasks), - e -> Log.e(TAG, "rescheduleTasksAll failed", e)); + result -> pendingResult.finish(), + e -> { + Log.e(TAG, "reschedule failed", e); + pendingResult.finish(); + }); } } From 973cbeb07ebfb0bd89dc4dbbe6a4e6c67af75393 Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 22:10:45 +0300 Subject: [PATCH 09/13] fix: preserve repeat schedule when tapping/dismissing interval reminder notification - ReminderTapActivity and cancelIntervalReminder now schedule the next daily/weekly/monthly occurrence instead of unconditionally clearing the reminder when repeatStr != NONE - Extract computeNextTime to ReminderManager (shared by receiver + activity) - Fix NoteDao: replace @Update with updateNoteContent query so auto-save no longer overwrites reminderTime/reminderRepeat fields - Fix SnoozeActivity: pass original repeatStr/intervalMinutes through snooze alarm instead of hardcoded NONE - Fix TaskReminderReceiver: use ReminderTapActivity PendingIntent for tap - Fix ReminderTapActivity: early-return when noteId/taskId == -1 - Add ReminderRepeatTest unit tests and reminder-specific NoteRepositoryTest - Add clearReminder stack-trace logging for future debugging --- .../pasich/mynotes/db/NoteRepositoryTest.java | 72 ++- app/src/main/AndroidManifest.xml | 4 + .../mynotes/data/database/AppDbHelper.java | 16 +- .../mynotes/data/database/dao/NoteDao.java | 13 +- .../mynotes/ui/receiver/ReminderReceiver.java | 124 ++-- .../ui/receiver/TaskReminderReceiver.java | 96 ++- .../ui/view/activity/ReminderTapActivity.java | 90 +++ .../ui/view/activity/SnoozeActivity.java | 7 +- .../ui/view/dialogs/MoreNoteDialog.java | 190 +++--- .../utils/reminder/ReminderManager.java | 21 + app/src/main/res/layout/dialog_more_note.xml | 553 +++++++++++------- app/src/main/res/values-be/strings.xml | 4 + app/src/main/res/values-de/strings.xml | 4 + app/src/main/res/values-en-rGB/strings.xml | 4 + app/src/main/res/values-es/strings.xml | 4 + app/src/main/res/values-fr/strings.xml | 4 + app/src/main/res/values-it/strings.xml | 4 + app/src/main/res/values-kk/strings.xml | 4 + app/src/main/res/values-pl/strings.xml | 4 + app/src/main/res/values-ru/strings.xml | 4 + app/src/main/res/values-uk/strings.xml | 4 + app/src/main/res/values/strings.xml | 5 + .../mynotes/utils/ReminderRepeatTest.java | 50 ++ 23 files changed, 914 insertions(+), 367 deletions(-) create mode 100644 app/src/main/java/com/pasich/mynotes/ui/view/activity/ReminderTapActivity.java create mode 100644 app/src/test/java/com/pasich/mynotes/utils/ReminderRepeatTest.java diff --git a/app/src/androidTest/java/com/pasich/mynotes/db/NoteRepositoryTest.java b/app/src/androidTest/java/com/pasich/mynotes/db/NoteRepositoryTest.java index 1a67358..cb0a753 100644 --- a/app/src/androidTest/java/com/pasich/mynotes/db/NoteRepositoryTest.java +++ b/app/src/androidTest/java/com/pasich/mynotes/db/NoteRepositoryTest.java @@ -73,7 +73,14 @@ public void updateNote_changesTitle() { long id = noteDao.addNote(note); note.setId((int) id); note.setTitle("New"); - noteDao.updateNote(note); + noteDao.updateNoteContent( + note.getId(), + note.getTitle(), + note.getValue(), + note.getValueJson(), + note.getDate(), + note.getTag(), + note.getAttachments()); Note updated = noteDao.getNoteForId(id).blockingGet(); assertThat(updated.getTitle()).isEqualTo("New"); } @@ -134,4 +141,67 @@ public void addMultipleNotes_allAppearInList() { List all = noteDao.getNotesAll().blockingFirst(); assertThat(all).hasSize(3); } + + @Test + public void updateNoteContent_doesNotOverwriteReminderFields() { + Note note = makeNote("Title", "content"); + int id = (int) noteDao.addNote(note); + note.setId(id); + + // Set reminder fields directly + long reminderTime = System.currentTimeMillis() + 3_600_000L; + noteDao.updateReminderFullSync(id, reminderTime, "DAILY", 0); + + // Now update content only — reminder fields must survive + noteDao.updateNoteContent(id, "New Title", "new value", "", note.getDate(), "", null); + + Note result = noteDao.getNoteForId(id).blockingGet(); + assertThat(result.getTitle()).isEqualTo("New Title"); + assertThat(result.getReminderTime()).isEqualTo(reminderTime); + assertThat(result.getReminderRepeat()).isEqualTo("DAILY"); + assertThat(result.getReminderIntervalMinutes()).isEqualTo(0); + } + + @Test + public void clearReminderSync_nullifiesReminderFields() { + Note note = makeNote("Reminder note", "body"); + int id = (int) noteDao.addNote(note); + noteDao.updateReminderFullSync(id, System.currentTimeMillis() + 3_600_000L, "DAILY", 0); + + noteDao.clearReminderSync(id); + + Note result = noteDao.getNoteForId(id).blockingGet(); + assertThat(result.getReminderTime()).isNull(); + assertThat(result.getReminderRepeat()).isEqualTo("NONE"); + assertThat(result.getReminderIntervalMinutes()).isEqualTo(0); + } + + @Test + public void updateReminderFullSync_setsAllReminderFields() { + Note note = makeNote("Interval note", "body"); + int id = (int) noteDao.addNote(note); + long time = System.currentTimeMillis() + 600_000L; + + noteDao.updateReminderFullSync(id, time, "NONE", 10); + + Note result = noteDao.getNoteForId(id).blockingGet(); + assertThat(result.getReminderTime()).isEqualTo(time); + assertThat(result.getReminderRepeat()).isEqualTo("NONE"); + assertThat(result.getReminderIntervalMinutes()).isEqualTo(10); + } + + @Test + public void getNotesWithActiveReminders_excludesPastReminders() { + Note future = makeNote("Future", ""); + Note past = makeNote("Past", ""); + int futureId = (int) noteDao.addNote(future); + int pastId = (int) noteDao.addNote(past); + long now = System.currentTimeMillis(); + noteDao.updateReminderFullSync(futureId, now + 3_600_000L, "NONE", 0); + noteDao.updateReminderFullSync(pastId, now - 3_600_000L, "DAILY", 0); + + List active = noteDao.getNotesWithActiveRemindersSync(now); + assertThat(active).hasSize(1); + assertThat(active.get(0).getTitle()).isEqualTo("Future"); + } } diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index db1af47..2f0053e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -140,6 +140,10 @@ android:name=".ui.view.activity.SnoozeActivity" android:theme="@style/Theme.Translucent" android:exported="false" /> + diff --git a/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java index 7439c2b..8a1bc9c 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/AppDbHelper.java @@ -214,7 +214,18 @@ public Completable deleteNote(ArrayList notes) { @Override public Completable updateNote(Note note) { - return Completable.fromAction(() -> appDatabase.noteDao().updateNote(note)); + return Completable.fromAction( + () -> + appDatabase + .noteDao() + .updateNoteContent( + note.getId(), + note.getTitle(), + note.getValue(), + note.getValueJson(), + note.getDate(), + note.getTag(), + note.getAttachments())); } @Override @@ -256,6 +267,9 @@ public Single> getNotesWithActiveReminders() { @Override public Completable clearReminder(int noteId) { + RuntimeException caller = + new RuntimeException("clearReminder(noteId=" + noteId + ") CALLER"); + android.util.Log.w("ReminderDebug", "clearReminder called for noteId=" + noteId, caller); return Completable.fromAction(() -> appDatabase.noteDao().clearReminderSync(noteId)) .subscribeOn(io.reactivex.schedulers.Schedulers.io()); } diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java index cd42d66..ce1fd4c 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java @@ -5,7 +5,6 @@ import androidx.room.Insert; import androidx.room.OnConflictStrategy; import androidx.room.Query; -import androidx.room.Update; import com.pasich.mynotes.data.model.Note; import io.reactivex.Flowable; import io.reactivex.Single; @@ -35,8 +34,16 @@ public interface NoteDao { @Insert(onConflict = OnConflictStrategy.REPLACE) void addNotes(List notes); - @Update - void updateNote(Note note); + @Query( + "UPDATE notes SET title = :title, value = :value, valueJson = :valueJson, date = :date, tag = :tag, attachments = :attachments WHERE id = :id") + void updateNoteContent( + int id, + String title, + String value, + String valueJson, + long date, + String tag, + String attachments); @Delete void deleteNote(Note note); diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java index 9c4681d..2a1eb78 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java @@ -14,12 +14,12 @@ import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.data.model.ReminderRepeat; import com.pasich.mynotes.ui.view.activity.MainActivity; +import com.pasich.mynotes.ui.view.activity.ReminderTapActivity; import com.pasich.mynotes.ui.view.activity.SnoozeActivity; import com.pasich.mynotes.ui.view.activity.noteEditor.NoteActivity; import com.pasich.mynotes.utils.navigation.NoteExtras; import com.pasich.mynotes.utils.reminder.ReminderManager; import dagger.hilt.android.AndroidEntryPoint; -import java.util.Calendar; import javax.inject.Inject; @AndroidEntryPoint @@ -39,11 +39,11 @@ public void onReceive(Context ctx, Intent intent) { if (noteId == -1) return; if (ACTION_DISMISS.equals(intent.getAction())) { - ReminderManager.cancelReminder(ctx, noteId); - dataManager - .clearReminder(noteId) - .subscribe(() -> {}, e -> Log.e(TAG, "dismiss clearReminder failed", e)); - NotificationManagerCompat.from(ctx).cancel(noteId); + String dRepeat = intent.getStringExtra(ReminderManager.EXTRA_NOTE_REPEAT); + int dInterval = intent.getIntExtra(ReminderManager.EXTRA_NOTE_INTERVAL_MINUTES, 0); + String dTitle = intent.getStringExtra(ReminderManager.EXTRA_NOTE_TITLE); + String dPreview = intent.getStringExtra(ReminderManager.EXTRA_NOTE_PREVIEW); + cancelIntervalReminder(ctx, noteId, dRepeat, dInterval, dTitle, dPreview); return; } @@ -52,10 +52,20 @@ public void onReceive(Context ctx, Intent intent) { String repeatStr = intent.getStringExtra(ReminderManager.EXTRA_NOTE_REPEAT); int intervalMinutes = intent.getIntExtra(ReminderManager.EXTRA_NOTE_INTERVAL_MINUTES, 0); - showNotification(ctx, noteId, title, preview, intervalMinutes); + Log.d( + TAG, + "onReceive: noteId=" + + noteId + + " repeatStr=" + + repeatStr + + " intervalMinutes=" + + intervalMinutes); + + showNotification(ctx, noteId, title, preview, repeatStr, intervalMinutes); if (intervalMinutes > 0) { long nextTime = System.currentTimeMillis() + intervalMinutes * 60_000L; + Log.d(TAG, "onReceive: INTERVAL path → nextTime=" + new java.util.Date(nextTime)); dataManager .updateNoteReminderFull( noteId, @@ -76,11 +86,18 @@ public void onReceive(Context ctx, Intent intent) { ReminderRepeat repeat = ReminderRepeat.from(repeatStr); if (repeat == ReminderRepeat.NONE) { + Log.d(TAG, "onReceive: ONE-TIME path → clearing reminder for noteId=" + noteId); dataManager .clearReminder(noteId) .subscribe(() -> {}, e -> Log.e(TAG, "clearReminder failed", e)); } else { - long nextTime = computeNextTime(System.currentTimeMillis(), repeat); + long nextTime = ReminderManager.computeNextTime(System.currentTimeMillis(), repeat); + Log.d( + TAG, + "onReceive: REPEAT(" + + repeat + + ") path → scheduling next at " + + new java.util.Date(nextTime)); dataManager .updateNoteReminderFull(noteId, nextTime, repeat.name(), 0) .subscribe(() -> {}, e -> Log.e(TAG, "updateReminder failed", e)); @@ -95,27 +112,44 @@ public void onReceive(Context ctx, Intent intent) { } } - private long computeNextTime(long from, ReminderRepeat repeat) { - Calendar cal = Calendar.getInstance(); - cal.setTimeInMillis(from); - switch (repeat) { - case DAILY: - cal.add(Calendar.DAY_OF_YEAR, 1); - break; - case WEEKLY: - cal.add(Calendar.WEEK_OF_YEAR, 1); - break; - case MONTHLY: - cal.add(Calendar.MONTH, 1); - break; - default: - break; + private void cancelIntervalReminder( + Context ctx, + int noteId, + String repeatStr, + int intervalMinutes, + String title, + String preview) { + ReminderManager.cancelReminder(ctx, noteId); + NotificationManagerCompat.from(ctx).cancel(noteId); + + ReminderRepeat repeat = ReminderRepeat.from(repeatStr); + if (repeat != ReminderRepeat.NONE) { + long nextTime = ReminderManager.computeNextTime(System.currentTimeMillis(), repeat); + dataManager + .updateNoteReminderFull(noteId, nextTime, repeat.name(), intervalMinutes) + .subscribe(() -> {}, e -> Log.e(TAG, "updateReminderFull failed", e)); + Note tempNote = new Note(); + tempNote.setId(noteId); + tempNote.setTitle(title != null ? title : ""); + tempNote.setValue(preview != null ? preview : ""); + tempNote.setReminderTime(nextTime); + tempNote.setReminderRepeat(repeat.name()); + tempNote.setReminderIntervalMinutes(intervalMinutes); + ReminderManager.scheduleReminder(ctx, tempNote); + } else { + dataManager + .clearReminder(noteId) + .subscribe(() -> {}, e -> Log.e(TAG, "clearReminder failed", e)); } - return cal.getTimeInMillis(); } private void showNotification( - Context ctx, int noteId, String title, String preview, int intervalMinutes) { + Context ctx, + int noteId, + String title, + String preview, + String repeatStr, + int intervalMinutes) { Intent noteIntent = new Intent(ctx, NoteActivity.class); noteIntent.putExtra(NoteExtras.EXTRA_NEW_NOTE, false); noteIntent.putExtra(NoteExtras.EXTRA_ID_NOTE, (long) noteId); @@ -128,10 +162,25 @@ private void showNotification( noteId, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + Intent tapIntent = new Intent(ctx, ReminderTapActivity.class); + tapIntent.putExtra(ReminderManager.EXTRA_NOTE_ID, noteId); + tapIntent.putExtra(ReminderManager.EXTRA_NOTE_TITLE, title); + tapIntent.putExtra(ReminderManager.EXTRA_NOTE_PREVIEW, preview); + tapIntent.putExtra(ReminderManager.EXTRA_NOTE_REPEAT, repeatStr); + tapIntent.putExtra(ReminderManager.EXTRA_NOTE_INTERVAL_MINUTES, intervalMinutes); + PendingIntent tapPi = + PendingIntent.getActivity( + ctx, + noteId + 20000, + tapIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + Intent snoozeIntent = new Intent(ctx, SnoozeActivity.class); snoozeIntent.putExtra(ReminderManager.EXTRA_NOTE_ID, noteId); snoozeIntent.putExtra(ReminderManager.EXTRA_NOTE_TITLE, title); snoozeIntent.putExtra(ReminderManager.EXTRA_NOTE_PREVIEW, preview); + snoozeIntent.putExtra(ReminderManager.EXTRA_NOTE_REPEAT, repeatStr); + snoozeIntent.putExtra(ReminderManager.EXTRA_NOTE_INTERVAL_MINUTES, intervalMinutes); snoozeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); PendingIntent snoozePi = PendingIntent.getActivity( @@ -140,16 +189,13 @@ private void showNotification( snoozeIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); - String notifTitle = - (title != null && !title.isEmpty()) ? title : ctx.getString(R.string.app_name); - String notifText = - (preview != null && preview.length() > 100) - ? preview.substring(0, 100) - : (preview != null ? preview : ""); - Intent dismissIntent = new Intent(ctx, ReminderReceiver.class); dismissIntent.setAction(ACTION_DISMISS); dismissIntent.putExtra(ReminderManager.EXTRA_NOTE_ID, noteId); + dismissIntent.putExtra(ReminderManager.EXTRA_NOTE_TITLE, title); + dismissIntent.putExtra(ReminderManager.EXTRA_NOTE_PREVIEW, preview); + dismissIntent.putExtra(ReminderManager.EXTRA_NOTE_REPEAT, repeatStr); + dismissIntent.putExtra(ReminderManager.EXTRA_NOTE_INTERVAL_MINUTES, intervalMinutes); PendingIntent dismissPi = PendingIntent.getBroadcast( ctx, @@ -157,6 +203,18 @@ private void showNotification( dismissIntent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + String notifTitle = + (title != null && !title.isEmpty()) ? title : ctx.getString(R.string.app_name); + String notifText = + (preview != null && preview.length() > 100) + ? preview.substring(0, 100) + : (preview != null ? preview : ""); + + // For interval reminders, tapping opens the note and stops the current interval cycle + // (scheduling next daily/weekly/monthly occurrence if repeatStr != NONE). + // For one-time / periodic reminders, tapping just opens the note. + PendingIntent contentPi = (intervalMinutes > 0) ? tapPi : openPi; + NotificationCompat.Builder builder = new NotificationCompat.Builder(ctx, notificationPreferencesCache.getChannelId()) .setSmallIcon(R.drawable.ic_bell_small) @@ -164,7 +222,7 @@ private void showNotification( .setContentText(notifText) .setPriority(NotificationCompat.PRIORITY_HIGH) .setAutoCancel(true) - .setContentIntent(openPi) + .setContentIntent(contentPi) .addAction(0, ctx.getString(R.string.reminder_snooze_label), snoozePi); if (intervalMinutes > 0) { diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java index 1dab55d..e4481e6 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/TaskReminderReceiver.java @@ -11,6 +11,7 @@ import com.pasich.mynotes.cache.NotificationPreferencesCache; import com.pasich.mynotes.data.DataManager; import com.pasich.mynotes.data.model.Task; +import com.pasich.mynotes.ui.view.activity.ReminderTapActivity; import com.pasich.mynotes.ui.view.activity.TasksActivity; import com.pasich.mynotes.utils.reminder.TaskReminderManager; import dagger.hilt.android.AndroidEntryPoint; @@ -19,6 +20,11 @@ @AndroidEntryPoint public class TaskReminderReceiver extends BroadcastReceiver { + private static final int NOTIF_ID_OFFSET = 100000; + + public static final String ACTION_DISMISS_TASK = + "com.pasich.mynotes.ACTION_DISMISS_TASK_REMINDER"; + @Inject DataManager dataManager; @Inject NotificationPreferencesCache notificationPreferencesCache; @@ -26,32 +32,18 @@ public class TaskReminderReceiver extends BroadcastReceiver { @Override public void onReceive(Context ctx, Intent intent) { int taskId = intent.getIntExtra(TaskReminderManager.EXTRA_TASK_ID, -1); - String title = intent.getStringExtra(TaskReminderManager.EXTRA_TASK_TITLE); - int intervalMinutes = - intent.getIntExtra(TaskReminderManager.EXTRA_TASK_INTERVAL_MINUTES, 0); if (taskId == -1) return; - Intent openIntent = new Intent(ctx, TasksActivity.class); - PendingIntent contentIntent = - TaskStackBuilder.create(ctx) - .addNextIntentWithParentStack(openIntent) - .getPendingIntent( - taskId + 200000, - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + if (ACTION_DISMISS_TASK.equals(intent.getAction())) { + cancelIntervalReminder(ctx, taskId); + return; + } - NotificationCompat.Builder builder = - new NotificationCompat.Builder(ctx, notificationPreferencesCache.getChannelId()) - .setSmallIcon(R.drawable.ic_bell_small) - .setContentTitle(ctx.getString(R.string.app_name)) - .setContentText(title) - .setAutoCancel(true) - .setPriority(NotificationCompat.PRIORITY_HIGH) - .setContentIntent(contentIntent); + String title = intent.getStringExtra(TaskReminderManager.EXTRA_TASK_TITLE); + int intervalMinutes = + intent.getIntExtra(TaskReminderManager.EXTRA_TASK_INTERVAL_MINUTES, 0); - try { - NotificationManagerCompat.from(ctx).notify(taskId + 100000, builder.build()); - } catch (SecurityException ignored) { - } + showNotification(ctx, taskId, title, intervalMinutes); if (intervalMinutes > 0) { long nextTime = System.currentTimeMillis() + intervalMinutes * 60_000L; @@ -73,4 +65,64 @@ public void onReceive(Context ctx, Intent intent) { .subscribeOn(io.reactivex.schedulers.Schedulers.io()) .subscribe(() -> {}, e -> {}); } + + private void cancelIntervalReminder(Context ctx, int taskId) { + TaskReminderManager.cancelReminder(ctx, taskId); + dataManager + .clearTaskReminder(taskId) + .subscribeOn(io.reactivex.schedulers.Schedulers.io()) + .subscribe(() -> {}, e -> {}); + NotificationManagerCompat.from(ctx).cancel(taskId + NOTIF_ID_OFFSET); + } + + private void showNotification(Context ctx, int taskId, String title, int intervalMinutes) { + Intent openIntent = new Intent(ctx, TasksActivity.class); + PendingIntent openPi = + TaskStackBuilder.create(ctx) + .addNextIntentWithParentStack(openIntent) + .getPendingIntent( + taskId + NOTIF_ID_OFFSET, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + + Intent tapIntent = new Intent(ctx, ReminderTapActivity.class); + tapIntent.putExtra(ReminderTapActivity.EXTRA_IS_TASK, true); + tapIntent.putExtra(TaskReminderManager.EXTRA_TASK_ID, taskId); + PendingIntent tapPi = + PendingIntent.getActivity( + ctx, + taskId + NOTIF_ID_OFFSET + 20000, + tapIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + + Intent dismissIntent = new Intent(ctx, TaskReminderReceiver.class); + dismissIntent.setAction(ACTION_DISMISS_TASK); + dismissIntent.putExtra(TaskReminderManager.EXTRA_TASK_ID, taskId); + PendingIntent dismissPi = + PendingIntent.getBroadcast( + ctx, + taskId + NOTIF_ID_OFFSET + 30000, + dismissIntent, + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + + // For interval reminders, tapping opens tasks AND cancels the repeat. + PendingIntent contentPi = (intervalMinutes > 0) ? tapPi : openPi; + + NotificationCompat.Builder builder = + new NotificationCompat.Builder(ctx, notificationPreferencesCache.getChannelId()) + .setSmallIcon(R.drawable.ic_bell_small) + .setContentTitle(ctx.getString(R.string.app_name)) + .setContentText(title) + .setAutoCancel(true) + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setContentIntent(contentPi); + + if (intervalMinutes > 0) { + builder.addAction(0, ctx.getString(R.string.reminder_dismiss_label), dismissPi); + } + + try { + NotificationManagerCompat.from(ctx).notify(taskId + NOTIF_ID_OFFSET, builder.build()); + } catch (SecurityException ignored) { + } + } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/ReminderTapActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/ReminderTapActivity.java new file mode 100644 index 0000000..6e1f681 --- /dev/null +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/ReminderTapActivity.java @@ -0,0 +1,90 @@ +package com.pasich.mynotes.ui.view.activity; + +import android.content.Intent; +import android.os.Bundle; +import androidx.appcompat.app.AppCompatActivity; +import androidx.core.app.TaskStackBuilder; +import com.pasich.mynotes.data.DataManager; +import com.pasich.mynotes.data.model.Note; +import com.pasich.mynotes.data.model.ReminderRepeat; +import com.pasich.mynotes.ui.view.activity.noteEditor.NoteActivity; +import com.pasich.mynotes.utils.navigation.NoteExtras; +import com.pasich.mynotes.utils.reminder.ReminderManager; +import com.pasich.mynotes.utils.reminder.TaskReminderManager; +import dagger.hilt.android.AndroidEntryPoint; +import javax.inject.Inject; + +/** + * Trampoline activity for interval reminder notification taps. Cancels the current interval alarm. + * If the note has a daily/weekly/monthly repeat, schedules the next occurrence; otherwise clears + * the reminder entirely. Then navigates to the note or tasks screen. + */ +@AndroidEntryPoint +public class ReminderTapActivity extends AppCompatActivity { + + public static final String EXTRA_IS_TASK = "isTask"; + + @Inject DataManager dataManager; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Intent incoming = getIntent(); + boolean isTask = incoming.getBooleanExtra(EXTRA_IS_TASK, false); + + if (isTask) { + int taskId = incoming.getIntExtra(TaskReminderManager.EXTRA_TASK_ID, -1); + if (taskId == -1) { + finish(); + return; + } + TaskReminderManager.cancelReminder(this, taskId); + dataManager.clearTaskReminder(taskId).subscribe(() -> {}, e -> {}); + TaskStackBuilder.create(this) + .addNextIntentWithParentStack(new Intent(this, TasksActivity.class)) + .startActivities(); + } else { + int noteId = incoming.getIntExtra(ReminderManager.EXTRA_NOTE_ID, -1); + if (noteId == -1) { + finish(); + return; + } + String repeatStr = incoming.getStringExtra(ReminderManager.EXTRA_NOTE_REPEAT); + int intervalMinutes = + incoming.getIntExtra(ReminderManager.EXTRA_NOTE_INTERVAL_MINUTES, 0); + String title = incoming.getStringExtra(ReminderManager.EXTRA_NOTE_TITLE); + String preview = incoming.getStringExtra(ReminderManager.EXTRA_NOTE_PREVIEW); + + ReminderManager.cancelReminder(this, noteId); + + ReminderRepeat repeat = ReminderRepeat.from(repeatStr); + if (repeat != ReminderRepeat.NONE) { + long nextTime = ReminderManager.computeNextTime(System.currentTimeMillis(), repeat); + Note tempNote = new Note(); + tempNote.setId(noteId); + tempNote.setTitle(title != null ? title : ""); + tempNote.setValue(preview != null ? preview : ""); + tempNote.setReminderTime(nextTime); + tempNote.setReminderRepeat(repeat.name()); + tempNote.setReminderIntervalMinutes(intervalMinutes); + dataManager + .updateNoteReminderFull(noteId, nextTime, repeat.name(), intervalMinutes) + .subscribe(() -> {}, e -> {}); + ReminderManager.scheduleReminder(this, tempNote); + } else { + dataManager.clearReminder(noteId).subscribe(() -> {}, e -> {}); + } + + Intent noteIntent = new Intent(this, NoteActivity.class); + noteIntent.putExtra(NoteExtras.EXTRA_NEW_NOTE, false); + noteIntent.putExtra(NoteExtras.EXTRA_ID_NOTE, (long) noteId); + TaskStackBuilder.create(this) + .addNextIntent(new Intent(this, MainActivity.class)) + .addNextIntent(noteIntent) + .startActivities(); + } + + finish(); + } +} diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java index f4117ab..6522ef4 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java @@ -18,6 +18,8 @@ public class SnoozeActivity extends AppCompatActivity { private int noteId; private String noteTitle; private String notePreview; + private String noteRepeat; + private int noteIntervalMinutes; @Override protected void onCreate(Bundle savedInstanceState) { @@ -27,6 +29,8 @@ protected void onCreate(Bundle savedInstanceState) { noteId = intent.getIntExtra(ReminderManager.EXTRA_NOTE_ID, -1); noteTitle = intent.getStringExtra(ReminderManager.EXTRA_NOTE_TITLE); notePreview = intent.getStringExtra(ReminderManager.EXTRA_NOTE_PREVIEW); + noteRepeat = intent.getStringExtra(ReminderManager.EXTRA_NOTE_REPEAT); + noteIntervalMinutes = intent.getIntExtra(ReminderManager.EXTRA_NOTE_INTERVAL_MINUTES, 0); if (noteId == -1) { finish(); @@ -85,7 +89,8 @@ private void scheduleSnooze(long time) { tempNote.setTitle(noteTitle != null ? noteTitle : ""); tempNote.setValue(notePreview != null ? notePreview : ""); tempNote.setReminderTime(time); - tempNote.setReminderRepeat(ReminderRepeat.NONE.name()); + tempNote.setReminderRepeat(noteRepeat != null ? noteRepeat : ReminderRepeat.NONE.name()); + tempNote.setReminderIntervalMinutes(noteIntervalMinutes); ReminderManager.scheduleReminder(this, tempNote); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/MoreNoteDialog.java b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/MoreNoteDialog.java index e0fca4a..7625a4b 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/MoreNoteDialog.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/dialogs/MoreNoteDialog.java @@ -24,7 +24,6 @@ import com.pasich.mynotes.databinding.DialogMoreNoteBinding; import com.pasich.mynotes.ui.contract.dialogs.MoreNoteDialogContract; import com.pasich.mynotes.ui.presenter.dialogs.MoreNoteDialogPresenter; -import com.pasich.mynotes.ui.view.widgets.TwoSideSwitchView; import com.pasich.mynotes.utils.navigation.GoogleTranslateHelper; import com.pasich.mynotes.utils.tool.TextStyleTool; import dagger.hilt.android.AndroidEntryPoint; @@ -39,7 +38,6 @@ public class MoreNoteDialog extends BaseDialogBottomSheets implements MoreNoteDi @Inject public MoreNoteDialogPresenter mPresenter; @Inject public TextStyleTool textStylePreferences; - // private Note mNote; private RootActivity rootActivity; private int positionItem; private DialogMoreNoteBinding binding; @@ -95,6 +93,10 @@ public View onCreateView( textStylePreferences.addButton(binding.settingsActivity.textStyleItem); + if (rootActivity != RootActivity.MainActivity) { + binding.setReminder.setBackgroundResource(R.drawable.bg_item_full); + } + return binding.getRoot(); } @@ -124,43 +126,51 @@ public void onNoteLoaded(Note mNote) { private void updatePinState(Note note) { if (note == null) return; - binding.pinNoteText.setText( - note.isPinned() ? getString(R.string.unpinNote) : getString(R.string.pinNote)); + boolean pinned = note.isPinned(); + String pinLabel = pinned ? getString(R.string.unpinNote) : getString(R.string.pinNote); + if (rootActivity == RootActivity.MainActivity) { + binding.pinNoteText.setText(pinLabel); + } else { + binding.quickPinLabel.setText(pinLabel); + } } private void updateReminderMenuState(Note note) { boolean hasReminder = note != null && note.hasReminder(); if (hasReminder) { - SimpleDateFormat fmt = new SimpleDateFormat("d MMM yyyy, HH:mm", Locale.getDefault()); - binding.reminderDate.setText(fmt.format(new Date(note.getReminderTime()))); - binding.reminderDate.setVisibility(View.VISIBLE); + SimpleDateFormat fmt = new SimpleDateFormat("d MMM · HH:mm", Locale.getDefault()); + binding.reminderSubtitle.setText(fmt.format(new Date(note.getReminderTime()))); + binding.reminderSubtitle.setVisibility(View.VISIBLE); } else { - binding.reminderDate.setVisibility(View.GONE); + binding.reminderSubtitle.setVisibility(View.GONE); } } public void setHideTextSize() { - binding.settingsActivity - .getRoot() - .setVisibility( - rootActivity == RootActivity.NoteActivity ? View.VISIBLE : View.GONE); + boolean showSettings = rootActivity == RootActivity.NoteActivity; + binding.settingsActivityWrapper.setVisibility(showSettings ? View.VISIBLE : View.GONE); } public void setChangeTypeEditor() { + if (rootActivity == RootActivity.MainActivity) return; if (mPresenter.getNote().isAttachments()) { - binding.changeTypeEditor.setMode(TwoSideSwitchView.Mode.INACTIVE); - binding.changeTypeEditor.setVisibility(GONE); + binding.editorSwitchRow.setVisibility(GONE); return; } - binding.changeTypeEditor.setMode( + String modeLabel = rootActivity == RootActivity.ExtendedActivity - ? TwoSideSwitchView.Mode.EXTENDED - : TwoSideSwitchView.Mode.SIMPLE); + ? getString(R.string.mode_extended) + : getString(R.string.mode_simple); + binding.editorTypeLabel.setText(modeLabel); } public void goneCopyNotesExtended() { - binding.copyNote.setVisibility( - mPresenter.getNote().isAttachments() ? View.GONE : View.VISIBLE); + boolean hasAttachments = mPresenter.getNote().isAttachments(); + if (rootActivity == RootActivity.MainActivity) { + binding.copyNote.setVisibility(hasAttachments ? GONE : View.VISIBLE); + } else { + binding.quickCopy.setVisibility(hasAttachments ? GONE : View.VISIBLE); + } } @Override @@ -206,7 +216,6 @@ public void initListeners() { } if (rootActivity != RootActivity.MainActivity) { - binding.noSave.setOnClickListener(v -> noteActivity.closeActivityNotSaved()); binding.settingsActivity.textSize.addOnSliderTouchListener( new Slider.OnSliderTouchListener() { @Override @@ -229,26 +238,46 @@ public void onStopTrackingTouch(@NonNull Slider slider) { noteActivity.changeTextStyle(); }); - binding.changeTypeEditor.setOnModeChangedListener( - mode -> - binding.changeTypeEditor.postDelayed( - () -> { - if (!isAdded() - || noteActivity == null - || mPresenter.getNote() == null) return; - - switch (mode) { - case SIMPLE: - case EXTENDED: - noteActivity.changeEditor( - mPresenter.getNote().getId()); - break; - case INACTIVE: - break; - } - dismiss(); - }, - 300)); + binding.editorSwitchRow.setOnClickListener( + v -> { + if (!isAdded() || noteActivity == null || mPresenter.getNote() == null) + return; + noteActivity.changeEditor(mPresenter.getNote().getId()); + dismiss(); + }); + + binding.quickShare.setOnClickListener( + v -> { + ShareOptionsDialog shareDialog = + new ShareOptionsDialog(mPresenter.getNote()); + shareDialog.show(getParentFragmentManager(), "ShareOptionsDialog"); + dismiss(); + }); + + binding.quickCopy.setOnClickListener( + v -> { + if (mPresenter.getNote().isAttachments()) return; + noteActivity.openCopyNote(mPresenter.getNote().getId()); + dismiss(); + }); + + binding.quickTranslate.setOnClickListener( + v -> { + GoogleTranslateHelper.startTranslation( + requireActivity(), mPresenter.getNote().getValue()); + dismiss(); + }); + + binding.quickPin.setOnClickListener( + v -> { + mPresenter.togglePinNote(); + dismiss(); + }); + + if (mPresenter.getNote().getValue().isEmpty()) { + binding.quickShare.setVisibility(GONE); + binding.quickCopy.setVisibility(GONE); + } } else { binding.actionPanelActivate.setOnClickListener( @@ -257,24 +286,34 @@ public void onStopTrackingTouch(@NonNull Slider slider) { mainActivity.actionStartNote(mPresenter.getNote(), positionItem); dismiss(); }); - } - binding.share.setVisibility(View.VISIBLE); - binding.share.setOnClickListener( - v -> { - // Open share options dialog - ShareOptionsDialog shareDialog = new ShareOptionsDialog(mPresenter.getNote()); - shareDialog.show(getParentFragmentManager(), "ShareOptionsDialog"); - dismiss(); - }); + binding.share.setOnClickListener( + v -> { + ShareOptionsDialog shareDialog = + new ShareOptionsDialog(mPresenter.getNote()); + shareDialog.show(getParentFragmentManager(), "ShareOptionsDialog"); + dismiss(); + }); + + binding.copyNote.setOnClickListener( + v -> { + if (mPresenter.getNote().isAttachments()) return; + mPresenter.copyNoteMainActivity(); + dismiss(); + }); + + binding.pinNote.setOnClickListener( + v -> { + mPresenter.togglePinNote(); + dismiss(); + }); + + if (mPresenter.getNote().getValue().isEmpty()) { + binding.share.setVisibility(GONE); + binding.copyNote.setVisibility(GONE); + } + } - binding.translateNote.setVisibility(View.VISIBLE); - binding.translateNote.setOnClickListener( - v -> { - GoogleTranslateHelper.startTranslation( - requireActivity(), mPresenter.getNote().getValue()); - dismiss(); - }); binding.setReminder.setOnClickListener( v -> { if (mPresenter.getNote() == null) return; @@ -283,12 +322,6 @@ public void onStopTrackingTouch(@NonNull Slider slider) { dismiss(); }); - binding.pinNote.setOnClickListener( - v -> { - mPresenter.togglePinNote(); - dismiss(); - }); - binding.moveToTrash.setOnClickListener( v -> { mPresenter.noteMoveToTrash(); @@ -299,24 +332,6 @@ public void onStopTrackingTouch(@NonNull Slider slider) { noteActivity.closeActivityNotSaved(); } }); - - binding.copyNote.setOnClickListener( - v -> { - if (mPresenter.getNote().isAttachments()) return; - if (rootActivity != RootActivity.MainActivity) { - noteActivity.openCopyNote(mPresenter.getNote().getId()); - } else { - mPresenter.copyNoteMainActivity(); - } - - dismiss(); - }); - - if (mPresenter.getNote().getValue().isEmpty()) { - binding.translateNote.setVisibility(GONE); - binding.share.setVisibility(GONE); - binding.copyNote.setVisibility(GONE); - } } @Override @@ -325,21 +340,24 @@ public void onDismiss(@NonNull DialogInterface dialog) { mPresenter.detachView(); if (rootActivity != RootActivity.MainActivity) { - binding.noSave.setOnClickListener(null); - binding.translateNote.setOnClickListener(null); + binding.editorSwitchRow.setOnClickListener(null); + binding.quickShare.setOnClickListener(null); + binding.quickCopy.setOnClickListener(null); + binding.quickTranslate.setOnClickListener(null); + binding.quickPin.setOnClickListener(null); binding.settingsActivity.textStyleItem.setOnClickListener(null); noteActivity = null; } else { mainActivity = null; binding.actionPanelActivate.setOnClickListener(null); + binding.share.setOnClickListener(null); + binding.copyNote.setOnClickListener(null); + binding.pinNote.setOnClickListener(null); positionItem = 0; } binding.setReminder.setOnClickListener(null); - binding.pinNote.setOnClickListener(null); binding.moveToTrash.setOnClickListener(null); - binding.copyNote.setOnClickListener(null); - binding.share.setOnClickListener(null); } @Override @@ -353,7 +371,7 @@ public void createChipsTag(List tags) { binding.scrollChips.setVisibility(View.VISIBLE); - String noteTag = mPresenter.getNote().getTag(); // може бути "" + String noteTag = mPresenter.getNote().getTag(); for (Tag tag : tags) { Chip chip = diff --git a/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java b/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java index 45697ba..8ae199b 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java @@ -6,7 +6,9 @@ import android.content.Intent; import android.os.Build; import com.pasich.mynotes.data.model.Note; +import com.pasich.mynotes.data.model.ReminderRepeat; import com.pasich.mynotes.ui.receiver.ReminderReceiver; +import java.util.Calendar; import java.util.List; public class ReminderManager { @@ -51,6 +53,25 @@ public static void rescheduleAll(Context ctx, List notes) { } } + public static long computeNextTime(long from, ReminderRepeat repeat) { + Calendar cal = Calendar.getInstance(); + cal.setTimeInMillis(from); + switch (repeat) { + case DAILY: + cal.add(Calendar.DAY_OF_YEAR, 1); + break; + case WEEKLY: + cal.add(Calendar.WEEK_OF_YEAR, 1); + break; + case MONTHLY: + cal.add(Calendar.MONTH, 1); + break; + default: + break; + } + return cal.getTimeInMillis(); + } + public static boolean canScheduleExact(Context ctx) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); diff --git a/app/src/main/res/layout/dialog_more_note.xml b/app/src/main/res/layout/dialog_more_note.xml index e0be956..c56ea1e 100644 --- a/app/src/main/res/layout/dialog_more_note.xml +++ b/app/src/main/res/layout/dialog_more_note.xml @@ -19,7 +19,6 @@ name="note" type="com.pasich.mynotes.data.model.Note" /> - - + + + - - - - - - - - - - - - - - + android:layout_marginHorizontal="20dp" + android:background="@drawable/bg_item_full" + android:orientation="horizontal" + android:visibility="@{activityNote ? View.VISIBLE : View.GONE}"> + android:layout_weight="1" + android:gravity="center" + android:orientation="vertical" + android:padding="12dp"> + android:layout_width="24dp" + android:layout_height="24dp" + android:contentDescription="@string/share" + android:src="@drawable/ic_share_app" + app:tint="?android:colorPrimary" /> + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ellipsize="end" + android:maxLines="1" + android:text="@string/share" + android:textSize="11sp" /> - - - + android:layout_weight="1" + android:gravity="center" + android:orientation="vertical" + android:padding="12dp"> + android:layout_width="24dp" + android:layout_height="24dp" + android:contentDescription="@string/copyNoteEdits" + android:src="@drawable/ic_copy" + app:tint="?android:colorPrimary" /> - + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ellipsize="end" + android:maxLines="1" + android:text="@string/copyNoteEdits" + android:textSize="11sp" /> - - + android:layout_weight="1" + android:gravity="center" + android:orientation="vertical" + android:padding="12dp" + android:visibility="@{valuesText ? View.VISIBLE : View.GONE}"> + android:src="@drawable/ic_translate" + app:tint="?android:colorPrimary" /> - + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:ellipsize="end" + android:maxLines="1" + android:text="@string/translateNote" + android:textSize="11sp" /> - - + android:layout_weight="1" + android:gravity="center" + android:orientation="vertical" + android:padding="12dp"> + android:id="@+id/quickPinIcon" + android:layout_width="24dp" + android:layout_height="24dp" + android:contentDescription="@string/pinNote" + android:src="@drawable/ic_pin" + app:tint="?android:colorPrimary" /> - - - - - - + android:layout_marginTop="4dp" + android:ellipsize="end" + android:maxLines="1" + android:text="@string/pinNote" + android:textSize="11sp" /> + - + - + + + + + + + + android:layout_gravity="center_vertical" + android:alpha="0.6" + android:paddingEnd="4dp" + android:textSize="13sp" + tools:text="Simple" /> + + - + - - + + + - + + - + - + - - + - + - + - + + + + + + - - + - + + + + + + - + - + + + + + + - - + + + + + + + + - + + + - + + + + + + + + android:layout_gravity="center_vertical" + android:alpha="0.6" + android:paddingEnd="4dp" + android:textSize="12sp" + android:visibility="gone" + tools:text="12 Jan · 17:00" /> + - - + + + + + + + + + + + + + + - \ No newline at end of file + diff --git a/app/src/main/res/values-be/strings.xml b/app/src/main/res/values-be/strings.xml index 3c2ad20..67dc17f 100644 --- a/app/src/main/res/values-be/strings.xml +++ b/app/src/main/res/values-be/strings.xml @@ -399,4 +399,8 @@ %d слова + Рэдактар + Просты + Пашыраны + \ No newline at end of file diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index c7826c2..f96902f 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -399,4 +399,8 @@ %d Wörter + Editor + Einfach + Erweitert + \ No newline at end of file diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 8cd93c6..1662dc3 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -443,4 +443,8 @@ %d words + Editor + Simple + Extended + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index ed27f68..0ca393f 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -400,4 +400,8 @@ %d palabras + Editor + Simple + Extendido + \ No newline at end of file diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 206dc7c..0579b91 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -395,4 +395,8 @@ %d mots + Éditeur + Simple + Étendu + \ No newline at end of file diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 65f1b4e..20e80e3 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -397,4 +397,8 @@ %d parole + Editor + Semplice + Esteso + \ No newline at end of file diff --git a/app/src/main/res/values-kk/strings.xml b/app/src/main/res/values-kk/strings.xml index 47e9eb0..927c5da 100644 --- a/app/src/main/res/values-kk/strings.xml +++ b/app/src/main/res/values-kk/strings.xml @@ -395,4 +395,8 @@ %d сөз + Редактор + Қарапайым + Кеңейтілген + \ No newline at end of file diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index aa85af8..2b866a4 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -400,4 +400,8 @@ %d słowa + Edytor + Prosty + Rozszerzony + \ No newline at end of file diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 504db67..52ebe6a 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -404,4 +404,8 @@ %d слова + Редактор + Простой + Расширенный + \ No newline at end of file diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index f80625b..3b91a49 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -425,4 +425,8 @@ %d слова + Редактор + Простий + Розширений + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 76ee5d0..797d855 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -455,5 +455,10 @@ 30 min 60 min + + Editor + Simple + Extended + diff --git a/app/src/test/java/com/pasich/mynotes/utils/ReminderRepeatTest.java b/app/src/test/java/com/pasich/mynotes/utils/ReminderRepeatTest.java new file mode 100644 index 0000000..37bce17 --- /dev/null +++ b/app/src/test/java/com/pasich/mynotes/utils/ReminderRepeatTest.java @@ -0,0 +1,50 @@ +package com.pasich.mynotes.utils; + +import static org.junit.Assert.assertEquals; + +import com.pasich.mynotes.data.model.ReminderRepeat; +import org.junit.Test; + +public class ReminderRepeatTest { + + @Test + public void from_null_returnsNone() { + assertEquals(ReminderRepeat.NONE, ReminderRepeat.from(null)); + } + + @Test + public void from_emptyString_returnsNone() { + assertEquals(ReminderRepeat.NONE, ReminderRepeat.from("")); + } + + @Test + public void from_unknownString_returnsNone() { + assertEquals(ReminderRepeat.NONE, ReminderRepeat.from("GARBAGE")); + } + + @Test + public void from_noneString_returnsNone() { + assertEquals(ReminderRepeat.NONE, ReminderRepeat.from("NONE")); + } + + @Test + public void from_daily_returnsDaily() { + assertEquals(ReminderRepeat.DAILY, ReminderRepeat.from("DAILY")); + } + + @Test + public void from_weekly_returnsWeekly() { + assertEquals(ReminderRepeat.WEEKLY, ReminderRepeat.from("WEEKLY")); + } + + @Test + public void from_monthly_returnsMonthly() { + assertEquals(ReminderRepeat.MONTHLY, ReminderRepeat.from("MONTHLY")); + } + + @Test + public void from_lowercaseDaily_returnsNone() { + // Case-sensitive: lowercase is not a valid enum value + assertEquals(ReminderRepeat.NONE, ReminderRepeat.from("daily")); + } +} From 172d16d82051ea3266f80db1301d594c212b5ba5 Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 22:38:00 +0300 Subject: [PATCH 10/13] fix: stop move-animation overlap and scroll to top when edited note becomes first What it covers: - endAnimations() before invalidateSpanAssignments() kills the in-flight SGLM move animation that was causing the visual gap/overlap when the moved-to position (0) was off-screen - previousTopId detection triggers scrollToPositionWithOffset(0, 0) when an edited note bubbles to the top, so the user sees it immediately without the broken animation artifact - Removed setHasStableIds (done last session) and all diagnostic logging --- .../mainActivity/MainRenderListsController.java | 10 ++++++++++ .../mynotes/ui/view/activity/MainActivity.java | 17 +++++++++++++---- .../utils/adapters/notes/NoteAdapter.java | 6 ------ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java index 15dd631..949ac70 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java @@ -6,6 +6,7 @@ import android.view.animation.DecelerateInterpolator; import android.view.animation.OvershootInterpolator; import androidx.annotation.Nullable; +import androidx.recyclerview.widget.StaggeredGridLayoutManager; import com.pasich.mynotes.R; import com.pasich.mynotes.data.model.Tag; import com.pasich.mynotes.databinding.ActivityMainBinding; @@ -62,6 +63,15 @@ public void scrollUpNoteList() { binding.listNotes.post(() -> binding.listNotes.smoothScrollToPosition(0)); } + public void scrollToTopInstant() { + binding.listNotes.post( + () -> { + StaggeredGridLayoutManager lm = + (StaggeredGridLayoutManager) binding.listNotes.getLayoutManager(); + if (lm != null) lm.scrollToPositionWithOffset(0, 0); + }); + } + /** Smoothly shows the notes list using fade + scale animation. */ private void animateShowList(View list) { if (list.getVisibility() == View.VISIBLE) return; diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java index 8e7a47b..bdedf60 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java @@ -3,7 +3,6 @@ import static com.pasich.mynotes.utils.navigation.ActivityResultKeys.EXTRA_UPDATE_THEME_STYLE; import static com.pasich.mynotes.utils.navigation.ActivityResultKeys.RESULT_CODE_THEME_UPDATE; -import android.annotation.SuppressLint; import android.app.Activity; import android.content.Intent; import android.os.Bundle; @@ -203,27 +202,37 @@ private void renderTags(List tags) { tagsAdapter.submitList(tags); } - @SuppressLint("NotifyDataSetChanged") private void renderNotes(List notes, Tag currentSelectedTag, UiEvent event) { switch (event) { case SORT_CHANGED, TAG_CHANGED -> mNoteAdapter.submitList( new ArrayList<>(notes), () -> { + RecyclerView.ItemAnimator animator = + mActivityBinding.listNotes.getItemAnimator(); + if (animator != null) animator.endAnimations(); mainRenderListsController.showStateNoteList( currentSelectedTag, notes.size()); - mNoteAdapter.notifyDataSetChanged(); + gridLayoutManager.invalidateSpanAssignments(); mainRenderListsController.animateNoteListChange(); }); default -> { boolean shouldScrollUp = event == UiEvent.NOTE_CREATED; + List currentList = mNoteAdapter.getCurrentList(); + int previousTopId = currentList.isEmpty() ? -1 : currentList.get(0).getId(); mNoteAdapter.submitList( - notes, + new ArrayList<>(notes), () -> { + RecyclerView.ItemAnimator animator = + mActivityBinding.listNotes.getItemAnimator(); + if (animator != null) animator.endAnimations(); mainRenderListsController.showStateNoteList( currentSelectedTag, notes.size()); + gridLayoutManager.invalidateSpanAssignments(); if (shouldScrollUp) { mainRenderListsController.scrollUpNoteList(); + } else if (!notes.isEmpty() && notes.get(0).getId() != previousTopId) { + mainRenderListsController.scrollToTopInstant(); } }); } diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java index 096996f..07c5db1 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java @@ -23,18 +23,12 @@ public class NoteAdapter extends ListAdapter { @Inject public NoteAdapter() { super(new NoteDiff()); - setHasStableIds(true); } public void setSelectionController(SelectionController controller) { this.selectionController = controller; } - @Override - public long getItemId(int position) { - return getItem(position).getId(); - } - @NonNull @Override public NoteHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { From 538d98f76a9c5ed58dcdc94233c752959f766ae5 Mon Sep 17 00:00:00 2001 From: pasichDev Date: Mon, 18 May 2026 22:59:56 +0300 Subject: [PATCH 11/13] docs: add Javadoc to all main packages, fix CI release notes and version --- .github/workflows/ci-cd.yml | 19 ++++++------ .../mynotes/cache/AppPreferencesCache.java | 5 --- .../cache/NotificationPreferencesCache.java | 2 ++ .../mynotes/data/database/AppDatabase.java | 1 + .../mynotes/data/database/dao/NoteDao.java | 1 + .../mynotes/data/database/dao/TagsDao.java | 1 + .../mynotes/data/database/dao/TaskDao.java | 1 + .../data/database/helpers/StatsHelper.java | 1 + .../mynotes/data/model/DonationProduct.java | 1 + .../mynotes/data/model/IndexFilter.java | 1 + .../com/pasich/mynotes/data/model/Note.java | 7 +++++ .../mynotes/data/model/ReminderRepeat.java | 2 ++ .../com/pasich/mynotes/data/model/Tag.java | 3 ++ .../com/pasich/mynotes/data/model/Task.java | 1 + .../preferences/AppPreferencesHelper.java | 3 ++ .../utils/EditorAttachmentsWebViewClient.java | 2 +- .../extendedEditor/utils/EditorJsScheme.java | 1 + .../view/AttachmentActionsDialog.java | 2 ++ .../view/OnAttachmentDeleteListener.java | 1 + .../mynotes/ui/contract/BackupContract.java | 23 ++++++++++++++ .../mynotes/ui/contract/MainContract.java | 26 ++++++++++++++++ .../mynotes/ui/contract/NoteContract.java | 31 +++++++++++++++++++ .../mynotes/ui/contract/TagsContract.java | 21 +++++++++++++ .../mynotes/ui/contract/TasksContract.java | 15 +++++++++ .../mynotes/ui/contract/TrashContract.java | 8 +++++ .../ui/controllers/SelectionController.java | 9 ++++-- .../mainActivity/AppUpdateController.java | 8 +++-- .../MainRenderListsController.java | 4 +++ .../mainActivity/NavigationController.java | 5 +++ .../mainActivity/SearchController.java | 4 +++ .../mynotes/ui/presenter/BackupPresenter.java | 3 +- .../mynotes/ui/presenter/MainPresenter.java | 12 ++----- .../mynotes/ui/presenter/NotePresenter.java | 1 + .../mynotes/ui/presenter/TagsPresenter.java | 3 +- .../mynotes/ui/presenter/TasksPresenter.java | 1 + .../mynotes/ui/presenter/TrashPresenter.java | 1 + .../mynotes/ui/receiver/BootReceiver.java | 1 + .../mynotes/ui/receiver/ReminderReceiver.java | 1 + .../mynotes/ui/state/MainViewState.java | 1 + .../pasich/mynotes/ui/state/StatsData.java | 1 + .../com/pasich/mynotes/ui/state/UiEvent.java | 1 + .../ui/view/activity/BackupActivity.java | 1 + .../ui/view/activity/MainActivity.java | 3 +- .../ui/view/activity/ShareActivity.java | 1 - .../ui/view/activity/SnoozeActivity.java | 1 + .../ui/view/activity/TasksActivity.java | 1 + .../ui/view/activity/TrashActivity.java | 1 + .../activity/noteEditor/NoteActivity.java | 1 + .../utils/adapters/notes/NoteAdapter.java | 2 ++ .../searchAdapter/SearchNotesAdapter.java | 1 + .../adapters/tagAdapter/TagsAdapter.java | 1 + .../mynotes/utils/file/FileExportUtils.java | 12 ++----- .../utils/file/HtmlTemplateGenerator.java | 7 ----- .../mynotes/utils/file/ImageOptimizer.java | 1 - .../mynotes/utils/file/SafeImageLoader.java | 4 --- .../utils/managers/BillingManager.java | 5 +++ .../utils/navigation/NoteNavigator.java | 3 ++ .../utils/recycler/diffutil/NoteDiff.java | 1 + .../utils/reminder/ReminderManager.java | 6 ++++ .../utils/reminder/TaskReminderManager.java | 4 +++ 60 files changed, 233 insertions(+), 58 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 088ec2c..b00d491 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -14,6 +14,8 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 + with: + fetch-depth: 0 - name: Set up JDK 17 uses: actions/setup-java@v4 @@ -47,7 +49,7 @@ jobs: run: | CODE=$(grep -oP '(?<=def appVersionCode = )\d+' app/build.gradle | head -n1) echo "code=$CODE" >> $GITHUB_OUTPUT - echo "name=2.5.$CODE" >> $GITHUB_OUTPUT + echo "name=2.6.$CODE" >> $GITHUB_OUTPUT - name: Decode keystore run: echo "${{ secrets.KEYSTORE_BASE64 }}" | base64 -d > /tmp/release.jks @@ -72,14 +74,13 @@ jobs: id: notes run: | PREV=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") - if [ -n "$PREV" ]; then - NOTES=$(git log "$PREV"..HEAD --pretty=format:"- %s" --no-merges) - else - NOTES=$(git log --pretty=format:"- %s" --no-merges | head -30) - fi - echo "notes<> $GITHUB_OUTPUT - echo "$NOTES" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT + RANGE=${PREV:+"$PREV..HEAD"} + { + echo 'notes<> "$GITHUB_OUTPUT" - name: Create GitHub Release uses: ncipollo/release-action@v1 diff --git a/app/src/main/java/com/pasich/mynotes/cache/AppPreferencesCache.java b/app/src/main/java/com/pasich/mynotes/cache/AppPreferencesCache.java index 0eb659f..8ab06bf 100644 --- a/app/src/main/java/com/pasich/mynotes/cache/AppPreferencesCache.java +++ b/app/src/main/java/com/pasich/mynotes/cache/AppPreferencesCache.java @@ -12,7 +12,6 @@ public class AppPreferencesCache { private static final String TAG = "AppPreferencesCache"; private final SafePreferences prefs; - // Cached values private volatile String lastKnownVersion; private volatile String sortPref; private volatile String tagsSortPref; @@ -71,8 +70,6 @@ private void setDefaults() { initialized = true; } - // ===================== GETTERS ===================== - public String getLastKnownVersion() { ensureInitialized(); return lastKnownVersion; @@ -143,8 +140,6 @@ public synchronized void setFormatPref(int format) { } } - // ===================== HELPERS ===================== - private void ensureInitialized() { if (!initialized) { Log.w(TAG, "Cache not initialized, initializing now"); diff --git a/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java b/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java index 016d91e..d067535 100644 --- a/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java +++ b/app/src/main/java/com/pasich/mynotes/cache/NotificationPreferencesCache.java @@ -6,6 +6,7 @@ import javax.inject.Inject; import javax.inject.Singleton; +/** Cache for notification channel and sound preferences. */ @Singleton public class NotificationPreferencesCache { @@ -86,6 +87,7 @@ public int getChannelVersion() { return channelVersion; } + /** Increments the channel version, persists it, and updates the channel ID. */ public synchronized int incrementAndPersistVersion() { ensureInitialized(); channelVersion++; diff --git a/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java b/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java index dd612e0..45748b6 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/AppDatabase.java @@ -21,6 +21,7 @@ import com.pasich.mynotes.utils.constants.settings.PreferencesConfig; import javax.inject.Singleton; +/** Room database definition with all migrations. */ @Database( version = DatabaseConstants.DB_VERSION, entities = {Tag.class, Note.class, Task.class, TaskCategory.class}, diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java index ce1fd4c..625b2db 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/NoteDao.java @@ -10,6 +10,7 @@ import io.reactivex.Single; import java.util.List; +/** DAO for note CRUD and reminder operations. */ @Dao public interface NoteDao { diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/TagsDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/TagsDao.java index b449f14..f99f3a1 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/TagsDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/TagsDao.java @@ -11,6 +11,7 @@ import io.reactivex.Flowable; import java.util.List; +/** DAO for tag CRUD operations. */ @Dao public interface TagsDao { @Query("SELECT * FROM tags") diff --git a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java index 631aaa9..38e09ce 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/dao/TaskDao.java @@ -9,6 +9,7 @@ import io.reactivex.Flowable; import java.util.List; +/** DAO for task CRUD and reminder operations. */ @Dao public interface TaskDao { diff --git a/app/src/main/java/com/pasich/mynotes/data/database/helpers/StatsHelper.java b/app/src/main/java/com/pasich/mynotes/data/database/helpers/StatsHelper.java index 0fd5aed..0cb77d0 100644 --- a/app/src/main/java/com/pasich/mynotes/data/database/helpers/StatsHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/database/helpers/StatsHelper.java @@ -2,6 +2,7 @@ import io.reactivex.Flowable; +/** Provides reactive queries for app usage statistics. */ public interface StatsHelper { Flowable getNotesCount(); diff --git a/app/src/main/java/com/pasich/mynotes/data/model/DonationProduct.java b/app/src/main/java/com/pasich/mynotes/data/model/DonationProduct.java index d97a527..f817c2b 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/DonationProduct.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/DonationProduct.java @@ -1,5 +1,6 @@ package com.pasich.mynotes.data.model; +/** Model representing an in-app donation product. */ public class DonationProduct { private String id; private String title; diff --git a/app/src/main/java/com/pasich/mynotes/data/model/IndexFilter.java b/app/src/main/java/com/pasich/mynotes/data/model/IndexFilter.java index 5077577..2858c17 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/IndexFilter.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/IndexFilter.java @@ -1,5 +1,6 @@ package com.pasich.mynotes.data.model; +/** Holds search-match character indices within a note. */ public class IndexFilter { private int indexTitle, indexValue; diff --git a/app/src/main/java/com/pasich/mynotes/data/model/Note.java b/app/src/main/java/com/pasich/mynotes/data/model/Note.java index 7d80c95..a1069e2 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/Note.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/Note.java @@ -7,6 +7,7 @@ import com.google.gson.annotations.SerializedName; import java.util.Objects; +/** Room entity representing a single note. */ @Entity(tableName = "notes") public class Note { @@ -57,6 +58,7 @@ public class Note { @androidx.room.ColumnInfo(name = "reminderIntervalMinutes") private int reminderIntervalMinutes = 0; + /** Initializes note fields and returns this instance. */ public Note create(String title, String value, long date, String tag) { this.title = title; this.tag = tag; @@ -116,6 +118,7 @@ public void setValue(String value) { this.value = value; } + /** Returns up to 400 characters of the note body. */ public String getValuePreview() { if (value == null) { return ""; // or return some default value if you prefer @@ -151,6 +154,7 @@ public String getAttachments() { return attachments; } + /** Returns true if the note has at least one valid attachment. */ public boolean isAttachments() { String mAttachments = attachments; if (mAttachments == null) return false; @@ -186,6 +190,7 @@ public void setReminderRepeat(String reminderRepeat) { this.reminderRepeat = reminderRepeat != null ? reminderRepeat : "NONE"; } + /** Returns true if the note has a future reminder set. */ public boolean hasReminder() { return reminderTime != null && reminderTime > System.currentTimeMillis(); } @@ -206,6 +211,7 @@ public void setReminderIntervalMinutes(int reminderIntervalMinutes) { this.reminderIntervalMinutes = reminderIntervalMinutes; } + /** Copies all mutable fields from another note into this one. */ public void copyFrom(Note other) { if (other == null) return; this.title = other.title; @@ -220,6 +226,7 @@ public void copyFrom(Note other) { this.reminderIntervalMinutes = other.reminderIntervalMinutes; } + /** Creates a new note copy without id, pinning, or reminder. */ public Note duplicate() { Note c = new Note(); c.setId(0); diff --git a/app/src/main/java/com/pasich/mynotes/data/model/ReminderRepeat.java b/app/src/main/java/com/pasich/mynotes/data/model/ReminderRepeat.java index dd1b575..8f3ca28 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/ReminderRepeat.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/ReminderRepeat.java @@ -1,11 +1,13 @@ package com.pasich.mynotes.data.model; +/** Repeat interval options for note reminders. */ public enum ReminderRepeat { NONE, DAILY, WEEKLY, MONTHLY; + /** Parses a string to a ReminderRepeat value, defaulting to NONE. */ public static ReminderRepeat from(String value) { if (value == null) return NONE; try { diff --git a/app/src/main/java/com/pasich/mynotes/data/model/Tag.java b/app/src/main/java/com/pasich/mynotes/data/model/Tag.java index 3d92dc4..f2feee4 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/Tag.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/Tag.java @@ -7,6 +7,7 @@ import androidx.room.PrimaryKey; import com.google.gson.annotations.SerializedName; +/** Room entity representing a note tag. */ @Entity(tableName = "tags") public class Tag { @@ -84,6 +85,7 @@ public void setVisibility(int arg0) { this.visibility = arg0; } + /** Sets visibility and returns this tag for chaining. */ public Tag setVisibilityReturn(int arg0) { this.visibility = arg0; return this; @@ -97,6 +99,7 @@ public void setPosition(int position) { this.position = position; } + /** Returns a shallow copy of this tag including transient fields. */ public Tag copy() { Tag t = new Tag(); diff --git a/app/src/main/java/com/pasich/mynotes/data/model/Task.java b/app/src/main/java/com/pasich/mynotes/data/model/Task.java index 77d2d00..a2b6878 100644 --- a/app/src/main/java/com/pasich/mynotes/data/model/Task.java +++ b/app/src/main/java/com/pasich/mynotes/data/model/Task.java @@ -6,6 +6,7 @@ import androidx.room.Ignore; import androidx.room.PrimaryKey; +/** Room entity representing a single task item. */ @Entity(tableName = "tasks") public class Task { diff --git a/app/src/main/java/com/pasich/mynotes/data/preferences/AppPreferencesHelper.java b/app/src/main/java/com/pasich/mynotes/data/preferences/AppPreferencesHelper.java index 08a456a..5a9716d 100644 --- a/app/src/main/java/com/pasich/mynotes/data/preferences/AppPreferencesHelper.java +++ b/app/src/main/java/com/pasich/mynotes/data/preferences/AppPreferencesHelper.java @@ -7,6 +7,7 @@ import javax.inject.Inject; import javax.inject.Singleton; +/** Singleton implementation of PreferenceHelper backed by caches. */ @Singleton public class AppPreferencesHelper implements PreferenceHelper { @@ -55,6 +56,7 @@ public void editSizeTextNoteActivity(int value) { themeCache.setSizeTextNoteActivity(value); } + /** Returns a snapshot of all current app preferences. */ @Override public PreferencesBackup getListPreferences() { return new PreferencesBackup( @@ -86,6 +88,7 @@ public PreferencesBackup getListPreferences() { PreferencesConfig.ARGUMENT_DEFAULT_UI_SCALING_VALUE)); } + /** Persists all fields from a backup and refreshes the caches. */ @Override public void setListPreferences(PreferencesBackup preferences) { diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorAttachmentsWebViewClient.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorAttachmentsWebViewClient.java index 23cf80c..4eefcf4 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorAttachmentsWebViewClient.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorAttachmentsWebViewClient.java @@ -16,6 +16,7 @@ import java.io.FileInputStream; import java.util.List; +/** WebViewClient that intercepts editorjs:// requests to serve local attachment files. */ public class EditorAttachmentsWebViewClient extends WebViewClient { private static final String TAG = "EditorAttachmentsClient"; @@ -47,7 +48,6 @@ public WebResourceResponse shouldInterceptRequest(WebView view, WebResourceReque int noteId = Integer.parseInt(noteStr.replace("note_", "")); - // Формуємо правильний EditorAttachment URL String internalUrl = new Uri.Builder() .scheme(EDITORJS_SCHEME) diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJsScheme.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJsScheme.java index b547e50..6cfc0af 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJsScheme.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/utils/EditorJsScheme.java @@ -1,5 +1,6 @@ package com.pasich.mynotes.extendedEditor.utils; +/** Defines the custom URI scheme used by Editor.js in the WebView. */ public class EditorJsScheme { public static final String EDITORJS_SCHEME = "editorjs"; } diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/view/AttachmentActionsDialog.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/view/AttachmentActionsDialog.java index 3d13b19..eb1c43e 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/view/AttachmentActionsDialog.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/view/AttachmentActionsDialog.java @@ -22,9 +22,11 @@ import java.io.FileInputStream; import java.io.OutputStream; +/** Bottom-sheet dialog offering open, download, and delete actions for an attachment. */ public class AttachmentActionsDialog { private static final String TAG = "AttachmentActionsDialog"; + /** Shows the actions bottom sheet for the given attachment. */ @SuppressLint("UseCompatLoadingForDrawables") public static void show( Context ctx, EditorAttachment attachment, OnAttachmentDeleteListener deleteListener) { diff --git a/app/src/main/java/com/pasich/mynotes/extendedEditor/view/OnAttachmentDeleteListener.java b/app/src/main/java/com/pasich/mynotes/extendedEditor/view/OnAttachmentDeleteListener.java index 131d4c9..108314c 100644 --- a/app/src/main/java/com/pasich/mynotes/extendedEditor/view/OnAttachmentDeleteListener.java +++ b/app/src/main/java/com/pasich/mynotes/extendedEditor/view/OnAttachmentDeleteListener.java @@ -2,6 +2,7 @@ import com.pasich.mynotes.extendedEditor.models.EditorAttachment; +/** Callback invoked when an attachment is requested to be deleted. */ public interface OnAttachmentDeleteListener { void onDeleteAttachment(EditorAttachment attachment, boolean fileLost); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/BackupContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/BackupContract.java index b4dd0c1..ed27d98 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/BackupContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/BackupContract.java @@ -9,56 +9,79 @@ import com.pasich.mynotes.utils.backup.models.googleKeep.GoogleKeepImportResult; import dagger.hilt.android.scopes.ActivityScoped; +/** Contract for the backup and restore screen. */ public interface BackupContract { interface view extends BaseView { + /** Initialises the activity UI and listeners. */ void initActivity(); + /** Launches the system file-picker to write a backup. */ void openIntentSaveBackup(JsonBackup jsonBackup); + /** Launches the system file-picker to read a backup. */ void openIntentReadBackup(); + /** Shows the confirmation dialog before restoring data. */ void dialogRestoreData(boolean local); + /** Called when a restore operation completes with a result code. */ void restoreFinish(int infoCode); + /** Shows the in-progress dialog during restore. */ void showProcessRestoreDialog(); + /** Notifies the user that there is no data available to back up. */ void emptyDataToBackup(); + /** Called after a local copy write attempt; error is true on failure. */ void createLocalCopyFinish(boolean error); + /** Shows a localised error message identified by errorCode and string. */ void showErrorsText(int errorCode, @StringRes int string); + /** Opens the share sheet for the given notes list. */ void openShareOptionsDialog(java.util.List notes, boolean isDataExport); + /** Displays the result summary of a Google Keep import. */ void showImportResultOtherApp(GoogleKeepImportResult result); + /** Registers the import result callback to confirm the import. */ void setupImportCallbackOtherApp(GoogleKeepImportResult result); + /** Processes the user-selected file URI from another app. */ void processSelectedFileOtherApp(Uri fileUri); + /** Sets a flag indicating a successful restore for the calling screen. */ void onRestoreSuccessFlag(); } @ActivityScoped interface presenter extends BasePresenter { + /** Initiates the backup-save flow for local or cloud storage. */ void saveBackupPresenter(boolean local); + /** Initiates the backup-restore flow for local or cloud storage. */ void restoreBackupPresenter(boolean local); + /** Writes the cached backup to the file at the given URI. */ void writeFileBackupLocal(Uri mUri); + /** Reads and restores a backup from the file at the given URI. */ void readFileBackupLocal(Uri mUri); + /** Exports all notes via the share sheet. */ void exportAllNotesPresenter(); + /** Imports parsed Google Keep data into the app database. */ void importDataOtherApp(GoogleKeepImportResult result); + /** Reads a Google Keep ZIP archive from the given URI. */ void importFromZipOtherApp(Uri fileUri); + /** Forwards the selected file URI to the view for processing. */ void startProcessSelectedFileOtherApp(Uri fileUri); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/MainContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/MainContract.java index 98f60c0..53295c3 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/MainContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/MainContract.java @@ -12,61 +12,87 @@ import java.util.ArrayList; import java.util.List; +/** Contract for the main notes list screen. */ public interface MainContract { interface view extends BaseView, MoreNoteMainActivityView { + /** Renders the full main screen state. */ void render(MainViewState state); + /** Configures the notes list settings. */ void settingsLists(); + /** Opens a new note with the given database id. */ void openNewNoteWithId(long id); + /** Shows the tag options dialog anchored to the given view. */ void choiceTagDialog(Tag tag, View mView); + /** Shows the note options dialog for the given position. */ void choiceNoteDialog(Note note, int position); + /** Shows the confirmation dialog to delete a tag. */ void startDeleteTagDialog(Tag tag); + /** Renders search results to the UI. */ void renderSearch(List filtered); + /** Updates the drawer statistics panel. */ void renderDrawerStats(StatsData stats); + /** Shows the dialog to select a tag from the full list. */ void allTagSelectDialog(List tagsList); + /** Shows the dialog to bulk-change tags on selected notes. */ void multipleTagChangerDialog(List tagsList); } @ActivityScoped interface presenter extends BasePresenter { + /** Handles the new-note FAB click. */ void newNotesClick(); + /** Moves an array of notes to trash. */ void deleteNotesArray(ArrayList notes); + /** Moves a single note to trash. */ void noteMoveToTrash(Note note); + /** Restores the last note that was moved to trash. */ void restoreNoteLastMoveToTrash(Note nNote); + /** Requests deletion of the given tag. */ void requestDeleteTag(Tag tag); + /** Persists a visibility change on the given tag. */ void editVisibleTag(Tag tag); + /** Returns the note saved before deletion for undo purposes. */ Note getBackupDeleteNote(); + /** Stores the note that will be used as the undo target. */ void setBackupDeleteNote(Note backupDeleteNote); + /** Called when the user selects a tag filter. */ void onTagSelected(Tag tag); + /** Called when the user changes the sort order. */ void onSortChanged(String newSort); + /** Clears the pending UI event after it has been consumed. */ void clearUiEvent(); + /** Updates the active search query. */ void updateSearchQuery(String query); + /** Restricts the search to the given tag name. */ void updateSearchTagFilter(String tagName); + /** Opens the tag-selection dialog; multiple controls single vs. multi mode. */ void requestTagSelection(boolean multiple); + /** Assigns selectedTag to all notes identified by notesIds. */ void requestTagChangeMultipleNotes(String selectedTag, List notesIds); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/NoteContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/NoteContract.java index fedab24..d03e8ae 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/NoteContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/NoteContract.java @@ -8,72 +8,103 @@ import com.pasich.mynotes.data.model.Note; import com.pasich.mynotes.utils.enums.SaveState; +/** Contract for the note editor screen. */ public interface NoteContract { interface view extends BaseView, ActionBar, MoreNoteNoteActivityView { + /** Reads and applies Activity intent parameters. */ void initParam(); + /** Detects whether the Activity opens a new or existing note. */ void initTypeActivity(); + /** Closes the note editor screen. */ void closeNoteActivity(); + /** Activates the editing mode in the UI. */ void activatedActivity(); + /** Populates the editor with the given note data. */ void loadingNote(Note note); + /** Updates the save-status indicator in the toolbar. */ void updateSaveStatus(SaveState saveState); + /** Triggers cleanup of orphaned attachments for the note. */ void runAttachmentsCleanup(Note note); + /** Plays the copy-success animation. */ void runCopyAnimation(); + /** Called after the note has been duplicated with its new id. */ void onNoteCopied(long newNoteId); + /** Reloads the extended editor with the current note content. */ void reloadExtendedEditor(); } interface presenter extends BasePresenter { + /** Saves pending changes and closes the note screen. */ void closeActivity(); + /** Extracts note id and flags from the launch Intent. */ void getLoadIntentData(Intent mIntent); + /** Loads the note with the given id from the database. */ void loadingData(long idNote); + /** Puts the note into edit mode. */ void activateEditNote(); + /** Returns the current note's database id. */ long getIdKey(); + /** Sets the current note's database id. */ void setIdKey(long idKey); + /** Returns the in-memory note being edited. */ Note getNote(); + /** Replaces the in-memory note. */ void setNote(Note mNote); + /** Returns true if this is a newly created note. */ boolean getNewNotesKey(); + /** Marks whether the current note is newly created. */ void setNewNoteKey(boolean newNoteKey); + /** Returns the Android Typeface constant for the given text-style name. */ int getTypeFace(String textStyle); + /** Notifies the presenter that the note content has changed. */ void onNoteChanged(); + /** Returns true if the extended (rich) editor is active. */ boolean getExtendedEditor(); + /** Enables or disables the extended editor mode. */ void setExtendedEditor(boolean extendedEditor); + /** Applies an extended-editor change to the current note. */ void extendedNoteChange(String title, String jsonData); + /** Applies a plain-text editor change to the current note. */ void simpleNoteChange(String title, String value, boolean emergencySave); + /** Returns true if a note is currently loaded. */ boolean hasNote(); + /** Requests duplication of the current note. */ void copyNoteRequest(); } interface AutoSaveCallback { + /** Called when the note was saved successfully. */ void onSuccess(); + /** Called when saving the note failed. */ void onError(Throwable error); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/TagsContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/TagsContract.java index bfa3e31..2d53ae1 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/TagsContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/TagsContract.java @@ -9,52 +9,73 @@ import dagger.hilt.android.scopes.ActivityScoped; import java.util.List; +/** Contract for the tag management screen. */ public interface TagsContract { interface view extends BaseView, ActionBar { + /** Initialises the tags RecyclerView and its adapter. */ void setupRecyclerView(); + /** Submits the tag list to the adapter. */ void loadTags(List tags); + /** Shows the dialog to create a tag at the given position. */ void showCreateTagDialog(int newPosition); + /** Shows the dialog to rename the given tag. */ void showEditTagDialog(Tag tag); + /** Shows the confirmation dialog to delete the given tag. */ void showDeleteTagDialog(Tag tag); + /** Shows the contextual options popup for the given tag. */ void showTagOptionsDialog(Tag tag, View anchorView); + /** Shows a short toast with the given text. */ void showToastMessage(String message); + /** Shows a short toast using a string resource id. */ void showToastMessage(@StringRes int message); + /** Notifies the user that the maximum tag count has been reached. */ void showToastCheckCountTags(); + /** Opens the sort-order selection dialog. */ void showSortDialog(); } @ActivityScoped interface presenter extends BasePresenter { + /** Loads and displays the current tag list. */ void loadTags(); + /** Toggles the visibility flag of the given tag. */ void toggleTagVisibility(Tag tag); + /** Handles the add-tag button click. */ void onAddTagClick(); + /** Handles a long-press on a tag item. */ void onTagLongClick(Tag tag, View anchorView); + /** Applies a new sort parameter and refreshes the list. */ void sortTags(String sortParam); + /** Persists new positions after a drag-and-drop reorder. */ void onDragCompleted(List currentTagOrder); + /** Handles the sort-menu button click. */ void onSortMenuClick(); + /** Returns the number of notes associated with the given tag. */ void getTagNotesCount(Tag tag, TagNotesCountCallback callback); } + /** Callback for receiving the note count of a tag. */ interface TagNotesCountCallback { + /** Called with the number of notes that belong to the tag. */ void onTagNotesCountReceived(int count); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/TasksContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/TasksContract.java index a6d0a51..b442ac9 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/TasksContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/TasksContract.java @@ -7,38 +7,53 @@ import dagger.hilt.android.scopes.ActivityScoped; import java.util.List; +/** Contract for the tasks (to-do) screen. */ public interface TasksContract { interface view extends BaseView { + /** Renders active and completed task lists. */ void renderTasks(List active, List completed); + /** Renders the category chip list. */ void renderCategories(List categories); + /** Wires all UI event listeners. */ void initListeners(); } @ActivityScoped interface presenter extends BasePresenter { + /** Filters tasks by the given category id. */ void onCategorySelected(int categoryId); + /** Creates and persists a new task. */ void addTask(String title, String description, int categoryId); + /** Updates the title and description of an existing task. */ void editTask(Task task, String newTitle, String newDescription); + /** Toggles the done state of the given task. */ void toggleTask(Task task); + /** Permanently deletes the given task. */ void deleteTask(Task task); + /** Removes all completed tasks. */ void clearCompleted(); + /** Creates and persists a new task category. */ void addCategory(String name, String colorHex); + /** Deletes a task category and its associated tasks. */ void deleteCategory(TaskCategory category); + /** Persists a new drag-and-drop ordering for the task list. */ void updatePositions(List tasks); + /** Schedules a reminder notification for the task at the given time. */ void setTaskReminder(Task task, long time); + /** Cancels the scheduled reminder for the given task. */ void clearTaskReminder(Task task); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/contract/TrashContract.java b/app/src/main/java/com/pasich/mynotes/ui/contract/TrashContract.java index f1c65f5..1bd366a 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/contract/TrashContract.java +++ b/app/src/main/java/com/pasich/mynotes/ui/contract/TrashContract.java @@ -7,24 +7,32 @@ import java.util.ArrayList; import java.util.List; +/** Contract for the trash (deleted notes) screen. */ public interface TrashContract { interface view extends BaseView, ActionBar { + /** Configures the recycler view for trash notes. */ void settingsNotesList(); + /** Shows the confirmation dialog to empty the trash. */ void cleanTrashDialogShow(); + /** Submits the trash note list to the adapter. */ void loadData(List trashList); } interface presenter extends BasePresenter { + /** Requests the view to show the empty-trash dialog. */ void cleanTrashDialogStart(); + /** Loads all trashed notes from the database. */ void loadingTrash(); + /** Permanently deletes all notes from the trash. */ void clearTrash(); + /** Restores the given notes from trash back to the main list. */ void restoreNotesArray(ArrayList notes); } } diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/SelectionController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/SelectionController.java index 7eac2a0..f0dbb90 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/SelectionController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/SelectionController.java @@ -10,6 +10,7 @@ import java.util.HashSet; import java.util.List; +/** Manages multi-select mode and the action panel for notes. */ public class SelectionController { private final HashSet selectedIds = new HashSet<>(); @@ -55,6 +56,7 @@ public boolean isInSelectionMode() { return selectionMode; } + /** Configures which action buttons are visible based on the given mode. */ public void setPanelMode(Mode mode) { switch (mode) { case NORMAL: @@ -71,6 +73,7 @@ public void setPanelMode(Mode mode) { } } + /** Enters selection mode with the given note as the first selected item. */ public void startSelection(Note note) { selectionMode = true; toggle(note); @@ -78,6 +81,7 @@ public void startSelection(Note note) { notifyListener(); } + /** Adds or removes a note from the selection set and updates its visual state. */ public void toggle(Note note) { int id = note.getId(); @@ -89,13 +93,11 @@ public void toggle(Note note) { updateNoteVisualState(id); - // If there are no more selections after toggling, exit the mode if (selectedIds.isEmpty()) { clearSelection(); return; } - // update count ui if (panel != null) { int count = selectedIds.size(); panel.selectedCount.setText( @@ -106,6 +108,7 @@ public void toggle(Note note) { notifyListener(); } + /** Deselects all notes, hides the action panel, and exits selection mode. */ public void clearSelection() { if (!selectionMode) return; @@ -121,6 +124,7 @@ public void clearSelection() { notifyListener(); } + /** Returns the full Note objects whose IDs are currently selected. */ public List getSelectedNotes() { List result = new ArrayList<>(); List data = adapter.getCurrentList(); @@ -153,6 +157,7 @@ private void notifyListener() { if (listener != null) listener.onSelectionCountChanged(selectedIds.size()); } + /** Removes all listeners and hides the panel to avoid memory leaks. */ public void cleanup() { panel.actionClose.setOnClickListener(null); panel.actionDelete.setOnClickListener(null); diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/AppUpdateController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/AppUpdateController.java index 9ba5e48..e52e84e 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/AppUpdateController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/AppUpdateController.java @@ -14,6 +14,7 @@ import com.pasich.mynotes.ui.view.dialogs.UpdateChangelogDialog; import com.pasich.mynotes.utils.UpdateChecker; +/** Handles in-app update checks and changelog display. */ public class AppUpdateController { private static final int REQUEST_UPDATE = 100; @@ -41,6 +42,7 @@ private void init() { checkForUpdate(); } + /** Resumes an in-progress immediate update flow if one was started. */ public void handleOnResume() { updateManager .getAppUpdateInfo() @@ -76,7 +78,7 @@ private void startImmediateUpdate(AppUpdateInfo info) { } } - /** Лише показ changelog-діалогу */ + /** Shows the changelog dialog if a new app version was detected. */ public void showChangelogIfNeeded() { if (updateChecker.hasNewVersion()) { UpdateChangelogDialog.newInstance() @@ -86,12 +88,12 @@ public void showChangelogIfNeeded() { } } - /** Потрібно NavigationController */ + /** Returns whether a newer app version is available. */ public boolean hasNewVersion() { return updateChecker.hasNewVersion(); } - /** Потрібно NavigationController */ + /** Launches the changelog screen via the registered activity launcher. */ public void openChangelog() { changelogLauncher.launch(new Intent(activity, ChangelogActivity.class)); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java index 949ac70..5beaf71 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/MainRenderListsController.java @@ -13,6 +13,7 @@ import com.pasich.mynotes.utils.managers.SystemTagsManager; import java.util.List; +/** Controls note list and empty-state rendering with animations. */ public class MainRenderListsController { private final ActivityMainBinding binding; @@ -23,6 +24,7 @@ public MainRenderListsController(ActivityMainBinding binding) { this.res = binding.getRoot().getResources(); } + /** Briefly fades and rescales the list to signal a dataset change. */ public void animateNoteListChange() { binding.listNotes .animate() @@ -59,10 +61,12 @@ public void showStateNoteList(@Nullable Tag selectedTag, int mNotesCount) { animateShowList(binding.listNotes); } + /** Smoothly scrolls the notes list to the top. */ public void scrollUpNoteList() { binding.listNotes.post(() -> binding.listNotes.smoothScrollToPosition(0)); } + /** Instantly jumps the staggered-grid list to position zero. */ public void scrollToTopInstant() { binding.listNotes.post( () -> { diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/NavigationController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/NavigationController.java index 8d15643..77e7b9f 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/NavigationController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/NavigationController.java @@ -25,6 +25,7 @@ import com.pasich.mynotes.ui.view.activity.TasksActivity; import com.pasich.mynotes.ui.view.activity.TrashActivity; +/** Manages the navigation drawer and app-level navigation actions. */ public class NavigationController { private final AppCompatActivity activity; @@ -50,6 +51,7 @@ public NavigationController( this.backHandler = backHandler; } + /** Sets up the drawer, navigation menu, header buttons, and back-press handling. */ public void init() { drawerLayout = binding.drawerLayout; headerBinding = NavHeaderMainBinding.bind(binding.navigationView.getHeaderView(0)); @@ -191,6 +193,7 @@ private void bindHeaderNewVersion(View header) { } } + /** Shows or hides the new-version badge in the navigation header. */ public void updateNewVersionIndicator(boolean visible) { View header = binding.navigationView.getHeaderView(0); if (header == null) return; @@ -244,6 +247,7 @@ public void handleShortcuts(Intent intent) { } } + /** Removes all click listeners and window-inset callbacks to prevent leaks. */ public void destroy() { binding.navigationView.setNavigationItemSelectedListener(null); @@ -271,6 +275,7 @@ public void destroy() { drawerLayout = null; } + /** Accumulates swipe-close distance for back-gesture detection. */ public void addSwipeClose(int delta) { this.swipeClose += delta; } diff --git a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/SearchController.java b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/SearchController.java index 70ae9e5..fac5a1a 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/SearchController.java +++ b/app/src/main/java/com/pasich/mynotes/ui/controllers/mainActivity/SearchController.java @@ -19,6 +19,7 @@ import com.pasich.mynotes.utils.recycler.SpacesItemDecoration; import java.util.List; +/** Manages the search view, debounced input, and tag-filter chips. */ public class SearchController { private static final String TAG = "SearchController"; @@ -110,6 +111,7 @@ private void ensureFullScreen() { } } + /** Populates the tag-filter chip group; hides the scroll if no tags exist. */ public void setAvailableTags(List tags) { binding.searchTagChips.removeAllViews(); if (tags == null || tags.isEmpty()) { @@ -147,6 +149,7 @@ public void setAvailableTags(List tags) { binding.searchTagsScroll.setVisibility(View.VISIBLE); } + /** Clears the search text, resets the tag chip to "All", and notifies the listener. */ public void clearSearch() { binding.searchView.getEditText().setText(""); listener.onSearchQuery(""); @@ -158,6 +161,7 @@ public void clearSearch() { listener.onTagFilterChanged(null); } + /** Cancels any pending debounce callback to prevent post-destroy callbacks. */ public void destroy() { if (searchRunnable != null) handler.removeCallbacks(searchRunnable); searchRunnable = null; diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/BackupPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/BackupPresenter.java index 7a8f7b3..9a76d7d 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/BackupPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/BackupPresenter.java @@ -27,6 +27,7 @@ import java.util.List; import javax.inject.Inject; +/** Presenter backing the backup and restore screen. */ @ActivityScoped public class BackupPresenter extends BasePresenter implements BackupContract.presenter { @@ -110,8 +111,6 @@ public void saveBackupPresenter(boolean local) { } }, throwable -> Log.e("RxError", "Error: ", throwable))); - - /* */ } /** Save local backup (3/3) - write appData to public file */ diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/MainPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/MainPresenter.java index a6c2eb9..e98de16 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/MainPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/MainPresenter.java @@ -29,6 +29,7 @@ import java.util.stream.Collectors; import javax.inject.Inject; +/** Presenter backing the main notes list screen. */ @ActivityScoped public class MainPresenter extends BasePresenter implements MainContract.presenter { @@ -143,7 +144,6 @@ private void startStateCombiner() { private MainViewState buildState( List tags, List notes, Tag selectedTag, String sort) { - // Build hidden tags set Set hidden = new HashSet<>(); for (Tag t : tags) { if (t.getVisibility() == 1) { @@ -151,7 +151,6 @@ private MainViewState buildState( } } - // Filter hidden List visible = new ArrayList<>(notes.size()); if (hidden.isEmpty()) { visible.addAll(notes); @@ -163,13 +162,11 @@ private MainViewState buildState( } } - // Determine if we show ALL notes boolean isAllNotes = selectedTag == null || selectedTag.getSystemAction() == SystemTagsManager.SYSTEM_ACTION_ALL_NOTES; - // Filter by selected tag List filtered; if (isAllNotes) { filtered = visible; @@ -183,10 +180,9 @@ private MainViewState buildState( } } - // Copy before sort List sorted = new ArrayList<>(filtered); - // Sort — pinned always first, then by date + // pinned always first, then by date boolean sortByNew = SortParam.DataSort.equals(sort); sorted.sort( (a, b) -> { @@ -413,9 +409,7 @@ private void deleteTagFromDb(Tag tag) { .subscribeOn(getSchedulerProvider().io()) .observeOn(getSchedulerProvider().ui()) .subscribe( - () -> { - /* ок */ - }, + () -> {}, throwable -> Log.e(TAG, "Error deleting tag", throwable))); } diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/NotePresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/NotePresenter.java index 0a95015..4b60635 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/NotePresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/NotePresenter.java @@ -20,6 +20,7 @@ import java.util.concurrent.TimeUnit; import javax.inject.Inject; +/** Presenter backing the note editor screen. */ public class NotePresenter extends BasePresenter implements NoteContract.presenter { diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/TagsPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/TagsPresenter.java index 58e8f76..3004d2a 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/TagsPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/TagsPresenter.java @@ -19,6 +19,7 @@ import java.util.stream.Collectors; import javax.inject.Inject; +/** Presenter backing the tag management screen. */ @ActivityScoped public class TagsPresenter extends BasePresenter implements TagsContract.presenter { @@ -55,7 +56,6 @@ public void loadTags() { .subscribe( tagList -> { if (isViewAttached()) { - // Створюємо спеціальний тег для кнопки "Додати" tagList.add(0, SystemTagsManager.createAddTag()); cachedTags = new ArrayList<>( @@ -74,7 +74,6 @@ public void loadTags() { })); } - // Сортуємо локальний кеш згідно з налаштуваннями public void displayTags(boolean isSort) { if (!isViewAttached() || cachedTags.isEmpty()) return; if (isSort) { diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java index 4a8e0b0..6f46c3e 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/TasksPresenter.java @@ -16,6 +16,7 @@ import java.util.List; import javax.inject.Inject; +/** Presenter backing the tasks (to-do) screen. */ @ActivityScoped public class TasksPresenter extends BasePresenter implements TasksContract.presenter { diff --git a/app/src/main/java/com/pasich/mynotes/ui/presenter/TrashPresenter.java b/app/src/main/java/com/pasich/mynotes/ui/presenter/TrashPresenter.java index 239df71..a102d25 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/presenter/TrashPresenter.java +++ b/app/src/main/java/com/pasich/mynotes/ui/presenter/TrashPresenter.java @@ -11,6 +11,7 @@ import java.util.List; import javax.inject.Inject; +/** Presenter backing the trash screen. */ public class TrashPresenter extends BasePresenter implements TrashContract.presenter { private static final String TAG = "TrashPresenter"; diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java index 6e98b7b..c5fc776 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/BootReceiver.java @@ -11,6 +11,7 @@ import io.reactivex.Single; import javax.inject.Inject; +/** BroadcastReceiver that reschedules all reminders after device reboot. */ @AndroidEntryPoint public class BootReceiver extends BroadcastReceiver { diff --git a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java index 2a1eb78..85284f4 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java +++ b/app/src/main/java/com/pasich/mynotes/ui/receiver/ReminderReceiver.java @@ -22,6 +22,7 @@ import dagger.hilt.android.AndroidEntryPoint; import javax.inject.Inject; +/** BroadcastReceiver that fires reminder notifications and reschedules repeating alarms. */ @AndroidEntryPoint public class ReminderReceiver extends BroadcastReceiver { diff --git a/app/src/main/java/com/pasich/mynotes/ui/state/MainViewState.java b/app/src/main/java/com/pasich/mynotes/ui/state/MainViewState.java index e97e954..8e2b45b 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/state/MainViewState.java +++ b/app/src/main/java/com/pasich/mynotes/ui/state/MainViewState.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Objects; +/** Immutable snapshot of the main screen UI state. */ public record MainViewState(List tags, List notes, Tag selectedTag, UiEvent uiEvent) { public static MainViewState empty() { return new MainViewState(List.of(), List.of(), null, UiEvent.NONE); diff --git a/app/src/main/java/com/pasich/mynotes/ui/state/StatsData.java b/app/src/main/java/com/pasich/mynotes/ui/state/StatsData.java index 6b68db9..6e2f42a 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/state/StatsData.java +++ b/app/src/main/java/com/pasich/mynotes/ui/state/StatsData.java @@ -1,5 +1,6 @@ package com.pasich.mynotes.ui.state; +/** Aggregated statistics shown in the navigation drawer. */ public record StatsData(int notesNow, int notesMonth, long chars) { public StatsData() { diff --git a/app/src/main/java/com/pasich/mynotes/ui/state/UiEvent.java b/app/src/main/java/com/pasich/mynotes/ui/state/UiEvent.java index e5513b2..351d310 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/state/UiEvent.java +++ b/app/src/main/java/com/pasich/mynotes/ui/state/UiEvent.java @@ -1,5 +1,6 @@ package com.pasich.mynotes.ui.state; +/** One-shot UI events consumed by the main screen after rendering. */ public enum UiEvent { NONE, // No UI animation required; plain list update SORT_CHANGED, // User changed the sorting order diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/BackupActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/BackupActivity.java index 72a07ff..1030650 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/BackupActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/BackupActivity.java @@ -42,6 +42,7 @@ import java.util.Objects; import javax.inject.Inject; +/** Activity for creating and restoring app data backups. */ @AndroidEntryPoint public class BackupActivity extends BaseActivity implements BackupContract.view { diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java index bdedf60..856941f 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/MainActivity.java @@ -63,10 +63,10 @@ import javax.inject.Inject; import javax.inject.Named; +/** Main screen showing the notes list and tag navigation. */ @AndroidEntryPoint public class MainActivity extends BaseActivity implements MainContract.view { - // Update theme listener private final ActivityResultLauncher themeUpdateListener = registerForActivityResult( new ActivityResultContracts.StartActivityForResult(), @@ -127,7 +127,6 @@ public void onCreate(Bundle savedInstanceState) { mActivityBinding = ActivityMainBinding.inflate(getLayoutInflater()); setContentView(mActivityBinding.getRoot()); - // set selection selectionController = new SelectionController(mNoteAdapter, mActivityBinding.getRoot()); mNoteAdapter.setSelectionController(selectionController); selectionController.setPanelMode(SelectionController.Mode.NORMAL); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/ShareActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/ShareActivity.java index 7e09eb3..048463c 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/ShareActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/ShareActivity.java @@ -69,7 +69,6 @@ public void onError(IOException e) { return; } - // 3) Стандартний share intent if (ShareProcessor.isShareIntent(intent)) { String text = ShareProcessor.extractSharedText(intent); createNoteAndOpen(text); diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java index 6522ef4..6ef52d6 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/SnoozeActivity.java @@ -12,6 +12,7 @@ import dagger.hilt.android.AndroidEntryPoint; import java.util.Calendar; +/** Activity for choosing a snooze duration for a reminder. */ @AndroidEntryPoint public class SnoozeActivity extends AppCompatActivity { diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java index 809c0f0..d3ae8b8 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TasksActivity.java @@ -24,6 +24,7 @@ import java.util.List; import javax.inject.Inject; +/** Activity for managing task lists and categories. */ @AndroidEntryPoint public class TasksActivity extends BaseActivity implements TasksContract.view { diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TrashActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TrashActivity.java index 41f4da3..9d1e028 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/TrashActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/TrashActivity.java @@ -23,6 +23,7 @@ import javax.inject.Inject; import javax.inject.Named; +/** Activity for viewing and restoring trashed notes. */ @AndroidEntryPoint public class TrashActivity extends BaseActivity implements TrashContract.view { diff --git a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java index 3b83584..7b1a818 100644 --- a/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java +++ b/app/src/main/java/com/pasich/mynotes/ui/view/activity/noteEditor/NoteActivity.java @@ -23,6 +23,7 @@ import com.pasich.mynotes.utils.linkMovement.CustomLinkMovementMethod; import dagger.hilt.android.AndroidEntryPoint; +/** Activity for creating and editing a single note. */ @AndroidEntryPoint public class NoteActivity extends BaseNoteEditorActivity { diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java index 07c5db1..040ea1c 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/notes/NoteAdapter.java @@ -14,6 +14,7 @@ import java.util.List; import javax.inject.Inject; +/** RecyclerView adapter for displaying note cards with selection support. */ @ActivityScoped public class NoteAdapter extends ListAdapter { @@ -25,6 +26,7 @@ public NoteAdapter() { super(new NoteDiff()); } + /** Attaches a SelectionController to enable per-item selection state rendering. */ public void setSelectionController(SelectionController controller) { this.selectionController = controller; } diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SearchNotesAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SearchNotesAdapter.java index 2b4da9a..386b5c7 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SearchNotesAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/searchAdapter/SearchNotesAdapter.java @@ -12,6 +12,7 @@ import dagger.hilt.android.scopes.ActivityScoped; import javax.inject.Inject; +/** RecyclerView adapter for displaying note search results. */ @ActivityScoped public class SearchNotesAdapter extends ListAdapter { diff --git a/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/TagsAdapter.java b/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/TagsAdapter.java index eaa15d7..6da82d5 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/TagsAdapter.java +++ b/app/src/main/java/com/pasich/mynotes/utils/adapters/tagAdapter/TagsAdapter.java @@ -13,6 +13,7 @@ import javax.inject.Inject; import javax.inject.Named; +/** RecyclerView adapter for displaying and selecting note tags. */ public class TagsAdapter extends ListAdapter { private OnItemClickListenerTag clickListener; diff --git a/app/src/main/java/com/pasich/mynotes/utils/file/FileExportUtils.java b/app/src/main/java/com/pasich/mynotes/utils/file/FileExportUtils.java index 29949f2..5b4ac2f 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/file/FileExportUtils.java +++ b/app/src/main/java/com/pasich/mynotes/utils/file/FileExportUtils.java @@ -68,7 +68,6 @@ public static Intent createSaveHtmlIntent(String noteTitle) { /** Open Google Drive intent to save file */ public static void saveToGoogleDrive(Context context, String noteTitle, String noteContent) { try { - // Check if Google Drive is installed if (!isAppInstalled(context)) { Toast.makeText( context, @@ -78,7 +77,6 @@ public static void saveToGoogleDrive(Context context, String noteTitle, String n return; } - // Create intent to share with Google Drive Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); intent.putExtra(Intent.EXTRA_TEXT, noteContent); @@ -93,11 +91,11 @@ public static void saveToGoogleDrive(Context context, String noteTitle, String n } } + /** Writes backup JSON to cache and delivers its URI via {@code callback}. */ public static void saveBackupToGoogleDrive( Context context, String jsonContent, DriveProcess callback) { String fileName = generateBackupFileName(); try { - // Перевірка, чи є Google Drive if (!isAppInstalled(context)) { Toast.makeText( context, @@ -107,7 +105,6 @@ public static void saveBackupToGoogleDrive( return; } - // Очимстка тимчасових файлів у кеші File dir = context.getCacheDir(); for (File f : Objects.requireNonNull(dir.listFiles())) { if (f.getName().startsWith("MyNotes_Backup_") && f.getName().endsWith(".json")) { @@ -115,14 +112,12 @@ public static void saveBackupToGoogleDrive( } } - // Створюємо тимчасовий файл у кеші File file = new File(context.getCacheDir(), fileName + ".json"); try (FileOutputStream fos = new FileOutputStream(file)) { fos.write(jsonContent.getBytes(StandardCharsets.UTF_8)); fos.flush(); } - // Робимо Uri через FileProvider Uri fileUri = FileProvider.getUriForFile( context, @@ -190,7 +185,6 @@ public static void saveTxtToUri( public static void savePdfToUri( Context context, Uri uri, String noteTitle, String noteContent) { try { - // Create PDF document PdfDocument pdfDocument = new PdfDocument(); PdfDocument.PageInfo pageInfo = new PdfDocument.PageInfo.Builder(595, 842, 1).create(); // A4 size @@ -206,7 +200,6 @@ public static void savePdfToUri( int lineHeight = 20; int maxWidth = 500; - // Title String title = (noteTitle == null || noteTitle.trim().isEmpty()) ? "***" : noteTitle.trim(); @@ -215,7 +208,6 @@ public static void savePdfToUri( canvas.drawText(title, 50, y, paint); y += 30; // Extra space after title - // Content paint.setTextSize(12); paint.setFakeBoldText(false); @@ -266,7 +258,6 @@ public static void savePdfToUri( pdfDocument.finishPage(page); - // Save PDF to selected URI OutputStream outputStream = context.getContentResolver().openOutputStream(uri); Log.d(TAG, "PDF OutputStream opened: " + (outputStream != null)); @@ -379,6 +370,7 @@ public static boolean isAppInstalled(Context context) { } } + /** Generates a timestamped backup file name with .json extension. */ public static String generateBackupFileName() { // Поточна дата Date now = new Date(); diff --git a/app/src/main/java/com/pasich/mynotes/utils/file/HtmlTemplateGenerator.java b/app/src/main/java/com/pasich/mynotes/utils/file/HtmlTemplateGenerator.java index a70c42e..4085352 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/file/HtmlTemplateGenerator.java +++ b/app/src/main/java/com/pasich/mynotes/utils/file/HtmlTemplateGenerator.java @@ -19,16 +19,13 @@ public static String generateHtmlContent( Context context, String noteTitle, String noteContent, List notes) { StringBuilder html = new StringBuilder(); - // Get current date SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy", getCurrentLocale(context)); String currentDate = dateFormat.format(new Date()); - // Get localized strings String notesTitle = getLocalizedNotesTitle(context); String exportFromText = getLocalizedExportFromText(context); - // HTML template start html.append("\n") .append("\n") .append(" \n\n"); - // Add notes content if (notes != null && !notes.isEmpty()) { - // Multiple notes for (Note note : notes) { addNoteToHtml(html, note.getTitle(), note.getValue()); } } else { - // Single note addNoteToHtml(html, noteTitle, noteContent); } - // HTML template end html.append("\n").append(""); return html.toString(); diff --git a/app/src/main/java/com/pasich/mynotes/utils/file/ImageOptimizer.java b/app/src/main/java/com/pasich/mynotes/utils/file/ImageOptimizer.java index 31178d0..275de8e 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/file/ImageOptimizer.java +++ b/app/src/main/java/com/pasich/mynotes/utils/file/ImageOptimizer.java @@ -68,7 +68,6 @@ public static byte[] optimizeRawImage( OutFormat outFormat; - // PNG if (inputFormat == OutFormat.PNG) { if (bitmap.hasAlpha()) { outFormat = OutFormat.PNG; diff --git a/app/src/main/java/com/pasich/mynotes/utils/file/SafeImageLoader.java b/app/src/main/java/com/pasich/mynotes/utils/file/SafeImageLoader.java index 2284463..7d74c8d 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/file/SafeImageLoader.java +++ b/app/src/main/java/com/pasich/mynotes/utils/file/SafeImageLoader.java @@ -36,7 +36,6 @@ public static Bitmap load(Context ctx, File file, int targetW, int targetH) thro */ public static Bitmap load(Context ctx, Uri uri, int targetW, int targetH) throws Exception { - // read only bounds (dimensions) BitmapFactory.Options bounds = new BitmapFactory.Options(); bounds.inJustDecodeBounds = true; @@ -47,19 +46,16 @@ public static Bitmap load(Context ctx, Uri uri, int targetW, int targetH) throws int srcW = bounds.outWidth; int srcH = bounds.outHeight; - // calculate scaling (inSampleSize) BitmapFactory.Options opts = new BitmapFactory.Options(); opts.inJustDecodeBounds = false; opts.inPreferredConfig = Bitmap.Config.ARGB_8888; opts.inSampleSize = calculateInSampleSize(srcW, srcH, targetW, targetH); - // decoding a real photo Bitmap bitmap; try (InputStream is = ctx.getContentResolver().openInputStream(uri)) { bitmap = BitmapFactory.decodeStream(is, null, opts); } - // EXIF correction (rotation) try (InputStream is = ctx.getContentResolver().openInputStream(uri)) { assert is != null; ExifInterface exif = new ExifInterface(is); diff --git a/app/src/main/java/com/pasich/mynotes/utils/managers/BillingManager.java b/app/src/main/java/com/pasich/mynotes/utils/managers/BillingManager.java index 0b54c22..785357d 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/managers/BillingManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/managers/BillingManager.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.List; +/** Manages in-app billing for donation products via Google Play Billing. */ public class BillingManager implements PurchasesUpdatedListener, BillingClientStateListener { private static final String TAG = "BillingManager"; @@ -210,6 +211,7 @@ private String getIconForProduct(String productId) { }; } + /** Initiates the purchase flow for the given product, consuming any stale purchase first. */ public void launchBillingFlow(Activity activity, String productId) { if (!isServiceConnected) { listener.onBillingError("Billing service not connected"); @@ -399,12 +401,14 @@ private void consumePurchase(Purchase purchase, Runnable onComplete) { }); } + /** Ends the billing client connection; call from Activity/Fragment onDestroy. */ public void destroy() { if (billingClient != null && billingClient.isReady()) { billingClient.endConnection(); } } + /** Maps a billing response code to a human-readable error description. */ public static String getBillingErrorMessage(int responseCode) { return switch (responseCode) { case BillingClient.BillingResponseCode.BILLING_UNAVAILABLE -> @@ -458,6 +462,7 @@ private double extractPrice(String priceString) { } } + /** Queries existing in-app purchases and delivers them to the listener. */ public void queryPurchases() { if (!isServiceConnected) { Log.w(TAG, "Billing service not connected, can't query purchases"); diff --git a/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteNavigator.java b/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteNavigator.java index 026e858..d683d1c 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteNavigator.java +++ b/app/src/main/java/com/pasich/mynotes/utils/navigation/NoteNavigator.java @@ -10,8 +10,10 @@ import com.pasich.mynotes.ui.view.activity.noteEditor.NoteActivity; import com.pasich.mynotes.ui.view.activity.noteEditor.NoteExtendedEditorActivity; +/** Navigates to the appropriate note editor based on settings and note type. */ public record NoteNavigator(Activity activity, ThemePreferencesCache prefs) { + /** Opens a note by model, optionally with a shared-element transition. */ public void openNote( @NonNull Note note, boolean isNew, @@ -21,6 +23,7 @@ public void openNote( openNote(note.id, isNew, tag, transitionView, transitionName, note.isAttachments()); } + /** Opens a note by ID, routing to extended or simple editor as needed. */ public void openNote( long noteId, boolean isNew, diff --git a/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/NoteDiff.java b/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/NoteDiff.java index fb2a43d..61bec9a 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/NoteDiff.java +++ b/app/src/main/java/com/pasich/mynotes/utils/recycler/diffutil/NoteDiff.java @@ -6,6 +6,7 @@ import dagger.hilt.android.scopes.ActivityScoped; import java.util.Objects; +/** DiffUtil callback for comparing Note items in a RecyclerView list. */ @ActivityScoped public class NoteDiff extends DiffUtil.ItemCallback { diff --git a/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java b/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java index 8ae199b..9562034 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/reminder/ReminderManager.java @@ -11,6 +11,7 @@ import java.util.Calendar; import java.util.List; +/** Schedules and cancels exact AlarmManager reminders for notes. */ public class ReminderManager { public static final String EXTRA_NOTE_ID = "noteId"; @@ -19,6 +20,7 @@ public class ReminderManager { public static final String EXTRA_NOTE_REPEAT = "noteRepeat"; public static final String EXTRA_NOTE_INTERVAL_MINUTES = "intervalMinutes"; + /** Schedules an exact alarm for the given note's reminder time. */ public static void scheduleReminder(Context ctx, Note note) { if (note.getReminderTime() == null) return; @@ -33,6 +35,7 @@ public static void scheduleReminder(Context ctx, Note note) { AlarmManager.RTC_WAKEUP, note.getReminderTime(), buildPendingIntent(ctx, note)); } + /** Cancels a previously scheduled alarm for the given note ID. */ public static void cancelReminder(Context ctx, int noteId) { AlarmManager alarmManager = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); if (alarmManager == null) return; @@ -47,12 +50,14 @@ public static void cancelReminder(Context ctx, int noteId) { pi.cancel(); } + /** Re-schedules alarms for all supplied notes (e.g. after reboot). */ public static void rescheduleAll(Context ctx, List notes) { for (Note note : notes) { scheduleReminder(ctx, note); } } + /** Computes the next trigger time by advancing {@code from} by one repeat unit. */ public static long computeNextTime(long from, ReminderRepeat repeat) { Calendar cal = Calendar.getInstance(); cal.setTimeInMillis(from); @@ -72,6 +77,7 @@ public static long computeNextTime(long from, ReminderRepeat repeat) { return cal.getTimeInMillis(); } + /** Returns whether the app has permission to schedule exact alarms. */ public static boolean canScheduleExact(Context ctx) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); diff --git a/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java b/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java index 691a23e..e42b209 100644 --- a/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java +++ b/app/src/main/java/com/pasich/mynotes/utils/reminder/TaskReminderManager.java @@ -9,6 +9,7 @@ import com.pasich.mynotes.ui.receiver.TaskReminderReceiver; import java.util.List; +/** Schedules and cancels exact AlarmManager reminders for tasks. */ public class TaskReminderManager { public static final String EXTRA_TASK_ID = "taskId"; @@ -18,6 +19,7 @@ public class TaskReminderManager { // Offset to avoid collision with note reminder PendingIntent request codes private static final int REQUEST_CODE_OFFSET = 100000; + /** Schedules an exact alarm for the given task's reminder time. */ public static void scheduleReminder(Context ctx, Task task) { if (task.getReminderTime() == null) return; AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); @@ -27,6 +29,7 @@ public static void scheduleReminder(Context ctx, Task task) { AlarmManager.RTC_WAKEUP, task.getReminderTime(), buildPendingIntent(ctx, task)); } + /** Cancels a previously scheduled alarm for the given task ID. */ public static void cancelReminder(Context ctx, int taskId) { AlarmManager am = (AlarmManager) ctx.getSystemService(Context.ALARM_SERVICE); if (am == null) return; @@ -41,6 +44,7 @@ public static void cancelReminder(Context ctx, int taskId) { pi.cancel(); } + /** Re-schedules alarms for all supplied tasks (e.g. after reboot). */ public static void rescheduleAll(Context ctx, List tasks) { for (Task t : tasks) scheduleReminder(ctx, t); } From e518b9b0d76fc1e6c105457895b0694065cca5b4 Mon Sep 17 00:00:00 2001 From: pasichDev Date: Tue, 19 May 2026 00:03:53 +0300 Subject: [PATCH 12/13] docs: auto release --- .github/workflows/ci-cd.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index b00d491..3fc793a 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -92,3 +92,23 @@ jobs: artifactErrorsFailBuild: true makeLatest: true token: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate Play Store release notes + run: | + mkdir -p distribution/whatsnew + PREV=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + RANGE=${PREV:+"$PREV..HEAD"} + git log ${RANGE} --no-merges --format='• %s' \ + | head -20 \ + | head -c 490 \ + > distribution/whatsnew/whatsnew-en-US + + - name: Publish to Google Play (Open Testing) + uses: r0adkll/upload-google-play@v3 + with: + serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} + packageName: com.pasich.mynotes + releaseFiles: artifacts/MyNotes-v${{ steps.version.outputs.name }}.aab + track: beta + status: completed + whatsNewDirectory: distribution/whatsnew From a736aa2c86937da2e0c04795b375b6ad5469247a Mon Sep 17 00:00:00 2001 From: pasichDev Date: Tue, 19 May 2026 00:14:13 +0300 Subject: [PATCH 13/13] off auto-release --- .github/workflows/ci-cd.yml | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 3fc793a..d3d0001 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -93,22 +93,22 @@ jobs: makeLatest: true token: ${{ secrets.GITHUB_TOKEN }} - - name: Generate Play Store release notes - run: | - mkdir -p distribution/whatsnew - PREV=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") - RANGE=${PREV:+"$PREV..HEAD"} - git log ${RANGE} --no-merges --format='• %s' \ - | head -20 \ - | head -c 490 \ - > distribution/whatsnew/whatsnew-en-US + # - name: Generate Play Store release notes + # run: | + # mkdir -p distribution/whatsnew + # PREV=$(git describe --tags --abbrev=0 HEAD^ 2>/dev/null || echo "") + # RANGE=${PREV:+"$PREV..HEAD"} + # git log ${RANGE} --no-merges --format='• %s' \ + # | head -20 \ + # | head -c 490 \ + # > distribution/whatsnew/whatsnew-en-US - - name: Publish to Google Play (Open Testing) - uses: r0adkll/upload-google-play@v3 - with: - serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} - packageName: com.pasich.mynotes - releaseFiles: artifacts/MyNotes-v${{ steps.version.outputs.name }}.aab - track: beta - status: completed - whatsNewDirectory: distribution/whatsnew + # - name: Publish to Google Play (Open Testing) + # uses: r0adkll/upload-google-play@v3 + # with: + # serviceAccountJsonPlainText: ${{ secrets.GOOGLE_PLAY_SERVICE_ACCOUNT_JSON }} + # packageName: com.pasich.mynotes + # releaseFiles: artifacts/MyNotes-v${{ steps.version.outputs.name }}.aab + # track: beta + # status: completed + # whatsNewDirectory: distribution/whatsnew