From 81a6e545bb01f5e4dc8a08aebf634861b1e23b79 Mon Sep 17 00:00:00 2001 From: loloakira <6253351+loloakira@users.noreply.github.com> Date: Thu, 4 Jun 2026 23:30:20 +0200 Subject: [PATCH 1/3] feat(widget): add interactive notes widget Add a third home-screen widget, "Interactive notes", that shows notes as a scrollable single column of rounded cards. Each card shows the note title and a multi-line content excerpt (the whole note when it is short). Tapping a card opens that specific note; a floating button in the bottom-right creates a new note in the widget's configured category/account. The favorite star is a non-clickable indicator. The configuration screen groups its controls under headings and separates selecting from saving: tapping an entry only selects it, and an "Add widget" button in the bottom-right commits the configuration. "All notes" is selected by default. When an existing widget is reconfigured the button reads "Edit widget" and the stored selection is restored. Sort options are a "favorites on top" toggle and a newest-first / oldest-first order. The widget is reconfigurable via long-press on Android 12+ and provides a live previewLayout for the widget picker. Reuses the existing NotesListWidgetData storage (no database migration) and the existing excerpt generation; sort options are kept in per-widget SharedPreferences. The two existing widgets are left unchanged. So reconfiguration can persist updated settings, the shared createOrUpdateNoteListWidgetData DAO now uses an upsert (REPLACE) conflict strategy instead of a plain insert that aborted on the existing primary key; this is transparent to the non-reconfigurable widgets, which never hit the conflict. A DAO test covers the upsert. Assisted-by: ClaudeCode:claude-opus-5 Signed-off-by: loloakira <6253351+loloakira@users.noreply.github.com> AI-assistant: Claude Code 2.1.226 (Claude Opus 5) --- REUSE.toml | 2 +- app/src/main/AndroidManifest.xml | 27 +++ .../notes/persistence/NotesRepository.java | 2 + .../persistence/dao/WidgetNotesListDao.java | 3 +- .../InteractiveNoteListWidget.kt | 176 ++++++++++++++ ...tiveNoteListWidgetConfigurationActivity.kt | 226 ++++++++++++++++++ .../InteractiveNoteListWidgetFactory.kt | 182 ++++++++++++++ .../InteractiveNoteListWidgetService.kt | 15 ++ .../InteractiveWidgetPreferences.kt | 44 ++++ .../InteractiveWidgetSelection.kt | 35 +++ .../widget/interactivelist/WidgetSortOrder.kt | 12 + .../interactive_note_list_widget_preview.webp | Bin 0 -> 27184 bytes .../res/drawable/widget_card_background.xml | 12 + .../res/drawable/widget_fab_background.xml | 11 + .../widget_interactive_background.xml | 12 + ...ivity_interactive_widget_configuration.xml | 95 ++++++++ .../res/layout/widget_interactive_entry.xml | 73 ++++++ .../layout/widget_interactive_note_list.xml | 49 ++++ .../res/layout/widget_interactive_preview.xml | 128 ++++++++++ app/src/main/res/values-night/colors.xml | 1 + app/src/main/res/values/colors.xml | 1 + app/src/main/res/values/dimens.xml | 4 + app/src/main/res/values/strings.xml | 10 + ...ractive_note_list_widget_provider_info.xml | 20 ++ .../persistence/WidgetNotesListDaoTest.kt | 93 +++++++ 25 files changed, 1231 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetFactory.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetService.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveWidgetPreferences.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveWidgetSelection.kt create mode 100644 app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/WidgetSortOrder.kt create mode 100644 app/src/main/res/drawable/interactive_note_list_widget_preview.webp create mode 100644 app/src/main/res/drawable/widget_card_background.xml create mode 100644 app/src/main/res/drawable/widget_fab_background.xml create mode 100644 app/src/main/res/drawable/widget_interactive_background.xml create mode 100644 app/src/main/res/layout/activity_interactive_widget_configuration.xml create mode 100644 app/src/main/res/layout/widget_interactive_entry.xml create mode 100644 app/src/main/res/layout/widget_interactive_note_list.xml create mode 100644 app/src/main/res/layout/widget_interactive_preview.xml create mode 100644 app/src/main/res/xml/interactive_note_list_widget_provider_info.xml create mode 100644 app/src/test/java/it/niedermann/owncloud/notes/persistence/WidgetNotesListDaoTest.kt diff --git a/REUSE.toml b/REUSE.toml index c8cc20b8a..bac23a37a 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -6,7 +6,7 @@ SPDX-PackageSupplier = "Nextcloud Android team " SPDX-PackageDownloadLocation = "https://github.com/nextcloud/notes-android" [[annotations]] -path = ["app/schemas/it.niedermann.owncloud.notes.persistence.NotesDatabase/**.json", "app/src/main/res/values-**/strings.xml", "renovate.json5", "**/.gitignore", ".idea/**", "scripts/analysis/findbugs-results.txt", "scripts/analysis/lint-results.txt", "app/src/test/resources/robolectric.properties", "app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker", "fastlane/metadata/**", "app/src/main/res/mipmap-**dpi/ic_launcher.png", "app/src/main/ic_launcher-web.png", ".bundle/config", "app/src/main/res/drawable-**dpi/ic_widget_create.png", "app/src/main/res/drawable-mdpi/context_based_formatting.png", "app/src/main/res/drawable/note_list_widget_preview.webp", "app/src/main/res/drawable/single_note_widget.webp", "app/src/qa/res/drawable/ic_launcher_foreground.xml"] +path = ["app/schemas/it.niedermann.owncloud.notes.persistence.NotesDatabase/**.json", "app/src/main/res/values-**/strings.xml", "renovate.json5", "**/.gitignore", ".idea/**", "scripts/analysis/findbugs-results.txt", "scripts/analysis/lint-results.txt", "app/src/test/resources/robolectric.properties", "app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker", "fastlane/metadata/**", "app/src/main/res/mipmap-**dpi/ic_launcher.png", "app/src/main/ic_launcher-web.png", ".bundle/config", "app/src/main/res/drawable-**dpi/ic_widget_create.png", "app/src/main/res/drawable-mdpi/context_based_formatting.png", "app/src/main/res/drawable/note_list_widget_preview.webp", "app/src/main/res/drawable/single_note_widget.webp", "app/src/main/res/drawable/interactive_note_list_widget_preview.webp", "app/src/qa/res/drawable/ic_launcher_foreground.xml"] precedence = "aggregate" SPDX-FileCopyrightText = "2016-2025 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "GPL-3.0-or-later" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 111a0dc01..3b1cd56f6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -159,6 +159,14 @@ + + + + + + + + + + + + + + + + + + { updateSingleNoteWidgets(context); updateNoteListWidgets(context); + updateInteractiveNoteListWidgets(context); }); } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/WidgetNotesListDao.java b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/WidgetNotesListDao.java index 1f34e9278..435c1fd36 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/WidgetNotesListDao.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/persistence/dao/WidgetNotesListDao.java @@ -8,6 +8,7 @@ import androidx.room.Dao; import androidx.room.Insert; +import androidx.room.OnConflictStrategy; import androidx.room.Query; import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData; @@ -15,7 +16,7 @@ @Dao public interface WidgetNotesListDao { - @Insert + @Insert(onConflict = OnConflictStrategy.REPLACE) void createOrUpdateNoteListWidgetData(NotesListWidgetData data); @Query("DELETE FROM NOTESLISTWIDGETDATA WHERE id = :appWidgetId") diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt new file mode 100644 index 000000000..136864783 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt @@ -0,0 +1,176 @@ +/* + * Nextcloud Notes - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.niedermann.owncloud.notes.widget.interactivelist + +import android.app.PendingIntent +import android.appwidget.AppWidgetManager +import android.appwidget.AppWidgetProvider +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.res.ColorStateList +import android.os.Build +import android.os.Bundle +import android.util.Log +import android.widget.RemoteViews +import androidx.core.net.toUri +import it.niedermann.owncloud.notes.R +import it.niedermann.owncloud.notes.branding.BrandingUtil +import it.niedermann.owncloud.notes.edit.EditNoteActivity +import it.niedermann.owncloud.notes.persistence.NotesRepository +import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData +import it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType +import it.niedermann.owncloud.notes.shared.model.NavigationCategory +import it.niedermann.owncloud.notes.shared.util.WidgetUtil +import java.util.concurrent.ExecutorService +import java.util.concurrent.Executors + +class InteractiveNoteListWidget : AppWidgetProvider() { + private val executor: ExecutorService = Executors.newCachedThreadPool() + + override fun onUpdate( + context: Context, + appWidgetManager: AppWidgetManager, + appWidgetIds: IntArray + ) { + super.onUpdate(context, appWidgetManager, appWidgetIds) + updateAppWidget(context, appWidgetManager, appWidgetIds) + } + + override fun onReceive(context: Context, intent: Intent) { + super.onReceive(context, intent) + val awm = AppWidgetManager.getInstance(context) + + if (intent.action == null) { + Log.w(TAG, "Intent action is null") + return + } + + if (intent.action != AppWidgetManager.ACTION_APPWIDGET_UPDATE) { + Log.w(TAG, "Intent action is not ACTION_APPWIDGET_UPDATE") + return + } + + val appWidgetIds = if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) { + intArrayOf( + intent.getIntExtra( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID + ) + ) + } else { + awm.getAppWidgetIds(ComponentName(context, InteractiveNoteListWidget::class.java)) + } + + updateAppWidget(context, awm, appWidgetIds) + } + + override fun onDeleted(context: Context, appWidgetIds: IntArray) { + super.onDeleted(context, appWidgetIds) + val repo = NotesRepository.getInstance(context) + + for (appWidgetId in appWidgetIds) { + executor.execute { + repo.removeNoteListWidget(appWidgetId) + InteractiveWidgetPreferences.remove(context, appWidgetId) + } + } + } + + companion object { + private val TAG: String = InteractiveNoteListWidget::class.java.simpleName + + fun updateAppWidget(context: Context, awm: AppWidgetManager, appWidgetIds: IntArray) { + val repo = NotesRepository.getInstance(context) + appWidgetIds.forEach { appWidgetId -> + val data = repo.getNoteListWidgetData(appWidgetId) ?: return@forEach + updateSingleWidget(context, awm, appWidgetId, data) + } + } + + private fun updateSingleWidget( + context: Context, + awm: AppWidgetManager, + appWidgetId: Int, + data: NotesListWidgetData + ) { + val serviceIntent = Intent(context, InteractiveNoteListWidgetService::class.java).apply { + putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + setData(toUri(Intent.URI_INTENT_SCHEME).toUri()) + } + + val openTemplateIntent = Intent(context, EditNoteActivity::class.java).apply { + setPackage(context.packageName) + } + val openTemplateFlags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE + } else { + PendingIntent.FLAG_UPDATE_CURRENT + } + val openTemplatePendingIntent = + PendingIntent.getActivity(context, 0, openTemplateIntent, openTemplateFlags) + + val createNotePendingIntent = + PendingIntent.getActivity( + context, + appWidgetId, + createNoteIntent(context, data), + WidgetUtil.pendingIntentFlagCompat(PendingIntent.FLAG_UPDATE_CURRENT) + ) + + val views = RemoteViews(context.packageName, R.layout.widget_interactive_note_list).apply { + setRemoteAdapter(R.id.interactive_note_list_lv, serviceIntent) + setPendingIntentTemplate(R.id.interactive_note_list_lv, openTemplatePendingIntent) + setEmptyView( + R.id.interactive_note_list_lv, + R.id.interactive_note_list_placeholder_tv + ) + setOnClickPendingIntent(R.id.interactive_create_note_button, createNotePendingIntent) + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + setColorStateList( + R.id.interactive_create_note_button, + "setBackgroundTintList", + ColorStateList.valueOf(BrandingUtil.readBrandMainColor(context)) + ) + } + } + + awm.run { + updateAppWidget(appWidgetId, views) + notifyAppWidgetViewDataChanged(intArrayOf(appWidgetId), R.id.interactive_note_list_lv) + } + } + + private fun createNoteIntent(context: Context, data: NotesListWidgetData): Intent { + val navigationCategory = if (data.mode == NotesListWidgetData.MODE_DISPLAY_STARRED) { + NavigationCategory(ENavigationCategoryType.FAVORITES) + } else { + NavigationCategory(data.accountId, data.category) + } + + val bundle = Bundle().apply { + putSerializable(EditNoteActivity.PARAM_CATEGORY, navigationCategory) + putLong(EditNoteActivity.PARAM_ACCOUNT_ID, data.accountId) + } + + return Intent(context, EditNoteActivity::class.java).apply { + setPackage(context.packageName) + putExtras(bundle) + setData("interactive-create://${data.id}".toUri()) + } + } + + @JvmStatic + fun updateInteractiveNoteListWidgets(context: Context) { + val intent = Intent(context, InteractiveNoteListWidget::class.java).apply { + action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + } + context.sendBroadcast(intent) + } + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.kt new file mode 100644 index 000000000..39b0df467 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.kt @@ -0,0 +1,226 @@ +/* + * Nextcloud Notes - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.niedermann.owncloud.notes.widget.interactivelist + +import android.app.Activity +import android.appwidget.AppWidgetManager +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.widget.Toast +import androidx.lifecycle.ViewModelProvider +import com.nextcloud.android.sso.exceptions.SSOException +import com.nextcloud.android.sso.helper.SingleAccountHelper +import it.niedermann.owncloud.notes.LockedActivity +import it.niedermann.owncloud.notes.NotesApplication +import it.niedermann.owncloud.notes.R +import it.niedermann.owncloud.notes.branding.BrandingUtil +import it.niedermann.owncloud.notes.databinding.ActivityInteractiveWidgetConfigurationBinding +import it.niedermann.owncloud.notes.main.MainActivity +import it.niedermann.owncloud.notes.main.navigation.NavigationAdapter +import it.niedermann.owncloud.notes.main.navigation.NavigationClickListener +import it.niedermann.owncloud.notes.main.navigation.NavigationItem +import it.niedermann.owncloud.notes.persistence.NotesRepository +import it.niedermann.owncloud.notes.persistence.entity.Account +import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData +import it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType +import it.niedermann.owncloud.notes.widget.notelist.NoteListViewModel +import java.util.concurrent.Executors + +class InteractiveNoteListWidgetConfigurationActivity : LockedActivity(), NavigationClickListener { + private val executor = Executors.newCachedThreadPool() + + private var binding: ActivityInteractiveWidgetConfigurationBinding? = null + private lateinit var viewModel: NoteListViewModel + private lateinit var adapterCategories: NavigationAdapter + private lateinit var repo: NotesRepository + + private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID + private var localAccount: Account? = null + private var selectedItemId = MainActivity.ADAPTER_KEY_RECENT + private var selectedItem: NavigationItem? = null + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setResult(Activity.RESULT_CANCELED) + + repo = NotesRepository.getInstance(this) + appWidgetId = intent.extras?.getInt( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID + ) ?: AppWidgetManager.INVALID_APPWIDGET_ID + + if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + Log.d(TAG, "INVALID_APPWIDGET_ID") + finish() + return + } + + viewModel = ViewModelProvider(this)[NoteListViewModel::class.java] + adapterCategories = NavigationAdapter(this, this) + + val views = ActivityInteractiveWidgetConfigurationBinding.inflate(layoutInflater) + binding = views + setContentView(views.root) + views.recyclerView.adapter = adapterCategories + views.saveWidget.setOnClickListener { saveWidget() } + restorePreferences(views) + + loadAccount() + } + + private fun restorePreferences(views: ActivityInteractiveWidgetConfigurationBinding) { + views.favoritesFirstCheckbox.isChecked = + InteractiveWidgetPreferences.isFavoritesFirst(this, appWidgetId) + + val sortOrder = InteractiveWidgetPreferences.getSortOrder(this, appWidgetId) + if (sortOrder == WidgetSortOrder.OLDEST_FIRST) { + views.sortOldestFirst.isChecked = true + } else { + views.sortNewestFirst.isChecked = true + } + } + + override fun onItemClick(item: NavigationItem) = select(item) + + override fun onIconClick(item: NavigationItem) = select(item) + + private fun applyNavigationItems(navigationItems: List) { + adapterCategories.setItems(navigationItems) + + val item = navigationItems.firstOrNull { it.id == selectedItemId } + ?: navigationItems.firstOrNull { it.id == MainActivity.ADAPTER_KEY_RECENT } + ?: return + + select(item) + } + + private fun select(item: NavigationItem) { + selectedItem = item + selectedItemId = item.id + adapterCategories.setSelectedItem(item.id) + } + + private fun saveWidget() { + val account = localAccount ?: run { + Log.w(TAG, "No account loaded; ignoring selection") + return + } + val item = selectedItem ?: run { + Log.w(TAG, "No category selected; ignoring selection") + return + } + val views = binding ?: return + + val favoritesFirst = views.favoritesFirstCheckbox.isChecked + val sortOrder = if (views.sortOldestFirst.isChecked) { + WidgetSortOrder.OLDEST_FIRST + } else { + WidgetSortOrder.NEWEST_FIRST + } + + val data = NotesListWidgetData().apply { + id = appWidgetId + accountId = account.id + themeMode = NotesApplication.getAppTheme(applicationContext).modeId + } + applyNavigationMode(data, item) + + executor.execute { + repo.createOrUpdateNoteListWidgetData(data) + InteractiveWidgetPreferences.save(applicationContext, appWidgetId, favoritesFirst, sortOrder) + + val updateIntent = Intent( + AppWidgetManager.ACTION_APPWIDGET_UPDATE, + null, + applicationContext, + InteractiveNoteListWidget::class.java + ).putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + + applicationContext.sendBroadcast(updateIntent) + + runOnUiThread { + setResult(Activity.RESULT_OK, updateIntent) + finish() + } + } + } + + private fun applyNavigationMode(data: NotesListWidgetData, item: NavigationItem) { + when (item.type) { + ENavigationCategoryType.RECENT -> data.mode = NotesListWidgetData.MODE_DISPLAY_ALL + ENavigationCategoryType.FAVORITES -> data.mode = NotesListWidgetData.MODE_DISPLAY_STARRED + ENavigationCategoryType.UNCATEGORIZED -> { + data.mode = NotesListWidgetData.MODE_DISPLAY_CATEGORY + data.category = null + } + + ENavigationCategoryType.DEFAULT_CATEGORY -> applyCategoryMode(data, item) + null -> fallBackToAllNotes(data) + } + } + + private fun applyCategoryMode(data: NotesListWidgetData, item: NavigationItem) { + if (item !is NavigationItem.CategoryNavigationItem) { + fallBackToAllNotes(data) + return + } + + data.mode = NotesListWidgetData.MODE_DISPLAY_CATEGORY + data.category = item.category + } + + private fun fallBackToAllNotes(data: NotesListWidgetData) { + data.mode = NotesListWidgetData.MODE_DISPLAY_ALL + Log.e(TAG, "Unknown item navigation type. Fallback to show ${ENavigationCategoryType.RECENT}") + } + + private fun loadAccount() { + executor.execute { + val account = currentAccount() + if (account == null) { + runOnUiThread { + Toast.makeText(this, R.string.widget_not_logged_in, Toast.LENGTH_LONG).show() + finish() + } + return@execute + } + + localAccount = account + val storedData = repo.getNoteListWidgetData(appWidgetId) + selectedItemId = InteractiveWidgetSelection.navigationItemId(storedData) + + runOnUiThread { + if (storedData != null) { + binding?.saveWidget?.setText(R.string.widget_edit) + } + viewModel.getAdapterCategories(account.id).observe(this, ::applyNavigationItems) + } + } + } + + private fun currentAccount(): Account? = try { + repo.getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(this).name) + } catch (e: SSOException) { + Log.w(TAG, "Account not found", e) + null + } + + override fun applyBrand(color: Int) { + val views = binding ?: return + val util = BrandingUtil.of(color, this) + + util.platform.themeCheckbox(views.favoritesFirstCheckbox) + util.platform.themeRadioButton(views.sortNewestFirst) + util.platform.themeRadioButton(views.sortOldestFirst) + util.material.themeExtendedFAB(views.saveWidget) + } + + companion object { + private val TAG: String = InteractiveNoteListWidgetConfigurationActivity::class.java.simpleName + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetFactory.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetFactory.kt new file mode 100644 index 000000000..502714cc2 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetFactory.kt @@ -0,0 +1,182 @@ +/* + * Nextcloud Notes - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.niedermann.owncloud.notes.widget.interactivelist + +import android.appwidget.AppWidgetManager +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.util.Log +import android.view.View +import android.widget.RemoteViews +import android.widget.RemoteViewsService.RemoteViewsFactory +import androidx.core.content.ContextCompat +import androidx.core.net.toUri +import com.nextcloud.android.common.ui.util.PlatformThemeUtil +import it.niedermann.owncloud.notes.R +import it.niedermann.owncloud.notes.edit.EditNoteActivity +import it.niedermann.owncloud.notes.persistence.NotesRepository +import it.niedermann.owncloud.notes.persistence.entity.Note +import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData +import it.niedermann.owncloud.notes.shared.util.NoteUtil + +class InteractiveNoteListWidgetFactory internal constructor(private val context: Context, intent: Intent) : + RemoteViewsFactory { + private val appWidgetId: Int = intent.getIntExtra( + AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID + ) + private val repo: NotesRepository = NotesRepository.getInstance(context) + private val dbNotes: MutableList = ArrayList() + + override fun onCreate() = Unit + + override fun onDataSetChanged() { + dbNotes.clear() + try { + val widgetData = repo.getNoteListWidgetData(appWidgetId) + if (widgetData == null) { + Log.w(TAG, "Widget data is null") + return + } + + when (widgetData.mode) { + NotesListWidgetData.MODE_DISPLAY_ALL -> dbNotes.addAll( + repo.searchRecentByModified( + widgetData.accountId, "%" + ) + ) + + NotesListWidgetData.MODE_DISPLAY_STARRED -> dbNotes.addAll( + repo.searchFavoritesByModified( + widgetData.accountId, "%" + ) + ) + + else -> { + if (widgetData.category != null) { + dbNotes.addAll( + repo.searchCategoryByModified( + widgetData.accountId, + "%", + widgetData.category + ) + ) + } else { + dbNotes.addAll( + repo.searchUncategorizedByModified( + widgetData.accountId, + "%" + ) + ) + } + } + } + + applySorting(context, appWidgetId, dbNotes) + } catch (e: Exception) { + Log.w(TAG, "Error caught at onDataSetChanged", e) + } + } + + override fun onDestroy() = Unit + + override fun getCount(): Int { + return dbNotes.size + } + + override fun getViewAt(position: Int): RemoteViews? { + val note = dbNotes.getOrNull(position) ?: return null + + return RemoteViews(context.packageName, R.layout.widget_interactive_entry).apply { + setOnClickFillInIntent(R.id.interactive_entry, getOpenNoteIntent(context, note)) + + setTextViewText(R.id.interactive_entry_title, note.title) + + if (note.excerpt.isEmpty()) { + setViewVisibility(R.id.interactive_entry_excerpt, View.GONE) + } else { + setViewVisibility(R.id.interactive_entry_excerpt, View.VISIBLE) + setTextViewText( + R.id.interactive_entry_excerpt, + note.excerpt.replace(NoteUtil.EXCERPT_LINE_SEPARATOR, "\n") + ) + } + + if (note.category.isEmpty()) { + setViewVisibility(R.id.interactive_entry_category, View.GONE) + } else { + setViewVisibility(R.id.interactive_entry_category, View.VISIBLE) + setTextViewText(R.id.interactive_entry_category, note.category) + + val textColorId = if (PlatformThemeUtil.isDarkMode(context)) { + R.color.text_color + } else { + R.color.category_border + } + val textColor = ContextCompat.getColor(context, textColorId) + setTextColor(R.id.interactive_entry_category, textColor) + } + + if (note.favorite) { + setViewVisibility(R.id.interactive_entry_fav_icon, View.VISIBLE) + setImageViewResource(R.id.interactive_entry_fav_icon, R.drawable.ic_star_yellow_24dp) + } else { + setViewVisibility(R.id.interactive_entry_fav_icon, View.GONE) + } + } + } + + override fun getLoadingView(): RemoteViews? { + return null + } + + override fun getViewTypeCount(): Int { + return 1 + } + + override fun getItemId(position: Int): Long { + return dbNotes[position].id + } + + override fun hasStableIds(): Boolean { + return true + } + + companion object { + private val TAG: String = InteractiveNoteListWidgetFactory::class.java.simpleName + } +} + +private fun applySorting(context: Context, appWidgetId: Int, notes: MutableList) { + val sortOrder = InteractiveWidgetPreferences.getSortOrder(context, appWidgetId) + val byDate: Comparator = if (sortOrder == WidgetSortOrder.OLDEST_FIRST) { + compareBy { it.modified?.timeInMillis ?: 0L } + } else { + compareByDescending { it.modified?.timeInMillis ?: 0L } + } + + if (!InteractiveWidgetPreferences.isFavoritesFirst(context, appWidgetId)) { + notes.sortWith(byDate) + return + } + + notes.sortWith(compareByDescending { it.favorite }.then(byDate)) +} + +private fun getOpenNoteIntent(context: Context, note: Note): Intent { + val bundle = Bundle().apply { + putLong(EditNoteActivity.PARAM_NOTE_ID, note.id) + putLong(EditNoteActivity.PARAM_ACCOUNT_ID, note.accountId) + } + + return Intent(context, EditNoteActivity::class.java).apply { + setPackage(context.packageName) + putExtras(bundle) + data = toUri(Intent.URI_INTENT_SCHEME).toUri() + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetService.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetService.kt new file mode 100644 index 000000000..bc5bb7954 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetService.kt @@ -0,0 +1,15 @@ +/* + * Nextcloud Notes - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.niedermann.owncloud.notes.widget.interactivelist + +import android.content.Intent +import android.widget.RemoteViewsService + +class InteractiveNoteListWidgetService : RemoteViewsService() { + override fun onGetViewFactory(intent: Intent): RemoteViewsFactory = + InteractiveNoteListWidgetFactory(applicationContext, intent) +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveWidgetPreferences.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveWidgetPreferences.kt new file mode 100644 index 000000000..12c245fea --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveWidgetPreferences.kt @@ -0,0 +1,44 @@ +/* + * Nextcloud Notes - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.niedermann.owncloud.notes.widget.interactivelist + +import android.content.Context +import androidx.core.content.edit + +object InteractiveWidgetPreferences { + private const val PREFS_NAME = "interactive_note_list_widget" + private const val KEY_FAVORITES_FIRST = "favorites_first_" + private const val KEY_SORT_ORDER = "sort_order_" + + private const val DEFAULT_FAVORITES_FIRST = true + + fun save(context: Context, appWidgetId: Int, favoritesFirst: Boolean, sortOrder: WidgetSortOrder) { + prefs(context).edit { + putBoolean(KEY_FAVORITES_FIRST + appWidgetId, favoritesFirst) + putString(KEY_SORT_ORDER + appWidgetId, sortOrder.name) + } + } + + fun isFavoritesFirst(context: Context, appWidgetId: Int): Boolean = + prefs(context).getBoolean(KEY_FAVORITES_FIRST + appWidgetId, DEFAULT_FAVORITES_FIRST) + + fun getSortOrder(context: Context, appWidgetId: Int): WidgetSortOrder { + val stored = prefs(context).getString(KEY_SORT_ORDER + appWidgetId, null) + ?: return WidgetSortOrder.NEWEST_FIRST + return runCatching { WidgetSortOrder.valueOf(stored) }.getOrDefault(WidgetSortOrder.NEWEST_FIRST) + } + + fun remove(context: Context, appWidgetId: Int) { + prefs(context).edit { + remove(KEY_FAVORITES_FIRST + appWidgetId) + remove(KEY_SORT_ORDER + appWidgetId) + } + } + + private fun prefs(context: Context) = + context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE) +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveWidgetSelection.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveWidgetSelection.kt new file mode 100644 index 000000000..88699aa0c --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveWidgetSelection.kt @@ -0,0 +1,35 @@ +/* + * Nextcloud Notes - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.niedermann.owncloud.notes.widget.interactivelist + +import it.niedermann.owncloud.notes.main.MainActivity +import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData + +object InteractiveWidgetSelection { + private const val CATEGORY_ID_PREFIX = "category:" + private const val CATEGORY_SEPARATOR = '/' + + fun navigationItemId(data: NotesListWidgetData?): String { + if (data == null) { + return MainActivity.ADAPTER_KEY_RECENT + } + + return when (data.mode) { + NotesListWidgetData.MODE_DISPLAY_STARRED -> MainActivity.ADAPTER_KEY_STARRED + NotesListWidgetData.MODE_DISPLAY_CATEGORY -> categoryId(data.category) + else -> MainActivity.ADAPTER_KEY_RECENT + } + } + + private fun categoryId(category: String?): String { + if (category.isNullOrEmpty()) { + return MainActivity.ADAPTER_KEY_UNCATEGORIZED + } + + return CATEGORY_ID_PREFIX + category.substringBefore(CATEGORY_SEPARATOR) + } +} diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/WidgetSortOrder.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/WidgetSortOrder.kt new file mode 100644 index 000000000..645b449e7 --- /dev/null +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/WidgetSortOrder.kt @@ -0,0 +1,12 @@ +/* + * Nextcloud Notes - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.niedermann.owncloud.notes.widget.interactivelist + +enum class WidgetSortOrder { + NEWEST_FIRST, + OLDEST_FIRST +} diff --git a/app/src/main/res/drawable/interactive_note_list_widget_preview.webp b/app/src/main/res/drawable/interactive_note_list_widget_preview.webp new file mode 100644 index 0000000000000000000000000000000000000000..367115b15bdc7b637ec27a3defac6380e190a74e GIT binary patch literal 27184 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 z z04GqfX+cor!Ymq!VQ%X&WgC1rTuttIrF-o$@<(w9gB&n78Eb5+_PJPIE(Mz$>HJ$g zxSj#}%1AB-3twTogz%aahoxrAU%!DlH*4+U+JRKl&vMkE+3L@Zgm_s(j$ zAE{b}GBo-BGn)>3Q>~|c6+CKFoJ7>LwXg^ScYG`;Mk4fpC8lM09NF9xcX%1uDN5W& z_VVvZZ)~2AXnlwa#%mpY;4^iOcuJd;kH{_(-!k1&*}-&h5Onq@X9BfYjY__}sAM*+ z(Mc}cwIXBAZn!h}J07IBRGiKzi24cV=6XK@f6F$c%I$Wd3U>=^LuCawd6@^DSNqN? zOA{B)@O*2E2gGH&i&}nv(m3Ut6_TkJZB^uRE6!6GQQF z!2g1@8EDKu*Ge%IJ<^X`P(hDS{^oE3`+K zI&Y;!<&t?$)zGzR>fo@dC9bJ_)l;oNRfOt_7yuBy4G#QNB)!07cjt#-C$Z~kLyY8+gukPOkpW~;qG6-T;Pk8r#K&BNQEXgz|<>Gm6 z*(G21m{+v+`ZB33497cpjc_QC;sNJGHhXNwXN2jStD5I6x~1}UZ)o7KOSAA{`GteU z0l<#Es+bbk_=@r8qP;|blH^9_ + + + + + diff --git a/app/src/main/res/drawable/widget_fab_background.xml b/app/src/main/res/drawable/widget_fab_background.xml new file mode 100644 index 000000000..321ed54d6 --- /dev/null +++ b/app/src/main/res/drawable/widget_fab_background.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/app/src/main/res/drawable/widget_interactive_background.xml b/app/src/main/res/drawable/widget_interactive_background.xml new file mode 100644 index 000000000..aa3c3d2d4 --- /dev/null +++ b/app/src/main/res/drawable/widget_interactive_background.xml @@ -0,0 +1,12 @@ + + + + + + diff --git a/app/src/main/res/layout/activity_interactive_widget_configuration.xml b/app/src/main/res/layout/activity_interactive_widget_configuration.xml new file mode 100644 index 000000000..fed71a812 --- /dev/null +++ b/app/src/main/res/layout/activity_interactive_widget_configuration.xml @@ -0,0 +1,95 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_interactive_entry.xml b/app/src/main/res/layout/widget_interactive_entry.xml new file mode 100644 index 000000000..3a5cb7875 --- /dev/null +++ b/app/src/main/res/layout/widget_interactive_entry.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_interactive_note_list.xml b/app/src/main/res/layout/widget_interactive_note_list.xml new file mode 100644 index 000000000..c72bfdb58 --- /dev/null +++ b/app/src/main/res/layout/widget_interactive_note_list.xml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/widget_interactive_preview.xml b/app/src/main/res/layout/widget_interactive_preview.xml new file mode 100644 index 000000000..afd8ed8d6 --- /dev/null +++ b/app/src/main/res/layout/widget_interactive_preview.xml @@ -0,0 +1,128 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/values-night/colors.xml b/app/src/main/res/values-night/colors.xml index 0857691c8..e681da78d 100644 --- a/app/src/main/res/values-night/colors.xml +++ b/app/src/main/res/values-night/colors.xml @@ -35,6 +35,7 @@ #dd000000 #d8d8d8 + #2b2b2b #222222 #ffffff diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml index f3a39be8a..a72fd5899 100644 --- a/app/src/main/res/values/colors.xml +++ b/app/src/main/res/values/colors.xml @@ -47,6 +47,7 @@ #ddffffff #222222 + #ffffff #ededed #666666 diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index b42e9c41b..cf8f26444 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -76,6 +76,10 @@ 4dp 26dp 20dp + 48dp + @dimen/spacer_2x + 11dp + 88dp 48dp diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 80d4284fc..509154536 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -245,6 +245,16 @@ Note not found Please login to Notes before using this widget Star icon is used to denote an item as a favorite + Interactive notes + Create note + Favorites + Favorites on top + Sort order + Newest first + Oldest first + Notes to show + Add widget + Edit widget Select note diff --git a/app/src/main/res/xml/interactive_note_list_widget_provider_info.xml b/app/src/main/res/xml/interactive_note_list_widget_provider_info.xml new file mode 100644 index 000000000..cd3f58f23 --- /dev/null +++ b/app/src/main/res/xml/interactive_note_list_widget_provider_info.xml @@ -0,0 +1,20 @@ + + + diff --git a/app/src/test/java/it/niedermann/owncloud/notes/persistence/WidgetNotesListDaoTest.kt b/app/src/test/java/it/niedermann/owncloud/notes/persistence/WidgetNotesListDaoTest.kt new file mode 100644 index 000000000..7e3ff6806 --- /dev/null +++ b/app/src/test/java/it/niedermann/owncloud/notes/persistence/WidgetNotesListDaoTest.kt @@ -0,0 +1,93 @@ +/* + * Nextcloud Notes - Android Client + * + * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ +package it.niedermann.owncloud.notes.persistence + +import androidx.arch.core.executor.testing.InstantTaskExecutorRule +import androidx.room.Room +import androidx.test.core.app.ApplicationProvider +import it.niedermann.owncloud.notes.persistence.entity.Account +import it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData +import it.niedermann.owncloud.notes.shared.model.Capabilities +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Rule +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner +import org.robolectric.annotation.Config + +@RunWith(RobolectricTestRunner::class) +@Config(sdk = [36]) +class WidgetNotesListDaoTest { + + @get:Rule + val instantTaskExecutorRule = InstantTaskExecutorRule() + + private lateinit var db: NotesDatabase + private var accountId: Long = 0 + + @Before + fun setupDB() { + db = Room + .inMemoryDatabaseBuilder(ApplicationProvider.getApplicationContext(), NotesDatabase::class.java) + .allowMainThreadQueries() + .build() + + val account = Account(ACCOUNT_URL, ACCOUNT_USERNAME, ACCOUNT_NAME, null, Capabilities()) + accountId = db.accountDao.insert(account) + } + + @After + fun closeDb() { + db.close() + } + + @Test + fun insertThenReadWidgetData() { + db.widgetNotesListDao.createOrUpdateNoteListWidgetData( + widgetData(NotesListWidgetData.MODE_DISPLAY_ALL) + ) + + val loaded = db.widgetNotesListDao.getNoteListWidgetData(WIDGET_ID) + assertNotNull(loaded) + assertEquals(NotesListWidgetData.MODE_DISPLAY_ALL, loaded.mode) + } + + /** + * Reconfiguring an interactive widget saves again for the same appWidgetId (primary key). + * The DAO must upsert; a plain insert would abort on the primary-key conflict. + */ + @Test + fun reconfigureUpdatesExistingWidgetData() { + db.widgetNotesListDao.createOrUpdateNoteListWidgetData( + widgetData(NotesListWidgetData.MODE_DISPLAY_ALL) + ) + db.widgetNotesListDao.createOrUpdateNoteListWidgetData( + widgetData(NotesListWidgetData.MODE_DISPLAY_STARRED) + ) + + val loaded = db.widgetNotesListDao.getNoteListWidgetData(WIDGET_ID) + assertNotNull(loaded) + assertEquals(NotesListWidgetData.MODE_DISPLAY_STARRED, loaded.mode) + } + + private fun widgetData(mode: Int) = NotesListWidgetData().apply { + id = WIDGET_ID + accountId = this@WidgetNotesListDaoTest.accountId + this.mode = mode + category = null + } + + companion object { + private const val WIDGET_ID = 1 + private const val ACCOUNT_URL = "https://example.com" + private const val ACCOUNT_USERNAME = "user" + private const val ACCOUNT_NAME = "user@example.com" + } +} From acaf384fed54e11514c381974b4cab8f585f7380 Mon Sep 17 00:00:00 2001 From: loloakira <6253351+loloakira@users.noreply.github.com> Date: Sat, 19 Sep 2026 00:26:05 +0200 Subject: [PATCH 2/3] fix(widget): drop the bundled preview image and route updates via widget ids The interactive widget shipped a screenshot as previewImage. The repository does not keep a preview asset per feature, so the drawable, its provider-info attribute and its REUSE entry are removed; previewLayout still gives Android 12+ a live preview in the picker. Updates now carry EXTRA_APPWIDGET_IDS, so AppWidgetProvider.onReceive dispatches to onUpdate on its own and the hand-written onReceive override, along with its INVALID_APPWIDGET_ID fallback, is gone. The configuration result intent keeps EXTRA_APPWIDGET_ID, which is what the widget host expects. AI-assistant: Claude Code 2.1.226 (Claude Opus 5) Assisted-by: ClaudeCode:claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: loloakira <6253351+loloakira@users.noreply.github.com> --- REUSE.toml | 2 +- .../InteractiveNoteListWidget.kt | 35 ++---------------- ...tiveNoteListWidgetConfigurationActivity.kt | 5 ++- .../interactive_note_list_widget_preview.webp | Bin 27184 -> 0 bytes .../res/layout/widget_interactive_entry.xml | 4 +- ...ractive_note_list_widget_provider_info.xml | 5 ++- 6 files changed, 14 insertions(+), 37 deletions(-) delete mode 100644 app/src/main/res/drawable/interactive_note_list_widget_preview.webp diff --git a/REUSE.toml b/REUSE.toml index bac23a37a..c8cc20b8a 100644 --- a/REUSE.toml +++ b/REUSE.toml @@ -6,7 +6,7 @@ SPDX-PackageSupplier = "Nextcloud Android team " SPDX-PackageDownloadLocation = "https://github.com/nextcloud/notes-android" [[annotations]] -path = ["app/schemas/it.niedermann.owncloud.notes.persistence.NotesDatabase/**.json", "app/src/main/res/values-**/strings.xml", "renovate.json5", "**/.gitignore", ".idea/**", "scripts/analysis/findbugs-results.txt", "scripts/analysis/lint-results.txt", "app/src/test/resources/robolectric.properties", "app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker", "fastlane/metadata/**", "app/src/main/res/mipmap-**dpi/ic_launcher.png", "app/src/main/ic_launcher-web.png", ".bundle/config", "app/src/main/res/drawable-**dpi/ic_widget_create.png", "app/src/main/res/drawable-mdpi/context_based_formatting.png", "app/src/main/res/drawable/note_list_widget_preview.webp", "app/src/main/res/drawable/single_note_widget.webp", "app/src/main/res/drawable/interactive_note_list_widget_preview.webp", "app/src/qa/res/drawable/ic_launcher_foreground.xml"] +path = ["app/schemas/it.niedermann.owncloud.notes.persistence.NotesDatabase/**.json", "app/src/main/res/values-**/strings.xml", "renovate.json5", "**/.gitignore", ".idea/**", "scripts/analysis/findbugs-results.txt", "scripts/analysis/lint-results.txt", "app/src/test/resources/robolectric.properties", "app/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker", "fastlane/metadata/**", "app/src/main/res/mipmap-**dpi/ic_launcher.png", "app/src/main/ic_launcher-web.png", ".bundle/config", "app/src/main/res/drawable-**dpi/ic_widget_create.png", "app/src/main/res/drawable-mdpi/context_based_formatting.png", "app/src/main/res/drawable/note_list_widget_preview.webp", "app/src/main/res/drawable/single_note_widget.webp", "app/src/qa/res/drawable/ic_launcher_foreground.xml"] precedence = "aggregate" SPDX-FileCopyrightText = "2016-2025 Nextcloud GmbH and Nextcloud contributors" SPDX-License-Identifier = "GPL-3.0-or-later" diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt index 136864783..332f823c4 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt @@ -15,7 +15,6 @@ import android.content.Intent import android.content.res.ColorStateList import android.os.Build import android.os.Bundle -import android.util.Log import android.widget.RemoteViews import androidx.core.net.toUri import it.niedermann.owncloud.notes.R @@ -41,34 +40,6 @@ class InteractiveNoteListWidget : AppWidgetProvider() { updateAppWidget(context, appWidgetManager, appWidgetIds) } - override fun onReceive(context: Context, intent: Intent) { - super.onReceive(context, intent) - val awm = AppWidgetManager.getInstance(context) - - if (intent.action == null) { - Log.w(TAG, "Intent action is null") - return - } - - if (intent.action != AppWidgetManager.ACTION_APPWIDGET_UPDATE) { - Log.w(TAG, "Intent action is not ACTION_APPWIDGET_UPDATE") - return - } - - val appWidgetIds = if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) { - intArrayOf( - intent.getIntExtra( - AppWidgetManager.EXTRA_APPWIDGET_ID, - AppWidgetManager.INVALID_APPWIDGET_ID - ) - ) - } else { - awm.getAppWidgetIds(ComponentName(context, InteractiveNoteListWidget::class.java)) - } - - updateAppWidget(context, awm, appWidgetIds) - } - override fun onDeleted(context: Context, appWidgetIds: IntArray) { super.onDeleted(context, appWidgetIds) val repo = NotesRepository.getInstance(context) @@ -82,8 +53,6 @@ class InteractiveNoteListWidget : AppWidgetProvider() { } companion object { - private val TAG: String = InteractiveNoteListWidget::class.java.simpleName - fun updateAppWidget(context: Context, awm: AppWidgetManager, appWidgetIds: IntArray) { val repo = NotesRepository.getInstance(context) appWidgetIds.forEach { appWidgetId -> @@ -167,8 +136,12 @@ class InteractiveNoteListWidget : AppWidgetProvider() { @JvmStatic fun updateInteractiveNoteListWidgets(context: Context) { + val appWidgetIds = AppWidgetManager.getInstance(context) + .getAppWidgetIds(ComponentName(context, InteractiveNoteListWidget::class.java)) + val intent = Intent(context, InteractiveNoteListWidget::class.java).apply { action = AppWidgetManager.ACTION_APPWIDGET_UPDATE + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, appWidgetIds) } context.sendBroadcast(intent) } diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.kt index 39b0df467..ef30cb03b 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.kt +++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.kt @@ -139,7 +139,10 @@ class InteractiveNoteListWidgetConfigurationActivity : LockedActivity(), Navigat null, applicationContext, InteractiveNoteListWidget::class.java - ).putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + ).apply { + putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId) + putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, intArrayOf(appWidgetId)) + } applicationContext.sendBroadcast(updateIntent) diff --git a/app/src/main/res/drawable/interactive_note_list_widget_preview.webp b/app/src/main/res/drawable/interactive_note_list_widget_preview.webp deleted file mode 100644 index 367115b15bdc7b637ec27a3defac6380e190a74e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 27184 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 z z04GqfX+cor!Ymq!VQ%X&WgC1rTuttIrF-o$@<(w9gB&n78Eb5+_PJPIE(Mz$>HJ$g zxSj#}%1AB-3twTogz%aahoxrAU%!DlH*4+U+JRKl&vMkE+3L@Zgm_s(j$ zAE{b}GBo-BGn)>3Q>~|c6+CKFoJ7>LwXg^ScYG`;Mk4fpC8lM09NF9xcX%1uDN5W& z_VVvZZ)~2AXnlwa#%mpY;4^iOcuJd;kH{_(-!k1&*}-&h5Onq@X9BfYjY__}sAM*+ z(Mc}cwIXBAZn!h}J07IBRGiKzi24cV=6XK@f6F$c%I$Wd3U>=^LuCawd6@^DSNqN? zOA{B)@O*2E2gGH&i&}nv(m3Ut6_TkJZB^uRE6!6GQQF z!2g1@8EDKu*Ge%IJ<^X`P(hDS{^oE3`+K zI&Y;!<&t?$)zGzR>fo@dC9bJ_)l;oNRfOt_7yuBy4G#QNB)!07cjt#-C$Z~kLyY8+gukPOkpW~;qG6-T;Pk8r#K&BNQEXgz|<>Gm6 z*(G21m{+v+`ZB33497cpjc_QC;sNJGHhXNwXN2jStD5I6x~1}UZ)o7KOSAA{`GteU z0l<#Es+bbk_=@r8qP;|blH^9_ + android:previewLayout="@layout/widget_interactive_preview" + tools:targetApi="s" /> From 59c27e1a9e14a0cc4862a80367faa378a4deaa03 Mon Sep 17 00:00:00 2001 From: loloakira <6253351+loloakira@users.noreply.github.com> Date: Sat, 19 Sep 2026 00:26:12 +0200 Subject: [PATCH 3/3] fix(notes): strip the note title from the excerpt only when the first line matches generateNoteExcerpt removed the title from the content by string prefix, so a note titled "Asdasd" whose first line is "Asdasd1" lost the first six characters and showed "1". The title is now only dropped when the whole first line equals it, which is the case the excerpt is meant to avoid duplicating. Excerpts are stored in the database, so notes written before this change keep their truncated excerpt until they are saved or synchronised again. AI-assistant: Claude Code 2.1.226 (Claude Opus 5) Assisted-by: ClaudeCode:claude-opus-5 Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: loloakira <6253351+loloakira@users.noreply.github.com> --- .../it/niedermann/owncloud/notes/shared/util/NoteUtil.java | 6 ++++-- .../niedermann/owncloud/notes/shared/util/NoteUtilTest.kt | 7 +++++++ 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NoteUtil.java b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NoteUtil.java index e6d8110e6..a93b99c4f 100644 --- a/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NoteUtil.java +++ b/app/src/main/java/it/niedermann/owncloud/notes/shared/util/NoteUtil.java @@ -85,8 +85,10 @@ public static String generateNoteExcerpt(@NonNull String content, @Nullable Stri if (!TextUtils.isEmpty(title)) { final String trimmedTitle = removeMarkdown(replaceCheckboxesWithEmojis(title.trim())); - if (result.startsWith(trimmedTitle)) { - result = result.substring(trimmedTitle.length()); + final int firstLineEnd = result.indexOf('\n'); + final String firstLine = firstLineEnd < 0 ? result : result.substring(0, firstLineEnd); + if (firstLine.equals(trimmedTitle)) { + result = firstLineEnd < 0 ? "" : result.substring(firstLineEnd + 1); } } diff --git a/app/src/test/java/it/niedermann/owncloud/notes/shared/util/NoteUtilTest.kt b/app/src/test/java/it/niedermann/owncloud/notes/shared/util/NoteUtilTest.kt index ed2495f92..c99482154 100644 --- a/app/src/test/java/it/niedermann/owncloud/notes/shared/util/NoteUtilTest.kt +++ b/app/src/test/java/it/niedermann/owncloud/notes/shared/util/NoteUtilTest.kt @@ -64,6 +64,7 @@ class NoteUtilTest : TestCase() { fun testGenerateNoteExcerpt() { testBasicExcerpts() testTitleMatchingExcerpts() + testTitleAsPrefixOfFirstLine() testEmptyLineHandling() testMarkdownInContent() testMarkdownInTitle() @@ -89,6 +90,12 @@ class NoteUtilTest : TestCase() { assertEquals("", NoteUtil.generateNoteExcerpt("", "Title")) } + private fun testTitleAsPrefixOfFirstLine() { + assertEquals("Asdasd1 Asda22", NoteUtil.generateNoteExcerpt("Asdasd1\nAsda22", "Asdasd")) + assertEquals("Titles Foo", NoteUtil.generateNoteExcerpt("Titles\nFoo", "Title")) + assertEquals("Title suffix", NoteUtil.generateNoteExcerpt("Title suffix", "Title")) + } + private fun testEmptyLineHandling() { // some empty lines between the actual contents → Should be ignored assertEquals("", NoteUtil.generateNoteExcerpt("\nTitle", "Title"))