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 @@ + + +