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/widget/interactivelist/InteractiveNoteListWidget.kt b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt
new file mode 100644
index 000000000..6ef26c5da
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidget.kt
@@ -0,0 +1,171 @@
+/*
+ * 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
+ }
+
+ if (!intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
+ updateAppWidget(
+ context,
+ awm,
+ awm.getAppWidgetIds(ComponentName(context, InteractiveNoteListWidget::class.java))
+ )
+ }
+
+ val appWidgetIds = intArrayOf(intent.extras?.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1) ?: -1)
+
+ 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 ->
+ repo.getNoteListWidgetData(appWidgetId)?.let { data ->
+ 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.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.java
new file mode 100644
index 000000000..a3f218564
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetConfigurationActivity.java
@@ -0,0 +1,175 @@
+/*
+ * 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.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.Toast;
+
+import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
+import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
+import com.nextcloud.android.sso.helper.SingleAccountHelper;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+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.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.widget.notelist.NoteListViewModel;
+
+import static it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData.MODE_DISPLAY_ALL;
+import static it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData.MODE_DISPLAY_CATEGORY;
+import static it.niedermann.owncloud.notes.persistence.entity.NotesListWidgetData.MODE_DISPLAY_STARRED;
+import static it.niedermann.owncloud.notes.shared.model.ENavigationCategoryType.RECENT;
+
+public class InteractiveNoteListWidgetConfigurationActivity extends LockedActivity {
+ private static final String TAG = InteractiveNoteListWidgetConfigurationActivity.class.getSimpleName();
+
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+
+ private int appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID;
+
+ private Account localAccount = null;
+
+ private ActivityInteractiveWidgetConfigurationBinding binding;
+ private NoteListViewModel viewModel;
+ private NavigationAdapter adapterCategories;
+ private NotesRepository repo = null;
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ setResult(RESULT_CANCELED);
+
+ repo = NotesRepository.getInstance(this);
+ final var args = getIntent().getExtras();
+
+ if (args != null) {
+ appWidgetId = args.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ }
+
+ if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ Log.d(TAG, "INVALID_APPWIDGET_ID");
+ finish();
+ }
+
+ viewModel = new ViewModelProvider(this).get(NoteListViewModel.class);
+ binding = ActivityInteractiveWidgetConfigurationBinding.inflate(getLayoutInflater());
+ setContentView(binding.getRoot());
+
+ binding.favoritesFirstCheckbox.setChecked(InteractiveWidgetPreferences.isFavoritesFirst(this, appWidgetId));
+ if (InteractiveWidgetPreferences.getSortOrder(this, appWidgetId) == WidgetSortOrder.OLDEST_FIRST) {
+ binding.sortOldestFirst.setChecked(true);
+ } else {
+ binding.sortNewestFirst.setChecked(true);
+ }
+
+ adapterCategories = new NavigationAdapter(this, new NavigationClickListener() {
+ @Override
+ public void onItemClick(NavigationItem item) {
+ final boolean favoritesFirst = binding.favoritesFirstCheckbox.isChecked();
+ final WidgetSortOrder sortOrder = binding.sortOldestFirst.isChecked()
+ ? WidgetSortOrder.OLDEST_FIRST
+ : WidgetSortOrder.NEWEST_FIRST;
+
+ final var data = new NotesListWidgetData();
+
+ data.setId(appWidgetId);
+ if (item.type != null) {
+ switch (item.type) {
+ case RECENT: {
+ data.setMode(MODE_DISPLAY_ALL);
+ break;
+ }
+ case FAVORITES: {
+ data.setMode(MODE_DISPLAY_STARRED);
+ break;
+ }
+ case UNCATEGORIZED: {
+ data.setMode(MODE_DISPLAY_CATEGORY);
+ data.setCategory(null);
+ break;
+ }
+ case DEFAULT_CATEGORY:
+ default: {
+ if (item.getClass() == NavigationItem.CategoryNavigationItem.class) {
+ data.setMode(MODE_DISPLAY_CATEGORY);
+ data.setCategory(((NavigationItem.CategoryNavigationItem) item).category);
+ } else {
+ data.setMode(MODE_DISPLAY_ALL);
+ Log.e(TAG, "Unknown item navigation type. Fallback to show " + RECENT);
+ }
+ }
+ }
+ } else {
+ data.setMode(MODE_DISPLAY_ALL);
+ Log.e(TAG, "Unknown item navigation type. Fallback to show " + RECENT);
+ }
+
+ data.setAccountId(localAccount.getId());
+ data.setThemeMode(NotesApplication.getAppTheme(getApplicationContext()).getModeId());
+
+ executor.submit(() -> {
+ repo.createOrUpdateNoteListWidgetData(data);
+ InteractiveWidgetPreferences.save(getApplicationContext(), appWidgetId, favoritesFirst, sortOrder);
+
+ final var updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, getApplicationContext(), InteractiveNoteListWidget.class)
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ setResult(RESULT_OK, updateIntent);
+ getApplicationContext().sendBroadcast(updateIntent);
+ finish();
+ });
+ }
+
+ @Override
+ public void onIconClick(NavigationItem item) {
+ onItemClick(item);
+ }
+ });
+
+ binding.recyclerView.setAdapter(adapterCategories);
+
+ executor.submit(() -> {
+ try {
+ this.localAccount = repo.getAccountByName(SingleAccountHelper.getCurrentSingleSignOnAccount(this).name);
+ } catch (NextcloudFilesAppAccountNotFoundException | NoCurrentAccountSelectedException e) {
+ Log.w(TAG, "Account not found", e);
+ Toast.makeText(this, R.string.widget_not_logged_in, Toast.LENGTH_LONG).show();
+ Log.w(TAG, "onCreate: user not logged in");
+ finish();
+ }
+ runOnUiThread(() -> viewModel.getAdapterCategories(localAccount.getId()).observe(this, (navigationItems) -> adapterCategories.setItems(navigationItems)));
+ });
+ }
+
+ @Override
+ public void applyBrand(int color) {
+ if (binding == null) {
+ return;
+ }
+
+ final var util = BrandingUtil.of(color, this);
+ util.platform.themeCheckbox(binding.favoritesFirstCheckbox);
+ util.platform.themeRadioButton(binding.sortNewestFirst);
+ util.platform.themeRadioButton(binding.sortOldestFirst);
+ }
+}
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..d2eaa7f8e
--- /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()
+ private var data: NotesListWidgetData? = null
+
+ override fun onCreate() = Unit
+
+ override fun onDataSetChanged() {
+ dbNotes.clear()
+ try {
+ data = repo.getNoteListWidgetData(appWidgetId)
+ if (data == null) {
+ Log.w(TAG, "Widget data is null")
+ return
+ }
+ val widgetData = data ?: 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()
+ } catch (e: Exception) {
+ Log.w(TAG, "Error caught at onDataSetChanged: $e")
+ }
+ }
+
+ private fun applySorting() {
+ val byDate: Comparator = if (InteractiveWidgetPreferences.getSortOrder(context, appWidgetId) == WidgetSortOrder.OLDEST_FIRST) {
+ compareBy { it.modified?.timeInMillis ?: 0L }
+ } else {
+ compareByDescending { it.modified?.timeInMillis ?: 0L }
+ }
+ val comparator = if (InteractiveWidgetPreferences.isFavoritesFirst(context, appWidgetId)) {
+ compareByDescending { it.favorite }.then(byDate)
+ } else {
+ byDate
+ }
+ dbNotes.sortWith(comparator)
+ }
+
+ override fun onDestroy() = Unit
+
+ override fun getCount(): Int {
+ return dbNotes.size
+ }
+
+ private fun getOpenNoteIntent(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()
+ }
+ }
+
+ 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(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
+ }
+}
diff --git a/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetService.java b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetService.java
new file mode 100644
index 000000000..8bb8ab353
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveNoteListWidgetService.java
@@ -0,0 +1,17 @@
+/*
+ * 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;
+
+public class InteractiveNoteListWidgetService extends RemoteViewsService {
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ return new InteractiveNoteListWidgetFactory(this.getApplicationContext(), 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..46518622f
--- /dev/null
+++ b/app/src/main/java/it/niedermann/owncloud/notes/widget/interactivelist/InteractiveWidgetPreferences.kt
@@ -0,0 +1,46 @@
+/*
+ * 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_"
+
+ @JvmStatic
+ 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)
+ }
+ }
+
+ @JvmStatic
+ fun isFavoritesFirst(context: Context, appWidgetId: Int): Boolean =
+ prefs(context).getBoolean(KEY_FAVORITES_FIRST + appWidgetId, false)
+
+ @JvmStatic
+ 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)
+ }
+
+ @JvmStatic
+ 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/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 000000000..367115b15
Binary files /dev/null and b/app/src/main/res/drawable/interactive_note_list_widget_preview.webp differ
diff --git a/app/src/main/res/drawable/widget_card_background.xml b/app/src/main/res/drawable/widget_card_background.xml
new file mode 100644
index 000000000..44654b997
--- /dev/null
+++ b/app/src/main/res/drawable/widget_card_background.xml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
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..3ff0153ba
--- /dev/null
+++ b/app/src/main/res/layout/activity_interactive_widget_configuration.xml
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
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..379270717 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -76,6 +76,9 @@
4dp
26dp
20dp
+ 48dp
+ @dimen/spacer_2x
+ 11dp
48dp
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 1d0c39640..9694440db 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -244,6 +244,12 @@
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
+ Favourites on top
+ Sort order
+ Newest first
+ Oldest first
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 @@
+
+
+