Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/stefan-niedermann/nextcloud-deck.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorStefan Niedermann <info@niedermann.it>2021-03-25 22:01:25 +0300
committerStefan Niedermann <info@niedermann.it>2021-03-25 22:01:25 +0300
commit7ae471d95d832d0f6f3460666ae7be76a9d714bc (patch)
tree656406b1dd1c8eeb7cf420fa87414f73f7c89d10 /app/src/main/java/it/niedermann/nextcloud
parent24360b8116a95d4a1e9bf49fa39b27a0ebf032bc (diff)
parentdcd3f7140c15634330f7284e096f9de6ef467cd6 (diff)
Merge branch 'master' into 587-compress-images
# Conflicts: # app/build.gradle # app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java # build.gradle
Diffstat (limited to 'app/src/main/java/it/niedermann/nextcloud')
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java102
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/DeckLog.java114
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/ApiProvider.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/DeckAPI.java116
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/IResponseCallback.java51
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java40
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudArrayDeserializer.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudDeserializer.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java19
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/ResponseCallback.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java28
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/enums/ActivityType.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/enums/EAttachmentType.java33
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/enums/EDueType.java31
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/enums/ESortCriteria.java35
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCard.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java33
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java26
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/EWidgetType.java45
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidget.java142
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetAccount.java161
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetBoard.java130
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetLabel.java79
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetProject.java89
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetSort.java126
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetStack.java89
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetUser.java89
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/dto/FilterWidgetCard.java45
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java426
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncWorker.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java49
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java418
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java435
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/converter/DateTypeConverter.java (renamed from app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DateTypeConverter.java)2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/converter/EnumConverter.java67
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java13
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/GenericDao.java3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetAccountDao.java18
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetBoardDao.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetDao.java44
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetLabelDao.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetProjectDao.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetSortDao.java18
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetStackDao.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetUserDao.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_10_11.java59
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_11_12.java20
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_12_13.java17
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_13_14.java20
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_14_15.java35
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_15_16.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_16_17.java34
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_17_18.java19
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_18_19.java21
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_19_20.java35
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_20_21.java123
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_21_22.java34
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_22_23.java30
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_23_24.java39
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_24_25.java22
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_25_26.java42
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_26_27.java62
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_27_28.java19
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_28_29.java22
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_29_30.java30
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_8_9.java27
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_9_10.java22
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java10
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java13
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java37
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardPropagationDataProvider.java42
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java1
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/util/RealPathUtils.java94
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java21
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java279
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java90
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/PickStackActivity.java24
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java186
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutActivity.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentCreditsTab.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java21
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java64
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java42
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentViewHolder.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsActivity.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java20
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java54
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsAdapter.java10
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java64
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedActivity.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDialogFragment.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedFragment.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedPreferenceCategory.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSwitchPreference.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandingUtil.java27
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java71
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardTabAdapter.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java120
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java26
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/LabelAutoCompleteAdapter.java3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentViewHolder.java19
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java170
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/DefaultAttachmentViewHolder.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/ImageAttachmentViewHolder.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsAdapter.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java31
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CommentsViewModel.java10
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java27
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java97
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceViewHolder.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java36
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsModel.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsViewHolder.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java10
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsViewModel.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java10
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/AccountAdapter.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java35
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsActivity.java17
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java74
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareTargetActivity.java29
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackAdapter.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java14
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareRelativeLayout.java3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidget.java78
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetConfigurationActivity.java67
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetFactory.java90
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetService.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetViewModel.java33
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java40
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidgetFactory.java100
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidgetService.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidget.java127
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationActivity.java54
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationViewModel.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetFactory.java83
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidget.java124
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidgetFactory.java224
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidgetService.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java91
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/CardUtil.java55
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/DateUtil.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/DrawerMenuUtil.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/FilesUtil.java78
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/MarkDownUtil.java47
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/SpannableUtil.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java70
165 files changed, 5399 insertions, 2163 deletions
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java b/app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java
index cf30a0971..972c098d6 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java
@@ -2,14 +2,20 @@ package it.niedermann.nextcloud.deck;
import android.content.Context;
import android.content.SharedPreferences;
+import android.content.res.Configuration;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.LiveData;
import androidx.multidex.MultiDexApplication;
import androidx.preference.PreferenceManager;
-import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO;
-import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
+import it.niedermann.android.sharedpreferences.SharedPreferenceIntLiveData;
+import it.niedermann.nextcloud.deck.model.Account;
+
import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode;
+import static androidx.lifecycle.Transformations.distinctUntilChanged;
public class DeckApplication extends MultiDexApplication {
@@ -17,46 +23,112 @@ public class DeckApplication extends MultiDexApplication {
public static final long NO_BOARD_ID = -1L;
public static final long NO_STACK_ID = -1L;
+ private static String PREF_KEY_THEME;
+ private static String PREF_KEY_DEBUGGING;
+
+ private static LiveData<Integer> currentAccountColor$;
+
@Override
public void onCreate() {
- setAppTheme(isDarkTheme(getApplicationContext()));
+ PREF_KEY_THEME = getString(R.string.pref_key_dark_theme);
+ PREF_KEY_DEBUGGING = getString(R.string.pref_key_debugging);
+ setAppTheme(getAppTheme(this));
+ DeckLog.enablePersistentLogs(isPersistentLoggingEnabled(this));
+ currentAccountColor$ = distinctUntilChanged(new SharedPreferenceIntLiveData(PreferenceManager.getDefaultSharedPreferences(this),
+ getString(R.string.shared_preference_last_account_color),
+ ContextCompat.getColor(this, R.color.defaultBrand)));
super.onCreate();
}
+ @Override
+ public void onLowMemory() {
+ super.onLowMemory();
+ DeckLog.clearDebugLog();
+ DeckLog.error("--- cleared log because of low memory ---");
+ }
+
+ // ---------
+ // Debugging
+ // ---------
+
+ public static boolean isPersistentLoggingEnabled(@NonNull Context context) {
+ final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ boolean enabled = sharedPreferences.getBoolean(PREF_KEY_DEBUGGING, false);
+ DeckLog.log("--- Read:", PREF_KEY_DEBUGGING, "|", enabled);
+ return enabled;
+ }
+
// -----------------
// Day / Night theme
// -----------------
- public static void setAppTheme(boolean darkTheme) {
- setDefaultNightMode(darkTheme ? MODE_NIGHT_YES : MODE_NIGHT_NO);
+ public static void setAppTheme(int setting) {
+ setDefaultNightMode(setting);
}
- public static boolean isDarkTheme(@NonNull Context context) {
+ public static int getAppTheme(@NonNull Context context) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- return prefs.getBoolean(context.getString(R.string.pref_key_dark_theme), false);
+ String mode;
+ try {
+ mode = prefs.getString(PREF_KEY_THEME, context.getString(R.string.pref_value_theme_system_default));
+ } catch (ClassCastException e) {
+ boolean darkModeEnabled = prefs.getBoolean(PREF_KEY_THEME, false);
+ mode = darkModeEnabled ? context.getString(R.string.pref_value_theme_dark) : context.getString(R.string.pref_value_theme_light);
+ }
+ return Integer.parseInt(mode);
+ }
+
+ public static boolean isDarkThemeActive(@NonNull Context context, int darkModeSetting) {
+ if (darkModeSetting == Integer.parseInt(context.getString(R.string.pref_value_theme_system_default))) {
+ return isDarkThemeActive(context);
+ } else {
+ return darkModeSetting == Integer.parseInt(context.getString(R.string.pref_value_theme_dark));
+ }
+ }
+
+ public static boolean isDarkThemeActive(@NonNull Context context) {
+ int uiMode = context.getResources().getConfiguration().uiMode;
+ return (uiMode & Configuration.UI_MODE_NIGHT_MASK) == Configuration.UI_MODE_NIGHT_YES;
+ }
+
+ public static boolean isDarkTheme(@NonNull Context context) {
+ return isDarkThemeActive(context, getAppTheme(context));
}
// --------------------------------------
// Current account / board / stack states
// --------------------------------------
- public static void saveCurrentAccountId(@NonNull Context context, long accountId) {
+ public static void saveCurrentAccount(@NonNull Context context, @NonNull Account account) {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
- DeckLog.log("--- Write: shared_preference_last_account" + " | " + accountId);
- editor.putLong(context.getString(R.string.shared_preference_last_account), accountId);
+ DeckLog.log("--- Write: shared_preference_last_account |", account.getId());
+ editor.putLong(context.getString(R.string.shared_preference_last_account), account.getId());
+ DeckLog.log("--- Write: shared_preference_last_account_color | ", account.getColor());
+ editor.putInt(context.getString(R.string.shared_preference_last_account_color), account.getColor());
editor.apply();
}
+ public static LiveData<Integer> readCurrentAccountColor() {
+ return currentAccountColor$;
+ }
+
+ @ColorInt
+ public static int readCurrentAccountColor(@NonNull Context context) {
+ final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
+ DeckLog.log("--- Read: shared_preference_last_account_color");
+ return sharedPreferences.getInt(context.getString(R.string.shared_preference_last_account_color), context.getApplicationContext().getResources().getColor(R.color.defaultBrand));
+ }
+
public static long readCurrentAccountId(@NonNull Context context) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
long accountId = sharedPreferences.getLong(context.getString(R.string.shared_preference_last_account), NO_ACCOUNT_ID);
- DeckLog.log("--- Read: shared_preference_last_account" + " | " + accountId);
+ DeckLog.log("--- Read: shared_preference_last_account |", accountId);
return accountId;
}
public static void saveCurrentBoardId(@NonNull Context context, long accountId, long boardId) {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
- DeckLog.log("--- Write: shared_preference_last_board_for_account_" + accountId + " | " + boardId);
+ DeckLog.log("--- Write: shared_preference_last_board_for_account_" + accountId, "|", boardId);
editor.putLong(context.getString(R.string.shared_preference_last_board_for_account_) + accountId, boardId);
editor.apply();
}
@@ -64,13 +136,13 @@ public class DeckApplication extends MultiDexApplication {
public static long readCurrentBoardId(@NonNull Context context, long accountId) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
long boardId = sharedPreferences.getLong(context.getString(R.string.shared_preference_last_board_for_account_) + accountId, NO_BOARD_ID);
- DeckLog.log("--- Read: shared_preference_last_board_for_account_" + accountId + " | " + boardId);
+ DeckLog.log("--- Read: shared_preference_last_board_for_account_" + accountId, "|", boardId);
return boardId;
}
public static void saveCurrentStackId(@NonNull Context context, long accountId, long boardId, long stackId) {
SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
- DeckLog.log("--- Write: shared_preference_last_stack_for_account_and_board_" + accountId + "_" + boardId + " | " + stackId);
+ DeckLog.log("--- Write: shared_preference_last_stack_for_account_and_board_" + accountId + "_" + boardId, "|", stackId);
editor.putLong(context.getString(R.string.shared_preference_last_stack_for_account_and_board_) + accountId + "_" + boardId, stackId);
editor.apply();
}
@@ -78,7 +150,7 @@ public class DeckApplication extends MultiDexApplication {
public static long readCurrentStackId(@NonNull Context context, long accountId, long boardId) {
SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
long savedStackId = sharedPreferences.getLong(context.getString(R.string.shared_preference_last_stack_for_account_and_board_) + accountId + "_" + boardId, NO_STACK_ID);
- DeckLog.log("--- Read: shared_preference_last_stack_for_account_and_board" + accountId + "_" + boardId + " | " + savedStackId);
+ DeckLog.log("--- Read: shared_preference_last_stack_for_account_and_board" + accountId + "_" + boardId, "|", savedStackId);
return savedStackId;
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/DeckLog.java b/app/src/main/java/it/niedermann/nextcloud/deck/DeckLog.java
index 47a0385e3..e857f0d56 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/DeckLog.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/DeckLog.java
@@ -1,55 +1,74 @@
package it.niedermann.nextcloud.deck;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
import android.util.Log;
+import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.content.FileProvider;
+import java.io.File;
+import java.io.FileWriter;
+import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
+import it.niedermann.nextcloud.deck.util.MimeTypeUtil;
+
public class DeckLog {
+
+ private static final StringBuffer DEBUG_LOG = new StringBuffer();
+ private static boolean PERSIST_LOGS = false;
private static final String TAG = DeckLog.class.getSimpleName();
+ public static void enablePersistentLogs(boolean persistLogs) {
+ PERSIST_LOGS = persistLogs;
+ if (!persistLogs) {
+ clearDebugLog();
+ }
+ }
+
public enum Severity {
VERBOSE, DEBUG, LOG, INFO, WARN, ERROR
}
- public static void verbose(String message) {
- log(message, Severity.VERBOSE, 4);
+ public static void verbose(Object... message) {
+ log(Severity.VERBOSE, 4, message);
}
- public static void log(String message) {
- log(message, Severity.DEBUG, 4);
+ public static void log(Object... message) {
+ log(Severity.DEBUG, 4, message);
}
- public static void info(String message) {
- log(message, Severity.INFO, 4);
+ public static void info(Object... message) {
+ log(Severity.INFO, 4, message);
}
- public static void warn(String message) {
- log(message, Severity.WARN, 4);
+ public static void warn(Object... message) {
+ log(Severity.WARN, 4, message);
}
- public static void error(String message) {
- log(message, Severity.ERROR, 4);
+ public static void error(Object... message) {
+ log(Severity.ERROR, 4, message);
}
- public static void log(String message, Severity severity) {
- log(message, severity, 3);
+ public static void log(@NonNull Severity severity, Object... message) {
+ log(severity, 3, message);
}
- private static void log(String message, Severity severity, int stackTracePosition) {
- final String print;
- if (BuildConfig.DEBUG) {
- final StackTraceElement caller = Thread.currentThread().getStackTrace()[stackTracePosition];
- print = caller.getMethodName() + "() (" + caller.getFileName() + ":" + caller.getLineNumber() + ") → " + message;
- } else {
- print = message;
+ private static void log(@NonNull Severity severity, int stackTracePosition, Object... messages) {
+ if (!(PERSIST_LOGS || BuildConfig.DEBUG)) {
+ return;
+ }
+ final StackTraceElement caller = Thread.currentThread().getStackTrace()[stackTracePosition];
+ final String print = "(" + caller.getFileName() + ":" + caller.getLineNumber() + ") " + caller.getMethodName() + "() → " + TextUtils.join(" ", messages);
+ if (PERSIST_LOGS) {
+ DEBUG_LOG.append(print).append("\n");
}
switch (severity) {
- case VERBOSE:
- Log.v(TAG, print);
- break;
case DEBUG:
Log.d(TAG, print);
break;
@@ -69,41 +88,46 @@ public class DeckLog {
}
public static void logError(@Nullable Throwable e) {
+ if (!(PERSIST_LOGS || BuildConfig.DEBUG)) {
+ return;
+ }
if (e == null) {
error("Could not log error because given error was null");
return;
}
final StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
- String stacktrace = sw.toString(); // stack trace as a string
+ final String stacktrace = sw.toString();
final StackTraceElement caller = Thread.currentThread().getStackTrace()[3];
- final String source = caller.getMethodName() + "() (" + caller.getFileName() + ":" + caller.getLineNumber() + ") -> ";
- Log.e(TAG, source + stacktrace);
+ final String print = "(" + caller.getFileName() + ":" + caller.getLineNumber() + ") " + caller.getMethodName() + "() → " + stacktrace;
+ if (PERSIST_LOGS) {
+ DEBUG_LOG.append(print).append("\n");
+ }
+ Log.e(TAG, print);
}
- public static void printCurrentStacktrace() {
- log(getCurrentStacktrace(4));
+ @NonNull
+ public static String getDebugLog() {
+ return DEBUG_LOG.toString();
}
- public static String getCurrentStacktrace() {
- return getCurrentStacktrace(4);
+ public static void clearDebugLog() {
+ DEBUG_LOG.setLength(0);
}
- private static String getCurrentStacktrace(@SuppressWarnings("SameParameterValue") int offset) {
- final StackTraceElement[] elements = Thread.currentThread().getStackTrace();
- final StringBuilder buff = new StringBuilder();
- for (int i = offset; i < elements.length; i++) {
- final StackTraceElement s = elements[i];
- buff.append("\tat ");
- buff.append(s.getClassName());
- buff.append(".");
- buff.append(s.getMethodName());
- buff.append("(");
- buff.append(s.getFileName());
- buff.append(":");
- buff.append(s.getLineNumber());
- buff.append(")\n");
- }
- return buff.toString();
+ /**
+ * Writes the current log to a temporary file and starts a share intent.
+ */
+ public static void shareLogAsFile(@NonNull Context context) throws IOException {
+ Toast.makeText(context, R.string.copying_logs_to_file, Toast.LENGTH_LONG).show();
+ final File logFile = new File(context.getCacheDir().getAbsolutePath() + "/log.txt");
+ final FileWriter writer = new FileWriter(logFile);
+ writer.write(DeckLog.getDebugLog());
+ writer.close();
+ context.startActivity(new Intent(Intent.ACTION_SEND)
+ .putExtra(Intent.EXTRA_TITLE, context.getString(R.string.log_file))
+ .putExtra(Intent.EXTRA_STREAM, FileProvider.getUriForFile(context, BuildConfig.APPLICATION_ID + ".provider", logFile))
+ .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
+ .setType(MimeTypeUtil.TEXT_PLAIN));
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/ApiProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/ApiProvider.java
index e2cfda22e..a6e8da168 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/ApiProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/ApiProvider.java
@@ -18,19 +18,18 @@ import retrofit2.NextcloudRetrofitApiBuilder;
/**
* Created by david on 26.05.17.
*/
-
public class ApiProvider {
- private static final String DECK_API_ENDPOINT = "/index.php/apps/deck/api/v1.0/";
+ private static final String DECK_API_ENDPOINT = "/index.php/apps/deck/api/";
private static final String NC_API_ENDPOINT = "/ocs/v2.php/";
private DeckAPI deckAPI;
private NextcloudServerAPI nextcloudAPI;
@NonNull
- private Context context;
- private SingleSignOnAccount ssoAccount;
+ private final Context context;
@Nullable
- private String ssoAccountName;
+ private final String ssoAccountName;
+ private SingleSignOnAccount ssoAccount;
public ApiProvider(Context context) {
this(context, null);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/DeckAPI.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/DeckAPI.java
index 1f0148b91..9e1b7d7f4 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/DeckAPI.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/DeckAPI.java
@@ -12,6 +12,7 @@ import it.niedermann.nextcloud.deck.model.Board;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.Stack;
+import it.niedermann.nextcloud.deck.model.enums.EAttachmentType;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.model.full.FullStack;
@@ -32,129 +33,148 @@ import retrofit2.http.Part;
import retrofit2.http.Path;
import retrofit2.http.Query;
+/**
+ * @link <a href="https://deck.readthedocs.io/en/latest/API/">Deck REST API</a>
+ */
public interface DeckAPI {
String MODIFIED_SINCE_HEADER = "If-Modified-Since";
String IF_NONE_MATCH = "If-None-Match";
- // ### BOARDS
- @POST("boards")
+
+ // Boards
+
+ @POST("v1.0/boards")
Observable<FullBoard> createBoard(@Body Board board);
- @GET("boards/{id}")
+ @GET("v1.0/boards/{id}")
Observable<FullBoard> getBoard(@Path("id") long id, @Header(MODIFIED_SINCE_HEADER) String lastSync);
- @PUT("boards/{id}")
+ @PUT("v1.0/boards/{id}")
Observable<FullBoard> updateBoard(@Path("id") long id, @Body Board board);
- @DELETE("boards/{id}")
+ @DELETE("v1.0/boards/{id}")
Observable<Void> deleteBoard(@Path("id") long id);
- @DELETE("boards/{id}/undo_delete")
+ @DELETE("v1.0/boards/{id}/undo_delete")
Observable<FullBoard> restoreBoard(@Path("id") long id);
- @GET("boards")
- Observable<ParsedResponse<List<FullBoard>>> getBoards(@Query ("details") boolean verbose, @Header(MODIFIED_SINCE_HEADER) String lastSync, @Header(IF_NONE_MATCH) String eTag);
+ @GET("v1.0/boards")
+ Observable<ParsedResponse<List<FullBoard>>> getBoards(@Query("details") boolean verbose, @Header(MODIFIED_SINCE_HEADER) String lastSync, @Header(IF_NONE_MATCH) String eTag);
+
+ @GET("v1.0/boards")
+ Observable<ParsedResponse<List<FullBoard>>> getBoards(@Query("details") boolean verbose, @Header(MODIFIED_SINCE_HEADER) String lastSync);
- // ### Stacks
- @POST("boards/{boardId}/stacks")
- Observable<FullStack> createStack(@Path("boardId") long boardId, @Body Stack stack);
+ // Stacks
- @PUT("boards/{boardId}/stacks/{stackId}")
+ @POST("v1.0/boards/{boardId}/stacks")
+ Observable<FullStack> createStack(@Path("boardId") long boardId, @Body Stack stack);
+
+ @PUT("v1.0/boards/{boardId}/stacks/{stackId}")
Observable<FullStack> updateStack(@Path("boardId") long boardId, @Path("stackId") long id, @Body Stack stack);
- @DELETE("boards/{boardId}/stacks/{stackId}")
+ @DELETE("v1.0/boards/{boardId}/stacks/{stackId}")
Observable<Void> deleteStack(@Path("boardId") long boardId, @Path("stackId") long id);
- @GET("boards/{boardId}/stacks/{stackId}")
+ @GET("v1.0/boards/{boardId}/stacks/{stackId}")
Observable<FullStack> getStack(@Path("boardId") long boardId, @Path("stackId") long id, @Header(MODIFIED_SINCE_HEADER) String lastSync);
- @GET("boards/{boardId}/stacks")
+ @GET("v1.0/boards/{boardId}/stacks")
Observable<List<FullStack>> getStacks(@Path("boardId") long boardId, @Header(MODIFIED_SINCE_HEADER) String lastSync);
- @GET("boards/{boardId}/stacks/archived")
+ @GET("v1.0/boards/{boardId}/stacks/archived")
Observable<List<Stack>> getArchivedStacks(@Path("boardId") long boardId, @Header(MODIFIED_SINCE_HEADER) String lastSync);
- // ### Cards
- @POST("boards/{boardId}/stacks/{stackId}/cards")
+ // Cards
+
+ @POST("v1.0/boards/{boardId}/stacks/{stackId}/cards")
Observable<FullCard> createCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Body Card card);
- @PUT("boards/{boardId}/stacks/{stackId}/cards/{cardId}")
+ @PUT("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}")
Observable<FullCard> updateCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Body CardUpdate card);
@FormUrlEncoded
- @PUT("boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel")
+ @PUT("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel")
Observable<Void> assignLabelToCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Field("labelId") long labelId);
@FormUrlEncoded
- @PUT("boards/{boardId}/stacks/{stackId}/cards/{cardId}/removeLabel")
+ @PUT("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/removeLabel")
Observable<Void> unassignLabelFromCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Field("labelId") long labelId);
@FormUrlEncoded
- @PUT("boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignUser")
+ @PUT("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignUser")
Observable<Void> assignUserToCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Field("userId") String userUID);
@FormUrlEncoded
- @PUT("boards/{boardId}/stacks/{stackId}/cards/{cardId}/unassignUser")
- Observable<Void> unassignUserFromCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Field("userId") String userUID);
+ @PUT("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/unassignUser")
+ Observable<Void> unassignUserFromCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Field("userId") String userUID);
- @PUT("boards/{boardId}/stacks/{stackId}/cards/{cardId}/reorder")
+ @PUT("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/reorder")
Observable<List<FullCard>> moveCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Body Reorder reorder);
- @DELETE("boards/{boardId}/stacks/{stackId}/cards/{cardId}")
+ @DELETE("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}")
Observable<Void> deleteCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId);
- @GET("boards/{boardId}/stacks/{stackId}/cards/{cardId}")
- Observable<FullCard> getCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Header(MODIFIED_SINCE_HEADER) String lastSync);
+ /**
+ * @see <a href="https://github.com/nextcloud/deck/issues/2874">This endpoint does only return {@link Attachment}s of type {@link EAttachmentType.DECK_FILE}</a>
+ */
+ @SuppressWarnings("JavadocReference")
+ @GET("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}")
+ Observable<FullCard> getCard_1_0(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Header(MODIFIED_SINCE_HEADER) String lastSync);
+
+ @GET("v1.1/boards/{boardId}/stacks/{stackId}/cards/{cardId}")
+ Observable<FullCard> getCard_1_1(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Header(MODIFIED_SINCE_HEADER) String lastSync);
- // ### LABELS
- @GET("boards/{boardId}/labels/{labelId}")
+ // Labels
+
+ @GET("v1.0/boards/{boardId}/labels/{labelId}")
Observable<Label> getLabel(@Path("boardId") long boardId, @Path("labelId") long labelId, @Header(MODIFIED_SINCE_HEADER) String lastSync);
- @PUT("boards/{boardId}/labels/{labelId}")
+ @PUT("v1.0/boards/{boardId}/labels/{labelId}")
Observable<Label> updateLabel(@Path("boardId") long boardId, @Path("labelId") long labelId, @Body Label label);
- @POST("boards/{boardId}/labels")
+ @POST("v1.0/boards/{boardId}/labels")
Observable<Label> createLabel(@Path("boardId") long boardId, @Body Label label);
- @DELETE("boards/{boardId}/labels/{labelId}")
+ @DELETE("v1.0/boards/{boardId}/labels/{labelId}")
Observable<Void> deleteLabel(@Path("boardId") long boardId, @Path("labelId") long labelId);
- // ### ATTACHMENTS
- @GET("boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}")
+ // Attachments
+
+ @GET("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}")
Observable<ResponseBody> downloadAttachment(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Path("attachmentId") long attachmentId);
- @GET("boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments")
+ @GET("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments")
Observable<List<Attachment>> getAttachments(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId);
@Multipart
- @POST("boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments?type=deck_file")
- Observable<Attachment> uploadAttachment(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Part MultipartBody.Part type, @Part MultipartBody.Part attachment);
+ @POST("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments")
+ Observable<Attachment> uploadAttachment(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Part MultipartBody.Part type, @Part MultipartBody.Part attachment);
@Multipart
- @PUT("boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments?type=deck_file")
- Observable<Attachment> updateAttachment(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Path("attachmentId") long attachmentId, @Part MultipartBody.Part type, @Part MultipartBody.Part attachment);
+ @PUT("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments")
+ Observable<Attachment> updateAttachment(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Path("attachmentId") long attachmentId, @Part MultipartBody.Part type, @Part MultipartBody.Part attachment);
- @DELETE("boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}")
+ @DELETE("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}")
Observable<Void> deleteAttachment(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Path("attachmentId") long attachmentId);
- @PUT("boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}/restore")
+ @PUT("v1.0/boards/{boardId}/stacks/{stackId}/cards/{cardId}/attachments/{attachmentId}/restore")
Observable<Attachment> restoreAttachment(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Path("attachmentId") long attachmentId);
- // ### ACL
- @POST("boards/{boardId}/acl")
+
+ // Access control lists
+
+ @POST("v1.0/boards/{boardId}/acl")
Observable<AccessControl> createAccessControl(@Path("boardId") long boardId, @Body AccessControl acl);
- @PUT("boards/{boardId}/acl/{aclId}")
+ @PUT("v1.0/boards/{boardId}/acl/{aclId}")
Observable<AccessControl> updateAccessControl(@Path("boardId") long boardId, @Path("aclId") long aclId, @Body AccessControl acl);
- @DELETE("boards/{boardId}/acl/{aclId}")
+ @DELETE("v1.0/boards/{boardId}/acl/{aclId}")
Observable<Void> deleteAccessControl(@Path("boardId") long boardId, @Path("aclId") long aclId, @Body AccessControl acl);
- // ### COMMENTS
-
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/IResponseCallback.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/IResponseCallback.java
index be3eaeb22..3134d45aa 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/IResponseCallback.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/IResponseCallback.java
@@ -1,44 +1,24 @@
package it.niedermann.nextcloud.deck.api;
-import androidx.annotation.CallSuper;
+import android.annotation.SuppressLint;
+
+import androidx.annotation.NonNull;
import java.util.Collection;
import java.util.List;
-import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
-public abstract class IResponseCallback<T> {
+public abstract class IResponseCallback<T> implements ResponseCallback<T> {
+ @NonNull
protected Account account;
- public IResponseCallback(Account account) {
+ public IResponseCallback(@NonNull Account account) {
this.account = account;
}
- public abstract void onResponse(T response);
-
- @CallSuper
- public void onError(Throwable throwable) {
- DeckLog.logError(throwable);
- }
-
- @CallSuper
- public void onError(Throwable throwable, T locallyCreatedEntity) {
- onError(throwable);
- }
-
- public static <T> IResponseCallback<T> getDefaultResponseCallback(Account account) {
- return new IResponseCallback<T>(account) {
- @Override
- public void onResponse(T response) {
- // Do Nothing
- }
-
- };
- }
-
public void fillAccountIDs(T response) {
if (response != null) {
if (isListOfRemoteEntity(response)) {
@@ -71,7 +51,26 @@ public abstract class IResponseCallback<T> {
return false;
}
+ @NonNull
public Account getAccount() {
return account;
}
+
+ /**
+ * Forwards responses and errors to the given {@param callback}
+ */
+ public static <T> IResponseCallback<T> from(@NonNull Account account, ResponseCallback<T> callback) {
+ return new IResponseCallback<T>(account) {
+ @Override
+ public void onResponse(T response) {
+ callback.onResponse(response);
+ }
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ callback.onError(throwable);
+ }
+ };
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java
index e759f27d4..2443a919c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java
@@ -23,6 +23,7 @@ import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.enums.ActivityType;
+import it.niedermann.nextcloud.deck.model.enums.EAttachmentType;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.model.full.FullStack;
@@ -75,7 +76,7 @@ public class JsonToEntityParser {
}
private static GroupMemberUIDs parseGroupMemberUIDs(JsonObject obj) {
- DeckLog.verbose(obj.toString());
+ DeckLog.verbose(obj);
GroupMemberUIDs uids = new GroupMemberUIDs();
makeTraceableIfFails(() -> {
JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
@@ -93,7 +94,7 @@ public class JsonToEntityParser {
}
private static OcsUserList parseOcsUserList(JsonObject obj) {
- DeckLog.verbose(obj.toString());
+ DeckLog.verbose(obj);
OcsUserList ocsUserList = new OcsUserList();
makeTraceableIfFails(() -> {
JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
@@ -118,7 +119,7 @@ public class JsonToEntityParser {
}
private static OcsUser parseSingleOcsUser(JsonObject obj) {
- DeckLog.verbose(obj.toString());
+ DeckLog.verbose(obj);
OcsUser ocsUser = new OcsUser();
makeTraceableIfFails(() -> {
JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
@@ -137,7 +138,7 @@ public class JsonToEntityParser {
}
private static OcsProjectList parseOcsProjectList(JsonObject obj) {
- DeckLog.verbose(obj.toString());
+ DeckLog.verbose(obj);
OcsProjectList projectList = new OcsProjectList();
makeTraceableIfFails(() -> {
JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
@@ -171,7 +172,7 @@ public class JsonToEntityParser {
}
private static OcsProjectResource parseOcsProjectResource(JsonObject obj) {
- DeckLog.verbose(obj.toString());
+ DeckLog.verbose(obj);
OcsProjectResource resource = new OcsProjectResource();
makeTraceableIfFails(() -> {
if (obj.has("id")) {
@@ -213,7 +214,7 @@ public class JsonToEntityParser {
}
private static OcsComment parseOcsComment(JsonObject obj) {
- DeckLog.verbose(obj.toString());
+ DeckLog.verbose(obj);
OcsComment comment = new OcsComment();
makeTraceableIfFails(() -> {
JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
@@ -229,7 +230,7 @@ public class JsonToEntityParser {
}
private static DeckComment parseDeckComment(JsonElement data) {
- DeckLog.verbose(data.toString());
+ DeckLog.verbose(data);
DeckComment deckComment = new DeckComment();
makeTraceableIfFails(() -> {
@@ -261,7 +262,7 @@ public class JsonToEntityParser {
private static Mention parseMention(JsonElement mentionJson) {
Mention mention = new Mention();
- DeckLog.verbose(mentionJson.toString());
+ DeckLog.verbose(mentionJson);
makeTraceableIfFails(() -> {
@@ -278,7 +279,7 @@ public class JsonToEntityParser {
protected static FullBoard parseBoard(JsonObject e) {
FullBoard fullBoard = new FullBoard();
- DeckLog.verbose(e.toString());
+ DeckLog.verbose(e);
Board board = new Board();
makeTraceableIfFails(() -> {
@@ -364,7 +365,7 @@ public class JsonToEntityParser {
}
protected static AccessControl parseAcl(JsonObject aclJson) {
- DeckLog.verbose(aclJson.toString());
+ DeckLog.verbose(aclJson);
AccessControl acl = new AccessControl();
if (aclJson.has("participant") && !aclJson.get("participant").isJsonNull()) {
@@ -387,7 +388,7 @@ public class JsonToEntityParser {
}
protected static FullCard parseCard(JsonObject e) {
- DeckLog.verbose(e.toString());
+ DeckLog.verbose(e);
FullCard fullCard = new FullCard();
Card card = new Card();
fullCard.setCard(card);
@@ -452,12 +453,12 @@ public class JsonToEntityParser {
}
protected static Attachment parseAttachment(JsonObject e) {
- DeckLog.verbose(e.toString());
+ DeckLog.verbose(e);
Attachment a = new Attachment();
makeTraceableIfFails(() -> {
a.setId(e.get("id").getAsLong());
a.setCardId(e.get("cardId").getAsLong());
- a.setType(e.get("type").getAsString());
+ a.setType(EAttachmentType.findByValue(e.get("type").getAsString()));
a.setEtag(getNullAsNull(e.get("ETag")));
a.setData(e.get("data").getAsString());
a.setLastModified(getTimestampFromLong(e.get("lastModified")));
@@ -468,6 +469,9 @@ public class JsonToEntityParser {
JsonObject extendedData = e.getAsJsonObject("extendedData").getAsJsonObject();
a.setFilesize(extendedData.get("filesize").getAsLong());
a.setMimetype(extendedData.get("mimetype").getAsString());
+ if (extendedData.has("fileid") && !extendedData.get("fileid").isJsonNull()) {
+ a.setFileId(extendedData.get("fileid").getAsLong());
+ }
if (extendedData.has("info") && !extendedData.get("info").isJsonNull()) {
JsonObject info = extendedData.getAsJsonObject("info").getAsJsonObject();
a.setDirname(info.get("dirname").getAsString());
@@ -484,7 +488,7 @@ public class JsonToEntityParser {
}
protected static User parseUser(JsonElement userElement) {
- DeckLog.verbose(userElement.toString());
+ DeckLog.verbose(userElement);
if (userElement.isJsonNull()) {
return null;
}
@@ -509,7 +513,7 @@ public class JsonToEntityParser {
protected static Capabilities parseCapabilities(JsonObject e) {
- DeckLog.verbose(e.toString());
+ DeckLog.verbose(e);
Capabilities capabilities = new Capabilities();
// not traceable, we need the original DeckException for error handling
if (e.has("ocs")) {
@@ -575,7 +579,7 @@ public class JsonToEntityParser {
}
protected static FullStack parseStack(JsonObject e) {
- DeckLog.verbose(e.toString());
+ DeckLog.verbose(e);
FullStack fullStack = new FullStack();
Stack stack = new Stack();
fullStack.setStack(stack);
@@ -605,7 +609,7 @@ public class JsonToEntityParser {
}
protected static List<Activity> parseActivity(JsonObject e) {
- DeckLog.verbose(e.toString());
+ DeckLog.verbose(e);
List<Activity> activityList = new ArrayList<>();
makeTraceableIfFails(() -> {
@@ -633,7 +637,7 @@ public class JsonToEntityParser {
}
protected static Label parseLabel(JsonObject e) {
- DeckLog.verbose(e.toString());
+ DeckLog.verbose(e);
Label label = new Label();
makeTraceableIfFails(() -> {
label.setId(e.get("id").getAsLong());
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudArrayDeserializer.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudArrayDeserializer.java
index 820cfc653..1e99c8817 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudArrayDeserializer.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudArrayDeserializer.java
@@ -35,8 +35,14 @@ public class NextcloudArrayDeserializer<T> implements JsonDeserializer<List<T>>
JsonObject obj = jArr.get(i).getAsJsonObject();
items.add(JsonToEntityParser.parseJsonObject(obj, mType));
}
+ } else if (json.isJsonObject()) {
+ try {
+ items.add(JsonToEntityParser.parseJsonObject(json.getAsJsonObject(), mType));
+ } catch (Exception e) {
+ throw new IllegalArgumentException("NextcloudArrayDeserializer got a Json Object, fallback parsing failed for input: " + json);
+ }
} else {
- throw new IllegalArgumentException("NextcloudArrayDeserializer got an non-array Json Object: " + json);
+ throw new IllegalArgumentException("NextcloudArrayDeserializer got a malformed Json Object: " + json);
}
return items;
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudDeserializer.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudDeserializer.java
index d23807957..0b84036a4 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudDeserializer.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudDeserializer.java
@@ -26,7 +26,7 @@ public class NextcloudDeserializer<T> implements JsonDeserializer<T> {
@Override
public T deserialize(final JsonElement json, final Type typeOfT, final JsonDeserializationContext context) throws JsonParseException {
- DeckLog.verbose(json.toString());
+ DeckLog.verbose(json);
return JsonToEntityParser.parseJsonObject(json.getAsJsonObject(), mType);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java
index 7b93ace49..54e573d73 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java
@@ -24,14 +24,27 @@ import retrofit2.http.PUT;
import retrofit2.http.Path;
import retrofit2.http.Query;
+
+/**
+ * @link <a href="https://deck.readthedocs.io/en/latest/API-Nextcloud/">Nextcloud REST API</a>
+ */
public interface NextcloudServerAPI {
+
+ // Capabilities
+
@GET("cloud/capabilities?format=json")
Observable<ParsedResponse<Capabilities>> getCapabilities(@Header("If-None-Match") String eTag);
+
+ // Projects
+
@GET("collaboration/resources/deck-card/{cardId}?format=json")
Observable<OcsProjectList> getProjectsForCard(@Path("cardId") long cardId);
+
+ // Users
+
@GET("apps/files_sharing/api/v1/sharees?format=json&perPage=20&itemType=0%2C1%2C7")
Observable<OcsUserList> searchUser(@Query("search") String searchTerm);
@@ -41,9 +54,15 @@ public interface NextcloudServerAPI {
@GET("cloud/users/{search}?format=json")
Observable<OcsUser> getSingleUserData(@Path("search") String userUid);
+
+ // Activities
+
@GET("apps/activity/api/v2/activity/filter?format=json&object_type=deck_card&limit=50&since=-1&sort=asc")
Observable<List<Activity>> getActivitiesForCard(@Query("object_id") long cardId);
+
+ // Comments
+
@Headers({
"Accept: application/json",
"Content-Type: application/json;charset=utf-8"
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/ResponseCallback.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/ResponseCallback.java
new file mode 100644
index 000000000..93b2bcc0d
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/ResponseCallback.java
@@ -0,0 +1,23 @@
+package it.niedermann.nextcloud.deck.api;
+
+import androidx.annotation.CallSuper;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+
+public interface ResponseCallback<T> {
+
+ void onResponse(T response);
+
+ @CallSuper
+ default void onError(Throwable throwable) {
+ DeckLog.logError(throwable);
+ }
+
+ /**
+ * @return a default {@link ResponseCallback} which does nothing {@link #onResponse(Object)} and the default action fo {@link #onError(Throwable)}
+ */
+ static <T> ResponseCallback<T> empty() {
+ return response -> {
+ };
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java
index 7eabcbe70..eaa50dfa1 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java
@@ -1,13 +1,14 @@
package it.niedermann.nextcloud.deck.model;
+import androidx.annotation.Nullable;
import androidx.room.Entity;
import androidx.room.ForeignKey;
-import androidx.room.Ignore;
import androidx.room.Index;
import java.io.Serializable;
import java.time.Instant;
+import it.niedermann.nextcloud.deck.model.enums.EAttachmentType;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@Entity(inheritSuperIndices = true,
@@ -24,7 +25,8 @@ import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
public class Attachment extends AbstractRemoteEntity implements Comparable<Attachment>, Serializable {
private long cardId;
- private String type = "deck_file";
+ // TODO use EAttachmentType
+ private EAttachmentType type = EAttachmentType.DECK_FILE;
private String data;
private Instant createdAt;
private String createdBy;
@@ -36,9 +38,8 @@ public class Attachment extends AbstractRemoteEntity implements Comparable<Attac
private String extension;
private String filename;
private String localPath;
- // TODO should probably be a Long... depends on https://github.com/nextcloud/deck/pull/2638
- @Ignore
- private String fileId;
+ @Nullable
+ private Long fileId;
public long getCardId() {
return cardId;
@@ -48,11 +49,11 @@ public class Attachment extends AbstractRemoteEntity implements Comparable<Attac
this.cardId = cardId;
}
- public String getType() {
+ public EAttachmentType getType() {
return type;
}
- public void setType(String type) {
+ public void setType(EAttachmentType type) {
this.type = type;
}
@@ -144,19 +145,12 @@ public class Attachment extends AbstractRemoteEntity implements Comparable<Attac
this.localPath = localPath;
}
- /**
- * TODO depends on https://github.com/nextcloud/deck/pull/2638
- */
- @Ignore
- public String getFileId() {
+ @Nullable
+ public Long getFileId() {
return this.fileId;
}
- /**
- * TODO depends on https://github.com/nextcloud/deck/pull/2638
- */
- @Ignore
- public void setFileId(String fileId) {
+ public void setFileId(@Nullable Long fileId) {
this.fileId = fileId;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/ActivityType.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/ActivityType.java
index afaeee69c..8b4246bf2 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/ActivityType.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/ActivityType.java
@@ -43,7 +43,7 @@ public enum ActivityType {
return s;
}
}
- DeckLog.error("unknown ActivityType path: " + id);
+ DeckLog.error("unknown ActivityType path:", id);
return CHANGE;
}
@@ -56,7 +56,7 @@ public enum ActivityType {
return s;
}
}
- DeckLog.error("unknown ActivityType path: " + path);
+ DeckLog.error("unknown ActivityType path:", path);
return CHANGE;
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/EAttachmentType.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/EAttachmentType.java
new file mode 100644
index 000000000..92c15d6e0
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/EAttachmentType.java
@@ -0,0 +1,33 @@
+package it.niedermann.nextcloud.deck.model.enums;
+
+public enum EAttachmentType {
+ // Do not change values. They match the Deck server apps values.
+ DECK_FILE(1, "deck_file"),
+ FILE(2, "file"),
+ UNKNOWN(1337, "unknown");
+
+ private final int id;
+ private final String value;
+
+ EAttachmentType(int id, String value) {
+ this.id = id;
+ this.value = value;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public static EAttachmentType findByValue(String value) {
+ for (EAttachmentType s : EAttachmentType.values()) {
+ if (s.value.equals(value)) {
+ return s;
+ }
+ }
+ return UNKNOWN;
+ }
+
+ public String getValue() {
+ return value;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/EDueType.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/EDueType.java
index 5241ae763..da9193635 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/EDueType.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/EDueType.java
@@ -8,17 +8,32 @@ import androidx.annotation.StringRes;
import it.niedermann.nextcloud.deck.R;
public enum EDueType {
- NO_FILTER(R.string.filter_no_filter),
- OVERDUE(R.string.filter_overdue),
- TODAY(R.string.filter_today),
- WEEK(R.string.filter_week),
- MONTH(R.string.filter_month),
- NO_DUE(R.string.filter_no_due);
+ NO_FILTER(1, R.string.filter_no_filter),
+ OVERDUE(2, R.string.filter_overdue),
+ TODAY(3, R.string.filter_today),
+ WEEK(4, R.string.filter_week),
+ MONTH(5, R.string.filter_month),
+ NO_DUE(6, R.string.filter_no_due);
- private int value;
+ private final int value;
+ private final int id;
- EDueType(@StringRes int value) {
+ EDueType(int id, @StringRes int value) {
this.value = value;
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public static EDueType findById(int id) {
+ for (EDueType s : EDueType.values()) {
+ if (s.getId() == id) {
+ return s;
+ }
+ }
+ throw new IllegalArgumentException("unknown " + EDueType.class.getSimpleName() + " key: " + id);
}
@NonNull
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/ESortCriteria.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/ESortCriteria.java
new file mode 100644
index 000000000..5b1343302
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/enums/ESortCriteria.java
@@ -0,0 +1,35 @@
+package it.niedermann.nextcloud.deck.model.enums;
+
+public enum ESortCriteria {
+ /**
+ * Account → Board → Stack
+ */
+ LOCATION(1),
+ /**
+ * Modification date of the card including comments, attachments etc.
+ */
+ MODIFIED(2),
+ LAST_COMMENTED(3),
+ DUE_DATE(4),
+ ASSIGNEE(5),
+ LABEL(6);
+
+ private final int id;
+
+ ESortCriteria(int id) {
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public static ESortCriteria findById(int id) {
+ for (ESortCriteria s : ESortCriteria.values()) {
+ if (s.getId() == id) {
+ return s;
+ }
+ }
+ throw new IllegalArgumentException("unknown " + ESortCriteria.class.getSimpleName() + " key: " + id);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCard.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCard.java
index 611baf3df..dc4cf1fef 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCard.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCard.java
@@ -110,7 +110,7 @@ public class FullCard implements IRemoteEntity, DragAndDropModel {
}
public List<Attachment> getAttachments() {
- if (!isAttachmentsSorted) {
+ if (!isAttachmentsSorted && attachments != null) {
Collections.sort(attachments);
isAttachmentsSorted = true;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java
index 1b5f0879d..81aa8cd80 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java
@@ -10,6 +10,7 @@ import java.util.List;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.enums.EDueType;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
public class FilterInformation implements Serializable {
@@ -21,11 +22,14 @@ public class FilterInformation implements Serializable {
private EDueType dueType = EDueType.NO_FILTER;
private boolean noAssignedLabel = false;
private boolean noAssignedUser = false;
+ private boolean noAssignedProject = false;
@NonNull
private List<User> users = new ArrayList<>();
@NonNull
private List<Label> labels = new ArrayList<>();
@NonNull
+ private List<OcsProject> projects = new ArrayList<>();
+ @NonNull
private EArchiveStatus archiveStatus = EArchiveStatus.NON_ARCHIVED;
public FilterInformation() {
@@ -41,6 +45,8 @@ public class FilterInformation implements Serializable {
this.noAssignedUser = filterInformation.isNoAssignedUser();
this.noAssignedLabel = filterInformation.isNoAssignedLabel();
this.archiveStatus = filterInformation.getArchiveStatus();
+ this.noAssignedProject = filterInformation.isNoAssignedProject();
+ this.projects = filterInformation.getProjects();
}
}
@@ -99,6 +105,31 @@ public class FilterInformation implements Serializable {
this.archiveStatus = archiveStatus;
}
+ public void setUsers(@NonNull List<User> users) {
+ this.users = users;
+ }
+
+ public boolean isNoAssignedProject() {
+ return noAssignedProject;
+ }
+
+ public void setNoAssignedProject(boolean noAssignedProject) {
+ this.noAssignedProject = noAssignedProject;
+ }
+
+ public void setLabels(@NonNull List<Label> labels) {
+ this.labels = labels;
+ }
+
+ @NonNull
+ public List<OcsProject> getProjects() {
+ return projects;
+ }
+
+ public void setProjects(@NonNull List<OcsProject> projects) {
+ this.projects = projects;
+ }
+
@NonNull
public EArchiveStatus getArchiveStatus() {
return archiveStatus;
@@ -126,8 +157,10 @@ public class FilterInformation implements Serializable {
}
return filterInformation.getDueType() != EDueType.NO_FILTER
|| filterInformation.getUsers().size() > 0
+ || filterInformation.getProjects().size() > 0
|| filterInformation.getLabels().size() > 0
|| filterInformation.noAssignedUser
+ || filterInformation.noAssignedProject
|| filterInformation.noAssignedLabel;
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java
index cfd973831..91817b73e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java
@@ -1,9 +1,6 @@
package it.niedermann.nextcloud.deck.model.ocs;
-import android.content.Context;
-
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import java.util.regex.Matcher;
@@ -15,11 +12,10 @@ import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
public class Version implements Comparable<Version> {
private static final Pattern NUMBER_EXTRACTION_PATTERN = Pattern.compile("[0-9]+");
+ private static final Version VERSION_0_6_4 = new Version("0.6.4", 0, 6, 4);
private static final Version VERSION_1_0_0 = new Version("1.0.0", 1, 0, 0);
private static final Version VERSION_1_0_3 = new Version("1.0.3", 1, 0, 3);
private static final Version VERSION_1_3_0 = new Version("1.3.0", 1, 3, 0);
- @Nullable
- private static Version VERSION_MINIMUM_SUPPORTED;
private String originalVersion = "?";
private int major = 0;
@@ -64,7 +60,7 @@ public class Version implements Comparable<Version> {
public static Version of(String versionString) {
int major = 0, minor = 0, micro = 0;
if (versionString != null) {
- String[] split = versionString.split("\\.");
+ final String[] split = versionString.split("\\.");
if (split.length > 0) {
major = extractNumber(split[0]);
if (split.length > 1) {
@@ -87,18 +83,12 @@ public class Version implements Comparable<Version> {
}
@NonNull
- public static Version minimumSupported(@NonNull Context context) {
- if (VERSION_MINIMUM_SUPPORTED == null) {
- final int minimumServerAppMajor = context.getResources().getInteger(R.integer.minimum_server_app_major);
- final int minimumServerAppMinor = context.getResources().getInteger(R.integer.minimum_server_app_minor);
- final int minimumServerAppPatch = context.getResources().getInteger(R.integer.minimum_server_app_patch);
- VERSION_MINIMUM_SUPPORTED = new Version(minimumServerAppMajor + "." + minimumServerAppMinor + "." + minimumServerAppPatch, minimumServerAppMajor, minimumServerAppMinor, minimumServerAppPatch);
- }
- return VERSION_MINIMUM_SUPPORTED;
+ public static Version minimumSupported() {
+ return VERSION_0_6_4;
}
- public boolean isSupported(@NonNull Context context) {
- return isGreaterOrEqualTo(Version.minimumSupported(context));
+ public boolean isSupported() {
+ return isGreaterOrEqualTo(minimumSupported());
}
/**
@@ -167,9 +157,7 @@ public class Version implements Comparable<Version> {
* @see <a href="https://github.com/nextcloud/deck/pull/2638">documentation in PR</a>
*/
public boolean supportsFileAttachments() {
- return false;
-// TODO depends on https://github.com/nextcloud/deck/pull/2638
-// return isGreaterOrEqualTo(VERSION_1_3_0);
+ return isGreaterOrEqualTo(VERSION_1_3_0);
}
/**
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/EWidgetType.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/EWidgetType.java
new file mode 100644
index 000000000..6121c7515
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/EWidgetType.java
@@ -0,0 +1,45 @@
+package it.niedermann.nextcloud.deck.model.widget.filter;
+
+import it.niedermann.nextcloud.deck.ui.widget.filter.FilterWidget;
+import it.niedermann.nextcloud.deck.ui.widget.stack.StackWidget;
+import it.niedermann.nextcloud.deck.ui.widget.upcoming.UpcomingWidget;
+
+public enum EWidgetType {
+ FILTER_WIDGET(1, FilterWidget.class),
+ UPCOMING_WIDGET(2, UpcomingWidget.class),
+ STACK_WIDGET(3, StackWidget.class);
+
+ private final int id;
+ private final Class<?> widgetClass;
+
+ EWidgetType(int id, Class<?> widgetClass) {
+ this.id = id;
+ this.widgetClass = widgetClass;
+ }
+
+ public static EWidgetType findById(int id) {
+ for (EWidgetType s : EWidgetType.values()) {
+ if (s.getId() == id) {
+ return s;
+ }
+ }
+ throw new IllegalArgumentException("unknown " + EWidgetType.class.getSimpleName() + " key: " + id);
+ }
+
+ public static EWidgetType findByClass(Class<?> clazz) {
+ for (EWidgetType s : EWidgetType.values()) {
+ if (s.getWidgetClass() == clazz) {
+ return s;
+ }
+ }
+ throw new IllegalArgumentException("unknown EWidgetType class");
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ public Class<?> getWidgetClass() {
+ return widgetClass;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidget.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidget.java
new file mode 100644
index 000000000..d6e7d4b08
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidget.java
@@ -0,0 +1,142 @@
+package it.niedermann.nextcloud.deck.model.widget.filter;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.room.Entity;
+import androidx.room.Ignore;
+import androidx.room.PrimaryKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.enums.EDueType;
+
+@Entity()
+public class FilterWidget {
+
+ @PrimaryKey()
+ private int id;
+
+ @Nullable
+ private String title;
+
+ @Nullable
+ private EDueType dueType;
+
+ @NonNull
+ private EWidgetType widgetType = EWidgetType.FILTER_WIDGET;
+
+ @Ignore
+ @NonNull
+ private final List<FilterWidgetAccount> accounts = new ArrayList<>();
+
+ @Ignore
+ @NonNull
+ private final List<FilterWidgetSort> sorts = new ArrayList<>();
+
+ @NonNull
+ public List<FilterWidgetAccount> getAccounts() {
+ return accounts;
+ }
+
+ public FilterWidget() {
+ // Default constructor
+ }
+
+ @Ignore
+ public FilterWidget(int appWidgetId, @NonNull EWidgetType widgetType) {
+ setId(appWidgetId);
+ setWidgetType(widgetType);
+ }
+
+ public void setAccounts(@NonNull List<FilterWidgetAccount> accounts) {
+ this.accounts.clear();
+ this.accounts.addAll(accounts);
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ @NonNull
+ public List<FilterWidgetSort> getSorts() {
+ return sorts;
+ }
+
+ @Ignore
+ public void setSorts(@NonNull FilterWidgetSort sorts) {
+ this.sorts.clear();
+ this.sorts.add(sorts);
+ }
+
+ public void setSorts(@NonNull List<FilterWidgetSort> sorts) {
+ this.sorts.clear();
+ this.sorts.addAll(sorts);
+ }
+
+ public void setId(int id) {
+ this.id = id;
+ }
+
+ @Nullable
+ public EDueType getDueType() {
+ return dueType;
+ }
+
+ public void setDueType(@Nullable EDueType dueType) {
+ this.dueType = dueType;
+ }
+
+ @NonNull
+ public EWidgetType getWidgetType() {
+ return widgetType;
+ }
+
+ public void setWidgetType(@NonNull EWidgetType widgetType) {
+ this.widgetType = widgetType;
+ }
+
+ @Nullable
+ public String getTitle() {
+ return title;
+ }
+
+ public void setTitle(@Nullable String title) {
+ this.title = title;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FilterWidget that = (FilterWidget) o;
+
+ if (id != that.id) return false;
+ if (title != null ? !title.equals(that.title) : that.title != null) return false;
+ if (dueType != that.dueType) return false;
+ if (widgetType != that.widgetType) return false;
+ if (!accounts.equals(that.accounts)) return false;
+ return sorts.equals(that.sorts);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id;
+ result = 31 * result + (title != null ? title.hashCode() : 0);
+ result = 31 * result + (dueType != null ? dueType.hashCode() : 0);
+ result = 31 * result + widgetType.hashCode();
+ result = 31 * result + accounts.hashCode();
+ result = 31 * result + sorts.hashCode();
+ return result;
+ }
+
+ public enum EChangedEntityType {
+ ACCOUNT,
+ BOARD,
+ STACK,
+ LABEL,
+ USER,
+ PROJECT,
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetAccount.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetAccount.java
new file mode 100644
index 000000000..caf4e5dd1
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetAccount.java
@@ -0,0 +1,161 @@
+package it.niedermann.nextcloud.deck.model.widget.filter;
+
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Ignore;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.Account;
+
+@Entity(
+ indices = {
+ @Index(value = "filterWidgetId", name = "index_FilterWidgetAccount_filterWidgetId"),
+ @Index(value = "accountId", name = "idx_FilterWidgetAccount_accountId"),
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = Account.class,
+ parentColumns = "id",
+ childColumns = "accountId", onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(
+ entity = FilterWidget.class,
+ parentColumns = "id",
+ childColumns = "filterWidgetId", onDelete = ForeignKey.CASCADE
+ ),
+ }
+)
+public class FilterWidgetAccount {
+
+ @PrimaryKey(autoGenerate = true)
+ private Long id;
+ private Long filterWidgetId;
+ private Long accountId;
+ private boolean includeNoUser = true;
+ private boolean includeNoProject = true;
+
+ @Ignore
+ private List<FilterWidgetBoard> boards = new ArrayList<>();
+
+ @Ignore
+ private List<FilterWidgetUser> users = new ArrayList<>();
+
+ @Ignore
+ private List<FilterWidgetProject> projects = new ArrayList<>();
+
+ public FilterWidgetAccount() {
+ // Default constructor
+ }
+
+ @Ignore
+ public FilterWidgetAccount(Long accountId, boolean includeNoUser) {
+ this.setAccountId(accountId);
+ this.setIncludeNoUser(includeNoUser);
+ }
+
+ public void setBoards(List<FilterWidgetBoard> boards) {
+ this.boards = boards;
+ }
+
+ public List<FilterWidgetBoard> getBoards() {
+ return boards;
+ }
+
+ public List<FilterWidgetUser> getUsers() {
+ return users;
+ }
+
+ public void setUsers(List<FilterWidgetUser> users) {
+ this.users = users;
+ }
+
+ @Ignore
+ public void setUsers(FilterWidgetUser user) {
+ this.users = Collections.singletonList(user);
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getFilterWidgetId() {
+ return filterWidgetId;
+ }
+
+ public void setFilterWidgetId(Long filterWidgetId) {
+ this.filterWidgetId = filterWidgetId;
+ }
+
+ public Long getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(Long accountId) {
+ this.accountId = accountId;
+ }
+
+ public boolean isIncludeNoUser() {
+ return includeNoUser;
+ }
+
+ public void setIncludeNoUser(boolean includeNoUser) {
+ this.includeNoUser = includeNoUser;
+ }
+
+ public boolean isIncludeNoProject() {
+ return includeNoProject;
+ }
+
+ public void setIncludeNoProject(boolean includeNoProject) {
+ this.includeNoProject = includeNoProject;
+ }
+
+ public List<FilterWidgetProject> getProjects() {
+ return projects;
+ }
+
+ public void setProjects(List<FilterWidgetProject> projects) {
+ this.projects = projects;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FilterWidgetAccount that = (FilterWidgetAccount) o;
+
+ if (includeNoUser != that.includeNoUser) return false;
+ if (includeNoProject != that.includeNoProject) return false;
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+ if (filterWidgetId != null ? !filterWidgetId.equals(that.filterWidgetId) : that.filterWidgetId != null)
+ return false;
+ if (accountId != null ? !accountId.equals(that.accountId) : that.accountId != null)
+ return false;
+ if (boards != null ? !boards.equals(that.boards) : that.boards != null) return false;
+ if (users != null ? !users.equals(that.users) : that.users != null) return false;
+ return projects != null ? projects.equals(that.projects) : that.projects == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (filterWidgetId != null ? filterWidgetId.hashCode() : 0);
+ result = 31 * result + (accountId != null ? accountId.hashCode() : 0);
+ result = 31 * result + (includeNoUser ? 1 : 0);
+ result = 31 * result + (includeNoProject ? 1 : 0);
+ result = 31 * result + (boards != null ? boards.hashCode() : 0);
+ result = 31 * result + (users != null ? users.hashCode() : 0);
+ result = 31 * result + (projects != null ? projects.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetBoard.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetBoard.java
new file mode 100644
index 000000000..b1697f9b7
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetBoard.java
@@ -0,0 +1,130 @@
+package it.niedermann.nextcloud.deck.model.widget.filter;
+
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Ignore;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.Board;
+
+@Entity(
+ indices = {
+ @Index(value = "filterAccountId", name = "idx_FilterWidgetBoard_filterAccountId"),
+ @Index(value = "boardId", name = "idx_FilterWidgetBoard_boardId")
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = Board.class,
+ parentColumns = "localId",
+ childColumns = "boardId", onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(
+ entity = FilterWidgetAccount.class,
+ parentColumns = "id",
+ childColumns = "filterAccountId", onDelete = ForeignKey.CASCADE
+ )
+ }
+)
+public class FilterWidgetBoard {
+
+ @PrimaryKey(autoGenerate = true)
+ private Long id;
+ private Long filterAccountId;
+ private Long boardId;
+ private boolean includeNoLabel = true;
+
+ public FilterWidgetBoard() {
+ // Default constructor
+ }
+
+ @Ignore
+ public FilterWidgetBoard(Long boardId, List<FilterWidgetStack> stacks) {
+ this.boardId = boardId;
+ this.stacks = stacks;
+ }
+
+ @Ignore
+ private List<FilterWidgetStack> stacks = new ArrayList<>();
+
+ @Ignore
+ private List<FilterWidgetLabel> labels = new ArrayList<>();
+
+ public List<FilterWidgetLabel> getLabels() {
+ return labels;
+ }
+
+ public void setLabels(List<FilterWidgetLabel> labels) {
+ this.labels = labels;
+ }
+
+ public List<FilterWidgetStack> getStacks() {
+ return stacks;
+ }
+
+ public void setStacks(List<FilterWidgetStack> stacks) {
+ this.stacks = stacks;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getFilterAccountId() {
+ return filterAccountId;
+ }
+
+ public void setFilterAccountId(Long filterAccountId) {
+ this.filterAccountId = filterAccountId;
+ }
+
+ public Long getBoardId() {
+ return boardId;
+ }
+
+ public void setBoardId(Long boardId) {
+ this.boardId = boardId;
+ }
+
+ public boolean isIncludeNoLabel() {
+ return includeNoLabel;
+ }
+
+ public void setIncludeNoLabel(boolean includeNoLabel) {
+ this.includeNoLabel = includeNoLabel;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FilterWidgetBoard that = (FilterWidgetBoard) o;
+
+ if (includeNoLabel != that.includeNoLabel) return false;
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+ if (filterAccountId != null ? !filterAccountId.equals(that.filterAccountId) : that.filterAccountId != null)
+ return false;
+ if (boardId != null ? !boardId.equals(that.boardId) : that.boardId != null) return false;
+ if (stacks != null ? !stacks.equals(that.stacks) : that.stacks != null) return false;
+ return labels != null ? labels.equals(that.labels) : that.labels == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (filterAccountId != null ? filterAccountId.hashCode() : 0);
+ result = 31 * result + (boardId != null ? boardId.hashCode() : 0);
+ result = 31 * result + (includeNoLabel ? 1 : 0);
+ result = 31 * result + (stacks != null ? stacks.hashCode() : 0);
+ result = 31 * result + (labels != null ? labels.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetLabel.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetLabel.java
new file mode 100644
index 000000000..0df280c98
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetLabel.java
@@ -0,0 +1,79 @@
+package it.niedermann.nextcloud.deck.model.widget.filter;
+
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import it.niedermann.nextcloud.deck.model.Label;
+
+@Entity(
+ indices = {
+ @Index(value = "filterBoardId", name = "idx_FilterWidgetLabel_filterBoardId"),
+ @Index(value = "labelId", name = "idx_FilterWidgetLabel_labelId")
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = Label.class,
+ parentColumns = "localId",
+ childColumns = "labelId", onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(
+ entity = FilterWidgetBoard.class,
+ parentColumns = "id",
+ childColumns = "filterBoardId", onDelete = ForeignKey.CASCADE
+ )
+ }
+)
+public class FilterWidgetLabel {
+
+ @PrimaryKey(autoGenerate = true)
+ private Long id;
+ private Long filterBoardId;
+ private Long labelId;
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getFilterBoardId() {
+ return filterBoardId;
+ }
+
+ public void setFilterBoardId(Long filterBoardId) {
+ this.filterBoardId = filterBoardId;
+ }
+
+ public Long getLabelId() {
+ return labelId;
+ }
+
+ public void setLabelId(Long labelId) {
+ this.labelId = labelId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FilterWidgetLabel that = (FilterWidgetLabel) o;
+
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+ if (filterBoardId != null ? !filterBoardId.equals(that.filterBoardId) : that.filterBoardId != null)
+ return false;
+ return labelId != null ? labelId.equals(that.labelId) : that.labelId == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (filterBoardId != null ? filterBoardId.hashCode() : 0);
+ result = 31 * result + (labelId != null ? labelId.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetProject.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetProject.java
new file mode 100644
index 000000000..7ed3f956c
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetProject.java
@@ -0,0 +1,89 @@
+package it.niedermann.nextcloud.deck.model.widget.filter;
+
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Ignore;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+
+@Entity(
+ indices = {
+ @Index(value = "filterAccountId", name = "idx_FilterWidgetProject_filterAccountId"),
+ @Index(value = "projectId", name = "idx_FilterWidgetProject_projectId")
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = OcsProject.class,
+ parentColumns = "localId",
+ childColumns = "projectId", onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(
+ entity = FilterWidgetAccount.class,
+ parentColumns = "id",
+ childColumns = "filterAccountId", onDelete = ForeignKey.CASCADE
+ )
+ }
+)
+public class FilterWidgetProject {
+
+ @PrimaryKey(autoGenerate = true)
+ private Long id;
+ private Long filterAccountId;
+ private Long projectId;
+
+ public FilterWidgetProject() {
+ // Default constructor
+ }
+
+ @Ignore
+ public FilterWidgetProject(Long projectId) {
+ this.projectId = projectId;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getFilterAccountId() {
+ return filterAccountId;
+ }
+
+ public void setFilterAccountId(Long filterAccountId) {
+ this.filterAccountId = filterAccountId;
+ }
+
+ public Long getProjectId() {
+ return projectId;
+ }
+
+ public void setProjectId(Long projectId) {
+ this.projectId = projectId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FilterWidgetProject that = (FilterWidgetProject) o;
+
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+ if (filterAccountId != null ? !filterAccountId.equals(that.filterAccountId) : that.filterAccountId != null)
+ return false;
+ return projectId != null ? projectId.equals(that.projectId) : that.projectId == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (filterAccountId != null ? filterAccountId.hashCode() : 0);
+ result = 31 * result + (projectId != null ? projectId.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetSort.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetSort.java
new file mode 100644
index 000000000..ae6516c94
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetSort.java
@@ -0,0 +1,126 @@
+package it.niedermann.nextcloud.deck.model.widget.filter;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Ignore;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import it.niedermann.nextcloud.deck.model.enums.ESortCriteria;
+
+@Entity(
+ indices = {
+ @Index(value = "filterWidgetId", name = "idx_FilterWidgetSort_filterWidgetId"),
+ @Index(value = {"filterWidgetId", "criteria"}, name = "unique_idx_FilterWidgetSort_filterWidgetId_criteria"),
+ @Index(value = {"filterWidgetId", "ruleOrder"}, name = "unique_idx_FilterWidgetSort_filterWidgetId_ruleOrder"),
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = FilterWidget.class,
+ parentColumns = "id",
+ childColumns = "filterWidgetId", onDelete = ForeignKey.CASCADE
+ )
+ }
+)
+public class FilterWidgetSort {
+
+ @PrimaryKey(autoGenerate = true)
+ private Long id;
+ private Long filterWidgetId;
+ private boolean direction = true;
+ @NonNull
+ private ESortCriteria criteria;
+ private int ruleOrder;
+
+ public FilterWidgetSort() {
+ // Default constructor
+ }
+
+ @Ignore
+ public FilterWidgetSort(ESortCriteria criteria, boolean ascending) {
+ setCriteria(criteria);
+ setDirection(ascending);
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getFilterWidgetId() {
+ return filterWidgetId;
+ }
+
+ public void setFilterWidgetId(Long filterWidgetId) {
+ this.filterWidgetId = filterWidgetId;
+ }
+
+ public boolean isDirectionAscending() {
+ return direction;
+ }
+
+ public boolean isDirectionDescending() {
+ return !direction;
+ }
+
+ public void setDirectionAscending() {
+ direction = true;
+ }
+
+ public void setDirectionDescending() {
+ direction = false;
+ }
+
+ public ESortCriteria getCriteria() {
+ return criteria;
+ }
+
+ public void setCriteria(ESortCriteria criteria) {
+ this.criteria = criteria;
+ }
+
+ public int getRuleOrder() {
+ return ruleOrder;
+ }
+
+ public void setRuleOrder(int ruleOrder) {
+ this.ruleOrder = ruleOrder;
+ }
+
+ public boolean isDirection() {
+ return direction;
+ }
+
+ public void setDirection(boolean direction) {
+ this.direction = direction;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (!(o instanceof FilterWidgetSort)) return false;
+
+ FilterWidgetSort that = (FilterWidgetSort) o;
+
+ if (direction != that.direction) return false;
+ if (ruleOrder != that.ruleOrder) return false;
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+ if (filterWidgetId != null ? !filterWidgetId.equals(that.filterWidgetId) : that.filterWidgetId != null)
+ return false;
+ return criteria == that.criteria;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (filterWidgetId != null ? filterWidgetId.hashCode() : 0);
+ result = 31 * result + (direction ? 1 : 0);
+ result = 31 * result + (criteria != null ? criteria.hashCode() : 0);
+ result = 31 * result + ruleOrder;
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetStack.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetStack.java
new file mode 100644
index 000000000..2da1c0b79
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetStack.java
@@ -0,0 +1,89 @@
+package it.niedermann.nextcloud.deck.model.widget.filter;
+
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Ignore;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import it.niedermann.nextcloud.deck.model.Stack;
+
+@Entity(
+ indices = {
+ @Index(value = "filterBoardId", name = "idx_FilterWidgetStack_filterBoardId"),
+ @Index(value = "stackId", name = "idx_FilterWidgetStack_stackId")
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = Stack.class,
+ parentColumns = "localId",
+ childColumns = "stackId", onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(
+ entity = FilterWidgetBoard.class,
+ parentColumns = "id",
+ childColumns = "filterBoardId", onDelete = ForeignKey.CASCADE
+ )
+ }
+)
+public class FilterWidgetStack {
+
+ @PrimaryKey(autoGenerate = true)
+ private Long id;
+ private Long filterBoardId;
+ private Long stackId;
+
+ public FilterWidgetStack() {
+ // Default constructor
+ }
+
+ @Ignore
+ public FilterWidgetStack(Long stackId) {
+ this.stackId = stackId;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getFilterBoardId() {
+ return filterBoardId;
+ }
+
+ public void setFilterBoardId(Long filterBoardId) {
+ this.filterBoardId = filterBoardId;
+ }
+
+ public Long getStackId() {
+ return stackId;
+ }
+
+ public void setStackId(Long stackId) {
+ this.stackId = stackId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FilterWidgetStack that = (FilterWidgetStack) o;
+
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+ if (filterBoardId != null ? !filterBoardId.equals(that.filterBoardId) : that.filterBoardId != null)
+ return false;
+ return stackId != null ? stackId.equals(that.stackId) : that.stackId == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (filterBoardId != null ? filterBoardId.hashCode() : 0);
+ result = 31 * result + (stackId != null ? stackId.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetUser.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetUser.java
new file mode 100644
index 000000000..9fd69aa85
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/FilterWidgetUser.java
@@ -0,0 +1,89 @@
+package it.niedermann.nextcloud.deck.model.widget.filter;
+
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Ignore;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import it.niedermann.nextcloud.deck.model.User;
+
+@Entity(
+ indices = {
+ @Index(value = "filterAccountId", name = "idx_FilterWidgetUser_filterAccountId"),
+ @Index(value = "userId", name = "idx_FilterWidgetUser_userId")
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = User.class,
+ parentColumns = "localId",
+ childColumns = "userId", onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(
+ entity = FilterWidgetAccount.class,
+ parentColumns = "id",
+ childColumns = "filterAccountId", onDelete = ForeignKey.CASCADE
+ )
+ }
+)
+public class FilterWidgetUser {
+
+ @PrimaryKey(autoGenerate = true)
+ private Long id;
+ private Long filterAccountId;
+ private Long userId;
+
+ public FilterWidgetUser() {
+ // Default constructor
+ }
+
+ @Ignore
+ public FilterWidgetUser(Long userId) {
+ this.userId = userId;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public Long getFilterAccountId() {
+ return filterAccountId;
+ }
+
+ public void setFilterAccountId(Long filterAccountId) {
+ this.filterAccountId = filterAccountId;
+ }
+
+ public Long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(Long userId) {
+ this.userId = userId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FilterWidgetUser that = (FilterWidgetUser) o;
+
+ if (id != null ? !id.equals(that.id) : that.id != null) return false;
+ if (filterAccountId != null ? !filterAccountId.equals(that.filterAccountId) : that.filterAccountId != null)
+ return false;
+ return userId != null ? userId.equals(that.userId) : that.userId == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = id != null ? id.hashCode() : 0;
+ result = 31 * result + (filterAccountId != null ? filterAccountId.hashCode() : 0);
+ result = 31 * result + (userId != null ? userId.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/dto/FilterWidgetCard.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/dto/FilterWidgetCard.java
new file mode 100644
index 000000000..2aaebb129
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/widget/filter/dto/FilterWidgetCard.java
@@ -0,0 +1,45 @@
+package it.niedermann.nextcloud.deck.model.widget.filter.dto;
+
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.Stack;
+import it.niedermann.nextcloud.deck.model.full.FullCard;
+
+public class FilterWidgetCard {
+ private FullCard card;
+ private Stack stack;
+ private Board board;
+
+ public FilterWidgetCard() {
+ // Default constructor
+ }
+
+ public FilterWidgetCard(FullCard card, Stack stack, Board board) {
+ this.card = card;
+ this.stack = stack;
+ this.board = board;
+ }
+
+ public FullCard getCard() {
+ return card;
+ }
+
+ public void setCard(FullCard card) {
+ this.card = card;
+ }
+
+ public Stack getStack() {
+ return stack;
+ }
+
+ public void setStack(Stack stack) {
+ this.stack = stack;
+ }
+
+ public Board getBoard() {
+ return board;
+ }
+
+ public void setBoard(Board board) {
+ this.board = board;
+ }
+}
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 d93835423..de0acc53b 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
@@ -37,6 +37,7 @@ import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.GsonConfig;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.api.LastSyncUtil;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.exceptions.DeckException;
import it.niedermann.nextcloud.deck.exceptions.OfflineException;
import it.niedermann.nextcloud.deck.model.AccessControl;
@@ -61,6 +62,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.FilterWidget;
+import it.niedermann.nextcloud.deck.model.widget.filter.dto.FilterWidgetCard;
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;
@@ -103,10 +106,14 @@ public class SyncManager {
@AnyThread
public SyncManager(@NonNull Context context, @Nullable String ssoAccountName) {
- appContext = context.getApplicationContext();
- LastSyncUtil.init(appContext);
- this.dataBaseAdapter = new DataBaseAdapter(appContext);
- this.serverAdapter = new ServerAdapter(appContext, ssoAccountName);
+ this(context, new DataBaseAdapter(context.getApplicationContext()), new ServerAdapter(context.getApplicationContext(), ssoAccountName));
+ LastSyncUtil.init(context.getApplicationContext());
+ }
+
+ private SyncManager(@NonNull Context context, @NonNull DataBaseAdapter databaseAdapter, @NonNull ServerAdapter serverAdapter) {
+ this.appContext = context.getApplicationContext();
+ this.dataBaseAdapter = databaseAdapter;
+ this.serverAdapter = serverAdapter;
}
@AnyThread
@@ -208,8 +215,13 @@ public class SyncManager {
refreshCapabilities(new IResponseCallback<Capabilities>(responseCallback.getAccount()) {
@Override
public void onResponse(Capabilities response) {
+ if (response != null) {
+ // When importing an account, the create account color can only be applied after the initial capabilities refresh
+ callbackAccount.setColor(response.getColor());
+ }
+
if (response != null && !response.isMaintenanceEnabled()) {
- if (response.getDeckVersion().isSupported(appContext)) {
+ if (response.getDeckVersion().isSupported()) {
long accountId = callbackAccountId;
Instant lastSyncDate = LastSyncUtil.getLastSyncDate(callbackAccountId);
@@ -260,12 +272,12 @@ public class SyncManager {
}
} else {
respondCallbacksAfterSync(callbacksQueueForSync, Boolean.FALSE, null);
- DeckLog.warn("No sync. Server version not supported: " + response.getDeckVersion().getOriginalVersion());
+ DeckLog.warn("No sync. Server version not supported:", response.getDeckVersion().getOriginalVersion());
}
} else {
respondCallbacksAfterSync(callbacksQueueForSync, Boolean.FALSE, null);
if (response != null) {
- DeckLog.warn("No sync. Status maintenance mode: " + response.isMaintenanceEnabled());
+ DeckLog.warn("No sync. Status maintenance mode:", response.isMaintenanceEnabled());
}
}
}
@@ -369,6 +381,11 @@ public class SyncManager {
return dataBaseAdapter.readAccount(id);
}
+ @WorkerThread
+ public Account readAccountDirectly(long id) {
+ return dataBaseAdapter.readAccountDirectly(id);
+ }
+
@AnyThread
public LiveData<Account> readAccount(@Nullable String name) {
return dataBaseAdapter.readAccount(name);
@@ -379,6 +396,11 @@ public class SyncManager {
return dataBaseAdapter.readAccounts();
}
+ @WorkerThread
+ public List<Account> readAccountsDirectly() {
+ return dataBaseAdapter.getAllAccountsDirectly();
+ }
+
/**
* <p>
* Since the return value is a {@link LiveData}, it should immediately return the available values from the database
@@ -423,6 +445,7 @@ public class SyncManager {
Account acc = dataBaseAdapter.getAccountByIdDirectly(account.getId());
acc.applyCapabilities(response.getResponse(), response.getHeaders().get("ETag"));
dataBaseAdapter.updateAccount(acc);
+ callback.getAccount().setServerDeckVersion(acc.getServerDeckVersion());
callback.onResponse(response.getResponse());
}
@@ -434,14 +457,14 @@ public class SyncManager {
if (requestFailedException.getStatusCode() == HTTP_UNAVAILABLE && requestFailedException.getCause() != null) {
String errorString = requestFailedException.getCause().getMessage();
Capabilities capabilities = GsonConfig.getGson().fromJson(errorString, Capabilities.class);
- DeckLog.verbose("HTTP Status " + HTTP_UNAVAILABLE + ": This server seems to be in maintenance mode.");
+ DeckLog.verbose("HTTP Status", HTTP_UNAVAILABLE + ": This server seems to be in maintenance mode.");
if (capabilities.isMaintenanceEnabled()) {
doAsync(() -> onResponse(ParsedResponse.of(capabilities)));
} else {
onError(throwable);
}
} else if (requestFailedException.getStatusCode() == HTTP_NOT_MODIFIED) {
- DeckLog.verbose("HTTP Status " + HTTP_NOT_MODIFIED + ": There haven't been any changes on the server side for this request.");
+ DeckLog.verbose("HTTP Status", HTTP_NOT_MODIFIED + ": There haven't been any changes on the server side for this request.");
//could be after maintenance. so we have to at least revert the maintenance flag
doAsync(() -> {
Account acc = dataBaseAdapter.getAccountByIdDirectly(account.getId());
@@ -524,14 +547,12 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<FullBoard> createBoard(long accountId, @NonNull Board board) {
- WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
+ public void createBoard(long accountId, @NonNull Board board, @NonNull ResponseCallback<FullBoard> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
User owner = dataBaseAdapter.getUserByUidDirectly(accountId, account.getUserName());
if (owner == null) {
- DeckLog.log("owner is null - this can be the case if the Deck app has never before been opened in the webinterface");
- liveData.postValue(null);
+ callback.onError(new Exception("Owner is null. This can be the case if the Deck app has never before been opened in the webinterface"));
} else {
FullBoard fullBoard = new FullBoard();
board.setOwnerId(owner.getLocalId());
@@ -539,21 +560,9 @@ public class SyncManager {
fullBoard.setBoard(board);
board.setAccountId(accountId);
fullBoard.setAccountId(accountId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new BoardDataProvider(), fullBoard, new IResponseCallback<FullBoard>(account) {
- @Override
- public void onResponse(FullBoard response) {
- liveData.postValue(response);
- }
-
- @SuppressLint("MissingSuperCall")
- @Override
- public void onError(Throwable throwable, FullBoard entity) {
- liveData.postError(throwable, entity);
- }
- });
+ new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new BoardDataProvider(), fullBoard, IResponseCallback.from(account, callback));
}
});
- return liveData;
}
/**
@@ -562,18 +571,14 @@ public class SyncManager {
*
* @param cloneCards determines whether or not the cards in this {@link Board} shall be cloned or not
* Does <strong>not</strong> clone any {@link Card} or {@link AccessControl} from the origin {@link Board}.
- * <p>
- * TODO implement https://github.com/stefan-niedermann/nextcloud-deck/issues/608
*/
@AnyThread
- public WrappedLiveData<FullBoard> cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, @ColorInt int targetBoardColor, boolean cloneCards) {
- final WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
-
+ public void cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, @ColorInt int targetBoardColor, boolean cloneCards, @NonNull ResponseCallback<FullBoard> callback) {
doAsync(() -> {
Account originAccount = dataBaseAdapter.getAccountByIdDirectly(originAccountId);
User newOwner = dataBaseAdapter.getUserByUidDirectly(originAccountId, originAccount.getUserName());
if (newOwner == null) {
- liveData.postError(new DeckException(DeckException.Hint.UNKNOWN_ACCOUNT_USER_ID, "User with Account-UID \"" + originAccount.getUserName() + "\" not found."));
+ callback.onError(new DeckException(DeckException.Hint.UNKNOWN_ACCOUNT_USER_ID, "User with Account-UID \"" + originAccount.getUserName() + "\" not found."));
return;
}
FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(originAccountId, originBoardLocalId);
@@ -663,7 +668,7 @@ public class SyncManager {
if (newLabelId != null) {
dataBaseAdapter.createJoinCardWithLabel(newLabelId, createdCardId, DBStatus.LOCAL_EDITED);
} else
- DeckLog.error("ID of created Label is null! Skipping assignment of \"" + oldLabel.getTitle() + "\"...");
+ DeckLog.error("ID of created Label is null! Skipping assignment of ", oldLabel.getTitle(), "…");
}
}
if (isSameAccount && oldCard.getAssignedUsers() != null) {
@@ -686,20 +691,19 @@ public class SyncManager {
.setResponseCallback(new IResponseCallback<Boolean>(targetAccount) {
@Override
public void onResponse(Boolean response) {
- liveData.postValue(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
+ callback.onResponse(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
- super.onError(throwable);
- liveData.postError(throwable);
+ callback.onError(throwable);
}
}).doUpSyncFor(new BoardWithStacksAndLabelsUpSyncDataProvider(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId)));
} else {
- liveData.postValue(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
+ callback.onResponse(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
}
});
- return liveData;
}
@AnyThread
@@ -715,7 +719,7 @@ public class SyncManager {
}
}).doSyncFor(new ActivityDataProvider(null, card));
} else {
- DeckLog.log("Can not fetch activities for card \"" + card.getTitle() + "\" because this card does not have a remote id yet.");
+ DeckLog.log("Can not fetch activities for card ", card.getTitle(), "because this card does not have a remote id yet.");
}
}
});
@@ -755,17 +759,15 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<Void> deleteComment(long accountId, long localCardId, long localCommentId) {
- WrappedLiveData<Void> liveData = new WrappedLiveData<>();
+ public void deleteComment(long accountId, long localCardId, long localCommentId, @NonNull ResponseCallback<Void> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
Card card = dataBaseAdapter.getCardByLocalIdDirectly(accountId, localCardId);
DeckComment entity = dataBaseAdapter.getCommentByLocalIdDirectly(accountId, localCommentId);
OcsComment commentEntity = OcsComment.of(entity);
new DataPropagationHelper(serverAdapter, dataBaseAdapter).deleteEntity(new DeckCommentsDataProvider(null, card),
- commentEntity, getCallbackToLiveDataConverter(account, liveData));
+ commentEntity, IResponseCallback.from(account, callback));
});
- return liveData;
}
public LiveData<List<FullDeckComment>> getFullCommentsForLocalCardId(long localCardId) {
@@ -773,37 +775,22 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<Void> deleteBoard(@NonNull Board board) {
- WrappedLiveData<Void> liveData = new WrappedLiveData<>();
+ public void deleteBoard(@NonNull Board board, @NonNull ResponseCallback<Void> callback) {
doAsync(() -> {
long accountId = board.getAccountId();
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
FullBoard fullBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, board.getLocalId());
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).deleteEntity(new BoardDataProvider(), fullBoard, getCallbackToLiveDataConverter(account, liveData));
+ new DataPropagationHelper(serverAdapter, dataBaseAdapter).deleteEntity(new BoardDataProvider(), fullBoard, IResponseCallback.from(account, callback));
});
- return liveData;
}
@AnyThread
- public WrappedLiveData<FullBoard> updateBoard(@NonNull FullBoard board) {
- WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
- long accountId = board.getAccountId();
+ public void updateBoard(@NonNull FullBoard board, @NonNull ResponseCallback<FullBoard> callback) {
doAsync(() -> {
+ long accountId = board.getAccountId();
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new BoardDataProvider(), board, new IResponseCallback<FullBoard>(account) {
- @Override
- public void onResponse(FullBoard response) {
- liveData.postValue(response);
- }
-
- @SuppressLint("MissingSuperCall")
- @Override
- public void onError(Throwable throwable) {
- liveData.postError(throwable);
- }
- });
+ new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new BoardDataProvider(), board, IResponseCallback.from(account, callback));
});
- return liveData;
}
public LiveData<List<Stack>> getStacksForBoard(long accountId, long localBoardId) {
@@ -814,20 +801,23 @@ public class SyncManager {
return dataBaseAdapter.getStack(accountId, localStackId);
}
+ @WorkerThread
+ public Long getBoardLocalIdByLocalCardIdDirectly(long localCardId) {
+ return dataBaseAdapter.getBoardLocalIdByLocalCardIdDirectly(localCardId);
+ }
+
@AnyThread
- public WrappedLiveData<AccessControl> createAccessControl(long accountId, AccessControl entity) {
- WrappedLiveData<AccessControl> liveData = new WrappedLiveData<>();
+ public void createAccessControl(long accountId, @NonNull AccessControl entity, @NonNull ResponseCallback<AccessControl> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, entity.getBoardId());
new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(
- new AccessControlDataProvider(null, board, Collections.singletonList(entity)), entity, getCallbackToLiveDataConverter(account, liveData), ((entity1, response) -> {
+ new AccessControlDataProvider(null, board, Collections.singletonList(entity)), entity, IResponseCallback.from(account, callback), ((entity1, response) -> {
response.setBoardId(entity.getBoardId());
response.setUserId(entity.getUser().getLocalId());
})
);
});
- return liveData;
}
@WorkerThread
@@ -840,36 +830,17 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<AccessControl> updateAccessControl(@NonNull AccessControl entity) {
- WrappedLiveData<AccessControl> liveData = new WrappedLiveData<>();
+ public void updateAccessControl(@NonNull AccessControl entity, @NonNull ResponseCallback<AccessControl> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(entity.getAccountId());
FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(entity.getAccountId(), entity.getBoardId());
new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(
- new AccessControlDataProvider(null, board, Collections.singletonList(entity)), entity, getCallbackToLiveDataConverter(account, liveData));
+ new AccessControlDataProvider(null, board, Collections.singletonList(entity)), entity, IResponseCallback.from(account, callback));
});
- return liveData;
}
@AnyThread
- private <T> IResponseCallback<T> getCallbackToLiveDataConverter(Account account, @NonNull WrappedLiveData<T> liveData) {
- return new IResponseCallback<T>(account) {
- @Override
- public void onResponse(T response) {
- liveData.postValue(response);
- }
-
- @SuppressLint("MissingSuperCall")
- @Override
- public void onError(Throwable throwable) {
- liveData.postError(throwable);
- }
- };
- }
-
- @AnyThread
- public WrappedLiveData<Void> deleteAccessControl(@NonNull AccessControl entity) {
- WrappedLiveData<Void> liveData = new WrappedLiveData<>();
+ public void deleteAccessControl(@NonNull AccessControl entity, @NonNull ResponseCallback<Void> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(entity.getAccountId());
FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(entity.getAccountId(), entity.getBoardId());
@@ -881,17 +852,16 @@ public class SyncManager {
if (entity.getAccountId() == entity.getAccountId() && entity.getUser().getUid().equals(account.getUserName())) {
dataBaseAdapter.deleteBoardPhysically(board.getBoard());
}
- liveData.postValue(response);
+ callback.onResponse(response);
}
@SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
- liveData.postError(throwable);
+ callback.onError(throwable);
}
});
});
- return liveData;
}
public LiveData<FullBoard> getFullBoardById(Long accountId, Long localId) {
@@ -899,8 +869,7 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<FullStack> createStack(long accountId, @NonNull String title, long boardLocalId) {
- WrappedLiveData<FullStack> liveData = new WrappedLiveData<>();
+ public void createStack(long accountId, @NonNull String title, long boardLocalId, @NonNull ResponseCallback<FullStack> callback) {
doAsync(() -> {
Stack stack = new Stack(title, boardLocalId);
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
@@ -911,65 +880,34 @@ public class SyncManager {
stack.setBoardId(board.getLocalId());
fullStack.setStack(stack);
fullStack.setAccountId(accountId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new StackDataProvider(null, board), fullStack, new IResponseCallback<FullStack>(account) {
- @Override
- public void onResponse(FullStack response) {
- liveData.postValue(response);
- }
-
- @SuppressLint("MissingSuperCall")
- @Override
- public void onError(Throwable throwable, FullStack entity) {
- liveData.postError(throwable, entity);
- }
- });
+ new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new StackDataProvider(null, board), fullStack, IResponseCallback.from(account, callback));
});
- return liveData;
}
@AnyThread
- public WrappedLiveData<Void> deleteStack(long accountId, long stackLocalId, long boardLocalId) {
- WrappedLiveData<Void> liveData = new WrappedLiveData<>();
+ public void deleteStack(long accountId, long stackLocalId, long boardLocalId, @NonNull ResponseCallback<Void> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
FullStack fullStack = dataBaseAdapter.getFullStackByLocalIdDirectly(stackLocalId);
FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, boardLocalId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).deleteEntity(new StackDataProvider(null, board), fullStack, getCallbackToLiveDataConverter(account, liveData));
+ new DataPropagationHelper(serverAdapter, dataBaseAdapter).deleteEntity(new StackDataProvider(null, board), fullStack, IResponseCallback.from(account, callback));
});
- return liveData;
}
@AnyThread
- public WrappedLiveData<FullStack> updateStackTitle(long localStackId, @NonNull String newTitle) {
- WrappedLiveData<FullStack> liveData = new WrappedLiveData<>();
+ public void updateStackTitle(long localStackId, @NonNull String newTitle, @NonNull ResponseCallback<FullStack> callback) {
doAsync(() -> {
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(localStackId);
FullBoard fullBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(stack.getAccountId(), stack.getStack().getBoardId());
Account account = dataBaseAdapter.getAccountByIdDirectly(stack.getAccountId());
stack.getStack().setTitle(newTitle);
- updateStack(account, fullBoard, stack, liveData);
+ updateStack(account, fullBoard, stack, callback);
});
- return liveData;
}
@AnyThread
- private void updateStack(@NonNull Account account, @NonNull FullBoard board, @NonNull FullStack stack, @Nullable WrappedLiveData<FullStack> liveData) {
- doAsync(() -> new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new StackDataProvider(null, board), stack, new IResponseCallback<FullStack>(account) {
- @Override
- public void onResponse(FullStack response) {
- if (liveData != null) {
- liveData.postValue(response);
- }
- }
-
- @SuppressLint("MissingSuperCall")
- @Override
- public void onError(Throwable throwable) {
- if (liveData != null) {
- liveData.postError(throwable);
- }
- }
- }));
+ private void updateStack(@NonNull Account account, @NonNull FullBoard board, @NonNull FullStack stack, @NonNull ResponseCallback<FullStack> callback) {
+ doAsync(() -> new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new StackDataProvider(null, board), stack, IResponseCallback.from(account, callback)));
}
/**
@@ -994,8 +932,8 @@ public class SyncManager {
int orderFirst = stacks.first.getStack().getOrder();
stacks.first.getStack().setOrder(stacks.second.getStack().getOrder());
stacks.second.getStack().setOrder(orderFirst);
- updateStack(account, fullBoard, stacks.first, null);
- updateStack(account, fullBoard, stacks.second, null);
+ updateStack(account, fullBoard, stacks.first, ResponseCallback.empty());
+ updateStack(account, fullBoard, stacks.second, ResponseCallback.empty());
});
}
@@ -1087,7 +1025,6 @@ public class SyncManager {
}
}
-
if (serverAdapter.hasInternetConnection()) {
new SyncHelper(serverAdapter, dataBaseAdapter, null)
.setResponseCallback(new IResponseCallback<Boolean>(account) {
@@ -1099,7 +1036,7 @@ public class SyncManager {
@SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
- if (throwable.getClass() == DeckException.class && ((DeckException)throwable).getHint().equals(DeckException.Hint.DEPENDENCY_NOT_SYNCED_YET)) {
+ if (throwable.getClass() == DeckException.class && ((DeckException) throwable).getHint().equals(DeckException.Hint.DEPENDENCY_NOT_SYNCED_YET)) {
liveData.postValue(card);
} else {
liveData.postError(throwable);
@@ -1115,8 +1052,7 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<Void> deleteCard(@NonNull Card card) {
- WrappedLiveData<Void> liveData = new WrappedLiveData<>();
+ public void deleteCard(@NonNull Card card, @NonNull ResponseCallback<Void> callback) {
doAsync(() -> {
FullCard fullCard = dataBaseAdapter.getFullCardByLocalIdDirectly(card.getAccountId(), card.getLocalId());
if (fullCard == null) {
@@ -1125,22 +1061,19 @@ public class SyncManager {
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).deleteEntity(new CardPropagationDataProvider(null, board, stack), fullCard, getCallbackToLiveDataConverter(account, liveData));
+ new DataPropagationHelper(serverAdapter, dataBaseAdapter).deleteEntity(new CardPropagationDataProvider(null, board, stack), fullCard, IResponseCallback.from(account, callback));
});
- return liveData;
}
@AnyThread
- public WrappedLiveData<FullCard> archiveCard(@NonNull FullCard card) {
- WrappedLiveData<FullCard> liveData = new WrappedLiveData<>();
+ public void archiveCard(@NonNull FullCard card, @NonNull ResponseCallback<FullCard> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getCard().getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
card.getCard().setArchived(true);
- updateCardForArchive(stack, board, card, getCallbackToLiveDataConverter(account, liveData));
+ updateCardForArchive(stack, board, card, IResponseCallback.from(account, callback));
});
- return liveData;
}
private void updateCardForArchive(FullStack stack, Board board, FullCard card, @NonNull IResponseCallback<FullCard> callback) {
@@ -1148,91 +1081,81 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<FullCard> dearchiveCard(@NonNull FullCard card) {
- WrappedLiveData<FullCard> liveData = new WrappedLiveData<>();
+ public void dearchiveCard(@NonNull FullCard card, @NonNull ResponseCallback<FullCard> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getCard().getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
card.getCard().setArchived(false);
- updateCardForArchive(stack, board, card, getCallbackToLiveDataConverter(account, liveData));
+ updateCardForArchive(stack, board, card, IResponseCallback.from(account, callback));
});
- return liveData;
}
@AnyThread
- public WrappedLiveData<Void> archiveCardsInStack(long accountId, long stackLocalId, @NonNull FilterInformation filterInformation) {
- WrappedLiveData<Void> liveData = new WrappedLiveData<>();
+ public void archiveCardsInStack(long accountId, long stackLocalId, @NonNull FilterInformation filterInformation, @NonNull ResponseCallback<Void> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(stackLocalId);
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
List<FullCard> cards = dataBaseAdapter.getFullCardsForStackDirectly(accountId, stackLocalId, filterInformation);
- if (cards.size() > 0) {
- CountDownLatch latch = new CountDownLatch(cards.size());
- for (FullCard card : cards) {
- if (card.getCard().isArchived()) {
+ if (cards.size() <= 0) {
+ callback.onResponse(null);
+ return;
+ }
+ final CountDownLatch latch = new CountDownLatch(cards.size());
+ for (FullCard card : cards) {
+ if (card.getCard().isArchived()) {
+ latch.countDown();
+ continue;
+ }
+ card.getCard().setArchived(true);
+ updateCardForArchive(stack, board, card, new IResponseCallback<FullCard>(account) {
+ @Override
+ public void onResponse(FullCard response) {
latch.countDown();
- continue;
}
- card.getCard().setArchived(true);
- updateCardForArchive(stack, board, card, new IResponseCallback<FullCard>(account) {
- @Override
- public void onResponse(FullCard response) {
- latch.countDown();
- }
- @SuppressLint("MissingSuperCall")
- @Override
- public void onError(Throwable throwable) {
- latch.countDown();
- liveData.postError(throwable);
- }
- });
- }
- try {
- latch.await();
- liveData.postValue(null);
- } catch (InterruptedException e) {
- liveData.postError(e);
- }
- } else {
- liveData.postValue(null);
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ latch.countDown();
+ callback.onError(throwable);
+ }
+ });
+ }
+ try {
+ latch.await();
+ callback.onResponse(null);
+ } catch (InterruptedException e) {
+ callback.onError(e);
}
});
- return liveData;
}
@AnyThread
- public WrappedLiveData<FullBoard> archiveBoard(@NonNull Board board) {
- WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
+ public void archiveBoard(@NonNull Board board, @NonNull ResponseCallback<FullBoard> callback) {
doAsync(() -> {
try {
FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
b.getBoard().setArchived(true);
- updateBoard(b);
- liveData.postValue(b);
+ updateBoard(b, callback);
} catch (Throwable e) {
- liveData.postError(e);
+ callback.onError(e);
}
});
- return liveData;
}
@AnyThread
- public WrappedLiveData<FullBoard> dearchiveBoard(@NonNull Board board) {
- WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
+ public void dearchiveBoard(@NonNull Board board, @NonNull ResponseCallback<FullBoard> callback) {
doAsync(() -> {
try {
FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
b.getBoard().setArchived(false);
- updateBoard(b);
- liveData.postValue(b);
+ updateBoard(b, callback);
} catch (Throwable e) {
- liveData.postError(e);
+ callback.onError(e);
}
});
- return liveData;
}
@AnyThread
@@ -1314,7 +1237,6 @@ public class SyncManager {
@AnyThread
public WrappedLiveData<Void> moveCard(long originAccountId, long originCardLocalId, long targetAccountId, long targetBoardLocalId, long targetStackLocalId) {
return LiveDataHelper.wrapInLiveData(() -> {
-
final FullCard originalCard = dataBaseAdapter.getFullCardByLocalIdDirectly(originAccountId, originCardLocalId);
int newIndex = dataBaseAdapter.getHighestCardOrderInStack(targetStackLocalId) + 1;
final FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalCardIdDirectly(originCardLocalId);
@@ -1325,7 +1247,7 @@ public class SyncManager {
}
// ### get rid of original card where it is now.
final Card originalInnerCard = originalCard.getCard();
- deleteCard(new Card(originalInnerCard));
+ deleteCard(new Card(originalInnerCard), ResponseCallback.empty());
// ### clone card itself
originalInnerCard.setAccountId(targetAccountId);
originalInnerCard.setId(null);
@@ -1442,31 +1364,18 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<Label> createLabel(long accountId, Label label, long localBoardId) {
- WrappedLiveData<Label> liveData = new WrappedLiveData<>();
+ public void createLabel(long accountId, Label label, long localBoardId, @NonNull ResponseCallback<Label> callback) {
doAsync(() -> {
Label existing = dataBaseAdapter.getLabelByBoardIdAndTitleDirectly(label.getBoardId(), label.getTitle());
if (existing != null) {
- liveData.postError(new SQLiteConstraintException("label \"" + label.getTitle() + "\" already exists for this board!"));
+ callback.onError(new SQLiteConstraintException("label \"" + label.getTitle() + "\" already exists for this board!"));
return;
}
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(localBoardId);
label.setAccountId(accountId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new LabelDataProvider(null, board, null), label, new IResponseCallback<Label>(account) {
- @Override
- public void onResponse(Label response) {
- liveData.postValue(response);
- }
-
- @SuppressLint("MissingSuperCall")
- @Override
- public void onError(Throwable throwable) {
- liveData.postError(throwable);
- }
- }, (entity, response) -> response.setBoardId(board.getLocalId()));
+ new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new LabelDataProvider(null, board, null), label, IResponseCallback.from(account, callback), (entity, response) -> response.setBoardId(board.getLocalId()));
});
- return liveData;
}
public MutableLiveData<Label> createAndAssignLabelToCard(long accountId, @NonNull Label label, long localCardId) {
@@ -1500,27 +1409,23 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<Void> deleteLabel(@NonNull Label label) {
- WrappedLiveData<Void> liveData = new WrappedLiveData<>();
+ public void deleteLabel(@NonNull Label label, @NonNull ResponseCallback<Void> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(label.getAccountId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(label.getBoardId());
new DataPropagationHelper(serverAdapter, dataBaseAdapter)
- .deleteEntity(new LabelDataProvider(null, board, Collections.emptyList()), label, getCallbackToLiveDataConverter(account, liveData));
+ .deleteEntity(new LabelDataProvider(null, board, Collections.emptyList()), label, IResponseCallback.from(account, callback));
});
- return liveData;
}
@AnyThread
- public WrappedLiveData<Label> updateLabel(@NonNull Label label) {
- WrappedLiveData<Label> liveData = new WrappedLiveData<>();
+ public void updateLabel(@NonNull Label label, @NonNull ResponseCallback<Label> callback) {
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(label.getAccountId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(label.getBoardId());
new DataPropagationHelper(serverAdapter, dataBaseAdapter)
- .updateEntity(new LabelDataProvider(null, board, Collections.emptyList()), label, getCallbackToLiveDataConverter(account, liveData));
+ .updateEntity(new LabelDataProvider(null, board, Collections.emptyList()), label, IResponseCallback.from(account, callback));
});
- return liveData;
}
@AnyThread
@@ -1850,8 +1755,7 @@ public class SyncManager {
* Also this leads to the attachment being present in the card forever with a DBStatus.LOCAL_EDITED
*/
@AnyThread
- public WrappedLiveData<Attachment> addAttachmentToCard(long accountId, long localCardId, @NonNull String mimeType, @NonNull File file) {
- WrappedLiveData<Attachment> liveData = new WrappedLiveData<>();
+ public void addAttachmentToCard(long accountId, long localCardId, @NonNull String mimeType, @NonNull File file, @NonNull ResponseCallback<Attachment> callback) {
doAsync(() -> {
Attachment attachment = populateAttachmentEntityForFile(new Attachment(), localCardId, mimeType, file);
final Instant now = Instant.now();
@@ -1863,10 +1767,9 @@ public class SyncManager {
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(
new AttachmentDataProvider(null, board, stack, card, Collections.singletonList(attachment)),
- attachment, getCallbackToLiveDataConverter(account, liveData)
+ attachment, IResponseCallback.from(account, callback)
);
});
- return liveData;
}
@AnyThread
@@ -1911,8 +1814,7 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<Void> deleteAttachmentOfCard(long accountId, long localCardId, long localAttachmentId) {
- WrappedLiveData<Void> liveData = new WrappedLiveData<>();
+ public void deleteAttachmentOfCard(long accountId, long localCardId, long localAttachmentId, @NonNull ResponseCallback<Void> callback) {
doAsync(() -> {
if (serverAdapter.hasInternetConnection()) {
FullCard card = dataBaseAdapter.getFullCardByLocalIdDirectly(accountId, localCardId);
@@ -1922,16 +1824,75 @@ public class SyncManager {
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
new DataPropagationHelper(serverAdapter, dataBaseAdapter)
- .deleteEntity(new AttachmentDataProvider(null, board, stack, card, Collections.singletonList(attachment)), attachment, getCallbackToLiveDataConverter(account, liveData));
+ .deleteEntity(new AttachmentDataProvider(null, board, stack, card, Collections.singletonList(attachment)), attachment, IResponseCallback.from(account, callback));
}
});
- return liveData;
}
// -------------------
// Widgets
// -------------------
+ // # filter widget
+
+ @AnyThread
+ public void createFilterWidget(@NonNull FilterWidget filterWidget, @NonNull ResponseCallback<Integer> callback) {
+ doAsync(() -> {
+ try {
+ int filterWidgetId = dataBaseAdapter.createFilterWidgetDirectly(filterWidget);
+ callback.onResponse(filterWidgetId);
+ } catch (Throwable t) {
+ callback.onError(t);
+ }
+ });
+ }
+
+ @AnyThread
+ public void updateFilterWidget(@NonNull FilterWidget filterWidget, @NonNull IResponseCallback<Boolean> callback) {
+ doAsync(() -> {
+ try {
+ dataBaseAdapter.updateFilterWidgetDirectly(filterWidget);
+ callback.onResponse(Boolean.TRUE);
+ } catch (Throwable t) {
+ callback.onError(t);
+ }
+ });
+ }
+
+ @AnyThread
+ public void getFilterWidget(@NonNull Integer filterWidgetId, @NonNull ResponseCallback<FilterWidget> callback) {
+ doAsync(() -> {
+ try {
+ callback.onResponse(dataBaseAdapter.getFilterWidgetByIdDirectly(filterWidgetId));
+ } catch (Throwable t) {
+ callback.onError(t);
+ }
+ });
+ }
+
+ @AnyThread
+ public void deleteFilterWidget(int filterWidgetId, @NonNull ResponseCallback<Boolean> callback) {
+ doAsync(() -> {
+ try {
+ dataBaseAdapter.deleteFilterWidgetDirectly(filterWidgetId);
+ callback.onResponse(Boolean.TRUE);
+ } catch (Throwable t) {
+ callback.onError(t);
+ }
+ });
+ }
+
+ public boolean filterWidgetExists(int id) {
+ return dataBaseAdapter.filterWidgetExists(id);
+ }
+
+ @WorkerThread
+ public List<FilterWidgetCard> getCardsForFilterWidget(@NonNull Integer filterWidgetId) {
+ return dataBaseAdapter.getCardsForFilterWidget(filterWidgetId);
+ }
+
+ // # single card widget
+
/**
* Can be called from a configuration screen or a picker.
* Creates a new entry in the database, if row with given widgetId does not yet exist.
@@ -1942,10 +1903,10 @@ public class SyncManager {
}
@WorkerThread
- public FullSingleCardWidgetModel getSingleCardWidgetModelDirectly(int widgetId) throws NoSuchElementException {
- final FullSingleCardWidgetModel model = dataBaseAdapter.getFullSingleCardWidgetModel(widgetId);
+ public FullSingleCardWidgetModel getSingleCardWidgetModelDirectly(int appWidgetId) throws NoSuchElementException {
+ final FullSingleCardWidgetModel model = dataBaseAdapter.getFullSingleCardWidgetModel(appWidgetId);
if (model == null) {
- throw new NoSuchElementException();
+ throw new NoSuchElementException("There is no " + FullSingleCardWidgetModel.class.getSimpleName() + " with the given appWidgetId " + appWidgetId);
}
return model;
}
@@ -1978,4 +1939,15 @@ public class SyncManager {
public static boolean ignoreExceptionOnVoidError(Throwable t) {
return t instanceof NullPointerException && "Attempt to invoke interface method 'void io.reactivex.disposables.Disposable.dispose()' on a null object reference".equals(t.getMessage());
}
+
+ @WorkerThread
+ public Stack getStackDirectly(long stackLocalId) {
+ return dataBaseAdapter.getStackByLocalIdDirectly(stackLocalId);
+ }
+
+ @ColorInt
+ @WorkerThread
+ public Integer getBoardColorDirectly(long accountId, long localBoardId) {
+ return dataBaseAdapter.getBoardColorDirectly(accountId, localBoardId);
+ }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncWorker.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncWorker.java
index f43cbd34f..af6e08826 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncWorker.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncWorker.java
@@ -40,7 +40,7 @@ public class SyncWorker extends Worker {
sharedPreferencesEditor.putLong(getApplicationContext().getString(R.string.shared_preference_last_background_sync), System.currentTimeMillis());
sharedPreferencesEditor.apply();
boolean success = syncManager.synchronizeEverything();
- DeckLog.info("Finishing background synchronization with result " + success);
+ DeckLog.info("Finishing background synchronization. Success: ", success);
return success ? Result.failure() : Result.success();
}
return Result.success();
@@ -66,17 +66,17 @@ public class SyncWorker extends Worker {
unit = TimeUnit.HOURS;
}
if (unit == null) {
- DeckLog.info("Do not register a new " + SyncWorker.class.getSimpleName() + " because setting " + preferenceValue + " is not a valid time frame");
+ DeckLog.info("Do not register a new", SyncWorker.class.getSimpleName(), "because setting", preferenceValue, "is not a valid time frame");
} else {
final PeriodicWorkRequest work = new PeriodicWorkRequest.Builder(SyncWorker.class, repeatInterval, unit)
.setConstraints(constraints).build();
- DeckLog.info("Registering " + SyncWorker.class.getSimpleName() + " running each " + repeatInterval + " " + unit);
+ DeckLog.info("Registering", SyncWorker.class.getSimpleName(), "running each", repeatInterval, unit);
WorkManager.getInstance(context.getApplicationContext()).enqueueUniquePeriodicWork(SyncWorker.WORKER_TAG, ExistingPeriodicWorkPolicy.REPLACE, work);
}
}
private static void deregister(@NonNull Context context) {
- DeckLog.info("Deregistering all " + SyncWorker.class.getSimpleName() + " with tag \"" + WORKER_TAG + "\"");
+ DeckLog.info("Deregistering all", SyncWorker.class.getSimpleName(), "with tag", WORKER_TAG);
WorkManager.getInstance(context.getApplicationContext()).cancelAllWorkByTag(WORKER_TAG);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java
index bff8ddfdb..8cadbaec8 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java
@@ -24,11 +24,13 @@ import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.api.RequestHelper;
import it.niedermann.nextcloud.deck.exceptions.OfflineException;
import it.niedermann.nextcloud.deck.model.AccessControl;
+import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
import it.niedermann.nextcloud.deck.model.Board;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.Stack;
+import it.niedermann.nextcloud.deck.model.enums.EAttachmentType;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.model.full.FullStack;
@@ -51,6 +53,8 @@ import static it.niedermann.nextcloud.deck.util.MimeTypeUtil.TEXT_PLAIN;
public class ServerAdapter {
private final String prefKeyWifiOnly;
+ private final String prefKeyEtags;
+ final SharedPreferences sharedPreferences;
@NonNull
private final Context applicationContext;
@@ -63,7 +67,9 @@ public class ServerAdapter {
public ServerAdapter(@NonNull Context applicationContext, @Nullable String ssoAccountName) {
this.applicationContext = applicationContext;
prefKeyWifiOnly = applicationContext.getResources().getString(R.string.pref_key_wifi_only);
+ prefKeyEtags = applicationContext.getResources().getString(R.string.pref_key_etags);
provider = new ApiProvider(applicationContext, ssoAccountName);
+ sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext);
}
public String getServerUrl() {
@@ -88,7 +94,6 @@ public class ServerAdapter {
public boolean hasInternetConnection() {
ConnectivityManager cm = (ConnectivityManager) applicationContext.getSystemService(Context.CONNECTIVITY_SERVICE);
if (cm != null) {
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(applicationContext);
if (sharedPreferences.getBoolean(prefKeyWifiOnly, false)) {
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
Network network = cm.getActiveNetwork();
@@ -104,8 +109,6 @@ public class ServerAdapter {
}
return networkInfo.isConnected();
}
-
-
} else {
return cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
}
@@ -126,9 +129,13 @@ public class ServerAdapter {
}
public void getBoards(IResponseCallback<ParsedResponse<List<FullBoard>>> responseCallback) {
- RequestHelper.request(provider, () ->
- provider.getDeckAPI().getBoards(true, getLastSyncDateFormatted(responseCallback.getAccount().getId()), responseCallback.getAccount().getBoardsEtag()),
- responseCallback);
+ RequestHelper.request(provider, () -> isEtagsEnabled()
+ ? provider.getDeckAPI().getBoards(true, getLastSyncDateFormatted(responseCallback.getAccount().getId()), responseCallback.getAccount().getBoardsEtag())
+ : provider.getDeckAPI().getBoards(true, getLastSyncDateFormatted(responseCallback.getAccount().getId())), responseCallback);
+ }
+
+ public boolean isEtagsEnabled() {
+ return sharedPreferences.getBoolean(prefKeyEtags, true);
}
public void getCapabilities(String eTag, IResponseCallback<ParsedResponse<Capabilities>> responseCallback) {
@@ -220,7 +227,13 @@ public class ServerAdapter {
public void getCard(long boardId, long stackId, long cardId, IResponseCallback<FullCard> responseCallback) {
ensureInternetConnection();
- RequestHelper.request(provider, () -> provider.getDeckAPI().getCard(boardId, stackId, cardId, getLastSyncDateFormatted(responseCallback.getAccount().getId())), responseCallback);
+ RequestHelper.request(provider, () -> {
+ final Account account = responseCallback.getAccount();
+ if (account != null && account.getServerDeckVersionAsObject().supportsFileAttachments()) {
+ return provider.getDeckAPI().getCard_1_1(boardId, stackId, cardId, getLastSyncDateFormatted(responseCallback.getAccount().getId()));
+ }
+ return provider.getDeckAPI().getCard_1_0(boardId, stackId, cardId, getLastSyncDateFormatted(responseCallback.getAccount().getId()));
+ }, responseCallback);
}
public void createCard(long boardId, long stackId, Card card, IResponseCallback<FullCard> responseCallback) {
@@ -281,10 +294,14 @@ public class ServerAdapter {
}
// ## ATTACHMENTS
- public void uploadAttachment(Long remoteBoardId, long remoteStackId, long remoteCardId, String contentType, File attachment, IResponseCallback<Attachment> responseCallback) {
- ensureInternetConnection();
- MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", attachment.getName(), RequestBody.create(MediaType.parse(getMimeType(attachment)), attachment));
- MultipartBody.Part typePart = MultipartBody.Part.createFormData("type", null, RequestBody.create(MediaType.parse(TEXT_PLAIN), "deck_file"));
+ public void uploadAttachment(Long remoteBoardId, long remoteStackId, long remoteCardId, File attachment, IResponseCallback<Attachment> responseCallback) {
+ ensureInternetConnection();
+ final Account account = responseCallback.getAccount();
+ final String type = (account != null && account.getServerDeckVersionAsObject().supportsFileAttachments())
+ ? EAttachmentType.FILE.getValue()
+ : EAttachmentType.DECK_FILE.getValue();
+ final MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", attachment.getName(), RequestBody.create(MediaType.parse(getMimeType(attachment)), attachment));
+ final MultipartBody.Part typePart = MultipartBody.Part.createFormData("type", null, RequestBody.create(MediaType.parse(TEXT_PLAIN), type));
RequestHelper.request(provider, () -> provider.getDeckAPI().uploadAttachment(remoteBoardId, remoteStackId, remoteCardId, typePart, filePart), responseCallback);
}
@@ -304,9 +321,13 @@ public class ServerAdapter {
public void updateAttachment(Long remoteBoardId, long remoteStackId, long remoteCardId, long remoteAttachmentId, String contentType, Uri attachmentUri, IResponseCallback<Attachment> responseCallback) {
ensureInternetConnection();
- File attachment = new File(attachmentUri.getPath());
- MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", attachment.getName(), RequestBody.create(MediaType.parse(contentType), attachment));
- MultipartBody.Part typePart = MultipartBody.Part.createFormData("type", attachment.getName(), RequestBody.create(MediaType.parse(TEXT_PLAIN), "deck_file"));
+ final File attachment = new File(attachmentUri.getPath());
+ final Account account = responseCallback.getAccount();
+ final String type = (account != null && account.getServerDeckVersionAsObject().supportsFileAttachments())
+ ? EAttachmentType.FILE.getValue()
+ : EAttachmentType.DECK_FILE.getValue();
+ final MultipartBody.Part filePart = MultipartBody.Part.createFormData("file", attachment.getName(), RequestBody.create(MediaType.parse(contentType), attachment));
+ final MultipartBody.Part typePart = MultipartBody.Part.createFormData("type", attachment.getName(), RequestBody.create(MediaType.parse(TEXT_PLAIN), type));
RequestHelper.request(provider, () -> provider.getDeckAPI().updateAttachment(remoteBoardId, remoteStackId, remoteCardId, remoteAttachmentId, typePart, filePart), responseCallback);
}
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..e21d689fe 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
@@ -1,8 +1,11 @@
package it.niedermann.nextcloud.deck.persistence.sync.adapters.db;
+import android.appwidget.AppWidgetManager;
import android.content.Context;
+import android.content.Intent;
import androidx.annotation.AnyThread;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
@@ -11,7 +14,15 @@ import androidx.sqlite.db.SimpleSQLiteQuery;
import java.time.Instant;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+import java.util.stream.Collectors;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.model.AccessControl;
@@ -45,23 +56,36 @@ import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
import it.niedermann.nextcloud.deck.model.relations.UserInBoard;
import it.niedermann.nextcloud.deck.model.relations.UserInGroup;
+import it.niedermann.nextcloud.deck.model.widget.filter.EWidgetType;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidget;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetAccount;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetBoard;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetLabel;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetProject;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetSort;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetStack;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetUser;
+import it.niedermann.nextcloud.deck.model.widget.filter.dto.FilterWidgetCard;
import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.widget.singlecard.SingleCardWidget;
-import it.niedermann.nextcloud.deck.ui.widget.stack.StackWidget;
import static androidx.lifecycle.Transformations.distinctUntilChanged;
public class DataBaseAdapter {
- private DeckDatabase db;
+ private final DeckDatabase db;
@NonNull
- private Context context;
+ private final Context context;
public DataBaseAdapter(@NonNull Context applicationContext) {
+ this(applicationContext, DeckDatabase.getInstance(applicationContext));
+ }
+
+ private DataBaseAdapter(@NonNull Context applicationContext, @NonNull DeckDatabase db) {
this.context = applicationContext;
- this.db = DeckDatabase.getInstance(applicationContext);
+ this.db = db;
}
@NonNull
@@ -169,7 +193,7 @@ public class DataBaseAdapter {
}
}
- private void filterRelationsForCard(@Nullable List<FullCard> card) {
+ private void filterRelationsForCard(@Nullable Collection<FullCard> card) {
if (card == null) {
return;
}
@@ -191,39 +215,51 @@ public class DataBaseAdapter {
}
- private void fillSqlWithListValues(StringBuilder query, List<Object> args, @NonNull List<? extends IRemoteEntity> entities) {
- for (int i = 0; i < entities.size(); i++) {
+ private void fillSqlWithEntityListValues(StringBuilder query, List<Object> args, @NonNull List<? extends IRemoteEntity> entities) {
+ List<Long> idList = entities.stream().map(IRemoteEntity::getLocalId).collect(Collectors.toList());
+ fillSqlWithListValues(query, args, idList);
+ }
+
+ private void fillSqlWithListValues(StringBuilder query, List<Object> args, @NonNull List<?> values) {
+ for (int i = 0; i < values.size(); i++) {
if (i > 0) {
query.append(", ");
}
query.append("?");
- args.add(entities.get(i).getLocalId());
+ args.add(values.get(i));
}
}
@WorkerThread
- public List<FullCard> getFullCardsForStackDirectly(long accountId, long localStackId, FilterInformation filter) {
- if (filter == null) {
- return db.getCardDao().getFullCardsForStackDirectly(accountId, localStackId);
- }
- List<Object> args = new ArrayList<>();
- args.add(accountId);
- args.add(localStackId);
-
- return db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, accountId, localStackId));
+ public List<FullCard> getFullCardsForStackDirectly(long accountId, long localStackId, @Nullable FilterInformation filter) {
+ return filter == null
+ ? db.getCardDao().getFullCardsForStackDirectly(accountId, localStackId)
+ : db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, accountId, localStackId));
}
@AnyThread
private SimpleSQLiteQuery getQueryForFilter(FilterInformation filter, long accountId, long localStackId) {
+ return getQueryForFilter(filter, Collections.singletonList(accountId), Collections.singletonList(localStackId));
+ }
+
+ @AnyThread
+ private SimpleSQLiteQuery getQueryForFilter(FilterInformation filter, List<Long> accountIds, List<Long> localStackIds) {
List<Object> args = new ArrayList<>();
- args.add(accountId);
- args.add(localStackId);
- StringBuilder query = new StringBuilder("SELECT * FROM card c " +
- "WHERE accountId = ? AND stackId = ? ");
+ StringBuilder query = new StringBuilder("SELECT * FROM card c WHERE 1=1 ");
+ if (accountIds != null && !accountIds.isEmpty()) {
+ query.append("and accountId in (");
+ fillSqlWithListValues(query, args, accountIds);
+ query.append(") ");
+ }
+ if (localStackIds != null && !localStackIds.isEmpty()) {
+ query.append("and stackId in (");
+ fillSqlWithListValues(query, args, localStackIds);
+ query.append(") ");
+ }
if (!filter.getLabels().isEmpty()) {
query.append("and (exists(select 1 from joincardwithlabel j where c.localId = cardId and labelId in (");
- fillSqlWithListValues(query, args, filter.getLabels());
+ fillSqlWithEntityListValues(query, args, filter.getLabels());
query.append(") and j.status<>3) ");
if (filter.isNoAssignedLabel()) {
query.append("or not exists(select 1 from joincardwithlabel j where c.localId = cardId and j.status<>3)) ");
@@ -236,7 +272,7 @@ public class DataBaseAdapter {
if (!filter.getUsers().isEmpty()) {
query.append("and (exists(select 1 from joincardwithuser j where c.localId = cardId and userId in (");
- fillSqlWithListValues(query, args, filter.getUsers());
+ fillSqlWithEntityListValues(query, args, filter.getUsers());
query.append(") and j.status<>3) ");
if (filter.isNoAssignedUser()) {
query.append("or not exists(select 1 from joincardwithuser j where c.localId = cardId and j.status<>3)) ");
@@ -247,6 +283,19 @@ public class DataBaseAdapter {
query.append("and not exists(select 1 from joincardwithuser j where c.localId = cardId and j.status<>3) ");
}
+ if (!filter.getProjects().isEmpty()) {
+ query.append("and (exists(select 1 from joincardwithproject j where c.localId = cardId and projectId in (");
+ fillSqlWithEntityListValues(query, args, filter.getProjects());
+ query.append(") and j.status<>3) ");
+ if (filter.isNoAssignedProject()) {
+ query.append("or not exists(select 1 from joincardwithproject j where c.localId = cardId and j.status<>3)) ");
+ } else {
+ query.append(") ");
+ }
+ } else if (filter.isNoAssignedProject()) {
+ query.append("and not exists(select 1 from joincardwithproject j where c.localId = cardId and j.status<>3) ");
+ }
+
if (filter.getDueType() != EDueType.NO_FILTER) {
switch (filter.getDueType()) {
case NO_DUE:
@@ -265,13 +314,13 @@ public class DataBaseAdapter {
query.append("and datetime(c.duedate/1000, 'unixepoch', 'localtime') between datetime('now', 'localtime') and datetime('now', '+30 day', 'localtime')");
break;
default:
- throw new IllegalArgumentException("Xou need to add your new EDueType value\"" + filter.getDueType() + "\" here!");
+ throw new IllegalArgumentException("You need to add your new EDueType value\"" + filter.getDueType() + "\" here!");
}
}
if (filter.getArchiveStatus() != FilterInformation.EArchiveStatus.ALL) {
query.append(" and c.archived = " + (filter.getArchiveStatus() == FilterInformation.EArchiveStatus.ARCHIVED ? 1 : 0));
}
- query.append(" and status<>3 order by `order`, createdAt asc;");
+ query.append(" and status<>3 order by accountId asc, stackId asc, `order`, createdAt asc;");
return new SimpleSQLiteQuery(query.toString(), args.toArray());
}
@@ -283,7 +332,25 @@ public class DataBaseAdapter {
@WorkerThread
public long createUser(long accountId, User user) {
user.setAccountId(accountId);
- return db.getUserDao().insert(user);
+ long newId = db.getUserDao().insert(user);
+ new Thread(() -> {
+ Account account = db.getAccountDao().getAccountByIdDirectly(accountId);
+ if (account.getUserName().equals(user.getUid())) {
+ for (FilterWidget widget : getFilterWidgetsByType(EWidgetType.UPCOMING_WIDGET)) {
+ for (FilterWidgetAccount widgetAccount : widget.getAccounts()) {
+ if (widgetAccount.getAccountId() == accountId && widgetAccount.getUsers().isEmpty()) {
+ FilterWidgetUser u = new FilterWidgetUser();
+ u.setFilterAccountId(widgetAccount.getId());
+ u.setUserId(newId);
+ widgetAccount.getUsers().add(u);
+ updateFilterWidgetDirectly(widget);
+ }
+ }
+ }
+ }
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.USER, newId);
+ }).start();
+ return newId;
}
@WorkerThread
@@ -291,6 +358,7 @@ public class DataBaseAdapter {
markAsEditedIfNeeded(user, setStatus);
user.setAccountId(accountId);
db.getUserDao().update(user);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.USER, user.getLocalId());
}
@AnyThread
@@ -306,7 +374,9 @@ public class DataBaseAdapter {
@WorkerThread
public long createLabelDirectly(long accountId, @NonNull Label label) {
label.setAccountId(accountId);
- return db.getLabelDao().insert(label);
+ long newId = db.getLabelDao().insert(label);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.LABEL, newId);
+ return newId;
}
public void createJoinCardWithLabel(long localLabelId, long localCardId) {
@@ -319,15 +389,15 @@ public class DataBaseAdapter {
// readded!
existing.setStatusEnum(DBStatus.LOCAL_EDITED);
db.getJoinCardWithLabelDao().update(existing);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.LABEL, existing.getLabelId());
} else {
JoinCardWithLabel join = new JoinCardWithLabel();
join.setCardId(localCardId);
join.setLabelId(localLabelId);
join.setStatus(status.getId());
db.getJoinCardWithLabelDao().insert(join);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.LABEL, join.getLabelId());
}
-
-
}
public void deleteJoinedLabelsForCard(long localCardId) {
@@ -336,10 +406,12 @@ public class DataBaseAdapter {
public void deleteJoinedLabelForCard(long localCardId, long localLabelId) {
db.getJoinCardWithLabelDao().setDbStatus(localCardId, localLabelId, DBStatus.LOCAL_DELETED.getId());
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.LABEL, localLabelId);
}
public void deleteJoinedUserForCard(long localCardId, long localUserId) {
db.getJoinCardWithUserDao().setDbStatus(localCardId, localUserId, DBStatus.LOCAL_DELETED.getId());
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.USER, localUserId);
}
public void deleteJoinedLabelForCardPhysically(long localCardId, long localLabelId) {
@@ -360,6 +432,7 @@ public class DataBaseAdapter {
// readded!
existing.setStatusEnum(DBStatus.LOCAL_EDITED);
db.getJoinCardWithUserDao().update(existing);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.USER, localUserId);
} else if (existing != null) {
return;
} else {
@@ -368,6 +441,7 @@ public class DataBaseAdapter {
join.setUserId(localUserId);
join.setStatus(status.getId());
db.getJoinCardWithUserDao().insert(join);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.USER, localUserId);
}
}
@@ -411,11 +485,13 @@ public class DataBaseAdapter {
public void updateLabel(Label label, boolean setStatus) {
markAsEditedIfNeeded(label, setStatus);
db.getLabelDao().update(label);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.LABEL, label.getLocalId());
}
public void deleteLabel(Label label, boolean setStatus) {
markAsDeletedIfNeeded(label, setStatus);
db.getLabelDao().update(label);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.LABEL, label.getLocalId());
}
public void deleteLabelPhysically(Label label) {
@@ -425,12 +501,23 @@ public class DataBaseAdapter {
public WrappedLiveData<Account> createAccount(Account account) {
return LiveDataHelper.wrapInLiveData(() -> {
long id = db.getAccountDao().insert(account);
+
+ new Thread(() -> {
+ DeckLog.verbose("Adding new created", Account.class.getSimpleName(), " with ", id, " to all instances of ", EWidgetType.UPCOMING_WIDGET.name());
+ for (FilterWidget widget : getFilterWidgetsByType(EWidgetType.UPCOMING_WIDGET)) {
+ widget.getAccounts().add(new FilterWidgetAccount(id, false));
+ updateFilterWidgetDirectly(widget);
+ }
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.ACCOUNT, id);
+ }).start();
return readAccountDirectly(id);
});
}
public void deleteAccount(long id) {
db.getAccountDao().deleteById(id);
+ notifyAllWidgets();
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.ACCOUNT, id);
}
public void updateAccount(Account account) {
@@ -474,7 +561,8 @@ public class DataBaseAdapter {
return LiveDataHelper.wrapInLiveData(() -> {
board.setAccountId(accountId);
long id = db.getBoardDao().insert(board);
- return db.getBoardDao().getBoardByIdDirectly(id);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.BOARD, id);
+ return db.getBoardDao().getBoardByLocalIdDirectly(id);
});
}
@@ -482,21 +570,27 @@ public class DataBaseAdapter {
@WorkerThread
public long createBoardDirectly(long accountId, @NonNull Board board) {
board.setAccountId(accountId);
- return db.getBoardDao().insert(board);
+ long id = db.getBoardDao().insert(board);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.BOARD, id);
+ return id;
}
public void deleteBoard(Board board, boolean setStatus) {
markAsDeletedIfNeeded(board, setStatus);
db.getBoardDao().update(board);
+ notifyAllWidgets();
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.BOARD, board.getLocalId());
}
public void deleteBoardPhysically(Board board) {
db.getBoardDao().delete(board);
+ notifyAllWidgets();
}
public void updateBoard(Board board, boolean setStatus) {
markAsEditedIfNeeded(board, setStatus);
db.getBoardDao().update(board);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.BOARD, board.getLocalId());
}
public LiveData<List<Stack>> getStacksForBoard(long accountId, long localBoardId) {
@@ -516,28 +610,35 @@ public class DataBaseAdapter {
@WorkerThread
public long createStack(long accountId, Stack stack) {
stack.setAccountId(accountId);
- return db.getStackDao().insert(stack);
+ long id = db.getStackDao().insert(stack);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.STACK, id);
+ return id;
}
@WorkerThread
public void deleteStack(Stack stack, boolean setStatus) {
markAsDeletedIfNeeded(stack, setStatus);
db.getStackDao().update(stack);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.STACK, stack.getLocalId());
+ notifyAllWidgets();
}
@WorkerThread
public void deleteStackPhysically(Stack stack) {
db.getStackDao().delete(stack);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.STACK, stack.getLocalId());
+ notifyAllWidgets();
}
@WorkerThread
public void updateStack(Stack stack, boolean setStatus) {
markAsEditedIfNeeded(stack, setStatus);
db.getStackDao().update(stack);
- if (db.getStackWidgetModelDao().containsStackLocalId(stack.getLocalId())) {
- DeckLog.info("Notifying " + StackWidget.class.getSimpleName() + " about card changes for \"" + stack.getTitle() + "\"");
- StackWidget.notifyDatasetChanged(context);
- }
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.STACK, stack.getLocalId());
+// if (db.getStackWidgetModelDao().containsStackLocalId(stack.getLocalId())) {
+// DeckLog.info("Notifying " + StackWidget.class.getSimpleName() + " about card changes for \"" + stack.getTitle() + "\"");
+// // FIXME StackWidget.notifyDatasetChanged(context);
+// }
}
@WorkerThread
@@ -571,6 +672,7 @@ public class DataBaseAdapter {
long newCardId = db.getCardDao().insert(card);
notifyStackWidgetsIfNeeded(card.getTitle(), card.getStackId());
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.STACK, card.getStackId());
return newCardId;
}
@@ -595,6 +697,7 @@ public class DataBaseAdapter {
}
notifyStackWidgetsIfNeeded(card.getTitle(), card.getStackId());
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.STACK, card.getStackId());
}
@WorkerThread
@@ -608,22 +711,24 @@ public class DataBaseAdapter {
Long originalStackLocalId = db.getCardDao().getLocalStackIdByLocalCardId(card.getLocalId());
db.getCardDao().update(card);
if (db.getSingleCardWidgetModelDao().containsCardLocalId(card.getLocalId())) {
- DeckLog.info("Notifying " + SingleCardWidget.class.getSimpleName() + " about card changes for \"" + card.getTitle() + "\"");
+ DeckLog.info("Notifying", SingleCardWidget.class.getSimpleName(), "about card changes for", card.getTitle());
SingleCardWidget.notifyDatasetChanged(context);
}
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.STACK, originalStackLocalId);
notifyStackWidgetsIfNeeded(card.getTitle(), card.getStackId(), originalStackLocalId);
}
private void notifyStackWidgetsIfNeeded(String cardTitle, long... affectedStackIds) {
- if (db.getStackWidgetModelDao().containsStackLocalId(affectedStackIds)) {
- DeckLog.info("Notifying " + StackWidget.class.getSimpleName() + " about card changes for \"" + cardTitle + "\"");
- StackWidget.notifyDatasetChanged(context);
- }
+// if (db.getStackWidgetModelDao().containsStackLocalId(affectedStackIds)) {
+// DeckLog.info("Notifying " + StackWidget.class.getSimpleName() + " about card changes for \"" + cardTitle + "\"");
+// // FIXME StackWidget.notifyDatasetChanged(context);
+// }
}
@WorkerThread
public long createAccessControl(long accountId, @NonNull AccessControl entity) {
entity.setAccountId(accountId);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.ACCOUNT, accountId);
return db.getAccessControlDao().insert(entity);
}
@@ -642,6 +747,7 @@ public class DataBaseAdapter {
public void updateAccessControl(AccessControl entity, boolean setStatus) {
markAsEditedIfNeeded(entity, setStatus);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.ACCOUNT, entity.getAccountId());
db.getAccessControlDao().update(entity);
}
@@ -652,6 +758,7 @@ public class DataBaseAdapter {
} else {
db.getAccessControlDao().delete(entity);
}
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.ACCOUNT, entity.getAccountId());
}
public LiveData<FullBoard> getFullBoardById(Long accountId, Long localId) {
@@ -660,7 +767,7 @@ public class DataBaseAdapter {
@WorkerThread
public Board getBoardByLocalIdDirectly(long localId) {
- return db.getBoardDao().getBoardByIdDirectly(localId);
+ return db.getBoardDao().getBoardByLocalIdDirectly(localId);
}
public LiveData<User> getUserByLocalId(long accountId, long localId) {
@@ -818,6 +925,11 @@ public class DataBaseAdapter {
}
@WorkerThread
+ public Long getBoardLocalIdByLocalCardIdDirectly(long localCardId) {
+ return db.getBoardDao().getBoardLocalIdByLocalCardIdDirectly(localCardId);
+ }
+
+ @WorkerThread
public FullBoard getFullBoardByLocalCardIdDirectly(long localCardId) {
return db.getBoardDao().getFullBoardByLocalCardIdDirectly(localCardId);
}
@@ -1038,24 +1150,224 @@ public class DataBaseAdapter {
db.getSingleCardWidgetModelDao().delete(model);
}
- public long createStackWidget(int appWidgetId, long accountId, long stackId, boolean darkTheme) {
+ public void createStackWidget(int appWidgetId, long accountId, long stackId, boolean darkTheme) {
StackWidgetModel model = new StackWidgetModel();
model.setAppWidgetId(appWidgetId);
model.setAccountId(accountId);
model.setStackId(stackId);
model.setDarkTheme(darkTheme);
- return db.getStackWidgetModelDao().insert(model);
+// db.getStackWidgetModelDao().insert(model);
}
public StackWidgetModel getStackWidgetModelDirectly(int appWidgetId) {
- return db.getStackWidgetModelDao().getStackWidgetByAppWidgetIdDirectly(appWidgetId);
+// return db.getStackWidgetModelDao().getStackWidgetByAppWidgetIdDirectly(appWidgetId);
+ return null;
+ }
+
+ public int createFilterWidgetDirectly(@NonNull FilterWidget filterWidget) {
+ db.getFilterWidgetDao().insert(filterWidget);
+ insertFilterWidgetDecendants(filterWidget);
+ return filterWidget.getId();
+ }
+
+ private void insertFilterWidgetDecendants(FilterWidget filterWidget) {
+ long widgetId = filterWidget.getId();
+ for (FilterWidgetAccount account : filterWidget.getAccounts()) {
+ account.setFilterWidgetId(widgetId);
+ long accountId = db.getFilterWidgetAccountDao().insert(account);
+ for (FilterWidgetUser user : account.getUsers()) {
+ user.setFilterAccountId(accountId);
+ db.getFilterWidgetUserDao().insert(user);
+ }
+ for (FilterWidgetProject project : account.getProjects()) {
+ project.setFilterAccountId(accountId);
+ db.getFilterWidgetProjectDao().insert(project);
+ }
+ for (FilterWidgetBoard board : account.getBoards()) {
+ board.setFilterAccountId(accountId);
+ long boardId = db.getFilterWidgetBoardDao().insert(board);
+ for (FilterWidgetStack stack : board.getStacks()) {
+ stack.setFilterBoardId(boardId);
+ db.getFilterWidgetStackDao().insert(stack);
+ }
+ for (FilterWidgetLabel label : board.getLabels()) {
+ label.setFilterBoardId(boardId);
+ db.getFilterWidgetLabelDao().insert(label);
+ }
+ }
+ }
+ for (FilterWidgetSort sort : filterWidget.getSorts()) {
+ sort.setFilterWidgetId(widgetId);
+ db.getFilterWidgetSortDao().insert(sort);
+ }
+ }
+
+ public void deleteFilterWidgetDirectly(Integer filterWidgetId) {
+ db.getFilterWidgetDao().delete(filterWidgetId);
+ }
+
+ public void updateFilterWidgetDirectly(FilterWidget filterWidget) {
+ db.getFilterWidgetSortDao().deleteByFilterWidgetId(filterWidget.getId());
+ db.getFilterWidgetAccountDao().deleteByFilterWidgetId(filterWidget.getId());
+ db.getFilterWidgetDao().update(filterWidget);
+ insertFilterWidgetDecendants(filterWidget);
+ }
+
+ public FilterWidget getFilterWidgetByIdDirectly(Integer filterWidgetId) {
+ FilterWidget filterWidget = db.getFilterWidgetDao().getFilterWidgetByIdDirectly(filterWidgetId);
+ if (filterWidget == null) {
+ throw new NoSuchElementException("No widget with id " + filterWidgetId + " configured.");
+ }
+ filterWidget.setSorts(db.getFilterWidgetSortDao().getFilterWidgetSortByFilterWidgetIdDirectly(filterWidgetId));
+ filterWidget.setAccounts(db.getFilterWidgetAccountDao().getFilterWidgetAccountsByFilterWidgetIdDirectly(filterWidgetId));
+ for (FilterWidgetAccount account : filterWidget.getAccounts()) {
+ account.setBoards(db.getFilterWidgetBoardDao().getFilterWidgetBoardsByFilterWidgetAccountIdDirectly(account.getId()));
+ account.setUsers(db.getFilterWidgetUserDao().getFilterWidgetUsersByFilterWidgetAccountIdDirectly(account.getId()));
+ account.setProjects(db.getFilterWidgetProjectDao().getFilterWidgetProjectsByFilterWidgetAccountIdDirectly(account.getId()));
+ for (FilterWidgetBoard board : account.getBoards()) {
+ board.setLabels(db.getFilterWidgetLabelDao().getFilterWidgetLabelsByFilterWidgetBoardIdDirectly(board.getId()));
+ board.setStacks(db.getFilterWidgetStackDao().getFilterWidgetStacksByFilterWidgetBoardIdDirectly(board.getId()));
+ }
+ }
+
+ return filterWidget;
+ }
+
+ public void notifyFilterWidgetsAboutChangedEntity(@NonNull FilterWidget.EChangedEntityType type, Long entityId) {
+ new Thread(() -> {
+ final List<EWidgetType> widgetTypesToNotify = db.getFilterWidgetDao().getChangedListTypesByEntity(type.toString(), entityId);
+ for (EWidgetType t : widgetTypesToNotify) {
+ DeckLog.info("Notifying", t.getWidgetClass().getSimpleName(), "about entity change:", type.name(), "with ID", entityId);
+ context.sendBroadcast(new Intent(context, t.getWidgetClass()).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
+ }
+ }).start();
+ }
+
+ public List<FilterWidgetCard> getCardsForFilterWidget(Integer filterWidgetId) {
+ FilterWidget filterWidget = getFilterWidgetByIdDirectly(filterWidgetId);
+ FilterInformation filter = new FilterInformation();
+ Set<FullCard> cardsResult = new HashSet<>();
+ if (filterWidget.getDueType() != null) {
+ filter.setDueType(filterWidget.getDueType());
+ } else filter.setDueType(EDueType.NO_FILTER);
+
+ if (filterWidget.getAccounts().isEmpty()) {
+ cardsResult.addAll(db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, null, null)));
+ } else {
+ for (FilterWidgetAccount account : filterWidget.getAccounts()) {
+ filter.setNoAssignedUser(account.isIncludeNoUser());
+ List<User> users = new ArrayList<>();
+ if (!account.getUsers().isEmpty()) {
+ for (FilterWidgetUser user : account.getUsers()) {
+ User u = new User();
+ u.setLocalId(user.getUserId());
+ users.add(u);
+ }
+ }
+ filter.setUsers(users);
+ filter.setNoAssignedProject(account.isIncludeNoProject());
+ List<OcsProject> projects = new ArrayList<>();
+ if (!account.getProjects().isEmpty()) {
+ for (FilterWidgetProject project : account.getProjects()) {
+ OcsProject u = new OcsProject();
+ u.setLocalId(project.getProjectId());
+ projects.add(u);
+ }
+ }
+ filter.setProjects(projects);
+ if (!account.getBoards().isEmpty()) {
+ for (FilterWidgetBoard board : account.getBoards()) {
+ filter.setNoAssignedLabel(board.isIncludeNoLabel());
+ List<Long> stacks;
+ for (FilterWidgetLabel label : board.getLabels()) {
+ Label l = new Label();
+ l.setLocalId(label.getLabelId());
+ filter.addLabel(l);
+ }
+ if (board.getStacks().isEmpty()) {
+ stacks = db.getStackDao().getLocalStackIdsByLocalBoardIdDirectly(board.getBoardId());
+ } else {
+ stacks = new ArrayList<>();
+ for (FilterWidgetStack stack : board.getStacks()) {
+ stacks.add(stack.getStackId());
+ }
+ }
+ cardsResult.addAll(db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, Collections.singletonList(account.getAccountId()), stacks)));
+ }
+ } else {
+ cardsResult.addAll(db.getCardDao().getFilteredFullCardsForStackDirectly(getQueryForFilter(filter, Collections.singletonList(account.getAccountId()), null)));
+ }
+ }
+ }
+
+ handleWidgetTypeExtras(filterWidget, cardsResult);
+
+ filterRelationsForCard(cardsResult);
+
+ List<FilterWidgetCard> result = new ArrayList<>(cardsResult.size());
+ Map<Long, Board> boardCache = new HashMap<>();
+ Map<Long, Stack> stackCache = new HashMap<>();
+ for (FullCard fullCard : cardsResult) {
+ Long stackId = fullCard.getCard().getStackId();
+ Stack stack = stackCache.get(stackId);
+ if (stack == null) {
+ stack = db.getStackDao().getStackByLocalIdDirectly(stackId);
+ stackCache.put(stackId, stack);
+ }
+
+ Board board = boardCache.get(stack.getBoardId());
+ if (board == null) {
+ board = db.getBoardDao().getBoardByLocalIdDirectly(stackId);
+ boardCache.put(stackId, board);
+ }
+ result.add(new FilterWidgetCard(fullCard, stack, board));
+ }
+ return result;
+ }
+
+ private void handleWidgetTypeExtras(FilterWidget filterWidget, Collection<FullCard> cardsResult) {
+ if (filterWidget.getWidgetType() == EWidgetType.UPCOMING_WIDGET) {
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/819 "no due" cards are only shown if they are on a shared board
+ for (FullCard fullCard : new ArrayList<>(cardsResult)) {
+ if (fullCard.getCard().getDueDate() == null && !db.getStackDao().isStackOnSharedBoardDirectly(fullCard.getCard().getStackId())) {
+ cardsResult.remove(fullCard);
+ }
+ }
+ List<Long> accountIds = null;
+ if (!filterWidget.getAccounts().isEmpty()) {
+ accountIds = filterWidget.getAccounts().stream().map(FilterWidgetAccount::getAccountId).collect(Collectors.toList());
+ }
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/822 exclude archived cards and boards
+ List<Long> archivedStacks = db.getStackDao().getLocalStackIdsInArchivedBoardsByAccountIdsDirectly(accountIds);
+ for (Long archivedStack : archivedStacks) {
+ List<FullCard> archivedCards = cardsResult.stream()
+ .filter(c -> c.getCard().isArchived() || archivedStack.equals(c.getCard().getStackId()))
+ .collect(Collectors.toList());
+ cardsResult.removeAll(archivedCards);
+ }
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/800 all cards within non-shared boards need to be included
+ cardsResult.addAll(db.getCardDao().getFullCardsForNonSharedBoardsWithDueDateForUpcomingCardsWidgetDirectly(accountIds));
+ }
+ }
+
+ public List<FilterWidget> getFilterWidgetsByType(EWidgetType type) {
+ List<Integer> ids = db.getFilterWidgetDao().getFilterWidgetIdsByType(type.getId());
+ List<FilterWidget> widgets = new ArrayList<>(ids.size());
+ for (Integer id : ids) {
+ widgets.add(getFilterWidgetByIdDirectly(id));
+ }
+ return widgets;
+ }
+
+ public boolean filterWidgetExists(int id) {
+ return db.getFilterWidgetDao().filterWidgetExists(id);
}
public void deleteStackWidget(int appWidgetId) {
StackWidgetModel model = new StackWidgetModel();
model.setAppWidgetId(appWidgetId);
- db.getStackWidgetModelDao().delete(model);
+// db.getStackWidgetModelDao().delete(model);
}
public LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String host, long boardRemoteId) {
@@ -1119,8 +1431,20 @@ public class DataBaseAdapter {
assignment.setStatus(DBStatus.UP_TO_DATE.getId());
assignment.setCardId(card.getLocalId());
assignment.setProjectId(localProjectId);
- db.getJoinCardWithOcsProjectDao().insert(assignment);
+ long id = db.getJoinCardWithOcsProjectDao().insert(assignment);
+ notifyFilterWidgetsAboutChangedEntity(FilterWidget.EChangedEntityType.PROJECT, id);
}
}
}
+
+ private void notifyAllWidgets() {
+ SingleCardWidget.notifyDatasetChanged(context);
+ /// FIXME StackWidget.notifyDatasetChanged(context);
+// UpcomingWidget.notifyDatasetChanged(context);
+ }
+
+ @ColorInt
+ public Integer getBoardColorDirectly(long accountId, long localBoardId) {
+ return db.getBoardDao().getBoardColorByLocalIdDirectly(accountId, localBoardId);
+ }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
index a491946ed..6f7ba45e0 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java
@@ -1,21 +1,14 @@
package it.niedermann.nextcloud.deck.persistence.sync.adapters.db;
import android.content.Context;
-import android.content.SharedPreferences;
-import android.database.Cursor;
-import android.graphics.Color;
-import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
-import androidx.preference.PreferenceManager;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.TypeConverters;
-import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
-import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.LastSyncUtil;
import it.niedermann.nextcloud.deck.model.AccessControl;
@@ -32,8 +25,6 @@ import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.Permission;
import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.User;
-import it.niedermann.nextcloud.deck.model.appwidgets.StackWidgetModel;
-import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.ocs.Activity;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
@@ -42,8 +33,17 @@ import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
import it.niedermann.nextcloud.deck.model.relations.UserInBoard;
import it.niedermann.nextcloud.deck.model.relations.UserInGroup;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidget;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetAccount;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetBoard;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetLabel;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetProject;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetSort;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetStack;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetUser;
import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncWorker;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.converter.DateTypeConverter;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.converter.EnumConverter;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.AccessControlDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.AccountDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.ActivityDao;
@@ -67,7 +67,36 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects.Jo
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects.OcsProjectDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects.OcsProjectResourceDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.SingleCardWidgetModelDao;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.StackWidgetModelDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter.FilterWidgetAccountDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter.FilterWidgetBoardDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter.FilterWidgetDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter.FilterWidgetLabelDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter.FilterWidgetProjectDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter.FilterWidgetSortDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter.FilterWidgetStackDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter.FilterWidgetUserDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_10_11;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_11_12;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_12_13;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_13_14;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_14_15;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_15_16;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_16_17;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_17_18;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_18_19;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_19_20;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_20_21;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_21_22;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_22_23;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_23_24;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_24_25;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_25_26;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_26_27;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_27_28;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_28_29;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_29_30;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_8_9;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration.Migration_9_10;
@Database(
entities = {
@@ -89,316 +118,34 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.Sta
DeckComment.class,
Mention.class,
SingleCardWidgetModel.class,
- StackWidgetModel.class,
OcsProject.class,
OcsProjectResource.class,
JoinCardWithProject.class,
UserInGroup.class,
UserInBoard.class,
+ FilterWidget.class,
+ FilterWidgetAccount.class,
+ FilterWidgetBoard.class,
+ FilterWidgetStack.class,
+ FilterWidgetLabel.class,
+ FilterWidgetUser.class,
+ FilterWidgetProject.class,
+ FilterWidgetSort.class,
},
exportSchema = false,
- version = 23
+ version = 30
)
-@TypeConverters({DateTypeConverter.class})
+@TypeConverters({DateTypeConverter.class, EnumConverter.class})
public abstract class DeckDatabase extends RoomDatabase {
-
private static final String DECK_DB_NAME = "NC_DECK_DB.db";
private static volatile DeckDatabase instance;
- private static final Migration MIGRATION_8_9 = new Migration(8, 9) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE `DeckComment` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `objectId` INTEGER, `actorType` TEXT, `creationDateTime` INTEGER, `actorId` TEXT, `actorDisplayName` TEXT, `message` TEXT, FOREIGN KEY(`objectId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
- database.execSQL("CREATE TABLE `Mention` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `commentId` INTEGER, `mentionId` TEXT, `mentionType` TEXT, `mentionDisplayName` TEXT, FOREIGN KEY(`commentId`) REFERENCES `DeckComment`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
- database.execSQL("CREATE INDEX `index_DeckComment_accountId` ON `DeckComment` (`accountId`)");
- database.execSQL("CREATE INDEX `comment_accID` ON `DeckComment` (`accountId`)");
- database.execSQL("CREATE UNIQUE INDEX `index_DeckComment_accountId_id` ON `DeckComment` (`accountId`, `id`)");
- database.execSQL("CREATE INDEX `index_DeckComment_id` ON `DeckComment` (`id`)");
- database.execSQL("CREATE INDEX `index_DeckComment_lastModifiedLocal` ON `DeckComment` (`lastModifiedLocal`)");
- database.execSQL("CREATE INDEX `index_DeckComment_objectId` ON `DeckComment` (`objectId`)");
- database.execSQL("CREATE INDEX `index_Mention_commentId` ON `Mention` (`commentId`)");
- }
- };
-
- private static final Migration MIGRATION_9_10 = new Migration(9, 10) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `Account` ADD `color` TEXT NOT NULL DEFAULT '#0082c9'");
- database.execSQL("ALTER TABLE `Account` ADD `textColor` TEXT NOT NULL DEFAULT '#ffffff'");
- database.execSQL("ALTER TABLE `Account` ADD `serverDeckVersion` TEXT NOT NULL DEFAULT '0.6.4'");
- database.execSQL("ALTER TABLE `Account` ADD `maintenanceEnabled` INTEGER NOT NULL DEFAULT 0");
- }
- };
-
- private static final Migration MIGRATION_10_11 = new Migration(10, 11) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- // replace duplicates with the server-known ones
- Cursor duplucatesCursor = database.query("SELECT boardId, title, count(*) FROM Label group by boardid, title having count(*) > 1");
- if (duplucatesCursor != null && duplucatesCursor.moveToFirst()) {
- do {
- long boardId = duplucatesCursor.getLong(0);
- String title = duplucatesCursor.getString(1);
- Cursor singleDuplicateCursor = database.query("select localId from Label where boardId = ? and title = ? order by id desc", new Object[]{boardId, title});
- if (singleDuplicateCursor != null && singleDuplicateCursor.moveToFirst()) {
- long idToUse = -1;
- do {
- if (idToUse < 0) {
- // desc order -> first one is the one with remote ID or a random one. keep this one.
- idToUse = singleDuplicateCursor.getLong(0);
- continue;
- }
- long idToReplace = singleDuplicateCursor.getLong(0);
- Cursor cardsAssignedToDuplicateCursor = database.query("select cardId, exists(select 1 from JoinCardWithLabel ij where ij.labelId = ? and ij.cardId = cardId) " +
- "from JoinCardWithLabel where labelId = ?", new Object[]{idToUse, idToReplace});
- if (cardsAssignedToDuplicateCursor != null && cardsAssignedToDuplicateCursor.moveToFirst()) {
- do {
- long cardId = cardsAssignedToDuplicateCursor.getLong(0);
- boolean hasDestinationLabelAssigned = cardsAssignedToDuplicateCursor.getInt(1) > 0;
- database.execSQL("DELETE FROM JoinCardWithLabel where labelId = ? and cardId = ?", new Object[]{idToReplace, cardId});
-
- if (!hasDestinationLabelAssigned) {
- database.execSQL("INSERT INTO JoinCardWithLabel (status,labelId,cardId) VALUES (?, ?, ?)", new Object[]{DBStatus.LOCAL_EDITED.getId(), idToUse, cardId});
- }
- } while (cardsAssignedToDuplicateCursor.moveToNext());
- }
- database.execSQL("DELETE FROM Label where localId = ?", new Object[]{idToReplace});
- } while (singleDuplicateCursor.moveToNext());
- }
- } while (duplucatesCursor.moveToNext());
- }
-// database.execSQL("DELETE FROM Label WHERE id IS NULL AND EXISTS(SELECT 1 FROM Label il WHERE il.boardId = boardId AND il.title = title AND id IS NOT NULL)");
- database.execSQL("CREATE UNIQUE INDEX idx_label_title_unique ON Label(boardId, title)");
- }
- };
-
- private static final Migration MIGRATION_11_12 = new Migration(11, 12) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE `SingleCardWidgetModel` (`widgetId` INTEGER PRIMARY KEY, `accountId` INTEGER, `boardId` INTEGER, `cardId` INTEGER, FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
- database.execSQL("CREATE INDEX `index_SingleCardWidgetModel_cardId` ON `SingleCardWidgetModel` (`cardId`)");
- }
- };
-
- private static final Migration MIGRATION_12_13 = new Migration(12, 13) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- database.execSQL("CREATE INDEX `idx_cardWidgetModel_accountId` ON `SingleCardWidgetModel` (`accountId`)");
- database.execSQL("CREATE INDEX `idx_cardWidgetModel_boardId` ON `SingleCardWidgetModel` (`boardId`)");
- }
- };
-
- private static final Migration MIGRATION_13_14 = new Migration(13, 14) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- database.execSQL("ALTER TABLE `DeckComment` ADD `parentId` INTEGER REFERENCES DeckComment(localId) ON DELETE CASCADE");
- database.execSQL("CREATE INDEX `idx_comment_parentID` ON DeckComment(parentId)");
- }
- };
-
- private static final Migration MIGRATION_15_16 = new Migration(15, 16) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE `StackWidgetModel` (`appWidgetId` INTEGER PRIMARY KEY, `accountId` INTEGER, `stackId` INTEGER, `darkTheme` INTEGER CHECK (`darkTheme` IN (0,1)) NOT NULL, " +
- "FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
- "FOREIGN KEY(`stackId`) REFERENCES `Stack`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
- database.execSQL("CREATE INDEX `index_StackWidgetModel_stackId` ON `StackWidgetModel` (`stackId`)");
- database.execSQL("CREATE INDEX `index_StackWidgetModel_accountId` ON `StackWidgetModel` (`accountId`)");
- }
- };
-
- private static final Migration MIGRATION_16_17 = new Migration(16, 17) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- database.execSQL("CREATE TABLE `OcsProject` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `name` TEXT NOT NULL, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER)");
- database.execSQL("CREATE UNIQUE INDEX `index_OcsProject_accountId_id` ON `OcsProject` (`accountId`, `id`)");
- database.execSQL("CREATE INDEX `index_project_accID` ON `OcsProject` (`accountId`)");
- database.execSQL("CREATE INDEX `index_OcsProject_id` ON `OcsProject` (`id`)");
- database.execSQL("CREATE INDEX `index_OcsProject_lastModifiedLocal` ON `OcsProject` (`lastModifiedLocal`)");
-
- database.execSQL("CREATE TABLE `OcsProjectResource` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `name` TEXT, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `projectId` INTEGER NOT NULL, `type` TEXT , `link` TEXT , `path` TEXT, `iconUrl` TEXT , `previewAvailable` INTEGER, `mimetype` TEXT, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
- database.execSQL("CREATE INDEX `index_projectResource_accID` ON `OcsProjectResource` (`accountId`)");
- database.execSQL("CREATE INDEX `index_projectResource_projectId` ON `OcsProjectResource` (`projectId`)");
- database.execSQL("CREATE UNIQUE INDEX `index_OcsProjectResource_accountId_id` ON `OcsProjectResource` (`accountId`, `id`, `projectId`)");
- database.execSQL("CREATE INDEX `index_OcsProjectResource_id` ON `OcsProjectResource` (`id`)");
- database.execSQL("CREATE INDEX `index_OcsProjectResource_lastModifiedLocal` ON `OcsProjectResource` (`lastModifiedLocal`)");
-
- database.execSQL("CREATE TABLE `JoinCardWithProject` (`status` INTEGER NOT NULL, `projectId` INTEGER NOT NULL, `cardId` INTEGER NOT NULL, PRIMARY KEY (`projectId`, `cardId`), FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
- database.execSQL("CREATE INDEX `index_JoinCardWithProject_projectId` ON `JoinCardWithProject` (`projectId`)");
- database.execSQL("CREATE INDEX `index_JoinCardWithProject_cardId` ON `JoinCardWithProject` (`cardId`)");
- }
- };
-
- private static final Migration MIGRATION_17_18 = new Migration(17, 18) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/435
- database.execSQL("ALTER TABLE `Account` ADD `etag` TEXT");
- }
- };
- private static final Migration MIGRATION_18_19 = new Migration(18, 19) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/619
- database.execSQL("DROP INDEX `index_OcsProjectResource_accountId_id`");
- database.execSQL("ALTER TABLE `OcsProjectResource` ADD `idString` TEXT");
- database.execSQL("CREATE UNIQUE INDEX `index_OcsProjectResource_accountId_id` ON `OcsProjectResource` (`accountId`, `id`, `idString`, `projectId`)");
- }
- };
- private static final Migration MIGRATION_19_20 = new Migration(19, 20) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/492
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/631
- database.execSQL("CREATE TABLE `UserInGroup` (`groupId` INTEGER NOT NULL, `memberId` INTEGER NOT NULL, " +
- "primary KEY(`groupId`, `memberId`), " +
- "FOREIGN KEY(`groupId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
- "FOREIGN KEY(`memberId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
- database.execSQL("CREATE UNIQUE INDEX `unique_idx_group_member` ON `UserInGroup` (`groupId`, `memberId`)");
- database.execSQL("CREATE INDEX `index_UserInGroup_groupId` ON `UserInGroup` (`groupId`)");
- database.execSQL("CREATE INDEX `index_UserInGroup_memberId` ON `UserInGroup` (`memberId`)");
-
- database.execSQL("CREATE TABLE `UserInBoard` (`userId` INTEGER NOT NULL, `boardId` INTEGER NOT NULL, " +
- "primary KEY(`userId`, `boardId`), " +
- "FOREIGN KEY(`userId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
- "FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
- database.execSQL("CREATE UNIQUE INDEX `unique_idx_user_board` ON `UserInBoard` (`userId`, `boardId`)");
- database.execSQL("CREATE INDEX `index_UserInBoard_userId` ON `UserInBoard` (`userId`)");
- database.execSQL("CREATE INDEX `index_UserInBoard_boardId` ON `UserInBoard` (`boardId`)");
- }
- };
-
- private static final Migration MIGRATION_20_21 = new Migration(20, 21) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/556
- String suffix = "_new";
- {
- String tableName = "Account";
- database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `userName` TEXT NOT NULL, `url` TEXT NOT NULL, " +
- "`color` INTEGER NOT NULL DEFAULT 0, `textColor` INTEGER NOT NULL DEFAULT 0, `serverDeckVersion` TEXT NOT NULL DEFAULT '0.6.4', `maintenanceEnabled` INTEGER NOT NULL DEFAULT 0, `etag` TEXT)");
- Cursor cursor = database.query("select * from `" + tableName + "`");
- while (cursor.moveToNext()) {
- String colorAsString1 = cursor.getString(4); // color
- String colorAsString2 = cursor.getString(5); // textColor
-
- @ColorInt Integer color1 = null;
- @ColorInt Integer color2 = null;
- try {
- color1 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString1));
- color2 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString2));
- } catch (Exception e) {
- color1 = Color.GRAY;
- color2 = Color.GRAY;
- }
- database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
- cursor.getLong(0), cursor.getString(1), cursor.getString(2), cursor.getString(3),
- color1, color2, cursor.getString(6), cursor.getInt(7), cursor.getString(8)});
-
- }
-
-
- database.execSQL("DROP TABLE `" + tableName + "`");
- database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
- database.execSQL("CREATE UNIQUE INDEX `index_Account_name` ON `" + tableName + "` (`name`)");
- database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
- }
- {
- String tableName = "Board";
- database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, " +
- "`lastModified` INTEGER, `lastModifiedLocal` INTEGER, `title` TEXT, `ownerId` INTEGER NOT NULL, `color` INTEGER, " +
- "`archived` INTEGER NOT NULL, `shared` INTEGER NOT NULL, `deletedAt` INTEGER, `permissionRead` INTEGER NOT NULL, " +
- "`permissionEdit` INTEGER NOT NULL, `permissionManage` INTEGER NOT NULL, `permissionShare` INTEGER NOT NULL, " +
- "FOREIGN KEY(`ownerId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE SET NULL )");
- Cursor cursor = database.query("select * from `" + tableName + "`");
- while (cursor.moveToNext()) {
- String colorAsString1 = cursor.getString(8); // color
-
- @ColorInt Integer color1 = null;
- try {
- color1 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString1));
- } catch (Exception e) {
- color1 = Color.GRAY;
- }
- database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
- cursor.getLong(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3),
- cursor.getLong(4), cursor.getLong(5), cursor.getString(6), cursor.getLong(7), color1,
- cursor.getInt(9), cursor.getInt(10), cursor.getInt(11), cursor.getInt(12),
- cursor.getInt(13), cursor.getInt(14), cursor.getInt(15)
- });
-
- }
-
-
- database.execSQL("DROP TABLE `" + tableName + "`");
- database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
- database.execSQL("CREATE INDEX `index_Board_accountId` ON `" + tableName + "` (`accountId`)");
- database.execSQL("CREATE UNIQUE INDEX `index_Board_accountId_id` ON `" + tableName + "` (`accountId`, `id`)");
- database.execSQL("CREATE INDEX `index_Board_id` ON `" + tableName + "` (`id`)");
- database.execSQL("CREATE INDEX `index_Board_ownerId` ON `" + tableName + "` (`ownerId`)");
- database.execSQL("CREATE INDEX `index_Board_lastModifiedLocal` ON `" + tableName + "` (`lastModifiedLocal`)");
- database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
- }
- {
- String tableName = "Label";
- database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, " +
- "`lastModified` INTEGER, `lastModifiedLocal` INTEGER, `title` TEXT, `color` INTEGER NOT NULL DEFAULT 0, `boardId` INTEGER NOT NULL, " +
- "FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
- Cursor cursor = database.query("select * from `" + tableName + "`");
- while (cursor.moveToNext()) {
- String colorAsString1 = cursor.getString(7); // color
-
- @ColorInt Integer color1 = null;
- try {
- color1 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString1));
- } catch (Exception e) {
- color1 = Color.GRAY;
- }
- database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
- cursor.getLong(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3),
- cursor.getLong(4), cursor.getLong(5), cursor.getString(6), color1, cursor.getLong(8)});
-
- }
-
-
- database.execSQL("DROP TABLE `" + tableName + "`");
- database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
- database.execSQL("CREATE UNIQUE INDEX `index_Label_accountId_id` ON `" + tableName + "` (`accountId`, `id`)");
- database.execSQL("CREATE INDEX `index_Label_boardId` ON `" + tableName + "` (`boardId`)");
- database.execSQL("CREATE INDEX `index_Label_accountId` ON `" + tableName + "` (`accountId`)");
- database.execSQL("CREATE UNIQUE INDEX `idx_label_title_unique` ON `" + tableName + "` (`boardId`, `title`)");
- database.execSQL("CREATE INDEX `index_Label_id` ON `" + tableName + "` (`id`)");
- database.execSQL("CREATE INDEX `index_Label_lastModifiedLocal` ON `" + tableName + "` (`lastModifiedLocal`)");
- database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
- }
- }
- };
-
- private static final Migration MIGRATION_22_23 = new Migration(22, 23) {
- @Override
- public void migrate(SupportSQLiteDatabase database) {
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/359
- database.execSQL("ALTER TABLE `Account` ADD `boardsEtag` TEXT");
- database.execSQL("ALTER TABLE `Board` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `Stack` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `Card` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `Label` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `AccessControl` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `Attachment` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `User` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `DeckComment` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `Activity` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `OcsProject` ADD `etag` TEXT");
- database.execSQL("ALTER TABLE `OcsProjectResource` ADD `etag` TEXT");
- }
- };
-
public static final RoomDatabase.Callback ON_CREATE_CALLBACK = new RoomDatabase.Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
- DeckLog.log("onCreate triggered!!!");
+ DeckLog.info("Database", DECK_DB_NAME, "created.");
LastSyncUtil.resetAll();
}
};
@@ -415,45 +162,28 @@ public abstract class DeckDatabase extends RoomDatabase {
context,
DeckDatabase.class,
DECK_DB_NAME)
- .addMigrations(MIGRATION_8_9)
- .addMigrations(MIGRATION_9_10)
- .addMigrations(MIGRATION_10_11)
- .addMigrations(MIGRATION_11_12)
- .addMigrations(MIGRATION_12_13)
- .addMigrations(MIGRATION_13_14)
- .addMigrations(new Migration(14, 15) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/570
- SyncWorker.update(context);
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/525
- PreferenceManager
- .getDefaultSharedPreferences(context)
- .edit()
- .remove("it.niedermann.nextcloud.deck.theme_text")
- .apply();
- }
- })
- .addMigrations(MIGRATION_15_16)
- .addMigrations(MIGRATION_16_17)
- .addMigrations(MIGRATION_17_18)
- .addMigrations(MIGRATION_18_19)
- .addMigrations(MIGRATION_19_20)
- .addMigrations(MIGRATION_20_21)
- .addMigrations(new Migration(21, 22) {
- @Override
- public void migrate(@NonNull SupportSQLiteDatabase database) {
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/715
- final SharedPreferences.Editor lastSyncPref = context.getApplicationContext().getSharedPreferences("it.niedermann.nextcloud.deck.last_sync", Context.MODE_PRIVATE).edit();
- Cursor cursor = database.query("select id from `Account`");
- while (cursor.moveToNext()) {
- lastSyncPref.remove("lS_" + cursor.getLong(0));
- }
- cursor.close();
- lastSyncPref.apply();
- }
- })
- .addMigrations(MIGRATION_22_23)
+ .addMigrations(new Migration_8_9())
+ .addMigrations(new Migration_9_10())
+ .addMigrations(new Migration_10_11())
+ .addMigrations(new Migration_11_12())
+ .addMigrations(new Migration_12_13())
+ .addMigrations(new Migration_13_14())
+ .addMigrations(new Migration_14_15(context))
+ .addMigrations(new Migration_15_16())
+ .addMigrations(new Migration_16_17())
+ .addMigrations(new Migration_17_18())
+ .addMigrations(new Migration_18_19())
+ .addMigrations(new Migration_19_20())
+ .addMigrations(new Migration_20_21())
+ .addMigrations(new Migration_21_22(context))
+ .addMigrations(new Migration_22_23())
+ .addMigrations(new Migration_23_24(context))
+ .addMigrations(new Migration_24_25())
+ .addMigrations(new Migration_25_26())
+ .addMigrations(new Migration_26_27())
+ .addMigrations(new Migration_27_28())
+ .addMigrations(new Migration_28_29())
+ .addMigrations(new Migration_29_30(context))
.fallbackToDestructiveMigration()
.addCallback(ON_CREATE_CALLBACK)
.build();
@@ -495,8 +225,6 @@ public abstract class DeckDatabase extends RoomDatabase {
public abstract SingleCardWidgetModelDao getSingleCardWidgetModelDao();
- public abstract StackWidgetModelDao getStackWidgetModelDao();
-
public abstract OcsProjectDao getOcsProjectDao();
public abstract OcsProjectResourceDao getOcsProjectResourceDao();
@@ -506,4 +234,21 @@ public abstract class DeckDatabase extends RoomDatabase {
public abstract UserInGroupDao getUserInGroupDao();
public abstract UserInBoardDao getUserInBoardDao();
+
+ public abstract FilterWidgetDao getFilterWidgetDao();
+
+ public abstract FilterWidgetAccountDao getFilterWidgetAccountDao();
+
+ public abstract FilterWidgetBoardDao getFilterWidgetBoardDao();
+
+ public abstract FilterWidgetStackDao getFilterWidgetStackDao();
+
+ public abstract FilterWidgetLabelDao getFilterWidgetLabelDao();
+
+ public abstract FilterWidgetUserDao getFilterWidgetUserDao();
+
+ public abstract FilterWidgetProjectDao getFilterWidgetProjectDao();
+
+ public abstract FilterWidgetSortDao getFilterWidgetSortDao();
+
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DateTypeConverter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/converter/DateTypeConverter.java
index f6811e033..a39a4a665 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DateTypeConverter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/converter/DateTypeConverter.java
@@ -1,4 +1,4 @@
-package it.niedermann.nextcloud.deck.persistence.sync.adapters.db;
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.converter;
import androidx.room.TypeConverter;
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/converter/EnumConverter.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/converter/EnumConverter.java
new file mode 100644
index 000000000..2cafaa8fe
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/converter/EnumConverter.java
@@ -0,0 +1,67 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.converter;
+
+import androidx.annotation.Nullable;
+import androidx.room.TypeConverter;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.model.enums.EAttachmentType;
+import it.niedermann.nextcloud.deck.model.enums.EDueType;
+import it.niedermann.nextcloud.deck.model.enums.ESortCriteria;
+import it.niedermann.nextcloud.deck.model.widget.filter.EWidgetType;
+
+public class EnumConverter {
+ // #### EWidgetType
+ @TypeConverter
+ public static EWidgetType toWidgetTypeEnum(Integer value) {
+ try {
+ return value == null ? null : EWidgetType.findById(value);
+ } catch (IllegalArgumentException e) {
+ DeckLog.error(EWidgetType.class.getSimpleName(), value, "not found. Falling back to generic", EWidgetType.FILTER_WIDGET);
+ return EWidgetType.FILTER_WIDGET;
+ }
+ }
+
+ @TypeConverter
+ public static Integer fromWidgetTypeEnum(EWidgetType value) {
+ return value == null ? null : value.getId();
+ }
+
+ // #### EDueType
+ @TypeConverter
+ @Nullable
+ public static EDueType toDueTypeEnum(@Nullable Integer value) {
+ return value == null ? null : EDueType.findById(value);
+ }
+
+ @TypeConverter
+ @Nullable
+ public static Integer fromDueTypeEnum(@Nullable EDueType value) {
+ return value == null ? null : value.getId();
+ }
+
+ // #### ESortCriteria
+ @TypeConverter
+ @Nullable
+ public static ESortCriteria toSortCriteriaEnum(@Nullable Integer value) {
+ return value == null ? null : ESortCriteria.findById(value);
+ }
+
+ @TypeConverter
+ @Nullable
+ public static Integer fromSortCriteriaEnum(@Nullable ESortCriteria value) {
+ return value == null ? null : value.getId();
+ }
+
+ // #### EAttachmentType
+ @TypeConverter
+ @Nullable
+ public static EAttachmentType toEAttachmentType(@Nullable String value) {
+ return value == null ? null : EAttachmentType.findByValue(value);
+ }
+
+ @TypeConverter
+ @Nullable
+ public static String fromEAttachmentType(@Nullable EAttachmentType value) {
+ return value == null ? null : value.getValue();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java
index 6cb322aa3..89ada6e18 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java
@@ -33,7 +33,7 @@ public interface BoardDao extends GenericDao<Board> {
Board getBoardByRemoteIdDirectly(long accountId, long remoteId);
@Query("SELECT * FROM board WHERE localId = :localId")
- Board getBoardByIdDirectly(long localId);
+ Board getBoardByLocalIdDirectly(long localId);
@Transaction
@Query("SELECT * FROM board WHERE accountId = :accountId and id = :remoteId")
@@ -51,10 +51,12 @@ public interface BoardDao extends GenericDao<Board> {
@Query("SELECT * FROM board WHERE accountId = :accountId and localId = :localId")
LiveData<FullBoard> getFullBoardById(final long accountId, final long localId);
-
- @Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId")
+ @Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON s.localId = c.stackId where c.localId = :localCardId")
Board getBoardByLocalCardIdDirectly(long localCardId);
+ @Query("SELECT b.localId FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON s.localId = c.stackId where c.localId = :localCardId")
+ Long getBoardLocalIdByLocalCardIdDirectly(long localCardId);
+
@Transaction
@Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId and c.stackId = s.localId")
FullBoard getFullBoardByLocalCardIdDirectly(long localCardId);
@@ -66,7 +68,6 @@ public interface BoardDao extends GenericDao<Board> {
@Query("SELECT * FROM board WHERE accountId = :accountId and archived = 0 and permissionEdit = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
LiveData<List<Board>> getBoardsWithEditPermissionsForAccount(long accountId);
-
@Query("SELECT b.localId " +
"FROM card c " +
"inner join stack s on s.localId = c.stackId " +
@@ -79,4 +80,7 @@ public interface BoardDao extends GenericDao<Board> {
@Query("SELECT * FROM board WHERE accountId = :accountId and title = :title")
Board getBoardForAccountByNameDirectly(long accountId, String title);
+
+ @Query("SELECT b.color FROM board b where b.localId = :localBoardId and b.accountId = :accountId")
+ Integer getBoardColorByLocalIdDirectly(long accountId, long localBoardId);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java
index 82bdf1b8d..51fbc0d89 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java
@@ -85,4 +85,17 @@ public interface CardDao extends GenericDao<Card> {
@Query("SELECT c.stackId FROM card c WHERE localId = :localCardId")
Long getLocalStackIdByLocalCardId(Long localCardId);
+
+ @Transaction
+ @Query("SELECT * FROM card c WHERE " +
+ "exists(select 1 from Stack s join Board b on s.boardId = b.localId where s.localId = c.stackId " +
+ "and b.archived = 0 " +
+ "and not exists(select 1 from AccessControl ac where ac.boardId = b.localId and status <> 3)) " +
+ "and dueDate is not null " +
+ "and (coalesce(:accountIds, null) is null or accountId in (:accountIds)) " +
+ "and status <> 3 " +
+ "and archived = 0")
+ List<FullCard> getFullCardsForNonSharedBoardsWithDueDateForUpcomingCardsWidgetDirectly(List<Long> accountIds);
+
+
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/GenericDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/GenericDao.java
index 97e62226d..f6a57a67a 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/GenericDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/GenericDao.java
@@ -9,12 +9,15 @@ public interface GenericDao<T> {
@Insert
long insert(T entity);
+ @SuppressWarnings("unchecked")
@Insert
long[] insert(T... entity);
+ @SuppressWarnings("unchecked")
@Update
void update(T... entity);
+ @SuppressWarnings("unchecked")
@Delete
void delete(T... entity);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java
index feb7e453b..1d2649488 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java
@@ -38,6 +38,12 @@ public interface StackDao extends GenericDao<Stack> {
@Query("SELECT * FROM stack WHERE accountId = :accountId and localId = :localId")
LiveData<FullStack> getFullStack(long accountId, long localId);
+ @Query("SELECT localId FROM stack WHERE accountId = :accountId")
+ List<Long> getLocalStackIdsByAccountIdDirectly(long accountId);
+
+ @Query("SELECT localId FROM stack WHERE boardId = :localBoardId")
+ List<Long> getLocalStackIdsByLocalBoardIdDirectly(long localBoardId);
+
@Transaction
@Query("SELECT * FROM stack WHERE accountId = :accountId and boardId = :localBoardId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<FullStack> getLocallyChangedStacksForBoardDirectly(long accountId, long localBoardId);
@@ -55,4 +61,10 @@ public interface StackDao extends GenericDao<Stack> {
@Query("SELECT coalesce(MAX(`order`), -1) FROM stack s WHERE boardId = :localBoardId")
Integer getHighestStackOrderInBoard(long localBoardId);
+
+ @Query("SELECT exists(select 1 from Stack s join Board b on s.boardId = b.localId where s.localId = :localStackId and exists(select 1 from AccessControl ac where ac.boardId = b.localId and status <> 3))")
+ boolean isStackOnSharedBoardDirectly(Long localStackId);
+
+ @Query("SELECT s.localId FROM stack s join Board b on s.boardId = b.localId where b.archived <> 0 and b.accountId in (:accountIds)")
+ List<Long> getLocalStackIdsInArchivedBoardsByAccountIdsDirectly(List<Long> accountIds);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetAccountDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetAccountDao.java
new file mode 100644
index 000000000..9133aa4bc
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetAccountDao.java
@@ -0,0 +1,18 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetAccount;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface FilterWidgetAccountDao extends GenericDao<FilterWidgetAccount> {
+ @Query("DELETE FROM FilterWidgetAccount WHERE filterWidgetId = :filterWidgetId")
+ void deleteByFilterWidgetId (Integer filterWidgetId);
+
+ @Query("select * FROM FilterWidgetAccount WHERE filterWidgetId = :filterWidgetId")
+ List<FilterWidgetAccount> getFilterWidgetAccountsByFilterWidgetIdDirectly(Integer filterWidgetId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetBoardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetBoardDao.java
new file mode 100644
index 000000000..e71c1b51e
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetBoardDao.java
@@ -0,0 +1,15 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetBoard;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface FilterWidgetBoardDao extends GenericDao<FilterWidgetBoard> {
+ @Query("SELECT * FROM FilterWidgetBoard where filterAccountId = :filterWidgetAccountId")
+ List<FilterWidgetBoard> getFilterWidgetBoardsByFilterWidgetAccountIdDirectly(Long filterWidgetAccountId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetDao.java
new file mode 100644
index 000000000..23dcbea0f
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetDao.java
@@ -0,0 +1,44 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+import androidx.room.Transaction;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.widget.filter.EWidgetType;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidget;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface FilterWidgetDao extends GenericDao<FilterWidget> {
+ @Query("DELETE FROM filterwidget WHERE id = :filterWidgetId")
+ void delete (Integer filterWidgetId);
+
+ @Query("SELECT * FROM FilterWidget where id = :filterWidgetId")
+ FilterWidget getFilterWidgetByIdDirectly(Integer filterWidgetId);
+
+ @Query("SELECT EXISTS (SELECT 1 FROM FilterWidget WHERE id = :filterWidgetId)")
+ boolean filterWidgetExists(int filterWidgetId);
+
+ @Query("SELECT id FROM FilterWidget WHERE widgetType = :type")
+ List<Integer> getFilterWidgetIdsByType(int type);
+
+ @Transaction
+ @Query("SELECT DISTINCT w.widgetType " +
+ "FROM FilterWidget w " +
+ "LEFT JOIN FilterWidgetAccount a ON w.id = a.filterWidgetId " +
+ "LEFT JOIN FilterWidgetBoard b ON a.id = b.filterAccountId " +
+ "LEFT JOIN FilterWidgetStack s ON b.id = s.filterBoardId " +
+ "LEFT JOIN FilterWidgetUser u ON a.id = u.filterAccountId " +
+ "LEFT JOIN FilterWidgetProject p ON a.id = p.filterAccountId " +
+ "LEFT JOIN FilterWidgetLabel l ON b.id = l.filterBoardId " +
+ "WHERE (:changedEntityType = 'ACCOUNT' AND (a.accountId = :localIdOfChangedEntity OR a.accountId IS NULL)) " +
+ "OR (:changedEntityType = 'BOARD' AND (b.boardId = :localIdOfChangedEntity OR b.boardId IS NULL)) " +
+ "OR (:changedEntityType = 'STACK' AND (s.stackId = :localIdOfChangedEntity OR s.stackId IS NULL)) " +
+ "OR (:changedEntityType = 'USER' AND (u.userId = :localIdOfChangedEntity OR u.userId IS NULL)) " +
+ "OR (:changedEntityType = 'PROJECT' AND (p.projectId = :localIdOfChangedEntity OR p.projectId IS NULL)) " +
+ "OR (:changedEntityType = 'LABEL' AND (l.labelId = :localIdOfChangedEntity OR l.labelId IS NULL)) "
+ )
+ List<EWidgetType> getChangedListTypesByEntity(String changedEntityType, Long localIdOfChangedEntity);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetLabelDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetLabelDao.java
new file mode 100644
index 000000000..61ed01545
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetLabelDao.java
@@ -0,0 +1,15 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetLabel;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface FilterWidgetLabelDao extends GenericDao<FilterWidgetLabel> {
+ @Query("SELECT * FROM FilterWidgetLabel where filterBoardId = :filterWidgetBoardId")
+ List<FilterWidgetLabel> getFilterWidgetLabelsByFilterWidgetBoardIdDirectly(Long filterWidgetBoardId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetProjectDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetProjectDao.java
new file mode 100644
index 000000000..4e49684e3
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetProjectDao.java
@@ -0,0 +1,15 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetProject;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface FilterWidgetProjectDao extends GenericDao<FilterWidgetProject> {
+ @Query("SELECT * FROM FilterWidgetProject where filterAccountId = :filterWidgetAccountId")
+ List<FilterWidgetProject> getFilterWidgetProjectsByFilterWidgetAccountIdDirectly(Long filterWidgetAccountId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetSortDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetSortDao.java
new file mode 100644
index 000000000..6aa86a4a3
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetSortDao.java
@@ -0,0 +1,18 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetSort;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface FilterWidgetSortDao extends GenericDao<FilterWidgetSort> {
+ @Query("DELETE FROM FilterWidgetSort WHERE filterWidgetId = :filterWidgetId")
+ void deleteByFilterWidgetId (Integer filterWidgetId);
+
+ @Query("select * FROM FilterWidgetSort WHERE filterWidgetId = :filterWidgetId order by ruleOrder asc")
+ List<FilterWidgetSort> getFilterWidgetSortByFilterWidgetIdDirectly(Integer filterWidgetId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetStackDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetStackDao.java
new file mode 100644
index 000000000..fd79a3ba9
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetStackDao.java
@@ -0,0 +1,15 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetStack;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface FilterWidgetStackDao extends GenericDao<FilterWidgetStack> {
+ @Query("SELECT * FROM FilterWidgetStack where filterBoardId = :filterWidgetBoardId")
+ List<FilterWidgetStack> getFilterWidgetStacksByFilterWidgetBoardIdDirectly(Long filterWidgetBoardId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetUserDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetUserDao.java
new file mode 100644
index 000000000..6b44103e9
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/filter/FilterWidgetUserDao.java
@@ -0,0 +1,15 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets.filter;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetUser;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface FilterWidgetUserDao extends GenericDao<FilterWidgetUser> {
+ @Query("SELECT * FROM FilterWidgetUser where filterAccountId = :filterWidgetAccountId")
+ List<FilterWidgetUser> getFilterWidgetUsersByFilterWidgetAccountIdDirectly(Long filterWidgetAccountId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_10_11.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_10_11.java
new file mode 100644
index 000000000..e50c8a62e
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_10_11.java
@@ -0,0 +1,59 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import android.database.Cursor;
+
+import androidx.annotation.NonNull;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import it.niedermann.nextcloud.deck.model.enums.DBStatus;
+
+/**
+ * Removes duplicate labels and ensures uniqueness
+ */
+public class Migration_10_11 extends Migration {
+
+ public Migration_10_11() {
+ super(10, 11);
+ }
+
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ // replace duplicates with the server-known ones
+ Cursor duplucatesCursor = database.query("SELECT boardId, title, count(*) FROM Label group by boardid, title having count(*) > 1");
+ if (duplucatesCursor != null && duplucatesCursor.moveToFirst()) {
+ do {
+ long boardId = duplucatesCursor.getLong(0);
+ String title = duplucatesCursor.getString(1);
+ Cursor singleDuplicateCursor = database.query("select localId from Label where boardId = ? and title = ? order by id desc", new Object[]{boardId, title});
+ if (singleDuplicateCursor != null && singleDuplicateCursor.moveToFirst()) {
+ long idToUse = -1;
+ do {
+ if (idToUse < 0) {
+ // desc order -> first one is the one with remote ID or a random one. keep this one.
+ idToUse = singleDuplicateCursor.getLong(0);
+ continue;
+ }
+ long idToReplace = singleDuplicateCursor.getLong(0);
+ Cursor cardsAssignedToDuplicateCursor = database.query("select cardId, exists(select 1 from JoinCardWithLabel ij where ij.labelId = ? and ij.cardId = cardId) " +
+ "from JoinCardWithLabel where labelId = ?", new Object[]{idToUse, idToReplace});
+ if (cardsAssignedToDuplicateCursor != null && cardsAssignedToDuplicateCursor.moveToFirst()) {
+ do {
+ long cardId = cardsAssignedToDuplicateCursor.getLong(0);
+ boolean hasDestinationLabelAssigned = cardsAssignedToDuplicateCursor.getInt(1) > 0;
+ database.execSQL("DELETE FROM JoinCardWithLabel where labelId = ? and cardId = ?", new Object[]{idToReplace, cardId});
+
+ if (!hasDestinationLabelAssigned) {
+ database.execSQL("INSERT INTO JoinCardWithLabel (status,labelId,cardId) VALUES (?, ?, ?)", new Object[]{DBStatus.LOCAL_EDITED.getId(), idToUse, cardId});
+ }
+ } while (cardsAssignedToDuplicateCursor.moveToNext());
+ }
+ database.execSQL("DELETE FROM Label where localId = ?", new Object[]{idToReplace});
+ } while (singleDuplicateCursor.moveToNext());
+ }
+ } while (duplucatesCursor.moveToNext());
+ }
+ // database.execSQL("DELETE FROM Label WHERE id IS NULL AND EXISTS(SELECT 1 FROM Label il WHERE il.boardId = boardId AND il.title = title AND id IS NOT NULL)");
+ database.execSQL("CREATE UNIQUE INDEX idx_label_title_unique ON Label(boardId, title)");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_11_12.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_11_12.java
new file mode 100644
index 000000000..acd646f63
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_11_12.java
@@ -0,0 +1,20 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Adds support for the Single note widget
+ */
+public class Migration_11_12 extends Migration {
+
+ public Migration_11_12() {
+ super(11, 12);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE `SingleCardWidgetModel` (`widgetId` INTEGER PRIMARY KEY, `accountId` INTEGER, `boardId` INTEGER, `cardId` INTEGER, FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE INDEX `index_SingleCardWidgetModel_cardId` ON `SingleCardWidgetModel` (`cardId`)");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_12_13.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_12_13.java
new file mode 100644
index 000000000..8a85f1651
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_12_13.java
@@ -0,0 +1,17 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+public class Migration_12_13 extends Migration {
+
+ public Migration_12_13() {
+ super(12, 13);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE INDEX `idx_cardWidgetModel_accountId` ON `SingleCardWidgetModel` (`accountId`)");
+ database.execSQL("CREATE INDEX `idx_cardWidgetModel_boardId` ON `SingleCardWidgetModel` (`boardId`)");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_13_14.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_13_14.java
new file mode 100644
index 000000000..dfb8994e8
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_13_14.java
@@ -0,0 +1,20 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Adds support for comment responses
+ */
+public class Migration_13_14 extends Migration {
+
+ public Migration_13_14() {
+ super(13, 14);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `DeckComment` ADD `parentId` INTEGER REFERENCES DeckComment(localId) ON DELETE CASCADE");
+ database.execSQL("CREATE INDEX `idx_comment_parentID` ON DeckComment(parentId)");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_14_15.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_14_15.java
new file mode 100644
index 000000000..dc07a12dd
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_14_15.java
@@ -0,0 +1,35 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceManager;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import it.niedermann.nextcloud.deck.persistence.sync.SyncWorker;
+
+/**
+ * @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/570">Reinitializes the background synchronization</a> and
+ * <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/525">cleans up old shared preferences</a>
+ */
+public class Migration_14_15 extends Migration {
+
+ @NonNull
+ private final Context context;
+
+ public Migration_14_15(@NonNull Context context) {
+ super(14, 15);
+ this.context = context;
+ }
+
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ SyncWorker.update(context);
+ PreferenceManager
+ .getDefaultSharedPreferences(context)
+ .edit()
+ .remove("it.niedermann.nextcloud.deck.theme_text")
+ .apply();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_15_16.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_15_16.java
new file mode 100644
index 000000000..3dd1a3d61
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_15_16.java
@@ -0,0 +1,23 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Adds support for Stack widget
+ */
+public class Migration_15_16 extends Migration {
+
+ public Migration_15_16() {
+ super(15, 16);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE `StackWidgetModel` (`appWidgetId` INTEGER PRIMARY KEY, `accountId` INTEGER, `stackId` INTEGER, `darkTheme` INTEGER CHECK (`darkTheme` IN (0,1)) NOT NULL, " +
+ "FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
+ "FOREIGN KEY(`stackId`) REFERENCES `Stack`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE INDEX `index_StackWidgetModel_stackId` ON `StackWidgetModel` (`stackId`)");
+ database.execSQL("CREATE INDEX `index_StackWidgetModel_accountId` ON `StackWidgetModel` (`accountId`)");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_16_17.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_16_17.java
new file mode 100644
index 000000000..f276fe879
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_16_17.java
@@ -0,0 +1,34 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/573">Adds support for projects</a>
+ */
+public class Migration_16_17 extends Migration {
+
+ public Migration_16_17() {
+ super(16, 17);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE `OcsProject` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `name` TEXT NOT NULL, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER)");
+ database.execSQL("CREATE UNIQUE INDEX `index_OcsProject_accountId_id` ON `OcsProject` (`accountId`, `id`)");
+ database.execSQL("CREATE INDEX `index_project_accID` ON `OcsProject` (`accountId`)");
+ database.execSQL("CREATE INDEX `index_OcsProject_id` ON `OcsProject` (`id`)");
+ database.execSQL("CREATE INDEX `index_OcsProject_lastModifiedLocal` ON `OcsProject` (`lastModifiedLocal`)");
+
+ database.execSQL("CREATE TABLE `OcsProjectResource` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `name` TEXT, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `projectId` INTEGER NOT NULL, `type` TEXT , `link` TEXT , `path` TEXT, `iconUrl` TEXT , `previewAvailable` INTEGER, `mimetype` TEXT, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
+ database.execSQL("CREATE INDEX `index_projectResource_accID` ON `OcsProjectResource` (`accountId`)");
+ database.execSQL("CREATE INDEX `index_projectResource_projectId` ON `OcsProjectResource` (`projectId`)");
+ database.execSQL("CREATE UNIQUE INDEX `index_OcsProjectResource_accountId_id` ON `OcsProjectResource` (`accountId`, `id`, `projectId`)");
+ database.execSQL("CREATE INDEX `index_OcsProjectResource_id` ON `OcsProjectResource` (`id`)");
+ database.execSQL("CREATE INDEX `index_OcsProjectResource_lastModifiedLocal` ON `OcsProjectResource` (`lastModifiedLocal`)");
+
+ database.execSQL("CREATE TABLE `JoinCardWithProject` (`status` INTEGER NOT NULL, `projectId` INTEGER NOT NULL, `cardId` INTEGER NOT NULL, PRIMARY KEY (`projectId`, `cardId`), FOREIGN KEY(`cardId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
+ database.execSQL("CREATE INDEX `index_JoinCardWithProject_projectId` ON `JoinCardWithProject` (`projectId`)");
+ database.execSQL("CREATE INDEX `index_JoinCardWithProject_cardId` ON `JoinCardWithProject` (`cardId`)");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_17_18.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_17_18.java
new file mode 100644
index 000000000..f42be42af
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_17_18.java
@@ -0,0 +1,19 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/435">Implement ETags for Capabilities endpoint</a>
+ */
+public class Migration_17_18 extends Migration {
+
+ public Migration_17_18() {
+ super(17, 18);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `Account` ADD `etag` TEXT");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_18_19.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_18_19.java
new file mode 100644
index 000000000..072a11541
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_18_19.java
@@ -0,0 +1,21 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/619">Handle unknown project types</a>
+ */
+public class Migration_18_19 extends Migration {
+
+ public Migration_18_19() {
+ super(18, 19);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("DROP INDEX `index_OcsProjectResource_accountId_id`");
+ database.execSQL("ALTER TABLE `OcsProjectResource` ADD `idString` TEXT");
+ database.execSQL("CREATE UNIQUE INDEX `index_OcsProjectResource_accountId_id` ON `OcsProjectResource` (`accountId`, `id`, `idString`, `projectId`)");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_19_20.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_19_20.java
new file mode 100644
index 000000000..f6667ec02
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_19_20.java
@@ -0,0 +1,35 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Fixes issues with LDAP users when filtering
+ * https://github.com/stefan-niedermann/nextcloud-deck/issues/492
+ * https://github.com/stefan-niedermann/nextcloud-deck/issues/631
+ */
+public class Migration_19_20 extends Migration {
+
+ public Migration_19_20() {
+ super(19, 20);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE `UserInGroup` (`groupId` INTEGER NOT NULL, `memberId` INTEGER NOT NULL, " +
+ "primary KEY(`groupId`, `memberId`), " +
+ "FOREIGN KEY(`groupId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
+ "FOREIGN KEY(`memberId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
+ database.execSQL("CREATE UNIQUE INDEX `unique_idx_group_member` ON `UserInGroup` (`groupId`, `memberId`)");
+ database.execSQL("CREATE INDEX `index_UserInGroup_groupId` ON `UserInGroup` (`groupId`)");
+ database.execSQL("CREATE INDEX `index_UserInGroup_memberId` ON `UserInGroup` (`memberId`)");
+
+ database.execSQL("CREATE TABLE `UserInBoard` (`userId` INTEGER NOT NULL, `boardId` INTEGER NOT NULL, " +
+ "primary KEY(`userId`, `boardId`), " +
+ "FOREIGN KEY(`userId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE, " +
+ "FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE)");
+ database.execSQL("CREATE UNIQUE INDEX `unique_idx_user_board` ON `UserInBoard` (`userId`, `boardId`)");
+ database.execSQL("CREATE INDEX `index_UserInBoard_userId` ON `UserInBoard` (`userId`)");
+ database.execSQL("CREATE INDEX `index_UserInBoard_boardId` ON `UserInBoard` (`boardId`)");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_20_21.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_20_21.java
new file mode 100644
index 000000000..36a907b20
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_20_21.java
@@ -0,0 +1,123 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import android.database.Cursor;
+import android.graphics.Color;
+
+import androidx.annotation.ColorInt;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import it.niedermann.android.util.ColorUtil;
+
+/**
+ * @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/556">Store colors as integer in database</a>
+ */
+public class Migration_20_21 extends Migration {
+
+ public Migration_20_21() {
+ super(20, 21);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ String suffix = "_new";
+ {
+ String tableName = "Account";
+ database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `name` TEXT NOT NULL, `userName` TEXT NOT NULL, `url` TEXT NOT NULL, " +
+ "`color` INTEGER NOT NULL DEFAULT 0, `textColor` INTEGER NOT NULL DEFAULT 0, `serverDeckVersion` TEXT NOT NULL DEFAULT '0.6.4', `maintenanceEnabled` INTEGER NOT NULL DEFAULT 0, `etag` TEXT)");
+ Cursor cursor = database.query("select * from `" + tableName + "`");
+ while (cursor.moveToNext()) {
+ String colorAsString1 = cursor.getString(4); // color
+ String colorAsString2 = cursor.getString(5); // textColor
+
+ @ColorInt int color1;
+ @ColorInt int color2;
+ try {
+ color1 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString1));
+ color2 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString2));
+ } catch (Exception e) {
+ color1 = Color.GRAY;
+ color2 = Color.GRAY;
+ }
+ database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
+ cursor.getLong(0), cursor.getString(1), cursor.getString(2), cursor.getString(3),
+ color1, color2, cursor.getString(6), cursor.getInt(7), cursor.getString(8)});
+
+ }
+
+
+ database.execSQL("DROP TABLE `" + tableName + "`");
+ database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
+ database.execSQL("CREATE UNIQUE INDEX `index_Account_name` ON `" + tableName + "` (`name`)");
+ database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
+ }
+ {
+ String tableName = "Board";
+ database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, " +
+ "`lastModified` INTEGER, `lastModifiedLocal` INTEGER, `title` TEXT, `ownerId` INTEGER NOT NULL, `color` INTEGER, " +
+ "`archived` INTEGER NOT NULL, `shared` INTEGER NOT NULL, `deletedAt` INTEGER, `permissionRead` INTEGER NOT NULL, " +
+ "`permissionEdit` INTEGER NOT NULL, `permissionManage` INTEGER NOT NULL, `permissionShare` INTEGER NOT NULL, " +
+ "FOREIGN KEY(`ownerId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE SET NULL )");
+ Cursor cursor = database.query("select * from `" + tableName + "`");
+ while (cursor.moveToNext()) {
+ String colorAsString1 = cursor.getString(8); // color
+
+ @ColorInt int color1;
+ try {
+ color1 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString1));
+ } catch (Exception e) {
+ color1 = Color.GRAY;
+ }
+ database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
+ cursor.getLong(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3),
+ cursor.getLong(4), cursor.getLong(5), cursor.getString(6), cursor.getLong(7), color1,
+ cursor.getInt(9), cursor.getInt(10), cursor.getInt(11), cursor.getInt(12),
+ cursor.getInt(13), cursor.getInt(14), cursor.getInt(15)
+ });
+
+ }
+
+
+ database.execSQL("DROP TABLE `" + tableName + "`");
+ database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
+ database.execSQL("CREATE INDEX `index_Board_accountId` ON `" + tableName + "` (`accountId`)");
+ database.execSQL("CREATE UNIQUE INDEX `index_Board_accountId_id` ON `" + tableName + "` (`accountId`, `id`)");
+ database.execSQL("CREATE INDEX `index_Board_id` ON `" + tableName + "` (`id`)");
+ database.execSQL("CREATE INDEX `index_Board_ownerId` ON `" + tableName + "` (`ownerId`)");
+ database.execSQL("CREATE INDEX `index_Board_lastModifiedLocal` ON `" + tableName + "` (`lastModifiedLocal`)");
+ database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
+ }
+ {
+ String tableName = "Label";
+ database.execSQL("CREATE TABLE `" + tableName + suffix + "` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, " +
+ "`lastModified` INTEGER, `lastModifiedLocal` INTEGER, `title` TEXT, `color` INTEGER NOT NULL DEFAULT 0, `boardId` INTEGER NOT NULL, " +
+ "FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ Cursor cursor = database.query("select * from `" + tableName + "`");
+ while (cursor.moveToNext()) {
+ String colorAsString1 = cursor.getString(7); // color
+
+ @ColorInt int color1;
+ try {
+ color1 = Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(colorAsString1));
+ } catch (Exception e) {
+ color1 = Color.GRAY;
+ }
+ database.execSQL("Insert into `" + tableName + suffix + "` VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", new Object[]{
+ cursor.getLong(0), cursor.getLong(1), cursor.getLong(2), cursor.getInt(3),
+ cursor.getLong(4), cursor.getLong(5), cursor.getString(6), color1, cursor.getLong(8)});
+
+ }
+
+
+ database.execSQL("DROP TABLE `" + tableName + "`");
+ database.execSQL("ALTER TABLE `" + tableName + suffix + "` RENAME TO `" + tableName + "`");
+ database.execSQL("CREATE UNIQUE INDEX `index_Label_accountId_id` ON `" + tableName + "` (`accountId`, `id`)");
+ database.execSQL("CREATE INDEX `index_Label_boardId` ON `" + tableName + "` (`boardId`)");
+ database.execSQL("CREATE INDEX `index_Label_accountId` ON `" + tableName + "` (`accountId`)");
+ database.execSQL("CREATE UNIQUE INDEX `idx_label_title_unique` ON `" + tableName + "` (`boardId`, `title`)");
+ database.execSQL("CREATE INDEX `index_Label_id` ON `" + tableName + "` (`id`)");
+ database.execSQL("CREATE INDEX `index_Label_lastModifiedLocal` ON `" + tableName + "` (`lastModifiedLocal`)");
+ database.execSQL("UPDATE SQLITE_SEQUENCE SET seq = (select max(id) from " + tableName + ") WHERE name = ?", new Object[]{tableName});
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_21_22.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_21_22.java
new file mode 100644
index 000000000..768de3c5d
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_21_22.java
@@ -0,0 +1,34 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.database.Cursor;
+
+import androidx.annotation.NonNull;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/715">Migrate from java.util.Date and java.util.Calendar to java.time.*</a>
+ */
+public class Migration_21_22 extends Migration {
+
+ @NonNull
+ private final Context context;
+
+ public Migration_21_22(@NonNull Context context) {
+ super(21, 22);
+ this.context = context;
+ }
+
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ final SharedPreferences.Editor lastSyncPref = context.getApplicationContext().getSharedPreferences("it.niedermann.nextcloud.deck.last_sync", Context.MODE_PRIVATE).edit();
+ final Cursor cursor = database.query("select id from `Account`");
+ while (cursor.moveToNext()) {
+ lastSyncPref.remove("lS_" + cursor.getLong(0));
+ }
+ cursor.close();
+ lastSyncPref.apply();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_22_23.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_22_23.java
new file mode 100644
index 000000000..240f14ac1
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_22_23.java
@@ -0,0 +1,30 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/359">Implement ETags for synchronization Speed-Up</a>
+ */
+public class Migration_22_23 extends Migration {
+
+ public Migration_22_23() {
+ super(22, 23);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `Account` ADD `boardsEtag` TEXT");
+ database.execSQL("ALTER TABLE `Board` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Stack` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Card` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Label` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `AccessControl` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Attachment` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `User` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `DeckComment` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `Activity` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `OcsProject` ADD `etag` TEXT");
+ database.execSQL("ALTER TABLE `OcsProjectResource` ADD `etag` TEXT");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_23_24.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_23_24.java
new file mode 100644
index 000000000..cd0eb094e
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_23_24.java
@@ -0,0 +1,39 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceManager;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import it.niedermann.nextcloud.deck.R;
+
+/**
+ * <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/392">Dark mode following system default</a>
+ */
+public class Migration_23_24 extends Migration {
+
+ @NonNull
+ private final Context context;
+
+ public Migration_23_24(@NonNull Context context) {
+ super(23, 24);
+ this.context = context;
+ }
+
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ final String themePref = context.getString(R.string.pref_key_dark_theme);
+
+ if (sharedPreferences.contains(themePref)) {
+ SharedPreferences.Editor editor = sharedPreferences.edit();
+ final boolean darkTheme = sharedPreferences.getBoolean(themePref, false);
+ editor.remove(themePref);
+ editor.putString(themePref, darkTheme ? context.getString(R.string.pref_value_theme_dark) : context.getString(R.string.pref_value_theme_light));
+ editor.apply();
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_24_25.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_24_25.java
new file mode 100644
index 000000000..6d2bff544
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_24_25.java
@@ -0,0 +1,22 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Reset ETags (comments weren't loading due to bug)
+ */
+public class Migration_24_25 extends Migration {
+
+ public Migration_24_25() {
+ super(24, 25);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("UPDATE `Account` SET `boardsEtag` = NULL");
+ database.execSQL("UPDATE `Board` SET `etag` = NULL");
+ database.execSQL("UPDATE `Stack` SET `etag` = NULL");
+ database.execSQL("UPDATE `Card` SET `etag` = NULL");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_25_26.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_25_26.java
new file mode 100644
index 000000000..37c648d00
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_25_26.java
@@ -0,0 +1,42 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Implement <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/597">Filter widget</a>
+ */
+public class Migration_25_26 extends Migration {
+
+ public Migration_25_26() {
+ super(25, 26);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE `FilterWidget` (`id` INTEGER PRIMARY KEY NOT NULL, `title` TEXT, `dueType` INTEGER, `widgetType` INTEGER NOT NULL)");
+ database.execSQL("CREATE TABLE `FilterWidgetAccount` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterWidgetId` INTEGER, `accountId` INTEGER, `includeNoUser` INTEGER NOT NULL, `includeNoProject` INTEGER NOT NULL, FOREIGN KEY(`accountId`) REFERENCES `Account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterWidgetId`) REFERENCES `FilterWidget`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE TABLE `FilterWidgetBoard` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterAccountId` INTEGER, `boardId` INTEGER, `includeNoLabel` INTEGER NOT NULL, FOREIGN KEY(`boardId`) REFERENCES `Board`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterAccountId`) REFERENCES `FilterWidgetAccount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE TABLE `FilterWidgetLabel` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterBoardId` INTEGER, `labelId` INTEGER, FOREIGN KEY(`labelId`) REFERENCES `Label`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterBoardId`) REFERENCES `FilterWidgetBoard`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE TABLE `FilterWidgetSort` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterWidgetId` INTEGER, `direction` INTEGER NOT NULL, `criteria` INTEGER NOT NULL, `ruleOrder` INTEGER NOT NULL, FOREIGN KEY(`filterWidgetId`) REFERENCES `FilterWidget`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE TABLE `FilterWidgetStack` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterBoardId` INTEGER, `stackId` INTEGER, FOREIGN KEY(`stackId`) REFERENCES `Stack`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterBoardId`) REFERENCES `FilterWidgetBoard`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE TABLE `FilterWidgetUser` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterAccountId` INTEGER, `userId` INTEGER, FOREIGN KEY(`userId`) REFERENCES `User`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterAccountId`) REFERENCES `FilterWidgetAccount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE TABLE `FilterWidgetProject` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `filterAccountId` INTEGER, `projectId` INTEGER, FOREIGN KEY(`projectId`) REFERENCES `OcsProject`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`filterAccountId`) REFERENCES `FilterWidgetAccount`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE INDEX `index_FilterWidgetAccount_filterWidgetId` ON `FilterWidgetAccount` (`filterWidgetId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetAccount_accountId` ON `FilterWidgetAccount` (`accountId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetBoard_boardId` ON `FilterWidgetBoard` (`boardId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetBoard_filterAccountId` ON `FilterWidgetBoard` (`filterAccountId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetLabel_filterBoardId` ON `FilterWidgetLabel` (`filterBoardId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetLabel_labelId` ON `FilterWidgetLabel` (`labelId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetSort_filterWidgetId` ON `FilterWidgetSort` (`filterWidgetId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetStack_filterBoardId` ON `FilterWidgetStack` (`filterBoardId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetStack_stackId` ON `FilterWidgetStack` (`stackId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetUser_filterAccountId` ON `FilterWidgetUser` (`filterAccountId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetUser_userId` ON `FilterWidgetUser` (`userId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetProject_filterAccountId` ON `FilterWidgetProject` (`filterAccountId`)");
+ database.execSQL("CREATE INDEX `idx_FilterWidgetProject_projectId` ON `FilterWidgetProject` (`projectId`)");
+ database.execSQL("CREATE INDEX `unique_idx_FilterWidgetSort_filterWidgetId_criteria` ON `FilterWidgetSort` (`filterWidgetId`, `criteria`)");
+ database.execSQL("CREATE INDEX `unique_idx_FilterWidgetSort_filterWidgetId_ruleOrder` ON `FilterWidgetSort` (`filterWidgetId`, `ruleOrder`)");
+
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_26_27.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_26_27.java
new file mode 100644
index 000000000..f8e27ec6b
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_26_27.java
@@ -0,0 +1,62 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+import it.niedermann.nextcloud.deck.model.widget.filter.EWidgetType;
+
+/**
+ * @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/767">Migrate Stack Widget to Filter Widget infrastructure</a>
+ */
+public class Migration_26_27 extends Migration {
+
+ public Migration_26_27() {
+ super(26, 27);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+
+ Cursor cursor = database.query("select s.localId, s.boardId, s.accountId, w.appWidgetId from `StackWidgetModel` w inner join `Stack` s on s.localId = w.stackId");
+ while (cursor.moveToNext()) {
+ Long localStackId = cursor.getLong(0);
+ Long localBoardId = cursor.getLong(1);
+ Long accountId = cursor.getLong(2);
+ Long filterWidgetId = cursor.getLong(3);
+
+ // widget
+ ContentValues values = new ContentValues();
+ values.put("widgetType", EWidgetType.STACK_WIDGET.getId());
+ values.put("id", filterWidgetId);
+ database.insert("FilterWidget", SQLiteDatabase.CONFLICT_NONE, values);
+
+ // account
+ values = new ContentValues();
+ values.put("filterWidgetId", filterWidgetId);
+ values.put("accountId", accountId);
+ values.put("includeNoUser", false);
+ values.put("includeNoProject", false);
+ long filterWidgetAccountId = database.insert("FilterWidgetAccount", SQLiteDatabase.CONFLICT_NONE, values);
+
+ // board
+ values = new ContentValues();
+ values.put("filterAccountId", filterWidgetAccountId);
+ values.put("boardId", localBoardId);
+ values.put("includeNoLabel", false);
+ long filterWidgetBoardId = database.insert("FilterWidgetBoard", SQLiteDatabase.CONFLICT_NONE, values);
+
+ // stack
+ values = new ContentValues();
+ values.put("filterBoardId", filterWidgetBoardId);
+ values.put("stackId", localStackId);
+ database.insert("FilterWidgetStack", SQLiteDatabase.CONFLICT_NONE, values);
+ }
+
+ // cleanup
+ database.execSQL("DROP TABLE `StackWidgetModel`");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_27_28.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_27_28.java
new file mode 100644
index 000000000..3f588e4f4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_27_28.java
@@ -0,0 +1,19 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Adds support for new attachment types with Deck server <code>1.3.0</code>
+ */
+public class Migration_27_28 extends Migration {
+
+ public Migration_27_28() {
+ super(27, 28);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `Attachment` ADD COLUMN `fileId` INTEGER");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_28_29.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_28_29.java
new file mode 100644
index 000000000..69c163639
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_28_29.java
@@ -0,0 +1,22 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Reset ETags for cards because <a href="https://github.com/nextcloud/deck/issues/2874">the attachments for this card might not be complete</a>.
+ */
+public class Migration_28_29 extends Migration {
+
+ public Migration_28_29() {
+ super(28, 29);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("UPDATE `Account` SET `boardsEtag` = NULL");
+ database.execSQL("UPDATE `Board` SET `etag` = NULL");
+ database.execSQL("UPDATE `Stack` SET `etag` = NULL");
+ database.execSQL("UPDATE `Card` SET `etag` = NULL");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_29_30.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_29_30.java
new file mode 100644
index 000000000..665fedeb4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_29_30.java
@@ -0,0 +1,30 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.preference.PreferenceManager;
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/392">Dark mode following system default</a>
+ */
+public class Migration_29_30 extends Migration {
+
+ @NonNull
+ private final Context context;
+
+ public Migration_29_30(@NonNull Context context) {
+ super(29, 30);
+ this.context = context;
+ }
+
+ @Override
+ public void migrate(@NonNull SupportSQLiteDatabase database) {
+ PreferenceManager.getDefaultSharedPreferences(context)
+ .edit()
+ .remove("branding")
+ .apply();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_8_9.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_8_9.java
new file mode 100644
index 000000000..4840dc050
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_8_9.java
@@ -0,0 +1,27 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Adds support for comments
+ */
+public class Migration_8_9 extends Migration {
+
+ public Migration_8_9() {
+ super(8, 9);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("CREATE TABLE `DeckComment` (`localId` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `id` INTEGER, `status` INTEGER NOT NULL, `lastModified` INTEGER, `lastModifiedLocal` INTEGER, `objectId` INTEGER, `actorType` TEXT, `creationDateTime` INTEGER, `actorId` TEXT, `actorDisplayName` TEXT, `message` TEXT, FOREIGN KEY(`objectId`) REFERENCES `Card`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE TABLE `Mention` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `commentId` INTEGER, `mentionId` TEXT, `mentionType` TEXT, `mentionDisplayName` TEXT, FOREIGN KEY(`commentId`) REFERENCES `DeckComment`(`localId`) ON UPDATE NO ACTION ON DELETE CASCADE )");
+ database.execSQL("CREATE INDEX `index_DeckComment_accountId` ON `DeckComment` (`accountId`)");
+ database.execSQL("CREATE INDEX `comment_accID` ON `DeckComment` (`accountId`)");
+ database.execSQL("CREATE UNIQUE INDEX `index_DeckComment_accountId_id` ON `DeckComment` (`accountId`, `id`)");
+ database.execSQL("CREATE INDEX `index_DeckComment_id` ON `DeckComment` (`id`)");
+ database.execSQL("CREATE INDEX `index_DeckComment_lastModifiedLocal` ON `DeckComment` (`lastModifiedLocal`)");
+ database.execSQL("CREATE INDEX `index_DeckComment_objectId` ON `DeckComment` (`objectId`)");
+ database.execSQL("CREATE INDEX `index_Mention_commentId` ON `Mention` (`commentId`)");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_9_10.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_9_10.java
new file mode 100644
index 000000000..59e9200f4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/migration/Migration_9_10.java
@@ -0,0 +1,22 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.migration;
+
+import androidx.room.migration.Migration;
+import androidx.sqlite.db.SupportSQLiteDatabase;
+
+/**
+ * Adds support for account colors, deck server versions and Nextcloud maintenance mode
+ */
+public class Migration_9_10 extends Migration {
+
+ public Migration_9_10() {
+ super(9, 10);
+ }
+
+ @Override
+ public void migrate(SupportSQLiteDatabase database) {
+ database.execSQL("ALTER TABLE `Account` ADD `color` TEXT NOT NULL DEFAULT '#0082c9'");
+ database.execSQL("ALTER TABLE `Account` ADD `textColor` TEXT NOT NULL DEFAULT '#ffffff'");
+ database.execSQL("ALTER TABLE `Account` ADD `serverDeckVersion` TEXT NOT NULL DEFAULT '0.6.4'");
+ database.execSQL("ALTER TABLE `Account` ADD `maintenanceEnabled` INTEGER NOT NULL DEFAULT 0");
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java
index 003bceb26..df2eac222 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java
@@ -1,11 +1,8 @@
package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.MutableLiveData;
-import it.niedermann.nextcloud.deck.DeckLog;
-
/**
* Extends a {@link MutableLiveData} with an error state
*
@@ -29,13 +26,7 @@ public class WrappedLiveData<T> extends MutableLiveData<T> {
}
public void postError(@Nullable Throwable error) {
- postError(error, null);
- }
- public void postError(@Nullable Throwable error, @NonNull T locallyCreatedEntity) {
- if (error == null) {
- DeckLog.warn("Given error is null");
- }
setError(error);
- postValue(locallyCreatedEntity);
+ postValue(null);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java
index 782b6d951..fa5483b26 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java
@@ -35,7 +35,7 @@ public class DataPropagationHelper {
boolean connected = serverAdapter.hasInternetConnection();
if (connected) {
try {
- provider.createOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(new Account(accountId)) {
+ provider.createOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(callback.getAccount()) {
@Override
public void onResponse(T response) {
new Thread(() -> {
@@ -53,11 +53,11 @@ public class DataPropagationHelper {
@Override
public void onError(Throwable throwable) {
super.onError(throwable);
- new Thread(() -> callback.onError(throwable, entity)).start();
+ new Thread(() -> callback.onError(throwable)).start();
}
}, entity);
} catch (Throwable t) {
- callback.onError(t, entity);
+ callback.onError(t);
}
} else {
callback.onResponse(entity);
@@ -90,12 +90,12 @@ public class DataPropagationHelper {
public void onError(Throwable throwable) {
super.onError(throwable);
new Thread(() -> {
- callback.onError(throwable, entity);
+ callback.onError(throwable);
}).start();
}
}, entity);
} catch (Throwable t) {
- callback.onError(t, entity);
+ callback.onError(t);
}
} else {
callback.onResponse(entity);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java
index 17c0de2cf..20421ff1a 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java
@@ -27,11 +27,14 @@ public class SyncHelper {
private long accountId;
private IResponseCallback<Boolean> responseCallback;
private final Instant lastSync;
+ private final boolean etagsEnabled;
public SyncHelper(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, Instant lastSync) {
this.serverAdapter = serverAdapter;
this.dataBaseAdapter = dataBaseAdapter;
this.lastSync = lastSync;
+ // check only once per sync
+ this.etagsEnabled = serverAdapter.isEtagsEnabled();
}
// Sync Server -> App
@@ -45,7 +48,7 @@ public class SyncHelper {
for (T entityFromServer : response) {
if (entityFromServer == null) {
// see https://github.com/stefan-niedermann/nextcloud-deck/issues/574
- DeckLog.error("Skipped null value from server for DataProvider: " + provider.getClass().getSimpleName());
+ DeckLog.error("Skipped null value from server for DataProvider:", provider.getClass().getSimpleName());
continue;
}
entityFromServer.setAccountId(accountId);
@@ -56,10 +59,10 @@ public class SyncHelper {
} else {
//TODO: how to handle deletes? what about archived?
if (existingEntity.getStatus() != DBStatus.UP_TO_DATE.getId()) {
- DeckLog.warn("Conflicting changes on entity: " + existingEntity);
+ DeckLog.warn("Conflicting changes on entity:", existingEntity);
// TODO: what to do?
} else {
- if (entityFromServer.getEtag() != null && entityFromServer.getEtag().equals(existingEntity.getEtag())) {
+ if (etagsEnabled && entityFromServer.getEtag() != null && entityFromServer.getEtag().equals(existingEntity.getEtag())) {
DeckLog.log("[" + provider.getClass().getSimpleName() + "] ETags do match! skipping " + existingEntity.getClass().getSimpleName() + " with localId: " + existingEntity.getLocalId());
continue;
}
@@ -80,7 +83,6 @@ public class SyncHelper {
@Override
public void onError(Throwable throwable) {
- super.onError(throwable);
if (throwable.getClass() == NextcloudHttpRequestFailedException.class) {
NextcloudHttpRequestFailedException requestFailedException = (NextcloudHttpRequestFailedException) throwable;
if (HttpURLConnection.HTTP_NOT_MODIFIED == requestFailedException.getStatusCode()){
@@ -90,7 +92,8 @@ public class SyncHelper {
return;
}
}
- provider.onError(throwable, responseCallback);
+ super.onError(throwable);
+ provider.onError(responseCallback);
responseCallback.onError(throwable);
}
}, lastSync);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java
index 11fb38a66..8e8104692 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java
@@ -129,12 +129,10 @@ public abstract class AbstractSyncDataProvider<T extends IRemoteEntity> {
//do nothing
}
- public void onError(Throwable error, IResponseCallback<Boolean> responseCallback) {
+ public void onError(IResponseCallback<Boolean> responseCallback) {
if (parent != null) {
parent.childDone(this, responseCallback, false);
}
- //TODO: what to do? what side effect would the following have:
-// responseCallback.onError(error);
}
public T applyUpdatesFromRemote(T localEntity, T remoteEntity, Long accountId) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java
index 6dde6b45c..6f908af1a 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java
@@ -65,7 +65,7 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
serverAdapter.getSingleUserData(uid, new IResponseCallback<OcsUser>(account) {
@Override
public void onResponse(OcsUser response) {
- DeckLog.log(response.toString());
+ DeckLog.log(response);
User user = new User();
user.setUid(response.getId());
user.setPrimaryKey(response.getId());
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java
index 781f7b1f4..9442085cb 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java
@@ -1,5 +1,6 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import android.annotation.SuppressLint;
import android.net.Uri;
import java.io.File;
@@ -67,7 +68,7 @@ public class AttachmentDataProvider extends AbstractSyncDataProvider<Attachment>
@Override
public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<Attachment> responder, Attachment entity) {
File file = new File(entity.getLocalPath());
- serverAdapter.uploadAttachment(board.getId(), stack.getId(), card.getId(), entity.getType(), file, new IResponseCallback<Attachment>(responder.getAccount()) {
+ serverAdapter.uploadAttachment(board.getId(), stack.getId(), card.getId(), file, new IResponseCallback<Attachment>(responder.getAccount()) {
@Override
public void onResponse(Attachment response) {
if (file.delete()) {
@@ -77,10 +78,11 @@ public class AttachmentDataProvider extends AbstractSyncDataProvider<Attachment>
}
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
if (!file.delete()) {
- DeckLog.error("Could not delete local file: " + file.getAbsolutePath());
+ DeckLog.error("Could not delete local file:", file.getAbsolutePath());
}
// if (HandledServerErrors.ATTACHMENTS_FILE_ALREADY_EXISTS == HandledServerErrors.fromThrowable(throwable)) {
dataBaseAdapter.deleteAttachment(accountId, entity, false);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java
index 0ceac2cd8..ddb34937f 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java
@@ -2,6 +2,8 @@ package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
import android.annotation.SuppressLint;
+import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
+
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
@@ -22,7 +24,6 @@ import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.model.full.FullStack;
-import it.niedermann.nextcloud.deck.model.ocs.Version;
import it.niedermann.nextcloud.deck.model.propagation.CardUpdate;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
@@ -30,6 +31,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper;
public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
+ private static final String ALREADY_ARCHIVED_INDICATOR = "Operation not allowed. This card is archived.";
protected Board board;
protected FullStack stack;
@@ -42,11 +44,12 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
@Override
public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullCard>> responder, Instant lastSync) {
- List<FullCard> result = new ArrayList<>();
+
if (stack.getCards() == null || stack.getCards().isEmpty()) {
- responder.onResponse(result);
+ responder.onResponse(new ArrayList<>());
return;
}
+ List<FullCard> result = Collections.synchronizedList(new ArrayList<>());
for (Card card : stack.getCards()) {
serverAdapter.getCard(board.getId(), stack.getId(), card.getId(), new IResponseCallback<FullCard>(responder.getAccount()) {
@Override
@@ -142,7 +145,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
syncHelper.doSyncFor(new AttachmentDataProvider(this, board, stack.getStack(), existingEntity, attachments));
- if (callback.getAccount().getServerDeckVersionAsObject().isGreaterOrEqualTo(new Version("1.0.0", 1, 0, 0))) {
+ if (callback.getAccount().getServerDeckVersionAsObject().supportsComments()) {
DeckLog.verbose("Comments - Version is OK, SYNC");
syncHelper.doSyncFor(new DeckCommentsDataProvider(this, existingEntity.getCard()));
} else {
@@ -167,7 +170,27 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
public void updateOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<FullCard> callback, FullCard entity) {
CardUpdate update = toCardUpdate(entity);
update.setStackId(stack.getId());
- serverAdapter.updateCard(board.getId(), stack.getId(), update, callback);
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/787 resolve archiving-conflict
+ serverAdapter.updateCard(board.getId(), stack.getId(), update, new IResponseCallback<FullCard>(callback.getAccount()) {
+ @Override
+ public void onResponse(FullCard response) {
+ callback.onResponse(response);
+ }
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ if (throwable.getClass() == NextcloudHttpRequestFailedException.class &&
+ throwable.getCause() != null &&
+ throwable.getCause().getClass() == IllegalStateException.class &&
+ throwable.getCause().getMessage() != null &&
+ throwable.getCause().getMessage().contains(ALREADY_ARCHIVED_INDICATOR)) {
+ callback.onResponse(entity);
+ } else {
+ callback.onError(throwable);
+ }
+ }
+ });
}
@Override
@@ -205,6 +228,10 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
Account account = callback.getAccount();
for (JoinCardWithLabel changedLabelLocal : changedLabels) {
Card card = dataBaseAdapter.getCardByLocalIdDirectly(account.getId(), changedLabelLocal.getCardId());
+ if (card == null) {
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/683#issuecomment-759116820
+ continue;
+ }
if (this.stack == null) {
stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
} else {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardPropagationDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardPropagationDataProvider.java
index 422a11caa..aa5da8558 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardPropagationDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardPropagationDataProvider.java
@@ -1,8 +1,11 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import android.annotation.SuppressLint;
+
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.model.Board;
import it.niedermann.nextcloud.deck.model.Card;
+import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.propagation.CardUpdate;
@@ -17,9 +20,42 @@ public class CardPropagationDataProvider extends CardDataProvider {
@Override
public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<FullCard> responder, FullCard entity) {
- Card card = entity.getCard();
- card.setStackId(stack.getId());
- serverAdapter.createCard(board.getId(), stack.getId(), card, responder);
+ // make sure, all ancestors are synced properly
+ if (board.getId() == null) {
+ serverAdapter.createBoard(board, new IResponseCallback<FullBoard>(responder.getAccount()) {
+ @Override
+ public void onResponse(FullBoard response) {
+ board.setId(response.getId());
+ board.setEtag(response.getEtag());
+ createOnServer(serverAdapter, dataBaseAdapter, accountId, responder, entity);
+ }
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ responder.onError(throwable);
+ }
+ });
+ } else if (stack.getId() == null) {
+ serverAdapter.createStack(board, stack.getStack(), new IResponseCallback<FullStack>(responder.getAccount()) {
+ @Override
+ public void onResponse(FullStack response) {
+ stack.setId(response.getId());
+ stack.setEtag(response.getEtag());
+ createOnServer(serverAdapter, dataBaseAdapter, accountId, responder, entity);
+ }
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ responder.onError(throwable);
+ }
+ });
+ } else {
+ Card card = entity.getCard();
+ card.setStackId(stack.getId());
+ serverAdapter.createCard(board.getId(), stack.getId(), card, responder);
+ }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java
index caa7c68e6..62354d1cf 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java
@@ -1,5 +1,7 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import android.annotation.SuppressLint;
+
import java.time.Instant;
import java.util.List;
@@ -61,10 +63,11 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
responder.onResponse(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
if (HandledServerErrors.LABELS_TITLE_MUST_BE_UNIQUE == HandledServerErrors.fromThrowable(throwable)) {
- DeckLog.log(throwable.getCause().getMessage() + ": " + entitiy.toString());
+ DeckLog.log(throwable.getCause().getMessage() + ":", entitiy);
dataBaseAdapter.deleteLabelPhysically(entitiy);
responder.onResponse(entitiy);
} else {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java
index b9bfb8b31..5379be6d9 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java
@@ -33,6 +33,7 @@ public class OcsProjectDataProvider extends AbstractSyncDataProvider<OcsProject>
public void onError(Throwable throwable) {
super.onError(throwable);
// dont break the sync!
+ // TODO i got here HTTP 404 once, maybe this should be considered?
DeckLog.logError(throwable);
responder.onResponse(Collections.emptyList());
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/util/RealPathUtils.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/util/RealPathUtils.java
deleted file mode 100644
index f45226ebd..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/util/RealPathUtils.java
+++ /dev/null
@@ -1,94 +0,0 @@
-package it.niedermann.nextcloud.deck.persistence.util;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Build;
-import android.provider.DocumentsContract;
-import android.provider.MediaStore;
-
-import androidx.loader.content.CursorLoader;
-
-import java.io.File;
-
-import it.niedermann.nextcloud.deck.DeckLog;
-
-public class RealPathUtils {
-
- public static File getRealPath(Context context, Uri uri){
- String path = null;
- if (Build.VERSION.SDK_INT < 11)
- path = RealPathUtils.getRealPathFromURI_BelowAPI11(context, uri);
-
- // SDK >= 11 && SDK < 19
- else if (Build.VERSION.SDK_INT < 19)
- path = RealPathUtils.getRealPathFromURI_API11to18(context, uri);
-
- // SDK > 19 (Android 4.4)
- else
- path = RealPathUtils.getRealPathFromURI_API19(context, uri);
- DeckLog.log("File Path: " + path);
- // Get the file instance
- return new File(path);
- }
-
-@SuppressLint("NewApi")
-public static String getRealPathFromURI_API19(Context context, Uri uri){
- String filePath = "";
- String wholeID = DocumentsContract.getDocumentId(uri);
-
- // Split at colon, use second item in the array
- String id;
- if (wholeID.contains(":")){
- id = wholeID.split(":")[1];
- } else {
- id = wholeID;
- }
-
- String[] column = { MediaStore.Images.Media.DATA };
-
- // where id is equal to
- String sel = MediaStore.Images.Media._ID + "=?";
-
- Cursor cursor = context.getContentResolver().query(MediaStore.Images.Media.EXTERNAL_CONTENT_URI,
- column, sel, new String[]{ id }, null);
-
- int columnIndex = cursor.getColumnIndex(column[0]);
-
- if (cursor.moveToFirst()) {
- filePath = cursor.getString(columnIndex);
- }
- cursor.close();
- return filePath;
-}
-
-
-@SuppressLint("NewApi")
-public static String getRealPathFromURI_API11to18(Context context, Uri contentUri) {
- String[] proj = { MediaStore.Images.Media.DATA };
- String result = null;
-
- CursorLoader cursorLoader = new CursorLoader(
- context,
- contentUri, proj, null, null, null);
- Cursor cursor = cursorLoader.loadInBackground();
-
- if(cursor != null){
- int column_index =
- cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
- cursor.moveToFirst();
- result = cursor.getString(column_index);
- }
- return result;
-}
-
-public static String getRealPathFromURI_BelowAPI11(Context context, Uri contentUri){
- String[] proj = { MediaStore.Images.Media.DATA };
- Cursor cursor = context.getContentResolver().query(contentUri, proj, null, null, null);
- int column_index
- = cursor.getColumnIndexOrThrow(MediaStore.Images.Media.DATA);
- cursor.moveToFirst();
- return cursor.getString(column_index);
-}
-} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java
index 3ded31bb7..bcf5340d8 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java
@@ -1,20 +1,17 @@
package it.niedermann.nextcloud.deck.ui;
import android.annotation.SuppressLint;
+import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteConstraintException;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.content.ContextCompat;
-import androidx.core.graphics.drawable.DrawableCompat;
import androidx.preference.PreferenceManager;
import com.nextcloud.android.sso.AccountImporter;
@@ -89,12 +86,6 @@ public class ImportAccountActivity extends AppCompatActivity {
AccountImporter.requestAndroidAccountPermissionsAndPickAccount(this);
}
});
-
- if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
- Drawable wrapDrawable = DrawableCompat.wrap(binding.progressCircular.getIndeterminateDrawable());
- DrawableCompat.setTint(wrapDrawable, ContextCompat.getColor(this, R.color.defaultBrand));
- binding.progressCircular.setIndeterminateDrawable(DrawableCompat.unwrap(wrapDrawable));
- }
}
@Override
@@ -137,7 +128,7 @@ public class ImportAccountActivity extends AppCompatActivity {
} else {
// Remember last account - THIS HAS TO BE DONE SYNCHRONOUSLY
SharedPreferences.Editor editor = sharedPreferences.edit();
- DeckLog.log("--- Write: shared_preference_last_account" + " | " + createdAccount.getId());
+ DeckLog.log("--- Write: shared_preference_last_account | ", createdAccount.getId());
editor.putLong(sharedPreferenceLastAccount, createdAccount.getId());
editor.commit();
@@ -145,7 +136,7 @@ public class ImportAccountActivity extends AppCompatActivity {
@Override
public void onResponse(Capabilities response) {
if (!response.isMaintenanceEnabled()) {
- if (response.getDeckVersion().isSupported(getApplicationContext())) {
+ if (response.getDeckVersion().isSupported()) {
syncManager.synchronize(new IResponseCallback<Boolean>(account) {
@Override
public void onResponse(Boolean response) {
@@ -216,7 +207,7 @@ public class ImportAccountActivity extends AppCompatActivity {
DeckLog.log("Rolling back account creation for " + accountId);
syncManager.deleteAccount(accountId);
SharedPreferences.Editor editor = sharedPreferences.edit();
- DeckLog.log("--- Remove: shared_preference_last_account" + " | " + accountId);
+ DeckLog.log("--- Remove: shared_preference_last_account |", accountId);
editor.remove(sharedPreferenceLastAccount);
editor.commit(); // Has to be done synchronously
runOnUiThread(() -> binding.addButton.setEnabled(true));
@@ -251,4 +242,8 @@ public class ImportAccountActivity extends AppCompatActivity {
editor.putBoolean(prefKeyWifiOnly, originalWifiOnlyValue);
editor.apply();
}
+
+ public static Intent createIntent(@NonNull Context context) {
+ return new Intent(context, ImportAccountActivity.class);
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java
index d7e726a7d..58f08237e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java
@@ -11,9 +11,7 @@ import android.net.Network;
import android.net.NetworkInfo;
import android.net.NetworkRequest;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
-import android.preference.PreferenceManager;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
@@ -22,10 +20,10 @@ import android.widget.PopupMenu;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.annotation.UiThread;
import androidx.appcompat.app.ActionBarDrawerToggle;
import androidx.appcompat.app.AppCompatDelegate;
+import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.core.util.Pair;
@@ -34,6 +32,7 @@ import androidx.core.view.ViewCompat;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
+import androidx.preference.PreferenceManager;
import androidx.viewpager2.widget.ViewPager2;
import com.bumptech.glide.Glide;
@@ -58,6 +57,7 @@ import it.niedermann.nextcloud.deck.DeckApplication;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.databinding.ActivityMainBinding;
import it.niedermann.nextcloud.deck.databinding.NavHeaderMainBinding;
import it.niedermann.nextcloud.deck.exceptions.OfflineException;
@@ -107,7 +107,7 @@ import static it.niedermann.nextcloud.deck.DeckApplication.NO_STACK_ID;
import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentAccountId;
import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentBoardId;
import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentStackId;
-import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccountId;
+import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccount;
import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentBoardId;
import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentStackId;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
@@ -132,9 +132,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
private FilterViewModel filterViewModel;
private PickStackViewModel pickStackViewModel;
- protected static final int ACTIVITY_ABOUT = 1;
protected static final int ACTIVITY_SETTINGS = 2;
- public static final int ACTIVITY_MANAGE_ACCOUNTS = 4;
@NonNull
protected List<Account> accountsList = new ArrayList<>();
@@ -158,8 +156,6 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
private boolean firstAccountAdded = false;
private ConnectivityManager.NetworkCallback networkCallback;
- private String accountAlreadyAdded;
- private String urlFragmentUpdateDeck;
private String addList;
private String addBoard;
@Nullable
@@ -186,8 +182,6 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
addList = getString(R.string.add_list);
addBoard = getString(R.string.add_board);
- accountAlreadyAdded = getString(R.string.account_already_added);
- urlFragmentUpdateDeck = getString(R.string.url_fragment_update_deck);
setSupportActionBar(binding.toolbar);
@@ -198,11 +192,26 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
binding.navigationView.setNavigationItemSelectedListener(this);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
+ DeckApplication.readCurrentAccountColor().observe(this, (accountColor) -> {
+ headerBinding.headerView.setBackgroundColor(accountColor);
+ @ColorInt final int headerTextColor = contrastRatioIsSufficientBigAreas(accountColor, Color.WHITE) ? Color.WHITE : Color.BLACK;
+ DrawableCompat.setTint(headerBinding.logo.getDrawable(), headerTextColor);
+ DrawableCompat.setTint(headerBinding.copyDebugLogs.getDrawable(), headerTextColor);
+ });
+
+ mainViewModel.isDebugModeEnabled().observe(this, (enabled) -> headerBinding.copyDebugLogs.setVisibility(enabled ? View.VISIBLE : View.GONE));
+ headerBinding.copyDebugLogs.setOnClickListener((v) -> {
+ try {
+ DeckLog.shareLogAsFile(this);
+ } catch (Exception e) {
+ ExceptionDialogFragment.newInstance(e, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ }
+ });
switchMap(mainViewModel.hasAccounts(), hasAccounts -> {
if (hasAccounts) {
return mainViewModel.readAccounts();
} else {
- startActivityForResult(new Intent(this, ImportAccountActivity.class), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
+ startActivityForResult(ImportAccountActivity.createIntent(this), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
return null;
}
}).observe(this, (List<Account> accounts) -> {
@@ -221,21 +230,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
mainViewModel.setCurrentAccount(account);
if (!firstAccountAdded) {
DeckLog.info("Syncing the current account on app start");
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- registerAutoSyncOnNetworkAvailable();
- } else {
- mainViewModel.synchronize(new IResponseCallback<Boolean>(mainViewModel.getCurrentAccount()) {
- @Override
- public void onResponse(Boolean response) {
- }
-
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- showSyncFailedSnackbar(throwable);
- }
- });
- }
+ registerAutoSyncOnNetworkAvailable();
firstAccountAdded = false;
}
break;
@@ -246,7 +241,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
SingleAccountHelper.setCurrentAccount(getApplicationContext(), mainViewModel.getCurrentAccount().getName());
mainViewModel.recreateSyncManager();
- saveCurrentAccountId(this, mainViewModel.getCurrentAccount().getId());
+ saveCurrentAccount(this, mainViewModel.getCurrentAccount());
if (mainViewModel.getCurrentAccount().isMaintenanceEnabled()) {
refreshCapabilities(mainViewModel.getCurrentAccount());
}
@@ -264,18 +259,21 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
}
boardsList = boards;
+ Board currentBoard = null;
if (boardsList.size() > 0) {
boolean currentBoardIdWasInList = false;
for (int i = 0; i < boardsList.size(); i++) {
if (lastBoardId == boardsList.get(i).getLocalId() || lastBoardId == NO_BOARD_ID) {
- setCurrentBoard(boardsList.get(i));
+ currentBoard = boardsList.get(i);
+ setCurrentBoard(currentBoard);
currentBoardIdWasInList = true;
break;
}
}
if (!currentBoardIdWasInList) {
- setCurrentBoard(boardsList.get(0));
+ currentBoard = boardsList.get(0);
+ setCurrentBoard(currentBoard);
}
binding.filter.setOnClickListener((v) -> FilterDialogFragment.newInstance().show(getSupportFragmentManager(), EditStackDialogFragment.class.getCanonicalName()));
@@ -286,13 +284,14 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
binding.filter.setOnClickListener(null);
}
+ final Board finalCurrentBoard = currentBoard;
if (hasArchivedBoardsLiveData != null && hasArchivedBoardsLiveDataObserver != null) {
hasArchivedBoardsLiveData.removeObserver(hasArchivedBoardsLiveDataObserver);
}
hasArchivedBoardsLiveData = mainViewModel.hasArchivedBoards(currentAccount.getId());
hasArchivedBoardsLiveDataObserver = (hasArchivedBoards) -> {
mainViewModel.setCurrentAccountHasArchivedBoards(Boolean.TRUE.equals(hasArchivedBoards));
- inflateBoardMenu();
+ inflateBoardMenu(finalCurrentBoard);
};
hasArchivedBoardsLiveData.observe(this, hasArchivedBoardsLiveDataObserver);
};
@@ -300,21 +299,21 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
Glide
.with(binding.accountSwitcher.getContext())
- .load(currentAccount.getAvatarUrl(64))
+ .load(currentAccount.getAvatarUrl(binding.accountSwitcher.getWidth()))
.placeholder(R.drawable.ic_baseline_account_circle_24)
.error(R.drawable.ic_baseline_account_circle_24)
.apply(RequestOptions.circleCropTransform())
.into(binding.accountSwitcher);
- DeckLog.verbose("Displaying maintenance mode info for " + mainViewModel.getCurrentAccount().getName() + ": " + mainViewModel.getCurrentAccount().isMaintenanceEnabled());
+ DeckLog.verbose("Displaying maintenance mode info for", mainViewModel.getCurrentAccount().getName() + ":" + mainViewModel.getCurrentAccount().isMaintenanceEnabled());
binding.infoBox.setVisibility(mainViewModel.getCurrentAccount().isMaintenanceEnabled() ? View.VISIBLE : View.GONE);
if (mainViewModel.isCurrentAccountIsSupportedVersion()) {
binding.infoBoxVersionNotSupported.setVisibility(View.GONE);
} else {
- binding.infoBoxVersionNotSupportedText.setText(getString(R.string.info_box_version_not_supported, mainViewModel.getCurrentAccount().getServerDeckVersion(), Version.minimumSupported(this).getOriginalVersion()));
+ binding.infoBoxVersionNotSupportedText.setText(getString(R.string.info_box_version_not_supported, mainViewModel.getCurrentAccount().getServerDeckVersion(), Version.minimumSupported().getOriginalVersion()));
binding.infoBoxVersionNotSupportedText.setOnClickListener((v) -> {
Intent openURL = new Intent(Intent.ACTION_VIEW);
- openURL.setData(Uri.parse(mainViewModel.getCurrentAccount().getUrl() + urlFragmentUpdateDeck));
+ openURL.setData(Uri.parse(mainViewModel.getCurrentAccount().getUrl() + getString(R.string.url_fragment_update_deck)));
startActivity(openURL);
});
binding.infoBoxVersionNotSupported.setVisibility(View.VISIBLE);
@@ -329,7 +328,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
dragAndDrop.register(binding.viewPager, binding.stackTitles, getSupportFragmentManager());
dragAndDrop.addItemMovedByDragListener((movedCard, stackId, position) -> {
mainViewModel.reorder(mainViewModel.getCurrentAccount().getId(), movedCard, stackId, position);
- DeckLog.info("Card \"" + movedCard.getCard().getTitle() + "\" was moved to Stack " + stackId + " on position " + position);
+ DeckLog.info("Card", movedCard.getCard().getTitle(), "was moved to Stack", stackId, "on position", position);
});
@@ -429,10 +428,6 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
applyBrandToFAB(mainColor, binding.fab);
// TODO We assume, that the background of the spinner is always white
binding.swipeRefreshLayout.setColorSchemeColors(contrastRatioIsSufficient(Color.WHITE, mainColor) ? mainColor : DeckApplication.isDarkTheme(this) ? Color.DKGRAY : colorAccent);
- headerBinding.headerView.setBackgroundColor(mainColor);
- @ColorInt final int headerTextColor = contrastRatioIsSufficientBigAreas(mainColor, Color.WHITE) ? Color.WHITE : Color.BLACK;
- DrawableCompat.setTint(headerBinding.logo.getDrawable(), headerTextColor);
- headerBinding.appName.setTextColor(headerTextColor);
DrawableCompat.setTint(binding.filterIndicator.getDrawable(), getSecondaryForegroundColorDependingOnTheme(this, mainColor));
}
@@ -446,27 +441,35 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@Override
public void onCreateStack(String stackName) {
- DeckLog.info("Create Stack in account " + mainViewModel.getCurrentAccount().getName() + " on board " + mainViewModel.getCurrentBoardLocalId());
- WrappedLiveData<FullStack> createLiveData = mainViewModel.createStack(mainViewModel.getCurrentAccount().getId(), stackName, mainViewModel.getCurrentBoardLocalId());
- observeOnce(createLiveData, this, (fullStack) -> {
- if (createLiveData.hasError()) {
- final Throwable error = createLiveData.getError();
- assert error != null;
- BrandedSnackbar.make(binding.coordinatorLayout, Objects.requireNonNull(error.getLocalizedMessage()), Snackbar.LENGTH_LONG)
+ DeckLog.info("Create Stack in account", mainViewModel.getCurrentAccount().getName(), "on board", mainViewModel.getCurrentBoardLocalId());
+ mainViewModel.createStack(mainViewModel.getCurrentAccount().getId(), stackName, mainViewModel.getCurrentBoardLocalId(), new ResponseCallback<FullStack>() {
+ @Override
+ public void onResponse(FullStack response) {
+ runOnUiThread(() -> binding.viewPager.setCurrentItem(stackAdapter.getItemCount()));
+ }
+
+ @Override
+ public void onError(Throwable error) {
+ ResponseCallback.super.onError(error);
+ runOnUiThread(() -> BrandedSnackbar.make(binding.coordinatorLayout, Objects.requireNonNull(error.getLocalizedMessage()), Snackbar.LENGTH_LONG)
.setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(error, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
- .show();
- } else {
- binding.viewPager.setCurrentItem(stackAdapter.getItemCount());
+ .show());
}
});
}
@Override
public void onUpdateStack(long localStackId, String stackName) {
- final WrappedLiveData<FullStack> liveData = mainViewModel.updateStackTitle(localStackId, stackName);
- observeOnce(liveData, this, (v) -> {
- if (liveData.hasError()) {
- ExceptionDialogFragment.newInstance(liveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.updateStackTitle(localStackId, stackName, new ResponseCallback<FullStack>() {
+ @Override
+ public void onResponse(FullStack response) {
+ DeckLog.info("Successfully updated", Stack.class.getSimpleName(), "to", stackName);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
}
});
}
@@ -481,30 +484,42 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
boardToCreate.setPermissionEdit(true);
boardToCreate.setPermissionManage(true);
- final WrappedLiveData<FullBoard> createLiveData = mainViewModel.createBoard(mainViewModel.getCurrentAccount().getId(), boardToCreate);
- observeOnce(createLiveData, this, (createdBoard) -> {
- if (createLiveData.hasError()) {
- BrandedSnackbar.make(binding.coordinatorLayout, R.string.synchronization_failed, Snackbar.LENGTH_LONG)
- .setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(createLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
- .show();
+ mainViewModel.createBoard(mainViewModel.getCurrentAccount().getId(), boardToCreate, new ResponseCallback<FullBoard>() {
+ @Override
+ public void onResponse(FullBoard response) {
+ runOnUiThread(() -> {
+ if (response != null) {
+ boardsList.add(response.getBoard());
+ setCurrentBoard(response.getBoard());
+ inflateBoardMenu(response.getBoard());
+ EditStackDialogFragment.newInstance(NO_STACK_ID).show(getSupportFragmentManager(), addList);
+ }
+ boardsLiveData.observe(MainActivity.this, boardsLiveDataObserver);
+ });
}
- if (createdBoard != null && !createLiveData.hasError()) {
- boardsList.add(createdBoard.getBoard());
- setCurrentBoard(createdBoard.getBoard());
- inflateBoardMenu();
- EditStackDialogFragment.newInstance(NO_STACK_ID).show(getSupportFragmentManager(), addList);
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> BrandedSnackbar.make(binding.coordinatorLayout, R.string.synchronization_failed, Snackbar.LENGTH_LONG)
+ .setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
+ .show());
}
- boardsLiveData.observe(this, boardsLiveDataObserver);
});
}
@Override
public void onUpdateBoard(FullBoard fullBoard) {
- final WrappedLiveData<FullBoard> updateLiveData = mainViewModel.updateBoard(fullBoard);
- observeOnce(updateLiveData, this, (next) -> {
- if (updateLiveData.hasError()) {
- ExceptionDialogFragment.newInstance(updateLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.updateBoard(fullBoard, new ResponseCallback<FullBoard>() {
+ @Override
+ public void onResponse(FullBoard response) {
+ DeckLog.info("Successfully updated board", fullBoard.getBoard().getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
}
});
}
@@ -517,18 +532,20 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
binding.swipeRefreshLayout.setRefreshing(false);
} else {
// If we notice after updating the capabilities, that the new version is not supported, but it was previously, recreate the activity to make sure all elements are disabled properly
- if (mainViewModel.getCurrentAccount().getServerDeckVersionAsObject().isSupported(MainActivity.this) && !response.getDeckVersion().isSupported(MainActivity.this)) {
- recreate();
+ if (mainViewModel.getCurrentAccount().getServerDeckVersionAsObject().isSupported() && !response.getDeckVersion().isSupported()) {
+ ActivityCompat.recreate(MainActivity.this);
}
}
}
@Override
public void onError(Throwable throwable) {
+ binding.swipeRefreshLayout.setRefreshing(false);
if (throwable instanceof OfflineException) {
DeckLog.info("Cannot refresh capabilities because device is offline.");
} else {
super.onError(throwable);
+ ExceptionDialogFragment.newInstance(throwable, account).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
}
}
});
@@ -552,6 +569,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
lastBoardId = board.getLocalId();
saveCurrentBoardId(this, mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId());
+ binding.navigationView.setCheckedItem(boardsList.indexOf(board));
binding.toolbar.setTitle(board.getTitle());
@@ -637,21 +655,22 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
}
@UiThread
- protected void inflateBoardMenu() {
+ protected void inflateBoardMenu(@Nullable Board currentBoard) {
binding.navigationView.setItemIconTintList(null);
Menu menu = binding.navigationView.getMenu();
menu.clear();
- DrawerMenuUtil.inflateBoards(this, menu, this.boardsList, mainViewModel.currentAccountHasArchivedBoards(), mainViewModel.getCurrentAccount().getServerDeckVersionAsObject().isSupported(this));
+ DrawerMenuUtil.inflateBoards(this, menu, this.boardsList, mainViewModel.currentAccountHasArchivedBoards(), mainViewModel.getCurrentAccount().getServerDeckVersionAsObject().isSupported());
+ binding.navigationView.setCheckedItem(boardsList.indexOf(currentBoard));
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch (item.getItemId()) {
case MENU_ID_ABOUT:
- startActivityForResult(AboutActivity.createIntent(this, mainViewModel.getCurrentAccount()), MainActivity.ACTIVITY_ABOUT);
+ startActivity(AboutActivity.createIntent(this, mainViewModel.getCurrentAccount()));
break;
case MENU_ID_SETTINGS:
- startActivityForResult(new Intent(this, SettingsActivity.class), MainActivity.ACTIVITY_SETTINGS);
+ startActivityForResult(SettingsActivity.createIntent(this), MainActivity.ACTIVITY_SETTINGS);
break;
case MENU_ID_ADD_BOARD:
EditBoardDialogFragment.newInstance().show(getSupportFragmentManager(), addBoard);
@@ -681,10 +700,18 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
: R.string.do_you_want_to_archive_all_cards_of_the_list, stack.getTitle()))
.setPositiveButton(R.string.simple_archive, (dialog, whichButton) -> {
final FilterInformation filterInformation = filterViewModel.getFilterInformation().getValue();
- final WrappedLiveData<Void> archiveStackLiveData = mainViewModel.archiveCardsInStack(mainViewModel.getCurrentAccount().getId(), stackLocalId, filterInformation == null ? new FilterInformation() : filterInformation);
- observeOnce(archiveStackLiveData, this, (result) -> {
- if (archiveStackLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(archiveStackLiveData.getError())) {
- ExceptionDialogFragment.newInstance(archiveStackLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.archiveCardsInStack(mainViewModel.getCurrentAccount().getId(), stackLocalId, filterInformation == null ? new FilterInformation() : filterInformation, new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully archived all cards in stack local id", stackLocalId);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
+ }
}
});
})
@@ -761,7 +788,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
switch (requestCode) {
case MainActivity.ACTIVITY_SETTINGS:
if (resultCode == RESULT_OK) {
- recreate();
+ ActivityCompat.recreate(this);
}
break;
case ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT:
@@ -787,17 +814,19 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@Override
public void onResponse(Capabilities response) {
if (!response.isMaintenanceEnabled()) {
- if (response.getDeckVersion().isSupported(getApplicationContext())) {
+ if (response.getDeckVersion().isSupported()) {
runOnUiThread(() -> {
mainViewModel.setSyncManager(importSyncManager);
- mainViewModel.setCurrentAccount(account);
final Snackbar importSnackbar = BrandedSnackbar.make(binding.coordinatorLayout, R.string.account_is_getting_imported, Snackbar.LENGTH_INDEFINITE);
importSnackbar.show();
- importSyncManager.synchronize(new IResponseCallback<Boolean>(mainViewModel.getCurrentAccount()) {
+ importSyncManager.synchronize(new IResponseCallback<Boolean>(createdAccount) {
@Override
public void onResponse(Boolean response) {
importSnackbar.dismiss();
+ runOnUiThread(() -> BrandedSnackbar.make(binding.coordinatorLayout, getString(R.string.account_imported), Snackbar.LENGTH_LONG)
+ .setAction(R.string.simple_switch, (a) -> mainViewModel.setCurrentAccount(createdAccount))
+ .show());
}
@Override
@@ -811,14 +840,14 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
});
});
} else {
- DeckLog.warn("Cannot import account because server version is too low (" + response.getDeckVersion() + "). Minimum server version is currently " + Version.minimumSupported(getApplicationContext()));
+ DeckLog.warn("Cannot import account because server version is too low (" + response.getDeckVersion() + "). Minimum server version is currently", Version.minimumSupported());
runOnUiThread(() -> new BrandedAlertDialogBuilder(MainActivity.this)
.setTitle(R.string.update_deck)
.setMessage(getString(R.string.deck_outdated_please_update, response.getDeckVersion().getOriginalVersion()))
.setNegativeButton(R.string.simple_discard, null)
.setPositiveButton(R.string.simple_update, (dialog, whichButton) -> {
final Intent openURL = new Intent(Intent.ACTION_VIEW);
- openURL.setData(Uri.parse(createdAccount.getUrl() + urlFragmentUpdateDeck));
+ openURL.setData(Uri.parse(createdAccount.getUrl() + getString(R.string.url_fragment_update_deck)));
startActivity(openURL);
finish();
}).show());
@@ -855,7 +884,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
final Throwable error = accountLiveData.getError();
if (error instanceof SQLiteConstraintException) {
DeckLog.warn("Account already added");
- BrandedSnackbar.make(binding.coordinatorLayout, accountAlreadyAdded, Snackbar.LENGTH_LONG).show();
+ BrandedSnackbar.make(binding.coordinatorLayout, R.string.account_already_added, Snackbar.LENGTH_LONG).show();
} else {
ExceptionDialogFragment.newInstance(error, createdAccount).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
}
@@ -877,7 +906,6 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
}
}
- @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
private void registerAutoSyncOnNetworkAvailable() {
final ConnectivityManager connectivityManager = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
@@ -919,10 +947,18 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@Override
public void onStackDeleted(Long stackLocalId) {
long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
- final WrappedLiveData<Void> deleteStackLiveData = mainViewModel.deleteStack(mainViewModel.getCurrentAccount().getId(), stackId, mainViewModel.getCurrentBoardLocalId());
- observeOnce(deleteStackLiveData, this, (v) -> {
- if (deleteStackLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteStackLiveData.getError())) {
- ExceptionDialogFragment.newInstance(deleteStackLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.deleteStack(mainViewModel.getCurrentAccount().getId(), stackId, mainViewModel.getCurrentBoardLocalId(), new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully deleted stack with local id", stackLocalId, "and remote id", stackId);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
+ }
}
});
}
@@ -942,10 +978,18 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
}
}
- final WrappedLiveData<Void> deleteLiveData = mainViewModel.deleteBoard(board);
- observeOnce(deleteLiveData, this, (next) -> {
- if (deleteLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteLiveData.getError())) {
- ExceptionDialogFragment.newInstance(deleteLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.deleteBoard(board, new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully deleted board", board.getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
+ }
}
});
@@ -968,10 +1012,16 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@Override
public void onArchive(@NonNull Board board) {
- final WrappedLiveData<FullBoard> liveData = mainViewModel.archiveBoard(board);
- observeOnce(liveData, this, (fullBoard) -> {
- if (liveData.hasError()) {
- ExceptionDialogFragment.newInstance(liveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.archiveBoard(board, new ResponseCallback<FullBoard>() {
+ @Override
+ public void onResponse(FullBoard response) {
+ DeckLog.info("Successfully archived board", board.getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
}
});
}
@@ -987,18 +1037,27 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
binding.drawerLayout.closeDrawer(GravityCompat.START);
final Snackbar snackbar = BrandedSnackbar.make(binding.coordinatorLayout, getString(R.string.cloning_board, board.getTitle()), Snackbar.LENGTH_INDEFINITE);
snackbar.show();
- final WrappedLiveData<FullBoard> liveData = mainViewModel.cloneBoard(board.getAccountId(), board.getLocalId(), board.getAccountId(), board.getColor(), checkedItems[0]);
- observeOnce(liveData, this, (fullBoard -> {
- snackbar.dismiss();
- if (liveData.hasError()) {
- ExceptionDialogFragment.newInstance(liveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- } else {
- setCurrentBoard(fullBoard.getBoard());
- BrandedSnackbar.make(binding.coordinatorLayout, getString(R.string.successfully_cloned_board, fullBoard.getBoard().getTitle()), Snackbar.LENGTH_LONG)
- .setAction(R.string.edit, v -> EditBoardDialogFragment.newInstance(fullBoard.getLocalId()).show(getSupportFragmentManager(), EditBoardDialogFragment.class.getSimpleName()))
- .show();
+ mainViewModel.cloneBoard(board.getAccountId(), board.getLocalId(), board.getAccountId(), board.getColor(), checkedItems[0], new ResponseCallback<FullBoard>() {
+ @Override
+ public void onResponse(FullBoard response) {
+ runOnUiThread(() -> {
+ snackbar.dismiss();
+ setCurrentBoard(response.getBoard());
+ BrandedSnackbar.make(binding.coordinatorLayout, getString(R.string.successfully_cloned_board, response.getBoard().getTitle()), Snackbar.LENGTH_LONG)
+ .setAction(R.string.edit, v -> EditBoardDialogFragment.newInstance(response.getLocalId()).show(getSupportFragmentManager(), EditBoardDialogFragment.class.getSimpleName()))
+ .show();
+ });
}
- }));
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> {
+ snackbar.dismiss();
+ ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ });
+ }
+ });
})
.setNeutralButton(android.R.string.cancel, null)
.show();
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java
index ac244b88e..53443d07c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java
@@ -9,11 +9,15 @@ import androidx.core.util.Pair;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
+import androidx.preference.PreferenceManager;
import java.io.File;
import java.util.List;
+import it.niedermann.android.sharedpreferences.SharedPreferenceBooleanLiveData;
+import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
@@ -48,6 +52,10 @@ public class MainViewModel extends AndroidViewModel {
this.syncManager = new SyncManager(application);
}
+ public LiveData<Boolean> isDebugModeEnabled() {
+ return new SharedPreferenceBooleanLiveData(PreferenceManager.getDefaultSharedPreferences(getApplication()), getApplication().getString(R.string.pref_key_debugging), false);
+ }
+
public Account getCurrentAccount() {
return currentAccount.getValue();
}
@@ -58,7 +66,7 @@ public class MainViewModel extends AndroidViewModel {
public void setCurrentAccount(Account currentAccount) {
this.currentAccount.setValue(currentAccount);
- this.currentAccountIsSupportedVersion = currentAccount.getServerDeckVersionAsObject().isSupported(getApplication().getApplicationContext());
+ this.currentAccountIsSupportedVersion = currentAccount.getServerDeckVersionAsObject().isSupported();
}
public void setCurrentBoard(@NonNull Board currentBoard) {
@@ -127,12 +135,12 @@ public class MainViewModel extends AndroidViewModel {
return syncManager.readAccounts();
}
- public WrappedLiveData<FullBoard> createBoard(long accountId, @NonNull Board board) {
- return syncManager.createBoard(accountId, board);
+ public void createBoard(long accountId, @NonNull Board board, @NonNull ResponseCallback<FullBoard> callback) {
+ syncManager.createBoard(accountId, board, callback);
}
- public WrappedLiveData<FullBoard> updateBoard(@NonNull FullBoard board) {
- return syncManager.updateBoard(board);
+ public void updateBoard(@NonNull FullBoard board, @NonNull ResponseCallback<FullBoard> callback) {
+ syncManager.updateBoard(board, callback);
}
public LiveData<List<Board>> getBoards(long accountId, boolean archived) {
@@ -143,64 +151,64 @@ public class MainViewModel extends AndroidViewModel {
return syncManager.getFullBoardById(accountId, localId);
}
- public WrappedLiveData<FullBoard> archiveBoard(@NonNull Board board) {
- return syncManager.archiveBoard(board);
+ public void archiveBoard(@NonNull Board board, @NonNull ResponseCallback<FullBoard> callback) {
+ syncManager.archiveBoard(board, callback);
}
- public WrappedLiveData<FullBoard> dearchiveBoard(@NonNull Board board) {
- return syncManager.dearchiveBoard(board);
+ public void dearchiveBoard(@NonNull Board board, @NonNull ResponseCallback<FullBoard> callback) {
+ syncManager.dearchiveBoard(board, callback);
}
- public WrappedLiveData<FullBoard> cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, @ColorInt int targetBoardColor, boolean cloneCards) {
- return syncManager.cloneBoard(originAccountId, originBoardLocalId, targetAccountId, targetBoardColor, cloneCards);
+ public void cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, @ColorInt int targetBoardColor, boolean cloneCards, @NonNull ResponseCallback<FullBoard> callback) {
+ syncManager.cloneBoard(originAccountId, originBoardLocalId, targetAccountId, targetBoardColor, cloneCards, callback);
}
- public WrappedLiveData<Void> deleteBoard(@NonNull Board board) {
- return syncManager.deleteBoard(board);
+ public void deleteBoard(@NonNull Board board, @NonNull ResponseCallback<Void> callback) {
+ syncManager.deleteBoard(board, callback);
}
public LiveData<Boolean> hasArchivedBoards(long accountId) {
return syncManager.hasArchivedBoards(accountId);
}
- public WrappedLiveData<AccessControl> createAccessControl(long accountId, AccessControl entity) {
- return syncManager.createAccessControl(accountId, entity);
+ public void createAccessControl(long accountId, @NonNull AccessControl entity, @NonNull ResponseCallback<AccessControl> callback) {
+ syncManager.createAccessControl(accountId, entity, callback);
}
- public WrappedLiveData<AccessControl> updateAccessControl(@NonNull AccessControl entity) {
- return syncManager.updateAccessControl(entity);
+ public void updateAccessControl(@NonNull AccessControl entity, @NonNull ResponseCallback<AccessControl> callback) {
+ syncManager.updateAccessControl(entity, callback);
}
public LiveData<List<AccessControl>> getAccessControlByLocalBoardId(long accountId, Long id) {
return syncManager.getAccessControlByLocalBoardId(accountId, id);
}
- public WrappedLiveData<Void> deleteAccessControl(@NonNull AccessControl entity) {
- return syncManager.deleteAccessControl(entity);
+ public void deleteAccessControl(@NonNull AccessControl entity, @NonNull ResponseCallback<Void> callback) {
+ syncManager.deleteAccessControl(entity, callback);
}
- public WrappedLiveData<Label> createLabel(long accountId, Label label, long localBoardId) {
- return syncManager.createLabel(accountId, label, localBoardId);
+ public void createLabel(long accountId, Label label, long localBoardId, @NonNull ResponseCallback<Label> callback) {
+ syncManager.createLabel(accountId, label, localBoardId, callback);
}
public LiveData<Integer> countCardsWithLabel(long localLabelId) {
return syncManager.countCardsWithLabel(localLabelId);
}
- public WrappedLiveData<Label> updateLabel(@NonNull Label label) {
- return syncManager.updateLabel(label);
+ public void updateLabel(@NonNull Label label, @NonNull ResponseCallback<Label> callback) {
+ syncManager.updateLabel(label, callback);
}
- public WrappedLiveData<Void> deleteLabel(@NonNull Label label) {
- return syncManager.deleteLabel(label);
+ public void deleteLabel(@NonNull Label label, @NonNull ResponseCallback<Void> callback) {
+ syncManager.deleteLabel(label, callback);
}
public LiveData<List<Stack>> getStacksForBoard(long accountId, long localBoardId) {
return syncManager.getStacksForBoard(accountId, localBoardId);
}
- public WrappedLiveData<FullStack> createStack(long accountId, @NonNull String title, long boardLocalId) {
- return syncManager.createStack(accountId, title, boardLocalId);
+ public void createStack(long accountId, @NonNull String title, long boardLocalId, @NonNull ResponseCallback<FullStack> callback) {
+ syncManager.createStack(accountId, title, boardLocalId, callback);
}
public LiveData<FullStack> getStack(long accountId, long localStackId) {
@@ -211,12 +219,12 @@ public class MainViewModel extends AndroidViewModel {
syncManager.swapStackOrder(accountId, boardLocalId, stackLocalIds);
}
- public WrappedLiveData<FullStack> updateStackTitle(long localStackId, @NonNull String newTitle) {
- return syncManager.updateStackTitle(localStackId, newTitle);
+ public void updateStackTitle(long localStackId, @NonNull String newTitle, @NonNull ResponseCallback<FullStack> callback) {
+ syncManager.updateStackTitle(localStackId, newTitle, callback);
}
- public WrappedLiveData<Void> deleteStack(long accountId, long stackLocalId, long boardLocalId) {
- return syncManager.deleteStack(accountId, stackLocalId, boardLocalId);
+ public void deleteStack(long accountId, long stackLocalId, long boardLocalId, @NonNull ResponseCallback<Void> callback) {
+ syncManager.deleteStack(accountId, stackLocalId, boardLocalId, callback);
}
public void reorder(long accountId, @NonNull FullCard movedCard, long newStackId, int newIndex) {
@@ -227,8 +235,8 @@ public class MainViewModel extends AndroidViewModel {
return syncManager.countCardsInStack(accountId, localStackId);
}
- public WrappedLiveData<Void> archiveCardsInStack(long accountId, long stackLocalId, @NonNull FilterInformation filterInformation) {
- return syncManager.archiveCardsInStack(accountId, stackLocalId, filterInformation);
+ public void archiveCardsInStack(long accountId, long stackLocalId, @NonNull FilterInformation filterInformation, @NonNull ResponseCallback<Void> callback) {
+ syncManager.archiveCardsInStack(accountId, stackLocalId, filterInformation, callback);
}
public WrappedLiveData<FullCard> updateCard(@NonNull FullCard fullCard) {
@@ -239,8 +247,8 @@ public class MainViewModel extends AndroidViewModel {
syncManager.addCommentToCard(accountId, cardId, comment);
}
- public WrappedLiveData<Attachment> addAttachmentToCard(long accountId, long localCardId, @NonNull String mimeType, @NonNull File file) {
- return syncManager.addAttachmentToCard(accountId, localCardId, mimeType, file);
+ public void addAttachmentToCard(long accountId, long localCardId, @NonNull String mimeType, @NonNull File file, @NonNull ResponseCallback<Attachment> callback) {
+ syncManager.addAttachmentToCard(accountId, localCardId, mimeType, file, callback);
}
public void addOrUpdateSingleCardWidget(int widgetId, long accountId, long boardId, long localCardId) {
@@ -271,15 +279,15 @@ public class MainViewModel extends AndroidViewModel {
return syncManager.getUserByUidDirectly(accountId, uid);
}
- public WrappedLiveData<FullCard> archiveCard(@NonNull FullCard card) {
- return syncManager.archiveCard(card);
+ public void archiveCard(@NonNull FullCard card, @NonNull ResponseCallback<FullCard> callback) {
+ syncManager.archiveCard(card, callback);
}
- public WrappedLiveData<FullCard> dearchiveCard(@NonNull FullCard card) {
- return syncManager.dearchiveCard(card);
+ public void dearchiveCard(@NonNull FullCard card, @NonNull ResponseCallback<FullCard> callback) {
+ syncManager.dearchiveCard(card, callback);
}
- public WrappedLiveData<Void> deleteCard(@NonNull Card card) {
- return syncManager.deleteCard(card);
+ public void deleteCard(@NonNull Card card, @NonNull ResponseCallback<Void> callback) {
+ syncManager.deleteCard(card, callback);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PickStackActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PickStackActivity.java
index 2339a8783..d60ce4002 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PickStackActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PickStackActivity.java
@@ -1,6 +1,5 @@
package it.niedermann.nextcloud.deck.ui;
-import android.content.Intent;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Bundle;
@@ -31,7 +30,6 @@ import it.niedermann.nextcloud.deck.ui.pickstack.PickStackViewModel;
import static androidx.lifecycle.Transformations.switchMap;
import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
import static it.niedermann.nextcloud.deck.util.DeckColorUtil.contrastRatioIsSufficientBigAreas;
public abstract class PickStackActivity extends AppCompatActivity implements Branded, PickStackListener {
@@ -39,8 +37,6 @@ public abstract class PickStackActivity extends AppCompatActivity implements Bra
protected ActivityPickStackBinding binding;
protected PickStackViewModel viewModel;
- private boolean brandingEnabled;
-
private Account selectedAccount;
private Board selectedBoard;
private Stack selectedStack;
@@ -52,8 +48,6 @@ public abstract class PickStackActivity extends AppCompatActivity implements Bra
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));
- brandingEnabled = isBrandingEnabled(this);
-
binding = ActivityPickStackBinding.inflate(getLayoutInflater());
viewModel = new ViewModelProvider(this).get(PickStackViewModel.class);
@@ -64,7 +58,7 @@ public abstract class PickStackActivity extends AppCompatActivity implements Bra
if (hasAccounts) {
return viewModel.readAccounts();
} else {
- startActivityForResult(new Intent(this, ImportAccountActivity.class), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
+ startActivityForResult(ImportAccountActivity.createIntent(this), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
return null;
}
}).observe(this, (List<Account> accounts) -> {
@@ -96,20 +90,18 @@ public abstract class PickStackActivity extends AppCompatActivity implements Bra
@Override
public void applyBrand(int mainColor) {
try {
- if (brandingEnabled) {
- @ColorInt final int finalMainColor = contrastRatioIsSufficientBigAreas(mainColor, ContextCompat.getColor(this, R.color.primary))
- ? mainColor
- : isDarkTheme(this) ? Color.WHITE : Color.BLACK;
- DrawableCompat.setTintList(binding.submit.getBackground(), ColorStateList.valueOf(finalMainColor));
- binding.submit.setTextColor(ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(finalMainColor));
- binding.cancel.setTextColor(getSecondaryForegroundColorDependingOnTheme(this, mainColor));
- }
+ @ColorInt final int finalMainColor = contrastRatioIsSufficientBigAreas(mainColor, ContextCompat.getColor(this, R.color.primary))
+ ? mainColor
+ : isDarkTheme(this) ? Color.WHITE : Color.BLACK;
+ DrawableCompat.setTintList(binding.submit.getBackground(), ColorStateList.valueOf(finalMainColor));
+ binding.submit.setTextColor(ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(finalMainColor));
+ binding.cancel.setTextColor(getSecondaryForegroundColorDependingOnTheme(this, mainColor));
} catch (Throwable t) {
DeckLog.logError(t);
}
}
- abstract protected void onSubmit(Account account, long boardId, long stackId);
+ abstract protected void onSubmit(Account account, long boardLocalId, long stackId);
abstract protected boolean showBoardsWithoutEditPermission();
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java
index cdc20ed50..280ecebd8 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java
@@ -18,6 +18,7 @@ import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.databinding.ActivityPushNotificationBinding;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.ui.card.EditActivity;
+import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
import it.niedermann.nextcloud.deck.util.ProjectUtil;
@@ -40,8 +41,9 @@ public class PushNotificationActivity extends AppCompatActivity {
super.onResume();
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
+ final Intent intent = getIntent();
- if (getIntent() == null) {
+ if (intent == null) {
throw new IllegalArgumentException("Could not retrieve intent");
}
@@ -51,101 +53,113 @@ public class PushNotificationActivity extends AppCompatActivity {
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
- binding.subject.setText(getIntent().getStringExtra(KEY_SUBJECT));
+ binding.subject.setText(intent.getStringExtra(KEY_SUBJECT));
- final String message = getIntent().getStringExtra(KEY_MESSAGE);
+ final String message = intent.getStringExtra(KEY_MESSAGE);
if (!TextUtils.isEmpty(message)) {
binding.message.setText(message);
binding.message.setVisibility(View.VISIBLE);
}
- final String link = getIntent().getStringExtra(KEY_LINK);
- long[] ids = ProjectUtil.extractBoardIdAndCardIdFromUrl(link);
-
- binding.cancel.setOnClickListener((v) -> finish());
-
- final String cardRemoteIdString = getIntent().getStringExtra(KEY_CARD_REMOTE_ID);
- final String accountString = getIntent().getStringExtra(KEY_ACCOUNT);
-
- DeckLog.verbose("cardRemoteIdString = " + cardRemoteIdString);
- if (ids.length == 2) {
- if (cardRemoteIdString != null) {
- try {
- final int cardRemoteId = Integer.parseInt(cardRemoteIdString);
- observeOnce(viewModel.readAccount(accountString), this, (account -> {
- if (account != null) {
- viewModel.setAccount(account.getName());
- DeckLog.verbose("account: " + account);
- observeOnce(viewModel.getBoardByRemoteId(account.getId(), ids[0]), PushNotificationActivity.this, (board -> {
- DeckLog.verbose("BoardLocalId " + board);
- if (board != null) {
- observeOnce(viewModel.getCardByRemoteID(account.getId(), cardRemoteId), PushNotificationActivity.this, (card -> {
- DeckLog.verbose("Card: " + card);
- if (card != null) {
- viewModel.synchronizeCard(new IResponseCallback<Boolean>(account) {
- @Override
- public void onResponse(Boolean response) {
- openCardOnSubmit(account, board.getLocalId(), card.getLocalId());
- }
-
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- openCardOnSubmit(account, board.getLocalId(), card.getLocalId());
- }
- }, card);
- } else {
- DeckLog.info("Card is not yet available locally. Synchronize board with localId " + board);
-
- viewModel.synchronizeBoard(new IResponseCallback<Boolean>(account) {
- @Override
- public void onResponse(Boolean response) {
- runOnUiThread(() -> {
- observeOnce(viewModel.getCardByRemoteID(account.getId(), cardRemoteId), PushNotificationActivity.this, (card -> {
- DeckLog.verbose("Card: " + card);
- if (card != null) {
- openCardOnSubmit(account, board.getLocalId(), card.getLocalId());
- } else {
- DeckLog.warn("Something went wrong while synchronizing the card " + cardRemoteId + " (cardRemoteId). Given fullCard is null.");
- applyBrandToSubmitButton(account);
- fallbackToBrowser(link);
- }
- }));
- });
- }
-
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- DeckLog.warn("Something went wrong while synchronizing the board with localId " + board + ".");
- applyBrandToSubmitButton(account);
- fallbackToBrowser(link);
- }
- }, board.getLocalId());
- }
- }));
- } else {
- DeckLog.warn("Given localBoardId for cardRemoteId " + cardRemoteId + " is null.");
- applyBrandToSubmitButton(account);
- fallbackToBrowser(link);
- }
- }));
- } else {
- DeckLog.warn("Given account for " + accountString + " is null.");
- fallbackToBrowser(link);
- }
- }));
- } catch (NumberFormatException e) {
- DeckLog.logError(e);
+ final String link = intent.getStringExtra(KEY_LINK);
+ final long[] ids;
+ try {
+ ids = ProjectUtil.extractBoardIdAndCardIdFromUrl(link);
+
+ binding.cancel.setOnClickListener((v) -> finish());
+
+ final String cardRemoteIdString = intent.getStringExtra(KEY_CARD_REMOTE_ID);
+ final String accountString = intent.getStringExtra(KEY_ACCOUNT);
+
+ DeckLog.verbose("cardRemoteIdString = ", cardRemoteIdString);
+ if (ids.length == 2) {
+ if (cardRemoteIdString != null) {
+ try {
+ final int cardRemoteId = Integer.parseInt(cardRemoteIdString);
+ observeOnce(viewModel.readAccount(accountString), this, (account -> {
+ if (account != null) {
+ viewModel.setAccount(account.getName());
+ DeckLog.verbose("account:", account);
+ observeOnce(viewModel.getBoardByRemoteId(account.getId(), ids[0]), PushNotificationActivity.this, (board -> {
+ DeckLog.verbose("BoardLocalId:", board);
+ if (board != null) {
+ observeOnce(viewModel.getCardByRemoteID(account.getId(), cardRemoteId), PushNotificationActivity.this, (card -> {
+ DeckLog.verbose("Card:", card);
+ if (card != null) {
+ viewModel.synchronizeCard(new IResponseCallback<Boolean>(account) {
+ @Override
+ public void onResponse(Boolean response) {
+ openCardOnSubmit(account, board.getLocalId(), card.getLocalId());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ openCardOnSubmit(account, board.getLocalId(), card.getLocalId());
+ }
+ }, card);
+ } else {
+ DeckLog.info("Card is not yet available locally. Synchronize board with localId", board);
+
+ viewModel.synchronizeBoard(new IResponseCallback<Boolean>(account) {
+ @Override
+ public void onResponse(Boolean response) {
+ runOnUiThread(() -> {
+ observeOnce(viewModel.getCardByRemoteID(account.getId(), cardRemoteId), PushNotificationActivity.this, (card -> {
+ DeckLog.verbose("Card:", card);
+ if (card != null) {
+ openCardOnSubmit(account, board.getLocalId(), card.getLocalId());
+ } else {
+ DeckLog.warn("Something went wrong while synchronizing the card", cardRemoteId, " (cardRemoteId). Given fullCard is null.");
+ applyBrandToSubmitButton(account);
+ fallbackToBrowser(link);
+ }
+ }));
+ });
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ DeckLog.warn("Something went wrong while synchronizing the board with localId", board);
+ applyBrandToSubmitButton(account);
+ fallbackToBrowser(link);
+ }
+ }, board.getLocalId());
+ }
+ }));
+ } else {
+ DeckLog.warn("Given localBoardId for cardRemoteId", cardRemoteId, "is null.");
+ applyBrandToSubmitButton(account);
+ fallbackToBrowser(link);
+ }
+ }));
+ } else {
+ DeckLog.warn("Given account for", accountString, "is null.");
+ fallbackToBrowser(link);
+ }
+ }));
+ } catch (NumberFormatException e) {
+ DeckLog.logError(e);
+ fallbackToBrowser(link);
+ }
+ } else {
+ DeckLog.warn(KEY_CARD_REMOTE_ID, "is null.");
fallbackToBrowser(link);
}
} else {
- DeckLog.warn(KEY_CARD_REMOTE_ID + " is null.");
+ DeckLog.warn("Link does not contain two IDs (expected one board id and one card id):", link);
fallbackToBrowser(link);
}
- } else {
- DeckLog.warn("Link does not contain two IDs (expected one board id and one card id): " + link);
- fallbackToBrowser(link);
+ } catch (Throwable t) {
+ final String params = "Error while receiving push notification:\n"
+ + KEY_SUBJECT + ": [" + intent.getStringExtra(KEY_SUBJECT) + "]\n"
+ + KEY_MESSAGE + ": [" + intent.getStringExtra(KEY_MESSAGE) + "]\n"
+ + KEY_LINK + ": [" + intent.getStringExtra(KEY_LINK) + "]\n"
+ + KEY_CARD_REMOTE_ID + ": [" + intent.getStringExtra(KEY_CARD_REMOTE_ID) + "]\n"
+ + KEY_ACCOUNT + ": [" + intent.getStringExtra(KEY_ACCOUNT) + "]";
+ ExceptionDialogFragment.newInstance(new Exception(params, t), null).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ DeckLog.logError(t);
}
}
@@ -182,7 +196,7 @@ public class PushNotificationActivity extends AppCompatActivity {
@UiThread
private void launchEditActivity(@NonNull Account account, Long boardId, Long cardId) {
- DeckLog.info("starting " + EditActivity.class.getSimpleName() + " with [" + account + ", " + boardId + ", " + cardId + "]");
+ DeckLog.info("starting", EditActivity.class.getSimpleName(), "with [" + account + ", " + boardId + ", " + cardId + "]");
startActivity(EditActivity.createEditCardIntent(this, account, boardId, cardId));
finish();
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutActivity.java
index bf3734b72..c37d0bc46 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutActivity.java
@@ -6,22 +6,22 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.Lifecycle;
+import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import com.google.android.material.tabs.TabLayoutMediator;
+import it.niedermann.nextcloud.deck.DeckApplication;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ActivityAboutBinding;
import it.niedermann.nextcloud.deck.model.Account;
-import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToPrimaryTabLayout;
-public class AboutActivity extends BrandedActivity {
+public class AboutActivity extends AppCompatActivity {
private static final String BUNDLE_KEY_ACCOUNT = "account";
private ActivityAboutBinding binding;
@@ -39,11 +39,11 @@ public class AboutActivity extends BrandedActivity {
binding = ActivityAboutBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
+ DeckApplication.readCurrentAccountColor().observe(this, (mainColor) -> applyBrandToPrimaryTabLayout(mainColor, binding.tabLayout));
+
setSupportActionBar(binding.toolbar);
- binding.viewPager.setAdapter(new TabsPagerAdapter(getSupportFragmentManager(), getLifecycle(), (Account) getIntent().getSerializableExtra(BUNDLE_KEY_ACCOUNT)));
+ binding.viewPager.setAdapter(new TabsPagerAdapter(this, (Account) getIntent().getSerializableExtra(BUNDLE_KEY_ACCOUNT)));
new TabLayoutMediator(binding.tabLayout, binding.viewPager, (tab, position) -> tab.setText(tabTitles[position])).attach();
-
- setResult(RESULT_OK);
}
private static class TabsPagerAdapter extends FragmentStateAdapter {
@@ -51,8 +51,8 @@ public class AboutActivity extends BrandedActivity {
@Nullable
private final Account account;
- TabsPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, @Nullable Account account) {
- super(fragmentManager, lifecycle);
+ TabsPagerAdapter(final FragmentActivity fa, @Nullable Account account) {
+ super(fa);
this.account = account;
}
@@ -83,11 +83,6 @@ public class AboutActivity extends BrandedActivity {
return true;
}
- @Override
- public void applyBrand(int mainColor) {
- applyBrandToPrimaryTabLayout(mainColor, binding.tabLayout);
- }
-
@NonNull
public static Intent createIntent(@NonNull Context context, @NonNull Account account) {
return new Intent(context, AboutActivity.class)
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentCreditsTab.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentCreditsTab.java
index 0b0f26e63..6dac2a2a1 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentCreditsTab.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentCreditsTab.java
@@ -55,7 +55,7 @@ public class AboutFragmentCreditsTab extends Fragment {
binding.lastBackgroundSync.setText(
lastBackgroundSync == BACKGROUND_SYNC_NEVER_EXECUTED || settingsBackgroundSync.equals(backgroundSyncOffValue)
? disabled(getString(R.string.simple_disabled), requireContext())
- : strong(DateUtil.getRelativeDateTimeString(getContext(), lastBackgroundSync))
+ : strong(DateUtil.getRelativeDateTimeString(requireContext(), lastBackgroundSync))
);
binding.aboutMaintainer.setText(url(getString(R.string.about_maintainer), getString(R.string.url_maintainer)));
binding.aboutMaintainer.setMovementMethod(new LinkMovementMethod());
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java
index 0bd92bc78..5e7b7a1ec 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java
@@ -11,19 +11,21 @@ import android.view.ViewGroup;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.fragment.app.Fragment;
import it.niedermann.android.util.ColorUtil;
+import it.niedermann.nextcloud.deck.DeckApplication;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.FragmentAboutLicenseTabBinding;
-import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment;
import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
import static it.niedermann.nextcloud.deck.util.DeckColorUtil.contrastRatioIsSufficientBigAreas;
import static it.niedermann.nextcloud.deck.util.SpannableUtil.setTextWithURL;
-public class AboutFragmentLicenseTab extends BrandedFragment {
+public class AboutFragmentLicenseTab extends Fragment {
private FragmentAboutLicenseTabBinding binding;
@@ -37,11 +39,14 @@ public class AboutFragmentLicenseTab extends BrandedFragment {
}
@Override
- public void applyBrand(int mainColor) {
- @ColorInt final int finalMainColor = contrastRatioIsSufficientBigAreas(mainColor, ContextCompat.getColor(requireContext(), R.color.primary))
- ? mainColor
- : isDarkTheme(requireContext()) ? Color.WHITE : Color.BLACK;
- DrawableCompat.setTintList(binding.aboutAppLicenseButton.getBackground(), ColorStateList.valueOf(finalMainColor));
- binding.aboutAppLicenseButton.setTextColor(ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(finalMainColor));
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+ DeckApplication.readCurrentAccountColor().observe(getViewLifecycleOwner(), (mainColor) -> {
+ @ColorInt final int finalMainColor = contrastRatioIsSufficientBigAreas(mainColor, ContextCompat.getColor(requireContext(), R.color.primary))
+ ? mainColor
+ : isDarkTheme(requireContext()) ? Color.WHITE : Color.BLACK;
+ DrawableCompat.setTintList(binding.aboutAppLicenseButton.getBackground(), ColorStateList.valueOf(finalMainColor));
+ binding.aboutAppLicenseButton.setTextColor(ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(finalMainColor));
+ });
}
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java
index 744498c4a..8a70cf12d 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java
@@ -1,7 +1,6 @@
package it.niedermann.nextcloud.deck.ui.accountswitcher;
import android.app.Dialog;
-import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -26,7 +25,6 @@ import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
import it.niedermann.nextcloud.deck.ui.manageaccounts.ManageAccountsActivity;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
-import static it.niedermann.nextcloud.deck.ui.MainActivity.ACTIVITY_MANAGE_ACCOUNTS;
public class AccountSwitcherDialog extends BrandedDialogFragment {
@@ -80,7 +78,7 @@ public class AccountSwitcherDialog extends BrandedDialogFragment {
});
binding.manageAccounts.setOnClickListener((v) -> {
- requireActivity().startActivityForResult(new Intent(requireContext(), ManageAccountsActivity.class), ACTIVITY_MANAGE_ACCOUNTS);
+ requireActivity().startActivity(ManageAccountsActivity.createIntent(requireContext()));
dismiss();
});
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java
index d6d9cac1e..5a198dec3 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java
@@ -10,12 +10,13 @@ import androidx.lifecycle.ViewModelProvider;
import java.util.Collections;
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.databinding.ActivityArchivedBinding;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Board;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import it.niedermann.nextcloud.deck.ui.board.ArchiveBoardListener;
import it.niedermann.nextcloud.deck.ui.board.DeleteBoardListener;
@@ -24,8 +25,6 @@ import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
-import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
-
public class ArchivedBoardsActvitiy extends BrandedActivity implements DeleteBoardListener, EditBoardListener, ArchiveBoardListener {
private static final String BUNDLE_KEY_ACCOUNT = "accountId";
@@ -58,14 +57,7 @@ public class ArchivedBoardsActvitiy extends BrandedActivity implements DeleteBoa
viewModel = new ViewModelProvider(this).get(MainViewModel.class);
viewModel.setCurrentAccount(account);
- adapter = new ArchivedBoardsAdapter(viewModel.isCurrentAccountIsSupportedVersion(), getSupportFragmentManager(), (board) -> {
- final WrappedLiveData<FullBoard> liveData = viewModel.dearchiveBoard(board);
- observeOnce(liveData, this, (fullBoard) -> {
- if (liveData.hasError()) {
- ExceptionDialogFragment.newInstance(liveData.getError(), viewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- }
- });
- });
+ adapter = new ArchivedBoardsAdapter(viewModel.isCurrentAccountIsSupportedVersion(), getSupportFragmentManager(), this::onArchive);
binding.recyclerView.setAdapter(adapter);
viewModel.getBoards(account.getId(), true).observe(this, (boards) -> {
@@ -89,36 +81,62 @@ public class ArchivedBoardsActvitiy extends BrandedActivity implements DeleteBoa
@Override
public void onBoardDeleted(Board board) {
- final WrappedLiveData<Void> deleteLiveData = viewModel.deleteBoard(board);
- observeOnce(deleteLiveData, this, (next) -> {
- if (deleteLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteLiveData.getError())) {
- ExceptionDialogFragment.newInstance(deleteLiveData.getError(), viewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ viewModel.deleteBoard(board, new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully deleted board", board.getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, viewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
+ }
}
});
}
@Override
public void onUpdateBoard(FullBoard fullBoard) {
- final WrappedLiveData<FullBoard> updateLiveData = viewModel.updateBoard(fullBoard);
- observeOnce(updateLiveData, this, (next) -> {
- if (updateLiveData.hasError()) {
- ExceptionDialogFragment.newInstance(updateLiveData.getError(), viewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ viewModel.updateBoard(fullBoard, new ResponseCallback<FullBoard>() {
+ @Override
+ public void onResponse(FullBoard response) {
+ DeckLog.info("Successfully updated board", fullBoard.getBoard().getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, viewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
}
});
}
@Override
public void onArchive(Board board) {
- final WrappedLiveData<FullBoard> liveData = viewModel.dearchiveBoard(board);
- observeOnce(liveData, this, (fullBoard) -> {
- if (liveData.hasError()) {
- ExceptionDialogFragment.newInstance(liveData.getError(), viewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ viewModel.dearchiveBoard(board, new ResponseCallback<FullBoard>() {
+ @Override
+ public void onResponse(FullBoard response) {
+ DeckLog.info("Successfully dearchived board", response.getBoard().getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, viewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
}
});
}
@Override
public void onClone(Board board) {
+ throw new IllegalStateException("Cloning boards is not available at " + ArchivedBoardsActvitiy.class.getSimpleName());
+ }
+ @Override
+ public boolean onSupportNavigateUp() {
+ finish(); // close this activity as oppose to navigating up
+ return true;
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java
index b5034ebfa..8968cbd72 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java
@@ -1,28 +1,28 @@
package it.niedermann.nextcloud.deck.ui.archivedcards;
-import android.content.Context;
+import android.app.Activity;
import android.view.MenuItem;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
+import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import it.niedermann.nextcloud.deck.ui.card.AbstractCardViewHolder;
import it.niedermann.nextcloud.deck.ui.card.CardAdapter;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
-import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
-
public class ArchivedCardsAdapter extends CardAdapter {
@SuppressWarnings("WeakerAccess")
- public ArchivedCardsAdapter(@NonNull Context context, @NonNull FragmentManager fragmentManager, @NonNull MainViewModel viewModel, @NonNull LifecycleOwner lifecycleOwner) {
- super(context, fragmentManager, 0L, viewModel, lifecycleOwner, null);
+ public ArchivedCardsAdapter(@NonNull Activity activity, @NonNull FragmentManager fragmentManager, @NonNull MainViewModel viewModel, @NonNull LifecycleOwner lifecycleOwner) {
+ super(activity, fragmentManager, 0L, viewModel, lifecycleOwner, null);
}
@Override
@@ -34,18 +34,32 @@ public class ArchivedCardsAdapter extends CardAdapter {
public boolean onCardOptionsItemSelected(@NonNull MenuItem menuItem, @NonNull FullCard fullCard) {
int itemId = menuItem.getItemId();
if (itemId == R.id.action_card_dearchive) {
- final WrappedLiveData<FullCard> liveData = mainViewModel.dearchiveCard(fullCard);
- observeOnce(liveData, lifecycleOwner, (next) -> {
- if (liveData.hasError()) {
- ExceptionDialogFragment.newInstance(liveData.getError(), mainViewModel.getCurrentAccount()).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.dearchiveCard(fullCard, new ResponseCallback<FullCard>() {
+ @Override
+ public void onResponse(FullCard response) {
+ DeckLog.info("Successfully dearchived", Card.class.getSimpleName(), fullCard.getCard().getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ activity.runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName()));
}
});
return true;
} else if (itemId == R.id.action_card_delete) {
- final WrappedLiveData<Void> liveData = mainViewModel.deleteCard(fullCard.getCard());
- observeOnce(liveData, lifecycleOwner, (next) -> {
- if (liveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(liveData.getError())) {
- ExceptionDialogFragment.newInstance(liveData.getError(), mainViewModel.getCurrentAccount()).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.deleteCard(fullCard.getCard(), new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully deleted card", fullCard.getCard().getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ activity.runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getCurrentAccount()).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName()));
+ }
}
});
return true;
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentViewHolder.java
index 6f4fe3c74..336e085d7 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentViewHolder.java
@@ -2,7 +2,6 @@ package it.niedermann.nextcloud.deck.ui.attachments;
import android.content.Context;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
@@ -37,12 +36,10 @@ public class AttachmentViewHolder extends RecyclerView.ViewHolder {
public void bind(@NonNull Account account, @NonNull Attachment attachment, long cardRemoteId) {
if (MimeTypeUtil.isImage(attachment.getMimetype())) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- binding.preview.setTransitionName(parentContext.getString(R.string.transition_attachment_preview, String.valueOf(attachment.getLocalId())));
- }
+ binding.preview.setTransitionName(parentContext.getString(R.string.transition_attachment_preview, String.valueOf(attachment.getLocalId())));
binding.preview.setImageResource(R.drawable.ic_image_grey600_24dp);
binding.preview.post(() -> {
- final String uri = AttachmentUtil.getThumbnailUrl(account.getServerDeckVersionAsObject(), account.getUrl(), cardRemoteId, attachment, binding.preview.getWidth());
+ final String uri = AttachmentUtil.getThumbnailUrl(account, cardRemoteId, attachment, binding.preview.getWidth(), binding.preview.getHeight());
Glide.with(parentContext)
.load(uri)
.listener(new RequestListener<Drawable>() {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsActivity.java
index 6618f7f72..b65b727aa 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsActivity.java
@@ -50,7 +50,7 @@ public class AttachmentsActivity extends AppCompatActivity {
supportPostponeEnterTransition();
setSupportActionBar(binding.toolbar);
- final Drawable navigationIcon = getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp);
+ final Drawable navigationIcon = ContextCompat.getDrawable(this, R.drawable.ic_arrow_back_white_24dp);
DrawableCompat.setTint(navigationIcon, ContextCompat.getColor(this, android.R.color.white));
binding.toolbar.setNavigationIcon(navigationIcon);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java
index 0a1281fae..e120c665c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java
@@ -28,7 +28,6 @@ import it.niedermann.nextcloud.deck.ui.branding.Branded;
import it.niedermann.nextcloud.deck.util.ViewUtil;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements Branded {
@@ -93,7 +92,8 @@ public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.View
ViewUtil.addAvatar(acHolder.binding.avatar, account.getUrl(), ac.getUser().getUid(), R.drawable.ic_person_grey600_24dp);
acHolder.binding.username.setText(ac.getUser().getDisplayname());
- acHolder.binding.username.setCompoundDrawables(null, null, ac.getStatus() == DBStatus.LOCAL_EDITED.getId() ? context.getResources().getDrawable(R.drawable.ic_sync_blue_24dp) : null, null);
+ acHolder.binding.username.setCompoundDrawables(null, null, ac.getStatus() == DBStatus.LOCAL_EDITED.getId()
+ ? ContextCompat.getDrawable(context, R.drawable.ic_sync_blue_24dp) : null, null);
acHolder.binding.delete.setOnClickListener((v) -> accessControlChangedListener.deleteAccessControl(ac));
if (hasManagePermission) {
@@ -122,13 +122,11 @@ public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.View
accessControlChangedListener.updateAccessControl(ac);
});
- if (isBrandingEnabled(context)) {
- if (hasManagePermission) {
- brandSwitch(context, acHolder.binding.permissionEdit, mainColor);
- brandSwitch(context, acHolder.binding.permissionManage, mainColor);
- }
- brandSwitch(context, acHolder.binding.permissionShare, mainColor);
+ if (hasManagePermission) {
+ brandSwitch(context, acHolder.binding.permissionEdit, mainColor);
+ brandSwitch(context, acHolder.binding.permissionManage, mainColor);
}
+ brandSwitch(context, acHolder.binding.permissionShare, mainColor);
break;
}
}
@@ -160,10 +158,8 @@ public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.View
@Override
public void applyBrand(int mainColor) {
- if (isBrandingEnabled(context)) {
- this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor);
- notifyDataSetChanged();
- }
+ this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor);
+ notifyDataSetChanged();
}
/**
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java
index 78ccd5333..44bbcfe19 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java
@@ -18,12 +18,12 @@ import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.databinding.DialogBoardShareBinding;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
@@ -31,7 +31,6 @@ import it.niedermann.nextcloud.deck.ui.branding.BrandedSnackbar;
import it.niedermann.nextcloud.deck.ui.card.UserAutoCompleteAdapter;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
-import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
import static it.niedermann.nextcloud.deck.ui.board.accesscontrol.AccessControlAdapter.HEADER_ITEM_LOCAL_ID;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText;
@@ -101,26 +100,39 @@ public class AccessControlDialogFragment extends BrandedDialogFragment implement
@Override
public void updateAccessControl(AccessControl accessControl) {
- WrappedLiveData<AccessControl> updateLiveData = viewModel.updateAccessControl(accessControl);
- observeOnce(updateLiveData, requireActivity(), (next) -> {
- if (updateLiveData.hasError()) {
- ExceptionDialogFragment.newInstance(updateLiveData.getError(), viewModel.getCurrentAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ viewModel.updateAccessControl(accessControl, new ResponseCallback<AccessControl>() {
+ @Override
+ public void onResponse(AccessControl response) {
+ DeckLog.info("Successfully updated", AccessControl.class.getSimpleName(), "for user", accessControl.getUser().getDisplayname());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ requireActivity().runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, viewModel.getCurrentAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
}
});
}
@Override
public void deleteAccessControl(AccessControl ac) {
- final WrappedLiveData<Void> wrappedDeleteLiveData = viewModel.deleteAccessControl(ac);
- adapter.remove(ac);
- observeOnce(wrappedDeleteLiveData, this, (ignored) -> {
- if (wrappedDeleteLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(wrappedDeleteLiveData.getError())) {
- DeckLog.logError(wrappedDeleteLiveData.getError());
- BrandedSnackbar.make(requireView(), getString(R.string.error_revoking_ac, ac.getUser().getDisplayname()), Snackbar.LENGTH_LONG)
- .setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(wrappedDeleteLiveData.getError(), viewModel.getCurrentAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
- .show();
+ viewModel.deleteAccessControl(ac, new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully deleted access control for user", ac.getUser().getDisplayname());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ requireActivity().runOnUiThread(() -> BrandedSnackbar.make(requireView(), getString(R.string.error_revoking_ac, ac.getUser().getDisplayname()), Snackbar.LENGTH_LONG)
+ .setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(throwable, viewModel.getCurrentAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
+ .show());
+ }
}
});
+ adapter.remove(ac);
}
@Override
@@ -132,10 +144,16 @@ public class AccessControlDialogFragment extends BrandedDialogFragment implement
ac.setType(0L); // https://github.com/nextcloud/deck/blob/master/docs/API.md#post-boardsboardidacl---add-new-acl-rule
ac.setUserId(user.getLocalId());
ac.setUser(user);
- final WrappedLiveData<AccessControl> createLiveData = viewModel.createAccessControl(viewModel.getCurrentAccount().getId(), ac);
- observeOnce(createLiveData, this, (next) -> {
- if (createLiveData.hasError()) {
- ExceptionDialogFragment.newInstance(createLiveData.getError(), viewModel.getCurrentAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ viewModel.createAccessControl(viewModel.getCurrentAccount().getId(), ac, new ResponseCallback<AccessControl>() {
+ @Override
+ public void onResponse(AccessControl response) {
+ DeckLog.info("Successfully created", AccessControl.class.getSimpleName(), "for user", user.getDisplayname());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ requireActivity().runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, viewModel.getCurrentAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
}
});
binding.people.setText("");
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsAdapter.java
index d8bb57a11..d6b097d89 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsAdapter.java
@@ -5,6 +5,7 @@ import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
+import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import java.util.LinkedList;
@@ -17,7 +18,6 @@ import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.ui.branding.Branded;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
public class ManageLabelsAdapter extends RecyclerView.Adapter<ManageLabelsViewHolder> implements Branded {
@@ -33,7 +33,7 @@ public class ManageLabelsAdapter extends RecyclerView.Adapter<ManageLabelsViewHo
ManageLabelsAdapter(@NonNull ManageLabelListener listener, @NonNull Context context) {
this.listener = listener;
this.context = context;
- this.mainColor = context.getResources().getColor(R.color.primary);
+ this.mainColor = ContextCompat.getColor(context, R.color.primary);
setHasStableIds(true);
}
@@ -76,9 +76,7 @@ public class ManageLabelsAdapter extends RecyclerView.Adapter<ManageLabelsViewHo
@Override
public void applyBrand(int mainColor) {
- if (isBrandingEnabled(context)) {
- this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor);
- notifyDataSetChanged();
- }
+ this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor);
+ notifyDataSetChanged();
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java
index bc0d98ca3..51ba42dd6 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java
@@ -15,10 +15,10 @@ import java.util.Random;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.databinding.DialogBoardManageLabelsBinding;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
import it.niedermann.nextcloud.deck.ui.branding.BrandedDeleteAlertDialogBuilder;
@@ -80,22 +80,26 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements
label.setTitle(binding.addLabelTitle.getText().toString());
label.setColor(colors[new Random().nextInt(colors.length)]);
- WrappedLiveData<Label> createLiveData = viewModel.createLabel(viewModel.getCurrentAccount().getId(), label, boardId);
- observeOnce(createLiveData, this, (createdLabel) -> {
- if (createLiveData.hasError()) {
- final Throwable error = createLiveData.getError();
- assert error != null;
- if (error instanceof SQLiteConstraintException) {
+ viewModel.createLabel(viewModel.getCurrentAccount().getId(), label, boardId, new ResponseCallback<Label>() {
+ @Override
+ public void onResponse(Label response) {
+ Toast.makeText(requireContext(), getString(R.string.tag_successfully_added, label.getTitle()), Toast.LENGTH_LONG).show();
+ requireActivity().runOnUiThread(() -> {
+ binding.fab.setEnabled(true);
+ binding.addLabelTitle.setText(null);
+ });
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ requireActivity().runOnUiThread(() -> binding.fab.setEnabled(true));
+ if (throwable instanceof SQLiteConstraintException) {
Toast.makeText(requireContext(), getString(R.string.tag_already_exists, label.getTitle()), Toast.LENGTH_LONG).show();
} else {
- Toast.makeText(requireContext(), error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
- DeckLog.logError(error);
+ Toast.makeText(requireContext(), throwable.getLocalizedMessage(), Toast.LENGTH_LONG).show();
+ ResponseCallback.super.onError(throwable);
}
- } else {
- binding.addLabelTitle.setText(null);
- Toast.makeText(requireContext(), getString(R.string.tag_successfully_added, label.getTitle()), Toast.LENGTH_LONG).show();
}
- binding.fab.setEnabled(true);
});
});
binding.addLabelTitle.setOnEditorActionListener((v, actionId, event) -> binding.fab.performClick());
@@ -139,13 +143,18 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements
}
private void deleteLabel(@NonNull Label label) {
- final WrappedLiveData<Void> deleteLiveData = viewModel.deleteLabel(label);
- observeOnce(deleteLiveData, this, (v) -> {
- if (deleteLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteLiveData.getError())) {
- final Throwable error = deleteLiveData.getError();
- assert error != null;
- Toast.makeText(requireContext(), error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
- DeckLog.logError(deleteLiveData.getError());
+ viewModel.deleteLabel(label, new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully deleted label", label.getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ Toast.makeText(requireContext(), throwable.getLocalizedMessage(), Toast.LENGTH_LONG).show();
+ }
}
});
}
@@ -157,16 +166,19 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements
@Override
public void onLabelUpdated(@NonNull Label label) {
- WrappedLiveData<Label> updateLiveData = viewModel.updateLabel(label);
- observeOnce(updateLiveData, this, (updatedLabel) -> {
- if (updateLiveData.hasError()) {
- final Throwable error = updateLiveData.getError();
- assert error != null;
+ viewModel.updateLabel(label, new ResponseCallback<Label>() {
+ @Override
+ public void onResponse(Label label) {
+ DeckLog.verbose("Successfully update label", label.getTitle());
+ }
+
+ @Override
+ public void onError(Throwable error) {
if (error instanceof SQLiteConstraintException) {
Toast.makeText(requireContext(), getString(R.string.tag_already_exists, label.getTitle()), Toast.LENGTH_LONG).show();
} else {
+ ResponseCallback.super.onError(error);
Toast.makeText(requireContext(), error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
- DeckLog.logError(updateLiveData.getError());
}
}
});
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedActivity.java
index 3420e888b..03c45305e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedActivity.java
@@ -8,7 +8,6 @@ import androidx.appcompat.app.AppCompatActivity;
import it.niedermann.nextcloud.deck.R;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.tintMenuIcon;
@@ -25,10 +24,8 @@ public abstract class BrandedActivity extends AppCompatActivity implements Brand
getTheme().resolveAttribute(R.attr.colorAccent, typedValue, true);
colorAccent = typedValue.data;
- if (isBrandingEnabled(this)) {
- @ColorInt final int mainColor = readBrandMainColor(this);
- applyBrand(mainColor);
- }
+ @ColorInt final int mainColor = readBrandMainColor(this);
+ applyBrand(mainColor);
}
@Override
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java
index 319df7f79..c84ac8274 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java
@@ -21,7 +21,6 @@ import it.niedermann.nextcloud.deck.util.DeckColorUtil;
import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
public class BrandedDatePickerDialog extends DatePickerDialog implements Branded {
@@ -31,9 +30,7 @@ public class BrandedDatePickerDialog extends DatePickerDialog implements Branded
@Nullable Context context = getContext();
if (context != null) {
setThemeDark(isDarkTheme(context));
- if (isBrandingEnabled(context)) {
- applyBrand(readBrandMainColor(context));
- }
+ applyBrand(readBrandMainColor(context));
}
return super.onCreateView(inflater, container, savedInstanceState);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDialogFragment.java
index adf3844e9..28d4da9ee 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDialogFragment.java
@@ -5,7 +5,6 @@ import android.content.Context;
import androidx.annotation.Nullable;
import androidx.fragment.app.DialogFragment;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
public abstract class BrandedDialogFragment extends DialogFragment implements Branded {
@@ -16,9 +15,7 @@ public abstract class BrandedDialogFragment extends DialogFragment implements Br
@Nullable Context context = getContext();
if (context != null) {
- if (isBrandingEnabled(context)) {
- applyBrand(readBrandMainColor(context));
- }
+ applyBrand(readBrandMainColor(context));
}
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedFragment.java
index fe202f48c..87cee2317 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedFragment.java
@@ -5,7 +5,6 @@ import android.content.Context;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
public abstract class BrandedFragment extends Fragment implements Branded {
@@ -15,8 +14,6 @@ public abstract class BrandedFragment extends Fragment implements Branded {
super.onStart();
@Nullable Context context = getContext();
- if (context != null && isBrandingEnabled(context)) {
- applyBrand(readBrandMainColor(context));
- }
+ applyBrand(readBrandMainColor(context));
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedPreferenceCategory.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedPreferenceCategory.java
index e01c523c6..70db03aa0 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedPreferenceCategory.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedPreferenceCategory.java
@@ -10,9 +10,8 @@ import androidx.annotation.Nullable;
import androidx.preference.PreferenceCategory;
import androidx.preference.PreferenceViewHolder;
+import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentAccountColor;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
public class BrandedPreferenceCategory extends PreferenceCategory {
@@ -20,13 +19,11 @@ public class BrandedPreferenceCategory extends PreferenceCategory {
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- if (isBrandingEnabled(getContext())) {
- final View v = holder.itemView.findViewById(android.R.id.title);
- @Nullable final Context context = getContext();
- if (context != null && v instanceof TextView) {
- @ColorInt final int mainColor = getSecondaryForegroundColorDependingOnTheme(context, readBrandMainColor(context));
- ((TextView) v).setTextColor(mainColor);
- }
+ final View v = holder.itemView.findViewById(android.R.id.title);
+ @Nullable final Context context = getContext();
+ if (context != null && v instanceof TextView) {
+ @ColorInt final int mainColor = getSecondaryForegroundColorDependingOnTheme(context, readCurrentAccountColor(context));
+ ((TextView) v).setTextColor(mainColor);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java
index 0159a59dc..8b4cf2f5a 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java
@@ -6,15 +6,12 @@ import android.view.View;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
-import androidx.core.content.ContextCompat;
import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar;
import it.niedermann.android.util.ColorUtil;
-import it.niedermann.nextcloud.deck.R;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
public class BrandedSnackbar {
@@ -23,12 +20,8 @@ public class BrandedSnackbar {
public static Snackbar make(
@NonNull View view, @NonNull CharSequence text, @BaseTransientBottomBar.Duration int duration) {
final Snackbar snackbar = Snackbar.make(view, text, duration);
- if (isBrandingEnabled(view.getContext())) {
- @ColorInt final int color = readBrandMainColor(view.getContext());
- snackbar.setActionTextColor(ColorUtil.INSTANCE.isColorDark(color) ? Color.WHITE : color);
- } else {
- snackbar.setActionTextColor(ContextCompat.getColor(view.getContext(), R.color.defaultBrand));
- }
+ @ColorInt final int color = readBrandMainColor(view.getContext());
+ snackbar.setActionTextColor(ColorUtil.INSTANCE.isColorDark(color) ? Color.WHITE : color);
return snackbar;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSwitchPreference.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSwitchPreference.java
index 463800a0a..607beba03 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSwitchPreference.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSwitchPreference.java
@@ -16,7 +16,6 @@ import androidx.preference.SwitchPreference;
import it.niedermann.nextcloud.deck.R;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
public class BrandedSwitchPreference extends SwitchPreference implements Branded {
@@ -46,7 +45,7 @@ public class BrandedSwitchPreference extends SwitchPreference implements Branded
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- if (isBrandingEnabled(getContext()) && holder.itemView instanceof ViewGroup) {
+ if (holder.itemView instanceof ViewGroup) {
switchView = findSwitchWidget(holder.itemView);
if (mainColor != null) {
applyBrand();
@@ -58,9 +57,7 @@ public class BrandedSwitchPreference extends SwitchPreference implements Branded
public void applyBrand(@ColorInt int mainColor) {
this.mainColor = mainColor;
// onBindViewHolder is called after applyBrand, therefore we have to store the given values and apply them later.
- if (isBrandingEnabled(getContext())) {
- applyBrand();
- }
+ applyBrand();
}
private void applyBrand() {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java
index 2e0c4d3b8..02e09e3c8 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java
@@ -21,7 +21,6 @@ import it.niedermann.nextcloud.deck.util.DeckColorUtil;
import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
public class BrandedTimePickerDialog extends TimePickerDialog implements Branded {
@@ -31,9 +30,7 @@ public class BrandedTimePickerDialog extends TimePickerDialog implements Branded
@Nullable Context context = getContext();
if (context != null) {
setThemeDark(isDarkTheme(context));
- if (isBrandingEnabled(context)) {
- applyBrand(readBrandMainColor(context));
- }
+ applyBrand(readBrandMainColor(context));
}
return super.onCreateView(inflater, container, savedInstanceState);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandingUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandingUtil.java
index b780efec5..863375ae5 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandingUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandingUtil.java
@@ -31,40 +31,31 @@ public abstract class BrandingUtil {
// Util class
}
- public static boolean isBrandingEnabled(@NonNull Context context) {
- SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
- return prefs.getBoolean(context.getString(R.string.pref_key_branding), true);
- }
-
@ColorInt
public static int readBrandMainColor(@NonNull Context context) {
- if (isBrandingEnabled(context)) {
- SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
- DeckLog.log("--- Read: shared_preference_theme_main");
- return sharedPreferences.getInt(context.getString(R.string.shared_preference_theme_main), context.getApplicationContext().getResources().getColor(R.color.defaultBrand));
- } else {
- return ContextCompat.getColor(context, R.color.defaultBrand);
- }
+ final SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context.getApplicationContext());
+ DeckLog.log("--- Read: shared_preference_theme_main");
+ return sharedPreferences.getInt(context.getString(R.string.shared_preference_theme_main), context.getApplicationContext().getResources().getColor(R.color.defaultBrand));
}
public static void saveBrandColors(@NonNull Context context, @ColorInt int mainColor) {
- if (isBrandingEnabled(context) && context instanceof BrandedActivity) {
+ if (context instanceof BrandedActivity) {
final BrandedActivity activity = (BrandedActivity) context;
activity.applyBrand(mainColor);
}
- SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
- DeckLog.log("--- Write: shared_preference_theme_main" + " | " + mainColor);
+ final SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
+ DeckLog.log("--- Write: shared_preference_theme_main |", mainColor);
editor.putInt(context.getString(R.string.shared_preference_theme_main), mainColor);
editor.apply();
}
public static void clearBrandColors(@NonNull Context context) {
- if (isBrandingEnabled(context) && context instanceof BrandedActivity) {
+ if (context instanceof BrandedActivity) {
final BrandedActivity activity = (BrandedActivity) context;
activity.applyBrand(ContextCompat.getColor(context, R.color.defaultBrand));
}
- SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
- DeckLog.log("--- Write: Remove: shared_preference_theme_main" + " | ");
+ final SharedPreferences.Editor editor = PreferenceManager.getDefaultSharedPreferences(context).edit();
+ DeckLog.log("--- Write: Remove: shared_preference_theme_main | ");
editor.remove(context.getString(R.string.shared_preference_theme_main));
editor.apply();
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java
index 87140e544..1e92eb1b3 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java
@@ -1,7 +1,7 @@
package it.niedermann.nextcloud.deck.ui.card;
+import android.app.Activity;
import android.content.ClipData;
-import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
import android.view.MenuItem;
@@ -24,6 +24,7 @@ import it.niedermann.android.crosstabdnd.DragAndDropAdapter;
import it.niedermann.android.crosstabdnd.DraggedItemLocalState;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.databinding.ItemCardCompactBinding;
import it.niedermann.nextcloud.deck.databinding.ItemCardDefaultBinding;
import it.niedermann.nextcloud.deck.databinding.ItemCardDefaultOnlyTitleBinding;
@@ -32,14 +33,13 @@ import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import it.niedermann.nextcloud.deck.ui.branding.Branded;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.movecard.MoveCardDialogFragment;
+import it.niedermann.nextcloud.deck.util.CardUtil;
import static androidx.preference.PreferenceManager.getDefaultSharedPreferences;
-import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
import static it.niedermann.nextcloud.deck.util.MimeTypeUtil.TEXT_PLAIN;
@@ -52,7 +52,7 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
protected final FragmentManager fragmentManager;
private final long stackId;
@NonNull
- private final Context context;
+ protected final Activity activity;
@Nullable
private final SelectCardListener selectCardListener;
@NonNull
@@ -66,17 +66,17 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
@StringRes
private final int shareLinkRes;
- public CardAdapter(@NonNull Context context, @NonNull FragmentManager fragmentManager, long stackId, @NonNull MainViewModel mainViewModel, @NonNull LifecycleOwner lifecycleOwner, @Nullable SelectCardListener selectCardListener) {
- this.context = context;
- this.counterMaxValue = context.getString(R.string.counter_max_value);
+ public CardAdapter(@NonNull Activity activity, @NonNull FragmentManager fragmentManager, long stackId, @NonNull MainViewModel mainViewModel, @NonNull LifecycleOwner lifecycleOwner, @Nullable SelectCardListener selectCardListener) {
+ this.activity = activity;
+ this.counterMaxValue = this.activity.getString(R.string.counter_max_value);
this.fragmentManager = fragmentManager;
this.lifecycleOwner = lifecycleOwner;
this.shareLinkRes = mainViewModel.getCurrentAccount().getServerDeckVersionAsObject().getShareLinkResource();
this.stackId = stackId;
this.mainViewModel = mainViewModel;
this.selectCardListener = selectCardListener;
- this.mainColor = ContextCompat.getColor(context, R.color.defaultBrand);
- this.compactMode = getDefaultSharedPreferences(context).getBoolean(context.getString(R.string.pref_key_compact), false);
+ this.mainColor = ContextCompat.getColor(this.activity, R.color.defaultBrand);
+ this.compactMode = getDefaultSharedPreferences(this.activity).getBoolean(this.activity.getString(R.string.pref_key_compact), false);
setHasStableIds(true);
}
@@ -120,7 +120,7 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
// Only enable details view if there is no one waiting for selecting a card.
viewHolder.bindCardClickListener((v) -> {
if (selectCardListener == null) {
- context.startActivity(EditActivity.createEditCardIntent(context, mainViewModel.getCurrentAccount(), mainViewModel.getCurrentBoardLocalId(), fullCard.getLocalId()));
+ activity.startActivity(EditActivity.createEditCardIntent(activity, mainViewModel.getCurrentAccount(), mainViewModel.getCurrentBoardLocalId(), fullCard.getLocalId()));
} else {
selectCardListener.onCardSelected(fullCard);
}
@@ -176,7 +176,7 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
@Override
public void applyBrand(int mainColor) {
- this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor);
+ this.mainColor = getSecondaryForegroundColorDependingOnTheme(activity, mainColor);
notifyDataSetChanged();
}
@@ -185,15 +185,22 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
int itemId = menuItem.getItemId();
final Account account = mainViewModel.getCurrentAccount();
if (itemId == R.id.share_link) {
- Intent shareIntent = new Intent()
+ final Intent shareIntent = new Intent()
.setAction(Intent.ACTION_SEND)
.setType(TEXT_PLAIN)
.putExtra(Intent.EXTRA_SUBJECT, fullCard.getCard().getTitle())
.putExtra(Intent.EXTRA_TITLE, fullCard.getCard().getTitle())
- .putExtra(Intent.EXTRA_TEXT, account.getUrl() + context.getString(shareLinkRes, mainViewModel.getCurrentBoardRemoteId(), fullCard.getCard().getId()));
- context.startActivity(Intent.createChooser(shareIntent, fullCard.getCard().getTitle()));
- new Thread(() -> mainViewModel.assignUserToCard(mainViewModel.getUserByUidDirectly(fullCard.getCard().getAccountId(), account.getUserName()), fullCard.getCard())).start();
+ .putExtra(Intent.EXTRA_TEXT, account.getUrl() + activity.getString(shareLinkRes, mainViewModel.getCurrentBoardRemoteId(), fullCard.getCard().getId()));
+ activity.startActivity(Intent.createChooser(shareIntent, fullCard.getCard().getTitle()));
return true;
+ } else if (itemId == R.id.share_content) {
+ final Intent shareIntent = new Intent()
+ .setAction(Intent.ACTION_SEND)
+ .setType(TEXT_PLAIN)
+ .putExtra(Intent.EXTRA_SUBJECT, fullCard.getCard().getTitle())
+ .putExtra(Intent.EXTRA_TITLE, fullCard.getCard().getTitle())
+ .putExtra(Intent.EXTRA_TEXT, CardUtil.getCardContentAsString(activity, fullCard));
+ activity.startActivity(Intent.createChooser(shareIntent, fullCard.getCard().getTitle()));
} else if (itemId == R.id.action_card_assign) {
new Thread(() -> mainViewModel.assignUserToCard(mainViewModel.getUserByUidDirectly(fullCard.getCard().getAccountId(), account.getUserName()), fullCard.getCard())).start();
return true;
@@ -202,21 +209,37 @@ public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> im
return true;
} else if (itemId == R.id.action_card_move) {
DeckLog.verbose("[Move card] Launch move dialog for " + Card.class.getSimpleName() + " \"" + fullCard.getCard().getTitle() + "\" (#" + fullCard.getLocalId() + ") from " + Stack.class.getSimpleName() + " #" + +stackId);
- MoveCardDialogFragment.newInstance(fullCard.getAccountId(), mainViewModel.getCurrentBoardLocalId(), fullCard.getCard().getTitle(), fullCard.getLocalId()).show(fragmentManager, MoveCardDialogFragment.class.getSimpleName());
+ MoveCardDialogFragment
+ .newInstance(fullCard.getAccountId(), mainViewModel.getCurrentBoardLocalId(), fullCard.getCard().getTitle(), fullCard.getLocalId(), CardUtil.cardHasCommentsOrAttachments(fullCard))
+ .show(fragmentManager, MoveCardDialogFragment.class.getSimpleName());
return true;
} else if (itemId == R.id.action_card_archive) {
- final WrappedLiveData<FullCard> archiveLiveData = mainViewModel.archiveCard(fullCard);
- observeOnce(archiveLiveData, lifecycleOwner, (v) -> {
- if (archiveLiveData.hasError()) {
- ExceptionDialogFragment.newInstance(archiveLiveData.getError(), account).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.archiveCard(fullCard, new ResponseCallback<FullCard>() {
+ @Override
+ public void onResponse(FullCard response) {
+ DeckLog.info("Successfully archived", Card.class.getSimpleName(), fullCard.getCard().getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ activity.runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, account).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName()));
}
});
return true;
} else if (itemId == R.id.action_card_delete) {
- final WrappedLiveData<Void> deleteLiveData = mainViewModel.deleteCard(fullCard.getCard());
- observeOnce(deleteLiveData, lifecycleOwner, (v) -> {
- if (deleteLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteLiveData.getError())) {
- ExceptionDialogFragment.newInstance(deleteLiveData.getError(), account).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName());
+ mainViewModel.deleteCard(fullCard.getCard(), new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully deleted card", fullCard.getCard().getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ activity.runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, account).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName()));
+ }
}
});
return true;
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardTabAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardTabAdapter.java
index c0ed29007..68c95bfb6 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardTabAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardTabAdapter.java
@@ -2,8 +2,7 @@ package it.niedermann.nextcloud.deck.ui.card;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.Lifecycle;
+import androidx.fragment.app.FragmentActivity;
import androidx.viewpager2.adapter.FragmentStateAdapter;
import it.niedermann.nextcloud.deck.ui.card.activities.CardActivityFragment;
@@ -15,9 +14,8 @@ public class CardTabAdapter extends FragmentStateAdapter {
private boolean hasCommentsAbility = false;
- @SuppressWarnings("WeakerAccess")
- public CardTabAdapter(@NonNull FragmentManager fm, @NonNull Lifecycle lifecycle) {
- super(fm, lifecycle);
+ public CardTabAdapter(final FragmentActivity fa) {
+ super(fa);
}
@NonNull
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java
index 2e1ff5e06..15a1a6fb7 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java
@@ -14,6 +14,7 @@ import android.view.MenuItem;
import android.view.WindowManager;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.lifecycle.ViewModelProvider;
@@ -25,7 +26,10 @@ import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ActivityEditBinding;
import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.ocs.Version;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity;
import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
@@ -33,7 +37,6 @@ import it.niedermann.nextcloud.deck.util.CardUtil;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToPrimaryTabLayout;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
public class EditActivity extends BrandedActivity {
private static final String BUNDLE_KEY_ACCOUNT = "account";
@@ -41,6 +44,7 @@ public class EditActivity extends BrandedActivity {
private static final String BUNDLE_KEY_STACK_ID = "stackId";
private static final String BUNDLE_KEY_CARD_ID = "cardId";
private static final String BUNDLE_KEY_TITLE = "title";
+ private static final String BUNDLE_KEY_DESCRIPTION = "description";
private ActivityEditBinding binding;
private EditCardViewModel viewModel;
@@ -76,12 +80,13 @@ public class EditActivity extends BrandedActivity {
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
+
binding = ActivityEditBinding.inflate(getLayoutInflater());
+ viewModel = new ViewModelProvider(this).get(EditCardViewModel.class);
+
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
- viewModel = new ViewModelProvider(this).get(EditCardViewModel.class);
-
loadDataFromIntent();
}
@@ -122,16 +127,10 @@ public class EditActivity extends BrandedActivity {
viewModel.setCanEdit(fullBoard.getBoard().isPermissionEdit());
invalidateOptionsMenu();
if (viewModel.isCreateMode()) {
- viewModel.initializeNewCard(boardId, args.getLong(BUNDLE_KEY_STACK_ID), account.getServerDeckVersionAsObject().isSupported(this));
+ viewModel.initializeNewCard(boardId, args.getLong(BUNDLE_KEY_STACK_ID), account.getServerDeckVersionAsObject().isSupported());
invalidateOptionsMenu();
- String title = args.getString(BUNDLE_KEY_TITLE);
- if (!TextUtils.isEmpty(title)) {
- if (title.length() > viewModel.getAccount().getServerDeckVersionAsObject().getCardTitleMaxLength()) {
- viewModel.getFullCard().getCard().setDescription(title);
- } else {
- viewModel.getFullCard().getCard().setTitle(title);
- }
- }
+ fillTitleAndDescription(viewModel.getFullCard().getCard(), viewModel.getAccount().getServerDeckVersionAsObject(),
+ args.getString(BUNDLE_KEY_TITLE), args.getString(BUNDLE_KEY_DESCRIPTION));
setupViewPager();
setupTitle();
} else {
@@ -143,7 +142,7 @@ public class EditActivity extends BrandedActivity {
.setPositiveButton(R.string.simple_close, (a, b) -> super.finish())
.show();
} else {
- viewModel.initializeExistingCard(boardId, fullCard, account.getServerDeckVersionAsObject().isSupported(this));
+ viewModel.initializeExistingCard(boardId, fullCard, account.getServerDeckVersionAsObject().isSupported());
invalidateOptionsMenu();
setupViewPager();
setupTitle();
@@ -152,7 +151,35 @@ public class EditActivity extends BrandedActivity {
}
}));
- DeckLog.verbose("Finished loading intent data: { accountId = " + viewModel.getAccount().getId() + " , cardId = " + cardId + " }");
+ DeckLog.verbose("Finished loading intent data: { accountId =", viewModel.getAccount().getId(), "cardId =", cardId, "}");
+ }
+
+ private static void fillTitleAndDescription(@NonNull Card card, @NonNull Version version, @Nullable String title, @Nullable String description) {
+ if (!TextUtils.isEmpty(title) && !TextUtils.isEmpty(description)) {
+ assert title != null;
+ if (title.length() > version.getCardTitleMaxLength()) {
+ card.setTitle(title.substring(0, version.getCardTitleMaxLength()));
+ } else {
+ card.setTitle(title);
+ }
+ card.setDescription(description);
+ } else if (!TextUtils.isEmpty(title)) {
+ assert title != null;
+ if (title.length() > version.getCardTitleMaxLength()) {
+ card.setDescription(title);
+ card.setTitle(title.substring(0, version.getCardTitleMaxLength()));
+ } else {
+ card.setTitle(title);
+ }
+ } else if (!TextUtils.isEmpty(description)) {
+ assert description != null;
+ if (description.length() > version.getCardTitleMaxLength()) {
+ card.setDescription(description);
+ card.setTitle(description.substring(0, version.getCardTitleMaxLength()));
+ } else {
+ card.setTitle(description);
+ }
+ }
}
@Override
@@ -169,16 +196,21 @@ public class EditActivity extends BrandedActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_card_save) {
- saveAndRun(super::finish);
+ saveAndFinish();
}
return super.onOptionsItemSelected(item);
}
+ @Override
+ public boolean onSupportNavigateUp() {
+ finish(); // close this activity as oppose to navigating up
+ return true;
+ }
+
/**
- * Tries to save the current {@link FullCard} from the {@link EditCardViewModel} and then runs the given {@link Runnable}
- * @param runnable
+ * Tries to save the current {@link FullCard} from the {@link EditCardViewModel} and then finishes this activity.
*/
- private void saveAndRun(@NonNull Runnable runnable) {
+ private void saveAndFinish() {
if (!viewModel.isPendingCreation()) {
viewModel.setPendingCreation(true);
final String title = viewModel.getFullCard().getCard().getTitle();
@@ -195,11 +227,13 @@ public class EditActivity extends BrandedActivity {
.setOnDismissListener(dialog -> viewModel.setPendingCreation(false))
.show();
} else {
- if (viewModel.isCreateMode()) {
- observeOnce(viewModel.createFullCard(viewModel.getAccount().getId(), viewModel.getBoardId(), viewModel.getFullCard().getCard().getStackId(), viewModel.getFullCard()), EditActivity.this, (card) -> runnable.run());
- } else {
- observeOnce(viewModel.updateCard(viewModel.getFullCard()), EditActivity.this, (card) -> runnable.run());
- }
+ final WrappedLiveData<FullCard> save$ = viewModel.saveCard();
+ save$.observe(this, (fullCard) -> {
+ if (save$.hasError()) {
+ DeckLog.logError(save$.getError());
+ }
+ });
+ super.finish();
}
}
}
@@ -208,7 +242,7 @@ public class EditActivity extends BrandedActivity {
binding.tabLayout.removeAllTabs();
binding.tabLayout.setTabGravity(TabLayout.GRAVITY_FILL);
- final CardTabAdapter adapter = new CardTabAdapter(getSupportFragmentManager(), getLifecycle());
+ final CardTabAdapter adapter = new CardTabAdapter(this);
final TabLayoutMediator mediator = new TabLayoutMediator(binding.tabLayout, binding.pager, (tab, position) -> {
tab.setIcon(!viewModel.isCreateMode() && viewModel.hasCommentsAbility()
? tabIconsWithComments[position]
@@ -272,7 +306,7 @@ public class EditActivity extends BrandedActivity {
new BrandedAlertDialogBuilder(this)
.setTitle(R.string.simple_save)
.setMessage(R.string.do_you_want_to_save_your_changes)
- .setPositiveButton(R.string.simple_save, (dialog, whichButton) -> saveAndRun(super::finish))
+ .setPositiveButton(R.string.simple_save, (dialog, whichButton) -> saveAndFinish())
.setNegativeButton(R.string.simple_discard, (dialog, whichButton) -> super.finish()).show();
} else {
super.finish();
@@ -288,39 +322,43 @@ public class EditActivity extends BrandedActivity {
@Override
public void applyBrand(int mainColor) {
- if (isBrandingEnabled(this)) {
- final Drawable navigationIcon = binding.toolbar.getNavigationIcon();
- if (navigationIcon == null) {
- DeckLog.error("Expected navigationIcon to be present.");
- } else {
- DrawableCompat.setTint(binding.toolbar.getNavigationIcon(), colorAccent);
- }
- applyBrandToPrimaryTabLayout(mainColor, binding.tabLayout);
+ final Drawable navigationIcon = binding.toolbar.getNavigationIcon();
+ if (navigationIcon == null) {
+ DeckLog.error("Expected navigationIcon to be present.");
+ } else {
+ DrawableCompat.setTint(binding.toolbar.getNavigationIcon(), colorAccent);
}
+ applyBrandToPrimaryTabLayout(mainColor, binding.tabLayout);
+ }
+
+ @NonNull
+ public static Intent createNewCardIntent(@NonNull Context context, @NonNull Account account, Long boardLocalId, Long stackId, @Nullable String title, @Nullable String description) {
+ return createNewCardIntent(context, account, boardLocalId, stackId, title)
+ .putExtra(BUNDLE_KEY_DESCRIPTION, description);
}
@NonNull
- public static Intent createNewCardIntent(@NonNull Context context, @NonNull Account account, Long boardId, Long stackId, @NonNull String title) {
- return createNewCardIntent(context, account, boardId, stackId)
+ public static Intent createNewCardIntent(@NonNull Context context, @NonNull Account account, Long boardLocalId, Long stackId, @Nullable String title) {
+ return createNewCardIntent(context, account, boardLocalId, stackId)
.putExtra(BUNDLE_KEY_TITLE, title);
}
@NonNull
- public static Intent createNewCardIntent(@NonNull Context context, @NonNull Account account, Long boardId, Long stackId) {
- return createBasicIntent(context, account, boardId)
+ public static Intent createNewCardIntent(@NonNull Context context, @NonNull Account account, Long boardLocalId, Long stackId) {
+ return createBasicIntent(context, account, boardLocalId)
.putExtra(BUNDLE_KEY_STACK_ID, stackId);
}
@NonNull
- public static Intent createEditCardIntent(@NonNull Context context, @NonNull Account account, Long boardId, Long cardId) {
- return createBasicIntent(context, account, boardId)
+ public static Intent createEditCardIntent(@NonNull Context context, @NonNull Account account, Long boardLocalId, Long cardId) {
+ return createBasicIntent(context, account, boardLocalId)
.putExtra(BUNDLE_KEY_CARD_ID, cardId);
}
- private static Intent createBasicIntent(@NonNull Context context, @NonNull Account account, Long boardId) {
+ private static Intent createBasicIntent(@NonNull Context context, @NonNull Account account, Long boardLocalId) {
return new Intent(context, EditActivity.class)
.putExtra(BUNDLE_KEY_ACCOUNT, account)
- .putExtra(BUNDLE_KEY_BOARD_ID, boardId)
+ .putExtra(BUNDLE_KEY_BOARD_ID, boardLocalId)
.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java
index c754d0800..321f70984 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java
@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
import it.niedermann.nextcloud.deck.model.Board;
@@ -130,32 +131,33 @@ public class EditCardViewModel extends AndroidViewModel {
return syncManager.getFullBoardById(accountId, localId);
}
- public WrappedLiveData<Label> createLabel(long accountId, Label label, long localBoardId) {
- return syncManager.createLabel(accountId, label, localBoardId);
+ public void createLabel(long accountId, Label label, long localBoardId, @NonNull ResponseCallback<Label> callback) {
+ syncManager.createLabel(accountId, label, localBoardId, callback);
}
public LiveData<FullCardWithProjects> getFullCardWithProjectsByLocalId(long accountId, long cardLocalId) {
return syncManager.getFullCardWithProjectsByLocalId(accountId, cardLocalId);
}
- public WrappedLiveData<FullCard> createFullCard(long accountId, long localBoardId, long localStackId, @NonNull FullCard card) {
- return syncManager.createFullCard(accountId, localBoardId, localStackId, card);
- }
-
- public WrappedLiveData<FullCard> updateCard(@NonNull FullCard card) {
- return syncManager.updateCard(card);
+ /**
+ * Saves the current {@link #fullCard}. If it is a new card, it will be created, otherwise it will be updated.
+ */
+ public WrappedLiveData<FullCard> saveCard() {
+ return isCreateMode()
+ ? syncManager.createFullCard(getAccount().getId(), getBoardId(), getFullCard().getCard().getStackId(), getFullCard())
+ : syncManager.updateCard(getFullCard());
}
public LiveData<List<Activity>> syncActivitiesForCard(@NonNull Card card) {
return syncManager.syncActivitiesForCard(card);
}
- public WrappedLiveData<Attachment> addAttachmentToCard(long accountId, long localCardId, @NonNull String mimeType, @NonNull File file) {
- return syncManager.addAttachmentToCard(accountId, localCardId, mimeType, file);
+ public void addAttachmentToCard(long accountId, long localCardId, @NonNull String mimeType, @NonNull File file, @NonNull ResponseCallback<Attachment> callback) {
+ syncManager.addAttachmentToCard(accountId, localCardId, mimeType, file, callback);
}
- public WrappedLiveData<Void> deleteAttachmentOfCard(long accountId, long localCardId, long localAttachmentId) {
- return syncManager.deleteAttachmentOfCard(accountId, localCardId, localAttachmentId);
+ public void deleteAttachmentOfCard(long accountId, long localCardId, long localAttachmentId, @NonNull ResponseCallback<Void> callback) {
+ syncManager.deleteAttachmentOfCard(accountId, localCardId, localAttachmentId, callback);
}
public LiveData<Card> getCardByRemoteID(long accountId, long remoteId) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/LabelAutoCompleteAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/LabelAutoCompleteAdapter.java
index fe974f2a0..de9d23829 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/LabelAutoCompleteAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/LabelAutoCompleteAdapter.java
@@ -12,6 +12,7 @@ import androidx.activity.ComponentActivity;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.lifecycle.LiveData;
@@ -68,7 +69,7 @@ public class LabelAutoCompleteAdapter extends AutoCompleteAdapter<Label> {
binding.label.setTextColor(color);
if (ITEM_CREATE == label.getLocalId()) {
- final Drawable plusIcon = DrawableCompat.wrap(binding.label.getContext().getResources().getDrawable(R.drawable.ic_plus));
+ final Drawable plusIcon = DrawableCompat.wrap(ContextCompat.getDrawable(binding.label.getContext(), R.drawable.ic_plus));
DrawableCompat.setTint(plusIcon, color);
binding.label.setChipIcon(plusIcon);
} else {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java
index 34d2eb3f3..b18f35de0 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java
@@ -24,10 +24,13 @@ import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.ui.branding.BrandedDeleteAlertDialogBuilder;
import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
+import it.niedermann.nextcloud.deck.ui.card.attachments.previewdialog.PreviewDialog;
import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
-@Deprecated
+/**
+ * TODO maybe this can be merged with {@link PreviewDialog}
+ */
public class CardAssigneeDialog extends BrandedDialogFragment {
private static final String KEY_USER = "user";
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentViewHolder.java
index 533bbe322..51087584e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentViewHolder.java
@@ -17,6 +17,7 @@ import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
+import it.niedermann.nextcloud.deck.model.enums.EAttachmentType;
import it.niedermann.nextcloud.deck.ui.branding.BrandingUtil;
import it.niedermann.nextcloud.deck.util.AttachmentUtil;
@@ -26,7 +27,10 @@ public abstract class AttachmentViewHolder extends RecyclerView.ViewHolder {
}
public void bind(@NonNull Account account, @NonNull MenuInflater menuInflater, @NonNull FragmentManager fragmentManager, Long cardRemoteId, Attachment attachment, @Nullable View.OnClickListener onClickListener, @ColorInt int mainColor) {
- bind(menuInflater, fragmentManager, cardRemoteId, attachment, onClickListener, mainColor, AttachmentUtil.getRemoteOrLocalUrl(account.getUrl(), cardRemoteId, attachment));
+ final String downloadUrl = (attachment.getId() == null || cardRemoteId == null)
+ ? attachment.getLocalPath()
+ : AttachmentUtil.getCopyDownloadUrl(account, cardRemoteId, attachment);
+ bind(menuInflater, fragmentManager, cardRemoteId, attachment, onClickListener, mainColor, downloadUrl);
}
@CallSuper
@@ -34,10 +38,15 @@ public abstract class AttachmentViewHolder extends RecyclerView.ViewHolder {
setNotSyncedYetStatus(!DBStatus.LOCAL_EDITED.equals(attachment.getStatusEnum()), mainColor);
itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
menuInflater.inflate(R.menu.attachment_menu, menu);
- menu.findItem(R.id.delete).setOnMenuItemClickListener(item -> {
- DeleteAttachmentDialogFragment.newInstance(attachment).show(fragmentManager, DeleteAttachmentDialogFragment.class.getCanonicalName());
- return false;
- });
+ if(EAttachmentType.DECK_FILE.equals(attachment.getType())) {
+ menu.findItem(R.id.delete).setOnMenuItemClickListener(item -> {
+ DeleteAttachmentDialogFragment.newInstance(attachment).show(fragmentManager, DeleteAttachmentDialogFragment.class.getCanonicalName());
+ return false;
+ });
+ menu.findItem(R.id.delete).setVisible(true);
+ } else {
+ menu.findItem(R.id.delete).setVisible(false);
+ }
if (attachmentUri == null || attachment.getId() == null || cardRemoteId == null) {
menu.findItem(android.R.id.copyUrl).setVisible(false);
} else {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java
index d601f6bbd..16af8bb8f 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java
@@ -3,7 +3,6 @@ package it.niedermann.nextcloud.deck.ui.card.attachments;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
-import android.os.Build;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.View;
@@ -103,7 +102,7 @@ public class CardAttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHo
onClickListener = (event) -> {
attachmentClickedListener.onAttachmentClicked(position);
final Intent intent = AttachmentsActivity.createIntent(context, account, cardLocalId, attachment.getLocalId());
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && context instanceof Activity) {
+ if (context instanceof Activity) {
String transitionName = context.getString(R.string.transition_attachment_preview, String.valueOf(attachment.getLocalId()));
holder.getPreview().setTransitionName(transitionName);
context.startActivity(intent, ActivityOptionsCompat.makeSceneTransitionAnimation((Activity) context, holder.getPreview(), transitionName).toBundle());
@@ -115,7 +114,7 @@ public class CardAttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHo
}
case VIEW_TYPE_DEFAULT:
default: {
- onClickListener = (event) -> openAttachmentInBrowser(context, account.getUrl(), cardRemoteId, attachment.getId());
+ onClickListener = (event) -> openAttachmentInBrowser(account, context, cardRemoteId, attachment);
break;
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java
index 9cb3fb778..0cb59c006 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java
@@ -45,12 +45,13 @@ import id.zelory.compressor.constraint.SizeConstraint;
import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.databinding.FragmentCardEditTabAttachmentsBinding;
import it.niedermann.nextcloud.deck.exceptions.UploadAttachmentFailedException;
import it.niedermann.nextcloud.deck.model.Attachment;
+import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment;
import it.niedermann.nextcloud.deck.ui.branding.BrandedSnackbar;
import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
@@ -74,7 +75,6 @@ import static android.Manifest.permission.READ_CONTACTS;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.app.Activity.RESULT_OK;
import static android.os.Build.VERSION.SDK_INT;
-import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static android.os.Build.VERSION_CODES.M;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
@@ -84,11 +84,10 @@ import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_
import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToFAB;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
import static it.niedermann.nextcloud.deck.ui.card.attachments.CardAttachmentAdapter.VIEW_TYPE_DEFAULT;
import static it.niedermann.nextcloud.deck.ui.card.attachments.CardAttachmentAdapter.VIEW_TYPE_IMAGE;
-import static it.niedermann.nextcloud.deck.util.AttachmentUtil.copyContentUriToTempFile;
+import static it.niedermann.nextcloud.deck.util.FilesUtil.copyContentUriToTempFile;
import static java.net.HttpURLConnection.HTTP_CONFLICT;
public class CardAttachmentsFragment extends BrandedFragment implements AttachmentDeletedListener, AttachmentClickedListener {
@@ -214,15 +213,11 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
if (editViewModel.canEdit()) {
binding.fab.setOnClickListener(v -> {
- if (SDK_INT < LOLLIPOP) {
- openNativeFilePicker();
- } else {
- binding.bottomNavigation.setSelectedItemId(R.id.gallery);
- showGalleryPicker();
- mBottomSheetBehaviour.setState(STATE_COLLAPSED);
- backPressedCallback.setEnabled(true);
- requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), backPressedCallback);
- }
+ binding.bottomNavigation.setSelectedItemId(R.id.gallery);
+ showGalleryPicker();
+ mBottomSheetBehaviour.setState(STATE_COLLAPSED);
+ backPressedCallback.setEnabled(true);
+ requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), backPressedCallback);
});
binding.fab.show();
binding.attachmentsList.addOnScrollListener(new RecyclerView.OnScrollListener() {
@@ -239,9 +234,7 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
binding.emptyContentView.hideDescription();
}
@Nullable Context context = requireContext();
- applyBrand(isBrandingEnabled(context)
- ? readBrandMainColor(context)
- : ContextCompat.getColor(context, R.color.defaultBrand));
+ applyBrand(readBrandMainColor(context));
return binding.getRoot();
}
@@ -309,35 +302,29 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
requestPermissions(new String[]{READ_EXTERNAL_STORAGE}, REQUEST_CODE_PICK_FILE_PERMISSION);
} else {
unbindPickerAdapter();
- if (SDK_INT >= LOLLIPOP) {
-// if (SDK_INT >= Build.VERSION_CODES.Q) {
-// // TODO Only usable with Scoped Storage
-// pickerAdapter = new FileAdapter(requireContext(), uri -> onActivityResult(REQUEST_CODE_PICK_FILE, RESULT_OK, new Intent().setData(uri)), this::openNativeFilePicker);
-// } else {
- pickerAdapter = new FileAdapterLegacy((uri, pair) -> {
- previewViewModel.prepareDialog(pair.first, pair.second);
- PreviewDialog.newInstance().show(getChildFragmentManager(), PreviewDialog.class.getSimpleName());
- observeOnce(previewViewModel.getResult(), getViewLifecycleOwner(), (submitPositive) -> {
- if (submitPositive) {
- onActivityResult(REQUEST_CODE_PICK_FILE, RESULT_OK, new Intent().setData(uri));
- }
- });
- }, this::openNativeFilePicker);
-// }
- removeGalleryItemDecoration();
- binding.pickerRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
- binding.pickerRecyclerView.setAdapter(pickerAdapter);
- }
+// if (SDK_INT >= Build.VERSION_CODES.Q) {
+// // TODO Only usable with Scoped Storage
+// pickerAdapter = new FileAdapter(requireContext(), uri -> onActivityResult(REQUEST_CODE_PICK_FILE, RESULT_OK, new Intent().setData(uri)), this::openNativeFilePicker);
+// } else {
+ pickerAdapter = new FileAdapterLegacy((uri, pair) -> {
+ previewViewModel.prepareDialog(pair.first, pair.second);
+ PreviewDialog.newInstance().show(getChildFragmentManager(), PreviewDialog.class.getSimpleName());
+ observeOnce(previewViewModel.getResult(), getViewLifecycleOwner(), (submitPositive) -> {
+ if (submitPositive) {
+ onActivityResult(REQUEST_CODE_PICK_FILE, RESULT_OK, new Intent().setData(uri));
+ }
+ });
+ }, this::openNativeFilePicker);
+// }
+ removeGalleryItemDecoration();
+ binding.pickerRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
+ binding.pickerRecyclerView.setAdapter(pickerAdapter);
}
}
}
private void openNativeCameraPicker() {
- if (SDK_INT >= LOLLIPOP) {
- startActivityForResult(TakePhotoActivity.createIntent(requireContext()), REQUEST_CODE_PICK_CAMERA);
- } else {
- ExceptionDialogFragment.newInstance(new UnsupportedOperationException("This feature requires Android 5"), editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- }
+ startActivityForResult(TakePhotoActivity.createIntent(requireContext()), REQUEST_CODE_PICK_CAMERA);
}
private void openNativeContactPicker() {
@@ -412,28 +399,38 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
super.onDestroy();
}
- private void uploadNewAttachmentFromUri(@NonNull Uri sourceUri, String mimeType) throws UploadAttachmentFailedException, IOException {
+ private void uploadNewAttachmentFromUri(@NonNull Uri sourceUri, String mimeType) throws UploadAttachmentFailedException {
if (sourceUri == null) {
throw new UploadAttachmentFailedException("sourceUri is null");
}
switch (sourceUri.getScheme()) {
case ContentResolver.SCHEME_CONTENT:
case ContentResolver.SCHEME_FILE: {
- DeckLog.verbose("--- found content URL " + sourceUri.getPath());
- final File originalFile = copyContentUriToTempFile(requireContext(), sourceUri, editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId());
- if (MimeTypeUtil.isImage(mimeType)) {
- JavaCompressor.compress(
- (AppCompatActivity) requireActivity(),
- originalFile,
- (status, file) -> uploadNewAttachmentFromFile(status ? file : originalFile, mimeType),
- new ResolutionConstraint(1920, 1920),
- new SizeConstraint(1_000_000, 10, 10, 10),
- new FormatConstraint(Bitmap.CompressFormat.JPEG),
- new QualityConstraint(80)
- );
- } else {
- uploadNewAttachmentFromFile(originalFile, mimeType);
- }
+ DeckLog.verbose("--- found content URL", sourceUri.getPath());
+ // Separate Thread required because picked file might not yet be locally available
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/814
+ new Thread(() -> {
+ try {
+ final File originalFile = copyContentUriToTempFile(requireContext(), sourceUri, editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId());
+ requireActivity().runOnUiThread(() -> {
+ if (MimeTypeUtil.isImage(mimeType)) {
+ JavaCompressor.compress(
+ (AppCompatActivity) requireActivity(),
+ originalFile,
+ (status, file) -> uploadNewAttachmentFromFile(status ? file : originalFile, mimeType),
+ new ResolutionConstraint(1920, 1920),
+ new SizeConstraint(1_000_000, 10, 10, 10),
+ new FormatConstraint(Bitmap.CompressFormat.JPEG),
+ new QualityConstraint(80)
+ );
+ } else {
+ uploadNewAttachmentFromFile(originalFile, mimeType);
+ }
+ });
+ } catch (IOException e) {
+ requireActivity().runOnUiThread(() -> ExceptionDialogFragment.newInstance(e, editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
+ }
+ }).start();
break;
}
default: {
@@ -464,22 +461,29 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
editViewModel.getFullCard().getAttachments().add(0, a);
adapter.addAttachment(a);
if (!editViewModel.isCreateMode()) {
- WrappedLiveData<Attachment> liveData = editViewModel.addAttachmentToCard(editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId(), a.getMimetype(), fileToUpload);
- observeOnce(liveData, getViewLifecycleOwner(), (next) -> {
- if (liveData.hasError()) {
- Throwable t = liveData.getError();
- if (t instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) t).getStatusCode() == HTTP_CONFLICT) {
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/534
+ editViewModel.addAttachmentToCard(editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId(), a.getMimetype(), fileToUpload, new ResponseCallback<Attachment>() {
+ @Override
+ public void onResponse(Attachment response) {
+ requireActivity().runOnUiThread(() -> {
editViewModel.getFullCard().getAttachments().remove(a);
- adapter.removeAttachment(a);
- BrandedSnackbar.make(binding.coordinatorLayout, R.string.attachment_already_exists, Snackbar.LENGTH_LONG).show();
- } else {
- ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Unknown URI scheme", t), editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- }
- } else {
- editViewModel.getFullCard().getAttachments().remove(a);
- editViewModel.getFullCard().getAttachments().add(0, next);
- adapter.replaceAttachment(a, next);
+ editViewModel.getFullCard().getAttachments().add(0, response);
+ adapter.replaceAttachment(a, response);
+ });
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ requireActivity().runOnUiThread(() -> {
+ if (throwable instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) throwable).getStatusCode() == HTTP_CONFLICT) {
+ ResponseCallback.super.onError(throwable);
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/534
+ editViewModel.getFullCard().getAttachments().remove(a);
+ adapter.removeAttachment(a);
+ BrandedSnackbar.make(binding.coordinatorLayout, R.string.attachment_already_exists, Snackbar.LENGTH_LONG).show();
+ } else {
+ ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Unknown URI scheme", throwable), editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ }
+ });
}
});
}
@@ -522,10 +526,18 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
adapter.removeAttachment(attachment);
editViewModel.getFullCard().getAttachments().remove(attachment);
if (!editViewModel.isCreateMode() && attachment.getLocalId() != null) {
- final WrappedLiveData<Void> deleteLiveData = editViewModel.deleteAttachmentOfCard(editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId(), attachment.getLocalId());
- observeOnce(deleteLiveData, this, (next) -> {
- if (deleteLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteLiveData.getError())) {
- ExceptionDialogFragment.newInstance(deleteLiveData.getError(), editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ editViewModel.deleteAttachmentOfCard(editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId(), attachment.getLocalId(), new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully delete", Attachment.class.getSimpleName(), attachment.getFilename(), "from", Card.class.getSimpleName(), editViewModel.getFullCard().getCard().getTitle());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ requireActivity().runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
+ }
}
});
}
@@ -539,7 +551,6 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
@Override
public void applyBrand(int mainColor) {
applyBrandToFAB(mainColor, binding.fab);
- adapter.applyBrand(mainColor);
@ColorInt final int finalMainColor = DeckColorUtil.contrastRatioIsSufficient(mainColor, primaryColor)
? mainColor
: accentColor;
@@ -555,6 +566,13 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
);
binding.bottomNavigation.setItemIconTintList(list);
binding.bottomNavigation.setItemTextColor(list);
+
+ // applyBrand() is also called onStart
+ // adapter might be null at this point
+ // https://github.com/stefan-niedermann/nextcloud-deck/issues/782
+ if (adapter != null) {
+ adapter.applyBrand(mainColor);
+ }
}
public static Fragment newInstance() {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/DefaultAttachmentViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/DefaultAttachmentViewHolder.java
index 2b5358eb9..b41a4e9d1 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/DefaultAttachmentViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/DefaultAttachmentViewHolder.java
@@ -40,7 +40,7 @@ public class DefaultAttachmentViewHolder extends AttachmentViewHolder {
public void bind(@NonNull Account account, @NonNull MenuInflater menuInflater, @NonNull FragmentManager fragmentManager, Long cardRemoteId, Attachment attachment, @Nullable View.OnClickListener onClickListener, @ColorInt int mainColor) {
super.bind(account, menuInflater, fragmentManager, cardRemoteId, attachment, onClickListener, mainColor);
getPreview().setImageResource(getIconForMimeType(attachment.getMimetype()));
- itemView.setOnClickListener((event) -> openAttachmentInBrowser(itemView.getContext(), account.getUrl(), cardRemoteId, attachment.getId()));
+ itemView.setOnClickListener((event) -> openAttachmentInBrowser(account, itemView.getContext(), cardRemoteId, attachment));
binding.filename.setText(attachment.getBasename());
binding.filesize.setText(Formatter.formatFileSize(binding.filesize.getContext(), attachment.getFilesize()));
if (attachment.getLastModifiedLocal() != null) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/ImageAttachmentViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/ImageAttachmentViewHolder.java
index 3c95da1b7..b93b3d025 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/ImageAttachmentViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/ImageAttachmentViewHolder.java
@@ -37,10 +37,13 @@ public class ImageAttachmentViewHolder extends AttachmentViewHolder {
}
public void bind(@NonNull Account account, @NonNull MenuInflater menuInflater, @NonNull FragmentManager fragmentManager, Long cardRemoteId, Attachment attachment, @Nullable View.OnClickListener onClickListener, @ColorInt int mainColor) {
- super.bind(menuInflater, fragmentManager, cardRemoteId, attachment, onClickListener, mainColor, AttachmentUtil.getRemoteOrLocalUrl(account.getUrl(), cardRemoteId, attachment));
+ final String downloadUrl = (attachment.getId() == null || cardRemoteId == null)
+ ? attachment.getLocalPath()
+ : AttachmentUtil.getCopyDownloadUrl(account, cardRemoteId, attachment);
+ super.bind(menuInflater, fragmentManager, cardRemoteId, attachment, onClickListener, mainColor, downloadUrl);
getPreview().post(() -> {
- @Nullable final String uri = AttachmentUtil.getThumbnailUrl(account.getServerDeckVersionAsObject(), account.getUrl(), cardRemoteId, attachment, getPreview().getWidth());
+ @Nullable final String uri = AttachmentUtil.getThumbnailUrl(account, cardRemoteId, attachment, getPreview().getWidth());
Glide.with(getPreview().getContext())
.load(uri)
.placeholder(R.drawable.ic_image_grey600_24dp)
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsAdapter.java
index 169a37c79..4fde5a4e1 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsAdapter.java
@@ -11,7 +11,9 @@ import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.databinding.ItemCommentBinding;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment;
@@ -21,7 +23,7 @@ import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMai
public class CardCommentsAdapter extends RecyclerView.Adapter<ItemCommentViewHolder> {
- private int mainColor;
+ private final int mainColor;
@NonNull
private final List<FullDeckComment> comments = new ArrayList<>();
@NonNull
@@ -34,13 +36,16 @@ public class CardCommentsAdapter extends RecyclerView.Adapter<ItemCommentViewHol
private final CommentSelectAsReplyListener selectAsReplyListener;
@NonNull
private final FragmentManager fragmentManager;
+ @NonNull
+ private final CommentEditedListener editListener;
- CardCommentsAdapter(@NonNull Context context, @NonNull Account account, @NonNull MenuInflater menuInflater, @NonNull CommentDeletedListener deletedListener, @NonNull CommentSelectAsReplyListener selectAsReplyListener, @NonNull FragmentManager fragmentManager) {
+ CardCommentsAdapter(@NonNull Context context, @NonNull Account account, @NonNull MenuInflater menuInflater, @NonNull CommentDeletedListener deletedListener, @NonNull CommentSelectAsReplyListener selectAsReplyListener, @NonNull FragmentManager fragmentManager, CommentEditedListener editListener) {
this.account = account;
this.menuInflater = menuInflater;
this.deletedListener = deletedListener;
this.selectAsReplyListener = selectAsReplyListener;
this.fragmentManager = fragmentManager;
+ this.editListener = editListener;
this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, readBrandMainColor(context));
setHasStableIds(true);
}
@@ -58,7 +63,19 @@ public class CardCommentsAdapter extends RecyclerView.Adapter<ItemCommentViewHol
@Override
public void onBindViewHolder(@NonNull ItemCommentViewHolder holder, int position) {
- holder.bind(comments.get(position), account, mainColor, menuInflater, deletedListener, selectAsReplyListener, fragmentManager);
+ final FullDeckComment comment = comments.get(position);
+ holder.bind(comment, account, mainColor, menuInflater, deletedListener, selectAsReplyListener, fragmentManager, (changedText) -> {
+ if (!Objects.equals(changedText, comment.getComment().getMessage())) {
+ DeckLog.info("Toggled checkbox in comment with localId", comment.getLocalId());
+ this.editListener.onCommentEdited(comment.getLocalId(), changedText.toString());
+ }
+ });
+ }
+
+ @Override
+ public void onViewRecycled(@NonNull ItemCommentViewHolder holder) {
+ super.onViewRecycled(holder);
+ holder.unbind();
}
@SuppressWarnings("WeakerAccess")
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java
index 3fd536aa6..2f538f9bb 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java
@@ -17,24 +17,24 @@ import androidx.lifecycle.ViewModelProvider;
import java.time.Instant;
+import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.databinding.FragmentCardEditTabCommentsBinding;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment;
import it.niedermann.nextcloud.deck.ui.card.EditActivity;
import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
+import it.niedermann.nextcloud.deck.util.ViewUtil;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
-import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToFAB;
-import static it.niedermann.nextcloud.deck.util.ViewUtil.setupMentions;
public class CardCommentsFragment extends BrandedFragment implements CommentEditedListener, CommentDeletedListener, CommentSelectAsReplyListener {
@@ -70,17 +70,18 @@ public class CardCommentsFragment extends BrandedFragment implements CommentEdit
commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class);
- adapter = new CardCommentsAdapter(requireContext(), mainViewModel.getAccount(), requireActivity().getMenuInflater(), this, this, getChildFragmentManager());
+ adapter = new CardCommentsAdapter(requireContext(), mainViewModel.getAccount(), requireActivity().getMenuInflater(), this, this, getChildFragmentManager(), this);
binding.comments.setAdapter(adapter);
-
binding.replyCommentCancelButton.setOnClickListener((v) -> commentsViewModel.setReplyToComment(null));
+ ViewUtil.addAvatar(binding.avatar, mainViewModel.getAccount().getUrl(), mainViewModel.getAccount().getUserName(), DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.icon_size_details), R.drawable.ic_person_grey600_24dp);
+
commentsViewModel.getReplyToComment().observe(getViewLifecycleOwner(), (comment) -> {
if (comment == null) {
binding.replyComment.setVisibility(GONE);
} else {
- binding.replyCommentText.setText(comment.getComment().getMessage());
+ binding.replyCommentText.setMarkdownString(comment.getComment().getMessage());
binding.replyComment.setVisibility(VISIBLE);
- setupMentions(mainViewModel.getAccount(), comment.getComment().getMentions(), binding.replyCommentText);
+// setupMentions(mainViewModel.getAccount(), comment.getComment().getMentions(), binding.replyCommentText);
}
});
commentsViewModel.getFullCommentsForLocalCardId(mainViewModel.getFullCard().getLocalId()).observe(getViewLifecycleOwner(),
@@ -140,10 +141,18 @@ public class CardCommentsFragment extends BrandedFragment implements CommentEdit
@Override
public void onCommentDeleted(Long localId) {
- final WrappedLiveData<Void> deleteLiveData = commentsViewModel.deleteComment(mainViewModel.getAccount().getId(), mainViewModel.getFullCard().getLocalId(), localId);
- observeOnce(deleteLiveData, this, (next) -> {
- if (deleteLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteLiveData.getError())) {
- ExceptionDialogFragment.newInstance(deleteLiveData.getError(), mainViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ commentsViewModel.deleteComment(mainViewModel.getAccount().getId(), mainViewModel.getFullCard().getLocalId(), localId, new ResponseCallback<Void>() {
+ @Override
+ public void onResponse(Void response) {
+ DeckLog.info("Successfully deleted comment with localId", localId);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ if (!SyncManager.ignoreExceptionOnVoidError(throwable)) {
+ ResponseCallback.super.onError(throwable);
+ requireActivity().runOnUiThread(() -> ExceptionDialogFragment.newInstance(throwable, mainViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName()));
+ }
}
});
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CommentsViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CommentsViewModel.java
index dada94d5b..7e29769cd 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CommentsViewModel.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CommentsViewModel.java
@@ -9,10 +9,12 @@ import androidx.lifecycle.MutableLiveData;
import java.util.List;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
+
+import static androidx.lifecycle.Transformations.distinctUntilChanged;
@SuppressWarnings("WeakerAccess")
public class CommentsViewModel extends AndroidViewModel {
@@ -35,7 +37,7 @@ public class CommentsViewModel extends AndroidViewModel {
}
public LiveData<List<FullDeckComment>> getFullCommentsForLocalCardId(long localCardId) {
- return syncManager.getFullCommentsForLocalCardId(localCardId);
+ return distinctUntilChanged(syncManager.getFullCommentsForLocalCardId(localCardId));
}
public void addCommentToCard(long accountId, long cardId, @NonNull DeckComment comment) {
@@ -46,7 +48,7 @@ public class CommentsViewModel extends AndroidViewModel {
syncManager.updateComment(accountId, localCardId, localCommentId, comment);
}
- public WrappedLiveData<Void> deleteComment(long accountId, long localCardId, long localCommentId) {
- return syncManager.deleteComment(accountId, localCardId, localCommentId);
+ public void deleteComment(long accountId, long localCardId, long localCommentId, @NonNull ResponseCallback<Void> callback) {
+ syncManager.deleteComment(accountId, localCardId, localCommentId, callback);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java
index 3e540c95e..c85f9d7b5 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java
@@ -1,5 +1,6 @@
package it.niedermann.nextcloud.deck.ui.card.comments;
+import android.text.method.LinkMovementMethod;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
@@ -14,6 +15,9 @@ import androidx.recyclerview.widget.RecyclerView;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.function.Consumer;
import it.niedermann.android.util.ClipboardUtil;
import it.niedermann.android.util.DimensionUtil;
@@ -21,12 +25,11 @@ import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemCommentBinding;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
+import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment;
import it.niedermann.nextcloud.deck.util.DateUtil;
import it.niedermann.nextcloud.deck.util.ViewUtil;
-import static it.niedermann.nextcloud.deck.util.ViewUtil.setupMentions;
-
public class ItemCommentViewHolder extends RecyclerView.ViewHolder {
private final ItemCommentBinding binding;
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
@@ -35,15 +38,22 @@ public class ItemCommentViewHolder extends RecyclerView.ViewHolder {
public ItemCommentViewHolder(ItemCommentBinding binding) {
super(binding.getRoot());
this.binding = binding;
+ this.binding.message.setMovementMethod(LinkMovementMethod.getInstance());
}
- public void bind(@NonNull FullDeckComment comment, @NonNull Account account, @ColorInt int mainColor, @NonNull MenuInflater inflater, @NonNull CommentDeletedListener deletedListener, @NonNull CommentSelectAsReplyListener selectAsReplyListener, @NonNull FragmentManager fragmentManager) {
+ public void bind(@NonNull FullDeckComment comment, @NonNull Account account, @ColorInt int mainColor, @NonNull MenuInflater inflater, @NonNull CommentDeletedListener deletedListener, @NonNull CommentSelectAsReplyListener selectAsReplyListener, @NonNull FragmentManager fragmentManager, @NonNull Consumer<CharSequence> editListener) {
ViewUtil.addAvatar(binding.avatar, account.getUrl(), comment.getComment().getActorId(), DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.icon_size_details), R.drawable.ic_person_grey600_24dp);
+ final Map<String, String> mentions = new HashMap<>(comment.getComment().getMentions().size());
+ for (Mention mention : comment.getComment().getMentions()) {
+ mentions.put(mention.getMentionId(), mention.getMentionDisplayName());
+ }
binding.message.setText(comment.getComment().getMessage());
+ binding.message.setMarkdownString(comment.getComment().getMessage(), mentions);
+ binding.message.setMarkdownStringChangedListener(editListener);
binding.actorDisplayName.setText(comment.getComment().getActorDisplayName());
binding.creationDateTime.setText(DateUtil.getRelativeDateTimeString(binding.creationDateTime.getContext(), comment.getComment().getCreationDateTime().toEpochMilli()));
- itemView.setOnClickListener(View::showContextMenu);
+ itemView.setOnClickListener(View::showContextMenu);
itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
inflater.inflate(R.menu.comment_menu, menu);
menu.findItem(android.R.id.copy).setOnMenuItemClickListener(item -> ClipboardUtil.INSTANCE.copyToClipboard(itemView.getContext(), comment.getComment().getMessage()));
@@ -72,12 +82,10 @@ public class ItemCommentViewHolder extends RecyclerView.ViewHolder {
}
});
+ TooltipCompat.setTooltipText(binding.creationDateTime, comment.getComment().getCreationDateTime().atZone(ZoneId.systemDefault()).format(dateFormatter));
DrawableCompat.setTint(binding.notSyncedYet.getDrawable(), mainColor);
binding.notSyncedYet.setVisibility(DBStatus.LOCAL_EDITED.equals(comment.getStatusEnum()) ? View.VISIBLE : View.GONE);
- TooltipCompat.setTooltipText(binding.creationDateTime, comment.getComment().getCreationDateTime().atZone(ZoneId.systemDefault()).format(dateFormatter));
- setupMentions(account, comment.getComment().getMentions(), binding.message);
-
if (comment.getParent() == null) {
binding.parentContainer.setVisibility(View.GONE);
} else {
@@ -95,4 +103,9 @@ public class ItemCommentViewHolder extends RecyclerView.ViewHolder {
});
}
}
+
+ public void unbind() {
+ binding.message.setText("");
+ binding.message.setMarkdownStringChangedListener(null);
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java
index 5251291c8..8fa24988d 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java
@@ -1,12 +1,14 @@
package it.niedermann.nextcloud.deck.ui.card.comments.util;
+import androidx.annotation.Nullable;
import androidx.core.util.Pair;
public class CommentsUtil {
+ @Nullable
public static Pair<String, Integer> getUserNameForMentionProposal(String text, int cursorPosition) {
- Pair result = null;
+ Pair<String, Integer> result = null;
if (text != null) {
// find start of relevant substring
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java
index 2c697de08..8376a6817 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java
@@ -4,9 +4,8 @@ import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
-import android.text.Editable;
import android.text.TextUtils;
-import android.text.TextWatcher;
+import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -19,6 +18,7 @@ import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.Fragment;
+import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
@@ -28,8 +28,6 @@ import com.wdullaer.materialdatetimepicker.date.DatePickerDialog;
import com.wdullaer.materialdatetimepicker.date.DatePickerDialog.OnDateSetListener;
import com.wdullaer.materialdatetimepicker.time.TimePickerDialog;
import com.wdullaer.materialdatetimepicker.time.TimePickerDialog.OnTimeSetListener;
-import com.yydcdut.markdown.MarkdownProcessor;
-import com.yydcdut.markdown.syntax.edit.EditFactory;
import java.time.Instant;
import java.time.LocalDate;
@@ -43,10 +41,11 @@ import it.niedermann.android.util.ColorUtil;
import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.databinding.FragmentCardEditTabDetailsBinding;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.User;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
+import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.ui.branding.BrandedDatePickerDialog;
import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment;
import it.niedermann.nextcloud.deck.ui.branding.BrandedSnackbar;
@@ -57,11 +56,9 @@ import it.niedermann.nextcloud.deck.ui.card.UserAutoCompleteAdapter;
import it.niedermann.nextcloud.deck.ui.card.assignee.CardAssigneeDialog;
import it.niedermann.nextcloud.deck.ui.card.assignee.CardAssigneeListener;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
-import it.niedermann.nextcloud.deck.util.MarkDownUtil;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
-import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandToEditText;
public class CardDetailsFragment extends BrandedFragment implements OnDateSetListener, OnTimeSetListener, CardAssigneeListener {
@@ -72,6 +69,7 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
private AppCompatActivity activity;
+ boolean editorActive = true;
@Override
public void onAttach(@NonNull Context context) {
@@ -110,7 +108,6 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
setupDueDate();
setupDescription();
setupProjects();
- binding.description.setText(viewModel.getFullCard().getCard().getDescription());
return binding.getRoot();
}
@@ -137,35 +134,48 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
applyBrandToEditText(mainColor, binding.dueDateDate);
applyBrandToEditText(mainColor, binding.dueDateTime);
applyBrandToEditText(mainColor, binding.people);
- applyBrandToEditText(mainColor, binding.description);
+ binding.descriptionEditor.setSearchColor(mainColor);
+ binding.descriptionViewer.setSearchColor(mainColor);
}
private void setupDescription() {
if (viewModel.canEdit()) {
- MarkdownProcessor markdownProcessor = new MarkdownProcessor(requireContext());
- markdownProcessor.config(MarkDownUtil.getMarkDownConfiguration(binding.description.getContext()).build());
- markdownProcessor.factory(EditFactory.create());
- markdownProcessor.live(binding.description);
- binding.description.addTextChangedListener(new TextWatcher() {
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- if (viewModel.getFullCard() != null) {
- viewModel.getFullCard().getCard().setDescription(binding.description.getText().toString());
- }
- }
-
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // Nothing to do
+ binding.descriptionViewer.setMovementMethod(LinkMovementMethod.getInstance());
+ binding.descriptionBar.setOnClickListener((v) -> binding.descriptionEditor.requestFocus());
+ binding.descriptionToggle.setOnClickListener((v) -> {
+ editorActive = !editorActive;
+ if (editorActive) {
+ binding.descriptionEditor.setMarkdownString(viewModel.getFullCard().getCard().getDescription());
+ binding.descriptionBar.setOnClickListener((view) -> binding.descriptionEditor.requestFocus());
+ binding.descriptionEditor.setVisibility(VISIBLE);
+ binding.descriptionViewer.setVisibility(GONE);
+ binding.descriptionToggle.setImageResource(R.drawable.ic_baseline_eye_24);
+ } else {
+ binding.descriptionViewer.setMarkdownString(viewModel.getFullCard().getCard().getDescription());
+ binding.descriptionBar.setOnClickListener(null);
+ binding.descriptionEditor.setVisibility(GONE);
+ binding.descriptionViewer.setVisibility(VISIBLE);
+ binding.descriptionToggle.setImageResource(R.drawable.ic_edit_grey600_24dp);
}
+ });
+ binding.descriptionEditor.setMarkdownString(viewModel.getFullCard().getCard().getDescription());
+ final Observer<CharSequence> descriptionObserver = (description) -> {
- @Override
- public void afterTextChanged(Editable s) {
- // Nothing to do
+ if (viewModel.getFullCard() != null) {
+ viewModel.getFullCard().getCard().setDescription(description == null ? "" : description.toString());
+ } else {
+ ExceptionDialogFragment.newInstance(new IllegalStateException(FullCard.class.getSimpleName() + " was empty when trying to setup description"), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
}
- });
+ binding.descriptionToggle.setVisibility(TextUtils.isEmpty(description) ? GONE : VISIBLE);
+ };
+ binding.descriptionEditor.getMarkdownString().observe(getViewLifecycleOwner(), descriptionObserver);
+ binding.descriptionViewer.getMarkdownString().observe(getViewLifecycleOwner(), descriptionObserver);
} else {
- binding.description.setEnabled(false);
+ binding.descriptionEditor.setEnabled(false);
+ binding.descriptionEditor.setVisibility(VISIBLE);
+ binding.descriptionViewer.setEnabled(false);
+ binding.descriptionViewer.setVisibility(GONE);
+ binding.descriptionViewer.setMarkdownString(viewModel.getFullCard().getCard().getDescription());
}
}
@@ -233,18 +243,23 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
newLabel.setBoardId(boardId);
newLabel.setTitle(((LabelAutoCompleteAdapter) binding.labels.getAdapter()).getLastFilterText());
newLabel.setLocalId(null);
- WrappedLiveData<Label> createLabelLiveData = viewModel.createLabel(accountId, newLabel, boardId);
- observeOnce(createLabelLiveData, CardDetailsFragment.this, createdLabel -> {
- if (createLabelLiveData.hasError()) {
- DeckLog.logError(createLabelLiveData.getError());
- BrandedSnackbar.make(requireView(), getString(R.string.error_create_label, newLabel.getTitle()), Snackbar.LENGTH_LONG)
- .setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(createLabelLiveData.getError(), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName())).show();
- } else {
- newLabel.setLocalId(createdLabel.getLocalId());
- ((LabelAutoCompleteAdapter) binding.labels.getAdapter()).exclude(createdLabel);
- viewModel.getFullCard().getLabels().add(createdLabel);
- binding.labelsGroup.addView(createChipFromLabel(newLabel));
- binding.labelsGroup.setVisibility(VISIBLE);
+ viewModel.createLabel(accountId, newLabel, boardId, new ResponseCallback<Label>() {
+ @Override
+ public void onResponse(Label response) {
+ requireActivity().runOnUiThread(() -> {
+ newLabel.setLocalId(response.getLocalId());
+ ((LabelAutoCompleteAdapter) binding.labels.getAdapter()).exclude(response);
+ viewModel.getFullCard().getLabels().add(response);
+ binding.labelsGroup.addView(createChipFromLabel(newLabel));
+ binding.labelsGroup.setVisibility(VISIBLE);
+ });
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ ResponseCallback.super.onError(throwable);
+ requireActivity().runOnUiThread(() -> BrandedSnackbar.make(requireView(), getString(R.string.error_create_label, newLabel.getTitle()), Snackbar.LENGTH_LONG)
+ .setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(throwable, viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName())).show());
}
});
} else {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceViewHolder.java
index 272945e45..c398d9814 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceViewHolder.java
@@ -86,14 +86,14 @@ public class CardProjectResourceViewHolder extends RecyclerView.ViewHolder {
break;
}
default: {
- DeckLog.info("Unknown resource type for " + resource.getName() + ": " + resource.getType());
+ DeckLog.info("Unknown resource type for", resource.getName() + ":", resource.getType());
binding.type.setVisibility(GONE);
linkifyViewHolder(account, link);
break;
}
}
} else {
- DeckLog.warn("Resource type for " + resource.getName() + " is null");
+ DeckLog.warn("Resource type for", resource.getName(), "is null");
binding.type.setVisibility(GONE);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java
index 6bfd82b13..b87cee5f9 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java
@@ -2,6 +2,7 @@ package it.niedermann.nextcloud.deck.ui.exception.tips;
import android.content.Context;
import android.content.Intent;
+import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
@@ -16,6 +17,7 @@ import androidx.annotation.StringRes;
import androidx.core.util.Consumer;
import androidx.recyclerview.widget.RecyclerView;
+import com.nextcloud.android.sso.Constants;
import com.nextcloud.android.sso.exceptions.NextcloudApiNotRespondingException;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotSupportedException;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
@@ -39,14 +41,15 @@ import static it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment.
public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> {
+ private static final String[] APPS = new String[]{Constants.PACKAGE_NAME_PROD, Constants.PACKAGE_NAME_DEV};
private static final Intent INTENT_APP_INFO = new Intent(ACTION_APPLICATION_DETAILS_SETTINGS)
.setData(Uri.parse("package:" + BuildConfig.APPLICATION_ID))
.putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_open_deck_info);
@NonNull
- private Consumer<Intent> actionButtonClickedListener;
+ private final Consumer<Intent> actionButtonClickedListener;
@NonNull
- private List<TipsModel> tips = new LinkedList<>();
+ private final List<TipsModel> tips = new LinkedList<>();
public TipsAdapter(@NonNull Consumer<Intent> actionButtonClickedListener) {
this.actionButtonClickedListener = actionButtonClickedListener;
@@ -113,6 +116,19 @@ public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> {
}
} else if (throwable instanceof UploadAttachmentFailedException) {
add(R.string.error_dialog_attachment_upload_failed);
+ } else if (throwable instanceof ClassNotFoundException) {
+ final Throwable cause = ((ClassNotFoundException) throwable).getCause();
+ if (cause != null) {
+ final String message = cause.getMessage();
+ if (message != null && message.toLowerCase().contains("certificate")) {
+ final Intent filesOpenIntent = getOpenFilesIntent(context);
+ if (filesOpenIntent == null) {
+ add(R.string.error_dialog_certificate);
+ } else {
+ add(R.string.error_dialog_certificate, filesOpenIntent);
+ }
+ }
+ }
} else if (throwable instanceof DeckException) {
switch (((DeckException) throwable).getHint()) {
case CAPABILITIES_VERSION_NOT_PARSABLE:
@@ -142,7 +158,7 @@ public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> {
add(R.string.error_dialog_tip_clear_storage_might_help);
add(R.string.error_dialog_tip_clear_storage, INTENT_APP_INFO);
} else if (throwable instanceof RuntimeException) {
- if (throwable.getMessage() != null && throwable.getMessage().contains("database")) {
+ if (throwable.getMessage() != null && throwable.getMessage().toLowerCase().contains("database")) {
Intent reportIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(context.getString(R.string.url_report_bug)))
.putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_report_issue);
add(R.string.error_dialog_tip_database_upgrade_failed, reportIntent);
@@ -159,4 +175,18 @@ public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> {
tips.add(new TipsModel(text, primaryAction));
notifyItemInserted(tips.size());
}
+
+ @Nullable
+ private static Intent getOpenFilesIntent(@NonNull Context context) {
+ final PackageManager pm = context.getPackageManager();
+ for (String app : APPS) {
+ try {
+ pm.getPackageInfo(app, PackageManager.GET_ACTIVITIES);
+ return pm.getLaunchIntentForPackage(app)
+ .putExtra(INTENT_EXTRA_BUTTON_TEXT, R.string.error_action_open_nextcloud_app);
+ } catch (PackageManager.NameNotFoundException ignored) {
+ }
+ }
+ return null;
+ }
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsModel.java
index 00bfa6b15..749579812 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsModel.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsModel.java
@@ -8,10 +8,9 @@ import androidx.annotation.StringRes;
@SuppressWarnings("WeakerAccess")
public class TipsModel {
@StringRes
- private int text;
+ private final int text;
@Nullable
- private
- Intent actionIntent;
+ private final Intent actionIntent;
TipsModel(@StringRes int text, @Nullable Intent actionIntent) {
this.text = text;
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsViewHolder.java
index 622a32e3b..7d254459e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsViewHolder.java
@@ -20,7 +20,7 @@ public class TipsViewHolder extends RecyclerView.ViewHolder {
binding = ItemTipBinding.bind(itemView);
}
- public void bind(TipsModel tip, Consumer<Intent> actionButtonClickedListener) {
+ public void bind(@NonNull TipsModel tip, @NonNull Consumer<Intent> actionButtonClickedListener) {
binding.tip.setText(tip.getText());
final Intent actionIntent = tip.getActionIntent();
if (actionIntent != null && actionIntent.hasExtra(INTENT_EXTRA_BUTTON_TEXT)) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java
index 6aa03b811..647dfd651 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java
@@ -11,8 +11,6 @@ import androidx.appcompat.app.AlertDialog;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.Fragment;
-import androidx.fragment.app.FragmentManager;
-import androidx.lifecycle.Lifecycle;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider;
import androidx.viewpager2.adapter.FragmentStateAdapter;
@@ -49,14 +47,14 @@ public class FilterDialogFragment extends BrandedDialogFragment {
indicator = ContextCompat.getDrawable(requireContext(), R.drawable.circle_grey600_8dp);
assert indicator != null;
- indicator.setColorFilter(getResources().getColor(R.color.defaultBrand), PorterDuff.Mode.SRC_ATOP);
+ indicator.setColorFilter(ContextCompat.getColor(getContext(), R.color.defaultBrand), PorterDuff.Mode.SRC_ATOP);
filterViewModel = new ViewModelProvider(requireActivity()).get(FilterViewModel.class);
final AlertDialog.Builder dialogBuilder = new BrandedAlertDialogBuilder(requireContext());
binding = DialogFilterBinding.inflate(requireActivity().getLayoutInflater());
- binding.viewPager.setAdapter(new TabsPagerAdapter(getChildFragmentManager(), getLifecycle()));
+ binding.viewPager.setAdapter(new TabsPagerAdapter(this));
binding.viewPager.setOffscreenPageLimit(tabTitles.length);
LiveData<FilterInformation> filterInformationDraft = filterViewModel.getFilterInformationDraft();
@@ -114,8 +112,8 @@ public class FilterDialogFragment extends BrandedDialogFragment {
private static class TabsPagerAdapter extends FragmentStateAdapter {
- TabsPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle) {
- super(fragmentManager, lifecycle);
+ TabsPagerAdapter(final Fragment f) {
+ super(f);
}
@NonNull
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java
index 8aa45e39a..aec258d59 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java
@@ -1,8 +1,11 @@
package it.niedermann.nextcloud.deck.ui.manageaccounts;
+import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.lifecycle.ViewModelProvider;
@@ -51,7 +54,6 @@ public class ManageAccountsActivity extends AppCompatActivity {
viewModel.readAccounts().observe(this, (localAccounts -> {
if (localAccounts.size() == 0) {
Log.i(TAG, "No accounts, finishing " + ManageAccountsActivity.class.getSimpleName());
- setResult(AppCompatActivity.RESULT_FIRST_USER);
finish();
} else {
adapter.setAccounts(localAccounts);
@@ -64,4 +66,8 @@ public class ManageAccountsActivity extends AppCompatActivity {
public void onBackPressed() {
onSupportNavigateUp();
}
+
+ public static Intent createIntent(@NonNull Context context) {
+ return new Intent(context, ManageAccountsActivity.class);
+ }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsViewModel.java
index 66e9d3850..da89f3588 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsViewModel.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsViewModel.java
@@ -13,7 +13,7 @@ import java.util.List;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccountId;
+import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccount;
@SuppressWarnings("WeakerAccess")
public class ManageAccountsViewModel extends AndroidViewModel {
@@ -36,7 +36,7 @@ public class ManageAccountsViewModel extends AndroidViewModel {
public void setNewAccount(@NonNull Account account) {
SingleAccountHelper.setCurrentAccount(getApplication(), account.getName());
syncManager = new SyncManager(getApplication());
- saveCurrentAccountId(getApplication(), account.getId());
+ saveCurrentAccount(getApplication(), account);
}
public void deleteAccount(long id) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java
index 2b7eb52fe..442f2c6eb 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java
@@ -32,10 +32,12 @@ public class MoveCardDialogFragment extends BrandedDialogFragment implements Pic
private static final String KEY_ORIGIN_BOARD_LOCAL_ID = "board_local_id";
private static final String KEY_ORIGIN_CARD_TITLE = "card_title";
private static final String KEY_ORIGIN_CARD_LOCAL_ID = "card_local_id";
+ private static final String KEY_ORIGIN_CARD_HAS_ATTACHMENTS_OR_COMMENTS = "card_has_attachments_or_comments";
private Long originAccountId;
private Long originBoardLocalId;
private String originCardTitle;
private Long originCardLocalId;
+ private boolean originCardHasAttachmentsOrComments;
private DialogMoveCardBinding binding;
private PickStackViewModel viewModel;
@@ -69,6 +71,7 @@ public class MoveCardDialogFragment extends BrandedDialogFragment implements Pic
if (originBoardLocalId < 0) {
throw new IllegalArgumentException("Missing " + KEY_ORIGIN_BOARD_LOCAL_ID);
}
+ originCardHasAttachmentsOrComments = args.getBoolean(KEY_ORIGIN_CARD_HAS_ATTACHMENTS_OR_COMMENTS, false);
originCardTitle = args.getString(KEY_ORIGIN_CARD_TITLE);
}
@@ -78,7 +81,7 @@ public class MoveCardDialogFragment extends BrandedDialogFragment implements Pic
binding = DialogMoveCardBinding.inflate(inflater);
binding.title.setText(getString(R.string.action_card_move_title, originCardTitle));
binding.submit.setOnClickListener((v) -> {
- DeckLog.verbose("[Move card] Attempt to move to " + Stack.class.getSimpleName() + " #" + selectedStack.getLocalId());
+ DeckLog.verbose("[Move card] Attempt to move to", Stack.class.getSimpleName(), "#" + selectedStack.getLocalId());
this.moveCardListener.move(originAccountId, originCardLocalId, selectedAccount.getId(), selectedBoard.getLocalId(), selectedStack.getLocalId());
dismiss();
});
@@ -104,7 +107,7 @@ public class MoveCardDialogFragment extends BrandedDialogFragment implements Pic
binding.moveWarning.setVisibility(GONE);
} else {
binding.submit.setEnabled(true);
- binding.moveWarning.setVisibility(board.getLocalId().equals(originBoardLocalId) ? GONE : VISIBLE);
+ binding.moveWarning.setVisibility(originCardHasAttachmentsOrComments && !board.getLocalId().equals(originBoardLocalId) ? VISIBLE : GONE);
}
}
@@ -115,13 +118,14 @@ public class MoveCardDialogFragment extends BrandedDialogFragment implements Pic
binding.submit.setTextColor(mainColorStateList);
}
- public static DialogFragment newInstance(long originAccountId, long originBoardLocalId, String originCardTitle, Long originCardLocalId) {
+ public static DialogFragment newInstance(long originAccountId, long originBoardLocalId, String originCardTitle, Long originCardLocalId, boolean hasAttachmentsOrComments) {
final DialogFragment dialogFragment = new MoveCardDialogFragment();
final Bundle args = new Bundle();
args.putLong(KEY_ORIGIN_ACCOUNT_ID, originAccountId);
args.putLong(KEY_ORIGIN_BOARD_LOCAL_ID, originBoardLocalId);
args.putString(KEY_ORIGIN_CARD_TITLE, originCardTitle);
args.putLong(KEY_ORIGIN_CARD_LOCAL_ID, originCardLocalId);
+ args.putBoolean(KEY_ORIGIN_CARD_HAS_ATTACHMENTS_OR_COMMENTS, hasAttachmentsOrComments);
dialogFragment.setArguments(args);
return dialogFragment;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java
index d65971cf4..6408f6b64 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java
@@ -1,7 +1,6 @@
package it.niedermann.nextcloud.deck.ui.pickstack;
import android.content.Context;
-import android.content.Intent;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
@@ -142,7 +141,7 @@ public class PickStackFragment extends Fragment {
if (hasAccounts) {
return viewModel.readAccounts();
} else {
- startActivityForResult(new Intent(requireActivity(), ImportAccountActivity.class), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
+ startActivityForResult(ImportAccountActivity.createIntent(requireContext()), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
return null;
}
}).observe(getViewLifecycleOwner(), (List<Account> accounts) -> {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/AccountAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/AccountAdapter.java
index f537c9fb4..9534b51b4 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/AccountAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/AccountAdapter.java
@@ -50,7 +50,7 @@ public class AccountAdapter extends AbstractAdapter<Account> {
}
Glide.with(getContext())
- .load(new SingleSignOnUrl(item.getName(), item.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.icon_size_details))))
+ .load(new SingleSignOnUrl(item.getName(), item.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.avatar_size))))
.placeholder(R.drawable.ic_baseline_account_circle_24)
.error(R.drawable.ic_baseline_account_circle_24)
.apply(RequestOptions.circleCropTransform())
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java
index 18317e078..53e25a0d9 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java
@@ -1,9 +1,7 @@
package it.niedermann.nextcloud.deck.ui.preparecreate;
-import android.content.ClipData;
import android.content.Intent;
import android.os.Bundle;
-import android.text.TextUtils;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
@@ -13,7 +11,7 @@ import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.ui.PickStackActivity;
import it.niedermann.nextcloud.deck.ui.card.EditActivity;
-import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccountId;
+import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccount;
import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentBoardId;
import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentStackId;
@@ -30,14 +28,16 @@ public class PrepareCreateActivity extends PickStackActivity {
@Override
protected void onSubmit(Account account, long boardId, long stackId) {
- final String receivedClipData = getReceivedClipData(getIntent());
- if (receivedClipData == null) {
+ final Intent intent = getIntent();
+ if (intent == null) {
startActivity(EditActivity.createNewCardIntent(this, account, boardId, stackId));
} else {
- startActivity(EditActivity.createNewCardIntent(this, account, boardId, stackId, receivedClipData));
+ startActivity(EditActivity.createNewCardIntent(this, account, boardId, stackId,
+ intent.getStringExtra(Intent.EXTRA_TITLE),
+ intent.getStringExtra(Intent.EXTRA_TEXT)));
}
- saveCurrentAccountId(this, account.getId());
+ saveCurrentAccount(this, account);
saveCurrentBoardId(this, account.getId(), boardId);
saveCurrentStackId(this, account.getId(), boardId, stackId);
applyBrand(account.getColor());
@@ -49,25 +49,4 @@ public class PrepareCreateActivity extends PickStackActivity {
protected boolean showBoardsWithoutEditPermission() {
return false;
}
-
- @Nullable
- private static String getReceivedClipData(@Nullable Intent intent) {
- if (intent == null) {
- return null;
- }
- final ClipData clipData = intent.getClipData();
- if (clipData == null) {
- return null;
- }
- final int itemCount = clipData.getItemCount();
- if (itemCount <= 0) {
- return null;
- }
- final ClipData.Item item = clipData.getItemAt(0);
- if (item == null) {
- return null;
- }
- final CharSequence text = item.getText();
- return TextUtils.isEmpty(text) ? null : text.toString();
- }
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsActivity.java
index 6f5a9a7e8..db0c4147c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsActivity.java
@@ -1,24 +1,25 @@
package it.niedermann.nextcloud.deck.ui.settings;
+import android.content.Context;
+import android.content.Intent;
import android.os.Bundle;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ActivitySettingsBinding;
-import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
-public class SettingsActivity extends BrandedActivity {
-
- private ActivitySettingsBinding binding;
+public class SettingsActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
- binding = ActivitySettingsBinding.inflate(getLayoutInflater());
+ final ActivitySettingsBinding binding = ActivitySettingsBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
@@ -36,8 +37,8 @@ public class SettingsActivity extends BrandedActivity {
return true;
}
- @Override
- public void applyBrand(int mainColor) {
- // Nothing to do...
+ @NonNull
+ public static Intent createIntent(@NonNull Context context) {
+ return new Intent(context, SettingsActivity.class);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java
index 04429f225..91c7ad995 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java
@@ -1,29 +1,30 @@
package it.niedermann.nextcloud.deck.ui.settings;
import android.app.Activity;
-import android.content.Context;
import android.os.Bundle;
+import android.view.View;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.app.ActivityCompat;
import androidx.preference.ListPreference;
import androidx.preference.Preference;
import androidx.preference.PreferenceFragmentCompat;
+import it.niedermann.nextcloud.deck.DeckApplication;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.persistence.sync.SyncWorker;
-import it.niedermann.nextcloud.deck.ui.branding.Branded;
import it.niedermann.nextcloud.deck.ui.branding.BrandedSwitchPreference;
import static it.niedermann.nextcloud.deck.DeckApplication.setAppTheme;
-import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
-public class SettingsFragment extends PreferenceFragmentCompat implements Branded {
+public class SettingsFragment extends PreferenceFragmentCompat {
private BrandedSwitchPreference wifiOnlyPref;
- private BrandedSwitchPreference themePref;
- private BrandedSwitchPreference brandingPref;
private BrandedSwitchPreference compactPref;
+ private BrandedSwitchPreference debuggingPref;
+ private BrandedSwitchPreference eTagPref;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -34,38 +35,23 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Brande
if (wifiOnlyPref != null) {
wifiOnlyPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> {
final Boolean syncOnWifiOnly = (Boolean) newValue;
- DeckLog.log("syncOnWifiOnly: " + syncOnWifiOnly);
+ DeckLog.log("syncOnWifiOnly:", syncOnWifiOnly);
return true;
});
} else {
- DeckLog.error("Could not find preference with key: \"" + getString(R.string.pref_key_wifi_only) + "\"");
+ DeckLog.error("Could not find preference with key: ", getString(R.string.pref_key_wifi_only));
}
- themePref = findPreference(getString(R.string.pref_key_dark_theme));
+ Preference themePref = findPreference(getString(R.string.pref_key_dark_theme));
if (themePref != null) {
themePref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> {
- final Boolean darkTheme = (Boolean) newValue;
- DeckLog.log("darkTheme: " + darkTheme);
- setAppTheme(darkTheme);
+ setAppTheme(Integer.parseInt((String) newValue));
requireActivity().setResult(Activity.RESULT_OK);
- requireActivity().recreate();
+ ActivityCompat.recreate(requireActivity());
return true;
});
} else {
- DeckLog.error("Could not find preference with key: \"" + getString(R.string.pref_key_dark_theme) + "\"");
- }
-
- brandingPref = findPreference(getString(R.string.pref_key_branding));
- if (brandingPref != null) {
- brandingPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> {
- final Boolean branding = (Boolean) newValue;
- DeckLog.log("branding: " + branding);
- requireActivity().setResult(Activity.RESULT_OK);
- requireActivity().recreate();
- return true;
- });
- } else {
- DeckLog.error("Could not find preference with key: \"" + getString(R.string.pref_key_dark_theme) + "\"");
+ DeckLog.error("Could not find preference with key:", getString(R.string.pref_key_dark_theme));
}
compactPref = findPreference(getString(R.string.pref_key_compact));
@@ -77,24 +63,32 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Brande
return true;
});
} else {
- DeckLog.error("Could not find preference with key: \"" + getString(R.string.pref_key_background_sync) + "\"");
+ DeckLog.error("Could not find preference with key", getString(R.string.pref_key_background_sync));
}
- }
- @Override
- public void onStart() {
- super.onStart();
- @Nullable Context context = getContext();
- if (context != null) {
- applyBrand(readBrandMainColor(context));
+ debuggingPref = findPreference(getString(R.string.pref_key_debugging));
+ if (debuggingPref != null) {
+ debuggingPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> {
+ DeckLog.enablePersistentLogs((Boolean) newValue);
+ DeckLog.log("persistet debug logs:", newValue);
+ return true;
+ });
+ } else {
+ DeckLog.error("Could not find preference with key:", getString(R.string.pref_key_debugging));
}
+
+ eTagPref = findPreference(getString(R.string.pref_key_etags));
}
@Override
- public void applyBrand(int mainColor) {
- wifiOnlyPref.applyBrand(mainColor);
- themePref.applyBrand(mainColor);
- brandingPref.applyBrand(mainColor);
- compactPref.applyBrand(mainColor);
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
+
+ DeckApplication.readCurrentAccountColor().observe(getViewLifecycleOwner(), (mainColor) -> {
+ wifiOnlyPref.applyBrand(mainColor);
+ compactPref.applyBrand(mainColor);
+ debuggingPref.applyBrand(mainColor);
+ eTagPref.applyBrand(mainColor);
+ });
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareTargetActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareTargetActivity.java
index 3a0005fb1..21aafed30 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareTargetActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareTargetActivity.java
@@ -23,6 +23,7 @@ import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
import it.niedermann.nextcloud.deck.exceptions.UploadAttachmentFailedException;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
@@ -37,7 +38,7 @@ import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.util.MimeTypeUtil;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
-import static it.niedermann.nextcloud.deck.util.AttachmentUtil.copyContentUriToTempFile;
+import static it.niedermann.nextcloud.deck.util.FilesUtil.copyContentUriToTempFile;
import static java.net.HttpURLConnection.HTTP_CONFLICT;
public class ShareTargetActivity extends MainActivity implements SelectCardListener {
@@ -125,19 +126,23 @@ public class ShareTargetActivity extends MainActivity implements SelectCardListe
if (mimeType == null) {
throw new IllegalArgumentException("MimeType of uri is null. [" + uri + "]");
}
- runOnUiThread(() -> {
- final WrappedLiveData<Attachment> liveData = mainViewModel.addAttachmentToCard(fullCard.getAccountId(), fullCard.getCard().getLocalId(), mimeType, tempFile);
- liveData.observe(ShareTargetActivity.this, (next) -> {
- if (liveData.hasError()) {
- if (liveData.getError() instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) liveData.getError()).getStatusCode() == HTTP_CONFLICT) {
+ mainViewModel.addAttachmentToCard(fullCard.getAccountId(), fullCard.getCard().getLocalId(), mimeType, tempFile, new ResponseCallback<Attachment>() {
+ @Override
+ public void onResponse(Attachment response) {
+ runOnUiThread(shareProgressViewModel::increaseProgress);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ runOnUiThread(() -> {
+ if (throwable instanceof NextcloudHttpRequestFailedException && ((NextcloudHttpRequestFailedException) throwable).getStatusCode() == HTTP_CONFLICT) {
+ ResponseCallback.super.onError(throwable);
shareProgressViewModel.addDuplicateAttachment(tempFile.getName());
} else {
- shareProgressViewModel.addException(liveData.getError());
+ shareProgressViewModel.addException(throwable);
}
- } else {
- shareProgressViewModel.increaseProgress();
- }
- });
+ });
+ }
});
} catch (Throwable t) {
runOnUiThread(() -> shareProgressViewModel.addException(new UploadAttachmentFailedException("Error while uploading attachment for uri [" + uri + "]", t)));
@@ -154,7 +159,7 @@ public class ShareTargetActivity extends MainActivity implements SelectCardListe
switch (which) {
case 0:
final String oldDescription = fullCard.getCard().getDescription();
- DeckLog.info("Adding to card #" + fullCard.getCard().getId() + " (" + fullCard.getCard().getTitle() + "): Text \"" + receivedText + "\"");
+ DeckLog.info("Adding to card with id", fullCard.getCard().getId(), "(" + fullCard.getCard().getTitle() + "):", receivedText);
fullCard.getCard().setDescription(
(oldDescription == null || oldDescription.length() == 0)
? receivedText
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackAdapter.java
index 8fc56759f..1e0fd4e72 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackAdapter.java
@@ -14,8 +14,8 @@ public class StackAdapter extends FragmentStateAdapter {
@NonNull
private final List<Stack> stackList = new ArrayList<>();
- public StackAdapter(@NonNull FragmentActivity fragmentActivity) {
- super(fragmentActivity);
+ public StackAdapter(final FragmentActivity fa) {
+ super(fa);
}
@Override
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java
index 313e6e821..80440b9d0 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java
@@ -81,7 +81,7 @@ public class StackFragment extends BrandedFragment implements DragAndDropTab<Car
return binding.getRoot();
}
- adapter = new CardAdapter(requireContext(), getChildFragmentManager(), stackId, mainViewModel, this,
+ adapter = new CardAdapter(requireActivity(), getChildFragmentManager(), stackId, mainViewModel, this,
(requireActivity() instanceof SelectCardListener)
? (SelectCardListener) requireActivity()
: null);
@@ -159,7 +159,7 @@ public class StackFragment extends BrandedFragment implements DragAndDropTab<Car
if (liveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(liveData.getError())) {
ExceptionDialogFragment.newInstance(liveData.getError(), null).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
} else {
- DeckLog.log("Moved " + Card.class.getSimpleName() + " \"" + originCardLocalId + "\" to " + Stack.class.getSimpleName() + " \"" + targetStackLocalId + "\"");
+ DeckLog.log("Moved", Card.class.getSimpleName(), originCardLocalId, "to", Stack.class.getSimpleName(), targetStackLocalId);
}
});
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java
index af17464dc..e1398bab7 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java
@@ -9,10 +9,10 @@ import android.util.Size;
import android.view.OrientationEventListener;
import android.view.Surface;
import android.view.View;
+import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.camera.core.Camera;
import androidx.camera.core.ImageCapture;
import androidx.camera.core.ImageCaptureException;
@@ -34,12 +34,10 @@ import it.niedermann.nextcloud.deck.databinding.ActivityTakePhotoBinding;
import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
-import it.niedermann.nextcloud.deck.util.AttachmentUtil;
+import it.niedermann.nextcloud.deck.util.FilesUtil;
-import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static it.niedermann.nextcloud.deck.util.MimeTypeUtil.IMAGE_JPEG;
-@RequiresApi(LOLLIPOP)
public class TakePhotoActivity extends BrandedActivity {
private ActivityTakePhotoBinding binding;
@@ -81,8 +79,9 @@ public class TakePhotoActivity extends BrandedActivity {
cameraProvider.unbindAll();
cameraProvider.bindToLifecycle(this, viewModel.getCameraSelector(), captureUseCase, previewUseCase);
});
- } catch (ExecutionException | InterruptedException e) {
+ } catch (IllegalArgumentException | ExecutionException | InterruptedException e) {
DeckLog.logError(e);
+ Toast.makeText(this, e.getMessage(), Toast.LENGTH_LONG).show();
finish();
}
}, ContextCompat.getMainExecutor(this));
@@ -118,13 +117,13 @@ public class TakePhotoActivity extends BrandedActivity {
binding.takePhoto.setEnabled(false);
final String photoFileName = Instant.now().atZone(ZoneId.systemDefault()).format(fileNameFromCameraFormatter);
try {
- final File photoFile = AttachmentUtil.getTempCacheFile(this, "photos/" + photoFileName);
+ final File photoFile = FilesUtil.getTempCacheFile(this, "photos/" + photoFileName);
final ImageCapture.OutputFileOptions options = new ImageCapture.OutputFileOptions.Builder(photoFile).build();
captureUseCase.takePicture(options, ContextCompat.getMainExecutor(this), new ImageCapture.OnImageSavedCallback() {
@Override
public void onImageSaved(@NonNull ImageCapture.OutputFileResults outputFileResults) {
final Uri savedUri = Uri.fromFile(photoFile);
- DeckLog.info("onImageSaved - savedUri: " + savedUri.toString());
+ DeckLog.info("onImageSaved - savedUri:", savedUri.toString());
setResult(RESULT_OK, new Intent().setDataAndType(savedUri, IMAGE_JPEG));
finish();
}
@@ -167,7 +166,6 @@ public class TakePhotoActivity extends BrandedActivity {
}
}
- @RequiresApi(LOLLIPOP)
public static Intent createIntent(@NonNull Context context) {
return new Intent(context, TakePhotoActivity.class).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java
index 0912a07dd..730ecbdd1 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java
@@ -1,8 +1,6 @@
package it.niedermann.nextcloud.deck.ui.view;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.os.Build;
import android.util.AttributeSet;
import androidx.constraintlayout.widget.ConstraintLayout;
@@ -21,7 +19,6 @@ public class SquareConstraintLayout extends ConstraintLayout {
super(context, attrs, defStyleAttr);
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SquareConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
@@ -29,6 +26,7 @@ public class SquareConstraintLayout extends ConstraintLayout {
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// Set a square layout.
+ //noinspection SuspiciousNameCombination
super.onMeasure(widthMeasureSpec, widthMeasureSpec);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareRelativeLayout.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareRelativeLayout.java
index 7f3649c94..cdce90251 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareRelativeLayout.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareRelativeLayout.java
@@ -1,8 +1,6 @@
package it.niedermann.nextcloud.deck.ui.view;
-import android.annotation.TargetApi;
import android.content.Context;
-import android.os.Build;
import android.util.AttributeSet;
import android.widget.RelativeLayout;
@@ -20,7 +18,6 @@ public class SquareRelativeLayout extends RelativeLayout {
super(context, attrs, defStyleAttr);
}
- @TargetApi(Build.VERSION_CODES.LOLLIPOP)
public SquareRelativeLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
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..89d75eeee
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidget.java
@@ -0,0 +1,78 @@
+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.DeckLog;
+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, response -> DeckLog.verbose("Successfully deleted " + FilterWidget.class.getSimpleName() + " with id " + appWidgetId));
+ }
+ }
+}
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..102860d7c
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetConfigurationActivity.java
@@ -0,0 +1,67 @@
+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.ui.exception.ExceptionHandler;
+
+public class FilterWidgetConfigurationActivity extends AppCompatActivity {
+ private int appWidgetId;
+ private ActivityFilterWidgetBinding binding;
+ private FilterWidgetViewModel viewModel;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));
+
+ binding = ActivityFilterWidgetBinding.inflate(getLayoutInflater());
+ viewModel = new ViewModelProvider(this).get(FilterWidgetViewModel.class);
+
+ setContentView(binding.getRoot());
+ setSupportActionBar(binding.toolbar);
+
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(R.string.add_filter_widget);
+ }
+
+ setResult(RESULT_CANCELED);
+ final Bundle args = getIntent().getExtras();
+
+ if (args != null) {
+ appWidgetId = args.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+ }
+
+ if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ DeckLog.error("INVALID_APPWIDGET_ID");
+ finish();
+ }
+ binding.cancel.setOnClickListener((v) -> finish());
+ binding.submit.setOnClickListener((v) -> {
+ final Bundle extras = new Bundle();
+
+ viewModel.updateFilterWidget(response -> DeckLog.verbose("Successfully updated", FilterWidget.class.getSimpleName(), "with id", appWidgetId));
+ Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null, getApplicationContext(), FilterWidget.class);
+ 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(FilterWidget.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..fbc3900f7
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/filter/FilterWidgetFactory.java
@@ -0,0 +1,90 @@
+package it.niedermann.nextcloud.deck.ui.widget.filter;
+
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import androidx.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+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.dto.FilterWidgetCard;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+
+public class FilterWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
+ private final Context context;
+ private final int appWidgetId;
+ private final SyncManager syncManager;
+
+ @NonNull
+ private final List<FilterWidgetCard> data = new ArrayList<>();
+
+ FilterWidgetFactory(Context context, Intent intent) {
+ this.context = context;
+ this.appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+ this.syncManager = new SyncManager(context);
+ }
+
+ @Override
+ public void onCreate() {
+ }
+
+ @Override
+ public void onDataSetChanged() {
+
+ }
+
+
+ @Override
+ public void onDestroy() {
+
+ }
+
+ @Override
+ public int getCount() {
+ return data.size();
+ }
+
+ @Override
+ public RemoteViews getViewAt(int i) {
+ RemoteViews widget_entry;
+
+ if (i > (data.size() - 1) || data.get(i) == null) {
+ DeckLog.error("Card not found at position", i);
+ return null;
+ }
+
+ FullCard card = data.get(i).getCard();
+
+ 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;
+ }
+}
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..4f50465f2
--- /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.api.ResponseCallback;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidget;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+
+public class FilterWidgetViewModel extends AndroidViewModel {
+
+ @NonNull
+ private final SyncManager syncManager;
+ @NonNull
+ private final MutableLiveData<FilterWidget> config$ = new MutableLiveData<>(new FilterWidget());
+
+ public FilterWidgetViewModel(@NonNull Application application) {
+ super(application);
+ this.syncManager = new SyncManager(application);
+ }
+
+ public LiveData<FilterWidget> getFilterWidgetConfiguration() {
+ return this.config$;
+ }
+
+ public void updateFilterWidget(@NonNull ResponseCallback<Integer> callback) {
+ syncManager.createFilterWidget(config$.getValue(), callback);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java
index b44b80d1b..766948062 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java
@@ -6,11 +6,11 @@ import android.appwidget.AppWidgetProvider;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
-import android.os.Build;
+import android.net.Uri;
+import android.text.TextUtils;
import android.view.View;
import android.widget.RemoteViews;
-import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
@@ -36,11 +36,21 @@ public class SingleCardWidget extends AppWidgetProvider {
final Intent intent = EditActivity.createEditCardIntent(context, fullModel.getAccount(), fullModel.getModel().getBoardId(), fullModel.getFullCard().getLocalId());
final PendingIntent pendingIntent = PendingIntent.getActivity(context, appWidgetId, intent, PendingIntent.FLAG_UPDATE_CURRENT);
final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_single_card);
+ final Intent serviceIntent = new Intent(context, SingleCardWidgetService.class);
- views.setOnClickPendingIntent(R.id.widget_card, pendingIntent);
+ serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ serviceIntent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
+
+ if (TextUtils.isEmpty(SingleCardWidgetFactory.getDescriptionOrNull(fullModel))) {
+ views.setViewVisibility(R.id.description_lv, View.GONE);
+ } else {
+ views.setViewVisibility(R.id.description_lv, View.VISIBLE);
+ }
+ views.setOnClickPendingIntent(R.id.widget_card, pendingIntent);
+ views.setPendingIntentTemplate(R.id.description_lv, pendingIntent);
views.setTextViewText(R.id.title, fullModel.getFullCard().getCard().getTitle());
- views.setTextViewText(R.id.description, fullModel.getFullCard().getCard().getDescription());
+ views.setRemoteAdapter(R.id.description_lv, serviceIntent);
if (fullModel.getFullCard().getCard().getDueDate() != null) {
views.setTextViewText(R.id.card_due_date, DateUtil.getRelativeDateTimeString(context, fullModel.getFullCard().getCard().getDueDate().toEpochMilli()));
@@ -49,7 +59,7 @@ public class SingleCardWidget extends AppWidgetProvider {
// Because otherwise using Reflection is the only way
views.setViewVisibility(R.id.card_due_date, View.VISIBLE);
views.setViewVisibility(R.id.card_due_date_image, View.VISIBLE);
- setImageDrawable(views, R.id.card_due_date_image, R.drawable.calendar_blank_grey600_24dp);
+ views.setImageViewResource(R.id.card_due_date_image, R.drawable.calendar_blank_grey600_24dp);
} else {
views.setViewVisibility(R.id.card_due_date, View.GONE);
views.setViewVisibility(R.id.card_due_date_image, View.GONE);
@@ -65,8 +75,8 @@ public class SingleCardWidget extends AppWidgetProvider {
} else {
views.setViewVisibility(R.id.card_count_attachments, View.VISIBLE);
views.setViewVisibility(R.id.card_count_attachments_image, View.VISIBLE);
+ views.setImageViewResource(R.id.card_count_attachments_image, R.drawable.ic_attach_file_grey600_24dp);
setupCounter(views, R.id.card_count_attachments, attachmentsCount, counterMaxValue);
- setImageDrawable(views, R.id.card_count_attachments_image, R.drawable.ic_check_grey600_24dp);
}
final int commentsCount = fullModel.getFullCard().getCommentCount();
@@ -76,8 +86,8 @@ public class SingleCardWidget extends AppWidgetProvider {
} else {
views.setViewVisibility(R.id.card_count_comments, View.VISIBLE);
views.setViewVisibility(R.id.card_count_comments_image, View.VISIBLE);
+ views.setImageViewResource(R.id.card_count_comments_image, R.drawable.ic_comment_white_24dp);
setupCounter(views, R.id.card_count_comments, commentsCount, counterMaxValue);
- setImageDrawable(views, R.id.card_count_comments_image, R.drawable.ic_comment_white_24dp);
}
final Card.TaskStatus taskStatus = fullModel.getFullCard().getCard().getTaskStatus();
@@ -85,13 +95,14 @@ public class SingleCardWidget extends AppWidgetProvider {
views.setViewVisibility(R.id.card_count_tasks, View.VISIBLE);
views.setViewVisibility(R.id.card_count_tasks_image, View.VISIBLE);
views.setTextViewText(R.id.card_count_tasks, context.getResources().getString(R.string.task_count, String.valueOf(taskStatus.doneCount), String.valueOf(taskStatus.taskCount)));
- setImageDrawable(views, R.id.card_count_tasks_image, R.drawable.ic_attach_file_grey600_24dp);
+ views.setImageViewResource(R.id.card_count_tasks_image, R.drawable.ic_check_grey600_24dp);
} else {
views.setViewVisibility(R.id.card_count_tasks, View.GONE);
views.setViewVisibility(R.id.card_count_tasks_image, View.GONE);
}
awm.updateAppWidget(appWidgetId, views);
+ awm.notifyAppWidgetViewDataChanged(appWidgetId, R.id.description_lv);
} catch (NoSuchElementException e) {
// onUpdate has been triggered before the user finished configuring the widget
}
@@ -102,19 +113,13 @@ public class SingleCardWidget extends AppWidgetProvider {
private static void setupCounter(@NonNull RemoteViews views, @IdRes int textViewId, int count, String counterMaxValue) {
if (count > 99) {
views.setTextViewText(textViewId, counterMaxValue);
- } else if (count > 1 || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ } else if (count > 1) {
views.setTextViewText(textViewId, String.valueOf(count));
} else if (count == 1) {
views.setTextViewText(textViewId, "");
}
}
- private static void setImageDrawable(@NonNull RemoteViews views, @IdRes int imageView, @DrawableRes int image) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- views.setImageViewResource(imageView, image);
- }
- }
-
@Override
public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
super.onUpdate(context, appWidgetManager, appWidgetIds);
@@ -124,10 +129,9 @@ public class SingleCardWidget extends AppWidgetProvider {
@Override
public void onReceive(Context context, Intent intent) {
super.onReceive(context, intent);
- AppWidgetManager awm = AppWidgetManager.getInstance(context);
+ final AppWidgetManager awm = AppWidgetManager.getInstance(context);
- updateAppWidget(context, AppWidgetManager.getInstance(context),
- (awm.getAppWidgetIds(new ComponentName(context, SingleCardWidget.class))));
+ updateAppWidget(context, AppWidgetManager.getInstance(context), (awm.getAppWidgetIds(new ComponentName(context, SingleCardWidget.class))));
}
@Override
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidgetFactory.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidgetFactory.java
new file mode 100644
index 000000000..db24afa43
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidgetFactory.java
@@ -0,0 +1,100 @@
+package it.niedermann.nextcloud.deck.ui.widget.singlecard;
+
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.text.TextUtils;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.NoSuchElementException;
+
+import it.niedermann.android.markdown.MarkdownUtil;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.ui.card.EditActivity;
+
+public class SingleCardWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
+ private final Context context;
+ private final int appWidgetId;
+ private final SyncManager syncManager;
+ private FullSingleCardWidgetModel model;
+
+ public SingleCardWidgetFactory(@NonNull Context context, @NonNull Intent intent) {
+ this.context = context;
+ this.appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+ this.syncManager = new SyncManager(context);
+ }
+
+ @Override
+ public void onCreate() {
+
+ }
+
+ @Override
+ public void onDataSetChanged() {
+ try {
+ this.model = syncManager.getSingleCardWidgetModelDirectly(appWidgetId);
+ } catch (NoSuchElementException e) {
+ this.model = null;
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+
+ }
+
+ @Override
+ public int getCount() {
+ return getDescriptionOrNull(model) == null ? 0 : 1;
+ }
+
+ @Override
+ public RemoteViews getViewAt(int position) {
+ final String description = getDescriptionOrNull(model);
+ if (description == null) {
+ return null;
+ }
+
+ final RemoteViews widget_entry = new RemoteViews(context.getPackageName(), R.layout.widget_single_card_content);
+ widget_entry.setTextViewText(R.id.description, MarkdownUtil.renderForRemoteView(context, description));
+
+ final Intent intent = EditActivity.createEditCardIntent(context, model.getAccount(), model.getModel().getBoardId(), model.getFullCard().getCard().getLocalId());
+ widget_entry.setOnClickFillInIntent(R.id.description, intent);
+
+ return widget_entry;
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ return null;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 1;
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @Nullable
+ public static String getDescriptionOrNull(@Nullable FullSingleCardWidgetModel model) {
+ if (model == null || model.getFullCard() == null || model.getFullCard().getCard() == null || TextUtils.isEmpty(model.getFullCard().getCard().getDescription())) {
+ return null;
+ }
+ return model.getFullCard().getCard().getDescription();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidgetService.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidgetService.java
new file mode 100644
index 000000000..d938e383a
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidgetService.java
@@ -0,0 +1,11 @@
+package it.niedermann.nextcloud.deck.ui.widget.singlecard;
+
+import android.content.Intent;
+import android.widget.RemoteViewsService;
+
+public class SingleCardWidgetService extends RemoteViewsService {
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ return new SingleCardWidgetFactory(this.getApplicationContext(), intent);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidget.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidget.java
index cb179953c..5b0907ef3 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidget.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidget.java
@@ -7,14 +7,13 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
-import android.os.Bundle;
import android.widget.RemoteViews;
-import java.util.NoSuchElementException;
+import androidx.annotation.ColorInt;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
-import it.niedermann.nextcloud.deck.model.Account;
-import it.niedermann.nextcloud.deck.model.appwidgets.StackWidgetModel;
+import it.niedermann.nextcloud.deck.model.Stack;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.MainActivity;
import it.niedermann.nextcloud.deck.ui.card.EditActivity;
@@ -22,82 +21,29 @@ import it.niedermann.nextcloud.deck.ui.card.EditActivity;
import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE;
public class StackWidget extends AppWidgetProvider {
- public static final String ACCOUNT_ID_KEY = "stack_widget_account_id";
- public static final String ACCOUNT_KEY = "stack_widget_account";
- public static final String STACK_ID_KEY = "stack_widget_stack_id";
- public static final String BUNDLE_KEY = "stack_widget_bundle";
private static final int PENDING_INTENT_OPEN_APP_RQ = 0;
private static final int PENDING_INTENT_EDIT_CARD_RQ = 1;
- static void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds, Account account) {
- final SyncManager syncManager = new SyncManager(context);
-
- for (int appWidgetId : appWidgetIds) {
- new Thread(() -> {
- try {
- final StackWidgetModel model = syncManager.getStackWidgetModelDirectly(appWidgetId);
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_stack);
- Intent serviceIntent = new Intent(context, StackWidgetService.class);
-
- serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
- serviceIntent.putExtra(ACCOUNT_ID_KEY + appWidgetId, model.getAccountId());
- serviceIntent.putExtra(STACK_ID_KEY + appWidgetId, model.getStackId());
- if (account != null) {
- Bundle extras = new Bundle();
- extras.putSerializable(StackWidget.ACCOUNT_KEY + appWidgetId, account);
- serviceIntent.putExtra(BUNDLE_KEY + appWidgetId, extras);
- }
- serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)));
-
- Intent intent = new Intent(Intent.ACTION_MAIN);
- intent.setComponent(new ComponentName(context.getPackageName(), MainActivity.class.getName()));
- PendingIntent pendingIntent = PendingIntent.getActivity(context, PENDING_INTENT_OPEN_APP_RQ,
- intent, PendingIntent.FLAG_UPDATE_CURRENT);
- views.setOnClickPendingIntent(R.id.widget_stack_header_rl, pendingIntent);
-
- PendingIntent templatePI = PendingIntent.getActivity(context, PENDING_INTENT_EDIT_CARD_RQ,
- new Intent(context, EditActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
-
- views.setPendingIntentTemplate(R.id.stack_widget_lv, templatePI);
- views.setRemoteAdapter(R.id.stack_widget_lv, serviceIntent);
- views.setEmptyView(R.id.stack_widget_lv, R.id.widget_stack_placeholder_iv);
- awm.notifyAppWidgetViewDataChanged(appWidgetId, R.id.stack_widget_lv);
- awm.updateAppWidget(appWidgetId, views);
- } 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);
+ updateAppWidget(context, appWidgetManager, appWidgetIds);
}
@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(StackWidget.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, StackWidget.class)), account);
- }
- }
+ final AppWidgetManager awm = AppWidgetManager.getInstance(context);
+
+ if (ACTION_APPWIDGET_UPDATE.equals(intent.getAction())) {
+ if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
+ final int appWidgetId = intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ DeckLog.verbose(ACTION_APPWIDGET_UPDATE, "for", StackWidget.class.getSimpleName(), "with id", appWidgetId, "→ perform update.");
+ updateAppWidget(context, awm, new int[]{appWidgetId});
+ } else {
+ DeckLog.verbose(ACTION_APPWIDGET_UPDATE, "→ Triggering update for all widgets of type", StackWidget.class.getSimpleName());
+ updateAppWidget(context, awm, awm.getAppWidgetIds(new ComponentName(context, StackWidget.class)));
}
}
}
@@ -108,14 +54,47 @@ public class StackWidget extends AppWidgetProvider {
final SyncManager syncManager = new SyncManager(context);
for (int appWidgetId : appWidgetIds) {
- syncManager.deleteStackWidgetModel(appWidgetId);
+ DeckLog.info("Delete", StackWidget.class.getSimpleName(), "with id", appWidgetId);
+ syncManager.deleteFilterWidget(appWidgetId, response -> DeckLog.verbose("Successfully deleted " + StackWidget.class.getSimpleName() + " with id " + appWidgetId));
}
}
- /**
- * Updates UI data of all {@link StackWidget} instances
- */
- public static void notifyDatasetChanged(Context context) {
- context.sendBroadcast(new Intent(context, StackWidget.class).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
+ private static void updateAppWidget(Context context, AppWidgetManager awm, int[] appWidgetIds) {
+ final SyncManager syncManager = new SyncManager(context);
+ for (int appWidgetId : appWidgetIds) {
+ new Thread(() -> {
+ if (syncManager.filterWidgetExists(appWidgetId)) {
+ final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_stack);
+
+ final Intent serviceIntent = new Intent(context, StackWidgetService.class);
+ serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)));
+
+ final Intent intent = new Intent(Intent.ACTION_MAIN).setComponent(new ComponentName(context.getPackageName(), MainActivity.class.getName()));
+ final PendingIntent pendingIntent = PendingIntent.getActivity(context, PENDING_INTENT_OPEN_APP_RQ,
+ intent, PendingIntent.FLAG_UPDATE_CURRENT);
+ final PendingIntent templatePI = PendingIntent.getActivity(context, PENDING_INTENT_EDIT_CARD_RQ,
+ new Intent(context, EditActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
+
+ views.setOnClickPendingIntent(R.id.widget_stack_header_rl, pendingIntent);
+
+ views.setPendingIntentTemplate(R.id.stack_widget_lv, templatePI);
+ views.setRemoteAdapter(R.id.stack_widget_lv, serviceIntent);
+ views.setEmptyView(R.id.stack_widget_lv, R.id.widget_stack_placeholder_iv);
+
+ syncManager.getFilterWidget(appWidgetId, response -> {
+ final Stack stack = syncManager.getStackDirectly(response.getAccounts().get(0).getBoards().get(0).getStacks().get(0).getStackId());
+ @ColorInt final Integer boardColor = syncManager.getBoardColorDirectly(response.getAccounts().get(0).getAccountId(), response.getAccounts().get(0).getBoards().get(0).getBoardId());
+ views.setTextViewText(R.id.widget_stack_title_tv, stack.getTitle());
+ views.setInt(R.id.widget_stack_header_icon, "setColorFilter", boardColor);
+
+ awm.notifyAppWidgetViewDataChanged(appWidgetId, R.id.stack_widget_lv);
+ awm.updateAppWidget(appWidgetId, views);
+ });
+ } else {
+ DeckLog.warn("Does not yet exist");
+ }
+ }).start();
+ }
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationActivity.java
index 96b5cf672..a34123e29 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationActivity.java
@@ -7,10 +7,19 @@ import android.os.Bundle;
import androidx.appcompat.app.ActionBar;
import androidx.lifecycle.ViewModelProvider;
+import java.util.Collections;
+
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.widget.filter.EWidgetType;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidget;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetAccount;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetBoard;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetStack;
import it.niedermann.nextcloud.deck.ui.PickStackActivity;
+import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
public class StackWidgetConfigurationActivity extends PickStackActivity {
private int appWidgetId;
@@ -43,22 +52,35 @@ public class StackWidgetConfigurationActivity extends PickStackActivity {
@Override
protected void onSubmit(Account account, long boardId, long stackId) {
- final Bundle extras = new Bundle();
-
- stackWidgetConfigurationViewModel.addStackWidget(appWidgetId, account.getId(), stackId, false);
- 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();
+ final FilterWidget config = new FilterWidget(appWidgetId, EWidgetType.STACK_WIDGET);
+ final FilterWidgetAccount filterWidgetAccount = new FilterWidgetAccount(account.getId(), false);
+ filterWidgetAccount.setIncludeNoProject(false);
+ FilterWidgetBoard filterWidgetBoard = new FilterWidgetBoard(boardId, Collections.singletonList(new FilterWidgetStack(stackId)));
+ filterWidgetBoard.setIncludeNoLabel(false);
+ filterWidgetAccount.setBoards(
+ Collections.singletonList(filterWidgetBoard));
+ config.setAccounts(Collections.singletonList(filterWidgetAccount));
+ stackWidgetConfigurationViewModel.addStackWidget(config, new IResponseCallback<Integer>(account) {
+
+ @Override
+ public void onResponse(Integer response) {
+ final Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null,
+ getApplicationContext(), StackWidget.class)
+ .putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ setResult(RESULT_OK, updateIntent);
+ getApplicationContext().sendBroadcast(updateIntent);
+
+ finish();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ ExceptionDialogFragment
+ .newInstance(throwable, account)
+ .show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ }
+ });
}
@Override
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationViewModel.java
index cc669accf..54ae5635e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationViewModel.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationViewModel.java
@@ -5,6 +5,8 @@ import android.app.Application;
import androidx.annotation.NonNull;
import androidx.lifecycle.AndroidViewModel;
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidget;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
@SuppressWarnings("WeakerAccess")
@@ -17,7 +19,7 @@ public class StackWidgetConfigurationViewModel extends AndroidViewModel {
this.syncManager = new SyncManager(application);
}
- public void addStackWidget(int appWidgetId, long accountId, long stackId, boolean darkTheme) {
- syncManager.addStackWidget(appWidgetId, accountId, stackId, darkTheme);
+ public void addStackWidget(@NonNull FilterWidget config, @NonNull IResponseCallback<Integer> callback) {
+ syncManager.createFilterWidget(config, callback);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetFactory.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetFactory.java
index 87f357e20..e44fd9876 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetFactory.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetFactory.java
@@ -1,76 +1,58 @@
package it.niedermann.nextcloud.deck.ui.widget.stack;
import android.appwidget.AppWidgetManager;
-import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.widget.RemoteViews;
import android.widget.RemoteViewsService;
-import androidx.lifecycle.LiveData;
+import androidx.annotation.NonNull;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
+import java.util.NoSuchElementException;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
-import it.niedermann.nextcloud.deck.model.Account;
-import it.niedermann.nextcloud.deck.model.full.FullBoard;
-import it.niedermann.nextcloud.deck.model.full.FullCard;
-import it.niedermann.nextcloud.deck.model.full.FullStack;
+import it.niedermann.nextcloud.deck.model.widget.filter.dto.FilterWidgetCard;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.card.EditActivity;
public class StackWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
private final Context context;
private final int appWidgetId;
- private final long accountId;
- private final long stackId;
+ private final SyncManager syncManager;
- private Account account;
- private FullStack stack;
- private List<FullCard> cardList;
+ @NonNull
+ private final List<FilterWidgetCard> data = new ArrayList<>();
StackWidgetFactory(Context context, Intent intent) {
this.context = context;
appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
- accountId = intent.getLongExtra(StackWidget.ACCOUNT_ID_KEY + appWidgetId, -1);
- stackId = intent.getLongExtra(StackWidget.STACK_ID_KEY + appWidgetId, -1);
- if (intent.hasExtra(StackWidget.BUNDLE_KEY + appWidgetId)) {
- account = (Account) intent.getBundleExtra(StackWidget.BUNDLE_KEY + appWidgetId).getSerializable(StackWidget.ACCOUNT_KEY + appWidgetId);
- }
+ this.syncManager = new SyncManager(context);
}
@Override
public void onCreate() {
- SyncManager syncManager = new SyncManager(context);
-
- LiveData<FullStack> stackLiveData = syncManager.getStack(accountId, stackId);
- stackLiveData.observeForever((FullStack fullStack) -> {
- if (fullStack != null) {
- RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_stack);
- stack = fullStack;
- views.setTextViewText(R.id.widget_stack_title_tv, stack.getStack().getTitle());
-
- LiveData<FullBoard> fullBoardLiveData = syncManager.getFullBoardById(accountId, stack.getStack().getBoardId());
- fullBoardLiveData.observeForever((FullBoard fullBoard) -> {
- if (fullBoard != null) {
- views.setInt(R.id.widget_stack_header_icon, "setColorFilter", fullBoard.getBoard().getColor());
- notifyAppWidgetUpdate(views);
- }
- });
-
- LiveData<List<FullCard>> fullCardData = syncManager.getFullCardsForStack(accountId, stackId, null);
- fullCardData.observeForever((List<FullCard> fullCards) -> cardList = fullCards);
- notifyAppWidgetUpdate(views);
- }
- });
+ // Nothing to do here...
}
@Override
public void onDataSetChanged() {
-
+ try {
+ final List<FilterWidgetCard> response = syncManager.getCardsForFilterWidget(appWidgetId);
+ DeckLog.verbose(StackWidget.class.getSimpleName(), "with id", appWidgetId, "fetched", response.size(), "cards from the database.");
+ data.clear();
+ Collections.sort(response, Comparator.comparingLong(value -> value.getCard().getCard().getOrder()));
+ data.addAll(response);
+ } catch (NoSuchElementException e) {
+ DeckLog.error("No", StackWidget.class.getSimpleName(), "for appWidgetId", appWidgetId, "found.");
+ DeckLog.logError(e);
+ }
}
@@ -81,24 +63,22 @@ public class StackWidgetFactory implements RemoteViewsService.RemoteViewsFactory
@Override
public int getCount() {
- return stack == null ? 0 : stack.getCards().size();
+ return data.size();
}
@Override
public RemoteViews getViewAt(int i) {
- RemoteViews widget_entry;
-
- if (cardList == null || i > (cardList.size() - 1) || cardList.get(i) == null) {
- DeckLog.error("Card not found at position " + i);
+ if (i > (data.size() - 1) || data.get(i) == null) {
+ DeckLog.error("No card or separator not found at position", i);
return null;
}
-
- FullCard card = cardList.get(i);
+ final RemoteViews widget_entry;
+ final FilterWidgetCard filterWidgetCard = data.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());
+ widget_entry.setTextViewText(R.id.widget_entry_content_tv, filterWidgetCard.getCard().getCard().getTitle());
- final Intent intent = EditActivity.createEditCardIntent(context, account, stack.getStack().getBoardId(), card.getCard().getLocalId());
+ final Intent intent = EditActivity.createEditCardIntent(context, syncManager.readAccountDirectly(filterWidgetCard.getCard().getAccountId()), filterWidgetCard.getStack().getBoardId(), filterWidgetCard.getCard().getLocalId());
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
widget_entry.setOnClickFillInIntent(R.id.widget_stack_entry, intent);
@@ -124,11 +104,4 @@ public class StackWidgetFactory implements RemoteViewsService.RemoteViewsFactory
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/upcoming/UpcomingWidget.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidget.java
new file mode 100644
index 000000000..bedf5253f
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidget.java
@@ -0,0 +1,124 @@
+package it.niedermann.nextcloud.deck.ui.widget.upcoming;
+
+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.net.Uri;
+import android.widget.RemoteViews;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.api.ResponseCallback;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.enums.ESortCriteria;
+import it.niedermann.nextcloud.deck.model.widget.filter.EWidgetType;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidget;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetAccount;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetSort;
+import it.niedermann.nextcloud.deck.model.widget.filter.FilterWidgetUser;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.ui.card.EditActivity;
+
+import static android.appwidget.AppWidgetManager.ACTION_APPWIDGET_UPDATE;
+
+public class UpcomingWidget extends AppWidgetProvider {
+ private static final int PENDING_INTENT_OPEN_APP_RQ = 0;
+ private static final int PENDING_INTENT_EDIT_CARD_RQ = 1;
+
+ @Override
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ super.onUpdate(context, appWidgetManager, appWidgetIds);
+ final SyncManager syncManager = new SyncManager(context);
+
+ for (int appWidgetId : appWidgetIds) {
+ new Thread(() -> {
+ if (syncManager.filterWidgetExists(appWidgetId)) {
+ DeckLog.warn(UpcomingWidget.class.getSimpleName(), "with id", appWidgetId, "already exists, perform update instead.");
+ updateAppWidget(context, appWidgetManager, appWidgetIds);
+ } else {
+ final List<Account> accountsList = syncManager.readAccountsDirectly();
+ final FilterWidget config = new FilterWidget(appWidgetId, EWidgetType.UPCOMING_WIDGET);
+ config.setSorts(new FilterWidgetSort(ESortCriteria.DUE_DATE, true));
+ config.setAccounts(accountsList.stream().map(account -> {
+ final FilterWidgetAccount fwa = new FilterWidgetAccount(account.getId(), false);
+ fwa.setUsers(new FilterWidgetUser(syncManager.getUserByUidDirectly(account.getId(), account.getUserName()).getLocalId()));
+ return fwa;
+ }).collect(Collectors.toList()));
+ syncManager.createFilterWidget(config, new ResponseCallback<Integer>() {
+ @Override
+ public void onResponse(Integer response) {
+ DeckLog.verbose("Successfully created", UpcomingWidget.class.getSimpleName(), "with id", appWidgetId);
+ updateAppWidget(context, appWidgetManager, appWidgetIds);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ DeckLog.error("Error while creating", UpcomingWidget.class.getSimpleName(), "with id", appWidgetId);
+ ResponseCallback.super.onError(throwable);
+ onDeleted(context, appWidgetIds);
+ }
+ });
+ }
+ }).start();
+ }
+ }
+
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ super.onReceive(context, intent);
+
+ final AppWidgetManager awm = AppWidgetManager.getInstance(context);
+
+ if (ACTION_APPWIDGET_UPDATE.equals(intent.getAction())) {
+ if (intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
+ final int appWidgetId = intent.getExtras().getInt(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);
+ DeckLog.verbose(ACTION_APPWIDGET_UPDATE, "for", UpcomingWidget.class.getSimpleName(), "with id", appWidgetId, "→ perform update.");
+ updateAppWidget(context, awm, new int[]{appWidgetId});
+ } else {
+ DeckLog.verbose(ACTION_APPWIDGET_UPDATE, "→ Triggering update for all widgets of type", UpcomingWidget.class.getSimpleName());
+ updateAppWidget(context, awm, awm.getAppWidgetIds(new ComponentName(context, UpcomingWidget.class)));
+ }
+ }
+ }
+
+ @Override
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ super.onDeleted(context, appWidgetIds);
+ final SyncManager syncManager = new SyncManager(context);
+
+ for (int appWidgetId : appWidgetIds) {
+ DeckLog.info("Delete", UpcomingWidget.class.getSimpleName(), "with id", appWidgetId);
+ syncManager.deleteFilterWidget(appWidgetId, response -> DeckLog.verbose("Successfully deleted " + UpcomingWidget.class.getSimpleName() + " with id " + appWidgetId));
+ }
+ }
+
+ private static void updateAppWidget(@NonNull Context context, @NonNull AppWidgetManager awm, int[] appWidgetIds) {
+ for (int appWidgetId : appWidgetIds) {
+ new Thread(() -> {
+ final RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.widget_upcoming);
+
+ final Intent serviceIntent = new Intent(context, UpcomingWidgetService.class);
+ serviceIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
+ serviceIntent.setData(Uri.parse(serviceIntent.toUri(Intent.URI_INTENT_SCHEME)));
+
+ final PendingIntent templatePI = PendingIntent.getActivity(context, PENDING_INTENT_EDIT_CARD_RQ,
+ new Intent(context, EditActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
+
+ views.setPendingIntentTemplate(R.id.upcoming_widget_lv, templatePI);
+ views.setRemoteAdapter(R.id.upcoming_widget_lv, serviceIntent);
+ views.setEmptyView(R.id.upcoming_widget_lv, R.id.widget_upcoming_placeholder_iv);
+
+ awm.notifyAppWidgetViewDataChanged(appWidgetId, R.id.upcoming_widget_lv);
+ awm.updateAppWidget(appWidgetId, views);
+ }).start();
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidgetFactory.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidgetFactory.java
new file mode 100644
index 000000000..964c21ffa
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidgetFactory.java
@@ -0,0 +1,224 @@
+package it.niedermann.nextcloud.deck.ui.widget.upcoming;
+
+import android.appwidget.AppWidgetManager;
+import android.content.Context;
+import android.content.Intent;
+import android.net.Uri;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.NoSuchElementException;
+
+import it.niedermann.android.util.DimensionUtil;
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.Card;
+import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.widget.filter.dto.FilterWidgetCard;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.ui.card.EditActivity;
+
+import static java.time.temporal.ChronoUnit.DAYS;
+
+public class UpcomingWidgetFactory implements RemoteViewsService.RemoteViewsFactory {
+ private final Context context;
+ private final int appWidgetId;
+ private final SyncManager syncManager;
+ private final int headerHorizontalPadding;
+ private final int headerVerticalPaddingNth;
+
+ @NonNull
+ private final List<Object> data = new ArrayList<>();
+
+ UpcomingWidgetFactory(@NonNull Context context, Intent intent) {
+ this.context = context;
+ this.appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, AppWidgetManager.INVALID_APPWIDGET_ID);
+ this.syncManager = new SyncManager(context);
+ this.headerHorizontalPadding = DimensionUtil.INSTANCE.dpToPx(context, R.dimen.spacer_1hx);
+ this.headerVerticalPaddingNth = DimensionUtil.INSTANCE.dpToPx(context, R.dimen.spacer_2x);
+ }
+
+ @Override
+ public void onCreate() {
+ // Nothing to do here...
+ }
+
+ @Override
+ public void onDataSetChanged() {
+ try {
+ final List<FilterWidgetCard> response = syncManager.getCardsForFilterWidget(appWidgetId);
+ DeckLog.verbose(UpcomingWidgetFactory.class.getSimpleName(), "with id", appWidgetId, "fetched", response.size(), "cards from the database.");
+ data.clear();
+ final Comparator<FilterWidgetCard> comparator = Comparator.comparing((card -> {
+ if (card != null &&
+ card.getCard() != null &&
+ card.getCard().getCard() != null &&
+ card.getCard().getCard().getDueDate() != null) {
+ return card.getCard().getCard().getDueDate();
+ }
+ return null;
+ }), Comparator.nullsLast(Comparator.naturalOrder()));
+ comparator.thenComparing(card -> {
+ if (card != null &&
+ card.getCard() != null &&
+ card.getCard().getCard().getDueDate() != null) {
+
+ Card c = card.getCard().getCard();
+
+ if (c.getLastModified() == null && c.getLastModifiedLocal() != null) {
+ return c.getLastModifiedLocal();
+ } else if (c.getLastModified() != null && c.getLastModifiedLocal() == null) {
+ return c.getLastModified();
+ } else {
+ return c.getLastModifiedLocal().toEpochMilli() > c.getLastModified().toEpochMilli() ?
+ c.getLastModifiedLocal() : c.getLastModified();
+ }
+ }
+ return null;
+ }, Comparator.nullsLast(Comparator.naturalOrder()));
+
+ Collections.sort(
+ response,
+ comparator
+ );
+ EUpcomingDueType lastDueType = null;
+ for (FilterWidgetCard filterWidgetCard : response) {
+ final EUpcomingDueType nextDueType = getDueType(filterWidgetCard.getCard().getCard().getDueDate());
+ DeckLog.info(filterWidgetCard.getCard().getCard().getTitle() + ":", nextDueType.name());
+ if (!nextDueType.equals(lastDueType)) {
+ data.add(new Separator(nextDueType.toString(context)));
+ lastDueType = nextDueType;
+ }
+ data.add(filterWidgetCard);
+ }
+ } catch (NoSuchElementException e) {
+ DeckLog.error("No", UpcomingWidget.class.getSimpleName(), "for appWidgetId", appWidgetId, "found.");
+ DeckLog.logError(e);
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+
+ }
+
+ @Override
+ public int getCount() {
+ return data.size();
+ }
+
+ @Override
+ public RemoteViews getViewAt(int i) {
+ if (i > (data.size() - 1) || data.get(i) == null) {
+ DeckLog.error("No card or separator not found at position", i);
+ return null;
+ }
+ final RemoteViews widget_entry;
+ if (data.get(i).getClass() == Separator.class) {
+ final Separator separator = (Separator) data.get(i);
+ widget_entry = new RemoteViews(context.getPackageName(), R.layout.widget_separator);
+ widget_entry.setTextViewText(R.id.widget_entry_content_tv, separator.title);
+ if(i == 0) {
+ widget_entry.setViewPadding(R.id.widget_entry_content_tv, headerHorizontalPadding, 0, headerHorizontalPadding, 0);
+ } else {
+ widget_entry.setViewPadding(R.id.widget_entry_content_tv, headerHorizontalPadding, headerVerticalPaddingNth, headerHorizontalPadding, 0);
+ }
+ } else {
+ final FullCard card = ((FilterWidgetCard) data.get(i)).getCard();
+ widget_entry = new RemoteViews(context.getPackageName(), R.layout.widget_stack_entry);
+ widget_entry.setTextViewText(R.id.widget_entry_content_tv, card.getCard().getTitle());
+
+ final Long localCardId = card.getCard().getLocalId();
+ final Intent intent = EditActivity.createEditCardIntent(context, syncManager.readAccountDirectly(card.getAccountId()), syncManager.getBoardLocalIdByLocalCardIdDirectly(localCardId), localCardId);
+ intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
+ widget_entry.setOnClickFillInIntent(R.id.widget_stack_entry, intent);
+ }
+ return widget_entry;
+ }
+
+ @Override
+ public RemoteViews getLoadingView() {
+ return null;
+ }
+
+ @Override
+ public int getViewTypeCount() {
+ return 2;
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ @NonNull
+ private static EUpcomingDueType getDueType(@Nullable Instant dueDate) {
+ if (dueDate == null) {
+ return EUpcomingDueType.NO_DUE;
+ }
+
+ long diff = DAYS.between(LocalDate.now(), dueDate.atZone(ZoneId.systemDefault()).toLocalDate());
+
+ if (diff > 7) {
+ return EUpcomingDueType.LATER;
+ } else if (diff > 1) {
+ return EUpcomingDueType.WEEK;
+ } else if (diff > 0) {
+ return EUpcomingDueType.TOMORROW;
+ } else if (diff == 0) {
+ return EUpcomingDueType.TODAY;
+ } else {
+ return EUpcomingDueType.OVERDUE;
+ }
+ }
+
+ private enum EUpcomingDueType {
+ OVERDUE(1, R.string.filter_overdue),
+ TODAY(2, R.string.filter_today),
+ TOMORROW(3, R.string.filter_tomorrow),
+ WEEK(4, R.string.filter_week),
+ LATER(5, R.string.filter_later),
+ NO_DUE(6, R.string.filter_no_due);
+
+ private final int value;
+ private final int id;
+
+ EUpcomingDueType(int id, @StringRes int value) {
+ this.value = value;
+ this.id = id;
+ }
+
+ public int getId() {
+ return id;
+ }
+
+ @NonNull
+ public String toString(Context context) {
+ return context.getString(this.value);
+ }
+ }
+
+ private static class Separator {
+ public String title;
+
+ private Separator(String title) {
+ this.title = title;
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidgetService.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidgetService.java
new file mode 100644
index 000000000..6cbd04549
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/upcoming/UpcomingWidgetService.java
@@ -0,0 +1,11 @@
+package it.niedermann.nextcloud.deck.ui.widget.upcoming;
+
+import android.content.Intent;
+import android.widget.RemoteViewsService;
+
+public class UpcomingWidgetService extends RemoteViewsService {
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ return new UpcomingWidgetFactory(this.getApplicationContext(), intent);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java
index ef3e14b37..a4bd5d6d9 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java
@@ -12,15 +12,11 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.Px;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
+import it.niedermann.nextcloud.deck.model.enums.EAttachmentType;
import it.niedermann.nextcloud.deck.model.ocs.Version;
/**
@@ -37,90 +33,63 @@ public class AttachmentUtil {
* If a thumbnail is not available (see {@link Version#supportsFileAttachments()}), a link to
* the {@link Attachment} itself will be returned instead.
*/
- public static String getThumbnailUrl(@NonNull Version version, @NonNull String accountUrl, @NonNull Long cardRemoteId, @NonNull Attachment attachment, @Px int previewSize) {
- return version.supportsFileAttachments() && !TextUtils.isEmpty(String.valueOf(attachment.getFileId()))
- ? accountUrl + "/index.php/core/preview?fileId=" + attachment.getFileId() + "&x=" + previewSize + "&y=" + previewSize
- : getRemoteOrLocalUrl(accountUrl, cardRemoteId, attachment);
+ public static String getThumbnailUrl(@NonNull Account account, @NonNull Long cardRemoteId, @NonNull Attachment attachment, @Px int previewSize) {
+ return getThumbnailUrl(account, cardRemoteId, attachment, previewSize, previewSize);
+ }
+
+ public static String getThumbnailUrl(@NonNull Account account, @NonNull Long cardRemoteId, @NonNull Attachment attachment, @Px int previewWidth, @Px int previewHeight) {
+ return account.getServerDeckVersionAsObject().supportsFileAttachments() &&
+ EAttachmentType.FILE.equals(attachment.getType()) &&
+ attachment.getFileId() != null
+ ? account.getUrl() + "/index.php/core/preview?fileId=" + attachment.getFileId() + "&x=" + previewWidth + "&y=" + previewHeight + "&a=true"
+ : getRemoteOrLocalUrl(account.getUrl(), cardRemoteId, attachment);
}
/**
- * @return {@link AttachmentUtil#getRemoteUrl} or {@link Attachment#getLocalPath()} as fallback
+ * @return {@link AttachmentUtil#getDeck_1_0_RemoteUrl} or {@link Attachment#getLocalPath()} as fallback
* in case this {@param attachment} has not yet been synced.
*/
@Nullable
- public static String getRemoteOrLocalUrl(@NonNull String accountUrl, @Nullable Long cardRemoteId, @NonNull Attachment attachment) {
+ private static String getRemoteOrLocalUrl(@NonNull String accountUrl, @Nullable Long cardRemoteId, @NonNull Attachment attachment) {
return (attachment.getId() == null || cardRemoteId == null)
? attachment.getLocalPath()
- : getRemoteUrl(accountUrl, cardRemoteId, attachment.getId());
+ : getDeck_1_0_RemoteUrl(accountUrl, cardRemoteId, attachment.getId());
}
/**
* Tries to open the given {@link Attachment} in web browser. Displays a toast on failure.
*/
- public static void openAttachmentInBrowser(@NonNull Context context, @NonNull String accountUrl, Long cardRemoteId, Long attachmentRemoteId) {
+ public static void openAttachmentInBrowser(@NonNull Account account, @NonNull Context context, Long cardRemoteId, Attachment attachment) {
if (cardRemoteId == null) {
Toast.makeText(context, R.string.card_does_not_yet_exist, Toast.LENGTH_LONG).show();
DeckLog.logError(new IllegalArgumentException("cardRemoteId must not be null."));
return;
}
- if (attachmentRemoteId == null) {
+
+ try {
+ context.startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(getCopyDownloadUrl(account, cardRemoteId, attachment))));
+ } catch (IllegalArgumentException e) {
Toast.makeText(context, R.string.attachment_does_not_yet_exist, Toast.LENGTH_LONG).show();
DeckLog.logError(new IllegalArgumentException("attachmentRemoteId must not be null."));
- return;
}
- context.startActivity(new Intent(Intent.ACTION_VIEW).setData(Uri.parse(AttachmentUtil.getRemoteUrl(accountUrl, cardRemoteId, attachmentRemoteId))));
}
- private static String getRemoteUrl(@NonNull String accountUrl, @NonNull Long cardRemoteId, @NonNull Long attachmentRemoteId) {
- return accountUrl + "/index.php/apps/deck/cards/" + cardRemoteId + "/attachment/" + attachmentRemoteId;
- }
-
- public static File copyContentUriToTempFile(@NonNull Context context, @NonNull Uri currentUri, long accountId, Long localCardId) throws IOException, IllegalArgumentException {
- final InputStream inputStream = context.getContentResolver().openInputStream(currentUri);
- if (inputStream == null) {
- throw new IOException("Could not open input stream for " + currentUri.getPath());
+ public static String getCopyDownloadUrl(@NonNull Account account, @NonNull Long cardRemoteId, @NonNull Attachment attachment) {
+ if (attachment.getId() == null) {
+ throw new IllegalArgumentException("attachment id must not be null");
}
- final File cacheFile = getTempCacheFile(context, "attachments/account-" + accountId + "/card-" + (localCardId == null ? "pending-creation" : localCardId) + '/' + UriUtils.getDisplayNameForUri(currentUri, context));
- final FileOutputStream outputStream = new FileOutputStream(cacheFile);
- byte[] buffer = new byte[4096];
- int count;
- while ((count = inputStream.read(buffer)) > 0) {
- outputStream.write(buffer, 0, count);
- }
- DeckLog.verbose("----- wrote");
- return cacheFile;
+ return (attachment.getFileId() != null)
+ ? account.getUrl() + "/f/" + attachment.getFileId()
+ : getDeck_1_0_RemoteUrl(account.getUrl(), cardRemoteId, attachment.getId());
}
/**
- * Creates a new {@link File}
+ * Attention! This does only work for attachments of type "deck_file" which are a legacy of Deck API 1.0
*/
- public static File getTempCacheFile(@NonNull Context context, String fileName) throws IOException {
- File cacheFile = new File(context.getApplicationContext().getFilesDir().getAbsolutePath() + "/" + fileName);
-
- DeckLog.verbose("- Full path for new cache file: " + cacheFile.getAbsolutePath());
-
- final File tempDir = cacheFile.getParentFile();
- if (tempDir == null) {
- throw new FileNotFoundException("could not cacheFile.getParentFile()");
- }
- if (!tempDir.exists()) {
- DeckLog.verbose("-- The folder in which the new file should be created does not exist yet. Trying to create it...");
- if (tempDir.mkdirs()) {
- DeckLog.verbose("--- Creation successful");
- } else {
- throw new IOException("Directory for temporary file does not exist and could not be created.");
- }
- }
-
- DeckLog.verbose("- Try to create actual cache file");
- if (cacheFile.createNewFile()) {
- DeckLog.verbose("-- Successfully created cache file");
- } else {
- throw new IOException("Failed to create cacheFile");
- }
-
- return cacheFile;
+ @Deprecated
+ private static String getDeck_1_0_RemoteUrl(@NonNull String accountUrl, @NonNull Long cardRemoteId, @NonNull Long attachmentRemoteId) {
+ return accountUrl + "/index.php/apps/deck/cards/" + cardRemoteId + "/attachment/" + attachmentRemoteId;
}
@DrawableRes
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/CardUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/CardUtil.java
index f89581c95..4f31b7250 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/CardUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/CardUtil.java
@@ -1,9 +1,21 @@
package it.niedermann.nextcloud.deck.util;
+import android.content.Context;
+import android.text.TextUtils;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.Card;
+import it.niedermann.nextcloud.deck.model.Label;
+import it.niedermann.nextcloud.deck.model.full.FullCard;
public class CardUtil {
private static final Pattern pLists = Pattern.compile("^\\s*[*+-]\\s+", Pattern.MULTILINE);
@@ -17,8 +29,34 @@ public class CardUtil {
// You shall not pass
}
+ /**
+ * @return a human readable String containing the description, due date and tags of the given {@param fullCard}
+ */
+ @NonNull
+ public static String getCardContentAsString(@NonNull Context context, @NonNull FullCard fullCard) {
+ final Card card = fullCard.getCard();
+ String text = card.getDescription();
+ if(card.getDueDate() != null) {
+ if(!TextUtils.isEmpty(text)) {
+ text += "\n";
+ }
+ text += context.getString(R.string.share_content_duedate, card.getDueDate().atZone(ZoneId.systemDefault()).format(DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM)));
+ }
+ if(fullCard.getLabels() != null && !fullCard.getLabels().isEmpty()) {
+ if(!TextUtils.isEmpty(text)) {
+ text += "\n";
+ }
+ text += context.getString(R.string.share_content_labels, fullCard.getLabels().stream().map(Label::getTitle).collect(Collectors.joining(", ")));
+ }
+ return text;
+ }
+
+ public static boolean cardHasCommentsOrAttachments(@NonNull FullCard fullCard) {
+ return fullCard.getCommentCount() > 0 || (fullCard.getAttachments() != null && !fullCard.getAttachments().isEmpty());
+ }
+
@NonNull
- public static String generateTitleFromDescription(String description) {
+ public static String generateTitleFromDescription(@Nullable String description) {
if(description == null) return "";
return getLineWithoutMarkDown(description, 0);
}
@@ -32,20 +70,19 @@ public class CardUtil {
*/
@NonNull
private static String getLineWithoutMarkDown(@NonNull String content, @SuppressWarnings("SameParameterValue") int lineNumber) {
- String line = "";
if (content.contains("\n")) {
- String[] lines = content.split("\n");
+ final String[] lines = content.split("\n");
int currentLine = lineNumber;
while (currentLine < lines.length && isEmptyLine(lines[currentLine])) {
currentLine++;
}
if (currentLine < lines.length) {
- line = removeMarkDown(lines[currentLine]);
+ return removeMarkDown(lines[currentLine]);
}
} else {
- line = content;
+ return content;
}
- return line;
+ return "";
}
/**
@@ -61,7 +98,7 @@ public class CardUtil {
* @param line String - a single Line which ends with \n
* @return boolean isEmpty
*/
- private static boolean isEmptyLine(@Nullable String line) {
+ private static boolean isEmptyLine(@NonNull String line) {
return removeMarkDown(line).trim().length() == 0;
}
@@ -72,9 +109,7 @@ public class CardUtil {
* @return Plain Text-String
*/
@NonNull
- private static String removeMarkDown(@Nullable String s) {
- if (s == null)
- return "";
+ private static String removeMarkDown(@NonNull String s) {
s = pLists.matcher(s).replaceAll("");
s = pHeadings.matcher(s).replaceAll("$1");
s = pHeadingLine.matcher(s).replaceAll("");
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/DateUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/DateUtil.java
index ac88519ff..6fbb9e30e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/DateUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/DateUtil.java
@@ -22,14 +22,14 @@ public final class DateUtil {
return context.getString(R.string.seconds_ago);
} else {
// in the future or past (larger than 60 seconds)
- CharSequence dateString = DateUtils.getRelativeDateTimeString(
+ final CharSequence dateString = DateUtils.getRelativeDateTimeString(
context,
time,
DateUtils.SECOND_IN_MILLIS,
DateUtils.WEEK_IN_MILLIS,
0
);
- String[] parts = dateString.toString().split(",");
+ final String[] parts = dateString.toString().split(",");
if (parts.length == DATE_TIME_PARTS_SIZE) {
if (parts[1].contains(":") && !parts[0].contains(":")) {
return parts[0];
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/DrawerMenuUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/DrawerMenuUtil.java
index 1a88bec97..43a9e6299 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/DrawerMenuUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/DrawerMenuUtil.java
@@ -39,7 +39,9 @@ public class DrawerMenuUtil {
SubMenu boardsMenu = menu.addSubMenu(R.string.simple_boards);
int index = 0;
for (Board board : boards) {
- MenuItem m = boardsMenu.add(Menu.NONE, index++, Menu.NONE, board.getTitle()).setIcon(ViewUtil.getTintedImageView(context, R.drawable.circle_grey600_36dp, board.getColor()));
+ MenuItem m = boardsMenu
+ .add(Menu.NONE, index++, Menu.NONE, board.getTitle()).setIcon(ViewUtil.getTintedImageView(context, R.drawable.circle_grey600_36dp, board.getColor()))
+ .setCheckable(true);
if (currentServerVersionIsSupported) {
if (board.isPermissionManage()) {
AppCompatImageButton contextMenu = new AppCompatImageButton(context);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/FilesUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/FilesUtil.java
new file mode 100644
index 000000000..6feb4d80c
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/FilesUtil.java
@@ -0,0 +1,78 @@
+package it.niedermann.nextcloud.deck.util;
+
+import android.content.Context;
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+
+/**
+ * Created by stefan on 07.03.20.
+ */
+
+public class FilesUtil {
+
+ private FilesUtil() {
+ }
+
+ /**
+ * https://help.nextcloud.com/t/android-app-select-file-with-nextcloud-app-file-cant-be-read/103706
+ * Must not be called from the UI thread because the {@param currentUri} might refer to a not yet locally available file.
+ */
+ @WorkerThread
+ public static File copyContentUriToTempFile(@NonNull Context context, @NonNull Uri currentUri, long accountId, Long localCardId) throws IOException, IllegalArgumentException {
+ final InputStream inputStream = context.getContentResolver().openInputStream(currentUri);
+ if (inputStream == null) {
+ throw new IOException("Could not open input stream for " + currentUri.getPath());
+ }
+ final File cacheFile = getTempCacheFile(context, "attachments/account-" + accountId + "/card-" + (localCardId == null ? "pending-creation" : localCardId) + '/' + UriUtils.getDisplayNameForUri(currentUri, context));
+ final FileOutputStream outputStream = new FileOutputStream(cacheFile);
+ byte[] buffer = new byte[4096];
+
+ int count;
+ while ((count = inputStream.read(buffer)) > 0) {
+ outputStream.write(buffer, 0, count);
+ }
+ DeckLog.verbose("----- wrote");
+ return cacheFile;
+ }
+
+ /**
+ * Creates a new {@link File}
+ */
+ public static File getTempCacheFile(@NonNull Context context, String fileName) throws IOException {
+ File cacheFile = new File(context.getApplicationContext().getFilesDir().getAbsolutePath() + "/" + fileName);
+
+ DeckLog.verbose("- Full path for new cache file:", cacheFile.getAbsolutePath());
+
+ final File tempDir = cacheFile.getParentFile();
+ if (tempDir == null) {
+ throw new FileNotFoundException("could not cacheFile.getParentFile()");
+ }
+ if (!tempDir.exists()) {
+ DeckLog.verbose("-- The folder in which the new file should be created does not exist yet. Trying to create it…");
+ if (tempDir.mkdirs()) {
+ DeckLog.verbose("--- Creation successful");
+ } else {
+ throw new IOException("Directory for temporary file does not exist and could not be created.");
+ }
+ }
+
+ DeckLog.verbose("- Try to create actual cache file");
+ if (cacheFile.createNewFile()) {
+ DeckLog.verbose("-- Successfully created cache file");
+ } else {
+ throw new IOException("Failed to create cacheFile");
+ }
+
+ return cacheFile;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/MarkDownUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/MarkDownUtil.java
deleted file mode 100644
index 9e1e5b147..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/MarkDownUtil.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package it.niedermann.nextcloud.deck.util;
-
-import android.content.Context;
-
-import androidx.core.content.ContextCompat;
-import androidx.core.content.res.ResourcesCompat;
-
-import com.yydcdut.rxmarkdown.RxMDConfiguration.Builder;
-
-import it.niedermann.nextcloud.deck.R;
-
-/**
- * Created by stefan on 07.12.16.
- */
-
-public class MarkDownUtil {
-
- private MarkDownUtil() {}
-
- /**
- * Ensures every instance of RxMD uses the same configuration
- *
- * @param context Context
- * @return RxMDConfiguration
- */
- public static Builder getMarkDownConfiguration(Context context) {
- return new Builder(context)
- .setHeader2RelativeSize(1.35f)
- .setHeader3RelativeSize(1.25f)
- .setHeader4RelativeSize(1.15f)
- .setHeader5RelativeSize(1.1f)
- .setHeader6RelativeSize(1.05f)
- .setHorizontalRulesHeight(2)
- .setLinkFontColor(ContextCompat.getColor(context, R.color.primary));
- }
-
- public static Builder getMarkDownConfiguration(Context context, Boolean darkTheme) {
- return new Builder(context)
- .setHeader2RelativeSize(1.35f)
- .setHeader3RelativeSize(1.25f)
- .setHeader4RelativeSize(1.15f)
- .setHeader5RelativeSize(1.1f)
- .setHeader6RelativeSize(1.05f)
- .setHorizontalRulesHeight(2)
- .setLinkFontColor(ResourcesCompat.getColor(context.getResources(), R.color.primary, null));
- }
-}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/SpannableUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/SpannableUtil.java
index c2ba6f46a..d11631fde 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/SpannableUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/SpannableUtil.java
@@ -5,7 +5,6 @@ import android.content.res.Resources;
import android.graphics.Typeface;
import android.text.Spannable;
import android.text.SpannableString;
-import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.method.LinkMovementMethod;
import android.text.style.ForegroundColorSpan;
@@ -42,9 +41,9 @@ public class SpannableUtil {
public static void setTextWithURL(@NonNull TextView textView, @NonNull Resources resources, @StringRes int containerTextId, @StringRes int linkLabelId, @StringRes int urlId) {
final String linkLabel = resources.getString(linkLabelId);
final String finalText = resources.getString(containerTextId, linkLabel);
- final SpannableStringBuilder finalTextBuilder = new SpannableStringBuilder(finalText);
- finalTextBuilder.setSpan(new URLSpan(resources.getString(urlId)), finalText.indexOf(linkLabel), finalText.indexOf(linkLabel) + linkLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
- textView.setText(finalTextBuilder);
+ final SpannableString finalSpannable = new SpannableString(finalText);
+ finalSpannable.setSpan(new URLSpan(resources.getString(urlId)), finalText.indexOf(linkLabel), finalText.indexOf(linkLabel) + linkLabel.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
+ textView.setText(finalSpannable);
textView.setMovementMethod(new LinkMovementMethod());
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java
index fbc7fc3fc..d90ce297c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java
@@ -2,12 +2,8 @@ package it.niedermann.nextcloud.deck.util;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
-import android.text.Spannable;
-import android.text.SpannableStringBuilder;
-import android.text.style.ImageSpan;
import android.widget.ImageView;
import android.widget.TextView;
@@ -15,7 +11,6 @@ import androidx.annotation.ColorInt;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
@@ -23,19 +18,12 @@ import androidx.core.widget.TextViewCompat;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
-import com.bumptech.glide.request.target.CustomTarget;
-import com.bumptech.glide.request.transition.Transition;
import java.time.LocalDate;
-import java.util.List;
import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.R;
-import it.niedermann.nextcloud.deck.model.Account;
-import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
-import static android.os.Build.VERSION.SDK_INT;
-import static android.os.Build.VERSION_CODES.LOLLIPOP;
import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
import static java.time.temporal.ChronoUnit.DAYS;
@@ -88,63 +76,7 @@ public final class ViewUtil {
return drawable;
}
- /**
- * Replaces all mentions in the textView with an avatar and the display name
- *
- * @param account {@link Account} where the users of those mentions belong to
- * @param mentions {@link List} of all mentions that should be substituted
- * @param textView target {@link TextView}
- */
- public static void setupMentions(@NonNull Account account, @NonNull List<Mention> mentions, TextView textView) {
- Context context = textView.getContext();
- SpannableStringBuilder messageBuilder = new SpannableStringBuilder(textView.getText());
-
- // Step 1
- // Add avatar icons and display names
- for (Mention m : mentions) {
- final String mentionId = "@" + m.getMentionId();
- final String mentionDisplayName = " " + m.getMentionDisplayName();
- int index = messageBuilder.toString().lastIndexOf(mentionId);
- while (index >= 0) {
- messageBuilder.setSpan(new ImageSpan(context, R.drawable.ic_person_grey600_24dp), index, index + mentionId.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- messageBuilder.insert(index + mentionId.length(), mentionDisplayName);
- index = messageBuilder.toString().substring(0, index).lastIndexOf(mentionId);
- }
- }
- textView.setText(messageBuilder);
-
- // Step 2
- // Replace avatar icons with real avatars
- final ImageSpan[] list = messageBuilder.getSpans(0, messageBuilder.length(), ImageSpan.class);
- for (ImageSpan span : list) {
- final int spanStart = messageBuilder.getSpanStart(span);
- final int spanEnd = messageBuilder.getSpanEnd(span);
- Glide.with(context)
- .asBitmap()
- .placeholder(R.drawable.ic_person_grey600_24dp)
- .load(account.getUrl() + "/index.php/avatar/" + messageBuilder.subSequence(spanStart + 1, spanEnd).toString() + "/" + DimensionUtil.INSTANCE.dpToPx(context, R.dimen.icon_size_details))
- .apply(RequestOptions.circleCropTransform())
- .into(new CustomTarget<Bitmap>() {
- @Override
- public void onResourceReady(@NonNull Bitmap resource, @Nullable Transition<? super Bitmap> transition) {
- messageBuilder.removeSpan(span);
- messageBuilder.setSpan(new ImageSpan(context, resource), spanStart, spanEnd, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
- }
-
- @Override
- public void onLoadCleared(@Nullable Drawable placeholder) {
- // silence is gold
- }
- });
- }
- textView.setText(messageBuilder);
- }
-
public static void setImageColor(@NonNull Context context, @NonNull ImageView imageView, @ColorRes int colorRes) {
- if (SDK_INT >= LOLLIPOP) {
- imageView.setImageTintList(ColorStateList.valueOf(ContextCompat.getColor(context, colorRes)));
- } else {
- imageView.setColorFilter(ContextCompat.getColor(context, colorRes));
- }
+ imageView.setImageTintList(ColorStateList.valueOf(ContextCompat.getColor(context, colorRes)));
}
}