diff options
13 files changed, 444 insertions, 0 deletions
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index f936fac67..847f5c0c6 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -136,6 +136,25 @@ android:name=".ui.exception.ExceptionActivity" android:process=":error_activity" /> + <activity android:name=".ui.widget.filter.FilterWidgetConfigurationActivity"> + <intent-filter> + <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> + </intent-filter> + </activity> + + <receiver + android:name="it.niedermann.nextcloud.deck.ui.widget.filter.FilterWidget" + android:label="@string/widget_filter_title"> + + <intent-filter> + <action android:name="android.appwidget.action.APPWIDGET_UPDATE" /> + </intent-filter> + + <meta-data + android:name="android.appwidget.provider" + android:resource="@xml/filter_widget_provider" /> + </receiver> + <activity android:name=".ui.widget.stack.StackWidgetConfigurationActivity"> <intent-filter> <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" /> diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetConfiguration.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetConfiguration.java new file mode 100644 index 000000000..0404c530c --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetConfiguration.java @@ -0,0 +1,8 @@ +package it.niedermann.nextcloud.deck.model.widget.filter; + +/** + * Contains all configurations related to a single {@link FilterWidget} instance. + */ +public class FilterWidgetConfiguration { + // TODO implement +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetData.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetData.java new file mode 100644 index 000000000..b419ee8b3 --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetData.java @@ -0,0 +1,18 @@ +package it.niedermann.nextcloud.deck.model.widget.filter; + +import java.util.Collections; +import java.util.List; + +import it.niedermann.nextcloud.deck.model.full.FullCard; + +/** + * Contains the payload data of a single {@link FilterWidget} instance + */ +// TODO implement +public class FilterWidgetData { + + // TODO maybe some kind of helping structure to get transport the sorting information + public List<FullCard> getCards() { + return Collections.emptyList(); + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java index 4d1829193..c12a347ef 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java @@ -61,6 +61,8 @@ import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment; import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment; import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment; import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource; +import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetConfiguration; +import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetData; import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter; import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper; @@ -1967,6 +1969,26 @@ public class SyncManager { } /** + * If a filter widget with the given id exists, it will be updated, otherwise it will be created. + */ + public void updateFilterWidgetConfiguration(@NonNull FilterWidgetConfiguration data) { + // TODO + throw new UnsupportedOperationException("Not yet implemented"); + } + + public void deleteFilterWidget(int appWidgetId) { + doAsync(() -> dataBaseAdapter.deleteFilterWidget(appWidgetId)); + } + + /** + * Returns the payload of the give {@param appWidgetId}. + */ + public LiveData<FilterWidgetData> getFilterWidgetData(int appWidgetId) { + // TODO + throw new UnsupportedOperationException("Not yet implemented"); + } + + /** * FIXME https://github.com/stefan-niedermann/nextcloud-deck/issues/640 */ public static boolean ignoreExceptionOnVoidError(Throwable t) { diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java index 982844f06..e02ce7aab 100644 --- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java +++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java @@ -1052,6 +1052,11 @@ public class DataBaseAdapter { return db.getStackWidgetModelDao().getStackWidgetByAppWidgetIdDirectly(appWidgetId); } + public void deleteFilterWidget(int appWidgetId) { + // TODO + throw new UnsupportedOperationException("Not yet implemented"); + } + public void deleteStackWidget(int appWidgetId) { StackWidgetModel model = new StackWidgetModel(); model.setAppWidgetId(appWidgetId); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidget.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidget.java new file mode 100644 index 000000000..263ac363e --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidget.java @@ -0,0 +1,84 @@ +package it.niedermann.nextcloud.deck.ui.widget.filter; + +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProvider; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; + +import java.util.NoSuchElementException; + +import it.niedermann.nextcloud.deck.model.Account; +import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; + +import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE; + +public class FilterWidget extends AppWidgetProvider { + public static final String ACCOUNT_KEY = "filter_widget_account"; + public static final String BUNDLE_KEY = "filter_widget_bundle"; + + static void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds, Account account) { + final SyncManager syncManager = new SyncManager(context); + + for (int appWidgetId : appWidgetIds) { + new Thread(() -> { + try { + // TODO implement + throw new UnsupportedOperationException("Not yet implemented"); + } catch (NoSuchElementException e) { + // onUpdate has been triggered before the user finished configuring the widget + } + }).start(); + } + } + + @Override + public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) { + super.onUpdate(context, appWidgetManager, appWidgetIds); + updateAppWidget(context, appWidgetManager, appWidgetIds, null); + } + + @Override + public void onReceive(Context context, Intent intent) { + final Account account; + + super.onReceive(context, intent); + + AppWidgetManager awm = AppWidgetManager.getInstance(context); + + if (intent.getAction() != null) { + if (intent.getAction().equals(ACTION_APPWIDGET_UPDATE)) { + if (intent.hasExtra(BUNDLE_KEY)) { + Bundle extras = intent.getBundleExtra(FilterWidget.BUNDLE_KEY); + account = (Account) extras.getSerializable(ACCOUNT_KEY); + + if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) { + if (intent.getExtras() != null) { + updateAppWidget(context, awm, new int[]{intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1)}, account); + } + } else { + updateAppWidget(context, awm, awm.getAppWidgetIds(new ComponentName(context, FilterWidget.class)), account); + } + } + } + } + } + + @Override + public void onDeleted(Context context, int[] appWidgetIds) { + super.onDeleted(context, appWidgetIds); + final SyncManager syncManager = new SyncManager(context); + + for (int appWidgetId : appWidgetIds) { + syncManager.deleteFilterWidget(appWidgetId); + } + } + + /** + * Updates UI data of all {@link FilterWidget} instances + */ + public static void notifyDatasetChanged(Context context) { + context.sendBroadcast(new Intent(context, FilterWidget.class).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE)); + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetConfigurationActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetConfigurationActivity.java new file mode 100644 index 000000000..ac466961d --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetConfigurationActivity.java @@ -0,0 +1,66 @@ +package it.niedermann.nextcloud.deck.ui.widget.filter; + +import android.appwidget.AppWidgetManager; +import android.content.Intent; +import android.os.Bundle; + +import androidx.appcompat.app.ActionBar; +import androidx.appcompat.app.AppCompatActivity; +import androidx.lifecycle.ViewModelProvider; + +import it.niedermann.nextcloud.deck.DeckLog; +import it.niedermann.nextcloud.deck.R; +import it.niedermann.nextcloud.deck.databinding.ActivityFilterWidgetBinding; +import it.niedermann.nextcloud.deck.model.Account; +import it.niedermann.nextcloud.deck.ui.widget.stack.StackWidget; + +public class FilterWidgetConfigurationActivity extends AppCompatActivity { + private int appWidgetId; + private ActivityFilterWidgetBinding binding; + private FilterWidgetViewModel viewModel; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + binding = ActivityFilterWidgetBinding.inflate(getLayoutInflater()); + viewModel = new ViewModelProvider(this).get(FilterWidgetViewModel.class); + + final ActionBar actionBar = getSupportActionBar(); + if (actionBar != null) { + actionBar.setTitle(R.string.add_filter_widget); + } + + setResult(RESULT_CANCELED); + final Bundle extras = getIntent().getExtras(); + + if (extras != null) { + appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, + AppWidgetManager.INVALID_APPWIDGET_ID); + } + + if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) { + DeckLog.error("INVALID_APPWIDGET_ID"); + finish(); + } + } + + protected void onSubmit(Account account, long boardId, long stackId) { + final Bundle extras = new Bundle(); + + viewModel.updateFilterWidget(); + Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, + getApplicationContext(), StackWidget.class); + extras.putSerializable(StackWidget.ACCOUNT_KEY, account); + extras.putInt(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId); + + // The `extras` bundle is added to the intent this way because using putExtras(extras) + // would have the OS attempt to reassemle the data and cause a crash + // when it finds classes that are only known to this application. + updateIntent.putExtra(StackWidget.BUNDLE_KEY, extras); + setResult(RESULT_OK, updateIntent); + getApplicationContext().sendBroadcast(updateIntent); + + finish(); + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetFactory.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetFactory.java new file mode 100644 index 000000000..cd2a6225b --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetFactory.java @@ -0,0 +1,103 @@ +package it.niedermann.nextcloud.deck.ui.widget.filter; + +import android.appwidget.AppWidgetManager; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.widget.RemoteViews; +import android.widget.RemoteViewsService; + +import androidx.lifecycle.LiveData; + +import it.niedermann.nextcloud.deck.DeckLog; +import it.niedermann.nextcloud.deck.R; +import it.niedermann.nextcloud.deck.model.full.FullCard; +import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetData; +import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; +import it.niedermann.nextcloud.deck.ui.widget.stack.StackWidget; + +public class FilterWidgetFactory implements RemoteViewsService.RemoteViewsFactory { + private final Context context; + private final int appWidgetId; + + private FilterWidgetData data; + + FilterWidgetFactory(Context context, Intent intent) { + this.context = context; + appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID); + } + + @Override + public void onCreate() { + final SyncManager syncManager = new SyncManager(context); + + LiveData<FilterWidgetData> filterWidgetData$ = syncManager.getFilterWidgetData(appWidgetId); + filterWidgetData$.observeForever((data) -> { + if (data != null) { + RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_stack); + this.data = data; + notifyAppWidgetUpdate(views); + } + }); + } + + @Override + public void onDataSetChanged() { + + } + + + @Override + public void onDestroy() { + + } + + @Override + public int getCount() { + return data == null ? 0 : data.getCards().size(); + } + + @Override + public RemoteViews getViewAt(int i) { + RemoteViews widget_entry; + + if (data.getCards() == null || i > (data.getCards().size() - 1) || data.getCards().get(i) == null) { + DeckLog.error("Card not found at position " + i); + return null; + } + + FullCard card = data.getCards().get(i); + + widget_entry = new RemoteViews(context.getPackageName(), R.layout.widget_stack_entry); + widget_entry.setTextViewText(R.id.widget_entry_content_tv, card.card.getTitle()); + + return widget_entry; + } + + @Override + public RemoteViews getLoadingView() { + return null; + } + + @Override + public int getViewTypeCount() { + return 1; + } + + @Override + public long getItemId(int i) { + return i; + } + + @Override + public boolean hasStableIds() { + return true; + } + + private void notifyAppWidgetUpdate(RemoteViews views) { + AppWidgetManager awm = AppWidgetManager.getInstance(context); + int[] appWidgetIds = awm.getAppWidgetIds(new ComponentName(context, StackWidget.class)); + awm.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stack_widget_lv); + awm.updateAppWidget(appWidgetId, views); + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetService.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetService.java new file mode 100644 index 000000000..20c4350ba --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetService.java @@ -0,0 +1,11 @@ +package it.niedermann.nextcloud.deck.ui.widget.filter; + +import android.content.Intent; +import android.widget.RemoteViewsService; + +public class FilterWidgetService extends RemoteViewsService { + @Override + public RemoteViewsFactory onGetViewFactory(Intent intent) { + return new FilterWidgetFactory(this.getApplicationContext(), intent); + } +} diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetViewModel.java new file mode 100644 index 000000000..14dc72aa9 --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetViewModel.java @@ -0,0 +1,33 @@ +package it.niedermann.nextcloud.deck.ui.widget.filter; + +import android.app.Application; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetConfiguration; +import it.niedermann.nextcloud.deck.persistence.sync.SyncManager; + +public class FilterWidgetViewModel extends AndroidViewModel { + + @NonNull + private final SyncManager syncManager; + @NonNull + private final MutableLiveData<FilterWidgetConfiguration> filterWidgetConfiguration$ = new MutableLiveData<>(new FilterWidgetConfiguration()); + + public FilterWidgetViewModel(@NonNull Application application) { + super(application); + this.syncManager = new SyncManager(application); + } + + public LiveData<FilterWidgetConfiguration> getFilterWidgetConfiguration() { + return this.filterWidgetConfiguration$; + } + + public void updateFilterWidget() { + //noinspection ConstantConditions + syncManager.updateFilterWidgetConfiguration(filterWidgetConfiguration$.getValue()); + } +} diff --git a/app/src/main/res/layout/activity_filter_widget.xml b/app/src/main/res/layout/activity_filter_widget.xml new file mode 100644 index 000000000..a2d5f409b --- /dev/null +++ b/app/src/main/res/layout/activity_filter_widget.xml @@ -0,0 +1,63 @@ +<?xml version="1.0" encoding="utf-8"?> +<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:app="http://schemas.android.com/apk/res-auto" + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + tools:showIn="@layout/activity_pick_stack"> + + <com.google.android.material.appbar.AppBarLayout + android:id="@+id/appBarLayout" + android:layout_width="match_parent" + android:layout_height="wrap_content"> + + <androidx.appcompat.widget.Toolbar + android:id="@+id/toolbar" + android:layout_width="match_parent" + android:layout_height="?android:actionBarSize" + app:title="@string/add_filter_widget" /> + </com.google.android.material.appbar.AppBarLayout> + + <ScrollView + android:layout_width="match_parent" + android:layout_height="match_parent" + android:layout_above="@+id/buttonBar" + android:layout_below="@id/appBarLayout" + android:padding="@dimen/spacer_2x"> + + <FrameLayout + android:id="@+id/fragment_container" + android:layout_width="match_parent" + android:layout_height="wrap_content" /> + </ScrollView> + + <LinearLayout + android:id="@+id/buttonBar" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_alignParentBottom="true" + android:orientation="horizontal" + android:padding="@dimen/spacer_2x" + android:weightSum="1.0"> + + <com.google.android.material.button.MaterialButton + android:id="@+id/cancel" + style="@style/Widget.MaterialComponents.Button.TextButton" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/spacer_1x" + android:layout_weight=".5" + android:text="@android:string/cancel" + android:textColor="@color/defaultBrand" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/submit" + style="@style/Widget.MaterialComponents.Button" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/spacer_1x" + android:layout_weight=".5" + android:text="@string/simple_add" + app:backgroundTint="@color/defaultBrand" /> + </LinearLayout> +</RelativeLayout>
\ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index d6d9470fe..1956f00d5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -291,6 +291,7 @@ <string name="card_does_not_yet_exist">Card does not yet exist in Deck</string> <string name="widget_stack_title">List</string> + <string name="widget_filter_title">Filter</string> <string name="widget_stack_header_icon">Widget header icon</string> <string name="widget_stack_placeholder_icon">Widget placeholder icon</string> <string name="select_stack">Select list</string> @@ -327,4 +328,5 @@ <string name="files">Files</string> <string name="gallery">Gallery</string> <string name="simple_attach">attach</string> + <string name="add_filter_widget">Add filter widget</string> </resources> diff --git a/app/src/main/res/xml/filter_widget_provider.xml b/app/src/main/res/xml/filter_widget_provider.xml new file mode 100644 index 000000000..329bb233f --- /dev/null +++ b/app/src/main/res/xml/filter_widget_provider.xml @@ -0,0 +1,10 @@ +<?xml version="1.0" encoding="utf-8"?> +<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android" + android:minHeight="110dp" + android:minWidth="180dp" + android:minResizeHeight="40dp" + android:minResizeWidth="80dp" + android:resizeMode="vertical|horizontal" + android:updatePeriodMillis="86400000" + android:widgetCategory="keyguard|home_screen" + android:configure="it.niedermann.nextcloud.deck.ui.widget.filter.FilterWidgetConfigurationActivity" /> |