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>2020-12-09 19:59:07 +0300
committerStefan Niedermann <info@niedermann.it>2020-12-09 19:59:07 +0300
commitdf900e53492c7b30cbb90b9180d6c3cdf59f38d9 (patch)
treeb88dc0386e6b448c95cf4fcb46fbeaf8edc8780f
parent034ae108ae4ab4c273ef4d74f1bfd39fbc4d8a84 (diff)
parentf29eed9db4c0906fa7887e446cf0325718ef6827 (diff)
Merge branch 'master' into fastlanefastlane
# Conflicts: # fastlane/metadata/android/en-US/images/phoneScreenshots/1.png # fastlane/metadata/android/en-US/images/phoneScreenshots/2.png # fastlane/metadata/android/en-US/images/phoneScreenshots/4.png
-rw-r--r--.github/ISSUE_TEMPLATE/bug_report.md24
-rw-r--r--.github/ISSUE_TEMPLATE/config.yml5
-rw-r--r--.github/ISSUE_TEMPLATE/feature_request.md10
-rw-r--r--FAQ.md51
-rw-r--r--PRIVACY.md50
-rw-r--r--README.md7
-rw-r--r--app/build.gradle40
-rw-r--r--app/play/release/output.json (renamed from glide-sso-integration/consumer-rules.pro)0
-rw-r--r--app/src/androidTest/java/it/niedermann/nextcloud/deck/util/AttachmentUtilTest.java50
-rw-r--r--app/src/androidTest/java/it/niedermann/nextcloud/deck/util/ColorUtilTest.java124
-rw-r--r--app/src/androidTest/java/it/niedermann/nextcloud/deck/util/DateUtilTest.java35
-rw-r--r--app/src/androidTest/java/it/niedermann/nextcloud/deck/util/DeckColorUtilTest.java49
-rw-r--r--app/src/androidTest/java/it/niedermann/nextcloud/deck/util/SpannableUtilTest.java52
-rw-r--r--app/src/main/AndroidManifest.xml44
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java18
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/DeckAPI.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/GsonUTCDateAdapter.java48
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/GsonUTCInstantAdapter.java43
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/IResponseCallback.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/JsonToEntityParser.java332
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/LastSyncUtil.java14
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/NextcloudServerAPI.java21
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/api/json/JsonColorSerializer.java26
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/exceptions/DeckException.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/exceptions/TraceableException.java20
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/AccessControl.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/Account.java81
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/Attachment.java60
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/Board.java48
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/Card.java58
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/Label.java53
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/Stack.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/User.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/appwidgets/StackWidgetModel.java68
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullBoard.java14
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCard.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCardWithProjects.java82
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullStack.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractJoinEntity.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractRemoteEntity.java60
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/IRemoteEntity.java43
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/internal/FilterInformation.java55
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Capabilities.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Version.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/comment/DeckComment.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/JoinCardWithProject.java66
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProject.java45
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProjectList.java24
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProjectResource.java135
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/full/OcsProjectWithResources.java50
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/GroupMemberUIDs.java27
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUserList.java13
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/relations/UserInBoard.java71
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/model/relations/UserInGroup.java70
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/SyncManager.java811
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/ServerAdapter.java57
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DataBaseAdapter.java294
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DateTypeConverter.java10
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/DeckDatabase.java256
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/BoardDao.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/CardDao.java16
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/StackDao.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java34
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInBoardDao.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInGroupDao.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/JoinCardWithOcsProjectDao.java13
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectDao.java13
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectResourceDao.java25
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/SingleCardWidgetModelDao.java (renamed from app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java)3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/StackWidgetModelDao.java19
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/WrappedLiveData.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java75
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java93
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/DataPropagationHelper.java119
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/SyncHelper.java77
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AbstractSyncDataProvider.java33
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AccessControlDataProvider.java97
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java17
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/AttachmentDataProvider.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java119
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/CardDataProvider.java76
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/LabelDataProvider.java26
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java100
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java43
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithAclDownSyncDataProvider.java (renamed from app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWitAclDownSyncDataProvider.java)2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithStacksAndLabelsUpSyncDataProvider.java37
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/util/AsyncUtil.java21
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/ImportAccountActivity.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/MainActivity.java315
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/MainViewModel.java223
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/PickStackActivity.java115
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationActivity.java146
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java52
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/about/AboutFragmentLicenseTab.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherDialog.java26
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardViewHolder.java37
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardsActvitiy.java43
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java22
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsAdapter.java72
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentAdapter.java56
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentViewHolder.java61
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsActivity.java20
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsViewModel.java25
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/ArchiveBoardListener.java1
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/BoardAdapter.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardDialogFragment.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardListener.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlAdapter.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/accesscontrol/AccessControlDialogFragment.java24
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/EditLabelDialogFragment.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsDialogFragment.java16
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsViewHolder.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedAlertDialogBuilder.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDatePickerDialog.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDeleteAlertDialogBuilder.java3
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedSnackbar.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedTimePickerDialog.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandingUtil.java17
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AbstractCardViewHolder.java122
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardAdapter.java345
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardOptionsItemSelectedListener.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CompactCardViewHolder.java84
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/DefaultCardOnlyTitleViewHolder.java61
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/DefaultCardViewHolder.java157
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditActivity.java44
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/EditCardViewModel.java78
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/ItemCardViewHolder.java15
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/LabelAutoCompleteAdapter.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java17
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityFragment.java9
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityViewHolder.java50
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java113
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeListener.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentViewHolder.java43
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentAdapter.java125
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsBottomsheetBehaviorCallback.java91
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsFragment.java492
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/DefaultAttachmentViewHolder.java36
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/ImageAttachmentViewHolder.java33
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractCursorPickerAdapter.java100
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java26
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java104
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java66
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactNativeItemViewHolder.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java85
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapterLegacy.java88
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java45
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileNativeItemViewHolder.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java100
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemDecoration.java29
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java42
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryPhotoPreviewItemViewHolder.java51
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialog.java102
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialogViewModel.java50
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsFragment.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsMentionProposer.java139
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CommentsViewModel.java37
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/ItemCommentViewHolder.java19
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java48
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeAdapter.java80
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeDecoration.java28
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeViewHolder.java29
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsFragment.java253
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsListener.java21
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardProjectsAdapter.java52
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardProjectsViewHolder.java32
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceAdapter.java54
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceViewHolder.java110
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourcesDialog.java83
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionActivity.java29
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java10
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionHandler.java19
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/tips/TipsAdapter.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterDialogFragment.java18
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsAdapter.java44
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsFragment.java25
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java43
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserFragment.java29
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterViewModel.java40
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/SelectionListener.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java6
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java28
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsViewModel.java45
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java128
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardListener.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java204
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackListener.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackViewModel.java45
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/AccountAdapter.java27
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/BoardAdapter.java8
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/PrepareCreateActivity.java226
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/StackAdapter.java16
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/settings/SettingsFragment.java4
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareProgressDialogFragment.java5
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareTargetActivity.java10
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackAdapter.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/stack/StackFragment.java49
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java182
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoViewModel.java57
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/ColorChooser.java56
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/OverlappingAvatars.java16
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java35
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/CompactLabelChip.java21
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/DefaultLabelChip.java21
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/LabelChip.java (renamed from app/src/main/java/it/niedermann/nextcloud/deck/ui/view/LabelChip.java)25
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/CompactLabelLayout.java22
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/DefaultLabelLayout.java21
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/LabelLayout.java (renamed from app/src/main/java/it/niedermann/nextcloud/deck/ui/view/LabelLayout.java)19
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java2
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SingleCardWidget.java7
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidget.java121
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationActivity.java68
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationViewModel.java23
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetFactory.java134
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetService.java11
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/AttachmentUtil.java127
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/ClipboardUtil.java38
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/ColorUtil.java165
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/DateUtil.java51
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/DeckColorUtil.java59
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/DimensionUtil.java17
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/DrawerMenuUtil.java47
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/ExceptionUtil.java81
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/MimeTypeUtil.java1
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/ProjectUtil.java90
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/SpannableUtil.java12
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/VCardUtil.java42
-rw-r--r--app/src/main/java/it/niedermann/nextcloud/deck/util/ViewUtil.java42
-rw-r--r--app/src/main/res/drawable-v21/bottom_sheet_rounded.xml9
-rw-r--r--app/src/main/res/drawable-xxxhdpi/background.pngbin44461 -> 0 bytes
-rw-r--r--app/src/main/res/drawable/bottom_sheet_rounded.xml8
-rw-r--r--app/src/main/res/drawable/ic_arrow_drop_down_black_24dp.xml5
-rw-r--r--app/src/main/res/drawable/ic_baseline_block_24.xml5
-rw-r--r--app/src/main/res/drawable/ic_baseline_camera_front_24.xml5
-rw-r--r--app/src/main/res/drawable/ic_baseline_camera_rear_24.xml5
-rw-r--r--app/src/main/res/drawable/ic_baseline_compact_24.xml5
-rw-r--r--app/src/main/res/drawable/ic_baseline_flash_off_24.xml5
-rw-r--r--app/src/main/res/drawable/ic_baseline_flash_on_24.xml5
-rw-r--r--app/src/main/res/drawable/ic_baseline_mention_24.xml5
-rw-r--r--app/src/main/res/drawable/ic_baseline_photo_camera_24.xml6
-rw-r--r--app/src/main/res/drawable/ic_baseline_search_24.xml5
-rw-r--r--app/src/main/res/drawable/ic_baseline_subject_24.xml5
-rw-r--r--app/src/main/res/drawable/ic_filter_list_active_white_24dp.xml8
-rw-r--r--app/src/main/res/drawable/ic_format_align_left_black_24dp.xml11
-rw-r--r--app/src/main/res/drawable/ic_more_horiz_black_24dp.xml5
-rw-r--r--app/src/main/res/drawable/ic_projects_24.xml5
-rw-r--r--app/src/main/res/drawable/project_deck_36dp.xml22
-rw-r--r--app/src/main/res/drawable/project_file_36dp.xml5
-rw-r--r--app/src/main/res/drawable/project_talk_36dp.xml11
-rw-r--r--app/src/main/res/drawable/type_add_color_36dp.xml2
-rw-r--r--app/src/main/res/drawable/type_delete_color_36dp.xml2
-rw-r--r--app/src/main/res/drawable/widget_stack_preview.pngbin0 -> 219542 bytes
-rw-r--r--app/src/main/res/layout/activity_archived.xml2
-rw-r--r--app/src/main/res/layout/activity_exception.xml1
-rw-r--r--app/src/main/res/layout/activity_main.xml12
-rw-r--r--app/src/main/res/layout/activity_pick_stack.xml (renamed from app/src/main/res/layout/activity_prepare_create.xml)33
-rw-r--r--app/src/main/res/layout/activity_take_photo.xml55
-rw-r--r--app/src/main/res/layout/dialog_move_card.xml80
-rw-r--r--app/src/main/res/layout/dialog_preview.xml23
-rw-r--r--app/src/main/res/layout/dialog_project_resources.xml10
-rw-r--r--app/src/main/res/layout/fragment_card_edit_tab_attachments.xml59
-rw-r--r--app/src/main/res/layout/fragment_card_edit_tab_comments.xml42
-rw-r--r--app/src/main/res/layout/fragment_card_edit_tab_details.xml284
-rw-r--r--app/src/main/res/layout/fragment_pick_stack.xml28
-rw-r--r--app/src/main/res/layout/fragment_stack.xml2
-rw-r--r--app/src/main/res/layout/item_assignee.xml17
-rw-r--r--app/src/main/res/layout/item_attachment_default.xml19
-rw-r--r--app/src/main/res/layout/item_card_compact.xml96
-rw-r--r--app/src/main/res/layout/item_card_default.xml (renamed from app/src/main/res/layout/item_card.xml)15
-rw-r--r--app/src/main/res/layout/item_card_default_only_title.xml79
-rw-r--r--app/src/main/res/layout/item_filter_user.xml2
-rw-r--r--app/src/main/res/layout/item_photo_preview.xml27
-rw-r--r--app/src/main/res/layout/item_picker_native.xml51
-rw-r--r--app/src/main/res/layout/item_picker_user.xml59
-rw-r--r--app/src/main/res/layout/item_prepare_create_account.xml6
-rw-r--r--app/src/main/res/layout/item_prepare_create_stack.xml4
-rw-r--r--app/src/main/res/layout/item_project.xml45
-rw-r--r--app/src/main/res/layout/item_project_resource.xml46
-rw-r--r--app/src/main/res/layout/item_tip.xml1
-rw-r--r--app/src/main/res/layout/widget_stack.xml64
-rw-r--r--app/src/main/res/layout/widget_stack_entry.xml24
-rw-r--r--app/src/main/res/menu/attachment_picker_menu.xml19
-rw-r--r--app/src/main/res/menu/navigation_context_menu.xml5
-rw-r--r--app/src/main/res/values-ca/strings.xml58
-rw-r--r--app/src/main/res/values-cs-rCZ/strings.xml81
-rw-r--r--app/src/main/res/values-da/strings.xml4
-rw-r--r--app/src/main/res/values-de/strings.xml82
-rw-r--r--app/src/main/res/values-el/strings.xml49
-rw-r--r--app/src/main/res/values-es/strings.xml79
-rw-r--r--app/src/main/res/values-eu/strings.xml94
-rw-r--r--app/src/main/res/values-fi-rFI/strings.xml147
-rw-r--r--app/src/main/res/values-fr/strings.xml114
-rw-r--r--app/src/main/res/values-gl/strings.xml86
-rw-r--r--app/src/main/res/values-he/strings.xml23
-rw-r--r--app/src/main/res/values-hr/strings.xml72
-rw-r--r--app/src/main/res/values-hu-rHU/strings.xml63
-rw-r--r--app/src/main/res/values-it/strings.xml68
-rw-r--r--app/src/main/res/values-ja-rJP/strings.xml76
-rw-r--r--app/src/main/res/values-ko/strings.xml90
-rw-r--r--app/src/main/res/values-nb-rNO/strings.xml277
-rw-r--r--app/src/main/res/values-nl/strings.xml78
-rw-r--r--app/src/main/res/values-pl/strings.xml84
-rw-r--r--app/src/main/res/values-pt-rBR/strings.xml64
-rw-r--r--app/src/main/res/values-ru/strings.xml94
-rw-r--r--app/src/main/res/values-sk-rSK/strings.xml58
-rw-r--r--app/src/main/res/values-sl/strings.xml81
-rw-r--r--app/src/main/res/values-sr/strings.xml69
-rw-r--r--app/src/main/res/values-sv/strings.xml88
-rw-r--r--app/src/main/res/values-tr/strings.xml72
-rw-r--r--app/src/main/res/values-uk/strings.xml47
-rw-r--r--app/src/main/res/values-v21/styles.xml9
-rw-r--r--app/src/main/res/values-zh-rCN/strings.xml30
-rw-r--r--app/src/main/res/values/colors.xml7
-rw-r--r--app/src/main/res/values/dimens.xml15
-rw-r--r--app/src/main/res/values/setup.xml1
-rw-r--r--app/src/main/res/values/strings.xml69
-rw-r--r--app/src/main/res/values/styles.xml19
-rw-r--r--app/src/main/res/xml/settings.xml8
-rw-r--r--app/src/main/res/xml/stack_widget_provider.xml12
-rw-r--r--app/src/main/widget_stack_preview-playstore.pngbin0 -> 23997 bytes
-rw-r--r--app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java41
-rw-r--r--app/src/test/java/it/niedermann/nextcloud/deck/DueDateUnitTest.java75
-rw-r--r--app/src/test/java/it/niedermann/nextcloud/deck/ProjectUtilTest.java124
-rw-r--r--build.gradle2
-rw-r--r--cross-tab-drag-and-drop/build.gradle14
-rw-r--r--cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java3
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1006000.txt6
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1007000.txt11
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1008000.txt16
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1008001.txt17
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1008002.txt23
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1008003.txt4
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1009000.txt5
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1009001.txt5
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1009002.txt2
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1009003.txt3
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1009004.txt1
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1009005.txt2
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1009006.txt2
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1009007.txt4
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1010000.txt7
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1011000.txt4
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1011001.txt1
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1012000.txt1
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1012001.txt8
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1012002.txt2
-rw-r--r--fastlane/metadata/android/en-US/changelogs/1013000.txt4
-rw-r--r--fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.pngbin366312 -> 359171 bytes
-rw-r--r--fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.pngbin347211 -> 324455 bytes
-rw-r--r--fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.pngbin401571 -> 322129 bytes
-rw-r--r--fastlane/metadata/android/en-US/images/phoneScreenshots/7.pngbin0 -> 418664 bytes
-rw-r--r--glide-sso-integration/.gitignore1
-rw-r--r--glide-sso-integration/build.gradle34
-rw-r--r--glide-sso-integration/proguard-rules.pro21
-rw-r--r--glide-sso-integration/src/main/AndroidManifest.xml4
-rw-r--r--glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnLibraryGlideModule.java33
-rw-r--r--glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnStreamFetcher.java145
-rw-r--r--glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrl.java88
-rw-r--r--glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrlLoader.java66
-rw-r--r--gradle/wrapper/gradle-wrapper.properties4
-rw-r--r--settings.gradle1
-rw-r--r--tab-layout-helper/build.gradle8
370 files changed, 13157 insertions, 4222 deletions
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index c93519c29..2f2b6593b 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -8,23 +8,22 @@ labels: bug
Guidelines for submitting bug reports:
* Bug reports which do not fill the complete issue template will be closed.
-* Please have a look at the [FAQ in our wiki](https://github.com/stefan-niedermann/nextcloud-deck/wiki/FAQ)
+* Please have a look at our [FAQ](https://github.com/stefan-niedermann/nextcloud-deck/blob/master/FAQ.md)
* Please search the existing issues first, it's likely that your issue was already reported or even fixed.
* This repository is *only* for issues within the Nextcloud Deck Android app
-->
-
-<!-- Please keep this note for other contributors -->
-### How to use GitHub
-
-* Please use the 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to show that you are interested into the same feature.
-* Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
-* Subscribe to receive notifications on status change and new comments.
+Please use **[GitHub reactions](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/)** 👍 to show that you are affected by the same issue. Please don't comment if you have no relevant information to add!
**Describe the bug**
<!-- A clear and concise description of what the bug is. -->
-**To Reproduce**
+
+**To reproduce**
+Issue is reproducible with an account from [try.nextcloud.com](https://try.nextcloud.com):
+- [ ] Yes
+- [ ] No
+
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
@@ -52,7 +51,14 @@ Steps to reproduce the behavior:
- Android-Version:
- App-Store:
- [ ] Google Play Store
+ - [ ] Google Play Store (Beta channel)
- [ ] F-Droid
+ - [ ] Huawei AppGallery
+
+
+**Are you using LDAP?**
+- [ ] Yes
+- [ ] No
**Stacktrace**
diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 000000000..f862478d0
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: false
+contact_links:
+ - name: 💼 Volume licenses
+ url: https://www.niedermann.it/kontakt.html
+ about: If you are a company, a club, a university or another organization, please contact us directly for volume licenses. \ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 95c06ceff..39f47140e 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -8,15 +8,9 @@ labels: enhancement
Guidelines for submitting issues:
* Please search the existing issues first, it's likely that your issue was already reported or even fixed.
-* This repository is *only* for issues within the Nextcloud Notes Android app
+* This repository is *only* for issues within the Nextcloud Deck Android app
-->
-
-<!-- Please keep this note for other contributors -->
-### How to use GitHub
-
-* Please use the 👍 [reaction](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/) to show that you are interested into the same feature.
-* Please don't comment if you have no relevant information to add. It's just extra noise for everyone subscribed to this issue.
-* Subscribe to receive notifications on status change and new comments.
+Please use **[GitHub reactions](https://blog.github.com/2016-03-10-add-reactions-to-pull-requests-issues-and-comments/)** 👍 to show that you are affected by the same issue. Please don't comment if you have no relevant information to add!
**Is your feature request related to a problem? Please describe.**
<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->
diff --git a/FAQ.md b/FAQ.md
new file mode 100644
index 000000000..ae568e11b
--- /dev/null
+++ b/FAQ.md
@@ -0,0 +1,51 @@
+# Frequently asked questions
+
+## I have experienced an error
+
+Sorry. There are so many different environments, that it is impossible for us to test each and every constellation.
+
+First of all make sure you have updated to and tried with the latest available versions of both, this app and the [Deck server app](https://apps.nextcloud.com/apps/deck).
+
+In case you receive a `NextcloudApiNotRespondingException`, try to disable the battery optimization for both apps.
+In all other cases please try to clear the storage of **both** apps, Nextcloud Android **and** Nextcloud Deck Android.
+
+You can achieve this by navigating to
+
+```
+Android settings
+ ↳ Apps
+ ↳ Nextcloud / Deck
+ ↳ Storage
+ ↳ Clear storage
+```
+
+Often there is an issue with the state of the server app. Try to create a new test account at your Nextcloud instance and connect to it.
+If there are no errors, share the boards of your actual account one by one with your test account to find the defect one.
+
+If the issue persists, [open a bug report in our issue tracker](https://github.com/stefan-niedermann/nextcloud-deck/issues/new?assignees=&labels=bug&template=bug_report.md&title=).
+
+## Why has my bug report been closed?
+
+As stated in the bug templates, we reserve to close issues which do not fill the **complete** issue template. The information we ask for is urgently needed, even if it might not seem to be important or relevant to you.
+
+We have very limited resources and capacity and we really want to help you fixing different bugs, but we can impossibly know your environment, your different software versions, the store you used.
+Therefore it is extremely important for you to describe the **exact steps to reproduce**. This includes information about your environment.
+
+Example for a bad description:
+
+> 1. The app crashes when i save a card
+
+Example for a good description:
+
+> 1. Open any existing card\
+> 2. Add text to the description\
+> 3. Click on the ✕ in the top left\
+> 4. Answer "Save" when asking to discard or save the changes\
+> 5. See app crash
+
+We also preserve to close issues where the original reporter does not answer within a certain time frame. We usually answer issues within a hour and expect you to respond to our questions within a week.
+
+This is necessary for two reasons:
+
+1. We have a rapid development cycle - bugs which have been reported weeks ago might no longer relevant
+2. We are loosing the context of a report or a question over the time. We have many things to care about and digging into an issue deep and then relying on an response which is not coming is a waste of our limited free time \ No newline at end of file
diff --git a/PRIVACY.md b/PRIVACY.md
new file mode 100644
index 000000000..f43b9d5af
--- /dev/null
+++ b/PRIVACY.md
@@ -0,0 +1,50 @@
+# Nextcloud Deck Android Privacy Policy
+
+The "Nextcloud Deck Android" Android-App (in the following referred to as "App") does not collect or send any data from you or your device to a server of the developers or the [Nextcloud GmbH](https://nextcloud.com/). The App sends all data exclusively to the server configured by you with the intention to synchronize the contents of the App with those of the server. This data can contain IP-addresses, timestamps and further information as meta data.
+It is important to mention that all contents of the App may also be transmitted to the configured server. This contents can also contain personal information depending on the use. The servers you configured are technically outside the access area of this App developers, so that we neither know nor can prevent what happens to your data there. Please consult the privacy policy of the respective server operator.
+
+The license of this project allows you to verify that no data is collected by the creators by reading the source code or asking someone else to do it.
+
+## Permissions
+
+This is a list of permissions required and asked by the App in order to properly work on your device:
+
+- `android.permission.CAMERA`
+
+ Used for the preview of the camera in the attachments picker and to capture photos to attach them directly to a card
+
+- `android.permission.READ_CONTACTS`
+
+ Used to display and choose contacts directly within the attachment picker without the need of opening an external app
+
+- `android.permission.ACCESS_NETWORK_STATE`
+
+ Used to provide offline support and make the "Sync only on Wi-Fi" option possible.
+
+- `android.permission.INTERNET`
+
+ Used by [Nextcloud Single Sign On library](https://github.com/nextcloud/Android-SingleSignOn/) to communicate with your Nextcloud instance and synchronize contents.
+
+- `android.permission.GET_ACCOUNTS`
+
+ Used by [Nextcloud Single Sign On library](https://github.com/nextcloud/Android-SingleSignOn/) to read available accounts to import.
+
+- `android.permission.VIBRATE`
+
+ Used by [Material DateTime Picker](https://github.com/wdullaer/MaterialDateTimePicker).
+
+- `android.permission.WAKE_LOCK`
+
+ Used by [AndroidX WorkManager](https://developer.android.com/jetpack/androidx/releases/work) for background synchronization.
+
+- `android.permission.RECEIVE_BOOT_COMPLETED`
+
+ Used by [AndroidX WorkManager](https://developer.android.com/jetpack/androidx/releases/work) for background synchronization.
+
+- `android.permission.FOREGROUND_SERVICE`
+
+ Used by [AndroidX WorkManager](https://developer.android.com/jetpack/androidx/releases/work) for background synchronization.
+
+## Nextcloud privacy policy
+
+You can get more information on Nextcloud general privacy policy which is accessible at [nextcloud.com/privacy](https://nextcloud.com/privacy/). \ No newline at end of file
diff --git a/README.md b/README.md
index e36b13988..2765894c0 100644
--- a/README.md
+++ b/README.md
@@ -42,12 +42,9 @@ An Android client for [Nextcloud Deck App](https://github.com/nextcloud/deck/).
* Translated in many languages 🌎
## 🏁 Planned features
-* [Widgets](https://github.com/stefan-niedermann/nextcloud-deck/issues/217)
-* [Add more sources to upload attachments](https://github.com/stefan-niedermann/nextcloud-deck/issues/289)
-* [Move cards to other boards](https://github.com/stefan-niedermann/nextcloud-deck/issues/453)
-* [Clone boards](https://github.com/stefan-niedermann/nextcloud-deck/issues/455)
+* [Improved Markdown support](https://github.com/stefan-niedermann/nextcloud-deck/issues/452)
+* [Filter Widget](https://github.com/stefan-niedermann/nextcloud-deck/issues/597)
* Further stabilization of the UI and bugfixes
-* [Improve synchronization speed](https://github.com/nextcloud/deck/issues/1650)
## 👪 Join the team
* Test the app with different devices
diff --git a/app/build.gradle b/app/build.gradle
index e7ebe318b..9ee447b25 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -4,10 +4,10 @@ android {
compileSdkVersion 29
defaultConfig {
applicationId "it.niedermann.nextcloud.deck"
- minSdkVersion 17
+ minSdkVersion 19
targetSdkVersion 29
- versionCode 1005006
- versionName "1.5.6"
+ versionCode 1013000
+ versionName "1.13.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
vectorDrawables.useSupportLibrary = true
javaCompileOptions {
@@ -34,6 +34,7 @@ android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
+ coreLibraryDesugaringEnabled true
}
flavorDimensions "version"
productFlavors {
@@ -53,7 +54,10 @@ android {
dependencies {
// Single-Sign-On
- implementation 'com.github.nextcloud:Android-SingleSignOn:0.5.1'
+ implementation 'com.github.nextcloud:Android-SingleSignOn:0.5.4'
+ implementation 'com.github.stefan-niedermann.nextcloud-commons:sso-glide:1.2.0'
+ implementation 'com.github.stefan-niedermann.nextcloud-commons:exception:1.2.0'
+ implementation 'com.github.stefan-niedermann:android-commons:0.1.1'
// --------------------
// --- UI-Libraries ---
@@ -62,7 +66,10 @@ dependencies {
// Glide
implementation 'com.github.bumptech.glide:glide:4.11.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
- implementation project(path: ':glide-sso-integration')
+
+ implementation "androidx.camera:camera-camera2:1.0.0-beta12"
+ implementation "androidx.camera:camera-lifecycle:1.0.0-beta12"
+ implementation "androidx.camera:camera-view:1.0.0-alpha19"
// Markdown
implementation 'com.yydcdut:markdown-processor:0.1.3'
@@ -70,20 +77,17 @@ dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
// Android X
- implementation 'com.google.android.material:material:1.1.0'
- implementation 'androidx.appcompat:appcompat:1.1.0'
- implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
+ implementation 'com.google.android.material:material:1.2.1'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
+ implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation 'androidx.legacy:legacy-support-v4:1.0.0'
- implementation 'androidx.work:work-runtime:2.3.4'
+ implementation 'androidx.work:work-runtime:2.4.0'
implementation 'androidx.preference:preference:1.1.1'
// Custom Date / Time Picker for branding support
implementation 'com.wdullaer:materialdatetimepicker:4.2.3'
- // Android backport for java.time
- implementation 'com.jakewharton.threetenabp:threetenabp:1.2.4'
-
// Flexbox
implementation 'com.google.android:flexbox:2.0.1'
@@ -101,8 +105,6 @@ dependencies {
// --- Backend-Libraries ---
// -------------------------
- implementation 'org.jetbrains.kotlin:kotlin-stdlib:1.3.72'
-
// Room components
implementation "androidx.room:room-runtime:$rootProject.roomVersion"
annotationProcessor "androidx.room:room-compiler:$rootProject.roomVersion"
@@ -119,15 +121,15 @@ dependencies {
// -----------------------
implementation 'androidx.multidex:multidex:2.0.1'
-
+ coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.1'
// -------------
// --- Tests ---
// -------------
// Tests
- testImplementation 'junit:junit:4.13'
- androidTestImplementation 'androidx.test:runner:1.2.0'
- androidTestImplementation 'androidx.test.ext:junit:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ testImplementation 'junit:junit:4.13.1'
+ androidTestImplementation 'androidx.test:runner:1.3.0'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
diff --git a/glide-sso-integration/consumer-rules.pro b/app/play/release/output.json
index e69de29bb..e69de29bb 100644
--- a/glide-sso-integration/consumer-rules.pro
+++ b/app/play/release/output.json
diff --git a/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/AttachmentUtilTest.java b/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/AttachmentUtilTest.java
new file mode 100644
index 000000000..a72f74ec1
--- /dev/null
+++ b/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/AttachmentUtilTest.java
@@ -0,0 +1,50 @@
+package it.niedermann.nextcloud.deck.util;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import it.niedermann.nextcloud.deck.model.Attachment;
+import it.niedermann.nextcloud.deck.model.ocs.Version;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+public class AttachmentUtilTest {
+
+ @Test
+ public void testGetThumbnailUrl() {
+ final Version versionThatDoesSupportFileAttachments = new Version("1.3.0", 1, 3, 0);
+ final Version versionThatDoesNotSupportFileAttachments = new Version("1.2.0", 1, 2, 0);
+ final String accountUrl = "https://example.com";
+
+ // TODO depends on https://github.com/nextcloud/deck/pull/2638
+// final Attachment attachment1 = new Attachment();
+// attachment1.setFileId("1337");
+// final String thumbnailUrl1 = AttachmentUtil.getThumbnailUrl(versionThatDoesSupportFileAttachments, accountUrl, -1L, attachment1, 500);
+// assertEquals("https://example.com/index.php/core/preview?fileId=1337&x=500&y=500", thumbnailUrl1);
+//
+// final Attachment attachment2 = new Attachment();
+// attachment2.setFileId("0815");
+// final String thumbnailUrl2 = AttachmentUtil.getThumbnailUrl(versionThatDoesSupportFileAttachments, accountUrl, 0L, attachment2, 4711);
+// assertEquals("https://example.com/index.php/core/preview?fileId=0815&x=4711&y=4711", thumbnailUrl2);
+
+ // Given there is an invalid fileId...
+ final Attachment attachment3 = new Attachment();
+ attachment3.setId(999L);
+ attachment3.setFileId("");
+ final String thumbnailUrl3 = AttachmentUtil.getThumbnailUrl(versionThatDoesSupportFileAttachments, accountUrl, 15L, attachment3, 205);
+ // ... a fallback to the attachment itself should be returned
+ assertEquals("https://example.com/index.php/apps/deck/cards/15/attachment/999", thumbnailUrl3);
+
+ // Given the server version does not support file attachments yet...
+ final Attachment attachment4 = new Attachment();
+ attachment4.setId(111L);
+ attachment4.setFileId("222");
+ final String thumbnailUrl4 = AttachmentUtil.getThumbnailUrl(versionThatDoesNotSupportFileAttachments, accountUrl, 333L, attachment4, 444);
+ // ... a fallback to the attachment itself should be returned
+ assertEquals("https://example.com/index.php/apps/deck/cards/333/attachment/111", thumbnailUrl4);
+ }
+
+}
diff --git a/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/ColorUtilTest.java b/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/ColorUtilTest.java
deleted file mode 100644
index afc09e762..000000000
--- a/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/ColorUtilTest.java
+++ /dev/null
@@ -1,124 +0,0 @@
-package it.niedermann.nextcloud.deck.util;
-
-import android.graphics.Color;
-
-import androidx.annotation.ColorInt;
-import androidx.core.util.Pair;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-
-import org.junit.Rule;
-import org.junit.Test;
-import org.junit.rules.ExpectedException;
-import org.junit.runner.RunWith;
-
-import java.util.ArrayList;
-import java.util.List;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-
-@RunWith(AndroidJUnit4.class)
-public class ColorUtilTest {
-
- @ColorInt
- private static final int[] DARK_COLORS = new int[]{
- Color.BLACK,
- Color.parseColor("#0082C9"), // "Nextcloud-Blue"
- Color.parseColor("#007676")
- };
-
- @ColorInt
- private static final int[] LIGHT_COLORS = new int[]{
- Color.WHITE,
- Color.YELLOW
- };
-
- @Test
- public void testGetForegroundColorForBackgroundColor() {
- for (@ColorInt int color : DARK_COLORS) {
- assertEquals(
- "Expect foreground color for " + String.format("#%06X", (0xFFFFFF & color)) + " to be " + String.format("#%06X", (0xFFFFFF & Color.WHITE)),
- Color.WHITE, ColorUtil.getForegroundColorForBackgroundColor(color)
- );
- }
- for (@ColorInt int color : LIGHT_COLORS) {
- assertEquals(
- "Expect foreground color for " + String.format("#%06X", (0xFFFFFF & color)) + " to be " + String.format("#%06X", (0xFFFFFF & Color.BLACK)),
- Color.BLACK, ColorUtil.getForegroundColorForBackgroundColor(color)
- );
- }
- assertEquals(
- "Expect foreground color for " + String.format("#%06X", (0xFFFFFF & Color.TRANSPARENT)) + " to be " + String.format("#%06X", (0xFFFFFF & Color.BLACK)),
- Color.BLACK, ColorUtil.getForegroundColorForBackgroundColor(Color.TRANSPARENT)
- );
- }
-
- @Test
- public void testIsColorDark() {
- for (@ColorInt int color : DARK_COLORS) {
- assertTrue(
- "Expect " + String.format("#%06X", (0xFFFFFF & color)) + " to be a dark color",
- ColorUtil.isColorDark(color)
- );
- }
- for (@ColorInt int color : LIGHT_COLORS) {
- assertFalse(
- "Expect " + String.format("#%06X", (0xFFFFFF & color)) + " to be a light color",
- ColorUtil.isColorDark(color)
- );
- }
- }
-
- @Test
- public void testContrastRatioIsSufficient() {
- final List<Pair<Integer, Integer>> sufficientContrastColorPairs = new ArrayList<>();
- sufficientContrastColorPairs.add(new Pair<>(Color.BLACK, Color.WHITE));
- sufficientContrastColorPairs.add(new Pair<>(Color.WHITE, Color.parseColor("#0082C9")));
-
- for (Pair<Integer, Integer> colorPair : sufficientContrastColorPairs) {
- assert colorPair.first != null;
- assert colorPair.second != null;
- assertTrue(
- "Expect contrast between " + String.format("#%06X", (0xFFFFFF & colorPair.first)) + " and " + String.format("#%06X", (0xFFFFFF & colorPair.second)) + " to be sufficient",
- ColorUtil.contrastRatioIsSufficient(colorPair.first, colorPair.second)
- );
- }
-
- final List<Pair<Integer, Integer>> insufficientContrastColorPairs = new ArrayList<>();
- insufficientContrastColorPairs.add(new Pair<>(Color.WHITE, Color.WHITE));
- insufficientContrastColorPairs.add(new Pair<>(Color.BLACK, Color.BLACK));
-
- for (Pair<Integer, Integer> colorPair : insufficientContrastColorPairs) {
- assert colorPair.first != null;
- assert colorPair.second != null;
- assertFalse(
- "Expect contrast between " + String.format("#%06X", (0xFFFFFF & colorPair.first)) + " and " + String.format("#%06X", (0xFFFFFF & colorPair.second)) + " to be insufficient",
- ColorUtil.contrastRatioIsSufficient(colorPair.first, colorPair.second)
- );
- }
- }
-
- @Rule
- public final ExpectedException exception = ExpectedException.none();
-
- @Test
- public void testGetCleanHexaColorString() {
- final List<Pair<String, String>> validColors = new ArrayList<>();
- validColors.add(new Pair<>("#0082C9", "#0082C9"));
- validColors.add(new Pair<>("0082C9", "#0082C9"));
- validColors.add(new Pair<>("#CCC", "#CCCCCC"));
- validColors.add(new Pair<>("ccc", "#cccccc"));
- validColors.add(new Pair<>("af0", "#aaff00"));
- validColors.add(new Pair<>("#af0", "#aaff00"));
- for (Pair<String, String> color : validColors) {
- assertEquals("Expect " + color.first + " to be cleaned up to " + color.second, color.second, ColorUtil.formatColorToParsableHexString(color.first));
- }
-
- final String[] invalidColors = new String[]{null, "", "cc", "c", "#a", "#55L", "55L"};
- for (String color : invalidColors) {
- exception.expect(IllegalArgumentException.class);
- ColorUtil.formatColorToParsableHexString(color);
- }
- }
-}
diff --git a/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/DateUtilTest.java b/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/DateUtilTest.java
new file mode 100644
index 000000000..845275122
--- /dev/null
+++ b/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/DateUtilTest.java
@@ -0,0 +1,35 @@
+package it.niedermann.nextcloud.deck.util;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.time.Duration;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.util.stream.Stream;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+public class DateUtilTest {
+
+ @Test
+ public void testGetRelativeDateTimeString() {
+ Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ Stream.of(10, 20, 30, 40, 50)
+ .map(secondsAgo -> ZonedDateTime.now().minus(Duration.ofSeconds(secondsAgo)).toInstant().toEpochMilli())
+ .forEach(secondsAgoInMillis -> assertEquals("Below one minute diff, it should just print \"seconds\"", "seconds ago", DateUtil.getRelativeDateTimeString(appContext, secondsAgoInMillis)));
+
+ Stream.of(10, 20, 30, 40, 50)
+ .forEach(minutesAgo -> assertEquals("Minutes ago should print the minutes", minutesAgo + " minutes ago", DateUtil.getRelativeDateTimeString(appContext, ZonedDateTime.now().minus(Duration.ofMinutes(minutesAgo)).toInstant().toEpochMilli())));
+
+ assertEquals("Very long time ago should print the complete date", "4/13/2018", DateUtil.getRelativeDateTimeString(appContext, ZonedDateTime.of(2018,
+ 4, 13, 10, 45, 0, 0, ZoneId.systemDefault()).toInstant().toEpochMilli()));
+ }
+
+}
diff --git a/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/DeckColorUtilTest.java b/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/DeckColorUtilTest.java
new file mode 100644
index 000000000..10ac5cdc9
--- /dev/null
+++ b/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/DeckColorUtilTest.java
@@ -0,0 +1,49 @@
+package it.niedermann.nextcloud.deck.util;
+
+import android.graphics.Color;
+
+import androidx.core.util.Pair;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class DeckColorUtilTest {
+
+ @Test
+ public void testContrastRatioIsSufficient() {
+ final List<Pair<Integer, Integer>> sufficientContrastColorPairs = new ArrayList<>();
+ sufficientContrastColorPairs.add(new Pair<>(Color.BLACK, Color.WHITE));
+ sufficientContrastColorPairs.add(new Pair<>(Color.WHITE, Color.parseColor("#0082C9")));
+
+ for (Pair<Integer, Integer> colorPair : sufficientContrastColorPairs) {
+ assert colorPair.first != null;
+ assert colorPair.second != null;
+ assertTrue(
+ "Expect contrast between " + String.format("#%06X", (0xFFFFFF & colorPair.first)) + " and " + String.format("#%06X", (0xFFFFFF & colorPair.second)) + " to be sufficient",
+ DeckColorUtil.contrastRatioIsSufficient(colorPair.first, colorPair.second)
+ );
+ }
+
+ final List<Pair<Integer, Integer>> insufficientContrastColorPairs = new ArrayList<>();
+ insufficientContrastColorPairs.add(new Pair<>(Color.WHITE, Color.WHITE));
+ insufficientContrastColorPairs.add(new Pair<>(Color.BLACK, Color.BLACK));
+
+ for (Pair<Integer, Integer> colorPair : insufficientContrastColorPairs) {
+ assert colorPair.first != null;
+ assert colorPair.second != null;
+ assertFalse(
+ "Expect contrast between " + String.format("#%06X", (0xFFFFFF & colorPair.first)) + " and " + String.format("#%06X", (0xFFFFFF & colorPair.second)) + " to be insufficient",
+ DeckColorUtil.contrastRatioIsSufficient(colorPair.first, colorPair.second)
+ );
+ }
+ }
+
+}
diff --git a/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/SpannableUtilTest.java b/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/SpannableUtilTest.java
new file mode 100644
index 000000000..1797bc405
--- /dev/null
+++ b/app/src/androidTest/java/it/niedermann/nextcloud/deck/util/SpannableUtilTest.java
@@ -0,0 +1,52 @@
+package it.niedermann.nextcloud.deck.util;
+
+import android.content.Context;
+import android.graphics.Typeface;
+import android.text.SpannableString;
+import android.text.style.ForegroundColorSpan;
+import android.text.style.StyleSpan;
+import android.text.style.URLSpan;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+public class SpannableUtilTest {
+
+ @Test
+ public void testStrong() {
+ final SpannableString spannableString = SpannableUtil.strong("test");
+ assertEquals(1, spannableString.getSpans(0, spannableString.length(), Object.class).length);
+ assertEquals(1, spannableString.getSpans(0, spannableString.length(), StyleSpan.class).length);
+ assertEquals(4, spannableString.length());
+ assertEquals(Typeface.BOLD, spannableString.getSpans(0, spannableString.length(), StyleSpan.class)[0].getStyle());
+ }
+
+ @Test
+ public void testDisabled() {
+ final Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
+
+ final SpannableString spannableString = SpannableUtil.disabled("test", appContext);
+ assertEquals(2, spannableString.getSpans(0, spannableString.length(), Object.class).length);
+ assertEquals(1, spannableString.getSpans(0, spannableString.length(), ForegroundColorSpan.class).length);
+ assertEquals(1, spannableString.getSpans(0, spannableString.length(), StyleSpan.class).length);
+ assertEquals(4, spannableString.length());
+ assertEquals(Typeface.ITALIC, spannableString.getSpans(0, spannableString.length(), StyleSpan.class)[0].getStyle());
+ }
+
+
+ @Test
+ public void testUrl() {
+ final SpannableString spannableString = SpannableUtil.url("test", "https://example.com");
+ assertEquals(1, spannableString.getSpans(0, spannableString.length(), Object.class).length);
+ assertEquals(1, spannableString.getSpans(0, spannableString.length(), URLSpan.class).length);
+ assertEquals(4, spannableString.length());
+ assertEquals("https://example.com", spannableString.getSpans(0, spannableString.length(), URLSpan.class)[0].getURL());
+ }
+
+}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index ce4530d5d..f936fac67 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -3,10 +3,15 @@
xmlns:tools="http://schemas.android.com/tools"
package="it.niedermann.nextcloud.deck">
- <uses-permission android:name="com.nextcloud.android.sso" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.READ_CONTACTS" />
+ <uses-permission android:name="android.permission.CAMERA" />
+
+ <uses-feature android:name="android.hardware.camera.any" />
+
+ <uses-sdk tools:overrideLibrary="androidx.camera.core, androidx.camera.camera2, androidx.camera.lifecycle, androidx.camera.view" />
<application
android:name="it.niedermann.nextcloud.deck.DeckApplication"
@@ -51,6 +56,11 @@
android:windowSoftInputMode="stateHidden" />
<activity
+ android:name=".ui.takephoto.TakePhotoActivity"
+ android:theme="@style/TakePhotoTheme"
+ android:windowSoftInputMode="stateHidden" />
+
+ <activity
android:name=".ui.sharetarget.ShareTargetActivity"
android:label="@string/share_add_to_card"
android:theme="@style/SplashTheme">
@@ -75,7 +85,7 @@
<activity
android:name=".ui.archivedboards.ArchivedBoardsActvitiy"
android:label="@string/archived_boards"
- android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" />
+ android:parentActivityName="it.niedermann.nextcloud.deck.ui.MainActivity" />
<activity
android:name=".ui.card.EditActivity"
@@ -100,7 +110,7 @@
<activity
android:name=".ui.preparecreate.PrepareCreateActivity"
android:description="@string/add_a_new_card_using_the_button"
- android:label="@string/add_card" >
+ android:label="@string/add_card">
<intent-filter>
<action android:name="android.intent.action.SEND" />
@@ -116,7 +126,7 @@
<activity
android:name=".ui.PushNotificationActivity"
- android:label="@string/app_name" >
+ android:label="@string/app_name">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
@@ -126,6 +136,25 @@
android:name=".ui.exception.ExceptionActivity"
android:process=":error_activity" />
+ <activity android:name=".ui.widget.stack.StackWidgetConfigurationActivity">
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_CONFIGURE" />
+ </intent-filter>
+ </activity>
+
+ <receiver
+ android:name="it.niedermann.nextcloud.deck.ui.widget.stack.StackWidget"
+ android:label="@string/widget_stack_title">
+
+ <intent-filter>
+ <action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
+ </intent-filter>
+
+ <meta-data
+ android:name="android.appwidget.provider"
+ android:resource="@xml/stack_widget_provider" />
+ </receiver>
+
<service
android:name=".ui.tiles.EditCardTileService"
android:description="@string/add_a_new_card_using_the_button"
@@ -137,6 +166,9 @@
</intent-filter>
</service>
+ <service
+ android:name=".ui.widget.stack.StackWidgetService"
+ android:permission="android.permission.BIND_REMOTEVIEWS" />
<activity
android:name=".ui.widget.singlecard.SelectCardForWidgetActivity"
@@ -148,7 +180,9 @@
</intent-filter>
</activity>
- <receiver android:name=".ui.widget.singlecard.SingleCardWidget">
+ <receiver
+ android:name=".ui.widget.singlecard.SingleCardWidget"
+ android:label="@string/single_card">
<intent-filter>
<action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
</intent-filter>
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 ff492c226..cf30a0971 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/DeckApplication.java
@@ -1,20 +1,17 @@
package it.niedermann.nextcloud.deck;
-import android.app.Application;
import android.content.Context;
import android.content.SharedPreferences;
import androidx.annotation.NonNull;
+import androidx.multidex.MultiDexApplication;
import androidx.preference.PreferenceManager;
-import com.jakewharton.threetenabp.AndroidThreeTen;
-
import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_NO;
import static androidx.appcompat.app.AppCompatDelegate.MODE_NIGHT_YES;
import static androidx.appcompat.app.AppCompatDelegate.setDefaultNightMode;
-import static androidx.multidex.MultiDex.install;
-public class DeckApplication extends Application {
+public class DeckApplication extends MultiDexApplication {
public static final long NO_ACCOUNT_ID = -1L;
public static final long NO_BOARD_ID = -1L;
@@ -24,17 +21,6 @@ public class DeckApplication extends Application {
public void onCreate() {
setAppTheme(isDarkTheme(getApplicationContext()));
super.onCreate();
- AndroidThreeTen.init(this);
- }
-
- // --------
- // Multidex
- // --------
-
- @Override
- protected void attachBaseContext(Context base) {
- super.attachBaseContext(base);
- install(this);
}
// -----------------
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 9883608c2..1f0148b91 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
@@ -1,6 +1,8 @@
package it.niedermann.nextcloud.deck.api;
+import com.nextcloud.android.sso.api.ParsedResponse;
+
import java.util.List;
import io.reactivex.Observable;
@@ -33,6 +35,7 @@ import retrofit2.http.Query;
public interface DeckAPI {
String MODIFIED_SINCE_HEADER = "If-Modified-Since";
+ String IF_NONE_MATCH = "If-None-Match";
// ### BOARDS
@POST("boards")
@@ -51,7 +54,7 @@ public interface DeckAPI {
Observable<FullBoard> restoreBoard(@Path("id") long id);
@GET("boards")
- Observable<List<FullBoard>> getBoards(@Query ("details") boolean verbose, @Header(MODIFIED_SINCE_HEADER) String lastSync );
+ Observable<ParsedResponse<List<FullBoard>>> getBoards(@Query ("details") boolean verbose, @Header(MODIFIED_SINCE_HEADER) String lastSync, @Header(IF_NONE_MATCH) String eTag);
// ### Stacks
@@ -83,11 +86,11 @@ public interface DeckAPI {
@FormUrlEncoded
@PUT("boards/{boardId}/stacks/{stackId}/cards/{cardId}/assignLabel")
- Observable assignLabelToCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Field("labelId") long labelId);
+ 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")
- Observable unassignLabelFromCard(@Path("boardId") long boardId, @Path("stackId") long stackId, @Path("cardId") long cardId, @Field("labelId") long labelId);
+ 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")
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java
index 29b3706fa..4e61abfb3 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonConfig.java
@@ -5,7 +5,7 @@ import com.google.gson.GsonBuilder;
import com.google.gson.reflect.TypeToken;
import java.lang.reflect.Type;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
import it.niedermann.nextcloud.deck.model.Attachment;
@@ -16,6 +16,8 @@ import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.ocs.Activity;
import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectList;
+import it.niedermann.nextcloud.deck.model.ocs.user.GroupMemberUIDs;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
@@ -42,14 +44,17 @@ public class GsonConfig {
Type ocsUserList = new TypeToken<OcsUserList>() {}.getType();
Type ocsUser = new TypeToken<OcsUser>() {}.getType();
Type activity = new TypeToken<Activity>() {}.getType();
+ Type activityList = new TypeToken<List<Activity>>() {}.getType();
Type attachment = new TypeToken<Attachment>() {}.getType();
Type attachmentList = new TypeToken<List<Attachment>>() {}.getType();
Type comment = new TypeToken<OcsComment>() {}.getType();
+ Type projectList = new TypeToken<OcsProjectList>() {}.getType();
+ Type groupMembers = new TypeToken<GroupMemberUIDs>() {}.getType();
INSTANCE = new GsonBuilder()
.setDateFormat(DATE_PATTERN)
.setLenient()
- .registerTypeAdapter(Date.class, new GsonUTCDateAdapter())
+ .registerTypeAdapter(Instant.class, new GsonUTCInstantAdapter())
.registerTypeAdapter(boardList, new NextcloudArrayDeserializer<>("boards", FullBoard.class))
.registerTypeAdapter(board, new NextcloudDeserializer<>("board", FullBoard.class))
.registerTypeAdapter(cardList, new NextcloudArrayDeserializer<>("cards", FullCard.class))
@@ -62,9 +67,12 @@ public class GsonConfig {
.registerTypeAdapter(ocsUserList, new NextcloudDeserializer<>("ocsUserList", OcsUserList.class))
.registerTypeAdapter(ocsUser, new NextcloudDeserializer<>("ocsUser", OcsUser.class))
.registerTypeAdapter(activity, new NextcloudDeserializer<>("activity", Activity.class))
+ .registerTypeAdapter(activityList, new NextcloudDeserializer<>("activityList", Activity.class))
.registerTypeAdapter(attachmentList, new NextcloudArrayDeserializer<>("attachments", Attachment.class))
.registerTypeAdapter(attachment, new NextcloudDeserializer<>("attachment", Attachment.class))
.registerTypeAdapter(comment, new NextcloudDeserializer<>("comment", OcsComment.class))
+ .registerTypeAdapter(projectList, new NextcloudDeserializer<>("projectList", OcsProjectList.class))
+ .registerTypeAdapter(groupMembers, new NextcloudDeserializer<>("groupMembers", GroupMemberUIDs.class))
.create();
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonUTCDateAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonUTCDateAdapter.java
deleted file mode 100644
index 4d30b3e81..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonUTCDateAdapter.java
+++ /dev/null
@@ -1,48 +0,0 @@
-package it.niedermann.nextcloud.deck.api;
-
-import com.google.gson.JsonDeserializationContext;
-import com.google.gson.JsonDeserializer;
-import com.google.gson.JsonElement;
-import com.google.gson.JsonParseException;
-import com.google.gson.JsonPrimitive;
-import com.google.gson.JsonSerializationContext;
-import com.google.gson.JsonSerializer;
-
-import java.lang.reflect.Type;
-import java.text.DateFormat;
-import java.text.ParseException;
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-import java.util.regex.Pattern;
-
-public class GsonUTCDateAdapter implements JsonSerializer<Date>, JsonDeserializer<Date> {
-
- private final DateFormat dateFormat;
- private final Pattern UNIX_TIMESTAMP = Pattern.compile("^[0-9]+$");
-
- public GsonUTCDateAdapter() {
- //This is the format I need
- dateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.US);
- //This is the key line which converts the date to UTC which cannot be accessed with the default serializer
- dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
- }
-
- @Override public synchronized JsonElement serialize(Date date, Type type, JsonSerializationContext jsonSerializationContext) {
- return new JsonPrimitive(dateFormat.format(date));
- }
-
- @Override public synchronized Date deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) {
- String dateValue = jsonElement.getAsString();
- try {
- return dateFormat.parse(dateValue);
- } catch (ParseException e) {
- // fallback to unix timestamp?
- if (UNIX_TIMESTAMP.matcher(dateValue).matches()){
- return new Date(Long.valueOf(dateValue));
- }
- throw new JsonParseException(e);
- }
- }
-} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonUTCInstantAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonUTCInstantAdapter.java
new file mode 100644
index 000000000..5a6b5b0c0
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/GsonUTCInstantAdapter.java
@@ -0,0 +1,43 @@
+package it.niedermann.nextcloud.deck.api;
+
+import com.google.gson.JsonDeserializationContext;
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParseException;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializationContext;
+import com.google.gson.JsonSerializer;
+
+import java.lang.reflect.Type;
+import java.text.ParseException;
+import java.time.Instant;
+import java.time.format.DateTimeFormatter;
+import java.util.regex.Pattern;
+
+public class GsonUTCInstantAdapter implements JsonSerializer<Instant>, JsonDeserializer<Instant> {
+
+ private static final Pattern UNIX_TIMESTAMP = Pattern.compile("^[0-9]+$");
+
+ @Override
+ public synchronized JsonElement serialize(Instant date, Type type, JsonSerializationContext jsonSerializationContext) {
+ return new JsonPrimitive(DateTimeFormatter.ISO_INSTANT.format(date));
+ }
+
+ @Override
+ public synchronized Instant deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) {
+ String dateValue = jsonElement.getAsString();
+ try {
+ final Instant parsedDate = Instant.parse(dateValue);
+ if (parsedDate == null) {
+ throw new ParseException("Parsed date is null", 0);
+ }
+ return parsedDate;
+ } catch (ParseException e) {
+ // fallback to unix timestamp?
+ if (UNIX_TIMESTAMP.matcher(dateValue).matches()) {
+ return Instant.ofEpochMilli(Long.parseLong(dateValue));
+ }
+ throw new JsonParseException(e);
+ }
+ }
+} \ No newline at end of file
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 0e523c318..be3eaeb22 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
@@ -24,12 +24,18 @@ public abstract class IResponseCallback<T> {
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
}
+
};
}
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 6dedefa01..e759f27d4 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
@@ -1,20 +1,20 @@
package it.niedermann.nextcloud.deck.api;
+import android.graphics.Color;
+
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
-import org.threeten.bp.DateTimeUtils;
-import org.threeten.bp.ZonedDateTime;
-import org.threeten.bp.format.DateTimeFormatter;
-
+import java.time.Instant;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.exceptions.DeckException;
-import it.niedermann.nextcloud.deck.exceptions.TraceableException;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Attachment;
import it.niedermann.nextcloud.deck.model.Board;
@@ -32,13 +32,19 @@ import it.niedermann.nextcloud.deck.model.ocs.Version;
import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
import it.niedermann.nextcloud.deck.model.ocs.comment.Mention;
import it.niedermann.nextcloud.deck.model.ocs.comment.OcsComment;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectList;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.model.ocs.user.GroupMemberUIDs;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
import static it.niedermann.nextcloud.deck.exceptions.DeckException.Hint.CAPABILITIES_VERSION_NOT_PARSABLE;
+import static it.niedermann.nextcloud.deck.exceptions.TraceableException.makeTraceableIfFails;
public class JsonToEntityParser {
+ @SuppressWarnings("unchecked")
protected static <T> T parseJsonObject(JsonObject obj, Class<T> mType) {
if (mType == FullBoard.class) {
return (T) parseBoard(obj);
@@ -55,44 +61,54 @@ public class JsonToEntityParser {
} else if (mType == OcsUserList.class) {
return (T) parseOcsUserList(obj);
} else if (mType == OcsUser.class) {
- return (T) parseOcsUser(obj);
+ return (T) parseSingleOcsUser(obj);
} else if (mType == Attachment.class) {
return (T) parseAttachment(obj);
} else if (mType == OcsComment.class) {
return (T) parseOcsComment(obj);
+ } else if (mType == GroupMemberUIDs.class) {
+ return (T) parseGroupMemberUIDs(obj);
+ } else if (mType == OcsProjectList.class) {
+ return (T) parseOcsProjectList(obj);
}
throw new IllegalArgumentException("unregistered type: " + mType.getCanonicalName());
}
- private static OcsUser parseOcsUser(JsonObject obj) {
+ private static GroupMemberUIDs parseGroupMemberUIDs(JsonObject obj) {
DeckLog.verbose(obj.toString());
- OcsUser ocsUser = new OcsUser();
- TraceableException.makeTraceableIfFails(() -> {
+ GroupMemberUIDs uids = new GroupMemberUIDs();
+ makeTraceableIfFails(() -> {
JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
- if (!data.isJsonNull()) {
- JsonObject jsonObject = data.getAsJsonObject();
- if (jsonObject.has("id")) {
- ocsUser.setId(getNullAsEmptyString(jsonObject.get("id")));
- }
- if (jsonObject.has("displayname")) {
- ocsUser.setDisplayName(getNullAsEmptyString(jsonObject.get("displayname")));
+ if (!data.isJsonNull() && data.getAsJsonObject().has("users")) {
+ JsonElement users = data.getAsJsonObject().get("users");
+ if (!users.isJsonNull() && users.isJsonArray()) {
+ for (JsonElement userElement : users.getAsJsonArray()) {
+ uids.add(userElement.getAsString());
+ }
}
}
}, obj);
- return ocsUser;
+ return uids;
}
private static OcsUserList parseOcsUserList(JsonObject obj) {
DeckLog.verbose(obj.toString());
OcsUserList ocsUserList = new OcsUserList();
- TraceableException.makeTraceableIfFails(() -> {
+ makeTraceableIfFails(() -> {
JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
if (!data.isJsonNull() && data.getAsJsonObject().has("users")) {
JsonElement users = data.getAsJsonObject().get("users");
if (!users.isJsonNull() && users.isJsonArray()) {
for (JsonElement userElement : users.getAsJsonArray()) {
- ocsUserList.add(userElement.getAsString());
+ JsonObject singleUserElement = userElement.getAsJsonObject();
+ OcsUser user = new OcsUser();
+ user.setDisplayName(singleUserElement.get("label").getAsString());
+ user.setId(
+ singleUserElement.get("value").getAsJsonObject()
+ .get("shareWith").getAsString()
+ );
+ ocsUserList.addUser(user);
}
}
}
@@ -101,10 +117,105 @@ public class JsonToEntityParser {
return ocsUserList;
}
+ private static OcsUser parseSingleOcsUser(JsonObject obj) {
+ DeckLog.verbose(obj.toString());
+ OcsUser ocsUser = new OcsUser();
+ makeTraceableIfFails(() -> {
+ JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
+ if (!data.isJsonNull()) {
+ JsonObject user = data.getAsJsonObject();
+ if (user.has("id")) {
+ ocsUser.setId(user.get("id").getAsString());
+ }
+ if (user.has("displayname")) {
+ ocsUser.setDisplayName(user.get("displayname").getAsString());
+ }
+ }
+
+ }, obj);
+ return ocsUser;
+ }
+
+ private static OcsProjectList parseOcsProjectList(JsonObject obj) {
+ DeckLog.verbose(obj.toString());
+ OcsProjectList projectList = new OcsProjectList();
+ makeTraceableIfFails(() -> {
+ JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
+ if (!data.isJsonNull() && data.isJsonArray()) {
+ JsonArray projectJsonArray = data.getAsJsonArray();
+ for (JsonElement jsonArrayElement : projectJsonArray) {
+ if (jsonArrayElement.isJsonObject()) {
+ JsonObject jsonObject = jsonArrayElement.getAsJsonObject();
+ OcsProject project = new OcsProject();
+ project.setId(jsonObject.get("id").getAsLong());
+ project.setName(getNullAsEmptyString(jsonObject.get("name")));
+ project.setResources(new ArrayList<>());
+ JsonElement jsonResources = jsonObject.get("resources");
+ if (jsonResources != null && jsonResources.isJsonArray()) {
+ JsonArray resourcesArray = jsonResources.getAsJsonArray();
+ for (JsonElement resourceElement : resourcesArray) {
+ if (resourceElement.isJsonObject()) {
+ OcsProjectResource resource = parseOcsProjectResource(resourceElement.getAsJsonObject());
+ resource.setProjectId(project.getId());
+ project.getResources().add(resource);
+ }
+ }
+ }
+ projectList.add(project);
+ }
+ }
+ }
+
+ }, obj);
+ return projectList;
+ }
+
+ private static OcsProjectResource parseOcsProjectResource(JsonObject obj) {
+ DeckLog.verbose(obj.toString());
+ OcsProjectResource resource = new OcsProjectResource();
+ makeTraceableIfFails(() -> {
+ if (obj.has("id")) {
+ String idString = obj.get("id").getAsString();
+ if (idString != null && idString.trim().length() > 0) {
+ if (idString.matches("[0-9]+")) {
+ resource.setId(Long.parseLong(idString.trim()));
+ } else {
+ resource.setIdString(idString);
+ }
+ }
+ }
+ if (obj.has("type")) {
+ resource.setType(getNullAsEmptyString(obj.get("type")));
+ }
+ if (obj.has("name")) {
+ resource.setName(getNullAsEmptyString(obj.get("name")));
+ }
+ if (obj.has("link")) {
+ resource.setLink(getNullAsEmptyString(obj.get("link")));
+ }
+ if (obj.has("iconUrl")) {
+ resource.setIconUrl(getNullAsEmptyString(obj.get("iconUrl")));
+ }
+ if (obj.has("path")) {
+ resource.setPath(obj.get("path").getAsString());
+ }
+ if (obj.has("mimetype")) {
+ resource.setMimetype(obj.get("mimetype").getAsString());
+ }
+ if (obj.has("preview-available")) {
+ resource.setPreviewAvailable(obj.get("preview-available").getAsBoolean());
+ } else {
+ resource.setPreviewAvailable(false);
+ }
+
+ }, obj);
+ return resource;
+ }
+
private static OcsComment parseOcsComment(JsonObject obj) {
DeckLog.verbose(obj.toString());
OcsComment comment = new OcsComment();
- TraceableException.makeTraceableIfFails(() -> {
+ makeTraceableIfFails(() -> {
JsonElement data = obj.get("ocs").getAsJsonObject().get("data");
if (data.isJsonArray()) {
for (JsonElement deckComment : data.getAsJsonArray()) {
@@ -121,7 +232,7 @@ public class JsonToEntityParser {
DeckLog.verbose(data.toString());
DeckComment deckComment = new DeckComment();
- TraceableException.makeTraceableIfFails(() -> {
+ makeTraceableIfFails(() -> {
JsonObject commentJson = data.getAsJsonObject();
deckComment.setId(commentJson.get("id").getAsLong());
@@ -132,17 +243,18 @@ public class JsonToEntityParser {
deckComment.setActorType(commentJson.get("actorType").getAsString());
deckComment.setCreationDateTime(getTimestampFromString(commentJson.get("creationDateTime")));
- if (commentJson.has("replyTo")){
- JsonObject replyTo = commentJson.get("replyTo").getAsJsonObject();
- deckComment.setParentId(replyTo.get("id").getAsLong());
- }
+ if (commentJson.has("replyTo")) {
+ JsonObject replyTo = commentJson.get("replyTo").getAsJsonObject();
+ deckComment.setParentId(replyTo.get("id").getAsLong());
+ }
- JsonElement mentions = commentJson.get("mentions");
- if (mentions != null && mentions.isJsonArray()) {
- for (JsonElement mention : mentions.getAsJsonArray()) {
- deckComment.addMention(parseMention(mention));
+ JsonElement mentions = commentJson.get("mentions");
+ if (mentions != null && mentions.isJsonArray()) {
+ for (JsonElement mention : mentions.getAsJsonArray()) {
+ deckComment.addMention(parseMention(mention));
+ }
}
- }}, data);
+ }, data);
return deckComment;
}
@@ -152,7 +264,7 @@ public class JsonToEntityParser {
DeckLog.verbose(mentionJson.toString());
- TraceableException.makeTraceableIfFails(() -> {
+ makeTraceableIfFails(() -> {
JsonObject mentionObject = mentionJson.getAsJsonObject();
mention.setMentionId(mentionObject.get("mentionId").getAsString());
mention.setMentionType(mentionObject.get("mentionType").getAsString());
@@ -169,9 +281,10 @@ public class JsonToEntityParser {
DeckLog.verbose(e.toString());
Board board = new Board();
- TraceableException.makeTraceableIfFails(() -> {
+ makeTraceableIfFails(() -> {
board.setTitle(getNullAsEmptyString(e.get("title")));
board.setColor(getNullAsEmptyString(e.get("color")));
+ board.setEtag(getNullAsNull(e.get("ETag")));
board.setArchived(e.get("archived").getAsBoolean());
board.setLastModified(getTimestampFromLong(e.get("lastModified")));
@@ -232,12 +345,19 @@ public class JsonToEntityParser {
}
}
- JsonElement owner = e.get("owner");
- if (owner != null) {
- if (owner.isJsonPrimitive()) {//TODO: remove if, let only else!
- DeckLog.verbose("owner is Primitive, skipping");
- } else
- fullBoard.setOwner(parseUser(owner.getAsJsonObject()));
+ if (e.has("owner")) {
+ fullBoard.setOwner(parseUser(e.get("owner")));
+ }
+ if (e.has("users")) {
+ JsonElement users = e.get("users");
+ if (users != null && !users.isJsonNull() && users.isJsonArray()) {
+ JsonArray usersArray = users.getAsJsonArray();
+ List<User> usersList = new ArrayList<>();
+ for (JsonElement userJson : usersArray) {
+ usersList.add(parseUser(userJson));
+ }
+ fullBoard.setUsers(usersList);
+ }
}
}, e);
return fullBoard;
@@ -248,8 +368,8 @@ public class JsonToEntityParser {
AccessControl acl = new AccessControl();
if (aclJson.has("participant") && !aclJson.get("participant").isJsonNull()) {
- TraceableException.makeTraceableIfFails(() -> {
- User participant = parseUser(aclJson.get("participant").getAsJsonObject());
+ makeTraceableIfFails(() -> {
+ User participant = parseUser(aclJson.get("participant"));
acl.setUser(participant);
acl.setType(aclJson.get("type").getAsLong());
acl.setBoardId(aclJson.get("boardId").getAsLong());
@@ -271,12 +391,13 @@ public class JsonToEntityParser {
FullCard fullCard = new FullCard();
Card card = new Card();
fullCard.setCard(card);
- TraceableException.makeTraceableIfFails(() -> {
+ makeTraceableIfFails(() -> {
card.setId(e.get("id").getAsLong());
card.setTitle(getNullAsEmptyString(e.get("title")));
card.setDescription(getNullAsEmptyString(e.get("description")));
card.setStackId(e.get("stackId").getAsLong());
card.setType(getNullAsEmptyString(e.get("type")));
+ card.setEtag(getNullAsNull(e.get("ETag")));
card.setLastModified(getTimestampFromLong(e.get("lastModified")));
card.setCreatedAt(getTimestampFromLong(e.get("createdAt")));
card.setDeletedAt(getTimestampFromLong(e.get("deletedAt")));
@@ -296,7 +417,7 @@ public class JsonToEntityParser {
for (JsonElement assignedUser : assignedUsers) {
JsonObject userJson = assignedUser.getAsJsonObject();
if (userJson.has("participant") && !userJson.get("participant").isJsonNull()) {
- users.add(parseUser(userJson.get("participant").getAsJsonObject()));
+ users.add(parseUser(userJson.get("participant")));
}
}
fullCard.setAssignedUsers(users);
@@ -322,10 +443,7 @@ public class JsonToEntityParser {
card.setCommentsUnread(e.get("commentsUnread").getAsInt());
JsonElement owner = e.get("owner");
if (owner != null) {
- if (owner.isJsonPrimitive()) {//TODO: remove if, let only else!
- DeckLog.verbose("owner is Primitive, skipping");
- } else
- fullCard.setOwner(parseUser(owner.getAsJsonObject()));
+ fullCard.setOwner(parseUser(owner));
}
card.setArchived(e.get("archived").getAsBoolean());
}, e);
@@ -336,16 +454,17 @@ public class JsonToEntityParser {
protected static Attachment parseAttachment(JsonObject e) {
DeckLog.verbose(e.toString());
Attachment a = new Attachment();
- TraceableException.makeTraceableIfFails(() -> {
+ makeTraceableIfFails(() -> {
a.setId(e.get("id").getAsLong());
a.setCardId(e.get("cardId").getAsLong());
a.setType(e.get("type").getAsString());
+ a.setEtag(getNullAsNull(e.get("ETag")));
a.setData(e.get("data").getAsString());
a.setLastModified(getTimestampFromLong(e.get("lastModified")));
a.setCreatedAt(getTimestampFromLong(e.get("createdAt")));
a.setCreatedBy(e.get("createdBy").getAsString());
a.setDeletedAt(getTimestampFromLong(e.get("deletedAt")));
- if (e.has("extendedData") && !e.get("extendedData").isJsonNull()) {
+ if (e.has("extendedData") && !e.get("extendedData").isJsonNull() && e.get("extendedData").isJsonObject()) {
JsonObject extendedData = e.getAsJsonObject("extendedData").getAsJsonObject();
a.setFilesize(extendedData.get("filesize").getAsLong());
a.setMimetype(extendedData.get("mimetype").getAsString());
@@ -364,14 +483,27 @@ public class JsonToEntityParser {
return a;
}
- protected static User parseUser(JsonObject e) {
- DeckLog.verbose(e.toString());
+ protected static User parseUser(JsonElement userElement) {
+ DeckLog.verbose(userElement.toString());
+ if (userElement.isJsonNull()) {
+ return null;
+ }
User user = new User();
- TraceableException.makeTraceableIfFails(() -> {
- user.setDisplayname(getNullAsEmptyString(e.get("displayname")));
- user.setPrimaryKey(getNullAsEmptyString(e.get("primaryKey")));
- user.setUid(getNullAsEmptyString(e.get("uid")));
- }, e);
+ makeTraceableIfFails(() -> {
+
+ if (userElement.isJsonPrimitive()) {
+ String uid = userElement.getAsString();
+ user.setDisplayname(uid);
+ user.setPrimaryKey(uid);
+ user.setUid(uid);
+ } else {
+ JsonObject userJson = userElement.getAsJsonObject();
+ user.setDisplayname(getNullAsEmptyString(userJson.get("displayname")));
+ user.setPrimaryKey(getNullAsEmptyString(userJson.get("primaryKey")));
+ user.setUid(getNullAsEmptyString(userJson.get("uid")));
+ }
+
+ }, userElement);
return user;
}
@@ -419,8 +551,8 @@ public class JsonToEntityParser {
}
if (caps.has("theming")) {
JsonObject theming = caps.getAsJsonObject("theming");
- capabilities.setColor(theming.get("color").getAsString());
- capabilities.setTextColor(theming.get("color-text").getAsString());
+ capabilities.setColor(getColorAsInt(theming, "color"));
+ capabilities.setTextColor(getColorAsInt(theming, "color-text"));
}
}
capabilities.setDeckVersion(Version.of(version));
@@ -429,31 +561,17 @@ public class JsonToEntityParser {
return capabilities;
}
- protected static List<Activity> parseActivity(JsonObject e) {
- DeckLog.verbose(e.toString());
- List<Activity> activityList = new ArrayList<>();
-
- TraceableException.makeTraceableIfFails(() -> {
- if (e.has("ocs")) {
- JsonObject ocs = e.getAsJsonObject("ocs");
- if (ocs.has("data")) {
- JsonArray data = ocs.getAsJsonArray("data");
- for (JsonElement activityJson : data) {
- Activity activity = new Activity();
- JsonObject activityObject = activityJson.getAsJsonObject();
-
- activity.setId(activityObject.get("activity_id").getAsLong());
- activity.setType(ActivityType.findByPath(getNullAsEmptyString(activityObject.get("icon"))).getId());
- activity.setSubject(getNullAsEmptyString(activityObject.get("subject")));
- activity.setCardId(activityObject.get("object_id").getAsLong());
- activity.setLastModified(getTimestampFromString(activityObject.get("datetime")));
-
- activityList.add(activity);
- }
- }
+ private static int getColorAsInt(JsonObject element, String field) {
+ String rawString = getNullAsEmptyString(element.get(field));
+ try {
+ if (!rawString.trim().isEmpty()) {
+ String colorAsString = ColorUtil.INSTANCE.formatColorToParsableHexString(rawString);
+ return Color.parseColor(colorAsString);
}
- }, e);
- return activityList;
+ } catch (Exception e) {
+ // Do mostly nothing, return default value
+ }
+ return Color.GRAY;
}
protected static FullStack parseStack(JsonObject e) {
@@ -461,10 +579,11 @@ public class JsonToEntityParser {
FullStack fullStack = new FullStack();
Stack stack = new Stack();
fullStack.setStack(stack);
- TraceableException.makeTraceableIfFails(() -> {
+ makeTraceableIfFails(() -> {
stack.setTitle(getNullAsEmptyString(e.get("title")));
stack.setBoardId(e.get("boardId").getAsLong());
stack.setId(e.get("id").getAsLong());
+ stack.setEtag(getNullAsNull(e.get("ETag")));
stack.setLastModified(getTimestampFromLong(e.get("lastModified")));
stack.setDeletedAt(getTimestampFromLong(e.get("deletedAt")));
if (e.has("order") && !e.get("order").isJsonNull()) {
@@ -485,37 +604,70 @@ public class JsonToEntityParser {
return fullStack;
}
+ protected static List<Activity> parseActivity(JsonObject e) {
+ DeckLog.verbose(e.toString());
+ List<Activity> activityList = new ArrayList<>();
+
+ makeTraceableIfFails(() -> {
+ if (e.has("ocs")) {
+ JsonObject ocs = e.getAsJsonObject("ocs");
+ if (ocs.has("data")) {
+ JsonArray data = ocs.getAsJsonArray("data");
+ for (JsonElement activityJson : data) {
+ Activity activity = new Activity();
+ JsonObject activityObject = activityJson.getAsJsonObject();
+
+ activity.setId(activityObject.get("activity_id").getAsLong());
+ activity.setType(ActivityType.findByPath(getNullAsEmptyString(activityObject.get("icon"))).getId());
+ activity.setSubject(getNullAsEmptyString(activityObject.get("subject")));
+ activity.setCardId(activityObject.get("object_id").getAsLong());
+ activity.setEtag(getNullAsNull(e.get("ETag")));
+ activity.setLastModified(getTimestampFromString(activityObject.get("datetime")));
+
+ activityList.add(activity);
+ }
+ }
+ }
+ }, e);
+ return activityList;
+ }
+
protected static Label parseLabel(JsonObject e) {
DeckLog.verbose(e.toString());
Label label = new Label();
- TraceableException.makeTraceableIfFails(() -> {
+ makeTraceableIfFails(() -> {
label.setId(e.get("id").getAsLong());
//todo: last modified!
// label.setLastModified(get);
label.setTitle(getNullAsEmptyString(e.get("title")));
- label.setColor(getNullAsEmptyString(e.get("color")));
+ label.setEtag(getNullAsNull(e.get("ETag")));
+ label.setColor(getColorAsInt(e, "color"));
}, e);
return label;
}
private static String getNullAsEmptyString(JsonElement jsonElement) {
- return jsonElement.isJsonNull() ? "" : jsonElement.getAsString();
+ return jsonElement == null || jsonElement.isJsonNull() ? "" : jsonElement.getAsString();
+ }
+
+ private static String getNullAsNull(JsonElement jsonElement) {
+ return jsonElement == null || jsonElement.isJsonNull() ? null : jsonElement.getAsString();
}
- private static Date getTimestampFromString(JsonElement jsonElement) {
+ private static Instant getTimestampFromString(JsonElement jsonElement) {
if (jsonElement.isJsonNull()) {
return null;
} else {
String dateAsString = jsonElement.getAsString();
- return DateTimeUtils.toDate(ZonedDateTime.from(DateTimeFormatter.ISO_DATE_TIME.parse(dateAsString)).toInstant());
+ return ZonedDateTime.from(DateTimeFormatter.ISO_DATE_TIME.parse(dateAsString)).toInstant();
}
}
- private static Date getTimestampFromLong(JsonElement jsonElement) {
+ private static Instant getTimestampFromLong(JsonElement jsonElement) {
if (jsonElement.isJsonNull()) {
return null;
} else {
- return new Date(jsonElement.getAsLong() * 1000);
+ return Instant.ofEpochMilli(jsonElement.getAsLong() * 1000);
}
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/LastSyncUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/LastSyncUtil.java
index 1d44bfe13..1492d78ac 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/api/LastSyncUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/LastSyncUtil.java
@@ -3,7 +3,7 @@ package it.niedermann.nextcloud.deck.api;
import android.content.Context;
import android.content.SharedPreferences;
-import java.util.Date;
+import java.time.Instant;
import it.niedermann.nextcloud.deck.R;
@@ -22,19 +22,19 @@ public class LastSyncUtil {
}
- public static long getLastSync(long accountId){
+ public static long getLastSync(long accountId) {
return INSTANCE.lastSyncPref.getLong(getSyncKeyForAccount(accountId), 0L);
}
- public static Date getLastSyncDate(long accountId){
- return new Date(getLastSync(accountId));
+ public static Instant getLastSyncDate(long accountId) {
+ return Instant.ofEpochMilli(getLastSync(accountId));
}
- public static void setLastSyncDate(long accountId, Date value){
- INSTANCE.lastSyncPref.edit().putLong(getSyncKeyForAccount(accountId), value.getTime()).apply();
+ public static void setLastSyncDate(long accountId, Instant value) {
+ INSTANCE.lastSyncPref.edit().putLong(getSyncKeyForAccount(accountId), value.toEpochMilli()).apply();
}
- public static void resetLastSyncDate(long accountId){
+ public static void resetLastSyncDate(long accountId) {
INSTANCE.lastSyncPref.edit().remove(getSyncKeyForAccount(accountId)).apply();
}
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 d69ee8513..7b93ace49 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
@@ -1,6 +1,8 @@
package it.niedermann.nextcloud.deck.api;
+import com.nextcloud.android.sso.api.ParsedResponse;
+
import java.util.List;
import io.reactivex.Observable;
@@ -8,11 +10,14 @@ import it.niedermann.nextcloud.deck.model.ocs.Activity;
import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
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.projects.OcsProjectList;
+import it.niedermann.nextcloud.deck.model.ocs.user.GroupMemberUIDs;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
import retrofit2.http.Body;
import retrofit2.http.DELETE;
import retrofit2.http.GET;
+import retrofit2.http.Header;
import retrofit2.http.Headers;
import retrofit2.http.POST;
import retrofit2.http.PUT;
@@ -22,13 +27,19 @@ import retrofit2.http.Query;
public interface NextcloudServerAPI {
@GET("cloud/capabilities?format=json")
- Observable<Capabilities> getCapabilities();
+ Observable<ParsedResponse<Capabilities>> getCapabilities(@Header("If-None-Match") String eTag);
+
+ @GET("collaboration/resources/deck-card/{cardId}?format=json")
+ Observable<OcsProjectList> getProjectsForCard(@Path("cardId") long cardId);
+
+ @GET("apps/files_sharing/api/v1/sharees?format=json&perPage=20&itemType=0%2C1%2C7")
+ Observable<OcsUserList> searchUser(@Query("search") String searchTerm);
- @GET("cloud/users?format=json")
- Observable<OcsUserList> getAllUsers();
+ @GET("cloud/groups/{search}?format=json")
+ Observable<GroupMemberUIDs> searchGroupMembers(@Path("search") String groupUid);
- @GET("cloud/users/{uid}?format=json")
- Observable<OcsUser> getUserDetails(@Path("uid") String uid);
+ @GET("cloud/users/{search}?format=json")
+ Observable<OcsUser> getSingleUserData(@Path("search") String userUid);
@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);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/api/json/JsonColorSerializer.java b/app/src/main/java/it/niedermann/nextcloud/deck/api/json/JsonColorSerializer.java
new file mode 100644
index 000000000..5169e7f5e
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/api/json/JsonColorSerializer.java
@@ -0,0 +1,26 @@
+package it.niedermann.nextcloud.deck.api.json;
+
+import com.google.gson.TypeAdapter;
+import com.google.gson.stream.JsonReader;
+import com.google.gson.stream.JsonWriter;
+
+import java.io.IOException;
+
+import it.niedermann.android.util.ColorUtil;
+
+public class JsonColorSerializer extends TypeAdapter<Integer> {
+ @Override
+ public void write(JsonWriter out, Integer value) throws IOException {
+ if (value == null) {
+ out.nullValue();
+ } else {
+ out.value(ColorUtil.INSTANCE.intColorToHexString(value));
+ }
+ }
+
+ @Override
+ public Integer read(JsonReader in) throws IOException {
+ // currently not needed
+ return null;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/DeckException.java b/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/DeckException.java
index 8c2904d2b..0512f8a60 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/DeckException.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/DeckException.java
@@ -5,6 +5,8 @@ public class DeckException extends IllegalArgumentException {
public enum Hint {
CAPABILITIES_NOT_PARSABLE,
CAPABILITIES_VERSION_NOT_PARSABLE,
+ UNKNOWN_ACCOUNT_USER_ID,
+ DEPENDENCY_NOT_SYNCED_YET
}
private Hint hint;
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/TraceableException.java b/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/TraceableException.java
index a53ce8c1f..8d436477d 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/TraceableException.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/exceptions/TraceableException.java
@@ -11,22 +11,24 @@ public class TraceableException extends RuntimeException {
public static void makeTraceableIfFails(Runnable runnable, Object... args) {
try {
runnable.run();
+ } catch (TraceableException t) {
+ throw t;
} catch (Throwable t) {
- String message = "Sorry, a wild error appeared!\n" +
- "### If you want to tell us about the following issue, " +
- "please make sure to censor sensitive data beforehand! ###\n" +
- "Failed to run traceable code";
+ final StringBuilder message = new StringBuilder("Sorry, a wild error appeared!\n\n" +
+ "⚠️ If you want to tell us about the following issue, " +
+ "please make sure to censor sensitive data beforehand! ⚠️\n\n" +
+ "Failed to run traceable code");
if (args != null && args.length > 0) {
- message += " with arguments:\n";
+ message.append(" with arguments:\n");
for (Object arg : args) {
- message += (arg == null ? "null" : arg.toString())+"\n";
+ message.append(arg == null ? "null" : arg.toString()).append("\n");
}
} else {
- message += ":\n";
+ message.append(":\n");
}
- message += "Cause: " + t.getLocalizedMessage();
- TraceableException ex = new TraceableException(message, t);
+ message.append("Cause: ").append(t.getLocalizedMessage());
+ TraceableException ex = new TraceableException(message.toString(), t);
DeckLog.logError(ex);
throw ex;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/AccessControl.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/AccessControl.java
index 299dd7ca0..e2a3ccc3b 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/AccessControl.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/AccessControl.java
@@ -8,6 +8,7 @@ import androidx.room.Index;
import com.google.gson.annotations.SerializedName;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
+import it.niedermann.nextcloud.deck.model.ocs.user.GroupMemberUIDs;
@Entity(inheritSuperIndices = true,
indices = {
@@ -35,6 +36,8 @@ public class AccessControl extends AbstractRemoteEntity {
@Ignore
@SerializedName("participant")
private User user;
+ @Ignore
+ private GroupMemberUIDs groupMemberUIDs;
public AccessControl() {
super();
@@ -115,6 +118,14 @@ public class AccessControl extends AbstractRemoteEntity {
this.user = user;
}
+ public GroupMemberUIDs getGroupMemberUIDs() {
+ return groupMemberUIDs;
+ }
+
+ public void setGroupMemberUIDs(GroupMemberUIDs groupMemberUIDs) {
+ this.groupMemberUIDs = groupMemberUIDs;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/Account.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/Account.java
index ba8fb72b7..8abcbced7 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/Account.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/Account.java
@@ -2,6 +2,7 @@ package it.niedermann.nextcloud.deck.model;
import android.net.Uri;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Px;
import androidx.room.ColumnInfo;
@@ -18,7 +19,6 @@ import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
import it.niedermann.nextcloud.deck.model.ocs.Version;
import it.niedermann.nextcloud.deck.ui.accountswitcher.AccountSwitcherDialog;
-import it.niedermann.nextcloud.deck.util.ColorUtil;
@Entity(indices = {@Index(value = "name", unique = true)})
public class Account implements Serializable {
@@ -38,12 +38,12 @@ public class Account implements Serializable {
private String url;
@NonNull
- @ColumnInfo(defaultValue = "#0082c9")
- private String color = "#0082c9";
+ @ColumnInfo(defaultValue = "0")
+ private Integer color = 0;
@NonNull
- @ColumnInfo(defaultValue = "#ffffff")
- private String textColor = "#ffffff";
+ @ColumnInfo(defaultValue = "0")
+ private Integer textColor = 0;
@NonNull
@ColumnInfo(defaultValue = "0.6.4")
@@ -53,6 +53,9 @@ public class Account implements Serializable {
@ColumnInfo(defaultValue = "0")
private boolean maintenanceEnabled = false;
+ private String etag;
+ private String boardsEtag;
+
@Ignore
public Account(Long id, @NonNull String name, @NonNull String userName, @NonNull String url) {
this(name, userName, url);
@@ -60,7 +63,7 @@ public class Account implements Serializable {
}
@Ignore
- public Account(String name, String userName, String url) {
+ public Account(@NonNull String name, @NonNull String userName, @NonNull String url) {
this.name = name;
this.userName = userName;
this.url = url;
@@ -74,22 +77,29 @@ public class Account implements Serializable {
public Account() {
}
- public void applyCapabilities(Capabilities capabilities) {
+ public void applyCapabilities(Capabilities capabilities, String eTag) {
+ if (capabilities == null) {
+ maintenanceEnabled = true;
+ return;
+ }
maintenanceEnabled = capabilities.isMaintenanceEnabled();
if (!isMaintenanceEnabled()) {
try {
// Nextcloud might return color format #000 which cannot be parsed by Color.parseColor()
// https://github.com/stefan-niedermann/nextcloud-deck/issues/466
- color = ColorUtil.formatColorToParsableHexString(capabilities.getColor());
- textColor = ColorUtil.formatColorToParsableHexString(capabilities.getTextColor());
+ color = capabilities.getColor();
+ textColor = capabilities.getTextColor();
} catch (Exception e) {
DeckLog.logError(e);
- color = "#0082c9";
- color = "#ffffff";
+ color = 0;
+ color = 0;
}
if (capabilities.getDeckVersion() != null) {
serverDeckVersion = capabilities.getDeckVersion().getOriginalVersion();
}
+ if (eTag != null) {
+ this.etag = eTag;
+ }
}
}
@@ -132,21 +142,23 @@ public class Account implements Serializable {
return serialVersionUID;
}
+ @ColorInt
@NonNull
- public String getColor() {
+ public Integer getColor() {
return color;
}
- public void setColor(@NonNull String color) {
+ public void setColor(@NonNull Integer color) {
this.color = color;
}
@NonNull
- public String getTextColor() {
+ public Integer getTextColor() {
return textColor;
}
- public void setTextColor(@NonNull String textColor) {
+ @Deprecated
+ public void setTextColor(@NonNull Integer textColor) {
this.textColor = textColor;
}
@@ -171,6 +183,31 @@ public class Account implements Serializable {
this.maintenanceEnabled = maintenanceEnabled;
}
+ public String getEtag() {
+ return etag;
+ }
+
+ public void setEtag(String etag) {
+ this.etag = etag;
+ }
+
+ public String getBoardsEtag() {
+ return boardsEtag;
+ }
+
+ public void setBoardsEtag(String boardsEtag) {
+ this.boardsEtag = boardsEtag;
+ }
+
+ /**
+ * A cache buster parameter is added for duplicate account names on different hosts which shall be fetched from the same {@link SingleSignOnAccount} (e. g. {@link AccountSwitcherDialog})
+ *
+ * @return an {@link String} to fetch the avatar for this account.
+ */
+ public String getAvatarUrl(@Px int size) {
+ return getUrl() + "/index.php/avatar/" + Uri.encode(getUserName()) + "/" + size;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -185,16 +222,8 @@ public class Account implements Serializable {
if (!url.equals(account.url)) return false;
if (!color.equals(account.color)) return false;
if (!textColor.equals(account.textColor)) return false;
- return serverDeckVersion.equals(account.serverDeckVersion);
- }
-
- /**
- * A cache buster parameter is added for duplicate account names on different hosts which shall be fetched from the same {@link SingleSignOnAccount} (e. g. {@link AccountSwitcherDialog})
- *
- * @return an {@link String} to fetch the avatar for this account.
- */
- public String getAvatarUrl(@Px int size) {
- return getUrl() + "/index.php/avatar/" + Uri.encode(getUserName()) + "/" + size;
+ if (!serverDeckVersion.equals(account.serverDeckVersion)) return false;
+ return etag != null ? etag.equals(account.etag) : account.etag == null;
}
@Override
@@ -207,6 +236,7 @@ public class Account implements Serializable {
result = 31 * result + textColor.hashCode();
result = 31 * result + serverDeckVersion.hashCode();
result = 31 * result + (maintenanceEnabled ? 1 : 0);
+ result = 31 * result + (etag != null ? etag.hashCode() : 0);
return result;
}
@@ -221,6 +251,7 @@ public class Account implements Serializable {
", textColor='" + textColor + '\'' +
", serverDeckVersion='" + serverDeckVersion + '\'' +
", maintenanceEnabled=" + maintenanceEnabled +
+ ", eTag='" + etag + '\'' +
'}';
}
}
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 91192f597..7eabcbe70 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
@@ -2,32 +2,33 @@ package it.niedermann.nextcloud.deck.model;
import androidx.room.Entity;
import androidx.room.ForeignKey;
+import androidx.room.Ignore;
import androidx.room.Index;
import java.io.Serializable;
-import java.util.Date;
+import java.time.Instant;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@Entity(inheritSuperIndices = true,
indices = {@Index("cardId")},
foreignKeys = {
- @ForeignKey(
- entity = Card.class,
- parentColumns = "localId",
- childColumns = "cardId",
- onDelete = ForeignKey.CASCADE
- )
- }
+ @ForeignKey(
+ entity = Card.class,
+ parentColumns = "localId",
+ childColumns = "cardId",
+ onDelete = ForeignKey.CASCADE
+ )
+ }
)
public class Attachment extends AbstractRemoteEntity implements Comparable<Attachment>, Serializable {
private long cardId;
private String type = "deck_file";
private String data;
- private Date createdAt;
+ private Instant createdAt;
private String createdBy;
- private Date deletedAt;
+ private Instant deletedAt;
private long filesize;
private String mimetype;
private String dirname;
@@ -35,6 +36,9 @@ 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;
public long getCardId() {
return cardId;
@@ -60,11 +64,11 @@ public class Attachment extends AbstractRemoteEntity implements Comparable<Attac
this.data = data;
}
- public Date getCreatedAt() {
+ public Instant getCreatedAt() {
return createdAt;
}
- public void setCreatedAt(Date createdAt) {
+ public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
@@ -76,11 +80,11 @@ public class Attachment extends AbstractRemoteEntity implements Comparable<Attac
this.createdBy = createdBy;
}
- public Date getDeletedAt() {
+ public Instant getDeletedAt() {
return deletedAt;
}
- public void setDeletedAt(Date deletedAt) {
+ public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
@@ -140,6 +144,22 @@ 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() {
+ return this.fileId;
+ }
+
+ /**
+ * TODO depends on https://github.com/nextcloud/deck/pull/2638
+ */
+ @Ignore
+ public void setFileId(String fileId) {
+ this.fileId = fileId;
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) return true;
@@ -202,7 +222,7 @@ public class Attachment extends AbstractRemoteEntity implements Comparable<Attac
private static int longToComparsionResult(long diff) {
if (diff > 0) {
return 1;
- } else if(diff < 0) {
+ } else if (diff < 0) {
return -1;
}
return 0;
@@ -210,18 +230,18 @@ public class Attachment extends AbstractRemoteEntity implements Comparable<Attac
public long getModificationTimeForComparsion() {
if (lastModifiedLocal != null) {
- return lastModifiedLocal.getTime();
+ return lastModifiedLocal.toEpochMilli();
}
if (lastModified != null) {
- return lastModified.getTime();
+ return lastModified.toEpochMilli();
}
- return new Date().getTime();
+ return Instant.now().toEpochMilli();
}
public long getCreationTimeForComparsion() {
if (createdAt != null) {
- return createdAt.getTime();
+ return createdAt.toEpochMilli();
}
- return new Date().getTime();
+ return Instant.now().toEpochMilli();
}
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/Board.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/Board.java
index 29afe844b..27dcd1df8 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/Board.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/Board.java
@@ -1,17 +1,23 @@
package it.niedermann.nextcloud.deck.model;
+import android.graphics.Color;
+
+import androidx.annotation.ColorInt;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index;
+import com.google.gson.annotations.JsonAdapter;
+
import java.io.Serializable;
-import java.util.Date;
+import java.time.Instant;
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.api.json.JsonColorSerializer;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
-import it.niedermann.nextcloud.deck.util.ColorUtil;
@Entity(
inheritSuperIndices = true,
@@ -31,20 +37,18 @@ public class Board extends AbstractRemoteEntity implements Serializable {
}
@Ignore
- public Board(String title, String color) {
- this.title = title;
+ public Board(String title, @ColorInt int color) {
+ setTitle(title);
setColor(color);
}
private String title;
private long ownerId;
- /**
- * Deck App sends color strings without leading # character
- */
- private String color;
+ @JsonAdapter(JsonColorSerializer.class)
+ private Integer color;
private boolean archived;
private int shared;
- private Date deletedAt;
+ private Instant deletedAt;
private boolean permissionRead = false;
private boolean permissionEdit = false;
private boolean permissionManage = false;
@@ -52,22 +56,22 @@ public class Board extends AbstractRemoteEntity implements Serializable {
@Override
- public Date getLastModified() {
+ public Instant getLastModified() {
return lastModified;
}
@Override
- public void setLastModified(Date lastModified) {
+ public void setLastModified(Instant lastModified) {
this.lastModified = lastModified;
}
@Override
- public Date getLastModifiedLocal() {
+ public Instant getLastModifiedLocal() {
return lastModifiedLocal;
}
@Override
- public void setLastModifiedLocal(Date lastModifiedLocal) {
+ public void setLastModifiedLocal(Instant lastModifiedLocal) {
this.lastModifiedLocal = lastModifiedLocal;
}
@@ -79,21 +83,25 @@ public class Board extends AbstractRemoteEntity implements Serializable {
this.id = id;
}
- public String getColor() {
+ @ColorInt
+ public Integer getColor() {
return color;
}
+
public void setColor(String color) {
try {
- // Nextcloud might return color format #000 which cannot be parsed by Color.parseColor()
- // https://github.com/stefan-niedermann/nextcloud-deck/issues/466
- this.color = ColorUtil.formatColorToParsableHexString(color).substring(1);
+ setColor(Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(color)));
} catch (Exception e) {
DeckLog.logError(e);
- this.color = "757575";
+ setColor(Color.GRAY);
}
}
+ public void setColor(@ColorInt Integer color) {
+ this.color = color;
+ }
+
public boolean isArchived() {
return archived;
}
@@ -114,11 +122,11 @@ public class Board extends AbstractRemoteEntity implements Serializable {
this.shared = shared;
}
- public Date getDeletedAt() {
+ public Instant getDeletedAt() {
return deletedAt;
}
- public void setDeletedAt(Date deletedAt) {
+ public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/Card.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/Card.java
index 732ccfc50..6f2b70020 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/Card.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/Card.java
@@ -8,7 +8,7 @@ import androidx.room.Index;
import com.google.gson.annotations.SerializedName;
-import java.util.Date;
+import java.time.Instant;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -16,22 +16,23 @@ import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@Entity(inheritSuperIndices = true,
- indices = {
- @Index(value = "accountId", name = "card_accID"),
- @Index("stackId")
- },
- foreignKeys = {
- @ForeignKey(
- entity = Stack.class,
- parentColumns = "localId",
- childColumns = "stackId", onDelete = ForeignKey.CASCADE
- )
- }
+ indices = {
+ @Index(value = "accountId", name = "card_accID"),
+ @Index("stackId")
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = Stack.class,
+ parentColumns = "localId",
+ childColumns = "stackId", onDelete = ForeignKey.CASCADE
+ )
+ }
)
public class Card extends AbstractRemoteEntity {
private static Pattern PATTERN_MD_TASK = Pattern.compile("\\[([xX ])]");
- public class TaskStatus {
+
+ public static class TaskStatus {
public int taskCount;
public int doneCount;
@@ -49,20 +50,21 @@ public class Card extends AbstractRemoteEntity {
@NonNull
private Long stackId;
private String type;
- private Date createdAt;
- private Date deletedAt;
+ private Instant createdAt;
+ private Instant deletedAt;
private int attachmentCount;
private Long userId;
private int order;
private boolean archived;
@SerializedName("duedate")
- private Date dueDate;
+ private Instant dueDate;
private boolean notified;
private int overdue;
private int commentsUnread;
- public Card() {}
+ public Card() {
+ }
@Ignore
public Card(String title, String description, long stackId) {
@@ -89,15 +91,15 @@ public class Card extends AbstractRemoteEntity {
this.commentsUnread = card.getCommentsUnread();
}
- public TaskStatus getTaskStatus(){
- if (taskStatus == null){
+ public TaskStatus getTaskStatus() {
+ if (taskStatus == null) {
int count = 0, done = 0;
if (description != null) {
Matcher matcher = PATTERN_MD_TASK.matcher(description);
- while (matcher.find()){
+ while (matcher.find()) {
count++;
char c = matcher.group().charAt(1);
- if (c == 'x' || c == 'X'){
+ if (c == 'x' || c == 'X') {
done++;
}
}
@@ -164,19 +166,19 @@ public class Card extends AbstractRemoteEntity {
this.type = type;
}
- public Date getCreatedAt() {
+ public Instant getCreatedAt() {
return createdAt;
}
- public void setCreatedAt(Date createdAt) {
+ public void setCreatedAt(Instant createdAt) {
this.createdAt = createdAt;
}
- public Date getDeletedAt() {
+ public Instant getDeletedAt() {
return deletedAt;
}
- public void setDeletedAt(Date deletedAt) {
+ public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
@@ -200,12 +202,12 @@ public class Card extends AbstractRemoteEntity {
this.archived = archived;
}
- public Date getDueDate() {
+ public Instant getDueDate() {
return dueDate;
}
- public void setDueDate(Date dueDate) {
- this.dueDate = dueDate;
+ public void setDueDate(Instant dateTime) {
+ this.dueDate = dateTime;
}
public int getOverdue() {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/Label.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/Label.java
index 0d97ce5a0..da24edb5a 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/Label.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/Label.java
@@ -1,27 +1,41 @@
package it.niedermann.nextcloud.deck.model;
+import android.graphics.Color;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Index;
+import com.google.gson.annotations.JsonAdapter;
+
import java.io.Serializable;
+import it.niedermann.android.util.ColorUtil;
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.api.json.JsonColorSerializer;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@Entity(inheritSuperIndices = true,
indices = {@Index("boardId"), @Index(value = {"boardId", "title"}, unique = true, name = "idx_label_title_unique")},
foreignKeys = {
- @ForeignKey(
- entity = Board.class,
- parentColumns = "localId",
- childColumns = "boardId",
- onDelete = ForeignKey.CASCADE
- )
- }
+ @ForeignKey(
+ entity = Board.class,
+ parentColumns = "localId",
+ childColumns = "boardId",
+ onDelete = ForeignKey.CASCADE
+ )
+ }
)
public class Label extends AbstractRemoteEntity implements Serializable {
private String title;
- private String color;
+
+ @JsonAdapter(JsonColorSerializer.class)
+ @NonNull
+ @ColumnInfo(defaultValue = "0")
+ private Integer color;
private long boardId;
public Label() {
@@ -42,14 +56,25 @@ public class Label extends AbstractRemoteEntity implements Serializable {
this.title = title;
}
- public String getColor() {
+ @NonNull
+ @ColorInt
+ public Integer getColor() {
return color;
}
- public void setColor(String color) {
+ public void setColor(@NonNull @ColorInt Integer color) {
this.color = color;
}
+ public void setColor(String color) {
+ try {
+ setColor(Color.parseColor(ColorUtil.INSTANCE.formatColorToParsableHexString(color)));
+ } catch (Exception e) {
+ DeckLog.logError(e);
+ setColor(Color.GRAY);
+ }
+ }
+
public long getBoardId() {
return boardId;
}
@@ -62,18 +87,20 @@ public class Label extends AbstractRemoteEntity implements Serializable {
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
+ if (!super.equals(o)) return false;
Label label = (Label) o;
if (boardId != label.boardId) return false;
if (title != null ? !title.equals(label.title) : label.title != null) return false;
- return color != null ? color.equals(label.color) : label.color == null;
+ return color.equals(label.color);
}
@Override
public int hashCode() {
- int result = title != null ? title.hashCode() : 0;
- result = 31 * result + (color != null ? color.hashCode() : 0);
+ int result = super.hashCode();
+ result = 31 * result + (title != null ? title.hashCode() : 0);
+ result = 31 * result + color.hashCode();
result = 31 * result + (int) (boardId ^ (boardId >>> 32));
return result;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/Stack.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/Stack.java
index 330ec16d7..00f054605 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/Stack.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/Stack.java
@@ -1,12 +1,11 @@
package it.niedermann.nextcloud.deck.model;
-import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index;
-import java.util.Date;
+import java.time.Instant;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@@ -35,12 +34,10 @@ public class Stack extends AbstractRemoteEntity {
private String title;
- @NonNull
private long boardId;
- private Date deletedAt;
+ private Instant deletedAt;
- @NonNull
private int order;
//
// @ToMany
@@ -64,11 +61,11 @@ public class Stack extends AbstractRemoteEntity {
this.boardId = boardId;
}
- public Date getDeletedAt() {
+ public Instant getDeletedAt() {
return deletedAt;
}
- public void setDeletedAt(Date deletedAt) {
+ public void setDeletedAt(Instant deletedAt) {
this.deletedAt = deletedAt;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/User.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/User.java
index cfb48d543..fd318ce7a 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/User.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/User.java
@@ -4,10 +4,12 @@ import androidx.room.Entity;
import androidx.room.Ignore;
import androidx.room.Index;
+import java.io.Serializable;
+
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@Entity(inheritSuperIndices = true, indices = {@Index(value = "uid", name = "user_uid")})
-public class User extends AbstractRemoteEntity {
+public class User extends AbstractRemoteEntity implements Serializable {
private String primaryKey;
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/appwidgets/StackWidgetModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/appwidgets/StackWidgetModel.java
new file mode 100644
index 000000000..fb7a74076
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/appwidgets/StackWidgetModel.java
@@ -0,0 +1,68 @@
+package it.niedermann.nextcloud.deck.model.appwidgets;
+
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
+import androidx.room.PrimaryKey;
+
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Stack;
+
+@Entity(
+ indices = {
+ @Index("stackId"),
+ @Index("accountId")
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = Account.class,
+ parentColumns = "id",
+ childColumns = "accountId", onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(
+ entity = Stack.class,
+ parentColumns = "localId",
+ childColumns = "stackId", onDelete = ForeignKey.CASCADE
+ )
+ }
+)
+public class StackWidgetModel {
+
+ @PrimaryKey()
+ private Integer appWidgetId;
+ private Long accountId;
+ private Long stackId;
+ private boolean darkTheme;
+
+ public Integer getAppWidgetId() {
+ return appWidgetId;
+ }
+
+ public void setAppWidgetId(Integer appWidgetId) {
+ this.appWidgetId = appWidgetId;
+ }
+
+ public Long getAccountId() {
+ return accountId;
+ }
+
+ public void setAccountId(Long accountId) {
+ this.accountId = accountId;
+ }
+
+ public Long getStackId() {
+ return stackId;
+ }
+
+ public void setStackId(Long stackId) {
+ this.stackId = stackId;
+ }
+
+ public boolean getDarkTheme() {
+ return darkTheme;
+ }
+
+ public void setDarkTheme(boolean darkTheme) {
+ this.darkTheme = darkTheme;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullBoard.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullBoard.java
index 31ad21f16..f9f1643ff 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullBoard.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullBoard.java
@@ -1,5 +1,6 @@
package it.niedermann.nextcloud.deck.model.full;
+import androidx.annotation.NonNull;
import androidx.room.Embedded;
import androidx.room.Ignore;
import androidx.room.Relation;
@@ -29,6 +30,8 @@ public class FullBoard implements IRemoteEntity {
@Relation(entity = Stack.class, parentColumn = "localId", entityColumn = "boardId")
public List<Stack> stacks;
+ @Ignore
+ public List<User> users;
public User getOwner() {
return owner;
@@ -54,9 +57,17 @@ public class FullBoard implements IRemoteEntity {
this.labels = labels;
}
+ public List<User> getUsers() {
+ return users;
+ }
+
+ public void setUsers(List<User> users) {
+ this.users = users;
+ }
+
@Ignore
@Override
- public IRemoteEntity getEntity() {
+ public Board getEntity() {
return board;
}
@@ -102,6 +113,7 @@ public class FullBoard implements IRemoteEntity {
return result;
}
+ @NonNull
@Override
public String toString() {
return "FullBoard{" +
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 41303cac6..611baf3df 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
@@ -6,8 +6,6 @@ import androidx.room.Ignore;
import androidx.room.Junction;
import androidx.room.Relation;
-import org.jetbrains.annotations.NotNull;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -25,7 +23,7 @@ import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
public class FullCard implements IRemoteEntity, DragAndDropModel {
@Ignore
- private transient boolean isAttachmentsSorted = false;
+ protected transient boolean isAttachmentsSorted = false;
@Embedded
public Card card;
@@ -48,7 +46,6 @@ public class FullCard implements IRemoteEntity, DragAndDropModel {
@Relation(entity = DeckComment.class, parentColumn = "localId", entityColumn = "objectId", projection = "localId")
public List<Long> commentIDs;
-
public FullCard() {
super();
}
@@ -126,11 +123,11 @@ public class FullCard implements IRemoteEntity, DragAndDropModel {
@Ignore
@Override
- public IRemoteEntity getEntity() {
+ public Card getEntity() {
return card;
}
- @NotNull
+ @NonNull
@Override
public String toString() {
return "FullCard{" +
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCardWithProjects.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCardWithProjects.java
new file mode 100644
index 000000000..cf43fc86b
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullCardWithProjects.java
@@ -0,0 +1,82 @@
+package it.niedermann.nextcloud.deck.model.full;
+
+import androidx.annotation.NonNull;
+import androidx.room.Junction;
+import androidx.room.Relation;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.full.OcsProjectWithResources;
+
+public class FullCardWithProjects extends FullCard {
+
+
+ @NonNull
+ @Relation(entity = OcsProject.class, parentColumn = "localId", entityColumn = "localId",
+ associateBy = @Junction(value = JoinCardWithProject.class, parentColumn = "cardId", entityColumn = "projectId"))
+
+ private List<OcsProjectWithResources> projects = new ArrayList<>();
+
+ public FullCardWithProjects() {
+ super();
+ }
+
+ public FullCardWithProjects(FullCardWithProjects fullCard) {
+ super(fullCard);
+ this.projects = copyList(fullCard.getProjects());
+ }
+
+ @NonNull
+ public List<OcsProjectWithResources> getProjects() {
+ return projects;
+ }
+
+ public void setProjects(@NonNull List<OcsProjectWithResources> projects) {
+ this.projects = projects;
+ }
+
+ @NonNull
+ @Override
+ public String toString() {
+ return "FullCard{" +
+ "card=" + card +
+ ", labels=" + labels +
+ ", assignedUsers=" + assignedUsers +
+ ", owner=" + owner +
+ ", attachments=" + attachments +
+ '}';
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ FullCardWithProjects fullCard = (FullCardWithProjects) o;
+
+ if (card != null ? !card.equals(fullCard.card) : fullCard.card != null) return false;
+ if (labels != null ? !labels.equals(fullCard.labels) : fullCard.labels != null)
+ return false;
+ if (assignedUsers != null ? !assignedUsers.equals(fullCard.assignedUsers) : fullCard.assignedUsers != null)
+ return false;
+ if (owner != null ? !owner.equals(fullCard.owner) : fullCard.owner != null) return false;
+ if (attachments != null ? !attachments.equals(fullCard.attachments) : fullCard.attachments != null)
+ return false;
+ return commentIDs != null ? commentIDs.equals(fullCard.commentIDs) : fullCard.commentIDs == null;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = (isAttachmentsSorted ? 1 : 0);
+ result = 31 * result + (card != null ? card.hashCode() : 0);
+ result = 31 * result + (labels != null ? labels.hashCode() : 0);
+ result = 31 * result + (assignedUsers != null ? assignedUsers.hashCode() : 0);
+ result = 31 * result + (owner != null ? owner.hashCode() : 0);
+ result = 31 * result + (attachments != null ? attachments.hashCode() : 0);
+ result = 31 * result + (commentIDs != null ? commentIDs.hashCode() : 0);
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullStack.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullStack.java
index eddbafce4..1c9e94899 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullStack.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/full/FullStack.java
@@ -14,7 +14,7 @@ public class FullStack implements IRemoteEntity {
@Embedded
public Stack stack;
- @Relation(entity = Card.class, parentColumn = "localId", entityColumn = "stackId")
+ @Relation(entity = Card.class, parentColumn = "localId", entityColumn = "stackId")
public List<Card> cards;
@@ -36,7 +36,7 @@ public class FullStack implements IRemoteEntity {
@Ignore
@Override
- public IRemoteEntity getEntity() {
+ public Stack getEntity() {
return stack;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractJoinEntity.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractJoinEntity.java
index 50057dfe4..ce14ac6be 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractJoinEntity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractJoinEntity.java
@@ -1,21 +1,20 @@
package it.niedermann.nextcloud.deck.model.interfaces;
-import androidx.annotation.NonNull;
import androidx.room.Entity;
import androidx.room.Ignore;
+
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
@Entity()
public abstract class AbstractJoinEntity {
- @NonNull
protected int status = DBStatus.UP_TO_DATE.getId();
public int getStatus() {
return status;
}
- public void setStatus(@NonNull int status) {
+ public void setStatus(int status) {
this.status = status;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractRemoteEntity.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractRemoteEntity.java
index 328c642ae..2f7771383 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractRemoteEntity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/AbstractRemoteEntity.java
@@ -1,31 +1,30 @@
package it.niedermann.nextcloud.deck.model.interfaces;
-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 java.util.Date;
+import java.time.Instant;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
@Entity(
- indices = {
- @Index("accountId"),
- @Index("id"),
- @Index("lastModifiedLocal"),
- @Index(value = {"accountId", "id"}, unique = true)
- },
- foreignKeys = {
- @ForeignKey(
- entity = Account.class,
- parentColumns = "id",
- childColumns = "accountId", onDelete = ForeignKey.CASCADE
- )
- }
+ indices = {
+ @Index("accountId"),
+ @Index("id"),
+ @Index("lastModifiedLocal"),
+ @Index(value = {"accountId", "id"}, unique = true)
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = Account.class,
+ parentColumns = "id",
+ childColumns = "accountId", onDelete = ForeignKey.CASCADE
+ )
+ }
)
public abstract class AbstractRemoteEntity implements IRemoteEntity {
@PrimaryKey(autoGenerate = true)
@@ -35,11 +34,12 @@ public abstract class AbstractRemoteEntity implements IRemoteEntity {
protected Long id;
- @NonNull
protected int status = DBStatus.UP_TO_DATE.getId();
- protected Date lastModified;
- protected Date lastModifiedLocal;
+ protected Instant lastModified;
+ protected Instant lastModifiedLocal;
+
+ protected String etag;
public AbstractRemoteEntity() {
}
@@ -102,35 +102,30 @@ public abstract class AbstractRemoteEntity implements IRemoteEntity {
@Override
- public void setStatus(@NonNull int status) {
+ public void setStatus(int status) {
this.status = status;
}
-
@Override
- public Date getLastModified() {
+ public Instant getLastModified() {
return this.lastModified;
}
-
@Override
- public void setLastModified(Date lastModified) {
+ public void setLastModified(Instant lastModified) {
this.lastModified = lastModified;
}
-
@Override
- public Date getLastModifiedLocal() {
+ public Instant getLastModifiedLocal() {
return this.lastModifiedLocal;
}
-
@Override
- public void setLastModifiedLocal(Date lastModifiedLocal) {
+ public void setLastModifiedLocal(Instant lastModifiedLocal) {
this.lastModifiedLocal = lastModifiedLocal;
}
-
@Ignore
@Override
public DBStatus getStatusEnum() {
@@ -144,6 +139,15 @@ public abstract class AbstractRemoteEntity implements IRemoteEntity {
this.status = status.getId();
}
+ @Override
+ public String getEtag() {
+ return etag;
+ }
+
+ @Override
+ public void setEtag(String etag) {
+ this.etag = etag;
+ }
@Override
public boolean equals(Object o) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/IRemoteEntity.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/IRemoteEntity.java
index 30a478808..f197c0d30 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/IRemoteEntity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/interfaces/IRemoteEntity.java
@@ -1,94 +1,87 @@
package it.niedermann.nextcloud.deck.model.interfaces;
-import androidx.annotation.NonNull;
-
+import java.time.Instant;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
public interface IRemoteEntity {
- default IRemoteEntity getEntity() {return this;}
+ default IRemoteEntity getEntity() {
+ return this;
+ }
default Long getLocalId() {
return getEntity().getLocalId();
}
-
default void setLocalId(Long localId) {
getEntity().setLocalId(localId);
}
-
default long getAccountId() {
return getEntity().getAccountId();
}
-
default void setAccountId(long accountId) {
getEntity().setAccountId(accountId);
}
-
default Long getId() {
return getEntity().getId();
}
-
default void setId(Long id) {
getEntity().setId(id);
}
-
default int getStatus() {
return getEntity().getStatus();
}
-
- default void setStatus(@NonNull int status) {
+ default void setStatus(int status) {
getEntity().setStatus(status);
}
-
- default Date getLastModified() {
+ default Instant getLastModified() {
return getEntity().getLastModified();
}
-
- default void setLastModified(Date lastModified) {
+ default void setLastModified(Instant lastModified) {
getEntity().setLastModified(lastModified);
}
-
- default Date getLastModifiedLocal() {
+ default Instant getLastModifiedLocal() {
return getEntity().getLastModifiedLocal();
}
-
- default void setLastModifiedLocal(Date lastModifiedLocal) {
+ default void setLastModifiedLocal(Instant lastModifiedLocal) {
getEntity().setLastModifiedLocal(lastModifiedLocal);
}
-
default DBStatus getStatusEnum() {
return getEntity().getStatusEnum();
}
-
default void setStatusEnum(DBStatus status) {
getEntity().setStatusEnum(status);
}
+ default String getEtag() {
+ return getEntity().getEtag();
+ }
+
+ default void setEtag(String etag) {
+ getEntity().setEtag(etag);
+ }
+
default <T> List<T> copyList(List<T> listToCopy) {
if (listToCopy == null) {
return null;
}
List<T> list = new ArrayList<>(listToCopy.size());
- for (T t : listToCopy) {
- list.add(t);
- }
+ list.addAll(listToCopy);
return list;
}
}
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 f3892dfa5..1b5f0879d 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
@@ -3,8 +3,6 @@ package it.niedermann.nextcloud.deck.model.internal;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import org.jetbrains.annotations.NotNull;
-
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
@@ -14,12 +12,21 @@ import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.enums.EDueType;
public class FilterInformation implements Serializable {
+
+ public enum EArchiveStatus{
+ ALL, ARCHIVED, NON_ARCHIVED
+ }
+
@NonNull
private EDueType dueType = EDueType.NO_FILTER;
+ private boolean noAssignedLabel = false;
+ private boolean noAssignedUser = false;
@NonNull
private List<User> users = new ArrayList<>();
@NonNull
private List<Label> labels = new ArrayList<>();
+ @NonNull
+ private EArchiveStatus archiveStatus = EArchiveStatus.NON_ARCHIVED;
public FilterInformation() {
// Default constructor
@@ -28,8 +35,12 @@ public class FilterInformation implements Serializable {
public FilterInformation(@Nullable FilterInformation filterInformation) {
if (filterInformation != null) {
this.dueType = filterInformation.getDueType();
+ this.archiveStatus = filterInformation.getArchiveStatus();
this.users.addAll(filterInformation.getUsers());
this.labels.addAll(filterInformation.getLabels());
+ this.noAssignedUser = filterInformation.isNoAssignedUser();
+ this.noAssignedLabel = filterInformation.isNoAssignedLabel();
+ this.archiveStatus = filterInformation.getArchiveStatus();
}
}
@@ -47,7 +58,7 @@ public class FilterInformation implements Serializable {
return users;
}
- @NotNull
+ @NonNull
public List<Label> getLabels() {
return labels;
}
@@ -68,23 +79,55 @@ public class FilterInformation implements Serializable {
users.remove(user);
}
- @NotNull
+ public boolean isNoAssignedUser() {
+ return noAssignedUser;
+ }
+
+ public void setNoAssignedUser(boolean noAssignedUser) {
+ this.noAssignedUser = noAssignedUser;
+ }
+
+ public boolean isNoAssignedLabel() {
+ return noAssignedLabel;
+ }
+
+ public void setNoAssignedLabel(boolean noAssignedLabel) {
+ this.noAssignedLabel = noAssignedLabel;
+ }
+
+ public void setArchiveStatus(@NonNull EArchiveStatus archiveStatus) {
+ this.archiveStatus = archiveStatus;
+ }
+
+ @NonNull
+ public EArchiveStatus getArchiveStatus() {
+ return archiveStatus;
+ }
+
+ @NonNull
@Override
public String toString() {
return "FilterInformation{" +
"dueType=" + dueType +
+ ", noAssignedLabel=" + noAssignedLabel +
+ ", noAssignedUser=" + noAssignedUser +
", users=" + users +
", labels=" + labels +
+ ", archiveStatus=" + archiveStatus +
'}';
}
/**
- * @return whether or not the given filterInformation has any actual filters set
+ * @return whether or not the given {@param filterInformation} has any actual filters set
*/
public static boolean hasActiveFilter(@Nullable FilterInformation filterInformation) {
if (filterInformation == null) {
return false;
}
- return filterInformation.getDueType() != EDueType.NO_FILTER || filterInformation.getUsers().size() > 0 || filterInformation.getLabels().size() > 0;
+ return filterInformation.getDueType() != EDueType.NO_FILTER
+ || filterInformation.getUsers().size() > 0
+ || filterInformation.getLabels().size() > 0
+ || filterInformation.noAssignedUser
+ || filterInformation.noAssignedLabel;
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Capabilities.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Capabilities.java
index 9ff4d7945..12e8692d4 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Capabilities.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/Capabilities.java
@@ -5,8 +5,8 @@ public class Capabilities {
private Version deckVersion;
private Version nextcloudVersion;
- private String color = "#0082c9";
- private String textColor = "#ffffff";
+ private int color = 0;
+ private int textColor = 0;
private boolean maintenanceEnabled = false;
public Capabilities() {
@@ -28,19 +28,19 @@ public class Capabilities {
this.nextcloudVersion = nextcloudVersion;
}
- public String getColor() {
+ public int getColor() {
return color;
}
- public void setColor(String color) {
+ public void setColor(int color) {
this.color = color;
}
- public String getTextColor() {
+ public int getTextColor() {
return textColor;
}
- public void setTextColor(String textColor) {
+ public void setTextColor(int textColor) {
this.textColor = textColor;
}
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 7ef97a294..cfd973831 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
@@ -6,18 +6,18 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
-import org.jetbrains.annotations.NotNull;
-
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.Attachment;
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_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;
@@ -125,7 +125,7 @@ public class Version implements Comparable<Version> {
return 0;
}
- @NotNull
+ @NonNull
@Override
public String toString() {
return "Version{" +
@@ -157,6 +157,22 @@ public class Version implements Comparable<Version> {
}
/**
+ * Before {@link #VERSION_1_3_0} all {@link Attachment}s have been stored in a special folder at the server.
+ * Starting with {@link #VERSION_1_3_0} {@link Attachment}s can be stored as regular files, allowing for example to make use of server side thumbnail generation.
+ * <p>
+ * Since the migration takes a long time, it does not happen on upgrading the server app but step by step via a cronjob.
+ * Therefore this method is just an indicator, that it is possible that {@link Attachment}s are stored as files, but it is no guarantee that all {@link Attachment}s already have been migrated to files.
+ *
+ * @return whether or not the server supports file attachments
+ * @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);
+ }
+
+ /**
* Title max length has been increased from 100 to 255 characters beginning with server {@link Version} 1.0.0
*
* @return the number of characters that the title fields of cards allow
@@ -167,6 +183,7 @@ public class Version implements Comparable<Version> {
? 255
: 100;
}
+
/**
* URL to view a card in the web interface has been changed in {@link Version} 1.0.0
*
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/comment/DeckComment.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/comment/DeckComment.java
index 707bae974..8bba9740b 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/comment/DeckComment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/comment/DeckComment.java
@@ -5,12 +5,13 @@ import androidx.room.ForeignKey;
import androidx.room.Ignore;
import androidx.room.Index;
+import java.time.Instant;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
+
@Entity(inheritSuperIndices = true,
indices = {
@Index(value = "accountId", name = "comment_accID"),
@@ -35,7 +36,7 @@ public class DeckComment extends AbstractRemoteEntity {
private Long objectId;
private String actorType;
- private Date creationDateTime;
+ private Instant creationDateTime;
private String actorId;
private String actorDisplayName;
private String message;
@@ -47,7 +48,7 @@ public class DeckComment extends AbstractRemoteEntity {
}
@Ignore
- public DeckComment(String message, String actorDisplayName, Date creationDateTime) {
+ public DeckComment(String message, String actorDisplayName, Instant creationDateTime) {
setMessage(message);
setActorDisplayName(actorDisplayName);
setCreationDateTime(creationDateTime);
@@ -76,11 +77,11 @@ public class DeckComment extends AbstractRemoteEntity {
this.actorType = actorType;
}
- public Date getCreationDateTime() {
+ public Instant getCreationDateTime() {
return creationDateTime;
}
- public void setCreationDateTime(Date creationDateTime) {
+ public void setCreationDateTime(Instant creationDateTime) {
this.creationDateTime = creationDateTime;
}
@@ -121,8 +122,8 @@ public class DeckComment extends AbstractRemoteEntity {
}
public void setMessage(String message) {
- if (message!= null && message.length() > MAX_MESSAGE_LENGTH) {
- throw new IllegalArgumentException("The server won't accept messages longer than "+MAX_MESSAGE_LENGTH+" characters!");
+ if (message != null && message.length() > MAX_MESSAGE_LENGTH) {
+ throw new IllegalArgumentException("The server won't accept messages longer than " + MAX_MESSAGE_LENGTH + " characters!");
}
this.message = message;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/JoinCardWithProject.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/JoinCardWithProject.java
new file mode 100644
index 000000000..615c7a2a2
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/JoinCardWithProject.java
@@ -0,0 +1,66 @@
+package it.niedermann.nextcloud.deck.model.ocs.projects;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
+
+import it.niedermann.nextcloud.deck.model.Card;
+import it.niedermann.nextcloud.deck.model.interfaces.AbstractJoinEntity;
+
+@Entity(
+ primaryKeys = {"projectId", "cardId"},
+ indices = {@Index("cardId"), @Index("projectId")},
+ foreignKeys = {
+ @ForeignKey(entity = OcsProject.class,
+ parentColumns = "localId",
+ childColumns = "projectId",
+ onDelete = ForeignKey.CASCADE
+ ),
+ @ForeignKey(entity = Card.class,
+ parentColumns = "localId",
+ childColumns = "cardId",
+ onDelete = ForeignKey.CASCADE
+ )
+ })
+public class JoinCardWithProject extends AbstractJoinEntity {
+ @NonNull
+ private Long projectId;
+ @NonNull
+ private Long cardId;
+
+ @NonNull
+ public Long getProjectId() {
+ return projectId;
+ }
+
+ public void setProjectId(@NonNull Long projectId) {
+ this.projectId = projectId;
+ }
+
+ public Long getCardId() {
+ return cardId;
+ }
+
+ public void setCardId(Long cardId) {
+ this.cardId = cardId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ JoinCardWithProject that = (JoinCardWithProject) o;
+
+ if (!projectId.equals(that.projectId)) return false;
+ return cardId.equals(that.cardId);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = projectId.hashCode();
+ result = 31 * result + cardId.hashCode();
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProject.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProject.java
new file mode 100644
index 000000000..49bc7296d
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProject.java
@@ -0,0 +1,45 @@
+package it.niedermann.nextcloud.deck.model.ocs.projects;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.Ignore;
+import androidx.room.Index;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
+
+@Entity(inheritSuperIndices = true,
+ indices = {
+ @Index(value = "accountId", name = "index_project_accID"),
+ },
+ foreignKeys = {
+ }
+)
+public class OcsProject extends AbstractRemoteEntity {
+ @NonNull
+ private String name;
+
+ @Ignore
+ @NonNull
+ private ArrayList<OcsProjectResource> resources = new ArrayList<>();
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ @NonNull
+ public ArrayList<OcsProjectResource> getResources() {
+ return resources;
+ }
+
+ public void setResources(@NonNull List<OcsProjectResource> resources) {
+ this.resources.clear();
+ this.resources.addAll(resources);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProjectList.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProjectList.java
new file mode 100644
index 000000000..af42b8432
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProjectList.java
@@ -0,0 +1,24 @@
+package it.niedermann.nextcloud.deck.model.ocs.projects;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class OcsProjectList {
+ List<OcsProject> projects;
+
+ public OcsProjectList() {
+ projects = new ArrayList<>();
+ }
+
+ public OcsProjectList(List<OcsProject> projects) {
+ this.projects = projects;
+ }
+
+ public List<OcsProject> getProjects() {
+ return projects;
+ }
+
+ public void add(OcsProject project) {
+ projects.add(project);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProjectResource.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProjectResource.java
new file mode 100644
index 000000000..cd63ccde4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/OcsProjectResource.java
@@ -0,0 +1,135 @@
+package it.niedermann.nextcloud.deck.model.ocs.projects;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
+import androidx.room.RoomWarnings;
+
+import java.io.Serializable;
+
+import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
+
+@SuppressWarnings(RoomWarnings.INDEX_FROM_PARENT_IS_DROPPED)
+@Entity(
+ indices = {
+ @Index(value = "id", name = "index_OcsProjectResource_id"),
+ @Index(value = "lastModifiedLocal", name = "index_OcsProjectResource_lastModifiedLocal"),
+ @Index(value = {"accountId", "id", "idString", "projectId"}, name = "index_OcsProjectResource_accountId_id", unique = true),
+ @Index(value = "accountId", name = "index_projectResource_accID"),
+ @Index(value = "projectId", name = "index_projectResource_projectId"),
+ },
+ foreignKeys = {
+ @ForeignKey(
+ entity = OcsProject.class,
+ parentColumns = "localId",
+ childColumns = "projectId", onDelete = ForeignKey.CASCADE
+ )
+ }
+)
+public class OcsProjectResource extends AbstractRemoteEntity implements Serializable {
+ @Nullable
+ private String type;
+ @Nullable
+ private String name;
+ @Nullable
+ private String link;
+ @Nullable
+ private String path;
+ @Nullable
+ private String iconUrl;
+ @Nullable
+ private String mimetype;
+ @Nullable
+ private Boolean previewAvailable;
+ @Nullable
+ private String idString;
+
+
+ @NonNull
+ private Long projectId;
+
+ public Long getProjectId() {
+ return projectId;
+ }
+
+ public void setProjectId(Long projectId) {
+ this.projectId = projectId;
+ }
+
+ @Nullable
+ public String getType() {
+ return type;
+ }
+
+ public void setType(@Nullable String type) {
+ this.type = type;
+ }
+
+ @Nullable
+ public String getName() {
+ return name;
+ }
+
+ public void setName(@Nullable String name) {
+ this.name = name;
+ }
+
+ /**
+ * Caution: the Link might be a full url or only the relative path!
+ * @return The link to the Resource
+ */
+ @Nullable
+ public String getLink() {
+ return link;
+ }
+
+ public void setLink(@Nullable String link) {
+ this.link = link;
+ }
+
+ @Nullable
+ public String getIconUrl() {
+ return iconUrl;
+ }
+
+ public void setIconUrl(@Nullable String iconUrl) {
+ this.iconUrl = iconUrl;
+ }
+
+ @Nullable
+ public String getPath() {
+ return path;
+ }
+
+ public void setPath(@Nullable String path) {
+ this.path = path;
+ }
+
+ @Nullable
+ public String getMimetype() {
+ return mimetype;
+ }
+
+ public void setMimetype(@Nullable String mimetype) {
+ this.mimetype = mimetype;
+ }
+
+ public Boolean getPreviewAvailable() {
+ return Boolean.TRUE.equals(previewAvailable);
+ }
+
+ public void setPreviewAvailable(@Nullable Boolean previewAvailable) {
+ this.previewAvailable = previewAvailable;
+ }
+
+ @Nullable
+ public String getIdString() {
+ return idString;
+ }
+
+ public void setIdString(@Nullable String idString) {
+ this.idString = idString;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/full/OcsProjectWithResources.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/full/OcsProjectWithResources.java
new file mode 100644
index 000000000..257cf6f60
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/projects/full/OcsProjectWithResources.java
@@ -0,0 +1,50 @@
+package it.niedermann.nextcloud.deck.model.ocs.projects.full;
+
+import androidx.annotation.NonNull;
+import androidx.room.Embedded;
+import androidx.room.Relation;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.interfaces.IRemoteEntity;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+
+public class OcsProjectWithResources implements IRemoteEntity {
+ @Embedded
+ public OcsProject project;
+
+
+ @Relation(entity = OcsProjectResource.class, parentColumn = "localId", entityColumn = "projectId")
+ public List<OcsProjectResource> resources;
+
+ public OcsProject getProject() {
+ return project;
+ }
+
+ public void setProject(OcsProject project) {
+ this.project = project;
+ }
+
+ @NonNull
+ public List<OcsProjectResource> getResources() {
+ return resources;
+ }
+
+ public void setResources(List<OcsProjectResource> resources) {
+ this.resources = resources;
+ }
+
+ public String getName() {
+ return project.getName();
+ }
+
+ public void setName(String name) {
+ project.setName(name);
+ }
+
+ @Override
+ public IRemoteEntity getEntity() {
+ return project;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/GroupMemberUIDs.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/GroupMemberUIDs.java
new file mode 100644
index 000000000..085ca786e
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/GroupMemberUIDs.java
@@ -0,0 +1,27 @@
+package it.niedermann.nextcloud.deck.model.ocs.user;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class GroupMemberUIDs {
+ private List<String> uids = new ArrayList<>();
+
+ public List<String> getUids() {
+ return uids;
+ }
+
+ public void setUids(List<String> uids) {
+ this.uids = uids;
+ }
+
+ @Override
+ public String toString() {
+ return "GroupMemberUIDs{" +
+ "uids=" + uids +
+ '}';
+ }
+
+ public void add(String uid) {
+ uids.add(uid);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUserList.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUserList.java
index a10ae005d..818d5b96e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUserList.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/ocs/user/OcsUserList.java
@@ -1,7 +1,16 @@
package it.niedermann.nextcloud.deck.model.ocs.user;
import java.util.ArrayList;
+import java.util.List;
-public class OcsUserList extends ArrayList<String> {
- // nothing.
+public class OcsUserList {
+ private List<OcsUser> users = new ArrayList<>();
+
+ public List<OcsUser> getUsers() {
+ return users;
+ }
+
+ public void addUser(OcsUser user) {
+ this.users.add(user);
+ }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/relations/UserInBoard.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/relations/UserInBoard.java
new file mode 100644
index 000000000..1f167bad1
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/relations/UserInBoard.java
@@ -0,0 +1,71 @@
+package it.niedermann.nextcloud.deck.model.relations;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
+
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.User;
+
+@Entity(
+ primaryKeys = {"userId", "boardId"},
+ indices = {@Index("userId"), @Index("boardId"), @Index(name = "unique_idx_user_board", value = {"userId","boardId"}, unique = true)},
+ foreignKeys = {
+ @ForeignKey(entity = User.class,
+ parentColumns = "localId",
+ childColumns = "userId", onDelete = ForeignKey.CASCADE),
+ @ForeignKey(entity = Board.class,
+ parentColumns = "localId",
+ childColumns = "boardId", onDelete = ForeignKey.CASCADE)
+ })
+public class UserInBoard {
+ @NonNull
+ private Long userId;
+ @NonNull
+ private Long boardId;
+
+ @NonNull
+ public Long getUserId() {
+ return userId;
+ }
+
+ public void setUserId(@NonNull Long userId) {
+ this.userId = userId;
+ }
+
+ @NonNull
+ public Long getBoardId() {
+ return boardId;
+ }
+
+ public void setBoardId(@NonNull Long boardId) {
+ this.boardId = boardId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ UserInBoard that = (UserInBoard) o;
+
+ if (!userId.equals(that.userId)) return false;
+ return boardId.equals(that.boardId);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = userId.hashCode();
+ result = 31 * result + boardId.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "UserInGroup{" +
+ "userId=" + userId +
+ ", boardId=" + boardId +
+ '}';
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/model/relations/UserInGroup.java b/app/src/main/java/it/niedermann/nextcloud/deck/model/relations/UserInGroup.java
new file mode 100644
index 000000000..48e16cce5
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/model/relations/UserInGroup.java
@@ -0,0 +1,70 @@
+package it.niedermann.nextcloud.deck.model.relations;
+
+import androidx.annotation.NonNull;
+import androidx.room.Entity;
+import androidx.room.ForeignKey;
+import androidx.room.Index;
+
+import it.niedermann.nextcloud.deck.model.User;
+
+@Entity(
+ primaryKeys = {"groupId", "memberId"},
+ indices = {@Index("groupId"), @Index("memberId"), @Index(name = "unique_idx_group_member", value = {"groupId","memberId"}, unique = true)},
+ foreignKeys = {
+ @ForeignKey(entity = User.class,
+ parentColumns = "localId",
+ childColumns = "groupId", onDelete = ForeignKey.CASCADE),
+ @ForeignKey(entity = User.class,
+ parentColumns = "localId",
+ childColumns = "memberId", onDelete = ForeignKey.CASCADE)
+ })
+public class UserInGroup {
+ @NonNull
+ private Long groupId;
+ @NonNull
+ private Long memberId;
+
+ @NonNull
+ public Long getGroupId() {
+ return groupId;
+ }
+
+ public void setGroupId(@NonNull Long groupId) {
+ this.groupId = groupId;
+ }
+
+ @NonNull
+ public Long getMemberId() {
+ return memberId;
+ }
+
+ public void setMemberId(@NonNull Long memberId) {
+ this.memberId = memberId;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ UserInGroup that = (UserInGroup) o;
+
+ if (!groupId.equals(that.groupId)) return false;
+ return memberId.equals(that.memberId);
+ }
+
+ @Override
+ public int hashCode() {
+ int result = groupId.hashCode();
+ result = 31 * result + memberId.hashCode();
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "UserInGroup{" +
+ "groupId=" + groupId +
+ ", memberId=" + memberId +
+ '}';
+ }
+}
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 9ed9035cf..d93835423 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
@@ -1,31 +1,43 @@
package it.niedermann.nextcloud.deck.persistence.sync;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.database.sqlite.SQLiteConstraintException;
import androidx.annotation.AnyThread;
+import androidx.annotation.ColorInt;
+import androidx.annotation.MainThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.annotation.Size;
import androidx.annotation.WorkerThread;
import androidx.core.util.Pair;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MediatorLiveData;
import androidx.lifecycle.MutableLiveData;
+import com.nextcloud.android.sso.api.ParsedResponse;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import java.io.File;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
+import java.util.HashMap;
import java.util.List;
+import java.util.Map;
import java.util.NoSuchElementException;
+import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
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.exceptions.DeckException;
import it.niedermann.nextcloud.deck.exceptions.OfflineException;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Account;
@@ -36,9 +48,11 @@ import it.niedermann.nextcloud.deck.model.JoinCardWithUser;
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.appwidgets.StackWidgetModel;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.internal.FilterInformation;
@@ -46,12 +60,12 @@ import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
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.user.OcsUser;
-import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
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;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.extrawurst.UserSearchLiveData;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.DataPropagationHelper;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.AbstractSyncDataProvider;
@@ -64,20 +78,23 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.CardPropa
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.DeckCommentsDataProvider;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.LabelDataProvider;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.StackDataProvider;
-import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial.BoardWitAclDownSyncDataProvider;
-import it.niedermann.nextcloud.deck.util.DateUtil;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial.BoardWithAclDownSyncDataProvider;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial.BoardWithStacksAndLabelsUpSyncDataProvider;
+import static java.net.HttpURLConnection.HTTP_NOT_MODIFIED;
import static java.net.HttpURLConnection.HTTP_UNAVAILABLE;
@SuppressWarnings("WeakerAccess")
public class SyncManager {
@NonNull
- private Context appContext;
+ private final Context appContext;
@NonNull
- private DataBaseAdapter dataBaseAdapter;
+ private final DataBaseAdapter dataBaseAdapter;
@NonNull
- private ServerAdapter serverAdapter;
+ private final ServerAdapter serverAdapter;
+
+ private static final Map<Long, List<IResponseCallback<Boolean>>> RUNNING_SYNCS = new ConcurrentHashMap<>();
@AnyThread
public SyncManager(@NonNull Context context) {
@@ -98,62 +115,35 @@ public class SyncManager {
}
@AnyThread
- public MutableLiveData<FullCard> synchronizeCardByRemoteId(long cardRemoteId, @NonNull Account account) {
- MutableLiveData<FullCard> liveData = new MutableLiveData<>();
- doAsync(() -> {
- Long accountId = account.getId();
- Card card = dataBaseAdapter.getCardByRemoteIdDirectly(accountId, cardRemoteId);
- FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
- // only sync this one card.
- stack.setCards(Collections.singletonList(card));
- Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
- new SyncHelper(serverAdapter, dataBaseAdapter, new Date()).setResponseCallback(new IResponseCallback<Boolean>(account) {
- @Override
- public void onResponse(Boolean response) {
- FullCard fullCard = dataBaseAdapter.getFullCardByLocalIdDirectly(accountId, card.getLocalId());
- liveData.postValue(fullCard);
- }
-
- @Override
- public void onError(Throwable throwable) {
- liveData.postValue(null);
- }
- }).doSyncFor(new CardDataProvider(null, board, stack));
- });
- return liveData;
- }
-
- // TODO if the card does not exist yet, try to synchronize it first, instead of directly returning null. If sync failed, return null.
- @AnyThread
public LiveData<Long> getLocalBoardIdByCardRemoteIdAndAccount(long cardRemoteId, @NonNull Account account) {
return dataBaseAdapter.getLocalBoardIdByCardRemoteIdAndAccountId(cardRemoteId, account.getId());
}
- @AnyThread
+ @WorkerThread
public boolean synchronizeEverything() {
List<Account> accounts = dataBaseAdapter.getAllAccountsDirectly();
if (accounts.size() > 0) {
- final BooleanResultHolder success = new BooleanResultHolder();
+ final AtomicBoolean success = new AtomicBoolean();
CountDownLatch latch = new CountDownLatch(accounts.size());
try {
for (Account account : accounts) {
new SyncManager(dataBaseAdapter.getContext(), account.getName()).synchronize(new IResponseCallback<Boolean>(account) {
@Override
public void onResponse(Boolean response) {
- success.result = success.result && Boolean.TRUE.equals(response);
+ success.set(success.get() && Boolean.TRUE.equals(response));
latch.countDown();
}
@Override
public void onError(Throwable throwable) {
- success.result = false;
+ success.set(false);
super.onError(throwable);
latch.countDown();
}
});
}
latch.await();
- return success.result;
+ return success.get();
} catch (InterruptedException e) {
DeckLog.logError(e);
return false;
@@ -164,76 +154,160 @@ public class SyncManager {
@AnyThread
public void synchronize(@NonNull IResponseCallback<Boolean> responseCallback) {
- if(responseCallback.getAccount() == null) {
+ synchronize(Collections.singletonList(responseCallback));
+ }
+
+ @AnyThread
+ public void synchronizeBoard(@NonNull IResponseCallback<Boolean> responseCallback, long localBoadId) {
+ doAsync(() -> {
+ FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(responseCallback.getAccount().getId(), localBoadId);
+ try {
+ new SyncHelper(serverAdapter, dataBaseAdapter, null).setResponseCallback(responseCallback).doSyncFor(new StackDataProvider(null, board));
+ } catch (OfflineException e) {
+ responseCallback.onError(e);
+ }
+ });
+ }
+
+ @AnyThread
+ public void synchronizeCard(@NonNull IResponseCallback<Boolean> responseCallback, Card card) {
+ doAsync(() -> {
+ FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
+ Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
+ try {
+ new SyncHelper(serverAdapter, dataBaseAdapter, null).setResponseCallback(responseCallback).doSyncFor(new CardDataProvider(null, board, stack));
+ } catch (OfflineException e) {
+ responseCallback.onError(e);
+ }
+ });
+ }
+
+ private void synchronize(@NonNull @Size(min = 1) List<IResponseCallback<Boolean>> responseCallbacks) {
+ if (responseCallbacks == null || responseCallbacks.size() < 1) {
+ return;
+ }
+ IResponseCallback<Boolean> responseCallback = responseCallbacks.get(0);
+ Account callbackAccount = responseCallback.getAccount();
+ if (callbackAccount == null) {
throw new IllegalArgumentException(Account.class.getSimpleName() + " object in given " + IResponseCallback.class.getSimpleName() + " must not be null.");
}
- if(responseCallback.getAccount().getId() == null) {
+ Long callbackAccountId = callbackAccount.getId();
+ if (callbackAccountId == null) {
throw new IllegalArgumentException(Account.class.getSimpleName() + " object in given " + IResponseCallback.class.getSimpleName() + " must contain a valid id, but given id was null.");
}
- doAsync(() -> refreshCapabilities(new IResponseCallback<Capabilities>(responseCallback.getAccount()) {
- @Override
- public void onResponse(Capabilities response) {
- if (!response.isMaintenanceEnabled()) {
- if (response.getDeckVersion().isSupported(appContext)) {
- long accountId = responseCallback.getAccount().getId();
- Date lastSyncDate = LastSyncUtil.getLastSyncDate(responseCallback.getAccount().getId());
- Date now = DateUtil.nowInGMT();
-
- final SyncHelper syncHelper = new SyncHelper(serverAdapter, dataBaseAdapter, lastSyncDate);
+ List<IResponseCallback<Boolean>> queuedCallbacks = RUNNING_SYNCS.get(callbackAccountId);
+ if (queuedCallbacks != null) {
+ queuedCallbacks.addAll(responseCallbacks);
+ return;
+ } else {
+ RUNNING_SYNCS.put(callbackAccountId, new ArrayList<>(responseCallbacks));
+ }
+ doAsync(() -> {
+ List<IResponseCallback<Boolean>> existingQueue = RUNNING_SYNCS.get(callbackAccountId);
+ List<IResponseCallback<Boolean>> callbacksQueueForSync = existingQueue == null ? new ArrayList<>() : new ArrayList<>(existingQueue);
+ refreshCapabilities(new IResponseCallback<Capabilities>(responseCallback.getAccount()) {
+ @Override
+ public void onResponse(Capabilities response) {
+ if (response != null && !response.isMaintenanceEnabled()) {
+ if (response.getDeckVersion().isSupported(appContext)) {
+ long accountId = callbackAccountId;
+ Instant lastSyncDate = LastSyncUtil.getLastSyncDate(callbackAccountId);
- IResponseCallback<Boolean> callback = new IResponseCallback<Boolean>(responseCallback.getAccount()) {
- @Override
- public void onResponse(Boolean response) {
- syncHelper.setResponseCallback(new IResponseCallback<Boolean>(account) {
- @Override
- public void onResponse(Boolean response) {
- // TODO deactivate for dev
- LastSyncUtil.setLastSyncDate(accountId, now);
- responseCallback.onResponse(response);
- }
+ final SyncHelper syncHelper = new SyncHelper(serverAdapter, dataBaseAdapter, lastSyncDate);
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- responseCallback.onError(throwable);
- }
- });
- doAsync(() -> {
- try {
- syncHelper.doUpSyncFor(new BoardDataProvider());
- } catch (Throwable e) {
- DeckLog.logError(e);
- responseCallback.onError(e);
- }
- });
+ IResponseCallback<Boolean> callback = new IResponseCallback<Boolean>(callbackAccount) {
+ @Override
+ public void onResponse(Boolean response) {
+ syncHelper.setResponseCallback(new IResponseCallback<Boolean>(account) {
+ @Override
+ public void onResponse(Boolean response) {
+ // TODO deactivate for dev
+ LastSyncUtil.setLastSyncDate(accountId, Instant.now());
+ respondCallbacksAfterSync(callbacksQueueForSync, response, null);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ respondCallbacksAfterSync(callbacksQueueForSync, null, throwable);
+ }
+ });
+ doAsync(() -> {
+ try {
+ syncHelper.doUpSyncFor(new BoardDataProvider());
+ } catch (Throwable e) {
+ DeckLog.logError(e);
+ respondCallbacksAfterSync(callbacksQueueForSync, null, e);
+ }
+ });
- }
+ }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- responseCallback.onError(throwable);
- }
- };
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ respondCallbacksAfterSync(callbacksQueueForSync, null, throwable);
+ }
+ };
- syncHelper.setResponseCallback(callback);
+ syncHelper.setResponseCallback(callback);
- try {
- syncHelper.doSyncFor(new BoardDataProvider());
- } catch (Throwable e) {
- DeckLog.logError(e);
- responseCallback.onError(e);
+ try {
+ syncHelper.doSyncFor(new BoardDataProvider());
+ } catch (Throwable e) {
+ DeckLog.logError(e);
+ respondCallbacksAfterSync(callbacksQueueForSync, null, e);
+ }
+ } else {
+ respondCallbacksAfterSync(callbacksQueueForSync, Boolean.FALSE, null);
+ DeckLog.warn("No sync. Server version not supported: " + response.getDeckVersion().getOriginalVersion());
}
} else {
- responseCallback.onResponse(false);
- DeckLog.warn("No sync. Server version not supported: " + response.getDeckVersion().getOriginalVersion());
+ respondCallbacksAfterSync(callbacksQueueForSync, Boolean.FALSE, null);
+ if (response != null) {
+ DeckLog.warn("No sync. Status maintenance mode: " + response.isMaintenanceEnabled());
+ }
}
- } else {
- responseCallback.onResponse(false);
- DeckLog.warn("No sync. Status maintenance mode: " + response.isMaintenanceEnabled());
}
+ });
+ });
+ }
+
+ private void respondCallbacksAfterSync(List<IResponseCallback<Boolean>> callbacksQueueForSync, Boolean response, Throwable throwable) {
+ if (callbacksQueueForSync == null || callbacksQueueForSync.isEmpty()) {
+ return;
+ }
+ // notify done callbacks
+ DeckLog.info("SyncQueue: responding sync for " + callbacksQueueForSync.size() + " queued callbacks!");
+ List<IResponseCallback<Boolean>> callbacksQueue = new ArrayList<>(callbacksQueueForSync);
+ if (throwable == null) {
+ //success:
+ for (IResponseCallback<Boolean> callback : callbacksQueue) {
+ if (callback != null) callback.onResponse(response);
}
- }));
+ } else {
+ // failure:
+ for (IResponseCallback<Boolean> callback : callbacksQueue) {
+ if (callback != null) callback.onError(throwable);
+ }
+ }
+ // remove done callbacks from queue
+ IResponseCallback<Boolean> firstCallbackOfAccount = callbacksQueue.iterator().next();
+ List<IResponseCallback<Boolean>> queuedCallbacks = RUNNING_SYNCS.get(firstCallbackOfAccount.getAccount().getId());
+ if (queuedCallbacks == null) {
+ return;
+ }
+ for (IResponseCallback<Boolean> callback : callbacksQueue) {
+ queuedCallbacks.remove(callback);
+ }
+ // cleanup if done, or proceed if not
+ if (queuedCallbacks.isEmpty()) {
+ RUNNING_SYNCS.remove(firstCallbackOfAccount.getAccount().getId());
+ } else {
+ DeckLog.info("SyncQueue: starting sync for " + queuedCallbacks.size() + " queued callbacks!");
+ RUNNING_SYNCS.remove(firstCallbackOfAccount.getAccount().getId());
+ synchronize(queuedCallbacks);
+ }
}
//
@@ -274,14 +348,15 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<Account> createAccount(@NonNull Account accout) {
- return dataBaseAdapter.createAccount(accout);
+ public WrappedLiveData<Account> createAccount(@NonNull Account account) {
+ return dataBaseAdapter.createAccount(account);
}
public boolean hasInternetConnection() {
return serverAdapter.hasInternetConnection();
}
+ @AnyThread
public void deleteAccount(long id) {
doAsync(() -> {
dataBaseAdapter.deleteAccount(id);
@@ -289,10 +364,6 @@ public class SyncManager {
});
}
- public void updateAccount(Account account) {
- dataBaseAdapter.updateAccount(account);
- }
-
@AnyThread
public LiveData<Account> readAccount(long id) {
return dataBaseAdapter.readAccount(id);
@@ -320,7 +391,7 @@ public class SyncManager {
* - located at the given {@param host}
* - and have the permission to read the board with the given {@param boardRemoteId} (aka the {@link Board} is shared with this {@link User}).
*/
- @AnyThread
+ @MainThread
public LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String host, long boardRemoteId) {
MediatorLiveData<List<Account>> liveData = new MediatorLiveData<>();
liveData.addSource(dataBaseAdapter.readAccountsForHostWithReadAccessToBoard(host, boardRemoteId), accounts -> {
@@ -333,7 +404,7 @@ public class SyncManager {
public void onResponse(Boolean response) {
liveData.postValue(dataBaseAdapter.readAccountsForHostWithReadAccessToBoardDirectly(host, boardRemoteId));
}
- }).doSyncFor(new BoardWitAclDownSyncDataProvider());
+ }).doSyncFor(new BoardWithAclDownSyncDataProvider());
}
});
});
@@ -345,15 +416,17 @@ public class SyncManager {
public void refreshCapabilities(@NonNull IResponseCallback<Capabilities> callback) {
doAsync(() -> {
try {
- serverAdapter.getCapabilities(new IResponseCallback<Capabilities>(callback.getAccount()) {
+ Account accountForEtag = dataBaseAdapter.getAccountByIdDirectly(callback.getAccount().getId());
+ serverAdapter.getCapabilities(accountForEtag.getEtag(), new IResponseCallback<ParsedResponse<Capabilities>>(callback.getAccount()) {
@Override
- public void onResponse(Capabilities response) {
+ public void onResponse(ParsedResponse<Capabilities> response) {
Account acc = dataBaseAdapter.getAccountByIdDirectly(account.getId());
- acc.applyCapabilities(response);
+ acc.applyCapabilities(response.getResponse(), response.getHeaders().get("ETag"));
dataBaseAdapter.updateAccount(acc);
- callback.onResponse(response);
+ callback.onResponse(response.getResponse());
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
if (throwable instanceof NextcloudHttpRequestFailedException) {
@@ -361,13 +434,28 @@ 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.");
if (capabilities.isMaintenanceEnabled()) {
- doAsync(() -> {
- onResponse(capabilities);
- });
+ 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.");
+ //could be after maintenance. so we have to at least revert the maintenance flag
+ doAsync(() -> {
+ Account acc = dataBaseAdapter.getAccountByIdDirectly(account.getId());
+ if (acc.isMaintenanceEnabled()) {
+ acc.setMaintenanceEnabled(false);
+ dataBaseAdapter.updateAccount(acc);
+ }
+ Capabilities capabilities = new Capabilities();
+ capabilities.setMaintenanceEnabled(false);
+ capabilities.setDeckVersion(acc.getServerDeckVersionAsObject());
+ capabilities.setTextColor(acc.getTextColor());
+ capabilities.setColor(acc.getColor());
+ callback.onResponse(capabilities);
+ });
}
} else {
callback.onError(throwable);
@@ -377,39 +465,30 @@ public class SyncManager {
} catch (OfflineException e) {
callback.onError(e);
}
-
- try {
- serverAdapter.getAllOcsUsers(new IResponseCallback<OcsUserList>(callback.getAccount()) {
- @Override
- public void onResponse(OcsUserList response) {
- Long accountId = callback.getAccount().getId();
- for (String ocsUserName : response) {
- User existingUser = dataBaseAdapter.getUserByUidDirectly(accountId, ocsUserName);
- if (existingUser == null) {
- // we don't know this user, lets get some details...
- serverAdapter.getOcsUserDetails(ocsUserName, new IResponseCallback<OcsUser>(callback.getAccount()) {
- @Override
- public void onResponse(OcsUser response) {
- User newUser = new User();
- newUser.setStatus(DBStatus.UP_TO_DATE.getId());
- newUser.setPrimaryKey(ocsUserName);
- newUser.setUid(ocsUserName);
- newUser.setDisplayname(response.getDisplayName());
- dataBaseAdapter.createUser(accountId, newUser);
- }
- });
- }
- }
- }
- });
- } catch (OfflineException ignored) {
- // Nothing to do here...
- }
});
}
/**
* @param accountId ID of the account
+ * @return all {@link Board}s no matter if {@link Board#archived} or not.
+ */
+ @SuppressWarnings("JavadocReference")
+ @AnyThread
+ public LiveData<List<Board>> getBoards(long accountId) {
+ return dataBaseAdapter.getBoards(accountId);
+ }
+
+ /**
+ * @param localProjectId LocalId of the OcsProject
+ * @return all {@link OcsProjectResource}s of the Project
+ */
+ @AnyThread
+ public LiveData<List<OcsProjectResource>> getResourcesForProject(long localProjectId) {
+ return dataBaseAdapter.getResourcesByLocalProjectId(localProjectId);
+ }
+
+ /**
+ * @param accountId ID of the account
* @param archived Decides whether only archived or not-archived boards for the specified account will be returned
* @return all archived or non-archived <code>Board</code>s depending on <code>archived</code> parameter
*/
@@ -445,8 +524,8 @@ public class SyncManager {
}
@AnyThread
- public LiveData<FullBoard> createBoard(long accountId, @NonNull Board board) {
- MutableLiveData<FullBoard> liveData = new MutableLiveData<>();
+ public WrappedLiveData<FullBoard> createBoard(long accountId, @NonNull Board board) {
+ WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
User owner = dataBaseAdapter.getUserByUidDirectly(accountId, account.getUserName());
@@ -465,6 +544,12 @@ public class SyncManager {
public void onResponse(FullBoard response) {
liveData.postValue(response);
}
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable, FullBoard entity) {
+ liveData.postError(throwable, entity);
+ }
});
}
});
@@ -474,58 +559,145 @@ public class SyncManager {
/**
* Creates a new {@link Board} and adds the same {@link Label} and {@link Stack} as in the origin {@link Board}.
* Owner of the target {@link Board} will be the {@link User} with the {@link Account} of {@param targetAccountId}.
- * Does <strong>not</strong> clone any {@link Card} or {@link AccessControl} from the origin {@link Board}.
+ *
+ * @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, String targetBoardTitle, String targetBoardColor) {
- WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
+ public WrappedLiveData<FullBoard> cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, @ColorInt int targetBoardColor, boolean cloneCards) {
+ final WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
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."));
+ return;
+ }
FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(originAccountId, originBoardLocalId);
+ String newBoardTitleBaseName = originalBoard.getBoard().getTitle().trim();
+ int newBoardTitleCopyIndex = 0;
+ //already a copy?
+ String regex = " \\(copy [0-9]+\\)$";
+ Pattern pattern = Pattern.compile(regex);
+ Matcher matcher = pattern.matcher(originalBoard.getBoard().getTitle());
+ if (matcher.find()) {
+ String found = matcher.group();
+ newBoardTitleBaseName = newBoardTitleBaseName.substring(0, newBoardTitleBaseName.length() - found.length());
+ Matcher indexMatcher = Pattern.compile("[0-9]+").matcher(found);
+ //noinspection ResultOfMethodCallIgnored
+ indexMatcher.find();
+ String oldIndexString = indexMatcher.group();
+ newBoardTitleCopyIndex = Integer.parseInt(oldIndexString);
+ }
+
+ String newBoardTitle;
+ do {
+ newBoardTitleCopyIndex++;
+ newBoardTitle = newBoardTitleBaseName + " (copy " + newBoardTitleCopyIndex + ")";
+
+ } while (dataBaseAdapter.getBoardForAccountByNameDirectly(targetAccountId, newBoardTitle) != null);
+
+
originalBoard.setAccountId(targetAccountId);
- originalBoard.getBoard().setTitle(targetBoardTitle);
- originalBoard.getBoard().setColor(targetBoardColor);
- originalBoard.getBoard().setOwnerId(newOwner.getId());
- originalBoard.setStatusEnum(DBStatus.LOCAL_EDITED);
- originalBoard.setOwner(newOwner);
originalBoard.setId(null);
originalBoard.setLocalId(null);
+ originalBoard.getBoard().setTitle(newBoardTitle);
+ originalBoard.getBoard().setColor(String.format("%06X", 0xFFFFFF & targetBoardColor));
+ originalBoard.getBoard().setOwnerId(newOwner.getLocalId());
+ originalBoard.setStatusEnum(DBStatus.LOCAL_EDITED);
+ originalBoard.setOwner(newOwner);
long newBoardId = dataBaseAdapter.createBoardDirectly(originAccountId, originalBoard.getBoard());
originalBoard.setLocalId(newBoardId);
- for (Stack stack : originalBoard.getStacks()) {
- stack.setLocalId(null);
- stack.setId(null);
- stack.setStatusEnum(DBStatus.LOCAL_EDITED);
- stack.setAccountId(targetAccountId);
- stack.setBoardId(newBoardId);
- dataBaseAdapter.createStack(targetAccountId, stack);
+ boolean isSameAccount = targetAccountId == originAccountId;
+
+ if (isSameAccount) {
+ List<AccessControl> aclList = originalBoard.getParticipants();
+ for (AccessControl acl : aclList) {
+ acl.setLocalId(null);
+ acl.setId(null);
+ acl.setBoardId(newBoardId);
+ dataBaseAdapter.createAccessControl(targetAccountId, acl);
+ }
}
+
+ Map<Long, Long> oldToNewLabelIdsDictionary = new HashMap<>();
+
for (Label label : originalBoard.getLabels()) {
+ Long oldLocalId = label.getLocalId();
label.setLocalId(null);
label.setId(null);
label.setAccountId(targetAccountId);
label.setStatusEnum(DBStatus.LOCAL_EDITED);
label.setBoardId(newBoardId);
- dataBaseAdapter.createLabel(targetAccountId, label);
+ long newLocalId = dataBaseAdapter.createLabelDirectly(targetAccountId, label);
+ oldToNewLabelIdsDictionary.put(oldLocalId, newLocalId);
}
- Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId);
- new SyncHelper(serverAdapter, dataBaseAdapter, null)
- .setResponseCallback(new IResponseCallback<Boolean>(targetAccount) {
- @Override
- public void onResponse(Boolean response) {
- liveData.postValue(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
- }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- liveData.postError(throwable);
+ List<Stack> oldStacks = originalBoard.getStacks();
+ for (Stack stack : oldStacks) {
+ Long oldStackId = stack.getLocalId();
+ stack.setLocalId(null);
+ stack.setId(null);
+ stack.setStatusEnum(DBStatus.LOCAL_EDITED);
+ stack.setAccountId(targetAccountId);
+ stack.setBoardId(newBoardId);
+ long createdStackId = dataBaseAdapter.createStack(targetAccountId, stack);
+ if (cloneCards) {
+ List<FullCard> oldCards = dataBaseAdapter.getFullCardsForStackDirectly(originAccountId, oldStackId, null);
+ for (FullCard oldCard : oldCards) {
+ Card newCard = oldCard.getCard();
+ newCard.setId(null);
+ newCard.setUserId(newOwner.getLocalId());
+ newCard.setLocalId(null);
+ newCard.setStackId(createdStackId);
+ newCard.setAccountId(targetAccountId);
+ newCard.setStatusEnum(DBStatus.LOCAL_EDITED);
+ long createdCardId = dataBaseAdapter.createCardDirectly(targetAccountId, newCard);
+ if (oldCard.getLabels() != null) {
+ for (Label oldLabel : oldCard.getLabels()) {
+ Long newLabelId = oldToNewLabelIdsDictionary.get(oldLabel.getLocalId());
+ if (newLabelId != null) {
+ dataBaseAdapter.createJoinCardWithLabel(newLabelId, createdCardId, DBStatus.LOCAL_EDITED);
+ } else
+ DeckLog.error("ID of created Label is null! Skipping assignment of \"" + oldLabel.getTitle() + "\"...");
+ }
+ }
+ if (isSameAccount && oldCard.getAssignedUsers() != null) {
+ for (User assignedUser : oldCard.getAssignedUsers()) {
+ dataBaseAdapter.createJoinCardWithUser(assignedUser.getLocalId(), createdCardId, DBStatus.LOCAL_EDITED);
+ }
}
- }).doSyncFor(new BoardDataProvider());
+ }
+ }
+ }
+ // dont trigger concurrent syncs!
+ List<IResponseCallback<Boolean>> queuedSync = RUNNING_SYNCS.get(targetAccountId);
+ if ((queuedSync == null || queuedSync.isEmpty()) && serverAdapter.hasInternetConnection()) {
+ Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId);
+ ServerAdapter serverAdapterToUse = this.serverAdapter;
+ if (originAccountId != targetAccountId) {
+ serverAdapterToUse = new ServerAdapter(appContext, targetAccount.getName());
+ }
+ new SyncHelper(serverAdapterToUse, dataBaseAdapter, null)
+ .setResponseCallback(new IResponseCallback<Boolean>(targetAccount) {
+ @Override
+ public void onResponse(Boolean response) {
+ liveData.postValue(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
+ }
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ liveData.postError(throwable);
+ }
+ }).doUpSyncFor(new BoardWithStacksAndLabelsUpSyncDataProvider(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId)));
+ } else {
+ liveData.postValue(dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, newBoardId));
+ }
});
return liveData;
}
@@ -624,6 +796,7 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -633,8 +806,8 @@ public class SyncManager {
return liveData;
}
- public LiveData<List<FullStack>> getStacksForBoard(long accountId, long localBoardId) {
- return dataBaseAdapter.getFullStacksForBoard(accountId, localBoardId);
+ public LiveData<List<Stack>> getStacksForBoard(long accountId, long localBoardId) {
+ return dataBaseAdapter.getStacksForBoard(accountId, localBoardId);
}
public LiveData<FullStack> getStack(long accountId, long localStackId) {
@@ -651,8 +824,7 @@ public class SyncManager {
new AccessControlDataProvider(null, board, Collections.singletonList(entity)), entity, getCallbackToLiveDataConverter(account, liveData), ((entity1, response) -> {
response.setBoardId(entity.getBoardId());
response.setUserId(entity.getUser().getLocalId());
- }
- )
+ })
);
});
return liveData;
@@ -687,6 +859,7 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -711,6 +884,7 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -725,18 +899,30 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<FullStack> createStack(long accountId, @NonNull Stack stack) {
+ public WrappedLiveData<FullStack> createStack(long accountId, @NonNull String title, long boardLocalId) {
WrappedLiveData<FullStack> liveData = new WrappedLiveData<>();
doAsync(() -> {
+ Stack stack = new Stack(title, boardLocalId);
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, stack.getBoardId());
FullStack fullStack = new FullStack();
- // TODO set stack order to (highest stack-order from board) + 1 and remove logic from caller
+ stack.setOrder(dataBaseAdapter.getHighestStackOrderInBoard(stack.getBoardId()) + 1);
stack.setAccountId(accountId);
stack.setBoardId(board.getLocalId());
fullStack.setStack(stack);
fullStack.setAccountId(accountId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new StackDataProvider(null, board), fullStack, getCallbackToLiveDataConverter(account, liveData));
+ 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);
+ }
+ });
});
return liveData;
}
@@ -754,36 +940,36 @@ public class SyncManager {
}
@AnyThread
- public WrappedLiveData<FullStack> updateStack(@NonNull FullStack stack) {
+ public WrappedLiveData<FullStack> updateStackTitle(long localStackId, @NonNull String newTitle) {
WrappedLiveData<FullStack> liveData = new WrappedLiveData<>();
doAsync(() -> {
+ FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(localStackId);
+ FullBoard fullBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(stack.getAccountId(), stack.getStack().getBoardId());
Account account = dataBaseAdapter.getAccountByIdDirectly(stack.getAccountId());
- FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(stack.getAccountId(), stack.getStack().getBoardId());
- updateStack(account, board, stack, liveData);
+ stack.getStack().setTitle(newTitle);
+ updateStack(account, fullBoard, stack, liveData);
});
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);
- }
+ 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);
}
+ }
- @Override
- public void onError(Throwable throwable) {
- if (liveData != null) {
- liveData.postError(throwable);
- }
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ if (liveData != null) {
+ liveData.postError(throwable);
}
- });
- });
+ }
+ }));
}
/**
@@ -813,8 +999,8 @@ public class SyncManager {
});
}
- public LiveData<FullCard> getCardByLocalId(long accountId, long cardLocalId) {
- return dataBaseAdapter.getCardByLocalId(accountId, cardLocalId);
+ public LiveData<FullCardWithProjects> getFullCardWithProjectsByLocalId(long accountId, long cardLocalId) {
+ return dataBaseAdapter.getCardWithProjectsByLocalId(accountId, cardLocalId);
}
public LiveData<List<FullCard>> getFullCardsForStack(long accountId, long localStackId, @Nullable FilterInformation filter) {
@@ -863,8 +1049,8 @@ public class SyncManager {
// }
@AnyThread
- public LiveData<FullCard> createFullCard(long accountId, long localBoardId, long localStackId, @NonNull FullCard card) {
- MutableLiveData<FullCard> liveData = new MutableLiveData<>();
+ public WrappedLiveData<FullCard> createFullCard(long accountId, long localBoardId, long localStackId, @NonNull FullCard card) {
+ WrappedLiveData<FullCard> liveData = new WrappedLiveData<>();
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
User owner = dataBaseAdapter.getUserByUidDirectly(accountId, account.getUserName());
@@ -875,7 +1061,7 @@ public class SyncManager {
card.getCard().setAccountId(accountId);
card.getCard().setStatusEnum(DBStatus.LOCAL_EDITED);
card.getCard().setOrder(dataBaseAdapter.getHighestCardOrderInStack(localStackId) + 1);
- long localCardId = dataBaseAdapter.createCard(accountId, card.getCard());
+ long localCardId = dataBaseAdapter.createCardDirectly(accountId, card.getCard());
card.getCard().setLocalId(localCardId);
List<User> assignedUsers = card.getAssignedUsers();
@@ -901,11 +1087,28 @@ public class SyncManager {
}
}
- liveData.postValue(card);
+
if (serverAdapter.hasInternetConnection()) {
new SyncHelper(serverAdapter, dataBaseAdapter, null)
- .setResponseCallback(IResponseCallback.getDefaultResponseCallback(account))
+ .setResponseCallback(new IResponseCallback<Boolean>(account) {
+ @Override
+ public void onResponse(Boolean response) {
+ liveData.postValue(card);
+ }
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ if (throwable.getClass() == DeckException.class && ((DeckException)throwable).getHint().equals(DeckException.Hint.DEPENDENCY_NOT_SYNCED_YET)) {
+ liveData.postValue(card);
+ } else {
+ liveData.postError(throwable);
+ }
+ }
+ })
.doUpSyncFor(new CardDataProvider(null, board, stack));
+ } else {
+ liveData.postValue(card);
}
});
return liveData;
@@ -917,7 +1120,7 @@ public class SyncManager {
doAsync(() -> {
FullCard fullCard = dataBaseAdapter.getFullCardByLocalIdDirectly(card.getAccountId(), card.getLocalId());
if (fullCard == null) {
- throw new IllegalArgumentException("card to delete does not exist.");
+ throw new IllegalArgumentException("card with id " + card.getLocalId() + " to delete does not exist.");
}
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
@@ -935,12 +1138,12 @@ public class SyncManager {
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getCard().getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
card.getCard().setArchived(true);
- updateCardForArchive(account, stack, board, card, getCallbackToLiveDataConverter(account, liveData));
+ updateCardForArchive(stack, board, card, getCallbackToLiveDataConverter(account, liveData));
});
return liveData;
}
- private void updateCardForArchive(Account account, FullStack stack, Board board, FullCard card, @NonNull IResponseCallback<FullCard> callback) {
+ private void updateCardForArchive(FullStack stack, Board board, FullCard card, @NonNull IResponseCallback<FullCard> callback) {
new DataPropagationHelper(serverAdapter, dataBaseAdapter).updateEntity(new CardDataProvider(null, board, stack), card, callback);
}
@@ -952,29 +1155,34 @@ public class SyncManager {
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getCard().getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
card.getCard().setArchived(false);
- updateCardForArchive(account, stack, board, card, getCallbackToLiveDataConverter(account, liveData));
+ updateCardForArchive(stack, board, card, getCallbackToLiveDataConverter(account, liveData));
});
return liveData;
}
@AnyThread
- public WrappedLiveData<Void> archiveCardsInStack(long accountId, long stackLocalId) {
+ public WrappedLiveData<Void> archiveCardsInStack(long accountId, long stackLocalId, @NonNull FilterInformation filterInformation) {
WrappedLiveData<Void> liveData = new WrappedLiveData<>();
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);
+ 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()) {
+ latch.countDown();
+ continue;
+ }
card.getCard().setArchived(true);
- updateCardForArchive(account, stack, board, card, new IResponseCallback<FullCard>(account) {
+ 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();
@@ -996,21 +1204,35 @@ public class SyncManager {
}
@AnyThread
- public void archiveBoard(@NonNull Board board) {
+ public WrappedLiveData<FullBoard> archiveBoard(@NonNull Board board) {
+ WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
doAsync(() -> {
- FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
- b.getBoard().setArchived(true);
- updateBoard(b);
+ try {
+ FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
+ b.getBoard().setArchived(true);
+ updateBoard(b);
+ liveData.postValue(b);
+ } catch (Throwable e) {
+ liveData.postError(e);
+ }
});
+ return liveData;
}
@AnyThread
- public void dearchiveBoard(@NonNull Board board) {
+ public WrappedLiveData<FullBoard> dearchiveBoard(@NonNull Board board) {
+ WrappedLiveData<FullBoard> liveData = new WrappedLiveData<>();
doAsync(() -> {
- FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
- b.getBoard().setArchived(false);
- updateBoard(b);
+ try {
+ FullBoard b = dataBaseAdapter.getFullBoardByLocalIdDirectly(board.getAccountId(), board.getLocalId());
+ b.getBoard().setArchived(false);
+ updateBoard(b);
+ liveData.postValue(b);
+ } catch (Throwable e) {
+ liveData.postError(e);
+ }
});
+ return liveData;
}
@AnyThread
@@ -1055,6 +1277,7 @@ public class SyncManager {
liveData.postValue(dataBaseAdapter.getFullCardByLocalIdDirectly(card.getAccountId(), card.getLocalId()));
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -1092,39 +1315,44 @@ public class SyncManager {
public WrappedLiveData<Void> moveCard(long originAccountId, long originCardLocalId, long targetAccountId, long targetBoardLocalId, long targetStackLocalId) {
return LiveDataHelper.wrapInLiveData(() -> {
- FullCard originalCard = dataBaseAdapter.getFullCardByLocalIdDirectly(originAccountId, originCardLocalId);
+ final FullCard originalCard = dataBaseAdapter.getFullCardByLocalIdDirectly(originAccountId, originCardLocalId);
int newIndex = dataBaseAdapter.getHighestCardOrderInStack(targetStackLocalId) + 1;
- FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalCardIdDirectly(originCardLocalId);
+ final FullBoard originalBoard = dataBaseAdapter.getFullBoardByLocalCardIdDirectly(originCardLocalId);
// ### maybe shortcut possible? (just moved to another stack)
if (targetBoardLocalId == originalBoard.getLocalId()) {
reorder(originAccountId, originalCard, targetStackLocalId, newIndex);
return null;
}
// ### get rid of original card where it is now.
- Card originalInnerCard = originalCard.getCard();
- deleteCard(originalInnerCard);
+ final Card originalInnerCard = originalCard.getCard();
+ deleteCard(new Card(originalInnerCard));
// ### clone card itself
- Card targetCard = originalInnerCard;
- targetCard.setAccountId(targetAccountId);
- targetCard.setId(null);
- targetCard.setLocalId(null);
- targetCard.setStatusEnum(DBStatus.LOCAL_EDITED);
- targetCard.setStackId(targetStackLocalId);
- targetCard.setOrder(newIndex);
- //TODO: this needs to propagate to server as well, since anything else propagates as well (otherwise card isn't known on server)
- FullCard fullCardForServerPropagation = new FullCard();
- fullCardForServerPropagation.setCard(targetCard);
-
- Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId);
- FullBoard targetBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, targetBoardLocalId);
- FullStack targetFullStack = dataBaseAdapter.getFullStackByLocalIdDirectly(targetStackLocalId);
- User userOfTargetAccount = dataBaseAdapter.getUserByUidDirectly(targetAccountId, targetAccount.getUserName());
- CountDownLatch latch = new CountDownLatch(1);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new CardPropagationDataProvider(null, targetBoard.getBoard(), targetFullStack), fullCardForServerPropagation, new IResponseCallback<FullCard>(targetAccount) {
+ originalInnerCard.setAccountId(targetAccountId);
+ originalInnerCard.setId(null);
+ originalInnerCard.setLocalId(null);
+ originalInnerCard.setStatusEnum(DBStatus.LOCAL_EDITED);
+ originalInnerCard.setStackId(targetStackLocalId);
+ originalInnerCard.setOrder(newIndex);
+ originalInnerCard.setArchived(false);
+ originalInnerCard.setAttachmentCount(0);
+ originalInnerCard.setCommentsUnread(0);
+ final FullCard fullCardForServerPropagation = new FullCard();
+ fullCardForServerPropagation.setCard(originalInnerCard);
+
+ final Account targetAccount = dataBaseAdapter.getAccountByIdDirectly(targetAccountId);
+ final FullBoard targetBoard = dataBaseAdapter.getFullBoardByLocalIdDirectly(targetAccountId, targetBoardLocalId);
+ final FullStack targetFullStack = dataBaseAdapter.getFullStackByLocalIdDirectly(targetStackLocalId);
+ final User userOfTargetAccount = dataBaseAdapter.getUserByUidDirectly(targetAccountId, targetAccount.getUserName());
+ final CountDownLatch latch = new CountDownLatch(1);
+ ServerAdapter serverToUse = serverAdapter;
+ if (originAccountId != targetAccountId) {
+ serverToUse = new ServerAdapter(appContext, targetAccount.getName());
+ }
+ new DataPropagationHelper(serverToUse, dataBaseAdapter).createEntity(new CardPropagationDataProvider(null, targetBoard.getBoard(), targetFullStack), fullCardForServerPropagation, new IResponseCallback<FullCard>(targetAccount) {
@Override
public void onResponse(FullCard response) {
- targetCard.setId(response.getId());
- targetCard.setLocalId(response.getLocalId());
+ originalInnerCard.setId(response.getId());
+ originalInnerCard.setLocalId(response.getLocalId());
latch.countDown();
}
@@ -1134,8 +1362,10 @@ public class SyncManager {
throw new RuntimeException("unable to create card in moveCard target", throwable);
}
}, (FullCard entity, FullCard response) -> {
- response.getCard().setUserId(entity.getCard().getUserId());
+ response.getCard().setUserId(userOfTargetAccount.getLocalId());
response.getCard().setStackId(targetFullStack.getLocalId());
+ entity.getCard().setUserId(userOfTargetAccount.getLocalId());
+ entity.getCard().setStackId(targetFullStack.getLocalId());
});
try {
@@ -1145,7 +1375,7 @@ public class SyncManager {
throw new RuntimeException("error fulfilling countDownLatch", e);
}
- long newCardId = targetCard.getLocalId();
+ long newCardId = originalInnerCard.getLocalId();
// ### clone labels, assign them
// prepare
@@ -1154,7 +1384,7 @@ public class SyncManager {
List<AccessControl> aclOfTargetBoard = dataBaseAdapter.getAccessControlByLocalBoardIdDirectly(targetAccountId, targetBoard.getLocalId());
if (!hasManagePermission) {
for (AccessControl accessControl : aclOfTargetBoard) {
- if (accessControl.getUserId() == userOfTargetAccount.getLocalId() && accessControl.isPermissionManage()) {
+ if (accessControl.getUserId().equals(userOfTargetAccount.getLocalId()) && accessControl.isPermissionManage()) {
hasManagePermission = true;
break;
}
@@ -1178,10 +1408,10 @@ public class SyncManager {
originalLabel.setLocalId(null);
originalLabel.setStatusEnum(DBStatus.LOCAL_EDITED);
originalLabel.setAccountId(targetBoard.getAccountId());
- createAndAssignLabelToCard(originalBoard.getAccountId(), originalLabel, newCardId);
+ createAndAssignLabelToCard(targetBoard.getAccountId(), originalLabel, newCardId, serverToUse);
}
} else {
- assignLabelToCard(existingMatch, targetCard);
+ assignLabelToCard(existingMatch, originalInnerCard, serverToUse);
}
}
@@ -1194,7 +1424,7 @@ public class SyncManager {
boolean hasViewPermission = targetBoard.getBoard().getOwnerId() == assignedUser.getLocalId();
if (!hasViewPermission) {
for (AccessControl accessControl : aclOfTargetBoard) {
- if (accessControl.getUserId() == userOfTargetAccount.getLocalId()) {
+ if (accessControl.getUserId().equals(userOfTargetAccount.getLocalId())) {
// ACL exists, so viewing is granted
hasViewPermission = true;
break;
@@ -1202,7 +1432,7 @@ public class SyncManager {
}
}
if (hasViewPermission) {
- assignUserToCard(assignedUser, targetCard);
+ assignUserToCard(assignedUser, originalInnerCard);
}
}
}
@@ -1229,25 +1459,28 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
}
- }, (entity, response) -> {
- response.setBoardId(board.getLocalId());
- });
+ }, (entity, response) -> response.setBoardId(board.getLocalId()));
});
return liveData;
}
- @AnyThread
public MutableLiveData<Label> createAndAssignLabelToCard(long accountId, @NonNull Label label, long localCardId) {
+ return createAndAssignLabelToCard(accountId, label, localCardId, serverAdapter);
+ }
+
+ @AnyThread
+ private MutableLiveData<Label> createAndAssignLabelToCard(long accountId, @NonNull Label label, long localCardId, ServerAdapter serverAdapterToUse) {
MutableLiveData<Label> liveData = new MutableLiveData<>();
doAsync(() -> {
Account account = dataBaseAdapter.getAccountByIdDirectly(accountId);
Board board = dataBaseAdapter.getBoardByLocalCardIdDirectly(localCardId);
label.setAccountId(accountId);
- new DataPropagationHelper(serverAdapter, dataBaseAdapter).createEntity(new LabelDataProvider(null, board, null), label, new IResponseCallback<Label>(account) {
+ new DataPropagationHelper(serverAdapterToUse, dataBaseAdapter).createEntity(new LabelDataProvider(null, board, null), label, new IResponseCallback<Label>(account) {
@Override
public void onResponse(Label response) {
assignLabelToCard(response, dataBaseAdapter.getCardByLocalIdDirectly(accountId, localCardId));
@@ -1317,6 +1550,11 @@ public class SyncManager {
@AnyThread
public void assignLabelToCard(@NonNull Label label, @NonNull Card card) {
+ assignLabelToCard(label, card, serverAdapter);
+ }
+
+ @AnyThread
+ public void assignLabelToCard(@NonNull Label label, @NonNull Card card, ServerAdapter serverAdapterToUse) {
doAsync(() -> {
final long localLabelId = label.getLocalId();
final long localCardId = card.getLocalId();
@@ -1327,8 +1565,8 @@ public class SyncManager {
Stack stack = dataBaseAdapter.getStackByLocalIdDirectly(card.getStackId());
Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getBoardId());
Account account = dataBaseAdapter.getAccountByIdDirectly(card.getAccountId());
- if (serverAdapter.hasInternetConnection()) {
- serverAdapter.assignLabelToCard(board.getId(), stack.getId(), card.getId(), label.getId(), new IResponseCallback<Void>(account) {
+ if (serverAdapterToUse.hasInternetConnection()) {
+ serverAdapterToUse.assignLabelToCard(board.getId(), stack.getId(), card.getId(), label.getId(), new IResponseCallback<Void>(account) {
@Override
public void onResponse(Void response) {
@@ -1395,11 +1633,6 @@ public class SyncManager {
return findProposalsForLabelsToAssign(accountId, boardId, -1L);
}
- // TODO Difference to getFullBoardByid() ??? I think those methods are equal, we should drop one of them.
- public LiveData<FullBoard> getFullBoard(Long accountId, Long localId) {
- return dataBaseAdapter.getFullBoardById(accountId, localId);
- }
-
public LiveData<User> getUserByLocalId(long accountId, long localId) {
return dataBaseAdapter.getUserByLocalId(accountId, localId);
}
@@ -1417,12 +1650,12 @@ public class SyncManager {
return dataBaseAdapter.searchUserByUidOrDisplayName(accountId, boardId, notYetAssignedToLocalCardId, searchTerm);
}
- public LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long notYetAssignedInACL, final String searchTerm) {
- return dataBaseAdapter.searchUserByUidOrDisplayNameForACL(accountId, notYetAssignedInACL, searchTerm);
+ public UserSearchLiveData searchUserByUidOrDisplayNameForACL() {
+ return new UserSearchLiveData(dataBaseAdapter, serverAdapter);
}
- public LiveData<Board> getBoard(long accountId, long remoteId) {
- return dataBaseAdapter.getBoard(accountId, remoteId);
+ public LiveData<Board> getBoardByRemoteId(long accountId, long remoteId) {
+ return dataBaseAdapter.getBoardByRemoteId(accountId, remoteId);
}
public LiveData<Stack> getStackByRemoteId(long accountId, long localBoardId, long remoteId) {
@@ -1445,10 +1678,6 @@ public class SyncManager {
return dataBaseAdapter.searchNotYetAssignedLabelsByTitle(accountId, boardId, notYetAssignedToLocalCardId, searchTerm);
}
- public String getServerUrl() {
- return serverAdapter.getServerUrl();
- }
-
/**
* @see <a href="https://github.com/stefan-niedermann/nextcloud-deck/issues/360">reenable reorder</a>
*/
@@ -1456,7 +1685,7 @@ public class SyncManager {
public void reorder(long accountId, @NonNull FullCard movedCard, long newStackId, int newIndex) {
doAsync(() -> {
// read cards of new stack
- List<FullCard> cardsOfNewStack = dataBaseAdapter.getFullCardsForStackDirectly(accountId, newStackId);
+ List<FullCard> cardsOfNewStack = dataBaseAdapter.getFullCardsForStackDirectly(accountId, newStackId, null);
int newOrder = newIndex;
if (cardsOfNewStack.size() > newIndex) {
newOrder = cardsOfNewStack.get(newIndex).getCard().getOrder();
@@ -1526,7 +1755,7 @@ public class SyncManager {
Stack stack = dataBaseAdapter.getStackByLocalIdDirectly(movedCard.getCard().getStackId());
FullBoard board = dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, stack.getBoardId());
Account account = dataBaseAdapter.getAccountByIdDirectly(movedCard.getCard().getAccountId());
- new SyncHelper(serverAdapter, dataBaseAdapter, new Date()).setResponseCallback(new IResponseCallback<Boolean>(account) {
+ new SyncHelper(serverAdapter, dataBaseAdapter, Instant.now()).setResponseCallback(new IResponseCallback<Boolean>(account) {
@Override
public void onResponse(Boolean response) {
// doNothing();
@@ -1597,7 +1826,7 @@ public class SyncManager {
}
private void reorderAscending(@NonNull Card movedCard, @NonNull List<Card> cardsToReorganize, int startingAtOrder) {
- Date now = new Date();
+ final Instant now = Instant.now();
for (Card card : cardsToReorganize) {
card.setOrder(startingAtOrder);
if (card.getStatus() == DBStatus.UP_TO_DATE.getId()) {
@@ -1625,7 +1854,7 @@ public class SyncManager {
WrappedLiveData<Attachment> liveData = new WrappedLiveData<>();
doAsync(() -> {
Attachment attachment = populateAttachmentEntityForFile(new Attachment(), localCardId, mimeType, file);
- Date now = new Date();
+ final Instant now = Instant.now();
attachment.setLastModifiedLocal(now);
attachment.setCreatedAt(now);
FullCard card = dataBaseAdapter.getFullCardByLocalIdDirectly(accountId, localCardId);
@@ -1645,7 +1874,7 @@ public class SyncManager {
WrappedLiveData<Attachment> liveData = new WrappedLiveData<>();
doAsync(() -> {
Attachment attachment = populateAttachmentEntityForFile(existing, existing.getCardId(), mimeType, file);
- attachment.setLastModifiedLocal(new Date());
+ attachment.setLastModifiedLocal(Instant.now());
if (serverAdapter.hasInternetConnection()) {
FullCard card = dataBaseAdapter.getFullCardByLocalIdDirectly(accountId, existing.getCardId());
Stack stack = dataBaseAdapter.getStackByLocalIdDirectly(card.getCard().getStackId());
@@ -1658,6 +1887,7 @@ public class SyncManager {
liveData.postValue(response);
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
liveData.postError(throwable);
@@ -1670,15 +1900,14 @@ public class SyncManager {
@AnyThread
private static Attachment populateAttachmentEntityForFile(@NonNull Attachment target, long localCardId, @NonNull String mimeType, @NonNull File file) {
- Attachment attachment = target;
- attachment.setCardId(localCardId);
- attachment.setMimetype(mimeType);
- attachment.setData(file.getName());
- attachment.setFilename(file.getName());
- attachment.setBasename(file.getName());
- attachment.setLocalPath(file.getAbsolutePath());
- attachment.setFilesize(file.length());
- return attachment;
+ target.setCardId(localCardId);
+ target.setMimetype(mimeType);
+ target.setData(file.getName());
+ target.setFilename(file.getName());
+ target.setBasename(file.getName());
+ target.setLocalPath(file.getAbsolutePath());
+ target.setFilesize(file.length());
+ return target;
}
@AnyThread
@@ -1726,7 +1955,27 @@ public class SyncManager {
doAsync(() -> dataBaseAdapter.deleteSingleCardWidget(widgetId));
}
- private static class BooleanResultHolder {
- public boolean result = true;
+ public void addStackWidget(int appWidgetId, long accountId, long stackId, boolean darkTheme) {
+ doAsync(() -> dataBaseAdapter.createStackWidget(appWidgetId, accountId, stackId, darkTheme));
+ }
+
+ @WorkerThread
+ public StackWidgetModel getStackWidgetModelDirectly(int appWidgetId) throws NoSuchElementException {
+ final StackWidgetModel model = dataBaseAdapter.getStackWidgetModelDirectly(appWidgetId);
+ if (model == null) {
+ throw new NoSuchElementException();
+ }
+ return model;
+ }
+
+ public void deleteStackWidgetModel(int appWidgetId) {
+ doAsync(() -> dataBaseAdapter.deleteStackWidget(appWidgetId));
+ }
+
+ /**
+ * FIXME https://github.com/stefan-niedermann/nextcloud-deck/issues/640
+ */
+ 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());
}
}
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 4f41173e9..bff8ddfdb 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
@@ -13,18 +13,14 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.preference.PreferenceManager;
+import com.nextcloud.android.sso.api.ParsedResponse;
+
import java.io.File;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Date;
import java.util.List;
-import java.util.Locale;
-import java.util.TimeZone;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.api.ApiProvider;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
-import it.niedermann.nextcloud.deck.api.LastSyncUtil;
import it.niedermann.nextcloud.deck.api.RequestHelper;
import it.niedermann.nextcloud.deck.exceptions.OfflineException;
import it.niedermann.nextcloud.deck.model.AccessControl;
@@ -39,11 +35,12 @@ import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
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.projects.OcsProjectList;
+import it.niedermann.nextcloud.deck.model.ocs.user.GroupMemberUIDs;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
import it.niedermann.nextcloud.deck.model.propagation.CardUpdate;
import it.niedermann.nextcloud.deck.model.propagation.Reorder;
-import it.niedermann.nextcloud.deck.util.DateUtil;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.RequestBody;
@@ -53,18 +50,11 @@ import static it.niedermann.nextcloud.deck.util.MimeTypeUtil.TEXT_PLAIN;
public class ServerAdapter {
- private String prefKeyWifiOnly;
-
- private static final DateFormat API_FORMAT =
- new SimpleDateFormat("E, dd MMM yyyy hh:mm:ss z", Locale.US);
-
- static {
- API_FORMAT.setTimeZone(TimeZone.getTimeZone("GMT"));
- }
+ private final String prefKeyWifiOnly;
@NonNull
- private Context applicationContext;
- private ApiProvider provider;
+ private final Context applicationContext;
+ private final ApiProvider provider;
public ServerAdapter(@NonNull Context applicationContext) {
this(applicationContext, null);
@@ -135,32 +125,35 @@ public class ServerAdapter {
// return lastSyncHeader;
}
- // TODO not used
- private Date getLastSync(long accountId) {
- Date lastSync = DateUtil.nowInGMT();
- lastSync.setTime(LastSyncUtil.getLastSync(accountId));
+ public void getBoards(IResponseCallback<ParsedResponse<List<FullBoard>>> responseCallback) {
+ RequestHelper.request(provider, () ->
+ provider.getDeckAPI().getBoards(true, getLastSyncDateFormatted(responseCallback.getAccount().getId()), responseCallback.getAccount().getBoardsEtag()),
+ responseCallback);
+ }
- return lastSync;
+ public void getCapabilities(String eTag, IResponseCallback<ParsedResponse<Capabilities>> responseCallback) {
+ ensureInternetConnection();
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().getCapabilities(eTag), responseCallback);
}
- public void getBoards(IResponseCallback<List<FullBoard>> responseCallback) {
- RequestHelper.request(provider, () ->
- provider.getDeckAPI().getBoards(true, getLastSyncDateFormatted(responseCallback.getAccount().getId())),
- responseCallback);
+ public void getProjectsForCard(long remoteCardId, IResponseCallback<OcsProjectList> responseCallback) {
+ ensureInternetConnection();
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().getProjectsForCard(remoteCardId), responseCallback);
}
- public void getCapabilities(IResponseCallback<Capabilities> responseCallback) {
+ public void searchUser(String searchTerm, IResponseCallback<OcsUserList> responseCallback) {
ensureInternetConnection();
- RequestHelper.request(provider, () -> provider.getNextcloudAPI().getCapabilities(), responseCallback);
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().searchUser(searchTerm), responseCallback);
}
- public void getAllOcsUsers(IResponseCallback<OcsUserList> responseCallback) {
+
+ public void getSingleUserData(String userUid, IResponseCallback<OcsUser> responseCallback) {
ensureInternetConnection();
- RequestHelper.request(provider, () -> provider.getNextcloudAPI().getAllUsers(), responseCallback);
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().getSingleUserData(userUid), responseCallback);
}
- public void getOcsUserDetails(String ocsUserName, IResponseCallback<OcsUser> responseCallback) {
+ public void searchGroupMembers(String groupUID, IResponseCallback<GroupMemberUIDs> responseCallback) {
ensureInternetConnection();
- RequestHelper.request(provider, () -> provider.getNextcloudAPI().getUserDetails(ocsUserName), responseCallback);
+ RequestHelper.request(provider, () -> provider.getNextcloudAPI().searchGroupMembers(groupUID), responseCallback);
}
public void getActivitiesForCard(long cardId, IResponseCallback<List<it.niedermann.nextcloud.deck.model.ocs.Activity>> 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 c643289bd..982844f06 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
@@ -2,16 +2,15 @@ package it.niedermann.nextcloud.deck.persistence.sync.adapters.db;
import android.content.Context;
+import androidx.annotation.AnyThread;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.WorkerThread;
import androidx.lifecycle.LiveData;
import androidx.sqlite.db.SimpleSQLiteQuery;
-import org.jetbrains.annotations.NotNull;
-
+import java.time.Instant;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
@@ -26,10 +25,12 @@ import it.niedermann.nextcloud.deck.model.JoinCardWithUser;
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.appwidgets.StackWidgetModel;
import it.niedermann.nextcloud.deck.model.enums.DBStatus;
import it.niedermann.nextcloud.deck.model.enums.EDueType;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.full.FullStack;
import it.niedermann.nextcloud.deck.model.interfaces.AbstractRemoteEntity;
@@ -39,10 +40,16 @@ 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;
import it.niedermann.nextcloud.deck.model.ocs.comment.full.FullDeckComment;
+import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
+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.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;
@@ -57,28 +64,28 @@ public class DataBaseAdapter {
this.db = DeckDatabase.getInstance(applicationContext);
}
- @NotNull
+ @NonNull
public Context getContext() {
return context;
}
private <T extends AbstractRemoteEntity> void markAsEditedIfNeeded(T entity, boolean setStatus) {
if (!setStatus) return;
- entity.setLastModifiedLocal(new Date()); // now.
+ entity.setLastModifiedLocal(Instant.now());
entity.setStatusEnum(DBStatus.LOCAL_EDITED);
}
private <T extends AbstractRemoteEntity> void markAsDeletedIfNeeded(T entity, boolean setStatus) {
if (!setStatus) return;
entity.setStatusEnum(DBStatus.LOCAL_DELETED);
- entity.setLastModifiedLocal(new Date()); // now.
+ entity.setLastModifiedLocal(Instant.now());
}
public LiveData<Boolean> hasAccounts() {
return LiveDataHelper.postCustomValue(db.getAccountDao().countAccounts(), data -> data != null && data > 0);
}
- public LiveData<Board> getBoard(long accountId, long remoteId) {
+ public LiveData<Board> getBoardByRemoteId(long accountId, long remoteId) {
return distinctUntilChanged(db.getBoardDao().getBoardByRemoteId(accountId, remoteId));
}
@@ -180,24 +187,66 @@ public class DataBaseAdapter {
if (filter == null) {
return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardsForStack(accountId, localStackId), this::filterRelationsForCard);
}
+ return LiveDataHelper.interceptLiveData(db.getCardDao().getFilteredFullCardsForStack(getQueryForFilter(filter, accountId, localStackId)), this::filterRelationsForCard);
+
+ }
+
+ private void fillSqlWithListValues(StringBuilder query, List<Object> args, @NonNull List<? extends IRemoteEntity> entities) {
+ for (int i = 0; i < entities.size(); i++) {
+ if (i > 0) {
+ query.append(", ");
+ }
+ query.append("?");
+ args.add(entities.get(i).getLocalId());
+ }
+ }
+
+ @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));
+ }
+ @AnyThread
+ private SimpleSQLiteQuery getQueryForFilter(FilterInformation filter, long accountId, long localStackId) {
List<Object> args = new ArrayList<>();
- StringBuilder query = new StringBuilder("SELECT * FROM card c " +
- "WHERE accountId = ? AND stackId = ? ");
args.add(accountId);
args.add(localStackId);
+ StringBuilder query = new StringBuilder("SELECT * FROM card c " +
+ "WHERE accountId = ? AND stackId = ? ");
if (!filter.getLabels().isEmpty()) {
- query.append("and exists(select 1 from joincardwithlabel j where c.localId = cardId and labelId in (");
+ query.append("and (exists(select 1 from joincardwithlabel j where c.localId = cardId and labelId in (");
fillSqlWithListValues(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)) ");
+ } else {
+ query.append(") ");
+ }
+ } else if (filter.isNoAssignedLabel()) {
+ query.append("and not exists(select 1 from joincardwithlabel j where c.localId = cardId and j.status<>3) ");
}
if (!filter.getUsers().isEmpty()) {
- query.append("and exists(select 1 from joincardwithuser j where c.localId = cardId and userId in (");
+ query.append("and (exists(select 1 from joincardwithuser j where c.localId = cardId and userId in (");
fillSqlWithListValues(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)) ");
+ } else {
+ query.append(") ");
+ }
+ } else if (filter.isNoAssignedUser()) {
+ query.append("and not exists(select 1 from joincardwithuser j where c.localId = cardId and j.status<>3) ");
}
+
if (filter.getDueType() != EDueType.NO_FILTER) {
switch (filter.getDueType()) {
case NO_DUE:
@@ -219,24 +268,11 @@ public class DataBaseAdapter {
throw new IllegalArgumentException("Xou need to add your new EDueType value\"" + filter.getDueType() + "\" here!");
}
}
- query.append(" and status<>3 order by `order`, createdAt asc;");
- return LiveDataHelper.interceptLiveData(db.getCardDao().getFilteredFullCardsForStack(new SimpleSQLiteQuery(query.toString(), args.toArray())), this::filterRelationsForCard);
-
- }
-
- private void fillSqlWithListValues(StringBuilder query, List<Object> args, @NonNull List<? extends IRemoteEntity> entities) {
- for (int i = 0; i < entities.size(); i++) {
- if (i > 0) {
- query.append(", ");
- }
- query.append("?");
- args.add(entities.get(i).getLocalId());
+ if (filter.getArchiveStatus() != FilterInformation.EArchiveStatus.ALL) {
+ query.append(" and c.archived = " + (filter.getArchiveStatus() == FilterInformation.EArchiveStatus.ARCHIVED ? 1 : 0));
}
- }
-
- @WorkerThread
- public List<FullCard> getFullCardsForStackDirectly(long accountId, long localStackId) {
- return db.getCardDao().getFullCardsForStackDirectly(accountId, localStackId);
+ query.append(" and status<>3 order by `order`, createdAt asc;");
+ return new SimpleSQLiteQuery(query.toString(), args.toArray());
}
@WorkerThread
@@ -244,17 +280,20 @@ public class DataBaseAdapter {
return db.getUserDao().getUserByUidDirectly(accountId, uid);
}
+ @WorkerThread
public long createUser(long accountId, User user) {
user.setAccountId(accountId);
return db.getUserDao().insert(user);
}
+ @WorkerThread
public void updateUser(long accountId, User user, boolean setStatus) {
markAsEditedIfNeeded(user, setStatus);
user.setAccountId(accountId);
db.getUserDao().update(user);
}
+ @AnyThread
public LiveData<Label> getLabelByRemoteId(long accountId, long remoteId) {
return distinctUntilChanged(db.getLabelDao().getLabelByRemoteId(accountId, remoteId));
}
@@ -264,7 +303,8 @@ public class DataBaseAdapter {
return db.getLabelDao().getLabelByRemoteIdDirectly(accountId, remoteId);
}
- public long createLabel(long accountId, @NonNull Label label) {
+ @WorkerThread
+ public long createLabelDirectly(long accountId, @NonNull Label label) {
label.setAccountId(accountId);
return db.getLabelDao().insert(label);
}
@@ -320,6 +360,8 @@ public class DataBaseAdapter {
// readded!
existing.setStatusEnum(DBStatus.LOCAL_EDITED);
db.getJoinCardWithUserDao().update(existing);
+ } else if (existing != null) {
+ return;
} else {
JoinCardWithUser join = new JoinCardWithUser();
join.setCardId(localCardId);
@@ -344,6 +386,28 @@ public class DataBaseAdapter {
db.getJoinBoardWithLabelDao().deleteByBoardId(localBoardId);
}
+ public void deleteGroupMembershipsOfGroup(Long localGroupUserId) {
+ db.getUserInGroupDao().deleteByGroupId(localGroupUserId);
+ }
+
+ public void deleteBoardMembershipsOfBoard(Long localBoardId) {
+ db.getUserInBoardDao().deleteByBoardId(localBoardId);
+ }
+
+ public void addUserToGroup(Long localGroupUserId, Long localGroupMemberId) {
+ UserInGroup relation = new UserInGroup();
+ relation.setGroupId(localGroupUserId);
+ relation.setMemberId(localGroupMemberId);
+ db.getUserInGroupDao().insert(relation);
+ }
+
+ public void addUserToBoard(Long localUserId, Long localBoardId) {
+ UserInBoard relation = new UserInBoard();
+ relation.setBoardId(localBoardId);
+ relation.setUserId(localUserId);
+ db.getUserInBoardDao().insert(relation);
+ }
+
public void updateLabel(Label label, boolean setStatus) {
markAsEditedIfNeeded(label, setStatus);
db.getLabelDao().update(label);
@@ -391,6 +455,10 @@ public class DataBaseAdapter {
return distinctUntilChanged(db.getAccountDao().getAllAccounts());
}
+ public LiveData<List<Board>> getBoards(long accountId) {
+ return distinctUntilChanged(db.getBoardDao().getBoardsForAccount(accountId));
+ }
+
public LiveData<List<Board>> getBoards(long accountId, boolean archived) {
return distinctUntilChanged(
archived
@@ -431,8 +499,8 @@ public class DataBaseAdapter {
db.getBoardDao().update(board);
}
- public LiveData<List<FullStack>> getFullStacksForBoard(long accountId, long localBoardId) {
- return distinctUntilChanged(db.getStackDao().getFullStacksForBoard(accountId, localBoardId));
+ public LiveData<List<Stack>> getStacksForBoard(long accountId, long localBoardId) {
+ return distinctUntilChanged(db.getStackDao().getStacksForBoard(accountId, localBoardId));
}
@WorkerThread
@@ -440,27 +508,36 @@ public class DataBaseAdapter {
return db.getStackDao().getFullStacksForBoardDirectly(accountId, localBoardId);
}
+ @AnyThread
public LiveData<FullStack> getStack(long accountId, long localStackId) {
return distinctUntilChanged(db.getStackDao().getFullStack(accountId, localStackId));
}
+ @WorkerThread
public long createStack(long accountId, Stack stack) {
stack.setAccountId(accountId);
return db.getStackDao().insert(stack);
}
+ @WorkerThread
public void deleteStack(Stack stack, boolean setStatus) {
markAsDeletedIfNeeded(stack, setStatus);
db.getStackDao().update(stack);
}
+ @WorkerThread
public void deleteStackPhysically(Stack stack) {
db.getStackDao().delete(stack);
}
+ @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);
+ }
}
@WorkerThread
@@ -468,10 +545,16 @@ public class DataBaseAdapter {
return db.getCardDao().getCardByLocalIdDirectly(accountId, localCardId);
}
+ @AnyThread
public LiveData<FullCard> getCardByLocalId(long accountId, long localCardId) {
return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardByLocalId(accountId, localCardId), this::filterRelationsForCard);
}
+ @AnyThread
+ public LiveData<FullCardWithProjects> getCardWithProjectsByLocalId(long accountId, long localCardId) {
+ return LiveDataHelper.interceptLiveData(db.getCardDao().getFullCardWithProjectsByLocalId(accountId, localCardId), this::filterRelationsForCard);
+ }
+
@WorkerThread
public List<FullCard> getLocallyChangedCardsDirectly(long accountId) {
return db.getCardDao().getLocallyChangedCardsDirectly(accountId);
@@ -482,15 +565,27 @@ public class DataBaseAdapter {
return db.getCardDao().getLocallyChangedCardsByLocalStackIdDirectly(accountId, localStackId);
}
- public long createCard(long accountId, Card card) {
+ @WorkerThread
+ public long createCardDirectly(long accountId, Card card) {
card.setAccountId(accountId);
- return db.getCardDao().insert(card);
+ long newCardId = db.getCardDao().insert(card);
+
+ notifyStackWidgetsIfNeeded(card.getTitle(), card.getStackId());
+
+ return newCardId;
}
- public int getHighestCardOrderInStack(long localStackId){
+ @WorkerThread
+ public int getHighestCardOrderInStack(long localStackId) {
return db.getCardDao().getHighestOrderInStack(localStackId);
}
+ @WorkerThread
+ public int getHighestStackOrderInBoard(long localBoardId) {
+ return db.getStackDao().getHighestStackOrderInBoard(localBoardId);
+ }
+
+ @WorkerThread
public void deleteCard(Card card, boolean setStatus) {
markAsDeletedIfNeeded(card, setStatus);
if (setStatus) {
@@ -498,21 +593,35 @@ public class DataBaseAdapter {
} else {
deleteCardPhysically(card);
}
+
+ notifyStackWidgetsIfNeeded(card.getTitle(), card.getStackId());
}
+ @WorkerThread
public void deleteCardPhysically(Card card) {
db.getCardDao().delete(card);
}
+ @WorkerThread
public void updateCard(@NonNull Card card, boolean setStatus) {
markAsEditedIfNeeded(card, setStatus);
+ Long originalStackLocalId = db.getCardDao().getLocalStackIdByLocalCardId(card.getLocalId());
db.getCardDao().update(card);
if (db.getSingleCardWidgetModelDao().containsCardLocalId(card.getLocalId())) {
- DeckLog.info("Notifying widget about card changes for \"" + card.getTitle() + "\"");
+ DeckLog.info("Notifying " + SingleCardWidget.class.getSimpleName() + " about card changes for \"" + card.getTitle() + "\"");
SingleCardWidget.notifyDatasetChanged(context);
}
+ 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);
+ }
}
+ @WorkerThread
public long createAccessControl(long accountId, @NonNull AccessControl entity) {
entity.setAccountId(accountId);
return db.getAccessControlDao().insert(entity);
@@ -571,9 +680,9 @@ public class DataBaseAdapter {
return db.getUserDao().searchUserByUidOrDisplayName(accountId, boardId, notYetAssignedToLocalCardId, "%" + searchTerm.trim() + "%");
}
- public LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long notYetAssignedToACL, final String searchTerm) {
+ public List<User> searchUserByUidOrDisplayNameForACLDirectly(final long accountId, final long notYetAssignedToACL, final String searchTerm) {
validateSearchTerm(searchTerm);
- return db.getUserDao().searchUserByUidOrDisplayNameForACL(accountId, notYetAssignedToACL, "%" + searchTerm.trim() + "%");
+ return db.getUserDao().searchUserByUidOrDisplayNameForACLDirectly(accountId, notYetAssignedToACL, "%" + searchTerm.trim() + "%");
}
public LiveData<List<Label>> searchNotYetAssignedLabelsByTitle(final long accountId, final long boardId, final long notYetAssignedToLocalCardId, String searchTerm) {
@@ -618,9 +727,14 @@ public class DataBaseAdapter {
return db.getAttachmentDao().getLocallyChangedAttachmentsDirectly(accountId);
}
+ @WorkerThread
+ public List<Attachment> getLocallyChangedAttachmentsForStackDirectly(long localStackId) {
+ return db.getAttachmentDao().getLocallyChangedAttachmentsForStackDirectly(localStackId);
+ }
+
public long createAttachment(long accountId, @NonNull Attachment attachment) {
attachment.setAccountId(accountId);
- attachment.setCreatedAt(new Date());
+ attachment.setCreatedAt(Instant.now());
return db.getAttachmentDao().insert(attachment);
}
@@ -720,16 +834,24 @@ public class DataBaseAdapter {
return db.getJoinCardWithLabelDao().getAllDeletedJoinsWithRemoteIDs();
}
- public List<JoinCardWithLabel> getAllChangedJoins() {
+ public List<JoinCardWithLabel> getAllChangedLabelJoins() {
return db.getJoinCardWithLabelDao().getAllChangedJoins();
}
- public JoinCardWithLabel getRemoteIdsForJoin(Long localCardId, Long localLabelId) {
+ public List<JoinCardWithLabel> getAllChangedLabelJoinsForStack(Long localStackId) {
+ return db.getJoinCardWithLabelDao().getAllChangedJoinsForStack(localStackId);
+ }
+
+ public JoinCardWithLabel getAllChangedLabelJoinsWithRemoteIDs(Long localCardId, Long localLabelId) {
return db.getJoinCardWithLabelDao().getRemoteIdsForJoin(localCardId, localLabelId);
}
- public List<JoinCardWithUser> getAllDeletedUserJoinsWithRemoteIDs() {
- return db.getJoinCardWithUserDao().getDeletedJoinsWithRemoteIDs();
+ public List<JoinCardWithUser> getAllChangedUserJoinsWithRemoteIDs() {
+ return db.getJoinCardWithUserDao().getChangedJoinsWithRemoteIDs();
+ }
+
+ public List<JoinCardWithUser> getAllChangedUserJoinsWithRemoteIDsForStack(Long localStackId) {
+ return db.getJoinCardWithUserDao().getChangedJoinsWithRemoteIDsForStack(localStackId);
}
public void deleteJoinedLabelForCardPhysicallyByRemoteIDs(Long accountId, Long remoteCardId, Long remoteLabelId) {
@@ -837,11 +959,17 @@ public class DataBaseAdapter {
return db.getCommentDao().getCommentByLocalCardIdDirectly(localCardId);
}
+ @WorkerThread
public List<Card> getCardsWithLocallyChangedCommentsDirectly(Long accountId) {
return db.getCardDao().getCardsWithLocallyChangedCommentsDirectly(accountId);
}
@WorkerThread
+ public List<Card> getCardsWithLocallyChangedCommentsForStackDirectly(Long localStackId) {
+ return db.getCardDao().getCardsWithLocallyChangedCommentsForStackDirectly(localStackId);
+ }
+
+ @WorkerThread
public Long getLocalStackIdByRemoteStackIdDirectly(long accountId, Long stackId) {
return db.getStackDao().getLocalStackIdByRemoteStackIdDirectly(accountId, stackId);
}
@@ -910,11 +1038,89 @@ public class DataBaseAdapter {
db.getSingleCardWidgetModelDao().delete(model);
}
+ public long 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);
+ }
+
+ public StackWidgetModel getStackWidgetModelDirectly(int appWidgetId) {
+ return db.getStackWidgetModelDao().getStackWidgetByAppWidgetIdDirectly(appWidgetId);
+ }
+
+ public void deleteStackWidget(int appWidgetId) {
+ StackWidgetModel model = new StackWidgetModel();
+ model.setAppWidgetId(appWidgetId);
+ db.getStackWidgetModelDao().delete(model);
+ }
+
public LiveData<List<Account>> readAccountsForHostWithReadAccessToBoard(String host, long boardRemoteId) {
- return db.getAccountDao().readAccountsForHostWithReadAccessToBoard("%"+host+"%", boardRemoteId);
+ return db.getAccountDao().readAccountsForHostWithReadAccessToBoard("%" + host + "%", boardRemoteId);
}
public List<Account> readAccountsForHostWithReadAccessToBoardDirectly(String host, long boardRemoteId) {
- return db.getAccountDao().readAccountsForHostWithReadAccessToBoardDirectly("%"+host+"%", boardRemoteId);
+ return db.getAccountDao().readAccountsForHostWithReadAccessToBoardDirectly("%" + host + "%", boardRemoteId);
+ }
+
+ public Board getBoardForAccountByNameDirectly(long account, String title) {
+ return db.getBoardDao().getBoardForAccountByNameDirectly(account, title);
+ }
+
+ public OcsProject getProjectByRemoteIdDirectly(long accountId, Long remoteId) {
+ return db.getOcsProjectDao().getProjectByRemoteIdDirectly(accountId, remoteId);
+ }
+
+ public Long createProjectDirectly(long accountId, OcsProject entity) {
+ entity.setAccountId(accountId);
+ return db.getOcsProjectDao().insert(entity);
+ }
+
+ public void deleteProjectResourcesForProjectIdDirectly(Long localProjectId) {
+ db.getOcsProjectResourceDao().deleteByProjectId(localProjectId);
+ }
+
+ public void updateProjectDirectly(long accountId, OcsProject entity) {
+ entity.setAccountId(accountId);
+ db.getOcsProjectDao().update(entity);
+ }
+
+ public void deleteProjectDirectly(OcsProject ocsProject) {
+ db.getOcsProjectResourceDao().deleteByProjectId(ocsProject.getLocalId());
+ db.getOcsProjectDao().delete(ocsProject);
+ }
+
+ public Long createProjectResourceDirectly(Long accountId, OcsProjectResource resource) {
+ resource.setAccountId(accountId);
+ return db.getOcsProjectResourceDao().insert(resource);
+ }
+
+ public int countProjectResourcesInProjectDirectly(Long projectLocalId) {
+ return db.getOcsProjectResourceDao().countProjectResourcesInProjectDirectly(projectLocalId);
+ }
+
+ public LiveData<Integer> countProjectResourcesInProject(Long projectLocalId) {
+ return db.getOcsProjectResourceDao().countProjectResourcesInProject(projectLocalId);
+ }
+
+ public LiveData<List<OcsProjectResource>> getResourcesByLocalProjectId(Long projectLocalId) {
+ return db.getOcsProjectResourceDao().getResourcesByLocalProjectId(projectLocalId);
+ }
+
+ public void assignCardToProjectIfMissng(Long accountId, Long localProjectId, Long remoteCardId) {
+ Card card = db.getCardDao().getCardByRemoteIdDirectly(accountId, remoteCardId);
+ if (card != null) {
+ JoinCardWithProject existing = db.getJoinCardWithOcsProjectDao().getAssignmentByCardIdAndProjectIdDirectly(card.getLocalId(), localProjectId);
+ if (existing == null) {
+ JoinCardWithProject assignment = new JoinCardWithProject();
+ assignment.setStatus(DBStatus.UP_TO_DATE.getId());
+ assignment.setCardId(card.getLocalId());
+ assignment.setProjectId(localProjectId);
+ db.getJoinCardWithOcsProjectDao().insert(assignment);
+ }
+ }
}
}
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/DateTypeConverter.java
index 6b198a502..f6811e033 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/DateTypeConverter.java
@@ -2,17 +2,17 @@ package it.niedermann.nextcloud.deck.persistence.sync.adapters.db;
import androidx.room.TypeConverter;
-import java.util.Date;
+import java.time.Instant;
public class DateTypeConverter {
@TypeConverter
- public static Date toDate(Long value) {
- return value == null ? null : new Date(value);
+ public static Instant toInstant(Long value) {
+ return value == null ? null : Instant.ofEpochMilli(value);
}
@TypeConverter
- public static Long toLong(Date value) {
- return value == null ? null : value.getTime();
+ public static Long fromInstant(Instant value) {
+ return value == null ? null : value.toEpochMilli();
}
} \ No newline at end of file
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 0e86557fa..a491946ed 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,8 +1,11 @@
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;
@@ -12,6 +15,7 @@ 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;
@@ -28,10 +32,16 @@ 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;
+import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
+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.singlecard.SingleCardWidgetModel;
import it.niedermann.nextcloud.deck.persistence.sync.SyncWorker;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.AccessControlDao;
@@ -49,9 +59,15 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.JoinCardWit
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.LabelDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.MentionDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.PermissionDao;
-import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.SingleCardWidgetModelDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.StackDao;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserInBoardDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserInGroupDao;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects.JoinCardWithOcsProjectDao;
+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;
@Database(
entities = {
@@ -73,9 +89,15 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.UserDao;
DeckComment.class,
Mention.class,
SingleCardWidgetModel.class,
+ StackWidgetModel.class,
+ OcsProject.class,
+ OcsProjectResource.class,
+ JoinCardWithProject.class,
+ UserInGroup.class,
+ UserInBoard.class,
},
exportSchema = false,
- version = 15
+ version = 23
)
@TypeConverters({DateTypeConverter.class})
public abstract class DeckDatabase extends RoomDatabase {
@@ -175,8 +197,204 @@ public abstract class DeckDatabase extends RoomDatabase {
}
};
- public static final RoomDatabase.Callback ON_CREATE_CALLBACK = new RoomDatabase.Callback() {
+ 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);
@@ -216,6 +434,26 @@ public abstract class DeckDatabase extends RoomDatabase {
.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)
.fallbackToDestructiveMigration()
.addCallback(ON_CREATE_CALLBACK)
.build();
@@ -256,4 +494,16 @@ public abstract class DeckDatabase extends RoomDatabase {
public abstract MentionDao getMentionDao();
public abstract SingleCardWidgetModelDao getSingleCardWidgetModelDao();
+
+ public abstract StackWidgetModelDao getStackWidgetModelDao();
+
+ public abstract OcsProjectDao getOcsProjectDao();
+
+ public abstract OcsProjectResourceDao getOcsProjectResourceDao();
+
+ public abstract JoinCardWithOcsProjectDao getJoinCardWithOcsProjectDao();
+
+ public abstract UserInGroupDao getUserInGroupDao();
+
+ public abstract UserInBoardDao getUserInBoardDao();
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java
index 2d0887903..8f7ee1cba 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/AttachmentDao.java
@@ -25,6 +25,10 @@ public interface AttachmentDao extends GenericDao<Attachment> {
@Query("SELECT * FROM attachment WHERE accountId = :accountId and (status<>1 or id is null or lastModified <> lastModifiedLocal)")
List<Attachment> getLocallyChangedAttachmentsDirectly(long accountId);
+ @Query("SELECT a.* FROM attachment a inner join card c on c.localId = a.cardId " +
+ "WHERE c.stackId = :localStackId and (a.status<>1 or a.id is null or a.lastModified <> a.lastModifiedLocal)")
+ List<Attachment> getLocallyChangedAttachmentsForStackDirectly(long localStackId);
+
@Query("SELECT * FROM attachment WHERE accountId = :accountId and cardId = :localCardId")
List<Attachment> getAttachmentsForLocalCardIdDirectly(long accountId, Long localCardId);
} \ No newline at end of file
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 bd189e846..6cb322aa3 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
@@ -13,6 +13,9 @@ import it.niedermann.nextcloud.deck.model.full.FullBoard;
@Dao
public interface BoardDao extends GenericDao<Board> {
+ @Query("SELECT * FROM board WHERE accountId = :accountId and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
+ LiveData<List<Board>> getBoardsForAccount(final long accountId);
+
@Query("SELECT * FROM board WHERE accountId = :accountId and archived = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3 order by title asc")
LiveData<List<Board>> getArchivedBoardsForAccount(final long accountId);
@@ -52,7 +55,8 @@ public interface BoardDao extends GenericDao<Board> {
@Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :localCardId")
Board getBoardByLocalCardIdDirectly(long localCardId);
- @Query("SELECT b.* FROM board b JOIN stack s ON s.boardId = b.localId JOIN card c ON c.localId = :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);
@Transaction
@@ -72,4 +76,7 @@ public interface BoardDao extends GenericDao<Board> {
@Query("SELECT count(*) FROM board WHERE accountId = :accountId and archived = 1 and (deletedAt = 0 or deletedAt is null) and status <> 3")
LiveData<Integer> countArchivedBoards(long accountId);
+
+ @Query("SELECT * FROM board WHERE accountId = :accountId and title = :title")
+ Board getBoardForAccountByNameDirectly(long accountId, String title);
} \ 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 57163112a..82bdf1b8d 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
@@ -11,6 +11,7 @@ import java.util.List;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
@Dao
public interface CardDao extends GenericDao<Card> {
@@ -36,17 +37,24 @@ public interface CardDao extends GenericDao<Card> {
@Query("SELECT * FROM card WHERE accountId = :accountId AND archived = 0 AND stackId = :localStackId and status<>3 order by `order`, createdAt asc")
LiveData<List<FullCard>> getFullCardsForStack(final long accountId, final long localStackId);
- @Transaction // v not deleted!
+ @Transaction
@RawQuery(observedEntities = Card.class)
LiveData<List<FullCard>> getFilteredFullCardsForStack(SupportSQLiteQuery query);
@Transaction
+ @RawQuery(observedEntities = Card.class)
+ List<FullCard> getFilteredFullCardsForStackDirectly(SupportSQLiteQuery query);
+
+ @Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId AND stackId = :localStackId order by `order`, createdAt asc")
List<FullCard> getFullCardsForStackDirectly(final long accountId, final long localStackId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localCardId")
LiveData<FullCard> getFullCardByLocalId(final long accountId, final long localCardId);
+ @Transaction
+ @Query("SELECT * FROM card WHERE accountId = :accountId and localId = :localCardId")
+ LiveData<FullCardWithProjects> getFullCardWithProjectsByLocalId(final long accountId, final long localCardId);
@Transaction
@Query("SELECT * FROM card WHERE accountId = :accountId and id = :remoteId")
@@ -66,9 +74,15 @@ public interface CardDao extends GenericDao<Card> {
@Query("SELECT * FROM card c WHERE accountId = :accountId and exists ( select 1 from DeckComment dc where dc.objectId = c.localId and dc.status<>1)")
List<Card> getCardsWithLocallyChangedCommentsDirectly(Long accountId);
+ @Query("SELECT * FROM card c WHERE stackId = :localStackId and exists ( select 1 from DeckComment dc where dc.objectId = c.localId and dc.status<>1)")
+ List<Card> getCardsWithLocallyChangedCommentsForStackDirectly(Long localStackId);
+
@Query("SELECT count(*) FROM card c WHERE accountId = :accountId and stackId = :localStackId and status <> 3")
LiveData<Integer> countCardsInStack(long accountId, long localStackId);
@Query("SELECT coalesce(MAX(`order`), -1) FROM card c WHERE stackId = :localStackId and status <> 3")
Integer getHighestOrderInStack(Long localStackId);
+
+ @Query("SELECT c.stackId FROM card c WHERE localId = :localCardId")
+ Long getLocalStackIdByLocalCardId(Long localCardId);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java
index c96c64e7d..26e6c65a8 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithLabelDao.java
@@ -40,6 +40,9 @@ public interface JoinCardWithLabelDao extends GenericDao<JoinCardWithLabel> {
@Query("select * from joincardwithlabel WHERE status <> 1") // not UP_TO_DATE
List<JoinCardWithLabel> getAllChangedJoins();
+ @Query("select j.* from joincardwithlabel j inner join card c on j.cardId = c.localId WHERE c.stackId = :localStackId and j.status <> 1") // not UP_TO_DATE
+ List<JoinCardWithLabel> getAllChangedJoinsForStack(Long localStackId);
+
@Query("delete from joincardwithlabel " +
"where cardId = (select c.localId from card c where c.accountId = :accountId and c.id = :remoteCardId) " +
"and labelId = (select l.localId from label l where l.accountId = :accountId and l.id = :remoteLabelId)")
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java
index 46554685f..416d52eed 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/JoinCardWithUserDao.java
@@ -25,7 +25,14 @@ public interface JoinCardWithUserDao extends GenericDao<JoinCardWithUser> {
"inner join card c on j.cardId = c.localId " +
"inner join user u on j.userId = u.localId " +
"WHERE j.status <> 1") // not UP_TO_DATE
- List<JoinCardWithUser> getDeletedJoinsWithRemoteIDs();
+ List<JoinCardWithUser> getChangedJoinsWithRemoteIDs();
+
+ @Query("select u.localId as userId, c.id as cardId, j.status from joincardwithuser j " +
+ "inner join card c on j.cardId = c.localId " +
+ "inner join user u on j.userId = u.localId " +
+ "WHERE c.stackId = :localStackId " +
+ "AND j.status <> 1") // not UP_TO_DATE
+ List<JoinCardWithUser> getChangedJoinsWithRemoteIDsForStack(Long localStackId);
@Query("delete from joincardwithuser " +
"where cardId = (select c.localId from card c where c.accountId = :accountId and c.id = :remoteCardId) " +
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 0fbccbe08..feb7e453b 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
@@ -13,7 +13,7 @@ import it.niedermann.nextcloud.deck.model.full.FullStack;
@Dao
public interface StackDao extends GenericDao<Stack> {
- @Query("SELECT * FROM stack WHERE accountId = :accountId AND boardId = :localBoardId order by `order` asc")
+ @Query("SELECT * FROM stack WHERE accountId = :accountId AND boardId = :localBoardId and status<>3 and (deletedAt is null or deletedAt = 0) order by `order` asc")
LiveData<List<Stack>> getStacksForBoard(final long accountId, final long localBoardId);
@Query("SELECT * FROM stack WHERE accountId = :accountId and boardId = :localBoardId and id = :remoteId")
@@ -31,10 +31,6 @@ public interface StackDao extends GenericDao<Stack> {
FullStack getFullStackByRemoteIdDirectly(final long accountId, final long localBoardId, final long remoteId);
@Transaction
- @Query("SELECT * FROM stack WHERE accountId = :accountId AND boardId = :localBoardId and status<>3 and (deletedAt is null or deletedAt = 0) order by `order` asc")
- LiveData<List<FullStack>> getFullStacksForBoard(final long accountId, final long localBoardId);
-
- @Transaction
@Query("SELECT * FROM stack WHERE accountId = :accountId and boardId = :localBoardId and id = :remoteId")
LiveData<FullStack> getFullStackByRemoteId(final long accountId, final long localBoardId, final long remoteId);
@@ -56,4 +52,7 @@ public interface StackDao extends GenericDao<Stack> {
@Query("SELECT localId FROM stack s WHERE accountId = :accountId and id = :stackId")
Long getLocalStackIdByRemoteStackIdDirectly(long accountId, Long stackId);
+
+ @Query("SELECT coalesce(MAX(`order`), -1) FROM stack s WHERE boardId = :localBoardId")
+ Integer getHighestStackOrderInBoard(long localBoardId);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java
index 6f712d07f..6e42cdaae 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserDao.java
@@ -26,12 +26,16 @@ public interface UserDao extends GenericDao<User> {
" where ju.userId = u.localId" +
" and ju.cardId = :notYetAssignedToLocalCardId AND status <> 3" + // not LOCAL_DELETED
" )" +
- " AND" +
- " (" +
- " EXISTS (" +
- " select 1 from accesscontrol" +
- " where userId = u.localId and boardId = :boardId" +
- " )" +
+ " AND ( " +
+ " EXISTS (" +
+ " select 1 from userinboard where boardId = :boardId AND userId = u.localId" +
+ " )" +
+ " OR" +
+ " EXISTS (" +
+ " select 1 from accesscontrol" + // v GROUP!
+ " where (userId = u.localId OR (type = 1 and exists(select 1 from UserInGroup uig where uig.memberId = u.localId and uig.groupId = userId))) " +
+ " and boardId = :boardId and status <> 3" +
+ " )" +
" OR" +
" EXISTS (" +
" select 1 from board where localId = :boardId AND ownerId = u.localId" +
@@ -48,7 +52,7 @@ public interface UserDao extends GenericDao<User> {
"and ( uid LIKE :searchTerm or displayname LIKE :searchTerm or primaryKey LIKE :searchTerm ) " +
"and u.localId <> (select b.ownerId from board b where localId = :boardId)" +
"ORDER BY u.displayname")
- LiveData<List<User>> searchUserByUidOrDisplayNameForACL(final long accountId, final long boardId, final String searchTerm);
+ List<User> searchUserByUidOrDisplayNameForACLDirectly(final long accountId, final long boardId, final String searchTerm);
@Query("SELECT * FROM user WHERE accountId = :accountId and uid = :uid")
User getUserByUidDirectly(final long accountId, final String uid);
@@ -66,12 +70,16 @@ public interface UserDao extends GenericDao<User> {
" where ju.userId = u.localId" +
" and ju.cardId = :notAssignedToLocalCardId AND status <> 3" + // not LOCAL_DELETED
" )" +
- " AND" +
- " (" +
- " EXISTS (" +
- " select 1 from accesscontrol" +
- " where userId = u.localId and boardId = :boardId" +
- " )" +
+ " AND ( " +
+ " EXISTS (" +
+ " select 1 from userinboard where boardId = :boardId AND userId = u.localId" +
+ " )" +
+ " OR" +
+ " EXISTS (" +
+ " select 1 from accesscontrol" + // v GROUP!
+ " where (userId = u.localId OR (type = 1 and exists(select 1 from UserInGroup uig where uig.memberId = u.localId and uig.groupId = userId))) " +
+ " and boardId = :boardId and status <> 3" +
+ " )" +
" OR" +
" EXISTS (" +
" select 1 from board where localId = :boardId AND ownerId = u.localId" +
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInBoardDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInBoardDao.java
new file mode 100644
index 000000000..7a0476c53
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInBoardDao.java
@@ -0,0 +1,12 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import it.niedermann.nextcloud.deck.model.relations.UserInBoard;
+
+@Dao
+public interface UserInBoardDao extends GenericDao<UserInBoard> {
+ @Query("DELETE FROM userinboard WHERE boardId = :localId")
+ void deleteByBoardId(long localId);
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInGroupDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInGroupDao.java
new file mode 100644
index 000000000..fb3f5b26e
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/UserInGroupDao.java
@@ -0,0 +1,12 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import it.niedermann.nextcloud.deck.model.relations.UserInGroup;
+
+@Dao
+public interface UserInGroupDao extends GenericDao<UserInGroup> {
+ @Query("DELETE FROM useringroup WHERE groupId = :localId")
+ void deleteByGroupId(long localId);
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/JoinCardWithOcsProjectDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/JoinCardWithOcsProjectDao.java
new file mode 100644
index 000000000..d132796e4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/JoinCardWithOcsProjectDao.java
@@ -0,0 +1,13 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import it.niedermann.nextcloud.deck.model.ocs.projects.JoinCardWithProject;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface JoinCardWithOcsProjectDao extends GenericDao<JoinCardWithProject> {
+ @Query("select * from JoinCardWithProject where projectId = :localProjectId and cardId = :localCardId")
+ JoinCardWithProject getAssignmentByCardIdAndProjectIdDirectly(Long localCardId, Long localProjectId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectDao.java
new file mode 100644
index 000000000..fb0e3f836
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectDao.java
@@ -0,0 +1,13 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface OcsProjectDao extends GenericDao<OcsProject> {
+ @Query("select * from OcsProject where accountId = :accountId and id = :remoteId")
+ OcsProject getProjectByRemoteIdDirectly(long accountId, Long remoteId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectResourceDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectResourceDao.java
new file mode 100644
index 000000000..8a544667a
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/projects/OcsProjectResourceDao.java
@@ -0,0 +1,25 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.projects;
+
+import androidx.lifecycle.LiveData;
+import androidx.room.Dao;
+import androidx.room.Query;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface OcsProjectResourceDao extends GenericDao<OcsProjectResource> {
+ @Query("delete from OcsProjectResource where projectId = :localProjectId")
+ void deleteByProjectId(Long localProjectId);
+
+ @Query("select * from OcsProjectResource where projectId = :localProjectId")
+ LiveData<List<OcsProjectResource>> getResourcesByLocalProjectId(Long localProjectId);
+
+ @Query("select count(id) from OcsProjectResource where projectId = :localProjectId")
+ int countProjectResourcesInProjectDirectly(Long localProjectId);
+
+ @Query("select count(id) from OcsProjectResource where projectId = :localProjectId")
+ LiveData<Integer> countProjectResourcesInProject(Long localProjectId);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/SingleCardWidgetModelDao.java
index 0c2a485c1..a26269c30 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/SingleCardWidgetModelDao.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/SingleCardWidgetModelDao.java
@@ -1,4 +1,4 @@
-package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao;
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets;
import androidx.room.Dao;
import androidx.room.Query;
@@ -6,6 +6,7 @@ import androidx.room.Transaction;
import it.niedermann.nextcloud.deck.model.full.FullSingleCardWidgetModel;
import it.niedermann.nextcloud.deck.model.widget.singlecard.SingleCardWidgetModel;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
@Dao
public interface SingleCardWidgetModelDao extends GenericDao<SingleCardWidgetModel> {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/StackWidgetModelDao.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/StackWidgetModelDao.java
new file mode 100644
index 000000000..9b370e32b
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/dao/widgets/StackWidgetModelDao.java
@@ -0,0 +1,19 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.widgets;
+
+import androidx.room.Dao;
+import androidx.room.Query;
+import androidx.room.Transaction;
+
+import it.niedermann.nextcloud.deck.model.appwidgets.StackWidgetModel;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.dao.GenericDao;
+
+@Dao
+public interface StackWidgetModelDao extends GenericDao<StackWidgetModel> {
+
+ @Query("SELECT * FROM stackwidgetmodel WHERE appwidgetid = :appWidgetId")
+ StackWidgetModel getStackWidgetByAppWidgetIdDirectly(final int appWidgetId);
+
+ @Transaction
+ @Query("SELECT EXISTS (SELECT 1 FROM stackwidgetmodel WHERE stackId in (:stackLocalIds))")
+ boolean containsStackLocalId(final long... stackLocalIds);
+}
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 038fcbad8..003bceb26 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,5 +1,6 @@
package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util;
+import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.MutableLiveData;
@@ -28,10 +29,13 @@ 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(null);
+ postValue(locallyCreatedEntity);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java
new file mode 100644
index 000000000..fca4ca369
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/Debouncer.java
@@ -0,0 +1,75 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.extrawurst;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.TimeUnit;
+
+public class Debouncer <T> {
+ private final ScheduledExecutorService sched = Executors.newScheduledThreadPool(1);
+ private final ConcurrentHashMap<T, TimerTask> delayedMap = new ConcurrentHashMap<T, TimerTask>();
+ private final Callback<T> callback;
+ private final int interval;
+
+ public Debouncer(Callback<T> c, int interval) {
+ this.callback = c;
+ this.interval = interval;
+ }
+
+ public void call(T key) {
+ TimerTask task = new TimerTask(key);
+
+ TimerTask prev;
+ do {
+ prev = delayedMap.putIfAbsent(key, task);
+ if (prev == null)
+ sched.schedule(task, interval, TimeUnit.MILLISECONDS);
+ // Exit only if new task was added to map, or existing task was extended successfully
+ } while (prev != null && !prev.extend());
+ }
+
+ public void terminate() {
+ sched.shutdownNow();
+ }
+
+ public interface Callback<T> {
+ void call(T key);
+ }
+
+ // The task that wakes up when the wait time elapses
+ private class TimerTask implements Runnable {
+ private final T key;
+ private long dueTime;
+ private final Object lock = new Object();
+
+ public TimerTask(T key) {
+ this.key = key;
+ extend();
+ }
+
+ public boolean extend() {
+ synchronized (lock) {
+ if (dueTime < 0) // Task has been shutdown
+ return false;
+ dueTime = System.currentTimeMillis() + interval;
+ return true;
+ }
+ }
+
+ public void run() {
+ synchronized (lock) {
+ long remaining = dueTime - System.currentTimeMillis();
+ if (remaining > 0) { // Re-schedule task
+ sched.schedule(this, remaining, TimeUnit.MILLISECONDS);
+ } else { // Mark as terminated and invoke callback
+ dueTime = -1;
+ try {
+ callback.call(key);
+ } finally {
+ delayedMap.remove(key);
+ }
+ }
+ }
+ }
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java
new file mode 100644
index 000000000..179d816eb
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/adapters/db/util/extrawurst/UserSearchLiveData.java
@@ -0,0 +1,93 @@
+package it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.extrawurst;
+
+import androidx.lifecycle.MediatorLiveData;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.exceptions.OfflineException;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.User;
+import it.niedermann.nextcloud.deck.model.enums.DBStatus;
+import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
+import it.niedermann.nextcloud.deck.model.ocs.user.OcsUserList;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
+
+public class UserSearchLiveData extends MediatorLiveData<List<User>> implements Debouncer.Callback<Long> {
+
+ private static final int DEBOUNCE_TIME = 300; // ms
+ private DataBaseAdapter db;
+ private ServerAdapter server;
+ long accountId;
+ String searchTerm;
+ long notYetAssignedInACL;
+ private Debouncer<Long> debouncer = new Debouncer<>(this, DEBOUNCE_TIME);
+
+ public UserSearchLiveData(DataBaseAdapter db, ServerAdapter server) {
+ this.db = db;
+ this.server = server;
+ }
+
+ public UserSearchLiveData search(long accountId, long notYetAssignedInACL, String searchTerm) {
+ this.accountId = accountId;
+ this.searchTerm = searchTerm;
+ this.notYetAssignedInACL = notYetAssignedInACL;
+ new Thread(() -> debouncer.call(notYetAssignedInACL)).start();
+ return this;
+ }
+
+
+ @Override
+ public void call(Long key) {
+ if (key!=notYetAssignedInACL){
+ return;
+ }
+
+ final String term = String.copyValueOf(searchTerm.toCharArray());
+
+ postCurrentFromDB(term);
+
+ if (server.hasInternetConnection()) {
+ try {
+ Account account = db.getAccountByIdDirectly(accountId);
+ server.searchUser(term, new IResponseCallback<OcsUserList>(account) {
+ @Override
+ public void onResponse(OcsUserList response) {
+ if (response == null || response.getUsers().isEmpty()){
+ return;
+ }
+ for (OcsUser user : response.getUsers()) {
+ User existingUser = db.getUserByUidDirectly(accountId, user.getId());
+ if (existingUser == null) {
+ User newUser = new User();
+ newUser.setStatus(DBStatus.UP_TO_DATE.getId());
+ newUser.setPrimaryKey(user.getId());
+ newUser.setUid(user.getId());
+ newUser.setDisplayname(user.getDisplayName());
+ db.createUser(accountId, newUser);
+ }
+ }
+ if (!term.equals(searchTerm)) {
+ return;
+ }
+ postCurrentFromDB(term);
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ }
+ });
+ } catch (OfflineException e) {
+ DeckLog.logError(e);
+ }
+ }
+ }
+
+ private void postCurrentFromDB(String term) {
+ List<User> foundInDB = db.searchUserByUidOrDisplayNameForACLDirectly(accountId, notYetAssignedInACL, term);
+ postValue(foundInDB);
+ }
+}
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 9590f5abd..782b6d951 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
@@ -34,27 +34,31 @@ public class DataPropagationHelper {
entity.setLocalId(newID);
boolean connected = serverAdapter.hasInternetConnection();
if (connected) {
- provider.createOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(new Account(accountId)) {
- @Override
- public void onResponse(T response) {
- new Thread(() -> {
- response.setAccountId(accountId);
- response.setLocalId(newID);
- if (actionOnResponse!= null) {
- actionOnResponse.onResponse(entity, response);
- }
- response.setStatus(DBStatus.UP_TO_DATE.getId());
- provider.updateInDB(dataBaseAdapter, accountId, response, false);
- callback.onResponse(response);
- }).start();
- }
+ try {
+ provider.createOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(new Account(accountId)) {
+ @Override
+ public void onResponse(T response) {
+ new Thread(() -> {
+ response.setAccountId(accountId);
+ response.setLocalId(newID);
+ if (actionOnResponse != null) {
+ actionOnResponse.onResponse(entity, response);
+ }
+ response.setStatus(DBStatus.UP_TO_DATE.getId());
+ provider.updateInDB(dataBaseAdapter, accountId, response, false);
+ callback.onResponse(response);
+ }).start();
+ }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- new Thread(() -> callback.onError(throwable)).start();
- }
- }, entity);
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ new Thread(() -> callback.onError(throwable, entity)).start();
+ }
+ }, entity);
+ } catch (Throwable t) {
+ callback.onError(t, entity);
+ }
} else {
callback.onResponse(entity);
}
@@ -71,24 +75,28 @@ public class DataPropagationHelper {
}
boolean connected = serverAdapter.hasInternetConnection();
if (entity.getId() != null && connected) {
- provider.updateOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(new Account(accountId)) {
- @Override
- public void onResponse(T response) {
- new Thread(() -> {
- entity.setStatus(DBStatus.UP_TO_DATE.getId());
- provider.updateInDB(dataBaseAdapter, accountId, entity, false);
- callback.onResponse(entity);
- }).start();
- }
+ try {
+ provider.updateOnServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<T>(new Account(accountId)) {
+ @Override
+ public void onResponse(T response) {
+ new Thread(() -> {
+ entity.setStatus(DBStatus.UP_TO_DATE.getId());
+ provider.updateInDB(dataBaseAdapter, accountId, entity, false);
+ callback.onResponse(entity);
+ }).start();
+ }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- new Thread(() -> {
- callback.onError(throwable);
- }).start();
- }
- }, entity);
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ new Thread(() -> {
+ callback.onError(throwable, entity);
+ }).start();
+ }
+ }, entity);
+ } catch (Throwable t) {
+ callback.onError(t, entity);
+ }
} else {
callback.onResponse(entity);
}
@@ -98,23 +106,28 @@ public class DataPropagationHelper {
provider.deleteInDB(dataBaseAdapter, accountId, entity);
boolean connected = serverAdapter.hasInternetConnection();
if (entity.getId() != null && connected) {
- provider.deleteOnServer(serverAdapter, accountId, new IResponseCallback<Void>(new Account(accountId)) {
- @Override
- public void onResponse(Void response) {
- new Thread(() -> {
- provider.deletePhysicallyInDB(dataBaseAdapter, accountId, entity);
- callback.onResponse(null);
- }).start();
- }
+ try {
+ provider.deleteOnServer(serverAdapter, accountId, new IResponseCallback<Void>(new Account(accountId)) {
+ @Override
+ public void onResponse(Void response) {
+ new Thread(() -> {
+ provider.deletePhysicallyInDB(dataBaseAdapter, accountId, entity);
+ callback.onResponse(null);
+ }).start();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ new Thread(() -> {
+ callback.onError(throwable);
+ }).start();
+ }
+ }, entity, dataBaseAdapter);
+ } catch (Throwable t) {
+ callback.onError(t);
+ }
- @Override
- public void onError(Throwable throwable) {
- super.onError(throwable);
- new Thread(() -> {
- callback.onError(throwable);
- }).start();
- }
- }, entity, dataBaseAdapter);
} else {
callback.onResponse(null);
}
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 18d8c3672..17c0de2cf 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
@@ -1,6 +1,12 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers;
-import java.util.Date;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
+
+import java.net.HttpURLConnection;
+import java.time.Instant;
import java.util.List;
import java.util.concurrent.CountDownLatch;
@@ -15,28 +21,33 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.AbstractS
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.IRelationshipProvider;
public class SyncHelper {
- private ServerAdapter serverAdapter;
- private DataBaseAdapter dataBaseAdapter;
+ private final ServerAdapter serverAdapter;
+ private final DataBaseAdapter dataBaseAdapter;
private Account account;
private long accountId;
private IResponseCallback<Boolean> responseCallback;
- private Date lastSync;
+ private final Instant lastSync;
- public SyncHelper(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, Date lastSync) {
+ public SyncHelper(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, Instant lastSync) {
this.serverAdapter = serverAdapter;
this.dataBaseAdapter = dataBaseAdapter;
this.lastSync = lastSync;
}
// Sync Server -> App
- public <T extends IRemoteEntity> void doSyncFor(final AbstractSyncDataProvider<T> provider){
+ public <T extends IRemoteEntity> void doSyncFor(@NonNull final AbstractSyncDataProvider<T> provider) {
provider.registerChildInParent(provider);
- provider.getAllFromServer(serverAdapter, accountId, new IResponseCallback<List<T>>(account) {
+ provider.getAllFromServer(serverAdapter, dataBaseAdapter, accountId, new IResponseCallback<List<T>>(account) {
@Override
public void onResponse(List<T> response) {
if (response != null) {
provider.goingDeeper();
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());
+ continue;
+ }
entityFromServer.setAccountId(accountId);
T existingEntity = provider.getSingleFromDB(dataBaseAdapter, accountId, entityFromServer);
@@ -44,13 +55,14 @@ public class SyncHelper {
provider.createInDB(dataBaseAdapter, accountId, entityFromServer);
} else {
//TODO: how to handle deletes? what about archived?
- if (existingEntity.getStatus() != DBStatus.UP_TO_DATE.getId()){
- DeckLog.log("Conflicting changes on entity: "+existingEntity);
+ if (existingEntity.getStatus() != DBStatus.UP_TO_DATE.getId()) {
+ DeckLog.warn("Conflicting changes on entity: " + existingEntity);
// TODO: what to do?
} else {
-// if (existingEntity.getLastModified().getTime() == entityFromServer.getLastModified().getTime()) {
-// continue; // TODO: is this is ok for sure? -> isn`t! NPE
-// }
+ if (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;
+ }
provider.updateInDB(dataBaseAdapter, accountId, applyUpdatesFromRemote(provider, existingEntity, entityFromServer, accountId), false);
}
}
@@ -58,7 +70,7 @@ public class SyncHelper {
provider.goDeeper(SyncHelper.this, existingEntity, entityFromServer, responseCallback);
}
- provider.handleDeletes(serverAdapter, dataBaseAdapter, accountId, response);
+ provider.handleDeletes(serverAdapter, dataBaseAdapter, accountId, response);
provider.doneGoingDeeper(responseCallback, true);
} else {
@@ -68,25 +80,35 @@ 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()){
+ DeckLog.log("[" + provider.getClass().getSimpleName() + "] ETags do match! skipping this one.");
+ // well, etags say we're fine here. no need to go deeper.
+ provider.childDone(provider, responseCallback, false);
+ return;
+ }
+ }
provider.onError(throwable, responseCallback);
- DeckLog.logError(throwable);
responseCallback.onError(throwable);
}
}, lastSync);
}
// Sync App -> Server
- public <T extends IRemoteEntity> void doUpSyncFor(AbstractSyncDataProvider<T> provider){
+ public <T extends IRemoteEntity> void doUpSyncFor(@NonNull AbstractSyncDataProvider<T> provider) {
doUpSyncFor(provider, null);
}
- public <T extends IRemoteEntity> void doUpSyncFor(AbstractSyncDataProvider<T> provider, CountDownLatch countDownLatch){
- List<T> allFromDB = provider.getAllChangedFromDB(dataBaseAdapter, accountId, lastSync);
+
+ public <T extends IRemoteEntity> void doUpSyncFor(@NonNull AbstractSyncDataProvider<T> provider, @Nullable CountDownLatch countDownLatch) {
+ final List<T> allFromDB = provider.getAllChangedFromDB(dataBaseAdapter, accountId, lastSync);
if (allFromDB != null && !allFromDB.isEmpty()) {
for (T entity : allFromDB) {
- if (entity.getId()!=null) {
+ if (entity.getId() != null) {
if (entity.getStatusEnum() == DBStatus.LOCAL_DELETED) {
provider.deleteOnServer(serverAdapter, accountId, getDeleteCallback(provider, entity), entity, dataBaseAdapter);
- if (countDownLatch != null){
+ if (countDownLatch != null) {
countDownLatch.countDown();
}
} else {
@@ -98,13 +120,13 @@ public class SyncHelper {
}
} else {
provider.goDeeperForUpSync(this, serverAdapter, dataBaseAdapter, responseCallback);
- if (countDownLatch != null){
+ if (countDownLatch != null) {
countDownLatch.countDown();
}
}
}
- private <T extends IRemoteEntity> IResponseCallback<Void> getDeleteCallback(AbstractSyncDataProvider<T> provider, T entity) {
+ private <T extends IRemoteEntity> IResponseCallback<Void> getDeleteCallback(@NonNull AbstractSyncDataProvider<T> provider, T entity) {
return new IResponseCallback<Void>(account) {
@Override
public void onResponse(Void response) {
@@ -120,16 +142,17 @@ public class SyncHelper {
};
}
- private <T extends IRemoteEntity> IResponseCallback<T> getUpdateCallback(AbstractSyncDataProvider<T> provider, T entity, CountDownLatch countDownLatch) {
+ private <T extends IRemoteEntity> IResponseCallback<T> getUpdateCallback(@NonNull AbstractSyncDataProvider<T> provider, @NonNull T entity, @Nullable CountDownLatch countDownLatch) {
return new IResponseCallback<T>(account) {
@Override
public void onResponse(T response) {
response.setAccountId(this.account.getId());
T update = applyUpdatesFromRemote(provider, entity, response, accountId);
+ update.setId(response.getId());
update.setStatus(DBStatus.UP_TO_DATE.getId());
provider.updateInDB(dataBaseAdapter, accountId, update, false);
provider.goDeeperForUpSync(SyncHelper.this, serverAdapter, dataBaseAdapter, responseCallback);
- if (countDownLatch != null){
+ if (countDownLatch != null) {
countDownLatch.countDown();
}
}
@@ -138,20 +161,20 @@ public class SyncHelper {
public void onError(Throwable throwable) {
super.onError(throwable);
responseCallback.onError(throwable);
- if (countDownLatch != null){
+ if (countDownLatch != null) {
countDownLatch.countDown();
}
}
};
}
- public void fixRelations(IRelationshipProvider relationshipProvider) {
+ public void fixRelations(@NonNull IRelationshipProvider relationshipProvider) {
// this is OK, since the delete only affects records with status UP_TO_DATE
relationshipProvider.deleteAllExisting(dataBaseAdapter, accountId);
relationshipProvider.insertAllNecessary(dataBaseAdapter, accountId);
}
- private <T extends IRemoteEntity> T applyUpdatesFromRemote(AbstractSyncDataProvider<T> provider, T localEntity, T remoteEntity, Long accountId) {
+ private <T extends IRemoteEntity> T applyUpdatesFromRemote(@NonNull AbstractSyncDataProvider<T> provider, @NonNull T localEntity, @NonNull T remoteEntity, @NonNull Long accountId) {
if (!accountId.equals(localEntity.getAccountId())) {
throw new IllegalArgumentException("IDs of Accounts are not matching! WTF are you doin?!");
}
@@ -160,7 +183,7 @@ public class SyncHelper {
return remoteEntity;
}
- public SyncHelper setResponseCallback(IResponseCallback<Boolean> callback) {
+ public SyncHelper setResponseCallback(@NonNull IResponseCallback<Boolean> callback) {
this.responseCallback = callback;
this.account = responseCallback.getAccount();
accountId = account.getId();
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 166d6c519..11fb38a66 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
@@ -1,9 +1,10 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import java.time.Instant;
import java.util.ArrayList;
-import java.util.Date;
import java.util.List;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.model.interfaces.IRemoteEntity;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
@@ -26,28 +27,37 @@ public abstract class AbstractSyncDataProvider<T extends IRemoteEntity> {
}
}
- public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<T> entitiesFromServer){
+ public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<T> entitiesFromServer) {
// do nothing as a default.
}
/**
* Searches each entry of <code>listB</code> in list <code>listA</code> and returns the missing ones
+ *
* @param listA List
* @param listB List
* @return all entries of <code>listB</code> missing in <code>listA</code>
*/
- public static <T extends IRemoteEntity> List<T> findDelta(List<T> listA, List<T> listB){
+ public static <T extends IRemoteEntity> List<T> findDelta(List<T> listA, List<T> listB) {
List<T> delta = new ArrayList<>();
for (T b : listB) {
+ if (b == null) {
+ DeckLog.error("Entry in listB is null! skipping...");
+ continue;
+ }
boolean found = false;
for (T a : listA) {
- if ((a.getLocalId()!= null && b.getLocalId()!= null ? (a.getLocalId().equals(b.getLocalId()))
+ if (a == null) {
+ DeckLog.error("Entry in listA is null! skipping...");
+ continue;
+ }
+ if ((a.getLocalId() != null && b.getLocalId() != null ? (a.getLocalId().equals(b.getLocalId()))
: a.getId().equals(b.getId())) && b.getAccountId() == a.getAccountId()) {
found = true;
break;
}
}
- if (!found){
+ if (!found) {
delta.add(b);
}
}
@@ -58,7 +68,14 @@ public abstract class AbstractSyncDataProvider<T extends IRemoteEntity> {
children.add(child);
}
- public abstract void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<T>> responder, Date lastSync);
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<T>> responder, Instant lastSync) {
+ return;
+ }
+
+ public void getAllFromServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<List<T>> responder, Instant lastSync) {
+ // Overridden, because we also need the DB-Adapter at some points here (see ACL data provider)
+ getAllFromServer(serverAdapter, accountId, responder, lastSync);
+ }
public abstract T getSingleFromDB(DataBaseAdapter dataBaseAdapter, long accountId, T entity);
@@ -72,7 +89,7 @@ public abstract class AbstractSyncDataProvider<T extends IRemoteEntity> {
public abstract void deleteInDB(DataBaseAdapter dataBaseAdapter, long accountId, T t);
- public void deletePhysicallyInDB(DataBaseAdapter dataBaseAdapter, long accountId, T t){
+ public void deletePhysicallyInDB(DataBaseAdapter dataBaseAdapter, long accountId, T t) {
deleteInDB(dataBaseAdapter, accountId, t);
}
@@ -106,7 +123,7 @@ public abstract class AbstractSyncDataProvider<T extends IRemoteEntity> {
stillGoingDeeper = true;
}
- public abstract List<T> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync);
+ public abstract List<T> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync);
public void goDeeperForUpSync(SyncHelper syncHelper, ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, IResponseCallback<Boolean> callback) {
//do nothing
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 3242ab7c1..6dde6b45c 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
@@ -1,17 +1,24 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.model.AccessControl;
+import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.User;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
+import it.niedermann.nextcloud.deck.model.ocs.user.GroupMemberUIDs;
+import it.niedermann.nextcloud.deck.model.ocs.user.OcsUser;
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.helpers.util.AsyncUtil;
public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessControl> {
+ private static final Long TYPE_GROUP = 1L;
private List<AccessControl> acl;
private FullBoard board;
@@ -22,10 +29,66 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<AccessControl>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<List<AccessControl>> responder, Instant lastSync) {
+ AsyncUtil.awaitAsyncWork(acl.size(), latch -> {
+ for (AccessControl accessControl : acl) {
+ if (accessControl.getType() == TYPE_GROUP) {
+ serverAdapter.searchGroupMembers(accessControl.getUser().getUid(), new IResponseCallback<GroupMemberUIDs>(responder.getAccount()) {
+ @Override
+ public void onResponse(GroupMemberUIDs response) {
+ accessControl.setGroupMemberUIDs(response);
+ if (response.getUids().size() > 0) {
+ ensureGroupMembersInDB(getAccount(), dataBaseAdapter, serverAdapter, response);
+ }
+ latch.countDown();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ latch.countDown();
+ }
+ });
+ } else latch.countDown();
+ }
+ });
+
responder.onResponse(acl);
}
+ private void ensureGroupMembersInDB(Account account, DataBaseAdapter dataBaseAdapter, ServerAdapter serverAdapter, GroupMemberUIDs response) {
+ CountDownLatch memberLatch = new CountDownLatch(response.getUids().size());
+ for (String uid : response.getUids()) {
+ User user = dataBaseAdapter.getUserByUidDirectly(account.getId(), uid);
+ if (user == null) {
+ // unknown user. fetch!
+ serverAdapter.getSingleUserData(uid, new IResponseCallback<OcsUser>(account) {
+ @Override
+ public void onResponse(OcsUser response) {
+ DeckLog.log(response.toString());
+ User user = new User();
+ user.setUid(response.getId());
+ user.setPrimaryKey(response.getId());
+ user.setDisplayname(response.getDisplayName());
+ dataBaseAdapter.createUser(account.getId(), user);
+ memberLatch.countDown();
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ memberLatch.countDown();
+ }
+ });
+ } else memberLatch.countDown();
+ }
+ try {
+ memberLatch.await();
+ } catch (InterruptedException e) {
+ DeckLog.logError(e);
+ }
+ }
+
@Override
public AccessControl getSingleFromDB(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl entity) {
return dataBaseAdapter.getAccessControlByRemoteIdDirectly(accountId, entity.getEntity().getId());
@@ -34,7 +97,26 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
@Override
public long createInDB(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl entity) {
prepareUser(dataBaseAdapter, accountId, entity);
- return dataBaseAdapter.createAccessControl(accountId, entity);
+ long newId = dataBaseAdapter.createAccessControl(accountId, entity);
+ entity.setLocalId(newId);
+ handleGroupMemberships(dataBaseAdapter, entity);
+ return newId;
+ }
+
+ private void handleGroupMemberships(DataBaseAdapter dataBaseAdapter, AccessControl entity) {
+ if (entity.getType() != TYPE_GROUP) {
+ return;
+ }
+ dataBaseAdapter.deleteGroupMembershipsOfGroup(entity.getUser().getLocalId());
+ if (entity.getGroupMemberUIDs() == null) {
+ return;
+ }
+ for (String groupMemberUID : entity.getGroupMemberUIDs().getUids()) {
+ User member = dataBaseAdapter.getUserByUidDirectly(entity.getAccountId(), groupMemberUID);
+ if (member != null) {
+ dataBaseAdapter.addUserToGroup(entity.getUserId(), member.getLocalId());
+ }
+ }
}
private void prepareUser(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl entity) {
@@ -42,6 +124,7 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
if (user == null) {
long userId = dataBaseAdapter.createUser(accountId, entity.getUser());
entity.setUserId(userId);
+ entity.getUser().setLocalId(userId);
} else {
entity.setUserId(user.getLocalId());
entity.getUser().setLocalId(user.getLocalId());
@@ -52,7 +135,9 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
@Override
public void updateInDB(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl entity, boolean setStatus) {
prepareUser(dataBaseAdapter, accountId, entity);
+ entity.setBoardId(board.getLocalId());
dataBaseAdapter.updateAccessControl(entity, setStatus);
+ handleGroupMemberships(dataBaseAdapter, entity);
}
@Override
@@ -64,6 +149,9 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<AccessControl> responder, AccessControl entity) {
AccessControl acl = new AccessControl(entity);
acl.setBoardId(board.getBoard().getId());
+ if (acl.getUser() == null && acl.getUserId() != null) {
+ acl.setUser(dataBaseAdapter.getUserByLocalIdDirectly(acl.getUserId()));
+ }
serverAdapter.createAccessControl(board.getBoard().getId(), acl, responder);
}
@@ -79,6 +167,7 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
@Override
public void deletePhysicallyInDB(DataBaseAdapter dataBaseAdapter, long accountId, AccessControl accessControl) {
+ dataBaseAdapter.deleteGroupMembershipsOfGroup(accessControl.getUser().getLocalId());
dataBaseAdapter.deleteAccessControl(accessControl, false);
}
@@ -88,7 +177,7 @@ public class AccessControlDataProvider extends AbstractSyncDataProvider<AccessCo
}
@Override
- public List<AccessControl> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<AccessControl> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return dataBaseAdapter.getLocallyChangedAccessControl(accountId, board.getLocalId());
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java
index a3785ca78..5a7abf732 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/ActivityDataProvider.java
@@ -1,7 +1,9 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
-import java.util.ArrayList;
-import java.util.Date;
+import androidx.annotation.NonNull;
+
+import java.time.Instant;
+import java.util.Collections;
import java.util.List;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
@@ -12,15 +14,16 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter
public class ActivityDataProvider extends AbstractSyncDataProvider<Activity> {
- protected Card card;
+ @NonNull
+ private final Card card;
- public ActivityDataProvider(AbstractSyncDataProvider<?> parent, Card card) {
+ public ActivityDataProvider(AbstractSyncDataProvider<?> parent, @NonNull Card card) {
super(parent);
this.card = card;
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Activity>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Activity>> responder, Instant lastSync) {
serverAdapter.getActivitiesForCard(card.getId(), responder);
}
@@ -65,7 +68,7 @@ public class ActivityDataProvider extends AbstractSyncDataProvider<Activity> {
}
@Override
- public List<Activity> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
- return new ArrayList<>();
+ public List<Activity> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
+ return Collections.emptyList();
}
}
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 37e00dada..781f7b1f4 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
@@ -4,7 +4,7 @@ import android.net.Uri;
import java.io.File;
import java.io.IOException;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
@@ -32,7 +32,7 @@ public class AttachmentDataProvider extends AbstractSyncDataProvider<Attachment>
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Attachment>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Attachment>> responder, Instant lastSync) {
responder.onResponse(attachments);
}
@@ -104,7 +104,7 @@ public class AttachmentDataProvider extends AbstractSyncDataProvider<Attachment>
}
@Override
- public List<Attachment> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<Attachment> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return dataBaseAdapter.getLocallyChangedAttachmentsByLocalCardIdDirectly(accountId, card.getLocalId());
}
@@ -120,7 +120,7 @@ public class AttachmentDataProvider extends AbstractSyncDataProvider<Attachment>
dataBaseAdapter.deleteAttachment(accountId, attachment, false);
}
for (Attachment attachment : entitiesFromServer) {
- if (attachment.getDeletedAt() != null && attachment.getDeletedAt().getTime() != 0) {
+ if (attachment.getDeletedAt() != null && attachment.getDeletedAt().toEpochMilli() != 0) {
Attachment toDelete = dataBaseAdapter.getAttachmentByRemoteIdDirectly(accountId, attachment.getId());
if (toDelete != null) {
dataBaseAdapter.deleteAttachment(accountId, toDelete, false);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java
index 98163029f..f5e071adb 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/BoardDataProvider.java
@@ -1,14 +1,16 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import android.annotation.SuppressLint;
+
+import com.nextcloud.android.sso.api.ParsedResponse;
+
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
-import java.util.concurrent.CountDownLatch;
-import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.model.AccessControl;
import it.niedermann.nextcloud.deck.model.Board;
@@ -19,16 +21,33 @@ import it.niedermann.nextcloud.deck.model.full.FullStack;
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.helpers.SyncHelper;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.util.AsyncUtil;
public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
- public BoardDataProvider(){
+ public BoardDataProvider() {
super(null);
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullBoard>> responder, Date lastSync) {
- serverAdapter.getBoards(responder);
+ public void getAllFromServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<List<FullBoard>> responder, Instant lastSync) {
+ serverAdapter.getBoards(new IResponseCallback<ParsedResponse<List<FullBoard>>>(responder.getAccount()) {
+ @Override
+ public void onResponse(ParsedResponse<List<FullBoard>> response) {
+ String etag = response.getHeaders().get("ETag");
+ if (etag != null && !etag.equals(account.getBoardsEtag())) {
+ account.setBoardsEtag(etag);
+ dataBaseAdapter.updateAccount(account);
+ }
+ responder.onResponse(response.getResponse());
+ }
+
+ @SuppressLint("MissingSuperCall")
+ @Override
+ public void onError(Throwable throwable) {
+ responder.onError(throwable);
+ }
+ });
}
@Override
@@ -39,27 +58,65 @@ public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
@Override
public long createInDB(DataBaseAdapter dataBaseAdapter, long accountId, FullBoard entity) {
handleOwner(dataBaseAdapter, accountId, entity);
- return dataBaseAdapter.createBoardDirectly(accountId, entity.getBoard());
+ Long localId = dataBaseAdapter.createBoardDirectly(accountId, entity.getBoard());
+ entity.getBoard().setLocalId(localId);
+ handleUsers(dataBaseAdapter, accountId, entity);
+ return localId;
}
private void handleOwner(DataBaseAdapter dataBaseAdapter, long accountId, FullBoard entity) {
- if (entity.getOwner()!=null) {
- User remoteOwner = entity.getOwner();
- User owner = dataBaseAdapter.getUserByUidDirectly(accountId, remoteOwner.getUid());
- if (owner == null){
- dataBaseAdapter.createUser(accountId, remoteOwner);
- } else {
- dataBaseAdapter.updateUser(accountId, remoteOwner, false);
- }
- owner = dataBaseAdapter.getUserByUidDirectly(accountId, remoteOwner.getUid());
+ if (entity.getOwner() != null) {
+ User owner = createOrUpdateUser(dataBaseAdapter, accountId, entity.getOwner());
entity.getBoard().setOwnerId(owner.getLocalId());
}
}
+ private void handleUsers(DataBaseAdapter dataBaseAdapter, long accountId, FullBoard entity) {
+ dataBaseAdapter.deleteBoardMembershipsOfBoard(entity.getLocalId());
+ if (entity.getUsers() != null && !entity.getUsers().isEmpty()) {
+ for (User user : entity.getUsers()) {
+ if (user == null) {
+ continue;
+ }
+ User existing = createOrUpdateUser(dataBaseAdapter, accountId, user);
+ dataBaseAdapter.addUserToBoard(existing.getLocalId(), entity.getLocalId());
+ }
+ }
+ }
+
+ private User createOrUpdateUser(DataBaseAdapter dataBaseAdapter, long accountId, User remoteUser) {
+ User owner = dataBaseAdapter.getUserByUidDirectly(accountId, remoteUser.getUid());
+ if (owner == null) {
+ dataBaseAdapter.createUser(accountId, remoteUser);
+ } else {
+ dataBaseAdapter.updateUser(accountId, remoteUser, false);
+ }
+ return dataBaseAdapter.getUserByUidDirectly(accountId, remoteUser.getUid());
+ }
+
@Override
public void updateInDB(DataBaseAdapter dataBaseAdapter, long accountId, FullBoard entity, boolean setStatus) {
+ handleDefaultLabels(dataBaseAdapter, entity);
handleOwner(dataBaseAdapter, accountId, entity);
dataBaseAdapter.updateBoard(entity.getBoard(), setStatus);
+ handleUsers(dataBaseAdapter, accountId, entity);
+ }
+
+ private void handleDefaultLabels(DataBaseAdapter dataBaseAdapter, FullBoard entity) {
+ // ## merge labels (created at board creation):
+ // the server creates four default labels. if a board is copied, they will also be copied. At sync, after creating the board, the labels are already there.
+ // this merges the created default ones with the ones i already have.
+ if (entity != null && entity.getLabels() != null) {
+ for (Label label : entity.getLabels()) {
+ // does this label exist and unknown to server yet?
+ Label existing = dataBaseAdapter.getLabelByBoardIdAndTitleDirectly(entity.getLocalId(), label.getTitle());
+ if (existing != null && existing.getId() == null) {
+ // take our label and lets say it IS the same as on server (but use the local color, no matter what the server says)
+ existing.setId(label.getId());
+ dataBaseAdapter.updateLabel(existing, false);
+ }
+ }
+ }
}
@Override
@@ -71,19 +128,19 @@ public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
@Override
public void goDeeper(SyncHelper syncHelper, FullBoard existingEntity, FullBoard entityFromServer, IResponseCallback<Boolean> callback) {
List<Label> labels = entityFromServer.getLabels();
- if (labels != null && !labels.isEmpty()){
+ if (labels != null && !labels.isEmpty()) {
syncHelper.doSyncFor(new LabelDataProvider(this, existingEntity.getBoard(), labels));
}
List<AccessControl> acl = entityFromServer.getParticipants();
- if (acl != null && !acl.isEmpty()){
- for (AccessControl ac : acl){
+ if (acl != null && !acl.isEmpty()) {
+ for (AccessControl ac : acl) {
ac.setBoardId(existingEntity.getLocalId());
}
syncHelper.doSyncFor(new AccessControlDataProvider(this, existingEntity, acl));
}
- if (entityFromServer.getStacks() != null && !entityFromServer.getStacks().isEmpty()){
+ if (entityFromServer.getStacks() != null && !entityFromServer.getStacks().isEmpty()) {
syncHelper.doSyncFor(new StackDataProvider(this, existingEntity));
}
}
@@ -94,7 +151,7 @@ public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
}
@Override
- public List<FullBoard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<FullBoard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return dataBaseAdapter.getLocallyChangedBoards(accountId);
}
@@ -102,21 +159,17 @@ public class BoardDataProvider extends AbstractSyncDataProvider<FullBoard> {
public void goDeeperForUpSync(SyncHelper syncHelper, ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, IResponseCallback<Boolean> callback) {
Long accountId = callback.getAccount().getId();
List<Label> locallyChangedLabels = dataBaseAdapter.getLocallyChangedLabels(accountId);
- CountDownLatch countDownLatch = new CountDownLatch(locallyChangedLabels.size());
- for (Label label : locallyChangedLabels) {
- Board board = dataBaseAdapter.getBoardByLocalIdDirectly(label.getBoardId());
- label.setBoardId(board.getId());
- syncHelper.doUpSyncFor(new LabelDataProvider(this, board, Collections.singletonList(label)), countDownLatch);
- }
- try {
- countDownLatch.await();
- } catch (InterruptedException e) {
- DeckLog.logError(e);
- }
+ AsyncUtil.awaitAsyncWork(locallyChangedLabels.size(), (countDownLatch) -> {
+ for (Label label : locallyChangedLabels) {
+ Board board = dataBaseAdapter.getBoardByLocalIdDirectly(label.getBoardId());
+ label.setBoardId(board.getId());
+ syncHelper.doUpSyncFor(new LabelDataProvider(this, board, Collections.singletonList(label)), countDownLatch);
+ }
+ });
List<Long> localBoardIDsWithChangedACL = dataBaseAdapter.getBoardIDsOfLocallyChangedAccessControl(accountId);
for (Long boardId : localBoardIDsWithChangedACL) {
- syncHelper.doUpSyncFor(new AccessControlDataProvider(this, dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, boardId) ,new ArrayList<>()));
+ syncHelper.doUpSyncFor(new AccessControlDataProvider(this, dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, boardId), new ArrayList<>()));
}
Set<Long> syncedBoards = new HashSet<>();
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 03e02bd70..0ceac2cd8 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
@@ -1,12 +1,15 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import android.annotation.SuppressLint;
+
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.exceptions.DeckException;
import it.niedermann.nextcloud.deck.exceptions.OfflineException;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
@@ -37,7 +40,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullCard>> responder, Date lastSync) {
+ 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()) {
@@ -54,6 +57,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
responder.onError(throwable);
@@ -70,7 +74,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
@Override
public long createInDB(DataBaseAdapter dataBaseAdapter, long accountId, FullCard entity) {
fixRelations(dataBaseAdapter, accountId, entity);
- return dataBaseAdapter.createCard(accountId, entity.getCard());
+ return dataBaseAdapter.createCardDirectly(accountId, entity.getCard());
}
protected CardUpdate toCardUpdate(FullCard card) {
@@ -144,10 +148,17 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
} else {
DeckLog.verbose("Comments - Version is too low, DONT SYNC");
}
+ syncHelper.doSyncFor(new OcsProjectDataProvider(this, existingEntity.getCard()));
}
@Override
public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<FullCard> responder, FullCard entity) {
+ if (stack.getId() == null) {
+ responder.onError(new DeckException(DeckException.Hint.DEPENDENCY_NOT_SYNCED_YET, "Stack \"" +
+ stack.getStack().getTitle() + "\" for Card \"" + entity.getCard().getTitle() +
+ "\" is not synced yet. Perform a full sync (pull to refresh) as soon as you are online again."));
+ return;
+ }
entity.getCard().setStackId(stack.getId());
serverAdapter.createCard(board.getId(), stack.getId(), entity.getCard(), responder);
}
@@ -170,7 +181,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
@Override
- public List<FullCard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<FullCard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
if (board == null || stack == null) {
// no cards changed!
// (see call from StackDataProvider: goDeeperForUpSync called with null for board.)
@@ -184,8 +195,13 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
public void goDeeperForUpSync(SyncHelper syncHelper, ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, IResponseCallback<Boolean> callback) {
FullStack stack;
Board board;
+ List<JoinCardWithLabel> changedLabels;
+ if (this.stack == null) {
+ changedLabels = dataBaseAdapter.getAllChangedLabelJoins();
+ } else {
+ changedLabels = dataBaseAdapter.getAllChangedLabelJoinsForStack(this.stack.getLocalId());
+ }
- List<JoinCardWithLabel> changedLabels = dataBaseAdapter.getAllChangedJoins();
Account account = callback.getAccount();
for (JoinCardWithLabel changedLabelLocal : changedLabels) {
Card card = dataBaseAdapter.getCardByLocalIdDirectly(account.getId(), changedLabelLocal.getCardId());
@@ -201,7 +217,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
board = this.board;
}
- JoinCardWithLabel changedLabel = dataBaseAdapter.getRemoteIdsForJoin(changedLabelLocal.getCardId(), changedLabelLocal.getLabelId());
+ JoinCardWithLabel changedLabel = dataBaseAdapter.getAllChangedLabelJoinsWithRemoteIDs(changedLabelLocal.getCardId(), changedLabelLocal.getLabelId());
if (changedLabel.getStatusEnum() == DBStatus.LOCAL_DELETED) {
if (changedLabel.getLabelId() == null || changedLabel.getCardId() == null) {
dataBaseAdapter.deleteJoinedLabelForCardPhysicallyByRemoteIDs(account.getId(), changedLabel.getCardId(), changedLabel.getLabelId());
@@ -229,11 +245,22 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
}
- List<JoinCardWithUser> deletedUsers = dataBaseAdapter.getAllDeletedUserJoinsWithRemoteIDs();
- for (JoinCardWithUser deletedUser : deletedUsers) {
- Card card = dataBaseAdapter.getCardByRemoteIdDirectly(account.getId(), deletedUser.getCardId());
+
+ List<JoinCardWithUser> changedUsers;
+ if (this.stack == null) {
+ changedUsers = dataBaseAdapter.getAllChangedUserJoinsWithRemoteIDs();
+ } else {
+ changedUsers = dataBaseAdapter.getAllChangedUserJoinsWithRemoteIDsForStack(this.stack.getLocalId());
+ }
+ for (JoinCardWithUser changedUser : changedUsers) {
+ // not already known to server?
+ if (changedUser.getCardId() == null) {
+ //skip for now
+ continue;
+ }
+ Card card = dataBaseAdapter.getCardByRemoteIdDirectly(account.getId(), changedUser.getCardId());
if (this.stack == null) {
- stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getLocalId());
+ stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getStackId());
} else {
stack = this.stack;
}
@@ -243,16 +270,16 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
} else {
board = this.board;
}
- User user = dataBaseAdapter.getUserByLocalIdDirectly(deletedUser.getUserId());
- if (deletedUser.getStatusEnum() == DBStatus.LOCAL_DELETED) {
- serverAdapter.unassignUserFromCard(board.getId(), stack.getId(), deletedUser.getCardId(), user.getUid(), new IResponseCallback<Void>(account) {
+ User user = dataBaseAdapter.getUserByLocalIdDirectly(changedUser.getUserId());
+ if (changedUser.getStatusEnum() == DBStatus.LOCAL_DELETED) {
+ serverAdapter.unassignUserFromCard(board.getId(), stack.getId(), changedUser.getCardId(), user.getUid(), new IResponseCallback<Void>(account) {
@Override
public void onResponse(Void response) {
- dataBaseAdapter.deleteJoinedUserForCardPhysicallyByRemoteIDs(account.getId(), deletedUser.getCardId(), user.getUid());
+ dataBaseAdapter.deleteJoinedUserForCardPhysicallyByRemoteIDs(account.getId(), changedUser.getCardId(), user.getUid());
}
});
- } else if (deletedUser.getStatusEnum() == DBStatus.LOCAL_EDITED) {
- serverAdapter.assignUserToCard(board.getId(), stack.getId(), deletedUser.getCardId(), user.getUid(), new IResponseCallback<Void>(account) {
+ } else if (changedUser.getStatusEnum() == DBStatus.LOCAL_EDITED) {
+ serverAdapter.assignUserToCard(board.getId(), stack.getId(), changedUser.getCardId(), user.getUid(), new IResponseCallback<Void>(account) {
@Override
public void onResponse(Void response) {
dataBaseAdapter.setStatusForJoinCardWithUser(card.getLocalId(), user.getLocalId(), DBStatus.UP_TO_DATE.getId());
@@ -261,7 +288,12 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
}
}
- List<Attachment> attachments = dataBaseAdapter.getLocallyChangedAttachmentsDirectly(account.getId());
+ List<Attachment> attachments;
+ if (this.stack == null) {
+ attachments = dataBaseAdapter.getLocallyChangedAttachmentsDirectly(account.getId());
+ } else {
+ attachments = dataBaseAdapter.getLocallyChangedAttachmentsForStackDirectly(this.stack.getLocalId());
+ }
for (Attachment attachment : attachments) {
FullCard card = dataBaseAdapter.getFullCardByLocalIdDirectly(account.getId(), attachment.getCardId());
stack = dataBaseAdapter.getFullStackByLocalIdDirectly(card.getCard().getStackId());
@@ -269,7 +301,12 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
syncHelper.doUpSyncFor(new AttachmentDataProvider(this, board, stack.getStack(), card, Collections.singletonList(attachment)));
}
- List<Card> cardsWithChangedComments = dataBaseAdapter.getCardsWithLocallyChangedCommentsDirectly(account.getId());
+ List<Card> cardsWithChangedComments;
+ if (this.stack == null) {
+ cardsWithChangedComments = dataBaseAdapter.getCardsWithLocallyChangedCommentsDirectly(account.getId());
+ } else {
+ cardsWithChangedComments = dataBaseAdapter.getCardsWithLocallyChangedCommentsForStackDirectly(this.stack.getLocalId());
+ }
for (Card card : cardsWithChangedComments) {
syncHelper.doUpSyncFor(new DeckCommentsDataProvider(this, card));
}
@@ -279,7 +316,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
@Override
public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<FullCard> entitiesFromServer) {
- List<FullCard> localCards = dataBaseAdapter.getFullCardsForStackDirectly(accountId, stack.getLocalId());
+ List<FullCard> localCards = dataBaseAdapter.getFullCardsForStackDirectly(accountId, stack.getLocalId(), null);
List<FullCard> delta = findDelta(entitiesFromServer, localCards);
for (FullCard cardToDelete : delta) {
if (cardToDelete.getId() == null) {
@@ -294,6 +331,7 @@ public class CardDataProvider extends AbstractSyncDataProvider<FullCard> {
// do not delete, it's still there and was just moved!
}
+ @SuppressLint("MissingSuperCall")
@Override
public void onError(Throwable throwable) {
if (!(throwable instanceof OfflineException)) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java
index 5b9be120f..06ef3030d 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/DeckCommentsDataProvider.java
@@ -1,8 +1,8 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
@@ -24,10 +24,13 @@ public class DeckCommentsDataProvider extends AbstractSyncDataProvider<OcsCommen
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<OcsComment>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<OcsComment>> responder, Instant lastSync) {
serverAdapter.getCommentsForRemoteCardId(card.getId(), new IResponseCallback<OcsComment>(responder.getAccount()) {
@Override
public void onResponse(OcsComment response) {
+ if (response == null) {
+ response = new OcsComment();
+ }
List<OcsComment> comments = response.split();
Collections.sort(comments, (o1, o2) -> o1.getSingle().getCreationDateTime().compareTo(o2.getSingle().getCreationDateTime()));
verifyCommentListIntegrity(comments);
@@ -131,7 +134,7 @@ public class DeckCommentsDataProvider extends AbstractSyncDataProvider<OcsCommen
}
@Override
- public List<OcsComment> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<OcsComment> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return new OcsComment(dataBaseAdapter.getLocallyChangedCommentsByLocalCardIdDirectly(accountId, card.getLocalId())).split();
}
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 403d71f87..caa7c68e6 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,8 +1,9 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
import it.niedermann.nextcloud.deck.exceptions.HandledServerErrors;
import it.niedermann.nextcloud.deck.model.Board;
@@ -12,14 +13,14 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter
public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
- private List<Label> labels;
- private Board board;
+ private final List<Label> labels;
+ private final Board board;
public LabelDataProvider(AbstractSyncDataProvider<?> parent, Board board, List<Label> labels) {
super(parent);
this.board = board;
this.labels = labels;
- if (this.labels!= null && board != null){
+ if (this.labels != null && board != null) {
for (Label label : labels) {
label.setBoardId(board.getLocalId());
}
@@ -27,7 +28,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Label>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<Label>> responder, Instant lastSync) {
responder.onResponse(labels);
}
@@ -44,7 +45,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
updateInDB(dataBaseAdapter, accountId, entity, false);
return entity.getLocalId();
} else {
- return dataBaseAdapter.createLabel(accountId, entity);
+ return dataBaseAdapter.createLabelDirectly(accountId, entity);
}
}
@@ -53,7 +54,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
dataBaseAdapter.updateLabel(entity, setStatus);
}
- private IResponseCallback<Label> getLabelUniqueHandler(DataBaseAdapter dataBaseAdapter, Label entitiy, IResponseCallback<Label> responder){
+ private IResponseCallback<Label> getLabelUniqueHandler(DataBaseAdapter dataBaseAdapter, Label entitiy, IResponseCallback<Label> responder) {
return new IResponseCallback<Label>(responder.getAccount()) {
@Override
public void onResponse(Label response) {
@@ -62,10 +63,13 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
@Override
public void onError(Throwable throwable) {
- if (HandledServerErrors.LABELS_TITLE_MUST_BE_UNIQUE == HandledServerErrors.fromThrowable(throwable)){
+ if (HandledServerErrors.LABELS_TITLE_MUST_BE_UNIQUE == HandledServerErrors.fromThrowable(throwable)) {
+ DeckLog.log(throwable.getCause().getMessage() + ": " + entitiy.toString());
dataBaseAdapter.deleteLabelPhysically(entitiy);
+ responder.onResponse(entitiy);
+ } else {
+ responder.onError(throwable);
}
- responder.onError(throwable);
}
};
}
@@ -92,7 +96,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
}
@Override
- public List<Label> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<Label> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return labels;
}
@@ -105,7 +109,7 @@ public class LabelDataProvider extends AbstractSyncDataProvider<Label> {
public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<Label> entitiesFromServer) {
List<Label> deletedLabels = findDelta(labels, dataBaseAdapter.getFullBoardByLocalIdDirectly(accountId, board.getLocalId()).getLabels());
for (Label deletedLabel : deletedLabels) {
- if (deletedLabel.getId()!=null){
+ if (deletedLabel.getId() != null) {
// preserve new, unsynced card.
dataBaseAdapter.deleteLabelPhysically(deletedLabel);
}
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
new file mode 100644
index 000000000..b9bfb8b31
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/OcsProjectDataProvider.java
@@ -0,0 +1,100 @@
+package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.model.Card;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProject;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectList;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.ServerAdapter;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.DataBaseAdapter;
+
+public class OcsProjectDataProvider extends AbstractSyncDataProvider<OcsProject> {
+ private Card card;
+
+ public OcsProjectDataProvider(AbstractSyncDataProvider<?> parent, Card card) {
+ super(parent);
+ this.card = card;
+ }
+
+ @Override
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<OcsProject>> responder, Instant lastSync) {
+ serverAdapter.getProjectsForCard(card.getId(), new IResponseCallback<OcsProjectList>(responder.getAccount()) {
+ @Override
+ public void onResponse(OcsProjectList response) {
+ responder.onResponse(response.getProjects());
+ }
+
+ @Override
+ public void onError(Throwable throwable) {
+ super.onError(throwable);
+ // dont break the sync!
+ DeckLog.logError(throwable);
+ responder.onResponse(Collections.emptyList());
+ }
+ });
+ }
+
+ @Override
+ public OcsProject getSingleFromDB(DataBaseAdapter dataBaseAdapter, long accountId, OcsProject entity) {
+ return dataBaseAdapter.getProjectByRemoteIdDirectly(accountId, entity.getId());
+ }
+
+ @Override
+ public long createInDB(DataBaseAdapter dataBaseAdapter, long accountId, OcsProject entity) {
+ Long newId = dataBaseAdapter.createProjectDirectly(accountId, entity);
+ entity.setLocalId(newId);
+ updateResources(dataBaseAdapter, accountId, entity);
+ return newId;
+ }
+
+ @Override
+ public void updateInDB(DataBaseAdapter dataBaseAdapter, long accountId, OcsProject entity, boolean setStatus) {
+ dataBaseAdapter.updateProjectDirectly(accountId, entity);
+ dataBaseAdapter.deleteProjectResourcesForProjectIdDirectly(entity.getLocalId());
+ updateResources(dataBaseAdapter, accountId, entity);
+ }
+
+ @Override
+ public void deleteInDB(DataBaseAdapter dataBaseAdapter, long accountId, OcsProject ocsProject) {
+ if (ocsProject != null && ocsProject.getLocalId() != null) {
+ dataBaseAdapter.deleteProjectDirectly(ocsProject);
+ }
+ }
+
+ private void updateResources(DataBaseAdapter dataBaseAdapter, Long accountId, OcsProject entity) {
+ if (entity.getResources() != null) {
+ for (OcsProjectResource resource : entity.getResources()) {
+ resource.setProjectId(entity.getLocalId());
+ resource.setLocalId(dataBaseAdapter.createProjectResourceDirectly(accountId, resource));
+ if ("deck-card".equals(resource.getType())) {
+ dataBaseAdapter.assignCardToProjectIfMissng(accountId, entity.getLocalId(), resource.getId());
+ }
+ }
+ }
+ }
+
+ @Override
+ public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<OcsProject> responder, OcsProject entity) {
+ // Do Nothing
+ }
+
+ @Override
+ public void updateOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<OcsProject> callback, OcsProject entity) {
+ // Do Nothing
+ }
+
+ @Override
+ public void deleteOnServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<Void> callback, OcsProject entity, DataBaseAdapter dataBaseAdapter) {
+ // Do Nothing
+ }
+
+ @Override
+ public List<OcsProject> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
+ return Collections.emptyList();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java
index a3c123afd..93761d616 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/StackDataProvider.java
@@ -1,12 +1,13 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
+import java.time.Instant;
import java.util.Collections;
-import java.util.Date;
-import java.util.HashSet;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.ConcurrentSkipListSet;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.exceptions.DeckException;
import it.niedermann.nextcloud.deck.model.Board;
import it.niedermann.nextcloud.deck.model.Card;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
@@ -19,13 +20,15 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper;
public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
private FullBoard board;
+ private Set<Long> syncedStacks = new ConcurrentSkipListSet<>();
+
public StackDataProvider(AbstractSyncDataProvider<?> parent, FullBoard board) {
super(parent);
this.board = board;
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullStack>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<FullStack>> responder, Instant lastSync) {
serverAdapter.getStacks(board.getId(), responder);
}
@@ -54,12 +57,12 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
@Override
public void goDeeper(SyncHelper syncHelper, FullStack existingEntity, FullStack entityFromServer, IResponseCallback<Boolean> callback) {
- boolean serverHasCards = entityFromServer.getCards() != null && !entityFromServer.getCards().isEmpty();
- boolean weHaveCards = existingEntity.getCards() != null && !existingEntity.getCards().isEmpty();
- if (serverHasCards || weHaveCards){
+ boolean serverHasCards = entityFromServer.getCards() != null && !entityFromServer.getCards().isEmpty();
+ boolean weHaveCards = existingEntity.getCards() != null && !existingEntity.getCards().isEmpty();
+ if (serverHasCards || weHaveCards) {
existingEntity.setCards(entityFromServer.getCards());
List<Card> cards = existingEntity.getCards();
- if (cards != null ){
+ if (cards != null) {
for (Card card : cards) {
card.setStackId(existingEntity.getLocalId());
}
@@ -72,6 +75,9 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
@Override
public void createOnServer(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, IResponseCallback<FullStack> responder, FullStack entity) {
+ if (board.getId() == null) {
+ throw new DeckException(DeckException.Hint.DEPENDENCY_NOT_SYNCED_YET, "Board for this stack is not synced yet. Perform a full sync (pull to referesh) as soon as you are online again.");
+ }
entity.getStack().setBoardId(board.getId());
serverAdapter.createStack(board.getBoard(), entity.getStack(), responder);
}
@@ -89,8 +95,8 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
}
@Override
- public List<FullStack> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
- if (board == null){
+ public List<FullStack> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
+ if (board == null) {
// no stacks changed!
// (see call from BoardDataProvider: goDeeperForUpSync called with null for board.)
// so we can just skip this one and proceed with cards.
@@ -103,16 +109,19 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
@Override
public void goDeeperForUpSync(SyncHelper syncHelper, ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, IResponseCallback<Boolean> callback) {
List<FullCard> changedCards = dataBaseAdapter.getLocallyChangedCardsDirectly(callback.getAccount().getId());
- Set<Long> syncedStacks = new HashSet<>();
- if (changedCards != null && changedCards.size() > 0){
+ if (changedCards != null && !changedCards.isEmpty()) {
for (FullCard changedCard : changedCards) {
long stackId = changedCard.getCard().getStackId();
- boolean added = syncedStacks.add(stackId);
- if (added) {
+ boolean alreadySynced = syncedStacks.contains(stackId);
+ if (!alreadySynced) {
FullStack stack = dataBaseAdapter.getFullStackByLocalIdDirectly(stackId);
- Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
- changedCard.getCard().setStackId(stack.getId());
- syncHelper.doUpSyncFor(new CardDataProvider(this, board, stack));
+ // already synced and known to server?
+ if (stack.getStack().getId() != null) {
+ syncedStacks.add(stackId);
+ Board board = dataBaseAdapter.getBoardByLocalIdDirectly(stack.getStack().getBoardId());
+ changedCard.getCard().setStackId(stack.getId());
+ syncHelper.doUpSyncFor(new CardDataProvider(this, board, stack));
+ }
}
}
} else {
@@ -132,7 +141,7 @@ public class StackDataProvider extends AbstractSyncDataProvider<FullStack> {
List<FullStack> localStacks = dataBaseAdapter.getFullStacksForBoardDirectly(accountId, board.getLocalId());
List<FullStack> delta = findDelta(entitiesFromServer, localStacks);
for (FullStack stackToDelete : delta) {
- if (stackToDelete.getId() == null){
+ if (stackToDelete.getId() == null) {
// not pushed up yet so:
continue;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java
index 60c906bda..279ce9e55 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/UserDataProvider.java
@@ -1,6 +1,6 @@
package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
import it.niedermann.nextcloud.deck.api.IResponseCallback;
@@ -27,7 +27,7 @@ public class UserDataProvider extends AbstractSyncDataProvider<User> {
}
@Override
- public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<User>> responder, Date lastSync) {
+ public void getAllFromServer(ServerAdapter serverAdapter, long accountId, IResponseCallback<List<User>> responder, Instant lastSync) {
responder.onResponse(users);
}
@@ -67,7 +67,7 @@ public class UserDataProvider extends AbstractSyncDataProvider<User> {
}
@Override
- public List<User> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Date lastSync) {
+ public List<User> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
return null;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWitAclDownSyncDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithAclDownSyncDataProvider.java
index 326d257ab..8516f0fc0 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWitAclDownSyncDataProvider.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithAclDownSyncDataProvider.java
@@ -11,7 +11,7 @@ import it.niedermann.nextcloud.deck.persistence.sync.helpers.SyncHelper;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.AccessControlDataProvider;
import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.BoardDataProvider;
-public class BoardWitAclDownSyncDataProvider extends BoardDataProvider {
+public class BoardWithAclDownSyncDataProvider extends BoardDataProvider {
@Override
public void goDeeper(SyncHelper syncHelper, FullBoard existingEntity, FullBoard entityFromServer, IResponseCallback<Boolean> callback) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithStacksAndLabelsUpSyncDataProvider.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithStacksAndLabelsUpSyncDataProvider.java
new file mode 100644
index 000000000..5f2c79ad3
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/providers/partial/BoardWithStacksAndLabelsUpSyncDataProvider.java
@@ -0,0 +1,37 @@
+package it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.partial;
+
+import java.time.Instant;
+import java.util.Collections;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.model.full.FullBoard;
+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.helpers.SyncHelper;
+import it.niedermann.nextcloud.deck.persistence.sync.helpers.providers.BoardDataProvider;
+
+public class BoardWithStacksAndLabelsUpSyncDataProvider extends BoardDataProvider {
+
+ private FullBoard board;
+
+ public BoardWithStacksAndLabelsUpSyncDataProvider(FullBoard boardToSync) {
+ board = boardToSync;
+ }
+
+ @Override
+ public List<FullBoard> getAllChangedFromDB(DataBaseAdapter dataBaseAdapter, long accountId, Instant lastSync) {
+ return Collections.singletonList(board);
+ }
+
+ @Override
+ public void goDeeper(SyncHelper syncHelper, FullBoard existingEntity, FullBoard entityFromServer, IResponseCallback<Boolean> callback) {
+ // do nothing!
+
+ }
+
+ @Override
+ public void handleDeletes(ServerAdapter serverAdapter, DataBaseAdapter dataBaseAdapter, long accountId, List<FullBoard> entitiesFromServer) {
+ // do nothing!
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/util/AsyncUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/util/AsyncUtil.java
new file mode 100644
index 000000000..faabda163
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/persistence/sync/helpers/util/AsyncUtil.java
@@ -0,0 +1,21 @@
+package it.niedermann.nextcloud.deck.persistence.sync.helpers.util;
+
+import java.util.concurrent.CountDownLatch;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+
+public class AsyncUtil {
+ public interface LatchCallback {
+ void doWork(CountDownLatch latch);
+ }
+
+ public static void awaitAsyncWork(int count, LatchCallback worker){
+ CountDownLatch countDownLatch = new CountDownLatch(count);
+ worker.doWork(countDownLatch);
+ try {
+ countDownLatch.await();
+ } catch (InterruptedException e) {
+ DeckLog.logError(e);
+ }
+ }
+}
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 854b99a6a..3ded31bb7 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
@@ -23,6 +23,7 @@ import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGrant
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
import com.nextcloud.android.sso.helper.SingleAccountHelper;
import com.nextcloud.android.sso.model.SingleSignOnAccount;
+import com.nextcloud.android.sso.ui.UiExceptionManager;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
@@ -36,7 +37,6 @@ import it.niedermann.nextcloud.deck.persistence.sync.SyncWorker;
import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
-import it.niedermann.nextcloud.deck.util.ExceptionUtil;
import static com.nextcloud.android.sso.AccountImporter.REQUEST_AUTH_TOKEN_SSO;
@@ -79,7 +79,11 @@ public class ImportAccountActivity extends AppCompatActivity {
try {
AccountImporter.pickNewAccount(this);
} catch (NextcloudFilesAppNotInstalledException e) {
- ExceptionUtil.handleNextcloudFilesAppNotInstalledException(this, e);
+ UiExceptionManager.showDialogForException(this, e);
+ DeckLog.warn("=============================================================");
+ DeckLog.warn("Nextcloud app is not installed. Cannot choose account");
+ DeckLog.logError(e);
+ binding.addButton.setEnabled(true);
} catch (AndroidGetAccountsPermissionNotGranted e) {
binding.addButton.setEnabled(true);
AccountImporter.requestAndroidAccountPermissionsAndPickAccount(this);
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 b5713909b..d7e726a7d 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
@@ -1,5 +1,6 @@
package it.niedermann.nextcloud.deck.ui;
+import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
@@ -53,6 +54,7 @@ import java.util.Objects;
import it.niedermann.android.crosstabdnd.CrossTabDragAndDrop;
import it.niedermann.android.tablayouthelper.TabLayoutHelper;
import it.niedermann.android.tablayouthelper.TabTitleGenerator;
+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;
@@ -65,6 +67,7 @@ import it.niedermann.nextcloud.deck.model.Stack;
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.internal.FilterInformation;
import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
import it.niedermann.nextcloud.deck.model.ocs.Version;
import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
@@ -86,6 +89,7 @@ import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
import it.niedermann.nextcloud.deck.ui.filter.FilterDialogFragment;
import it.niedermann.nextcloud.deck.ui.filter.FilterViewModel;
+import it.niedermann.nextcloud.deck.ui.pickstack.PickStackViewModel;
import it.niedermann.nextcloud.deck.ui.settings.SettingsActivity;
import it.niedermann.nextcloud.deck.ui.stack.DeleteStackDialogFragment;
import it.niedermann.nextcloud.deck.ui.stack.DeleteStackListener;
@@ -112,8 +116,8 @@ import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.applyBrandTo
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.clearBrandColors;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.saveBrandColors;
-import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficient;
-import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficientBigAreas;
+import static it.niedermann.nextcloud.deck.util.DeckColorUtil.contrastRatioIsSufficient;
+import static it.niedermann.nextcloud.deck.util.DeckColorUtil.contrastRatioIsSufficientBigAreas;
import static it.niedermann.nextcloud.deck.util.DrawerMenuUtil.MENU_ID_ABOUT;
import static it.niedermann.nextcloud.deck.util.DrawerMenuUtil.MENU_ID_ADD_BOARD;
import static it.niedermann.nextcloud.deck.util.DrawerMenuUtil.MENU_ID_ARCHIVED_BOARDS;
@@ -126,6 +130,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
protected MainViewModel mainViewModel;
private FilterViewModel filterViewModel;
+ private PickStackViewModel pickStackViewModel;
protected static final int ACTIVITY_ABOUT = 1;
protected static final int ACTIVITY_SETTINGS = 2;
@@ -133,7 +138,6 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@NonNull
protected List<Account> accountsList = new ArrayList<>();
- protected SyncManager syncManager;
protected SharedPreferences sharedPreferences;
private StackAdapter stackAdapter;
long lastBoardId;
@@ -143,7 +147,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
private Observer<List<Board>> boardsLiveDataObserver;
private Menu listMenu;
- private LiveData<List<FullStack>> stacksLiveData;
+ private LiveData<List<Stack>> stacksLiveData;
private LiveData<Boolean> hasArchivedBoardsLiveData;
private Observer<Boolean> hasArchivedBoardsLiveDataObserver;
@@ -178,6 +182,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
mainViewModel = new ViewModelProvider(this).get(MainViewModel.class);
filterViewModel = new ViewModelProvider(this).get(FilterViewModel.class);
+ pickStackViewModel = new ViewModelProvider(this).get(PickStackViewModel.class);
addList = getString(R.string.add_list);
addBoard = getString(R.string.add_board);
@@ -191,12 +196,11 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
toggle.syncState();
binding.navigationView.setNavigationItemSelectedListener(this);
- syncManager = new SyncManager(this);
sharedPreferences = PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
- switchMap(syncManager.hasAccounts(), hasAccounts -> {
+ switchMap(mainViewModel.hasAccounts(), hasAccounts -> {
if (hasAccounts) {
- return syncManager.readAccounts();
+ return mainViewModel.readAccounts();
} else {
startActivityForResult(new Intent(this, ImportAccountActivity.class), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
return null;
@@ -220,7 +224,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
registerAutoSyncOnNetworkAvailable();
} else {
- syncManager.synchronize(new IResponseCallback<Boolean>(mainViewModel.getCurrentAccount()) {
+ mainViewModel.synchronize(new IResponseCallback<Boolean>(mainViewModel.getCurrentAccount()) {
@Override
public void onResponse(Boolean response) {
}
@@ -240,7 +244,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
mainViewModel.getCurrentAccountLiveData().observe(this, (currentAccount) -> {
SingleAccountHelper.setCurrentAccount(getApplicationContext(), mainViewModel.getCurrentAccount().getName());
- syncManager = new SyncManager(this);
+ mainViewModel.recreateSyncManager();
saveCurrentAccountId(this, mainViewModel.getCurrentAccount().getId());
if (mainViewModel.getCurrentAccount().isMaintenanceEnabled()) {
@@ -253,7 +257,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
boardsLiveData.removeObserver(boardsLiveDataObserver);
}
- boardsLiveData = syncManager.getBoards(currentAccount.getId(), false);
+ boardsLiveData = mainViewModel.getBoards(currentAccount.getId(), false);
boardsLiveDataObserver = (boards) -> {
if (boards == null) {
throw new IllegalStateException("List<Board> boards must not be null.");
@@ -273,15 +277,19 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
if (!currentBoardIdWasInList) {
setCurrentBoard(boardsList.get(0));
}
+
+ binding.filter.setOnClickListener((v) -> FilterDialogFragment.newInstance().show(getSupportFragmentManager(), EditStackDialogFragment.class.getCanonicalName()));
} else {
clearBrandColors(this);
clearCurrentBoard();
+
+ binding.filter.setOnClickListener(null);
}
if (hasArchivedBoardsLiveData != null && hasArchivedBoardsLiveDataObserver != null) {
hasArchivedBoardsLiveData.removeObserver(hasArchivedBoardsLiveDataObserver);
}
- hasArchivedBoardsLiveData = syncManager.hasArchivedBoards(currentAccount.getId());
+ hasArchivedBoardsLiveData = mainViewModel.hasArchivedBoards(currentAccount.getId());
hasArchivedBoardsLiveDataObserver = (hasArchivedBoards) -> {
mainViewModel.setCurrentAccountHasArchivedBoards(Boolean.TRUE.equals(hasArchivedBoards));
inflateBoardMenu();
@@ -320,7 +328,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
CrossTabDragAndDrop<StackFragment, CardAdapter, FullCard> dragAndDrop = new CrossTabDragAndDrop<>(getResources(), ViewCompat.getLayoutDirection(binding.getRoot()) == ViewCompat.LAYOUT_DIRECTION_LTR);
dragAndDrop.register(binding.viewPager, binding.stackTitles, getSupportFragmentManager());
dragAndDrop.addItemMovedByDragListener((movedCard, stackId, position) -> {
- syncManager.reorder(mainViewModel.getCurrentAccount().getId(), 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);
});
@@ -374,8 +382,6 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
});
filterViewModel.getFilterInformation().observe(this, (info) ->
binding.filterIndicator.setVisibility(filterViewModel.getFilterInformation().getValue() == null ? View.GONE : View.VISIBLE));
-
- binding.filter.setOnClickListener((v) -> FilterDialogFragment.newInstance().show(getSupportFragmentManager(), EditStackDialogFragment.class.getCanonicalName()));
binding.archivedCards.setOnClickListener((v) -> startActivity(ArchivedCardsActvitiy.createIntent(this, mainViewModel.getCurrentAccount(), mainViewModel.getCurrentBoardLocalId(), mainViewModel.currentBoardHasEditPermission())));
@@ -395,7 +401,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
}
} else DeckLog.warn("ConnectivityManager is null");
refreshCapabilities(mainViewModel.getCurrentAccount());
- syncManager.synchronize(new IResponseCallback<Boolean>(mainViewModel.getCurrentAccount()) {
+ mainViewModel.synchronize(new IResponseCallback<Boolean>(mainViewModel.getCurrentAccount()) {
@Override
public void onResponse(Boolean response) {
runOnUiThread(() -> binding.swipeRefreshLayout.setRefreshing(false));
@@ -422,7 +428,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
applyBrandToPrimaryTabLayout(mainColor, binding.stackTitles);
applyBrandToFAB(mainColor, binding.fab);
// TODO We assume, that the background of the spinner is always white
- binding.swipeRefreshLayout.setColorSchemeColors(contrastRatioIsSufficient(Color.WHITE, mainColor) ? mainColor : colorAccent);
+ 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);
@@ -440,62 +446,49 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@Override
public void onCreateStack(String stackName) {
- // TODO this outer call is only necessary to get the highest order. Move logic to SyncManager.
- observeOnce(syncManager.getStacksForBoard(mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId()), MainActivity.this, fullStacks -> {
- final Stack s = new Stack(stackName, mainViewModel.getCurrentBoardLocalId());
- int heighestOrder = 0;
- for (FullStack fullStack : fullStacks) {
- int currentStackOrder = fullStack.stack.getOrder();
- if (currentStackOrder >= heighestOrder) {
- heighestOrder = currentStackOrder + 1;
- }
+ 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)
+ .setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(error, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
+ .show();
+ } else {
+ binding.viewPager.setCurrentItem(stackAdapter.getItemCount());
}
- s.setOrder(heighestOrder);
- DeckLog.info("Create Stack in account " + mainViewModel.getCurrentAccount().getName() + " on board " + mainViewModel.getCurrentBoardLocalId());
- WrappedLiveData<FullStack> createLiveData = syncManager.createStack(mainViewModel.getCurrentAccount().getId(), s);
- 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)
- .setAction(R.string.simple_more, v -> ExceptionDialogFragment.newInstance(error, mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
- .show();
- } else {
- binding.viewPager.setCurrentItem(stackAdapter.getItemCount());
- }
- });
});
}
@Override
public void onUpdateStack(long localStackId, String stackName) {
- observeOnce(syncManager.getStack(mainViewModel.getCurrentAccount().getId(), localStackId), MainActivity.this, fullStack -> {
- fullStack.getStack().setTitle(stackName);
- final WrappedLiveData<FullStack> archiveLiveData = syncManager.updateStack(fullStack);
- observeOnce(archiveLiveData, this, (v) -> {
- if (archiveLiveData.hasError()) {
- ExceptionDialogFragment.newInstance(archiveLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- }
- });
+ 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());
+ }
});
}
@Override
- public void onCreateBoard(String title, String color) {
+ public void onCreateBoard(String title, @ColorInt int color) {
if (boardsLiveData == null || boardsLiveDataObserver == null) {
throw new IllegalStateException("Cannot create board when noone observe boards yet. boardsLiveData or observer is null.");
}
boardsLiveData.removeObserver(boardsLiveDataObserver);
- final Board boardToCreate = new Board(title, color.startsWith("#") ? color.substring(1) : color);
+ final Board boardToCreate = new Board(title, color);
boardToCreate.setPermissionEdit(true);
boardToCreate.setPermissionManage(true);
- observeOnce(syncManager.createBoard(mainViewModel.getCurrentAccount().getId(), boardToCreate), this, createdBoard -> {
- if (createdBoard == null) {
- BrandedSnackbar.make(binding.coordinatorLayout, "Open Deck in web interface first!", Snackbar.LENGTH_LONG)
- // TODO implement action!
- // .setAction(R.string.simple_open, v -> ExceptionDialogFragment.newInstance(throwable).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName()))
+
+ 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();
- } else {
+ }
+ if (createdBoard != null && !createLiveData.hasError()) {
boardsList.add(createdBoard.getBoard());
setCurrentBoard(createdBoard.getBoard());
@@ -508,11 +501,16 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@Override
public void onUpdateBoard(FullBoard fullBoard) {
- syncManager.updateBoard(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());
+ }
+ });
}
private void refreshCapabilities(final Account account) {
- syncManager.refreshCapabilities(new IResponseCallback<Capabilities>(account) {
+ mainViewModel.refreshCapabilities(new IResponseCallback<Capabilities>(account) {
@Override
public void onResponse(Capabilities response) {
if (response.isMaintenanceEnabled()) {
@@ -548,7 +546,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
if (stacksLiveData != null) {
stacksLiveData.removeObservers(this);
}
- saveBrandColors(this, Color.parseColor('#' + board.getColor()));
+ saveBrandColors(this, board.getColor());
mainViewModel.setCurrentBoard(board);
filterViewModel.clearFilterInformation();
@@ -569,12 +567,12 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
binding.emptyContentViewBoards.setVisibility(View.GONE);
binding.swipeRefreshLayout.setVisibility(View.VISIBLE);
- stacksLiveData = syncManager.getStacksForBoard(mainViewModel.getCurrentAccount().getId(), board.getLocalId());
- stacksLiveData.observe(this, (List<FullStack> fullStacks) -> {
- if (fullStacks == null) {
+ stacksLiveData = mainViewModel.getStacksForBoard(mainViewModel.getCurrentAccount().getId(), board.getLocalId());
+ stacksLiveData.observe(this, (List<Stack> stacks) -> {
+ if (stacks == null) {
throw new IllegalStateException("Given List<FullStack> must not be null");
}
- currentBoardStacksCount = fullStacks.size();
+ currentBoardStacksCount = stacks.size();
if (currentBoardStacksCount == 0) {
binding.emptyContentViewStacks.setVisibility(View.VISIBLE);
@@ -586,19 +584,19 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
listMenu.findItem(R.id.archive_cards).setVisible(currentBoardHasStacks);
int stackPositionInAdapter = 0;
- stackAdapter.setStacks(fullStacks);
+ stackAdapter.setStacks(stacks);
long currentStackId = readCurrentStackId(this, mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId());
for (int i = 0; i < currentBoardStacksCount; i++) {
- if (fullStacks.get(i).getLocalId() == currentStackId || currentStackId == NO_STACK_ID) {
+ if (stacks.get(i).getLocalId() == currentStackId || currentStackId == NO_STACK_ID) {
stackPositionInAdapter = i;
break;
}
}
final int stackPositionInAdapterClone = stackPositionInAdapter;
final TabTitleGenerator tabTitleGenerator = position -> {
- if (fullStacks.size() > position) {
- return fullStacks.get(position).getStack().getTitle();
+ if (stacks.size() > position) {
+ return stacks.get(position).getTitle();
} else {
DeckLog.logError(new IllegalStateException("Could not generate tab title for position " + position + " because list size is only " + currentBoardStacksCount));
return "ERROR";
@@ -671,71 +669,67 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.archive_cards: {
- final FullStack fullStack = stackAdapter.getItem(binding.viewPager.getCurrentItem());
- final long stackLocalId = fullStack.getLocalId();
- observeOnce(syncManager.countCardsInStack(mainViewModel.getCurrentAccount().getId(), stackLocalId), MainActivity.this, (numberOfCards) -> {
- new BrandedAlertDialogBuilder(this)
- .setTitle(R.string.archive_cards)
- .setMessage(getString(R.string.do_you_want_to_archive_all_cards_of_the_list, fullStack.getStack().getTitle()))
- .setPositiveButton(R.string.simple_archive, (dialog, whichButton) -> {
- final WrappedLiveData<Void> archiveStackLiveData = syncManager.archiveCardsInStack(mainViewModel.getCurrentAccount().getId(), stackLocalId);
- observeOnce(archiveStackLiveData, this, (result) -> {
- if (archiveStackLiveData.hasError()) {
- ExceptionDialogFragment.newInstance(archiveStackLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- }
- });
- })
- .setNeutralButton(android.R.string.cancel, null)
- .create()
- .show();
- });
- return true;
- }
- case R.id.add_list: {
- EditStackDialogFragment.newInstance(NO_STACK_ID).show(getSupportFragmentManager(), addList);
- return true;
- }
- case R.id.rename_list: {
- final long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
- observeOnce(syncManager.getStack(mainViewModel.getCurrentAccount().getId(), stackId), MainActivity.this, fullStack ->
- EditStackDialogFragment.newInstance(fullStack.getLocalId(), fullStack.getStack().getTitle())
- .show(getSupportFragmentManager(), EditStackDialogFragment.class.getCanonicalName()));
- return true;
- }
- case R.id.move_list_left: {
- final long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
- // TODO error handling
- final int stackLeftPosition = binding.viewPager.getCurrentItem() - 1;
- final long stackLeftId = stackAdapter.getItem(stackLeftPosition).getLocalId();
- syncManager.swapStackOrder(mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId(), new Pair<>(stackId, stackLeftId));
- stackMoved = true;
- return true;
- }
- case R.id.move_list_right: {
- final long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
- // TODO error handling
- final int stackRightPosition = binding.viewPager.getCurrentItem() + 1;
- final long stackRightId = stackAdapter.getItem(stackRightPosition).getLocalId();
- syncManager.swapStackOrder(mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId(), new Pair<>(stackId, stackRightId));
- stackMoved = true;
- return true;
- }
- case R.id.delete_list: {
- final long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
- observeOnce(syncManager.countCardsInStack(mainViewModel.getCurrentAccount().getId(), stackId), MainActivity.this, (numberOfCards) -> {
- if (numberOfCards != null && numberOfCards > 0) {
- DeleteStackDialogFragment.newInstance(stackId, numberOfCards).show(getSupportFragmentManager(), DeleteStackDialogFragment.class.getCanonicalName());
- } else {
- onStackDeleted(stackId);
- }
- });
- return true;
- }
- default:
- return super.onOptionsItemSelected(item);
+ int itemId = item.getItemId();
+ if (itemId == R.id.archive_cards) {
+ final Stack stack = stackAdapter.getItem(binding.viewPager.getCurrentItem());
+ final long stackLocalId = stack.getLocalId();
+ observeOnce(mainViewModel.countCardsInStack(mainViewModel.getCurrentAccount().getId(), stackLocalId), MainActivity.this, (numberOfCards) -> {
+ new BrandedAlertDialogBuilder(this)
+ .setTitle(R.string.archive_cards)
+ .setMessage(getString(FilterInformation.hasActiveFilter(filterViewModel.getFilterInformation().getValue())
+ ? R.string.do_you_want_to_archive_all_cards_of_the_filtered_list
+ : 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());
+ }
+ });
+ })
+ .setNeutralButton(android.R.string.cancel, null)
+ .create()
+ .show();
+ });
+ return true;
+ } else if (itemId == R.id.add_list) {
+ EditStackDialogFragment.newInstance(NO_STACK_ID).show(getSupportFragmentManager(), addList);
+ return true;
+ } else if (itemId == R.id.rename_list) {
+ final long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
+ observeOnce(mainViewModel.getStack(mainViewModel.getCurrentAccount().getId(), stackId), MainActivity.this, fullStack ->
+ EditStackDialogFragment.newInstance(fullStack.getLocalId(), fullStack.getStack().getTitle())
+ .show(getSupportFragmentManager(), EditStackDialogFragment.class.getCanonicalName()));
+ return true;
+ } else if (itemId == R.id.move_list_left) {
+ final long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
+ // TODO error handling
+ final int stackLeftPosition = binding.viewPager.getCurrentItem() - 1;
+ final long stackLeftId = stackAdapter.getItem(stackLeftPosition).getLocalId();
+ mainViewModel.swapStackOrder(mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId(), new Pair<>(stackId, stackLeftId));
+ stackMoved = true;
+ return true;
+ } else if (itemId == R.id.move_list_right) {
+ final long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
+ // TODO error handling
+ final int stackRightPosition = binding.viewPager.getCurrentItem() + 1;
+ final long stackRightId = stackAdapter.getItem(stackRightPosition).getLocalId();
+ mainViewModel.swapStackOrder(mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId(), new Pair<>(stackId, stackRightId));
+ stackMoved = true;
+ return true;
+ } else if (itemId == R.id.delete_list) {
+ final long stackId = stackAdapter.getItem(binding.viewPager.getCurrentItem()).getLocalId();
+ observeOnce(mainViewModel.countCardsInStack(mainViewModel.getCurrentAccount().getId(), stackId), MainActivity.this, (numberOfCards) -> {
+ if (numberOfCards != null && numberOfCards > 0) {
+ DeleteStackDialogFragment.newInstance(stackId, numberOfCards).show(getSupportFragmentManager(), DeleteStackDialogFragment.class.getCanonicalName());
+ } else {
+ onStackDeleted(stackId);
+ }
+ });
+ return true;
}
+ return super.onOptionsItemSelected(item);
}
protected void showFabIfEditPermissionGranted() {
@@ -780,7 +774,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
default:
try {
AccountImporter.onActivityResult(requestCode, resultCode, data, this, (account) -> {
- final WrappedLiveData<Account> accountLiveData = this.syncManager.createAccount(new Account(account.name, account.userId, account.url));
+ final WrappedLiveData<Account> accountLiveData = mainViewModel.createAccount(new Account(account.name, account.userId, account.url));
accountLiveData.observe(this, (createdAccount) -> {
if (!accountLiveData.hasError()) {
if (createdAccount == null) {
@@ -789,12 +783,13 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
final SyncManager importSyncManager = new SyncManager(this, account.name);
importSyncManager.refreshCapabilities(new IResponseCallback<Capabilities>(createdAccount) {
+ @SuppressLint("StringFormatInvalid")
@Override
public void onResponse(Capabilities response) {
if (!response.isMaintenanceEnabled()) {
if (response.getDeckVersion().isSupported(getApplicationContext())) {
runOnUiThread(() -> {
- syncManager = importSyncManager;
+ mainViewModel.setSyncManager(importSyncManager);
mainViewModel.setCurrentAccount(account);
final Snackbar importSnackbar = BrandedSnackbar.make(binding.coordinatorLayout, R.string.account_is_getting_imported, Snackbar.LENGTH_INDEFINITE);
@@ -827,7 +822,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
startActivity(openURL);
finish();
}).show());
- syncManager.deleteAccount(createdAccount.getId());
+ mainViewModel.deleteAccount(createdAccount.getId());
}
} else {
DeckLog.warn("Cannot import account because server version is currently in maintenance mode.");
@@ -836,14 +831,14 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
.setMessage(getString(R.string.maintenance_mode_explanation, createdAccount.getUrl()))
.setPositiveButton(R.string.simple_close, null)
.show());
- syncManager.deleteAccount(createdAccount.getId());
+ mainViewModel.deleteAccount(createdAccount.getId());
}
}
@Override
public void onError(Throwable throwable) {
super.onError(throwable);
- syncManager.deleteAccount(createdAccount.getId());
+ mainViewModel.deleteAccount(createdAccount.getId());
if (throwable instanceof OfflineException) {
DeckLog.warn("Cannot import account because device is currently offline.");
runOnUiThread(() -> new BrandedAlertDialogBuilder(MainActivity.this)
@@ -893,7 +888,7 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@Override
public void onAvailable(@NonNull Network network) {
DeckLog.log("Got Network connection");
- syncManager.synchronize(new IResponseCallback<Boolean>(mainViewModel.getCurrentAccount()) {
+ mainViewModel.synchronize(new IResponseCallback<Boolean>(mainViewModel.getCurrentAccount()) {
@Override
public void onResponse(Boolean response) {
DeckLog.log("Auto-Sync after connection available successful");
@@ -924,9 +919,9 @@ 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 = syncManager.deleteStack(mainViewModel.getCurrentAccount().getId(), stackId, mainViewModel.getCurrentBoardLocalId());
+ final WrappedLiveData<Void> deleteStackLiveData = mainViewModel.deleteStack(mainViewModel.getCurrentAccount().getId(), stackId, mainViewModel.getCurrentBoardLocalId());
observeOnce(deleteStackLiveData, this, (v) -> {
- if (deleteStackLiveData.hasError()) {
+ if (deleteStackLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteStackLiveData.getError())) {
ExceptionDialogFragment.newInstance(deleteStackLiveData.getError(), mainViewModel.getCurrentAccount()).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
}
});
@@ -946,7 +941,14 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
EditBoardDialogFragment.newInstance().show(getSupportFragmentManager(), addBoard);
}
}
- syncManager.deleteBoard(board);
+
+ 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());
+ }
+ });
+
binding.drawerLayout.closeDrawer(GravityCompat.START);
}
@@ -966,6 +968,39 @@ public class MainActivity extends BrandedActivity implements DeleteStackListener
@Override
public void onArchive(@NonNull Board board) {
- syncManager.archiveBoard(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());
+ }
+ });
+ }
+
+ @Override
+ public void onClone(Board board) {
+ final String[] animals = {getString(R.string.clone_cards)};
+ final boolean[] checkedItems = {false};
+ new BrandedAlertDialogBuilder(this)
+ .setTitle(R.string.clone_board)
+ .setMultiChoiceItems(animals, checkedItems, (dialog, which, isChecked) -> checkedItems[0] = isChecked)
+ .setPositiveButton(R.string.simple_clone, (dialog, which) -> {
+ 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();
+ }
+ }));
+ })
+ .setNeutralButton(android.R.string.cancel, null)
+ .show();
}
} \ No newline at end of file
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 e5bd4f482..ac244b88e 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
@@ -2,19 +2,42 @@ package it.niedermann.nextcloud.deck.ui;
import android.app.Application;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.util.Pair;
import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
+import java.io.File;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
+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.User;
+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.internal.FilterInformation;
+import it.niedermann.nextcloud.deck.model.ocs.Capabilities;
+import it.niedermann.nextcloud.deck.model.ocs.comment.DeckComment;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
@SuppressWarnings("WeakerAccess")
public class MainViewModel extends AndroidViewModel {
- private MutableLiveData<Account> currentAccount = new MutableLiveData<>();
+ private SyncManager syncManager;
+
+ private final MutableLiveData<Account> currentAccount = new MutableLiveData<>();
+ @Nullable
private Board currentBoard;
private boolean currentAccountHasArchivedBoards = false;
@@ -22,6 +45,7 @@ public class MainViewModel extends AndroidViewModel {
public MainViewModel(@NonNull Application application) {
super(application);
+ this.syncManager = new SyncManager(application);
}
public Account getCurrentAccount() {
@@ -37,16 +61,21 @@ public class MainViewModel extends AndroidViewModel {
this.currentAccountIsSupportedVersion = currentAccount.getServerDeckVersionAsObject().isSupported(getApplication().getApplicationContext());
}
- public void setCurrentBoard(Board currentBoard) {
+ public void setCurrentBoard(@NonNull Board currentBoard) {
this.currentBoard = currentBoard;
}
public Long getCurrentBoardLocalId() {
+ if (currentBoard == null) {
+ throw new IllegalStateException("getCurrentBoardLocalId() called before setCurrentBoard()");
+ }
return this.currentBoard.getLocalId();
}
- @Nullable
public Long getCurrentBoardRemoteId() {
+ if (currentBoard == null) {
+ throw new IllegalStateException("getCurrentBoardRemoteId() called before setCurrentBoard()");
+ }
return this.currentBoard.getId();
}
@@ -65,4 +94,192 @@ public class MainViewModel extends AndroidViewModel {
public boolean isCurrentAccountIsSupportedVersion() {
return currentAccountIsSupportedVersion;
}
+
+ public void recreateSyncManager() {
+ this.syncManager = new SyncManager(getApplication());
+ }
+
+ public void setSyncManager(@NonNull SyncManager syncManager) {
+ this.syncManager = syncManager;
+ }
+
+ public void synchronize(@NonNull IResponseCallback<Boolean> responseCallback) {
+ syncManager.synchronize(responseCallback);
+ }
+
+ public void refreshCapabilities(@NonNull IResponseCallback<Capabilities> callback) {
+ syncManager.refreshCapabilities(callback);
+ }
+
+ public LiveData<Boolean> hasAccounts() {
+ return syncManager.hasAccounts();
+ }
+
+ public WrappedLiveData<Account> createAccount(@NonNull Account accout) {
+ return syncManager.createAccount(accout);
+ }
+
+ public void deleteAccount(long id) {
+ syncManager.deleteAccount(id);
+ }
+
+ public LiveData<List<Account>> readAccounts() {
+ return syncManager.readAccounts();
+ }
+
+ public WrappedLiveData<FullBoard> createBoard(long accountId, @NonNull Board board) {
+ return syncManager.createBoard(accountId, board);
+ }
+
+ public WrappedLiveData<FullBoard> updateBoard(@NonNull FullBoard board) {
+ return syncManager.updateBoard(board);
+ }
+
+ public LiveData<List<Board>> getBoards(long accountId, boolean archived) {
+ return syncManager.getBoards(accountId, archived);
+ }
+
+ public LiveData<FullBoard> getFullBoardById(Long accountId, Long localId) {
+ return syncManager.getFullBoardById(accountId, localId);
+ }
+
+ public WrappedLiveData<FullBoard> archiveBoard(@NonNull Board board) {
+ return syncManager.archiveBoard(board);
+ }
+
+ public WrappedLiveData<FullBoard> dearchiveBoard(@NonNull Board board) {
+ return syncManager.dearchiveBoard(board);
+ }
+
+ public WrappedLiveData<FullBoard> cloneBoard(long originAccountId, long originBoardLocalId, long targetAccountId, @ColorInt int targetBoardColor, boolean cloneCards) {
+ return syncManager.cloneBoard(originAccountId, originBoardLocalId, targetAccountId, targetBoardColor, cloneCards);
+ }
+
+ public WrappedLiveData<Void> deleteBoard(@NonNull Board board) {
+ return syncManager.deleteBoard(board);
+ }
+
+ public LiveData<Boolean> hasArchivedBoards(long accountId) {
+ return syncManager.hasArchivedBoards(accountId);
+ }
+
+ public WrappedLiveData<AccessControl> createAccessControl(long accountId, AccessControl entity) {
+ return syncManager.createAccessControl(accountId, entity);
+ }
+
+ public WrappedLiveData<AccessControl> updateAccessControl(@NonNull AccessControl entity) {
+ return syncManager.updateAccessControl(entity);
+ }
+
+ 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 WrappedLiveData<Label> createLabel(long accountId, Label label, long localBoardId) {
+ return syncManager.createLabel(accountId, label, localBoardId);
+ }
+
+ public LiveData<Integer> countCardsWithLabel(long localLabelId) {
+ return syncManager.countCardsWithLabel(localLabelId);
+ }
+
+ public WrappedLiveData<Label> updateLabel(@NonNull Label label) {
+ return syncManager.updateLabel(label);
+ }
+
+ public WrappedLiveData<Void> deleteLabel(@NonNull Label label) {
+ return syncManager.deleteLabel(label);
+ }
+
+ 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 LiveData<FullStack> getStack(long accountId, long localStackId) {
+ return syncManager.getStack(accountId, localStackId);
+ }
+
+ public void swapStackOrder(long accountId, long boardLocalId, @NonNull Pair<Long, Long> stackLocalIds) {
+ syncManager.swapStackOrder(accountId, boardLocalId, stackLocalIds);
+ }
+
+ public WrappedLiveData<FullStack> updateStackTitle(long localStackId, @NonNull String newTitle) {
+ return syncManager.updateStackTitle(localStackId, newTitle);
+ }
+
+ public WrappedLiveData<Void> deleteStack(long accountId, long stackLocalId, long boardLocalId) {
+ return syncManager.deleteStack(accountId, stackLocalId, boardLocalId);
+ }
+
+ public void reorder(long accountId, @NonNull FullCard movedCard, long newStackId, int newIndex) {
+ syncManager.reorder(accountId, movedCard, newStackId, newIndex);
+ }
+
+ public LiveData<Integer> countCardsInStack(long accountId, long localStackId) {
+ return syncManager.countCardsInStack(accountId, localStackId);
+ }
+
+ public WrappedLiveData<Void> archiveCardsInStack(long accountId, long stackLocalId, @NonNull FilterInformation filterInformation) {
+ return syncManager.archiveCardsInStack(accountId, stackLocalId, filterInformation);
+ }
+
+ public WrappedLiveData<FullCard> updateCard(@NonNull FullCard fullCard) {
+ return syncManager.updateCard(fullCard);
+ }
+
+ public void addCommentToCard(long accountId, long cardId, @NonNull DeckComment comment) {
+ 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 addOrUpdateSingleCardWidget(int widgetId, long accountId, long boardId, long localCardId) {
+ syncManager.addOrUpdateSingleCardWidget(widgetId, accountId, boardId, localCardId);
+ }
+
+ public LiveData<List<FullCard>> getFullCardsForStack(long accountId, long localStackId, @Nullable FilterInformation filter) {
+ return syncManager.getFullCardsForStack(accountId, localStackId, filter);
+ }
+
+ public WrappedLiveData<Void> moveCard(long originAccountId, long originCardLocalId, long targetAccountId, long targetBoardLocalId, long targetStackLocalId) {
+ return syncManager.moveCard(originAccountId, originCardLocalId, targetAccountId, targetBoardLocalId, targetStackLocalId);
+ }
+
+ public LiveData<List<FullCard>> getArchivedFullCardsForBoard(long accountId, long localBoardId) {
+ return syncManager.getArchivedFullCardsForBoard(accountId, localBoardId);
+ }
+
+ public void assignUserToCard(@NonNull User user, @NonNull Card card) {
+ syncManager.assignUserToCard(user, card);
+ }
+
+ public void unassignUserFromCard(@NonNull User user, @NonNull Card card) {
+ syncManager.unassignUserFromCard(user, card);
+ }
+
+ public User getUserByUidDirectly(long accountId, String uid) {
+ return syncManager.getUserByUidDirectly(accountId, uid);
+ }
+
+ public WrappedLiveData<FullCard> archiveCard(@NonNull FullCard card) {
+ return syncManager.archiveCard(card);
+ }
+
+ public WrappedLiveData<FullCard> dearchiveCard(@NonNull FullCard card) {
+ return syncManager.dearchiveCard(card);
+ }
+
+ public WrappedLiveData<Void> deleteCard(@NonNull Card card) {
+ return syncManager.deleteCard(card);
+ }
}
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
new file mode 100644
index 000000000..2339a8783
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PickStackActivity.java
@@ -0,0 +1,115 @@
+package it.niedermann.nextcloud.deck.ui;
+
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.os.Bundle;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.List;
+
+import it.niedermann.android.util.ColorUtil;
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ActivityPickStackBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.Stack;
+import it.niedermann.nextcloud.deck.ui.branding.Branded;
+import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
+import it.niedermann.nextcloud.deck.ui.pickstack.PickStackFragment;
+import it.niedermann.nextcloud.deck.ui.pickstack.PickStackListener;
+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 {
+
+ protected ActivityPickStackBinding binding;
+ protected PickStackViewModel viewModel;
+
+ private boolean brandingEnabled;
+
+ private Account selectedAccount;
+ private Board selectedBoard;
+ private Stack selectedStack;
+
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));
+
+ brandingEnabled = isBrandingEnabled(this);
+
+ binding = ActivityPickStackBinding.inflate(getLayoutInflater());
+ viewModel = new ViewModelProvider(this).get(PickStackViewModel.class);
+
+ setContentView(binding.getRoot());
+ setSupportActionBar(binding.toolbar);
+
+ switchMap(viewModel.hasAccounts(), hasAccounts -> {
+ if (hasAccounts) {
+ return viewModel.readAccounts();
+ } else {
+ startActivityForResult(new Intent(this, ImportAccountActivity.class), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
+ return null;
+ }
+ }).observe(this, (List<Account> accounts) -> {
+ if (accounts == null || accounts.size() == 0) {
+ throw new IllegalStateException("hasAccounts() returns true, but readAccounts() returns null or has no entry");
+ }
+ getSupportFragmentManager()
+ .beginTransaction()
+ .add(R.id.fragment_container, PickStackFragment.newInstance(showBoardsWithoutEditPermission()))
+ .commit();
+ });
+ binding.cancel.setOnClickListener((v) -> finish());
+ binding.submit.setOnClickListener((v) -> onSubmit(selectedAccount, selectedBoard.getLocalId(), selectedStack.getLocalId()));
+ }
+
+ @Override
+ public void onStackPicked(@NonNull Account account, @Nullable Board board, @Nullable Stack stack) {
+ this.selectedAccount = account;
+ this.selectedBoard = board;
+ this.selectedStack = stack;
+ if (board == null) {
+ binding.submit.setEnabled(false);
+ } else {
+ applyBrand(board.getColor());
+ binding.submit.setEnabled(stack != null);
+ }
+ }
+
+ @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));
+ }
+ } catch (Throwable t) {
+ DeckLog.logError(t);
+ }
+ }
+
+ abstract protected void onSubmit(Account account, long boardId, 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 b0b0d68ae..cdc20ed50 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
@@ -5,26 +5,28 @@ import android.net.Uri;
import android.text.TextUtils;
import android.view.View;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.UiThread;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.lifecycle.ViewModelProvider;
-import com.nextcloud.android.sso.helper.SingleAccountHelper;
-
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
+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.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.card.EditActivity;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
+import it.niedermann.nextcloud.deck.util.ProjectUtil;
-import static android.graphics.Color.parseColor;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
public class PushNotificationActivity extends AppCompatActivity {
private ActivityPushNotificationBinding binding;
+ private PushNotificationViewModel viewModel;
// Provided by Files app NotificationJob
private static final String KEY_SUBJECT = "subject";
@@ -44,6 +46,8 @@ public class PushNotificationActivity extends AppCompatActivity {
}
binding = ActivityPushNotificationBinding.inflate(getLayoutInflater());
+ viewModel = new ViewModelProvider(this).get(PushNotificationViewModel.class);
+
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
@@ -56,62 +60,105 @@ public class PushNotificationActivity extends AppCompatActivity {
}
final String link = getIntent().getStringExtra(KEY_LINK);
+ long[] ids = ProjectUtil.extractBoardIdAndCardIdFromUrl(link);
binding.cancel.setOnClickListener((v) -> finish());
- final SyncManager accountReadingSyncManager = new SyncManager(this);
final String cardRemoteIdString = getIntent().getStringExtra(KEY_CARD_REMOTE_ID);
final String accountString = getIntent().getStringExtra(KEY_ACCOUNT);
DeckLog.verbose("cardRemoteIdString = " + cardRemoteIdString);
- if (cardRemoteIdString != null) {
- try {
- final int cardRemoteId = Integer.parseInt(cardRemoteIdString);
- observeOnce(accountReadingSyncManager.readAccount(accountString), this, (account -> {
- if (account != null) {
- SingleAccountHelper.setCurrentAccount(this, account.getName());
- final SyncManager syncManager = new SyncManager(this);
- DeckLog.verbose("account: " + account);
- observeOnce(syncManager.getLocalBoardIdByCardRemoteIdAndAccount(cardRemoteId, account), PushNotificationActivity.this, (boardLocalId -> {
- DeckLog.verbose("BoardLocalId " + boardLocalId);
- if (boardLocalId != null) {
- observeOnce(syncManager.synchronizeCardByRemoteId(cardRemoteId, account), PushNotificationActivity.this, (fullCard -> {
- DeckLog.verbose("FullCard: " + fullCard);
- if (fullCard != null) {
- runOnUiThread(() -> {
- binding.submit.setOnClickListener((v) -> launchEditActivity(account, boardLocalId, fullCard.getLocalId()));
- binding.submit.setText(R.string.simple_open);
- applyBrandToSubmitButton(account);
- binding.submit.setEnabled(true);
- binding.progress.setVisibility(View.INVISIBLE);
- });
- } else {
- DeckLog.warn("Something went wrong while synchronizing the card " + cardRemoteId + " (cardRemoteId). Given fullCard is null.");
- applyBrandToSubmitButton(account);
- fallbackToBrowser(link);
- }
- }));
- } 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);
+ 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);
}
}
+ private void openCardOnSubmit(@NonNull Account account, long boardLocalId, long cardlocalId) {
+ runOnUiThread(() -> {
+ binding.submit.setOnClickListener((v) -> launchEditActivity(account, boardLocalId, cardlocalId));
+ binding.submit.setText(R.string.simple_open);
+ applyBrandToSubmitButton(account);
+ binding.submit.setEnabled(true);
+ binding.progress.setVisibility(View.INVISIBLE);
+ });
+ }
+
/**
* If anything goes wrong and we cannot open the card directly, we fall back to open the given link in the webbrowser
*/
@@ -146,10 +193,13 @@ public class PushNotificationActivity extends AppCompatActivity {
return true;
}
+ // TODO implement Branded interface
+ // TODO apply branding based on board color
public void applyBrandToSubmitButton(@NonNull Account account) {
+ @ColorInt final int mainColor = account.getColor();
try {
- binding.submit.setBackgroundColor(parseColor(account.getColor()));
- binding.submit.setTextColor(parseColor(account.getTextColor()));
+ binding.submit.setBackgroundColor(mainColor);
+ binding.submit.setTextColor(ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(mainColor));
} catch (Throwable t) {
DeckLog.logError(t);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java
new file mode 100644
index 000000000..d15b412f4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/PushNotificationViewModel.java
@@ -0,0 +1,52 @@
+package it.niedermann.nextcloud.deck.ui;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+
+import com.nextcloud.android.sso.helper.SingleAccountHelper;
+
+import it.niedermann.nextcloud.deck.api.IResponseCallback;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.Card;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+
+public class PushNotificationViewModel extends AndroidViewModel {
+
+ private final SyncManager readAccountSyncManager;
+ private SyncManager accountSpecificSyncManager;
+
+ public PushNotificationViewModel(@NonNull Application application) {
+ super(application);
+ this.readAccountSyncManager = new SyncManager(application);
+ }
+
+ public LiveData<Account> readAccount(@Nullable String name) {
+ return readAccountSyncManager.readAccount(name);
+ }
+
+ public void setAccount(@NonNull String accountName) {
+ SingleAccountHelper.setCurrentAccount(getApplication(), accountName);
+ accountSpecificSyncManager = new SyncManager(getApplication());
+ }
+
+ public LiveData<Board> getBoardByRemoteId(long accountId, long remoteId) {
+ return accountSpecificSyncManager.getBoardByRemoteId(accountId, remoteId);
+ }
+
+ public LiveData<Card> getCardByRemoteID(long accountId, long remoteId) {
+ return accountSpecificSyncManager.getCardByRemoteID(accountId, remoteId);
+ }
+
+ public void synchronizeCard(@NonNull IResponseCallback<Boolean> responseCallback, Card card) {
+ accountSpecificSyncManager.synchronizeCard(responseCallback, card);
+ }
+
+ public void synchronizeBoard(@NonNull IResponseCallback<Boolean> responseCallback, long localBoadId) {
+ accountSpecificSyncManager.synchronizeBoard(responseCallback, localBoadId);
+ }
+}
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 c00ff212a..0bd92bc78 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
@@ -14,13 +14,13 @@ import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.FragmentAboutLicenseTabBinding;
import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment;
-import it.niedermann.nextcloud.deck.util.ColorUtil;
import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
-import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficientBigAreas;
+import static it.niedermann.nextcloud.deck.util.DeckColorUtil.contrastRatioIsSufficientBigAreas;
import static it.niedermann.nextcloud.deck.util.SpannableUtil.setTextWithURL;
public class AboutFragmentLicenseTab extends BrandedFragment {
@@ -42,6 +42,6 @@ public class AboutFragmentLicenseTab extends BrandedFragment {
? mainColor
: isDarkTheme(requireContext()) ? Color.WHITE : Color.BLACK;
DrawableCompat.setTintList(binding.aboutAppLicenseButton.getBackground(), ColorStateList.valueOf(finalMainColor));
- binding.aboutAppLicenseButton.setTextColor(ColorUtil.getForegroundColorForBackgroundColor(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 592f2e8cc..744498c4a 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.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
@@ -16,43 +15,37 @@ import com.bumptech.glide.request.RequestOptions;
import com.nextcloud.android.sso.AccountImporter;
import com.nextcloud.android.sso.exceptions.AndroidGetAccountsPermissionNotGranted;
import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
+import com.nextcloud.android.sso.ui.UiExceptionManager;
+import it.niedermann.android.util.DimensionUtil;
+import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.DialogAccountSwitcherBinding;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
import it.niedermann.nextcloud.deck.ui.manageaccounts.ManageAccountsActivity;
-import it.niedermann.nextcloud.deck.util.ExceptionUtil;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
import static it.niedermann.nextcloud.deck.ui.MainActivity.ACTIVITY_MANAGE_ACCOUNTS;
-import static it.niedermann.nextcloud.deck.util.DimensionUtil.dpToPx;
public class AccountSwitcherDialog extends BrandedDialogFragment {
private AccountSwitcherAdapter adapter;
- private SyncManager syncManager;
private DialogAccountSwitcherBinding binding;
private MainViewModel viewModel;
- @Override
- public void onAttach(@NonNull Context context) {
- super.onAttach(context);
- viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
- syncManager = new SyncManager(requireActivity());
- }
-
@NonNull
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
binding = DialogAccountSwitcherBinding.inflate(requireActivity().getLayoutInflater());
+ viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
+
binding.accountName.setText(viewModel.getCurrentAccount().getUserName());
binding.accountHost.setText(Uri.parse(viewModel.getCurrentAccount().getUrl()).getHost());
binding.check.setSelected(true);
Glide.with(requireContext())
- .load(viewModel.getCurrentAccount().getAvatarUrl(dpToPx(binding.currentAccountItemAvatar.getContext(), R.dimen.avatar_size)))
+ .load(viewModel.getCurrentAccount().getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.currentAccountItemAvatar.getContext(), R.dimen.avatar_size)))
.placeholder(R.drawable.ic_baseline_account_circle_24)
.error(R.drawable.ic_baseline_account_circle_24)
.apply(RequestOptions.circleCropTransform())
@@ -65,7 +58,7 @@ public class AccountSwitcherDialog extends BrandedDialogFragment {
dismiss();
}));
- observeOnce(syncManager.readAccounts(), requireActivity(), (accounts) -> {
+ observeOnce(viewModel.readAccounts(), requireActivity(), (accounts) -> {
accounts.remove(viewModel.getCurrentAccount());
adapter.setAccounts(accounts);
});
@@ -76,7 +69,10 @@ public class AccountSwitcherDialog extends BrandedDialogFragment {
try {
AccountImporter.pickNewAccount(requireActivity());
} catch (NextcloudFilesAppNotInstalledException e) {
- ExceptionUtil.handleNextcloudFilesAppNotInstalledException(requireContext(), e);
+ UiExceptionManager.showDialogForException(requireContext(), e);
+ DeckLog.warn("=============================================================");
+ DeckLog.warn("Nextcloud app is not installed. Cannot choose account");
+ DeckLog.logError(e);
} catch (AndroidGetAccountsPermissionNotGranted e) {
AccountImporter.requestAndroidAccountPermissionsAndPickAccount(requireActivity());
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java
index 9c93c422e..a60b6a0ea 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/accountswitcher/AccountSwitcherViewHolder.java
@@ -10,12 +10,11 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
-import it.niedermann.android.glidesso.SingleSignOnUrl;
+import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemAccountChooseBinding;
import it.niedermann.nextcloud.deck.model.Account;
-
-import static it.niedermann.nextcloud.deck.util.DimensionUtil.dpToPx;
+import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl;
public class AccountSwitcherViewHolder extends RecyclerView.ViewHolder {
@@ -30,7 +29,7 @@ public class AccountSwitcherViewHolder extends RecyclerView.ViewHolder {
binding.accountName.setText(account.getUserName());
binding.accountHost.setText(Uri.parse(account.getUrl()).getHost());
Glide.with(itemView.getContext())
- .load(new SingleSignOnUrl(account.getName(), account.getAvatarUrl(dpToPx(binding.accountItemAvatar.getContext(), R.dimen.avatar_size))))
+ .load(new SingleSignOnUrl(account.getName(), account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.accountItemAvatar.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/archivedboards/ArchivedBoardViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardViewHolder.java
index 5ab94b4f6..30f1e5d49 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedboards/ArchivedBoardViewHolder.java
@@ -6,6 +6,7 @@ import android.view.MenuItem;
import android.view.View;
import androidx.appcompat.widget.PopupMenu;
+import androidx.core.content.ContextCompat;
import androidx.core.util.Consumer;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
@@ -30,14 +31,13 @@ public class ArchivedBoardViewHolder extends RecyclerView.ViewHolder {
void bind(boolean isSupportedVersion, Board board, FragmentManager fragmentManager, Consumer<Board> dearchiveBoardListener) {
final Context context = itemView.getContext();
- binding.boardIcon.setImageDrawable(ViewUtil.getTintedImageView(binding.boardIcon.getContext(), R.drawable.circle_grey600_36dp, "#" + board.getColor()));
+ binding.boardIcon.setImageDrawable(ViewUtil.getTintedImageView(binding.boardIcon.getContext(), R.drawable.circle_grey600_36dp, board.getColor()));
binding.boardMenu.setVisibility(View.GONE);
binding.boardTitle.setText(board.getTitle());
if (isSupportedVersion) {
if (board.isPermissionManage()) {
binding.boardMenu.setVisibility(View.VISIBLE);
- binding.boardMenu.setImageDrawable(ViewUtil.getTintedImageView(context, R.drawable.ic_menu, R.color.grey600));
-
+ binding.boardMenu.setImageDrawable(ViewUtil.getTintedImageView(context, R.drawable.ic_menu, ContextCompat.getColor(context, R.color.grey600)));
binding.boardMenu.setOnClickListener((v) -> {
PopupMenu popup = new PopupMenu(context, binding.boardMenu);
popup.getMenuInflater().inflate(R.menu.archived_board_menu, popup.getMenu());
@@ -47,28 +47,27 @@ public class ArchivedBoardViewHolder extends RecyclerView.ViewHolder {
}
popup.setOnMenuItemClickListener((MenuItem item) -> {
final String editBoard = context.getString(R.string.edit_board);
- switch (item.getItemId()) {
- case SHARE_BOARD_ID:
- AccessControlDialogFragment.newInstance(board.getLocalId()).show(fragmentManager, AccessControlDialogFragment.class.getSimpleName());
- return true;
- case R.id.edit_board:
- EditBoardDialogFragment.newInstance(board.getLocalId()).show(fragmentManager, editBoard);
- return true;
- case R.id.dearchive_board:
- dearchiveBoardListener.accept(board);
- return true;
- case R.id.delete_board:
- DeleteBoardDialogFragment.newInstance(board).show(fragmentManager, DeleteBoardDialogFragment.class.getSimpleName());
- return true;
- default:
- return false;
+ int itemId = item.getItemId();
+ if (itemId == SHARE_BOARD_ID) {
+ AccessControlDialogFragment.newInstance(board.getLocalId()).show(fragmentManager, AccessControlDialogFragment.class.getSimpleName());
+ return true;
+ } else if (itemId == R.id.edit_board) {
+ EditBoardDialogFragment.newInstance(board.getLocalId()).show(fragmentManager, editBoard);
+ return true;
+ } else if (itemId == R.id.dearchive_board) {
+ dearchiveBoardListener.accept(board);
+ return true;
+ } else if (itemId == R.id.delete_board) {
+ DeleteBoardDialogFragment.newInstance(board).show(fragmentManager, DeleteBoardDialogFragment.class.getSimpleName());
+ return true;
}
+ return false;
});
popup.show();
});
} else if (board.isPermissionShare()) {
binding.boardMenu.setVisibility(View.VISIBLE);
- binding.boardMenu.setImageDrawable(ViewUtil.getTintedImageView(context, R.drawable.ic_share_grey600_18dp, R.color.grey600));
+ binding.boardMenu.setImageDrawable(ViewUtil.getTintedImageView(context, R.drawable.ic_share_grey600_18dp, ContextCompat.getColor(context, R.color.grey600)));
binding.boardMenu.setOnClickListener((v) -> AccessControlDialogFragment.newInstance(board.getLocalId()).show(fragmentManager, AccessControlDialogFragment.class.getSimpleName()));
}
binding.boardMenu.setVisibility(View.VISIBLE);
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 7c3a2d23c..d6d9cac1e 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
@@ -15,13 +15,17 @@ 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;
import it.niedermann.nextcloud.deck.ui.board.EditBoardListener;
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";
@@ -29,7 +33,6 @@ public class ArchivedBoardsActvitiy extends BrandedActivity implements DeleteBoa
private MainViewModel viewModel;
private ActivityArchivedBinding binding;
private ArchivedBoardsAdapter adapter;
- private SyncManager syncManager;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -54,12 +57,18 @@ public class ArchivedBoardsActvitiy extends BrandedActivity implements DeleteBoa
viewModel = new ViewModelProvider(this).get(MainViewModel.class);
viewModel.setCurrentAccount(account);
- syncManager = new SyncManager(this);
- adapter = new ArchivedBoardsAdapter(viewModel.isCurrentAccountIsSupportedVersion(), getSupportFragmentManager(), (board) -> syncManager.dearchiveBoard(board));
+ 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());
+ }
+ });
+ });
binding.recyclerView.setAdapter(adapter);
- syncManager.getBoards(account.getId(), true).observe(this, (boards) -> {
+ viewModel.getBoards(account.getId(), true).observe(this, (boards) -> {
viewModel.setCurrentAccountHasArchivedBoards(boards != null && boards.size() > 0);
adapter.setBoards(boards == null ? Collections.emptyList() : boards);
});
@@ -80,16 +89,36 @@ public class ArchivedBoardsActvitiy extends BrandedActivity implements DeleteBoa
@Override
public void onBoardDeleted(Board board) {
- syncManager.deleteBoard(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());
+ }
+ });
}
@Override
public void onUpdateBoard(FullBoard fullBoard) {
- syncManager.updateBoard(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());
+ }
+ });
}
@Override
public void onArchive(Board board) {
- syncManager.dearchiveBoard(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());
+ }
+ });
+ }
+
+ @Override
+ public void onClone(Board board) {
+
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java
index ed2ee7097..b3533528e 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/archivedcards/ArchivedCardsActvitiy.java
@@ -6,12 +6,15 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.lifecycle.ViewModelProvider;
import it.niedermann.nextcloud.deck.databinding.ActivityArchivedBinding;
import it.niedermann.nextcloud.deck.model.Account;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper;
+import it.niedermann.nextcloud.deck.ui.MainViewModel;
import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
+import it.niedermann.nextcloud.deck.ui.pickstack.PickStackViewModel;
public class ArchivedCardsActvitiy extends BrandedActivity {
@@ -21,7 +24,8 @@ public class ArchivedCardsActvitiy extends BrandedActivity {
private ActivityArchivedBinding binding;
private ArchivedCardsAdapter adapter;
- private SyncManager syncManager;
+ private MainViewModel viewModel;
+ private PickStackViewModel pickStackViewModel;
private Account account;
private long boardId;
@@ -50,16 +54,20 @@ public class ArchivedCardsActvitiy extends BrandedActivity {
}
binding = ActivityArchivedBinding.inflate(getLayoutInflater());
+ viewModel = new ViewModelProvider(this).get(MainViewModel.class);
+ pickStackViewModel = new ViewModelProvider(this).get(PickStackViewModel.class);
+
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
- syncManager = new SyncManager(this);
+ viewModel.setCurrentAccount(account);
+ LiveDataHelper.observeOnce(viewModel.getFullBoardById(account.getId(), boardId), this, (fullBoard) -> {
+ viewModel.setCurrentBoard(fullBoard.getBoard());
- adapter = new ArchivedCardsAdapter(this, getSupportFragmentManager(), account, boardId, false, syncManager, this);
- binding.recyclerView.setAdapter(adapter);
+ adapter = new ArchivedCardsAdapter(this, getSupportFragmentManager(), viewModel, this);
+ binding.recyclerView.setAdapter(adapter);
- syncManager.getArchivedFullCardsForBoard(account.getId(), boardId).observe(this, (fullCards) -> {
- adapter.setCardList(fullCards);
+ viewModel.getArchivedFullCardsForBoard(account.getId(), boardId).observe(this, (fullCards) -> adapter.setCardList(fullCards));
});
}
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 e6abf0ccc..b5034ebfa 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,67 +1,55 @@
package it.niedermann.nextcloud.deck.ui.archivedcards;
import android.content.Context;
-import android.view.Menu;
import android.view.MenuItem;
-import android.view.View;
-import android.widget.PopupMenu;
import androidx.annotation.NonNull;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
-import org.jetbrains.annotations.NotNull;
-
import it.niedermann.nextcloud.deck.R;
-import it.niedermann.nextcloud.deck.model.Account;
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.card.ItemCardViewHolder;
+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 Account account, long boardId, boolean canEdit, @NonNull SyncManager syncManager, @NonNull LifecycleOwner lifecycleOwner) {
- super(context, fragmentManager, account, boardId, 0L, 0L, canEdit, syncManager, lifecycleOwner, null);
+ public ArchivedCardsAdapter(@NonNull Context context, @NonNull FragmentManager fragmentManager, @NonNull MainViewModel viewModel, @NonNull LifecycleOwner lifecycleOwner) {
+ super(context, fragmentManager, 0L, viewModel, lifecycleOwner, null);
}
@Override
- public void onBindViewHolder(@NonNull ItemCardViewHolder viewHolder, int position) {
- super.onBindViewHolder(viewHolder, position);
- viewHolder.binding.card.setOnClickListener(null);
- viewHolder.binding.card.setOnLongClickListener(null);
- }
-
- protected void onOverflowIconClicked(@NotNull View view, FullCard card) {
- final Context context = view.getContext();
- final PopupMenu popup = new PopupMenu(context, view);
- popup.inflate(R.menu.card_menu);
- prepareOptionsMenu(popup.getMenu(), card);
-
- popup.setOnMenuItemClickListener(item -> optionsItemSelected(context, item, card));
- popup.show();
- }
-
- protected void prepareOptionsMenu(Menu menu, @NotNull FullCard card) {
- // Nothing to do
+ public void onBindViewHolder(@NonNull AbstractCardViewHolder viewHolder, int position) {
+ viewHolder.bind(cardList.get(position), mainViewModel.getCurrentAccount(), mainViewModel.getCurrentBoardRemoteId(), false, R.menu.archived_card_menu, this, counterMaxValue, mainColor);
}
- protected boolean optionsItemSelected(@NonNull Context context, @NotNull MenuItem item, FullCard fullCard) {
- switch (item.getItemId()) {
- case R.id.action_card_dearchive: {
- // TODO error handling
- new Thread(() -> syncManager.dearchiveCard(fullCard)).start();
- return true;
- }
- case R.id.action_card_delete: {
- // TODO error handling
- syncManager.deleteCard(fullCard.getCard());
- return true;
- }
- default: {
- return false;
- }
+ @Override
+ 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());
+ }
+ });
+ 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());
+ }
+ });
+ return true;
}
+ return super.onCardOptionsItemSelected(menuItem, fullCard);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentAdapter.java
index 0794323ec..c7d32bd37 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentAdapter.java
@@ -1,43 +1,31 @@
package it.niedermann.nextcloud.deck.ui.attachments;
import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.DataSource;
-import com.bumptech.glide.load.engine.GlideException;
-import com.bumptech.glide.request.RequestListener;
-import com.bumptech.glide.request.target.Target;
-
+import java.util.ArrayList;
import java.util.List;
-import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemAttachmentBinding;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
-import it.niedermann.nextcloud.deck.util.AttachmentUtil;
-import it.niedermann.nextcloud.deck.util.MimeTypeUtil;
public class AttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHolder> {
private final Account account;
private final long cardRemoteId;
@NonNull
- private List<Attachment> attachments;
- private Context context;
+ private final List<Attachment> attachments = new ArrayList<>();
@SuppressWarnings("WeakerAccess")
public AttachmentAdapter(@NonNull Account account, long cardRemoteId, @NonNull List<Attachment> attachments) {
super();
- this.attachments = attachments;
+ this.attachments.clear();
+ this.attachments.addAll(attachments);
this.account = account;
this.cardRemoteId = cardRemoteId;
}
@@ -45,43 +33,13 @@ public class AttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHolder
@NonNull
@Override
public AttachmentViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
- this.context = parent.getContext();
- return new AttachmentViewHolder(ItemAttachmentBinding.inflate(LayoutInflater.from(context), parent, false));
+ final Context context = parent.getContext();
+ return new AttachmentViewHolder(context, ItemAttachmentBinding.inflate(LayoutInflater.from(context), parent, false));
}
@Override
public void onBindViewHolder(@NonNull AttachmentViewHolder holder, int position) {
- final Attachment attachment = attachments.get(position);
- final String uri = AttachmentUtil.getRemoteUrl(account.getUrl(), cardRemoteId, attachment.getId());
- if (MimeTypeUtil.isImage(attachment.getMimetype())) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- holder.binding.preview.setTransitionName(context.getString(R.string.transition_attachment_preview, String.valueOf(attachment.getLocalId())));
- }
- holder.binding.preview.setImageResource(R.drawable.ic_image_grey600_24dp);
- Glide.with(context)
- .load(uri)
- .listener(new RequestListener<Drawable>() {
- @Override
- public boolean onLoadFailed(@Nullable GlideException e, Object model,
- Target<Drawable> target, boolean isFirstResource) {
- if (context instanceof FragmentActivity) {
- ((FragmentActivity) context).supportStartPostponedEnterTransition();
- }
- return false;
- }
-
- @Override
- public boolean onResourceReady(Drawable resource, Object model,
- Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
- if (context instanceof FragmentActivity) {
- ((FragmentActivity) context).supportStartPostponedEnterTransition();
- }
- return false;
- }
- })
- .error(R.drawable.ic_image_grey600_24dp)
- .into(holder.binding.preview);
- }
+ holder.bind(account, attachments.get(position), cardRemoteId);
}
@Override
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 584a57d1d..6f4fe3c74 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
@@ -1,15 +1,72 @@
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;
+import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.RecyclerView;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.load.DataSource;
+import com.bumptech.glide.load.engine.GlideException;
+import com.bumptech.glide.request.RequestListener;
+import com.bumptech.glide.request.target.Target;
+
+import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemAttachmentBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Attachment;
+import it.niedermann.nextcloud.deck.util.AttachmentUtil;
+import it.niedermann.nextcloud.deck.util.MimeTypeUtil;
public class AttachmentViewHolder extends RecyclerView.ViewHolder {
- public ItemAttachmentBinding binding;
+ @NonNull
+ private final Context parentContext;
+ @NonNull
+ private final ItemAttachmentBinding binding;
@SuppressWarnings("WeakerAccess")
- public AttachmentViewHolder(ItemAttachmentBinding binding) {
+ public AttachmentViewHolder(@NonNull Context parentContext, @NonNull ItemAttachmentBinding binding) {
super(binding.getRoot());
+ this.parentContext = parentContext;
this.binding = binding;
}
+
+ 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.setImageResource(R.drawable.ic_image_grey600_24dp);
+ binding.preview.post(() -> {
+ final String uri = AttachmentUtil.getThumbnailUrl(account.getServerDeckVersionAsObject(), account.getUrl(), cardRemoteId, attachment, binding.preview.getWidth());
+ Glide.with(parentContext)
+ .load(uri)
+ .listener(new RequestListener<Drawable>() {
+ @Override
+ public boolean onLoadFailed(@Nullable GlideException e, Object model,
+ Target<Drawable> target, boolean isFirstResource) {
+ if (parentContext instanceof FragmentActivity) {
+ ((FragmentActivity) parentContext).supportStartPostponedEnterTransition();
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onResourceReady(Drawable resource, Object model,
+ Target<Drawable> target, DataSource dataSource, boolean isFirstResource) {
+ if (parentContext instanceof FragmentActivity) {
+ ((FragmentActivity) parentContext).supportStartPostponedEnterTransition();
+ }
+ return false;
+ }
+ })
+ .error(R.drawable.ic_image_grey600_24dp)
+ .into(binding.preview);
+ });
+ }
+ }
} \ No newline at end of file
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 98cfd4440..6618f7f72 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
@@ -2,6 +2,7 @@ package it.niedermann.nextcloud.deck.ui.attachments;
import android.content.Context;
import android.content.Intent;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.view.MotionEvent;
import android.view.View;
@@ -9,6 +10,9 @@ import android.view.View;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.SharedElementCallback;
+import androidx.core.content.ContextCompat;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.RecyclerView;
import androidx.viewpager2.widget.ViewPager2;
@@ -21,7 +25,6 @@ import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ActivityAttachmentsBinding;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.Attachment;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
import it.niedermann.nextcloud.deck.util.MimeTypeUtil;
@@ -32,6 +35,7 @@ public class AttachmentsActivity extends AppCompatActivity {
private static final String BUNDLE_KEY_CURRENT_ATTACHMENT_LOCAL_ID = "currentAttachmenLocaltId";
private ActivityAttachmentsBinding binding;
+ private AttachmentsViewModel viewModel;
private ViewPager2.OnPageChangeCallback onPageChangeCallback;
@Override
@@ -40,10 +44,15 @@ public class AttachmentsActivity extends AppCompatActivity {
Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
binding = ActivityAttachmentsBinding.inflate(getLayoutInflater());
+ viewModel = new ViewModelProvider(this).get(AttachmentsViewModel.class);
+
setContentView(binding.getRoot());
supportPostponeEnterTransition();
setSupportActionBar(binding.toolbar);
+ final Drawable navigationIcon = getResources().getDrawable(R.drawable.ic_arrow_back_white_24dp);
+ DrawableCompat.setTint(navigationIcon, ContextCompat.getColor(this, android.R.color.white));
+ binding.toolbar.setNavigationIcon(navigationIcon);
final Bundle args = getIntent().getExtras();
if (args == null || !args.containsKey(BUNDLE_KEY_ACCOUNT) || !args.containsKey(BUNDLE_KEY_CARD_ID)) {
@@ -58,8 +67,7 @@ public class AttachmentsActivity extends AppCompatActivity {
long cardId = args.getLong(BUNDLE_KEY_CARD_ID);
- final SyncManager syncManager = new SyncManager(this);
- syncManager.getCardByLocalId(account.getId(), cardId).observe(this, fullCard -> {
+ viewModel.getFullCardWithProjectsByLocalId(account.getId(), cardId).observe(this, fullCard -> {
final List<Attachment> attachments = new ArrayList<>();
for (Attachment a : fullCard.getAttachments()) {
if (MimeTypeUtil.isImage(a.getMimetype())) {
@@ -67,7 +75,7 @@ public class AttachmentsActivity extends AppCompatActivity {
}
}
if (fullCard.getAttachments().size() == 0) {
- DeckLog.logError(new IllegalStateException(AttachmentsActivity.class.getSimpleName() + " called, but card " + fullCard.getLocalId() + "has no attachments"));
+ DeckLog.logError(new IllegalStateException(AttachmentsActivity.class.getSimpleName() + " called, but card " + fullCard.getCard().getTitle() + " has no attachments"));
supportFinishAfterTransition();
return;
}
@@ -79,7 +87,7 @@ public class AttachmentsActivity extends AppCompatActivity {
binding.toolbar.setTitle(attachments.get(position).getBasename());
}
};
- RecyclerView.Adapter adapter = new AttachmentAdapter(account, fullCard.getId(), attachments);
+ RecyclerView.Adapter<AttachmentViewHolder> adapter = new AttachmentAdapter(account, fullCard.getId(), attachments);
binding.viewPager.setAdapter(adapter);
binding.viewPager.registerOnPageChangeCallback(onPageChangeCallback);
@@ -104,7 +112,7 @@ public class AttachmentsActivity extends AppCompatActivity {
long currentAttachmentLocalId = attachments.get(binding.viewPager.getCurrentItem()).getLocalId();
String transitionKey = getString(R.string.transition_attachment_preview, String.valueOf(currentAttachmentLocalId));
if (transitionKey.equals(names.get(0))) {
- sharedElements.put(transitionKey, binding.viewPager.getRootView().findViewById(R.id.preview)
+ sharedElements.put(transitionKey, binding.viewPager.getRootView().findViewById(R.id.avatar)
);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsViewModel.java
new file mode 100644
index 000000000..87a17470c
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/attachments/AttachmentsViewModel.java
@@ -0,0 +1,25 @@
+package it.niedermann.nextcloud.deck.ui.attachments;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+
+import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+
+@SuppressWarnings("WeakerAccess")
+public class AttachmentsViewModel extends AndroidViewModel {
+
+ private final SyncManager syncManager;
+
+ public AttachmentsViewModel(@NonNull Application application) {
+ super(application);
+ this.syncManager = new SyncManager(application);
+ }
+
+ public LiveData<FullCardWithProjects> getFullCardWithProjectsByLocalId(long accountId, long cardLocalId) {
+ return syncManager.getFullCardWithProjectsByLocalId(accountId, cardLocalId);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/ArchiveBoardListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/ArchiveBoardListener.java
index b7e27aa97..ff0d3e941 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/ArchiveBoardListener.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/ArchiveBoardListener.java
@@ -4,4 +4,5 @@ import it.niedermann.nextcloud.deck.model.Board;
public interface ArchiveBoardListener {
void onArchive(Board board);
+ void onClone(Board board);
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/BoardAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/BoardAdapter.java
index 7049cc16c..c9501ffa9 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/BoardAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/BoardAdapter.java
@@ -37,7 +37,7 @@ public class BoardAdapter extends ArrayAdapter<Board> {
TextView boardName = convertView.findViewById(R.id.boardName);
if (board != null) {
boardName.setText(board.getTitle());
- boardName.setCompoundDrawables(ViewUtil.getTintedImageView(context, R.drawable.circle_grey600_36dp, "#" + board.getColor()), null, null, null);
+ boardName.setCompoundDrawables(ViewUtil.getTintedImageView(context, R.drawable.circle_grey600_36dp, board.getColor()), null, null, null);
} else {
DeckLog.logError(new IllegalArgumentException("board at position " + position + "is null"));
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardDialogFragment.java
index e5d0a482b..9da836c4c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardDialogFragment.java
@@ -7,13 +7,13 @@ import android.os.Bundle;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
+import androidx.core.content.ContextCompat;
import androidx.fragment.app.DialogFragment;
import androidx.lifecycle.ViewModelProvider;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.DialogTextColorInputBinding;
import it.niedermann.nextcloud.deck.model.full.FullBoard;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
@@ -53,24 +53,24 @@ public class EditBoardDialogFragment extends BrandedDialogFragment {
if (args != null && args.containsKey(KEY_BOARD_ID)) {
dialogBuilder.setTitle(R.string.edit_board);
dialogBuilder.setPositiveButton(R.string.simple_save, (dialog, which) -> {
- this.fullBoard.board.setColor(binding.colorChooser.getSelectedColor().substring(1));
+ this.fullBoard.board.setColor(binding.colorChooser.getSelectedColor());
this.fullBoard.board.setTitle(binding.input.getText().toString());
- editBoardListener.onUpdateBoard(fullBoard);
+ this.editBoardListener.onUpdateBoard(fullBoard);
});
final MainViewModel viewModel = new ViewModelProvider(requireActivity()).get(MainViewModel.class);
- new SyncManager(requireActivity()).getFullBoardById(viewModel.getCurrentAccount().getId(), args.getLong(KEY_BOARD_ID)).observe(EditBoardDialogFragment.this, (FullBoard fb) -> {
+ viewModel.getFullBoardById(viewModel.getCurrentAccount().getId(), args.getLong(KEY_BOARD_ID)).observe(EditBoardDialogFragment.this, (FullBoard fb) -> {
if (fb.board != null) {
this.fullBoard = fb;
String title = this.fullBoard.getBoard().getTitle();
binding.input.setText(title);
binding.input.setSelection(title.length());
- binding.colorChooser.selectColor("#" + fullBoard.getBoard().getColor());
+ binding.colorChooser.selectColor(fullBoard.getBoard().getColor());
}
});
} else {
dialogBuilder.setTitle(R.string.add_board);
dialogBuilder.setPositiveButton(R.string.simple_add, (dialog, which) -> editBoardListener.onCreateBoard(binding.input.getText().toString(), binding.colorChooser.getSelectedColor()));
- binding.colorChooser.selectColor(String.format("#%06X", 0xFFFFFF & getResources().getColor(R.color.board_default_color)));
+ binding.colorChooser.selectColor(ContextCompat.getColor(requireContext(), R.color.board_default_color));
}
return dialogBuilder
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardListener.java
index ee9ba9b9d..9d8fcdbde 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardListener.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/EditBoardListener.java
@@ -1,11 +1,13 @@
package it.niedermann.nextcloud.deck.ui.board;
+import androidx.annotation.ColorInt;
+
import it.niedermann.nextcloud.deck.model.full.FullBoard;
public interface EditBoardListener {
void onUpdateBoard(FullBoard fullBoard);
- default void onCreateBoard(String title, String color) {
+ default void onCreateBoard(String title, @ColorInt int color) {
// Creating board is not necessary
}
} \ No newline at end of file
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 e5a50d9f4..0a1281fae 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
@@ -10,6 +10,7 @@ import android.view.ViewGroup;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.SwitchCompat;
+import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.recyclerview.widget.RecyclerView;
@@ -51,7 +52,7 @@ public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.View
this.account = account;
this.accessControlChangedListener = accessControlChangedListener;
this.context = context;
- this.mainColor = context.getResources().getColor(R.color.primary);
+ this.mainColor = ContextCompat.getColor(context, R.color.primary);
setHasStableIds(true);
}
@@ -172,9 +173,9 @@ public class AccessControlAdapter extends RecyclerView.Adapter<RecyclerView.View
final int finalMainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor);
DrawableCompat.setTintList(switchCompat.getThumbDrawable(), new ColorStateList(
new int[][]{new int[]{android.R.attr.state_checked}, new int[]{}},
- new int[]{finalMainColor, context.getResources().getColor(R.color.fg_secondary)}
+ new int[]{finalMainColor, ContextCompat.getColor(context, R.color.fg_secondary)}
));
- final int trackColor = context.getResources().getColor(R.color.fg_secondary);
+ final int trackColor = ContextCompat.getColor(context, R.color.fg_secondary);
final int lightTrackColor = Color.argb(77, Color.red(trackColor), Color.green(trackColor), Color.blue(trackColor));
final int lightTrackColorChecked = Color.argb(77, Color.red(finalMainColor), Color.green(finalMainColor), Color.blue(finalMainColor));
DrawableCompat.setTintList(switchCompat.getTrackDrawable(), new ColorStateList(
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 33c0fe5b1..78ccd5333 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
@@ -43,7 +43,6 @@ public class AccessControlDialogFragment extends BrandedDialogFragment implement
private static final String KEY_BOARD_ID = "board_id";
private long boardId;
- private SyncManager syncManager;
private UserAutoCompleteAdapter userAutoCompleteAdapter;
private AccessControlAdapter adapter;
@@ -75,10 +74,9 @@ public class AccessControlDialogFragment extends BrandedDialogFragment implement
adapter = new AccessControlAdapter(viewModel.getCurrentAccount(), this, requireContext());
binding.peopleList.setAdapter(adapter);
- syncManager = new SyncManager(requireActivity());
- syncManager.getFullBoardById(viewModel.getCurrentAccount().getId(), boardId).observe(this, (FullBoard fullBoard) -> {
+ viewModel.getFullBoardById(viewModel.getCurrentAccount().getId(), boardId).observe(this, (FullBoard fullBoard) -> {
if (fullBoard != null) {
- syncManager.getAccessControlByLocalBoardId(viewModel.getCurrentAccount().getId(), boardId).observe(this, (List<AccessControl> accessControlList) -> {
+ viewModel.getAccessControlByLocalBoardId(viewModel.getCurrentAccount().getId(), boardId).observe(this, (List<AccessControl> accessControlList) -> {
final AccessControl ownerControl = new AccessControl();
ownerControl.setLocalId(HEADER_ITEM_LOCAL_ID);
ownerControl.setUser(fullBoard.getOwner());
@@ -103,15 +101,20 @@ public class AccessControlDialogFragment extends BrandedDialogFragment implement
@Override
public void updateAccessControl(AccessControl accessControl) {
- syncManager.updateAccessControl(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());
+ }
+ });
}
@Override
public void deleteAccessControl(AccessControl ac) {
- final WrappedLiveData<Void> wrappedDeleteLiveData = syncManager.deleteAccessControl(ac);
+ final WrappedLiveData<Void> wrappedDeleteLiveData = viewModel.deleteAccessControl(ac);
adapter.remove(ac);
observeOnce(wrappedDeleteLiveData, this, (ignored) -> {
- if (wrappedDeleteLiveData.hasError()) {
+ 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()))
@@ -129,7 +132,12 @@ 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);
- syncManager.createAccessControl(viewModel.getCurrentAccount().getId(), ac);
+ 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());
+ }
+ });
binding.people.setText("");
userAutoCompleteAdapter.exclude(user);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/EditLabelDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/EditLabelDialogFragment.java
index d460d1590..2dacfe6ac 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/EditLabelDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/EditLabelDialogFragment.java
@@ -58,14 +58,14 @@ public class EditLabelDialogFragment extends BrandedDialogFragment {
dialogBuilder.setTitle(getString(R.string.edit_tag, label.getTitle()));
dialogBuilder.setPositiveButton(R.string.simple_save, (dialog, which) -> {
- this.label.setColor(binding.colorChooser.getSelectedColor().substring(1));
+ this.label.setColor(binding.colorChooser.getSelectedColor());
this.label.setTitle(binding.input.getText().toString());
listener.onLabelUpdated(this.label);
});
String title = this.label.getTitle();
binding.input.setText(title);
binding.input.setSelection(title.length());
- binding.colorChooser.selectColor("#" + this.label.getColor());
+ binding.colorChooser.selectColor(this.label.getColor());
return dialogBuilder
.setView(binding.getRoot())
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 3391c7a99..bc0d98ca3 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
@@ -38,7 +38,6 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements
private static final String KEY_BOARD_ID = "board_id";
private long boardId;
- private SyncManager syncManager;
@Override
public void onAttach(@NonNull Context context) {
@@ -67,8 +66,7 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements
colors = getResources().getStringArray(R.array.board_default_colors);
adapter = new ManageLabelsAdapter(this, requireContext());
binding.labels.setAdapter(adapter);
- syncManager = new SyncManager(requireActivity());
- syncManager.getFullBoardById(viewModel.getCurrentAccount().getId(), boardId).observe(this, (fullBoard) -> {
+ viewModel.getFullBoardById(viewModel.getCurrentAccount().getId(), boardId).observe(this, (fullBoard) -> {
if (fullBoard == null) {
throw new IllegalStateException("FullBoard should not be null");
}
@@ -80,9 +78,9 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements
final Label label = new Label();
label.setBoardId(boardId);
label.setTitle(binding.addLabelTitle.getText().toString());
- label.setColor(colors[new Random().nextInt(colors.length)].substring(1));
+ label.setColor(colors[new Random().nextInt(colors.length)]);
- WrappedLiveData<Label> createLiveData = syncManager.createLabel(viewModel.getCurrentAccount().getId(), label, boardId);
+ WrappedLiveData<Label> createLiveData = viewModel.createLabel(viewModel.getCurrentAccount().getId(), label, boardId);
observeOnce(createLiveData, this, (createdLabel) -> {
if (createLiveData.hasError()) {
final Throwable error = createLiveData.getError();
@@ -126,7 +124,7 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements
@Override
public void requestDelete(@NonNull Label label) {
- observeOnce(syncManager.countCardsWithLabel(label.getLocalId()), this, (count) -> {
+ observeOnce(viewModel.countCardsWithLabel(label.getLocalId()), this, (count) -> {
if (count > 0) {
new BrandedDeleteAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.delete_something, label.getTitle()))
@@ -141,9 +139,9 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements
}
private void deleteLabel(@NonNull Label label) {
- final WrappedLiveData<Void> deleteLiveData = syncManager.deleteLabel(label);
+ final WrappedLiveData<Void> deleteLiveData = viewModel.deleteLabel(label);
observeOnce(deleteLiveData, this, (v) -> {
- if (deleteLiveData.hasError()) {
+ if (deleteLiveData.hasError() && !SyncManager.ignoreExceptionOnVoidError(deleteLiveData.getError())) {
final Throwable error = deleteLiveData.getError();
assert error != null;
Toast.makeText(requireContext(), error.getLocalizedMessage(), Toast.LENGTH_LONG).show();
@@ -159,7 +157,7 @@ public class ManageLabelsDialogFragment extends BrandedDialogFragment implements
@Override
public void onLabelUpdated(@NonNull Label label) {
- WrappedLiveData<Label> updateLiveData = syncManager.updateLabel(label);
+ WrappedLiveData<Label> updateLiveData = viewModel.updateLabel(label);
observeOnce(updateLiveData, this, (updatedLabel) -> {
if (updateLiveData.hasError()) {
final Throwable error = updateLiveData.getError();
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsViewHolder.java
index 7fa3abd89..381a290e6 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/board/managelabels/ManageLabelsViewHolder.java
@@ -1,14 +1,13 @@
package it.niedermann.nextcloud.deck.ui.board.managelabels;
import android.content.res.ColorStateList;
-import android.graphics.Color;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.databinding.ItemManageLabelBinding;
import it.niedermann.nextcloud.deck.model.Label;
-import it.niedermann.nextcloud.deck.util.ColorUtil;
public class ManageLabelsViewHolder extends RecyclerView.ViewHolder {
private ItemManageLabelBinding binding;
@@ -17,13 +16,14 @@ public class ManageLabelsViewHolder extends RecyclerView.ViewHolder {
public ManageLabelsViewHolder(ItemManageLabelBinding binding) {
super(binding.getRoot());
this.binding = binding;
+ this.binding.label.setClickable(false);
}
public void bind(@NonNull Label label, @NonNull ManageLabelListener listener) {
binding.label.setText(label.getTitle());
- final int labelColor = Color.parseColor("#" + label.getColor());
+ final int labelColor = label.getColor();
binding.label.setChipBackgroundColor(ColorStateList.valueOf(labelColor));
- final int color = ColorUtil.getForegroundColorForBackgroundColor(labelColor);
+ final int color = ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(labelColor);
binding.label.setTextColor(color);
binding.delete.setOnClickListener((v) -> listener.requestDelete(label));
binding.editText.setOnClickListener((v) -> listener.requestEdit(label));
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedAlertDialogBuilder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedAlertDialogBuilder.java
index cfeffe7dc..880e21073 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedAlertDialogBuilder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedAlertDialogBuilder.java
@@ -9,8 +9,6 @@ import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
-import org.jetbrains.annotations.NotNull;
-
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
@@ -22,7 +20,7 @@ public class BrandedAlertDialogBuilder extends AlertDialog.Builder implements Br
super(context);
}
- @NotNull
+ @NonNull
@Override
public AlertDialog create() {
this.dialog = super.create();
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 5bef66f2c..319df7f79 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
@@ -17,7 +17,7 @@ import com.wdullaer.materialdatetimepicker.date.DatePickerDialog;
import java.util.Calendar;
import it.niedermann.nextcloud.deck.R;
-import it.niedermann.nextcloud.deck.util.ColorUtil;
+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;
@@ -44,7 +44,7 @@ public class BrandedDatePickerDialog extends DatePickerDialog implements Branded
setOkColor(buttonTextColor);
setCancelColor(buttonTextColor);
// Text in picker title is always white
- setAccentColor(ColorUtil.contrastRatioIsSufficientBigAreas(Color.WHITE, mainColor) ? mainColor : ContextCompat.getColor(requireContext(), R.color.accent));
+ setAccentColor(DeckColorUtil.contrastRatioIsSufficientBigAreas(Color.WHITE, mainColor) ? mainColor : ContextCompat.getColor(requireContext(), R.color.accent));
}
/**
@@ -52,13 +52,13 @@ public class BrandedDatePickerDialog extends DatePickerDialog implements Branded
*
* @param callBack How the parent is notified that the date is set.
* @param year The initial year of the dialog.
- * @param monthOfYear The initial month of the dialog.
+ * @param monthOfYear The initial month of the dialog. [0 - 11]
* @param dayOfMonth The initial day of the dialog.
* @return a new DatePickerDialog instance.
*/
public static DatePickerDialog newInstance(OnDateSetListener callBack, int year, int monthOfYear, int dayOfMonth) {
DatePickerDialog ret = new BrandedDatePickerDialog();
- ret.initialize(callBack, year, monthOfYear, dayOfMonth);
+ ret.initialize(callBack, year, monthOfYear - 1, dayOfMonth);
return ret;
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDeleteAlertDialogBuilder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDeleteAlertDialogBuilder.java
index ec3cef553..d88fdd6cc 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDeleteAlertDialogBuilder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/branding/BrandedDeleteAlertDialogBuilder.java
@@ -5,6 +5,7 @@ import android.content.DialogInterface;
import android.widget.Button;
import androidx.annotation.CallSuper;
+import androidx.core.content.ContextCompat;
import it.niedermann.nextcloud.deck.R;
@@ -20,7 +21,7 @@ public class BrandedDeleteAlertDialogBuilder extends BrandedAlertDialogBuilder {
super.applyBrand(mainColor);
final Button positiveButton = dialog.getButton(DialogInterface.BUTTON_POSITIVE);
if (positiveButton != null) {
- positiveButton.setTextColor(getContext().getResources().getColor(R.color.danger));
+ positiveButton.setTextColor(ContextCompat.getColor(getContext(), R.color.danger));
}
}
}
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 20e6f8dc8..0159a59dc 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
@@ -11,8 +11,8 @@ 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 it.niedermann.nextcloud.deck.util.ColorUtil;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.isBrandingEnabled;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.readBrandMainColor;
@@ -25,9 +25,9 @@ public class BrandedSnackbar {
final Snackbar snackbar = Snackbar.make(view, text, duration);
if (isBrandingEnabled(view.getContext())) {
@ColorInt final int color = readBrandMainColor(view.getContext());
- snackbar.setActionTextColor(ColorUtil.isColorDark(color) ? Color.WHITE : color);
+ snackbar.setActionTextColor(ColorUtil.INSTANCE.isColorDark(color) ? Color.WHITE : color);
} else {
- snackbar.setActionTextColor(ContextCompat.getColor(view.getContext(), R.color.primary));
+ snackbar.setActionTextColor(ContextCompat.getColor(view.getContext(), R.color.defaultBrand));
}
return snackbar;
}
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 a1963aa18..2e0c4d3b8 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
@@ -14,10 +14,10 @@ import androidx.core.content.ContextCompat;
import com.wdullaer.materialdatetimepicker.time.TimePickerDialog;
-import java.util.Calendar;
+import java.time.LocalTime;
import it.niedermann.nextcloud.deck.R;
-import it.niedermann.nextcloud.deck.util.ColorUtil;
+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;
@@ -44,7 +44,7 @@ public class BrandedTimePickerDialog extends TimePickerDialog implements Branded
setOkColor(buttonTextColor);
setCancelColor(buttonTextColor);
// Text in picker title is always white
- setAccentColor(ColorUtil.contrastRatioIsSufficientBigAreas(Color.WHITE, mainColor) ? mainColor : ContextCompat.getColor(requireContext(), R.color.accent));
+ setAccentColor(DeckColorUtil.contrastRatioIsSufficientBigAreas(Color.WHITE, mainColor) ? mainColor : ContextCompat.getColor(requireContext(), R.color.accent));
}
/**
@@ -86,9 +86,9 @@ public class BrandedTimePickerDialog extends TimePickerDialog implements Branded
* @param is24HourMode True to render 24 hour mode, false to render AM / PM selectors.
* @return a new TimePickerDialog instance.
*/
- @SuppressWarnings({"unused", "SameParameterValue"})
+ @SuppressWarnings({"SameParameterValue"})
public static TimePickerDialog newInstance(OnTimeSetListener callback, boolean is24HourMode) {
- Calendar now = Calendar.getInstance();
- return newInstance(callback, now.get(Calendar.HOUR_OF_DAY), now.get(Calendar.MINUTE), is24HourMode);
+ LocalTime now = LocalTime.now();
+ return newInstance(callback, now.getHour(), now.getMinute(), is24HourMode);
}
}
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 02ad6b309..b780efec5 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
@@ -17,14 +17,13 @@ import androidx.preference.PreferenceManager;
import com.google.android.material.floatingactionbutton.FloatingActionButton;
import com.google.android.material.tabs.TabLayout;
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
-import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficient;
-import static it.niedermann.nextcloud.deck.util.ColorUtil.contrastRatioIsSufficientBigAreas;
-import static it.niedermann.nextcloud.deck.util.ColorUtil.getContrastRatio;
-import static it.niedermann.nextcloud.deck.util.ColorUtil.getForegroundColorForBackgroundColor;
+import static it.niedermann.nextcloud.deck.util.DeckColorUtil.contrastRatioIsSufficient;
+import static it.niedermann.nextcloud.deck.util.DeckColorUtil.contrastRatioIsSufficientBigAreas;
public abstract class BrandingUtil {
@@ -44,7 +43,7 @@ public abstract class BrandingUtil {
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 context.getResources().getColor(R.color.defaultBrand);
+ return ContextCompat.getColor(context, R.color.defaultBrand);
}
}
@@ -87,13 +86,13 @@ public abstract class BrandingUtil {
fab.setSupportBackgroundTintList(ColorStateList.valueOf(contrastRatioIsSufficient
? mainColor
: ContextCompat.getColor(fab.getContext(), R.color.accent)));
- fab.setColorFilter(contrastRatioIsSufficient ? getForegroundColorForBackgroundColor(mainColor) : mainColor);
+ fab.setColorFilter(contrastRatioIsSufficient ? ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(mainColor) : mainColor);
}
public static void applyBrandToPrimaryTabLayout(@ColorInt int mainColor, @NonNull TabLayout tabLayout) {
- @ColorInt int finalMainColor = getSecondaryForegroundColorDependingOnTheme(tabLayout.getContext(), mainColor);
+ @ColorInt final int finalMainColor = getSecondaryForegroundColorDependingOnTheme(tabLayout.getContext(), mainColor);
tabLayout.setBackgroundColor(ContextCompat.getColor(tabLayout.getContext(), R.color.primary));
- final boolean contrastRatioIsSufficient = getContrastRatio(mainColor, ContextCompat.getColor(tabLayout.getContext(), R.color.primary)) > 1.7d;
+ final boolean contrastRatioIsSufficient = ColorUtil.INSTANCE.getContrastRatio(mainColor, ContextCompat.getColor(tabLayout.getContext(), R.color.primary)) > 1.7d;
tabLayout.setSelectedTabIndicatorColor(contrastRatioIsSufficient ? mainColor : finalMainColor);
}
@@ -112,7 +111,7 @@ public abstract class BrandingUtil {
finalMainColor,
finalMainColor,
finalMainColor,
- editText.getContext().getResources().getColor(R.color.fg_secondary)
+ ContextCompat.getColor(editText.getContext(), R.color.fg_secondary)
}
));
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AbstractCardViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AbstractCardViewHolder.java
new file mode 100644
index 000000000..4d3b8bb35
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/AbstractCardViewHolder.java
@@ -0,0 +1,122 @@
+package it.niedermann.nextcloud.deck.ui.card;
+
+import android.content.Context;
+import android.view.Menu;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.CallSuper;
+import androidx.annotation.ColorInt;
+import androidx.annotation.MenuRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.widget.PopupMenu;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.android.material.card.MaterialCardView;
+
+import org.jetbrains.annotations.Contract;
+
+import java.time.ZoneId;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Card;
+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.util.DateUtil;
+import it.niedermann.nextcloud.deck.util.ViewUtil;
+
+public abstract class AbstractCardViewHolder extends RecyclerView.ViewHolder {
+
+ public AbstractCardViewHolder(@NonNull View itemView) {
+ super(itemView);
+ }
+
+ /**
+ * Removes all {@link OnClickListener} and {@link OnLongClickListener}
+ */
+ @CallSuper
+ public void bind(@NonNull FullCard fullCard, @NonNull Account account, @Nullable Long boardRemoteId, boolean hasEditPermission, @MenuRes int optionsMenu, @NonNull CardOptionsItemSelectedListener optionsItemsSelectedListener, @NonNull String counterMaxValue, @ColorInt int mainColor) {
+ final Context context = itemView.getContext();
+
+ bindCardClickListener(null);
+ bindCardLongClickListener(null);
+
+ getCardMenu().setVisibility(hasEditPermission ? View.VISIBLE : View.GONE);
+ getCardTitle().setText(fullCard.getCard().getTitle().trim());
+
+ DrawableCompat.setTint(getNotSyncedYet().getDrawable(), mainColor);
+ getNotSyncedYet().setVisibility(DBStatus.LOCAL_EDITED.equals(fullCard.getStatusEnum()) ? View.VISIBLE : View.GONE);
+
+ if (fullCard.getCard().getDueDate() != null) {
+ setupDueDate(getCardDueDate(), fullCard.getCard());
+ getCardDueDate().setVisibility(View.VISIBLE);
+ } else {
+ getCardDueDate().setVisibility(View.GONE);
+ }
+
+ getCardMenu().setOnClickListener(view -> {
+ final PopupMenu popup = new PopupMenu(context, view);
+ popup.inflate(optionsMenu);
+ final Menu menu = popup.getMenu();
+ if (containsUser(fullCard.getAssignedUsers(), account.getUserName())) {
+ menu.removeItem(menu.findItem(R.id.action_card_assign).getItemId());
+ } else {
+ menu.removeItem(menu.findItem(R.id.action_card_unassign).getItemId());
+ }
+ if (boardRemoteId == null || fullCard.getCard().getId() == null) {
+ menu.removeItem(R.id.share_link);
+ }
+
+ popup.setOnMenuItemClickListener(item -> optionsItemsSelectedListener.onCardOptionsItemSelected(item, fullCard));
+ popup.show();
+ });
+ }
+
+ protected abstract TextView getCardDueDate();
+
+ protected abstract ImageView getNotSyncedYet();
+
+ protected abstract TextView getCardTitle();
+
+ protected abstract View getCardMenu();
+
+ protected abstract MaterialCardView getCard();
+
+ public void bindCardClickListener(@Nullable OnClickListener l) {
+ getCard().setOnClickListener(l);
+ }
+
+ public void bindCardLongClickListener(@Nullable OnLongClickListener l) {
+ getCard().setOnLongClickListener(l);
+ }
+
+ public MaterialCardView getDraggable() {
+ return getCard();
+ }
+
+ private static void setupDueDate(@NonNull TextView cardDueDate, @NonNull Card card) {
+ final Context context = cardDueDate.getContext();
+ cardDueDate.setText(DateUtil.getRelativeDateTimeString(context, card.getDueDate().toEpochMilli()));
+ ViewUtil.themeDueDate(context, cardDueDate, card.getDueDate().atZone(ZoneId.systemDefault()).toLocalDate());
+ }
+
+ @Contract("null, _ -> false")
+ private static boolean containsUser(List<User> userList, String username) {
+ if (userList != null) {
+ for (User user : userList) {
+ if (user.getPrimaryKey().equals(username)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+} \ No newline at end of file
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 986a66cad..87140e544 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,99 +1,82 @@
package it.niedermann.nextcloud.deck.ui.card;
-import android.annotation.SuppressLint;
import android.content.ClipData;
import android.content.Context;
import android.content.Intent;
import android.view.LayoutInflater;
-import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.PopupMenu;
-import android.widget.TextView;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
-import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LifecycleOwner;
import androidx.recyclerview.widget.RecyclerView;
-import org.jetbrains.annotations.Contract;
-import org.jetbrains.annotations.NotNull;
-
import java.util.ArrayList;
-import java.util.LinkedList;
import java.util.List;
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.databinding.ItemCardBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemCardCompactBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemCardDefaultBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemCardDefaultOnlyTitleBinding;
import it.niedermann.nextcloud.deck.model.Account;
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.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.persistence.sync.SyncManager;
-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.MainViewModel;
import it.niedermann.nextcloud.deck.ui.branding.Branded;
-import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
-import it.niedermann.nextcloud.deck.util.DateUtil;
-import it.niedermann.nextcloud.deck.util.ViewUtil;
+import it.niedermann.nextcloud.deck.ui.movecard.MoveCardDialogFragment;
+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;
-public class CardAdapter extends RecyclerView.Adapter<ItemCardViewHolder> implements DragAndDropAdapter<FullCard>, Branded {
-
- protected final SyncManager syncManager;
+public class CardAdapter extends RecyclerView.Adapter<AbstractCardViewHolder> implements DragAndDropAdapter<FullCard>, CardOptionsItemSelectedListener, Branded {
- private final FragmentManager fragmentManager;
- private final Account account;
- @Nullable
- private final Long currentBoardRemoteId;
- private final long boardId;
+ private final boolean compactMode;
+ @NonNull
+ protected final MainViewModel mainViewModel;
+ @NonNull
+ protected final FragmentManager fragmentManager;
private final long stackId;
- private final boolean canEdit;
@NonNull
private final Context context;
@Nullable
private final SelectCardListener selectCardListener;
- private List<FullCard> cardList = new LinkedList<>();
- private LifecycleOwner lifecycleOwner;
- private List<FullStack> availableStacks = new ArrayList<>();
- private String counterMaxValue;
-
- private int mainColor;
+ @NonNull
+ protected List<FullCard> cardList = new ArrayList<>();
+ @NonNull
+ protected LifecycleOwner lifecycleOwner;
+ @NonNull
+ protected String counterMaxValue;
+ @ColorInt
+ protected int mainColor;
@StringRes
- private int shareLinkRes;
+ private final int shareLinkRes;
- public CardAdapter(@NonNull Context context, @NonNull FragmentManager fragmentManager, @NonNull Account account, long boardId, @Nullable Long currentBoardRemoteId, long stackId, boolean canEdit, @NonNull SyncManager syncManager, @NonNull LifecycleOwner lifecycleOwner, @Nullable SelectCardListener selectCardListener) {
+ 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);
this.fragmentManager = fragmentManager;
this.lifecycleOwner = lifecycleOwner;
- this.account = account;
- this.shareLinkRes = account.getServerDeckVersionAsObject().getShareLinkResource();
- this.boardId = boardId;
- this.currentBoardRemoteId = currentBoardRemoteId;
+ this.shareLinkRes = mainViewModel.getCurrentAccount().getServerDeckVersionAsObject().getShareLinkResource();
this.stackId = stackId;
- this.canEdit = canEdit;
- this.syncManager = syncManager;
+ this.mainViewModel = mainViewModel;
this.selectCardListener = selectCardListener;
- this.mainColor = context.getResources().getColor(R.color.primary);
- syncManager.getStacksForBoard(account.getId(), boardId).observe(this.lifecycleOwner, (stacks) -> {
- availableStacks.clear();
- availableStacks.addAll(stacks);
- });
+ this.mainColor = ContextCompat.getColor(context, R.color.defaultBrand);
+ this.compactMode = getDefaultSharedPreferences(context).getBoolean(context.getString(R.string.pref_key_compact), false);
setHasStableIds(true);
}
@@ -104,118 +87,62 @@ public class CardAdapter extends RecyclerView.Adapter<ItemCardViewHolder> implem
@NonNull
@Override
- public ItemCardViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int position) {
- final Context context = viewGroup.getContext();
- counterMaxValue = context.getString(R.string.counter_max_value);
+ public AbstractCardViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
+ if (viewType == R.layout.item_card_compact) {
+ return new CompactCardViewHolder(ItemCardCompactBinding.inflate(LayoutInflater.from(viewGroup.getContext()), viewGroup, false));
+ } else if (viewType == R.layout.item_card_default_only_title) {
+ return new DefaultCardOnlyTitleViewHolder(ItemCardDefaultOnlyTitleBinding.inflate(LayoutInflater.from(viewGroup.getContext()), viewGroup, false));
+ }
+ return new DefaultCardViewHolder(ItemCardDefaultBinding.inflate(LayoutInflater.from(viewGroup.getContext()), viewGroup, false));
+ }
- LayoutInflater layoutInflater = LayoutInflater.from(context);
- ItemCardBinding binding = ItemCardBinding.inflate(layoutInflater, viewGroup, false);
- return new ItemCardViewHolder(binding);
+ @Override
+ public int getItemViewType(int position) {
+ if (compactMode) {
+ return R.layout.item_card_compact;
+ } else {
+ final FullCard fullCard = cardList.get(position);
+ if (fullCard.getAttachments().size() == 0
+ && fullCard.getAssignedUsers().size() == 0
+ && fullCard.getLabels().size() == 0
+ && fullCard.getCommentCount() == 0) {
+ return R.layout.item_card_default_only_title;
+ }
+ return R.layout.item_card_default;
+ }
}
- @SuppressLint("SetTextI18n")
@Override
- public void onBindViewHolder(@NonNull ItemCardViewHolder viewHolder, int position) {
- final Context context = viewHolder.itemView.getContext();
- final FullCard card = cardList.get(position);
+ public void onBindViewHolder(@NonNull AbstractCardViewHolder viewHolder, int position) {
+ @NonNull FullCard fullCard = cardList.get(position);
+ viewHolder.bind(fullCard, mainViewModel.getCurrentAccount(), mainViewModel.getCurrentBoardRemoteId(), mainViewModel.currentBoardHasEditPermission(), R.menu.card_menu, this, counterMaxValue, mainColor);
- viewHolder.binding.card.setOnClickListener((v) -> {
+ // 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, account, boardId, card.getLocalId()));
+ context.startActivity(EditActivity.createEditCardIntent(context, mainViewModel.getCurrentAccount(), mainViewModel.getCurrentBoardLocalId(), fullCard.getLocalId()));
} else {
- selectCardListener.onCardSelected(card);
+ selectCardListener.onCardSelected(fullCard);
}
});
- if (canEdit && selectCardListener == null) {
- viewHolder.binding.card.setOnLongClickListener((v) -> {
+
+ // Only enable Drag and Drop if there is no one waiting for selecting a card.
+ if (selectCardListener == null) {
+ viewHolder.bindCardLongClickListener((v) -> {
DeckLog.log("Starting drag and drop");
- v.startDrag(ClipData.newPlainText("cardid", String.valueOf(card.getLocalId())),
+ v.startDrag(ClipData.newPlainText("cardid", String.valueOf(fullCard.getLocalId())),
new View.DragShadowBuilder(v),
- new DraggedItemLocalState<>(card, viewHolder.binding.card, this, position),
+ new DraggedItemLocalState<>(fullCard, viewHolder.getDraggable(), this, position),
0
);
return true;
});
- } else {
- viewHolder.binding.cardMenu.setVisibility(View.GONE);
- }
- viewHolder.binding.cardTitle.setText(card.getCard().getTitle().trim());
-
- if (card.getAssignedUsers() != null && card.getAssignedUsers().size() > 0) {
- viewHolder.binding.overlappingAvatars.setAvatars(account, card.getAssignedUsers());
- viewHolder.binding.overlappingAvatars.setVisibility(View.VISIBLE);
- } else {
- viewHolder.binding.overlappingAvatars.setVisibility(View.GONE);
}
-
- DrawableCompat.setTint(viewHolder.binding.notSyncedYet.getDrawable(), mainColor);
- viewHolder.binding.notSyncedYet.setVisibility(DBStatus.LOCAL_EDITED.equals(card.getStatusEnum()) ? View.VISIBLE : View.GONE);
-
- if (card.getCard().getDueDate() != null) {
- setupDueDate(viewHolder.binding.cardDueDate, card.getCard());
- viewHolder.binding.cardDueDate.setVisibility(View.VISIBLE);
- } else {
- viewHolder.binding.cardDueDate.setVisibility(View.GONE);
- }
-
- final int attachmentsCount = card.getAttachments().size();
-
- if (attachmentsCount == 0) {
- viewHolder.binding.cardCountAttachments.setVisibility(View.GONE);
- } else {
- setupCounter(viewHolder.binding.cardCountAttachments, attachmentsCount);
- viewHolder.binding.cardCountAttachments.setVisibility(View.VISIBLE);
- }
-
- final int commentsCount = card.getCommentCount();
-
- if (commentsCount == 0) {
- viewHolder.binding.cardCountComments.setVisibility(View.GONE);
- } else {
- setupCounter(viewHolder.binding.cardCountComments, commentsCount);
-
- viewHolder.binding.cardCountComments.setVisibility(View.VISIBLE);
- }
-
- List<Label> labels = card.getLabels();
- if (labels != null && labels.size() > 0) {
- viewHolder.binding.labels.updateLabels(labels);
- viewHolder.binding.labels.setVisibility(View.VISIBLE);
- } else {
- viewHolder.binding.labels.removeAllViews();
- viewHolder.binding.labels.setVisibility(View.GONE);
- }
-
- Card.TaskStatus taskStatus = card.getCard().getTaskStatus();
- if (taskStatus.taskCount > 0) {
- viewHolder.binding.cardCountTasks.setText(context.getResources().getString(R.string.task_count, String.valueOf(taskStatus.doneCount), String.valueOf(taskStatus.taskCount)));
- viewHolder.binding.cardCountTasks.setVisibility(View.VISIBLE);
- } else {
- viewHolder.binding.cardCountTasks.setVisibility(View.GONE);
- }
-
- viewHolder.binding.cardMenu.setOnClickListener(v -> onOverflowIconClicked(v, card));
- }
-
- private void setupCounter(@NonNull TextView textView, int count) {
- if (count > 99) {
- textView.setText(counterMaxValue);
- } else if (count > 1) {
- textView.setText(String.valueOf(count));
- } else if (count == 1) {
- textView.setText("");
- }
- }
-
- private void setupDueDate(@NonNull TextView cardDueDate, @NotNull Card card) {
- final Context context = cardDueDate.getContext();
- cardDueDate.setText(DateUtil.getRelativeDateTimeString(context, card.getDueDate().getTime()));
- ViewUtil.themeDueDate(context, cardDueDate, card.getDueDate());
}
@Override
public int getItemCount() {
- return cardList == null ? 0 : cardList.size();
+ return cardList.size();
}
public void insertItem(FullCard fullCard, int position) {
@@ -223,6 +150,7 @@ public class CardAdapter extends RecyclerView.Adapter<ItemCardViewHolder> implem
notifyItemInserted(position);
}
+ @NonNull
@Override
public List<FullCard> getItemList() {
return this.cardList;
@@ -240,114 +168,59 @@ public class CardAdapter extends RecyclerView.Adapter<ItemCardViewHolder> implem
notifyItemRemoved(position);
}
- protected void onOverflowIconClicked(@NotNull View view, FullCard card) {
- final Context context = view.getContext();
- final PopupMenu popup = new PopupMenu(context, view);
- popup.inflate(R.menu.card_menu);
- prepareOptionsMenu(popup.getMenu(), card);
-
- popup.setOnMenuItemClickListener(item -> optionsItemSelected(context, item, card));
- popup.show();
- }
-
- protected void prepareOptionsMenu(Menu menu, @NotNull FullCard card) {
- if (containsUser(card.getAssignedUsers(), account.getUserName())) {
- menu.removeItem(menu.findItem(R.id.action_card_assign).getItemId());
- } else {
- menu.removeItem(menu.findItem(R.id.action_card_unassign).getItemId());
- }
- if (currentBoardRemoteId == null || card.getCard().getId() == null) {
- menu.removeItem(R.id.share_link);
- }
- }
-
public void setCardList(@NonNull List<FullCard> cardList) {
this.cardList.clear();
this.cardList.addAll(cardList);
notifyDataSetChanged();
}
- @Contract("null, _ -> false")
- private boolean containsUser(List<User> userList, String username) {
- if (userList != null) {
- for (User user : userList) {
- if (user.getPrimaryKey().equals(username)) {
- return true;
- }
- }
- }
- return false;
+ @Override
+ public void applyBrand(int mainColor) {
+ this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor);
+ notifyDataSetChanged();
}
- protected boolean optionsItemSelected(@NonNull Context context, @NotNull MenuItem item, FullCard fullCard) {
- switch (item.getItemId()) {
- case R.id.share_link: {
- 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, currentBoardRemoteId, fullCard.getCard().getId()));
- context.startActivity(Intent.createChooser(shareIntent, fullCard.getCard().getTitle()));
- }
- case R.id.action_card_assign: {
- new Thread(() -> syncManager.assignUserToCard(syncManager.getUserByUidDirectly(fullCard.getCard().getAccountId(), account.getUserName()), fullCard.getCard())).start();
- return true;
- }
- case R.id.action_card_unassign: {
- new Thread(() -> syncManager.unassignUserFromCard(syncManager.getUserByUidDirectly(fullCard.getCard().getAccountId(), account.getUserName()), fullCard.getCard())).start();
- return true;
- }
- case R.id.action_card_move: {
- int currentStackItem = 0;
- CharSequence[] items = new CharSequence[availableStacks.size()];
- for (int i = 0; i < availableStacks.size(); i++) {
- final Stack stack = availableStacks.get(i).getStack();
- items[i] = stack.getTitle();
- if (stack.getLocalId().equals(stackId)) {
- currentStackItem = i;
- }
+ @Override
+ public boolean onCardOptionsItemSelected(@NonNull MenuItem menuItem, @NonNull FullCard fullCard) {
+ int itemId = menuItem.getItemId();
+ final Account account = mainViewModel.getCurrentAccount();
+ if (itemId == R.id.share_link) {
+ 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();
+ return true;
+ } else if (itemId == R.id.action_card_assign) {
+ new Thread(() -> mainViewModel.assignUserToCard(mainViewModel.getUserByUidDirectly(fullCard.getCard().getAccountId(), account.getUserName()), fullCard.getCard())).start();
+ return true;
+ } else if (itemId == R.id.action_card_unassign) {
+ new Thread(() -> mainViewModel.unassignUserFromCard(mainViewModel.getUserByUidDirectly(fullCard.getCard().getAccountId(), account.getUserName()), fullCard.getCard())).start();
+ 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());
+ 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());
}
- final FullCard newCard = fullCard;
- new BrandedAlertDialogBuilder(context)
- .setSingleChoiceItems(items, currentStackItem, (dialog, which) -> {
- dialog.cancel();
- newCard.getCard().setStackId(availableStacks.get(which).getStack().getLocalId());
- LiveDataHelper.observeOnce(syncManager.updateCard(newCard), lifecycleOwner, (c) -> {
- // Nothing to do here...
- });
- DeckLog.log("Moved card \"" + fullCard.getCard().getTitle() + "\" to \"" + availableStacks.get(which).getStack().getTitle() + "\"");
- })
- .setNeutralButton(android.R.string.cancel, null)
- .setTitle(context.getString(R.string.action_card_move_title, fullCard.getCard().getTitle()))
- .show();
- return true;
- }
- case R.id.action_card_archive: {
- final WrappedLiveData<FullCard> archiveLiveData = syncManager.archiveCard(fullCard);
- observeOnce(archiveLiveData, lifecycleOwner, (v) -> {
- if (archiveLiveData.hasError()) {
- ExceptionDialogFragment.newInstance(archiveLiveData.getError(), account).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName());
- }
- });
- return true;
- }
- case R.id.action_card_delete: {
- final WrappedLiveData<Void> deleteLiveData = syncManager.deleteCard(fullCard.getCard());
- observeOnce(deleteLiveData, lifecycleOwner, (v) -> {
- if (deleteLiveData.hasError()) {
- ExceptionDialogFragment.newInstance(deleteLiveData.getError(), account).show(fragmentManager, ExceptionDialogFragment.class.getSimpleName());
- }
- });
- return true;
- }
+ });
+ 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());
+ }
+ });
+ return true;
}
return true;
}
-
- @Override
- public void applyBrand(int mainColor) {
- this.mainColor = getSecondaryForegroundColorDependingOnTheme(context, mainColor);
- notifyDataSetChanged();
- }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardOptionsItemSelectedListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardOptionsItemSelectedListener.java
new file mode 100644
index 000000000..d3050b732
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CardOptionsItemSelectedListener.java
@@ -0,0 +1,11 @@
+package it.niedermann.nextcloud.deck.ui.card;
+
+import android.view.MenuItem;
+
+import androidx.annotation.NonNull;
+
+import it.niedermann.nextcloud.deck.model.full.FullCard;
+
+public interface CardOptionsItemSelectedListener {
+ boolean onCardOptionsItemSelected(@NonNull MenuItem menuItem, @NonNull FullCard fullCard);
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CompactCardViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CompactCardViewHolder.java
new file mode 100644
index 000000000..e9f366d99
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/CompactCardViewHolder.java
@@ -0,0 +1,84 @@
+package it.niedermann.nextcloud.deck.ui.card;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.MenuRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.android.material.card.MaterialCardView;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.databinding.ItemCardCompactBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Label;
+import it.niedermann.nextcloud.deck.model.full.FullCard;
+
+public class CompactCardViewHolder extends AbstractCardViewHolder {
+ private ItemCardCompactBinding binding;
+
+ @SuppressWarnings("WeakerAccess")
+ public CompactCardViewHolder(@NonNull ItemCardCompactBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ /**
+ * Removes all {@link OnClickListener} and {@link OnLongClickListener}
+ */
+ public void bind(@NonNull FullCard fullCard, @NonNull Account account, @Nullable Long boardRemoteId, boolean hasEditPermission, @MenuRes int optionsMenu, @NonNull CardOptionsItemSelectedListener optionsItemsSelectedListener, @NonNull String counterMaxValue, @ColorInt int mainColor) {
+ super.bind(fullCard, account, boardRemoteId, hasEditPermission, optionsMenu, optionsItemsSelectedListener, counterMaxValue, mainColor);
+
+ List<Label> labels = fullCard.getLabels();
+ if (labels != null && labels.size() > 0) {
+ binding.labels.updateLabels(labels);
+ binding.labels.setVisibility(View.VISIBLE);
+ } else {
+ binding.labels.removeAllViews();
+ binding.labels.setVisibility(View.GONE);
+ }
+ }
+
+ public void bindCardClickListener(@Nullable OnClickListener l) {
+ binding.card.setOnClickListener(l);
+ }
+
+ public void bindCardLongClickListener(@Nullable OnLongClickListener l) {
+ binding.card.setOnLongClickListener(l);
+ }
+
+ public MaterialCardView getDraggable() {
+ return binding.card;
+ }
+
+ @Override
+ protected TextView getCardDueDate() {
+ return binding.cardDueDate;
+ }
+
+ @Override
+ protected ImageView getNotSyncedYet() {
+ return binding.notSyncedYet;
+ }
+
+ @Override
+ protected TextView getCardTitle() {
+ return binding.cardTitle;
+ }
+
+ @Override
+ protected View getCardMenu() {
+ return binding.cardMenu;
+ }
+
+ @Override
+ protected MaterialCardView getCard() {
+ return binding.card;
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/DefaultCardOnlyTitleViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/DefaultCardOnlyTitleViewHolder.java
new file mode 100644
index 000000000..2f9e132c9
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/DefaultCardOnlyTitleViewHolder.java
@@ -0,0 +1,61 @@
+package it.niedermann.nextcloud.deck.ui.card;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.android.material.card.MaterialCardView;
+
+import it.niedermann.nextcloud.deck.databinding.ItemCardDefaultOnlyTitleBinding;
+
+public class DefaultCardOnlyTitleViewHolder extends AbstractCardViewHolder {
+ private ItemCardDefaultOnlyTitleBinding binding;
+
+ @SuppressWarnings("WeakerAccess")
+ public DefaultCardOnlyTitleViewHolder(@NonNull ItemCardDefaultOnlyTitleBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bindCardClickListener(@Nullable OnClickListener l) {
+ binding.card.setOnClickListener(l);
+ }
+
+ public void bindCardLongClickListener(@Nullable OnLongClickListener l) {
+ binding.card.setOnLongClickListener(l);
+ }
+
+ public MaterialCardView getDraggable() {
+ return binding.card;
+ }
+
+ @Override
+ protected TextView getCardDueDate() {
+ return binding.cardDueDate;
+ }
+
+ @Override
+ protected ImageView getNotSyncedYet() {
+ return binding.notSyncedYet;
+ }
+
+ @Override
+ protected TextView getCardTitle() {
+ return binding.cardTitle;
+ }
+
+ @Override
+ protected View getCardMenu() {
+ return binding.cardMenu;
+ }
+
+ @Override
+ protected MaterialCardView getCard() {
+ return binding.card;
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/DefaultCardViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/DefaultCardViewHolder.java
new file mode 100644
index 000000000..6025cdada
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/DefaultCardViewHolder.java
@@ -0,0 +1,157 @@
+package it.niedermann.nextcloud.deck.ui.card;
+
+import android.content.Context;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLongClickListener;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.MenuRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.card.MaterialCardView;
+
+import org.jetbrains.annotations.Contract;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemCardDefaultBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Card.TaskStatus;
+import it.niedermann.nextcloud.deck.model.Label;
+import it.niedermann.nextcloud.deck.model.User;
+import it.niedermann.nextcloud.deck.model.full.FullCard;
+
+public class DefaultCardViewHolder extends AbstractCardViewHolder {
+ private ItemCardDefaultBinding binding;
+
+ @SuppressWarnings("WeakerAccess")
+ public DefaultCardViewHolder(@NonNull ItemCardDefaultBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ /**
+ * Removes all {@link OnClickListener} and {@link OnLongClickListener}
+ */
+ public void bind(@NonNull FullCard fullCard, @NonNull Account account, @Nullable Long boardRemoteId, boolean hasEditPermission, @MenuRes int optionsMenu, @NonNull CardOptionsItemSelectedListener optionsItemsSelectedListener, @NonNull String counterMaxValue, @ColorInt int mainColor) {
+ super.bind(fullCard, account, boardRemoteId, hasEditPermission, optionsMenu, optionsItemsSelectedListener, counterMaxValue, mainColor);
+
+ final Context context = itemView.getContext();
+
+ if (fullCard.getAssignedUsers() != null && fullCard.getAssignedUsers().size() > 0) {
+ binding.overlappingAvatars.setAvatars(account, fullCard.getAssignedUsers());
+ binding.overlappingAvatars.setVisibility(View.VISIBLE);
+ } else {
+ binding.overlappingAvatars.setVisibility(View.GONE);
+ }
+
+ final int attachmentsCount = fullCard.getAttachments().size();
+ if (attachmentsCount == 0) {
+ binding.cardCountAttachments.setVisibility(View.GONE);
+ } else {
+ setupCounter(binding.cardCountAttachments, counterMaxValue, attachmentsCount);
+ binding.cardCountAttachments.setVisibility(View.VISIBLE);
+ }
+
+ final int commentsCount = fullCard.getCommentCount();
+ if (commentsCount == 0) {
+ binding.cardCountComments.setVisibility(View.GONE);
+ } else {
+ setupCounter(binding.cardCountComments, counterMaxValue, commentsCount);
+
+ binding.cardCountComments.setVisibility(View.VISIBLE);
+ }
+
+ final List<Label> labels = fullCard.getLabels();
+ if (labels != null && labels.size() > 0) {
+ binding.labels.updateLabels(labels);
+ binding.labels.setVisibility(View.VISIBLE);
+ } else {
+ binding.labels.removeAllViews();
+ binding.labels.setVisibility(View.GONE);
+ }
+
+ final TaskStatus taskStatus = fullCard.getCard().getTaskStatus();
+ if (taskStatus.taskCount > 0) {
+ binding.cardCountTasks.setText(context.getResources().getString(R.string.task_count, String.valueOf(taskStatus.doneCount), String.valueOf(taskStatus.taskCount)));
+ binding.cardCountTasks.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(context, R.drawable.ic_check_grey600_24dp), null, null, null);
+ binding.cardCountTasks.setVisibility(View.VISIBLE);
+ } else {
+ final String description = fullCard.getCard().getDescription();
+ if (!TextUtils.isEmpty(description)) {
+ binding.cardCountTasks.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(context, R.drawable.ic_baseline_subject_24), null, null, null);
+ binding.cardCountTasks.setText(null);
+ binding.cardCountTasks.setVisibility(View.VISIBLE);
+ } else {
+ binding.cardCountTasks.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ @Override
+ protected TextView getCardDueDate() {
+ return binding.cardDueDate;
+ }
+
+ @Override
+ protected ImageView getNotSyncedYet() {
+ return binding.notSyncedYet;
+ }
+
+ @Override
+ protected TextView getCardTitle() {
+ return binding.cardTitle;
+ }
+
+ @Override
+ protected View getCardMenu() {
+ return binding.cardMenu;
+ }
+
+ @Override
+ protected MaterialCardView getCard() {
+ return binding.card;
+ }
+
+ public void bindCardClickListener(@Nullable OnClickListener l) {
+ binding.card.setOnClickListener(l);
+ }
+
+ public void bindCardLongClickListener(@Nullable OnLongClickListener l) {
+ binding.card.setOnLongClickListener(l);
+ }
+
+ public MaterialCardView getDraggable() {
+ return binding.card;
+ }
+
+
+ private static void setupCounter(@NonNull TextView textView, @NonNull String counterMaxValue, int count) {
+ if (count > 99) {
+ textView.setText(counterMaxValue);
+ } else if (count > 1) {
+ textView.setText(String.valueOf(count));
+ } else if (count == 1) {
+ textView.setText("");
+ }
+ }
+
+ @Contract("null, _ -> false")
+ private static boolean containsUser(List<User> userList, String username) {
+ if (userList != null) {
+ for (User user : userList) {
+ if (user.getPrimaryKey().equals(username)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+} \ No newline at end of file
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 0710b7d7c..2e1ff5e06 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
@@ -25,13 +25,12 @@ 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.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.model.full.FullCard;
import it.niedermann.nextcloud.deck.ui.branding.BrandedActivity;
import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionHandler;
import it.niedermann.nextcloud.deck.util.CardUtil;
-import static android.graphics.Color.parseColor;
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;
@@ -45,7 +44,6 @@ public class EditActivity extends BrandedActivity {
private ActivityEditBinding binding;
private EditCardViewModel viewModel;
- private SyncManager syncManager;
private static final int[] tabTitles = new int[]{
R.string.card_edit_details,
@@ -83,7 +81,6 @@ public class EditActivity extends BrandedActivity {
setSupportActionBar(binding.toolbar);
viewModel = new ViewModelProvider(this).get(EditCardViewModel.class);
- syncManager = new SyncManager(this);
loadDataFromIntent();
}
@@ -120,8 +117,8 @@ public class EditActivity extends BrandedActivity {
final long boardId = args.getLong(BUNDLE_KEY_BOARD_ID);
- observeOnce(syncManager.getFullBoardById(account.getId(), boardId), EditActivity.this, (fullBoard -> {
- applyBrand(parseColor('#' + fullBoard.getBoard().getColor()));
+ observeOnce(viewModel.getFullBoardById(account.getId(), boardId), EditActivity.this, (fullBoard -> {
+ applyBrand(fullBoard.getBoard().getColor());
viewModel.setCanEdit(fullBoard.getBoard().isPermissionEdit());
invalidateOptionsMenu();
if (viewModel.isCreateMode()) {
@@ -138,7 +135,7 @@ public class EditActivity extends BrandedActivity {
setupViewPager();
setupTitle();
} else {
- observeOnce(syncManager.getCardByLocalId(account.getId(), cardId), EditActivity.this, (fullCard) -> {
+ observeOnce(viewModel.getFullCardWithProjectsByLocalId(account.getId(), cardId), EditActivity.this, (fullCard) -> {
if (fullCard == null) {
new BrandedAlertDialogBuilder(this)
.setTitle(R.string.card_not_found)
@@ -154,6 +151,8 @@ public class EditActivity extends BrandedActivity {
});
}
}));
+
+ DeckLog.verbose("Finished loading intent data: { accountId = " + viewModel.getAccount().getId() + " , cardId = " + cardId + " }");
}
@Override
@@ -170,12 +169,16 @@ public class EditActivity extends BrandedActivity {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == R.id.action_card_save) {
- saveAndFinish();
+ saveAndRun(super::finish);
}
return super.onOptionsItemSelected(item);
}
- private void saveAndFinish() {
+ /**
+ * Tries to save the current {@link FullCard} from the {@link EditCardViewModel} and then runs the given {@link Runnable}
+ * @param runnable
+ */
+ private void saveAndRun(@NonNull Runnable runnable) {
if (!viewModel.isPendingCreation()) {
viewModel.setPendingCreation(true);
final String title = viewModel.getFullCard().getCard().getTitle();
@@ -193,9 +196,9 @@ public class EditActivity extends BrandedActivity {
.show();
} else {
if (viewModel.isCreateMode()) {
- observeOnce(syncManager.createFullCard(viewModel.getAccount().getId(), viewModel.getBoardId(), viewModel.getFullCard().getCard().getStackId(), viewModel.getFullCard()), EditActivity.this, (card) -> super.finish());
+ observeOnce(viewModel.createFullCard(viewModel.getAccount().getId(), viewModel.getBoardId(), viewModel.getFullCard().getCard().getStackId(), viewModel.getFullCard()), EditActivity.this, (card) -> runnable.run());
} else {
- observeOnce(syncManager.updateCard(viewModel.getFullCard()), EditActivity.this, (card) -> super.finish());
+ observeOnce(viewModel.updateCard(viewModel.getFullCard()), EditActivity.this, (card) -> runnable.run());
}
}
}
@@ -264,26 +267,15 @@ public class EditActivity extends BrandedActivity {
}
@Override
- public boolean onSupportNavigateUp() {
- finish(); // close this activity as oppose to navigating up
- return true;
- }
-
- @Override
- public void onBackPressed() {
- finish();
- }
-
- @Override
public void finish() {
if (!viewModel.hasChanges() && viewModel.canEdit()) {
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) -> saveAndFinish())
+ .setPositiveButton(R.string.simple_save, (dialog, whichButton) -> saveAndRun(super::finish))
.setNegativeButton(R.string.simple_discard, (dialog, whichButton) -> super.finish()).show();
} else {
- directFinish();
+ super.finish();
}
}
@@ -296,10 +288,10 @@ public class EditActivity extends BrandedActivity {
@Override
public void applyBrand(int mainColor) {
- if(isBrandingEnabled(this)) {
+ if (isBrandingEnabled(this)) {
final Drawable navigationIcon = binding.toolbar.getNavigationIcon();
if (navigationIcon == null) {
- DeckLog.error("Excpected navigationIcon to be present.");
+ DeckLog.error("Expected navigationIcon to be present.");
} else {
DrawableCompat.setTint(binding.toolbar.getNavigationIcon(), colorAccent);
}
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 c8c93c838..c754d0800 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
@@ -1,38 +1,57 @@
package it.niedermann.nextcloud.deck.ui.card;
+import android.app.Application;
+
import androidx.annotation.NonNull;
-import androidx.lifecycle.ViewModel;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+import java.io.File;
import java.util.ArrayList;
+import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
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.full.FullBoard;
import it.niedermann.nextcloud.deck.model.full.FullCard;
+import it.niedermann.nextcloud.deck.model.full.FullCardWithProjects;
+import it.niedermann.nextcloud.deck.model.ocs.Activity;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiveData;
@SuppressWarnings("WeakerAccess")
-public class EditCardViewModel extends ViewModel {
+public class EditCardViewModel extends AndroidViewModel {
+ private SyncManager syncManager;
private Account account;
private long boardId;
- private FullCard originalCard;
- private FullCard fullCard;
+ private FullCardWithProjects originalCard;
+ private FullCardWithProjects fullCard;
private boolean isSupportedVersion = false;
private boolean hasCommentsAbility = false;
private boolean pendingCreation = false;
private boolean canEdit = false;
private boolean createMode = false;
+ public EditCardViewModel(@NonNull Application application) {
+ super(application);
+ this.syncManager = new SyncManager(application);
+ }
+
/**
* Stores a deep copy of the given fullCard to be able to compare the state at every time in #{@link EditCardViewModel#hasChanges()}
*
* @param boardId Local ID, expecting a positive long value
* @param fullCard The card that is currently edited
*/
- public void initializeExistingCard(long boardId, @NonNull FullCard fullCard, boolean isSupportedVersion) {
+ public void initializeExistingCard(long boardId, @NonNull FullCardWithProjects fullCard, boolean isSupportedVersion) {
this.boardId = boardId;
this.fullCard = fullCard;
- this.originalCard = new FullCard(this.fullCard);
+ this.originalCard = new FullCardWithProjects(this.fullCard);
this.isSupportedVersion = isSupportedVersion;
}
@@ -43,7 +62,7 @@ public class EditCardViewModel extends ViewModel {
* @param stackId Local ID, expecting a positive long value where the card should be created
*/
public void initializeNewCard(long boardId, long stackId, boolean isSupportedVersion) {
- final FullCard fullCard = new FullCard();
+ final FullCardWithProjects fullCard = new FullCardWithProjects();
fullCard.setLabels(new ArrayList<>());
fullCard.setAssignedUsers(new ArrayList<>());
fullCard.setAttachments(new ArrayList<>());
@@ -55,11 +74,12 @@ public class EditCardViewModel extends ViewModel {
public void setAccount(@NonNull Account account) {
this.account = account;
+ this.syncManager = new SyncManager(getApplication(), account.getName());
hasCommentsAbility = account.getServerDeckVersionAsObject().supportsComments();
}
public boolean hasChanges() {
- if(fullCard == null) {
+ if (fullCard == null) {
DeckLog.info("Can not check for changes because fullCard is null → assuming no changes have been made yet.");
return false;
}
@@ -74,7 +94,7 @@ public class EditCardViewModel extends ViewModel {
return account;
}
- public FullCard getFullCard() {
+ public FullCardWithProjects getFullCard() {
return fullCard;
}
@@ -105,4 +125,44 @@ public class EditCardViewModel extends ViewModel {
public long getBoardId() {
return boardId;
}
+
+ public LiveData<FullBoard> getFullBoardById(Long accountId, Long localId) {
+ return syncManager.getFullBoardById(accountId, localId);
+ }
+
+ public WrappedLiveData<Label> createLabel(long accountId, Label label, long localBoardId) {
+ return syncManager.createLabel(accountId, label, localBoardId);
+ }
+
+ 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);
+ }
+
+ 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 WrappedLiveData<Void> deleteAttachmentOfCard(long accountId, long localCardId, long localAttachmentId) {
+ return syncManager.deleteAttachmentOfCard(accountId, localCardId, localAttachmentId);
+ }
+
+ public LiveData<Card> getCardByRemoteID(long accountId, long remoteId) {
+ return syncManager.getCardByRemoteID(accountId, remoteId);
+ }
+
+ public LiveData<Board> getBoardByRemoteId(long accountId, long remoteId) {
+ return syncManager.getBoardByRemoteId(accountId, remoteId);
+ }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/ItemCardViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/ItemCardViewHolder.java
deleted file mode 100644
index c9e41c37f..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/ItemCardViewHolder.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package it.niedermann.nextcloud.deck.ui.card;
-
-import androidx.recyclerview.widget.RecyclerView;
-
-import it.niedermann.nextcloud.deck.databinding.ItemCardBinding;
-
-public class ItemCardViewHolder extends RecyclerView.ViewHolder {
- public ItemCardBinding binding;
-
- @SuppressWarnings("WeakerAccess")
- public ItemCardViewHolder(ItemCardBinding binding) {
- super(binding.getRoot());
- this.binding = binding;
- }
-} \ No newline at end of file
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 f77282e11..fe974f2a0 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
@@ -9,6 +9,7 @@ import android.view.ViewGroup;
import android.widget.Filter;
import androidx.activity.ComponentActivity;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.graphics.drawable.DrawableCompat;
@@ -18,11 +19,11 @@ import java.util.Collection;
import java.util.List;
import java.util.Random;
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemAutocompleteLabelBinding;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.util.AutoCompleteAdapter;
-import it.niedermann.nextcloud.deck.util.ColorUtil;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
@@ -35,7 +36,7 @@ public class LabelAutoCompleteAdapter extends AutoCompleteAdapter<Label> {
public LabelAutoCompleteAdapter(@NonNull ComponentActivity activity, long accountId, long boardId, long cardId) {
super(activity, accountId, boardId, cardId);
final String[] colors = activity.getResources().getStringArray(R.array.board_default_colors);
- final String createLabelColor = colors[new Random().nextInt(colors.length)].substring(1);
+ @ColorInt int createLabelColor = Color.parseColor(colors[new Random().nextInt(colors.length)]);
observeOnce(syncManager.getFullBoardById(accountId, boardId), activity, (fullBoard) -> {
if (fullBoard.getBoard().isPermissionManage()) {
canManage = true;
@@ -59,8 +60,8 @@ public class LabelAutoCompleteAdapter extends AutoCompleteAdapter<Label> {
}
final Label label = getItem(position);
- final int labelColor = Color.parseColor("#" + label.getColor());
- final int color = ColorUtil.getForegroundColorForBackgroundColor(labelColor);
+ final int labelColor = label.getColor();
+ final int color = ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(labelColor);
binding.label.setText(label.getTitle());
binding.label.setChipBackgroundColor(ColorStateList.valueOf(labelColor));
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java
index d2fd4d40d..473ad06c1 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/UserAutoCompleteAdapter.java
@@ -8,6 +8,7 @@ import android.widget.Filter;
import androidx.activity.ComponentActivity;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
import java.util.List;
@@ -15,14 +16,16 @@ import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemAutocompleteUserBinding;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.model.User;
+import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.extrawurst.UserSearchLiveData;
import it.niedermann.nextcloud.deck.util.AutoCompleteAdapter;
import it.niedermann.nextcloud.deck.util.ViewUtil;
-import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
-
public class UserAutoCompleteAdapter extends AutoCompleteAdapter<User> {
@NonNull
private Account account;
+ private UserSearchLiveData liveSearchForACL;
+ private LiveData<List<User>> liveData;
+ private Observer<List<User>> observer;
public UserAutoCompleteAdapter(@NonNull ComponentActivity activity, @NonNull Account account, long boardId) {
this(activity, account, boardId, NO_CARD);
@@ -31,6 +34,7 @@ public class UserAutoCompleteAdapter extends AutoCompleteAdapter<User> {
public UserAutoCompleteAdapter(@NonNull ComponentActivity activity, @NonNull Account account, long boardId, long cardId) {
super(activity, account.getId(), boardId, cardId);
this.account = account;
+ this.liveSearchForACL = syncManager.searchUserByUidOrDisplayNameForACL();
}
@Override
@@ -56,23 +60,24 @@ public class UserAutoCompleteAdapter extends AutoCompleteAdapter<User> {
protected FilterResults performFiltering(CharSequence constraint) {
if (constraint != null) {
activity.runOnUiThread(() -> {
- LiveData<List<User>> liveData;
final int constraintLength = constraint.toString().trim().length();
if (cardId == NO_CARD) {
liveData = constraintLength > 0
- ? syncManager.searchUserByUidOrDisplayNameForACL(accountId, boardId, constraint.toString())
+ ? liveSearchForACL.search(accountId, boardId, constraint.toString())
: syncManager.findProposalsForUsersToAssignForACL(accountId, boardId, activity.getResources().getInteger(R.integer.max_users_suggested));
} else {
liveData = constraintLength > 0
? syncManager.searchUserByUidOrDisplayName(accountId, boardId, cardId, constraint.toString())
: syncManager.findProposalsForUsersToAssign(accountId, boardId, cardId, activity.getResources().getInteger(R.integer.max_users_suggested));
}
- observeOnce(liveData, activity, users -> {
+ liveData.removeObservers(activity);
+ observer = users -> {
users.removeAll(itemsToExclude);
filterResults.values = users;
filterResults.count = users.size();
publishResults(constraint, filterResults);
- });
+ };
+ liveData.observe(activity, observer);
});
}
return filterResults;
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityFragment.java
index 08d960257..f95eea89f 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityFragment.java
@@ -8,11 +8,9 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
-import androidx.recyclerview.widget.RecyclerView;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.databinding.FragmentCardEditTabActivitiesBinding;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
public class CardActivityFragment extends Fragment {
@@ -39,17 +37,14 @@ public class CardActivityFragment extends Fragment {
}
if (!viewModel.isCreateMode()) {
- final SyncManager syncManager = new SyncManager(requireContext());
-
- syncManager.syncActivitiesForCard(viewModel.getFullCard().getCard()).observe(getViewLifecycleOwner(), (activities -> {
+ viewModel.syncActivitiesForCard(viewModel.getFullCard().getCard()).observe(getViewLifecycleOwner(), (activities -> {
if (activities == null || activities.size() == 0) {
binding.emptyContentView.setVisibility(View.VISIBLE);
binding.activitiesList.setVisibility(View.GONE);
} else {
binding.emptyContentView.setVisibility(View.GONE);
binding.activitiesList.setVisibility(View.VISIBLE);
- RecyclerView.Adapter adapter = new CardActivityAdapter(activities, requireActivity().getMenuInflater());
- binding.activitiesList.setAdapter(adapter);
+ binding.activitiesList.setAdapter(new CardActivityAdapter(activities, requireActivity().getMenuInflater()));
}
}));
} else {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityViewHolder.java
index 7d49b932d..6362f90dd 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/activities/CardActivityViewHolder.java
@@ -3,17 +3,18 @@ package it.niedermann.nextcloud.deck.ui.card.activities;
import android.content.Context;
import android.view.MenuInflater;
import android.view.View;
+import android.widget.ImageView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
+import it.niedermann.android.util.ClipboardUtil;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemActivityBinding;
import it.niedermann.nextcloud.deck.model.enums.ActivityType;
import it.niedermann.nextcloud.deck.model.ocs.Activity;
import it.niedermann.nextcloud.deck.util.DateUtil;
-
-import static it.niedermann.nextcloud.deck.util.ClipboardUtil.copyToClipboard;
+import it.niedermann.nextcloud.deck.util.ViewUtil;
public class CardActivityViewHolder extends RecyclerView.ViewHolder {
public ItemActivityBinding binding;
@@ -26,38 +27,61 @@ public class CardActivityViewHolder extends RecyclerView.ViewHolder {
public void bind(@NonNull Activity activity, @NonNull MenuInflater inflater) {
final Context context = itemView.getContext();
- binding.date.setText(DateUtil.getRelativeDateTimeString(context, activity.getLastModified().getTime()));
+ binding.date.setText(DateUtil.getRelativeDateTimeString(context, activity.getLastModified().toEpochMilli()));
binding.subject.setText(activity.getSubject());
itemView.setOnClickListener(View::showContextMenu);
itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
inflater.inflate(R.menu.activity_menu, menu);
- menu.findItem(android.R.id.copy).setOnMenuItemClickListener(item -> copyToClipboard(context, activity.getSubject()));
+ menu.findItem(android.R.id.copy).setOnMenuItemClickListener(item -> ClipboardUtil.INSTANCE.copyToClipboard(context, activity.getSubject()));
});
- switch (ActivityType.findById(activity.getType())) {
+ final ActivityType type = ActivityType.findById(activity.getType());
+ setImageResource(binding.type, type);
+ setImageColor(context, binding.type, type);
+ }
+
+ private static void setImageResource(@NonNull ImageView imageView, @NonNull ActivityType type) {
+ switch (type) {
case CHANGE:
- binding.type.setImageResource(R.drawable.type_change_36dp);
+ imageView.setImageResource(R.drawable.type_change_36dp);
break;
case ADD:
- binding.type.setImageResource(R.drawable.type_add_color_36dp);
+ imageView.setImageResource(R.drawable.type_add_color_36dp);
break;
case DELETE:
- binding.type.setImageResource(R.drawable.type_delete_color_36dp);
+ imageView.setImageResource(R.drawable.type_delete_color_36dp);
break;
case ARCHIVE:
- binding.type.setImageResource(R.drawable.type_archive_grey600_36dp);
+ imageView.setImageResource(R.drawable.type_archive_grey600_36dp);
break;
case TAGGED_WITH_LABEL:
- binding.type.setImageResource(R.drawable.type_label_grey600_36dp);
+ imageView.setImageResource(R.drawable.type_label_grey600_36dp);
break;
case COMMENT:
- binding.type.setImageResource(R.drawable.type_comment_grey600_36dp);
+ imageView.setImageResource(R.drawable.type_comment_grey600_36dp);
break;
case FILES:
- binding.type.setImageResource(R.drawable.type_file_36dp);
+ imageView.setImageResource(R.drawable.type_file_36dp);
+ break;
case HISTORY:
- binding.type.setImageResource(R.drawable.type_file_36dp);
+ imageView.setImageResource(R.drawable.type_history_36dp);
+ break;
case DECK:
default:
+ imageView.setImageResource(R.drawable.ic_app_logo);
+ break;
+ }
+ }
+
+ private static void setImageColor(@NonNull Context context, @NonNull ImageView imageView, @NonNull ActivityType type) {
+ switch (type) {
+ case ADD:
+ ViewUtil.setImageColor(context, imageView, R.color.activity_create);
+ break;
+ case DELETE:
+ ViewUtil.setImageColor(context, imageView, R.color.activity_delete);
+ break;
+ default:
+ ViewUtil.setImageColor(context, imageView, R.color.grey600);
break;
}
}
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
new file mode 100644
index 000000000..34d2eb3f3
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeDialog.java
@@ -0,0 +1,113 @@
+package it.niedermann.nextcloud.deck.ui.card.assignee;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.graphics.Color;
+import android.net.Uri;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.swiperefreshlayout.widget.CircularProgressDrawable;
+
+import com.bumptech.glide.Glide;
+
+import java.io.Serializable;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.DialogPreviewBinding;
+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 static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
+
+@Deprecated
+public class CardAssigneeDialog extends BrandedDialogFragment {
+
+ private static final String KEY_USER = "user";
+ private DialogPreviewBinding binding;
+ private EditCardViewModel viewModel;
+
+ @Nullable
+ private CardAssigneeListener cardAssigneeListener = null;
+ @SuppressWarnings("NotNullFieldNotInitialized")
+ @NonNull
+ private User user;
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+
+ if (getParentFragment() instanceof CardAssigneeListener) {
+ this.cardAssigneeListener = (CardAssigneeListener) getParentFragment();
+ } else if (context instanceof CardAssigneeListener) {
+ this.cardAssigneeListener = (CardAssigneeListener) context;
+ }
+
+ final Bundle args = requireArguments();
+ if (!args.containsKey(KEY_USER)) {
+ throw new IllegalArgumentException("Provide at least " + KEY_USER);
+ }
+ final Serializable user = args.getSerializable(KEY_USER);
+ if (user == null) {
+ throw new IllegalArgumentException(KEY_USER + " must not be null.");
+ }
+ this.user = (User) user;
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ binding = DialogPreviewBinding.inflate(LayoutInflater.from(requireContext()));
+ viewModel = new ViewModelProvider(requireActivity()).get(EditCardViewModel.class);
+
+ AlertDialog.Builder dialogBuilder = new BrandedDeleteAlertDialogBuilder(requireContext());
+
+ if (viewModel.canEdit() && cardAssigneeListener != null) {
+ dialogBuilder.setPositiveButton(R.string.simple_unassign, (d, w) -> cardAssigneeListener.onUnassignUser(user));
+ }
+
+ return dialogBuilder
+ .setView(binding.getRoot())
+ .setNeutralButton(R.string.simple_close, null)
+ .create();
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+
+ final Context context = requireContext();
+
+ final CircularProgressDrawable circularProgressDrawable = new CircularProgressDrawable(context);
+ circularProgressDrawable.setStrokeWidth(5f);
+ circularProgressDrawable.setCenterRadius(30f);
+ circularProgressDrawable.setColorSchemeColors(isDarkTheme(context) ? Color.LTGRAY : Color.DKGRAY);
+ circularProgressDrawable.start();
+
+ binding.avatar.post(() -> Glide.with(binding.avatar.getContext())
+ .load(viewModel.getAccount().getUrl() + "/index.php/avatar/" + Uri.encode(user.getUid()) + "/" + binding.avatar.getWidth())
+ .placeholder(circularProgressDrawable)
+ .error(R.drawable.ic_person_grey600_24dp)
+ .into(binding.avatar));
+ binding.title.setText(user.getDisplayname());
+ }
+
+ @Override
+ public void applyBrand(int mainColor) {
+ }
+
+ public static DialogFragment newInstance(@NonNull User user) {
+ final DialogFragment fragment = new CardAssigneeDialog();
+ final Bundle args = new Bundle();
+ args.putSerializable(KEY_USER, user);
+ fragment.setArguments(args);
+ return fragment;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeListener.java
new file mode 100644
index 000000000..259a8b57c
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/assignee/CardAssigneeListener.java
@@ -0,0 +1,11 @@
+package it.niedermann.nextcloud.deck.ui.card.assignee;
+
+import androidx.annotation.NonNull;
+
+import it.niedermann.nextcloud.deck.model.User;
+
+public interface CardAssigneeListener {
+
+ void onUnassignUser(@NonNull User user);
+
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java
index c236fa4c5..2d3ece255 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/AttachmentDeletedListener.java
@@ -3,5 +3,5 @@ package it.niedermann.nextcloud.deck.ui.card.attachments;
import it.niedermann.nextcloud.deck.model.Attachment;
public interface AttachmentDeletedListener {
- void onAttachmentDeleted(Attachment attachment);
- } \ No newline at end of file
+ void onAttachmentDeleted(Attachment attachment);
+} \ No newline at end of file
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 ed3031b7c..533bbe322 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
@@ -1,18 +1,59 @@
package it.niedermann.nextcloud.deck.ui.card.attachments;
+import android.view.MenuInflater;
import android.view.View;
import android.widget.ImageView;
+import androidx.annotation.CallSuper;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
+import it.niedermann.android.util.ClipboardUtil;
+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.ui.branding.BrandingUtil;
+import it.niedermann.nextcloud.deck.util.AttachmentUtil;
+
public abstract class AttachmentViewHolder extends RecyclerView.ViewHolder {
AttachmentViewHolder(@NonNull View itemView) {
super(itemView);
}
+ 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));
+ }
+
+ @CallSuper
+ public void bind(@NonNull MenuInflater menuInflater, @NonNull FragmentManager fragmentManager, Long cardRemoteId, Attachment attachment, @Nullable View.OnClickListener onClickListener, @ColorInt int mainColor, @Nullable String attachmentUri) {
+ 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 (attachmentUri == null || attachment.getId() == null || cardRemoteId == null) {
+ menu.findItem(android.R.id.copyUrl).setVisible(false);
+ } else {
+ menu.findItem(android.R.id.copyUrl).setVisible(true);
+ menu.findItem(android.R.id.copyUrl).setOnMenuItemClickListener(item -> ClipboardUtil.INSTANCE.copyToClipboard(itemView.getContext(), attachment.getFilename(), attachmentUri));
+ }
+ });
+ }
+
abstract protected ImageView getPreview();
- abstract protected void setNotSyncedYetStatus(boolean synced, @ColorInt int color);
+ protected void setNotSyncedYetStatus(boolean synced, @ColorInt int mainColor) {
+ final ImageView notSyncedYet = getNotSyncedYetStatusIcon();
+ DrawableCompat.setTint(notSyncedYet.getDrawable(), BrandingUtil.getSecondaryForegroundColorDependingOnTheme(notSyncedYet.getContext(), mainColor));
+ notSyncedYet.setVisibility(synced ? View.GONE : View.VISIBLE);
+ }
+
+ abstract protected ImageView getNotSyncedYetStatusIcon();
} \ No newline at end of file
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 a72ed8ef2..d601f6bbd 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,9 +3,7 @@ package it.niedermann.nextcloud.deck.ui.card.attachments;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
-import android.net.Uri;
import android.os.Build;
-import android.text.format.Formatter;
import android.view.LayoutInflater;
import android.view.MenuInflater;
import android.view.View;
@@ -16,10 +14,10 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.app.ActivityOptionsCompat;
import androidx.fragment.app.FragmentManager;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
import androidx.recyclerview.widget.RecyclerView;
-import com.bumptech.glide.Glide;
-
import java.util.ArrayList;
import java.util.List;
@@ -28,21 +26,23 @@ import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding;
import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding;
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.ui.attachments.AttachmentsActivity;
-import it.niedermann.nextcloud.deck.util.AttachmentUtil;
-import it.niedermann.nextcloud.deck.util.DateUtil;
+import it.niedermann.nextcloud.deck.ui.branding.Branded;
import it.niedermann.nextcloud.deck.util.MimeTypeUtil;
+import static androidx.lifecycle.Transformations.distinctUntilChanged;
import static androidx.recyclerview.widget.RecyclerView.NO_ID;
-import static it.niedermann.nextcloud.deck.util.ClipboardUtil.copyToClipboard;
+import static it.niedermann.nextcloud.deck.util.AttachmentUtil.openAttachmentInBrowser;
@SuppressWarnings("WeakerAccess")
-public class CardAttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHolder> {
+public class CardAttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHolder> implements Branded {
public static final int VIEW_TYPE_DEFAULT = 2;
public static final int VIEW_TYPE_IMAGE = 1;
+ @NonNull
+ private final MutableLiveData<Boolean> isEmpty = new MutableLiveData<>(true);
+ @NonNull
private final MenuInflater menuInflater;
@ColorInt
private int mainColor;
@@ -51,17 +51,16 @@ public class CardAttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHo
private Long cardRemoteId = null;
private final long cardLocalId;
@NonNull
- FragmentManager fragmentManager;
+ private final FragmentManager fragmentManager;
+ @NonNull
+ private final List<Attachment> attachments = new ArrayList<>();
@NonNull
- private List<Attachment> attachments = new ArrayList<>();
- @Nullable
private final AttachmentClickedListener attachmentClickedListener;
CardAttachmentAdapter(
- @NonNull Context context,
@NonNull FragmentManager fragmentManager,
@NonNull MenuInflater menuInflater,
- @Nullable AttachmentClickedListener attachmentClickedListener,
+ @NonNull AttachmentClickedListener attachmentClickedListener,
@NonNull Account account,
@Nullable Long cardLocalId
) {
@@ -95,39 +94,14 @@ public class CardAttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHo
@Override
public void onBindViewHolder(@NonNull AttachmentViewHolder holder, int position) {
- final Context context = holder.itemView.getContext();
final Attachment attachment = attachments.get(position);
- final int viewType = getItemViewType(position);
-
- @Nullable final String uri = (attachment.getId() == null || cardRemoteId == null)
- ? attachment.getLocalPath() :
- AttachmentUtil.getRemoteUrl(account.getUrl(), cardRemoteId, attachment.getId());
- holder.setNotSyncedYetStatus(!DBStatus.LOCAL_EDITED.equals(attachment.getStatusEnum()), mainColor);
- holder.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 (uri == null) {
- menu.findItem(android.R.id.copyUrl).setVisible(false);
- } else {
- menu.findItem(android.R.id.copyUrl).setOnMenuItemClickListener(item -> copyToClipboard(context, attachment.getFilename(), uri));
- }
- });
+ final Context context = holder.itemView.getContext();
+ final View.OnClickListener onClickListener;
- switch (viewType) {
+ switch (getItemViewType(position)) {
case VIEW_TYPE_IMAGE: {
- holder.getPreview().setImageResource(R.drawable.ic_image_grey600_24dp);
- Glide.with(context)
- .load(uri)
- .placeholder(R.drawable.ic_image_grey600_24dp)
- .error(R.drawable.ic_image_grey600_24dp)
- .into(holder.getPreview());
- holder.itemView.setOnClickListener((v) -> {
- if (attachmentClickedListener != null) {
- attachmentClickedListener.onAttachmentClicked(position);
- }
+ 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) {
String transitionName = context.getString(R.string.transition_attachment_preview, String.valueOf(attachment.getLocalId()));
@@ -136,46 +110,16 @@ public class CardAttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHo
} else {
context.startActivity(intent);
}
- });
+ };
break;
}
case VIEW_TYPE_DEFAULT:
default: {
- DefaultAttachmentViewHolder defaultHolder = (DefaultAttachmentViewHolder) holder;
-
- if (MimeTypeUtil.isAudio(attachment.getMimetype())) {
- holder.getPreview().setImageResource(R.drawable.ic_music_note_grey600_24dp);
- } else if (MimeTypeUtil.isVideo(attachment.getMimetype())) {
- holder.getPreview().setImageResource(R.drawable.ic_local_movies_grey600_24dp);
- } else if (MimeTypeUtil.isPdf(attachment.getMimetype())) {
- holder.getPreview().setImageResource(R.drawable.ic_baseline_picture_as_pdf_24);
- } else if (MimeTypeUtil.isContact(attachment.getMimetype())) {
- holder.getPreview().setImageResource(R.drawable.ic_baseline_contact_mail_24);
- } else {
- holder.getPreview().setImageResource(R.drawable.ic_attach_file_grey600_24dp);
- }
-
- if (cardRemoteId != null) {
- defaultHolder.itemView.setOnClickListener((event) -> {
- Intent openURL = new Intent(Intent.ACTION_VIEW);
- openURL.setData(Uri.parse(AttachmentUtil.getRemoteUrl(account.getUrl(), cardRemoteId, attachment.getId())));
- context.startActivity(openURL);
- });
- }
- defaultHolder.binding.filename.setText(attachment.getBasename());
- defaultHolder.binding.filesize.setText(Formatter.formatFileSize(context, attachment.getFilesize()));
- if (attachment.getLastModifiedLocal() != null) {
- defaultHolder.binding.modified.setText(DateUtil.getRelativeDateTimeString(context, attachment.getLastModifiedLocal().getTime()));
- defaultHolder.binding.modified.setVisibility(View.VISIBLE);
- } else if (attachment.getLastModified() != null) {
- defaultHolder.binding.modified.setText(DateUtil.getRelativeDateTimeString(context, attachment.getLastModified().getTime()));
- defaultHolder.binding.modified.setVisibility(View.VISIBLE);
- } else {
- defaultHolder.binding.modified.setVisibility(View.GONE);
- }
+ onClickListener = (event) -> openAttachmentInBrowser(context, account.getUrl(), cardRemoteId, attachment.getId());
break;
}
}
+ holder.bind(account, menuInflater, fragmentManager, cardRemoteId, attachment, onClickListener, mainColor);
}
@Override
@@ -188,21 +132,46 @@ public class CardAttachmentAdapter extends RecyclerView.Adapter<AttachmentViewHo
return attachments.size();
}
+ private void updateIsEmpty() {
+ this.isEmpty.postValue(getItemCount() <= 0);
+ }
+
+ @NonNull
+ public LiveData<Boolean> isEmpty() {
+ return distinctUntilChanged(this.isEmpty);
+ }
+
public void setAttachments(@NonNull List<Attachment> attachments, @Nullable Long cardRemoteId) {
this.cardRemoteId = cardRemoteId;
this.attachments.clear();
this.attachments.addAll(attachments);
notifyDataSetChanged();
+ this.updateIsEmpty();
}
public void addAttachment(Attachment a) {
- this.attachments.add(a);
+ this.attachments.add(0, a);
notifyItemInserted(this.attachments.size());
+ this.updateIsEmpty();
}
public void removeAttachment(Attachment a) {
final int index = this.attachments.indexOf(a);
this.attachments.remove(a);
notifyItemRemoved(index);
+ this.updateIsEmpty();
+ }
+
+ public void replaceAttachment(Attachment toReplace, Attachment with) {
+ final int index = this.attachments.indexOf(toReplace);
+ this.attachments.remove(toReplace);
+ this.attachments.add(index, with);
+ notifyItemChanged(index);
+ }
+
+ @Override
+ public void applyBrand(@ColorInt int mainColor) {
+ this.mainColor = mainColor;
+ notifyDataSetChanged();
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsBottomsheetBehaviorCallback.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsBottomsheetBehaviorCallback.java
new file mode 100644
index 000000000..6b60bbffd
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/CardAttachmentsBottomsheetBehaviorCallback.java
@@ -0,0 +1,91 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments;
+
+import android.content.Context;
+import android.view.View;
+
+import androidx.activity.OnBackPressedCallback;
+import androidx.annotation.ColorInt;
+import androidx.annotation.ColorRes;
+import androidx.annotation.DimenRes;
+import androidx.annotation.NonNull;
+import androidx.annotation.Px;
+import androidx.core.content.ContextCompat;
+
+import com.google.android.material.animation.ArgbEvaluatorCompat;
+import com.google.android.material.bottomnavigation.BottomNavigationView;
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
+import com.google.android.material.floatingactionbutton.FloatingActionButton;
+
+import it.niedermann.android.util.DimensionUtil;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN;
+
+public class CardAttachmentsBottomsheetBehaviorCallback extends BottomSheetBehavior.BottomSheetCallback {
+ @NonNull
+ private final OnBackPressedCallback backPressedCallback;
+ @NonNull
+ private final FloatingActionButton fab;
+ @NonNull
+ private final View pickerBackdrop;
+ @NonNull
+ private final BottomNavigationView bottomNavigation;
+ @ColorInt
+ private final int backdropColorExpanded;
+ @ColorInt
+ private final int backdropColorCollapsed;
+ @Px
+ private final int bottomNavigationHeight;
+
+ private float lastOffset = -1;
+
+ public CardAttachmentsBottomsheetBehaviorCallback(@NonNull Context context,
+ @NonNull OnBackPressedCallback backPressedCallback,
+ @NonNull FloatingActionButton fab,
+ @NonNull View pickerBackdrop,
+ @NonNull BottomNavigationView bottomNavigation,
+ @ColorRes int backdropColorExpanded,
+ @ColorRes int backdropColorCollapsed,
+ @DimenRes int bottomNavigationHeight
+ ) {
+ this.backPressedCallback = backPressedCallback;
+ this.fab = fab;
+ this.pickerBackdrop = pickerBackdrop;
+ this.bottomNavigation = bottomNavigation;
+ this.backdropColorExpanded = ContextCompat.getColor(context, backdropColorExpanded);
+ this.backdropColorCollapsed = ContextCompat.getColor(context, backdropColorCollapsed);
+ this.bottomNavigationHeight = DimensionUtil.INSTANCE.dpToPx(context, bottomNavigationHeight);
+ }
+
+ @Override
+ public void onStateChanged(@NonNull View bottomSheet, int newState) {
+ if (newState == STATE_HIDDEN) {
+ backPressedCallback.setEnabled(false);
+ if (pickerBackdrop.getVisibility() != GONE) {
+ pickerBackdrop.setVisibility(GONE);
+ }
+ } else if (pickerBackdrop.getVisibility() != VISIBLE) {
+ pickerBackdrop.setVisibility(VISIBLE);
+ }
+ }
+
+ @Override
+ public void onSlide(@NonNull View bottomSheet, float slideOffset) {
+ if (slideOffset <= 0) {
+ final float bottomSheetPercentageShown = slideOffset * -1;
+ pickerBackdrop.setBackgroundColor(ArgbEvaluatorCompat.getInstance().evaluate(bottomSheetPercentageShown, backdropColorExpanded, backdropColorCollapsed));
+ bottomNavigation.setTranslationY(bottomSheetPercentageShown * bottomNavigationHeight);
+ if (slideOffset <= lastOffset && slideOffset != 0) {
+ if (fab.getVisibility() == GONE) {
+ fab.show();
+ }
+ } else {
+ if (fab.getVisibility() == VISIBLE) {
+ fab.hide();
+ }
+ }
+ }
+ lastOffset = slideOffset;
+ }
+}
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 c291c5bdf..07e8d39dd 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
@@ -1,35 +1,42 @@
package it.niedermann.nextcloud.deck.ui.card.attachments;
-import android.Manifest;
-import android.app.Activity;
import android.content.ContentResolver;
+import android.content.Context;
import android.content.Intent;
+import android.content.res.ColorStateList;
import android.net.Uri;
-import android.os.Build;
import android.os.Bundle;
+import android.provider.ContactsContract;
import android.util.DisplayMetrics;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+import androidx.activity.OnBackPressedCallback;
+import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
import androidx.core.app.SharedElementCallback;
+import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
import androidx.recyclerview.widget.GridLayoutManager;
+import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
+import com.google.android.material.bottomsheet.BottomSheetBehavior;
import com.google.android.material.snackbar.Snackbar;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import java.io.File;
import java.io.IOException;
-import java.util.Date;
+import java.time.Instant;
import java.util.List;
import java.util.Map;
+import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.FragmentCardEditTabAttachmentsBinding;
@@ -41,10 +48,36 @@ import it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.WrappedLiv
import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment;
import it.niedermann.nextcloud.deck.ui.branding.BrandedSnackbar;
import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
+import it.niedermann.nextcloud.deck.ui.card.attachments.picker.AbstractPickerAdapter;
+import it.niedermann.nextcloud.deck.ui.card.attachments.picker.ContactAdapter;
+import it.niedermann.nextcloud.deck.ui.card.attachments.picker.FileAdapter;
+import it.niedermann.nextcloud.deck.ui.card.attachments.picker.FileAdapterLegacy;
+import it.niedermann.nextcloud.deck.ui.card.attachments.picker.GalleryAdapter;
+import it.niedermann.nextcloud.deck.ui.card.attachments.picker.GalleryItemDecoration;
+import it.niedermann.nextcloud.deck.ui.card.attachments.previewdialog.PreviewDialog;
+import it.niedermann.nextcloud.deck.ui.card.attachments.previewdialog.PreviewDialogViewModel;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
-
+import it.niedermann.nextcloud.deck.ui.takephoto.TakePhotoActivity;
+import it.niedermann.nextcloud.deck.util.DeckColorUtil;
+import it.niedermann.nextcloud.deck.util.VCardUtil;
+
+import static android.Manifest.permission.CAMERA;
+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;
+import static androidx.core.content.PermissionChecker.PERMISSION_GRANTED;
+import static androidx.core.content.PermissionChecker.checkSelfPermission;
+import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_COLLAPSED;
+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;
@@ -53,14 +86,35 @@ import static java.net.HttpURLConnection.HTTP_CONFLICT;
public class CardAttachmentsFragment extends BrandedFragment implements AttachmentDeletedListener, AttachmentClickedListener {
private FragmentCardEditTabAttachmentsBinding binding;
- private EditCardViewModel viewModel;
+ private EditCardViewModel editViewModel;
+ private PreviewDialogViewModel previewViewModel;
+ private BottomSheetBehavior<LinearLayout> mBottomSheetBehaviour;
+
+ private RecyclerView.ItemDecoration galleryItemDecoration;
+
+ private static final int REQUEST_CODE_PICK_FILE = 1;
+ private static final int REQUEST_CODE_PICK_FILE_PERMISSION = 2;
+ private static final int REQUEST_CODE_PICK_CAMERA = 3;
+ private static final int REQUEST_CODE_PICK_GALLERY_PERMISSION = 4;
+ private static final int REQUEST_CODE_PICK_CONTACT = 5;
+ private static final int REQUEST_CODE_PICK_CONTACT_PICKER_PERMISSION = 6;
- private static final int REQUEST_CODE_ADD_ATTACHMENT = 1;
- private static final int REQUEST_PERMISSION = 2;
+ @ColorInt
+ private int accentColor;
+ @ColorInt
+ private int primaryColor;
- private SyncManager syncManager;
private CardAttachmentAdapter adapter;
+ private AbstractPickerAdapter<?> pickerAdapter;
+
+ private final OnBackPressedCallback backPressedCallback = new OnBackPressedCallback(true) {
+ @Override
+ public void handleOnBackPressed() {
+ mBottomSheetBehaviour.setState(STATE_HIDDEN);
+ }
+ };
+
private int clickedItemPosition;
@Override
@@ -69,30 +123,58 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
Bundle savedInstanceState) {
binding = FragmentCardEditTabAttachmentsBinding.inflate(inflater, container, false);
- viewModel = new ViewModelProvider(requireActivity()).get(EditCardViewModel.class);
+ editViewModel = new ViewModelProvider(requireActivity()).get(EditCardViewModel.class);
+ previewViewModel = new ViewModelProvider(requireActivity()).get(PreviewDialogViewModel.class);
+ binding.bottomNavigation.setOnNavigationItemSelectedListener(item -> {
+ if (item.getItemId() == R.id.gallery) {
+ showGalleryPicker();
+ } else if (item.getItemId() == R.id.contacts) {
+ showContactPicker();
+ } else if (item.getItemId() == R.id.files) {
+ showFilePicker();
+ }
+ return true;
+ });
+ accentColor = ContextCompat.getColor(requireContext(), R.color.accent);
+ primaryColor = ContextCompat.getColor(requireContext(), R.color.primary);
// This might be a zombie fragment with an empty EditCardViewModel after Android killed the activity (but not the fragment instance
// See https://github.com/stefan-niedermann/nextcloud-deck/issues/478
- if (viewModel.getFullCard() == null) {
+ if (editViewModel.getFullCard() == null) {
DeckLog.logError(new IllegalStateException("Cannot populate " + CardAttachmentsFragment.class.getSimpleName() + " because viewModel.getFullCard() is null"));
return binding.getRoot();
}
- syncManager = new SyncManager(requireContext());
adapter = new CardAttachmentAdapter(
- requireContext(),
getChildFragmentManager(),
requireActivity().getMenuInflater(),
this,
- viewModel.getAccount(),
- viewModel.getFullCard().getLocalId());
+ editViewModel.getAccount(),
+ editViewModel.getFullCard().getLocalId());
binding.attachmentsList.setAdapter(adapter);
- updateEmptyContentView();
+ adapter.isEmpty().observe(getViewLifecycleOwner(), (isEmpty) -> {
+ if (isEmpty) {
+ this.binding.emptyContentView.setVisibility(VISIBLE);
+ this.binding.attachmentsList.setVisibility(GONE);
+ } else {
+ this.binding.emptyContentView.setVisibility(GONE);
+ this.binding.attachmentsList.setVisibility(VISIBLE);
+ }
+ });
+ galleryItemDecoration = new GalleryItemDecoration(DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.spacer_1qx));
+ mBottomSheetBehaviour = BottomSheetBehavior.from(binding.bottomSheetParent);
+ mBottomSheetBehaviour.setDraggable(true);
+ mBottomSheetBehaviour.setHideable(true);
+ mBottomSheetBehaviour.setState(STATE_HIDDEN);
+ mBottomSheetBehaviour.addBottomSheetCallback(new CardAttachmentsBottomsheetBehaviorCallback(
+ requireContext(), backPressedCallback, binding.fab, binding.pickerBackdrop, binding.bottomNavigation,
+ R.color.mdtp_transparent_black, android.R.color.transparent, R.dimen.attachments_bottom_navigation_height));
+ binding.pickerBackdrop.setOnClickListener(v -> mBottomSheetBehaviour.setState(STATE_HIDDEN));
final DisplayMetrics displayMetrics = getResources().getDisplayMetrics();
- int spanCount = (int) ((displayMetrics.widthPixels / displayMetrics.density) / getResources().getInteger(R.integer.max_dp_attachment_column));
- GridLayoutManager glm = new GridLayoutManager(getContext(), spanCount);
+ final int spanCount = (int) ((displayMetrics.widthPixels / displayMetrics.density) / getResources().getInteger(R.integer.max_dp_attachment_column));
+ final GridLayoutManager glm = new GridLayoutManager(getContext(), spanCount);
glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
@@ -106,7 +188,7 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
}
});
binding.attachmentsList.setLayoutManager(glm);
- if (!viewModel.isCreateMode()) {
+ if (!editViewModel.isCreateMode()) {
// https://android-developers.googleblog.com/2018/02/continuous-shared-element-transitions.html?m=1
// https://github.com/android/animation-samples/blob/master/GridToPager/app/src/main/java/com/google/samples/gridtopager/fragment/ImagePagerFragment.java
setExitSharedElementCallback(new SharedElementCallback() {
@@ -119,17 +201,19 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
}
}
});
- adapter.setAttachments(viewModel.getFullCard().getAttachments(), viewModel.getFullCard().getId());
- updateEmptyContentView();
+ adapter.setAttachments(editViewModel.getFullCard().getAttachments(), editViewModel.getFullCard().getId());
}
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT && viewModel.canEdit()) {
+ if (editViewModel.canEdit()) {
binding.fab.setOnClickListener(v -> {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
- requestPermissions(new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
- REQUEST_PERMISSION);
+ if (SDK_INT < LOLLIPOP) {
+ openNativeFilePicker();
} else {
- startFilePickerIntent();
+ binding.bottomNavigation.setSelectedItemId(R.id.gallery);
+ showGalleryPicker();
+ mBottomSheetBehaviour.setState(STATE_COLLAPSED);
+ backPressedCallback.setEnabled(true);
+ requireActivity().getOnBackPressedDispatcher().addCallback(getViewLifecycleOwner(), backPressedCallback);
}
});
binding.fab.show();
@@ -146,116 +230,281 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
binding.fab.hide();
binding.emptyContentView.hideDescription();
}
+ @Nullable Context context = requireContext();
+ applyBrand(isBrandingEnabled(context)
+ ? readBrandMainColor(context)
+ : ContextCompat.getColor(context, R.color.defaultBrand));
return binding.getRoot();
}
- @RequiresApi(api = Build.VERSION_CODES.KITKAT)
- private void startFilePickerIntent() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
- intent.addCategory(Intent.CATEGORY_OPENABLE);
- intent.setType("*/*");
- startActivityForResult(intent, REQUEST_CODE_ADD_ATTACHMENT);
+ @Override
+ public void onPause() {
+ super.onPause();
+ backPressedCallback.setEnabled(false);
}
@Override
- public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
- if (requestCode == REQUEST_CODE_ADD_ATTACHMENT && resultCode == Activity.RESULT_OK) {
- if (data == null) {
- ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Intent data is null"), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- return;
+ public void onResume() {
+ super.onResume();
+ backPressedCallback.setEnabled(binding.bottomNavigation.getTranslationY() == 0);
+ }
+
+ private void showGalleryPicker() {
+ if (!(pickerAdapter instanceof GalleryAdapter)) {
+ if (isPermissionRequestNeeded(READ_EXTERNAL_STORAGE) || isPermissionRequestNeeded(CAMERA)) {
+ requestPermissions(new String[]{READ_EXTERNAL_STORAGE, CAMERA}, REQUEST_CODE_PICK_GALLERY_PERMISSION);
+ } else {
+ unbindPickerAdapter();
+ pickerAdapter = new GalleryAdapter(requireContext(), (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::openNativeCameraPicker, getViewLifecycleOwner());
+ if (binding.pickerRecyclerView.getItemDecorationCount() == 0) {
+ binding.pickerRecyclerView.addItemDecoration(galleryItemDecoration);
+ }
+ binding.pickerRecyclerView.setLayoutManager(new GridLayoutManager(requireContext(), 3));
+ binding.pickerRecyclerView.setAdapter(pickerAdapter);
}
- final Uri sourceUri = data.getData();
- if (sourceUri == null) {
- ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("sourceUri is null"), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- return;
+ }
+ }
+
+ private void showContactPicker() {
+ if (!(pickerAdapter instanceof ContactAdapter)) {
+ if (isPermissionRequestNeeded(READ_CONTACTS)) {
+ requestPermissions(new String[]{READ_CONTACTS}, REQUEST_CODE_PICK_CONTACT_PICKER_PERMISSION);
+ } else {
+ unbindPickerAdapter();
+ pickerAdapter = new ContactAdapter(requireContext(), (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_CONTACT, RESULT_OK, new Intent().setData(uri));
+ }
+ });
+ }, this::openNativeContactPicker);
+ removeGalleryItemDecoration();
+ binding.pickerRecyclerView.setLayoutManager(new LinearLayoutManager(requireContext()));
+ binding.pickerRecyclerView.setAdapter(pickerAdapter);
}
- if (!ContentResolver.SCHEME_CONTENT.equals(sourceUri.getScheme())) {
- ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Unknown URI scheme: " + sourceUri.getScheme()), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- return;
+ }
+ }
+
+ private void showFilePicker() {
+ if (!(pickerAdapter instanceof FileAdapter) && !(pickerAdapter instanceof FileAdapterLegacy)) {
+ if (isPermissionRequestNeeded(READ_EXTERNAL_STORAGE)) {
+ 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);
+ }
}
+ }
+ }
- DeckLog.verbose("--- found content URL " + sourceUri.getPath());
- File fileToUpload;
+ 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());
+ }
+ }
- try {
- DeckLog.verbose("---- so, now copy & upload: " + sourceUri.getPath());
- fileToUpload = copyContentUriToTempFile(requireContext(), sourceUri, viewModel.getAccount().getId(), viewModel.getFullCard().getCard().getLocalId());
- } catch (IllegalArgumentException | IOException e) {
- ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Could not copy content URI to temporary file", e), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- return;
- }
+ private void openNativeContactPicker() {
+ final Intent intent = new Intent(Intent.ACTION_PICK).setType(ContactsContract.Contacts.CONTENT_TYPE);
+ if (intent.resolveActivity(requireContext().getPackageManager()) != null) {
+ startActivityForResult(intent, REQUEST_CODE_PICK_CONTACT);
+ }
+ }
+
+ private void openNativeFilePicker() {
+ startActivityForResult(new Intent(Intent.ACTION_GET_CONTENT)
+ .addCategory(Intent.CATEGORY_OPENABLE)
+ .setType("*/*"), REQUEST_CODE_PICK_FILE);
+ }
+
+ /**
+ * Checks the current Android version and whether the permission has already been granted.
+ *
+ * @param permission see {@link android.Manifest.permission}
+ * @return whether or not requesting permission is needed
+ */
+ private boolean isPermissionRequestNeeded(@NonNull String permission) {
+ return SDK_INT >= M && checkSelfPermission(requireActivity(), permission) != PERMISSION_GRANTED;
+ }
+
+ private void unbindPickerAdapter() {
+ if (pickerAdapter != null) {
+ pickerAdapter.onDestroy();
+ }
+ }
+
+ private void removeGalleryItemDecoration() {
+ if (binding.pickerRecyclerView.getItemDecorationCount() > 0) {
+ binding.pickerRecyclerView.removeItemDecoration(galleryItemDecoration);
+ }
+ }
- for (Attachment existingAttachment : viewModel.getFullCard().getAttachments()) {
- final String existingPath = existingAttachment.getLocalPath();
- if (existingPath != null && existingPath.equals(fileToUpload.getAbsolutePath())) {
- BrandedSnackbar.make(binding.coordinatorLayout, R.string.attachment_already_exists, Snackbar.LENGTH_LONG).show();
- return;
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
+ super.onActivityResult(requestCode, resultCode, data);
+ switch (requestCode) {
+ case REQUEST_CODE_PICK_CONTACT:
+ case REQUEST_CODE_PICK_CAMERA:
+ case REQUEST_CODE_PICK_FILE: {
+ if (resultCode == RESULT_OK) {
+ final Uri sourceUri = requestCode == REQUEST_CODE_PICK_CONTACT
+ ? VCardUtil.getVCardContentUri(requireContext(), Uri.parse(data.getDataString()))
+ : data.getData();
+ try {
+ uploadNewAttachmentFromUri(sourceUri, requestCode == REQUEST_CODE_PICK_CAMERA
+ ? data.getType()
+ : requireContext().getContentResolver().getType(sourceUri));
+ mBottomSheetBehaviour.setState(STATE_HIDDEN);
+ } catch (Exception e) {
+ ExceptionDialogFragment.newInstance(e, editViewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ }
}
+ break;
+ }
+ default: {
+ super.onActivityResult(requestCode, resultCode, data);
}
+ }
+ }
+
+ @Override
+ public void onDestroy() {
+ if (this.pickerAdapter != null) {
+ this.pickerAdapter.onDestroy();
+ this.binding.pickerRecyclerView.setAdapter(null);
+ }
+ super.onDestroy();
+ }
- final Date now = new Date();
- final Attachment a = new Attachment();
- a.setMimetype(requireContext().getContentResolver().getType(sourceUri));
- a.setData(fileToUpload.getName());
- a.setFilename(fileToUpload.getName());
- a.setBasename(fileToUpload.getName());
- a.setFilesize(fileToUpload.length());
- a.setLocalPath(fileToUpload.getAbsolutePath());
- a.setLastModifiedLocal(now);
- a.setStatusEnum(DBStatus.LOCAL_EDITED);
- a.setCreatedAt(now);
- viewModel.getFullCard().getAttachments().add(a);
- adapter.addAttachment(a);
- if (!viewModel.isCreateMode()) {
- WrappedLiveData<Attachment> liveData = syncManager.addAttachmentToCard(viewModel.getAccount().getId(), viewModel.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
- viewModel.getFullCard().getAttachments().remove(a);
- adapter.removeAttachment(a);
- BrandedSnackbar.make(binding.coordinatorLayout, R.string.attachment_already_exists, Snackbar.LENGTH_LONG).show();
+ private void uploadNewAttachmentFromUri(@NonNull Uri sourceUri, String mimeType) throws UploadAttachmentFailedException, IOException {
+ 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 fileToUpload = copyContentUriToTempFile(requireContext(), sourceUri, editViewModel.getAccount().getId(), editViewModel.getFullCard().getLocalId());
+ for (Attachment existingAttachment : editViewModel.getFullCard().getAttachments()) {
+ final String existingPath = existingAttachment.getLocalPath();
+ if (existingPath != null && existingPath.equals(fileToUpload.getAbsolutePath())) {
+ BrandedSnackbar.make(binding.coordinatorLayout, R.string.attachment_already_exists, Snackbar.LENGTH_LONG).show();
+ return;
+ }
+ }
+ final Instant now = Instant.now();
+ final Attachment a = new Attachment();
+ a.setMimetype(mimeType);
+ a.setData(fileToUpload.getName());
+ a.setFilename(fileToUpload.getName());
+ a.setBasename(fileToUpload.getName());
+ a.setFilesize(fileToUpload.length());
+ a.setLocalPath(fileToUpload.getAbsolutePath());
+ a.setLastModifiedLocal(now);
+ a.setCreatedAt(now);
+ a.setStatusEnum(DBStatus.LOCAL_EDITED);
+ 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.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 {
- ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException("Unknown URI scheme", t), viewModel.getAccount()).show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ editViewModel.getFullCard().getAttachments().remove(a);
+ editViewModel.getFullCard().getAttachments().add(0, next);
+ adapter.replaceAttachment(a, next);
}
- } else {
- viewModel.getFullCard().getAttachments().remove(a);
- adapter.removeAttachment(a);
- viewModel.getFullCard().getAttachments().add(next);
- adapter.addAttachment(next);
- }
- });
+ });
+ }
+ break;
+ }
+ default: {
+ throw new UploadAttachmentFailedException("Unknown URI scheme: " + sourceUri.getScheme());
}
- updateEmptyContentView();
}
-
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
- if (requestCode == REQUEST_PERMISSION) {
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
- startFilePickerIntent();
+ switch (requestCode) {
+ case REQUEST_CODE_PICK_FILE_PERMISSION: {
+ if (checkSelfPermission(requireActivity(), READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED) {
+ showFilePicker();
+ } else {
+ Toast.makeText(requireContext(), R.string.cannot_upload_files_without_permission, Toast.LENGTH_LONG).show();
+ }
+ break;
}
- } else {
- super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+ case REQUEST_CODE_PICK_GALLERY_PERMISSION: {
+ if (checkSelfPermission(requireActivity(), READ_EXTERNAL_STORAGE) == PERMISSION_GRANTED && checkSelfPermission(requireActivity(), CAMERA) == PERMISSION_GRANTED) {
+ showGalleryPicker();
+ } else {
+ Toast.makeText(requireContext(), R.string.cannot_upload_files_without_permission, Toast.LENGTH_LONG).show();
+ }
+ break;
+ }
+ case REQUEST_CODE_PICK_CONTACT_PICKER_PERMISSION: {
+ if (checkSelfPermission(requireActivity(), READ_CONTACTS) == PERMISSION_GRANTED) {
+ showContactPicker();
+ } else {
+ Toast.makeText(requireContext(), R.string.cannot_upload_files_without_permission, Toast.LENGTH_LONG).show();
+ }
+ break;
+ }
+ default:
+ super.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
}
- public static Fragment newInstance() {
- return new CardAttachmentsFragment();
- }
-
@Override
public void onAttachmentDeleted(Attachment attachment) {
adapter.removeAttachment(attachment);
- viewModel.getFullCard().getAttachments().remove(attachment);
- if (!viewModel.isCreateMode() && attachment.getLocalId() != null) {
- syncManager.deleteAttachmentOfCard(viewModel.getAccount().getId(), viewModel.getFullCard().getLocalId(), attachment.getLocalId());
+ 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());
+ }
+ });
}
- updateEmptyContentView();
}
@Override
@@ -263,19 +512,28 @@ public class CardAttachmentsFragment extends BrandedFragment implements Attachme
this.clickedItemPosition = position;
}
-
- private void updateEmptyContentView() {
- if (this.adapter == null || this.adapter.getItemCount() == 0) {
- this.binding.emptyContentView.setVisibility(View.VISIBLE);
- this.binding.attachmentsList.setVisibility(View.GONE);
- } else {
- this.binding.emptyContentView.setVisibility(View.GONE);
- this.binding.attachmentsList.setVisibility(View.VISIBLE);
- }
- }
-
@Override
public void applyBrand(int mainColor) {
applyBrandToFAB(mainColor, binding.fab);
+ adapter.applyBrand(mainColor);
+ @ColorInt final int finalMainColor = DeckColorUtil.contrastRatioIsSufficient(mainColor, primaryColor)
+ ? mainColor
+ : accentColor;
+ final ColorStateList list = new ColorStateList(
+ new int[][]{
+ new int[]{android.R.attr.state_checked},
+ new int[]{}
+ },
+ new int[]{
+ finalMainColor,
+ accentColor
+ }
+ );
+ binding.bottomNavigation.setItemIconTintList(list);
+ binding.bottomNavigation.setItemTextColor(list);
+ }
+
+ public static Fragment newInstance() {
+ return new CardAttachmentsFragment();
}
}
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 7acdd390e..2b5358eb9 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
@@ -1,15 +1,25 @@
package it.niedermann.nextcloud.deck.ui.card.attachments;
+import android.text.format.Formatter;
+import android.view.MenuInflater;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.ColorInt;
-import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentManager;
import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Attachment;
+import it.niedermann.nextcloud.deck.util.DateUtil;
+
+import static it.niedermann.nextcloud.deck.util.AttachmentUtil.getIconForMimeType;
+import static it.niedermann.nextcloud.deck.util.AttachmentUtil.openAttachmentInBrowser;
public class DefaultAttachmentViewHolder extends AttachmentViewHolder {
- ItemAttachmentDefaultBinding binding;
+ private final ItemAttachmentDefaultBinding binding;
@SuppressWarnings("WeakerAccess")
public DefaultAttachmentViewHolder(ItemAttachmentDefaultBinding binding) {
@@ -23,8 +33,24 @@ public class DefaultAttachmentViewHolder extends AttachmentViewHolder {
}
@Override
- protected void setNotSyncedYetStatus(boolean synced, @ColorInt int mainColor) {
- DrawableCompat.setTint(binding.notSyncedYet.getDrawable(), mainColor);
- binding.notSyncedYet.setVisibility(synced ? View.GONE : View.VISIBLE);
+ protected ImageView getNotSyncedYetStatusIcon() {
+ return binding.notSyncedYet;
+ }
+
+ 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()));
+ binding.filename.setText(attachment.getBasename());
+ binding.filesize.setText(Formatter.formatFileSize(binding.filesize.getContext(), attachment.getFilesize()));
+ if (attachment.getLastModifiedLocal() != null) {
+ binding.modified.setText(DateUtil.getRelativeDateTimeString(binding.modified.getContext(), attachment.getLastModifiedLocal().toEpochMilli()));
+ binding.modified.setVisibility(View.VISIBLE);
+ } else if (attachment.getLastModified() != null) {
+ binding.modified.setText(DateUtil.getRelativeDateTimeString(binding.modified.getContext(), attachment.getLastModified().toEpochMilli()));
+ binding.modified.setVisibility(View.VISIBLE);
+ } else {
+ binding.modified.setVisibility(View.GONE);
+ }
}
} \ No newline at end of file
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 d13675a30..3c95da1b7 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
@@ -1,15 +1,24 @@
package it.niedermann.nextcloud.deck.ui.card.attachments;
+import android.view.MenuInflater;
import android.view.View;
import android.widget.ImageView;
import androidx.annotation.ColorInt;
-import androidx.core.graphics.drawable.DrawableCompat;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.FragmentManager;
+import com.bumptech.glide.Glide;
+
+import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Attachment;
+import it.niedermann.nextcloud.deck.util.AttachmentUtil;
public class ImageAttachmentViewHolder extends AttachmentViewHolder {
- private ItemAttachmentImageBinding binding;
+ private final ItemAttachmentImageBinding binding;
@SuppressWarnings("WeakerAccess")
public ImageAttachmentViewHolder(ItemAttachmentImageBinding binding) {
@@ -23,8 +32,22 @@ public class ImageAttachmentViewHolder extends AttachmentViewHolder {
}
@Override
- protected void setNotSyncedYetStatus(boolean synced, @ColorInt int mainColor) {
- DrawableCompat.setTint(binding.notSyncedYet.getDrawable(), mainColor);
- binding.notSyncedYet.setVisibility(synced ? View.GONE : View.VISIBLE);
+ protected ImageView getNotSyncedYetStatusIcon() {
+ return binding.notSyncedYet;
+ }
+
+ 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));
+
+ getPreview().post(() -> {
+ @Nullable final String uri = AttachmentUtil.getThumbnailUrl(account.getServerDeckVersionAsObject(), account.getUrl(), cardRemoteId, attachment, getPreview().getWidth());
+ Glide.with(getPreview().getContext())
+ .load(uri)
+ .placeholder(R.drawable.ic_image_grey600_24dp)
+ .error(R.drawable.ic_image_grey600_24dp)
+ .into(getPreview());
+ });
+
+ itemView.setOnClickListener(onClickListener);
}
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractCursorPickerAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractCursorPickerAdapter.java
new file mode 100644
index 000000000..a2ea6dd37
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractCursorPickerAdapter.java
@@ -0,0 +1,100 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.RequestBuilder;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.function.BiConsumer;
+
+import static android.database.Cursor.FIELD_TYPE_INTEGER;
+import static android.database.Cursor.FIELD_TYPE_NULL;
+import static androidx.recyclerview.widget.RecyclerView.NO_ID;
+import static java.util.Objects.requireNonNull;
+
+/**
+ * An {@link RecyclerView.Adapter} which provides previews of one type of files and also an option to open a native dialog.
+ * <p>
+ * Example: Previews for images of the gallery as well a one option to take a photo
+ */
+public abstract class AbstractCursorPickerAdapter<T extends RecyclerView.ViewHolder> extends AbstractPickerAdapter<T> {
+
+ private final int count;
+ protected final int columnIndex;
+ private final int columnIndexType;
+ @NonNull
+ protected final BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect;
+ @NonNull
+ protected final Runnable openNativePicker;
+ @NonNull
+ protected final Cursor cursor;
+ @NonNull
+ protected final ContentResolver contentResolver;
+
+ /**
+ * Should be used to bind heavy operations like when dealing with {@link Bitmap}.
+ * This must only be one {@link Thread} because otherwise the cursor might change while fetching data from it.
+ */
+ @NonNull
+ protected final ExecutorService bindExecutor = Executors.newFixedThreadPool(1);
+
+ public AbstractCursorPickerAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker, Uri subject, String idColumn, String sortOrder) {
+ this(context, onSelect, openNativePicker, subject, idColumn, new String[]{idColumn}, sortOrder);
+ }
+
+ public AbstractCursorPickerAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker, Uri subject, String idColumn, String[] requestedColumns, String sortOrder) {
+ this(context, onSelect, openNativePicker, idColumn, requireNonNull(context.getContentResolver().query(subject, requestedColumns, null, null, sortOrder)));
+ }
+
+ public AbstractCursorPickerAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker, String idColumn, @NonNull Cursor cursor) {
+ this.contentResolver = context.getContentResolver();
+ this.onSelect = onSelect;
+ this.openNativePicker = openNativePicker;
+ this.cursor = cursor;
+ this.cursor.moveToFirst();
+ this.columnIndex = this.cursor.getColumnIndex(idColumn);
+ this.count = cursor.getCount() + 1;
+ this.columnIndexType = (this.count > 1) ? this.cursor.getType(columnIndex) : FIELD_TYPE_NULL;
+ setHasStableIds(true);
+ }
+
+ /**
+ * Moves the {@link #cursor} to the given position
+ */
+ @Override
+ public long getItemId(int position) {
+ if (!cursor.isClosed() && cursor.moveToPosition(position - 1)) {
+ //noinspection SwitchStatementWithTooFewBranches
+ switch (columnIndexType) {
+ case FIELD_TYPE_INTEGER:
+ return cursor.getLong(columnIndex);
+ default:
+ throw new IllegalStateException("Unknown type for columnIndex \"" + columnIndex + "\": " + columnIndexType);
+ }
+ } else {
+ return NO_ID;
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return count;
+ }
+
+ /**
+ * Call this method when the {@link AbstractCursorPickerAdapter} is no longer need to free resources.
+ */
+ public void onDestroy() {
+ cursor.close();
+ bindExecutor.shutdownNow();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java
new file mode 100644
index 000000000..901d204cd
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/AbstractPickerAdapter.java
@@ -0,0 +1,26 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import androidx.recyclerview.widget.RecyclerView;
+
+public abstract class AbstractPickerAdapter<T extends RecyclerView.ViewHolder> extends RecyclerView.Adapter<T> {
+
+ protected static final int VIEW_TYPE_NONE = -1;
+ protected static final int VIEW_TYPE_ITEM = 0;
+ protected static final int VIEW_TYPE_ITEM_NATIVE = 1;
+
+ @Override
+ public int getItemViewType(int position) {
+ if (position > 0) {
+ return VIEW_TYPE_ITEM;
+ } else if (position == 0) {
+ return VIEW_TYPE_ITEM_NATIVE;
+ } else {
+ return VIEW_TYPE_NONE;
+ }
+ }
+
+ /**
+ * Call this method when the {@link AbstractPickerAdapter} is no longer need to free resources.
+ */
+ public abstract void onDestroy();
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java
new file mode 100644
index 000000000..22ac0c694
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactAdapter.java
@@ -0,0 +1,104 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.ContactsContract;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.RequestBuilder;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.function.BiConsumer;
+
+import it.niedermann.nextcloud.deck.databinding.ItemPickerNativeBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerUserBinding;
+
+import static android.provider.ContactsContract.CommonDataKinds.Email.DATA;
+import static android.provider.ContactsContract.CommonDataKinds.Phone.LOOKUP_KEY;
+import static android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER;
+import static android.provider.ContactsContract.Contacts.CONTENT_LOOKUP_URI;
+import static android.provider.ContactsContract.Contacts.CONTENT_URI;
+import static android.provider.ContactsContract.Contacts.DISPLAY_NAME;
+import static android.provider.ContactsContract.Contacts.SORT_KEY_PRIMARY;
+import static android.provider.ContactsContract.Contacts._ID;
+
+public class ContactAdapter extends AbstractCursorPickerAdapter<RecyclerView.ViewHolder> {
+
+ private final int lookupKeyColumnIndex;
+ private final int displayNameColumnIndex;
+
+ public ContactAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable onSelectPicker) {
+ super(context, onSelect, onSelectPicker, CONTENT_URI, _ID, new String[]{_ID, LOOKUP_KEY, DISPLAY_NAME}, SORT_KEY_PRIMARY);
+ lookupKeyColumnIndex = cursor.getColumnIndex(LOOKUP_KEY);
+ displayNameColumnIndex = cursor.getColumnIndex(DISPLAY_NAME);
+ notifyItemRangeInserted(0, getItemCount() + 1);
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case VIEW_TYPE_ITEM_NATIVE:
+ return new ContactNativeItemViewHolder(ItemPickerNativeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ case VIEW_TYPE_ITEM:
+ return new ContactItemViewHolder(ItemPickerUserBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ default:
+ throw new IllegalStateException("Unknown viewType " + viewType);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ switch (getItemViewType(position)) {
+ case VIEW_TYPE_ITEM_NATIVE: {
+ ((ContactNativeItemViewHolder) holder).bind(openNativePicker);
+ break;
+ }
+ case VIEW_TYPE_ITEM: {
+ final ContactItemViewHolder viewHolder = (ContactItemViewHolder) holder;
+ if (!cursor.isClosed()) {
+ cursor.moveToPosition(position - 1);
+ final String displayName = cursor.getString(displayNameColumnIndex);
+ final String lookupKey = cursor.getString(lookupKeyColumnIndex);
+ bindExecutor.execute(() -> {
+ try (InputStream inputStream = ContactsContract.Contacts.openContactPhotoInputStream(contentResolver, Uri.withAppendedPath(CONTENT_LOOKUP_URI, lookupKey))) {
+ final Bitmap thumbnail = BitmapFactory.decodeStream(inputStream);
+ String contactInformation = "";
+ try (final Cursor phoneCursor = contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, new String[]{NUMBER}, LOOKUP_KEY + " = ?", new String[]{lookupKey}, null)) {
+ if (phoneCursor != null && phoneCursor.moveToFirst()) {
+ contactInformation = phoneCursor.getString(phoneCursor.getColumnIndex(NUMBER));
+ }
+ }
+ if (TextUtils.isEmpty(contactInformation)) {
+ try (final Cursor emailCursor = contentResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, new String[]{DATA}, LOOKUP_KEY + " = ?", new String[]{lookupKey}, null)) {
+ if (emailCursor != null && emailCursor.moveToFirst()) {
+ contactInformation = emailCursor.getString(emailCursor.getColumnIndex(DATA));
+ }
+ }
+ }
+ final String finalContactInformation = contactInformation;
+ new Handler(Looper.getMainLooper()).post(() -> viewHolder.bind(Uri.withAppendedPath(CONTENT_LOOKUP_URI, lookupKey), thumbnail, displayName, finalContactInformation, onSelect));
+ } catch (IOException ignored) {
+ new Handler(Looper.getMainLooper()).post(viewHolder::bindError);
+ }
+ });
+ } else {
+ new Handler(Looper.getMainLooper()).post(viewHolder::bindError);
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java
new file mode 100644
index 000000000..f403fed21
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactItemViewHolder.java
@@ -0,0 +1,66 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.ColorDrawable;
+import android.net.Uri;
+import android.text.TextUtils;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestBuilder;
+import com.bumptech.glide.request.RequestOptions;
+
+import java.util.function.BiConsumer;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerUserBinding;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static it.niedermann.nextcloud.deck.util.VCardUtil.getColorBasedOnDisplayName;
+
+public class ContactItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemPickerUserBinding binding;
+
+ public ContactItemViewHolder(@NonNull ItemPickerUserBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Uri uri, @Nullable Bitmap image, @NonNull String displayName, @Nullable String contactInformation, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect) {
+ itemView.setOnClickListener((v) -> onSelect.accept(uri, new Pair<>(displayName, image == null ? null : Glide.with(itemView.getContext()).load(image))));
+ binding.title.setText(displayName);
+ binding.contactInformation.setText(contactInformation);
+ if (image == null) {
+ binding.initials.setVisibility(VISIBLE);
+ binding.initials.setText(TextUtils.isEmpty(displayName)
+ ? null
+ : String.valueOf(displayName.charAt(0))
+ );
+ Glide.with(itemView.getContext())
+ .load(new ColorDrawable(getColorBasedOnDisplayName(itemView.getContext(), displayName)))
+ .apply(RequestOptions.circleCropTransform())
+ .into(binding.avatar);
+ } else {
+ binding.initials.setVisibility(GONE);
+ binding.initials.setText(null);
+ Glide.with(itemView.getContext())
+ .load(image)
+ .placeholder(R.drawable.ic_person_grey600_24dp)
+ .apply(RequestOptions.circleCropTransform())
+ .into(binding.avatar);
+ }
+ }
+
+ public void bindError() {
+ itemView.setOnClickListener(null);
+ Glide.with(itemView.getContext())
+ .load(R.drawable.ic_person_grey600_24dp)
+ .into(binding.avatar);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactNativeItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactNativeItemViewHolder.java
new file mode 100644
index 000000000..a1d7d5921
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/ContactNativeItemViewHolder.java
@@ -0,0 +1,23 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerNativeBinding;
+
+public class ContactNativeItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemPickerNativeBinding binding;
+
+ public ContactNativeItemViewHolder(@NonNull ItemPickerNativeBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Runnable onOpenMajorPicker) {
+ binding.title.setText(R.string.show_all_contacts);
+ binding.subtitle.setText(R.string.contacts);
+ itemView.setOnClickListener((v) -> onOpenMajorPicker.run());
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java
new file mode 100644
index 000000000..aa96a0e69
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapter.java
@@ -0,0 +1,85 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.content.ContentUris;
+import android.content.Context;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.MediaStore;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.RequestBuilder;
+
+import java.util.function.BiConsumer;
+
+import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerNativeBinding;
+
+import static android.provider.MediaStore.Downloads.DATE_ADDED;
+import static android.provider.MediaStore.Downloads.DATE_MODIFIED;
+import static android.provider.MediaStore.Downloads.EXTERNAL_CONTENT_URI;
+import static android.provider.MediaStore.Downloads.MIME_TYPE;
+import static android.provider.MediaStore.Downloads.SIZE;
+import static android.provider.MediaStore.Downloads.TITLE;
+import static android.provider.MediaStore.Downloads._ID;
+import static java.util.Objects.requireNonNull;
+
+@RequiresApi(api = 29)
+public class FileAdapter extends AbstractCursorPickerAdapter<RecyclerView.ViewHolder> {
+
+ private final int displayNameColumnIndex;
+ private final int sizeColumnIndex;
+ private final int modifiedColumnIndex;
+ private final int mimeTypeColumnIndex;
+
+ private FileAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable onSelectPicker) {
+ super(context, onSelect, onSelectPicker, _ID, requireNonNull(context.getContentResolver().query(EXTERNAL_CONTENT_URI, new String[]{_ID, TITLE, SIZE, DATE_MODIFIED, MIME_TYPE}, null, null, DATE_ADDED + " DESC")));
+ displayNameColumnIndex = cursor.getColumnIndex(TITLE);
+ sizeColumnIndex = cursor.getColumnIndex(SIZE);
+ modifiedColumnIndex = cursor.getColumnIndex(DATE_MODIFIED);
+ mimeTypeColumnIndex = cursor.getColumnIndex(MIME_TYPE);
+ notifyItemRangeInserted(0, getItemCount() + 1);
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case VIEW_TYPE_ITEM_NATIVE:
+ return new FileNativeItemViewHolder(ItemPickerNativeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ case VIEW_TYPE_ITEM:
+ return new FileItemViewHolder(ItemAttachmentDefaultBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ default:
+ throw new IllegalStateException("Unknown viewType " + viewType);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ switch (getItemViewType(position)) {
+ case VIEW_TYPE_ITEM_NATIVE: {
+ ((FileNativeItemViewHolder) holder).bind(openNativePicker);
+ break;
+ }
+ case VIEW_TYPE_ITEM: {
+ if (!cursor.isClosed()) {
+ bindExecutor.execute(() -> {
+ final long id = getItemId(position);
+ final String name = cursor.getString(displayNameColumnIndex);
+ final String mimeType = cursor.getString(mimeTypeColumnIndex);
+ final long size = cursor.getLong(sizeColumnIndex);
+ final long modified = cursor.getLong(modifiedColumnIndex);
+ new Handler(Looper.getMainLooper()).post(() -> ((FileItemViewHolder) holder).bind(ContentUris.withAppendedId(MediaStore.Files.getContentUri("external"), id), name, mimeType, size, modified, onSelect));
+ });
+ }
+ break;
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapterLegacy.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapterLegacy.java
new file mode 100644
index 000000000..1ac14361a
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileAdapterLegacy.java
@@ -0,0 +1,88 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.net.Uri;
+import android.os.Environment;
+import android.util.Pair;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.RequestBuilder;
+
+import java.io.File;
+import java.util.Arrays;
+import java.util.List;
+import java.util.function.BiConsumer;
+
+import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerNativeBinding;
+import it.niedermann.nextcloud.deck.util.AttachmentUtil;
+
+import static java.util.Collections.reverseOrder;
+import static java.util.Comparator.comparingLong;
+import static java.util.Objects.requireNonNull;
+import static java.util.stream.Collectors.toList;
+
+@Deprecated
+public class FileAdapterLegacy extends AbstractPickerAdapter<RecyclerView.ViewHolder> {
+
+ @NonNull
+ private final List<File> files;
+ @NonNull
+ protected final BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect;
+ @NonNull
+ protected final Runnable openNativePicker;
+
+ public FileAdapterLegacy(@NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker) {
+ // TODO run in separate thread?
+ this.onSelect = onSelect;
+ this.openNativePicker = openNativePicker;
+ this.files = Arrays.stream(requireNonNull(requireNonNull(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)).listFiles()))
+ .sorted(reverseOrder(comparingLong(File::lastModified)))
+ .collect(toList());
+
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case VIEW_TYPE_ITEM_NATIVE:
+ return new FileNativeItemViewHolder(ItemPickerNativeBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ case VIEW_TYPE_ITEM:
+ return new FileItemViewHolder(ItemAttachmentDefaultBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ default:
+ throw new IllegalStateException("Unknown viewType " + viewType);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ switch (getItemViewType(position)) {
+ case VIEW_TYPE_ITEM_NATIVE: {
+ ((FileNativeItemViewHolder) holder).bind(openNativePicker);
+ break;
+ }
+ case VIEW_TYPE_ITEM: {
+ final File file = files.get(position - 1);
+ if (file.isFile()) {
+ ((FileItemViewHolder) holder).bind(Uri.fromFile(file), file.getName(), AttachmentUtil.getMimeType(file.getAbsolutePath()), file.length(), file.lastModified(), onSelect);
+ } else {
+ ((FileItemViewHolder) holder).bindError();
+ }
+ break;
+ }
+ }
+ }
+
+ @Override
+ public int getItemCount() {
+ return files.size();
+ }
+
+ public void onDestroy() {
+ // Let GarbageCollection do this stuff...
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java
new file mode 100644
index 000000000..f7d64aca8
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileItemViewHolder.java
@@ -0,0 +1,45 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.net.Uri;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.RequestBuilder;
+
+import java.util.function.BiConsumer;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemAttachmentDefaultBinding;
+
+import static android.text.format.Formatter.formatFileSize;
+import static it.niedermann.nextcloud.deck.util.AttachmentUtil.getIconForMimeType;
+import static it.niedermann.nextcloud.deck.util.DateUtil.getRelativeDateTimeString;
+
+public class FileItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemAttachmentDefaultBinding binding;
+
+ public FileItemViewHolder(@NonNull ItemAttachmentDefaultBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Uri uri, @NonNull String name, String mimeType, long size, long modified, @Nullable BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect) {
+ itemView.setOnClickListener(onSelect == null ? null : (v) -> onSelect.accept(uri, new Pair<>(name, null)));
+ binding.filename.setText(name);
+ binding.filesize.setText(formatFileSize(binding.filesize.getContext(), size));
+ binding.modified.setText(getRelativeDateTimeString(binding.modified.getContext(), modified));
+ binding.preview.setImageResource(getIconForMimeType(mimeType));
+ }
+
+ public void bindError() {
+ binding.filename.setText(R.string.simple_exception);
+ binding.filesize.setText(null);
+ binding.modified.setText(null);
+ itemView.setOnClickListener(null);
+ binding.preview.setImageResource(R.drawable.ic_attach_file_grey600_24dp);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileNativeItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileNativeItemViewHolder.java
new file mode 100644
index 000000000..79129f26a
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/FileNativeItemViewHolder.java
@@ -0,0 +1,23 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import androidx.annotation.NonNull;
+import androidx.recyclerview.widget.RecyclerView;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemPickerNativeBinding;
+
+public class FileNativeItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemPickerNativeBinding binding;
+
+ public FileNativeItemViewHolder(@NonNull ItemPickerNativeBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(Runnable onOpenMajorPicker) {
+ binding.title.setText(R.string.show_all_files);
+ binding.subtitle.setText(R.string.downloads);
+ itemView.setOnClickListener((v) -> onOpenMajorPicker.run());
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java
new file mode 100644
index 000000000..658eb1ee3
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryAdapter.java
@@ -0,0 +1,100 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.annotation.SuppressLint;
+import android.content.ContentUris;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Handler;
+import android.os.Looper;
+import android.provider.MediaStore;
+import android.util.Pair;
+import android.util.Size;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.RequestBuilder;
+
+import java.io.IOException;
+import java.util.function.BiConsumer;
+
+import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding;
+import it.niedermann.nextcloud.deck.databinding.ItemPhotoPreviewBinding;
+
+import static android.os.Build.VERSION.SDK_INT;
+import static android.os.Build.VERSION_CODES.Q;
+import static android.provider.BaseColumns._ID;
+import static android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+
+public class GalleryAdapter extends AbstractCursorPickerAdapter<RecyclerView.ViewHolder> {
+
+ @NonNull
+ private final LifecycleOwner lifecycleOwner;
+
+ @SuppressLint("InlinedApi")
+ private static final String sortOrder = (SDK_INT >= Q)
+ ? MediaStore.Images.Media.DATE_TAKEN
+ : MediaStore.Images.Media.DATE_ADDED;
+
+ public GalleryAdapter(@NonNull Context context, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect, @NonNull Runnable openNativePicker, @NonNull LifecycleOwner lifecycleOwner) {
+ super(context, onSelect, openNativePicker, EXTERNAL_CONTENT_URI, _ID, sortOrder + " DESC");
+ this.lifecycleOwner = lifecycleOwner;
+ notifyItemRangeInserted(0, getItemCount() + 1);
+ }
+
+ @NonNull
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ switch (viewType) {
+ case VIEW_TYPE_ITEM_NATIVE:
+ return new GalleryPhotoPreviewItemViewHolder(ItemPhotoPreviewBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ case VIEW_TYPE_ITEM:
+ return new GalleryItemViewHolder(ItemAttachmentImageBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ default:
+ throw new IllegalStateException("Unknown viewType " + viewType);
+ }
+
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
+ switch (getItemViewType(position)) {
+ case VIEW_TYPE_ITEM_NATIVE: {
+ ((GalleryPhotoPreviewItemViewHolder) holder).bind(openNativePicker, lifecycleOwner);
+ break;
+ }
+ case VIEW_TYPE_ITEM: {
+ final long id = getItemId(position);
+ bindExecutor.execute(() -> {
+ try {
+ final Bitmap thumbnail;
+ if (SDK_INT >= Q) {
+ thumbnail = contentResolver.loadThumbnail(ContentUris.withAppendedId(
+ EXTERNAL_CONTENT_URI, id), new Size(512, 384), null);
+ } else {
+ thumbnail = MediaStore.Images.Thumbnails.getThumbnail(
+ contentResolver, id,
+ MediaStore.Images.Thumbnails.MINI_KIND, null);
+ }
+ new Handler(Looper.getMainLooper()).post(() -> ((GalleryItemViewHolder) holder).bind(ContentUris.withAppendedId(
+ EXTERNAL_CONTENT_URI, id), thumbnail, onSelect));
+ } catch (IOException ignored) {
+ new Handler(Looper.getMainLooper()).post(((GalleryItemViewHolder) holder)::bindError);
+ }
+ });
+ }
+ }
+ }
+
+ @Override
+ public void onViewDetachedFromWindow(@NonNull RecyclerView.ViewHolder holder) {
+ super.onViewDetachedFromWindow(holder);
+ if (holder instanceof GalleryPhotoPreviewItemViewHolder) {
+ ((GalleryPhotoPreviewItemViewHolder) holder).unbind();
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemDecoration.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemDecoration.java
new file mode 100644
index 000000000..c70dc8277
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemDecoration.java
@@ -0,0 +1,29 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Px;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class GalleryItemDecoration extends RecyclerView.ItemDecoration {
+
+ @Px
+ private final int gutter;
+
+ public GalleryItemDecoration(@Px int gutter) {
+ this.gutter = gutter;
+ }
+
+ @Override
+ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+ final int position = parent.getChildAdapterPosition(view);
+ if (position >= 0) {
+ outRect.left = gutter;
+ outRect.top = gutter;
+ outRect.right = gutter;
+ outRect.bottom = gutter;
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java
new file mode 100644
index 000000000..346fca9c3
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryItemViewHolder.java
@@ -0,0 +1,42 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.util.Pair;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.RequestBuilder;
+
+import java.util.function.BiConsumer;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemAttachmentImageBinding;
+
+public class GalleryItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemAttachmentImageBinding binding;
+
+ public GalleryItemViewHolder(@NonNull ItemAttachmentImageBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Uri uri, @Nullable Bitmap image, @NonNull BiConsumer<Uri, Pair<String, RequestBuilder<?>>> onSelect) {
+ itemView.setOnClickListener((v) -> onSelect.accept(uri, new Pair<>(null, Glide.with(itemView.getContext()).load(image))));
+ Glide.with(itemView.getContext())
+ .load(image)
+ .placeholder(R.drawable.ic_image_grey600_24dp)
+ .into(binding.preview);
+ }
+
+ public void bindError() {
+ itemView.setOnClickListener(null);
+ Glide.with(itemView.getContext())
+ .load(R.drawable.ic_image_grey600_24dp)
+ .into(binding.preview);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryPhotoPreviewItemViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryPhotoPreviewItemViewHolder.java
new file mode 100644
index 000000000..00a833e57
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/picker/GalleryPhotoPreviewItemViewHolder.java
@@ -0,0 +1,51 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.picker;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.Preview;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.recyclerview.widget.RecyclerView;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.ExecutionException;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.databinding.ItemPhotoPreviewBinding;
+
+import static androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA;
+
+public class GalleryPhotoPreviewItemViewHolder extends RecyclerView.ViewHolder {
+
+ private final ItemPhotoPreviewBinding binding;
+ private ProcessCameraProvider cameraProvider;
+
+ public GalleryPhotoPreviewItemViewHolder(@NonNull ItemPhotoPreviewBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Runnable openNativePicker, @NonNull LifecycleOwner lifecycleOwner) {
+ itemView.setOnClickListener((v) -> openNativePicker.run());
+ ListenableFuture<ProcessCameraProvider> cameraProviderFuture = ProcessCameraProvider.getInstance(itemView.getContext());
+ cameraProviderFuture.addListener(() -> {
+ try {
+ unbind();
+ cameraProvider = cameraProviderFuture.get();
+ Preview previewUseCase = new Preview.Builder().build();
+ previewUseCase.setSurfaceProvider(binding.preview.getSurfaceProvider());
+ cameraProvider.bindToLifecycle(lifecycleOwner, DEFAULT_BACK_CAMERA, previewUseCase);
+ } catch (ExecutionException | InterruptedException | IllegalArgumentException e) {
+ DeckLog.logError(e);
+ }
+ }, ContextCompat.getMainExecutor(itemView.getContext()));
+ }
+
+
+ public void unbind() {
+ if (cameraProvider != null) {
+ cameraProvider.unbindAll();
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialog.java
new file mode 100644
index 000000000..8ebdf1b50
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialog.java
@@ -0,0 +1,102 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.previewdialog;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.graphics.Color;
+import android.os.Bundle;
+import android.text.TextUtils;
+import android.view.LayoutInflater;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.ViewModelProvider;
+import androidx.swiperefreshlayout.widget.CircularProgressDrawable;
+
+import com.bumptech.glide.RequestBuilder;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.DialogPreviewBinding;
+import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
+import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
+
+public class PreviewDialog extends BrandedDialogFragment {
+
+ private DialogPreviewBinding binding;
+ private PreviewDialogViewModel viewModel;
+ private LiveData<RequestBuilder<?>> imageBuilder$;
+ private LiveData<String> title$;
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ viewModel = new ViewModelProvider(requireActivity()).get(PreviewDialogViewModel.class);
+ binding = DialogPreviewBinding.inflate(LayoutInflater.from(requireContext()));
+
+ final Context context = requireContext();
+
+ this.imageBuilder$ = this.viewModel.getImageBuilder();
+ this.imageBuilder$.observe(requireActivity(), builder -> {
+ if (builder == null) {
+ binding.avatar.setVisibility(GONE);
+ } else {
+ final CircularProgressDrawable circularProgressDrawable = new CircularProgressDrawable(context);
+ circularProgressDrawable.setStrokeWidth(5f);
+ circularProgressDrawable.setCenterRadius(30f);
+ circularProgressDrawable.setColorSchemeColors(isDarkTheme(context) ? Color.LTGRAY : Color.DKGRAY);
+ circularProgressDrawable.start();
+ binding.avatar.setVisibility(VISIBLE);
+ binding.avatar.post(() -> builder
+ .placeholder(circularProgressDrawable)
+ .into(binding.avatar));
+ }
+ });
+ this.title$ = this.viewModel.getTitle();
+ this.title$.observe(requireActivity(), title -> {
+ if (TextUtils.isEmpty(title)) {
+ binding.title.setVisibility(GONE);
+ } else {
+ binding.title.setVisibility(VISIBLE);
+ binding.title.setText(title);
+ }
+ });
+
+ return new BrandedAlertDialogBuilder(requireContext())
+ .setPositiveButton(R.string.simple_attach, (d, w) -> {
+ viewModel.setResult(true);
+ dismiss();
+ })
+ .setNeutralButton(R.string.simple_close, (d, w) -> {
+ viewModel.setResult(false);
+ dismiss();
+ })
+ .setView(binding.getRoot())
+ .create();
+ }
+
+ @Override
+ public void onCancel(@NonNull DialogInterface dialog) {
+ viewModel.setResult(false);
+ super.onCancel(dialog);
+ }
+
+ @Override
+ public void applyBrand(int mainColor) {
+ }
+
+ @Override
+ public void onDestroy() {
+ this.imageBuilder$.removeObservers(requireActivity());
+ this.title$.removeObservers(requireActivity());
+ super.onDestroy();
+ }
+
+ public static DialogFragment newInstance() {
+ return new PreviewDialog();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialogViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialogViewModel.java
new file mode 100644
index 000000000..8ee8a0e08
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/attachments/previewdialog/PreviewDialogViewModel.java
@@ -0,0 +1,50 @@
+package it.niedermann.nextcloud.deck.ui.card.attachments.previewdialog;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.ViewModel;
+
+import com.bumptech.glide.RequestBuilder;
+
+import static androidx.lifecycle.Transformations.distinctUntilChanged;
+
+public class PreviewDialogViewModel extends ViewModel {
+
+ @NonNull
+ private final MutableLiveData<String> title$ = new MutableLiveData<>();
+ @NonNull
+ private final MutableLiveData<RequestBuilder<?>> imageBuilder$ = new MutableLiveData<>();
+ private MutableLiveData<Boolean> result$ = new MutableLiveData<>();
+
+ /**
+ * Call this before observing {@link #getResult()} to prepare the {@link PreviewDialog}.
+ */
+ public void prepareDialog(@Nullable String title, @Nullable RequestBuilder<?> imageBuilder) {
+ this.result$ = new MutableLiveData<>();
+ this.title$.setValue(title);
+ this.imageBuilder$.setValue(imageBuilder);
+ }
+
+ /**
+ * This will be a new instance after each call of {@link #prepareDialog(String, RequestBuilder)}.
+ *
+ * @return {@link Boolean#TRUE} if a positive action has been submitted, {@link Boolean#FALSE} if the dialog has been canceled.
+ */
+ public LiveData<Boolean> getResult() {
+ return this.result$;
+ }
+
+ protected LiveData<String> getTitle() {
+ return distinctUntilChanged(this.title$);
+ }
+
+ protected LiveData<RequestBuilder<?>> getImageBuilder() {
+ return distinctUntilChanged(this.imageBuilder$);
+ }
+
+ protected void setResult(boolean submittedPositive) {
+ result$.setValue(submittedPositive);
+ }
+}
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 e261c37a2..3fd536aa6 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
@@ -15,7 +15,7 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
-import java.util.Date;
+import java.time.Instant;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
@@ -23,12 +23,15 @@ import it.niedermann.nextcloud.deck.databinding.FragmentCardEditTabCommentsBindi
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 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;
@@ -38,7 +41,6 @@ public class CardCommentsFragment extends BrandedFragment implements CommentEdit
private FragmentCardEditTabCommentsBinding binding;
private EditCardViewModel mainViewModel;
private CommentsViewModel commentsViewModel;
- private SyncManager syncManager;
private CardCommentsAdapter adapter;
public static Fragment newInstance() {
@@ -68,7 +70,6 @@ public class CardCommentsFragment extends BrandedFragment implements CommentEdit
commentsViewModel = new ViewModelProvider(this).get(CommentsViewModel.class);
- syncManager = new SyncManager(requireActivity());
adapter = new CardCommentsAdapter(requireContext(), mainViewModel.getAccount(), requireActivity().getMenuInflater(), this, this, getChildFragmentManager());
binding.comments.setAdapter(adapter);
@@ -82,7 +83,7 @@ public class CardCommentsFragment extends BrandedFragment implements CommentEdit
setupMentions(mainViewModel.getAccount(), comment.getComment().getMentions(), binding.replyCommentText);
}
});
- syncManager.getFullCommentsForLocalCardId(mainViewModel.getFullCard().getLocalId()).observe(getViewLifecycleOwner(),
+ commentsViewModel.getFullCommentsForLocalCardId(mainViewModel.getFullCard().getLocalId()).observe(getViewLifecycleOwner(),
(comments) -> {
if (comments != null && comments.size() > 0) {
binding.emptyContentView.setVisibility(GONE);
@@ -100,13 +101,13 @@ public class CardCommentsFragment extends BrandedFragment implements CommentEdit
if (!TextUtils.isEmpty(binding.message.getText().toString().trim())) {
binding.emptyContentView.setVisibility(GONE);
binding.comments.setVisibility(VISIBLE);
- final DeckComment comment = new DeckComment(binding.message.getText().toString().trim(), mainViewModel.getAccount().getUserName(), new Date());
+ final DeckComment comment = new DeckComment(binding.message.getText().toString().trim(), mainViewModel.getAccount().getUserName(), Instant.now());
final FullDeckComment parent = commentsViewModel.getReplyToComment().getValue();
if (parent != null) {
comment.setParentId(parent.getId());
commentsViewModel.setReplyToComment(null);
}
- syncManager.addCommentToCard(mainViewModel.getAccount().getId(), mainViewModel.getFullCard().getLocalId(), comment);
+ commentsViewModel.addCommentToCard(mainViewModel.getAccount().getId(), mainViewModel.getFullCard().getLocalId(), comment);
}
binding.message.setText(null);
});
@@ -116,6 +117,7 @@ public class CardCommentsFragment extends BrandedFragment implements CommentEdit
}
return true;
});
+ binding.message.addTextChangedListener(new CardCommentsMentionProposer(getViewLifecycleOwner(), mainViewModel.getAccount(), mainViewModel.getBoardId(), binding.message, binding.mentionProposerWrapper, binding.mentionProposer));
} else {
binding.addCommentLayout.setVisibility(GONE);
}
@@ -133,12 +135,17 @@ public class CardCommentsFragment extends BrandedFragment implements CommentEdit
@Override
public void onCommentEdited(Long id, String comment) {
- syncManager.updateComment(mainViewModel.getAccount().getId(), mainViewModel.getFullCard().getLocalId(), id, comment);
+ commentsViewModel.updateComment(mainViewModel.getAccount().getId(), mainViewModel.getFullCard().getLocalId(), id, comment);
}
@Override
public void onCommentDeleted(Long localId) {
- syncManager.deleteComment(mainViewModel.getAccount().getId(), mainViewModel.getFullCard().getLocalId(), 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());
+ }
+ });
}
@Override
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsMentionProposer.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsMentionProposer.java
new file mode 100644
index 000000000..7ca7a6384
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/CardCommentsMentionProposer.java
@@ -0,0 +1,139 @@
+package it.niedermann.nextcloud.deck.ui.card.comments;
+
+import android.annotation.SuppressLint;
+import android.net.Uri;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.View;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.LinearLayout;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Pair;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+
+import java.util.ArrayList;
+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.User;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.ui.card.comments.util.CommentsUtil;
+
+import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
+
+public class CardCommentsMentionProposer implements TextWatcher {
+
+ private final int avatarSize;
+ @NonNull
+ private final SyncManager syncManager;
+ @NonNull
+ private final LinearLayout.LayoutParams layoutParams;
+ @NonNull
+ private final LifecycleOwner owner;
+ @NonNull
+ private final Account account;
+ private final long boardLocalId;
+ @NonNull
+ private final EditText editText;
+ @NonNull
+ private final LinearLayout mentionProposer;
+ @NonNull
+ private final LinearLayout mentionProposerWrapper;
+
+ @NonNull
+ private final List<User> users = new ArrayList<>();
+
+ public CardCommentsMentionProposer(@NonNull LifecycleOwner owner, @NonNull Account account, long boardLocalId, @NonNull EditText editText, LinearLayout mentionProposerWrapper, @NonNull LinearLayout avatarProposer) {
+ this.owner = owner;
+ this.account = account;
+ this.boardLocalId = boardLocalId;
+ this.editText = editText;
+ this.mentionProposerWrapper = mentionProposerWrapper;
+ this.mentionProposer = avatarProposer;
+ syncManager = new SyncManager(editText.getContext());
+ avatarSize = DimensionUtil.INSTANCE.dpToPx(mentionProposer.getContext(), R.dimen.avatar_size_small);
+ layoutParams = new LinearLayout.LayoutParams(avatarSize, avatarSize);
+ layoutParams.setMarginEnd(DimensionUtil.INSTANCE.dpToPx(mentionProposer.getContext(), R.dimen.spacer_1x));
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ final int selectionStart = editText.getSelectionStart();
+ final int selectionEnd = editText.getSelectionEnd();
+ final Pair<String, Integer> mentionProposal = CommentsUtil.getUserNameForMentionProposal(s.toString(), selectionStart);
+ if (mentionProposal == null || (mentionProposal.first != null && mentionProposal.first.length() == 0) || selectionStart != selectionEnd) {
+ mentionProposer.removeAllViews();
+ mentionProposerWrapper.setVisibility(View.GONE);
+ this.users.clear();
+ } else {
+ if (mentionProposal.first != null && mentionProposal.second != null) {
+ observeOnce(syncManager.searchUserByUidOrDisplayName(account.getId(), boardLocalId, -1L, mentionProposal.first), owner, (users) -> {
+ if (!users.equals(this.users)) {
+ mentionProposer.removeAllViews();
+ if (users.size() > 0) {
+ mentionProposerWrapper.setVisibility(View.VISIBLE);
+ for (User user : users) {
+ final ImageView avatar = new ImageView(mentionProposer.getContext());
+ avatar.setLayoutParams(layoutParams);
+ updateListenerOfView(avatar, s, mentionProposal, user);
+
+ mentionProposer.addView(avatar);
+
+ Glide.with(avatar.getContext())
+ .load(account.getUrl() + "/index.php/avatar/" + Uri.encode(user.getUid()) + "/" + avatarSize)
+ .placeholder(R.drawable.ic_person_grey600_24dp)
+ .error(R.drawable.ic_person_grey600_24dp)
+ .apply(RequestOptions.circleCropTransform())
+ .into(avatar);
+ }
+ } else {
+ mentionProposerWrapper.setVisibility(View.GONE);
+ }
+ this.users.clear();
+ this.users.addAll(users);
+ } else {
+ int i = 0;
+ for (User user : users) {
+ updateListenerOfView(mentionProposer.getChildAt(i), s, mentionProposal, user);
+ i++;
+ }
+ }
+ });
+ } else {
+ this.users.clear();
+ mentionProposer.removeAllViews();
+ mentionProposerWrapper.setVisibility(View.GONE);
+ }
+ }
+ }
+
+ @SuppressLint("SetTextI18n")
+ private void updateListenerOfView(View avatar, CharSequence s, Pair<String, Integer> mentionProposal, User user) {
+ avatar.setOnClickListener((c) -> {
+ editText.setText(
+ s.subSequence(0, mentionProposal.second) +
+ user.getUid() +
+ s.subSequence(mentionProposal.second + mentionProposal.first.length(), s.length())
+ );
+ editText.setSelection(mentionProposal.second + user.getUid().length());
+ mentionProposerWrapper.setVisibility(View.GONE);
+ });
+ }
+
+ @Override
+ public void afterTextChanged(Editable s) {
+
+ }
+}
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 f7fd247a9..dada94d5b 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
@@ -1,15 +1,30 @@
package it.niedermann.nextcloud.deck.ui.card.comments;
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.ViewModel;
+import java.util.List;
+
+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;
@SuppressWarnings("WeakerAccess")
-public class CommentsViewModel extends ViewModel {
+public class CommentsViewModel extends AndroidViewModel {
- private MutableLiveData<FullDeckComment> replyToComment = new MutableLiveData<>();
+ private final SyncManager syncManager;
+
+ private final MutableLiveData<FullDeckComment> replyToComment = new MutableLiveData<>();
+
+ public CommentsViewModel(@NonNull Application application) {
+ super(application);
+ this.syncManager = new SyncManager(application);
+ }
public void setReplyToComment(FullDeckComment replyToComment) {
this.replyToComment.postValue(replyToComment);
@@ -18,4 +33,20 @@ public class CommentsViewModel extends ViewModel {
public LiveData<FullDeckComment> getReplyToComment() {
return this.replyToComment;
}
+
+ public LiveData<List<FullDeckComment>> getFullCommentsForLocalCardId(long localCardId) {
+ return syncManager.getFullCommentsForLocalCardId(localCardId);
+ }
+
+ public void addCommentToCard(long accountId, long cardId, @NonNull DeckComment comment) {
+ syncManager.addCommentToCard(accountId, cardId, comment);
+ }
+
+ public void updateComment(long accountId, long localCardId, long localCommentId, String comment) {
+ syncManager.updateComment(accountId, localCardId, localCommentId, comment);
+ }
+
+ public WrappedLiveData<Void> deleteComment(long accountId, long localCardId, long localCommentId) {
+ return syncManager.deleteComment(accountId, localCardId, localCommentId);
+ }
}
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 086d799af..3e540c95e 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
@@ -11,22 +11,25 @@ import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.FragmentManager;
import androidx.recyclerview.widget.RecyclerView;
-import java.text.DateFormat;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+import it.niedermann.android.util.ClipboardUtil;
+import it.niedermann.android.util.DimensionUtil;
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.full.FullDeckComment;
import it.niedermann.nextcloud.deck.util.DateUtil;
-import it.niedermann.nextcloud.deck.util.DimensionUtil;
import it.niedermann.nextcloud.deck.util.ViewUtil;
-import static it.niedermann.nextcloud.deck.util.ClipboardUtil.copyToClipboard;
import static it.niedermann.nextcloud.deck.util.ViewUtil.setupMentions;
public class ItemCommentViewHolder extends RecyclerView.ViewHolder {
- private ItemCommentBinding binding;
+ private final ItemCommentBinding binding;
+ private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
@SuppressWarnings("WeakerAccess")
public ItemCommentViewHolder(ItemCommentBinding binding) {
@@ -35,15 +38,15 @@ public class ItemCommentViewHolder extends RecyclerView.ViewHolder {
}
public void bind(@NonNull FullDeckComment comment, @NonNull Account account, @ColorInt int mainColor, @NonNull MenuInflater inflater, @NonNull CommentDeletedListener deletedListener, @NonNull CommentSelectAsReplyListener selectAsReplyListener, @NonNull FragmentManager fragmentManager) {
- ViewUtil.addAvatar(binding.avatar, account.getUrl(), comment.getComment().getActorId(), DimensionUtil.dpToPx(binding.avatar.getContext(), R.dimen.icon_size_details), R.drawable.ic_person_grey600_24dp);
+ 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);
binding.message.setText(comment.getComment().getMessage());
binding.actorDisplayName.setText(comment.getComment().getActorDisplayName());
- binding.creationDateTime.setText(DateUtil.getRelativeDateTimeString(binding.creationDateTime.getContext(), comment.getComment().getCreationDateTime().getTime()));
+ binding.creationDateTime.setText(DateUtil.getRelativeDateTimeString(binding.creationDateTime.getContext(), comment.getComment().getCreationDateTime().toEpochMilli()));
itemView.setOnClickListener(View::showContextMenu);
itemView.setOnCreateContextMenuListener((menu, v, menuInfo) -> {
inflater.inflate(R.menu.comment_menu, menu);
- menu.findItem(android.R.id.copy).setOnMenuItemClickListener(item -> copyToClipboard(itemView.getContext(), comment.getComment().getMessage()));
+ menu.findItem(android.R.id.copy).setOnMenuItemClickListener(item -> ClipboardUtil.INSTANCE.copyToClipboard(itemView.getContext(), comment.getComment().getMessage()));
final MenuItem replyMenuItem = menu.findItem(R.id.reply);
if (comment.getStatusEnum() != DBStatus.LOCAL_EDITED && account.getServerDeckVersionAsObject().supportsCommentsReplys()) {
replyMenuItem.setOnMenuItemClickListener(item -> {
@@ -72,7 +75,7 @@ public class ItemCommentViewHolder extends RecyclerView.ViewHolder {
DrawableCompat.setTint(binding.notSyncedYet.getDrawable(), mainColor);
binding.notSyncedYet.setVisibility(DBStatus.LOCAL_EDITED.equals(comment.getStatusEnum()) ? View.VISIBLE : View.GONE);
- TooltipCompat.setTooltipText(binding.creationDateTime, DateFormat.getDateTimeInstance().format(comment.getComment().getCreationDateTime()));
+ TooltipCompat.setTooltipText(binding.creationDateTime, comment.getComment().getCreationDateTime().atZone(ZoneId.systemDefault()).format(dateFormatter));
setupMentions(account, comment.getComment().getMentions(), binding.message);
if (comment.getParent() == null) {
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
new file mode 100644
index 000000000..5251291c8
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/comments/util/CommentsUtil.java
@@ -0,0 +1,48 @@
+package it.niedermann.nextcloud.deck.ui.card.comments.util;
+
+
+import androidx.core.util.Pair;
+
+public class CommentsUtil {
+
+ public static Pair<String, Integer> getUserNameForMentionProposal(String text, int cursorPosition) {
+ Pair result = null;
+
+ if (text != null) {
+ // find start of relevant substring
+ int cursor = cursorPosition;
+ if (cursor < 1) {
+ return null;
+ }
+ int start = 0;
+ while (cursor > 0) {
+ cursor--;
+ if (Character.isWhitespace(text.charAt(cursor))) {
+ start = cursor + 1;
+ break;
+ }
+ }
+ if (text.length()-1 < start || text.charAt(start) != '@') {
+ return null;
+ }
+
+ // find end of relevant substring
+ cursor = cursorPosition;
+ int textLength = text.length();
+ int end = textLength;
+ while (cursor < textLength) {
+ if (Character.isWhitespace(text.charAt(cursor))) {
+ end = cursor;
+ break;
+ }
+ cursor++;
+ }
+
+ start++;
+ result = Pair.create(text.substring(start, end), start);
+
+ }
+
+ return result;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeAdapter.java
new file mode 100644
index 000000000..aa8c3e8f6
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeAdapter.java
@@ -0,0 +1,80 @@
+package it.niedermann.nextcloud.deck.ui.card.details;
+
+import android.content.Context;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.core.util.Consumer;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.databinding.ItemAssigneeBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.User;
+
+import static androidx.recyclerview.widget.RecyclerView.NO_ID;
+
+@SuppressWarnings("WeakerAccess")
+public class AssigneeAdapter extends RecyclerView.Adapter<AssigneeViewHolder> {
+
+ private final Account account;
+ @NonNull
+ private List<User> users = new ArrayList<>();
+ @NonNull
+ private final Consumer<User> userClickedListener;
+
+ AssigneeAdapter(
+ @NonNull Consumer<User> userClickedListener,
+ @NonNull Account account
+ ) {
+ super();
+ this.userClickedListener = userClickedListener;
+ this.account = account;
+ setHasStableIds(true);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ Long id = users.get(position).getLocalId();
+ return id == null ? NO_ID : id;
+ }
+
+ @NonNull
+ @Override
+ public AssigneeViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ final Context context = parent.getContext();
+ return new AssigneeViewHolder(ItemAssigneeBinding.inflate(LayoutInflater.from(context)));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull AssigneeViewHolder holder, int position) {
+ final User user = users.get(position);
+ holder.bind(account, user, userClickedListener);
+ }
+
+ @Override
+ public int getItemCount() {
+ return users.size();
+ }
+
+ public void setUsers(@NonNull List<User> users) {
+ this.users.clear();
+ this.users.addAll(users);
+ notifyDataSetChanged();
+ }
+
+ public void addUser(@NonNull User user) {
+ this.users.add(user);
+ notifyItemInserted(this.users.size());
+ }
+
+ public void removeUser(@NonNull User user) {
+ final int index = this.users.indexOf(user);
+ this.users.remove(user);
+ notifyItemRemoved(index);
+ }
+
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeDecoration.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeDecoration.java
new file mode 100644
index 000000000..096dcfa53
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeDecoration.java
@@ -0,0 +1,28 @@
+package it.niedermann.nextcloud.deck.ui.card.details;
+
+import android.graphics.Rect;
+import android.view.View;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Px;
+import androidx.recyclerview.widget.RecyclerView;
+
+public class AssigneeDecoration extends RecyclerView.ItemDecoration {
+
+ private final int gutter;
+
+ public AssigneeDecoration(@Px int gutter) {
+ this.gutter = gutter;
+ }
+
+ @Override
+ public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
+ final int position = parent.getChildAdapterPosition(view);
+
+ if (position >= 0) {
+ // All columns get some spacing at the bottom and at the right side
+ outRect.right = gutter;
+ outRect.bottom = gutter;
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeViewHolder.java
new file mode 100644
index 000000000..ddb1236b6
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/AssigneeViewHolder.java
@@ -0,0 +1,29 @@
+package it.niedermann.nextcloud.deck.ui.card.details;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.core.util.Consumer;
+import androidx.recyclerview.widget.RecyclerView;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemAssigneeBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.User;
+import it.niedermann.nextcloud.deck.util.ViewUtil;
+
+public class AssigneeViewHolder extends RecyclerView.ViewHolder {
+ private ItemAssigneeBinding binding;
+
+ @SuppressWarnings("WeakerAccess")
+ public AssigneeViewHolder(ItemAssigneeBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull Account account, @NonNull User user, @Nullable Consumer<User> onClickListener) {
+ ViewUtil.addAvatar(binding.avatar, account.getUrl(), user.getUid(), R.drawable.ic_person_grey600_24dp);
+ if(onClickListener != null) {
+ itemView.setOnClickListener((v) -> onClickListener.accept(user));
+ }
+ }
+} \ No newline at end of file
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 3182fffa2..2c697de08 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
@@ -2,25 +2,25 @@ package it.niedermann.nextcloud.deck.ui.card.details;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.text.Editable;
+import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ImageView;
import android.widget.LinearLayout;
import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.appcompat.app.AppCompatActivity;
+import androidx.core.content.ContextCompat;
import androidx.core.graphics.ColorUtils;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
+import androidx.recyclerview.widget.GridLayoutManager;
import com.google.android.material.chip.Chip;
import com.google.android.material.snackbar.Snackbar;
@@ -31,18 +31,21 @@ import com.wdullaer.materialdatetimepicker.time.TimePickerDialog.OnTimeSetListen
import com.yydcdut.markdown.MarkdownProcessor;
import com.yydcdut.markdown.syntax.edit.EditFactory;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.Calendar;
-import java.util.Date;
-import java.util.Locale;
+import java.time.Instant;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
+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.databinding.FragmentCardEditTabDetailsBinding;
import it.niedermann.nextcloud.deck.model.Label;
import it.niedermann.nextcloud.deck.model.User;
-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.BrandedDatePickerDialog;
import it.niedermann.nextcloud.deck.ui.branding.BrandedFragment;
@@ -51,26 +54,23 @@ import it.niedermann.nextcloud.deck.ui.branding.BrandedTimePickerDialog;
import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
import it.niedermann.nextcloud.deck.ui.card.LabelAutoCompleteAdapter;
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.ColorUtil;
import it.niedermann.nextcloud.deck.util.MarkDownUtil;
-import it.niedermann.nextcloud.deck.util.ViewUtil;
-import static android.text.format.DateFormat.getDateFormat;
+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.util.DimensionUtil.dpToPx;
-public class CardDetailsFragment extends BrandedFragment implements OnDateSetListener, OnTimeSetListener {
+public class CardDetailsFragment extends BrandedFragment implements OnDateSetListener, OnTimeSetListener, CardAssigneeListener {
private FragmentCardEditTabDetailsBinding binding;
private EditCardViewModel viewModel;
- private SyncManager syncManager;
- private DateFormat dateFormat;
- private DateFormat dueTime = new SimpleDateFormat("HH:mm", Locale.ROOT);
- @Px
- private int avatarSize;
- private LinearLayout.LayoutParams avatarLayoutParams;
+ private AssigneeAdapter adapter;
+ private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM);
+ private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT);
private AppCompatActivity activity;
@Override
@@ -92,8 +92,6 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
ViewGroup container,
Bundle savedInstanceState) {
binding = FragmentCardEditTabDetailsBinding.inflate(inflater, container, false);
- dateFormat = getDateFormat(activity);
-
viewModel = new ViewModelProvider(activity).get(EditCardViewModel.class);
// This might be a zombie fragment with an empty EditCardViewModel after Android killed the activity (but not the fragment instance
@@ -103,22 +101,20 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
return binding.getRoot();
}
- syncManager = new SyncManager(requireContext());
-
- avatarSize = dpToPx(requireContext(), R.dimen.avatar_size);
- avatarLayoutParams = new LinearLayout.LayoutParams(avatarSize, avatarSize);
- avatarLayoutParams.setMargins(0, 0, dpToPx(requireContext(), R.dimen.spacer_1x), 0);
+ @Px final int avatarSize = DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.avatar_size);
+ final LinearLayout.LayoutParams avatarLayoutParams = new LinearLayout.LayoutParams(avatarSize, avatarSize);
+ avatarLayoutParams.setMargins(0, 0, DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.spacer_1x), 0);
- setupPeople();
+ setupAssignees();
setupLabels();
setupDueDate();
setupDescription();
+ setupProjects();
binding.description.setText(viewModel.getFullCard().getCard().getDescription());
return binding.getRoot();
}
-
@Override
public void onResume() {
super.onResume();
@@ -173,49 +169,14 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
}
}
- private TimePickerDialog createTimePickerDialogFromDate(
- @Nullable OnTimeSetListener listener,
- @Nullable Date date
- ) {
- int hourOfDay = 0;
- int minutes = 0;
-
- if (date != null) {
- hourOfDay = date.getHours();
- minutes = date.getMinutes();
- }
- return BrandedTimePickerDialog.newInstance(listener, hourOfDay, minutes, true);
- }
-
- private DatePickerDialog createDatePickerDialogFromDate(
- @Nullable OnDateSetListener listener,
- @Nullable Date date
- ) {
- int year;
- int month;
- int day;
-
- Calendar cal = Calendar.getInstance();
- if (date != null) {
- cal.setTime(date);
- year = cal.get(Calendar.YEAR);
- month = cal.get(Calendar.MONTH);
- day = cal.get(Calendar.DAY_OF_MONTH);
- } else {
- year = cal.get(Calendar.YEAR);
- month = cal.get(Calendar.MONTH);
- day = cal.get(Calendar.DAY_OF_MONTH);
- }
- return BrandedDatePickerDialog.newInstance(listener, year, month, day);
- }
-
private void setupDueDate() {
if (this.viewModel.getFullCard().getCard().getDueDate() != null) {
- binding.dueDateDate.setText(dateFormat.format(this.viewModel.getFullCard().getCard().getDueDate()));
- binding.dueDateTime.setText(dueTime.format(this.viewModel.getFullCard().getCard().getDueDate()));
- binding.clearDueDate.setVisibility(View.VISIBLE);
+ final ZonedDateTime dueDate = this.viewModel.getFullCard().getCard().getDueDate().atZone(ZoneId.systemDefault());
+ binding.dueDateDate.setText(dueDate == null ? null : dueDate.format(dateFormatter));
+ binding.dueDateTime.setText(dueDate == null ? null : dueDate.format(timeFormatter));
+ binding.clearDueDate.setVisibility(VISIBLE);
} else {
- binding.clearDueDate.setVisibility(View.GONE);
+ binding.clearDueDate.setVisibility(GONE);
binding.dueDateDate.setText(null);
binding.dueDateTime.setText(null);
}
@@ -223,31 +184,37 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
if (viewModel.canEdit()) {
binding.dueDateDate.setOnClickListener(v -> {
- if (viewModel.getFullCard() != null && viewModel.getFullCard().getCard() != null) {
- createDatePickerDialogFromDate(this, viewModel.getFullCard().getCard().getDueDate()).show(getChildFragmentManager(), BrandedDatePickerDialog.class.getCanonicalName());
+ final LocalDate date;
+ if (viewModel.getFullCard() != null && viewModel.getFullCard().getCard() != null && viewModel.getFullCard().getCard().getDueDate() != null) {
+ date = viewModel.getFullCard().getCard().getDueDate().atZone(ZoneId.systemDefault()).toLocalDate();
} else {
- createDatePickerDialogFromDate(this, null).show(getChildFragmentManager(), BrandedDatePickerDialog.class.getCanonicalName());
+ date = LocalDate.now();
}
+ BrandedDatePickerDialog.newInstance(this, date.getYear(), date.getMonthValue(), date.getDayOfMonth())
+ .show(getChildFragmentManager(), BrandedDatePickerDialog.class.getCanonicalName());
});
binding.dueDateTime.setOnClickListener(v -> {
- if (viewModel.getFullCard() != null && viewModel.getFullCard().getCard() != null) {
- createTimePickerDialogFromDate(this, viewModel.getFullCard().getCard().getDueDate()).show(getChildFragmentManager(), BrandedTimePickerDialog.class.getCanonicalName());
+ final LocalTime time;
+ if (viewModel.getFullCard() != null && viewModel.getFullCard().getCard() != null && viewModel.getFullCard().getCard().getDueDate() != null) {
+ time = viewModel.getFullCard().getCard().getDueDate().atZone(ZoneId.systemDefault()).toLocalTime();
} else {
- createTimePickerDialogFromDate(this, null).show(getChildFragmentManager(), BrandedTimePickerDialog.class.getCanonicalName());
+ time = LocalTime.now();
}
+ BrandedTimePickerDialog.newInstance(this, time.getHour(), time.getMinute(), true)
+ .show(getChildFragmentManager(), BrandedTimePickerDialog.class.getCanonicalName());
});
binding.clearDueDate.setOnClickListener(v -> {
binding.dueDateDate.setText(null);
binding.dueDateTime.setText(null);
viewModel.getFullCard().getCard().setDueDate(null);
- binding.clearDueDate.setVisibility(View.GONE);
+ binding.clearDueDate.setVisibility(GONE);
});
} else {
binding.dueDateDate.setEnabled(false);
binding.dueDateTime.setEnabled(false);
- binding.clearDueDate.setVisibility(View.GONE);
+ binding.clearDueDate.setVisibility(GONE);
}
}
@@ -266,7 +233,7 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
newLabel.setBoardId(boardId);
newLabel.setTitle(((LabelAutoCompleteAdapter) binding.labels.getAdapter()).getLastFilterText());
newLabel.setLocalId(null);
- WrappedLiveData<Label> createLabelLiveData = syncManager.createLabel(accountId, newLabel, boardId);
+ WrappedLiveData<Label> createLabelLiveData = viewModel.createLabel(accountId, newLabel, boardId);
observeOnce(createLabelLiveData, CardDetailsFragment.this, createdLabel -> {
if (createLabelLiveData.hasError()) {
DeckLog.logError(createLabelLiveData.getError());
@@ -277,14 +244,14 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
((LabelAutoCompleteAdapter) binding.labels.getAdapter()).exclude(createdLabel);
viewModel.getFullCard().getLabels().add(createdLabel);
binding.labelsGroup.addView(createChipFromLabel(newLabel));
- binding.labelsGroup.setVisibility(View.VISIBLE);
+ binding.labelsGroup.setVisibility(VISIBLE);
}
});
} else {
((LabelAutoCompleteAdapter) binding.labels.getAdapter()).exclude(label);
viewModel.getFullCard().getLabels().add(label);
binding.labelsGroup.addView(createChipFromLabel(label));
- binding.labelsGroup.setVisibility(View.VISIBLE);
+ binding.labelsGroup.setVisibility(VISIBLE);
}
binding.labels.setText("");
@@ -296,18 +263,17 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
for (Label label : viewModel.getFullCard().getLabels()) {
binding.labelsGroup.addView(createChipFromLabel(label));
}
- binding.labelsGroup.setVisibility(View.VISIBLE);
+ binding.labelsGroup.setVisibility(VISIBLE);
} else {
binding.labelsGroup.setVisibility(View.INVISIBLE);
}
}
-
private Chip createChipFromLabel(Label label) {
final Chip chip = new Chip(activity);
chip.setText(label.getTitle());
if (viewModel.canEdit()) {
- chip.setCloseIcon(getResources().getDrawable(R.drawable.ic_close_circle_grey600));
+ chip.setCloseIcon(ContextCompat.getDrawable(requireContext(), R.drawable.ic_close_circle_grey600));
chip.setCloseIconVisible(true);
chip.setOnCloseIconClickListener(v -> {
binding.labelsGroup.removeView(chip);
@@ -316,9 +282,9 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
});
}
try {
- final int labelColor = Color.parseColor("#" + label.getColor());
+ final int labelColor = label.getColor();
chip.setChipBackgroundColor(ColorStateList.valueOf(labelColor));
- final int color = ColorUtil.getForegroundColorForBackgroundColor(labelColor);
+ final int color = ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(labelColor);
chip.setTextColor(color);
if (chip.getCloseIcon() != null) {
@@ -331,7 +297,15 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
return chip;
}
- private void setupPeople() {
+ private void setupAssignees() {
+ adapter = new AssigneeAdapter((user) -> CardAssigneeDialog.newInstance(user).show(getChildFragmentManager(), CardAssigneeDialog.class.getSimpleName()), viewModel.getAccount());
+ binding.assignees.setAdapter(adapter);
+ binding.assignees.post(() -> {
+ @Px final int gutter = DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.spacer_1x);
+ final int spanCount = (int) (float) binding.assignees.getWidth() / (DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.avatar_size) + gutter);
+ binding.assignees.setLayoutManager(new GridLayoutManager(getContext(), spanCount));
+ binding.assignees.addItemDecoration(new AssigneeDecoration(gutter));
+ });
if (viewModel.canEdit()) {
Long localCardId = viewModel.getFullCard().getCard().getLocalId();
localCardId = localCardId == null ? -1 : localCardId;
@@ -340,81 +314,90 @@ public class CardDetailsFragment extends BrandedFragment implements OnDateSetLis
User user = (User) adapterView.getItemAtPosition(position);
viewModel.getFullCard().getAssignedUsers().add(user);
((UserAutoCompleteAdapter) binding.people.getAdapter()).exclude(user);
- addAvatar(viewModel.getAccount().getUrl(), user);
+ adapter.addUser(user);
binding.people.setText("");
});
if (this.viewModel.getFullCard().getAssignedUsers() != null) {
- binding.peopleList.removeAllViews();
- for (User user : this.viewModel.getFullCard().getAssignedUsers()) {
- addAvatar(viewModel.getAccount().getUrl(), user);
- }
+ adapter.setUsers(this.viewModel.getFullCard().getAssignedUsers());
}
} else {
binding.people.setEnabled(false);
}
}
- private void addAvatar(String baseUrl, User user) {
- ImageView avatar = new ImageView(activity);
- avatar.setLayoutParams(avatarLayoutParams);
- if (viewModel.canEdit()) {
- avatar.setOnClickListener(v -> {
- viewModel.getFullCard().getAssignedUsers().remove(user);
- binding.peopleList.removeView(avatar);
- ((UserAutoCompleteAdapter) binding.people.getAdapter()).include(user);
- BrandedSnackbar.make(
- requireView(), getString(R.string.unassigned_user, user.getDisplayname()),
- Snackbar.LENGTH_LONG)
- .setAction(R.string.simple_undo, v1 -> {
- viewModel.getFullCard().getAssignedUsers().add(user);
- ((UserAutoCompleteAdapter) binding.people.getAdapter()).exclude(user);
- addAvatar(baseUrl, user);
- }).show();
- });
- }
- binding.peopleList.addView(avatar);
- avatar.requestLayout();
- ViewUtil.addAvatar(avatar, baseUrl, user.getUid(), avatarSize, R.drawable.ic_person_grey600_24dp);
- }
-
@Override
- public void onDateSet(com.wdullaer.materialdatetimepicker.date.DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
- Calendar c = Calendar.getInstance();
+ public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) {
int hourOfDay;
int minute;
- if (binding.dueDateTime.getText() != null && binding.dueDateTime.length() > 0) {
- hourOfDay = this.viewModel.getFullCard().getCard().getDueDate().getHours();
- minute = this.viewModel.getFullCard().getCard().getDueDate().getMinutes();
- } else {
+ final CharSequence selectedTime = binding.dueDateTime.getText();
+ if (TextUtils.isEmpty(selectedTime)) {
hourOfDay = 0;
minute = 0;
+ } else {
+ final LocalTime oldTime = LocalTime.from(this.viewModel.getFullCard().getCard().getDueDate().atZone(ZoneId.systemDefault()));
+ hourOfDay = oldTime.getHour();
+ minute = oldTime.getMinute();
}
- c.set(year, monthOfYear, dayOfMonth, hourOfDay, minute);
- this.viewModel.getFullCard().getCard().setDueDate(c.getTime());
- binding.dueDateDate.setText(dateFormat.format(c.getTime()));
+ final ZonedDateTime newDateTime = ZonedDateTime.of(
+ LocalDate.of(year, monthOfYear + 1, dayOfMonth),
+ LocalTime.of(hourOfDay, minute),
+ ZoneId.systemDefault()
+ );
+ this.viewModel.getFullCard().getCard().setDueDate(newDateTime.toInstant());
+ binding.dueDateDate.setText(newDateTime.format(dateFormatter));
- if (this.viewModel.getFullCard().getCard().getDueDate() == null || this.viewModel.getFullCard().getCard().getDueDate().getTime() == 0) {
- binding.clearDueDate.setVisibility(View.GONE);
+ if (this.viewModel.getFullCard().getCard().getDueDate() == null || this.viewModel.getFullCard().getCard().getDueDate().toEpochMilli() == 0) {
+ binding.clearDueDate.setVisibility(GONE);
} else {
- binding.clearDueDate.setVisibility(View.VISIBLE);
+ binding.clearDueDate.setVisibility(VISIBLE);
}
}
@Override
- public void onTimeSet(com.wdullaer.materialdatetimepicker.time.TimePickerDialog view, int hourOfDay, int minute, int second) {
- if (this.viewModel.getFullCard().getCard().getDueDate() == null) {
- this.viewModel.getFullCard().getCard().setDueDate(new Date());
+ public void onTimeSet(TimePickerDialog view, int hourOfDay, int minute, int second) {
+ final Instant oldInstant = this.viewModel.getFullCard().getCard().getDueDate();
+ final ZonedDateTime oldDateTime = oldInstant == null ? ZonedDateTime.now() : oldInstant.atZone(ZoneId.systemDefault());
+ final ZonedDateTime newDateTime = oldDateTime.with(
+ LocalTime.of(hourOfDay, minute)
+ );
+
+ this.viewModel.getFullCard().getCard().setDueDate(newDateTime.toInstant());
+ binding.dueDateTime.setText(newDateTime.format(timeFormatter));
+ if (this.viewModel.getFullCard().getCard().getDueDate() == null || this.viewModel.getFullCard().getCard().getDueDate().toEpochMilli() == 0) {
+ binding.clearDueDate.setVisibility(GONE);
+ } else {
+ binding.clearDueDate.setVisibility(VISIBLE);
}
- this.viewModel.getFullCard().getCard().getDueDate().setHours(hourOfDay);
- this.viewModel.getFullCard().getCard().getDueDate().setMinutes(minute);
- binding.dueDateTime.setText(dueTime.format(this.viewModel.getFullCard().getCard().getDueDate().getTime()));
- if (this.viewModel.getFullCard().getCard().getDueDate() == null || this.viewModel.getFullCard().getCard().getDueDate().getTime() == 0) {
- binding.clearDueDate.setVisibility(View.GONE);
+ }
+
+ private void setupProjects() {
+ if (viewModel.getFullCard().getProjects().size() > 0) {
+ binding.projectsTitle.setVisibility(VISIBLE);
+ binding.projects.setNestedScrollingEnabled(false);
+ final CardProjectsAdapter adapter = new CardProjectsAdapter(viewModel.getFullCard().getProjects(), getChildFragmentManager());
+ binding.projects.setAdapter(adapter);
+ binding.projects.setVisibility(VISIBLE);
} else {
- binding.clearDueDate.setVisibility(View.VISIBLE);
+ binding.projectsTitle.setVisibility(GONE);
+ binding.projects.setVisibility(GONE);
}
}
+
+ @Override
+ public void onUnassignUser(@NonNull User user) {
+ viewModel.getFullCard().getAssignedUsers().remove(user);
+ adapter.removeUser(user);
+ ((UserAutoCompleteAdapter) binding.people.getAdapter()).include(user);
+ BrandedSnackbar.make(
+ requireView(), getString(R.string.unassigned_user, user.getDisplayname()),
+ Snackbar.LENGTH_LONG)
+ .setAction(R.string.simple_undo, v1 -> {
+ viewModel.getFullCard().getAssignedUsers().add(user);
+ ((UserAutoCompleteAdapter) binding.people.getAdapter()).exclude(user);
+ adapter.addUser(user);
+ }).show();
+ }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsListener.java
deleted file mode 100644
index 2efbab789..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDetailsListener.java
+++ /dev/null
@@ -1,21 +0,0 @@
-package it.niedermann.nextcloud.deck.ui.card.details;
-
-import java.util.Date;
-
-import it.niedermann.nextcloud.deck.model.Label;
-import it.niedermann.nextcloud.deck.model.User;
-
-public interface CardDetailsListener {
-
- void onDescriptionChanged(String toString);
-
- void onDueDateChanged(Date dueDate);
-
- void onUserAdded(User user);
-
- void onUserRemoved(User user);
-
- void onLabelRemoved(Label label);
-
- void onLabelAdded(Label createdLabel);
-} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardProjectsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardProjectsAdapter.java
new file mode 100644
index 000000000..0c2d63d74
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardProjectsAdapter.java
@@ -0,0 +1,52 @@
+package it.niedermann.nextcloud.deck.ui.card.details;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.fragment.app.FragmentManager;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.databinding.ItemProjectBinding;
+import it.niedermann.nextcloud.deck.model.ocs.projects.full.OcsProjectWithResources;
+import it.niedermann.nextcloud.deck.ui.card.projectresources.CardProjectResourcesDialog;
+
+public class CardProjectsAdapter extends RecyclerView.Adapter<CardProjectsViewHolder> {
+
+ @NonNull
+ private final List<OcsProjectWithResources> projects;
+ @NonNull
+ private final FragmentManager fragmentManager;
+
+ public CardProjectsAdapter(@NonNull List<OcsProjectWithResources> projects, @NonNull FragmentManager fragmentManager) {
+ this.projects = new ArrayList<>(projects.size());
+ this.projects.addAll(projects);
+ this.fragmentManager = fragmentManager;
+ setHasStableIds(true);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return projects.get(position).getLocalId();
+ }
+
+ @NonNull
+ @Override
+ public CardProjectsViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new CardProjectsViewHolder(ItemProjectBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull CardProjectsViewHolder holder, int position) {
+ final OcsProjectWithResources project = projects.get(position);
+ holder.bind(project, (v) -> CardProjectResourcesDialog.newInstance(project.getName(), project.getResources()).show(fragmentManager, CardProjectResourcesDialog.class.getSimpleName()));
+ }
+
+ @Override
+ public int getItemCount() {
+ return projects.size();
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardProjectsViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardProjectsViewHolder.java
new file mode 100644
index 000000000..9c5494af7
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardProjectsViewHolder.java
@@ -0,0 +1,32 @@
+package it.niedermann.nextcloud.deck.ui.card.details;
+
+import android.view.View.OnClickListener;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.recyclerview.widget.RecyclerView;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemProjectBinding;
+import it.niedermann.nextcloud.deck.model.ocs.projects.full.OcsProjectWithResources;
+
+public class CardProjectsViewHolder extends RecyclerView.ViewHolder {
+
+ private ItemProjectBinding binding;
+
+ public CardProjectsViewHolder(@NonNull ItemProjectBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull OcsProjectWithResources project, @Nullable OnClickListener onClickListener) {
+ binding.projectName.setText(project.getName());
+ final int resourcesCount = project.getResources().size();
+ binding.resourcesCount.setText(itemView.getContext().getResources().getQuantityString(R.plurals.resources_count, resourcesCount, resourcesCount));
+ if (resourcesCount > 0) {
+ binding.getRoot().setOnClickListener(onClickListener);
+ } else {
+ binding.getRoot().setOnClickListener(null);
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceAdapter.java
new file mode 100644
index 000000000..4c95574c3
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceAdapter.java
@@ -0,0 +1,54 @@
+package it.niedermann.nextcloud.deck.ui.card.projectresources;
+
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.recyclerview.widget.RecyclerView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.databinding.ItemProjectResourceBinding;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
+
+public class CardProjectResourceAdapter extends RecyclerView.Adapter<CardProjectResourceViewHolder> {
+
+ @NonNull
+ private final EditCardViewModel viewModel;
+ @NonNull
+ private final List<OcsProjectResource> resources;
+ @NonNull
+ private final LifecycleOwner owner;
+
+ public CardProjectResourceAdapter(@NonNull EditCardViewModel viewModel, @NonNull List<OcsProjectResource> resources, @NonNull LifecycleOwner owner) {
+ this.viewModel = viewModel;
+ this.resources = new ArrayList<>(resources.size());
+ this.resources.addAll(resources);
+ this.owner = owner;
+ setHasStableIds(true);
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return resources.get(position).getLocalId();
+ }
+
+ @NonNull
+ @Override
+ public CardProjectResourceViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
+ return new CardProjectResourceViewHolder(ItemProjectResourceBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false));
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull CardProjectResourceViewHolder holder, int position) {
+ holder.bind(viewModel, resources.get(position), owner);
+ }
+
+ @Override
+ public int getItemCount() {
+ return this.resources.size();
+ }
+}
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
new file mode 100644
index 000000000..272945e45
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourceViewHolder.java
@@ -0,0 +1,110 @@
+package it.niedermann.nextcloud.deck.ui.card.projectresources;
+
+import android.content.Intent;
+import android.content.res.Resources;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.recyclerview.widget.RecyclerView;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.ItemProjectResourceBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.ui.card.EditActivity;
+import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
+import it.niedermann.nextcloud.deck.util.ProjectUtil;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+import static it.niedermann.nextcloud.deck.util.ProjectUtil.getResourceUri;
+
+public class CardProjectResourceViewHolder extends RecyclerView.ViewHolder {
+ @NonNull
+ private final ItemProjectResourceBinding binding;
+
+ public CardProjectResourceViewHolder(@NonNull ItemProjectResourceBinding binding) {
+ super(binding.getRoot());
+ this.binding = binding;
+ }
+
+ public void bind(@NonNull EditCardViewModel viewModel, @NonNull OcsProjectResource resource, @NonNull LifecycleOwner owner) {
+ final Account account = viewModel.getAccount();
+ final Resources resources = itemView.getResources();
+ binding.name.setText(resource.getName());
+ final @Nullable String link = resource.getLink();
+ binding.type.setVisibility(VISIBLE);
+ if (resource.getType() != null) {
+ switch (resource.getType()) {
+ case "deck": {
+ // TODO https://github.com/stefan-niedermann/nextcloud-deck/issues/671
+ linkifyViewHolder(account, link);
+ binding.type.setText(resources.getString(R.string.project_type_deck_board));
+ binding.image.setImageResource(R.drawable.project_deck_36dp);
+ break;
+ }
+ case "deck-card": {
+ try {
+ long[] ids = ProjectUtil.extractBoardIdAndCardIdFromUrl(link);
+ if (ids.length == 2) {
+ viewModel.getCardByRemoteID(account.getId(), ids[1]).observe(owner, (fullCard) -> {
+ if (fullCard != null) {
+ viewModel.getBoardByRemoteId(account.getId(), ids[0]).observe(owner, (board) -> {
+ if (board != null) {
+ binding.getRoot().setOnClickListener((v) -> itemView.getContext().startActivity(EditActivity.createEditCardIntent(itemView.getContext(), account, board.getLocalId(), fullCard.getLocalId())));
+ } else {
+ linkifyViewHolder(account, link);
+ }
+ });
+ } else {
+ linkifyViewHolder(account, link);
+ }
+ });
+ } else {
+ linkifyViewHolder(account, link);
+ }
+ } catch (IllegalArgumentException e) {
+ DeckLog.logError(e);
+ linkifyViewHolder(account, link);
+ }
+ binding.type.setText(resources.getString(R.string.project_type_deck_card));
+ binding.image.setImageResource(R.drawable.project_deck_36dp);
+ break;
+ }
+ case "file": {
+ binding.type.setText(resources.getString(R.string.project_type_file));
+ linkifyViewHolder(account, link);
+ binding.image.setImageResource(R.drawable.project_file_36dp);
+ break;
+ }
+ case "room": {
+ binding.type.setText(resources.getString(R.string.project_type_room));
+ linkifyViewHolder(account, link);
+ binding.image.setImageResource(R.drawable.project_talk_36dp);
+ break;
+ }
+ default: {
+ 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");
+ binding.type.setVisibility(GONE);
+ }
+ }
+
+ private void linkifyViewHolder(@NonNull Account account, @Nullable String link) {
+ if (link != null) {
+ try {
+ binding.getRoot().setOnClickListener((v) -> itemView.getContext().startActivity(new Intent(Intent.ACTION_VIEW).setData(getResourceUri(account, link))));
+ } catch (IllegalArgumentException e) {
+ DeckLog.logError(e);
+ }
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourcesDialog.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourcesDialog.java
new file mode 100644
index 000000000..46195b309
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/projectresources/CardProjectResourcesDialog.java
@@ -0,0 +1,83 @@
+package it.niedermann.nextcloud.deck.ui.card.projectresources;
+
+import android.app.Dialog;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AlertDialog;
+import androidx.fragment.app.DialogFragment;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.DialogProjectResourcesBinding;
+import it.niedermann.nextcloud.deck.model.ocs.projects.OcsProjectResource;
+import it.niedermann.nextcloud.deck.ui.branding.BrandedAlertDialogBuilder;
+import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
+import it.niedermann.nextcloud.deck.ui.card.EditCardViewModel;
+
+public class CardProjectResourcesDialog extends BrandedDialogFragment {
+
+ private static final String KEY_RESOURCES = "resources";
+ private static final String KEY_PROJECT_NAME = "projectName";
+ private DialogProjectResourcesBinding binding;
+ private EditCardViewModel viewModel;
+
+ private String projectName;
+ @NonNull
+ private List<OcsProjectResource> resources = new ArrayList<>();
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ final Bundle args = requireArguments();
+ if (!args.containsKey(KEY_RESOURCES)) {
+ throw new IllegalArgumentException("Provide at least " + KEY_RESOURCES);
+ }
+ //noinspection unchecked
+ this.resources.addAll((ArrayList<OcsProjectResource>) Objects.requireNonNull(args.getSerializable(KEY_RESOURCES)));
+ this.projectName = args.getString(KEY_PROJECT_NAME);
+ }
+
+ @NonNull
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ binding = DialogProjectResourcesBinding.inflate(LayoutInflater.from(requireContext()));
+ viewModel = new ViewModelProvider(requireActivity()).get(EditCardViewModel.class);
+
+ AlertDialog.Builder dialogBuilder = new BrandedAlertDialogBuilder(requireContext());
+
+ return dialogBuilder
+ .setTitle(projectName)
+ .setView(binding.getRoot())
+ .setNeutralButton(R.string.simple_close, null)
+ .create();
+ }
+
+ @Override
+ public void onActivityCreated(@Nullable Bundle savedInstanceState) {
+ final CardProjectResourceAdapter adapter = new CardProjectResourceAdapter(viewModel, resources, requireActivity());
+ binding.getRoot().setAdapter(adapter);
+ super.onActivityCreated(savedInstanceState);
+ }
+
+ @Override
+ public void applyBrand(int mainColor) {
+
+ }
+
+ public static DialogFragment newInstance(@Nullable String projectName, @NonNull List<OcsProjectResource> resources) {
+ final DialogFragment fragment = new CardProjectResourcesDialog();
+ final Bundle args = new Bundle();
+ args.putString(KEY_PROJECT_NAME, projectName);
+ args.putSerializable(KEY_RESOURCES, new ArrayList<>(resources));
+ fragment.setArguments(args);
+ return fragment;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionActivity.java
index 9eef878c3..ac6335b90 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionActivity.java
@@ -8,13 +8,12 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
-import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.android.util.ClipboardUtil;
+import it.niedermann.nextcloud.deck.BuildConfig;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ActivityExceptionBinding;
import it.niedermann.nextcloud.deck.ui.exception.tips.TipsAdapter;
-import it.niedermann.nextcloud.deck.util.ExceptionUtil;
-
-import static it.niedermann.nextcloud.deck.util.ClipboardUtil.copyToClipboard;
+import it.niedermann.nextcloud.exception.ExceptionUtil;
public class ExceptionActivity extends AppCompatActivity {
@@ -22,9 +21,12 @@ public class ExceptionActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
final ActivityExceptionBinding binding = ActivityExceptionBinding.inflate(getLayoutInflater());
+
setContentView(binding.getRoot());
- super.onCreate(savedInstanceState);
+ setSupportActionBar(binding.toolbar);
Throwable throwable = ((Throwable) getIntent().getSerializableExtra(KEY_THROWABLE));
@@ -32,23 +34,18 @@ public class ExceptionActivity extends AppCompatActivity {
throwable = new Exception("Could not get exception");
}
- DeckLog.logError(throwable);
+ final TipsAdapter adapter = new TipsAdapter(this::startActivity);
+ final String debugInfo = "Full Crash:\n\n" + ExceptionUtil.INSTANCE.getDebugInfos(this, throwable, BuildConfig.FLAVOR);
- setSupportActionBar(binding.toolbar);
+ binding.tips.setAdapter(adapter);
+ binding.tips.setNestedScrollingEnabled(false);
binding.toolbar.setTitle(R.string.error);
binding.message.setText(throwable.getMessage());
-
- final String debugInfo = ExceptionUtil.getDebugInfos(this, throwable, null);
-
binding.stacktrace.setText(debugInfo);
+ binding.copy.setOnClickListener((v) -> ClipboardUtil.INSTANCE.copyToClipboard(this, getString(R.string.simple_exception), "```\n" + debugInfo + "\n```"));
+ binding.close.setOnClickListener((v) -> finish());
- final TipsAdapter adapter = new TipsAdapter(this::startActivity);
- binding.tips.setAdapter(adapter);
- binding.tips.setNestedScrollingEnabled(false);
adapter.setThrowable(this, null, throwable);
-
- binding.copy.setOnClickListener((v) -> copyToClipboard(this, getString(R.string.simple_exception), "```\n" + debugInfo + "\n```"));
- binding.close.setOnClickListener((v) -> finish());
}
@NonNull
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java
index 6c0d0ba79..7a84ce0b6 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionDialogFragment.java
@@ -11,14 +11,14 @@ import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatDialogFragment;
import androidx.fragment.app.DialogFragment;
+import it.niedermann.android.util.ClipboardUtil;
+import it.niedermann.nextcloud.deck.BuildConfig;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.DialogExceptionBinding;
import it.niedermann.nextcloud.deck.model.Account;
import it.niedermann.nextcloud.deck.ui.exception.tips.TipsAdapter;
-import it.niedermann.nextcloud.deck.util.ExceptionUtil;
-
-import static it.niedermann.nextcloud.deck.util.ClipboardUtil.copyToClipboard;
+import it.niedermann.nextcloud.exception.ExceptionUtil;
public class ExceptionDialogFragment extends AppCompatDialogFragment {
@@ -52,7 +52,7 @@ public class ExceptionDialogFragment extends AppCompatDialogFragment {
final TipsAdapter adapter = new TipsAdapter((actionIntent) -> requireActivity().startActivity(actionIntent));
- final String debugInfos = ExceptionUtil.getDebugInfos(requireContext(), throwable, account);
+ final String debugInfos = ExceptionUtil.INSTANCE.getDebugInfos(requireContext(), throwable, BuildConfig.FLAVOR, account == null ? null : account.getServerDeckVersion());
binding.tips.setAdapter(adapter);
binding.stacktrace.setText(debugInfos);
@@ -65,7 +65,7 @@ public class ExceptionDialogFragment extends AppCompatDialogFragment {
.setView(binding.getRoot())
.setTitle(R.string.error_dialog_title)
.setPositiveButton(android.R.string.copy, (a, b) -> {
- copyToClipboard(requireContext(), getString(R.string.simple_exception), "```\n" + debugInfos + "\n```");
+ ClipboardUtil.INSTANCE.copyToClipboard(requireContext(), getString(R.string.simple_exception), "```\n" + debugInfos + "\n```");
a.dismiss();
})
.setNegativeButton(R.string.simple_close, null)
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionHandler.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionHandler.java
index 8f0bfce33..c62b23e51 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionHandler.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/exception/ExceptionHandler.java
@@ -2,21 +2,24 @@ package it.niedermann.nextcloud.deck.ui.exception;
import android.app.Activity;
-import org.jetbrains.annotations.NotNull;
+import androidx.annotation.NonNull;
+
+import it.niedermann.nextcloud.deck.DeckLog;
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
- private Activity context;
+ @NonNull
+ private final Activity activity;
- public ExceptionHandler(Activity context) {
- super();
- this.context = context;
+ public ExceptionHandler(@NonNull Activity activity) {
+ this.activity = activity;
}
@Override
- public void uncaughtException(@NotNull Thread t, Throwable e) {
- context.getApplicationContext().startActivity(ExceptionActivity.createIntent(context, e));
- context.finish();
+ public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
+ DeckLog.logError(e);
+ activity.getApplicationContext().startActivity(ExceptionActivity.createIntent(activity, e));
+ activity.finish();
Runtime.getRuntime().exit(0);
}
}
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 a059b2956..6bfd82b13 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
@@ -39,6 +39,10 @@ import static it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment.
public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> {
+ 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;
@NonNull
@@ -68,11 +72,8 @@ public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> {
public void setThrowable(@NonNull Context context, @Nullable Account account, @NonNull Throwable throwable) {
if (throwable instanceof TokenMismatchException) {
add(R.string.error_dialog_tip_token_mismatch_retry);
- add(R.string.error_dialog_tip_token_mismatch_clear_storage);
- Intent intent = 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);
- add(R.string.error_dialog_tip_clear_storage, intent);
+ 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 NextcloudFilesAppNotSupportedException) {
add(R.string.error_dialog_tip_files_outdated);
} else if (throwable instanceof NextcloudApiNotRespondingException) {
@@ -122,6 +123,10 @@ public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> {
} else {
add(R.string.error_dialog_version_not_parsable);
}
+ add(R.string.error_dialog_account_might_not_be_authorized);
+ break;
+ case UNKNOWN_ACCOUNT_USER_ID:
+ add(R.string.error_dialog_user_not_found_in_database);
break;
case CAPABILITIES_NOT_PARSABLE:
default:
@@ -133,15 +138,15 @@ public class TipsAdapter extends RecyclerView.Adapter<TipsViewHolder> {
add(R.string.error_dialog_capabilities_not_parsable);
}
}
+ // Files app might no longer be authenticated: https://github.com/stefan-niedermann/nextcloud-deck/issues/621#issuecomment-665533567
+ 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")) {
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);
- Intent clearIntent = 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);
- add(R.string.error_dialog_tip_clear_storage, clearIntent);
+ add(R.string.error_dialog_tip_clear_storage, INTENT_APP_INFO);
}
}
}
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 aa6f59d04..6aa03b811 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
@@ -8,6 +8,7 @@ import android.os.Bundle;
import androidx.annotation.ColorInt;
import androidx.annotation.NonNull;
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;
@@ -19,6 +20,7 @@ import androidx.viewpager2.widget.ViewPager2;
import com.google.android.material.tabs.TabLayoutMediator;
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.DialogFilterBinding;
import it.niedermann.nextcloud.deck.model.enums.EDueType;
@@ -45,8 +47,9 @@ public class FilterDialogFragment extends BrandedDialogFragment {
public Dialog onCreateDialog(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- indicator = getResources().getDrawable(R.drawable.circle_grey600_8dp);
- indicator.setColorFilter(getResources().getColor(R.color.primary), PorterDuff.Mode.SRC_ATOP);
+ indicator = ContextCompat.getDrawable(requireContext(), R.drawable.circle_grey600_8dp);
+ assert indicator != null;
+ indicator.setColorFilter(getResources().getColor(R.color.defaultBrand), PorterDuff.Mode.SRC_ATOP);
filterViewModel = new ViewModelProvider(requireActivity()).get(FilterViewModel.class);
@@ -61,10 +64,10 @@ public class FilterDialogFragment extends BrandedDialogFragment {
filterInformationDraft.observe(this, (draft) -> {
switch (position) {
case 0:
- tab.setIcon(draft.getLabels().size() > 0 ? indicator : null);
+ tab.setIcon(draft.getLabels().size() > 0 || draft.isNoAssignedLabel() ? indicator : null);
break;
case 1:
- tab.setIcon(draft.getUsers().size() > 0 ? indicator : null);
+ tab.setIcon(draft.getUsers().size() > 0 || draft.isNoAssignedUser() ? indicator : null);
break;
case 2:
tab.setIcon(draft.getDueType() != EDueType.NO_FILTER ? indicator : null);
@@ -103,9 +106,10 @@ public class FilterDialogFragment extends BrandedDialogFragment {
@Override
public void applyBrand(int mainColor) {
- @ColorInt int finalMainColor = getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor);
- binding.tabLayout.setSelectedTabIndicatorColor(finalMainColor);
- indicator.setColorFilter(finalMainColor, PorterDuff.Mode.SRC_ATOP);
+ @ColorInt final int finalMainColor = getSecondaryForegroundColorDependingOnTheme(binding.tabLayout.getContext(), mainColor);
+ final boolean contrastRatioIsSufficient = ColorUtil.INSTANCE.getContrastRatio(mainColor, ContextCompat.getColor(binding.tabLayout.getContext(), R.color.primary)) > 1.7d;
+ binding.tabLayout.setSelectedTabIndicatorColor(contrastRatioIsSufficient ? mainColor : finalMainColor);
+ indicator.setColorFilter(contrastRatioIsSufficient ? mainColor : finalMainColor, PorterDuff.Mode.SRC_ATOP);
}
private static class TabsPagerAdapter extends FragmentStateAdapter {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsAdapter.java
index 096f0db9c..39fb791be 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsAdapter.java
@@ -1,20 +1,21 @@
package it.niedermann.nextcloud.deck.ui.filter;
import android.content.res.ColorStateList;
-import android.graphics.Color;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList;
import java.util.List;
+import it.niedermann.android.util.ColorUtil;
+import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemFilterLabelBinding;
import it.niedermann.nextcloud.deck.model.Label;
-import it.niedermann.nextcloud.deck.util.ColorUtil;
@SuppressWarnings("WeakerAccess")
public class FilterLabelsAdapter extends RecyclerView.Adapter<FilterLabelsAdapter.LabelViewHolder> {
@@ -23,11 +24,17 @@ public class FilterLabelsAdapter extends RecyclerView.Adapter<FilterLabelsAdapte
@NonNull
private final List<Label> selectedLabels = new ArrayList<>();
@Nullable
+ private static final Label NOT_ASSIGNED = null;
+ @Nullable
private final SelectionListener<Label> selectionListener;
- public FilterLabelsAdapter(@NonNull List<Label> labels, @NonNull List<Label> selectedLabels, @Nullable SelectionListener<Label> selectionListener) {
+ public FilterLabelsAdapter(@NonNull List<Label> labels, @NonNull List<Label> selectedLabels, boolean noAssignedLabel, @Nullable SelectionListener<Label> selectionListener) {
super();
+ this.labels.add(NOT_ASSIGNED);
this.labels.addAll(labels);
+ if (noAssignedLabel) {
+ this.selectedLabels.add(NOT_ASSIGNED);
+ }
this.selectedLabels.addAll(selectedLabels);
this.selectionListener = selectionListener;
setHasStableIds(true);
@@ -36,7 +43,8 @@ public class FilterLabelsAdapter extends RecyclerView.Adapter<FilterLabelsAdapte
@Override
public long getItemId(int position) {
- return labels.get(position).getLocalId();
+ @Nullable final Label label = labels.get(position);
+ return label == null ? -1L : label.getLocalId();
}
@NonNull
@@ -47,7 +55,11 @@ public class FilterLabelsAdapter extends RecyclerView.Adapter<FilterLabelsAdapte
@Override
public void onBindViewHolder(@NonNull LabelViewHolder viewHolder, int position) {
- viewHolder.bind(labels.get(position));
+ if (position == 0) {
+ viewHolder.bindNotAssigned();
+ } else {
+ viewHolder.bind(labels.get(position));
+ }
}
@Override
@@ -55,26 +67,36 @@ public class FilterLabelsAdapter extends RecyclerView.Adapter<FilterLabelsAdapte
return labels.size();
}
- public List<Label> getSelected() {
- return selectedLabels;
- }
-
class LabelViewHolder extends RecyclerView.ViewHolder {
private ItemFilterLabelBinding binding;
LabelViewHolder(@NonNull ItemFilterLabelBinding binding) {
super(binding.getRoot());
this.binding = binding;
+ this.binding.label.setClickable(false);
}
void bind(final Label label) {
binding.label.setText(label.getTitle());
- final int labelColor = Color.parseColor("#" + label.getColor());
+ final int labelColor = label.getColor();
binding.label.setChipBackgroundColor(ColorStateList.valueOf(labelColor));
- final int color = ColorUtil.getForegroundColorForBackgroundColor(labelColor);
+ final int color = ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(labelColor);
binding.label.setTextColor(color);
itemView.setSelected(selectedLabels.contains(label));
+ bindClickListener(label);
+ }
+
+ public void bindNotAssigned() {
+ binding.label.setText(itemView.getContext().getString(R.string.no_assigned_label));
+ binding.label.setTextColor(ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.accent)));
+ binding.label.setChipIcon(ContextCompat.getDrawable(itemView.getContext(), R.drawable.ic_baseline_block_24));
+ binding.label.setChipBackgroundColor(ColorStateList.valueOf(ContextCompat.getColor(itemView.getContext(), R.color.primary)));
+ binding.label.setRippleColor(null);
+ itemView.setSelected(selectedLabels.contains(NOT_ASSIGNED));
+ bindClickListener(NOT_ASSIGNED);
+ }
+ private void bindClickListener(@Nullable Label label) {
itemView.setOnClickListener(view -> {
if (selectedLabels.contains(label)) {
selectedLabels.remove(label);
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsFragment.java
index 357f93cf9..e7d693185 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterLabelsFragment.java
@@ -12,7 +12,6 @@ import androidx.lifecycle.ViewModelProvider;
import it.niedermann.nextcloud.deck.databinding.DialogFilterLabelsBinding;
import it.niedermann.nextcloud.deck.model.Label;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
@@ -31,21 +30,33 @@ public class FilterLabelsFragment extends Fragment implements SelectionListener<
filterViewModel = new ViewModelProvider(requireActivity()).get(FilterViewModel.class);
- observeOnce(new SyncManager(requireContext()).findProposalsForLabelsToAssign(mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId()), requireActivity(), (labels) -> {
+ observeOnce(filterViewModel.findProposalsForLabelsToAssign(mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId()), requireActivity(), (labels) -> {
binding.labels.setNestedScrollingEnabled(false);
- binding.labels.setAdapter(new FilterLabelsAdapter(labels, requireNonNull(filterViewModel.getFilterInformationDraft().getValue()).getLabels(), this));
+ binding.labels.setAdapter(new FilterLabelsAdapter(
+ labels,
+ requireNonNull(filterViewModel.getFilterInformationDraft().getValue()).getLabels(),
+ requireNonNull(filterViewModel.getFilterInformationDraft().getValue()).isNoAssignedLabel(),
+ this));
});
return binding.getRoot();
}
@Override
- public void onItemSelected(Label item) {
- filterViewModel.addFilterInformationDraftLabel(item);
+ public void onItemSelected(@Nullable Label item) {
+ if (item == null) {
+ filterViewModel.setNotAssignedLabel(true);
+ } else {
+ filterViewModel.addFilterInformationDraftLabel(item);
+ }
}
@Override
- public void onItemDeselected(Label item) {
- filterViewModel.removeFilterInformationLabel(item);
+ public void onItemDeselected(@Nullable Label item) {
+ if (item == null) {
+ filterViewModel.setNotAssignedLabel(false);
+ } else {
+ filterViewModel.removeFilterInformationLabel(item);
+ }
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java
index b4ae8f679..4b75b985f 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserAdapter.java
@@ -8,6 +8,8 @@ import androidx.annotation.Nullable;
import androidx.annotation.Px;
import androidx.recyclerview.widget.RecyclerView;
+import com.bumptech.glide.Glide;
+
import java.util.ArrayList;
import java.util.List;
@@ -23,6 +25,8 @@ public class FilterUserAdapter extends RecyclerView.Adapter<FilterUserAdapter.Us
final int avatarSize;
@NonNull
private final Account account;
+ @Nullable
+ private static final User NOT_ASSIGNED = null;
@NonNull
private final List<User> users = new ArrayList<>();
@NonNull
@@ -30,11 +34,15 @@ public class FilterUserAdapter extends RecyclerView.Adapter<FilterUserAdapter.Us
@Nullable
private final SelectionListener<User> selectionListener;
- public FilterUserAdapter(@Px int avatarSize, @NonNull Account account, @NonNull List<User> users, @NonNull List<User> selectedUsers, @Nullable SelectionListener selectionListener) {
+ public FilterUserAdapter(@Px int avatarSize, @NonNull Account account, @NonNull List<User> users, @NonNull List<User> selectedUsers, boolean noAssignedUser, @Nullable SelectionListener<User> selectionListener) {
super();
this.avatarSize = avatarSize;
this.account = account;
+ this.users.add(NOT_ASSIGNED);
this.users.addAll(users);
+ if (noAssignedUser) {
+ this.selectedUsers.add(NOT_ASSIGNED);
+ }
this.selectedUsers.addAll(selectedUsers);
this.selectionListener = selectionListener;
setHasStableIds(true);
@@ -43,7 +51,8 @@ public class FilterUserAdapter extends RecyclerView.Adapter<FilterUserAdapter.Us
@Override
public long getItemId(int position) {
- return users.get(position).getLocalId();
+ @Nullable final User user = users.get(position);
+ return user == null ? -1L : user.getLocalId();
}
@NonNull
@@ -54,7 +63,11 @@ public class FilterUserAdapter extends RecyclerView.Adapter<FilterUserAdapter.Us
@Override
public void onBindViewHolder(@NonNull UserViewHolder viewHolder, int position) {
- viewHolder.bind(users.get(position));
+ if (position == 0) {
+ viewHolder.bindNotAssigned();
+ } else {
+ viewHolder.bind(users.get(position));
+ }
}
@Override
@@ -62,10 +75,6 @@ public class FilterUserAdapter extends RecyclerView.Adapter<FilterUserAdapter.Us
return users.size();
}
- public List<User> getSelected() {
- return selectedUsers;
- }
-
class UserViewHolder extends RecyclerView.ViewHolder {
private ItemFilterUserBinding binding;
@@ -74,22 +83,34 @@ public class FilterUserAdapter extends RecyclerView.Adapter<FilterUserAdapter.Us
this.binding = binding;
}
- void bind(final User user) {
- binding.displayName.setText(user.getDisplayname());
+ void bind(@NonNull final User user) {
+ binding.title.setText(user.getDisplayname());
ViewUtil.addAvatar(binding.avatar, account.getUrl(), user.getUid(), avatarSize, R.drawable.ic_person_grey600_24dp);
itemView.setSelected(selectedUsers.contains(user));
+ bindClickListener(user);
+ }
+
+ public void bindNotAssigned() {
+ binding.title.setText(itemView.getContext().getString(R.string.simple_unassigned));
+ Glide.with(itemView.getContext())
+ .load(R.drawable.ic_baseline_block_24)
+ .into(binding.avatar);
+ itemView.setSelected(selectedUsers.contains(NOT_ASSIGNED));
+ bindClickListener(NOT_ASSIGNED);
+ }
+ private void bindClickListener(@Nullable User user) {
itemView.setOnClickListener(view -> {
if (selectedUsers.contains(user)) {
selectedUsers.remove(user);
itemView.setSelected(false);
- if(selectionListener != null) {
+ if (selectionListener != null) {
selectionListener.onItemDeselected(user);
}
} else {
selectedUsers.add(user);
itemView.setSelected(true);
- if(selectionListener != null) {
+ if (selectionListener != null) {
selectionListener.onItemSelected(user);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserFragment.java
index 64bc1db1f..6ffaec6a6 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterUserFragment.java
@@ -10,14 +10,13 @@ import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider;
+import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.DialogFilterAssigneesBinding;
import it.niedermann.nextcloud.deck.model.User;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import it.niedermann.nextcloud.deck.ui.MainViewModel;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
-import static it.niedermann.nextcloud.deck.util.DimensionUtil.dpToPx;
import static java.util.Objects.requireNonNull;
public class FilterUserFragment extends Fragment implements SelectionListener<User> {
@@ -33,21 +32,35 @@ public class FilterUserFragment extends Fragment implements SelectionListener<Us
filterViewModel = new ViewModelProvider(requireActivity()).get(FilterViewModel.class);
- observeOnce(new SyncManager(requireContext()).findProposalsForUsersToAssign(mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId()), requireActivity(), (users) -> {
+ observeOnce(filterViewModel.findProposalsForUsersToAssign(mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId()), requireActivity(), (users) -> {
binding.users.setNestedScrollingEnabled(false);
- binding.users.setAdapter(new FilterUserAdapter(dpToPx(requireContext(), R.dimen.avatar_size), mainViewModel.getCurrentAccount(), users, requireNonNull(filterViewModel.getFilterInformationDraft().getValue()).getUsers(), this));
+ binding.users.setAdapter(new FilterUserAdapter(
+ DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.avatar_size),
+ mainViewModel.getCurrentAccount(),
+ users,
+ requireNonNull(filterViewModel.getFilterInformationDraft().getValue()).getUsers(),
+ requireNonNull(filterViewModel.getFilterInformationDraft().getValue()).isNoAssignedUser(),
+ this));
});
return binding.getRoot();
}
@Override
- public void onItemSelected(User item) {
- filterViewModel.addFilterInformationUser(item);
+ public void onItemSelected(@Nullable User item) {
+ if (item == null) {
+ filterViewModel.setNotAssignedUser(true);
+ } else {
+ filterViewModel.addFilterInformationUser(item);
+ }
}
@Override
- public void onItemDeselected(User item) {
- filterViewModel.removeFilterInformationUser(item);
+ public void onItemDeselected(@Nullable User item) {
+ if (item == null) {
+ filterViewModel.setNotAssignedUser(false);
+ } else {
+ filterViewModel.removeFilterInformationUser(item);
+ }
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterViewModel.java
index cf8dc1754..c42a61ebd 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterViewModel.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/FilterViewModel.java
@@ -1,28 +1,40 @@
package it.niedermann.nextcloud.deck.ui.filter;
+import android.app.Application;
+
import androidx.annotation.IntRange;
import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
-import androidx.lifecycle.ViewModel;
+
+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.internal.FilterInformation;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import static it.niedermann.nextcloud.deck.model.internal.FilterInformation.hasActiveFilter;
@SuppressWarnings("WeakerAccess")
-public class FilterViewModel extends ViewModel {
+public class FilterViewModel extends AndroidViewModel {
+
+ private final SyncManager syncManager;
@IntRange(from = 0, to = 2)
private int currentFilterTab = 0;
@NonNull
- private MutableLiveData<FilterInformation> filterInformationDraft = new MutableLiveData<>(new FilterInformation());
+ private final MutableLiveData<FilterInformation> filterInformationDraft = new MutableLiveData<>(new FilterInformation());
@NonNull
- private MutableLiveData<FilterInformation> filterInformation = new MutableLiveData<>();
+ private final MutableLiveData<FilterInformation> filterInformation = new MutableLiveData<>();
+
+ public FilterViewModel(@NonNull Application application) {
+ super(application);
+ this.syncManager = new SyncManager(application);
+ }
public void publishFilterInformationDraft() {
this.filterInformation.postValue(hasActiveFilter(filterInformationDraft.getValue()) ? filterInformationDraft.getValue() : null);
@@ -66,6 +78,18 @@ public class FilterViewModel extends ViewModel {
this.filterInformationDraft.postValue(newDraft);
}
+ public void setNotAssignedUser(boolean notAssignedUser) {
+ FilterInformation newDraft = new FilterInformation(filterInformationDraft.getValue());
+ newDraft.setNoAssignedUser(notAssignedUser);
+ this.filterInformationDraft.postValue(newDraft);
+ }
+
+ public void setNotAssignedLabel(boolean notAssignedLabel) {
+ FilterInformation newDraft = new FilterInformation(filterInformationDraft.getValue());
+ newDraft.setNoAssignedLabel(notAssignedLabel);
+ this.filterInformationDraft.postValue(newDraft);
+ }
+
public void removeFilterInformationLabel(@NonNull Label label) {
FilterInformation newDraft = new FilterInformation(filterInformationDraft.getValue());
newDraft.removeLabel(label);
@@ -86,4 +110,12 @@ public class FilterViewModel extends ViewModel {
public int getCurrentFilterTab() {
return this.currentFilterTab;
}
+
+ public LiveData<List<User>> findProposalsForUsersToAssign(final long accountId, long boardId) {
+ return syncManager.findProposalsForUsersToAssign(accountId, boardId, -1L, -1);
+ }
+
+ public LiveData<List<Label>> findProposalsForLabelsToAssign(final long accountId, final long boardId) {
+ return syncManager.findProposalsForLabelsToAssign(accountId, boardId, -1L);
+ }
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/SelectionListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/SelectionListener.java
index d2635a860..3fad71773 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/SelectionListener.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/filter/SelectionListener.java
@@ -1,9 +1,11 @@
package it.niedermann.nextcloud.deck.ui.filter;
+import androidx.annotation.Nullable;
+
public interface SelectionListener<T> {
- void onItemSelected(T item);
+ void onItemSelected(@Nullable T item);
- default void onItemDeselected(T item) {
+ default void onItemDeselected(@Nullable T item) {
// Deselecting is optional
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java
index 4b43cbed6..0892eb437 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountViewHolder.java
@@ -11,14 +11,14 @@ import androidx.recyclerview.widget.RecyclerView;
import com.bumptech.glide.Glide;
import com.bumptech.glide.request.RequestOptions;
-import it.niedermann.android.glidesso.SingleSignOnUrl;
+import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemAccountChooseBinding;
import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl;
import static android.view.View.GONE;
import static android.view.View.VISIBLE;
-import static it.niedermann.nextcloud.deck.util.DimensionUtil.dpToPx;
public class ManageAccountViewHolder extends RecyclerView.ViewHolder {
@@ -33,7 +33,7 @@ public class ManageAccountViewHolder extends RecyclerView.ViewHolder {
binding.accountName.setText(account.getUserName());
binding.accountHost.setText(Uri.parse(account.getUrl()).getHost());
Glide.with(itemView.getContext())
- .load(new SingleSignOnUrl(account.getName(), account.getAvatarUrl(dpToPx(binding.accountItemAvatar.getContext(), R.dimen.avatar_size))))
+ .load(new SingleSignOnUrl(account.getName(), account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.accountItemAvatar.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/manageaccounts/ManageAccountsActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsActivity.java
index 9d273cdcb..8aa45e39a 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
@@ -5,15 +5,12 @@ import android.util.Log;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
-
-import com.nextcloud.android.sso.helper.SingleAccountHelper;
+import androidx.lifecycle.ViewModelProvider;
import it.niedermann.nextcloud.deck.databinding.ActivityManageAccountsBinding;
import it.niedermann.nextcloud.deck.model.Account;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentAccountId;
-import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentAccountId;
import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
public class ManageAccountsActivity extends AppCompatActivity {
@@ -21,44 +18,37 @@ public class ManageAccountsActivity extends AppCompatActivity {
private static final String TAG = ManageAccountsActivity.class.getSimpleName();
private ActivityManageAccountsBinding binding;
+ private ManageAccountsViewModel viewModel;
private ManageAccountAdapter adapter;
- private SyncManager syncManager = null;
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityManageAccountsBinding.inflate(getLayoutInflater());
- setContentView(binding.getRoot());
+ viewModel = new ViewModelProvider(this).get(ManageAccountsViewModel.class);
+ setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
- syncManager = new SyncManager(this);
-
- adapter = new ManageAccountAdapter((account) -> {
- SingleAccountHelper.setCurrentAccount(getApplicationContext(), account.getName());
- syncManager = new SyncManager(this);
- saveCurrentAccountId(this, account.getId());
- }, (accountPair) -> {
+ adapter = new ManageAccountAdapter((account) -> viewModel.setNewAccount(account), (accountPair) -> {
if (accountPair.first != null) {
- syncManager.deleteAccount(accountPair.first.getId());
+ viewModel.deleteAccount(accountPair.first.getId());
} else {
throw new IllegalArgumentException("Could not delete account because given account was null.");
}
Account newAccount = accountPair.second;
if (newAccount != null) {
- SingleAccountHelper.setCurrentAccount(getApplicationContext(), newAccount.getName());
- saveCurrentAccountId(this, newAccount.getId());
- syncManager = new SyncManager(this);
+ viewModel.setNewAccount(newAccount);
} else {
Log.i(TAG, "Got delete account request, but new account is null. Maybe last account has been deleted?");
}
});
binding.accounts.setAdapter(adapter);
- observeOnce(syncManager.readAccount(readCurrentAccountId(this)), this, (account -> {
+ observeOnce(viewModel.readAccount(readCurrentAccountId(this)), this, (account -> {
adapter.setCurrentAccount(account);
- syncManager.readAccounts().observe(this, (localAccounts -> {
+ viewModel.readAccounts().observe(this, (localAccounts -> {
if (localAccounts.size() == 0) {
Log.i(TAG, "No accounts, finishing " + ManageAccountsActivity.class.getSimpleName());
setResult(AppCompatActivity.RESULT_FIRST_USER);
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
new file mode 100644
index 000000000..66e9d3850
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/manageaccounts/ManageAccountsViewModel.java
@@ -0,0 +1,45 @@
+package it.niedermann.nextcloud.deck.ui.manageaccounts;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+
+import com.nextcloud.android.sso.helper.SingleAccountHelper;
+
+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;
+
+@SuppressWarnings("WeakerAccess")
+public class ManageAccountsViewModel extends AndroidViewModel {
+
+ private SyncManager syncManager;
+
+ public ManageAccountsViewModel(@NonNull Application application) {
+ super(application);
+ this.syncManager = new SyncManager(application);
+ }
+
+ public LiveData<Account> readAccount(long id) {
+ return syncManager.readAccount(id);
+ }
+
+ public LiveData<List<Account>> readAccounts() {
+ return syncManager.readAccounts();
+ }
+
+ public void setNewAccount(@NonNull Account account) {
+ SingleAccountHelper.setCurrentAccount(getApplication(), account.getName());
+ syncManager = new SyncManager(getApplication());
+ saveCurrentAccountId(getApplication(), account.getId());
+ }
+
+ public void deleteAccount(long id) {
+ syncManager.deleteAccount(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
new file mode 100644
index 000000000..2b7eb52fe
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardDialogFragment.java
@@ -0,0 +1,128 @@
+package it.niedermann.nextcloud.deck.ui.movecard;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.DialogFragment;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.databinding.DialogMoveCardBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.Stack;
+import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
+import it.niedermann.nextcloud.deck.ui.branding.BrandingUtil;
+import it.niedermann.nextcloud.deck.ui.pickstack.PickStackFragment;
+import it.niedermann.nextcloud.deck.ui.pickstack.PickStackListener;
+import it.niedermann.nextcloud.deck.ui.pickstack.PickStackViewModel;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+public class MoveCardDialogFragment extends BrandedDialogFragment implements PickStackListener {
+
+ private static final String KEY_ORIGIN_ACCOUNT_ID = "account_id";
+ 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 Long originAccountId;
+ private Long originBoardLocalId;
+ private String originCardTitle;
+ private Long originCardLocalId;
+
+ private DialogMoveCardBinding binding;
+ private PickStackViewModel viewModel;
+ private MoveCardListener moveCardListener;
+
+ private Account selectedAccount;
+ private Board selectedBoard;
+ private Stack selectedStack;
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ if (getParentFragment() instanceof MoveCardListener) {
+ this.moveCardListener = (MoveCardListener) getParentFragment();
+ } else if (context instanceof MoveCardListener) {
+ this.moveCardListener = (MoveCardListener) context;
+ } else {
+ throw new IllegalArgumentException("Caller must implement " + MoveCardListener.class.getSimpleName());
+ }
+
+ final Bundle args = requireArguments();
+ originAccountId = args.getLong(KEY_ORIGIN_ACCOUNT_ID, -1L);
+ if (originAccountId < 0) {
+ throw new IllegalArgumentException("Missing " + KEY_ORIGIN_ACCOUNT_ID);
+ }
+ originCardLocalId = args.getLong(KEY_ORIGIN_CARD_LOCAL_ID, -1L);
+ if (originCardLocalId < 0) {
+ throw new IllegalArgumentException("Missing " + KEY_ORIGIN_CARD_LOCAL_ID);
+ }
+ originBoardLocalId = args.getLong(KEY_ORIGIN_BOARD_LOCAL_ID, -1L);
+ if (originBoardLocalId < 0) {
+ throw new IllegalArgumentException("Missing " + KEY_ORIGIN_BOARD_LOCAL_ID);
+ }
+ originCardTitle = args.getString(KEY_ORIGIN_CARD_TITLE);
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
+ 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());
+ this.moveCardListener.move(originAccountId, originCardLocalId, selectedAccount.getId(), selectedBoard.getLocalId(), selectedStack.getLocalId());
+ dismiss();
+ });
+ binding.cancel.setOnClickListener((v) -> dismiss());
+ return binding.getRoot();
+ }
+
+ @Override
+ public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
+ getChildFragmentManager()
+ .beginTransaction()
+ .add(R.id.fragment_container, PickStackFragment.newInstance(false))
+ .commit();
+ }
+
+ @Override
+ public void onStackPicked(@NonNull Account account, @Nullable Board board, @Nullable Stack stack) {
+ this.selectedAccount = account;
+ this.selectedBoard = board;
+ this.selectedStack = stack;
+ if (board == null || stack == null) {
+ binding.submit.setEnabled(false);
+ binding.moveWarning.setVisibility(GONE);
+ } else {
+ binding.submit.setEnabled(true);
+ binding.moveWarning.setVisibility(board.getLocalId().equals(originBoardLocalId) ? GONE : VISIBLE);
+ }
+ }
+
+ @Override
+ public void applyBrand(int mainColor) {
+ final ColorStateList mainColorStateList = ColorStateList.valueOf(BrandingUtil.getSecondaryForegroundColorDependingOnTheme(requireContext(), mainColor));
+ binding.cancel.setTextColor(mainColorStateList);
+ binding.submit.setTextColor(mainColorStateList);
+ }
+
+ public static DialogFragment newInstance(long originAccountId, long originBoardLocalId, String originCardTitle, Long originCardLocalId) {
+ 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);
+ dialogFragment.setArguments(args);
+ return dialogFragment;
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardListener.java
new file mode 100644
index 000000000..f6f7a7a1f
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/movecard/MoveCardListener.java
@@ -0,0 +1,5 @@
+package it.niedermann.nextcloud.deck.ui.movecard;
+
+public interface MoveCardListener {
+ void move(long originAccountId, long originCardLocalId, long targetAccountId, long targetBoardLocalId, long targetStackLocalId);
+}
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
new file mode 100644
index 000000000..d65971cf4
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackFragment.java
@@ -0,0 +1,204 @@
+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;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.fragment.app.Fragment;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.Observer;
+import androidx.lifecycle.ViewModelProvider;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.databinding.FragmentPickStackBinding;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.Stack;
+import it.niedermann.nextcloud.deck.ui.ImportAccountActivity;
+import it.niedermann.nextcloud.deck.ui.preparecreate.AccountAdapter;
+import it.niedermann.nextcloud.deck.ui.preparecreate.BoardAdapter;
+import it.niedermann.nextcloud.deck.ui.preparecreate.SelectedListener;
+import it.niedermann.nextcloud.deck.ui.preparecreate.StackAdapter;
+
+import static androidx.lifecycle.Transformations.switchMap;
+import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentAccountId;
+import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentBoardId;
+import static it.niedermann.nextcloud.deck.DeckApplication.readCurrentStackId;
+
+public class PickStackFragment extends Fragment {
+
+ private FragmentPickStackBinding binding;
+ private PickStackViewModel viewModel;
+
+ private static final String KEY_SHOW_BOARDS_WITHOUT_EDIT_PERMISSION = "show_boards_without_edit_permission";
+
+ private PickStackListener pickStackListener;
+
+ private boolean showBoardsWithoutEditPermission = false;
+ private long lastAccountId;
+ private long lastBoardId;
+ private long lastStackId;
+
+ private ArrayAdapter<Account> accountAdapter;
+ private ArrayAdapter<Board> boardAdapter;
+ private ArrayAdapter<Stack> stackAdapter;
+
+ @Nullable
+ private LiveData<List<Board>> boardsLiveData;
+ @NonNull
+ private Observer<List<Board>> boardsObserver = (boards) -> {
+ boardAdapter.clear();
+ boardAdapter.addAll(boards);
+ binding.boardSelect.setEnabled(true);
+
+ if (boards.size() > 0) {
+ binding.boardSelect.setEnabled(true);
+
+ Board boardToSelect = null;
+ for (Board board : boards) {
+ if (board.getLocalId() == lastBoardId) {
+ boardToSelect = board;
+ break;
+ }
+ }
+ if (boardToSelect == null) {
+ boardToSelect = boards.get(0);
+ }
+ binding.boardSelect.setSelection(boardAdapter.getPosition(boardToSelect));
+ } else {
+ binding.boardSelect.setEnabled(false);
+ pickStackListener.onStackPicked((Account) binding.accountSelect.getSelectedItem(), null, null);
+ }
+ };
+
+ @Nullable
+ private LiveData<List<Stack>> stacksLiveData;
+ @NonNull
+ private Observer<List<Stack>> stacksObserver = (stacks) -> {
+ stackAdapter.clear();
+ stackAdapter.addAll(stacks);
+
+ if (stacks.size() > 0) {
+ binding.stackSelect.setEnabled(true);
+
+ Stack stackToSelect = null;
+ for (Stack stack : stacks) {
+ if (stack.getLocalId() == lastStackId) {
+ stackToSelect = stack;
+ break;
+ }
+ }
+ if (stackToSelect == null) {
+ stackToSelect = stacks.get(0);
+ }
+ binding.stackSelect.setSelection(stackAdapter.getPosition(stackToSelect));
+ } else {
+ binding.stackSelect.setEnabled(false);
+ pickStackListener.onStackPicked((Account) binding.accountSelect.getSelectedItem(), (Board) binding.boardSelect.getSelectedItem(), null);
+ }
+ };
+
+ @Override
+ public void onAttach(@NonNull Context context) {
+ super.onAttach(context);
+ if (getParentFragment() instanceof PickStackListener) {
+ this.pickStackListener = (PickStackListener) getParentFragment();
+ } else if (context instanceof PickStackListener) {
+ this.pickStackListener = (PickStackListener) context;
+ } else {
+ throw new IllegalArgumentException("Caller must implement " + PickStackListener.class.getSimpleName());
+ }
+ final Bundle args = getArguments();
+ if (args != null) {
+ this.showBoardsWithoutEditPermission = args.getBoolean(KEY_SHOW_BOARDS_WITHOUT_EDIT_PERMISSION, false);
+ }
+ }
+
+ @Override
+ public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ binding = FragmentPickStackBinding.inflate(getLayoutInflater());
+ viewModel = new ViewModelProvider(requireActivity()).get(PickStackViewModel.class);
+
+ accountAdapter = new AccountAdapter(requireContext());
+ binding.accountSelect.setAdapter(accountAdapter);
+ binding.accountSelect.setEnabled(false);
+ boardAdapter = new BoardAdapter(requireContext());
+ binding.boardSelect.setAdapter(boardAdapter);
+ binding.stackSelect.setEnabled(false);
+ stackAdapter = new StackAdapter(requireContext());
+ binding.stackSelect.setAdapter(stackAdapter);
+ binding.stackSelect.setEnabled(false);
+
+ switchMap(viewModel.hasAccounts(), hasAccounts -> {
+ if (hasAccounts) {
+ return viewModel.readAccounts();
+ } else {
+ startActivityForResult(new Intent(requireActivity(), ImportAccountActivity.class), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
+ return null;
+ }
+ }).observe(getViewLifecycleOwner(), (List<Account> accounts) -> {
+ if (accounts == null || accounts.size() == 0) {
+ throw new IllegalStateException("hasAccounts() returns true, but readAccounts() returns null or has no entry");
+ }
+
+ lastAccountId = readCurrentAccountId(requireContext());
+ lastBoardId = readCurrentBoardId(requireContext(), lastAccountId);
+ lastStackId = readCurrentStackId(requireContext(), lastAccountId, lastBoardId);
+
+ accountAdapter.clear();
+ accountAdapter.addAll(accounts);
+ binding.accountSelect.setEnabled(true);
+
+ for (Account account : accounts) {
+ if (account.getId() == lastAccountId) {
+ binding.accountSelect.setSelection(accountAdapter.getPosition(account));
+ break;
+ }
+ }
+ });
+
+ binding.accountSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> {
+ updateLiveDataSource(boardsLiveData, boardsObserver, showBoardsWithoutEditPermission
+ ? viewModel.getBoards(parent.getSelectedItemId())
+ : viewModel.getBoardsWithEditPermission(parent.getSelectedItemId()));
+ });
+
+ binding.boardSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> {
+ updateLiveDataSource(stacksLiveData, stacksObserver, viewModel.getStacksForBoard(binding.accountSelect.getSelectedItemId(), parent.getSelectedItemId()));
+ });
+
+ binding.stackSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> {
+ pickStackListener.onStackPicked((Account) binding.accountSelect.getSelectedItem(), (Board) binding.boardSelect.getSelectedItem(), (Stack) parent.getSelectedItem());
+ });
+
+ return binding.getRoot();
+ }
+
+ /**
+ * Updates the source of the given liveData and de- and reregisters the given observer.
+ */
+ private <T> void updateLiveDataSource(@Nullable LiveData<T> liveData, Observer<T> observer, LiveData<T> newSource) {
+ if (liveData != null) {
+ liveData.removeObserver(observer);
+ }
+ liveData = newSource;
+ liveData.observe(getViewLifecycleOwner(), observer);
+ }
+
+ public static PickStackFragment newInstance(boolean showBoardsWithoutEditPermission) {
+ final PickStackFragment fragment = new PickStackFragment();
+ final Bundle args = new Bundle();
+ args.putBoolean(KEY_SHOW_BOARDS_WITHOUT_EDIT_PERMISSION, showBoardsWithoutEditPermission);
+ fragment.setArguments(args);
+ return fragment;
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackListener.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackListener.java
new file mode 100644
index 000000000..ce227746a
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackListener.java
@@ -0,0 +1,12 @@
+package it.niedermann.nextcloud.deck.ui.pickstack;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.Stack;
+
+public interface PickStackListener {
+ void onStackPicked(@NonNull Account account, @Nullable Board board, @Nullable Stack stack);
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackViewModel.java
new file mode 100644
index 000000000..cc9fc2259
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/pickstack/PickStackViewModel.java
@@ -0,0 +1,45 @@
+package it.niedermann.nextcloud.deck.ui.pickstack;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+import androidx.lifecycle.LiveData;
+
+import java.util.List;
+
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.model.Board;
+import it.niedermann.nextcloud.deck.model.Stack;
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+
+@SuppressWarnings("WeakerAccess")
+public class PickStackViewModel extends AndroidViewModel {
+
+ private final SyncManager syncManager;
+
+ public PickStackViewModel(@NonNull Application application) {
+ super(application);
+ this.syncManager = new SyncManager(application);
+ }
+
+ public LiveData<Boolean> hasAccounts() {
+ return syncManager.hasAccounts();
+ }
+
+ public LiveData<List<Account>> readAccounts() {
+ return syncManager.readAccounts();
+ }
+
+ public LiveData<List<Board>> getBoards(long accountId) {
+ return syncManager.getBoards(accountId);
+ }
+
+ public LiveData<List<Board>> getBoardsWithEditPermission(long accountId) {
+ return syncManager.getBoardsWithEditPermission(accountId);
+ }
+
+ public LiveData<List<Stack>> getStacksForBoard(long accountId, long localBoardId) {
+ return syncManager.getStacksForBoard(accountId, localBoardId);
+ }
+}
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 35a98efd7..f537c9fb4 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
@@ -6,14 +6,17 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
-import org.jetbrains.annotations.NotNull;
+import com.bumptech.glide.Glide;
+import com.bumptech.glide.request.RequestOptions;
+import java.net.URL;
+
+import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemPrepareCreateAccountBinding;
import it.niedermann.nextcloud.deck.model.Account;
-import it.niedermann.nextcloud.deck.util.DimensionUtil;
-import it.niedermann.nextcloud.deck.util.ViewUtil;
+import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl;
public class AccountAdapter extends AbstractAdapter<Account> {
@@ -27,9 +30,9 @@ public class AccountAdapter extends AbstractAdapter<Account> {
return item.getId();
}
- @NotNull
+ @NonNull
@Override
- public View getView(int position, View convertView, @NotNull ViewGroup parent) {
+ public View getView(int position, View convertView, @NonNull ViewGroup parent) {
final ItemPrepareCreateAccountBinding binding;
if (convertView == null) {
binding = ItemPrepareCreateAccountBinding.inflate(inflater, parent, false);
@@ -40,8 +43,18 @@ public class AccountAdapter extends AbstractAdapter<Account> {
final Account item = getItem(position);
if (item != null) {
binding.username.setText(item.getUserName());
- binding.instance.setText(item.getUrl());
- ViewUtil.addAvatar(binding.avatar, item.getUrl(), item.getUserName(), DimensionUtil.dpToPx(binding.avatar.getContext(), R.dimen.icon_size_details), R.drawable.ic_person_grey600_24dp);
+ try {
+ binding.instance.setText(new URL(item.getUrl()).getHost());
+ } catch (Throwable t) {
+ binding.instance.setText(item.getUrl());
+ }
+
+ Glide.with(getContext())
+ .load(new SingleSignOnUrl(item.getName(), item.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.icon_size_details))))
+ .placeholder(R.drawable.ic_baseline_account_circle_24)
+ .error(R.drawable.ic_baseline_account_circle_24)
+ .apply(RequestOptions.circleCropTransform())
+ .into(binding.avatar);
} else {
DeckLog.logError(new IllegalArgumentException("No item for position " + position));
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/BoardAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/BoardAdapter.java
index 190c55e4b..c27fa04ee 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/BoardAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/BoardAdapter.java
@@ -6,8 +6,6 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
-import org.jetbrains.annotations.NotNull;
-
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemPrepareCreateBoardBinding;
@@ -26,9 +24,9 @@ public class BoardAdapter extends AbstractAdapter<Board> {
return item.getLocalId();
}
- @NotNull
+ @NonNull
@Override
- public View getView(int position, View convertView, @NotNull ViewGroup parent) {
+ public View getView(int position, View convertView, @NonNull ViewGroup parent) {
final ItemPrepareCreateBoardBinding binding;
if (convertView == null) {
binding = ItemPrepareCreateBoardBinding.inflate(inflater, parent, false);
@@ -39,7 +37,7 @@ public class BoardAdapter extends AbstractAdapter<Board> {
final Board item = getItem(position);
if (item != null) {
binding.boardTitle.setText(item.getTitle());
- binding.avatar.setImageDrawable(ViewUtil.getTintedImageView(binding.avatar.getContext(), R.drawable.circle_grey600_36dp, "#" + item.getColor()));
+ binding.avatar.setImageDrawable(ViewUtil.getTintedImageView(binding.avatar.getContext(), R.drawable.circle_grey600_36dp, item.getColor()));
} else {
DeckLog.logError(new IllegalArgumentException("No item for position " + position));
}
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 ab0cc4816..18317e078 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
@@ -2,214 +2,52 @@ package it.niedermann.nextcloud.deck.ui.preparecreate;
import android.content.ClipData;
import android.content.Intent;
-import android.content.res.ColorStateList;
-import android.graphics.Color;
import android.os.Bundle;
import android.text.TextUtils;
-import android.widget.ArrayAdapter;
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
-import androidx.appcompat.app.AppCompatActivity;
-import androidx.core.content.ContextCompat;
-import androidx.core.graphics.drawable.DrawableCompat;
-import androidx.lifecycle.LiveData;
-import androidx.lifecycle.Observer;
+import androidx.appcompat.app.ActionBar;
-import java.util.List;
-
-import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
-import it.niedermann.nextcloud.deck.databinding.ActivityPrepareCreateBinding;
import it.niedermann.nextcloud.deck.model.Account;
-import it.niedermann.nextcloud.deck.model.Board;
-import it.niedermann.nextcloud.deck.model.full.FullStack;
-import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
-import it.niedermann.nextcloud.deck.ui.ImportAccountActivity;
-import it.niedermann.nextcloud.deck.ui.branding.Branded;
+import it.niedermann.nextcloud.deck.ui.PickStackActivity;
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.ColorUtil;
-import static android.graphics.Color.parseColor;
-import static androidx.lifecycle.Transformations.switchMap;
-import static it.niedermann.nextcloud.deck.DeckApplication.isDarkTheme;
-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.saveCurrentBoardId;
import static it.niedermann.nextcloud.deck.DeckApplication.saveCurrentStackId;
-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.ColorUtil.contrastRatioIsSufficientBigAreas;
-
-public class PrepareCreateActivity extends AppCompatActivity implements Branded {
-
- private ActivityPrepareCreateBinding binding;
-
- private SyncManager syncManager;
-
- private boolean brandingEnabled;
-
- private long lastAccountId;
- private long lastBoardId;
- private long lastStackId;
- private ArrayAdapter<Account> accountAdapter;
- private ArrayAdapter<Board> boardAdapter;
- private ArrayAdapter<FullStack> stackAdapter;
-
- @Nullable
- private LiveData<List<Board>> boardsLiveData;
- @NonNull
- private Observer<List<Board>> boardsObserver = (boards) -> {
- boardAdapter.clear();
- boardAdapter.addAll(boards);
- binding.boardSelect.setEnabled(true);
-
- if (boards.size() > 0) {
- binding.boardSelect.setEnabled(true);
-
- for (Board board : boards) {
- if (board.getLocalId() == lastBoardId) {
- binding.boardSelect.setSelection(boardAdapter.getPosition(board));
- applyBrand(Color.parseColor('#' + board.getColor()));
- break;
- }
- }
- } else {
- applyBrand(ContextCompat.getColor(this, R.color.defaultBrand));
- binding.boardSelect.setEnabled(false);
- binding.submit.setEnabled(false);
- }
- };
-
- @Nullable
- private LiveData<List<FullStack>> stacksLiveData;
- @NonNull
- private Observer<List<FullStack>> stacksObserver = (fullStacks) -> {
- stackAdapter.clear();
- stackAdapter.addAll(fullStacks);
-
- if (fullStacks.size() > 0) {
- binding.stackSelect.setEnabled(true);
- binding.submit.setEnabled(true);
-
- for (FullStack fullStack : fullStacks) {
- if (fullStack.getLocalId() == lastStackId) {
- binding.stackSelect.setSelection(stackAdapter.getPosition(fullStack));
- break;
- }
- }
- } else {
- binding.stackSelect.setEnabled(false);
- binding.submit.setEnabled(false);
- }
- };
+public class PrepareCreateActivity extends PickStackActivity {
@Override
- protected void onCreate(Bundle savedInstanceState) {
+ public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
- Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler(this));
-
- brandingEnabled = isBrandingEnabled(this);
-
- binding = ActivityPrepareCreateBinding.inflate(getLayoutInflater());
- setContentView(binding.getRoot());
- setSupportActionBar(binding.toolbar);
-
- accountAdapter = new AccountAdapter(this);
- binding.accountSelect.setAdapter(accountAdapter);
- binding.accountSelect.setEnabled(false);
- boardAdapter = new BoardAdapter(this);
- binding.boardSelect.setAdapter(boardAdapter);
- binding.stackSelect.setEnabled(false);
- stackAdapter = new StackAdapter(this);
- binding.stackSelect.setAdapter(stackAdapter);
- binding.stackSelect.setEnabled(false);
-
- syncManager = new SyncManager(this);
-
- switchMap(syncManager.hasAccounts(), hasAccounts -> {
- if (hasAccounts) {
- return syncManager.readAccounts();
- } else {
- startActivityForResult(new Intent(this, ImportAccountActivity.class), ImportAccountActivity.REQUEST_CODE_IMPORT_ACCOUNT);
- return null;
- }
- }).observe(this, (List<Account> accounts) -> {
- if (accounts == null || accounts.size() == 0) {
- throw new IllegalStateException("hasAccounts() returns true, but readAccounts() returns null or has no entry");
- }
-
- lastAccountId = readCurrentAccountId(this);
- lastBoardId = readCurrentBoardId(this, lastAccountId);
- lastStackId = readCurrentStackId(this, lastAccountId, lastBoardId);
-
- accountAdapter.clear();
- accountAdapter.addAll(accounts);
- binding.accountSelect.setEnabled(true);
-
- for (Account account : accounts) {
- if (account.getId() == lastAccountId) {
- binding.accountSelect.setSelection(accountAdapter.getPosition(account));
- break;
- }
- }
- });
-
- binding.accountSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> {
- updateLiveDataSource(boardsLiveData, boardsObserver, syncManager.getBoardsWithEditPermission(parent.getSelectedItemId()));
- });
-
- binding.boardSelect.setOnItemSelectedListener((SelectedListener) (parent, view, position, id) -> {
- applyBrand(Color.parseColor('#' + ((Board) binding.boardSelect.getSelectedItem()).getColor()));
- updateLiveDataSource(stacksLiveData, stacksObserver, syncManager.getStacksForBoard(binding.accountSelect.getSelectedItemId(), parent.getSelectedItemId()));
- });
-
- binding.cancel.setOnClickListener((v) -> finish());
- binding.submit.setOnClickListener((v) -> onSubmit());
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(R.string.add_card);
+ }
}
- /**
- * Updates the source of the given liveData and de- and reregisters the given observer.
- */
- private <T> void updateLiveDataSource(@Nullable LiveData<T> liveData, Observer<T> observer, LiveData<T> newSource) {
- if (liveData != null) {
- liveData.removeObserver(observer);
+ @Override
+ protected void onSubmit(Account account, long boardId, long stackId) {
+ final String receivedClipData = getReceivedClipData(getIntent());
+ if (receivedClipData == null) {
+ startActivity(EditActivity.createNewCardIntent(this, account, boardId, stackId));
+ } else {
+ startActivity(EditActivity.createNewCardIntent(this, account, boardId, stackId, receivedClipData));
}
- liveData = newSource;
- liveData.observe(PrepareCreateActivity.this, observer);
- }
- /**
- * Starts EditActivity and passes parameters.
- */
- private void onSubmit() {
- final Account account = accountAdapter.getItem(binding.accountSelect.getSelectedItemPosition());
- if (account != null) {
- final long boardId = binding.boardSelect.getSelectedItemId();
- final long stackId = binding.stackSelect.getSelectedItemId();
- final String receivedClipData = getReceivedClipData(getIntent());
- if (receivedClipData == null) {
- startActivity(EditActivity.createNewCardIntent(this, account, boardId, stackId));
- } else {
- startActivity(EditActivity.createNewCardIntent(this, account, boardId, stackId, receivedClipData));
- }
+ saveCurrentAccountId(this, account.getId());
+ saveCurrentBoardId(this, account.getId(), boardId);
+ saveCurrentStackId(this, account.getId(), boardId, stackId);
+ applyBrand(account.getColor());
- saveCurrentAccountId(this, account.getId());
- saveCurrentBoardId(this, account.getId(), boardId);
- saveCurrentStackId(this, account.getId(), boardId, stackId);
- applyBrand(parseColor(account.getColor()));
+ finish();
+ }
- finish();
- } else {
- ExceptionDialogFragment.newInstance(new IllegalStateException("Selected account at position " + binding.accountSelect.getSelectedItemPosition() + " is null."), null).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
- }
+ @Override
+ protected boolean showBoardsWithoutEditPermission() {
+ return false;
}
@Nullable
@@ -232,20 +70,4 @@ public class PrepareCreateActivity extends AppCompatActivity implements Branded
final CharSequence text = item.getText();
return TextUtils.isEmpty(text) ? null : text.toString();
}
-
- @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.getForegroundColorForBackgroundColor(finalMainColor));
- binding.cancel.setTextColor(getSecondaryForegroundColorDependingOnTheme(this, mainColor));
- }
- } catch (Throwable t) {
- DeckLog.logError(t);
- }
- }
} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/StackAdapter.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/StackAdapter.java
index 89c702075..e3e0ad5b5 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/StackAdapter.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/preparecreate/StackAdapter.java
@@ -6,14 +6,12 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
-import org.jetbrains.annotations.NotNull;
-
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.ItemPrepareCreateStackBinding;
-import it.niedermann.nextcloud.deck.model.full.FullStack;
+import it.niedermann.nextcloud.deck.model.Stack;
-public class StackAdapter extends AbstractAdapter<FullStack> {
+public class StackAdapter extends AbstractAdapter<Stack> {
@SuppressWarnings("WeakerAccess")
public StackAdapter(@NonNull Context context) {
@@ -21,13 +19,13 @@ public class StackAdapter extends AbstractAdapter<FullStack> {
}
@Override
- protected long getItemId(@NonNull FullStack item) {
+ protected long getItemId(@NonNull Stack item) {
return item.getLocalId();
}
- @NotNull
+ @NonNull
@Override
- public View getView(int position, View convertView, @NotNull ViewGroup parent) {
+ public View getView(int position, View convertView, @NonNull ViewGroup parent) {
final ItemPrepareCreateStackBinding binding;
if (convertView == null) {
binding = ItemPrepareCreateStackBinding.inflate(inflater, parent, false);
@@ -35,9 +33,9 @@ public class StackAdapter extends AbstractAdapter<FullStack> {
binding = ItemPrepareCreateStackBinding.bind(convertView);
}
- final FullStack item = getItem(position);
+ final Stack item = getItem(position);
if (item != null) {
- binding.stackTitle.setText(item.getStack().getTitle());
+ binding.stackTitle.setText(item.getTitle());
} else {
DeckLog.logError(new IllegalArgumentException("No item for position " + position));
}
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 ea8d90f22..04429f225 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
@@ -23,6 +23,7 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Brande
private BrandedSwitchPreference wifiOnlyPref;
private BrandedSwitchPreference themePref;
private BrandedSwitchPreference brandingPref;
+ private BrandedSwitchPreference compactPref;
@Override
public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
@@ -67,6 +68,8 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Brande
DeckLog.error("Could not find preference with key: \"" + getString(R.string.pref_key_dark_theme) + "\"");
}
+ compactPref = findPreference(getString(R.string.pref_key_compact));
+
final ListPreference backgroundSyncPref = findPreference(getString(R.string.pref_key_background_sync));
if (backgroundSyncPref != null) {
backgroundSyncPref.setOnPreferenceChangeListener((Preference preference, Object newValue) -> {
@@ -92,5 +95,6 @@ public class SettingsFragment extends PreferenceFragmentCompat implements Brande
wifiOnlyPref.applyBrand(mainColor);
themePref.applyBrand(mainColor);
brandingPref.applyBrand(mainColor);
+ compactPref.applyBrand(mainColor);
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareProgressDialogFragment.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareProgressDialogFragment.java
index 271b60489..3028ea952 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareProgressDialogFragment.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/sharetarget/ShareProgressDialogFragment.java
@@ -14,15 +14,16 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.lifecycle.ViewModelProvider;
+import it.niedermann.nextcloud.deck.BuildConfig;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.DialogShareProgressBinding;
import it.niedermann.nextcloud.deck.exceptions.UploadAttachmentFailedException;
import it.niedermann.nextcloud.deck.ui.branding.BrandedDialogFragment;
import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
+import it.niedermann.nextcloud.exception.ExceptionUtil;
import static android.graphics.PorterDuff.Mode;
import static it.niedermann.nextcloud.deck.ui.branding.BrandingUtil.getSecondaryForegroundColorDependingOnTheme;
-import static it.niedermann.nextcloud.deck.util.ExceptionUtil.getDebugInfos;
public class ShareProgressDialogFragment extends BrandedDialogFragment {
@@ -70,7 +71,7 @@ public class ShareProgressDialogFragment extends BrandedDialogFragment {
binding.errorReportButton.setOnClickListener((v) -> {
final StringBuilder debugInfos = new StringBuilder(exceptionsCount + " attachments failed to upload:");
for (Throwable t : exceptions) {
- debugInfos.append(getDebugInfos(requireContext(), t, null));
+ debugInfos.append(ExceptionUtil.INSTANCE.getDebugInfos(requireContext(), t, BuildConfig.FLAVOR));
}
ExceptionDialogFragment.newInstance(new UploadAttachmentFailedException(debugInfos.toString()), null)
.show(getChildFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
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 a64629bd7..3a0005fb1 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
@@ -16,9 +16,9 @@ import androidx.lifecycle.ViewModelProvider;
import com.nextcloud.android.sso.exceptions.NextcloudHttpRequestFailedException;
import java.io.File;
+import java.time.Instant;
import java.util.ArrayList;
import java.util.Collections;
-import java.util.Date;
import java.util.List;
import it.niedermann.nextcloud.deck.DeckLog;
@@ -126,7 +126,7 @@ public class ShareTargetActivity extends MainActivity implements SelectCardListe
throw new IllegalArgumentException("MimeType of uri is null. [" + uri + "]");
}
runOnUiThread(() -> {
- final WrappedLiveData<Attachment> liveData = syncManager.addAttachmentToCard(fullCard.getAccountId(), fullCard.getCard().getLocalId(), mimeType, tempFile);
+ 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) {
@@ -160,7 +160,7 @@ public class ShareTargetActivity extends MainActivity implements SelectCardListe
? receivedText
: oldDescription + "\n\n" + receivedText
);
- WrappedLiveData<FullCard> liveData = syncManager.updateCard(fullCard);
+ WrappedLiveData<FullCard> liveData = mainViewModel.updateCard(fullCard);
observeOnce(liveData, this, (next) -> {
if (liveData.hasError()) {
cardSelected = false;
@@ -173,8 +173,8 @@ public class ShareTargetActivity extends MainActivity implements SelectCardListe
break;
case 1:
final Account currentAccount = mainViewModel.getCurrentAccount();
- final DeckComment comment = new DeckComment(receivedText.trim(), currentAccount.getUserName(), new Date());
- syncManager.addCommentToCard(currentAccount.getId(), fullCard.getLocalId(), comment);
+ final DeckComment comment = new DeckComment(receivedText.trim(), currentAccount.getUserName(), Instant.now());
+ mainViewModel.addCommentToCard(currentAccount.getId(), fullCard.getLocalId(), comment);
Toast.makeText(getApplicationContext(), getString(R.string.share_success, "\"" + receivedText + "\"", "\"" + fullCard.getCard().getTitle() + "\""), Toast.LENGTH_LONG).show();
finish();
break;
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 e2cc83372..8fc56759f 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
@@ -8,11 +8,11 @@ import androidx.viewpager2.adapter.FragmentStateAdapter;
import java.util.ArrayList;
import java.util.List;
-import it.niedermann.nextcloud.deck.model.full.FullStack;
+import it.niedermann.nextcloud.deck.model.Stack;
public class StackAdapter extends FragmentStateAdapter {
@NonNull
- private List<FullStack> stackList = new ArrayList<>();
+ private final List<Stack> stackList = new ArrayList<>();
public StackAdapter(@NonNull FragmentActivity fragmentActivity) {
super(fragmentActivity);
@@ -23,7 +23,7 @@ public class StackAdapter extends FragmentStateAdapter {
return stackList.size();
}
- public FullStack getItem(int position) {
+ public Stack getItem(int position) {
return stackList.get(position);
}
@@ -34,7 +34,7 @@ public class StackAdapter extends FragmentStateAdapter {
@Override
public boolean containsItem(long itemId) {
- for (FullStack stack : stackList) {
+ for (Stack stack : stackList) {
if (stack.getLocalId() == itemId) {
return true;
}
@@ -48,9 +48,9 @@ public class StackAdapter extends FragmentStateAdapter {
return StackFragment.newInstance(stackList.get(position).getLocalId());
}
- public void setStacks(@NonNull List<FullStack> fullStacks) {
+ public void setStacks(@NonNull List<Stack> stacks) {
this.stackList.clear();
- this.stackList.addAll(fullStacks);
+ this.stackList.addAll(stacks);
notifyDataSetChanged();
}
} \ No newline at end of file
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 726a74184..313e6e821 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
@@ -20,20 +20,27 @@ import java.util.List;
import it.niedermann.android.crosstabdnd.DragAndDropTab;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.databinding.FragmentStackBinding;
+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.BrandedFragment;
import it.niedermann.nextcloud.deck.ui.card.CardAdapter;
import it.niedermann.nextcloud.deck.ui.card.SelectCardListener;
+import it.niedermann.nextcloud.deck.ui.exception.ExceptionDialogFragment;
import it.niedermann.nextcloud.deck.ui.filter.FilterViewModel;
+import it.niedermann.nextcloud.deck.ui.movecard.MoveCardListener;
-public class StackFragment extends BrandedFragment implements DragAndDropTab<CardAdapter> {
+import static it.niedermann.nextcloud.deck.persistence.sync.adapters.db.util.LiveDataHelper.observeOnce;
+
+public class StackFragment extends BrandedFragment implements DragAndDropTab<CardAdapter>, MoveCardListener {
private static final String KEY_STACK_ID = "stackId";
private FragmentStackBinding binding;
- private SyncManager syncManager;
+ private MainViewModel mainViewModel;
private FragmentActivity activity;
private OnScrollListener onScrollListener;
@@ -61,10 +68,10 @@ public class StackFragment extends BrandedFragment implements DragAndDropTab<Car
@Override
public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
- binding = FragmentStackBinding.inflate(inflater, container, false);
activity = requireActivity();
+ binding = FragmentStackBinding.inflate(inflater, container, false);
+ mainViewModel = new ViewModelProvider(activity).get(MainViewModel.class);
- final MainViewModel mainViewModel = new ViewModelProvider(activity).get(MainViewModel.class);
final FilterViewModel filterViewModel = new ViewModelProvider(activity).get(FilterViewModel.class);
// This might be a zombie fragment with an empty MainViewModel after Android killed the activity (but not the fragment instance
@@ -74,19 +81,10 @@ public class StackFragment extends BrandedFragment implements DragAndDropTab<Car
return binding.getRoot();
}
- syncManager = new SyncManager(activity);
-
- adapter = new CardAdapter(
- requireContext(),
- getChildFragmentManager(),
- mainViewModel.getCurrentAccount(),
- mainViewModel.getCurrentBoardLocalId(),
- mainViewModel.getCurrentBoardRemoteId(),
- stackId,
- mainViewModel.currentBoardHasEditPermission(),
- syncManager,
- this,
- (requireActivity() instanceof SelectCardListener) ? (SelectCardListener) requireActivity() : null);
+ adapter = new CardAdapter(requireContext(), getChildFragmentManager(), stackId, mainViewModel, this,
+ (requireActivity() instanceof SelectCardListener)
+ ? (SelectCardListener) requireActivity()
+ : null);
binding.recyclerView.setAdapter(adapter);
if (onScrollListener != null) {
@@ -114,12 +112,12 @@ public class StackFragment extends BrandedFragment implements DragAndDropTab<Car
}
});
- cardsLiveData = syncManager.getFullCardsForStack(mainViewModel.getCurrentAccount().getId(), stackId, filterViewModel.getFilterInformation().getValue());
+ cardsLiveData = mainViewModel.getFullCardsForStack(mainViewModel.getCurrentAccount().getId(), stackId, filterViewModel.getFilterInformation().getValue());
cardsLiveData.observe(getViewLifecycleOwner(), cardsObserver);
filterViewModel.getFilterInformation().observe(getViewLifecycleOwner(), (filterInformation -> {
cardsLiveData.removeObserver(cardsObserver);
- cardsLiveData = syncManager.getFullCardsForStack(mainViewModel.getCurrentAccount().getId(), stackId, filterInformation);
+ cardsLiveData = mainViewModel.getFullCardsForStack(mainViewModel.getCurrentAccount().getId(), stackId, filterInformation);
cardsLiveData.observe(getViewLifecycleOwner(), cardsObserver);
}));
@@ -153,4 +151,17 @@ public class StackFragment extends BrandedFragment implements DragAndDropTab<Car
return fragment;
}
+
+ @Override
+ public void move(long originAccountId, long originCardLocalId, long targetAccountId, long targetBoardLocalId, long targetStackLocalId) {
+ final WrappedLiveData<Void> liveData = mainViewModel.moveCard(originAccountId, originCardLocalId, targetAccountId, targetBoardLocalId, targetStackLocalId);
+ observeOnce(liveData, requireActivity(), (next) -> {
+ 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 + "\"");
+ }
+ });
+ }
+
} \ No newline at end of file
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
new file mode 100644
index 000000000..af17464dc
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoActivity.java
@@ -0,0 +1,182 @@
+package it.niedermann.nextcloud.deck.ui.takephoto;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.ColorStateList;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Size;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.View;
+
+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;
+import androidx.camera.core.Preview;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.ViewModelProvider;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.io.File;
+import java.time.Instant;
+import java.time.ZoneId;
+import java.time.format.DateTimeFormatter;
+import java.util.concurrent.ExecutionException;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+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 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;
+ private TakePhotoViewModel viewModel;
+
+ private View[] brandedViews;
+
+ private ListenableFuture<ProcessCameraProvider> cameraProviderFuture;
+ private OrientationEventListener orientationEventListener;
+
+ private final DateTimeFormatter fileNameFromCameraFormatter = DateTimeFormatter.ofPattern("'JPG_'yyyyMMdd'_'HHmmss'.jpg'");
+
+ @Override
+ protected void onCreate(@Nullable Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Thread.currentThread().setUncaughtExceptionHandler(new ExceptionHandler(this));
+
+ binding = ActivityTakePhotoBinding.inflate(getLayoutInflater());
+ viewModel = new ViewModelProvider(this).get(TakePhotoViewModel.class);
+
+ setContentView(binding.getRoot());
+
+ cameraProviderFuture = ProcessCameraProvider.getInstance(this);
+ cameraProviderFuture.addListener(() -> {
+ try {
+ final ProcessCameraProvider cameraProvider = cameraProviderFuture.get();
+ final Preview previewUseCase = getPreviewUseCase();
+ final ImageCapture captureUseCase = getCaptureUseCase();
+ final Camera camera = cameraProvider.bindToLifecycle(this, viewModel.getCameraSelector(), captureUseCase, previewUseCase);
+
+ viewModel.getCameraSelectorToggleButtonImageResource().observe(this, res -> binding.switchCamera.setImageDrawable(ContextCompat.getDrawable(this, res)));
+ viewModel.getTorchToggleButtonImageResource().observe(this, res -> binding.toggleTorch.setImageDrawable(ContextCompat.getDrawable(this, res)));
+ viewModel.isTorchEnabled().observe(this, enabled -> camera.getCameraControl().enableTorch(enabled));
+
+ binding.toggleTorch.setOnClickListener((v) -> viewModel.toggleTorchEnabled());
+ binding.switchCamera.setOnClickListener((v) -> {
+ viewModel.toggleCameraSelector();
+ cameraProvider.unbindAll();
+ cameraProvider.bindToLifecycle(this, viewModel.getCameraSelector(), captureUseCase, previewUseCase);
+ });
+ } catch (ExecutionException | InterruptedException e) {
+ DeckLog.logError(e);
+ finish();
+ }
+ }, ContextCompat.getMainExecutor(this));
+
+ brandedViews = new View[]{binding.takePhoto, binding.switchCamera, binding.toggleTorch};
+ }
+
+ private ImageCapture getCaptureUseCase() {
+ final ImageCapture captureUseCase = new ImageCapture.Builder().setTargetResolution(new Size(720, 1280)).build();
+
+ orientationEventListener = new OrientationEventListener(this) {
+ @Override
+ public void onOrientationChanged(int orientation) {
+ int rotation;
+
+ // Monitors orientation values to determine the target rotation value
+ if (orientation >= 45 && orientation < 135) {
+ rotation = Surface.ROTATION_270;
+ } else if (orientation >= 135 && orientation < 225) {
+ rotation = Surface.ROTATION_180;
+ } else if (orientation >= 225 && orientation < 315) {
+ rotation = Surface.ROTATION_90;
+ } else {
+ rotation = Surface.ROTATION_0;
+ }
+
+ captureUseCase.setTargetRotation(rotation);
+ }
+ };
+ orientationEventListener.enable();
+
+ binding.takePhoto.setOnClickListener((v) -> {
+ binding.takePhoto.setEnabled(false);
+ final String photoFileName = Instant.now().atZone(ZoneId.systemDefault()).format(fileNameFromCameraFormatter);
+ try {
+ final File photoFile = AttachmentUtil.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());
+ setResult(RESULT_OK, new Intent().setDataAndType(savedUri, IMAGE_JPEG));
+ finish();
+ }
+
+ @Override
+ public void onError(@NonNull ImageCaptureException e) {
+ e.printStackTrace();
+ //noinspection ResultOfMethodCallIgnored
+ photoFile.delete();
+ binding.takePhoto.setEnabled(true);
+ }
+ });
+ } catch (Exception e) {
+ ExceptionDialogFragment.newInstance(e, null).show(getSupportFragmentManager(), ExceptionDialogFragment.class.getSimpleName());
+ }
+ });
+
+ return captureUseCase;
+ }
+
+ private Preview getPreviewUseCase() {
+ Preview previewUseCase = new Preview.Builder().build();
+ previewUseCase.setSurfaceProvider(binding.preview.getSurfaceProvider());
+ return previewUseCase;
+ }
+
+ @Override
+ protected void onPause() {
+ if (this.orientationEventListener != null) {
+ this.orientationEventListener.disable();
+ }
+ super.onPause();
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ if (this.orientationEventListener != null) {
+ this.orientationEventListener.enable();
+ }
+ }
+
+ @RequiresApi(LOLLIPOP)
+ public static Intent createIntent(@NonNull Context context) {
+ return new Intent(context, TakePhotoActivity.class).setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
+ }
+
+ @Override
+ public void applyBrand(int mainColor) {
+ final ColorStateList colorStateList = ColorStateList.valueOf(mainColor);
+ for (View v : brandedViews) {
+ v.setBackgroundTintList(colorStateList);
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoViewModel.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoViewModel.java
new file mode 100644
index 000000000..a71291ff2
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/takephoto/TakePhotoViewModel.java
@@ -0,0 +1,57 @@
+package it.niedermann.nextcloud.deck.ui.takephoto;
+
+import androidx.annotation.NonNull;
+import androidx.camera.core.CameraSelector;
+import androidx.lifecycle.LiveData;
+import androidx.lifecycle.MutableLiveData;
+import androidx.lifecycle.Transformations;
+import androidx.lifecycle.ViewModel;
+
+import it.niedermann.nextcloud.deck.R;
+
+import static androidx.camera.core.CameraSelector.DEFAULT_BACK_CAMERA;
+import static androidx.camera.core.CameraSelector.DEFAULT_FRONT_CAMERA;
+
+public class TakePhotoViewModel extends ViewModel {
+
+ @NonNull
+ private CameraSelector cameraSelector = DEFAULT_BACK_CAMERA;
+ @NonNull
+ private final MutableLiveData<Integer> cameraSelectorToggleButtonImageResource = new MutableLiveData<>(R.drawable.ic_baseline_camera_front_24);
+ @NonNull
+ private final MutableLiveData<Boolean> torchEnabled = new MutableLiveData<>(false);
+
+ @NonNull
+ public CameraSelector getCameraSelector() {
+ return this.cameraSelector;
+ }
+
+ public LiveData<Integer> getCameraSelectorToggleButtonImageResource() {
+ return this.cameraSelectorToggleButtonImageResource;
+ }
+
+ public void toggleCameraSelector() {
+ if (this.cameraSelector == DEFAULT_BACK_CAMERA) {
+ this.cameraSelector = DEFAULT_FRONT_CAMERA;
+ this.cameraSelectorToggleButtonImageResource.postValue(R.drawable.ic_baseline_camera_rear_24);
+ } else {
+ this.cameraSelector = DEFAULT_BACK_CAMERA;
+ this.cameraSelectorToggleButtonImageResource.postValue(R.drawable.ic_baseline_camera_front_24);
+ }
+ }
+
+ public void toggleTorchEnabled() {
+ //noinspection ConstantConditions
+ this.torchEnabled.postValue(!this.torchEnabled.getValue());
+ }
+
+ public LiveData<Boolean> isTorchEnabled() {
+ return this.torchEnabled;
+ }
+
+ public LiveData<Integer> getTorchToggleButtonImageResource() {
+ return Transformations.map(isTorchEnabled(), enabled -> enabled
+ ? R.drawable.ic_baseline_flash_off_24
+ : R.drawable.ic_baseline_flash_on_24);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/ColorChooser.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/ColorChooser.java
index 30dc0ada4..0dd431ff9 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/ColorChooser.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/ColorChooser.java
@@ -2,6 +2,7 @@ package it.niedermann.nextcloud.deck.ui.view;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Color;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -9,31 +10,31 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
+import androidx.annotation.ColorInt;
import androidx.annotation.Nullable;
+import androidx.core.content.ContextCompat;
import com.google.android.flexbox.FlexboxLayout;
import com.skydoves.colorpickerview.listeners.ColorEnvelopeListener;
+import java.util.Arrays;
+
+import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.databinding.WidgetColorChooserBinding;
import it.niedermann.nextcloud.deck.util.ViewUtil;
-import static it.niedermann.nextcloud.deck.util.DimensionUtil.dpToPx;
-
public class ColorChooser extends LinearLayout {
- private WidgetColorChooserBinding binding;
-
- private final FlexboxLayout.LayoutParams params = new FlexboxLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
- );
+ private final WidgetColorChooserBinding binding;
- private Context context;
- private String[] colors;
+ private final Context context;
+ private final int[] colors;
- private String selectedColor;
- private String previouslySelectedColor;
+ @ColorInt
+ private int selectedColor;
+ @ColorInt
+ private int previouslySelectedColor;
@Nullable
private ImageView previouslySelectedImageView;
@@ -41,17 +42,22 @@ public class ColorChooser extends LinearLayout {
super(context, attrs);
this.context = context;
- params.setMargins(0, dpToPx(context, R.dimen.spacer_1x), 0, 0);
+ final FlexboxLayout.LayoutParams params = new FlexboxLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+ params.setMargins(0, DimensionUtil.INSTANCE.dpToPx(context, R.dimen.spacer_1x), 0, 0);
params.setFlexBasisPercent(.15f);
- TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.ColorChooser, 0, 0);
- colors = getResources().getStringArray(a.getResourceId(R.styleable.ColorChooser_colors, 0));
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.ColorChooser, 0, 0);
+ colors = Arrays.stream(getResources().getStringArray(a.getResourceId(R.styleable.ColorChooser_colors, 0)))
+ .mapToInt(Color::parseColor)
+ .toArray();
a.recycle();
binding = WidgetColorChooserBinding.inflate(LayoutInflater.from(context), this, true);
- for (final String color : colors) {
- ImageView image = new ImageView(getContext());
+ for (final int color : colors) {
+ final ImageView image = new ImageView(getContext());
image.setLayoutParams(params);
image.setOnClickListener((imageView) -> {
if (previouslySelectedImageView != null) { // null when first selection
@@ -61,7 +67,7 @@ public class ColorChooser extends LinearLayout {
selectedColor = color;
this.previouslySelectedColor = color;
this.previouslySelectedImageView = image;
- binding.customColorChooser.setImageDrawable(ViewUtil.getTintedImageView(this.context, R.drawable.circle_alpha_colorize_36dp, R.color.board_default_custom_color));
+ binding.customColorChooser.setImageDrawable(ViewUtil.getTintedImageView(this.context, R.drawable.circle_alpha_colorize_36dp, ContextCompat.getColor(context, R.color.board_default_custom_color)));
binding.customColorPicker.setVisibility(View.GONE);
binding.brightnessSlide.setVisibility(View.GONE);
});
@@ -84,19 +90,20 @@ public class ColorChooser extends LinearLayout {
previouslySelectedImageView.setImageDrawable(ViewUtil.getTintedImageView(this.context, R.drawable.circle_grey600_36dp, previouslySelectedColor));
previouslySelectedImageView = null;
}
- String customColor = "#" + envelope.getHexCode().substring(2);
+ @ColorInt
+ final int customColor = envelope.getColor();
selectedColor = customColor;
previouslySelectedColor = customColor;
binding.customColorChooser.setImageDrawable(ViewUtil.getTintedImageView(context, R.drawable.circle_alpha_colorize_36dp, selectedColor));
});
}
- public void selectColor(String newColor) {
+ public void selectColor(@ColorInt int newColor) {
boolean newColorIsCustomColor = true;
selectedColor = newColor;
for (int i = 0; i < colors.length; i++) {
- if (colors[i].equals(newColor)) {
- binding.customColorChooser.setImageDrawable(ViewUtil.getTintedImageView(this.context, R.drawable.circle_alpha_colorize_36dp, R.color.board_default_custom_color));
+ if (colors[i] == newColor) {
+ binding.customColorChooser.setImageDrawable(ViewUtil.getTintedImageView(this.context, R.drawable.circle_alpha_colorize_36dp, ContextCompat.getColor(context, R.color.board_default_custom_color)));
binding.colorPicker.getChildAt(i).performClick();
newColorIsCustomColor = false;
break;
@@ -107,7 +114,8 @@ public class ColorChooser extends LinearLayout {
}
}
- public String getSelectedColor() {
+ @ColorInt
+ public int getSelectedColor() {
return this.selectedColor;
}
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/OverlappingAvatars.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/OverlappingAvatars.java
index 501d33106..0facc385c 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/OverlappingAvatars.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/OverlappingAvatars.java
@@ -10,6 +10,7 @@ import android.widget.RelativeLayout;
import androidx.annotation.NonNull;
import androidx.annotation.Px;
+import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import com.bumptech.glide.Glide;
@@ -17,12 +18,11 @@ import com.bumptech.glide.request.RequestOptions;
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.User;
-import static it.niedermann.nextcloud.deck.util.DimensionUtil.dpToPx;
-
public class OverlappingAvatars extends RelativeLayout {
final int maxAvatarCount;
@Px
@@ -44,11 +44,12 @@ public class OverlappingAvatars extends RelativeLayout {
public OverlappingAvatars(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
maxAvatarCount = context.getResources().getInteger(R.integer.max_avatar_count);
- avatarBorderSize = dpToPx(context, R.dimen.avatar_size_small_overlapping_border);
- avatarSize = dpToPx(context, R.dimen.avatar_size_small) + avatarBorderSize * 2;
- overlapPx = dpToPx(context, R.dimen.avatar_size_small_overlapping);
- borderDrawable = getResources().getDrawable(R.drawable.avatar_border);
- DrawableCompat.setTint(borderDrawable, getResources().getColor(R.color.bg_card));
+ avatarBorderSize = DimensionUtil.INSTANCE.dpToPx(context, R.dimen.avatar_size_small_overlapping_border);
+ avatarSize = DimensionUtil.INSTANCE.dpToPx(context, R.dimen.avatar_size_small) + avatarBorderSize * 2;
+ overlapPx = DimensionUtil.INSTANCE.dpToPx(context, R.dimen.avatar_size_small_overlapping);
+ borderDrawable = ContextCompat.getDrawable(context, R.drawable.avatar_border);
+ assert borderDrawable != null;
+ DrawableCompat.setTint(borderDrawable, ContextCompat.getColor(context, R.color.bg_card));
}
public void setAvatars(@NonNull Account account, @NonNull List<User> assignedUsers) {
@@ -70,6 +71,7 @@ public class OverlappingAvatars extends RelativeLayout {
avatar.requestLayout();
Glide.with(context)
.load(account.getUrl() + "/index.php/avatar/" + Uri.encode(assignedUsers.get(avatarCount).getUid()) + "/" + avatarSize)
+ .placeholder(R.drawable.ic_person_grey600_24dp)
.error(R.drawable.ic_person_grey600_24dp)
.apply(RequestOptions.circleCropTransform())
.into(avatar);
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
new file mode 100644
index 000000000..0912a07dd
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/SquareConstraintLayout.java
@@ -0,0 +1,35 @@
+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;
+
+public class SquareConstraintLayout extends ConstraintLayout {
+
+ public SquareConstraintLayout(Context context) {
+ super(context);
+ }
+
+ public SquareConstraintLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public SquareConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @TargetApi(Build.VERSION_CODES.LOLLIPOP)
+ public SquareConstraintLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ // Set a square layout.
+ super.onMeasure(widthMeasureSpec, widthMeasureSpec);
+ }
+
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/CompactLabelChip.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/CompactLabelChip.java
new file mode 100644
index 000000000..a2a50430c
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/CompactLabelChip.java
@@ -0,0 +1,21 @@
+package it.niedermann.nextcloud.deck.ui.view.labelchip;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Px;
+
+import it.niedermann.android.util.DimensionUtil;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.Label;
+
+@SuppressLint("ViewConstructor")
+public class CompactLabelChip extends LabelChip {
+
+ public CompactLabelChip(@NonNull Context context, @NonNull Label label, @Px int gutter) {
+ super(context, label, gutter);
+ params.setFlexBasisPercent(1 / 6.5f);
+ setHeight(DimensionUtil.INSTANCE.dpToPx(context, R.dimen.compact_label_height));
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/DefaultLabelChip.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/DefaultLabelChip.java
new file mode 100644
index 000000000..80e44d7e0
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/DefaultLabelChip.java
@@ -0,0 +1,21 @@
+package it.niedermann.nextcloud.deck.ui.view.labelchip;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Px;
+
+import it.niedermann.nextcloud.deck.model.Label;
+
+import static android.text.TextUtils.TruncateAt.MIDDLE;
+
+@SuppressLint("ViewConstructor")
+public class DefaultLabelChip extends LabelChip {
+
+ public DefaultLabelChip(@NonNull Context context, @NonNull Label label, @Px int gutter) {
+ super(context, label, gutter);
+ setText(label.getTitle());
+ setEllipsize(MIDDLE);
+ }
+} \ No newline at end of file
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/LabelChip.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/LabelChip.java
index db4e123d3..83853f0b2 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/LabelChip.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labelchip/LabelChip.java
@@ -1,9 +1,8 @@
-package it.niedermann.nextcloud.deck.ui.view;
+package it.niedermann.nextcloud.deck.ui.view.labelchip;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.ColorStateList;
-import android.graphics.Color;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
@@ -12,26 +11,24 @@ import androidx.annotation.Px;
import com.google.android.flexbox.FlexboxLayout;
import com.google.android.material.chip.Chip;
+import it.niedermann.android.util.ColorUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.model.Label;
-import it.niedermann.nextcloud.deck.util.ColorUtil;
-
-import static android.text.TextUtils.TruncateAt.MIDDLE;
@SuppressLint("ViewConstructor")
public class LabelChip extends Chip {
private final Label label;
+ protected final FlexboxLayout.LayoutParams params = new FlexboxLayout.LayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT
+ );
+
public LabelChip(@NonNull Context context, @NonNull Label label, @Px int gutter) {
super(context);
this.label = label;
- FlexboxLayout.LayoutParams params = new FlexboxLayout.LayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT
- );
-
params.setMargins(0, 0, gutter, 0);
setLayoutParams(params);
setEnsureMinTouchTargetSize(false);
@@ -42,15 +39,13 @@ public class LabelChip extends Chip {
setTextStartPadding(gutter);
setTextEndPadding(gutter);
setChipEndPadding(gutter);
-
- setText(label.getTitle());
- setEllipsize(MIDDLE);
+ setClickable(false);
try {
- int labelColor = Color.parseColor("#" + label.getColor());
+ int labelColor = label.getColor();
ColorStateList c = ColorStateList.valueOf(labelColor);
setChipBackgroundColor(c);
- setTextColor(ColorUtil.getForegroundColorForBackgroundColor(labelColor));
+ setTextColor(ColorUtil.INSTANCE.getForegroundColorForBackgroundColor(labelColor));
} catch (IllegalArgumentException e) {
DeckLog.logError(e);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/CompactLabelLayout.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/CompactLabelLayout.java
new file mode 100644
index 000000000..1c5e35d97
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/CompactLabelLayout.java
@@ -0,0 +1,22 @@
+package it.niedermann.nextcloud.deck.ui.view.labellayout;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+
+import it.niedermann.nextcloud.deck.model.Label;
+import it.niedermann.nextcloud.deck.ui.view.labelchip.CompactLabelChip;
+import it.niedermann.nextcloud.deck.ui.view.labelchip.LabelChip;
+
+public class CompactLabelLayout extends LabelLayout {
+
+ public CompactLabelLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected LabelChip createLabelChip(@NonNull Label label) {
+ return new CompactLabelChip(getContext(), label, gutter);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/DefaultLabelLayout.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/DefaultLabelLayout.java
new file mode 100644
index 000000000..f2d6d0752
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/DefaultLabelLayout.java
@@ -0,0 +1,21 @@
+package it.niedermann.nextcloud.deck.ui.view.labellayout;
+
+import android.content.Context;
+import android.util.AttributeSet;
+
+import androidx.annotation.NonNull;
+
+import it.niedermann.nextcloud.deck.model.Label;
+import it.niedermann.nextcloud.deck.ui.view.labelchip.DefaultLabelChip;
+
+public class DefaultLabelLayout extends LabelLayout {
+
+ public DefaultLabelLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ @Override
+ protected DefaultLabelChip createLabelChip(@NonNull Label label) {
+ return new DefaultLabelChip(getContext(), label, gutter);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/LabelLayout.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/LabelLayout.java
index 814c63ce1..3db539e53 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/LabelLayout.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/view/labellayout/LabelLayout.java
@@ -1,4 +1,4 @@
-package it.niedermann.nextcloud.deck.ui.view;
+package it.niedermann.nextcloud.deck.ui.view.labellayout;
import android.content.Context;
import android.util.AttributeSet;
@@ -11,21 +11,22 @@ import com.google.android.flexbox.FlexboxLayout;
import java.util.LinkedList;
import java.util.List;
+import it.niedermann.android.util.DimensionUtil;
import it.niedermann.nextcloud.deck.DeckLog;
import it.niedermann.nextcloud.deck.R;
import it.niedermann.nextcloud.deck.model.Label;
+import it.niedermann.nextcloud.deck.ui.view.labelchip.LabelChip;
-import static it.niedermann.nextcloud.deck.util.DimensionUtil.dpToPx;
-
-public class LabelLayout extends FlexboxLayout {
+public abstract class LabelLayout extends FlexboxLayout {
@Px
- private int gutter;
- private List<LabelChip> chipList = new LinkedList<>();
+ final protected int gutter;
+ @NonNull
+ final private List<LabelChip> chipList = new LinkedList<>();
public LabelLayout(Context context, AttributeSet attrs) {
super(context, attrs);
- this.gutter = dpToPx(context, R.dimen.spacer_1hx);
+ this.gutter = DimensionUtil.INSTANCE.dpToPx(context, R.dimen.spacer_1hx);
}
/**
@@ -82,9 +83,11 @@ public class LabelLayout extends FlexboxLayout {
continue labelList;
}
}
- LabelChip chip = new LabelChip(getContext(), label, gutter);
+ final LabelChip chip = createLabelChip(label);
addView(chip);
chipList.add(chip);
}
}
+
+ protected abstract LabelChip createLabelChip(@NonNull Label label);
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java
index 8c1fbe1fa..fe6e969e7 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/singlecard/SelectCardForWidgetActivity.java
@@ -45,7 +45,7 @@ public class SelectCardForWidgetActivity extends MainActivity implements SelectC
@Override
public void onCardSelected(FullCard fullCard) {
- syncManager.addOrUpdateSingleCardWidget(appWidgetId, mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId(), fullCard.getLocalId());
+ mainViewModel.addOrUpdateSingleCardWidget(appWidgetId, mainViewModel.getCurrentAccount().getId(), mainViewModel.getCurrentBoardLocalId(), fullCard.getLocalId());
final Intent updateIntent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE, null,
getApplicationContext(), SingleCardWidget.class)
.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
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 783f98e00..b44b80d1b 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
@@ -43,7 +43,7 @@ public class SingleCardWidget extends AppWidgetProvider {
views.setTextViewText(R.id.description, fullModel.getFullCard().getCard().getDescription());
if (fullModel.getFullCard().getCard().getDueDate() != null) {
- views.setTextViewText(R.id.card_due_date, DateUtil.getRelativeDateTimeString(context, fullModel.getFullCard().getCard().getDueDate().getTime()));
+ views.setTextViewText(R.id.card_due_date, DateUtil.getRelativeDateTimeString(context, fullModel.getFullCard().getCard().getDueDate().toEpochMilli()));
// TODO Use multiple views for background colors and only set the necessary to View.VISIBLE
// https://stackoverflow.com/a/3376537
// Because otherwise using Reflection is the only way
@@ -141,13 +141,10 @@ public class SingleCardWidget extends AppWidgetProvider {
super.onDeleted(context, appWidgetIds);
}
-
/**
* Updates UI data of all {@link SingleCardWidget} instances
*/
public static void notifyDatasetChanged(Context context) {
- Intent intent = new Intent(context, SingleCardWidget.class);
- intent.setAction("android.appwidget.action.APPWIDGET_UPDATE");
- context.sendBroadcast(intent);
+ context.sendBroadcast(new Intent(context, SingleCardWidget.class).setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE));
}
}
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
new file mode 100644
index 000000000..cb179953c
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidget.java
@@ -0,0 +1,121 @@
+package it.niedermann.nextcloud.deck.ui.widget.stack;
+
+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.os.Bundle;
+import android.widget.RemoteViews;
+
+import java.util.NoSuchElementException;
+
+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.persistence.sync.SyncManager;
+import it.niedermann.nextcloud.deck.ui.MainActivity;
+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);
+ }
+
+ @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);
+ }
+ }
+ }
+ }
+ }
+
+ @Override
+ public void onDeleted(Context context, int[] appWidgetIds) {
+ super.onDeleted(context, appWidgetIds);
+ final SyncManager syncManager = new SyncManager(context);
+
+ for (int appWidgetId : appWidgetIds) {
+ syncManager.deleteStackWidgetModel(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));
+ }
+}
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
new file mode 100644
index 000000000..96b5cf672
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationActivity.java
@@ -0,0 +1,68 @@
+package it.niedermann.nextcloud.deck.ui.widget.stack;
+
+import android.appwidget.AppWidgetManager;
+import android.content.Intent;
+import android.os.Bundle;
+
+import androidx.appcompat.app.ActionBar;
+import androidx.lifecycle.ViewModelProvider;
+
+import it.niedermann.nextcloud.deck.DeckLog;
+import it.niedermann.nextcloud.deck.R;
+import it.niedermann.nextcloud.deck.model.Account;
+import it.niedermann.nextcloud.deck.ui.PickStackActivity;
+
+public class StackWidgetConfigurationActivity extends PickStackActivity {
+ private int appWidgetId;
+ private StackWidgetConfigurationViewModel stackWidgetConfigurationViewModel;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ stackWidgetConfigurationViewModel = new ViewModelProvider(this).get(StackWidgetConfigurationViewModel.class);
+
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null) {
+ actionBar.setTitle(R.string.add_stack_widget);
+ }
+
+ setResult(RESULT_CANCELED);
+ final Bundle extras = getIntent().getExtras();
+
+ if (extras != null) {
+ appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID,
+ AppWidgetManager.INVALID_APPWIDGET_ID);
+ }
+
+ if (appWidgetId == AppWidgetManager.INVALID_APPWIDGET_ID) {
+ DeckLog.error("INVALID_APPWIDGET_ID");
+ finish();
+ }
+ }
+
+ @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();
+ }
+
+ @Override
+ protected boolean showBoardsWithoutEditPermission() {
+ return true;
+ }
+}
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
new file mode 100644
index 000000000..cc669accf
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetConfigurationViewModel.java
@@ -0,0 +1,23 @@
+package it.niedermann.nextcloud.deck.ui.widget.stack;
+
+import android.app.Application;
+
+import androidx.annotation.NonNull;
+import androidx.lifecycle.AndroidViewModel;
+
+import it.niedermann.nextcloud.deck.persistence.sync.SyncManager;
+
+@SuppressWarnings("WeakerAccess")
+public class StackWidgetConfigurationViewModel extends AndroidViewModel {
+
+ private final SyncManager syncManager;
+
+ public StackWidgetConfigurationViewModel(@NonNull Application application) {
+ super(application);
+ this.syncManager = new SyncManager(application);
+ }
+
+ public void addStackWidget(int appWidgetId, long accountId, long stackId, boolean darkTheme) {
+ syncManager.addStackWidget(appWidgetId, accountId, stackId, darkTheme);
+ }
+}
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
new file mode 100644
index 000000000..87f357e20
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetFactory.java
@@ -0,0 +1,134 @@
+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 java.util.List;
+
+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.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 Account account;
+ private FullStack stack;
+ private List<FullCard> cardList;
+
+ 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);
+ }
+ }
+
+ @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);
+ }
+ });
+ }
+
+ @Override
+ public void onDataSetChanged() {
+
+ }
+
+
+ @Override
+ public void onDestroy() {
+
+ }
+
+ @Override
+ public int getCount() {
+ return stack == null ? 0 : stack.getCards().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);
+ return null;
+ }
+
+ FullCard card = cardList.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());
+
+ final Intent intent = EditActivity.createEditCardIntent(context, account, stack.getStack().getBoardId(), card.getCard().getLocalId());
+ 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 1;
+ }
+
+ @Override
+ public long getItemId(int i) {
+ return i;
+ }
+
+ @Override
+ public boolean hasStableIds() {
+ return true;
+ }
+
+ private void notifyAppWidgetUpdate(RemoteViews views) {
+ AppWidgetManager awm = AppWidgetManager.getInstance(context);
+ int[] appWidgetIds = awm.getAppWidgetIds(new ComponentName(context, StackWidget.class));
+ awm.notifyAppWidgetViewDataChanged(appWidgetIds, R.id.stack_widget_lv);
+ awm.updateAppWidget(appWidgetId, views);
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetService.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetService.java
new file mode 100644
index 000000000..9299a96e2
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/widget/stack/StackWidgetService.java
@@ -0,0 +1,11 @@
+package it.niedermann.nextcloud.deck.ui.widget.stack;
+
+import android.content.Intent;
+import android.widget.RemoteViewsService;
+
+public class StackWidgetService extends RemoteViewsService {
+ @Override
+ public RemoteViewsFactory onGetViewFactory(Intent intent) {
+ return new StackWidgetFactory(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 6b2d6925f..ef3e14b37 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
@@ -1,9 +1,16 @@
package it.niedermann.nextcloud.deck.util;
import android.content.Context;
+import android.content.Intent;
import android.net.Uri;
+import android.text.TextUtils;
+import android.webkit.MimeTypeMap;
+import android.widget.Toast;
+import androidx.annotation.DrawableRes;
import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.Px;
import java.io.File;
import java.io.FileNotFoundException;
@@ -12,6 +19,9 @@ 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.Attachment;
+import it.niedermann.nextcloud.deck.model.ocs.Version;
/**
* Created by stefan on 07.03.20.
@@ -22,38 +32,121 @@ public class AttachmentUtil {
private AttachmentUtil() {
}
- public static String getRemoteUrl(String accountUrl, long cardRemoteId, long attachmentRemoteId) {
+ /**
+ * @return a link to the thumbnail of the given {@link Attachment}.
+ * 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);
+ }
+
+ /**
+ * @return {@link AttachmentUtil#getRemoteUrl} 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) {
+ return (attachment.getId() == null || cardRemoteId == null)
+ ? attachment.getLocalPath()
+ : getRemoteUrl(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) {
+ 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) {
+ 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 localId) throws IOException, IllegalArgumentException {
- String fullTempPath = context.getApplicationContext().getFilesDir().getAbsolutePath() + "/attachments/account-" + accountId + "/card-" + (localId == null ? "pending-creation" : localId) + '/' + UriUtils.getDisplayNameForUri(currentUri, context);
- DeckLog.verbose("----- fullTempPath: " + fullTempPath);
- InputStream inputStream = context.getContentResolver().openInputStream(currentUri);
+ 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());
}
- File cacheFile = new File(fullTempPath);
- File tempDir = cacheFile.getParentFile();
+ 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.getPranetFile()");
+ throw new FileNotFoundException("could not cacheFile.getParentFile()");
}
if (!tempDir.exists()) {
- if (!tempDir.mkdirs()) {
+ 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.");
}
}
- if (!cacheFile.createNewFile()) {
+
+ 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");
}
- FileOutputStream outputStream = new FileOutputStream(fullTempPath);
- byte[] buffer = new byte[4096];
- int count;
- while ((count = inputStream.read(buffer)) > 0) {
- outputStream.write(buffer, 0, count);
- }
- DeckLog.verbose("----- wrote");
return cacheFile;
}
+
+ @DrawableRes
+ public static int getIconForMimeType(@NonNull String mimeType) {
+ if (TextUtils.isEmpty(mimeType)) {
+ return R.drawable.ic_attach_file_grey600_24dp;
+ } else if (MimeTypeUtil.isAudio(mimeType)) {
+ return R.drawable.ic_music_note_grey600_24dp;
+ } else if (MimeTypeUtil.isVideo(mimeType)) {
+ return R.drawable.ic_local_movies_grey600_24dp;
+ } else if (MimeTypeUtil.isPdf(mimeType)) {
+ return R.drawable.ic_baseline_picture_as_pdf_24;
+ } else if (MimeTypeUtil.isContact(mimeType)) {
+ return R.drawable.ic_baseline_contact_mail_24;
+ } else {
+ return R.drawable.ic_attach_file_grey600_24dp;
+ }
+ }
+
+ public static String getMimeType(@Nullable String url) {
+ String type = null;
+ String extension = MimeTypeMap.getFileExtensionFromUrl(url);
+ if (extension != null) {
+ type = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ }
+ return type;
+ }
+
}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/ClipboardUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/ClipboardUtil.java
deleted file mode 100644
index 1dad224e9..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/ClipboardUtil.java
+++ /dev/null
@@ -1,38 +0,0 @@
-package it.niedermann.nextcloud.deck.util;
-
-import android.content.ClipData;
-import android.content.ClipboardManager;
-import android.content.Context;
-import android.widget.Toast;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import it.niedermann.nextcloud.deck.DeckLog;
-import it.niedermann.nextcloud.deck.R;
-
-import static android.content.Context.CLIPBOARD_SERVICE;
-
-public class ClipboardUtil {
-
- private ClipboardUtil() {
- }
-
- public static boolean copyToClipboard(@NonNull Context context, @Nullable String text) {
- return copyToClipboard(context, text, text);
- }
-
- public static boolean copyToClipboard(@NonNull Context context, @Nullable String label, @Nullable String text) {
- final ClipboardManager clipboardManager = (ClipboardManager) context.getSystemService(CLIPBOARD_SERVICE);
- if (clipboardManager == null) {
- DeckLog.error("ClipboardManager is null");
- Toast.makeText(context, R.string.could_not_copy_to_clipboard, Toast.LENGTH_LONG).show();
- return false;
- }
- final ClipData clipData = ClipData.newPlainText(label, text);
- clipboardManager.setPrimaryClip(clipData);
- DeckLog.info("Copied to clipboard: [" + label + "] \"" + text + "\"");
- Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show();
- return true;
- }
-}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/ColorUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/ColorUtil.java
deleted file mode 100644
index 08354c5fb..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/ColorUtil.java
+++ /dev/null
@@ -1,165 +0,0 @@
-package it.niedermann.nextcloud.deck.util;
-
-import android.graphics.Color;
-
-import androidx.annotation.ColorInt;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.core.util.Pair;
-
-import java.util.HashMap;
-import java.util.Map;
-
-public final class ColorUtil {
-
- private static final Map<ColorPair, Boolean> CONTRAST_RATIO_SUFFICIENT_CACHE = new HashMap<>();
- private static final Map<Integer, Integer> FOREGROUND_CACHE = new HashMap<>();
- private static final Map<Integer, Boolean> IS_DARK_COLOR_CACHE = new HashMap<>();
-
- private ColorUtil() {
- }
-
- @ColorInt
- public static int getForegroundColorForBackgroundColor(@ColorInt int color) {
- Integer ret = FOREGROUND_CACHE.get(color);
- if (ret == null) {
- if (Color.TRANSPARENT == color)
- ret = Color.BLACK;
- else if (isColorDark(color))
- ret = Color.WHITE;
- else
- ret = Color.BLACK;
-
- FOREGROUND_CACHE.put(color, ret);
- }
- return ret;
- }
-
- /**
- * @return well formatted string starting with a hash followed by 6 hex numbers that is parsable by {@link Color#parseColor(String)}.
- */
- public static String formatColorToParsableHexString(String input) {
- if (input == null) {
- throw new IllegalArgumentException("input color string is null");
- }
- if (isParsableValidHexColorString(input)) {
- return input;
- }
- final char[] chars = input.replaceAll("#", "").toCharArray();
- final StringBuilder sb = new StringBuilder(7).append("#");
- if (chars.length == 6) {
- sb.append(chars);
- } else if (chars.length == 3) {
- for (char c : chars) {
- sb.append(c).append(c);
- }
- } else {
- throw new IllegalArgumentException("unparsable color string: \"" + input + "\"");
- }
- final String formattedHexColor = sb.toString();
- if (isParsableValidHexColorString(formattedHexColor)) {
- return formattedHexColor;
- } else {
- throw new IllegalArgumentException("\"" + input + "\" is not a valid color string. Result of tried normalizing: " + formattedHexColor);
- }
- }
-
- /**
- * Checking for {@link Color#parseColor(String)} being able to parse the input is the important part because we don't know the implementation and rely on it to be able to parse the color.
- *
- * @return true, if the input starts with a hash followed by 6 characters of hex numbers and is parsable by {@link Color#parseColor(String)}.
- */
- private static boolean isParsableValidHexColorString(@NonNull String input) {
- try {
- Color.parseColor(input);
- return input.matches("#[a-fA-F0-9]{6}");
- } catch (Exception e) {
- return false;
- }
- }
-
- public static boolean isColorDark(@ColorInt int color) {
- Boolean ret = IS_DARK_COLOR_CACHE.get(color);
- if (ret == null) {
- ret = getBrightness(color) < 200;
- IS_DARK_COLOR_CACHE.put(color, ret);
- }
- return ret;
- }
-
- private static int getBrightness(@ColorInt int color) {
- final int[] rgb = {Color.red(color), Color.green(color), Color.blue(color)};
-
- return (int) Math.sqrt(rgb[0] * rgb[0] * .241 + rgb[1]
- * rgb[1] * .691 + rgb[2] * rgb[2] * .068);
- }
-
- // ---------------------------------------------------
- // Based on https://github.com/LeaVerou/contrast-ratio
- // ---------------------------------------------------
-
- public static boolean contrastRatioIsSufficient(@ColorInt int colorOne, @ColorInt int colorTwo) {
- ColorPair key = new ColorPair(colorOne, colorTwo);
- Boolean ret = CONTRAST_RATIO_SUFFICIENT_CACHE.get(key);
- if (ret == null) {
- ret = getContrastRatio(colorOne, colorTwo) > 3d;
- CONTRAST_RATIO_SUFFICIENT_CACHE.put(key, ret);
- return ret;
- }
- return ret;
- }
-
- public static boolean contrastRatioIsSufficientBigAreas(@ColorInt int colorOne, @ColorInt int colorTwo) {
- ColorPair key = new ColorPair(colorOne, colorTwo);
- Boolean ret = CONTRAST_RATIO_SUFFICIENT_CACHE.get(key);
- if (ret == null) {
- ret = getContrastRatio(colorOne, colorTwo) > 1.47d;
- CONTRAST_RATIO_SUFFICIENT_CACHE.put(key, ret);
- return ret;
- }
- return ret;
- }
-
- public static double getContrastRatio(@ColorInt int colorOne, @ColorInt int colorTwo) {
- final double lum1 = getLuminanace(colorOne);
- final double lum2 = getLuminanace(colorTwo);
- final double brightest = Math.max(lum1, lum2);
- final double darkest = Math.min(lum1, lum2);
- return (brightest + 0.05) / (darkest + 0.05);
- }
-
- private static double getLuminanace(@ColorInt int color) {
- final int[] rgb = {Color.red(color), Color.green(color), Color.blue(color)};
- return getSubcolorLuminance(rgb[0]) * 0.2126 + getSubcolorLuminance(rgb[1]) * 0.7152 + getSubcolorLuminance(rgb[2]) * 0.0722;
- }
-
- private static double getSubcolorLuminance(@ColorInt int color) {
- final double value = color / 255d;
- return value <= 0.03928
- ? value / 12.92
- : Math.pow((value + 0.055) / 1.055, 2.4);
- }
-
- private static class ColorPair extends Pair<Integer, Integer> {
-
- private ColorPair(@Nullable Integer first, @Nullable Integer second) {
- super(first, second);
- }
-
- @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass", "NumberEquality"})
- @Override
- public boolean equals(Object o) {
- final ColorPair colorPair = (ColorPair) o;
- if (first != colorPair.first) return false;
- return second == colorPair.second;
- }
-
- @SuppressWarnings("ConstantConditions")
- @Override
- public int hashCode() {
- int result = first;
- result = 31 * result + second;
- return result;
- }
- }
-}
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 5c2ffd76c..ac88519ff 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
@@ -3,9 +3,9 @@ package it.niedermann.nextcloud.deck.util;
import android.content.Context;
import android.text.format.DateUtils;
-import java.util.Date;
-import java.util.TimeZone;
-import java.util.concurrent.TimeUnit;
+import androidx.annotation.NonNull;
+
+import java.time.ZonedDateTime;
import it.niedermann.nextcloud.deck.R;
@@ -15,48 +15,9 @@ public final class DateUtil {
private DateUtil() {
}
- public static Date nowInGMT() {
- return convertToGMT(new Date());
- }
-
- private static Date convertToGMT(Date date ){
- TimeZone tz = TimeZone.getDefault();
- Date ret = new Date( date.getTime() - tz.getRawOffset() );
-
- // if we are now in DST, back off by the delta. Note that we are checking the GMT date, this is the KEY.
- if ( tz.inDaylightTime( ret )){
- Date dstDate = new Date( ret.getTime() - tz.getDSTSavings() );
-
- // check to make sure we have not crossed back into standard time
- // this happens when we are on the cusp of DST (7pm the day before the change for PDT)
- if ( tz.inDaylightTime( dstDate )){
- ret = dstDate;
- }
- }
- return ret;
- }
-
- /**
- * Get difference between 2 dates in days (hours, minutes will be set to zero).
- *
- * @param sourceDateFrom start date
- * @param sourceDateUntil end date
- * @return difference between the to dates in days.
- */
- public static long getDayDifference(Date sourceDateFrom, Date sourceDateUntil) {
- Date dateFrom = new Date(sourceDateFrom.getTime());
- dateFrom.setHours(0);
- dateFrom.setMinutes(0);
-
- Date dateUntil = new Date(sourceDateUntil.getTime());
- dateUntil.setHours(0);
- dateUntil.setMinutes(0);
-
- return TimeUnit.DAYS.convert(dateUntil.getTime() - dateFrom.getTime(), TimeUnit.MILLISECONDS);
- }
-
- public static CharSequence getRelativeDateTimeString(Context context, long time) {
- if ((System.currentTimeMillis() - time) < 60 * 1000 && System.currentTimeMillis() > time) {
+ public static CharSequence getRelativeDateTimeString(@NonNull Context context, long time) {
+ long now = ZonedDateTime.now().toInstant().toEpochMilli();
+ if ((now - time) < 60 * 1000 && now > time) {
// < 60 seconds -> seconds ago
return context.getString(R.string.seconds_ago);
} else {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/DeckColorUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/DeckColorUtil.java
new file mode 100644
index 000000000..e01d0ec29
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/DeckColorUtil.java
@@ -0,0 +1,59 @@
+package it.niedermann.nextcloud.deck.util;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.Nullable;
+import androidx.core.util.Pair;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import it.niedermann.android.util.ColorUtil;
+
+public final class DeckColorUtil {
+
+ private static final Map<ColorPair, Boolean> CONTRAST_RATIO_SUFFICIENT_CACHE = new HashMap<>();
+
+ public static boolean contrastRatioIsSufficient(@ColorInt int colorOne, @ColorInt int colorTwo) {
+ ColorPair key = new ColorPair(colorOne, colorTwo);
+ Boolean ret = CONTRAST_RATIO_SUFFICIENT_CACHE.get(key);
+ if (ret == null) {
+ ret = ColorUtil.INSTANCE.getContrastRatio(colorOne, colorTwo) > 3d;
+ CONTRAST_RATIO_SUFFICIENT_CACHE.put(key, ret);
+ return ret;
+ }
+ return ret;
+ }
+
+ public static boolean contrastRatioIsSufficientBigAreas(@ColorInt int colorOne, @ColorInt int colorTwo) {
+ ColorPair key = new ColorPair(colorOne, colorTwo);
+ Boolean ret = CONTRAST_RATIO_SUFFICIENT_CACHE.get(key);
+ if (ret == null) {
+ ret = ColorUtil.INSTANCE.getContrastRatio(colorOne, colorTwo) > 1.47d;
+ CONTRAST_RATIO_SUFFICIENT_CACHE.put(key, ret);
+ return ret;
+ }
+ return ret;
+ }
+
+ private static class ColorPair extends Pair<Integer, Integer> {
+
+ private ColorPair(@Nullable Integer first, @Nullable Integer second) {
+ super(first, second);
+ }
+
+ @SuppressWarnings({"EqualsWhichDoesntCheckParameterClass", "NumberEquality"})
+ @Override
+ public boolean equals(Object o) {
+ final ColorPair colorPair = (ColorPair) o;
+ if (first != colorPair.first) return false;
+ return second == colorPair.second;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = first;
+ result = 31 * result + second;
+ return result;
+ }
+ }
+}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/DimensionUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/DimensionUtil.java
deleted file mode 100644
index 6ff22eace..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/DimensionUtil.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package it.niedermann.nextcloud.deck.util;
-
-import android.content.Context;
-
-import androidx.annotation.DimenRes;
-import androidx.annotation.NonNull;
-import androidx.annotation.Px;
-
-public final class DimensionUtil {
- private DimensionUtil() {
- }
-
- @Px
- public static int dpToPx(@NonNull Context context, @DimenRes int resource) {
- return context.getResources().getDimensionPixelSize(resource);
- }
-}
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 7457c0df7..1a88bec97 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
@@ -7,6 +7,7 @@ import android.view.SubMenu;
import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageButton;
import androidx.appcompat.widget.PopupMenu;
+import androidx.core.content.ContextCompat;
import androidx.fragment.app.FragmentActivity;
import java.util.List;
@@ -38,12 +39,12 @@ 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()));
if (currentServerVersionIsSupported) {
if (board.isPermissionManage()) {
AppCompatImageButton contextMenu = new AppCompatImageButton(context);
contextMenu.setBackgroundDrawable(null);
- contextMenu.setImageDrawable(ViewUtil.getTintedImageView(context, R.drawable.ic_menu, R.color.grey600));
+ contextMenu.setImageDrawable(ViewUtil.getTintedImageView(context, R.drawable.ic_menu, ContextCompat.getColor(context, R.color.grey600)));
contextMenu.setOnClickListener((v) -> {
PopupMenu popup = new PopupMenu(context, contextMenu);
popup.getMenuInflater().inflate(R.menu.navigation_context_menu, popup.getMenu());
@@ -53,25 +54,27 @@ public class DrawerMenuUtil {
}
popup.setOnMenuItemClickListener((MenuItem item) -> {
final String editBoard = context.getString(R.string.edit_board);
- switch (item.getItemId()) {
- case SHARE_BOARD_ID:
- AccessControlDialogFragment.newInstance(board.getLocalId()).show(context.getSupportFragmentManager(), AccessControlDialogFragment.class.getSimpleName());
- return true;
- case R.id.edit_board:
- EditBoardDialogFragment.newInstance(board.getLocalId()).show(context.getSupportFragmentManager(), editBoard);
- return true;
- case R.id.manage_labels:
- ManageLabelsDialogFragment.newInstance(board.getLocalId()).show(context.getSupportFragmentManager(), editBoard);
- return true;
- case R.id.archive_board:
- context.onArchive(board);
- return true;
- case R.id.delete_board:
- DeleteBoardDialogFragment.newInstance(board).show(context.getSupportFragmentManager(), DeleteBoardDialogFragment.class.getCanonicalName());
- return true;
- default:
- return false;
+ int itemId = item.getItemId();
+ if (itemId == SHARE_BOARD_ID) {
+ AccessControlDialogFragment.newInstance(board.getLocalId()).show(context.getSupportFragmentManager(), AccessControlDialogFragment.class.getSimpleName());
+ return true;
+ } else if (itemId == R.id.edit_board) {
+ EditBoardDialogFragment.newInstance(board.getLocalId()).show(context.getSupportFragmentManager(), editBoard);
+ return true;
+ } else if (itemId == R.id.manage_labels) {
+ ManageLabelsDialogFragment.newInstance(board.getLocalId()).show(context.getSupportFragmentManager(), editBoard);
+ return true;
+ } else if (itemId == R.id.clone_board) {
+ context.onClone(board);
+ return true;
+ } else if (itemId == R.id.archive_board) {
+ context.onArchive(board);
+ return true;
+ } else if (itemId == R.id.delete_board) {
+ DeleteBoardDialogFragment.newInstance(board).show(context.getSupportFragmentManager(), DeleteBoardDialogFragment.class.getCanonicalName());
+ return true;
}
+ return false;
});
popup.show();
});
@@ -79,7 +82,7 @@ public class DrawerMenuUtil {
} else if (board.isPermissionShare()) {
AppCompatImageButton contextMenu = new AppCompatImageButton(context);
contextMenu.setBackgroundDrawable(null);
- contextMenu.setImageDrawable(ViewUtil.getTintedImageView(context, R.drawable.ic_share_grey600_18dp, R.color.grey600));
+ contextMenu.setImageDrawable(ViewUtil.getTintedImageView(context, R.drawable.ic_share_grey600_18dp, ContextCompat.getColor(context, R.color.grey600)));
contextMenu.setOnClickListener((v) -> AccessControlDialogFragment.newInstance(board.getLocalId()).show(context.getSupportFragmentManager(), AccessControlDialogFragment.class.getSimpleName()));
m.setActionView(contextMenu);
}
@@ -87,7 +90,7 @@ public class DrawerMenuUtil {
}
if (hasArchivedBoards) {
- boardsMenu.add(Menu.NONE, MENU_ID_ARCHIVED_BOARDS, Menu.NONE, R.string.archived_boards).setIcon(ViewUtil.getTintedImageView(context, R.drawable.ic_archive_white_24dp, R.color.grey600));
+ boardsMenu.add(Menu.NONE, MENU_ID_ARCHIVED_BOARDS, Menu.NONE, R.string.archived_boards).setIcon(ViewUtil.getTintedImageView(context, R.drawable.ic_archive_white_24dp, ContextCompat.getColor(context, R.color.grey600)));
}
if (currentServerVersionIsSupported) {
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/ExceptionUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/ExceptionUtil.java
deleted file mode 100644
index 8599f0b6d..000000000
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/ExceptionUtil.java
+++ /dev/null
@@ -1,81 +0,0 @@
-package it.niedermann.nextcloud.deck.util;
-
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.os.Build;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.UiThread;
-
-import com.nextcloud.android.sso.exceptions.NextcloudFilesAppNotInstalledException;
-import com.nextcloud.android.sso.helper.VersionCheckHelper;
-import com.nextcloud.android.sso.ui.UiExceptionManager;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-
-import it.niedermann.nextcloud.deck.BuildConfig;
-import it.niedermann.nextcloud.deck.DeckLog;
-import it.niedermann.nextcloud.deck.model.Account;
-
-public class ExceptionUtil {
-
- private ExceptionUtil() {
-
- }
-
- public static String getDebugInfos(@NonNull Context context, Throwable throwable, @Nullable Account account) {
- return "" +
- getAppVersions(context, account) +
- "\n\n---\n" +
- getDeviceInfos() +
- "\n\n---" +
- "\n\n" +
- getStacktraceOf(throwable);
- }
-
- private static String getAppVersions(Context context, @Nullable Account account) {
- String versions = ""
- + "App Version: " + BuildConfig.VERSION_NAME + "\n"
- + "App Version Code: " + BuildConfig.VERSION_CODE + "\n"
- + "App Flavor: " + BuildConfig.FLAVOR + "\n";
-
- if (account != null) {
- versions += "\n";
- versions += "Deck Server Version: " + account.getServerDeckVersion() + "\n";
- }
-
- versions += "\n";
- try {
- versions += "Files App Version Code: " + VersionCheckHelper.getNextcloudFilesVersionCode(context);
- } catch (PackageManager.NameNotFoundException e) {
- versions += "Files App Version Code: " + e.getMessage();
- e.printStackTrace();
- }
- return versions;
- }
-
- private static String getDeviceInfos() {
- return ""
- + "\nOS Version: " + System.getProperty("os.version") + "(" + Build.VERSION.INCREMENTAL + ")"
- + "\nOS API Level: " + Build.VERSION.SDK_INT
- + "\nDevice: " + Build.DEVICE
- + "\nManufacturer: " + Build.MANUFACTURER
- + "\nModel (and Product): " + Build.MODEL + " (" + Build.PRODUCT + ")";
- }
-
- private static String getStacktraceOf(Throwable e) {
- StringWriter sw = new StringWriter();
- e.printStackTrace(new PrintWriter(sw));
- return sw.toString();
- }
-
- @UiThread
- public static void handleNextcloudFilesAppNotInstalledException(@NonNull Context context, @NonNull NextcloudFilesAppNotInstalledException exception) {
- UiExceptionManager.showDialogForException(context, exception);
- DeckLog.warn("=============================================================");
- DeckLog.warn("Nextcloud app is not installed. Cannot choose account");
- exception.printStackTrace();
- }
-}
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/MimeTypeUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/MimeTypeUtil.java
index 0390bf96d..04694a058 100644
--- a/app/src/main/java/it/niedermann/nextcloud/deck/util/MimeTypeUtil.java
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/MimeTypeUtil.java
@@ -6,6 +6,7 @@ import java.util.Locale;
public class MimeTypeUtil {
+ public static final String IMAGE_JPEG = "image/jpeg";
public static final String TEXT_PLAIN = "text/plain";
public static final String TEXT_VCARD = "text/vcard";
public static final String APPLICATION_PDF = "application/pdf";
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/ProjectUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/ProjectUtil.java
new file mode 100644
index 000000000..58c465290
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/ProjectUtil.java
@@ -0,0 +1,90 @@
+package it.niedermann.nextcloud.deck.util;
+
+import android.net.Uri;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.net.URL;
+
+import it.niedermann.nextcloud.deck.model.Account;
+
+public class ProjectUtil {
+
+ private ProjectUtil() {
+ }
+
+ @NonNull
+ public static Uri getResourceUri(@NonNull Account account, @NonNull String link) throws IllegalArgumentException {
+ try {
+ // Assume link contains a fully qualified Uri including host
+ final URL u = new URL(link);
+ return Uri.parse(u.toString());
+ } catch (Throwable linkIsNotQualified) {
+ try {
+ // Assume link is a absolute path that needs to be concatenated with account url for a complete Uri
+ final URL u = new URL(account.getUrl() + link);
+ return Uri.parse(u.toString());
+ } catch (Throwable throwable) {
+ throw new IllegalArgumentException("Could not parse " + Uri.class.getSimpleName() + ": " + link, throwable);
+ }
+ }
+ }
+
+ /**
+ * extracts the values of board- and card-ID from url.
+ * Depending on what kind of url it gets, it will return a long[] of length 1 or 2:
+ * If the url contains both values, you'll get 2, if it contains only the board, you'll get 1.
+ * <p>
+ * The order is fixed here: [boardId, cardId]
+ *
+ * @param url to extract from
+ * @return extracted and parsed values as long[] with length 1-2
+ */
+ public static long[] extractBoardIdAndCardIdFromUrl(@Nullable String url) throws IllegalArgumentException {
+ if (url == null) {
+ throw new IllegalArgumentException("provided url is null");
+ }
+ url = url.trim();
+ if (url.length() == 0) {
+ throw new IllegalArgumentException("trimmed url is empty");
+ }
+ // extract important part
+ String[] splitByPrefix = url.split(".*(index\\.php/)?apps/deck(/#)?/board/");
+ // split into board- and card part
+ if (splitByPrefix.length < 2) {
+ throw new IllegalArgumentException("This URL doesn't seem to be an URL containing the boardId: \"" + url + "\"");
+ }
+ String[] splitBySeparator = splitByPrefix[1].split("/card/");
+
+ // remove any unexpected stuff
+ if (splitBySeparator.length > 1 && splitBySeparator[1].contains("/")) {
+ splitBySeparator[1] = splitBySeparator[1].split("/")[0];
+ }
+ if (splitBySeparator.length > 0 && splitBySeparator[0].contains("/")) {
+ splitBySeparator[0] = splitBySeparator[0].split("/")[0];
+ }
+
+ if (splitBySeparator.length < 1) {
+ throw new IllegalArgumentException("This URL doesn't seem to be an URL containing the boardId: \"" + url + "\"");
+ }
+
+ // return result
+ long boardId = Long.parseLong(splitBySeparator[0]);
+ if (boardId < 1) {
+ throw new IllegalArgumentException("Invalid boardId \"" + boardId + "\" for url \"" + url + "\".");
+ }
+ if (splitBySeparator.length == 1) {
+ return new long[]{boardId};
+ } else if (splitBySeparator.length == 2) {
+ long cardId = Long.parseLong(splitBySeparator[1]);
+ if (cardId > 0) {
+ return new long[]{boardId, cardId};
+ } else {
+ return new long[]{boardId};
+ }
+ } else {
+ throw new IllegalArgumentException("could not parse URL for boardId and/or cardId: \"" + url + "\"");
+ }
+ }
+}
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 54f3c8adc..c2ba6f46a 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
@@ -21,28 +21,28 @@ import it.niedermann.nextcloud.deck.R;
public class SpannableUtil {
public static SpannableString strong(@NonNull CharSequence text) {
- SpannableString span = new SpannableString(text);
+ final SpannableString span = new SpannableString(text);
span.setSpan(new StyleSpan(Typeface.BOLD), 0, span.length(), 0);
return span;
}
public static SpannableString disabled(@NonNull CharSequence text, @NonNull Context context) {
- SpannableString span = new SpannableString(text);
+ final SpannableString span = new SpannableString(text);
span.setSpan(new StyleSpan(Typeface.ITALIC), 0, span.length(), 0);
span.setSpan(new ForegroundColorSpan(ContextCompat.getColor(context, R.color.fg_secondary)), 0, span.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return span;
}
public static SpannableString url(@NonNull CharSequence text, @NonNull String target) {
- SpannableString span = new SpannableString(text);
+ final SpannableString span = new SpannableString(text);
span.setSpan(new URLSpan(target), 0, span.length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
return span;
}
public static void setTextWithURL(@NonNull TextView textView, @NonNull Resources resources, @StringRes int containerTextId, @StringRes int linkLabelId, @StringRes int urlId) {
- String linkLabel = resources.getString(linkLabelId);
- String finalText = resources.getString(containerTextId, linkLabel);
- SpannableStringBuilder finalTextBuilder = new SpannableStringBuilder(finalText);
+ 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);
textView.setMovementMethod(new LinkMovementMethod());
diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/util/VCardUtil.java b/app/src/main/java/it/niedermann/nextcloud/deck/util/VCardUtil.java
new file mode 100644
index 000000000..274af332d
--- /dev/null
+++ b/app/src/main/java/it/niedermann/nextcloud/deck/util/VCardUtil.java
@@ -0,0 +1,42 @@
+package it.niedermann.nextcloud.deck.util;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Color;
+import android.net.Uri;
+import android.provider.ContactsContract;
+
+import androidx.annotation.ColorInt;
+import androidx.annotation.NonNull;
+
+import java.util.NoSuchElementException;
+import java.util.Objects;
+
+import it.niedermann.nextcloud.deck.R;
+
+public class VCardUtil {
+
+ private VCardUtil() {
+ // You shall not pass
+ }
+
+ public static Uri getVCardContentUri(@NonNull Context context, @NonNull Uri contactUri) throws NoSuchElementException {
+ final ContentResolver cr = context.getContentResolver();
+ try (final Cursor cursor = cr.query(contactUri, null, null, null, null)) {
+ if (cursor != null && cursor.moveToFirst()) {
+ final String lookupKey = cursor.getString(cursor.getColumnIndex(ContactsContract.Contacts.LOOKUP_KEY));
+ return Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_VCARD_URI, lookupKey);
+ } else {
+ throw new NoSuchElementException("Cursor has zero entries");
+ }
+ }
+ }
+
+ @ColorInt
+ public static int getColorBasedOnDisplayName(@NonNull Context context, @NonNull String displayName) {
+ final String[] colors = context.getResources().getStringArray(R.array.board_default_colors);
+ final int hashCode = Objects.hashCode(displayName);
+ return Color.parseColor(colors[(hashCode < 0 ? hashCode * -1 : hashCode) % colors.length]);
+ }
+}
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 6abbde557..fbc7fc3fc 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
@@ -3,7 +3,6 @@ package it.niedermann.nextcloud.deck.util;
import android.content.Context;
import android.content.res.ColorStateList;
import android.graphics.Bitmap;
-import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.text.Spannable;
@@ -12,10 +11,13 @@ import android.text.style.ImageSpan;
import android.widget.ImageView;
import android.widget.TextView;
+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;
import androidx.core.widget.TextViewCompat;
@@ -24,34 +26,39 @@ import com.bumptech.glide.request.RequestOptions;
import com.bumptech.glide.request.target.CustomTarget;
import com.bumptech.glide.request.transition.Transition;
-import java.util.Date;
+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;
public final class ViewUtil {
private ViewUtil() {
}
public static void addAvatar(@NonNull ImageView avatar, @NonNull String baseUrl, @NonNull String userId, @DrawableRes int errorResource) {
- addAvatar(avatar, baseUrl, userId, DimensionUtil.dpToPx(avatar.getContext(), R.dimen.avatar_size), errorResource);
+ addAvatar(avatar, baseUrl, userId, DimensionUtil.INSTANCE.dpToPx(avatar.getContext(), R.dimen.avatar_size), errorResource);
}
public static void addAvatar(@NonNull ImageView avatar, @NonNull String baseUrl, @NonNull String userId, @Px int avatarSizeInPx, @DrawableRes int errorResource) {
final String uri = baseUrl + "/index.php/avatar/" + Uri.encode(userId) + "/" + avatarSizeInPx;
Glide.with(avatar.getContext())
.load(uri)
+ .placeholder(errorResource)
.error(errorResource)
.apply(RequestOptions.circleCropTransform())
.into(avatar);
}
- public static void themeDueDate(Context context, TextView cardDueDate, Date dueDate) {
- long diff = DateUtil.getDayDifference(new Date(), dueDate);
+ public static void themeDueDate(@NonNull Context context, @NonNull TextView cardDueDate, @NonNull LocalDate dueDate) {
+ long diff = DAYS.between(LocalDate.now(), dueDate);
int backgroundDrawable = 0;
int textColor = isDarkTheme(context) ? R.color.dark_fg_primary : R.color.grey600;
@@ -69,21 +76,18 @@ public final class ViewUtil {
}
cardDueDate.setBackgroundResource(backgroundDrawable);
- cardDueDate.setTextColor(context.getResources().getColor(textColor));
- TextViewCompat.setCompoundDrawableTintList(cardDueDate, ColorStateList.valueOf(context.getResources().getColor(textColor)));
+ cardDueDate.setTextColor(ContextCompat.getColor(context, textColor));
+ TextViewCompat.setCompoundDrawableTintList(cardDueDate, ColorStateList.valueOf(ContextCompat.getColor(context, textColor)));
}
- public static Drawable getTintedImageView(@NonNull Context context, @DrawableRes int imageId, @NonNull String color) {
- final Drawable drawable = context.getResources().getDrawable(imageId);
+ public static Drawable getTintedImageView(@NonNull Context context, @DrawableRes int imageId, @ColorInt int color) {
+ final Drawable drawable = ContextCompat.getDrawable(context, imageId);
+ assert drawable != null;
final Drawable wrapped = DrawableCompat.wrap(drawable).mutate();
- DrawableCompat.setTint(wrapped, Color.parseColor(color));
+ DrawableCompat.setTint(wrapped, color);
return drawable;
}
- public static Drawable getTintedImageView(@NonNull Context context, @DrawableRes int imageId, int colorId) {
- return getTintedImageView(context, imageId, context.getResources().getString(colorId));
- }
-
/**
* Replaces all mentions in the textView with an avatar and the display name
*
@@ -118,7 +122,7 @@ public final class ViewUtil {
Glide.with(context)
.asBitmap()
.placeholder(R.drawable.ic_person_grey600_24dp)
- .load(account.getUrl() + "/index.php/avatar/" + messageBuilder.subSequence(spanStart + 1, spanEnd).toString() + "/" + DimensionUtil.dpToPx(context, R.dimen.icon_size_details))
+ .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
@@ -135,4 +139,12 @@ public final class ViewUtil {
}
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));
+ }
+ }
}
diff --git a/app/src/main/res/drawable-v21/bottom_sheet_rounded.xml b/app/src/main/res/drawable-v21/bottom_sheet_rounded.xml
new file mode 100644
index 000000000..ba266ed32
--- /dev/null
+++ b/app/src/main/res/drawable-v21/bottom_sheet_rounded.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="?attr/colorSurface" />
+ <corners
+ android:topLeftRadius="16dp"
+ android:topRightRadius="16dp" />
+
+</shape> \ No newline at end of file
diff --git a/app/src/main/res/drawable-xxxhdpi/background.png b/app/src/main/res/drawable-xxxhdpi/background.png
deleted file mode 100644
index 90856f4c8..000000000
--- a/app/src/main/res/drawable-xxxhdpi/background.png
+++ /dev/null
Binary files differ
diff --git a/app/src/main/res/drawable/bottom_sheet_rounded.xml b/app/src/main/res/drawable/bottom_sheet_rounded.xml
new file mode 100644
index 000000000..cef4c2314
--- /dev/null
+++ b/app/src/main/res/drawable/bottom_sheet_rounded.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="utf-8"?>
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid android:color="@color/primary" />
+ <corners
+ android:topLeftRadius="16dp"
+ android:topRightRadius="16dp" />
+</shape> \ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_arrow_drop_down_black_24dp.xml b/app/src/main/res/drawable/ic_arrow_drop_down_black_24dp.xml
deleted file mode 100644
index 95b52e8bd..000000000
--- a/app/src/main/res/drawable/ic_arrow_drop_down_black_24dp.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<vector android:autoMirrored="true" android:height="24dp"
- android:tint="#FFFFFF" android:viewportHeight="24.0"
- android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#FF000000" android:pathData="M7,10l5,5 5,-5z"/>
-</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_block_24.xml b/app/src/main/res/drawable/ic_baseline_block_24.xml
new file mode 100644
index 000000000..26080c324
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_block_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="@dimen/avatar_size" android:tint="#757575"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="@dimen/avatar_size" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM4,12c0,-4.42 3.58,-8 8,-8 1.85,0 3.55,0.63 4.9,1.69L5.69,16.9C4.63,15.55 4,13.85 4,12zM12,20c-1.85,0 -3.55,-0.63 -4.9,-1.69L18.31,7.1C19.37,8.45 20,10.15 20,12c0,4.42 -3.58,8 -8,8z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_camera_front_24.xml b/app/src/main/res/drawable/ic_baseline_camera_front_24.xml
new file mode 100644
index 000000000..25c1a79b8
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_camera_front_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#757575"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M10,20L5,20v2h5v2l3,-3 -3,-3v2zM14,20v2h5v-2h-5zM12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -1.99,0.9 -1.99,2S10.9,8 12,8zM17,0L7,0C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,2c0,-1.1 -0.9,-2 -2,-2zM7,2h10v10.5c0,-1.67 -3.33,-2.5 -5,-2.5s-5,0.83 -5,2.5L7,2z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_camera_rear_24.xml b/app/src/main/res/drawable/ic_baseline_camera_rear_24.xml
new file mode 100644
index 000000000..51cea2177
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_camera_rear_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#757575"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M10,20L5,20v2h5v2l3,-3 -3,-3v2zM14,20v2h5v-2h-5zM17,0L7,0C5.9,0 5,0.9 5,2v14c0,1.1 0.9,2 2,2h10c1.1,0 2,-0.9 2,-2L19,2c0,-1.1 -0.9,-2 -2,-2zM12,6c-1.11,0 -2,-0.9 -2,-2s0.89,-2 1.99,-2 2,0.9 2,2C14,5.1 13.1,6 12,6z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_compact_24.xml b/app/src/main/res/drawable/ic_baseline_compact_24.xml
new file mode 100644
index 000000000..d395a7338
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_compact_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#757575"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M8,19h3v4h2v-4h3l-4,-4 -4,4zM16,5h-3L13,1h-2v4L8,5l4,4 4,-4zM4,11v2h16v-2L4,11z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_flash_off_24.xml b/app/src/main/res/drawable/ic_baseline_flash_off_24.xml
new file mode 100644
index 000000000..2a3b0ff5d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_flash_off_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#757575"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M3.27,3L2,4.27l5,5V13h3v9l3.58,-6.14L17.73,20 19,18.73 3.27,3zM17,10h-4l4,-8H7v2.18l8.46,8.46L17,10z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_flash_on_24.xml b/app/src/main/res/drawable/ic_baseline_flash_on_24.xml
new file mode 100644
index 000000000..4574d0e20
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_flash_on_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#757575"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M7,2v11h3v9l7,-12h-4l4,-8z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_mention_24.xml b/app/src/main/res/drawable/ic_baseline_mention_24.xml
new file mode 100644
index 000000000..32c6f57e9
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_mention_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#757575"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10h5v-2h-5c-4.34,0 -8,-3.66 -8,-8s3.66,-8 8,-8 8,3.66 8,8v1.43c0,0.79 -0.71,1.57 -1.5,1.57s-1.5,-0.78 -1.5,-1.57L17,12c0,-2.76 -2.24,-5 -5,-5s-5,2.24 -5,5 2.24,5 5,5c1.38,0 2.64,-0.56 3.54,-1.47 0.65,0.89 1.77,1.47 2.96,1.47 1.97,0 3.5,-1.6 3.5,-3.57L22,12c0,-5.52 -4.48,-10 -10,-10zM12,15c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_photo_camera_24.xml b/app/src/main/res/drawable/ic_baseline_photo_camera_24.xml
new file mode 100644
index 000000000..497db8383
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_photo_camera_24.xml
@@ -0,0 +1,6 @@
+<vector android:autoMirrored="true" android:height="24dp"
+ android:tint="#757575" android:viewportHeight="24"
+ android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M12,12m-3.2,0a3.2,3.2 0,1 1,6.4 0a3.2,3.2 0,1 1,-6.4 0"/>
+ <path android:fillColor="@android:color/white" android:pathData="M9,2L7.17,4L4,4c-1.1,0 -2,0.9 -2,2v12c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,6c0,-1.1 -0.9,-2 -2,-2h-3.17L15,2L9,2zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_search_24.xml b/app/src/main/res/drawable/ic_baseline_search_24.xml
new file mode 100644
index 000000000..2eb5033c1
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_search_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#757575"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M15.5,14h-0.79l-0.28,-0.27C15.41,12.59 16,11.11 16,9.5 16,5.91 13.09,3 9.5,3S3,5.91 3,9.5 5.91,16 9.5,16c1.61,0 3.09,-0.59 4.23,-1.57l0.27,0.28v0.79l5,4.99L20.49,19l-4.99,-5zM9.5,14C7.01,14 5,11.99 5,9.5S7.01,5 9.5,5 14,7.01 14,9.5 11.99,14 9.5,14z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_baseline_subject_24.xml b/app/src/main/res/drawable/ic_baseline_subject_24.xml
new file mode 100644
index 000000000..37cd0c43d
--- /dev/null
+++ b/app/src/main/res/drawable/ic_baseline_subject_24.xml
@@ -0,0 +1,5 @@
+<vector android:autoMirrored="true" android:height="24dp"
+ android:tint="#757575" android:viewportHeight="24"
+ android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="#FF000000" android:pathData="M14,17L4,17v2h10v-2zM20,9L4,9v2h16L20,9zM4,15h16v-2L4,13v2zM4,5v2h16L20,5L4,5z"/>
+</vector>
diff --git a/app/src/main/res/drawable/ic_filter_list_active_white_24dp.xml b/app/src/main/res/drawable/ic_filter_list_active_white_24dp.xml
deleted file mode 100644
index 39225dc3c..000000000
--- a/app/src/main/res/drawable/ic_filter_list_active_white_24dp.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-<vector android:autoMirrored="true" android:height="24dp"
- android:tint="#FFFFFF" android:viewportHeight="24.0"
- android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#FF000000" android:pathData="M10,18h4v-2h-4v2zM3,6v2h18L21,6L3,6zM6,13h12v-2L6,11v2z"/>
- <path
- android:fillColor="#ffffff"
- android:pathData="M20,20m-4,0a4,4 0,1 1,8 0a4,4 0,1 1,-8 0"/>
-</vector>
diff --git a/app/src/main/res/drawable/ic_format_align_left_black_24dp.xml b/app/src/main/res/drawable/ic_format_align_left_black_24dp.xml
deleted file mode 100644
index b676ff3e8..000000000
--- a/app/src/main/res/drawable/ic_format_align_left_black_24dp.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="24dp"
- android:height="24dp"
- android:autoMirrored="true"
- android:tint="#757575"
- android:viewportWidth="24"
- android:viewportHeight="24">
- <path
- android:fillColor="#FF000000"
- android:pathData="M15,15L3,15v2h12v-2zM15,7L3,7v2h12L15,7zM3,13h18v-2L3,11v2zM3,21h18v-2L3,19v2zM3,3v2h18L21,3L3,3z" />
-</vector>
diff --git a/app/src/main/res/drawable/ic_more_horiz_black_24dp.xml b/app/src/main/res/drawable/ic_more_horiz_black_24dp.xml
deleted file mode 100644
index 817e37227..000000000
--- a/app/src/main/res/drawable/ic_more_horiz_black_24dp.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-<vector android:autoMirrored="true" android:height="24dp"
- android:tint="#000000" android:viewportHeight="24.0"
- android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
- <path android:fillColor="#FF000000" android:pathData="M6,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM18,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z"/>
-</vector>
diff --git a/app/src/main/res/drawable/ic_projects_24.xml b/app/src/main/res/drawable/ic_projects_24.xml
new file mode 100644
index 000000000..8fb5da418
--- /dev/null
+++ b/app/src/main/res/drawable/ic_projects_24.xml
@@ -0,0 +1,5 @@
+<vector android:height="24dp" android:tint="#757575"
+ android:viewportHeight="24" android:viewportWidth="24"
+ android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="M13,13v8h8v-8h-8zM3,21h8v-8L3,13v8zM3,3v8h8L11,3L3,3zM16.66,1.69L11,7.34 16.66,13l5.66,-5.66 -5.66,-5.65z"/>
+</vector>
diff --git a/app/src/main/res/drawable/project_deck_36dp.xml b/app/src/main/res/drawable/project_deck_36dp.xml
new file mode 100644
index 000000000..4a9468e4e
--- /dev/null
+++ b/app/src/main/res/drawable/project_deck_36dp.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="36dp"
+ android:height="36dp"
+ android:tint="#757575"
+ android:viewportWidth="16"
+ android:viewportHeight="16">
+ <group>
+ <path
+ android:fillColor="#fff"
+ android:pathData="M2,7L14,7A1,1 0,0 1,15 8L15,14A1,1 0,0 1,14 15L2,15A1,1 0,0 1,1 14L1,8A1,1 0,0 1,2 7z" />
+ <path
+ android:fillColor="#fff"
+ android:pathData="M2.5,5L13.5,5A0.5,0.5 0,0 1,14 5.5L14,5.5A0.5,0.5 0,0 1,13.5 6L2.5,6A0.5,0.5 0,0 1,2 5.5L2,5.5A0.5,0.5 0,0 1,2.5 5z" />
+ <path
+ android:fillColor="#fff"
+ android:pathData="M3.5,3L12.5,3A0.5,0.5 0,0 1,13 3.5L13,3.5A0.5,0.5 0,0 1,12.5 4L3.5,4A0.5,0.5 0,0 1,3 3.5L3,3.5A0.5,0.5 0,0 1,3.5 3z" />
+ <path
+ android:fillColor="#fff"
+ android:pathData="M4.5,1L11.5,1A0.5,0.5 0,0 1,12 1.5L12,1.5A0.5,0.5 0,0 1,11.5 2L4.5,2A0.5,0.5 0,0 1,4 1.5L4,1.5A0.5,0.5 0,0 1,4.5 1z" />
+ </group>
+</vector>
diff --git a/app/src/main/res/drawable/project_file_36dp.xml b/app/src/main/res/drawable/project_file_36dp.xml
new file mode 100644
index 000000000..8d025588c
--- /dev/null
+++ b/app/src/main/res/drawable/project_file_36dp.xml
@@ -0,0 +1,5 @@
+<vector android:autoMirrored="false" android:height="36dp"
+ android:tint="#757575" android:viewportHeight="36"
+ android:viewportWidth="36" android:width="36dp" xmlns:android="http://schemas.android.com/apk/res/android">
+ <path android:fillColor="@android:color/white" android:pathData="m3 4c-0.5 0-1 0.5-1 1v22c0 0.52 0.48 1 1 1h26c0.52 0 1-0.482 1-1v-18c0-0.5-0.5-1-1-1h-13l-4-4z"/>
+</vector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/project_talk_36dp.xml b/app/src/main/res/drawable/project_talk_36dp.xml
new file mode 100644
index 000000000..46fe60de5
--- /dev/null
+++ b/app/src/main/res/drawable/project_talk_36dp.xml
@@ -0,0 +1,11 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="16dp"
+ android:height="16dp"
+ android:autoMirrored="false"
+ android:tint="#757575"
+ android:viewportWidth="16"
+ android:viewportHeight="16">
+ <path
+ android:fillColor="@android:color/white"
+ android:pathData="m7.9992 0.999a6.9993 6.9994 0 0 0 -6.9992 6.9996 6.9993 6.9994 0 0 0 6.9992 6.9994 6.9993 6.9994 0 0 0 3.6308 -1.024c0.86024 0.34184 2.7871 1.356 3.2457 0.91794 0.47922-0.45765-0.56261-2.6116-0.81238-3.412a6.9993 6.9994 0 0 0 0.935 -3.4814 6.9993 6.9994 0 0 0 -6.9991 -6.9993zm0.0008 2.6611a4.34 4.3401 0 0 1 4.34 4.3401 4.34 4.3401 0 0 1 -4.34 4.3398 4.34 4.3401 0 0 1 -4.34 -4.3398 4.34 4.3401 0 0 1 4.34 -4.3401z" />
+</vector> \ No newline at end of file
diff --git a/app/src/main/res/drawable/type_add_color_36dp.xml b/app/src/main/res/drawable/type_add_color_36dp.xml
index 4d964871e..145abfc94 100644
--- a/app/src/main/res/drawable/type_add_color_36dp.xml
+++ b/app/src/main/res/drawable/type_add_color_36dp.xml
@@ -1,5 +1,5 @@
<vector android:autoMirrored="true" android:height="36dp"
- android:tint="#00D400" android:viewportHeight="24.0"
+ android:tint="@color/activity_create" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="36dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
</vector>
diff --git a/app/src/main/res/drawable/type_delete_color_36dp.xml b/app/src/main/res/drawable/type_delete_color_36dp.xml
index 014bf20b8..3b2394fa0 100644
--- a/app/src/main/res/drawable/type_delete_color_36dp.xml
+++ b/app/src/main/res/drawable/type_delete_color_36dp.xml
@@ -1,5 +1,5 @@
<vector android:autoMirrored="true" android:height="36dp"
- android:tint="#D40000" android:viewportHeight="24.0"
+ android:tint="@color/activity_delete" android:viewportHeight="24.0"
android:viewportWidth="24.0" android:width="36dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#FF000000" android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"/>
</vector>
diff --git a/app/src/main/res/drawable/widget_stack_preview.png b/app/src/main/res/drawable/widget_stack_preview.png
new file mode 100644
index 000000000..442c78fbe
--- /dev/null
+++ b/app/src/main/res/drawable/widget_stack_preview.png
Binary files differ
diff --git a/app/src/main/res/layout/activity_archived.xml b/app/src/main/res/layout/activity_archived.xml
index 6dd7024dc..d5e9646a8 100644
--- a/app/src/main/res/layout/activity_archived.xml
+++ b/app/src/main/res/layout/activity_archived.xml
@@ -35,5 +35,5 @@
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- tools:listitem="@layout/item_card" />
+ tools:listitem="@layout/item_card_default" />
</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/activity_exception.xml b/app/src/main/res/layout/activity_exception.xml
index 881c8fa85..932fa7bf2 100644
--- a/app/src/main/res/layout/activity_exception.xml
+++ b/app/src/main/res/layout/activity_exception.xml
@@ -30,7 +30,6 @@
android:layout_weight="1"
android:hint="An error appeared."
app:drawableEndCompat="@drawable/ic_bug_report_black_24dp"
- app:drawableRightCompat="@drawable/ic_bug_report_black_24dp"
tools:ignore="HardcodedText" />
</LinearLayout>
diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml
index a5c926a5e..0692fb8d6 100644
--- a/app/src/main/res/layout/activity_main.xml
+++ b/app/src/main/res/layout/activity_main.xml
@@ -45,7 +45,6 @@
android:paddingEnd="@dimen/spacer_1hx"
android:text="@string/info_box_maintenance_mode"
android:textColor="@color/grey600"
- app:drawableLeftCompat="@drawable/ic_info_outline_grey600_24dp"
app:drawableStartCompat="@drawable/ic_info_outline_grey600_24dp" />
</LinearLayout>
@@ -71,7 +70,6 @@
android:paddingEnd="@dimen/spacer_1hx"
android:text="@string/info_box_version_not_supported"
android:textColor="@android:color/white"
- app:drawableLeftCompat="@drawable/ic_warning_white_24dp"
app:drawableStartCompat="@drawable/ic_warning_white_24dp" />
</LinearLayout>
@@ -135,9 +133,9 @@
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/simple_filter"
android:padding="12dp"
- android:tint="?attr/colorAccent"
android:tooltipText="@string/simple_filter"
app:srcCompat="@drawable/ic_filter_list_white_24dp"
+ app:tint="?attr/colorAccent"
tools:targetApi="o" />
<ImageView
@@ -149,7 +147,9 @@
android:layout_marginBottom="12dp"
android:contentDescription="@null"
android:src="@drawable/circle_grey600_8dp"
- android:tint="@color/defaultBrand" />
+ android:visibility="gone"
+ app:tint="@color/defaultBrand"
+ tools:visibility="visible" />
</FrameLayout>
<ImageView
@@ -160,11 +160,11 @@
android:background="?attr/selectableItemBackgroundBorderless"
android:contentDescription="@string/action_archived_cards"
android:padding="12dp"
- android:tint="?attr/colorAccent"
android:tooltipText="@string/action_archived_cards"
android:translationX="12dp"
android:visibility="gone"
app:srcCompat="@drawable/ic_archive_white_24dp"
+ app:tint="?attr/colorAccent"
tools:targetApi="o"
tools:visibility="visible" />
</androidx.appcompat.widget.Toolbar>
@@ -192,9 +192,9 @@
android:background="?attr/colorPrimary"
android:contentDescription="@string/add_list"
android:foreground="?attr/selectableItemBackgroundBorderless"
- android:tint="?attr/colorAccent"
android:tooltipText="@string/manage_list"
app:srcCompat="@drawable/ic_menu"
+ app:tint="?attr/colorAccent"
tools:ignore="UnusedAttribute" />
</LinearLayout>
diff --git a/app/src/main/res/layout/activity_prepare_create.xml b/app/src/main/res/layout/activity_pick_stack.xml
index 1128f04f4..6ba9b567f 100644
--- a/app/src/main/res/layout/activity_prepare_create.xml
+++ b/app/src/main/res/layout/activity_pick_stack.xml
@@ -4,7 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
- tools:showIn="@layout/activity_prepare_create">
+ tools:showIn="@layout/activity_pick_stack">
<com.google.android.material.appbar.AppBarLayout
android:id="@+id/appBarLayout"
@@ -25,35 +25,10 @@
android:layout_below="@id/appBarLayout"
android:padding="@dimen/spacer_2x">
- <LinearLayout
+ <FrameLayout
+ android:id="@+id/fragment_container"
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical">
-
- <androidx.appcompat.widget.AppCompatSpinner
- android:id="@+id/account_select"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:prompt="@string/choose_account"
- android:spinnerMode="dialog"
- tools:listitem="@layout/item_prepare_create_account" />
-
- <androidx.appcompat.widget.AppCompatSpinner
- android:id="@+id/board_select"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:prompt="@string/choose_board"
- android:spinnerMode="dialog"
- tools:listitem="@layout/item_board" />
-
- <androidx.appcompat.widget.AppCompatSpinner
- android:id="@+id/stack_select"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:prompt="@string/choose_list"
- android:spinnerMode="dialog"
- tools:listitem="@layout/item_board" />
- </LinearLayout>
+ android:layout_height="wrap_content" />
</ScrollView>
<LinearLayout
diff --git a/app/src/main/res/layout/activity_take_photo.xml b/app/src/main/res/layout/activity_take_photo.xml
new file mode 100644
index 000000000..419bb54f7
--- /dev/null
+++ b/app/src/main/res/layout/activity_take_photo.xml
@@ -0,0 +1,55 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/black"
+ android:orientation="vertical"
+ tools:theme="@style/TransparentTheme">
+
+ <androidx.camera.view.PreviewView
+ android:id="@+id/preview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <com.google.android.flexbox.FlexboxLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ android:background="@color/mdtp_transparent_black"
+ app:alignItems="center"
+ app:justifyContent="space_evenly">
+
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
+ android:id="@+id/switchCamera"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/take_photo_switch_camera"
+ android:tint="@android:color/white"
+ app:backgroundTint="@color/defaultBrand"
+ app:fabSize="mini"
+ tools:srcCompat="@drawable/ic_baseline_camera_front_24" />
+
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
+ android:id="@+id/takePhoto"
+ android:layout_marginTop="@dimen/spacer_3x"
+ android:layout_marginBottom="@dimen/spacer_3x"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/take_photo"
+ android:tint="@android:color/white"
+ app:backgroundTint="@color/defaultBrand"
+ app:srcCompat="@drawable/ic_baseline_photo_camera_24" />
+
+ <com.google.android.material.floatingactionbutton.FloatingActionButton
+ android:id="@+id/toggle_torch"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:contentDescription="@string/take_photo_toggle_torch"
+ android:tint="@android:color/white"
+ app:backgroundTint="@color/defaultBrand"
+ app:fabSize="mini"
+ tools:srcCompat="@drawable/ic_baseline_flash_on_24" />
+ </com.google.android.flexbox.FlexboxLayout>
+</RelativeLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_move_card.xml b/app/src/main/res/layout/dialog_move_card.xml
new file mode 100644
index 000000000..c00dcfbf4
--- /dev/null
+++ b/app/src/main/res/layout/dialog_move_card.xml
@@ -0,0 +1,80 @@
+<?xml version="1.0" encoding="utf-8"?>
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:paddingStart="@dimen/spacer_2x"
+ android:paddingTop="@dimen/spacer_2x"
+ android:paddingEnd="@dimen/spacer_2x"
+ android:paddingBottom="@dimen/spacer_1x">
+
+ <TextView
+ android:id="@+id/title"
+ style="@style/TextAppearance.AppCompat.Title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacer_1x"
+ android:layout_marginEnd="@dimen/spacer_1x"
+ android:ellipsize="end"
+ android:maxLines="5"
+ tools:text="@string/action_card_move_title" />
+
+ <ScrollView
+ android:id="@+id/scrollView"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/title"
+ android:layout_marginTop="@dimen/spacer_2x">
+
+ <FrameLayout
+ android:id="@+id/fragment_container"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+ </ScrollView>
+
+ <TextView
+ android:id="@+id/move_warning"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/scrollView"
+ android:layout_marginTop="@dimen/spacer_2x"
+ android:drawablePadding="@dimen/spacer_3x"
+ android:paddingStart="@dimen/spacer_3x"
+ android:paddingEnd="@dimen/spacer_1x"
+ android:text="@string/move_warning"
+ android:textColor="@color/danger"
+ android:visibility="gone"
+ app:drawableStartCompat="@drawable/ic_warning_white_24dp"
+ app:drawableTint="@color/danger"
+ tools:visibility="visible" />
+
+ <com.google.android.flexbox.FlexboxLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/move_warning"
+ android:layout_marginTop="@dimen/spacer_1x"
+ android:orientation="horizontal"
+ app:justifyContent="space_between">
+
+ <Button
+ android:id="@+id/cancel"
+ style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="start"
+ android:layout_marginEnd="@dimen/spacer_1x"
+ android:text="@android:string/cancel"
+ android:textColor="@color/defaultBrand" />
+
+ <Button
+ android:id="@+id/submit"
+ style="@style/Widget.AppCompat.Button.ButtonBar.AlertDialog"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="end"
+ android:text="@string/simple_move"
+ android:textColor="@color/defaultBrand" />
+ </com.google.android.flexbox.FlexboxLayout>
+</RelativeLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_preview.xml b/app/src/main/res/layout/dialog_preview.xml
new file mode 100644
index 000000000..5ab35d63e
--- /dev/null
+++ b/app/src/main/res/layout/dialog_preview.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/avatar"
+ android:layout_width="match_parent"
+ android:layout_height="200dp"
+ android:contentDescription="@string/user_avatar"
+ android:scaleType="centerCrop"
+ android:background="@color/bg_info_box" />
+
+ <TextView
+ android:padding="?dialogPreferredPadding"
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/textAppearanceHeadline1"
+ tools:text="@tools:sample/full_names" />
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/dialog_project_resources.xml b/app/src/main/res/layout/dialog_project_resources.xml
new file mode 100644
index 000000000..3de37db8b
--- /dev/null
+++ b/app/src/main/res/layout/dialog_project_resources.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="utf-8"?>
+<androidx.recyclerview.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_below="@id/projectName"
+ android:paddingTop="@dimen/spacer_1x"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ tools:listitem="@layout/item_project_resource" /> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml b/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml
index 5aa23961d..c1cccc310 100644
--- a/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml
+++ b/app/src/main/res/layout/fragment_card_edit_tab_attachments.xml
@@ -31,8 +31,67 @@
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
+ android:contentDescription="@string/upload_a_new_attachment"
android:visibility="gone"
app:backgroundTint="@color/defaultBrand"
app:srcCompat="@drawable/ic_file_upload_white_24dp"
tools:visibility="visible" />
+
+ <FrameLayout
+ android:id="@+id/pickerBackdrop"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@android:color/transparent"
+ android:visibility="gone" />
+
+ <LinearLayout
+ android:id="@+id/bottom_sheet_parent"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center"
+ android:orientation="vertical"
+ app:elevation="@dimen/spacer_1x"
+ app:layout_behavior="com.google.android.material.bottomsheet.BottomSheetBehavior">
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/spacer_2x"
+ android:background="@drawable/bottom_sheet_rounded">
+
+ <View
+ android:layout_width="@dimen/spacer_4x"
+ android:layout_height="@dimen/spacer_1hx"
+ android:layout_alignParentBottom="true"
+ android:layout_centerInParent="true"
+ android:background="@color/bg_info_box" />
+ </RelativeLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/pickerRecyclerView"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/colorPrimarySurface"
+ android:paddingStart="@dimen/spacer_1hx"
+ android:paddingTop="@dimen/spacer_1x"
+ android:paddingEnd="@dimen/spacer_1hx"
+ android:paddingBottom="@dimen/attachments_bottom_navigation_height"
+ tools:listitem="@layout/support_simple_spinner_dropdown_item"
+ tools:visibility="gone" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="bottom">
+
+ <com.google.android.material.bottomnavigation.BottomNavigationView
+ android:id="@+id/bottomNavigation"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/attachments_bottom_navigation_height"
+ android:translationY="@dimen/attachments_bottom_navigation_height"
+ app:backgroundTint="?attr/colorPrimary"
+ app:itemIconTint="?attr/colorAccent"
+ app:itemTextColor="?attr/colorAccent"
+ app:menu="@menu/attachment_picker_menu" />
+ </LinearLayout>
</androidx.coordinatorlayout.widget.CoordinatorLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_card_edit_tab_comments.xml b/app/src/main/res/layout/fragment_card_edit_tab_comments.xml
index 3350480cb..cc5fa77b7 100644
--- a/app/src/main/res/layout/fragment_card_edit_tab_comments.xml
+++ b/app/src/main/res/layout/fragment_card_edit_tab_comments.xml
@@ -44,9 +44,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="@dimen/spacer_1x"
- android:layout_marginLeft="@dimen/spacer_1x"
android:layout_marginEnd="@dimen/spacer_1x"
- android:layout_marginRight="@dimen/spacer_1x"
android:contentDescription="@string/simple_reply"
android:padding="@dimen/spacer_1x"
app:srcCompat="@drawable/ic_reply_grey600_24dp" />
@@ -57,7 +55,6 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_marginStart="@dimen/spacer_1x"
- android:layout_marginLeft="@dimen/spacer_1x"
android:layout_weight="1"
android:ellipsize="end"
android:maxLines="2"
@@ -75,6 +72,38 @@
app:srcCompat="@drawable/ic_close_circle_grey600" />
</LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/mentionProposerWrapper"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/spacer_2x"
+ android:paddingTop="@dimen/spacer_1x"
+ android:paddingEnd="@dimen/spacer_2x"
+ android:paddingBottom="@dimen/spacer_1x"
+ android:visibility="gone"
+ tools:visibility="visible">
+
+ <ImageView
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacer_1x"
+ android:layout_marginEnd="@dimen/spacer_1x"
+ android:contentDescription="@string/simple_reply"
+ android:padding="@dimen/spacer_1x"
+ app:srcCompat="@drawable/ic_baseline_mention_24" />
+
+ <LinearLayout
+ android:id="@+id/mention_proposer"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/avatar_size_small"
+ android:layout_marginStart="@dimen/spacer_1x"
+ android:layout_marginEnd="@dimen/spacer_1x"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" />
+ </LinearLayout>
+
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -85,10 +114,11 @@
<ImageView
android:layout_width="@dimen/icon_size_details"
android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/spacer_2x"
+ android:layout_marginStart="@dimen/spacer_2x"
+ android:layout_marginEnd="@dimen/spacer_1x"
android:contentDescription="@null"
- android:tint="@color/grey600"
- app:srcCompat="@drawable/ic_comment_white_24dp" />
+ app:srcCompat="@drawable/ic_comment_white_24dp"
+ app:tint="@color/grey600" />
<EditText
android:id="@+id/message"
diff --git a/app/src/main/res/layout/fragment_card_edit_tab_details.xml b/app/src/main/res/layout/fragment_card_edit_tab_details.xml
index b1d52378a..2dfb79889 100644
--- a/app/src/main/res/layout/fragment_card_edit_tab_details.xml
+++ b/app/src/main/res/layout/fragment_card_edit_tab_details.xml
@@ -1,163 +1,179 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:fillViewport="true">
- <LinearLayout xmlns:tools="http://schemas.android.com/tools"
+ <RelativeLayout
android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical"
- android:padding="@dimen/spacer_2x">
+ android:layout_height="wrap_content">
<LinearLayout
+ android:id="@+id/details"
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/spacer_2x"
- android:orientation="horizontal">
-
- <ImageView
- android:layout_width="@dimen/icon_size_details"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/spacer_2x"
- android:contentDescription="@null"
- app:srcCompat="@drawable/ic_label_grey600_24dp" />
+ android:orientation="vertical"
+ android:padding="@dimen/spacer_2x">
- <it.niedermann.nextcloud.deck.ui.view.ToggleAutoCompleteTextView
- android:id="@+id/labels"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:completionThreshold="1"
- android:hint="@string/label_labels"
- android:inputType="text" />
- </LinearLayout>
-
- <com.google.android.material.chip.ChipGroup
- android:id="@+id/labelsGroup"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="40dp"
- android:animateLayoutChanges="true" />
-
- <LinearLayout
- android:id="@+id/colorPicker"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/spacer_2x">
-
-
- <ImageView
- android:layout_width="@dimen/icon_size_details"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/spacer_2x"
- android:contentDescription="@null"
- app:srcCompat="@drawable/calendar_blank_grey600_24dp" />
+ android:layout_marginTop="@dimen/spacer_2x"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="@dimen/icon_size_details"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/spacer_2x"
+ android:contentDescription="@null"
+ app:srcCompat="@drawable/ic_label_grey600_24dp" />
+
+ <it.niedermann.nextcloud.deck.ui.view.ToggleAutoCompleteTextView
+ android:id="@+id/labels"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:completionThreshold="1"
+ android:hint="@string/label_labels"
+ android:inputType="text" />
+ </LinearLayout>
+
+ <com.google.android.material.chip.ChipGroup
+ android:id="@+id/labelsGroup"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="40dp"
+ android:animateLayoutChanges="true" />
- <EditText
- android:id="@+id/dueDateDate"
- android:layout_width="wrap_content"
+ <LinearLayout
+ android:id="@+id/colorPicker"
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_weight="2"
- android:enabled="true"
- android:focusable="false"
- android:hint="@string/hint_due_date_date"
- android:importantForAutofill="no"
- android:inputType="date"
- android:maxLines="1"
- tools:text="01/07/2020" />
-
- <EditText
- android:id="@+id/dueDateTime"
- android:layout_width="wrap_content"
+ android:layout_marginTop="@dimen/spacer_2x">
+
+
+ <ImageView
+ android:layout_width="@dimen/icon_size_details"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/spacer_2x"
+ android:contentDescription="@null"
+ app:srcCompat="@drawable/calendar_blank_grey600_24dp" />
+
+ <EditText
+ android:id="@+id/dueDateDate"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_weight="2"
+ android:enabled="true"
+ android:focusable="false"
+ android:hint="@string/hint_due_date_date"
+ android:importantForAutofill="no"
+ android:inputType="date"
+ android:maxLines="1"
+ tools:text="01/07/2020" />
+
+ <EditText
+ android:id="@+id/dueDateTime"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:layout_weight="1"
+ android:enabled="true"
+ android:focusable="false"
+ android:hint="@string/hint_due_date_time"
+ android:importantForAutofill="no"
+ android:inputType="datetime"
+ android:maxLines="1"
+ android:minLines="0"
+ android:textAlignment="center"
+ tools:text="11:45" />
+
+ <ImageView
+ android:id="@+id/clearDueDate"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:contentDescription="@string/label_clear_due_date"
+ android:paddingStart="@dimen/spacer_1x"
+ android:paddingEnd="@dimen/spacer_1x"
+ app:srcCompat="@drawable/ic_close_circle_grey600" />
+ </LinearLayout>
+
+ <LinearLayout
+ android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:layout_gravity="center_horizontal"
- android:layout_weight="1"
- android:enabled="true"
- android:focusable="false"
- android:hint="@string/hint_due_date_time"
- android:importantForAutofill="no"
- android:inputType="datetime"
- android:maxLines="1"
- android:minLines="0"
- android:textAlignment="center"
- tools:text="11:45" />
-
- <ImageView
- android:id="@+id/clearDueDate"
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:contentDescription="@string/label_clear_due_date"
- android:paddingStart="@dimen/spacer_1x"
- android:paddingEnd="@dimen/spacer_1x"
- app:srcCompat="@drawable/ic_close_circle_grey600" />
- </LinearLayout>
-
- <LinearLayout
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/spacer_2x"
- android:orientation="horizontal">
-
- <ImageView
- android:layout_width="@dimen/icon_size_details"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/spacer_2x"
- android:contentDescription="@null"
- app:srcCompat="@drawable/ic_person_grey600_24dp" />
+ android:layout_marginTop="@dimen/spacer_2x"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="@dimen/icon_size_details"
+ android:layout_height="match_parent"
+ android:layout_marginEnd="@dimen/spacer_2x"
+ android:contentDescription="@null"
+ app:srcCompat="@drawable/ic_person_grey600_24dp" />
+
+ <it.niedermann.nextcloud.deck.ui.view.ToggleAutoCompleteTextView
+ android:id="@+id/people"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:completionThreshold="1"
+ android:hint="@string/hint_assign_people"
+ android:inputType="text" />
+ </LinearLayout>
+
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/assignees"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="40dp"
+ android:layout_marginTop="@dimen/spacer_1x"
+ app:layoutManager="androidx.recyclerview.widget.GridLayoutManager"
+ tools:listitem="@tools:sample/avatars" />
- <it.niedermann.nextcloud.deck.ui.view.ToggleAutoCompleteTextView
- android:id="@+id/people"
+ <LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
- android:completionThreshold="1"
- android:hint="@string/hint_assign_people"
- android:inputType="text" />
+ android:layout_marginTop="@dimen/spacer_2x"
+ android:orientation="horizontal">
+
+ <ImageView
+ android:layout_width="@dimen/icon_size_details"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="10dp"
+ android:layout_marginEnd="@dimen/spacer_2x"
+ android:contentDescription="@null"
+ app:srcCompat="@drawable/ic_baseline_subject_24" />
+
+ <com.yydcdut.markdown.MarkdownEditText
+ android:id="@+id/description"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="top"
+ android:hint="@string/label_description"
+ android:importantForAutofill="no"
+ android:inputType="textMultiLine|textCapSentences"
+ android:scrollbars="vertical" />
+ </LinearLayout>
</LinearLayout>
- <LinearLayout
- android:id="@+id/peopleList"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginStart="40dp"
- android:orientation="horizontal" />
-
- <LinearLayout
+ <TextView
+ android:id="@+id/projectsTitle"
+ style="?attr/textAppearanceOverline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
+ android:layout_below="@id/details"
+ android:layout_marginStart="@dimen/icon_size_details"
android:layout_marginTop="@dimen/spacer_2x"
- android:orientation="horizontal">
-
- <ImageView
- android:layout_width="@dimen/icon_size_details"
- android:layout_height="wrap_content"
- android:layout_marginTop="10dp"
- android:layout_marginEnd="@dimen/spacer_2x"
- android:contentDescription="@null"
- app:srcCompat="@drawable/ic_format_align_left_black_24dp" />
+ android:paddingStart="@dimen/spacer_4x"
+ android:paddingEnd="@null"
+ android:text="@string/projects_title" />
- <com.yydcdut.markdown.MarkdownEditText
- android:id="@+id/description"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:gravity="top"
- android:hint="@string/label_description"
- android:importantForAutofill="no"
- android:inputType="textMultiLine|textCapSentences"
- android:scrollbars="vertical" />
- </LinearLayout>
-
- <RelativeLayout
+ <androidx.recyclerview.widget.RecyclerView
+ android:id="@+id/projects"
android:layout_width="match_parent"
- android:layout_height="wrap_content">
-
- <androidx.fragment.app.FragmentContainerView
- android:id="@+id/commentsFragment"
- android:layout_width="match_parent"
- android:layout_height="wrap_content"
- android:layout_marginTop="@dimen/spacer_2x" />
-
- </RelativeLayout>
-
- </LinearLayout>
+ android:layout_height="wrap_content"
+ android:layout_below="@id/projectsTitle"
+ app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
+ tools:listitem="@layout/item_project" />
+ </RelativeLayout>
</ScrollView>
diff --git a/app/src/main/res/layout/fragment_pick_stack.xml b/app/src/main/res/layout/fragment_pick_stack.xml
new file mode 100644
index 000000000..9769969ff
--- /dev/null
+++ b/app/src/main/res/layout/fragment_pick_stack.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <androidx.appcompat.widget.AppCompatSpinner
+ android:id="@+id/account_select"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:prompt="@string/choose_account"
+ tools:listitem="@layout/item_prepare_create_account" />
+
+ <androidx.appcompat.widget.AppCompatSpinner
+ android:id="@+id/board_select"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:prompt="@string/choose_board"
+ tools:listitem="@layout/item_board" />
+
+ <androidx.appcompat.widget.AppCompatSpinner
+ android:id="@+id/stack_select"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:prompt="@string/choose_list"
+ tools:listitem="@layout/item_board" />
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_stack.xml b/app/src/main/res/layout/fragment_stack.xml
index d4cbd2ee8..e0fb35c8a 100644
--- a/app/src/main/res/layout/fragment_stack.xml
+++ b/app/src/main/res/layout/fragment_stack.xml
@@ -24,5 +24,5 @@
android:scrollbarStyle="outsideOverlay"
android:scrollbars="vertical"
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
- tools:listitem="@layout/item_card" />
+ tools:listitem="@layout/item_card_default" />
</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/item_assignee.xml b/app/src/main/res/layout/item_assignee.xml
new file mode 100644
index 000000000..961bdea30
--- /dev/null
+++ b/app/src/main/res/layout/item_assignee.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+
+ <androidx.appcompat.widget.AppCompatImageView
+ android:id="@+id/avatar"
+ android:layout_width="@dimen/avatar_size"
+ android:layout_height="@dimen/avatar_size"
+ android:layout_gravity="center"
+ android:scaleType="centerCrop"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ app:srcCompat="@drawable/ic_person_grey600_24dp"/>
+
+</FrameLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/item_attachment_default.xml b/app/src/main/res/layout/item_attachment_default.xml
index 98e3114eb..88e2126cd 100644
--- a/app/src/main/res/layout/item_attachment_default.xml
+++ b/app/src/main/res/layout/item_attachment_default.xml
@@ -9,23 +9,25 @@
android:padding="@dimen/spacer_2x">
<FrameLayout
- android:layout_width="wrap_content"
- android:layout_height="match_parent"
- android:layout_marginEnd="@dimen/spacer_1x">
+ android:layout_width="@dimen/avatar_size"
+ android:layout_height="@dimen/avatar_size"
+ android:layout_marginEnd="@dimen/spacer_2x">
- <androidx.appcompat.widget.AppCompatImageView
+ <ImageView
android:id="@+id/preview"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
android:layout_gravity="center"
- android:layout_margin="@dimen/spacer_1x"
+ android:contentDescription="@null"
+ android:padding="@dimen/spacer_1hx"
app:srcCompat="@drawable/ic_attach_file_grey600_24dp" />
- <androidx.appcompat.widget.AppCompatImageView
+ <ImageView
android:id="@+id/not_synced_yet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
+ android:contentDescription="@string/not_synced_yet"
android:visibility="gone"
app:srcCompat="@drawable/ic_sync_blue_24dp"
tools:visibility="visible" />
@@ -36,6 +38,7 @@
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
+ android:layout_marginEnd="@dimen/spacer_1x"
android:layout_weight="1"
android:textAppearance="?attr/textAppearanceListItem"
tools:maxLength="30"
diff --git a/app/src/main/res/layout/item_card_compact.xml b/app/src/main/res/layout/item_card_compact.xml
new file mode 100644
index 000000000..e5e719bd9
--- /dev/null
+++ b/app/src/main/res/layout/item_card_compact.xml
@@ -0,0 +1,96 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/card"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacer_2x"
+ android:layout_marginTop="@dimen/spacer_1x"
+ android:layout_marginEnd="@dimen/spacer_2x"
+ android:layout_marginBottom="@dimen/spacer_1x"
+ android:focusable="true"
+ app:cardElevation="@dimen/spacer_1qx"
+ app:cardBackgroundColor="@color/bg_card">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical"
+ android:paddingTop="@dimen/spacer_1x"
+ android:paddingBottom="@dimen/spacer_1x">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/spacer_2x"
+ android:paddingEnd="@dimen/spacer_1x">
+
+ <TextView
+ android:id="@+id/card_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4sp"
+ android:layout_weight="1"
+ android:textColor="?attr/colorAccent"
+ android:textSize="18sp"
+ tools:ignore="RtlSymmetry"
+ tools:text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l" />
+
+ <ImageView
+ android:id="@+id/not_synced_yet"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8sp"
+ android:contentDescription="@string/not_synced_yet"
+ android:visibility="gone"
+ app:srcCompat="@drawable/ic_sync_blue_24dp"
+ tools:visibility="visible" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/spacer_1hx"
+ tools:ignore="RtlSymmetry">
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/card_due_date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/due_tomorrow_background"
+ android:drawablePadding="@dimen/spacer_1hx"
+ android:gravity="center"
+ android:padding="@dimen/spacer_1hx"
+ android:paddingEnd="@dimen/spacer_1x"
+ android:textColor="@color/fg_secondary"
+ app:drawableStartCompat="@drawable/calendar_blank_grey600_24dp"
+ tools:text="tomorrow" />
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/card_menu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:contentDescription="@string/label_menu"
+ android:padding="@dimen/spacer_1hx"
+ android:tint="?attr/colorAccent"
+ app:srcCompat="@drawable/ic_menu" />
+ </LinearLayout>
+
+ <it.niedermann.nextcloud.deck.ui.view.labellayout.CompactLabelLayout
+ android:id="@+id/labels"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacer_1x"
+ android:animateLayoutChanges="true"
+ android:paddingStart="@dimen/spacer_2x"
+ android:paddingEnd="@dimen/spacer_2x"
+ app:flexWrap="nowrap"
+ tools:layout_height="@dimen/avatar_size" />
+
+ </LinearLayout>
+</com.google.android.material.card.MaterialCardView> \ No newline at end of file
diff --git a/app/src/main/res/layout/item_card.xml b/app/src/main/res/layout/item_card_default.xml
index c0457f5e4..ad775fd1b 100644
--- a/app/src/main/res/layout/item_card.xml
+++ b/app/src/main/res/layout/item_card_default.xml
@@ -9,8 +9,9 @@
android:layout_marginTop="@dimen/spacer_1x"
android:layout_marginEnd="@dimen/spacer_2x"
android:layout_marginBottom="@dimen/spacer_1x"
- app:cardBackgroundColor="@color/bg_card"
- android:focusable="true">
+ android:focusable="true"
+ app:cardElevation="@dimen/spacer_1qx"
+ app:cardBackgroundColor="@color/bg_card">
<LinearLayout
android:layout_width="match_parent"
@@ -32,8 +33,8 @@
android:layout_height="wrap_content"
android:layout_marginTop="4sp"
android:layout_weight="1"
- android:textSize="18sp"
android:textColor="?attr/colorAccent"
+ android:textSize="18sp"
tools:ignore="RtlSymmetry"
tools:text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l" />
@@ -62,6 +63,7 @@
android:drawablePadding="@dimen/spacer_1hx"
android:gravity="center"
android:padding="@dimen/spacer_1hx"
+ android:paddingEnd="@dimen/spacer_1x"
android:textColor="@color/fg_secondary"
app:drawableStartCompat="@drawable/calendar_blank_grey600_24dp"
tools:text="tomorrow" />
@@ -69,7 +71,7 @@
</LinearLayout>
</LinearLayout>
- <it.niedermann.nextcloud.deck.ui.view.LabelLayout
+ <it.niedermann.nextcloud.deck.ui.view.labellayout.DefaultLabelLayout
android:id="@+id/labels"
android:layout_width="match_parent"
android:layout_height="wrap_content"
@@ -100,7 +102,6 @@
android:drawablePadding="@dimen/spacer_1hx"
android:gravity="center_vertical"
android:padding="@dimen/spacer_1hx"
- app:drawableLeftCompat="@drawable/ic_comment_white_24dp"
app:drawableStartCompat="@drawable/ic_comment_white_24dp"
app:drawableTint="@color/grey600"
tools:text="2" />
@@ -111,8 +112,7 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="@dimen/spacer_1hx"
- app:drawableLeftCompat="@drawable/ic_check_grey600_24dp"
- app:drawableStartCompat="@drawable/ic_check_grey600_24dp"
+ tools:drawableStartCompat="@drawable/ic_check_grey600_24dp"
tools:text="1/2" />
<androidx.appcompat.widget.AppCompatTextView
@@ -121,7 +121,6 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:padding="@dimen/spacer_1hx"
- app:drawableLeftCompat="@drawable/ic_attach_file_grey600_24dp"
app:drawableStartCompat="@drawable/ic_attach_file_grey600_24dp"
tools:text="3" />
</LinearLayout>
diff --git a/app/src/main/res/layout/item_card_default_only_title.xml b/app/src/main/res/layout/item_card_default_only_title.xml
new file mode 100644
index 000000000..a8db3d4e0
--- /dev/null
+++ b/app/src/main/res/layout/item_card_default_only_title.xml
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="utf-8"?>
+<com.google.android.material.card.MaterialCardView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/card"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginStart="@dimen/spacer_2x"
+ android:layout_marginTop="@dimen/spacer_1x"
+ android:layout_marginEnd="@dimen/spacer_2x"
+ android:layout_marginBottom="@dimen/spacer_1x"
+ android:focusable="true"
+ app:cardElevation="@dimen/spacer_1qx"
+ app:cardBackgroundColor="@color/bg_card">
+
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/spacer_2x"
+ android:paddingTop="@dimen/spacer_1x"
+ android:paddingEnd="@dimen/spacer_1x"
+ android:paddingBottom="@dimen/spacer_1x">
+
+ <TextView
+ android:id="@+id/card_title"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="4sp"
+ android:layout_weight="1"
+ android:textColor="?attr/colorAccent"
+ android:textSize="18sp"
+ tools:ignore="RtlSymmetry"
+ tools:text="Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut l" />
+
+ <ImageView
+ android:id="@+id/not_synced_yet"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="8sp"
+ android:contentDescription="@string/not_synced_yet"
+ android:visibility="gone"
+ app:srcCompat="@drawable/ic_sync_blue_24dp"
+ tools:visibility="visible" />
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:paddingStart="@dimen/spacer_1hx"
+ tools:ignore="RtlSymmetry">
+
+ <androidx.appcompat.widget.AppCompatTextView
+ android:id="@+id/card_due_date"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="@drawable/due_tomorrow_background"
+ android:drawablePadding="@dimen/spacer_1hx"
+ android:gravity="center"
+ android:padding="@dimen/spacer_1hx"
+ android:paddingEnd="@dimen/spacer_1x"
+ android:textColor="@color/fg_secondary"
+ app:drawableStartCompat="@drawable/calendar_blank_grey600_24dp"
+ tools:text="tomorrow" />
+
+ </LinearLayout>
+
+ <ImageView
+ android:id="@+id/card_menu"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackgroundBorderless"
+ android:contentDescription="@string/label_menu"
+ android:padding="@dimen/spacer_1hx"
+ android:tint="?attr/colorAccent"
+ app:srcCompat="@drawable/ic_menu" />
+ </LinearLayout>
+</com.google.android.material.card.MaterialCardView> \ No newline at end of file
diff --git a/app/src/main/res/layout/item_filter_user.xml b/app/src/main/res/layout/item_filter_user.xml
index 2c29bf5ad..0aced6dc2 100644
--- a/app/src/main/res/layout/item_filter_user.xml
+++ b/app/src/main/res/layout/item_filter_user.xml
@@ -31,7 +31,7 @@
</FrameLayout>
<TextView
- android:id="@+id/displayName"
+ android:id="@+id/title"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
diff --git a/app/src/main/res/layout/item_photo_preview.xml b/app/src/main/res/layout/item_photo_preview.xml
new file mode 100644
index 000000000..280ed063c
--- /dev/null
+++ b/app/src/main/res/layout/item_photo_preview.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<it.niedermann.nextcloud.deck.ui.view.SquareConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/squareRelativeLayout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <androidx.camera.view.PreviewView
+ android:id="@+id/preview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <ImageView
+ android:id="@+id/imageView"
+ android:layout_width="0dp"
+ android:layout_height="0dp"
+ android:contentDescription="@string/take_photo"
+ app:layout_constraintBottom_toBottomOf="parent"
+ app:layout_constraintEnd_toEndOf="parent"
+ app:layout_constraintHeight_percent=".3"
+ app:layout_constraintStart_toStartOf="parent"
+ app:layout_constraintTop_toTopOf="parent"
+ app:layout_constraintWidth_percent=".3"
+ app:srcCompat="@drawable/ic_baseline_photo_camera_24"
+ app:tint="@android:color/white" />
+</it.niedermann.nextcloud.deck.ui.view.SquareConstraintLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/item_picker_native.xml b/app/src/main/res/layout/item_picker_native.xml
new file mode 100644
index 000000000..167cbd7ff
--- /dev/null
+++ b/app/src/main/res/layout/item_picker_native.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:orientation="horizontal"
+ android:padding="@dimen/spacer_2x">
+
+ <ImageView
+ android:id="@+id/avatar"
+ android:layout_width="@dimen/avatar_size"
+ android:layout_height="@dimen/avatar_size"
+ android:layout_marginEnd="@dimen/spacer_2x"
+ android:contentDescription="@null"
+ android:padding="@dimen/spacer_1hx"
+ app:srcCompat="@drawable/ic_baseline_search_24" />
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textAppearance="?attr/textAppearanceListItem"
+ tools:text="Search all files" />
+ </LinearLayout>
+
+
+ <View
+ android:layout_width="match_parent"
+ android:layout_height="1dp"
+ android:background="@color/bg_info_box" />
+
+ <TextView
+ android:id="@+id/subtitle"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="@dimen/spacer_2x"
+ android:paddingStart="72dp"
+ android:paddingTop="@dimen/spacer_1x"
+ android:paddingEnd="@dimen/spacer_1x"
+ android:paddingBottom="@dimen/spacer_1x"
+ android:text="@string/recent"
+ android:textAppearance="?attr/textAppearanceOverline" />
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/item_picker_user.xml b/app/src/main/res/layout/item_picker_user.xml
new file mode 100644
index 000000000..f9a17508e
--- /dev/null
+++ b/app/src/main/res/layout/item_picker_user.xml
@@ -0,0 +1,59 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:padding="@dimen/spacer_2x">
+
+ <FrameLayout
+ android:layout_width="@dimen/avatar_size"
+ android:layout_height="@dimen/avatar_size"
+ android:layout_marginEnd="@dimen/spacer_2x">
+
+ <ImageView
+ android:id="@+id/avatar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:contentDescription="@null"
+ tools:background="@color/board_default_color" />
+
+ <TextView
+ android:id="@+id/initials"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:textColor="@android:color/white"
+ android:textSize="20dp"
+ android:translationY="-2dp"
+ android:visibility="gone"
+ tools:ignore="SpUsage"
+ tools:text="G"
+ tools:visibility="visible" />
+
+ </FrameLayout>
+
+ <LinearLayout
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textAppearance="?attr/textAppearanceListItem"
+ tools:text="@tools:sample/full_names" />
+
+ <TextView
+ android:id="@+id/contactInformation"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:textAppearance="?attr/textAppearanceListItemSecondary"
+ tools:text="@tools:sample/us_phones" />
+ </LinearLayout>
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/item_prepare_create_account.xml b/app/src/main/res/layout/item_prepare_create_account.xml
index f21c71326..9a76b898e 100644
--- a/app/src/main/res/layout/item_prepare_create_account.xml
+++ b/app/src/main/res/layout/item_prepare_create_account.xml
@@ -14,7 +14,7 @@
android:layout_marginEnd="@dimen/spacer_2x"
android:contentDescription="@null"
app:srcCompat="@drawable/ic_person_grey600_24dp"
- tools:srcCompat="@tools:sample/avatars" />
+ tools:src="@tools:sample/avatars" />
<LinearLayout
@@ -37,8 +37,10 @@
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"
+ android:ellipsize="middle"
+ android:singleLine="true"
android:textAppearance="?attr/textAppearanceListItemSecondary"
- tools:text="https://example.com/" />
+ tools:text="example.com" />
</LinearLayout>
</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/layout/item_prepare_create_stack.xml b/app/src/main/res/layout/item_prepare_create_stack.xml
index f5a1f97b1..516941997 100644
--- a/app/src/main/res/layout/item_prepare_create_stack.xml
+++ b/app/src/main/res/layout/item_prepare_create_stack.xml
@@ -6,9 +6,9 @@
android:layout_height="wrap_content"
android:ellipsize="middle"
android:paddingStart="72dp"
- android:paddingTop="@dimen/spacer_2x"
+ android:paddingTop="20dp"
android:paddingEnd="@dimen/spacer_2x"
- android:paddingBottom="@dimen/spacer_2x"
+ android:paddingBottom="20dp"
android:singleLine="true"
android:textAppearance="?attr/textAppearanceListItem"
tools:text="@tools:sample/full_names" />
diff --git a/app/src/main/res/layout/item_project.xml b/app/src/main/res/layout/item_project.xml
new file mode 100644
index 000000000..47ecf732d
--- /dev/null
+++ b/app/src/main/res/layout/item_project.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:padding="@dimen/spacer_2x">
+
+ <androidx.appcompat.widget.AppCompatImageView
+ android:layout_width="@dimen/icon_size_details"
+ android:layout_height="@dimen/icon_size_details"
+ android:contentDescription="@null"
+ android:focusable="false"
+ android:scaleType="center"
+ android:layout_marginEnd="@dimen/spacer_2x"
+ app:srcCompat="@drawable/ic_projects_24" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/projectName"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="middle"
+ android:singleLine="true"
+ android:textAppearance="?attr/textAppearanceListItem"
+ tools:text="@tools:sample/full_names" />
+
+ <TextView
+ android:id="@+id/resourcesCount"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="middle"
+ android:singleLine="true"
+ android:textAppearance="?attr/textAppearanceListItemSecondary"
+ tools:text="4 resources" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/item_project_resource.xml b/app/src/main/res/layout/item_project_resource.xml
new file mode 100644
index 000000000..6288b2c85
--- /dev/null
+++ b/app/src/main/res/layout/item_project_resource.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="?attr/selectableItemBackground"
+ android:gravity="center_vertical"
+ android:orientation="horizontal"
+ android:padding="@dimen/spacer_2x">
+
+ <androidx.appcompat.widget.AppCompatImageView
+ android:id="@+id/image"
+ android:layout_width="@dimen/icon_size_details"
+ android:layout_height="@dimen/icon_size_details"
+ android:layout_marginEnd="@dimen/spacer_2x"
+ android:contentDescription="@null"
+ android:focusable="false"
+ android:scaleType="fitCenter"
+ app:srcCompat="@drawable/ic_projects_24" />
+
+ <LinearLayout
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:layout_weight="1"
+ android:orientation="vertical">
+
+ <TextView
+ android:id="@+id/name"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="middle"
+ android:singleLine="true"
+ android:textAppearance="?attr/textAppearanceListItem"
+ tools:text="@tools:sample/full_names" />
+
+ <TextView
+ android:id="@+id/type"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:ellipsize="middle"
+ android:singleLine="true"
+ android:textAppearance="?attr/textAppearanceListItemSecondary"
+ tools:text="4 resources" />
+ </LinearLayout>
+</LinearLayout>
diff --git a/app/src/main/res/layout/item_tip.xml b/app/src/main/res/layout/item_tip.xml
index 2177d013c..fc2d3587a 100644
--- a/app/src/main/res/layout/item_tip.xml
+++ b/app/src/main/res/layout/item_tip.xml
@@ -15,7 +15,6 @@
android:drawablePadding="@dimen/spacer_2x"
android:gravity="start|center"
android:textAppearance="?attr/textAppearanceListItem"
- app:drawableLeftCompat="@drawable/ic_lightbulb_outline_grey600_24dp"
app:drawableStartCompat="@drawable/ic_lightbulb_outline_grey600_24dp"
tools:maxLength="200"
tools:text="@tools:sample/lorem/random" />
diff --git a/app/src/main/res/layout/widget_stack.xml b/app/src/main/res/layout/widget_stack.xml
new file mode 100644
index 000000000..d920bff87
--- /dev/null
+++ b/app/src/main/res/layout/widget_stack.xml
@@ -0,0 +1,64 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="@drawable/widget_single_card_background"
+ android:orientation="vertical">
+
+ <!-- Widget header -->
+ <RelativeLayout
+ android:id="@+id/widget_stack_header_rl"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/widget_stack_header_height"
+ android:padding="@dimen/widget_stack_card_padding">
+
+ <ImageView
+ android:id="@+id/widget_stack_header_icon"
+ android:layout_width="@dimen/widget_stack_icon_width"
+ android:layout_height="match_parent"
+ android:src="@drawable/circle_grey600_8dp"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentBottom="true"
+ android:paddingStart="@dimen/widget_stack_card_padding"
+ android:paddingEnd="@dimen/widget_stack_card_padding"
+ android:contentDescription="@string/widget_stack_header_icon" />
+
+ <TextView
+ android:id="@+id/widget_stack_title_tv"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:textAppearance="?attr/textAppearanceBody1"
+ android:textSize="18sp"
+ android:layout_toEndOf="@id/widget_stack_header_icon"
+ android:gravity="center_vertical"
+ android:paddingStart="@dimen/widget_stack_card_padding"
+ android:paddingEnd="@dimen/widget_stack_card_padding"
+ android:textColor="@color/widget_foreground"
+ tools:text="@string/app_name"/>
+
+ </RelativeLayout>
+ <!-- End header -->
+
+ <ListView
+ android:id="@+id/stack_widget_lv"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:divider="@android:color/transparent"
+ android:dividerHeight="@dimen/spacer_1x"
+ android:padding="@dimen/spacer_1x"
+ tools:listitem="@layout/widget_stack_entry" />
+
+ <ImageView
+ android:id="@+id/widget_stack_placeholder_iv"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:gravity="center"
+ android:text="@string/app_name_short"
+ android:textColor="@color/fg_secondary"
+ app:srcCompat="@drawable/ic_local_movies_grey600_24dp"
+ tools:visibility="gone"
+ android:contentDescription="@string/widget_stack_placeholder_icon" />
+
+</LinearLayout>
diff --git a/app/src/main/res/layout/widget_stack_entry.xml b/app/src/main/res/layout/widget_stack_entry.xml
new file mode 100644
index 000000000..f67a53e61
--- /dev/null
+++ b/app/src/main/res/layout/widget_stack_entry.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:id="@+id/widget_stack_entry"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:padding="@dimen/widget_stack_card_padding"
+ android:background="@color/primary">
+
+ <TextView
+ android:id="@+id/widget_entry_content_tv"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="center_vertical"
+ android:layout_weight="1"
+ android:paddingStart="@dimen/widget_stack_card_padding"
+ android:paddingEnd="@dimen/widget_stack_card_padding"
+ android:textAppearance="?attr/textAppearanceListItem"
+ android:textSize="14sp"
+ android:textColor="@color/widget_foreground"
+ tools:maxLength="60"
+ tools:text="@tools:sample/lorem/random" />
+</LinearLayout> \ No newline at end of file
diff --git a/app/src/main/res/menu/attachment_picker_menu.xml b/app/src/main/res/menu/attachment_picker_menu.xml
new file mode 100644
index 000000000..f280ac752
--- /dev/null
+++ b/app/src/main/res/menu/attachment_picker_menu.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item
+ android:id="@+id/gallery"
+ android:icon="@drawable/ic_baseline_photo_camera_24"
+ android:title="@string/gallery"
+ app:showAsAction="ifRoom" />
+ <item
+ android:id="@+id/contacts"
+ android:icon="@drawable/ic_person_grey600_24dp"
+ android:title="@string/contacts"
+ app:showAsAction="ifRoom" />
+ <item
+ android:id="@+id/files"
+ android:icon="@drawable/ic_attach_file_grey600_24dp"
+ android:title="@string/files"
+ app:showAsAction="ifRoom" />
+</menu>
diff --git a/app/src/main/res/menu/navigation_context_menu.xml b/app/src/main/res/menu/navigation_context_menu.xml
index d56f07595..ece2b917e 100644
--- a/app/src/main/res/menu/navigation_context_menu.xml
+++ b/app/src/main/res/menu/navigation_context_menu.xml
@@ -12,6 +12,11 @@
android:title="@string/manage_tags"
app:showAsAction="never" />
<item
+ android:id="@+id/clone_board"
+ android:orderInCategory="10"
+ android:title="@string/clone_board"
+ app:showAsAction="never" />
+ <item
android:id="@+id/archive_board"
android:orderInCategory="20"
android:title="@string/archive_board"
diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml
index 5d0c33072..593570303 100644
--- a/app/src/main/res/values-ca/strings.xml
+++ b/app/src/main/res/values-ca/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Darrer compte</string>
<string name="drawer_manage_accounts">Gestiona els comptes</string>
- <!-- Simple values -->
<string name="simple_boards">Taulells</string>
<string name="simple_add">Afegeix</string>
<string name="simple_save">Desa</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">Desactivat</string>
<string name="simple_copied">Copiat</string>
<string name="simple_archive">Arxiu</string>
+ <string name="simple_unassigned">Sense assignar</string>
<string name="edit_board">Edita el tauler</string>
<string name="archive_board">Arxiva el tauler</string>
<string name="delete_board">Suprimeix el tauler</string>
<string name="delete_something">Elimina %1$s</string>
- <!-- About -->
<string name="about">Quant a</string>
<string name="about_version_title">Versió</string>
<string name="about_version">Esteu fent servir %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Crèdits</string>
<string name="about_contribution_tab_title">Contribució</string>
<string name="about_license_tab_title">Llicència</string>
-
- <string name="copied_to_clipboard">S\'ha copiat al porta-retalls</string>
-
<string name="seconds_ago">fa uns segons</string>
<string name="edit">Edita</string>
<string name="label_labels">Selecciona etiquetes</string>
@@ -129,6 +125,7 @@
<string name="delete_board_message">Això suprimirà permanentment aquest tauler, incloent-hi totes les llistes i targetes.</string>
<string name="settings_theme_title">Tema fosc</string>
<string name="settings_branding_title">Marcatge</string>
+ <string name="settings_compact_title">Mode compacte</string>
<string name="settings_background_sync">Sincronització en segon pla</string>
<string name="pref_value_wifi_and_mobile">Sincronitza per Wi-Fi i dades mòbils</string>
<string name="pref_value_wifi_only">Sincronitza només per Wi-Fi</string>
@@ -156,7 +153,6 @@
<string name="maintenance_mode_explanation">El servidor %1$s és actualment en mode de manteniment. Si us plau, contacteu el vostre administrador o proveu de nou més tard.</string>
<string name="share_add_to_card">Afegeix a la targeta</string>
<string name="share_success">S\'ha afegit amb èxit %1$s a %2$s</string>
- <string name="could_not_copy_to_clipboard">No s\'ha pogut copiar al porta-retalls</string>
<string name="add_comment">Afegeix un comentari</string>
<string name="card_edit_comments">Comentaris</string>
<string name="no_comments_yet">Encara no hi ha comentaris</string>
@@ -168,11 +164,9 @@
<string name="open_in_browser">Obre al navegador</string>
<string name="updating_card">S\'està actualitzant la targeta...</string>
- <!-- Move lists -->
<string name="move_list_right">Mou la llista a la dreta</string>
<string name="move_list_left">Mou la llista a l\'esquerra</string>
- <!-- Filter -->
<string name="filter_no_filter">Totes</string>
<string name="filter_overdue">Endarrerit</string>
<string name="filter_today">Avui</string>
@@ -183,7 +177,6 @@
<string name="filter_by_assigned_user">Filtra per usuari assignat</string>
<string name="filter_by_duedate">Filtra per data de venciment</string>
- <!-- Archived cards -->
<string name="archived_cards">Targetes arxivades</string>
<string name="action_card_dearchive">Desfés l\'arxivament de la targeta</string>
<string name="action_archived_cards">Navega per les targetes arxivades</string>
@@ -198,11 +191,9 @@
<string name="filter_user_title">Usuaris</string>
<string name="filter_duedate_title">Data de venciment</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Desfés l\'arxivament del tauler</string>
<string name="archived_boards">Taulers arxivats</string>
- <!-- Errors -->
<string name="error">S\'ha produït un error</string>
<string name="synchronization_failed">Ha fallat la sincronització</string>
<string name="operation_not_yet_supported">Encara no està suportat</string>
@@ -216,7 +207,8 @@
<string name="error_dialog_title">Oh no! - I ara què? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Si us plau, mireu de forçar el tancament de l\'aplicació i reiniciar-la de nou. Hi deu haver hagut una connexió incorrecta a l\'aplicació Nextcloud.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Si el problema persisteix, mireu de netejar l\'emmagatzematge de totes dues aplicacions: Nextcloud i Nextcloud Deck per tal de resoldre el problema.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Si el problema persisteix, mireu de netejar l\'emmagatzematge de totes dues aplicacions: Nextcloud i Nextcloud Deck per tal de resoldre el problema.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Ha fallat l\'actualització de la base de dades. Informeu del problema i esborreu l\'emmagatzematge per utilitzar l\'aplicació normalment.</string>
<string name="error_dialog_tip_clear_storage">Podeu netejar l\'emmagatzematge obrint la informació de l\'aplicació i seleccionant Emmagatzematge → Neteja l\'emmagatzematge.</string>
<string name="error_dialog_tip_files_outdated">La vostra aplicació Nextcloud sembla que està obsoleta. Si us plau, visiteu la Play Store o l\'F-Droid per tal d\'aconseguir la darrera versió.</string>
<string name="error_dialog_tip_files_force_stop">Sembla que alguna cosa està malament a la vostra aplicació Nextcloud. Si us plau, intenteu forçar l\'aturada tant de l\'aplicació Nextcloud com de l\'aplicació Nextcloud Deck.</string>
@@ -230,17 +222,57 @@
<string name="error_dialog_we_need_info">Ens cal la següent informació tècnica per poder-vos ajudar:</string>
<string name="error_dialog_redirect">El vostre servidor ha respost amb un codi d\'estat HTTP 302, que vol dir que no teniu instal·lada l\'aplicació Deck al vostre servidor o que alguna cosa està mal configurada. Això pot estar causat per una sobreescriptura a .htaccess-file o per aplicacions de Nextcloud com OID Client.</string>
<string name="error_dialog_version_not_parsable">No hem pogut determinar la versió de l\'aplicació Deck de la part del servidor. Si us plau, assegureu-vos que està instal·lada i habilitada.</string>
+ <string name="error_dialog_account_might_not_be_authorized">És possible que el compte de la vostra aplicació Nextcloud ja no estigui autoritzat.</string>
+ <string name="error_dialog_user_not_found_in_database">L’usuari actual no coincideix amb l’usuari que tenim a la nostra base de dades. Si feu servir LDAP a la vostra instància de Nextcloud, és possible que l\'aplicació Nextcloud hagi emmagatzemat un ID d\'usuari antic.</string>
<string name="error_dialog_capabilities_not_parsable">No podem recuperar les capacitats del vostre servidor. Si us plau, assegureu-vos que el servidor funciona bé i que altres aplicacions de client poden accedir al Nextcloud.</string>
+ <string name="error_dialog_attachment_upload_failed">No s\'ha pogut carregar un fitxer adjunt. Proveu de compartir-ho d’una altra manera i informeu-nos d’aquest error.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Desactiveu totes les optimitzacions de bateries de Nextcloud i l\'aplicació Deck.</string>
<string name="error_action_open_deck_info">Obre la informació de l\'aplicació</string>
<string name="error_action_open_network">Arranjaments de xarxa</string>
<string name="error_action_server_logs">Bitàcoles del servidor</string>
<string name="error_action_install">Instal·lació</string>
+ <string name="error_action_report_issue">Informar</string>
<string name="info_box_maintenance_mode">Servidor en mode de manteniment</string>
<string name="info_box_version_not_supported">La versió del servidor %1$s no està suportada, si us plau, actualitzeu a %2$s.</string>
<string name="share_link">Enllaç de compartició</string>
<string name="archive_cards">Arxiva les targetes</string>
+
<string name="manage_accounts">Gestiona els comptes</string>
<string name="manage_list">Gestiona la llista</string>
<string name="simple_reply">Respon</string>
+ <string name="error_while_uploading_attachment">S\'ha produït un error en penjar el fitxer adjunt: %1$s</string>
+ <string name="append_text_to_description">Apunteu a la descripció</string>
+ <string name="add_text_as_comment">Afegir com a comentari</string>
+ <string name="progress_count">%1$d de %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d error durant la càrrega</item>
+ <item quantity="other">%1$d errors durant la càrrega</item>
+ </plurals>
<string name="simple_report">Informar</string>
-</resources>
+ <string name="error_action_open_battery_settings">Paràmetres de la bateria</string>
+ <string name="move_warning">Ni es poden transferir comentaris ni fitxers adjunts quan es mogui la targeta a un altre tauler.</string>
+ <string name="clone_board">Clonar tauler</string>
+ <string name="cloning_board">Clonant %1$s ...</string>
+ <string name="successfully_cloned_board">S\'ha clonat amb èxit %1$s</string>
+ <string name="attachment_does_not_yet_exist">Encara no existeix el fitxer adjunt al Deck</string>
+ <string name="card_does_not_yet_exist">La targeta encara no existeix al Deck</string>
+
+ <string name="widget_stack_title">Llista</string>
+ <string name="widget_stack_header_icon">Icona de capçalera de widget</string>
+ <string name="widget_stack_placeholder_icon">Icona de marcador de lloc de widget</string>
+ <string name="select_stack">Seleccionar llista</string>
+ <string name="project_type_deck_board">Tauler de Deck</string>
+ <string name="project_type_deck_card">Targeta de Deck</string>
+ <string name="project_type_file">Fitxer</string>
+ <string name="projects_title">Projectes</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d recursos</item>
+ <item quantity="other">%1$d recursos</item>
+ </plurals>
+ <string name="no_assigned_label">Sense etiqueta assignada</string>
+ <string name="single_card">Targeta única</string>
+ <string name="project_type_room">Sala de xerrades</string>
+ <string name="simple_move">Mou</string>
+ <string name="cannot_upload_files_without_permission">No es poden pujar fitxers sense permís</string>
+ <string name="simple_unassign">No ho assignis</string>
+ </resources>
diff --git a/app/src/main/res/values-cs-rCZ/strings.xml b/app/src/main/res/values-cs-rCZ/strings.xml
index 25e1429ad..07f9eb0f3 100644
--- a/app/src/main/res/values-cs-rCZ/strings.xml
+++ b/app/src/main/res/values-cs-rCZ/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Poslední účet</string>
<string name="drawer_manage_accounts">Spravovat účty</string>
- <!-- Simple values -->
<string name="simple_boards">Tabule</string>
<string name="simple_add">Přidat</string>
<string name="simple_save">Uložit</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">vypnuto</string>
<string name="simple_copied">Zkopírováno</string>
<string name="simple_archive">Archiv</string>
+ <string name="simple_unassigned">Nepřiřazeno</string>
<string name="edit_board">Upravit tabuli</string>
<string name="archive_board">Archivovat tabuli</string>
<string name="delete_board">Smazat tabuli</string>
<string name="delete_something">Smazat %1$s</string>
- <!-- About -->
<string name="about">O aplikaci</string>
<string name="about_version_title">Verze</string>
<string name="about_version">Nyní používáte %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Zásluhy</string>
<string name="about_contribution_tab_title">Zapojení se</string>
<string name="about_license_tab_title">Licence</string>
-
- <string name="copied_to_clipboard">Zkopírováno do schránky</string>
-
<string name="seconds_ago">před několika sekundami</string>
<string name="edit">Upravit</string>
<string name="label_labels">Vybrat štítky</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">Zatím žádné seznamy</string>
<string name="do_you_want_to_save_your_changes">Chcete vámi provedené změny uložit?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Chcete archivovat všechny karty %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Chcete archivovat všechny filtrované karty %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Toto nadobro vymaže %1$d kartu z tohoto seznamu.</item>
<item quantity="few">Toto nadobro vymaže obě %1$d karty z tohoto seznamu.</item>
@@ -133,6 +130,7 @@
<string name="delete_board_message">Toto tabuli nadobro smaže, včetně všech seznamů a karet.</string>
<string name="settings_theme_title">Tmavý motiv vzhledu</string>
<string name="settings_branding_title">Opatření vlastním logem</string>
+ <string name="settings_compact_title">Kompaktní režim</string>
<string name="settings_background_sync">Synchronizace na pozadí</string>
<string name="pref_value_wifi_and_mobile">Synchr. přes Wi-Fi a mobilní data</string>
<string name="pref_value_wifi_only">Synchr. pouze přes Wi-Fi</string>
@@ -160,7 +158,6 @@
<string name="maintenance_mode_explanation">Na serveru %1$s v tuto chvíli probíhá údržba. Obraťte se na jeho správce nebo to zkuste později znovu.</string>
<string name="share_add_to_card">Přidat do karty</string>
<string name="share_success">%1$s úspěšně přidáno do %2$s</string>
- <string name="could_not_copy_to_clipboard">Nedaří se zkopírovat do schránky</string>
<string name="add_comment">Přidat komentář</string>
<string name="card_edit_comments">Komentáře</string>
<string name="no_comments_yet">Zatím žádné komentáře</string>
@@ -172,11 +169,9 @@
<string name="open_in_browser">Otevřít v prohlížeči</string>
<string name="updating_card">Aktualizace karty…</string>
- <!-- Move lists -->
<string name="move_list_right">Přesunout seznam vpravo</string>
<string name="move_list_left">Přesunout seznam vlevo</string>
- <!-- Filter -->
<string name="filter_no_filter">Vše</string>
<string name="filter_overdue">Po termínu</string>
<string name="filter_today">Dnes</string>
@@ -187,7 +182,6 @@
<string name="filter_by_assigned_user">Filtrovat podle uživatele, který je úkolem pověřen</string>
<string name="filter_by_duedate">Filtrovat podle termínu</string>
- <!-- Archived cards -->
<string name="archived_cards">Archivované karty</string>
<string name="action_card_dearchive">Vzít zpět archivaci karty</string>
<string name="action_archived_cards">Procházet archivované karty</string>
@@ -202,11 +196,9 @@
<string name="filter_user_title">Uživatelé</string>
<string name="filter_duedate_title">Datum termínu</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Vzít zpět archivaci tabule</string>
<string name="archived_boards">Archivované tabule</string>
- <!-- Errors -->
<string name="error">Objevila se chyba</string>
<string name="synchronization_failed">Synchronizace se nezdařila</string>
<string name="operation_not_yet_supported">Zatím nepodorováno</string>
@@ -220,7 +212,8 @@
<string name="error_dialog_title">Ó ne– co teď? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Prosím pokuste se vynutit ukončení aplikace a spusťte ji znovu. Možná se něco nepodařilo při spojení s aplikací Nextcloud.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Pokud problém přetrvává, zkuste problém vyřešit vyčištěním úložišť obou aplikací: Nextcloud a Nextcloud Deck.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Pokud problém přetrvává, zkuste problém vyřešit vyčištěním úložišť obou aplikací: Nextcloud a Nextcloud Deck.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Přechod na novější verzi databáze se nezdařil. Prosíme nahlaste tento problém vývojářům a aby bylo možné aplikaci normálně používat, vyčistěte úložiště.</string>
<string name="error_dialog_tip_clear_storage">Úložiště můžete vyčistit otevřením informací o aplikací a vybráním Úložiště → Vyčistit úložiště.</string>
<string name="error_dialog_tip_files_outdated">Zdá se, že aplikace Nextcloud, nainstalovaná na vašem zařízení, je staré verze. Přejděte prosím do Play Store nebo F-Droid a nainstalujte si nejnovější verzi.</string>
<string name="error_dialog_tip_files_force_stop">Zdá se, že s vaší aplikací Nextcloud není něco v pořádku. Zkuste vynutit zastavení obojího – aplikace Nextcloud a Nextcloud Deck.</string>
@@ -234,18 +227,80 @@
<string name="error_dialog_we_need_info">Abychom vám mohli pomoci, potřebujeme následující technické údaje:</string>
<string name="error_dialog_redirect">Vámi využívaný server odpověděl HTTP stavovým kódem 302, což ukazuje na to, že na serveru buď není nainstalovaná aplikace Deck, nebo je něco nesprávně nastavené. Toto může být způsobeno uživatelsky určenými přepsáními v souboru .htaccess nebo Nextcloud aplikacemi jako je OID klient.</string>
<string name="error_dialog_version_not_parsable">Nepodařilo se zjistit verze aplikace Deck na straně serveru. Zajistěte, aby byla nainstalovaná a zapnutá.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Může být, že účet vaší Nextcloud aplikace už nemá potřebná pověření.</string>
+ <string name="error_dialog_user_not_found_in_database">Stávající uživatel se neshoduje s uživatelem, který je v databázi. Pokud na své instanci Nextcloud používáte LDAP, je možné, že Nextcloud aplikace má uložený starý identifikátor uživatele.</string>
<string name="error_dialog_capabilities_not_parsable">Nepodařilo se zjistit schopnosti serveru, který využíváte. Ověřte, že server v pořádku běží a ostatní klientské aplikace mohou k Nextcloud přistupovat.</string>
<string name="error_dialog_attachment_upload_failed">Přílohu se nepodařilo nahrát. Zkuste ji nasdílet jiným způsobem a dejte nám o této chybě vědět.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">V nastavení systému prosím pro aplikace Nextcloud a Deck vypněte veškeré optimalizace spotřeby energie z akumulátoru.</string>
<string name="error_action_open_deck_info">Otevřít informace o aplikaci</string>
<string name="error_action_open_network">Nastavení sítě</string>
<string name="error_action_server_logs">Záznamy událostí na serveru</string>
<string name="error_action_install">Nainstalovat</string>
+ <string name="error_action_report_issue">Hlášení</string>
<string name="info_box_maintenance_mode">Na serveru probíhá údržba</string>
<string name="info_box_version_not_supported">Verze serveru %1$s není podporována, přejděte na verzi %2$s</string>
<string name="share_link">Odkaz na sdílení</string>
<string name="archive_cards">Archivovat karty</string>
+
<string name="manage_accounts">Spravovat účty</string>
<string name="manage_list">Spravovat sloupec</string>
<string name="simple_reply">Odpovědět</string>
+ <string name="error_while_uploading_attachment">Chyba při nahrávání přílohy: %1$s</string>
+ <string name="append_text_to_description">Připojit k popisu</string>
+ <string name="add_text_as_comment">Přidat jako komentář</string>
+ <string name="progress_count">%1$d z %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d chyba při nahrávání</item>
+ <item quantity="few">%1$d chyby při nahrávání</item>
+ <item quantity="many">%1$d chyb při nahrávání</item>
+ <item quantity="other">%1$d chyby při nahrávání</item>
+ </plurals>
<string name="simple_report">Hlášení</string>
-</resources>
+ <string name="error_action_open_battery_settings">Nastavení správy napájení</string>
+ <string name="move_warning">Při přesunutí karty na jinou tabuli není možné přenést také komentáře či přílohy.</string>
+ <string name="clone_board">Klonovat tabuli</string>
+ <string name="cloning_board">Klonování %1$s…</string>
+ <string name="successfully_cloned_board">%1$s úspěšně naklonováno</string>
+ <string name="attachment_does_not_yet_exist">Příloha ještě neexistuje v Deck</string>
+ <string name="card_does_not_yet_exist">Karta ještě neexistuje v Deck</string>
+
+ <string name="widget_stack_title">Seznam</string>
+ <string name="widget_stack_header_icon">Ikona hlavičky ovládacího prvku</string>
+ <string name="widget_stack_placeholder_icon">Ikona výplně ovládacího prvku</string>
+ <string name="select_stack">Vybrat seznam</string>
+ <string name="project_type_deck_board">Tabule aplikace Deck</string>
+ <string name="project_type_deck_card">Karta aplikace Deck</string>
+ <string name="project_type_file">Soubor</string>
+ <string name="projects_title">Projekty</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d prostředek</item>
+ <item quantity="few">%1$d prostředky</item>
+ <item quantity="many">%1$d prostředků</item>
+ <item quantity="other">%1$d prostředky</item>
+ </plurals>
+ <string name="no_assigned_label">Nepřiřazena značka</string>
+ <string name="single_card">Jedna karta</string>
+ <string name="project_type_room">Diskuzní místnost</string>
+ <string name="simple_move">Přesunout</string>
+ <string name="cannot_upload_files_without_permission">Není možné nahrávat soubory bez oprávnění</string>
+ <string name="clone_cards">Klonovat karty</string>
+ <string name="simple_clone">Klonovat</string>
+ <string name="user_avatar">Zástupný obrázek uživatele</string>
+ <string name="simple_unassign">Zrušit přirazení</string>
+ <string name="simple_contact">Kontakt</string>
+ <string name="simple_file">Soubor</string>
+ <string name="simple_camera">Kamera</string>
+ <string name="min_api_21">Tato funkce vyžaduje minimálně verzi 5 systému Android</string>
+ <string name="take_photo">Vyfotit</string>
+ <string name="take_photo_switch_camera">Přepnout kameru</string>
+ <string name="take_photo_toggle_torch">Vyp/zap. přisvícení</string>
+ <string name="show_all_contacts">Zobrazit všechny kontakty</string>
+ <string name="show_all_files">Zobrazit všechny soubory</string>
+ <string name="recent">Nedávné</string>
+ <string name="upload_a_new_attachment">Nahrát novou přílohu</string>
+ <string name="contacts">Kontakty</string>
+ <string name="downloads">Stažené</string>
+ <string name="files">Soubory</string>
+ <string name="gallery">Galerie</string>
+ <string name="simple_attach">přiložit</string>
+ </resources>
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 59d9cd8e1..efd98ec81 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -37,7 +37,6 @@
<string name="simple_disabled">slået fra</string>
<string name="simple_copied">opieret</string>
<string name="simple_archive">Arkivér</string>
-
<string name="edit_board">Rediger liste</string>
<string name="archive_board">Arkivér liste</string>
<string name="delete_board">Slet liste</string>
@@ -185,9 +184,10 @@
<string name="error_dialog_check_maintenance">Sørg venligst for at din Nextcloud-instans ikke er i vedligeholdelsestilstand. </string>
<string name="error_dialog_insufficient_storage">Din Nextcloud-instans har ikke mere ledig lagerplads. Slet venligst nogle filer for at synkronisere dine lokale ændringer ind i skyen.</string>
<string name="error_dialog_we_need_info">Vi har brug for følgende tekniske information for at hjælpe dig:</string>
+ <string name="error_action_report_issue">Rapporter</string>
<string name="info_box_maintenance_mode">Server i vedligeholdelsestilstand</string>
<string name="share_link">Del link</string>
<string name="manage_accounts">Administrer konti</string>
<string name="simple_reply">Besvar</string>
<string name="simple_report">Rapporter</string>
-</resources>
+ </resources>
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index 0f92faa35..053b042e8 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Letztes Konto</string>
<string name="drawer_manage_accounts">Konten verwalten</string>
- <!-- Simple values -->
<string name="simple_boards">Boards</string>
<string name="simple_add">Hinzufügen</string>
<string name="simple_save">Speichern</string>
@@ -24,7 +23,7 @@
<string name="simple_switch">Wechseln</string>
<string name="simple_filter">Filter</string>
<string name="simple_overdue">Überfällig</string>
- <string name="simple_clear">Löschen</string>
+ <string name="simple_clear">Leeren</string>
<string name="simple_discard">Verwerfen</string>
<string name="simple_update">Aktualisieren</string>
<string name="simple_delete">Löschen</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">Deaktiviert</string>
<string name="simple_copied">Kopiert</string>
<string name="simple_archive">Archivieren</string>
+ <string name="simple_unassigned">Nicht zugewiesen</string>
<string name="edit_board">Board bearbeiten</string>
<string name="archive_board">Board archivieren</string>
<string name="delete_board">Board löschen</string>
<string name="delete_something">Lösche %1$s</string>
- <!-- About -->
<string name="about">Über</string>
<string name="about_version_title">Version</string>
<string name="about_version">Sie benutzen aktuell %1$s</string>
@@ -57,7 +56,7 @@
<string name="about_source_title">Quellcode</string>
<string name="about_source">Dieses Projekt wird auf GitHub gehostet: %1$s</string>
<string name="about_issues_title">Probleme</string>
- <string name="about_issues">Sie können Fehler, Verbesserungsvorschläge oder Wünsche für neue Funktionen im GitHub-Ticket-System erfassen: %1$s</string>
+ <string name="about_issues">Sie können Fehler, Verbesserungsvorschläge oder Wünsche für neue Funktionen als Issue auf GitHub erfassen: %1$s</string>
<string name="about_translate_title">Übersetzen</string>
<string name="about_translate">Treten Sie dem Nextcloud-Team bei Transifex bei und helfen Sie uns, diese App zu übersetzen: %1$s</string>
<string name="about_app_license_title">App-Lizenz</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Credits</string>
<string name="about_contribution_tab_title">Beitrag</string>
<string name="about_license_tab_title">Lizenz</string>
-
- <string name="copied_to_clipboard">In die Zwischenablage kopiert</string>
-
<string name="seconds_ago">Gerade eben</string>
<string name="edit">Bearbeiten</string>
<string name="label_labels">Schlagworte auswählen</string>
@@ -96,23 +92,24 @@
<string name="action_card_delete">Karte löschen</string>
<string name="add_board">Board hinzufügen</string>
- <string name="label_clear_due_date">Fälligkeitsdatum löschen</string>
+ <string name="label_clear_due_date">Fälligkeitsdatum entfernen</string>
<string name="label_add">%1$s hinzufügen</string>
<string name="card_edit_details">Details</string>
<string name="card_edit_attachments">Anhänge</string>
<string name="card_edit_activity">Aktivitäten</string>
<string name="about_server_app_version_text">Serverversion der App:</string>
- <string name="no_files_attached_to_this_card">An diese Karte sind keine Dateien angehängt.</string>
+ <string name="no_files_attached_to_this_card">Dieser Karte sind keine Dateien angehängt.</string>
<string name="attachments">Anhänge</string>
<string name="no_cards">Bislang keine Karten</string>
- <string name="no_account">Kein Konto konfiguriert</string>
+ <string name="no_account">Kein Konto eingerichtet</string>
<string name="account_already_added">Konto wurde bereits hinzugefügt</string>
<string name="account_is_getting_imported">Konto wird importiert</string>
<string name="not_synced_yet">Noch nicht synchronisiert</string>
<string name="no_lists_yet">Noch keine Listen vorhanden</string>
<string name="do_you_want_to_save_your_changes">Möchten Sie Ihre Änderungen speichern?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Möchten Sie alle Karten von %1$s archivieren?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Möchten Sie alle gefilterten Karten von %1$s archivieren?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Dies wird %1$d Karte permanent aus dieser Liste löschen.</item>
<item quantity="other">Dies wird %1$d Karten permanent aus dieser Liste löschen.</item>
@@ -129,6 +126,7 @@
<string name="delete_board_message">Dies wird dieses Board endgültig inklusive aller Listen und Karten löschen.</string>
<string name="settings_theme_title">Dunkles Design</string>
<string name="settings_branding_title">Branding</string>
+ <string name="settings_compact_title">Kompakt-Modus</string>
<string name="settings_background_sync">Hintergrundsynchronisierung</string>
<string name="pref_value_wifi_and_mobile">Über WLAN und mobile Daten synchronisieren</string>
<string name="pref_value_wifi_only">Nur über WLAN synchronisieren</string>
@@ -156,7 +154,6 @@
<string name="maintenance_mode_explanation">Der Server %1$s ist momentan im Wartungsmodus. Bitte nehmen Sie Kontakt mit Ihrem Administrator auf oder versuchen Sie es später nochmal!</string>
<string name="share_add_to_card">Zur Karte hinzufügen</string>
<string name="share_success">%1$s erfolgreich zu %2$s hinzugefügt</string>
- <string name="could_not_copy_to_clipboard">Konnte nicht in die Zwischenablage kopieren</string>
<string name="add_comment">Kommentar hinzufügen</string>
<string name="card_edit_comments">Kommentare</string>
<string name="no_comments_yet">Noch keine Kommentare vorhanden</string>
@@ -168,11 +165,9 @@
<string name="open_in_browser">Im Browser öffnen</string>
<string name="updating_card">Karte wird aktualisiert…</string>
- <!-- Move lists -->
<string name="move_list_right">Liste nach rechts schieben</string>
<string name="move_list_left">Liste nach links schieben</string>
- <!-- Filter -->
<string name="filter_no_filter">Alle</string>
<string name="filter_overdue">Überfällig</string>
<string name="filter_today">Heute</string>
@@ -183,7 +178,6 @@
<string name="filter_by_assigned_user">Nach zugewiesenem Benutzer filtern</string>
<string name="filter_by_duedate">Nach Fälligkeitsdatum filtern</string>
- <!-- Archived cards -->
<string name="archived_cards">Archivierte Karten</string>
<string name="action_card_dearchive">Karte archivieren rückgängig machen</string>
<string name="action_archived_cards">Archivierte Karten durchsuchen</string>
@@ -198,11 +192,9 @@
<string name="filter_user_title">Benutzer</string>
<string name="filter_duedate_title">Fälligkeitsdatum</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Board archivieren rückgängig machen</string>
<string name="archived_boards">Archivierte Boards</string>
- <!-- Errors -->
<string name="error">Es ist ein Fehler aufgetreten</string>
<string name="synchronization_failed">Synchronisierung fehlgeschlagen</string>
<string name="operation_not_yet_supported">Wird bislang noch nicht unterstützt</string>
@@ -216,11 +208,12 @@
<string name="error_dialog_title">Oh nein - Was jetzt? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Bitte versuchen Sie, das Schließen der App zu erzwingen und erneut zu starten. Möglicherweise bestand eine gestörte Verbindung zur Nextcloud-App.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Falls dieser Fehler weiterhin besteht können Sie versuchen den Speicherinhalt der beiden Apps Nextcloud und Nextcloud Deck zu löschen um das Problem zu beheben.</string>
- <string name="error_dialog_tip_clear_storage">Sie können den Speicherinhalt löschen indem Sie die App öffnen und Speicher → Speicherinhalt löschen auswählen.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Falls dieser Fehler weiterhin besteht können Sie versuchen den Speicherinhalt der beiden Apps Nextcloud und Nextcloud Deck zu leeren um das Problem zu beheben.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Die Aktualisierung der Datenbank ist fehlgeschlagen. Bitte melden Sie das Problem und leeren Sie den Speicher, um die App normal zu verwenden.</string>
+ <string name="error_dialog_tip_clear_storage">Sie können den Speicherinhalt leeren indem Sie die App öffnen und Speicher → Speicherinhalt löschen auswählen.</string>
<string name="error_dialog_tip_files_outdated">Sie scheinen eine alte Version der Nextcloud-App installiert zu haben. Bitte besuchen Sie den Play Store oder F-Droid, um die neueste Version zu installieren.</string>
<string name="error_dialog_tip_files_force_stop">Etwas scheint bei der Nextcloud App nicht zu funktionieren. Bitte versuchen Sie sowohl die Nextcloud App als auch die Nextcloud Deck App zu stoppen.</string>
- <string name="error_dialog_tip_files_delete_storage">Wenn das erzwungene Stoppen nicht hilft, können Sie versuchen, den Speicher beider Apps zu löschen.</string>
+ <string name="error_dialog_tip_files_delete_storage">Wenn das erzwungene Stoppen nicht hilft, können Sie versuchen, den Speicher beider Apps zu leeren .</string>
<string name="error_dialog_timeout_instance">Der Server reagierte nicht in der üblichen Zeit. Bitte stellen Sie sicher, dass Ihre Instanz einwandfrei läuft.</string>
<string name="error_dialog_timeout_toggle">Überprüfen Sie Ihre Netzwerkverbindung. Manchmal kann es hilfreich sein, die mobilen Daten oder das WLAN aus- und wieder einzuschalten.</string>
<string name="error_dialog_check_server">Die Antwort des Servers war fehlerhaft. Bitte überprüfen Sie ob die Deck App über die Weboberfläche erreichbar ist.</string>
@@ -230,16 +223,21 @@
<string name="error_dialog_we_need_info">Wir benötigen die folgenden technischen Informationen, um Ihnen helfen zu können:</string>
<string name="error_dialog_redirect">Ihr Server hat mit einem HTTP 302-Statuscode geantwortet, was bedeutet, dass Sie die Deck-App nicht auf Ihrem Server installiert haben oder etwas falsch konfiguriert ist. Dies kann durch benutzerdefinierte Überschreibungen in einer .htaccess-Datei oder durch Nextcloud-Apps wie OID-Client verursacht werden.</string>
<string name="error_dialog_version_not_parsable">Version der auf dem Server installierten Deck-App konnte nicht ermittelt werden. Bitte sicherstellen, dass die Deck-App intstalliert und aktiviert ist.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Das Konto Ihrer Nextcloud-App ist möglicherweise nicht mehr autorisiert.</string>
+ <string name="error_dialog_user_not_found_in_database">Der aktuelle Benutzer stimmt nicht mit dem Benutzer überein, den wir in unserer Datenbank haben. Wenn Sie LDAP in Ihrer Nextcloud-Instanz verwenden, hat Ihre Nextcloud-App möglicherweise eine alte Benutzer-ID gespeichert.</string>
<string name="error_dialog_capabilities_not_parsable">Funktionsumfang Ihres Serves konnte nicht nicht abgerufen werden. Stellen Sie sicher, dass Ihr Server ordnungsgemäß funktioniert und Client-Apps auf Nextcloud zugreifen können.</string>
<string name="error_dialog_attachment_upload_failed">Ein Anhang konnte nicht hochgeladen werden. Bitte versuchen Sie es auf andere Weise zu teilen und teilen Sie uns diesen Fehler mit.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Bitte alle Batterieoptimierungen für Nextcloud und die Deck-App deaktivieren.</string>
<string name="error_action_open_deck_info">App-Informationen öffnen</string>
<string name="error_action_open_network">Netzwerkeinstellungen</string>
<string name="error_action_server_logs">Server-Protokolle</string>
<string name="error_action_install">Installieren</string>
+ <string name="error_action_report_issue">Melden</string>
<string name="info_box_maintenance_mode">Server befindet sich im Wartungsmodus</string>
<string name="info_box_version_not_supported">Serverversion %1$s wird nicht unterstützt. Bitte aktualisiere auf %2$s.</string>
<string name="share_link">Link teilen</string>
<string name="archive_cards">Karten archivieren</string>
+
<string name="manage_accounts">Konten verwalten</string>
<string name="manage_list">Liste verwalten</string>
<string name="simple_reply">Antworten</string>
@@ -252,4 +250,50 @@
<item quantity="other">%1$d Fehler beim Hochladen</item>
</plurals>
<string name="simple_report">Melden</string>
+ <string name="error_action_open_battery_settings">Batterie-Einstellungen</string>
+ <string name="move_warning">Beim Verschieben einer Karte auf ein anderes Board werden weder Kommentare noch Anhänge transferriert.</string>
+ <string name="clone_board">Board klonen</string>
+ <string name="cloning_board">Klone %1$s…</string>
+ <string name="successfully_cloned_board">%1$s erfolgreich geklont</string>
+ <string name="attachment_does_not_yet_exist">Anhang existiert bislang nicht in Deck</string>
+ <string name="card_does_not_yet_exist">Karte existiert bislang nicht in Deck</string>
+
+ <string name="widget_stack_title">Liste</string>
+ <string name="widget_stack_header_icon">Widget-Kopf-Icon</string>
+ <string name="widget_stack_placeholder_icon">Widget-Platzhalter-Symbol</string>
+ <string name="select_stack">Liste auswählen</string>
+ <string name="project_type_deck_board">Deck-Board</string>
+ <string name="project_type_deck_card">Deck-Karte</string>
+ <string name="project_type_file">Datei</string>
+ <string name="projects_title">Projekte</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d Ressource</item>
+ <item quantity="other">%1$d Ressourcen</item>
+ </plurals>
+ <string name="no_assigned_label">Kein Schlagwort zugewiesen</string>
+ <string name="single_card">Einzelne Karte</string>
+ <string name="project_type_room">Talk-Raum</string>
+ <string name="simple_move">Verschieben</string>
+ <string name="cannot_upload_files_without_permission">Dateien können nicht ohne Berechtigung hochgeladen werden</string>
+ <string name="clone_cards">Karten klonen</string>
+ <string name="simple_clone">Klonen</string>
+ <string name="user_avatar">Benutzer-Avatar</string>
+ <string name="simple_unassign">Zuordnung aufheben</string>
+ <string name="simple_contact">Kontakt</string>
+ <string name="simple_file">Datei</string>
+ <string name="simple_camera">Kamera</string>
+ <string name="min_api_21">Diese Funktion benötigt Android 5 oder neuer</string>
+ <string name="take_photo">Foto aufnehmen</string>
+ <string name="take_photo_switch_camera">Kamera wechseln</string>
+ <string name="take_photo_toggle_torch">Taschenlampe umschalten</string>
+ <string name="show_all_contacts">Alle Kontakte anzeigen</string>
+ <string name="show_all_files">Alle Dateien anzeigen</string>
+ <string name="recent">Neueste</string>
+ <string name="upload_a_new_attachment">Neuen Anhang hochladen</string>
+ <string name="contacts">Kontakte</string>
+ <string name="downloads">Downloads</string>
+ <string name="files">Dateien</string>
+ <string name="gallery">Galerie</string>
+ <string name="simple_attach">anhängen</string>
+ <string name="add_stack_widget">Ein Listen-Widget hinzufügen</string>
</resources>
diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml
index 3c25559f3..5afd6e1f6 100644
--- a/app/src/main/res/values-el/strings.xml
+++ b/app/src/main/res/values-el/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Τελευταίος λογαριασμός</string>
<string name="drawer_manage_accounts">Διαχείριση λογαριασμών</string>
- <!-- Simple values -->
<string name="simple_boards">Πίνακες</string>
<string name="simple_add">Προσθήκη</string>
<string name="simple_save">Αποθήκευση</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">απενεργοποιημένο</string>
<string name="simple_copied">Αντιγράφτηκε</string>
<string name="simple_archive">Αρχειοθέτηση</string>
+ <string name="simple_unassigned">Χωρίς ανάθεση</string>
<string name="edit_board">Επεξεργασία πίνακα</string>
<string name="archive_board">Αρχειοθέτηση πίνακα</string>
<string name="delete_board">Διαγραφή πίνακα</string>
<string name="delete_something">Διαγραφή %1$s</string>
- <!-- About -->
<string name="about">Περί</string>
<string name="about_version_title">Έκδοση</string>
<string name="about_version">Προς το παρόν χρησιμοποιείτε %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Μνεία</string>
<string name="about_contribution_tab_title">Συνεισφορά</string>
<string name="about_license_tab_title"> Άδεια χρήσης </string>
-
- <string name="copied_to_clipboard">Αντιγράφηκε στο πρόχειρο</string>
-
<string name="seconds_ago"> δευτερόλεπτα πριν </string>
<string name="edit">Επεξεργασία</string>
<string name="label_labels">Επιλογή Ετικετών</string>
@@ -129,6 +125,7 @@
<string name="delete_board_message">Μόνιμη διαγραφή πίνακα με όλες τις λίστες και κάρτες.</string>
<string name="settings_theme_title">Σκούρο θέμα</string>
<string name="settings_branding_title">Επωνυμία</string>
+ <string name="settings_compact_title">Συμπαγής λειτουργία</string>
<string name="settings_background_sync">Συγχρονισμός στο παρασκήνιο</string>
<string name="pref_value_wifi_and_mobile">Συγχρονισμός σε Wi-Fi και δεδομένα κινητού</string>
<string name="pref_value_wifi_only">Συγχρονισμός μόνο σε Wi-Fi</string>
@@ -156,7 +153,6 @@
<string name="maintenance_mode_explanation">Ο διακομιστής %1$s είναι σε κατάσταση συντήρησης. Επικοινωνήστε με τον διαχειριστή ή δοκιμάστε αργότερα.</string>
<string name="share_add_to_card">Προσθήκη στην κάρτα</string>
<string name="share_success">Επιτυχής προσθήκη %1$s από %2$s</string>
- <string name="could_not_copy_to_clipboard">Δεν μπορεί να αντιγραφή στο πρόχειρο</string>
<string name="add_comment">Προσθήκη σχολίου</string>
<string name="card_edit_comments">Σχόλια</string>
<string name="no_comments_yet">Δεν υπάρχουν ακόμα σχόλια</string>
@@ -168,11 +164,9 @@
<string name="open_in_browser">Άνοιγμα στον περιηγητή ιστού</string>
<string name="updating_card">Ενημέρωση καρτέλας...</string>
- <!-- Move lists -->
<string name="move_list_right">Μετακίνηση λίστας δεξιά</string>
<string name="move_list_left">Μετακίνηση λίστας αριστερά</string>
- <!-- Filter -->
<string name="filter_no_filter">\'Ολα</string>
<string name="filter_overdue">Εκπρόθεσμος</string>
<string name="filter_today">Σήμερα</string>
@@ -183,7 +177,6 @@
<string name="filter_by_assigned_user">Φίλτρο ανά χρήστη</string>
<string name="filter_by_duedate">Φίλτρο ανά ημερομηνία λήξης</string>
- <!-- Archived cards -->
<string name="archived_cards">Αρχειοθετημένες κάρτες</string>
<string name="action_card_dearchive">Αναίρεση αρχειοθέτησης κάρτας</string>
<string name="action_archived_cards">Περιήγηση στις αρχειοθετημένες καρτέλες</string>
@@ -198,11 +191,9 @@
<string name="filter_user_title">Χρήστες</string>
<string name="filter_duedate_title">Προθεσμία</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Αναίρεση αρχειοθέτησης πίνακα</string>
<string name="archived_boards">Αρχειοθέτηση πινάκων </string>
- <!-- Errors -->
<string name="error">Παρουσιάστηκε σφάλμα</string>
<string name="synchronization_failed">Αποτυχία συγχρονισμού</string>
<string name="operation_not_yet_supported">Δεν υποστηρίζεται ακόμη</string>
@@ -216,7 +207,8 @@
<string name="error_dialog_title">Ωχ όχι - Τώρα τί; 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Δοκιμάστε τερματισμό της εφαρμογή και επανεκκίνηση. Ίσως υπάρχει λάθος σύνδεση.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Εάν το πρόβλημα παραμένει, προσπαθήστε να εκκαθαρίσετε τον χώρο αποθήκευσης και των δύο εφαρμογών: του Nextcloud και του Nextcloud Deck για επίλυση.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Εάν το πρόβλημα παραμένει, προσπαθήστε να εκκαθαρίσετε τον χώρο αποθήκευσης και των δύο εφαρμογών: του Nextcloud και του Nextcloud Deck για επίλυση.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Η αναβάθμιση της βάσης δεδομένων απέτυχε. Παρακαλούμε αναφέρετε το πρόβλημα και εκκαθαρίστε τον χώρο αποθήκευσης για να χρησιμοποιήσετε την εφαρμογή κανονικά.</string>
<string name="error_dialog_tip_clear_storage">Μπορείτε να εκκαθαρίσετε τον χώρο αποθήκευσης από τις πληροφορίες εφαρμογής και επιλέγοντας Αποθηκευτικός χώρος → Εκκαθάριση χώρου αποθήκευσης.</string>
<string name="error_dialog_tip_files_outdated">Η εφαρμογή Nextcloud δεν είναι ενημερωμένη. Παρακαλούμε επισκεφθείτε το Play Store ή το F-Groid για λήψη της τελευταίας έκδοσης.</string>
<string name="error_dialog_tip_files_force_stop">Κάτι φαίνεται να πάει στραβά με την εφαρμογή Nextcloud. Προσπαθήστε να τερματίσετε την εφαρμογή Nextcloud και την εφαρμογή Nextcloud Deck.</string>
@@ -230,16 +222,21 @@
<string name="error_dialog_we_need_info">Χρειαζόμαστε τις ακόλουθες τεχνικές πληροφορίες για να σας βοηθήσουμε:</string>
<string name="error_dialog_redirect">Ο διακομιστής απάντησε με κωδικό κατάστασης HTTP 302, που σημαίνει, πως δεν έχετε εγκατεστημένη την εφαρμογή Deck στον διακομιστή σας ή υπάρχει λάθος ρύθμιση. Αυτό μπορεί να προκλήθηκε από λάθος καταχώρηση στο αρχείο .htaccess-file ή σε εφαρμογές του Nextcloud όπως την OID Client.</string>
<string name="error_dialog_version_not_parsable">Δεν μπορέσαμε να προσδιορίσουμε την έκδοση της εφαρμογής Deck του διακομιστή σας. Βεβαιωθείτε ότι είναι εγκατεστημένο και ενεργοποιημένο.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Ο λογαριασμός στην εφαρμογή σας Nextcloud, ίσως δεν έχει πλέον πιστοποίηση.</string>
+ <string name="error_dialog_user_not_found_in_database">Ο τρέχων χρήστης δεν είναι ίδιος με αυτόν που έχουμε καταχωρημένο στη βάση δεδομένων μας. Εάν χρησιμοποιείτε την λειτουρία LDAP στον διακομιστή σας Nextcloud, η εφαρμογή σας ενδέχεται να έχει αποθηκευμένο ένα παλιό αναγνωριστικό χρήστη.</string>
<string name="error_dialog_capabilities_not_parsable">Δεν ήταν δυνατός ο έλεγχος των δυνατοτήτων του διακομιστή σας. Βεβαιωθείτε ότι ο διακομιστής σας λειτουργεί σωστά και οι εφαρμογές έχουν πρόσβαση στο Nextcloud.</string>
<string name="error_dialog_attachment_upload_failed">Δεν ήταν δυνατή η μεταφόρτωση ενός συνημμένου. Προσπαθήστε να το διαμοιράσετε με άλλο τρόπο και ενημερώστε μας σχετικά με το σφάλμα.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Παρακαλούμε απενεργοποιήστε όλες τις βελτιστοποιήσεις μπαταρίας για το Nextcloud και την εφαρμογή Deck.</string>
<string name="error_action_open_deck_info">Πληροφορίες εφαρμογής</string>
<string name="error_action_open_network">Ρυθμίσεις δικτύου</string>
<string name="error_action_server_logs">Αρχεία καταγραφής διακομιστή</string>
<string name="error_action_install">Εγκατάσταση</string>
+ <string name="error_action_report_issue">Αναφορά</string>
<string name="info_box_maintenance_mode">Ο διακομιστής είναι σε κατάσταση συντήρησης</string>
<string name="info_box_version_not_supported">Η έκδοση του διακομιστή %1$s δεν υποστηρίζεται, παρακαλούμε αναβαθμίστε στην %2$s</string>
<string name="share_link">Διαμοιρασμός συνδέσμου</string>
<string name="archive_cards">Αρχειοθετημένες κάρτες</string>
+
<string name="manage_accounts">Διαχείριση λογαριασμών</string>
<string name="manage_list">Διαχείριση λίστας</string>
<string name="simple_reply">Απάντηση</string>
@@ -252,4 +249,30 @@
<item quantity="other">%1$d σφάλματα κατά την μεταφόρτωση</item>
</plurals>
<string name="simple_report">Αναφορά</string>
-</resources>
+ <string name="error_action_open_battery_settings">Ρυθμίσεις μπαταρίας</string>
+ <string name="move_warning">Δεν μπορούν να μεταφερθούν τα σχόλια, ούτε τα συνημμένα κατά τη μετακίνηση της κάρτας σε άλλο πίνακα.</string>
+ <string name="clone_board">Κλωνοποίηση πίνακα</string>
+ <string name="cloning_board">Κλωνοποίηση %1$s…</string>
+ <string name="successfully_cloned_board">Επιτυχής κλωνοποίηση %1$s</string>
+ <string name="attachment_does_not_yet_exist">Δεν υπάρχει ακόμα το συνημμένο στο Deck</string>
+ <string name="card_does_not_yet_exist">Η κάρτα δεν υπάρχει ακόμα στο Deck</string>
+
+ <string name="widget_stack_title">Λίστα</string>
+ <string name="widget_stack_header_icon">Εικονίδιο επικεφαλίδας του γραφικού στοιχείου</string>
+ <string name="widget_stack_placeholder_icon">Εικονίδιο θέσης κράτησης του γραφικού στοιχείου</string>
+ <string name="select_stack">Επιλέξτε λίστα</string>
+ <string name="project_type_deck_board">Πίνακας του Deck</string>
+ <string name="project_type_deck_card">Κάρτα του Deck</string>
+ <string name="project_type_file">Αρχείο</string>
+ <string name="projects_title">Projects</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d πόρος</item>
+ <item quantity="other">%1$d πόροι</item>
+ </plurals>
+ <string name="no_assigned_label">Δεν καταχωρήθηκε ετικέτα</string>
+ <string name="single_card">Απλή καρτέλα</string>
+ <string name="project_type_room">Δωμάτιο του Talk </string>
+ <string name="simple_move">Μετακίνηση</string>
+ <string name="cannot_upload_files_without_permission">Δεν μπορείτε να ανεβάσετε αρχεία χωρίς άδεια</string>
+ <string name="simple_unassign">Αποδέσμευση</string>
+ </resources>
diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml
index 222ad2b53..763c1f02f 100644
--- a/app/src/main/res/values-es/strings.xml
+++ b/app/src/main/res/values-es/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Última cuenta</string>
<string name="drawer_manage_accounts">Gestionar cuentas</string>
- <!-- Simple values -->
<string name="simple_boards">Tableros</string>
<string name="simple_add">Añadir</string>
<string name="simple_save">Guardar</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">deshabilitado</string>
<string name="simple_copied">Copiado</string>
<string name="simple_archive">Archivar</string>
+ <string name="simple_unassigned">No asignado</string>
<string name="edit_board">Editar tablero</string>
<string name="archive_board">Archivar tablero</string>
<string name="delete_board">Eliminar tablero</string>
<string name="delete_something">Borrar %1$s</string>
- <!-- About -->
<string name="about">Acerca de</string>
<string name="about_version_title">Version</string>
<string name="about_version">Actualmente estás usando %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Créditos</string>
<string name="about_contribution_tab_title">Contribuir</string>
<string name="about_license_tab_title">Licencia</string>
-
- <string name="copied_to_clipboard">Copiado al portapapeles</string>
-
<string name="seconds_ago">hace unos segundos</string>
<string name="edit">Editar</string>
<string name="label_labels">Seleccionar etiquetas</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">Aún no hay listas</string>
<string name="do_you_want_to_save_your_changes">¿Desea guardar los cambios?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">¿Desea archivar todas las tarjetas de %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">¿Desea archivar todas las tarjetas filtradas de %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Esto eliminará permanentemente %1$d tarjeta de esta lista.</item>
<item quantity="other">Esto eliminará permanentemente todas las %1$d tarjetas de esta lista.</item>
@@ -129,6 +126,7 @@
<string name="delete_board_message">Esto borrará permanente el tablero, incluyendo todas las listas y tarjetas.</string>
<string name="settings_theme_title">Tema oscuro</string>
<string name="settings_branding_title">Branding</string>
+ <string name="settings_compact_title">Modo compacto</string>
<string name="settings_background_sync">Sincronización en segundo plano</string>
<string name="pref_value_wifi_and_mobile">Sincronizar sobre Wi-Fi y datos móviles</string>
<string name="pref_value_wifi_only">Sincronizar solo sobre Wi-Fi</string>
@@ -156,7 +154,6 @@
<string name="maintenance_mode_explanation">El servidor %1$s está actualmente en modo de mantenimiento. Por favor, contacta con tu administrador o vuelve a intentarlo más tarde.</string>
<string name="share_add_to_card">Añadir a la tarjeta</string>
<string name="share_success">Añadido satisfactoriamente %1$s a %2$s</string>
- <string name="could_not_copy_to_clipboard">No se puede copiar al portapapeles</string>
<string name="add_comment">Agregar comentario</string>
<string name="card_edit_comments">Comentarios</string>
<string name="no_comments_yet">Todavía no hay comentarios.</string>
@@ -168,11 +165,9 @@
<string name="open_in_browser">Abrir en navegador</string>
<string name="updating_card">Actualizando tarjeta</string>
- <!-- Move lists -->
<string name="move_list_right">Mover lista a la derecha</string>
<string name="move_list_left">Mover lista a la izquierda</string>
- <!-- Filter -->
<string name="filter_no_filter">Todo</string>
<string name="filter_overdue">Demorado</string>
<string name="filter_today">Hoy</string>
@@ -183,7 +178,6 @@
<string name="filter_by_assigned_user">Filtrar por usuario asignado</string>
<string name="filter_by_duedate">Filtrar por fecha de finalización</string>
- <!-- Archived cards -->
<string name="archived_cards">Tarjetas archivadas</string>
<string name="action_card_dearchive">Deshacer archivado de tarjeta</string>
<string name="action_archived_cards">Explorar las tarjetas archivadas</string>
@@ -198,11 +192,9 @@
<string name="filter_user_title">Usuarios</string>
<string name="filter_duedate_title">Fecha de fin</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Deshacer archivado del tablero</string>
<string name="archived_boards">Tableros archivados</string>
- <!-- Errors -->
<string name="error">Ha aparecido un error</string>
<string name="synchronization_failed">Fallo en la sincronización</string>
<string name="operation_not_yet_supported">No está soportado todavía</string>
@@ -216,13 +208,14 @@
<string name="error_dialog_title">Oh no - ¿Ahora qué? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Por favor, intenta forzar el cierre de la aplicación y vuelve a abrirla. Es posible que haya habido un problema de conexión con la aplicación Nextcloud.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Si el problema persiste, intenta limpiar borrar el almacenamiento de ambas apps, Nextcloud y Nextcloud Deck para solucionar este problema.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Si el problema persiste, intenta borrar el almacenamiento de ambas apps: Nextcloud y Nextcloud Deck para solucionar este problema.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Falló la actualización de la base de datos. Por favor, informe del problema y limpie el almacenamiento para usar la aplicación de manera normal.</string>
<string name="error_dialog_tip_clear_storage">Puedes limpiar el almacenamiento abriendo la información de la app y seleccionando Almacenamiento → Eliminar datos.</string>
<string name="error_dialog_tip_files_outdated">Su aplicación Nextcloud parece que está desactualizada. Por favor visite la Play Store o F-Droid para conseguir la versión más reciente.</string>
<string name="error_dialog_tip_files_force_stop">Algo parece ir mal en tu app de Nextcloud. Por favor, intenta forzar el cierre de ambas, Nextcloud y Nextcloud Deck.</string>
<string name="error_dialog_tip_files_delete_storage">Si pararlas no ayuda, puede intentar borrar el almacenamiento de ambas aplicaciones.</string>
<string name="error_dialog_timeout_instance">No hubo una respuesta de su servidor en un tiempo determinado. Por favor asegúrese de que su instancia está funcionando sin problemas.</string>
- <string name="error_dialog_timeout_toggle">Comprueba tu conexión de red. A veces, desactivar y activar de nuevo los datos móviles o la Wi-Fi puede ayudar.</string>
+ <string name="error_dialog_timeout_toggle">Comprueba tu conexión de red. A veces, desactivar y activar de nuevo los datos móviles o el Wi-Fi puede ayudar.</string>
<string name="error_dialog_check_server">La respuesta de tu servidor no es correcta. Por favor, comprueba si puedes accede a la app de Deck mediante la interfaz web.</string>
<string name="error_dialog_check_server_logs">Hay un problema con sus ajustes de Nextcloud. Por favor eche un vistazo a los archivos de registros del servidor.</string>
<string name="error_dialog_check_maintenance">Por favor compruebe que su intancia Nextcloud no este actualmente en modo de mantenimiento.</string>
@@ -230,17 +223,77 @@
<string name="error_dialog_we_need_info">Necesitamos la siguiente información técnica para ayudarle:</string>
<string name="error_dialog_redirect">Tu servidor respondió con el código de estado HTTP 302, lo que implica que no está instalada la aplicación Deck en su servidor o que algo está mal configurado. Esto puede estar causado por anulaciones personalizadas en un archivo .htaccess o por aplicaciones de Nexcloud como OID Client. </string>
<string name="error_dialog_version_not_parsable">No se pudo determinar la versión de la aplicación Deck en el lado del servidor. Por favor, compruebe que está instalada y activada. </string>
+ <string name="error_dialog_account_might_not_be_authorized">La cuenta de tu app Nextcloud podría no estar autorizada.</string>
+ <string name="error_dialog_user_not_found_in_database">El usuario actual no coincide con el usuario que tenemos en nuestra base de datos. Si estás usando LDAP en tu instancia de Nextcloud, tu app de Nextcloud puede haber almacenado una ID de usuario antigua.</string>
<string name="error_dialog_capabilities_not_parsable">No se ha podido comprobar las capacidades de su servidor. Por favor compruebe que su servidor está funcionando bien y que otras aplicaciones de cliente son capaces de acceder a Nextcloud.</string>
+ <string name="error_dialog_attachment_upload_failed">No se ha podido subir un adjunto. Por favor, intenta compartirlo de otra forma y comunícanos este fallo.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Por favor, desactiva todas las optimizaciones de la batería para Nextcloud y la aplicación Deck.</string>
<string name="error_action_open_deck_info">Abrir info de la app</string>
<string name="error_action_open_network">Configuración de red</string>
<string name="error_action_server_logs">Registros del servidor</string>
<string name="error_action_install">Instalar</string>
+ <string name="error_action_report_issue">Informe</string>
<string name="info_box_maintenance_mode">Servidor en modo de mantenimiento</string>
<string name="info_box_version_not_supported">La versión %1$s del servidor no está soportada. Por favor, actualiza a %2$s</string>
<string name="share_link">Compartir enlace</string>
<string name="archive_cards">Tarjetas archivadas</string>
+
<string name="manage_accounts">Gestionar cuentas</string>
<string name="manage_list">Administrar su lista</string>
<string name="simple_reply">Responder</string>
+ <string name="error_while_uploading_attachment">Error al subir adjunto: %1$s</string>
+ <string name="append_text_to_description">Añadir a la descripción</string>
+ <string name="add_text_as_comment">Añadir como comentario</string>
+ <string name="progress_count">%1$d de %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d error al subir</item>
+ <item quantity="other">%1$d errores al subir</item>
+ </plurals>
<string name="simple_report">Informe</string>
+ <string name="error_action_open_battery_settings">Ajustes de batería</string>
+ <string name="move_warning">No se pueden transferir cio los comentarios ni los adjuntos al mover una tarjeta a otro tablero.</string>
+ <string name="clone_board">Clonar tablero</string>
+ <string name="cloning_board">Clonando %1$s…</string>
+ <string name="successfully_cloned_board">Clonados con éxito %1$s</string>
+ <string name="attachment_does_not_yet_exist">El adjunto todavía no existe en Deck</string>
+ <string name="card_does_not_yet_exist">La tarjeta todavía no existe en Deck</string>
+
+ <string name="widget_stack_title">Lista</string>
+ <string name="widget_stack_header_icon">Icono de la cabecera del widget</string>
+ <string name="widget_stack_placeholder_icon">Espacio para icono del widget</string>
+ <string name="select_stack">Seleccione lista</string>
+ <string name="project_type_deck_board">Tablero Deck</string>
+ <string name="project_type_deck_card">Tarjeta Deck</string>
+ <string name="project_type_file">Archivo</string>
+ <string name="projects_title">Proyectos</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d recurso</item>
+ <item quantity="other">%1$d recursos</item>
+ </plurals>
+ <string name="no_assigned_label">Sin etiqueta asignada</string>
+ <string name="single_card">Carta única</string>
+ <string name="project_type_room">Sala de conversación</string>
+ <string name="simple_move">Mover</string>
+ <string name="cannot_upload_files_without_permission">No se puede subir archivos sin permiso</string>
+ <string name="clone_cards">Clonar tarjetas</string>
+ <string name="simple_clone">Clonar</string>
+ <string name="user_avatar">Usar avatar</string>
+ <string name="simple_unassign">Sin asignar</string>
+ <string name="simple_contact">Contacto</string>
+ <string name="simple_file">Archivo</string>
+ <string name="simple_camera">Camera</string>
+ <string name="min_api_21">Esta característica requiere Android 5 como mínimo</string>
+ <string name="take_photo">Tomar una foto</string>
+ <string name="take_photo_switch_camera">Cambiar cámara</string>
+ <string name="take_photo_toggle_torch">Activar/desactivar linterna</string>
+ <string name="show_all_contacts">Mostrar todos los contactos</string>
+ <string name="show_all_files">Mostrar todos los archivos</string>
+ <string name="recent">Reciente</string>
+ <string name="upload_a_new_attachment">Subir un nuevo adjunto</string>
+ <string name="contacts">Contactos</string>
+ <string name="downloads">Descargas</string>
+ <string name="files">Archivos</string>
+ <string name="gallery">Galería</string>
+ <string name="simple_attach">adjuntar</string>
+ <string name="add_stack_widget">Agregar lista de widget</string>
</resources>
diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml
index f7e51fc35..6a7460eb9 100644
--- a/app/src/main/res/values-eu/strings.xml
+++ b/app/src/main/res/values-eu/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Azken kontua</string>
<string name="drawer_manage_accounts">Kudeatu kontuak</string>
- <!-- Simple values -->
<string name="simple_boards">Mahaiak</string>
<string name="simple_add">Gehitu</string>
<string name="simple_save">Gorde</string>
@@ -28,7 +27,7 @@
<string name="simple_discard">baztertu</string>
<string name="simple_update">Eguneratu</string>
<string name="simple_delete">Ezabatu</string>
- <string name="simple_rename">Berrizendatu</string>
+ <string name="simple_rename">Aldatu izena</string>
<string name="simple_settings">Ezarpenak</string>
<string name="simple_undo">Desegin</string>
<string name="simple_manage">kudeatu</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">desgaituta</string>
<string name="simple_copied">Kopiatuta</string>
<string name="simple_archive">Artxibatu</string>
+ <string name="simple_unassigned">Esleitu gabea</string>
<string name="edit_board">Editatu mahaia</string>
<string name="archive_board">Artxibatu mahaia</string>
<string name="delete_board">Ezabatu mahaia</string>
<string name="delete_something">Ezabatu %1$s</string>
- <!-- About -->
<string name="about">Honi buruz</string>
<string name="about_version_title">Bertsioa</string>
<string name="about_version">%1$s erabiltzen ari zara</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Kredituak</string>
<string name="about_contribution_tab_title">Lagundu!</string>
<string name="about_license_tab_title">Lizentzia</string>
-
- <string name="copied_to_clipboard">Kopiatu arbelera</string>
-
<string name="seconds_ago">segundo lehenago</string>
<string name="edit">Editatu</string>
<string name="label_labels">Hautatu etiketak</string>
@@ -86,9 +82,9 @@
<string name="choose_account">Aukeratu kontua</string>
<string name="add_card">Gehitu txartel berria</string>
<string name="activity">Jarduera</string>
- <string name="add_list">Zerrenda gehitu</string>
- <string name="rename_list">Zerrenda berrizendatu</string>
- <string name="delete_list">Zerrenda ezabatu</string>
+ <string name="add_list">Gehitu zerrenda</string>
+ <string name="rename_list">Aldatu izena zerrendari</string>
+ <string name="delete_list">Ezabatu zerrenda</string>
<string name="label_menu">menua</string>
<string name="action_card_assign">Esleitu txartel hau niri</string>
<string name="action_card_unassign">Txartel hau niri esleitzeari utzi</string>
@@ -112,6 +108,7 @@
<string name="not_synced_yet">Oraindik sinkronizatu gabe</string>
<string name="no_lists_yet">Ez dago zerrendarik</string>
<string name="do_you_want_to_save_your_changes">Zure aldaketak gorde nahi dituzu?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">%1$s-ko txartel guztiak artxibatu nahi dituzu?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Honek zerrenda honen txartel %1$d ezabatuko du betirako.</item>
<item quantity="other">Honek zerrenda honen %1$d txartel ezabatuko ditu betirako.</item>
@@ -122,12 +119,13 @@
</plurals>
<string name="add_a_new_list_using_the_button">Gehitu zerrenda berria + botoia erabiliz</string>
<string name="add_a_new_card_using_the_button">Gehitu txartel berria + botoia erabiliz</string>
- <string name="update_deck">Eguneratu sorta</string>
- <string name="your_deck_version_is_too_old">Zure sorta bertsioa zaharregia da</string>
- <string name="deck_outdated_please_update">Zure sorta bertsioa zaharregia da (%1$s). Mesedez eguneratu ezazu android aplikazio hau bezero gisa erabiltzeko.</string>
+ <string name="update_deck">Eguneratu Deck</string>
+ <string name="your_deck_version_is_too_old">Zure Deck bertsioa zaharregia da</string>
+ <string name="deck_outdated_please_update">Zure Deck bertsioa zaharregia da (%1$s). Mesedez eguneratu ezazu android aplikazio hau bezero gisa erabiltzeko.</string>
<string name="delete_board_message">Honek mahai hau eta bertako zerrenda eta txartel guztiak ezabatuko ditu.</string>
<string name="settings_theme_title">Gai iluna</string>
<string name="settings_branding_title">Marka</string>
+ <string name="settings_compact_title">Modu trinkoa</string>
<string name="settings_background_sync">Atzeko planoko sinkronizazioa</string>
<string name="pref_value_wifi_and_mobile">Sinkronizatu Wi-Fi eta datu mugikorrekin</string>
<string name="pref_value_wifi_only">Sinkronizatu Wi-Fiarekin soilik</string>
@@ -147,7 +145,7 @@
<string name="hours_6">6 ordu</string>
<string name="action_card_move">Mugitu txartela</string>
<string name="action_card_move_title">Mugitu %1$s</string>
- <string name="please_add_an_account_first">Mesedez, gehitu kontu bat lehenengo</string>
+ <string name="please_add_an_account_first">Gehitu kontu bat lehenengo</string>
<string name="title_is_mandatory">Izenburua jartzea nahitaezkoa da</string>
<string name="provide_at_least_a_title_or_description">Jarri gutxienez izenburu edo deskribapen bat</string>
<string name="welcome_text">Ongi etorri %1$s(e)ra</string>
@@ -155,23 +153,20 @@
<string name="maintenance_mode_explanation">%1$s zerbitzaria mantentze moduan dago. Mesedez jarri harremanetan zure administratzailearekin edo saiatu berriro.</string>
<string name="share_add_to_card">Gehitu txartelera</string>
<string name="share_success">%1$s ondo gehitu da %2$s -ra</string>
- <string name="could_not_copy_to_clipboard">Ezin izan da arbelera kopiatu</string>
<string name="add_comment">Gehitu iruzkina</string>
<string name="card_edit_comments">Iruzkinak</string>
<string name="no_comments_yet">Iruzkinik ez oraindik</string>
<string name="no_boards">Ez dago mahairik oraindik</string>
- <string name="add_a_new_board_using_the_button">Gehitu mahai berri bat + botoia erabiliz</string>
+ <string name="add_a_new_board_using_the_button">Gehitu mahai berria + botoia erabiliz</string>
<string name="choose_board">Aukeratu mahaia</string>
<string name="choose_list">Aukeratu zerrenda</string>
<string name="task_count">%2$s(e)tik %1$s</string>
<string name="open_in_browser">Ireki nabigatzailean</string>
<string name="updating_card">Txartela eguneratzen...</string>
- <!-- Move lists -->
<string name="move_list_right">Mugitu zerrenda eskuinera</string>
<string name="move_list_left">Mugitu zerrenda ezkerrera</string>
- <!-- Filter -->
<string name="filter_no_filter">Denak</string>
<string name="filter_overdue">Atzeratuta</string>
<string name="filter_today">Gaur</string>
@@ -182,7 +177,6 @@
<string name="filter_by_assigned_user">Iragazi esleitutako erabiltzailez</string>
<string name="filter_by_duedate">Iragazi epe-mugaz</string>
- <!-- Archived cards -->
<string name="archived_cards">Artxibatutako txartelak</string>
<string name="action_card_dearchive">Desegin txartelak artxibatzea</string>
<string name="action_archived_cards">Arakatu artxibatutako txartelak</string>
@@ -197,11 +191,9 @@
<string name="filter_user_title">Erabiltzaileak</string>
<string name="filter_duedate_title">Epe-muga</string>
- <!-- Archived boards -->
- <string name="action_board_dearchive">Desegin mahaiaren artxiboa</string>
- <string name="archived_boards">Artxibatutako taulak</string>
+ <string name="action_board_dearchive">Desegin mahaia artxibatzea</string>
+ <string name="archived_boards">Artxibatutako mahaiak</string>
- <!-- Errors -->
<string name="error">Errore bat agertu da</string>
<string name="synchronization_failed">Sinkronizazioak huts egin du</string>
<string name="operation_not_yet_supported">Oraingoz ez da onartzen</string>
@@ -211,27 +203,75 @@
<string name="server_misconfigured">Zerbitzaria gaizki konfiguratuta</string>
<string name="server_error">Zerbitzari akatsa</string>
<string name="shared_error">Hirugarrenen aplikazioetatik edukiak partekatzea ez da guztiz onartzen oraingoz. Saiatu lehenengo deskargatzen eta gero zure fitxategi kudeatzailetik edo galeriatik partekatzen.</string>
+ <string name="error_edit_activity_killed_by_android">Androidek edizio modua eten du sistemako baliabide gehiago behar dituelako beste aplikazio batzuentzat.</string>
+
<string name="error_dialog_title">Oh ez - Orain zer? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Saia zaitez aplikazioa itxi eta berrabiarazten. Agian Nextcloud aplikazioarekin gaizki konektatzea gertatu da.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Arazoak jarraitzen badu, saiatu bi aplikazioen biltegiak garbitzen: Nextcloud eta Nextcloud Sortak aplikazioenak arazoa konpontzeko.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Arazoak jarraitzen badu, saiatu bi aplikazioen biltegiak garbitzen: Nextcloud eta Nextcloud Deck aplikazioenak, arazoa konpontzeko.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Datu-basearen bertsio-berritzeak huts egin du. Eman arazoaren berri eta biltegia garbitu aplikazioa normaltasunez erabiltzeko.</string>
+ <string name="error_dialog_tip_clear_storage">Biltegia garbi dezakezu aplikazioaren informazioa ireki eta hautatuz: Biltegia → Garbitu biltegia.</string>
<string name="error_dialog_tip_files_outdated">Daukazun Nextcloud aplikazioa zaharkituta dagoela dirudi. Zoaz Play Store edo F-Droidera azken bertsioa lortzeko.</string>
- <string name="error_dialog_tip_files_force_stop">Daukazun Nextcloud aplikazioan zerbait gaizki dabilela dirudi. Saia zaitez Nextcloud aplikazioa eta Nextcloud Sortak geldiarazten. </string>
+ <string name="error_dialog_tip_files_force_stop">Daukazun Nextcloud aplikazioan zerbait gaizki dabilela dirudi. Saia zaitez Nextcloud aplikazioa eta Nextcloud Deck geldiarazten. </string>
<string name="error_dialog_tip_files_delete_storage">Geldiarazteak ez badu arazoa konpontzen, bi biltegiak garbitzen saia zaitezke.</string>
<string name="error_dialog_timeout_instance">Ez da zerbitzariaren erantzunik jaso emandako denboran. Ziurta ezazu zure instantzia ondo dabilela.</string>
- <string name="error_dialog_check_server">Zerbitzariaren erantzuna ez da zuzena izan. Egiazta ezazu Sortak aplikazioa atzitu dezakezula web interfazearen bidez.</string>
+ <string name="error_dialog_timeout_toggle">Egiaztatu zure datu konexioa. Batzuetan mugikorraren datuak edo WI-FI konexioa itzali eta piztea lagungarri da.</string>
+ <string name="error_dialog_check_server">Zerbitzariaren erantzuna ez da zuzena izan. Egiazta ezazu Deck aplikazioa atzitu dezakezula web interfazearen bidez.</string>
<string name="error_dialog_check_server_logs">Arazo bat dago zure Nextcloud ezarpenekin. Eman begiratu bat zerbitzariko egunkari-fitxategiei.</string>
<string name="error_dialog_check_maintenance">Egiazta ezazu zure Nextcloud instantzia ez dagoela mantentze moduan une honetan.</string>
<string name="error_dialog_insufficient_storage">Zure Nextcloud instantziak ez du biltegiratze leku libre gehiagorik. Ezabatu fitxategi batzuk, aldaketa lokalak hodeiarekin sinkroniza daitezen.</string>
<string name="error_dialog_we_need_info">Zuri laguntzeko hurrengo informazio teknikoa behar dugu:</string>
- <string name="error_dialog_redirect">Zure zerbitzariak HTTP 302 egoera kodearekin erantzun du, honek esan nahi du ez duzula Sortak aplikazioa ez duzula zure zerbitzarian instalatu edo zerbait txarto konfiguratuta dagoela. Hau .htaccess fitxategi batean arau pertsonalizatuak daudelako edo OID Bezeroa izeneko aplikazioarengatik sortua izan daiteke. </string>
+ <string name="error_dialog_redirect">Zure zerbitzariak HTTP 302 egoera kodearekin erantzun du, honek esan nahi du ez duzula Deck aplikazioa ez duzula zure zerbitzarian instalatu edo zerbait txarto konfiguratuta dagoela. Hau .htaccess fitxategi batean arau pertsonalizatuak daudelako edo OID Bezeroa izeneko aplikazioarengatik sortua izan daiteke. </string>
+ <string name="error_dialog_version_not_parsable">Ezin izan dugu zerbitzariaren Deck aplikazioaren bertsioa zehaztu. Ziurtatu instalatuta eta gaituta dagoela.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Baliteke Nextcloud aplikazioaren kontua baimenduta ez egotea.</string>
+ <string name="error_dialog_user_not_found_in_database">Egungo erabiltzailea ez dator bat gure datu-basean daukagun erabiltzailearekin. Zure Nextcloud instantzian LDAP erabiltzen ari bazara, baliteke zure Nextcloud aplikazioak erabiltzaile-ID zahar bat biltegiratu izana.</string>
+ <string name="error_dialog_capabilities_not_parsable">Ezin izan ditugu zure zerbitzariko gaitasunak eskuratu. Ziurtatu zure zerbitzaria badabilela eta beste bezero aplikazioak Nexcloud atzitzeko gai direla.</string>
+ <string name="error_dialog_attachment_upload_failed">Eranskin bat ezin izan da igo. Parteka ezazu beste bide batetik eta emaiguzu akats honen berri, mesedez.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Desgaitu bateria optimizazio guztiak Nextcloudentzat eta Deck aplikazioarentzat.</string>
<string name="error_action_open_deck_info">Ireki aplikazioaren informazioa</string>
<string name="error_action_open_network">Sareko ezarpenak</string>
<string name="error_action_server_logs">Zerbitzariaren egunkariak</string>
<string name="error_action_install">Instalatu</string>
+ <string name="error_action_report_issue">Jakinarazi</string>
<string name="info_box_maintenance_mode">Zerbitzaria mantentze-lanen moduan</string>
<string name="info_box_version_not_supported">Zerbitzariaren %1$s bertsioa ez dago onartuta, mesedez eguneratu %2$s(e)ra</string>
<string name="share_link">Partekatu esteka</string>
+ <string name="archive_cards">Artxibatu txartelak</string>
+
<string name="manage_accounts">Kudeatu kontuak</string>
+ <string name="manage_list">Kudeatu zerrenda</string>
<string name="simple_reply">Erantzun</string>
+ <string name="error_while_uploading_attachment">Errorea eranskin hau igotzean: %1$s</string>
+ <string name="append_text_to_description">Erantsi deskripzioari</string>
+ <string name="add_text_as_comment">Gehitu iruzkin gisa</string>
+ <string name="progress_count">%2$dtik%1$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$derrore kargatzerakoan</item>
+ <item quantity="other">%1$derrore kargatzerakoan</item>
+ </plurals>
<string name="simple_report">Jakinarazi</string>
-</resources>
+ <string name="error_action_open_battery_settings">Bateria ezarpenak</string>
+ <string name="move_warning">Ez iruzkinak eta ez eranskinak ezin izan dira transferitu txartela beste mahai batera mugitzean.</string>
+ <string name="clone_board">Klonatu mahaia</string>
+ <string name="cloning_board">%1$sklonatzen...</string>
+ <string name="successfully_cloned_board">%1$sondo klonatu da</string>
+ <string name="attachment_does_not_yet_exist">Eranskina oraindik ez dago Deck-en</string>
+ <string name="card_does_not_yet_exist">Txartela oraindik ez dago Deck-en</string>
+
+ <string name="widget_stack_title">Zerrenda</string>
+ <string name="widget_stack_header_icon">Goiburu widgetaren ikonoa</string>
+ <string name="widget_stack_placeholder_icon">Leku-marka widgetaren ikonoa</string>
+ <string name="select_stack">Hautatu zerrenda</string>
+ <string name="project_type_deck_board">Deck mahaia</string>
+ <string name="project_type_deck_card">Deck txartela</string>
+ <string name="project_type_file">Fitxategia</string>
+ <string name="projects_title">Proiektuak</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$dbaliabide</item>
+ <item quantity="other">%1$dbaliabide</item>
+ </plurals>
+ <string name="no_assigned_label">Esleitutako etiketarik ez</string>
+ <string name="single_card">Txartel bakana</string>
+ <string name="project_type_room">Hitz egiteko gela</string>
+ <string name="simple_move">Mugitu</string>
+ <string name="cannot_upload_files_without_permission">Ezin da fitxategirik kargatu baimenik gabe</string>
+ </resources>
diff --git a/app/src/main/res/values-fi-rFI/strings.xml b/app/src/main/res/values-fi-rFI/strings.xml
index 3847d85a8..088be8c83 100644
--- a/app/src/main/res/values-fi-rFI/strings.xml
+++ b/app/src/main/res/values-fi-rFI/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Viimeisin tili</string>
<string name="drawer_manage_accounts">Tilien hallinta</string>
- <!-- Simple values -->
<string name="simple_boards">Taulut</string>
<string name="simple_add">Lisää</string>
<string name="simple_save">Tallenna</string>
@@ -21,6 +20,7 @@
<string name="simple_exception">Poikkeus</string>
<string name="simple_close">sulje</string>
<string name="simple_open">Avoinna</string>
+ <string name="simple_switch">Kytkin</string>
<string name="simple_filter">Suodata</string>
<string name="simple_overdue">Myöhässä</string>
<string name="simple_clear">Tyhjennä</string>
@@ -36,13 +36,14 @@
<string name="simple_comment">Kommentti</string>
<string name="simple_disabled">poistettu käytöstä</string>
<string name="simple_copied">Kopioitu</string>
+ <string name="simple_archive">Arkisto</string>
+ <string name="simple_unassigned">Määrittämätön</string>
<string name="edit_board">Muokkaa taulua</string>
<string name="archive_board">Arkistoi taulu</string>
<string name="delete_board">Poista taulu</string>
<string name="delete_something">Poista %1$s</string>
- <!-- About -->
<string name="about">Tietoja</string>
<string name="about_version_title">Versio</string>
<string name="about_version">Käytössäsi on %1$s</string>
@@ -68,16 +69,21 @@
<string name="about_credits_tab_title">Tekijät</string>
<string name="about_contribution_tab_title">Kontribuutio</string>
<string name="about_license_tab_title">Lisenssi</string>
-
<string name="seconds_ago">sekuntia sitten</string>
<string name="edit">Muokkaa</string>
<string name="label_labels">Valitse tunnisteet</string>
<string name="label_description">Kuvaus</string>
<string name="hint_assign_people">Määritä käyttäjät</string>
<string name="hint_due_date_date">Eräpäivä</string>
+ <string name="card_not_found">Korttia ei löydy</string>
+ <string name="card_not_found_message">Korttia ei löytynyt. Se voi olla äskettäin poistettu.</string>
+
<string name="add_account">Lisää tili</string>
<string name="choose_account">Valitse tili</string>
+ <string name="add_card">Lisää uusi kortti</string>
<string name="activity">Aktiviteetti</string>
+ <string name="add_list">Lisää lista</string>
+ <string name="rename_list">Nimeä lista uudelleen</string>
<string name="delete_list">Poista lista</string>
<string name="label_menu">Valikko</string>
<string name="action_card_assign">Määritä kortti minulle</string>
@@ -89,13 +95,6 @@
<string name="label_clear_due_date">Poista eräpäivä</string>
<string name="label_add">Lisää %1$s</string>
- <!-- Error messages -->
- <string name="error">Tapahtui virhe</string>
- <string name="copied_to_clipboard">Kopioitu leikepöydälle</string>
- <string name="maintenance_mode">Palvelin on huoltotilassa</string>
- <string name="server_misconfigured">Virheelliset palvelimen asetukset</string>
- <string name="server_misconfigured_explanation">Palvelimesi vastasi HTTP 302 tilakoodilla, joka tarkoittaa, että Deck/pakka sovellusta ei ole asennettu palvelimellesi tai jossain on virheelliset asetukset. Tämän voi aiheuttaa muunneltu .htaccess tiedosto tai Nextcloud sovellus kuten OID asiakas. Tukipalvelun yhteystiedot löytyvät muuta valikosta. </string>
- <string name="server_error">Palvelinvirhe</string>
<string name="card_edit_details">Tiedot</string>
<string name="card_edit_attachments">Liitteet</string>
<string name="card_edit_activity">Tapahtumat</string>
@@ -107,12 +106,26 @@
<string name="account_already_added">Tili on jo lisätty</string>
<string name="account_is_getting_imported">Tiliä tuodaan sisään</string>
<string name="not_synced_yet">Ei vielä synkronoitu</string>
+ <string name="no_lists_yet">Ei vielä listoja</string>
<string name="do_you_want_to_save_your_changes">Haluatko tallentaa muutokset?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">Haluatko arkistoida kaikki kortit listalta %1$s?</string>
+ <plurals name="do_you_want_to_delete_the_current_list">
+ <item quantity="one">Tämä poistaa %1$d kortin tältä listalta.</item>
+ <item quantity="other">Tämä poistaa kaikki %1$d korttia tältä listalta.</item>
+ </plurals>
+ <plurals name="do_you_want_to_delete_the_label">
+ <item quantity="one">Tämä poistaa nimikkeen %1$d kortilta.</item>
+ <item quantity="other">Tämä poistaa nimikkeen %1$d kortilta.</item>
+ </plurals>
+ <string name="add_a_new_list_using_the_button">Lisää uusi lista painamalla +</string>
<string name="add_a_new_card_using_the_button">Lisää uusi kortti painamalla +</string>
<string name="update_deck">Päivitä pakka</string>
<string name="your_deck_version_is_too_old">Pakan versio on liian vanha</string>
- <string name="deck_outdated_please_update">Pakan versio on liian vanha. Päivitä kayttämällä tätä android sovellusta asiakkaana.</string>
+ <string name="deck_outdated_please_update">Deck-versiosi (%1$s) on liian vanha. Päivitä sovellus.</string>
+ <string name="delete_board_message">Tämä poistaa tämän taulun ja sen listat ja kortit pysyvästi.</string>
<string name="settings_theme_title">Tumma teema</string>
+ <string name="settings_branding_title">Brändäys</string>
+ <string name="settings_compact_title">Kompakti tila</string>
<string name="settings_background_sync">Taustasynkronointi</string>
<string name="pref_value_wifi_and_mobile">Synkronoi wifi- ja mobiilidatayhteydellä</string>
<string name="pref_value_wifi_only">Synkronoi vain wifi-yhteydellä</string>
@@ -137,12 +150,23 @@
<string name="provide_at_least_a_title_or_description">Anna vähintään otsikko tai kuvaus</string>
<string name="welcome_text">Tervetuloa kohteeseen %1$s</string>
<string name="save_card_before_attachment">Kortti pitää tallentaa ennen kuin liitetiedostoja voidaan lisätä.</string>
+ <string name="maintenance_mode_explanation">Palvelin%1$s on juuri ylläpitotilassa. Ota yhteys järjestelmän ylläpitäjään tai yritä myöhemmin uudelleen.</string>
<string name="share_add_to_card">Lisää kortille</string>
<string name="share_success">Onnistuneesti lisätty %1$s kohteeseen %2$s</string>
- <string name="could_not_copy_to_clipboard">Ei voitu kopioida leikepöydälle</string>
+ <string name="add_comment">Lisää kommentti</string>
<string name="card_edit_comments">Kommentit</string>
+ <string name="no_comments_yet">Ei kommentteja</string>
+ <string name="no_boards">Ei vielä tauluja</string>
+ <string name="add_a_new_board_using_the_button">Lisää uusi taulu painamalla +</string>
+ <string name="choose_board">Valitse taulu</string>
+ <string name="choose_list">Valitse lista</string>
+ <string name="task_count">%1$s/%2$s</string>
<string name="open_in_browser">Avaa selaimessa</string>
- <!-- Filter -->
+ <string name="updating_card">Päivitetään korttia...</string>
+
+ <string name="move_list_right">Siirrä lista oikealle</string>
+ <string name="move_list_left">Siirrä lista vasemmalle</string>
+
<string name="filter_no_filter">Kaikki</string>
<string name="filter_overdue">Myöhässä</string>
<string name="filter_today">Tänään</string>
@@ -150,6 +174,101 @@
<string name="filter_month">Seuraavat 30 päivää</string>
<string name="filter_no_due">Ei eräpäivää</string>
<string name="filter_by_tag">Suodata tunnisteen perusteella</string>
- <!-- Archived cards -->
+ <string name="filter_by_assigned_user">Suodata määritetyn käyttäjän mukaan</string>
+ <string name="filter_by_duedate">Suodata määräpäivän mukaan</string>
+
<string name="archived_cards">Arkistoidut kortit</string>
+ <string name="action_card_dearchive">Kumoa kortin arkistointi</string>
+ <string name="action_archived_cards">Selaa arkistoituja kortteja</string>
+ <string name="attachment_already_exists">Liite on jo olemassa</string>
+ <string name="pick_custom_color">Valitse mukautettu väri</string>
+ <string name="manage_tags">Hallitse tunnisteita</string>
+ <string name="add_tag">Lisää tunniste</string>
+ <string name="tag_already_exists">%1$s on jo olemassa</string>
+ <string name="tag_successfully_added">%1$s lisätty onnistuneesti</string>
+ <string name="edit_tag">Muokkaa tunnistetta %1$s</string>
+ <string name="filter_tags_title">Tunnisteet</string>
+ <string name="filter_user_title">Käyttäjät</string>
+ <string name="filter_duedate_title">Eräpäivä</string>
+
+ <string name="action_board_dearchive">Kumoa taulun arkistointi</string>
+ <string name="archived_boards">Arkistoidut taulut</string>
+
+ <string name="error">Tapahtui virhe</string>
+ <string name="synchronization_failed">Synkronointi epäonnistui</string>
+ <string name="operation_not_yet_supported">Ei vielä tuettu</string>
+ <string name="error_revoking_ac">Virhe käyttöoikeuden kumoamisessa (%1$s)</string>
+ <string name="error_create_label">Virhe nimikkeen luonnissa (%1$s)</string>
+ <string name="maintenance_mode">Palvelin on huoltotilassa</string>
+ <string name="server_misconfigured">Virheelliset palvelimen asetukset</string>
+ <string name="server_error">Palvelinvirhe</string>
+ <string name="shared_error">Sisällön jako 3. osapuolen sovelluksista ei ole vielä täysin tuettu. Lataa kohde ensin ja jaa se laitteen tiedostohallinnasta tai galleriasta.</string>
+ <string name="error_edit_activity_killed_by_android">Android päätti muokkaustilan, koska resursseja tarvitaan muille sovelluksille.</string>
+
+ <string name="error_dialog_title">Voi ei - Mitä nyt? 🙁</string>
+ <string name="error_dialog_tip_token_mismatch_retry">Yritä pakottaa sovelluksen sulkeminen ja käynnistä se uudelleen. Nextcloud-sovellukseen on saattanut olla virheellinen yhteys.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Jos ongelma jatkuu, tyhjennä Nextcloudin ja Nextcloud Deckin tallennustila laitteesta ratkaistaaksesi ongelman.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Tietokannan päivittäminen epäonnistui. Ole hyvä ja raportoi ongelmasta ja tyhjennä tallennustila käyttääksesi sovellusta normaalisti.</string>
+ <string name="error_dialog_tip_clear_storage">Voit tyhjentää tallennustilan avaamalla sovelluksen tiedot ja valitsemalla Tallennustila → Tyhjennä tallennustila.</string>
+ <string name="error_dialog_tip_files_outdated">Nextcloud-sovelluksesi on vanhentunut. Lataa uusin version Play Storesta tai F-Droidista.</string>
+ <string name="error_dialog_tip_files_force_stop">Nextcloud-sovelluksessasi on jotain vialla. Pakota Nextcloudin ja Nextcloud Deckin lopetus.</string>
+ <string name="error_dialog_tip_files_delete_storage">Jos pakotettu lopettaminen ei auta, voit yrittää tyhjentää sovelluksien tallennustilan.</string>
+ <string name="error_dialog_timeout_instance">Palvelimen aikakatkaisu. Varmista, että se on toiminnassa.</string>
+ <string name="error_dialog_timeout_toggle">Tarkista verkkoyhteytesi. Joskus mobiilidatan tai Wi-Fin kytkeminen pois päältä ja päälle saattaa ratkaista ongelman.</string>
+ <string name="error_dialog_check_server">Palvelin vastasi virheellisesti. Tarkista, saatko yhteyden Deckiin selaimen kautta.</string>
+ <string name="error_dialog_check_server_logs">Nextcloud-asennuksessasi on ongelma. Katso lisätietoja palvelimen lokitiedostoista.</string>
+ <string name="error_dialog_check_maintenance">Varmista, ettei Nextcloud-palvelin ole ylläpitotilassa.</string>
+ <string name="error_dialog_insufficient_storage">Nextcloud-palvelimellasi ei ole vapaata tallennustilaa. Poista tiedostoja, jotta paikallisten muutosten synkronointi pilveen onnistuu.</string>
+ <string name="error_dialog_we_need_info">Tarvitsemme seuraavat tekniset tiedot auttaaksemme sinua:</string>
+ <string name="error_dialog_redirect">Palvelimesi vastasi HTTP-tilakoodilla 302, joka viittaa siihen, että Deckiä ei ole asennettu tai se on määritelty virheellisesti. Syynä saattaa olla muokattu .htaccess-tiedosto tai jokin Nextcloud-sovellus, kuten OID Client.</string>
+ <string name="error_dialog_version_not_parsable">Palvelimen Deck-sovelluksen versiota ei voitu määrittää. Varmista, että se on asennettu ja otettu käyttöön.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Tilisi valtuutus Nextcloud-sovellukseen saattaa olla päättynyt.</string>
+ <string name="error_dialog_user_not_found_in_database">Käyttäjää ei löydy tietokannastamme. Jos LDAP on käytössä Nextcloudissa, Nextcloud-sovellksessa saattaa olla tallennettuna vanha käyttäjän tunnus.</string>
+ <string name="error_dialog_capabilities_not_parsable">Palvelimesi ominaisuuksia ei voitu hakea. Varmista, että palvelin on käynnissä ja muut asiakasohjelmat saavat yhteyden Nextcloudiin.</string>
+ <string name="error_dialog_attachment_upload_failed">Liitettä ei voitu lähettää. Yritä jakaa se toista kautta ja kerro meille tästä bugista.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Ota pois käytöstä akun optimointi Nextcloud- ja Deck-sovelluksilta.</string>
+ <string name="error_action_open_deck_info">Avaa sovelluksen tiedot</string>
+ <string name="error_action_open_network">Verkkoasetukset</string>
+ <string name="error_action_server_logs">Palvelimen lokit</string>
+ <string name="error_action_report_issue">Raportti</string>
+ <string name="info_box_maintenance_mode">Palvelin on huoltotilassa</string>
+ <string name="info_box_version_not_supported">Palvelinversio %1$s ei ole tuettu, päivitä versioon %2$s</string>
+ <string name="share_link">Jaa linkki</string>
+ <string name="archive_cards">Arkistoi kortit</string>
+
+ <string name="manage_accounts">Tilien hallinta</string>
+ <string name="manage_list">Hallinnoi listaa</string>
+ <string name="simple_reply">Vastaa</string>
+ <string name="error_while_uploading_attachment">Virhe liitteen lähttämisessä: %1$s</string>
+ <string name="append_text_to_description">Lisää selitteeseen</string>
+ <string name="add_text_as_comment">Lisää kommenttina</string>
+ <string name="progress_count">%1$d / %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d virhe lähettämisen aikana</item>
+ <item quantity="other">%1$d virhettä lähettämisen aikana</item>
+ </plurals>
+ <string name="simple_report">Raportti</string>
+ <string name="error_action_open_battery_settings">Akkuasetukset</string>
+ <string name="move_warning">Kommentteja tai liitteitä ei siirretä, kun kortti siirretään toiseen tauluun.</string>
+ <string name="clone_board">Monista taulu</string>
+ <string name="cloning_board">Kloonataan %1$s…</string>
+ <string name="successfully_cloned_board">Taulun %1$s kloonaus onnistui</string>
+ <string name="attachment_does_not_yet_exist">Liitettä ei vielä ole Deckissä</string>
+ <string name="card_does_not_yet_exist">Korttia ei vielä ole Deckissä</string>
+
+ <string name="widget_stack_title">Lista</string>
+ <string name="select_stack">Valitse lista</string>
+ <string name="project_type_deck_board">Deck-taulu</string>
+ <string name="project_type_deck_card">Deck-kortti</string>
+ <string name="project_type_file">Tiedosto</string>
+ <string name="projects_title">Projektit</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d resurssi</item>
+ <item quantity="other">%1$d resurssia</item>
+ </plurals>
+ <string name="no_assigned_label">Ei määritettyä tunnistetta</string>
+ <string name="single_card">Yksittäinen kortti</string>
+ <string name="project_type_room">Keskusteluhuone</string>
+ <string name="simple_move">Siirrä</string>
+ <string name="cannot_upload_files_without_permission">Tiedostojen lähettäminen ei ole mahdollista ilman käyttöoikeutta</string>
</resources>
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index fce1c75ee..c98042618 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Dernier compte</string>
<string name="drawer_manage_accounts">Gérer les comptes</string>
- <!-- Simple values -->
<string name="simple_boards">Tableaux</string>
<string name="simple_add">Ajouter</string>
<string name="simple_save">Enregistrer</string>
@@ -20,9 +19,9 @@
<string name="simple_error">Erreur</string>
<string name="simple_exception">Exception</string>
<string name="simple_close">fermer</string>
- <string name="simple_open">Ouvert</string>
+ <string name="simple_open">Ouvrir</string>
<string name="simple_switch">Permuter</string>
- <string name="simple_filter">Filtre</string>
+ <string name="simple_filter">Filtrer</string>
<string name="simple_overdue">En retard</string>
<string name="simple_clear">Effacer</string>
<string name="simple_discard">ignorer</string>
@@ -38,16 +37,16 @@
<string name="simple_disabled">désactivé</string>
<string name="simple_copied">Copié</string>
<string name="simple_archive">Archiver</string>
+ <string name="simple_unassigned">Non assigné(s)</string>
<string name="edit_board">Modifier le tableau</string>
<string name="archive_board">Archiver le tableau</string>
<string name="delete_board">Supprimer le tableau</string>
<string name="delete_something">Supprimer %1$s</string>
- <!-- About -->
<string name="about">À propos de</string>
<string name="about_version_title">Version</string>
- <string name="about_version">Vous utilisez actuellement %1$s</string>
+ <string name="about_version">Vous utilisez actuellement la version : %1$s</string>
<string name="about_maintainer_title">Responsable</string>
<string name="about_developers_title">Développeurs</string>
<string name="about_translators_title">Traducteurs</string>
@@ -64,22 +63,19 @@
<string name="about_app_license">Cette application est sous licence GNU GENERAL PUBLIC LICENSE v3+.</string>
<string name="about_app_license_button">Voir la licence</string>
<string name="about_icons_disclaimer_title">Icônes</string>
- <string name="about_icons_disclaimer_app_icon">Pour l’icône original voir %1$s.</string>
+ <string name="about_icons_disclaimer_app_icon">Pour l’icône originale voir %1$s.</string>
<string name="about_icons_disclaimer_mdi_icons">Les icônes suivantes utilisées dans cette applications sont %1$s réalisées par Google Inc. et sous licence Apache 2.0.</string>
<string name="about_icons_disclaimer_mdi">Icônes Material Design</string>
<string name="about_credits_tab_title">Crédits</string>
<string name="about_contribution_tab_title">Contribution</string>
<string name="about_license_tab_title">Licence</string>
-
- <string name="copied_to_clipboard">Copié dans le presse-papier</string>
-
<string name="seconds_ago">à l\'instant</string>
<string name="edit">Modifier</string>
<string name="label_labels">Sélectionner les étiquettes</string>
<string name="label_description">Description</string>
<string name="hint_assign_people">Assigner des utilisateurs</string>
<string name="hint_due_date_date">Date d\'échéance</string>
- <string name="card_not_found">Carte non trouvé</string>
+ <string name="card_not_found">Carte non trouvée</string>
<string name="card_not_found_message">La carte est introuvable. Elle a peut-être été supprimée récemment.</string>
<string name="add_account">Ajouter un compte</string>
@@ -112,7 +108,8 @@
<string name="not_synced_yet">Aucune synchronisation pour l\'instant</string>
<string name="no_lists_yet">Aucune liste pour l\'instant</string>
<string name="do_you_want_to_save_your_changes">Souhaitez-vous enregistrer vos modifications ?</string>
- <string name="do_you_want_to_archive_all_cards_of_the_list">Voulez vous archiver toutes les carte de %1$s ?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">Souhaitez-vous archiver toutes les carte de %1$s ?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Souhaitez-vous archiver toutes les cartes de %1$s affichées par le filtre?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Cela supprimera définitivement l\'ensemble des %1$d cartes de cette liste.</item>
<item quantity="other">Cela supprimera définitivement l\'ensemble des %1$d cartes de cette liste.</item>
@@ -125,13 +122,14 @@
<string name="add_a_new_card_using_the_button">Ajouter une nouvelle carte en utilisant le bouton +.</string>
<string name="update_deck">Mettre à jour Deck</string>
<string name="your_deck_version_is_too_old">Votre version de Deck est trop ancienne</string>
- <string name="deck_outdated_please_update">Votre version de deck est trop ancienne (%1$s). Veuillez mettre à jour pour utiliser cette application Android en tant que client.</string>
- <string name="delete_board_message">Cette action supprimera définitivement ce tableau incluant ses listes et ses cartes.</string>
+ <string name="deck_outdated_please_update">Votre version de l\'application Deck est trop ancienne (%1$s). Veuillez la mettre à jour afin de pouvoir utiliser cette application Android en tant que client.</string>
+ <string name="delete_board_message">Cette action supprimera définitivement ce tableau, y compris la totalité de ses listes et de ses cartes.</string>
<string name="settings_theme_title">Thème sombre</string>
- <string name="settings_branding_title">marque</string>
+ <string name="settings_branding_title">Thème du serveur</string>
+ <string name="settings_compact_title">Mode compact</string>
<string name="settings_background_sync">Synchronisation en tâche de fond</string>
<string name="pref_value_wifi_and_mobile">Synchroniser en Wifi et données mobiles</string>
- <string name="pref_value_wifi_only">Synchroniser uniquement en Wifi</string>
+ <string name="pref_value_wifi_only">Synchroniser uniquement en Wi-Fi</string>
<string name="pref_value_theme_light">Clair</string>
<string name="unassigned_user">%1$s non assigné</string>
<string name="no_activities">Il n\'y a aucune activité sur cette carte. Vous devez être connectés à Internet pour charger et afficher les activités.</string>
@@ -155,24 +153,21 @@
<string name="save_card_before_attachment">Vous devez enregistrer la carte avant d\'ajouter une pièce jointe.</string>
<string name="maintenance_mode_explanation">Le serveur %1$s est actuellement en maintenance. Veuillez contacter votre administrateur ou réessayer plus tard.</string>
<string name="share_add_to_card">Ajouter à la carte</string>
- <string name="share_success">Ajout de %1$s à %2$s réussit</string>
- <string name="could_not_copy_to_clipboard">Impossible de copier dans le presse-papiers</string>
+ <string name="share_success">Ajout de %1$s à %2$s réussi</string>
<string name="add_comment">Ajouter un commentaire</string>
<string name="card_edit_comments">Commentaires</string>
<string name="no_comments_yet">Aucun commentaire pour l\'instant</string>
<string name="no_boards">Pas encore de tableaux</string>
- <string name="add_a_new_board_using_the_button">Ajouter un nouveau table en utilisant le bouton +</string>
+ <string name="add_a_new_board_using_the_button">Ajouter un nouveau tableau en utilisant le bouton +</string>
<string name="choose_board">Choisir un tableau</string>
<string name="choose_list">Sélectionner la liste</string>
<string name="task_count">%1$s/%2$s</string>
<string name="open_in_browser">Ouvrir dans le navigateur web</string>
<string name="updating_card">Mise à jour de la carte en cours...</string>
- <!-- Move lists -->
<string name="move_list_right">Déplacer la liste à droite</string>
<string name="move_list_left">Déplacer la liste à gauche</string>
- <!-- Filter -->
<string name="filter_no_filter">Tout</string>
<string name="filter_overdue">En retard</string>
<string name="filter_today">Aujourd\'hui</string>
@@ -180,10 +175,9 @@
<string name="filter_month">30 prochains jours</string>
<string name="filter_no_due">Sans échéance</string>
<string name="filter_by_tag">Filtrer par étiquette</string>
- <string name="filter_by_assigned_user">Filtrer par utilisateur</string>
+ <string name="filter_by_assigned_user">Filtrer par utilisateur assigné</string>
<string name="filter_by_duedate">Filtrer par échéance</string>
- <!-- Archived cards -->
<string name="archived_cards">Cartes archivées</string>
<string name="action_card_dearchive">Annuler l\'archivage de la carte</string>
<string name="action_archived_cards">Parcourir les cartes archivées</string>
@@ -198,11 +192,9 @@
<string name="filter_user_title">Utilisateurs</string>
<string name="filter_duedate_title">Date d\'échéance</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Annuler l\'archivage du tableau</string>
<string name="archived_boards">Tableaux archivés</string>
- <!-- Errors -->
<string name="error">Une erreur est survenue</string>
<string name="synchronization_failed">Échec de synchronisation</string>
<string name="operation_not_yet_supported">Non supporté actuellement</string>
@@ -215,31 +207,37 @@
<string name="error_edit_activity_killed_by_android">Android a mis fin au mode édition car il avait besoin de plus de ressources système pour les autres applications.</string>
<string name="error_dialog_title">Oh non ! - Et maintenant ? 🙁</string>
- <string name="error_dialog_tip_token_mismatch_retry">Essayez de forcer la fermeture de l\'application puis redémarrer la. Il y avait peut-être une mauvaise connexion à Nextcloud.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Si le problème persiste, essayez d\'effacer les données d\'application des deux applications: Nextcloud et Nextcloud Deck pour résoudre ce problème.</string>
+ <string name="error_dialog_tip_token_mismatch_retry">Essayez de forcer la fermeture de l\'application puis de la redémarrer. Il y avait peut-être une mauvaise connexion à Nextcloud.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Si le problème persiste, essayez d\'effacer les données des applications Nextcloud et Nextcloud Deck pour résoudre ce problème.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">La mise à jour de la base de données a échoué. Veuillez signaler l\'erreur et supprimer les données pour utiliser l\'application de manière normale.</string>
<string name="error_dialog_tip_clear_storage">Vous pouvez nettoyer l\'espace de stockage en ouvrant les paramètres de l\'application et en sélectionnant Stockage → Effacer le stockage.</string>
- <string name="error_dialog_tip_files_outdated">Votre application Nextcloud semble ancienne. Veuillez visiter le Play Store ou F-Droid pour installer la dernière version.</string>
+ <string name="error_dialog_tip_files_outdated">Votre application Nextcloud semble trop ancienne. Veuillez visiter le Play Store ou F-Droid afin d\'installer la dernière version.</string>
<string name="error_dialog_tip_files_force_stop">Quelque chose semble ne pas fonctionner avec votre application Nextcloud. Essayer de forcer l\'arrêt des applications Nextcloud et Nextcloud Notes.</string>
<string name="error_dialog_tip_files_delete_storage">Si l’arrêt forcé ne fonctionne pas, vous pouvez essayer d\'effacer les données des deux applications.</string>
<string name="error_dialog_timeout_instance">Le serveur n’a pas répondu dans le temps imparti. Assurez-vous que votre instance fonctionne correctement.</string>
<string name="error_dialog_timeout_toggle">Vérifiez votre connexion réseau. L\'activation et la désactivation des données mobiles ou du Wi-Fi peuvent aider.</string>
- <string name="error_dialog_check_server">Le serveur a renvoyé une réponse incorrecte. Vérifier si vous pouvez accéder à vos notes via l\'interface Web.</string>
- <string name="error_dialog_check_server_logs">Il y a un problème avec votre configuration Nextcloud. Consulter les fichiers journaux du serveur.</string>
- <string name="error_dialog_check_maintenance">Vérifier si votre instance Nextcloud n\'est pas actuellement en mode maintenance.</string>
+ <string name="error_dialog_check_server">Votre serveur a renvoyé une réponse incorrecte. Vérifiez si vous pouvez accéder à l\'application Deck via l\'interface Web.</string>
+ <string name="error_dialog_check_server_logs">Il y a un problème avec votre configuration Nextcloud. Consultez s\'il vous plaît les fichiers journaux du serveur.</string>
+ <string name="error_dialog_check_maintenance">Vérifiez s\'il vous plaît si votre instance Nextcloud n\'est actuellement pas en mode maintenance.</string>
<string name="error_dialog_insufficient_storage">Votre instance Nextcloud n\'a plus d\'espace disponible. Veuillez supprimer certains fichiers pour pouvoir synchroniser vos modifications locales dans votre cloud.</string>
<string name="error_dialog_we_need_info">Nous avons besoin des informations techniques suivantes pour pouvoir vous aider :</string>
- <string name="error_dialog_redirect">Votre serveur a répondu avec le code d\'état HTTP 302, ce qui implique que vous n\'avez pas installé l\'application Deck sur votre serveur ou que quelque chose est mal configuré. Cela peut être dû à des modifications personnalisés du fichier .htaccess ou à des applications Nextcloud comme un client OID. Vous pouvez trouver nos coordonnées pour le support dans la section «À propos».</string>
- <string name="error_dialog_version_not_parsable">Nous n\'avons pas pu déterminer la version de l\'app Deck côté serveur. Veuillez vérifier qu\'elle est bien installé et activé.</string>
- <string name="error_dialog_capabilities_not_parsable">Nous n\'avons pas pu récupérer les capacités de votre serveur. Veuillez vous assurer que votre serveur fonctionne bien et que les autres apps client peuvent accéder à Nextcloud.</string>
+ <string name="error_dialog_redirect">Votre serveur a répondu avec le code d\'état HTTP 302, ce qui implique que vous n\'avez pas installé l\'application Deck sur votre serveur ou que quelque chose est mal configuré. Cela peut être dû à des modifications personnalisées du fichier .htaccess ou à des applications Nextcloud comme un client OID.</string>
+ <string name="error_dialog_version_not_parsable">Nous n\'avons pas pu déterminer la version de l\'application Deck sur votre serveur. Veuillez vérifier qu\'elle est bien installée et activée.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Il semble possible que le compte de votre application Nextcloud ne soit plus autorisé.</string>
+ <string name="error_dialog_user_not_found_in_database">L\'utilisateur actuel est différent de l\'utilisateur que nous avons dans notre base de données. Si vous utilisez LDAP sur votre instance Nextcloud, il est alors possible que votre application Nextcloud ait stocké un identifiant utilisateur obsolète</string>
+ <string name="error_dialog_capabilities_not_parsable">Nous n\'avons pas pu évaluer les capacités de votre serveur. Veuillez vous assurer que votre serveur fonctionne bien et que les autres applications clientes peuvent accéder à Nextcloud.</string>
<string name="error_dialog_attachment_upload_failed">Une pièce jointe n’a pas pu être téléversée. Veuillez essayer de la partager d’une autre façon et faites-nous part de ce bogue.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Merci de bien vouloir désactiver toutes les optimisations de batterie pour Nextcloud ainsi que pour l\'application Deck.</string>
<string name="error_action_open_deck_info">Ouvrir les informations de l\'application</string>
<string name="error_action_open_network">Paramètres réseau</string>
<string name="error_action_server_logs">Journaux serveur</string>
<string name="error_action_install">Installer</string>
+ <string name="error_action_report_issue">Signaler</string>
<string name="info_box_maintenance_mode">Serveur en maintenance</string>
<string name="info_box_version_not_supported">Version du serveur %1$s non prise en charge, veuillez mettre à jour en %2$s</string>
<string name="share_link">Lien de partage</string>
<string name="archive_cards">Archiver les cartes</string>
+
<string name="manage_accounts">Gérer les comptes</string>
<string name="manage_list">Gérer la liste</string>
<string name="simple_reply">Répondre</string>
@@ -247,5 +245,55 @@
<string name="append_text_to_description">Ajouter à la description</string>
<string name="add_text_as_comment">Ajouter en commentaire</string>
<string name="progress_count">%1$d sur %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d erreur lors de l\'envoi</item>
+ <item quantity="other">%1$d erreurs lors de l\'envoi</item>
+ </plurals>
<string name="simple_report">Signaler</string>
+ <string name="error_action_open_battery_settings">Paramètres de batterie</string>
+ <string name="move_warning">Ni les commentaires ni les pièces jointes ne peuvent être transférés lorsque la carte est déplacée sur un autre tableau.</string>
+ <string name="clone_board">Dupliquer le tableau</string>
+ <string name="cloning_board">Clonage de %1$s…</string>
+ <string name="successfully_cloned_board">Cloné avec succès %1$s</string>
+ <string name="attachment_does_not_yet_exist">Pièce jointe non encore présente dans le tableau</string>
+ <string name="card_does_not_yet_exist">Carte non encore présente dans le tableau</string>
+
+ <string name="widget_stack_title">Liste</string>
+ <string name="widget_stack_header_icon">Icône d’en-tête du widget</string>
+ <string name="widget_stack_placeholder_icon">Icône d’espace réservé du widget</string>
+ <string name="select_stack">Sélectionner la liste</string>
+ <string name="project_type_deck_board">Tableau de l\'application Deck</string>
+ <string name="project_type_deck_card">Carte de l\'application Deck</string>
+ <string name="project_type_file">Fichier</string>
+ <string name="projects_title">Projets</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d ressource</item>
+ <item quantity="other">%1$d ressources</item>
+ </plurals>
+ <string name="no_assigned_label">Pas d’étiquette attribuée</string>
+ <string name="single_card">Carte unique</string>
+ <string name="project_type_room">Salon de discussion</string>
+ <string name="simple_move">Déplacer</string>
+ <string name="cannot_upload_files_without_permission">Impossible de téléverser des fichiers sans permission</string>
+ <string name="clone_cards">Cloner les cartes</string>
+ <string name="simple_clone">Cloner</string>
+ <string name="user_avatar">Avatar utilisateur</string>
+ <string name="simple_unassign">Désassigner</string>
+ <string name="simple_contact">Contact</string>
+ <string name="simple_file">Fichier</string>
+ <string name="simple_camera">Appareil photo</string>
+ <string name="min_api_21">Cette fonctionnalité est disponible à partir d\'Android 5</string>
+ <string name="take_photo">Prendre une photo</string>
+ <string name="take_photo_switch_camera">Changer d\'appareil photo</string>
+ <string name="take_photo_toggle_torch">Allumer la lampe</string>
+ <string name="show_all_contacts">Afficher toutes les contacts</string>
+ <string name="show_all_files">Afficher tous les fichiers</string>
+ <string name="recent">Récent</string>
+ <string name="upload_a_new_attachment">Envoyer une nouvelle pièce jointe</string>
+ <string name="contacts">Contacts</string>
+ <string name="downloads">Téléchargements</string>
+ <string name="files">Fichiers</string>
+ <string name="gallery">Galerie</string>
+ <string name="simple_attach">joindre</string>
+ <string name="add_stack_widget">Ajouter un widget liste</string>
</resources>
diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml
index 14cb3f3a4..2b8b7aaef 100644
--- a/app/src/main/res/values-gl/strings.xml
+++ b/app/src/main/res/values-gl/strings.xml
@@ -6,9 +6,8 @@
<string name="drawer_current_account">Conta actual</string>
<string name="drawer_middle_account">Conta media</string>
<string name="drawer_end_account">Última conta</string>
- <string name="drawer_manage_accounts">Administrar contas</string>
+ <string name="drawer_manage_accounts">Xestionar contas</string>
- <!-- Simple values -->
<string name="simple_boards">Taboleiros</string>
<string name="simple_add">Engadir</string>
<string name="simple_save">Gardar</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">desactivado</string>
<string name="simple_copied">Copiada</string>
<string name="simple_archive">Aquivar</string>
+ <string name="simple_unassigned">Sen asignar</string>
<string name="edit_board">Editar taboleiro</string>
<string name="archive_board">Arquivar taboleiro</string>
<string name="delete_board">Eliminar taboleiro</string>
<string name="delete_something">Eliminar %1$s</string>
- <!-- About -->
<string name="about">Sobre</string>
<string name="about_version_title">Versión</string>
<string name="about_version">Agora está a empregar %1$s</string>
@@ -56,7 +55,7 @@
<string name="about_testers_title">Probadores</string>
<string name="about_source_title">Código fonte</string>
<string name="about_source">Este proxecto está aloxado en GitHub: %1$s</string>
- <string name="about_issues_title">Incidentes</string>
+ <string name="about_issues_title">Incidencias</string>
<string name="about_issues">Vostede pode informar de fallos, propoñer melloras e solicitar funcionalidades na ferramenta de seguimento de erros do GitHub: %1$s</string>
<string name="about_translate_title">Traducir</string>
<string name="about_translate">Únase ao equipo Nextcloud en Transifex e axúdenos a traducir esta aplicación: %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Recoñecementos</string>
<string name="about_contribution_tab_title">Colaboración</string>
<string name="about_license_tab_title">Licenza</string>
-
- <string name="copied_to_clipboard">Copiado no portapapeis.</string>
-
<string name="seconds_ago">hai uns segundos</string>
<string name="edit">Editar</string>
<string name="label_labels">Seleccionar etiquetas</string>
@@ -111,11 +107,12 @@
<string name="account_is_getting_imported">A conta está a importarse</string>
<string name="not_synced_yet">Aínda non foi sincronizado</string>
<string name="no_lists_yet">Non hai listas</string>
- <string name="do_you_want_to_save_your_changes">Confirma que quere gardar os cambios?</string>
+ <string name="do_you_want_to_save_your_changes">Quere gardar os cambios?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Quere arquivar todas as tarxetas de %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Quere arquivar todas as tarxetas filtradas de %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
- <item quantity="one">Isto eliminará permanentemente %1$d tarxeta desta lista.</item>
- <item quantity="other">Isto eliminará permanentemente as %1$d tarxetas desta lista.</item>
+ <item quantity="one">Isto eliminará de xeito permanente %1$d tarxeta desta lista.</item>
+ <item quantity="other">Isto eliminará de xeito permanente as %1$d tarxetas desta lista.</item>
</plurals>
<plurals name="do_you_want_to_delete_the_label">
<item quantity="one">Isto retirará a etiqueta de %1$d tarxeta.</item>
@@ -129,6 +126,7 @@
<string name="delete_board_message">Isto eliminará este taboleiro de xeito permanente incluíndo todas as listas e tarxetas.</string>
<string name="settings_theme_title">Tema escuro</string>
<string name="settings_branding_title">Xestión da marca</string>
+ <string name="settings_compact_title">Modo compacto</string>
<string name="settings_background_sync">Sincronización do traballo en segundo plano</string>
<string name="pref_value_wifi_and_mobile">Sincronización con wifi e con datos móbiles</string>
<string name="pref_value_wifi_only">Sincronizar só con wifi</string>
@@ -156,7 +154,6 @@
<string name="maintenance_mode_explanation">O servidor %1$s está atopase en modo de mantemento. Póñase en contacto co seu administrador ou ténteo de novo máis tarde.</string>
<string name="share_add_to_card">Engadir á tarxeta</string>
<string name="share_success">%1$s foi engadido satisfactoriamente a %2$s</string>
- <string name="could_not_copy_to_clipboard">Non foi posíbel copiar ao portapapeis</string>
<string name="add_comment">Engadir comentario</string>
<string name="card_edit_comments">Comentarios</string>
<string name="no_comments_yet">Aínda non hai comentarios</string>
@@ -168,11 +165,9 @@
<string name="open_in_browser">Abrir no navegador</string>
<string name="updating_card">Actualizado tarxeta…</string>
- <!-- Move lists -->
<string name="move_list_right">Mover a lista á dereita</string>
<string name="move_list_left">Mover a lista á esquerda</string>
- <!-- Filter -->
<string name="filter_no_filter">Todo</string>
<string name="filter_overdue">Caducado</string>
<string name="filter_today">Hoxe</string>
@@ -183,7 +178,6 @@
<string name="filter_by_assigned_user">Filtrar polo usuario asignado</string>
<string name="filter_by_duedate">Filtrar pola data de caducidade</string>
- <!-- Archived cards -->
<string name="archived_cards">Tarxetas arquivadas</string>
<string name="action_card_dearchive">Desfacer o arquivo de tarxetas</string>
<string name="action_archived_cards">Examinar as tarxetas arquivadas</string>
@@ -198,11 +192,9 @@
<string name="filter_user_title">Usuarios</string>
<string name="filter_duedate_title">Data de caducidade</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Desfacer o arquivo de taboleiros</string>
<string name="archived_boards">Taboleiros arquivados</string>
- <!-- Errors -->
<string name="error">Apareceu un erro</string>
<string name="synchronization_failed">Produciuse un fallo na sincronización</string>
<string name="operation_not_yet_supported">Aínda non está admitido</string>
@@ -216,7 +208,8 @@
<string name="error_dialog_title">Vaites, e agora que? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Probe a forzar o peche da apli e reiniciala de novo. Pode haber unha conexión incorrecta coa apli Nextcloud.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Se o problema continúa, tente limpar o almacenamento de ambas as aplis: Nextcloud e Nextcloud Deck para resolver este problema.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Se o problema continúa, tente limpar o almacenamento de ambas as aplis: Nextcloud e Nextcloud Deck para resolver este problema.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Produciuse un fallo ao anovar a base de datos. Informe do problema e borre o almacenamento para usar a apli normalmente.</string>
<string name="error_dialog_tip_clear_storage">Pode limpar o almacenamento abrindo a información da apli e seleccionando Almacenamento → Limpar o almacenamento.</string>
<string name="error_dialog_tip_files_outdated">A súa apli Nextcloud semella estar desactualizada. Visite Play Store ou F-Droid para obter a última versión.</string>
<string name="error_dialog_tip_files_force_stop">Semella que algo está mal na súa apli Nextcloud. Tente forzar a parar ambas, a apli Nextcloud e a apli Nextcloud Deck.</string>
@@ -230,26 +223,77 @@
<string name="error_dialog_we_need_info">Necesitamos a seguinte información técnica para axudarlle:</string>
<string name="error_dialog_redirect">O seu servidor respondeu cun código de estado HTTP 302, o que implica que non ten instalada a aplicación Deck no servidor ou que algo está mal configurado. Isto pode ser causado por substitucións personalizadas nun ficheiro .htaccess ou por aplicacións Nextcloud como Client OID.</string>
<string name="error_dialog_version_not_parsable">Non foi posíbel determinar a versión da apli Deck do lado do servidor. Asegúrese de que estea instalado e activado.</string>
+ <string name="error_dialog_account_might_not_be_authorized">É posíbel que a conta da súa apli do Nextcloud xa non estea autorizada.</string>
+ <string name="error_dialog_user_not_found_in_database">O usuario actual non coincide co que temos na nosa base de datos. Se está a usar LDAP na súa instancia do Nextcloud, é posíbel que a súa apli do Nextcloud teña almacenado un ID de usuario antigo.</string>
<string name="error_dialog_capabilities_not_parsable">Non foi posíbel obter as funcionalidades do seu servidor. Asegúrese de que o seu servidor funciona correctamente e que outras aplis de cliente poidan acceder ao Nextcloud.</string>
<string name="error_dialog_attachment_upload_failed">Non foi posíbel enviar un arquivo anexo. Probe compartilo doutro xeito e infórmenos deste erro.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Desactive todas as optimizacións de batería para Nextcloud e a apli Deck.</string>
<string name="error_action_open_deck_info">Abrir a info da apli</string>
<string name="error_action_open_network">Axustes da rede</string>
<string name="error_action_server_logs">Rexistros do servidor</string>
<string name="error_action_install">Instalar</string>
+ <string name="error_action_report_issue">Informe</string>
<string name="info_box_maintenance_mode">Servidor en modo de mantemento</string>
<string name="info_box_version_not_supported">A versión %1$s do servidor non é compatíbel, actualice a %2$s</string>
<string name="share_link">Compartir ligazón</string>
<string name="archive_cards">Arquivar as tarxetas</string>
- <string name="manage_accounts">Administrar contas</string>
- <string name="manage_list">Administrar a lista</string>
+
+ <string name="manage_accounts">Xestionar contas</string>
+ <string name="manage_list">Xestionar a lista</string>
<string name="simple_reply">Responder</string>
<string name="error_while_uploading_attachment">Produciuse un erro ao enviar o anexo: %1$s</string>
<string name="append_text_to_description">Anexo á descrición</string>
<string name="add_text_as_comment">Engadir como comentario</string>
<string name="progress_count">%1$d de %2$d</string>
<plurals name="progress_error_count">
- <item quantity="one">%1$d erros ao enviar.</item>
- <item quantity="other">%1$d erros ao enviar.</item>
+ <item quantity="one">%1$d erros ao enviar</item>
+ <item quantity="other">%1$d erros ao enviar</item>
</plurals>
<string name="simple_report">Informe</string>
+ <string name="error_action_open_battery_settings">Axustes da batería</string>
+ <string name="move_warning">Non é posíbel transferir nin comentarios nin ficheiros anexos ao mover a tarxeta a outro taboleiro.</string>
+ <string name="clone_board">Clonar taboleiro</string>
+ <string name="cloning_board">Clonando %1$s…</string>
+ <string name="successfully_cloned_board">%1$s foi clonado satisfactoriamente</string>
+ <string name="attachment_does_not_yet_exist">Aínda non existe o anexo no Deck</string>
+ <string name="card_does_not_yet_exist">Aínda non existe a tarxeta no Deck</string>
+
+ <string name="widget_stack_title">Lista</string>
+ <string name="widget_stack_header_icon">Icona de cabeceira do trebello</string>
+ <string name="widget_stack_placeholder_icon">Icona de marcador de posición do trebello</string>
+ <string name="select_stack">Seleccionar a lista</string>
+ <string name="project_type_deck_board">Taboleiro do Deck</string>
+ <string name="project_type_deck_card">Tarxeta do Deck</string>
+ <string name="project_type_file">Ficheiro</string>
+ <string name="projects_title">Proxectos</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d recurso</item>
+ <item quantity="other">%1$d recursos</item>
+ </plurals>
+ <string name="no_assigned_label">Non hai etiqueta asignada</string>
+ <string name="single_card">Terxeta única</string>
+ <string name="project_type_room">Sala de conversas</string>
+ <string name="simple_move">Mover</string>
+ <string name="cannot_upload_files_without_permission">Non é posíbel enviar ficheiros sen permiso</string>
+ <string name="clone_cards">Clonar tarxetas</string>
+ <string name="simple_clone">Clonar</string>
+ <string name="user_avatar">Avatar do usuario</string>
+ <string name="simple_unassign">Desasignar</string>
+ <string name="simple_contact">Contacto</string>
+ <string name="simple_file">Ficheiro</string>
+ <string name="simple_camera">Cámara</string>
+ <string name="min_api_21">Esta función require polo menos Android 5</string>
+ <string name="take_photo">Tirar unha foto</string>
+ <string name="take_photo_switch_camera">Cambiar de cámara</string>
+ <string name="take_photo_toggle_torch">Alternar o facho</string>
+ <string name="show_all_contacts">Amosar todos os contactos</string>
+ <string name="show_all_files">Amosar todos os ficheiros</string>
+ <string name="recent">Recente</string>
+ <string name="upload_a_new_attachment">Enviar un novo anexo</string>
+ <string name="contacts">Contactos</string>
+ <string name="downloads">Descargas</string>
+ <string name="files">Ficheiros</string>
+ <string name="gallery">Galería</string>
+ <string name="simple_attach">anexar</string>
+ <string name="add_stack_widget">Engadir o trebello de lista</string>
</resources>
diff --git a/app/src/main/res/values-he/strings.xml b/app/src/main/res/values-he/strings.xml
index c5c0d07f6..366e55de1 100644
--- a/app/src/main/res/values-he/strings.xml
+++ b/app/src/main/res/values-he/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">חשבון אחרון</string>
<string name="drawer_manage_accounts">ניהול חשבונות</string>
- <!-- Simple values -->
<string name="simple_boards">לוחות</string>
<string name="simple_add">הוספה</string>
<string name="simple_save">שמור</string>
@@ -38,13 +37,11 @@
<string name="simple_disabled">מושבת</string>
<string name="simple_copied">הועתק</string>
<string name="simple_archive">לארכיון</string>
-
<string name="edit_board">ערוך לוח</string>
<string name="archive_board">לוח פעיל</string>
<string name="delete_board">מחק לוח</string>
<string name="delete_something">מחיקת %1$s</string>
- <!-- About -->
<string name="about">על אודות</string>
<string name="about_version_title">גרסה</string>
<string name="about_version">אתה משתמש כרגע ב%1$s</string>
@@ -70,9 +67,6 @@
<string name="about_credits_tab_title">תודות</string>
<string name="about_contribution_tab_title">תרומה</string>
<string name="about_license_tab_title">רישיון</string>
-
- <string name="copied_to_clipboard">הועתק ללוח הגזירים</string>
-
<string name="seconds_ago">לפני מספר שניות</string>
<string name="edit">עריכה</string>
<string name="label_labels">בחירת תגיות</string>
@@ -159,7 +153,6 @@
<string name="maintenance_mode_explanation">השרת %1$s נמצא במצב תחזוקה. נא ליצור קשר עם ההנהלה או לנסות מאוחר יותר.</string>
<string name="share_add_to_card">הוספה לכרטיס</string>
<string name="share_success">%1$s נוסף בהצלחה אל %2$s</string>
- <string name="could_not_copy_to_clipboard">לא ניתן להעתיק ללוח הגזירים</string>
<string name="add_comment">הוספת הערה</string>
<string name="card_edit_comments">תגובות</string>
<string name="no_comments_yet">אין הערות עדיין</string>
@@ -171,11 +164,9 @@
<string name="open_in_browser">פתיחה בדפדפן</string>
<string name="updating_card">הכרטיס מעודכן…</string>
- <!-- Move lists -->
<string name="move_list_right">העברת רשימה ימינה</string>
<string name="move_list_left">העברת רשימה שמאלה</string>
- <!-- Filter -->
<string name="filter_no_filter">הכול</string>
<string name="filter_overdue">באיחור</string>
<string name="filter_today">היום</string>
@@ -186,7 +177,6 @@
<string name="filter_by_assigned_user">סינון לפי משתמש מוקצה</string>
<string name="filter_by_duedate">סינון לפי תאריך יעד</string>
- <!-- Archived cards -->
<string name="archived_cards">כרטיסים בארכיון</string>
<string name="action_card_dearchive">ביטול העברת כרטיס לארכיון</string>
<string name="action_archived_cards">עיון בכרטיסים בארכיון</string>
@@ -201,11 +191,9 @@
<string name="filter_user_title">משתמשים</string>
<string name="filter_duedate_title">תאריך יעד</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">ביטול העברת הלוח לארכיון</string>
<string name="archived_boards">לוחות שנשמרו בארכיון</string>
- <!-- Errors -->
<string name="error">הופיעה שגיאה</string>
<string name="synchronization_failed">הסנכרון נכשל</string>
<string name="operation_not_yet_supported">אין תמיכה עדיין</string>
@@ -216,16 +204,25 @@
<string name="server_error">שגיאת שרת</string>
<string name="shared_error">שיתוף תוכן מיישומוני צד־שלישי עדיין לא נתמך לגמרי. נא לנסות להוריד תחילה ואז לשתף ממנהל הקבצים או הגלריה המובנים.</string>
<string name="error_dialog_title">שוד ושבר - מה עכשיו? 🙁</string>
+ <string name="error_dialog_tip_files_outdated">נראה כי יישומון ה־Nextcloud שלך אינו עדכני. נא לבקר בחנות Play או ב־F-Droid כדי לעדכן לגרסה האחרונה.</string>
+ <string name="error_dialog_tip_files_delete_storage">אם כפיית עצירה לא עוזרת, ניתן לנסות לרוקן את האחסון של שני היישומונים.</string>
+ <string name="error_dialog_timeout_instance">לא חזרה תגובה מהשרת שלך בזמן קצוב. נא לוודא שהעותק פועל כראוי.</string>
<string name="error_dialog_check_maintenance">נא לבדוק שעותק ה־Nextcloud שלך אינו במצב תחזוקה כרגע.</string>
<string name="error_dialog_we_need_info">אנו זקוקים לפירוט הטכני הבא כדי לסייע לך:</string>
<string name="error_action_open_deck_info">פתיחת פרטי יישומון</string>
<string name="error_action_open_network">הגדרות רשת</string>
<string name="error_action_server_logs">יומני שרת</string>
<string name="error_action_install">התקנה</string>
+ <string name="error_action_report_issue">דיווח</string>
<string name="info_box_maintenance_mode">שרת במצב תחזוקה</string>
<string name="info_box_version_not_supported">גרסת השרת %1$s אינה נתמכת, נא לעדכן לגרסה %2$s</string>
<string name="share_link">קישור שיתוף</string>
<string name="manage_accounts">ניהול חשבונות</string>
<string name="simple_reply">תגובה</string>
<string name="simple_report">דיווח</string>
-</resources>
+ <string name="clone_board">שכפול לוח</string>
+ <string name="widget_stack_title">רשימה</string>
+ <string name="project_type_file">קובץ</string>
+ <string name="projects_title">מיזמים</string>
+ <string name="simple_move">העברה</string>
+ </resources>
diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml
index fbf4ec485..edd334cf3 100644
--- a/app/src/main/res/values-hr/strings.xml
+++ b/app/src/main/res/values-hr/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Prethodni račun</string>
<string name="drawer_manage_accounts">Upravljaj računima</string>
- <!-- Simple values -->
<string name="simple_boards">Ploče</string>
<string name="simple_add">Dodaj</string>
<string name="simple_save">Spremi</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">onemogućeno</string>
<string name="simple_copied">Kopirano</string>
<string name="simple_archive">Arhiva</string>
+ <string name="simple_unassigned">Nedodijeljeno</string>
<string name="edit_board">Uredi ploču</string>
<string name="archive_board">Arhiviraj ploču</string>
<string name="delete_board">Izbriši ploču</string>
<string name="delete_something">Izbriši %1$s</string>
- <!-- About -->
<string name="about">Informacije</string>
<string name="about_version_title">Inačica</string>
<string name="about_version">Trenutno se koristite %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Zasluge</string>
<string name="about_contribution_tab_title">Doprinos</string>
<string name="about_license_tab_title">Licenca</string>
-
- <string name="copied_to_clipboard">Kopirano u međuspremnik</string>
-
<string name="seconds_ago">prije nekoliko sekundi</string>
<string name="edit">Uredi</string>
<string name="label_labels">Odaberite oznake</string>
@@ -80,6 +76,8 @@
<string name="hint_assign_people">Dodijeli korisnike</string>
<string name="hint_due_date_date">Datum dospijeća</string>
<string name="card_not_found">Kartica nije pronadjena</string>
+ <string name="card_not_found_message">Kartica nije pronađena. Možda je nedavno izbrisana.</string>
+
<string name="add_account">Dodaj račun</string>
<string name="choose_account">Odaberi račun</string>
<string name="add_card">Dodaj karticu</string>
@@ -110,6 +108,7 @@
<string name="not_synced_yet">Još nije sinkronizirano</string>
<string name="no_lists_yet">Još nema popisa</string>
<string name="do_you_want_to_save_your_changes">Želite li spremiti promjene?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">Želite li arhivirati sve kartice iz %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Trajno ćete izbrisati %1$d karticu s ovog popisa.</item>
<item quantity="few">Trajno ćete izbrisati %1$d kartice s ovog popisa.</item>
@@ -128,6 +127,7 @@
<string name="delete_board_message">Trajno ćete izbrisati ovu ploču, uključujući sve stogove i kartice.</string>
<string name="settings_theme_title">Tamna tema</string>
<string name="settings_branding_title">Brendiranje</string>
+ <string name="settings_compact_title">Kompaktni način rada</string>
<string name="settings_background_sync">Pozadinska sinkronizacija</string>
<string name="pref_value_wifi_and_mobile">Sinkronizacija putem bežičnih (Wi-Fi) i mobilnih podataka</string>
<string name="pref_value_wifi_only">Sinkroniziraj samo putem bežične (Wi-Fi) mreže</string>
@@ -155,7 +155,6 @@
<string name="maintenance_mode_explanation">Poslužitelj %1$s trenutno je u načinu održavanja. Obratite se svom administratoru ili pokušajte kasnije.</string>
<string name="share_add_to_card">Dodaj kartici</string>
<string name="share_success">%1$s uspješno dodano %2$s</string>
- <string name="could_not_copy_to_clipboard">Kopiranje u međuspremnik nije uspjelo</string>
<string name="add_comment">Dodaj komentar</string>
<string name="card_edit_comments">Komentari</string>
<string name="no_comments_yet">Još nema komentara</string>
@@ -167,11 +166,9 @@
<string name="open_in_browser">Otvori u pregledniku</string>
<string name="updating_card">Ažuriranje kartice…</string>
- <!-- Move lists -->
<string name="move_list_right">Pomakni popis desno</string>
<string name="move_list_left">Pomakni popis lijevo</string>
- <!-- Filter -->
<string name="filter_no_filter">Sve</string>
<string name="filter_overdue">Kasni</string>
<string name="filter_today">Danas</string>
@@ -182,7 +179,6 @@
<string name="filter_by_assigned_user">Filtriraj prema dodijeljenom korisniku</string>
<string name="filter_by_duedate">Filtriraj prema datumu dospijeća</string>
- <!-- Archived cards -->
<string name="archived_cards">Arhivirane kartice</string>
<string name="action_card_dearchive">Poništi arhiviranje kartice</string>
<string name="action_archived_cards">Pretraži arhivirane kartice</string>
@@ -197,11 +193,9 @@
<string name="filter_user_title">Korisnici</string>
<string name="filter_duedate_title">Datum dospijeća</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Poništi arhiviranje ploče</string>
<string name="archived_boards">Arhivirane ploče</string>
- <!-- Errors -->
<string name="error">Pojavila se pogreška</string>
<string name="synchronization_failed">Sinkronizacija nije uspjela</string>
<string name="operation_not_yet_supported">Još nije podržano</string>
@@ -211,25 +205,77 @@
<string name="server_misconfigured">Pogrešna konfiguracija poslužitelja</string>
<string name="server_error">Greška poslužitelja</string>
<string name="shared_error">Dijeljenje sadržaja iz aplikacija trećih strana još uvijek nije potpuno podržano. Pokušajte ga preuzeti i zatim dijeliti putem upravitelja podacima ili galerije.</string>
+ <string name="error_edit_activity_killed_by_android">Android je zatvorio način uređivanja jer treba više resursa sustava za ostale aplikacije.</string>
+
<string name="error_dialog_title">Ups, što sada? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Pokušajte prisilno zatvoriti aplikaciju i ponovo je pokrenuti. Možda se radi o nepravilnoj vezi s aplikacijom Nextcloud.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Ako problem i dalje nije otklonjen, pokušajte izbrisati pohranjene podatke iz obje aplikacije: Nextcloud i Nextcloud Deck.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Ako problem i dalje nije otklonjen, pokušajte izbrisati pohranjene podatke iz obje aplikacije: Nextcloud i Nextcloud Deck.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Nadogradnja baze podataka nije uspjela. Prijavite ovu poteškoću i izbrišite pohranu kako biste se koristili aplikacijom na uobičajeni način.</string>
+ <string name="error_dialog_tip_clear_storage">Pohranjene podatke možete izbrisati tako da otvorite informacije o aplikaciji i odaberete Pohrana → Brisanje pohrane podataka.</string>
<string name="error_dialog_tip_files_outdated">Čini se da je vaša aplikacija Nextcloud zastarjela. Posjetite trgovinu Play Store ili F-Droid kako biste dohvatili najnoviju inačicu.</string>
<string name="error_dialog_tip_files_force_stop">Čini se da nešto nije u redu s vašom aplikacijom Nextcloud. Pokušajte prisilno zatvoriti aplikaciju Nextcloud i aplikaciju Nextcloud Deck.</string>
<string name="error_dialog_tip_files_delete_storage">Ako prisilno zatvaranje nije otklonilo problem, pokušajte izbrisati pohranjene podatke iz obje aplikacije.</string>
<string name="error_dialog_timeout_instance">Nije stigao odgovor poslužitelja u zadanom vremenskom razdoblju. Provjerite radi li vaša instanca.</string>
+ <string name="error_dialog_timeout_toggle">Provjerite mrežnu vezu. Ponekad može pomoći isključivanje i ponovno uključivanje mobilnih podataka ili bežične (Wi-Fi) mreže.</string>
<string name="error_dialog_check_server">Odgovor poslužitelja nije točan. Provjerite možete li aplikaciji Deck pristupiti putem web-sučelja.</string>
<string name="error_dialog_check_server_logs">Postoji određeni problem s vašim Nextcloudom. Provjerite datoteke zapisa poslužitelja.</string>
<string name="error_dialog_check_maintenance">Provjerite je li vaša instanca Nextclouda trenutno u načinu održavanja.</string>
<string name="error_dialog_insufficient_storage">Nema slobodnog prostora za pohranu u vašoj instanci Nextclouda. Izbrišite dio datoteka kako biste sinkronizirali lokalne promjene u oblak.</string>
<string name="error_dialog_we_need_info">Potrebne su nam sljedeće tehničke informacije kako bismo vam pomogli:</string>
<string name="error_dialog_redirect">Vaš je poslužitelj vratio šifru statusa HTTP 302, što znači da aplikacija Deck nije instalirana na poslužitelju ili postoji pogreška u konfiguraciji. To mogu uzrokovati prilagođena prepisivanja u datoteci .htaccess ili određene aplikacije u Nextcloudu, kao što je OID Client.</string>
+ <string name="error_dialog_version_not_parsable">Nismo mogli utvrditi inačicu aplikacije Deck na strani poslužitelja. Provjerite je li instalirana i omogućena.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Račun vaše aplikacije Nextcloud možda više nije ovlašten.</string>
+ <string name="error_dialog_user_not_found_in_database">Trenutačni korisnik ne odgovara korisniku kojeg imamo u našoj bazi podataka. Ako upotrebljavate LDAP na svojoj instanci Nextclouda, vaša aplikacija Nextcloud možda je pohranila stari ID korisnika.</string>
+ <string name="error_dialog_capabilities_not_parsable">Nismo uspjeli dohvatiti mogućnosti vašeg poslužitelja. Provjerite radi li vaš poslužitelj pravilno i mogu li druge korisničke aplikacije pristupiti Nextcloudu.</string>
+ <string name="error_dialog_attachment_upload_failed">Nije moguće otpremiti privitak. Pokušajte ga podijeliti na neki drugi način i javite nam pojedinosti o ovoj pogrešci.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Onemogućite sve optimizacije baterije za aplikacije Nextcloud i Deck.</string>
+ <string name="error_action_open_deck_info">Otvori informacije o aplikaciji</string>
<string name="error_action_open_network">Postavke mreže</string>
+ <string name="error_action_server_logs">Zapisi poslužitelja</string>
<string name="error_action_install">Instaliraj</string>
+ <string name="error_action_report_issue">Prijavi</string>
<string name="info_box_maintenance_mode">Poslužitelj u načinu održavanja</string>
<string name="info_box_version_not_supported">Inačica poslužitelja %1$s nije podržana, ažurirajte poslužitelj na inačicu %2$s</string>
<string name="share_link">Dijeli poveznicu</string>
+ <string name="archive_cards">Arhiviraj kartice</string>
+
<string name="manage_accounts">Upravljaj računima</string>
+ <string name="manage_list">Upravljaj popisom</string>
<string name="simple_reply">Odgovori</string>
+ <string name="error_while_uploading_attachment">Pogreška pri otpremanju privitka: %1$s</string>
+ <string name="append_text_to_description">Dodaj opisu</string>
+ <string name="add_text_as_comment">Dodaj kao komentar</string>
+ <string name="progress_count">%1$d od %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d pogreška pri otpremanju</item>
+ <item quantity="few">%1$d pogreške pri otpremanju</item>
+ <item quantity="other">%1$d pogreški pri otpremanju</item>
+ </plurals>
<string name="simple_report">Prijavi</string>
-</resources>
+ <string name="error_action_open_battery_settings">Postavke baterije</string>
+ <string name="move_warning">Pri premještanju kartice na drugu ploču ne mogu se prenijeti komentari ni privitci.</string>
+ <string name="clone_board">Kloniraj ploču</string>
+ <string name="cloning_board">Kloniranje %1$s…</string>
+ <string name="successfully_cloned_board">Uspješno klonirano %1$s</string>
+ <string name="attachment_does_not_yet_exist">Privitak ne postoji u aplikaciji Deck</string>
+ <string name="card_does_not_yet_exist">Kartica ne postoji u aplikaciji Deck</string>
+
+ <string name="widget_stack_title">Popis</string>
+ <string name="widget_stack_header_icon">Ikona zaglavlja widgeta</string>
+ <string name="widget_stack_placeholder_icon">Privremena ikona widgeta</string>
+ <string name="select_stack">Odaberi popis</string>
+ <string name="project_type_deck_board">Deck ploča</string>
+ <string name="project_type_deck_card">Deck kartica</string>
+ <string name="project_type_file">Datoteka</string>
+ <string name="projects_title">Projekti</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d resurs</item>
+ <item quantity="few">%1$d resursa</item>
+ <item quantity="other">%1$d resursa</item>
+ </plurals>
+ <string name="no_assigned_label">Nema dodijeljenih oznaka</string>
+ <string name="single_card">Jedna kartica</string>
+ <string name="project_type_room">Soba za razgovor</string>
+ <string name="simple_move">Premjesti</string>
+ <string name="cannot_upload_files_without_permission">Otpremanje datoteka bez dopuštenja nije moguće</string>
+ </resources>
diff --git a/app/src/main/res/values-hu-rHU/strings.xml b/app/src/main/res/values-hu-rHU/strings.xml
index c7d30faa3..e6f882727 100644
--- a/app/src/main/res/values-hu-rHU/strings.xml
+++ b/app/src/main/res/values-hu-rHU/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Utolsó fiók</string>
<string name="drawer_manage_accounts">Fiókok kezelése</string>
- <!-- Simple values -->
<string name="simple_boards">Táblák</string>
<string name="simple_add">Hozzáadás</string>
<string name="simple_save">Mentés</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">letiltva</string>
<string name="simple_copied">Másolva</string>
<string name="simple_archive">Archívum</string>
+ <string name="simple_unassigned">Nem hozzárendelt</string>
<string name="edit_board">Tábla szerkesztése</string>
<string name="archive_board">Tábla archiválása</string>
<string name="delete_board">Tábla törlése</string>
<string name="delete_something">%1$s törlése</string>
- <!-- About -->
<string name="about">Névjegy</string>
<string name="about_version_title">Verzió</string>
<string name="about_version">Jelenleg ezt a verziót használja: %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Köszönet</string>
<string name="about_contribution_tab_title">Közreműködés</string>
<string name="about_license_tab_title">Licenc</string>
-
- <string name="copied_to_clipboard">Vágólapra másolva</string>
-
<string name="seconds_ago">pár másodperce</string>
<string name="edit">Szerkesztés</string>
<string name="label_labels">Címkék kiválasztása</string>
@@ -112,6 +108,8 @@
<string name="not_synced_yet">Még nincs szinkronizálva</string>
<string name="no_lists_yet">Még nincsenek listák</string>
<string name="do_you_want_to_save_your_changes">Biztos, hogy menti a változásokat?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">Biztos, hogy archiválja a(z) %1$s összes kártyáját?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Kívánja a %1$s minden szűrt kártyáját arhiválni?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Ez véglegesen töröl %1$d kártyát a listából.</item>
<item quantity="other">Ez véglegesen törli mind a(z) %1$d kártyát a listából.</item>
@@ -128,6 +126,7 @@
<string name="delete_board_message">Ez véglegesen törli ezt a táblát az összes listával és kártyával együtt.</string>
<string name="settings_theme_title">Sötét téma</string>
<string name="settings_branding_title">Márkázas</string>
+ <string name="settings_compact_title">Kompakt mód</string>
<string name="settings_background_sync">Háttér-szinkronizálás</string>
<string name="pref_value_wifi_and_mobile">Szinkronizálás Wi-Fin és mobil adatkapcsolaton</string>
<string name="pref_value_wifi_only">Szinkronizálás csak Wi-Fin</string>
@@ -155,7 +154,6 @@
<string name="maintenance_mode_explanation">A(z) %1$s kiszolgáló jelenleg karbantartási módban van. Lépjen kapcsolatba a rendszergazdával vagy próbálja újra.</string>
<string name="share_add_to_card">Kártyához adás</string>
<string name="share_success">%1$s sikeresen hozzáadva ehhez: %2$s</string>
- <string name="could_not_copy_to_clipboard">Nem sikerült a vágólapra másolni</string>
<string name="add_comment">Hozzászólás hozzáadása</string>
<string name="card_edit_comments">Hozzászólások</string>
<string name="no_comments_yet">Még nincsenek hozzászólások</string>
@@ -167,11 +165,9 @@
<string name="open_in_browser">Megnyitás böngészőben</string>
<string name="updating_card">Kártya frissítése…</string>
- <!-- Move lists -->
<string name="move_list_right">Lista áthelyezése jobbra</string>
<string name="move_list_left">Lista áthelyezése balra</string>
- <!-- Filter -->
<string name="filter_no_filter">Mind</string>
<string name="filter_overdue">Lejárt</string>
<string name="filter_today">Ma</string>
@@ -182,7 +178,6 @@
<string name="filter_by_assigned_user">Szűrés hozzárendelt felhasználó szerint</string>
<string name="filter_by_duedate">Szűrés határidő szerint</string>
- <!-- Archived cards -->
<string name="archived_cards">Archivált kártyák</string>
<string name="action_card_dearchive">Kártya archiválásának visszavonása</string>
<string name="action_archived_cards">Archivált kártyák tallózása</string>
@@ -197,11 +192,9 @@
<string name="filter_user_title">Felhasználók</string>
<string name="filter_duedate_title">Határidő</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Tábla archiválásának visszavonása</string>
<string name="archived_boards">Archivált táblák</string>
- <!-- Errors -->
<string name="error">Hiba történt</string>
<string name="synchronization_failed">Szinkronizálás sikertelen</string>
<string name="operation_not_yet_supported">Még nem támogatott</string>
@@ -215,7 +208,8 @@
<string name="error_dialog_title">Jaj ne – Most mi legyen? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Próbálja kényszeríteni az alkalmazás bezárását, és indítsa újra. Lehet, hogy hibás volt a kapcsolat a többi Nextcloud alkalmazással.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Ha a probléma továbbra is fennáll, próbálja kiüríteni mindkét alkalmazás tárolóját: a Nextcloudét és a Nextcloud Kártyákét.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Ha a probléma továbbra is fennáll, próbálja kiüríteni mindkét alkalmazás tárolóját: a Nextcloudét és a Nextcloud Kártyákét.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Az adatbázis frissítése sikertelen. Jelentse a problémát és az alkalmazás normális használatához törölje a tárolót.</string>
<string name="error_dialog_tip_clear_storage">A tárolót az alkalmazásinformáció megnyitásával, és a Tárhely → Tárhely ürítése választásával törölheti.</string>
<string name="error_dialog_tip_files_outdated">A Nextcloud alkalmazása elavultnak tűnik. Keresse fel a Play Áruházat vagy az F-Droidot, hogy beszerezze a legfrissebb verziót.</string>
<string name="error_dialog_tip_files_force_stop">Valami hibásnak tűnik a Nextcloud alkalmazásában. Próbálja mindkettőt leállítani, a Nextcloud és a Nextcloud Kártyák alkalmazást is.</string>
@@ -229,16 +223,61 @@
<string name="error_dialog_we_need_info">A következő műszaki információkra van szükségünk, hogy segíthessünk:</string>
<string name="error_dialog_redirect">A kiszolgálója 302-es HTTP állapotkóddal válaszolt, amely arra utal, hogy nincs telepítve a Kártyák alkalmazás a kiszolgálón, vagy valami hibásan van beállítva. Ezt egyéni felülírások is okozhatják, mint egy .htaccess-fájl vagy az OID klienshez hasonló Nextcloud alkalmazások.</string>
<string name="error_dialog_version_not_parsable">Nem sikerült meghatározni a kiszolgálóoldali Kártyák alkalmazás verzióját. Győződjön meg róla, hogy telepítve és engedélyezve van.</string>
+ <string name="error_dialog_account_might_not_be_authorized">A Nextcloud alkalmazáshoz tartozó fiókja lehet hogy már lehet, hogy nincs hitelesítve.</string>
+ <string name="error_dialog_user_not_found_in_database">A jelenlegi felhasználó nem egyezik az adatbázisban tárolttal. Ha LDAP-ot használt a Nextcloud példányán, akkor lehet, hogy a Nextcloud alkalmazás még egy régebbi felhasználóazonosítót tárol.</string>
<string name="error_dialog_capabilities_not_parsable">A képességek lekérése a kiszolgálójáról sikertelen. Győződjön meg róla, hogy a kiszolgálója rendesen fut-e, és hogy más kliensalkalmazások elérik-e a Nextcloudod.</string>
+ <string name="error_dialog_attachment_upload_failed">A melléklet nem tölthető fel. Próbálja meg máshogy megosztani, és tájékoztasson minket a hibáról.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Kapcsoljon ki minden energiatakarékossági beállítást a Nextcloud valamint a Kártyák alkalmazáshoz. </string>
<string name="error_action_open_deck_info">Alkalmazásinformációk megnyitása</string>
<string name="error_action_open_network">Hálózati beállítások</string>
<string name="error_action_server_logs">Kiszolgálónaplók</string>
<string name="error_action_install">Telepítés</string>
+ <string name="error_action_report_issue">Jelentés</string>
<string name="info_box_maintenance_mode">A kiszolgáló karbantartási módban van</string>
<string name="info_box_version_not_supported">A kiszolgálóverzió nem támogatott: %1$s, frissítsen erre: %2$s</string>
<string name="share_link">Hivatkozás megosztása</string>
<string name="archive_cards">Archív kártyák</string>
+
<string name="manage_accounts">Fiókok kezelése</string>
+ <string name="manage_list">Lista kezelése</string>
<string name="simple_reply">Válasz</string>
+ <string name="error_while_uploading_attachment">Hiba a melléklet feltöltésekor: %1$s</string>
+ <string name="append_text_to_description">Hozzáfűzés a leíráshoz</string>
+ <string name="add_text_as_comment">Hozzáadás megjegyzésként</string>
+ <string name="progress_count">%1$d / %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d hiba történt feltöltéskor</item>
+ <item quantity="other">%1$d hiba történt feltöltéskor</item>
+ </plurals>
<string name="simple_report">Jelentés</string>
+ <string name="error_action_open_battery_settings">Akkumulátorbeállítások</string>
+ <string name="move_warning">Sem a megjegyzések, sem a mellékletek nem vihetők át, ha másik táblára helyezi át a kártyát.</string>
+ <string name="clone_board">Tábla klónozása</string>
+ <string name="cloning_board">%1$s klónozása…</string>
+ <string name="successfully_cloned_board">%1$s sikeresen klónozva.</string>
+ <string name="attachment_does_not_yet_exist">A melléklet még nem létezik a Kártyákban</string>
+ <string name="card_does_not_yet_exist">A kártya még nem létezik a Kártyákban</string>
+
+ <string name="widget_stack_title">Lista</string>
+ <string name="widget_stack_header_icon">Modul fejlécikonja</string>
+ <string name="widget_stack_placeholder_icon">Modul helykitöltő ikonja</string>
+ <string name="select_stack">Válasszon listát</string>
+ <string name="project_type_deck_board">Kártyatábla</string>
+ <string name="project_type_deck_card">Kártya</string>
+ <string name="project_type_file">Fájl</string>
+ <string name="projects_title">Projektek</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d erőforrás</item>
+ <item quantity="other">%1$d erőforrás</item>
+ </plurals>
+ <string name="no_assigned_label">Nincs címke hozzárendelve</string>
+ <string name="single_card">Egyetlen kártya</string>
+ <string name="project_type_room">Beszélgetés szoba</string>
+ <string name="simple_move">Áthelyezés</string>
+ <string name="cannot_upload_files_without_permission">Engedély nélkül nem tölthetők fel fájlok</string>
+ <string name="clone_cards">Kártyák klónozása</string>
+ <string name="simple_clone">Klónozás</string>
+ <string name="user_avatar">Felhasználói avatar</string>
+ <string name="simple_unassign">Elvétel</string>
+ <string name="add_stack_widget">Lista felületi elem hozzáadása</string>
</resources>
diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml
index 77d6b4535..a971fa004 100644
--- a/app/src/main/res/values-it/strings.xml
+++ b/app/src/main/res/values-it/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Ultimo account</string>
<string name="drawer_manage_accounts">Gestisci account</string>
- <!-- Simple values -->
<string name="simple_boards">Lavagne</string>
<string name="simple_add">Aggiungi</string>
<string name="simple_save">Salva</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">disabilitato</string>
<string name="simple_copied">Copiato</string>
<string name="simple_archive">Archivio</string>
+ <string name="simple_unassigned">Non assegnato</string>
<string name="edit_board">Modifica lavagna</string>
<string name="archive_board">Archivia lavagna</string>
<string name="delete_board">Elimina lavagna</string>
<string name="delete_something">Elimina %1$s</string>
- <!-- About -->
<string name="about">Informazioni</string>
<string name="about_version_title">Versione</string>
<string name="about_version">Stai utilizzando attualmente %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Riconoscimenti</string>
<string name="about_contribution_tab_title">Contributi</string>
<string name="about_license_tab_title">Licenza</string>
-
- <string name="copied_to_clipboard">Copiato negli appunti</string>
-
<string name="seconds_ago">secondi fa</string>
<string name="edit">Modifica</string>
<string name="label_labels">Seleziona etichette</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">Ancora nessun elenco</string>
<string name="do_you_want_to_save_your_changes">Vuoi salvare le tue modifiche?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Vuoi archiviare tutte le schede di %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Vuoi archiviare tutte le schede filtrate di %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Questo eliminerà definitivamente %1$d scheda di questo elenco</item>
<item quantity="other">Questo eliminerà definitivamente %1$d schede di questo elenco</item>
@@ -129,6 +126,7 @@
<string name="delete_board_message">Questo eliminerà definitivamente questa lavagna inclusi tutti gli elenchi e le schede.</string>
<string name="settings_theme_title">Tema scuro</string>
<string name="settings_branding_title">Marchio</string>
+ <string name="settings_compact_title">Modalità compatta</string>
<string name="settings_background_sync">Sincronizzazione in background</string>
<string name="pref_value_wifi_and_mobile">Sincronizza su Wi-FI e dati mobili</string>
<string name="pref_value_wifi_only">Sincronizza solo con Wi-Fi</string>
@@ -156,7 +154,6 @@
<string name="maintenance_mode_explanation">Il server %1$s attualmente è in manutenzione. Contatta l\'amministratore o riprova più tardi.</string>
<string name="share_add_to_card">Aggiungi a scheda</string>
<string name="share_success">Aggiunto %1$s a %2$s correttamente</string>
- <string name="could_not_copy_to_clipboard">Impossibile copiare negli appunti</string>
<string name="add_comment">Aggiungi commento</string>
<string name="card_edit_comments">Commenti</string>
<string name="no_comments_yet">Ancora nessun commento</string>
@@ -168,11 +165,9 @@
<string name="open_in_browser">Apri nel browser</string>
<string name="updating_card">Aggiornamento scheda...</string>
- <!-- Move lists -->
<string name="move_list_right">Sposta a destra l\'elenco</string>
<string name="move_list_left">Sposta a sinistra l\'elenco</string>
- <!-- Filter -->
<string name="filter_no_filter">Tutti</string>
<string name="filter_overdue">Scadenza</string>
<string name="filter_today">Oggi</string>
@@ -183,7 +178,6 @@
<string name="filter_by_assigned_user">Filtra per utente assegnato</string>
<string name="filter_by_duedate">Filtra per data di scadenza</string>
- <!-- Archived cards -->
<string name="archived_cards">Schede archiviate</string>
<string name="action_card_dearchive">Annulla archiviazione della scheda</string>
<string name="action_archived_cards">Sfoglia le schede archiviate</string>
@@ -198,11 +192,9 @@
<string name="filter_user_title">Utenti</string>
<string name="filter_duedate_title">Data di scadenza</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Annulla archiviazione della lavagna</string>
<string name="archived_boards">Lavagne archiviate</string>
- <!-- Errors -->
<string name="error">Si è verificato un errore</string>
<string name="synchronization_failed">Sincronizzazione non riuscita</string>
<string name="operation_not_yet_supported">Non ancora supportato</string>
@@ -216,7 +208,8 @@
<string name="error_dialog_title">Oh no - E adesso? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Prova a forzare la chiusura dell\'applicazione e riavviarla nuovamente. Potrebbe essersi verificato un problema di connessione all\'applicazione Nextcloud. </string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Se il problema persiste, prova a cancellare l\'archiviazione di entrambe le applicazioni Nextcloud e Nextcloud Deck per risolvere il problema.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Se il problema persiste, prova a cancellare l\'archiviazione di entrambe le applicazioni Nextcloud e Nextcloud Deck per risolvere il problema.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">L\'aggiornamento del database non è riuscito. Segnala il problema e cancella l\'archiviazione per utilizzare l\'applicazione normalmente.</string>
<string name="error_dialog_tip_clear_storage">Puoi a cancellare l\'archiviazione aprendo le informazioni dell\'applicazione e selezionando Archiviazione → Cancella archiviazione.</string>
<string name="error_dialog_tip_files_outdated">La tua applicazione di Nextcloud sembra essere datata. Visita il Play Store o F-Droid per ottenere l\'ultima versione.</string>
<string name="error_dialog_tip_files_force_stop">Sembra che ci sia un problema con la tua applicazione di Nextcloud. Prova a fermare entrambe le applicazioni Nextcloud e Nextcloud Deck.</string>
@@ -230,16 +223,21 @@
<string name="error_dialog_we_need_info">Ci servono le seguenti informazioni tecniche per aiutarti:</string>
<string name="error_dialog_redirect">Il tuo server ha risposto con un codice di stato HTTP 302, che implica che non hai installato l\'applicazione Deck sul tuo server o qualcosa non è configurato correttamente. Questo può essere causato da configurazioni personalizzate nel file .htaccess o da applicazioni di Nextcloud come OID Client.</string>
<string name="error_dialog_version_not_parsable">Impossibile determinare la versione dell\'applicazione Deck lato server. Assicurati che sia installata e abilitata.</string>
+ <string name="error_dialog_account_might_not_be_authorized">L\'account della tua applicazione Nextcloud potrebbe non essere più autorizzato.</string>
+ <string name="error_dialog_user_not_found_in_database">L\'utente attuale non corrisponde all\'utente presente nel nostro database. Se stai usando LDAP sulla tua istanza di Nextcloud, la tua applicazione potrebbe aver memorizzato un vecchio identificativo utente.</string>
<string name="error_dialog_capabilities_not_parsable">Non siamo riusciti a recuperare le funzionalità del tuo server. Assicurati che il server sia in esecuzione e che le altre applicazioni siano in grado di accedere a Nextcloud.</string>
<string name="error_dialog_attachment_upload_failed">Un allegato non può essere caricato. Prova a condividerlo in un altro modo e informaci di questo problema.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Disabilita tutte le ottimizzazioni della batteria per le applicazioni Nextcloud e Deck.</string>
<string name="error_action_open_deck_info">Apri Informazioni applicazione</string>
<string name="error_action_open_network">Impostazioni di rete</string>
<string name="error_action_server_logs">Log del server</string>
<string name="error_action_install">Installa</string>
+ <string name="error_action_report_issue">Segnala</string>
<string name="info_box_maintenance_mode">Server in manutenzione</string>
<string name="info_box_version_not_supported">Versione del server %1$s non supportata, aggiorna a %2$s</string>
<string name="share_link">Condividi collegamento</string>
<string name="archive_cards">Archivia schede</string>
+
<string name="manage_accounts">Gestisci account</string>
<string name="manage_list">Gestisci elenco</string>
<string name="simple_reply">Rispondi</string>
@@ -252,4 +250,50 @@
<item quantity="other">%1$d errori durante il caricamento</item>
</plurals>
<string name="simple_report">Segnala</string>
+ <string name="error_action_open_battery_settings">Impostazioni batteria</string>
+ <string name="move_warning">Né i commenti, né gli allegati possono essere trasferiti quando si sposta la scheda su un\'altra lavagna.</string>
+ <string name="clone_board">Clona lavagna</string>
+ <string name="cloning_board">Clonazione di %1$s…</string>
+ <string name="successfully_cloned_board">%1$s clonato correttamente</string>
+ <string name="attachment_does_not_yet_exist">L\'allegato non esiste ancora in Deck</string>
+ <string name="card_does_not_yet_exist">La scheda non esiste ancora in Deck</string>
+
+ <string name="widget_stack_title">Elenco</string>
+ <string name="widget_stack_header_icon">Icona intestazione widget</string>
+ <string name="widget_stack_placeholder_icon">Icona segnaposto widget</string>
+ <string name="select_stack">Seleziona elenco</string>
+ <string name="project_type_deck_board">Lavagna di Deck</string>
+ <string name="project_type_deck_card">Scheda di Deck</string>
+ <string name="project_type_file">File</string>
+ <string name="projects_title">Progetti</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d risorsa</item>
+ <item quantity="other">%1$d risorse</item>
+ </plurals>
+ <string name="no_assigned_label">Nessuna etichetta assegnata</string>
+ <string name="single_card">Scheda singola</string>
+ <string name="project_type_room">Stanza di Talk</string>
+ <string name="simple_move">Sposta</string>
+ <string name="cannot_upload_files_without_permission">Impossible caricare file senza permesso</string>
+ <string name="clone_cards">Clona schede</string>
+ <string name="simple_clone">Clona</string>
+ <string name="user_avatar">Avatar dell\'utente</string>
+ <string name="simple_unassign">Rimuovi assegnazione</string>
+ <string name="simple_contact">Contatto</string>
+ <string name="simple_file">File</string>
+ <string name="simple_camera">Fotocamera</string>
+ <string name="min_api_21">Questa funzionalità richiede almeno Android 5</string>
+ <string name="take_photo">Scatta una foto</string>
+ <string name="take_photo_switch_camera">Cambia fotocamera</string>
+ <string name="take_photo_toggle_torch">Attiva la torcia</string>
+ <string name="show_all_contacts">Mostra tutti i contatti</string>
+ <string name="show_all_files">Mostra tutti i file</string>
+ <string name="recent">Recenti</string>
+ <string name="upload_a_new_attachment">Carica un nuovo allegato</string>
+ <string name="contacts">Contatti</string>
+ <string name="downloads">Scaricamenti</string>
+ <string name="files">File</string>
+ <string name="gallery">Galleria</string>
+ <string name="simple_attach">allega</string>
+ <string name="add_stack_widget">Aggiungi widget elenco</string>
</resources>
diff --git a/app/src/main/res/values-ja-rJP/strings.xml b/app/src/main/res/values-ja-rJP/strings.xml
index 425a5ffbf..57642a08e 100644
--- a/app/src/main/res/values-ja-rJP/strings.xml
+++ b/app/src/main/res/values-ja-rJP/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">最後のアカウント</string>
<string name="drawer_manage_accounts">アカウント管理</string>
- <!-- Simple values -->
<string name="simple_boards">ボード</string>
<string name="simple_add">追加</string>
<string name="simple_save">保存</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">無効</string>
<string name="simple_copied">コピーしました</string>
<string name="simple_archive">アーカイブ</string>
+ <string name="simple_unassigned">未割り当て</string>
<string name="edit_board">ボードを編集</string>
<string name="archive_board">ボードをアーカイブ</string>
<string name="delete_board">ボードを削除</string>
<string name="delete_something">%1$sを削除</string>
- <!-- About -->
<string name="about">バージョン情報</string>
<string name="about_version_title">バージョン</string>
<string name="about_version">現在%1$sを使用しています</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">クレジット</string>
<string name="about_contribution_tab_title">貢献</string>
<string name="about_license_tab_title">ライセンス</string>
-
- <string name="copied_to_clipboard">クリップボードにコピーされました</string>
-
<string name="seconds_ago">数秒前</string>
<string name="edit">編集</string>
<string name="label_labels">タグを選択..</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">まだリストがありません</string>
<string name="do_you_want_to_save_your_changes">変更を保存しますか?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">%1$s の全カードをアーカイブしますか?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">%1$s のフィルターされた全カードをアーカイブしますか?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="other">このリストの%1$dのカードすべてが完全に削除されます。</item>
</plurals>
@@ -127,6 +124,7 @@
<string name="delete_board_message">このボードと含まれるすべてのリストやカードは完全に削除されます.</string>
<string name="settings_theme_title">ダークテーマ</string>
<string name="settings_branding_title">ブランディング</string>
+ <string name="settings_compact_title">コンパクトモード</string>
<string name="settings_background_sync">バックグラウンド同期</string>
<string name="pref_value_wifi_and_mobile">Wi-Fiおよびモバイル接続時に同期する</string>
<string name="pref_value_wifi_only">Wi-Fi接続時のみ同期する</string>
@@ -154,7 +152,6 @@
<string name="maintenance_mode_explanation">サーバー%1$sは現在メンテナンスモードです。管理者に問い合わせるか後で試してください。</string>
<string name="share_add_to_card">カードに追加</string>
<string name="share_success">%1$sを%2$sに追加できました</string>
- <string name="could_not_copy_to_clipboard">クリップボードにコピーできませんでした</string>
<string name="add_comment">コメントを追加</string>
<string name="card_edit_comments">コメント</string>
<string name="no_comments_yet">コメントはまだありません</string>
@@ -166,11 +163,9 @@
<string name="open_in_browser">ブラウザーで開く</string>
<string name="updating_card">カードの更新中...</string>
- <!-- Move lists -->
<string name="move_list_right">リストを右に移動</string>
<string name="move_list_left">リストを左に移動</string>
- <!-- Filter -->
<string name="filter_no_filter">すべて</string>
<string name="filter_overdue">期限超過</string>
<string name="filter_today">今日</string>
@@ -181,7 +176,6 @@
<string name="filter_by_assigned_user">割当ユーザーでフィルター</string>
<string name="filter_by_duedate">期限でフィルター</string>
- <!-- Archived cards -->
<string name="archived_cards">カードをアーカイブ</string>
<string name="action_card_dearchive">カードのアーカイブを元に戻す</string>
<string name="action_archived_cards">アーカイブされたカードをブラウズする</string>
@@ -196,11 +190,9 @@
<string name="filter_user_title">ユーザー</string>
<string name="filter_duedate_title">期限</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">ボードのアーカイブを元に戻す</string>
<string name="archived_boards">アーカイブ済みのボード</string>
- <!-- Errors -->
<string name="error">エラーが見つかりました</string>
<string name="synchronization_failed">同期に失敗</string>
<string name="operation_not_yet_supported">未サポート</string>
@@ -214,7 +206,8 @@
<string name="error_dialog_title">おやまあ、どうしたことでしょう?🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">アプリの強制終了と再起動を試みてください。Nextcloudアプリへの接続が正しくない接続があったかも知れません。</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">この問題が続くようでしたら、下記両方のアプリでストレージをクリアして問題が解決できるか試してください:Nextcloud と Nextcloud Deck</string>
+ <string name="error_dialog_tip_clear_storage_might_help">この問題が続くようでしたら、下記両方のアプリでストレージをクリアして問題が解決できるか試してください:Nextcloud と Nextcloud Deck</string>
+ <string name="error_dialog_tip_database_upgrade_failed">データベースのアップグレードに失敗しました。問題を報告し、ストレージを初期化してアプリを正常に使用できるようにしてください</string>
<string name="error_dialog_tip_clear_storage">以下の方法でストレージをクリアできます:アプリ情報→ストレージ→ストレージをクリア.</string>
<string name="error_dialog_tip_files_outdated">Nextcloudアプリが古いようです。プレイストアかF-Droidから最新版を入手してください。</string>
<string name="error_dialog_tip_files_force_stop">Nextcloudアプリで何か不具合が発生しました。NextcloudアプリとNextcloud Deckアプリの両方を強制終了してください。</string>
@@ -228,17 +221,76 @@
<string name="error_dialog_we_need_info">サポートには下記技術情報が必要です:</string>
<string name="error_dialog_redirect">サーバがHTTPステータスコード 302を返しました。これはDeckアプリがインストールされていないか、構成が誤っていることを意味します。.htaccessファイルのカスタムオーバーライドやOIDクライアントのようなNextcloudアプリが原因の可能性があります。</string>
<string name="error_dialog_version_not_parsable">サーバー側のDeckアプリのバージョンを判別できませんでした。Deckアプリがインストール,有効化されていることを確認してください。</string>
+ <string name="error_dialog_account_might_not_be_authorized">Nextcloudアプリのアカウントが認証されていない可能性があります。</string>
+ <string name="error_dialog_user_not_found_in_database">現在のユーザーは、データベースに登録されているユーザーと一致しません。NextcloudインスタンスでLDAPを使用している場合、Nextcloudアプリに古いユーザーIDが保存されている可能性があります。</string>
<string name="error_dialog_capabilities_not_parsable">サーバーの機能を取得できませんでした。サーバーが正常に動作しており、他のクライアントアプリがNextcloudにアクセスできることを確認してください。</string>
+ <string name="error_dialog_attachment_upload_failed">添付ファイルをアップロードできませんでした。別の方法で共有してみてください。
+また、このバグについて報告してください。</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">NextcloudとDeckアプリのバッテリー最適化をすべて無効にしてください。</string>
<string name="error_action_open_deck_info">アプリ情報を開く</string>
<string name="error_action_open_network">ネットワーク設定</string>
<string name="error_action_server_logs">サーバーログ</string>
<string name="error_action_install">インストール</string>
+ <string name="error_action_report_issue">報告</string>
<string name="info_box_maintenance_mode">サーバーはメンテナンスモードです。</string>
<string name="info_box_version_not_supported">サーバーのバージョン%1$sはサポートしていませんので、%2$sにアップデートしてください。</string>
<string name="share_link">URLで共有</string>
<string name="archive_cards">カードをアーカイブ</string>
+
<string name="manage_accounts">アカウント管理</string>
<string name="manage_list">リスト管理</string>
<string name="simple_reply">返信</string>
+ <string name="error_while_uploading_attachment">%1$s添付ファイルのアップロード中にエラーが発生しました</string>
+ <string name="append_text_to_description">説明を追加</string>
+ <string name="add_text_as_comment">コメントとして追加</string>
+ <string name="progress_count">%1$dの%2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="other">%1$dアップロード中にエラーが発生しました。</item>
+ </plurals>
<string name="simple_report">報告</string>
+ <string name="error_action_open_battery_settings">バッテリー設定</string>
+ <string name="move_warning">カードを別のボードに移動する際に、コメントや添付ファイルを転送することはできません。</string>
+ <string name="clone_board">ボードを複製</string>
+ <string name="cloning_board">%1$sを複製しています...</string>
+ <string name="successfully_cloned_board">%1$sの複製に成功しました</string>
+ <string name="attachment_does_not_yet_exist">添付ファイルはDeckには存在しません</string>
+ <string name="card_does_not_yet_exist">カードはDeckには存在しません</string>
+
+ <string name="widget_stack_title">リスト</string>
+ <string name="widget_stack_header_icon">ウィジェットのヘッダーアイコン</string>
+ <string name="widget_stack_placeholder_icon">ウィジェットの代替アイコン</string>
+ <string name="select_stack">リストを選択</string>
+ <string name="project_type_deck_board">Deckボード</string>
+ <string name="project_type_deck_card">Deckカード</string>
+ <string name="project_type_file">ファイル</string>
+ <string name="projects_title">プロジェクト</string>
+ <plurals name="resources_count">
+ <item quantity="other">%1$dリソース</item>
+ </plurals>
+ <string name="no_assigned_label">割り当てられたタグはありません</string>
+ <string name="single_card">1つのカード</string>
+ <string name="project_type_room">トークルーム</string>
+ <string name="simple_move">移動</string>
+ <string name="cannot_upload_files_without_permission">権限が無いためアップロードできません</string>
+ <string name="clone_cards">カードを複製</string>
+ <string name="simple_clone">複製</string>
+ <string name="user_avatar">ユーザーのアバター</string>
+ <string name="simple_unassign">割り当てを解除</string>
+ <string name="simple_contact">連絡先</string>
+ <string name="simple_file">ファイル</string>
+ <string name="simple_camera">カメラ</string>
+ <string name="min_api_21">この機能には少なくともAndroid5以上が必要です。</string>
+ <string name="take_photo">写真を撮る</string>
+ <string name="take_photo_switch_camera">カメラ切替</string>
+ <string name="take_photo_toggle_torch">フラッシュを切替</string>
+ <string name="show_all_contacts">すべての連絡先を表示</string>
+ <string name="show_all_files">すべてのファイルを表示</string>
+ <string name="recent">最近</string>
+ <string name="upload_a_new_attachment">新しい添付をアップロードする</string>
+ <string name="contacts">連絡先</string>
+ <string name="downloads">ダウンロード</string>
+ <string name="files">ファイル</string>
+ <string name="gallery">ギャラリー</string>
+ <string name="simple_attach">参加</string>
+ <string name="add_stack_widget">リストのウィジェットを追加</string>
</resources>
diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml
index 94803a997..da6a79cdc 100644
--- a/app/src/main/res/values-ko/strings.xml
+++ b/app/src/main/res/values-ko/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">마지막 계정</string>
<string name="drawer_manage_accounts">계정 관리</string>
- <!-- Simple values -->
<string name="simple_boards">게시판</string>
<string name="simple_add">추가</string>
<string name="simple_save">저장</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">비활성화</string>
<string name="simple_copied">복사됨</string>
<string name="simple_archive">보관</string>
+ <string name="simple_unassigned">할당되지 않음</string>
<string name="edit_board">게시판 편집</string>
<string name="archive_board">게시판 보관</string>
<string name="delete_board">게시판 삭제</string>
<string name="delete_something">%1$s 삭제</string>
- <!-- About -->
<string name="about">정보</string>
<string name="about_version_title">버전</string>
<string name="about_version"> 현재 %1$s사용 중</string>
@@ -64,13 +63,12 @@
<string name="about_app_license">이 앱은 GNU General Public License v3+로 배포됩니다.</string>
<string name="about_app_license_button">라이선스 보기</string>
<string name="about_icons_disclaimer_title">아이콘</string>
+ <string name="about_icons_disclaimer_app_icon">원래 아이콘을 보고싶으시면 %1$s</string>
+ <string name="about_icons_disclaimer_mdi_icons">이 후 %1$s앱의 모든 아이콘의 사용은 Google. Inc.가 만들고 Apache 2.0 License아래 보호됩니다.</string>
<string name="about_icons_disclaimer_mdi">Material 디자인 아이콘</string>
<string name="about_credits_tab_title">크레딧</string>
<string name="about_contribution_tab_title">기여자</string>
<string name="about_license_tab_title">라이선스</string>
-
- <string name="copied_to_clipboard">클립보드로 복사</string>
-
<string name="seconds_ago">몇 초 전</string>
<string name="edit">수정</string>
<string name="label_labels">태그 선택</string>
@@ -110,6 +108,8 @@
<string name="not_synced_yet">아직 동기화되지 않았음</string>
<string name="no_lists_yet">리스트 없음</string>
<string name="do_you_want_to_save_your_changes">변경된 사항을 저장하시겠습니까?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">모든 %1$s카드들을 보관 하시겠습니까?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">필터링된 %1$s 카드를 모두 보관하시겠습니까?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="other">이 리스트의 모든 %1$d카드가 영구적으로 지워집니다.</item>
</plurals>
@@ -123,6 +123,8 @@
<string name="deck_outdated_please_update">너무 오래된 버전입니다(%1$s). 업데이트 해주십시오.</string>
<string name="delete_board_message">모든 리스트와 카드를 포함한 이 보드를 영구적으로 지웁니다.</string>
<string name="settings_theme_title">어두운 테마</string>
+ <string name="settings_branding_title">브랜딩</string>
+ <string name="settings_compact_title">축소 모드</string>
<string name="settings_background_sync">백그라운드 동기화</string>
<string name="pref_value_wifi_and_mobile">Wi-Fi나 모바일 데이터를 사용해 동기화</string>
<string name="pref_value_wifi_only">Wi-Fi만 사용해 동기화</string>
@@ -150,7 +152,6 @@
<string name="maintenance_mode_explanation">%1$s서버는 현재 유지보수 모드입니다. 관리자에게 연락하거나 나중에 시도해주십시오.</string>
<string name="share_add_to_card">카드를 더하십시오.</string>
<string name="share_success">%1$s가 %2$s에 성공적으로 더해졌습니다.</string>
- <string name="could_not_copy_to_clipboard">클립보드에 복사할 수 없습니다.</string>
<string name="add_comment">덧글 달기</string>
<string name="card_edit_comments">댓글</string>
<string name="no_comments_yet">덧글이 없음</string>
@@ -158,14 +159,13 @@
<string name="add_a_new_board_using_the_button">+버튼을 사용해서 새로운 보드를 더하십시오.</string>
<string name="choose_board">보드 선택</string>
<string name="choose_list">리스트 선택</string>
+ <string name="task_count">%1$s/%2$s</string>
<string name="open_in_browser">브라우저에서 열기</string>
<string name="updating_card">카드 업데이트 중...</string>
- <!-- Move lists -->
<string name="move_list_right">리스트를 오른쪽으로 옮기기</string>
<string name="move_list_left">리스트를 왼쪽으로 옮기기</string>
- <!-- Filter -->
<string name="filter_no_filter">모두</string>
<string name="filter_overdue">기한 넘김</string>
<string name="filter_today">오늘</string>
@@ -176,7 +176,6 @@
<string name="filter_by_assigned_user">지정된 사용자로 필터링</string>
<string name="filter_by_duedate">기한으로 필터링</string>
- <!-- Archived cards -->
<string name="archived_cards">보관된 카드들</string>
<string name="action_card_dearchive">카드 보관 되돌리기</string>
<string name="action_archived_cards">보관된 카드 둘러보기</string>
@@ -191,11 +190,9 @@
<string name="filter_user_title">사용자</string>
<string name="filter_duedate_title">만료 날짜</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">보드 보관 되돌리기</string>
<string name="archived_boards">보관된 게시판</string>
- <!-- Errors -->
<string name="error">오류가 나타남</string>
<string name="synchronization_failed">동기화에 실패했습니다.</string>
<string name="operation_not_yet_supported">아직 지원되지 않습니다!</string>
@@ -209,21 +206,90 @@
<string name="error_dialog_title">세상에 - 무슨일이야?</string>
<string name="error_dialog_tip_token_mismatch_retry">앱을 종료하고 다시 시작해주십시오. 아마 Nextcloud에 잘못된 연결이 있었을 것입니다.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">이슈가 지속되면 Nextcloud와 Nextcloud Deck라는 두 앱을 스토리지에서 지워 이 문제를 해결하십시오.ㅣ</string>
+ <string name="error_dialog_tip_clear_storage_might_help">이슈가 지속되면 Nextcloud와 Nextcloud Deck라는 두 앱을 스토리지에서 지워 이 문제를 해결하십시오.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">데이터베이스 업그레이드 실패. 오류를 보고하고 정상적인 앱사용을 위해 저장소를 정리하세요.</string>
<string name="error_dialog_tip_clear_storage">저장공간을 비우기 위해 app info를 열고 Storage → Clear storage를 선택해주십시오.</string>
<string name="error_dialog_tip_files_outdated">당신의 Nextcloud 앱은 예전 버전입니다. Play Store나 F-Droid에 가서 최신 버전을 다운로드 하십시오.</string>
<string name="error_dialog_tip_files_force_stop">Nextcloud 앱에 문제가 생겼습니다. Nextcloud앱과 Nextcloud Deck앱 둘 다 강제종료 해보십시오.</string>
<string name="error_dialog_tip_files_delete_storage">강제 종료가 도움이 되지 않는다면, 두 앱 모두의 저장공간을 비워보십시오.</string>
<string name="error_dialog_timeout_instance">서버로부터 응답이 없습니다. 당신의 인스턴스가 잘 작동하는지 확인해 주십시오.</string>
<string name="error_dialog_timeout_toggle">네트워크 연결을 확인하십시오. 종종 모바일데이터나 와이파이를 껐다가 키는 것이 도움이 될 것입니다.</string>
+ <string name="error_dialog_check_server">서버의 응답이 틀렸습니다. 덱에 접근할 수 있는지 웹인터페이스로 확인해 주세요.</string>
+ <string name="error_dialog_check_server_logs">Nextcloud 설정에 문제가 있습니다. 서버 로그 파일을 살펴보시기 바랍니다.</string>
+ <string name="error_dialog_check_maintenance">Nextcloud 인스턴스가 현재 유지 보수 모드에 있지 않은지 확인해 주세요.</string>
+ <string name="error_dialog_insufficient_storage">Nextcloud 인스턴스에 사용 가능한 스토리지가 남아 있지 않습니다. 클라우드에 로컬 변경사항을 동기화하려면 일부 파일을 삭제해 주십시오.</string>
+ <string name="error_dialog_we_need_info">귀하를 돕기 위해 다음 기술 정보가 필요합니다.</string>
+ <string name="error_dialog_redirect">서버가 HTTP 302 상태 코드로 응답했습니다. 이는 서버에 Deck 앱을 설치하지 않았거나 잘못 구성되었음을 의미합니다. 이 문제는 .htaccess 파일의 사용자 지정 재정의 또는 OID Client와 같은 Nextcloud 앱에서 발생할 수 있습니다.</string>
+ <string name="error_dialog_version_not_parsable">서버 측 Deck 앱의 버전을 확인할 수 없습니다. 설치 및 활성화되어 있는지 확인하십시오.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Nextcloud 앱의 계정이 더 이상 인증되지 않을 수 있습니다.</string>
+ <string name="error_dialog_user_not_found_in_database">현재 사용자가 데이터베이스에 있는 사용자와 일치하지 않습니다. Nextcloud 인스턴스에서 LDAP을 사용하는 경우 Nextcloud 앱에 이전 사용자 ID가 저장되었을 수 있습니다.</string>
+ <string name="error_dialog_capabilities_not_parsable">서버의 기능을 가져올 수 없습니다. 서버가 제대로 실행되고 다른 클라이언트 앱이 Nextcloud에 액세스할 수 있는지 확인하십시오.</string>
+ <string name="error_dialog_attachment_upload_failed">첨부 파일을 업로드 할 수 없습니다. 다른 방법으로 공유하시고 저희에게 이 버그에 대해 알려주세요.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Nextcloud 및 Deck 앱에 대한 모든 배터리 최적화를 비활성화해 주십시오.</string>
<string name="error_action_open_deck_info">앱 정보 열기</string>
<string name="error_action_open_network">네트워크 환경설정</string>
<string name="error_action_server_logs">서버 기록</string>
<string name="error_action_install">설치</string>
+ <string name="error_action_report_issue">보고</string>
<string name="info_box_maintenance_mode">서버가 유지 관리 모드임</string>
<string name="info_box_version_not_supported">%1$s서버 버전은 지원되지 않습니다. %2$s로 업데이트 해주십시오.</string>
<string name="share_link">링크 공유</string>
+ <string name="archive_cards">카드들 보관</string>
+
<string name="manage_accounts">계정 관리</string>
+ <string name="manage_list">목록 관리</string>
<string name="simple_reply">답장</string>
+ <string name="error_while_uploading_attachment">%1$s 첨부 파일을 업로드하는 동안 오류가 발생했습니다.</string>
+ <string name="append_text_to_description">설명에 추가</string>
+ <string name="add_text_as_comment">댓글로 추가하기</string>
+ <string name="progress_count">%2$d의 %1$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="other">업로드하는 동안 %1$d 오류가 발생했습니다.</item>
+ </plurals>
<string name="simple_report">보고</string>
+ <string name="error_action_open_battery_settings">배터리 설정</string>
+ <string name="move_warning">카드를 다른 보드로 옮길 때 댓글이나 첨부 파일을 전송할 수 없습니다.</string>
+ <string name="clone_board">게시판 복제</string>
+ <string name="cloning_board">%1$s 복제중...</string>
+ <string name="successfully_cloned_board">%1$s이(가) 성공적으로 복제되었습니다.</string>
+ <string name="attachment_does_not_yet_exist">첨부 파일이 아직 Deck에 없습니다.</string>
+ <string name="card_does_not_yet_exist">카드가 아직 Deck에 없습니다.</string>
+
+ <string name="widget_stack_title">목록</string>
+ <string name="widget_stack_header_icon">위젯 헤더 아이콘</string>
+ <string name="widget_stack_placeholder_icon">위젯 자리 표시 아이콘</string>
+ <string name="select_stack">목록 선택</string>
+ <string name="project_type_deck_board">Deck 보드</string>
+ <string name="project_type_deck_card">Deck 카드</string>
+ <string name="project_type_file">파일</string>
+ <string name="projects_title">프로젝트</string>
+ <plurals name="resources_count">
+ <item quantity="other">%1$d 자원</item>
+ </plurals>
+ <string name="no_assigned_label">할당된 태그 없음</string>
+ <string name="single_card">단일 카드</string>
+ <string name="project_type_room">대화방</string>
+ <string name="simple_move">이동</string>
+ <string name="cannot_upload_files_without_permission">권한 없이 파일을 업로드할 수 없습니다.</string>
+ <string name="clone_cards">복제 카드</string>
+ <string name="simple_clone">복제</string>
+ <string name="user_avatar">사용자 아바타</string>
+ <string name="simple_unassign">할당 해제</string>
+ <string name="simple_contact">연락처</string>
+ <string name="simple_file">파일</string>
+ <string name="simple_camera">카메라</string>
+ <string name="min_api_21">이 기능을 사용하려면 적어도 Android 5가 필요합니다.</string>
+ <string name="take_photo">사진 찍기</string>
+ <string name="take_photo_switch_camera">카메라 전환</string>
+ <string name="take_photo_toggle_torch">토치 전환</string>
+ <string name="show_all_contacts">모든 연락처 보기</string>
+ <string name="show_all_files">모든 파일 보기</string>
+ <string name="recent">최근 항목</string>
+ <string name="upload_a_new_attachment">새 첨부 파일 업로드</string>
+ <string name="contacts">연락처</string>
+ <string name="downloads">다운로드</string>
+ <string name="files">파일</string>
+ <string name="gallery">갤러리</string>
+ <string name="simple_attach">첨부하기</string>
+ <string name="add_stack_widget">목록 위젯 추가</string>
</resources>
diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml
new file mode 100644
index 000000000..760e340c8
--- /dev/null
+++ b/app/src/main/res/values-nb-rNO/strings.xml
@@ -0,0 +1,277 @@
+<resources>
+ <string name="navigation_drawer_open">Åpne navigasjonsskuff</string>
+ <string name="navigation_drawer_close">Lukk navigasjonsskuff</string>
+
+ <string name="drawer_header_background">Bakgrunsbilde for innledning på meny</string>
+ <string name="drawer_current_account">Nåværende konto</string>
+ <string name="drawer_middle_account">Mellomkonto</string>
+ <string name="drawer_end_account">Siste konto</string>
+ <string name="drawer_manage_accounts">Håndter kontoer</string>
+
+ <string name="simple_boards">Tavler</string>
+ <string name="simple_add">Legg til</string>
+ <string name="simple_save">Lagre</string>
+ <string name="simple_more">Mer</string>
+ <string name="simple_title">Tittel</string>
+ <string name="simple_copy">kopier</string>
+ <string name="simple_synchronization">Synkronisering</string>
+ <string name="simple_appearance">Utseende</string>
+ <string name="simple_error">Feil</string>
+ <string name="simple_exception">Avbrudd</string>
+ <string name="simple_close">lukk</string>
+ <string name="simple_open">Åpne</string>
+ <string name="simple_switch">Endre</string>
+ <string name="simple_filter">Filter</string>
+ <string name="simple_overdue">Utløpt</string>
+ <string name="simple_clear">Tøm</string>
+ <string name="simple_discard">slett</string>
+ <string name="simple_update">Oppdater</string>
+ <string name="simple_delete">Slett</string>
+ <string name="simple_rename">Endre navn</string>
+ <string name="simple_settings">Innstillinger</string>
+ <string name="simple_undo">Angre</string>
+ <string name="simple_manage">håndtere</string>
+ <string name="simple_share">del</string>
+ <string name="simple_select">Velg</string>
+ <string name="simple_comment">Kommentar</string>
+ <string name="simple_disabled">deaktiver</string>
+ <string name="simple_copied">Kopiert</string>
+ <string name="simple_archive">Arkiv</string>
+ <string name="simple_unassigned">Ikke tildelt</string>
+
+ <string name="edit_board">Rediger tavle</string>
+ <string name="archive_board">Arkiver tavle</string>
+ <string name="delete_board">Slett tavle</string>
+ <string name="delete_something">Slett %1$s</string>
+
+ <string name="about">Om</string>
+ <string name="about_version_title">Versjon</string>
+ <string name="about_version">Du bruker forøyeblikket %1$s</string>
+ <string name="about_maintainer_title">Vedlikeholder</string>
+ <string name="about_developers_title">Utviklere</string>
+ <string name="about_translators_title">Oversettere</string>
+ <string name="about_translators_transifex">Nextcloud-fellesskapet på %1$s</string>
+ <string name="about_translators_transifex_label">Transifex</string>
+ <string name="about_testers_title">Testere</string>
+ <string name="about_source_title">Kildekode</string>
+ <string name="about_source">Dette prosjektet er på GitHub: %1$s</string>
+ <string name="about_issues_title">Problemer</string>
+ <string name="about_issues">Du kan rapportere inn feil, forbedringsforslag og funksjonsforespørsler på GitHub issue tracker: %1$s</string>
+ <string name="about_translate_title">Oversett</string>
+ <string name="about_translate">Bli med og hjelp oss å oversette Nextcloud på vår Transifex side: %1$s </string>
+ <string name="about_app_license_title">App-lisens</string>
+ <string name="about_app_license">Denne appen er lisensiert under GNU GENERAL PUBLIC LICENSE v3+.</string>
+ <string name="about_app_license_button">Se lisens</string>
+ <string name="about_icons_disclaimer_title">Ikoner</string>
+ <string name="about_icons_disclaimer_app_icon">For original ikon se%1$s.</string>
+ <string name="about_icons_disclaimer_mdi_icons">Alle øvrige ikoner brukt i denne appen er %1$s laget av Google Inc. og lisensiert under Apache 2.0-lisens.</string>
+ <string name="about_icons_disclaimer_mdi">Materialdesignikoner</string>
+ <string name="about_credits_tab_title">Bidragsytere</string>
+ <string name="about_contribution_tab_title">Bidrag</string>
+ <string name="about_license_tab_title">Lisens</string>
+ <string name="seconds_ago">sekunder siden</string>
+ <string name="edit">Rediger</string>
+ <string name="label_labels">Velg merkelapper</string>
+ <string name="label_description">Beskrivelse</string>
+ <string name="hint_assign_people">Tildel brukere</string>
+ <string name="hint_due_date_date">Forfallsdato</string>
+ <string name="card_not_found">Kort ikke funnet</string>
+ <string name="card_not_found_message">Kortet kunne ikke bli funnet. Mulig det har blitt slettet nylig.</string>
+
+ <string name="add_account">Legg til en konto</string>
+ <string name="choose_account">Velg konto</string>
+ <string name="add_card">Legg til nytt kort</string>
+ <string name="activity">Aktivitet</string>
+ <string name="add_list">Legg til liste</string>
+ <string name="rename_list">Gi nytt navn på listen</string>
+ <string name="delete_list">Slett listen</string>
+ <string name="label_menu">Meny</string>
+ <string name="action_card_assign">Tildel til meg</string>
+ <string name="action_card_unassign">Fjern tildeling til meg</string>
+ <string name="action_card_archive">Arkiver kort</string>
+ <string name="action_card_delete">Slett kort</string>
+ <string name="add_board">Legg til tavle</string>
+
+ <string name="label_clear_due_date">Fjern forfallsdato</string>
+ <string name="label_add">Legg til%1$s</string>
+
+ <string name="card_edit_details">Detaljer</string>
+ <string name="card_edit_attachments">Vedlegg</string>
+ <string name="card_edit_activity">Aktivitet</string>
+ <string name="about_server_app_version_text">Tjenerversjon:</string>
+ <string name="no_files_attached_to_this_card">Det er ingen filter vedlagt dette kortet.</string>
+ <string name="attachments">Vedlegg</string>
+ <string name="no_cards">Ingen kort enda</string>
+ <string name="no_account">Ingen konto konfigurert</string>
+ <string name="account_already_added">Kontoen er allerede lagt til</string>
+ <string name="account_is_getting_imported">Kontoen blir importert</string>
+ <string name="not_synced_yet">Ikke synkronisert enda</string>
+ <string name="no_lists_yet">Ingen lister enda</string>
+ <string name="do_you_want_to_save_your_changes">Ønsker du å lagre din endringer?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">Vil du arkivere all kort av %1$s?</string>
+ <plurals name="do_you_want_to_delete_the_current_list">
+ <item quantity="one">Dette vil slette %1$d kortet i listen permanent.</item>
+ <item quantity="other">Dette vil slette alle %1$d kortene i listen permanent.</item>
+ </plurals>
+ <plurals name="do_you_want_to_delete_the_label">
+ <item quantity="one">Dette vil fjerne etiketten fra %1$d kortet.</item>
+ <item quantity="other">Dette vil fjerne etiketten fra %1$d kortene.</item>
+ </plurals>
+ <string name="add_a_new_list_using_the_button">Legg til ny liste ved å bruke + knappen</string>
+ <string name="add_a_new_card_using_the_button">Legg til nytt kort ved å bruke + knappen</string>
+ <string name="update_deck">Oppdater «deck»</string>
+ <string name="your_deck_version_is_too_old">Din tjenerversjon av «deck» er updatert</string>
+ <string name="deck_outdated_please_update">Din tjenerversjon av «deck» er utdatert (%1$s). Vennligst oppdater for å bruke denne android appen som klient.</string>
+ <string name="delete_board_message">Dette vil uopprettelig slette denne tavlen inkludert alle lister og kort.</string>
+ <string name="settings_theme_title">Mørkt tema</string>
+ <string name="settings_branding_title">Branding</string>
+ <string name="settings_compact_title">Kompakt modus</string>
+ <string name="settings_background_sync">Synkronisering i bakgrunnen</string>
+ <string name="pref_value_wifi_and_mobile">Synkroniser med WiFi og mobil data</string>
+ <string name="pref_value_wifi_only">Synkroniser kun med WiFi</string>
+ <string name="pref_value_theme_light">Lys</string>
+ <string name="unassigned_user">Ikke tildelt %1$s</string>
+ <string name="no_activities">Det er ingen aktiviteter på dette kortet. Du må være koblet til internett for å kunne laste inn og vise aktivitetene.</string>
+ <string name="share_board">Del tavle</string>
+ <string name="you_are_currently_offline">Du er frakoblet for øyeblikket</string>
+ <string name="you_have_to_be_connected_to_the_internet_in_order_to_add_an_account">Du må være koblet til internett for å kunne legge til en konto.</string>
+ <string name="owner">Eier</string>
+ <string name="attachment_delete_message">Dette vil slette dette vedlegget permanent.</string>
+ <string name="no_content">Ikke noe innhold enda</string>
+ <string name="last_background_sync">Siste bakgrunnssynkronisering:</string>
+ <string name="simple_off">ett kvarter</string>
+ <string name="minutes_15">15 minutter</string>
+ <string name="hour_1">1 time</string>
+ <string name="hours_6">6 timer</string>
+ <string name="action_card_move">Flytt kort</string>
+ <string name="action_card_move_title">Flytt %1$s</string>
+ <string name="please_add_an_account_first">Vennligst legg til en konto først</string>
+ <string name="title_is_mandatory">Tittel er obligatorisk</string>
+ <string name="provide_at_least_a_title_or_description">I det minste gi en tittel eller beskrivelse</string>
+ <string name="welcome_text">Velkommen til %1$s</string>
+ <string name="save_card_before_attachment">Kortet må lagres før det kan legges til vedlegg.</string>
+ <string name="maintenance_mode_explanation">Serveren %1$s er for øyeblikket i vedlikeholds modus. Vennligs kontakt din administrator eller prøv igjen senere.</string>
+ <string name="share_add_to_card">Legg til i kort</string>
+ <string name="share_success">%1$sble vellykket lagt til %2$s</string>
+ <string name="add_comment">Legg til kommentar</string>
+ <string name="card_edit_comments">Kommentarer</string>
+ <string name="no_comments_yet">Ingen kommentarer enda</string>
+ <string name="no_boards">Ingen tavle enda</string>
+ <string name="add_a_new_board_using_the_button">Legg til ny tavle ved å bruke + knappen</string>
+ <string name="choose_board">Velg tavle</string>
+ <string name="choose_list">Velg liste</string>
+ <string name="task_count">%1$s/%2$s</string>
+ <string name="open_in_browser">Åpne i nettleser</string>
+ <string name="updating_card">Oppdaterer kort...</string>
+
+ <string name="move_list_right">Flytt listen til høyre</string>
+ <string name="move_list_left">Flytt listen til venstre</string>
+
+ <string name="filter_no_filter">Alle</string>
+ <string name="filter_overdue">Utløpt</string>
+ <string name="filter_today">I dag</string>
+ <string name="filter_week">Neste 7 dager</string>
+ <string name="filter_month">Neste 30 dager</string>
+ <string name="filter_no_due">Ingen forfallsdato</string>
+ <string name="filter_by_tag">Filtrer på knagg</string>
+ <string name="filter_by_assigned_user">Filtrer på tildelt bruker</string>
+ <string name="filter_by_duedate">Filtrer på forfallsdato</string>
+
+ <string name="archived_cards">Arkiver tavle</string>
+ <string name="action_card_dearchive">Angre arkivering av kort</string>
+ <string name="action_archived_cards">Bla gjennom arkiverte kort</string>
+ <string name="attachment_already_exists">Vedlegget eksisterer allerede</string>
+ <string name="pick_custom_color">Velg tilpasset farge</string>
+ <string name="manage_tags">Håndtere etiketter</string>
+ <string name="add_tag">Legg til merkelapp</string>
+ <string name="tag_already_exists">%1$s eksisterer allerede</string>
+ <string name="tag_successfully_added">%1$s </string>
+ <string name="edit_tag">Rediger %1$s</string>
+ <string name="filter_tags_title">Merkelapper</string>
+ <string name="filter_user_title">Brukere</string>
+ <string name="filter_duedate_title">Forfallsdato</string>
+
+ <string name="action_board_dearchive">Angre arkivering av tavle</string>
+ <string name="archived_boards">Arkiverte tavler</string>
+
+ <string name="error">En feil oppstod</string>
+ <string name="synchronization_failed">Synkronisering mislyktes</string>
+ <string name="operation_not_yet_supported">Ikke støttet enda</string>
+ <string name="error_revoking_ac">Feil ved tilbakekalling av tilgang for%1$s</string>
+ <string name="error_create_label">Feil ved opprettelse av etikett %1$s</string>
+ <string name="maintenance_mode">Server i vedlikeholdsmodus</string>
+ <string name="server_misconfigured">Server feilkonfigurert</string>
+ <string name="server_error">Tjenerfeil</string>
+ <string name="shared_error">Deling av innhold fra 3dje-partsapper støttes ikke fullt ut. Prøv å laste den ned først og deretter dele den fra din lokale filbehandler eller ditt galleri.</string>
+ <string name="error_edit_activity_killed_by_android">Android avsluttet redigeringsmodus fordi det trengte flere systemressurser til andre apper.</string>
+
+ <string name="error_dialog_title">Å nei - Hva nå? 🙁</string>
+ <string name="error_dialog_tip_token_mismatch_retry">Prøv å tvinge avslutning av appen og start den på nytt. Det kan ha vært en feiltilkobling til Nextcloud-appen.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Hvis problemet vedvarer, prøv å tømme lageret for begge disse appene for å løse problemet: Nextcloud og Nextcloud Deck.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Oppgraderingen av databasen mislyktes. Vennligst rapporter problemet og tøm lageret for å bruke appen normalt.</string>
+ <string name="error_dialog_tip_clear_storage">Du kan tømme lageret ved å åpne App info og velge Lagring og buffer → Tøm lagring.</string>
+ <string name="error_dialog_tip_files_outdated">Det virker som Nextcloud appen er utdatert. Vennligst besøk Play Store eller F-Droid for å finne siste versjon.</string>
+ <string name="error_dialog_tip_files_force_stop">Det virker som noe er galt med Nextcloud appen. Vennligst prøv å tvinge avslutning av Nextcloud appen og Nextcloud Deck appen.</string>
+ <string name="error_dialog_tip_files_delete_storage">Hvis det ikke hjelper med tving avslutt, så kan du prøve å tømme lageret for begge appene.</string>
+ <string name="error_dialog_timeout_instance">Serveren svarer ikke innen en gitt tid. Vennligst påse at serveren er tilgjengelig.</string>
+ <string name="error_dialog_timeout_toggle">Sjekk nettverkstilkoblingen. Noen ganger hjelper det å skru av og på mobil data eller WI-FI tilkoblingen.</string>
+ <string name="error_dialog_check_server">Svaret fra serveren var feil. Vennligst påse at du har tilgang til Deck appen via nettgrensesnittet.</string>
+ <string name="error_dialog_check_server_logs">Det er et problem med Nextcloud oppsettet ditt. Vennligst ta en titt i loggfilene til serveren.</string>
+ <string name="error_dialog_check_maintenance">Vennligst sjekk at Nextcloud instansen ikke er i vedlikeholdsmodus for øyeblikket.</string>
+ <string name="error_dialog_insufficient_storage">Nextcloud instansen har ikke mer ledig lagringsplass. Vennligst slett noen filer for å synkronisere de lokale endringen til skyen din.</string>
+ <string name="error_dialog_we_need_info">Vi trenger følgende teknisk informasjon for å kunne hjelpe deg:</string>
+ <string name="error_dialog_redirect">Serveren din svarte med en HTTP 302-statuskode, noe som antyder at du ikke har installert Deck-appen på serveren din, eller at noe er feilkonfigurert. Dette kan være forårsaket av tilpassede overstyringer i en .htaccess-fil eller av Nextcloud-apper som OID Client.</string>
+ <string name="error_dialog_version_not_parsable">Vi kunne ikke avgjøre hvilken versjon av Deck-appen på serveren. Vennligst forsikre deg om at den er installert og aktivert.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Kontoen til Nextcloud-appen din er kanskje ikke lenger autorisert.</string>
+ <string name="error_dialog_user_not_found_in_database">Den nåværende brukeren samsvarer ikke med brukeren vi har i databasen vår. Hvis du bruker LDAP i Nextcloud-instansen, kan det hende at Nextcloud-appen din har lagret en gammel bruker-ID.</string>
+ <string name="error_dialog_capabilities_not_parsable">Vi kunne ikke hente funksjonene til serveren din. Forsikre deg om at serveren din kjører bra, og at andre klientapper har tilgang til Nextcloud.</string>
+ <string name="error_dialog_attachment_upload_failed">Et vedlegg kunne ikke lastes opp. Prøv å dele den på en annen måte og gi oss beskjed om denne feilen.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Deaktiver alle batterioptimaliseringer for Nextcloud og Deck-appen.</string>
+ <string name="error_action_open_deck_info">Åpne App info</string>
+ <string name="error_action_open_network">Nettverksinnstillinger</string>
+ <string name="error_action_server_logs">Serverlogger</string>
+ <string name="error_action_install">Installer</string>
+ <string name="error_action_report_issue">Rapporter</string>
+ <string name="info_box_maintenance_mode">Server i vedlikeholdsmodus</string>
+ <string name="info_box_version_not_supported">Server versjon %1$ser ikke støttet, vennligst oppgrader til %2$s</string>
+ <string name="share_link">Share link</string>
+ <string name="archive_cards">Arkiver kortene</string>
+
+ <string name="manage_accounts">Håndter kontoer</string>
+ <string name="manage_list">Håndtere liste</string>
+ <string name="simple_reply">Svar</string>
+ <string name="error_while_uploading_attachment">Feil ved opplasting av vedlegg: %1$s</string>
+ <string name="append_text_to_description">Legg til beskrivelsen</string>
+ <string name="add_text_as_comment">Legg til som kommentar</string>
+ <string name="progress_count">%1$d av %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d feil ved opplasting</item>
+ <item quantity="other">%1$d feil under opplasting</item>
+ </plurals>
+ <string name="simple_report">Rapporter</string>
+ <string name="error_action_open_battery_settings">Batteri innstillinger</string>
+ <string name="move_warning">Verken kommentarer eller vedlegg kan overføres når du flytter kortet til en annen tavle.</string>
+ <string name="clone_board">Klon tavle</string>
+ <string name="cloning_board">Kloner %1$s...</string>
+ <string name="successfully_cloned_board">Kloning av %1$s var vellykket</string>
+ <string name="attachment_does_not_yet_exist">Vedlegget finnes ikke i Deck enda</string>
+ <string name="card_does_not_yet_exist">Kortet finnes ikke i Deck enda</string>
+
+ <string name="widget_stack_title">Liste</string>
+ <string name="widget_stack_header_icon">Widget topptekstikon</string>
+ <string name="widget_stack_placeholder_icon">Widget plassholderikon</string>
+ <string name="select_stack">Velg liste</string>
+ <string name="project_type_deck_board">Deck tavle</string>
+ <string name="project_type_deck_card">Deck kort</string>
+ <string name="project_type_file">Fil</string>
+ <string name="projects_title">Prosjekter</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d ressurs</item>
+ <item quantity="other">%1$dressurser</item>
+ </plurals>
+ <string name="no_assigned_label">Ingen etikett tildelt</string>
+ <string name="single_card">Enkelt kort</string>
+ <string name="project_type_room">Talk rom</string>
+ <string name="simple_move">Flytt</string>
+ <string name="cannot_upload_files_without_permission">Kan ikke laste opp filer uten tillatelse</string>
+ </resources>
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index ccf551f00..d931f8b20 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Laatste account</string>
<string name="drawer_manage_accounts">Accounts beheren </string>
- <!-- Simple values -->
<string name="simple_boards">Bord</string>
<string name="simple_add">Toevoegen</string>
<string name="simple_save">Bewaren</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">uitgeschakeld</string>
<string name="simple_copied">Gekopieerd</string>
<string name="simple_archive">Archiveer</string>
+ <string name="simple_unassigned">Niet-toegewezen</string>
<string name="edit_board">Pas bord aan</string>
<string name="archive_board">Archiveer bord</string>
<string name="delete_board">Bord verwijderen</string>
<string name="delete_something">Verwijder %1$s</string>
- <!-- About -->
<string name="about">Over</string>
<string name="about_version_title">Versie</string>
<string name="about_version">Je gebruikt nu %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Credits</string>
<string name="about_contribution_tab_title">Bijdrage</string>
<string name="about_license_tab_title">Licentie</string>
-
- <string name="copied_to_clipboard">Gekopieerd naar het klembord</string>
-
<string name="seconds_ago">seconden geleden</string>
<string name="edit">Bewerken</string>
<string name="label_labels">Kies tags</string>
@@ -80,7 +76,7 @@
<string name="hint_assign_people">Toewijzen gebruikers</string>
<string name="hint_due_date_date">Vervaldatum</string>
<string name="card_not_found">Kaart niet gevonden</string>
- <string name="card_not_found_message">De kaart kon niet gevonden worden. Misschien is deze recent verwijderd.</string>
+ <string name="card_not_found_message">De kaart kon niet worden gevonden. Misschien is die recent verwijderd.</string>
<string name="add_account">Account toevoegen</string>
<string name="choose_account">Kies account</string>
@@ -91,7 +87,7 @@
<string name="delete_list">Stapel verwijderen</string>
<string name="label_menu">menu</string>
<string name="action_card_assign">Wijs kaart toe aan mij</string>
- <string name="action_card_unassign">Kaart van mij afstoten</string>
+ <string name="action_card_unassign">Kaart van mij terughalen</string>
<string name="action_card_archive">Archiveer kaart</string>
<string name="action_card_delete">Verwijderen kaart</string>
<string name="add_board">Toevoegen bord</string>
@@ -106,12 +102,13 @@
<string name="no_files_attached_to_this_card">Er zijn geen bestanden gekoppeld aan deze kaart.</string>
<string name="attachments">Bijlagen</string>
<string name="no_cards">Nog geen kaarten</string>
- <string name="no_account">Geen account geconfigureerd.</string>
- <string name="account_already_added">Account al toegevoegd.</string>
+ <string name="no_account">Geen account geconfigureerd</string>
+ <string name="account_already_added">Account al toegevoegd</string>
<string name="account_is_getting_imported">Account wordt geïmporteerd</string>
<string name="not_synced_yet">Nog niet gesynchroniseerd</string>
- <string name="no_lists_yet">Nog geen stapels</string>
+ <string name="no_lists_yet">Nog geen lijsten</string>
<string name="do_you_want_to_save_your_changes">Wil je de wijzigingen opslaan?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">Wil je alle kaarten van %1$s archiveren?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Dit zal deze %1$dkaart permanent van de lijst verwijderen.</item>
<item quantity="other">Dit zal alle %1$dkaarten definitief uit de stapel verwijderen.</item>
@@ -127,7 +124,8 @@
<string name="deck_outdated_please_update">Je deck versie is te oud (%1$s). Update deze android app om de deck te kunnen gebruiken.</string>
<string name="delete_board_message">Dit zal dit bord inclusief alle stapels en kaarten definitief verwijderen.</string>
<string name="settings_theme_title">Donker thema</string>
- <string name="settings_branding_title">Brandmerken</string>
+ <string name="settings_branding_title">Branding</string>
+ <string name="settings_compact_title">Compacte modus</string>
<string name="settings_background_sync">Achtergrond synchronisatie</string>
<string name="pref_value_wifi_and_mobile">Sync met Wi-Fi en mobiele data</string>
<string name="pref_value_wifi_only">Sync alleen met Wi-Fi</string>
@@ -152,10 +150,9 @@
<string name="provide_at_least_a_title_or_description">Geef ten minste een titel of beschrijving op</string>
<string name="welcome_text">Welkom bij %1$s</string>
<string name="save_card_before_attachment">De kaart moet worden bewaard voordat er bijlagen aan kunnen worden toegevoegd.</string>
- <string name="maintenance_mode_explanation">De server %1$s is momenteel in onderhoud modus. Neem a.u.b. contact op met de beheerder of probeer het later opnieuw</string>
+ <string name="maintenance_mode_explanation">De server %1$s is momenteel in onderhoud modus. Neem alstublieft contact op met de beheerder of probeer het later opnieuw</string>
<string name="share_add_to_card">Toevoegen aan kaart</string>
<string name="share_success">Succesvol %1$s toegevoegd aan %2$s</string>
- <string name="could_not_copy_to_clipboard">Kon niet kopiëren naar het klembord</string>
<string name="add_comment">Reactie toevoegen</string>
<string name="card_edit_comments">Reacties</string>
<string name="no_comments_yet">Nog geen reacties</string>
@@ -167,11 +164,9 @@
<string name="open_in_browser">Open in browser</string>
<string name="updating_card">Kaart wordt geüpdatet...</string>
- <!-- Move lists -->
<string name="move_list_right">Verplaats de stapel naar rechts</string>
<string name="move_list_left">Verplaats de stapel naar links</string>
- <!-- Filter -->
<string name="filter_no_filter">Alle</string>
<string name="filter_overdue">Vervallen</string>
<string name="filter_today">Vandaag</string>
@@ -182,7 +177,6 @@
<string name="filter_by_assigned_user">Filter op toegewezen gebruiker</string>
<string name="filter_by_duedate">Filter op vervaldatum</string>
- <!-- Archived cards -->
<string name="archived_cards">Gearchiveerde kaarten</string>
<string name="action_card_dearchive">Kaartarchivering ongedaan maken</string>
<string name="action_archived_cards">Blader door gearchiveerde kaarten</string>
@@ -197,11 +191,9 @@
<string name="filter_user_title">Gebruikers</string>
<string name="filter_duedate_title">Vervaldatum</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Archiveren van bord ongedaan maken</string>
<string name="archived_boards">Gearchiveerde borden</string>
- <!-- Errors -->
<string name="error">Er is een fout verschenen</string>
<string name="synchronization_failed">Synchronisatie mislukt</string>
<string name="operation_not_yet_supported">Nog niet ondersteund</string>
@@ -210,34 +202,76 @@
<string name="maintenance_mode">Server in onderhoudsmodus</string>
<string name="server_misconfigured">Server verkeerd geconfigureerd</string>
<string name="server_error">Serverfout</string>
- <string name="shared_error">Het delen van content vanaf 3e-partij apps wordt nog niet volledig ondersteund. Probeer jhet eerst te downloaden en deel dan vanaf je eigen bestandsbeheerder of galerij.</string>
+ <string name="shared_error">Het delen van content vanaf 3e-partij apps wordt nog niet volledig ondersteund. Probeer het eerst te downloaden en deel dan vanaf je eigen bestandsbeheerder of galerij.</string>
<string name="error_edit_activity_killed_by_android">Android beëindigde de bewerkingsmodus omdat het meer systeembronnen nodig had voor andere apps.</string>
<string name="error_dialog_title">Oh nee - wat nu? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Probeer de app te sluiten en opnieuw te starten. Er was misschien een onjuiste verbinding met de Nextcloud-app.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Als het probleem blijft aanhouden, probeer dan de gegevensopslag van beide apps te wissen: zowel Nextcloud als Nextcloud Deck.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Als het probleem blijft aanhouden, probeer dan de gegevensopslag van beide apps te wissen: zowel Nextcloud als Nextcloud Deck.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Het upgraden van de database is mislukt. Gelieve het probleem te melden en opslagruimte vrij te maken om de app naar behoren te kunnen gebruiken.</string>
<string name="error_dialog_tip_clear_storage">Je kan de opslag leegmaken door in Android de app info te openen en kies dan Opslag → Gegevens verwijderen.</string>
<string name="error_dialog_tip_files_outdated">Je Nextcloud app lijkt te zijn verouderd. Download alstublieft de nieuwste versie in de Google Play Store of in F-Droid.</string>
<string name="error_dialog_tip_files_force_stop">Er lijkt iets mis te zijn met je Nextcloud app. Probeer alstublieft beide apps geforceerd te sluiten, zowel de Nextcloud app als de Nextcloud Deck app.</string>
<string name="error_dialog_tip_files_delete_storage">Als geforceerd afsluiten niet helpt, probeer dan van beide apps de gegevensopslag te wissen.</string>
<string name="error_dialog_timeout_instance">Er was binnen de gestelde tijd geen reactie ontvangen van je server. Controleer alstublieft of je Nextcloud-instantie wel goed loopt.</string>
<string name="error_dialog_timeout_toggle">Controleer je netwerk verbinding. Het kan ook helpen de dataverbinding of WIFI uit en weer aan te zetten.</string>
- <string name="error_dialog_check_server">Je server gaf een onjuiste reactie. Controleer alstublieft of de Deck app in de web-interface wel goed werkt.</string>
+ <string name="error_dialog_check_server">Je server gaf een onjuiste reactie. Controleer alstublieft of je toegang krijgt tot de Deck app via de web-interface.</string>
<string name="error_dialog_check_server_logs">Er is iets mis met je Nextcloud instellingen. Controleer alstublieft de server log bestanden.</string>
<string name="error_dialog_check_maintenance">Controleer alstublieft of je Nextcloud instantie momenteel niet in onderhouds-modus draait.</string>
<string name="error_dialog_insufficient_storage">Je Nextcloud instantie heeft geen vrije ruimte meer. Verwijder alstublieft enkele bestanden om je lokale gegevens te kunnen synchroniseren.</string>
<string name="error_dialog_we_need_info">We hebben de volgende technische informatie nodig om je te kunnen helpen: </string>
<string name="error_dialog_redirect">Je server heeft gereageerd met een HTTP 302-statuscode, wat inhoudt dat je de Deck-app niet op je server hebt geïnstalleerd of dat er iets verkeerd is geconfigureerd. Dit kan worden veroorzaakt door maatwerk aanpassingen in een .htaccess-bestand of door Nextcloud-apps zoals de OID Client.</string>
<string name="error_dialog_version_not_parsable">We konden de versie van de Deck-app aan de serverzijde niet bepalen. Zorg ervoor dat de app is geïnstalleerd en ingeschakeld.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Het account van uw Nextcloud-app is mogelijk niet langer geautoriseerd.</string>
+ <string name="error_dialog_user_not_found_in_database">De huidige gebruiker komt niet overeen met de gebruiker die we in onze database hebben. Als u LDAP gebruikt op uw Nextcloud-instantie, heeft uw Nextcloud-app mogelijk een oude gebruikers-ID opgeslagen.</string>
<string name="error_dialog_capabilities_not_parsable">We kunnen de mogelijkheden van je server niet vaststellen. Zorg ervoor dat je server goed werkt en dat andere client-apps toegang hebben tot Nextcloud.</string>
+ <string name="error_dialog_attachment_upload_failed">Een bijlage kon niet worden opgeladen. Gelieve het op een andere manier te delen en ons in te lichten over deze fout.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Gelieve de batterijoptimalisatie uit te schakelen voor de Nextcloud en Deck applicatie.</string>
<string name="error_action_open_deck_info">Open App informatie</string>
<string name="error_action_open_network">Netwerk instellingen</string>
<string name="error_action_server_logs">Server logbestanden</string>
<string name="error_action_install">Installeren</string>
+ <string name="error_action_report_issue">Rapporteer</string>
<string name="info_box_maintenance_mode">Server in onderhoudsmodus</string>
<string name="info_box_version_not_supported">Server versie %1$s is niet ondersteund, werk bij naar %2$s</string>
<string name="share_link">Delen link</string>
+ <string name="archive_cards">Archiveer kaarten</string>
+
<string name="manage_accounts">Accounts beheren </string>
+ <string name="manage_list">Beheer lijst</string>
<string name="simple_reply">Antwoord</string>
+ <string name="error_while_uploading_attachment">Fout tijdens het opladen van bijlage: %1$s</string>
+ <string name="append_text_to_description">Toevoegen aan beschrijving</string>
+ <string name="add_text_as_comment">Toevoegen als opmerking</string>
+ <string name="progress_count">%1$d op %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d fouten tijdens het uploaden</item>
+ <item quantity="other">%1$d fouten tijdens het uploaden</item>
+ </plurals>
<string name="simple_report">Rapporteer</string>
-</resources>
+ <string name="error_action_open_battery_settings">Batterij instellingen</string>
+ <string name="move_warning">Bij het verplaatsen van de kaart naar een ander bord kunnen geen opmerkingen of bijlagen worden overgebracht.</string>
+ <string name="clone_board">Dupliceer bord</string>
+ <string name="cloning_board">Klonen %1$s ...</string>
+ <string name="successfully_cloned_board">Succesvol gekloond %1$s</string>
+ <string name="attachment_does_not_yet_exist">Bijlage bestaat nog niet in Deck</string>
+ <string name="card_does_not_yet_exist">Kaart bestaat nog niet in Deck</string>
+
+ <string name="widget_stack_title">Lijst</string>
+ <string name="widget_stack_header_icon">Widget koptekst icoon</string>
+ <string name="widget_stack_placeholder_icon">Widget placeholder icoon</string>
+ <string name="select_stack">Lijst selecteren</string>
+ <string name="project_type_deck_board">Deck board</string>
+ <string name="project_type_deck_card">Deck kaart</string>
+ <string name="project_type_file">Bestand</string>
+ <string name="projects_title">Projecten</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d middelen</item>
+ <item quantity="other">%1$d middelen</item>
+ </plurals>
+ <string name="no_assigned_label">Geen toegewezen label</string>
+ <string name="single_card">Enkele kaart</string>
+ <string name="project_type_room">Praatruimte</string>
+ <string name="simple_move">Verplaatsen</string>
+ <string name="cannot_upload_files_without_permission">Kan geen bestanden uploaden zonder toestemming</string>
+ </resources>
diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml
index 4aff11579..626b4dacc 100644
--- a/app/src/main/res/values-pl/strings.xml
+++ b/app/src/main/res/values-pl/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Ostatnie konto</string>
<string name="drawer_manage_accounts">Zarządzaj kontami</string>
- <!-- Simple values -->
<string name="simple_boards">Tablice</string>
<string name="simple_add">Dodaj</string>
<string name="simple_save">Zapisz</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">wyłączony</string>
<string name="simple_copied">Skopiowano</string>
<string name="simple_archive">Archiwizuj</string>
+ <string name="simple_unassigned">Nieprzypisane</string>
<string name="edit_board">Edytuj tablicę</string>
<string name="archive_board">Zarchiwizuj tablicę</string>
<string name="delete_board">Usuń tablicę</string>
<string name="delete_something">Usuń %1$s</string>
- <!-- About -->
<string name="about">O aplikacji</string>
<string name="about_version_title">Wersja</string>
<string name="about_version">Aktualnie używasz %1$s</string>
@@ -57,11 +56,11 @@
<string name="about_source_title">Kod źródłowy</string>
<string name="about_source">Projekt jest hostowany na GitHub: %1$s</string>
<string name="about_issues_title">Problemy</string>
- <string name="about_issues">Możesz zgłaszać błędy, propozycje ulepszeń i prośby o funkcje na GitHub: %1$s</string>
+ <string name="about_issues">Możesz zgłaszać błędy, propozycje ulepszeń i prośby o funkcje w liście problemów na GitHub: %1$s</string>
<string name="about_translate_title">Tłumaczyć</string>
<string name="about_translate">Dołącz do zespołu Nextcloud na Transifex i pomóż nam przetłumaczyć tę aplikację: %1$s</string>
<string name="about_app_license_title">Licencja</string>
- <string name="about_app_license">Ta aplikacja jest licencjonowana na podstawie GNU GENERAL PUBLIC LICENSE v3+.</string>
+ <string name="about_app_license">Aplikacja jest na licencji GNU GENERAL PUBLIC LICENSE v3+.</string>
<string name="about_app_license_button">Zobacz licencję</string>
<string name="about_icons_disclaimer_title">Ikony</string>
<string name="about_icons_disclaimer_app_icon">Oryginalna ikona. Zobacz %1$s.</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Autorzy</string>
<string name="about_contribution_tab_title">Wkład</string>
<string name="about_license_tab_title">Licencja</string>
-
- <string name="copied_to_clipboard">Skopiowano do schowka</string>
-
<string name="seconds_ago">przed chwilą</string>
<string name="edit">Edytuj</string>
<string name="label_labels">Wybierz etykietę</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">Brak list</string>
<string name="do_you_want_to_save_your_changes">Czy chcesz zachować zmiany?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Czy chcesz zarchiwizować wszystkie karty %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Czy chcesz zarchiwizować wszystkie odfiltrowane karty z %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Spowoduje to trwałe usunięcie %1$d karty z tej listy.</item>
<item quantity="few">Spowoduje to trwałe usunięcie wszystkich %1$d kart z tej listy.</item>
@@ -133,6 +130,7 @@
<string name="delete_board_message">Spowoduje to trwałe usunięcie tej tablicy, w tym wszystkich list i kart.</string>
<string name="settings_theme_title">Ciemny motyw</string>
<string name="settings_branding_title">Motyw serwera</string>
+ <string name="settings_compact_title">Tryb kompaktowy</string>
<string name="settings_background_sync">Synchronizacja w tle</string>
<string name="pref_value_wifi_and_mobile">Synchronizacja w sieci Wi-Fi i komórkowej transmisji danych</string>
<string name="pref_value_wifi_only">Synchronizuj tylko przez Wi-Fi</string>
@@ -160,7 +158,6 @@
<string name="maintenance_mode_explanation">Serwer %1$s jest obecnie w trybie konserwacji. Skontaktuj się z administratorem lub spróbuj ponownie później.</string>
<string name="share_add_to_card">Dodaj do karty</string>
<string name="share_success">Pomyślnie dodano %1$s do %2$s</string>
- <string name="could_not_copy_to_clipboard">Nie można skopiować do schowka</string>
<string name="add_comment">Dodaj komentarz</string>
<string name="card_edit_comments">Komentarze</string>
<string name="no_comments_yet">Brak komentarzy</string>
@@ -172,22 +169,19 @@
<string name="open_in_browser">Otwórz w przeglądarce</string>
<string name="updating_card">Aktualizowanie karty…</string>
- <!-- Move lists -->
<string name="move_list_right">Przesuń listę w prawo</string>
<string name="move_list_left">Przesuń listę w lewo</string>
- <!-- Filter -->
<string name="filter_no_filter">Wszystko</string>
<string name="filter_overdue">Zaległe</string>
<string name="filter_today">Dzisiaj</string>
<string name="filter_week">Następne 7 dni</string>
<string name="filter_month">Następne 30 dni</string>
<string name="filter_no_due">Brak daty ważności</string>
- <string name="filter_by_tag">Filtruj po tagu</string>
+ <string name="filter_by_tag">Filtruj według etykiety</string>
<string name="filter_by_assigned_user">Filtruj według przypisanego użytkownika</string>
<string name="filter_by_duedate">Filtruj według terminu</string>
- <!-- Archived cards -->
<string name="archived_cards">Zarchiwizowane karty</string>
<string name="action_card_dearchive">Cofnij archiwizację karty</string>
<string name="action_archived_cards">Przeglądaj zarchiwizowane karty</string>
@@ -202,11 +196,9 @@
<string name="filter_user_title">Użytkownicy</string>
<string name="filter_duedate_title">Data realizacji</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Cofnij archiwizację tablicy</string>
<string name="archived_boards">Zarchiwizowane tablice</string>
- <!-- Errors -->
<string name="error">Wystąpił błąd</string>
<string name="synchronization_failed">Synchronizacja nie powiodła się</string>
<string name="operation_not_yet_supported">Jeszcze nie obsługiwane</string>
@@ -220,7 +212,8 @@
<string name="error_dialog_title">O nie - co teraz? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Spróbuj wymusić zamknięcie aplikacji i uruchomić ją ponownie. Być może połączenie z aplikacją Nextcloud było nieprawidłowe.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Jeśli problem będzie się powtarzać, spróbuj wyczyścić pamięć obu aplikacji: Nextcloud i Nextcloud Deck, aby rozwiązać ten problem.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Jeśli problem będzie się powtarzać, spróbuj wyczyścić pamięć obu aplikacji: Nextcloud i Nextcloud Deck, aby rozwiązać ten problem.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Aktualizacja bazy danych nie powiodła się. Zgłoś problem oraz wyczyść pamięć, aby ponownie korzystać z aplikacji.</string>
<string name="error_dialog_tip_clear_storage">Możesz wyczyścić pamięć, otwierając informacje o aplikacji i wybierając Pamięć → Wyczyść pamięć podręczną.</string>
<string name="error_dialog_tip_files_outdated">Twoja aplikacja Nextcloud wydaje się, że jest nieaktualna. Odwiedź Sklep Play lub F-Droid, aby pobrać najnowszą wersję.</string>
<string name="error_dialog_tip_files_force_stop">Wydaje się, że coś jest nie tak z Twoją aplikacją Nextcloud. Spróbuj wymusić zatrzymanie aplikacji Nextcloud i Nextcloud Deck.</string>
@@ -234,16 +227,21 @@
<string name="error_dialog_we_need_info">Potrzebujemy następujących informacji technicznych, aby Ci pomóc:</string>
<string name="error_dialog_redirect">Twój serwer odpowiedział kodem stanu HTTP 302, co oznacza, że nie masz zainstalowanej aplikacji Deck na serwerze lub coś jest źle skonfigurowane. Może to być spowodowane niestandardowymi przekierowaniami pliku .htaccess lub aplikacjami Nextcloud, takimi jak OID Client.</string>
<string name="error_dialog_version_not_parsable">Nie udało się ustalić wersji aplikacji Deck po stronie serwera. Upewnij się, że jest zainstalowany i włączony.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Konto Twojej aplikacji Nextcloud może nie być już autoryzowane.</string>
+ <string name="error_dialog_user_not_found_in_database">Bieżący użytkownik nie jest zgodny z użytkownikiem, którego mamy w naszej bazie danych. Jeśli używasz protokołu LDAP w swojej instancji Nextcloud, to Twoja aplikacja Nextcloud może przechowywać stary identyfikator użytkownika.</string>
<string name="error_dialog_capabilities_not_parsable">Nie udało się sprawdzić możliwości Twojego serwera. Upewnij się, że serwer działa poprawnie oraz czy inne aplikacje klienckie mają dostęp do Nextcloud.</string>
<string name="error_dialog_attachment_upload_failed">Nie można wysłać załącznika. Spróbuj udostępnić go w inny sposób oraz powiadom nas o tym błędzie.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Wyłącz wszystkie optymalizacje baterii dla aplikacji Nextcloud i aplikacji Deck.</string>
<string name="error_action_open_deck_info">Otwórz informacje o aplikacji</string>
<string name="error_action_open_network">Ustawienia sieci</string>
<string name="error_action_server_logs">Logi serwera</string>
<string name="error_action_install">Zainstaluj</string>
+ <string name="error_action_report_issue">Raport</string>
<string name="info_box_maintenance_mode">Serwer w trybie konserwacji</string>
<string name="info_box_version_not_supported">Wersja serwera %1$s nie jest wspierana, zaktualizuj do wersji %2$s</string>
<string name="share_link">Udostępnij link</string>
<string name="archive_cards">Zarchiwizuj karty</string>
+
<string name="manage_accounts">Zarządzaj kontami</string>
<string name="manage_list">Zarządzaj listą</string>
<string name="simple_reply">Odpowiedz</string>
@@ -252,10 +250,58 @@
<string name="add_text_as_comment">Dodaj jako komentarz</string>
<string name="progress_count">%1$d z %2$d</string>
<plurals name="progress_error_count">
- <item quantity="one">%1$d błąd podczas wysyłania.</item>
- <item quantity="few">%1$d błędy podczas wysyłania.</item>
- <item quantity="many">%1$d błędów podczas wysyłania.</item>
- <item quantity="other">%1$d błędów podczas wysyłania.</item>
+ <item quantity="one">%1$d błąd podczas wysyłania</item>
+ <item quantity="few">%1$d błędy podczas wysyłania</item>
+ <item quantity="many">%1$d błędów podczas wysyłania</item>
+ <item quantity="other">%1$d błędów podczas wysyłania</item>
</plurals>
<string name="simple_report">Raport</string>
+ <string name="error_action_open_battery_settings">Ustawienia baterii</string>
+ <string name="move_warning">Nie można przenosić komentarzy, ani załączników karty na inną tablicę.</string>
+ <string name="clone_board">Klonuj tablicę</string>
+ <string name="cloning_board">Klonowanie %1$s…</string>
+ <string name="successfully_cloned_board">Pomyślnie sklonowano %1$s</string>
+ <string name="attachment_does_not_yet_exist">Załącznik nie istnieje jeszcze w Deck</string>
+ <string name="card_does_not_yet_exist">Karta nie istnieje jeszcze w Deck</string>
+
+ <string name="widget_stack_title">Lista</string>
+ <string name="widget_stack_header_icon">Ikona nagłówka widżetu</string>
+ <string name="widget_stack_placeholder_icon">Ikona zastępcza widżetu</string>
+ <string name="select_stack">Wybierz listę</string>
+ <string name="project_type_deck_board">Tablica Deck</string>
+ <string name="project_type_deck_card">Karta Deck</string>
+ <string name="project_type_file">Plik</string>
+ <string name="projects_title">Projektowanie</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d zasób</item>
+ <item quantity="few">%1$d zasoby</item>
+ <item quantity="many">%1$d zasobów</item>
+ <item quantity="other">%1$d zasobów</item>
+ </plurals>
+ <string name="no_assigned_label">Brak przypisanej etykiety</string>
+ <string name="single_card">Pojedyncza karta</string>
+ <string name="project_type_room">Pokój rozmów</string>
+ <string name="simple_move">Przenieś</string>
+ <string name="cannot_upload_files_without_permission">Nie można wysyłać plików bez uprawnienia</string>
+ <string name="clone_cards">Klonuj karty</string>
+ <string name="simple_clone">Klonuj</string>
+ <string name="user_avatar">Awatar użytkownika</string>
+ <string name="simple_unassign">Cofnij przypisanie</string>
+ <string name="simple_contact">Kontakt</string>
+ <string name="simple_file">Plik</string>
+ <string name="simple_camera">Aparat</string>
+ <string name="min_api_21">Funkcja ta wymaga co najmniej Androida 5</string>
+ <string name="take_photo">Zrobić zdjęcie</string>
+ <string name="take_photo_switch_camera">Przełącz aparat</string>
+ <string name="take_photo_toggle_torch">Włącz latarkę</string>
+ <string name="show_all_contacts">Pokaż wszystkie kontakty</string>
+ <string name="show_all_files">Pokaż wszystkie pliki</string>
+ <string name="recent">Ostatnie</string>
+ <string name="upload_a_new_attachment">Prześlij nowy załącznik</string>
+ <string name="contacts">Kontakty</string>
+ <string name="downloads">Pobrane</string>
+ <string name="files">Pliki</string>
+ <string name="gallery">Galeria</string>
+ <string name="simple_attach">dołącz</string>
+ <string name="add_stack_widget">Dodaj widżet listy</string>
</resources>
diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml
index ed575093f..a20656dd1 100644
--- a/app/src/main/res/values-pt-rBR/strings.xml
+++ b/app/src/main/res/values-pt-rBR/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Última conta</string>
<string name="drawer_manage_accounts">Gerenciar contas</string>
- <!-- Simple values -->
<string name="simple_boards">Painéis</string>
<string name="simple_add">Adicionar</string>
<string name="simple_save">Salvar</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">desativado</string>
<string name="simple_copied">Copiado</string>
<string name="simple_archive">Arquivados</string>
+ <string name="simple_unassigned">Não atribuído</string>
<string name="edit_board">Editar painel</string>
<string name="archive_board">Arquivar painel</string>
<string name="delete_board">Excluir painel</string>
<string name="delete_something">Excluir %1$s</string>
- <!-- About -->
<string name="about">Sobre</string>
<string name="about_version_title">Versão</string>
<string name="about_version">Atualmente você está usando %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Créditos</string>
<string name="about_contribution_tab_title">Contribuição</string>
<string name="about_license_tab_title">Licença</string>
-
- <string name="copied_to_clipboard">Copiado para a área de transferência</string>
-
<string name="seconds_ago">segundos atrás</string>
<string name="edit">Editar</string>
<string name="label_labels">Selecionar etiquetas</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">Ainda sem listas</string>
<string name="do_you_want_to_save_your_changes">Deseja salvar suas alterações?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Deseja arquivar todos os cartões de %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Você deseja arquivar todos os cartões filtrados de %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Isto excluirá permanentemente %1$d cartão desta lista.</item>
<item quantity="other">Isto excluirá permanentemente todos os %1$d cartões desta lista.</item>
@@ -129,6 +126,7 @@
<string name="delete_board_message">Isso excluirá permanentemente este painel incluindo listas e cartões.</string>
<string name="settings_theme_title">Tema escuro</string>
<string name="settings_branding_title">Marcação</string>
+ <string name="settings_compact_title">Modo compacto</string>
<string name="settings_background_sync">Sincronização em segundo plano</string>
<string name="pref_value_wifi_and_mobile">Sincronizar com Wi-Fi e dados móveis</string>
<string name="pref_value_wifi_only">Sincronizar apenas com Wi-Fi</string>
@@ -156,7 +154,6 @@
<string name="maintenance_mode_explanation">O servidor %1$s está no modo de manutenção. Entre em contato com o administrador ou tente novamente mais tarde.</string>
<string name="share_add_to_card">Adicionar ao cartão</string>
<string name="share_success">%1$s adicionado com sucesso à %2$s</string>
- <string name="could_not_copy_to_clipboard">Não foi possível copiar para a área de transferência</string>
<string name="add_comment">Adicionar comentário</string>
<string name="card_edit_comments">Comentários</string>
<string name="no_comments_yet">Nenhum comentário ainda</string>
@@ -168,11 +165,9 @@
<string name="open_in_browser">Abrir no navegador</string>
<string name="updating_card">Atualizando cartão...</string>
- <!-- Move lists -->
<string name="move_list_right">Mover lista para a direita</string>
<string name="move_list_left">Mover lista para a esquerda</string>
- <!-- Filter -->
<string name="filter_no_filter">Tudo</string>
<string name="filter_overdue">Vencidos</string>
<string name="filter_today">Hoje</string>
@@ -183,7 +178,6 @@
<string name="filter_by_assigned_user">Filtrar por usuário atribuído</string>
<string name="filter_by_duedate">Filtrar por data de vencimento</string>
- <!-- Archived cards -->
<string name="archived_cards">Cartões arquivados</string>
<string name="action_card_dearchive">Desfazer arquivamento do cartão</string>
<string name="action_archived_cards">Navegar pelos cartões arquivados</string>
@@ -198,11 +192,9 @@
<string name="filter_user_title">Usuários</string>
<string name="filter_duedate_title">Data de vencimento</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Desfazer arquivamento do painel</string>
<string name="archived_boards">Painéis arquivados</string>
- <!-- Errors -->
<string name="error">Ocorreu um erro</string>
<string name="synchronization_failed">A sincronização falhou</string>
<string name="operation_not_yet_supported">Ainda não suportado</string>
@@ -216,7 +208,8 @@
<string name="error_dialog_title">Oh não - E agora? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Tente forçar o fechamento do aplicativo e reinicie-o novamente. Pode ter havido uma conexão incorreta com o aplicativo Nextcloud.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Se o problema persistir, tente limpar o armazenamento dos dois aplicativos: Nextcloud e Nextcloud Deck para resolver esse problema.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Se o problema persistir, tente limpar o armazenamento dos dois aplicativos: Nextcloud e Nextcloud Deck para resolver esse problema.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">A atualização do banco de dados falhou. Relate o problema e limpe o armazenamento para usar o aplicativo normalmente.</string>
<string name="error_dialog_tip_clear_storage">Você pode limpar o armazenamento abrindo as informações do aplicativo e selecionando Armazenamento → Limpar Armazenamento</string>
<string name="error_dialog_tip_files_outdated">Seu aplicativo Nextcloud parece estar desatualizado. Visite a Play Store ou o F-Droid para obter a versão mais recente.</string>
<string name="error_dialog_tip_files_force_stop">Parece que algo está errado com seu aplicativo Nextcloud. Tente forçar a parada de ambos, o aplicativo Nextcloud e o Nextcloud Deck.</string>
@@ -230,16 +223,21 @@
<string name="error_dialog_we_need_info">Precisamos das seguintes informações técnicas para ajudá-lo:</string>
<string name="error_dialog_redirect">Seu servidor respondeu com um código de status HTTP 302, o que implica que você não instalou o aplicativo Deck no servidor ou que algo está configurado incorretamente. Isso pode ser causado por alterações no arquivo .htaccess ou por aplicativos Nextcloud como o Client OID.</string>
<string name="error_dialog_version_not_parsable">Não conseguimos determinar a versão do servidor do aplicativo Deck. Confirme se ele está instalado e ativo.</string>
+ <string name="error_dialog_account_might_not_be_authorized">A conta do seu aplicativo Nextcloud pode não estar mais autorizada.</string>
+ <string name="error_dialog_user_not_found_in_database">O usuário atual não corresponde ao usuário que temos em nosso banco de dados. Se você estiver usando LDAP na sua instância do Nextcloud, seu aplicativo Nextcloud pode ter armazenado um ID do usuário antigo.</string>
<string name="error_dialog_capabilities_not_parsable">Não foi possível buscar os recursos do seu servidor. Verifique se o servidor está funcionando bem e se outros aplicativos clientes podem acessar o Nextcloud.</string>
<string name="error_dialog_attachment_upload_failed">Não foi possível carregar um anexo. Tente compartilhá-lo de outra maneira e informe-nos sobre esse bug.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Desative todas as otimizações de bateria para o Nextcloud e o aplicativo Deck.</string>
<string name="error_action_open_deck_info">Abra as informações do Aplicativo</string>
<string name="error_action_open_network">Configurações da rede</string>
<string name="error_action_server_logs">Logs do servidor</string>
<string name="error_action_install">Instalar</string>
+ <string name="error_action_report_issue">Reportar</string>
<string name="info_box_maintenance_mode">Servidor em modo de manutenção</string>
<string name="info_box_version_not_supported">A versão %1$s do servidor não é suportada, atualize para %2$s</string>
<string name="share_link">Link de compartilhamento</string>
<string name="archive_cards">Cartões arquivados</string>
+
<string name="manage_accounts">Gerenciar contas</string>
<string name="manage_list">Gerenciar lista</string>
<string name="simple_reply">Responder</string>
@@ -248,8 +246,44 @@
<string name="add_text_as_comment">Adicionar como comentário</string>
<string name="progress_count">%1$d de %2$d</string>
<plurals name="progress_error_count">
- <item quantity="one">%1$d erros durante o envio.</item>
- <item quantity="other">%1$d erros durante o envio.</item>
+ <item quantity="one">%1$d erro ao fazer o upload</item>
+ <item quantity="other">%1$d erros ao fazer o upload</item>
</plurals>
<string name="simple_report">Reportar</string>
-</resources>
+ <string name="error_action_open_battery_settings">Configurações da bateria</string>
+ <string name="move_warning">Comentários ou anexos não podem ser transferidos ao mover o cartão para outra placa.</string>
+ <string name="clone_board">Clonar painel</string>
+ <string name="cloning_board">Clonando %1$s…</string>
+ <string name="successfully_cloned_board">%1$s clonado com sucesso</string>
+ <string name="attachment_does_not_yet_exist">O anexo ainda não existe no Deck</string>
+ <string name="card_does_not_yet_exist">O cartão ainda não existe no Deck</string>
+
+ <string name="widget_stack_title">Lista</string>
+ <string name="widget_stack_header_icon">Ícone do cabeçalho do widget</string>
+ <string name="widget_stack_placeholder_icon">Ícone de marcador de posição do widget</string>
+ <string name="select_stack">Selecionar lista</string>
+ <string name="project_type_deck_board">Painel de deck</string>
+ <string name="project_type_deck_card">Cartão de deck</string>
+ <string name="project_type_file">Arquivo</string>
+ <string name="projects_title">Projetos</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d recurso</item>
+ <item quantity="other">%1$d recursos</item>
+ </plurals>
+ <string name="no_assigned_label">Nenhum rótulo atribuído</string>
+ <string name="single_card">Cartão único</string>
+ <string name="project_type_room">Sala de conversação</string>
+ <string name="simple_move">Mover</string>
+ <string name="cannot_upload_files_without_permission">Não é possível enviar arquivos sem permissão</string>
+ <string name="clone_cards">Clonar cartões</string>
+ <string name="simple_clone">Clonar</string>
+ <string name="user_avatar">Avatar do usuário</string>
+ <string name="simple_unassign">Desatribuir</string>
+ <string name="simple_contact">Contato</string>
+ <string name="simple_file">Arquivo</string>
+ <string name="simple_camera">Câmera</string>
+ <string name="min_api_21">Este recurso requer pelo menos Android 5</string>
+ <string name="take_photo">Tire uma foto</string>
+ <string name="take_photo_switch_camera">Trocar câmera</string>
+ <string name="take_photo_toggle_torch">Alternar tocha</string>
+ </resources>
diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml
index 42c1514f9..bd5a1dd6b 100644
--- a/app/src/main/res/values-ru/strings.xml
+++ b/app/src/main/res/values-ru/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Последняя учётная запись</string>
<string name="drawer_manage_accounts">Управление аккаунтами</string>
- <!-- Simple values -->
<string name="simple_boards">Доски</string>
<string name="simple_add">Добавить</string>
<string name="simple_save">Сохранить</string>
@@ -24,7 +23,7 @@
<string name="simple_switch">Переключить</string>
<string name="simple_filter">Фильтр</string>
<string name="simple_overdue">Просроченные</string>
- <string name="simple_clear">Отчистить</string>
+ <string name="simple_clear">Очистить</string>
<string name="simple_discard">Отменить</string>
<string name="simple_update">Обновить</string>
<string name="simple_delete">Удалить</string>
@@ -34,17 +33,17 @@
<string name="simple_manage">Управлять</string>
<string name="simple_share">общий ресурс</string>
<string name="simple_select">Выбрать</string>
- <string name="simple_comment">Коментарий</string>
+ <string name="simple_comment">Комментарий</string>
<string name="simple_disabled">отключено</string>
<string name="simple_copied">Скопировано</string>
<string name="simple_archive">Переместить в архив</string>
+ <string name="simple_unassigned">не назначен</string>
<string name="edit_board">Редактировать доску</string>
<string name="archive_board">Переместить доску в архив</string>
<string name="delete_board">Удалить доску</string>
<string name="delete_something">Удалить %1$s</string>
- <!-- About -->
<string name="about">О программе</string>
<string name="about_version_title">Версия</string>
<string name="about_version">Вы используете %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Сообщество</string>
<string name="about_contribution_tab_title">Участие</string>
<string name="about_license_tab_title">Лицензия</string>
-
- <string name="copied_to_clipboard">Скопировано в буфер</string>
-
<string name="seconds_ago">менее минуты</string>
<string name="edit">Править</string>
<string name="label_labels">Выбор меток</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">Нет ни одного списка</string>
<string name="do_you_want_to_save_your_changes">Сохранить изменения?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Вы хотите архивировать все карточки из %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Переместить в архив все отобранные в списке «%1$s» карточки?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Это действие безвозвратно удалит %1$d карточку этого списка.</item>
<item quantity="few">Это действие безвозвратно удалит %1$d карточки этого списка.</item>
@@ -133,6 +130,7 @@
<string name="delete_board_message">Это действие приведёт к удалению этой рабочей доски, всех списков и карточек.</string>
<string name="settings_theme_title">Тёмное оформление</string>
<string name="settings_branding_title">Брендирование</string>
+ <string name="settings_compact_title">компактный режим</string>
<string name="settings_background_sync">Синхр. в фоновом режиме</string>
<string name="pref_value_wifi_and_mobile">Синхр. по Wi-Fi и мобильной связи</string>
<string name="pref_value_wifi_only">Синхр. только по Wi-Fi</string>
@@ -147,9 +145,9 @@
<string name="no_content">Содержимого ещё нет</string>
<string name="last_background_sync">Последняя фоновая синхронизация:</string>
<string name="simple_off">Отключены</string>
- <string name="minutes_15">15 Минут</string>
- <string name="hour_1">1 Час</string>
- <string name="hours_6">6 Часов</string>
+ <string name="minutes_15">15 минут</string>
+ <string name="hour_1">1 час</string>
+ <string name="hours_6">6 часов</string>
<string name="action_card_move">Переместить карточку</string>
<string name="action_card_move_title">Перемещение %1$s</string>
<string name="please_add_an_account_first">Начните с добавления учётной записи</string>
@@ -160,7 +158,6 @@
<string name="maintenance_mode_explanation">Сервер %1$s в режиме обслуживания. Свяжитесь с вашим администратором или повторите попытку позже.</string>
<string name="share_add_to_card">Добавить в карточку</string>
<string name="share_success">%1$s добавлено в %2$s</string>
- <string name="could_not_copy_to_clipboard">Не удалось скопировать в буфер обмена.</string>
<string name="add_comment">Добавить комментарий</string>
<string name="card_edit_comments">Комментарии</string>
<string name="no_comments_yet">Пока никто не прокомментировал</string>
@@ -172,11 +169,9 @@
<string name="open_in_browser">Открыть в браузере</string>
<string name="updating_card">Обновление карточки…</string>
- <!-- Move lists -->
<string name="move_list_right">Переместить вправо</string>
<string name="move_list_left">Переместить влево</string>
- <!-- Filter -->
<string name="filter_no_filter">Все</string>
<string name="filter_overdue">Просроченные</string>
<string name="filter_today">Сегодня</string>
@@ -187,7 +182,6 @@
<string name="filter_by_assigned_user">Отфильтровать по назначению</string>
<string name="filter_by_duedate">Отфильтровать по сроку исполнения</string>
- <!-- Archived cards -->
<string name="archived_cards">Карточки в архиве</string>
<string name="action_card_dearchive">Отменить архивацию карточки</string>
<string name="action_archived_cards">Показать архив карточек</string>
@@ -202,11 +196,9 @@
<string name="filter_user_title">Пользователи</string>
<string name="filter_duedate_title">Дата исполнения</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Отменить архивацию доски</string>
<string name="archived_boards">Архив досок</string>
- <!-- Errors -->
<string name="error">Произошла ошибка</string>
<string name="synchronization_failed">Сбой синхронизации</string>
<string name="operation_not_yet_supported">Ещё не поддерживается</string>
@@ -220,7 +212,9 @@
<string name="error_dialog_title">О нет, что теперь? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Попробуйте принудительно закрыть приложение и запустить его снова. Это могло быть связано с некорректным подключением к приложению Nextcloud.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Если проблема повторяется, попробуйте для её решения очистить хранилище приложений Nextcloud и Nextcloud Карточки.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Если проблема повторяется, попробуйте для её решения очистить хранилища данных приложений Nextcloud и Nextcloud Карточки.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Не удалось выполнить обновление базы данных. Сообщите об этой ошибке разработчикам. Чтобы продолжить использовать приложение, требуется выполнить очистку данных приложения.</string>
+ <string name="error_dialog_tip_clear_storage">Для очистки хранилища на откройте приложение «Настройки» и выберите Приложения → Nextcloud / Nextcloud Deck → Хранилище → Очистить хранилище</string>
<string name="error_dialog_tip_files_outdated">Ваше приложение Nextcloud устарело. Установите новую версию с Play Store или F-Droid.</string>
<string name="error_dialog_tip_files_force_stop">Похоже, что с приложением Nextcloud ведёт себя неожиданным образом. Попробуйте принудительно остановить приложения Nextcloud и Nextcloud Карточки. </string>
<string name="error_dialog_tip_files_delete_storage">Если принудительная остановка не поможет, попробуйте очистить хранилище обоих приложений.</string>
@@ -232,16 +226,82 @@
<string name="error_dialog_insufficient_storage">На вашем экземпляре Nextcloud нет свободного места. Удалите ненужные файлы для синхронизации изменений в облако.</string>
<string name="error_dialog_we_need_info">Требуется следующая техническая информация от вас:</string>
<string name="error_dialog_redirect">На запрос сервер вернул код состояния HTTP 302, что означает, что приложение Карточки либо не установлено, либо сервер настроен неверно. Такое поведение может являться следствием замены стандартного файла .htaccess администратором сервера или приложением Nextcloud, таким как OID Client.</string>
+ <string name="error_dialog_version_not_parsable">Не удалось определить серверную версию приложения Карточки. Убедитесь, что приложение установлено и включено на сервере.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Учётная запись из приложения Nextcloud не может использована для авторизации.</string>
+ <string name="error_dialog_user_not_found_in_database">Данные о текущем пользователе отличаются от сохранённых в базе данных приложения. Это может произойти при использовании каталогов LDAP на сервере Nextcloud.</string>
+ <string name="error_dialog_capabilities_not_parsable">Не удалось получить параметры совместимости сервера. Убедитесь, что сервер работоспособен и у других приложений-клиентов имеется доступ.</string>
+ <string name="error_dialog_attachment_upload_failed">Ошибка передачи вложения на сервер. Используйте другой способ публикации и сообщите разработчикам об этой ошибке.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Пожалуйста отключите все оптимизации аккумулятора для приложений Nextcloud и Deck.</string>
<string name="error_action_open_deck_info">Открыть информацию о приложении</string>
<string name="error_action_open_network">Параметры сети</string>
<string name="error_action_server_logs">Журналы сервера</string>
<string name="error_action_install">Установить</string>
+ <string name="error_action_report_issue">Сообщить</string>
<string name="info_box_maintenance_mode">Сервер находится в режиме обслуживания</string>
<string name="info_box_version_not_supported">Версия сервера %1$s не поддерживается, необходимо выполнить обновление до версии %2$s</string>
<string name="share_link">Предоставление общего доступа по ссылке</string>
<string name="archive_cards">Архивировать карточки</string>
+
<string name="manage_accounts">Управление аккаунтами</string>
<string name="manage_list">Управлять списком</string>
<string name="simple_reply">Ответить</string>
+ <string name="error_while_uploading_attachment">Ошибка передачи вложения «%1$s» на сервер.</string>
+ <string name="append_text_to_description">Добавить в описание</string>
+ <string name="add_text_as_comment">Добавить как комментарий</string>
+ <string name="progress_count">%1$d из %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">Во время передачи произошла %1$d ошибка</item>
+ <item quantity="few">Во время передачи произошло %1$d ошибки</item>
+ <item quantity="many">Во время передачи произошло %1$d ошибок</item>
+ <item quantity="other">Во время передачи произошло %1$d ошибки</item>
+ </plurals>
<string name="simple_report">Сообщить</string>
+ <string name="error_action_open_battery_settings">Настройки аккумулятора</string>
+ <string name="move_warning">нет ни одного комментария или вложения, которые можно перенести при перемещении карточки на другую доску</string>
+ <string name="clone_board">Скопировать доску</string>
+ <string name="cloning_board">Копирование %1$s...</string>
+ <string name="successfully_cloned_board">Успешно скопировано%1$s</string>
+ <string name="attachment_does_not_yet_exist">Вложение еще не создано в карточке</string>
+ <string name="card_does_not_yet_exist">Карточка не создана на доске</string>
+
+ <string name="widget_stack_title">Список</string>
+ <string name="widget_stack_header_icon">иконка заголовка виджета</string>
+ <string name="widget_stack_placeholder_icon">Подстановочный значок виджета</string>
+ <string name="select_stack">Выберите список</string>
+ <string name="project_type_deck_board">Доска</string>
+ <string name="project_type_deck_card">Карточка</string>
+ <string name="project_type_file">Файл</string>
+ <string name="projects_title">Проекты</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d ресурс</item>
+ <item quantity="few">%1$d ресурса</item>
+ <item quantity="many">%1$d ресурсов</item>
+ <item quantity="other">%1$d ресурса</item>
+ </plurals>
+ <string name="no_assigned_label">Метки не присвоены</string>
+ <string name="single_card">Отдельная карточка</string>
+ <string name="project_type_room">Комната конференции</string>
+ <string name="simple_move">Переместить</string>
+ <string name="cannot_upload_files_without_permission">Отсутствуют права для загрузки файла</string>
+ <string name="clone_cards">Скопировать карточку</string>
+ <string name="simple_clone">Скопировать</string>
+ <string name="user_avatar">Изображение профиля</string>
+ <string name="simple_unassign">Отменить назначение</string>
+ <string name="simple_contact">Контакт</string>
+ <string name="simple_file">Файл</string>
+ <string name="simple_camera">Камера</string>
+ <string name="min_api_21">Эта возможность поддерживается на Android версии 5 и старше</string>
+ <string name="take_photo">Сделать фотографию</string>
+ <string name="take_photo_switch_camera">Переключить камеру</string>
+ <string name="take_photo_toggle_torch">Подсветка</string>
+ <string name="show_all_contacts">Показать все контакты</string>
+ <string name="show_all_files">Показать всё файлы</string>
+ <string name="recent">Недавние</string>
+ <string name="upload_a_new_attachment">Загрузить вложение</string>
+ <string name="contacts">Контакты</string>
+ <string name="downloads">Загрузки</string>
+ <string name="files">Файлы</string>
+ <string name="gallery">Галерея</string>
+ <string name="simple_attach">вложить</string>
+ <string name="add_stack_widget">Добавить виджет списка</string>
</resources>
diff --git a/app/src/main/res/values-sk-rSK/strings.xml b/app/src/main/res/values-sk-rSK/strings.xml
index 24eee9531..624e48ef6 100644
--- a/app/src/main/res/values-sk-rSK/strings.xml
+++ b/app/src/main/res/values-sk-rSK/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Posledný účet</string>
<string name="drawer_manage_accounts">Spravovať účty</string>
- <!-- Simple values -->
<string name="simple_boards">Nástenky</string>
<string name="simple_add">Pridať</string>
<string name="simple_save">Uložiť</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">zakázané</string>
<string name="simple_copied">Skopírované</string>
<string name="simple_archive">Archivovať</string>
+ <string name="simple_unassigned">Nepriradený</string>
<string name="edit_board">Upraviť nástenku</string>
<string name="archive_board">Archivovať nástenku</string>
<string name="delete_board">Zmazať nástenku</string>
<string name="delete_something">Zmazať %1$s</string>
- <!-- About -->
<string name="about">O aplikácii</string>
<string name="about_version_title">Verzia</string>
<string name="about_version">Momentálne používate %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Zásluhy</string>
<string name="about_contribution_tab_title">Príspevok</string>
<string name="about_license_tab_title">Licencia</string>
-
- <string name="copied_to_clipboard">Skopírované do schránky</string>
-
<string name="seconds_ago">pred niekoľkými sekundami</string>
<string name="edit">Upraviť</string>
<string name="label_labels">Vybrať štítky</string>
@@ -133,6 +129,7 @@
<string name="delete_board_message">Táto nástenka sa natrvalo odstráni, vrátane všetkých zoznamov a kariet.</string>
<string name="settings_theme_title">Tmavý motív</string>
<string name="settings_branding_title">Použitie vlastného loga</string>
+ <string name="settings_compact_title">Kompaktný režim</string>
<string name="settings_background_sync">Synchronizácia na pozadí</string>
<string name="pref_value_wifi_and_mobile">Synchronizovať na Wi-Fi a mobilných dátach</string>
<string name="pref_value_wifi_only">Synchronizovať len na Wi-Fi</string>
@@ -160,7 +157,6 @@
<string name="maintenance_mode_explanation">Server %1$s je v móde údržby. Kontaktujte svojho správcu alebo to skúste znova neskôr.</string>
<string name="share_add_to_card">Pridať do košíka</string>
<string name="share_success">%1$s úspešne pridané do %2$s</string>
- <string name="could_not_copy_to_clipboard">Nepodarilo sa skopírovať do schránky</string>
<string name="add_comment">Pridať komentár</string>
<string name="card_edit_comments">Komentáre</string>
<string name="no_comments_yet">Zatiaľ žiadne komentáre</string>
@@ -172,11 +168,9 @@
<string name="open_in_browser">Otvoriť v prehliadači</string>
<string name="updating_card">Aktualizácia karty…</string>
- <!-- Move lists -->
<string name="move_list_right">Presunúť zoznam doprava</string>
<string name="move_list_left">Presunúť zoznam doľava</string>
- <!-- Filter -->
<string name="filter_no_filter">Všetko</string>
<string name="filter_overdue">Po termíne</string>
<string name="filter_today">Dnes</string>
@@ -187,7 +181,6 @@
<string name="filter_by_assigned_user">Filtrovať podľa priradeného používateľa</string>
<string name="filter_by_duedate">Filtrovať podľa termínu</string>
- <!-- Archived cards -->
<string name="archived_cards">Archivované karty</string>
<string name="action_card_dearchive">Odarchivovať kartu</string>
<string name="action_archived_cards">Prechádzať archivované karty</string>
@@ -202,11 +195,9 @@
<string name="filter_user_title">Používatelia</string>
<string name="filter_duedate_title">Termín dokončenia</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Zrušiť archiváciu nástenky</string>
<string name="archived_boards">Archivované nástenky</string>
- <!-- Errors -->
<string name="error">Vyskytla sa chyba</string>
<string name="synchronization_failed">Synchronizácia zlyhala</string>
<string name="operation_not_yet_supported">Zatiaľ nepodporované</string>
@@ -220,7 +211,8 @@
<string name="error_dialog_title">Ale nie - Čo teraz? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Skúste vyinútiť zatvorenie aplikácie a znova ju reštartovať. Možno došlo k chybnému pripojeniu k aplikácii Nextcloud.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Ak problém pretrváva, pokúste sa tento problém vyriešiť vymazaním úložiska oboch aplikácií: Nextcloud a Nextcloud Deck.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Ak problém pretrváva, pokúste sa tento problém vyriešiť vymazaním úložiska oboch aplikácií: Nextcloud a Nextcloud Deck.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Aktualizácia databázy zlyhala. Nahláste problém a vymažte ukladací priestor, aby ste aplikáciu mohli normálne používať.</string>
<string name="error_dialog_tip_clear_storage">Úložisko môžete vyčistiť otvorením informácii o aplikácii a výberom Úložisko → Vyčistiť úložisko.</string>
<string name="error_dialog_tip_files_outdated">Váš Nextcloud vyzerá byť zastaralý. Navštívte Play Store alebo F-Droid pre získanie najnovšej verzie.</string>
<string name="error_dialog_tip_files_force_stop">Zdá sa, že s Nextcloudom niečo nie je v poriadku. Pokúste sa vynútiť zastavenie aplikácie Nextcloud aj aplikácie Nextcloud Deck.</string>
@@ -234,16 +226,21 @@
<string name="error_dialog_we_need_info">Aby sme vám pomohli, potrebujeme následovné technické informácie:</string>
<string name="error_dialog_redirect">Váš server odpovedal stavovým kódom HTTP 302, čo znamená, že na svojom serveri nemáte nainštalovanú aplikáciu Deck alebo je niečo nesprávne nastavené. Môže to byť spôsobené vlastnými zmenami v súbore .htaccess alebo aplikáciami Nextcloud, ako je OID Client.</string>
<string name="error_dialog_version_not_parsable">Nepodarilo sa určiť verziu aplikácie Deck na strane servera. Skontrolujte, či je nainštalovaný a povolený.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Účet vašej aplikácie Nextcloud už nemusí byť autorizovaný.</string>
+ <string name="error_dialog_user_not_found_in_database">Aktuálny používateľ sa nezhoduje s používateľom, ktorý máme v našej databáze. Ak používate server LDAP vo vašej inštancii Nextcloud, vaša aplikácia Nextcloud pravdepodobne uložila staré ID používateľa.</string>
<string name="error_dialog_capabilities_not_parsable">Nepodarilo sa načítať možnosti vášho servera. Uistite sa, že váš server funguje správne a že ďalšie klientske aplikácie majú prístup k serveru Nextcloud.</string>
<string name="error_dialog_attachment_upload_failed">Prílohu nebolo možné nahrať. Skúste to zdieľať iným spôsobom a dajte nám vedieť o tejto chybe.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Vypnite všetky optimalizácie batérie pre aplikácie Nextcloud a Deck.</string>
<string name="error_action_open_deck_info">Otvoriť informácie o aplikácii</string>
<string name="error_action_open_network">Nastavenia siete</string>
<string name="error_action_server_logs">Záznamy udalostí na serveri</string>
<string name="error_action_install">Inštalovať</string>
+ <string name="error_action_report_issue">Hlásenie</string>
<string name="info_box_maintenance_mode">Server je v móde údržby</string>
<string name="info_box_version_not_supported">Verzia servera %1$s nie je podporovaná, aktualizujte ju na %2$s</string>
<string name="share_link">Sprístupniť odkaz</string>
<string name="archive_cards">Archivovať karty</string>
+
<string name="manage_accounts">Spravovať účty</string>
<string name="manage_list">Spravovať zoznam</string>
<string name="simple_reply">Odpoveď</string>
@@ -252,10 +249,37 @@
<string name="add_text_as_comment">Pridať ako komentár</string>
<string name="progress_count">%1$d z %2$d</string>
<plurals name="progress_error_count">
- <item quantity="one">%1$d chyba pri nahrávaní</item>
- <item quantity="few">%1$d chyby pri nahrávaní</item>
- <item quantity="many">%1$d chyby pri nahrávaní</item>
- <item quantity="other">%1$d chýb pri nahrávaní</item>
+ <item quantity="one">%1$d chyba počas nahrávania</item>
+ <item quantity="few">%1$d chyby počas nahrávania</item>
+ <item quantity="many">%1$d chyby počas nahrávania</item>
+ <item quantity="other">%1$d chýb počas nahrávania</item>
</plurals>
<string name="simple_report">Hlásenie</string>
-</resources>
+ <string name="error_action_open_battery_settings">Nastavenia batérie.</string>
+ <string name="move_warning">Pri presúvaní karty na inú nástenku nie je možné prenášať komentáre ani prílohy.</string>
+ <string name="clone_board">Duplikovať nástenku</string>
+ <string name="cloning_board">Klonovanie %1$s…</string>
+ <string name="successfully_cloned_board">%1$s úspešne klonovaný</string>
+ <string name="attachment_does_not_yet_exist">Príloha ešte na nástenke neexistuje</string>
+ <string name="card_does_not_yet_exist">Karta ešte na nástenke neexistuje</string>
+
+ <string name="widget_stack_title">Zoznam</string>
+ <string name="widget_stack_header_icon">Ikona hlavičky miniaplikácie</string>
+ <string name="widget_stack_placeholder_icon">Ikona zástupného symbolu miniaplikácie</string>
+ <string name="select_stack">Vyberte zoznam</string>
+ <string name="project_type_deck_board">Palubná doska</string>
+ <string name="project_type_deck_card">Palubná karta</string>
+ <string name="project_type_file">Súbor</string>
+ <string name="projects_title">Projekty</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d zdroj</item>
+ <item quantity="few">%1$d zdroje</item>
+ <item quantity="many">%1$d zdroje</item>
+ <item quantity="other">%1$d zdrojov</item>
+ </plurals>
+ <string name="no_assigned_label">Žiadna priradená značka</string>
+ <string name="single_card">Jedna karta</string>
+ <string name="project_type_room">Diskusná miestnosť</string>
+ <string name="simple_move">Presunúť</string>
+ <string name="cannot_upload_files_without_permission">Nie je možné nahrávať súbory bez povolenia</string>
+ </resources>
diff --git a/app/src/main/res/values-sl/strings.xml b/app/src/main/res/values-sl/strings.xml
index 56c23a7b6..77e23ca7c 100644
--- a/app/src/main/res/values-sl/strings.xml
+++ b/app/src/main/res/values-sl/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Zadnji račun</string>
<string name="drawer_manage_accounts">Upravljanje z računi</string>
- <!-- Simple values -->
<string name="simple_boards">Zbirke</string>
<string name="simple_add">Dodaj</string>
<string name="simple_save">Shrani</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">onemogočeno</string>
<string name="simple_copied">Kopirano!</string>
<string name="simple_archive">Arhiv</string>
+ <string name="simple_unassigned">Nedodeljeno</string>
<string name="edit_board">Uredi zbirko</string>
<string name="archive_board">Arhiviraj zbirko</string>
<string name="delete_board">Izbriši zbirko</string>
<string name="delete_something">Izbriši %1$s</string>
- <!-- About -->
<string name="about">O programu</string>
<string name="about_version_title">Različica</string>
<string name="about_version">Trenutno je v uporabi %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Zasluge</string>
<string name="about_contribution_tab_title">Skupnost</string>
<string name="about_license_tab_title">Dovoljenje</string>
-
- <string name="copied_to_clipboard">Kopirano v odložišče</string>
-
<string name="seconds_ago">pred nekaj sekundami</string>
<string name="edit">Uredi</string>
<string name="label_labels">Izbor oznak</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">Ni še dodanega seznama</string>
<string name="do_you_want_to_save_your_changes">Ali želite shraniti spremembe?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Ali želite arhivirati vse naloge %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Ali želite arhivirati vse filtrirane naloge %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">S tem boste trajno izbrisali %1$d nalogo tega seznama.</item>
<item quantity="two">S tem boste trajno izbrisali %1$d nalogi tega seznama.</item>
@@ -133,6 +130,7 @@
<string name="delete_board_message">S tem dejanjem bo zbirka z vsemi paketi nalog in nalogami trajno izbrisana.</string>
<string name="settings_theme_title">Temna tema</string>
<string name="settings_branding_title">Prilagajanje oblikovanja</string>
+ <string name="settings_compact_title">Stisnjen pogled</string>
<string name="settings_background_sync"> Usklajevanje v ozadju</string>
<string name="pref_value_wifi_and_mobile">Usklajuj na Wi-Fi in mobilni povezavi</string>
<string name="pref_value_wifi_only">Usklajuj le na Wi-Fi</string>
@@ -160,7 +158,6 @@
<string name="maintenance_mode_explanation">Strežnik %1$s je trenutno v načinu vzdrževanja. Stopite v stik s skrbnikom, ali pa poskusite kasneje.</string>
<string name="share_add_to_card">Dodaj k nalogi</string>
<string name="share_success">Uspešno dodano %1$s v %2$s</string>
- <string name="could_not_copy_to_clipboard">Predmeta ni mogoče kopirati v odložišče</string>
<string name="add_comment">Dodaj opombo</string>
<string name="card_edit_comments">Opombe</string>
<string name="no_comments_yet">Ni še dodane nobene opombe</string>
@@ -172,11 +169,9 @@
<string name="open_in_browser">Odpri v brskalniku</string>
<string name="updating_card">Poteka posodabljanje naloge ...</string>
- <!-- Move lists -->
<string name="move_list_right">Pomakni seznam desno</string>
<string name="move_list_left">Pomakni seznam levo</string>
- <!-- Filter -->
<string name="filter_no_filter">Vse</string>
<string name="filter_overdue">Preteklo</string>
<string name="filter_today">Danes</string>
@@ -187,7 +182,6 @@
<string name="filter_by_assigned_user">Filtriraj po dodeljenem uporabniku</string>
<string name="filter_by_duedate">Filtriraj po datumu preteka</string>
- <!-- Archived cards -->
<string name="archived_cards">Arhivirane naloge</string>
<string name="action_card_dearchive">Razveljavi arhiviranje naloge</string>
<string name="action_archived_cards">Prebrskaj med arhiviranimi nalogami</string>
@@ -202,11 +196,9 @@
<string name="filter_user_title">Uporabniki</string>
<string name="filter_duedate_title">Datum preteka</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Razveljavi arhiviranje zbirke</string>
<string name="archived_boards">Arhivirane zbirke</string>
- <!-- Errors -->
<string name="error">Prišlo je do napake</string>
<string name="synchronization_failed">Usklajevanje je spodletelo</string>
<string name="operation_not_yet_supported">Možnost še ni podprta</string>
@@ -220,7 +212,8 @@
<string name="error_dialog_title">Ojoj, kaj pa zdaj? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Poskusite vsiliti končanje programa in ga ponovno zagnati. Morda je težava v neustrezni povezavi z okoljem Nextcloud</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Če se težava še naprej pojavlja, poskusite počistiti podatke programa Nextcloud in Nextcloud Deck.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Če se težava še naprej pojavlja, poskusite počistiti podatke programa Nextcloud in Nextcloud Deck.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Nadgradnja podatkovne zbirke je spodletela. Pošljite poročilo o napaki in nato počistite shrambo za normalno uporabo programa.</string>
<string name="error_dialog_tip_clear_storage">Shrambo je mogoče počistiti med podrobnostmi programa Shramba → Počisti shrambo.</string>
<string name="error_dialog_tip_files_outdated">Program Nextcloud je očitno starejše različice. Posodobite jo prek Trgovine Play ali s posodabljalnikom F-Droid.</string>
<string name="error_dialog_tip_files_force_stop">Kaže, da je nekaj narobe s programom Nextcloud. Poskusite vsiljeno zaustaviti programa Nextcloud in Nextcloud Deck.</string>
@@ -234,17 +227,81 @@
<string name="error_dialog_we_need_info">Z ustrezno pomoč potrebujemo nekaj tehničnih podrobnosti sistema:</string>
<string name="error_dialog_redirect">S strežnika je prejet odziv s kodo stanja HTTP 302, kar kaže na to, da program Deck ni nameščen, ali pa je napačno nastavljen. Napaka se pojavi tudi pri nekaterih določilih v datoteki .htaccess oziroma programih, kot je odjemalec OID.</string>
<string name="error_dialog_version_not_parsable">Ni mogoče določiti različice strežnika, povezanega s programom Deck. Prepričajte se, da je nameščen in omogočen.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Račun v programu Nextcloud morda ni več overjen.</string>
+ <string name="error_dialog_user_not_found_in_database">Trenutno izbrani uporabnik nima ustreznih dovoljenj. Če uporabljate LDAP ima morda progam Nextcloud shranjen star ID uporabnika.</string>
<string name="error_dialog_capabilities_not_parsable">Ni mogoče pridobiti podatkov o zmožnostih strežnika. Prepričajte se, da strežnik teče in da imajo programi dostop do okolja Nextcloud.</string>
+ <string name="error_dialog_attachment_upload_failed">Priloge ni mogoče poslati. Souporabo omogočite na nek drug način, nam pa posredujte poročilo o hrošču.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Onemogočite vso optimizacijo porabe baterije za programa Nextcloud in Deck.</string>
<string name="error_action_open_deck_info">Odpri podrobnosti programa</string>
<string name="error_action_open_network">Nastavitve omrežja</string>
<string name="error_action_server_logs">Dnevniki strežnika</string>
<string name="error_action_install">Namesti</string>
+ <string name="error_action_report_issue">Poročilo</string>
<string name="info_box_maintenance_mode">Strežnik je v načinu vzdrževanja</string>
<string name="info_box_version_not_supported">Različica strežnika %1$s ni podprta. Priporočljiva je posodobitev na različico %2$s.</string>
<string name="share_link">Povezava za souporabo</string>
<string name="archive_cards">Arhiviraj naloge</string>
+
<string name="manage_accounts">Upravljanje z računi</string>
<string name="manage_list">Upravljanje seznama</string>
<string name="simple_reply">Odgovori</string>
+ <string name="error_while_uploading_attachment">Prišlo je do napake med pošiljanjem priloge: %1$s</string>
+ <string name="append_text_to_description">Pripni k opisu</string>
+ <string name="add_text_as_comment">Dodaj opombo</string>
+ <string name="progress_count">%1$d od %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d napaka med pošiljanjem</item>
+ <item quantity="two">%1$d napaki med pošiljanjem</item>
+ <item quantity="few">%1$d napake med pošiljanjem</item>
+ <item quantity="other">%1$d napak med pošiljanjem</item>
+ </plurals>
<string name="simple_report">Poročilo</string>
+ <string name="error_action_open_battery_settings">Nastavitve baterije</string>
+ <string name="move_warning">Ni mogoče prenesti ne opomb ne prilog med premikanjem naloge v drugo zbirko.</string>
+ <string name="clone_board">Kloniraj zbirko</string>
+ <string name="cloning_board">Poteka kloniranje %1$s…</string>
+ <string name="successfully_cloned_board">Kloniranje %1$s je uspešno končano</string>
+ <string name="attachment_does_not_yet_exist">Priloga v Deck še ne obstaja</string>
+ <string name="card_does_not_yet_exist">Naloga v Deck še ne obstaja</string>
+
+ <string name="widget_stack_title">Seznam</string>
+ <string name="widget_stack_header_icon">Ikona glave gradnika</string>
+ <string name="widget_stack_placeholder_icon">Ikona ročnika gradnika</string>
+ <string name="select_stack">Izbor seznama</string>
+ <string name="project_type_deck_board">Zbirka Deck</string>
+ <string name="project_type_deck_card">Naloga Deck</string>
+ <string name="project_type_file">Datoteka</string>
+ <string name="projects_title">Projekti</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d vir</item>
+ <item quantity="two">%1$d vira</item>
+ <item quantity="few">%1$d viri</item>
+ <item quantity="other">%1$d virov</item>
+ </plurals>
+ <string name="no_assigned_label">Ni dodeljene oznake</string>
+ <string name="single_card">Enojna naloga</string>
+ <string name="project_type_room">Klepetalnica</string>
+ <string name="simple_move">Premakni</string>
+ <string name="cannot_upload_files_without_permission">Brez ustreznih dovoljenj ni mogoče naložiti datoteke</string>
+ <string name="clone_cards">Kloniraj nalogo</string>
+ <string name="simple_clone">Kloniraj</string>
+ <string name="user_avatar">Podoba uporabnika</string>
+ <string name="simple_unassign">Odstrani</string>
+ <string name="simple_contact">Stik</string>
+ <string name="simple_file">Datoteka</string>
+ <string name="simple_camera">Kamera</string>
+ <string name="min_api_21">Ta možnost zahteva vsaj sistem Android 5.</string>
+ <string name="take_photo">Zajemi posnetek</string>
+ <string name="take_photo_switch_camera">Preklopi kamero</string>
+ <string name="take_photo_toggle_torch">Preklopi bliskavico</string>
+ <string name="show_all_contacts">Prikaži vse stike</string>
+ <string name="show_all_files">Pokaži vse datoteke</string>
+ <string name="recent">Nedavne</string>
+ <string name="upload_a_new_attachment">Pošlji novo prilogo</string>
+ <string name="contacts">Stiki</string>
+ <string name="downloads">Prejemi</string>
+ <string name="files">Datoteke</string>
+ <string name="gallery">Galerija</string>
+ <string name="simple_attach">pripni</string>
+ <string name="add_stack_widget">Dodaj gradnik seznama</string>
</resources>
diff --git a/app/src/main/res/values-sr/strings.xml b/app/src/main/res/values-sr/strings.xml
index c5d52389e..e87f183df 100644
--- a/app/src/main/res/values-sr/strings.xml
+++ b/app/src/main/res/values-sr/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Последњи налог</string>
<string name="drawer_manage_accounts">Управљање налозима</string>
- <!-- Simple values -->
<string name="simple_boards">Табле</string>
<string name="simple_add">Додај</string>
<string name="simple_save">Сачувај</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">искључено</string>
<string name="simple_copied">Копирано</string>
<string name="simple_archive">Архива</string>
+ <string name="simple_unassigned">Недодељен</string>
<string name="edit_board">Измени таблу</string>
<string name="archive_board">Архивирај таблу</string>
<string name="delete_board">Избриши таблу</string>
<string name="delete_something">Обриши %1$s</string>
- <!-- About -->
<string name="about">О програму</string>
<string name="about_version_title">Верзија</string>
<string name="about_version">Тренутно користите %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Заслуге</string>
<string name="about_contribution_tab_title">Допринос</string>
<string name="about_license_tab_title">Лиценца</string>
-
- <string name="copied_to_clipboard">Копирано у клипборд</string>
-
<string name="seconds_ago">пре неколико секунди</string>
<string name="edit">Измени</string>
<string name="label_labels">Одаберите ознаке</string>
@@ -112,6 +108,8 @@
<string name="not_synced_yet">Још није синхронизовано</string>
<string name="no_lists_yet">Нема још спискова</string>
<string name="do_you_want_to_save_your_changes">Да ли желите да сачувате измене?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">Да ли желите да архивирате све картице са %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Да ли желите да архивирате све филтриране картице са %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Овим ћете заувек обрисати %1$d картицу са овог списка.</item>
<item quantity="few">Овим ћете заувек обрисати %1$d картице са овог списка.</item>
@@ -124,12 +122,13 @@
</plurals>
<string name="add_a_new_list_using_the_button">Додај нови списак користећи + дугме</string>
<string name="add_a_new_card_using_the_button">Додајте нову картицу на + дугме</string>
- <string name="update_deck">Ажурирај шпил</string>
+ <string name="update_deck">Ажурирај Шпил</string>
<string name="your_deck_version_is_too_old">Ваша верзија Шпила је превише стара</string>
<string name="deck_outdated_please_update">Верзија апликације Шпил је стара (%1$s). Ажурирајте да бисте користили ову Андроид апликацију као клијента.</string>
<string name="delete_board_message">Ово ће трајно да уклони ову таблу укључујући све спискове и картице.</string>
<string name="settings_theme_title">Тамна тема</string>
<string name="settings_branding_title">Брендирање</string>
+ <string name="settings_compact_title">Компактан режим</string>
<string name="settings_background_sync">Позадинска синхронизација</string>
<string name="pref_value_wifi_and_mobile">Синхронизуј и на бежичној и на мобилној вези</string>
<string name="pref_value_wifi_only">Синхронизуј само преко бежичне везе</string>
@@ -157,7 +156,6 @@
<string name="maintenance_mode_explanation">Сервер %1$s је тренутно у режиму одржавања. Контактирајте администратора или покушајте касније.</string>
<string name="share_add_to_card">Додај на картицу</string>
<string name="share_success">Успешно додат %1$s на %2$s</string>
- <string name="could_not_copy_to_clipboard">Не могу да копирам у оставу</string>
<string name="add_comment">Додај коментар</string>
<string name="card_edit_comments">Коментари</string>
<string name="no_comments_yet">Још нема коментара</string>
@@ -169,11 +167,9 @@
<string name="open_in_browser">Отвори у прегледачу</string>
<string name="updating_card">Ажурирам картицу…</string>
- <!-- Move lists -->
<string name="move_list_right">Помери списак удесно</string>
<string name="move_list_left">Помери списак улево</string>
- <!-- Filter -->
<string name="filter_no_filter">Све</string>
<string name="filter_overdue">Прошао рок</string>
<string name="filter_today">Данас</string>
@@ -184,7 +180,6 @@
<string name="filter_by_assigned_user">Филтрирај по додељеном кориснику</string>
<string name="filter_by_duedate">Филтрирај по року</string>
- <!-- Archived cards -->
<string name="archived_cards">Архивиране картице</string>
<string name="action_card_dearchive">Поништи архивирање картице</string>
<string name="action_archived_cards">Прегледај архивиране картице</string>
@@ -199,11 +194,9 @@
<string name="filter_user_title">Корисници</string>
<string name="filter_duedate_title">Рок</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Поништи архивирање табле</string>
<string name="archived_boards">Архивиране табле</string>
- <!-- Errors -->
<string name="error">Дошло је до грешке</string>
<string name="synchronization_failed">Синхронизација није успела</string>
<string name="operation_not_yet_supported">Није још подржано</string>
@@ -217,7 +210,8 @@
<string name="error_dialog_title">О, не - шта сад? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Покушајте да форсирано затворите апликацију и стартујете је поново. Можда је била неисправна конекција ка Некстклауд апликацији.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Ако проблем настави да се дешава, пробајте да очистите податке од обе апликације: и Некстклауд и Некстклауд Шпила да бисте решили проблем.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Ако проблем настави да се дешава, пробајте да очистите податке од обе апликације: и Некстклауд и Некстклауд Шпила да бисте решили проблем.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Ажурирање базе није успело. Пријавите овај проблем и очистите складиште да користите апликацију нормално.</string>
<string name="error_dialog_tip_clear_storage">Складиште можете очистити ако отворите информације о апликацији и бирањем Меморија → Обриши податке.</string>
<string name="error_dialog_tip_files_outdated">Изгледа да је верзија Некстклауд апликације стара. Посетите Play Store или Ф-дроид да скинете најновију верзију.</string>
<string name="error_dialog_tip_files_force_stop">Нешто није у реду да Вашом Некстклауд апликацијом. Пробајте да форсирано зауставите обе, и Некстклауд апликацију и апликацију Некстклауд Шпила.</string>
@@ -231,15 +225,62 @@
<string name="error_dialog_we_need_info">Потребне су нам следеће техничке информација да бисмо Вам помогли:</string>
<string name="error_dialog_redirect">Сервер је одговорио са HTTP 302 кодом, што значи да немате инсталирану Deck апликацију или да Вам сервер није правилно конфигурисан. Ово може да буде проузорковано произвољним додавањима у .htaccess фајлу или другим Некстклауд апликацијама као што је OID Client.</string>
<string name="error_dialog_version_not_parsable">Не можемо да одредимо серверску верзију апликације Шпила. Проверите да ли је инсталирана и укључена.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Ваш налог од Некстклауд апликације можда више није ауторизован.</string>
+ <string name="error_dialog_user_not_found_in_database">Тренутни корисник се не поклапа са корисником у нашој бази података. Ако користите LDAP у тренутној Некстклауд сесији, вероватно Вам је Некстклауд апликација сачувала стари кориснички ИД.</string>
<string name="error_dialog_capabilities_not_parsable">Не можемо да дохватимо могућности Вашег сервера. Проверите да Вам сервер ради добро и да ли друге клијентске апликације могу да му приступе.</string>
+ <string name="error_dialog_attachment_upload_failed">Прилог није могао бити отпремљен. Молимо пробајте да га поделите на други начин и пријавите нам ову грешку.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Искључите све оптимизације батерије за Некстаклауд и Шпил апликације.</string>
<string name="error_action_open_deck_info">Отвори инфо о апликацији</string>
<string name="error_action_open_network">Поставке мреже</string>
<string name="error_action_server_logs">Дневник са сервера</string>
<string name="error_action_install">Инсталирај</string>
+ <string name="error_action_report_issue">Пријави</string>
<string name="info_box_maintenance_mode">Сервер у режиму одржавања</string>
<string name="info_box_version_not_supported">Серверска верзија %1$s није подржана, ажурирајте на %2$s</string>
<string name="share_link">Веза дељења</string>
+ <string name="archive_cards">Архивирај картице</string>
+
<string name="manage_accounts">Управљање налозима</string>
+ <string name="manage_list">Управљај списком</string>
<string name="simple_reply">Одговори</string>
+ <string name="error_while_uploading_attachment">Грешка приликом отпремања прилога: %1$s</string>
+ <string name="append_text_to_description">Придодај на опис</string>
+ <string name="add_text_as_comment">Додај као коментар</string>
+ <string name="progress_count">%1$d од %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d грешка приликом отпремања</item>
+ <item quantity="few">%1$d грешке приликом отпремања</item>
+ <item quantity="other">%1$d грешака приликом отпремања</item>
+ </plurals>
<string name="simple_report">Пријави</string>
-</resources>
+ <string name="error_action_open_battery_settings">Подешавања батерије</string>
+ <string name="move_warning">Ни коментари ни прилози не могу да се пребаце када се картица помера на другу таблу.</string>
+ <string name="clone_board">Клонирај таблу</string>
+ <string name="cloning_board">Клонирам %1$s…</string>
+ <string name="successfully_cloned_board">Успешно клониран %1$s</string>
+ <string name="attachment_does_not_yet_exist">Прилог још не постоји у Шпил апликацији</string>
+ <string name="card_does_not_yet_exist">Картица још не постоји у Шпил апликацији</string>
+
+ <string name="widget_stack_title">Излистај</string>
+ <string name="widget_stack_header_icon">Заглавње иконице виџета</string>
+ <string name="widget_stack_placeholder_icon">Иконица виџета</string>
+ <string name="select_stack">Одабир списка</string>
+ <string name="project_type_deck_board">Табла Шпила</string>
+ <string name="project_type_deck_card">Картица Шпила</string>
+ <string name="project_type_file">Фајл</string>
+ <string name="projects_title">Пројекти</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d ресурс</item>
+ <item quantity="few">%1$d ресурси</item>
+ <item quantity="other">%1$d ресурси</item>
+ </plurals>
+ <string name="no_assigned_label">Нема додељене ознаке</string>
+ <string name="single_card">Засебна картица</string>
+ <string name="project_type_room">Причаоница</string>
+ <string name="simple_move">Помери</string>
+ <string name="cannot_upload_files_without_permission">Не могу да отпремим фајлове без дозвола</string>
+ <string name="clone_cards">Клонирај картице</string>
+ <string name="simple_clone">Клонирај</string>
+ <string name="user_avatar">Кориснички аватар</string>
+ <string name="simple_unassign">Уклони доделу</string>
+ </resources>
diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml
index 5b126e526..afc762bff 100644
--- a/app/src/main/res/values-sv/strings.xml
+++ b/app/src/main/res/values-sv/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Sista konto</string>
<string name="drawer_manage_accounts">Hantera konton</string>
- <!-- Simple values -->
<string name="simple_boards">Tavlor</string>
<string name="simple_add">Lägg till</string>
<string name="simple_save">Spara</string>
@@ -27,7 +26,7 @@
<string name="simple_clear">Rensa</string>
<string name="simple_discard">kassera</string>
<string name="simple_update">Uppdatera</string>
- <string name="simple_delete">Radera</string>
+ <string name="simple_delete">Ta bort</string>
<string name="simple_rename">Byt namn</string>
<string name="simple_settings">Inställningar</string>
<string name="simple_undo">Ångra</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">inaktiverad</string>
<string name="simple_copied">Kopierad</string>
<string name="simple_archive">Arkivera</string>
+ <string name="simple_unassigned">Ej tilldelad</string>
<string name="edit_board">Ändra tavla</string>
<string name="archive_board">Arkivera tavla</string>
- <string name="delete_board">Radera tavla</string>
- <string name="delete_something">Radera %1$s</string>
+ <string name="delete_board">Ta bort tavla</string>
+ <string name="delete_something">Ta bort %1$s</string>
- <!-- About -->
<string name="about">Om</string>
<string name="about_version_title">Version</string>
<string name="about_version">Du använder för närvarande %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Tack</string>
<string name="about_contribution_tab_title">Bidrag</string>
<string name="about_license_tab_title">Licens</string>
-
- <string name="copied_to_clipboard">Kopierat till urklipp</string>
-
<string name="seconds_ago">sekunder sedan</string>
<string name="edit">Redigera</string>
<string name="label_labels">Välj taggar</string>
@@ -88,12 +84,12 @@
<string name="activity">Aktivitet</string>
<string name="add_list">Lägg till lista...</string>
<string name="rename_list">Byt namn på lista</string>
- <string name="delete_list">Radera lista</string>
+ <string name="delete_list">Ta bort lista</string>
<string name="label_menu">meny</string>
<string name="action_card_assign">Tilldela kort till mig</string>
<string name="action_card_unassign">Ta bort kort från mig</string>
<string name="action_card_archive">Arkivera kort</string>
- <string name="action_card_delete">Radera kort</string>
+ <string name="action_card_delete">Ta bort kort</string>
<string name="add_board">Lägg till tavla</string>
<string name="label_clear_due_date">Rensa slutdatum</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">Inga listor än</string>
<string name="do_you_want_to_save_your_changes">Vill du spara dina ändringar?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Vill du arkivera alla kort av %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Vill du arkivera alla filtrerade kort av %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Detta kommer att permanent radera %1$d kortet i denna lista.</item>
<item quantity="other">Detta kommer att permanent radera alla %1$d kort i denna lista.</item>
@@ -129,6 +126,7 @@
<string name="delete_board_message">Detta kommer att permanent radera denna tavla inklusive alla listor och kort.</string>
<string name="settings_theme_title">Mörkt tema</string>
<string name="settings_branding_title">Varumärke</string>
+ <string name="settings_compact_title">Kompakt läge</string>
<string name="settings_background_sync">Bakgrundssynkronisering</string>
<string name="pref_value_wifi_and_mobile">Synkronisera med Wi-Fi och mobildata</string>
<string name="pref_value_wifi_only">Synkronisera endast med Wi-Fi</string>
@@ -156,7 +154,6 @@
<string name="maintenance_mode_explanation">Servern %1$s är för närvarande i underhållsläge. Kontakta din administratör eller försök igen senare.</string>
<string name="share_add_to_card">Lägg till i kort</string>
<string name="share_success">La till %1$s till %2$s</string>
- <string name="could_not_copy_to_clipboard">Kunde inte kopiera till urklipp</string>
<string name="add_comment">Lägg till kommentar</string>
<string name="card_edit_comments">Kommentarer</string>
<string name="no_comments_yet">Inga kommentarer än</string>
@@ -168,11 +165,9 @@
<string name="open_in_browser">Öppna i webbläsare</string>
<string name="updating_card">Uppdaterar kortet...</string>
- <!-- Move lists -->
<string name="move_list_right">Flytta listan åt höger</string>
<string name="move_list_left">Flytta listan åt vänster</string>
- <!-- Filter -->
<string name="filter_no_filter">Alla</string>
<string name="filter_overdue">Förfallen</string>
<string name="filter_today">Idag</string>
@@ -183,7 +178,6 @@
<string name="filter_by_assigned_user">Filtrera efter tilldelad användare</string>
<string name="filter_by_duedate">Filtrera efter förfallodatum</string>
- <!-- Archived cards -->
<string name="archived_cards">Arkiverade kort</string>
<string name="action_card_dearchive">Ångra kortarkivering</string>
<string name="action_archived_cards">Bläddra i arkiverade kort</string>
@@ -198,11 +192,9 @@
<string name="filter_user_title">Användare</string>
<string name="filter_duedate_title">Slutdatum</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Ångra tavelarkivering</string>
<string name="archived_boards">Arkiverade tavlor</string>
- <!-- Errors -->
<string name="error">Ett fel inträffade</string>
<string name="synchronization_failed">Synkroniseringen misslyckades</string>
<string name="operation_not_yet_supported">Stöds inte än</string>
@@ -212,17 +204,79 @@
<string name="server_misconfigured">Server felkonfigurerad</string>
<string name="server_error">Serverfel</string>
<string name="shared_error">Delning av innehåll från appar från tredje part stöds ännu inte helt. Vänligen försök att hämta det först och dela det sedan från din ursprungliga filhanterare eller galleri.</string>
+ <string name="error_edit_activity_killed_by_android">Android avslutade redigeringsläget eftersom det behövde mer systemresurser för andra appar.</string>
+
<string name="error_dialog_title">Å nej - Vad händer nu?🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Försök med att Tvinga appen att stänga och återstarta. En felaktig anslutning till Nextcloud-appen kan ha förekommit.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Om problemet kvarstår kan du försöka rensa lagringen av båda apparna: Nextcloud och Nextcloud Deck för att lösa problemet.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Uppgraderingen av databasen misslyckades. Var god anmäl problemet, rensa sedan appens data för att kunna återgå till normal användning.</string>
+ <string name="error_dialog_tip_clear_storage">Du kan rensa diskutrymmet genom att öppna appens info, välj Lagring -> Rensa data</string>
+ <string name="error_dialog_tip_files_outdated">Din Nextcloud-app verkar utdaterad. Besök Play Butiken eller F-Droid för att hämta senaste versionen</string>
+ <string name="error_dialog_tip_files_force_stop">Något verkar fel med din Nextcloud-app. Försök att tvångsavsluta både Nextcloud och Nextcloud Deck-appen.</string>
+ <string name="error_dialog_tip_files_delete_storage">Om tvångsavslutning ej hjälper kan du försöka rensa datan på båda appar.</string>
+ <string name="error_dialog_timeout_instance">Din server svarade inte inom den angivna tiden. Kontrollera att den fungerar normalt.</string>
+ <string name="error_dialog_timeout_toggle">Kontrollera din nätverksanslutning. Ibland kan det hjälpa att slå av och på mobildata eller Wi-Fi.</string>
+ <string name="error_dialog_check_server">Inkorrekt svar från din server. Kontrollera att du har tillgång till Deck-appen genom din webbläsare.</string>
+ <string name="error_dialog_check_server_logs">Det finns problem med din Nextcloud-installation. Ta gärna en titt i serverns loggfiler. </string>
+ <string name="error_dialog_check_maintenance">Kontrollera att underhållsläge inte är aktivt på Nextcloud-installation.</string>
+ <string name="error_dialog_insufficient_storage">Din Nextcloud-installation har slut på diskutrymme. Ta bort några filer för att kunna synkronisera ändringar till ditt moln.</string>
+ <string name="error_dialog_we_need_info">Vi behöver följande teknisk information för att kunna hjälpa dig:</string>
+ <string name="error_dialog_redirect">Din server svarade med ett HTTP 302 statusmeddelande vilket innebär att du inte har installerat Deck appen på din server eller att något är felkonfigurerat. Detta kan orsakas av manuella ändringar i en .htaccess-fil eller av Nextcloud-appar som \"OID Client\".</string>
+ <string name="error_dialog_version_not_parsable">Vi kunde inte avgöra vilken version av Deck-appen som är installerad på servern. Kontrollera att den är installerad och aktiverad.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Ditt Nextcloud-konto är eventuellt inte längre auktoriserat. </string>
+ <string name="error_dialog_user_not_found_in_database">Den nuvarande användaren matchar inte användaren vi har i vår databas. Om ni använder LDAP i er Nextcloud-instans kan din Nextcloud-app ha lagrat ett gammalt användar-ID.</string>
+ <string name="error_dialog_capabilities_not_parsable">Vi kunde inte hämta er servers förmågor. Vänligen säkerställ att er server fungerar som den ska och att klient-appar kan komma åt Nextcloud.</string>
+ <string name="error_dialog_attachment_upload_failed">En bifogad fil kunde inte laddas upp. Försök dela den på ett annat och sätt och vänligen berätta för oss om den här buggen.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Stäng av alla batterioptimeringar för Nextcloud och Deck-appen</string>
<string name="error_action_open_deck_info">Öppna appinformation</string>
<string name="error_action_open_network">Nätverksinställningar</string>
<string name="error_action_server_logs">Serverloggar</string>
<string name="error_action_install">Installera</string>
+ <string name="error_action_report_issue">Rapportera</string>
<string name="info_box_maintenance_mode">Server i underhållsläge</string>
<string name="info_box_version_not_supported">Serverversion %1$s stöds inte, uppdatera till %2$s</string>
<string name="share_link">Dela länk</string>
<string name="archive_cards">Arkivera kort</string>
+
<string name="manage_accounts">Hantera konton</string>
+ <string name="manage_list">Hantera lista</string>
<string name="simple_reply">Svara</string>
+ <string name="error_while_uploading_attachment">Fel vid uppladdning av bilaga: %1$s</string>
+ <string name="append_text_to_description">Lägg till beskrivning</string>
+ <string name="add_text_as_comment">Lägg till som kommentar</string>
+ <string name="progress_count">%1$d av %2$d</string>
+ <plurals name="progress_error_count">
+ <item quantity="one">%1$d fel vid uppladdning</item>
+ <item quantity="other">%1$d fel vid uppladdning</item>
+ </plurals>
<string name="simple_report">Rapportera</string>
-</resources>
+ <string name="error_action_open_battery_settings">Batteriinställningar</string>
+ <string name="move_warning">Varken kommentarer eller bifogade filer följer med när man flyttar kortet till ett annat plank.</string>
+ <string name="clone_board">Kopiera tavla</string>
+ <string name="cloning_board">Klonar %1$s ...</string>
+ <string name="successfully_cloned_board">Lyckades klona %1$s</string>
+ <string name="attachment_does_not_yet_exist">Bifogade filen finns inte ännu i Deck</string>
+ <string name="card_does_not_yet_exist">Kortet finns inte ännu i Deck</string>
+
+ <string name="widget_stack_title">Lista</string>
+ <string name="widget_stack_header_icon">Ikon för widgetens sidhuvud</string>
+ <string name="widget_stack_placeholder_icon">Ikon för widgetens platshållare</string>
+ <string name="select_stack">Välj lista</string>
+ <string name="project_type_deck_board">Deck-plank</string>
+ <string name="project_type_deck_card">Deck-kort</string>
+ <string name="project_type_file">Fil</string>
+ <string name="projects_title">Projekt</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d resurs</item>
+ <item quantity="other">%1$d resurser</item>
+ </plurals>
+ <string name="no_assigned_label">Ingen tilldelad tagg</string>
+ <string name="single_card">Enskilt kort</string>
+ <string name="project_type_room">Talk-rum</string>
+ <string name="simple_move">Flytta</string>
+ <string name="cannot_upload_files_without_permission">Kan inte ladda upp filer utan tillåtelse</string>
+ <string name="clone_cards">Klona kort</string>
+ <string name="simple_clone">Klona</string>
+ <string name="user_avatar">Användar-avatar</string>
+ <string name="simple_unassign">Ta bort tilldelning</string>
+ </resources>
diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml
index e76dbe391..c5c09d4fa 100644
--- a/app/src/main/res/values-tr/strings.xml
+++ b/app/src/main/res/values-tr/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Son hesap</string>
<string name="drawer_manage_accounts">Hesap yönetimi</string>
- <!-- Simple values -->
<string name="simple_boards">Katlar</string>
<string name="simple_add">Ekle</string>
<string name="simple_save">Kaydet</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">devre dışı</string>
<string name="simple_copied">Kopyalandı</string>
<string name="simple_archive">Arşivle</string>
+ <string name="simple_unassigned">Atanmamış</string>
<string name="edit_board">Panoyu düzenle</string>
<string name="archive_board">Panoyu arşivle</string>
<string name="delete_board">Panoyu sil</string>
<string name="delete_something">%1$s Sil</string>
- <!-- About -->
<string name="about">Hakkında</string>
<string name="about_version_title">Sürüm</string>
<string name="about_version">Şu anda %1$s kullanıyorsunuz</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Emeği Geçenler</string>
<string name="about_contribution_tab_title">Katkıda Bulunanlar</string>
<string name="about_license_tab_title">Lisans</string>
-
- <string name="copied_to_clipboard">Panoya kopyalandı</string>
-
<string name="seconds_ago">saniye önce</string>
<string name="edit">Düzenle</string>
<string name="label_labels">Etiketleri Seçin</string>
@@ -113,6 +109,7 @@
<string name="no_lists_yet">Henüz bir liste yok</string>
<string name="do_you_want_to_save_your_changes">Değişiklikleri kaydetmek ister misiniz?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Tüm %1$s kartlarını arşivlemek ister misiniz?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Tüm %1$s süzülmüş kartlarını arşivlemek ister misiniz?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Bu işlem, bu listedeki tüm %1$d kartı kalıcı olarak silecek.</item>
<item quantity="other">Bu işlem bu listedeki tüm %1$d kartı kalıcı olarak silecek.</item>
@@ -129,6 +126,7 @@
<string name="delete_board_message">Bu işlem içindeki tüm liste ve kartlarla birlikte bu panoyu silecek.</string>
<string name="settings_theme_title">Koyu tema</string>
<string name="settings_branding_title">Markalama</string>
+ <string name="settings_compact_title">Sıkışık kip</string>
<string name="settings_background_sync">Arka planda eşitleme</string>
<string name="pref_value_wifi_and_mobile">Wi-Fi ve mobil veri ile eşitlensin</string>
<string name="pref_value_wifi_only">Yalnız Wi-Fi ile eşitlensin</string>
@@ -156,7 +154,6 @@
<string name="maintenance_mode_explanation">%1$s sunucusu şu anda bakım kipinde. Lütfen yöneticinizle görüşün ya da daha sonra yeniden deneyin.</string>
<string name="share_add_to_card">Karta ekle</string>
<string name="share_success">%1$s, %2$s üzerine eklendi</string>
- <string name="could_not_copy_to_clipboard">Panoya kopyalanamadı</string>
<string name="add_comment">Yorum ekle</string>
<string name="card_edit_comments">Yorumlar</string>
<string name="no_comments_yet">Henüz bir yorum yok</string>
@@ -168,11 +165,9 @@
<string name="open_in_browser">Tarayıcıda aç</string>
<string name="updating_card">Kart güncelleniyor…</string>
- <!-- Move lists -->
<string name="move_list_right">Listeyi sağa taşı</string>
<string name="move_list_left">Listeyi sola taşı</string>
- <!-- Filter -->
<string name="filter_no_filter">Tümü</string>
<string name="filter_overdue">Zamanı geçmiş</string>
<string name="filter_today">Bugün</string>
@@ -183,7 +178,6 @@
<string name="filter_by_assigned_user">Atanmış kullanıcıya göre süz</string>
<string name="filter_by_duedate">Bitiş tarihine göre süz</string>
- <!-- Archived cards -->
<string name="archived_cards">Arşivlenmiş kartlar</string>
<string name="action_card_dearchive">Kart arşivlemeyi geri al</string>
<string name="action_archived_cards">Arşivlenmiş kartlara göz at</string>
@@ -198,11 +192,9 @@
<string name="filter_user_title">Kullanıcılar</string>
<string name="filter_duedate_title">Bitiş tarihi</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Pano arşivlemeyi geri al</string>
<string name="archived_boards">Arşivlenmiş panolar</string>
- <!-- Errors -->
<string name="error">Bir sorun çıktı</string>
<string name="synchronization_failed">Eşitlenemedi</string>
<string name="operation_not_yet_supported">Henüz desteklenmiyor</string>
@@ -216,7 +208,8 @@
<string name="error_dialog_title">Hayıır. Şimdi ne olacak? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Lütfen uygulamayı kapanmaya zorlayıp yeniden başlatın. Yanlış bir Nextcloud uygulaması bağlantısı olmalı.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">Sorun sürerse, çözmek için hem Nextcloud uygulamasının hem de Nextcloud Tahta uygulamasının depolamalarını temizlemeyi deneyin.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">Sorun sürerse, çözmek için hem Nextcloud uygulamasının hem de Nextcloud Tahta uygulamasının depolamalarını temizlemeyi deneyin.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">Veritabanı güncellenemedi. Lütfen sorunu bildirin ve uygulamayı normal şekilde kullanabilmek için depolamayı temizleyin.</string>
<string name="error_dialog_tip_clear_storage">Depolamayı temizlemek için uygulama bilgilerini açın ve Depolama (Hafıza) → Depolamayı temizle (Veriyi sil) seçin.</string>
<string name="error_dialog_tip_files_outdated">Nextcloud uygulamanız eski görünüyor. Lütfen son sürümü almak için Play Store ya da F-Droid mağazalarına gidin.</string>
<string name="error_dialog_tip_files_force_stop">Nextcloud uygulamanızda bir sorun var gibi görünüyor. Lütfen hem Nextcloud uygulamasını hem de Nextcloud Tahta uygulamasını zorla kapatmayı deneyin.</string>
@@ -230,16 +223,21 @@
<string name="error_dialog_we_need_info">Size yardımcı olabilmemiz için şu teknik bilgilere gerek duyacağız:</string>
<string name="error_dialog_redirect">Sunucunuz, üzerine Tahta uygulamasının kurulmadığı ya da bir şeyin yanlış yapılandırıldığı anlamına gelen HTTP 302 durum kodu ile yanıt verdi. Bu durum, bir .htaccess dosyasındaki özel kısıtlamalardan ya da OID İstemcisi gibi Nextcloud uygulamalarından kaynaklanabilir.</string>
<string name="error_dialog_version_not_parsable">Sunucu tarafındaki tahta uygulamasının sürümü belirlenemedi. Lütfen kurulmuş ve etkinleştirilmiş olduğundan emin olun.</string>
+ <string name="error_dialog_account_might_not_be_authorized">Nextcloud uygulamanızın hesabına artık izin verilmiyor olabilir.</string>
+ <string name="error_dialog_user_not_found_in_database">Geçerli kullanıcı veritabanımızdaki kullanıcı ile eşleşmiyor. Nextcloud kopyanızda LDAP kullanıyorsanız, Nextcloud uygulamanızda eski bir kullanıcı kodu kayıtlı olabilir.</string>
<string name="error_dialog_capabilities_not_parsable">Sunucunuzun özellikleri alınamadı. Lütfen sunucunuzun düzgün çalışır durumda olduğundan ve diğer istemci uygulamalarının Nextcloud sunucusuna erişebildiğinden emin olun.</string>
<string name="error_dialog_attachment_upload_failed">Bir ek dosya yüklenemedi. Lütfen dosyayı başka bir şekilde paylaşın ve bu hatayı bize bildirin.</string>
+ <string name="error_dialog_tip_disable_battery_optimizations">Lütfen Nextcloud ve Pano uygulaması için tüm pil iyileştirmelerini devre dışı bırakın.</string>
<string name="error_action_open_deck_info">Uygulama bilgilerini aç</string>
<string name="error_action_open_network">Ağ ayarları</string>
<string name="error_action_server_logs">Sunucu günlükleri</string>
<string name="error_action_install">Kur</string>
+ <string name="error_action_report_issue">Sorun bildirin</string>
<string name="info_box_maintenance_mode">Sunucu bakım kipinde</string>
<string name="info_box_version_not_supported">%1$s sunucu sürümü desteklenmiyor. Lütfen %2$s sürümüne güncelleyin</string>
<string name="share_link">Bağlantıyı paylaş</string>
<string name="archive_cards">Kartları arşivle</string>
+
<string name="manage_accounts">Hesap yönetimi</string>
<string name="manage_list">Liste yönetimi</string>
<string name="simple_reply">Yanıtla</string>
@@ -248,8 +246,54 @@
<string name="add_text_as_comment">Yorum olarak ekle</string>
<string name="progress_count">%1$d / %2$d</string>
<plurals name="progress_error_count">
- <item quantity="one">Yükleme sırasında %1$d sorun çıktı.</item>
- <item quantity="other">Yükleme sırasında %1$d sorun çıktı.</item>
+ <item quantity="one">Yükleme sırasında %1$d sorun çıktı</item>
+ <item quantity="other">Yükleme sırasında %1$d sorun çıktı</item>
</plurals>
<string name="simple_report">Hata bildirin</string>
+ <string name="error_action_open_battery_settings">Pil ayarları</string>
+ <string name="move_warning">Kart başka bir panoya taşınırken yorum ya da ek dosyalar aktarılamaz.</string>
+ <string name="clone_board">Panoyu kopyala</string>
+ <string name="cloning_board">%1$s kopyalanıyor…</string>
+ <string name="successfully_cloned_board">%1$s kopyalandı</string>
+ <string name="attachment_does_not_yet_exist">Ek dosya henüz panoda bulunmuyor</string>
+ <string name="card_does_not_yet_exist">Kart henüz panoda bulunmuyor</string>
+
+ <string name="widget_stack_title">Listele</string>
+ <string name="widget_stack_header_icon">Pano bileşeni başlık simgesi</string>
+ <string name="widget_stack_placeholder_icon">Pano bileşeni yer belirteci simgesi</string>
+ <string name="select_stack">Liste seçin</string>
+ <string name="project_type_deck_board">Tahta panosu</string>
+ <string name="project_type_deck_card">Tahta kartı</string>
+ <string name="project_type_file">Dosya</string>
+ <string name="projects_title">Projeler</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d kaynak</item>
+ <item quantity="other">%1$d kaynak</item>
+ </plurals>
+ <string name="no_assigned_label">Henüz bir etiket atanmamış</string>
+ <string name="single_card">Tek kart</string>
+ <string name="project_type_room">Talk odası</string>
+ <string name="simple_move">Taşı</string>
+ <string name="cannot_upload_files_without_permission">İzin olmadan dosya yüklenemez</string>
+ <string name="clone_cards">Kartları kopyala</string>
+ <string name="simple_clone">Kopyala</string>
+ <string name="user_avatar">Kullanıcı avatarı</string>
+ <string name="simple_unassign">Atamayı kaldır</string>
+ <string name="simple_contact">Kişi</string>
+ <string name="simple_file">Dosya</string>
+ <string name="simple_camera">Kamera</string>
+ <string name="min_api_21">Bu özellik için en az Android 5 sürümü gereklidir</string>
+ <string name="take_photo">Bir fotoğraf çekin</string>
+ <string name="take_photo_switch_camera">Kamerayı değiştir</string>
+ <string name="take_photo_toggle_torch">Feneri aç/kapat</string>
+ <string name="show_all_contacts">Tüm kişileri görüntüle</string>
+ <string name="show_all_files">Tüm dosyaları görüntüle</string>
+ <string name="recent">Son kullanılan</string>
+ <string name="upload_a_new_attachment">Ek dosya yükle</string>
+ <string name="contacts">Kişiler</string>
+ <string name="downloads">İndirmeler</string>
+ <string name="files">Dosyalar</string>
+ <string name="gallery">Galeri</string>
+ <string name="simple_attach">ekle</string>
+ <string name="add_stack_widget">Liste pano bileşeni ekle</string>
</resources>
diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml
index 024f60d62..572da1f8e 100644
--- a/app/src/main/res/values-uk/strings.xml
+++ b/app/src/main/res/values-uk/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">Останній обліковий запис</string>
<string name="drawer_manage_accounts">Облікові записи</string>
- <!-- Simple values -->
<string name="simple_boards">Дошки</string>
<string name="simple_add">Додати</string>
<string name="simple_save">Зберегти</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">вимкнено</string>
<string name="simple_copied">Скопійовано</string>
<string name="simple_archive">Архів</string>
+ <string name="simple_unassigned">Скасовано призначення</string>
<string name="edit_board">Редагувати дошку</string>
<string name="archive_board">Архівувати дошку</string>
<string name="delete_board">Вилучити дошку</string>
<string name="delete_something">Вилучити %1$s</string>
- <!-- About -->
<string name="about">Про програму</string>
<string name="about_version_title">Версія</string>
<string name="about_version">Ви використовуєте%1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">Подяки</string>
<string name="about_contribution_tab_title">Внесок</string>
<string name="about_license_tab_title">Ліцензія</string>
-
- <string name="copied_to_clipboard">Скопійовано</string>
-
<string name="seconds_ago">секунд тому</string>
<string name="edit">Редагувати</string>
<string name="label_labels">Вибрати позначки</string>
@@ -112,6 +108,7 @@
<string name="not_synced_yet">Поки не синхронізовано</string>
<string name="no_lists_yet">Поки що відсутні списки</string>
<string name="do_you_want_to_save_your_changes">Зберегти зміни?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_list">Дійсно заархівувати всі картки з %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">Це вилучіть %1$d картку з цього списку.</item>
<item quantity="few">Це вилучіть усі %1$d карток з цього списку.</item>
@@ -159,7 +156,6 @@
<string name="maintenance_mode_explanation">Зараз сервер %1$s у режимі технічного обслуговування. Будь ласка, сконтактуйте з вашим адміністратором або спробуйте пізніше.</string>
<string name="share_add_to_card">Додати до картки</string>
<string name="share_success">Успішно додано %1$s до %2$s</string>
- <string name="could_not_copy_to_clipboard">Неможливо скопіювати</string>
<string name="add_comment">Додати коментар</string>
<string name="card_edit_comments">Коментарі</string>
<string name="no_comments_yet">Коментарі відсутні</string>
@@ -171,11 +167,9 @@
<string name="open_in_browser">Відкрити у переглядачі</string>
<string name="updating_card">Оновлюю картку...</string>
- <!-- Move lists -->
<string name="move_list_right">Пересунути список праворуч</string>
<string name="move_list_left">Пересунути список ліворуч</string>
- <!-- Filter -->
<string name="filter_no_filter">Всі</string>
<string name="filter_overdue">Протерміновано</string>
<string name="filter_today">Сьогодні</string>
@@ -186,7 +180,6 @@
<string name="filter_by_assigned_user">Відібрати за призначеним користувачем</string>
<string name="filter_by_duedate">Відібрати за датою завершення</string>
- <!-- Archived cards -->
<string name="archived_cards">Заархівовані картки</string>
<string name="action_card_dearchive">Скасувати архівування картки</string>
<string name="action_archived_cards">Перегляд заархівованих карток</string>
@@ -201,11 +194,9 @@
<string name="filter_user_title">Користувачі</string>
<string name="filter_duedate_title">Дата завершення</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Скасувати архівування дошки</string>
<string name="archived_boards">Архівовані дошки</string>
- <!-- Errors -->
<string name="error">Сталася помилка </string>
<string name="synchronization_failed">Синхронізація не вдалася</string>
<string name="operation_not_yet_supported">Ще не підтримується</string>
@@ -215,17 +206,49 @@
<string name="server_misconfigured">Сервер налаштовано некоректно</string>
<string name="server_error">Помилка серверу</string>
<string name="shared_error">Спільний доступ до вмісту з інших застосунків ще не підтримується повністю. Будь ласка, спробуйте спочатку звантажити цей вміст, а потім поділитися ним через вбудований менеджер файлів або галерею.</string>
+ <string name="error_edit_activity_killed_by_android">Android вивантажив режим редагування через нестачу системних ресурсів.</string>
+
<string name="error_dialog_title">От халепа!</string>
<string name="error_dialog_tip_token_mismatch_retry">Спробуйте примусово закрити застосунок та наново запустити. Можливо, встановлено неправильне з\'єднання із застосунком Nextcloud.</string>
+ <string name="error_dialog_tip_clear_storage">Ви можете очистити сховище, відкривши інформацію про програму та вибравши Пам’ять → Очистити пам’ять.</string>
+ <string name="error_dialog_tip_files_outdated">Ваш додаток Nextcloud здається застарілим. Будь ласка, відвідайте Play Маркет або F-Droid, щоб отримати останню версію.</string>
+ <string name="error_dialog_tip_files_delete_storage">Якщо примусове їх завершення не допомагає, спробуйте очистити сховище обох програм.</string>
+ <string name="error_dialog_timeout_instance">За відведений час відповіді від вашого сервера не надійшло. Переконайтесь, що ваш сервер працює добре.</string>
+ <string name="error_dialog_check_server_logs">Виникла проблема з налаштуванням Nextcloud. Будь ласка, подивіться файли журналу сервера.</string>
+ <string name="error_dialog_check_maintenance">Будь ласка, перевірте, чи ваш сервер Nextcloud наразі не перебуває в режимі обслуговування.</string>
<string name="error_dialog_insufficient_storage">Ваш сервер Nextcloud не має вільного місця. Будь ласка видаліть деякі файлі, щоб синхронізувати ваші локальні зміни з вашим хмарним сховищем.</string>
+ <string name="error_dialog_we_need_info">Нам потрібна наступна технічна інформація, щоб допомогти вам:</string>
<string name="error_action_open_deck_info">Відкрити інформацію про застосунок</string>
<string name="error_action_open_network">Налаштування мережі</string>
<string name="error_action_server_logs">Журнал сервера</string>
<string name="error_action_install">Встановити</string>
+ <string name="error_action_report_issue">Звіт</string>
<string name="info_box_maintenance_mode">Сервер у режимі обслуговування</string>
<string name="info_box_version_not_supported">Версія сервера %1$s не підтримується, будь ласка, поновіться до %2$s</string>
<string name="share_link">Поширити посилання</string>
+ <string name="archive_cards">Архівувати картки</string>
+
<string name="manage_accounts">Облікові записи</string>
+ <string name="manage_list">Керувати списком</string>
<string name="simple_reply">Відповісти</string>
+ <string name="error_while_uploading_attachment">Помилка під час завантаження вкладення: %1$s</string>
+ <string name="append_text_to_description">Додати до опису</string>
+ <string name="add_text_as_comment">Додати коментар</string>
+ <string name="progress_count">%1$d з %2$d</string>
<string name="simple_report">Звіт</string>
-</resources>
+ <string name="error_action_open_battery_settings">Налаштування батареї</string>
+ <string name="move_warning">У разі переміщення картки до іншої дошки коментарі та вкладення не буде перенесено.</string>
+ <string name="clone_board">Копіювати дошку</string>
+ <string name="cloning_board">Копіювання %1$s...</string>
+ <string name="successfully_cloned_board">Успішно скопійовано %1$s</string>
+ <string name="attachment_does_not_yet_exist">Вкладення поки відсутнє у застосунку</string>
+ <string name="card_does_not_yet_exist">Картка поки відсутня у застосунку</string>
+
+ <string name="widget_stack_title">Список</string>
+ <string name="widget_stack_header_icon">Піктограма заголовку віджету</string>
+ <string name="widget_stack_placeholder_icon">Піктограма поля віджету</string>
+ <string name="select_stack">Виберіть список</string>
+ <string name="project_type_file">Файл</string>
+ <string name="projects_title">Проєкти</string>
+ <string name="simple_move">Перемістити</string>
+ </resources>
diff --git a/app/src/main/res/values-v21/styles.xml b/app/src/main/res/values-v21/styles.xml
new file mode 100644
index 000000000..61e9c4157
--- /dev/null
+++ b/app/src/main/res/values-v21/styles.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="AppBottomSheetDialogTheme" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog">
+ <item name="bottomSheetStyle">@style/AppModalStyle</item>
+ <item name="android:windowIsFloating">false</item>
+ <item name="android:navigationBarColor">@color/primary</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ </style>
+</resources> \ No newline at end of file
diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml
index 6e19325ec..20f7d4b05 100644
--- a/app/src/main/res/values-zh-rCN/strings.xml
+++ b/app/src/main/res/values-zh-rCN/strings.xml
@@ -8,7 +8,6 @@
<string name="drawer_end_account">最后一个账号</string>
<string name="drawer_manage_accounts">管理账号</string>
- <!-- Simple values -->
<string name="simple_boards">面板</string>
<string name="simple_add">添加</string>
<string name="simple_save">保存</string>
@@ -38,13 +37,13 @@
<string name="simple_disabled">已禁用</string>
<string name="simple_copied">已复制</string>
<string name="simple_archive">归档</string>
+ <string name="simple_unassigned">未分配</string>
<string name="edit_board">编辑面板</string>
<string name="archive_board">归档面板</string>
<string name="delete_board">删除面板</string>
<string name="delete_something">删除 %1$s</string>
- <!-- About -->
<string name="about">关于</string>
<string name="about_version_title">版本</string>
<string name="about_version">您当前正在使用 %1$s</string>
@@ -70,9 +69,6 @@
<string name="about_credits_tab_title">致谢</string>
<string name="about_contribution_tab_title">贡献</string>
<string name="about_license_tab_title">授权</string>
-
- <string name="copied_to_clipboard">复制到剪贴板</string>
-
<string name="seconds_ago">几秒前</string>
<string name="edit">编辑</string>
<string name="label_labels">选择标签</string>
@@ -153,7 +149,6 @@
<string name="maintenance_mode_explanation">服务器 %1$s 正处于维护模式。请联系你的管理员或稍后再试。</string>
<string name="share_add_to_card">添加到卡片</string>
<string name="share_success">成功将 %1$s 添加到 %2$s</string>
- <string name="could_not_copy_to_clipboard">无法复制到剪贴板</string>
<string name="add_comment">添加评论</string>
<string name="card_edit_comments">评论</string>
<string name="no_comments_yet">还没有评论</string>
@@ -165,11 +160,9 @@
<string name="open_in_browser">在浏览器中打开</string>
<string name="updating_card">正在更新卡片…</string>
- <!-- Move lists -->
<string name="move_list_right">将列表移动到右侧</string>
<string name="move_list_left">将列表移动到左侧</string>
- <!-- Filter -->
<string name="filter_no_filter">全部</string>
<string name="filter_overdue">逾期</string>
<string name="filter_today">今天</string>
@@ -180,7 +173,6 @@
<string name="filter_by_assigned_user">根据指定用户过滤</string>
<string name="filter_by_duedate">根据截止日期过滤</string>
- <!-- Archived cards -->
<string name="archived_cards">已存档卡片</string>
<string name="action_card_dearchive">取消存档卡片</string>
<string name="action_archived_cards">浏览已存档卡片</string>
@@ -197,7 +189,6 @@
<string name="archived_boards">已归档面板</string>
- <!-- Errors -->
<string name="error">发生了一个错误</string>
<string name="synchronization_failed">同步失败</string>
<string name="operation_not_yet_supported">尚未支持</string>
@@ -209,19 +200,34 @@
<string name="shared_error">目前还不完全支持从第三方应用程序中分享内容。请尝试先下载,然后从您的本地文件管理器或图库中分享。</string>
<string name="error_dialog_title">哦,糟糕。所以会怎么样?🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">请尝试强行关闭应用并重新打开。可能与 Nextcloud 应用的连接不正确。</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">如果问题持续存在,尝试通过清除 Nextcloud 和 Nextcloud 看板两个应用的存储空间来解决问题。</string>
+ <string name="error_dialog_tip_clear_storage_might_help">如果问题持续存在,尝试通过清除 Nextcloud 和 Nextcloud 看板两个应用的存储空间来解决问题。</string>
<string name="error_dialog_tip_files_outdated">您的 Nextcloud 应用似乎已经过时了。请访问 Play 商店或 F-Droid 以获取最新版本。</string>
<string name="error_dialog_tip_files_force_stop">您的 Nextcloud 应用似乎出现了问题。请尝试强行停止 Nextcloud 和 Nextcloud 看板两个应用。</string>
<string name="error_dialog_tip_files_delete_storage">如果强行停止没有效果,您可以尝试清除两个应用的存储空间。</string>
<string name="error_dialog_timeout_instance">服务器没有在规定的时间内回答。请确保您的服务器实例在正常运行。</string>
+ <string name="error_dialog_timeout_toggle">检查您的网络连接。有时候关闭并重新开启移动数据或 Wi-Fi 有帮助。</string>
<string name="error_dialog_check_server">服务器的回答不正确。请检查您是否能通过网页访问看板应用。</string>
<string name="error_dialog_check_server_logs">您的 Nextcloud 配置有一些问题。请查看服务器日志。</string>
<string name="error_dialog_check_maintenance">请检查您的 Nextcloud 实例是否处于维护模式。</string>
<string name="error_dialog_insufficient_storage">您的 Nextcloud 实例已经没有可用的存储空间了。请删除一些文件来允许本地的变更同步到云。</string>
<string name="error_dialog_we_need_info">我们需要以下技术信息来帮助您:</string>
+ <string name="error_dialog_account_might_not_be_authorized">您的Nextcloud应用账号可能不再被授权。</string>
+ <string name="error_action_open_network">网络设置</string>
+ <string name="error_action_server_logs">服务器日志</string>
+ <string name="error_action_install">安装</string>
+ <string name="error_action_report_issue">报告</string>
<string name="info_box_maintenance_mode">服务器处于维护模式</string>
<string name="share_link">共享链接</string>
<string name="manage_accounts">管理账号</string>
<string name="simple_reply">回复</string>
<string name="simple_report">报告</string>
-</resources>
+ <string name="error_action_open_battery_settings">电池设置</string>
+ <string name="clone_board">克隆面板</string>
+ <string name="widget_stack_title">列表</string>
+ <string name="project_type_file">文件</string>
+ <string name="projects_title">项目</string>
+ <string name="simple_move">移动</string>
+ <string name="simple_clone">克隆</string>
+ <string name="user_avatar">用户头像</string>
+ <string name="simple_unassign">取消分配</string>
+ </resources>
diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml
index 6db0c7441..d7907027a 100644
--- a/app/src/main/res/values/colors.xml
+++ b/app/src/main/res/values/colors.xml
@@ -3,19 +3,18 @@
<color name="primary">@android:color/white</color>
<color name="accent">#000000</color>
<color name="defaultBrand">#0082C9</color>
- <color name="toolbarEditTextHighlightColor">#55ffffff</color>
<color name="danger">#d40000</color>
- <color name="fg_accent">#fff</color>
- <color name="fg_primary">#333</color>
<color name="fg_secondary">#999</color>
<color name="bg_highlighted">#eee</color>
<color name="grey600">#757575</color>
<color name="bg_info_box">#dddddd</color>
<color name="bg_card">@android:color/white</color>
<color name="bg_card_wrapper">#fafafa</color>
-
<color name="dark_fg_primary">#e5e5e5</color>
+ <color name="activity_create">#00D400</color>
+ <color name="activity_delete">#D40000</color>
+
<!-- due date colors -->
<color name="due_tomorrow">#7fffc53a</color>
diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml
index db8b51610..c19fe8de1 100644
--- a/app/src/main/res/values/dimens.xml
+++ b/app/src/main/res/values/dimens.xml
@@ -1,8 +1,13 @@
<resources>
+ <dimen name="spacer_1qx">2dp</dimen>
<dimen name="spacer_1hx">4dp</dimen>
<dimen name="spacer_1x">8dp</dimen>
<dimen name="spacer_2x">16dp</dimen>
<dimen name="spacer_3x">24dp</dimen>
+ <dimen name="spacer_4x">32dp</dimen>
+
+ <dimen name="compact_label_height">6dp</dimen>
+ <dimen name="attachments_bottom_navigation_height">64dp</dimen>
<!-- Drawer header -->
<dimen name="drawer_header_height">100dp</dimen>
@@ -16,11 +21,9 @@
<dimen name="avatar_size_small_overlapping_border">2dp</dimen>
<dimen name="icon_size_details">24dp</dimen>
- <dimen name="nav_drawer_header_avatar">56dp</dimen>
- <dimen name="nav_drawer_header_avatar_other_accounts_size">40dp</dimen>
- <dimen name="nav_drawer_header_avatar_second_account_margin">56dp</dimen>
- <dimen name="drawer_header_text">14sp</dimen>
- <dimen name="drawer_header_subtext">12sp</dimen>
- <dimen name="text_size_attachments">14sp</dimen>
<dimen name="empty_content_font_size">26sp</dimen>
+
+ <dimen name="widget_stack_card_padding">@dimen/spacer_1hx</dimen>
+ <dimen name="widget_stack_icon_width">30dp</dimen>
+ <dimen name="widget_stack_header_height">36dp</dimen>
</resources> \ No newline at end of file
diff --git a/app/src/main/res/values/setup.xml b/app/src/main/res/values/setup.xml
index 247bb0697..c790a5868 100644
--- a/app/src/main/res/values/setup.xml
+++ b/app/src/main/res/values/setup.xml
@@ -8,6 +8,7 @@
<string name="pref_key_wifi_only" translatable="false">wifiOnly</string>
<string name="pref_key_dark_theme" translatable="false">darkTheme</string>
<string name="pref_key_branding" translatable="false">branding</string>
+ <string name="pref_key_compact" translatable="false">compact</string>
<string name="pref_key_background_sync" translatable="false">backgroundSync</string>
<string name="pref_value_background_sync_off">off</string>
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index 256f9bc91..8b2b4efcb 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -10,7 +10,6 @@
<string name="drawer_end_account">Last account</string>
<string name="drawer_manage_accounts">Manage accounts</string>
- <!-- Simple values -->
<string name="simple_boards">Boards</string>
<string name="simple_add">Add</string>
<string name="simple_save">Save</string>
@@ -40,13 +39,13 @@
<string name="simple_disabled">disabled</string>
<string name="simple_copied">Copied</string>
<string name="simple_archive">Archive</string>
+ <string name="simple_unassigned">Unassigned</string>
<string name="edit_board">Edit board</string>
<string name="archive_board">Archive board</string>
<string name="delete_board">Delete board</string>
<string name="delete_something">Delete %1$s</string>
- <!-- About -->
<string name="about">About</string>
<string name="about_version_title">Version</string>
<string name="about_version">You are currently using %1$s</string>
@@ -76,9 +75,6 @@
<string name="about_credits_tab_title">Credits</string>
<string name="about_contribution_tab_title">Contribution</string>
<string name="about_license_tab_title">License</string>
-
- <string name="copied_to_clipboard">Copied to clipboard</string>
-
<string name="seconds_ago">seconds ago</string>
<string name="edit">Edit</string>
<string name="label_labels">Select Tags</string>
@@ -109,7 +105,6 @@
<string name="label_clear_due_date">Clear due date</string>
<string name="label_add">Add %1$s</string>
- <!-- URLs -->
<string name="url_maintainer" translatable="false">https://www.niedermann.it/</string>
<string name="url_about_icon_author" translatable="false">https://github.com/nextcloud/deck/commit/8c04ea8dc99e9b392f4039e8e5e6964d5a6d3453#diff-f3716cc279904617b1a21078526b6bf1R1</string>
<string name="url_source" translatable="false">https://github.com/stefan-niedermann/nextcloud-deck</string>
@@ -138,6 +133,7 @@
<string name="no_lists_yet">No lists yet</string>
<string name="do_you_want_to_save_your_changes">Do you want to save your changes?</string>
<string name="do_you_want_to_archive_all_cards_of_the_list">Do you want to archive all cards of %1$s?</string>
+ <string name="do_you_want_to_archive_all_cards_of_the_filtered_list">Do you want to archive all filtered cards of %1$s?</string>
<plurals name="do_you_want_to_delete_the_current_list">
<item quantity="one">This will permanently delete %1$d card of this list.</item>
<item quantity="other">This will permanently delete all %1$d cards of this list.</item>
@@ -154,6 +150,7 @@
<string name="delete_board_message">This will permanently delete this board including all lists and cards.</string>
<string name="settings_theme_title">Dark theme</string>
<string name="settings_branding_title">Branding</string>
+ <string name="settings_compact_title">Compact mode</string>
<string name="settings_background_sync">Background synchronization</string>
<string name="pref_value_wifi_and_mobile">Sync on Wi-Fi and mobile data</string>
<string name="pref_value_wifi_only">Sync only on Wi-Fi</string>
@@ -181,7 +178,6 @@
<string name="maintenance_mode_explanation">The server %1$s is currently in maintenance mode. Please contact your administrator or try later again.</string>
<string name="share_add_to_card">Add to card</string>
<string name="share_success">Successfully added %1$s to %2$s</string>
- <string name="could_not_copy_to_clipboard">Could not copy to clipboard</string>
<string name="add_comment">Add comment</string>
<string name="card_edit_comments">Comments</string>
<string name="no_comments_yet">No comments yet</string>
@@ -193,11 +189,9 @@
<string name="open_in_browser">Open in browser</string>
<string name="updating_card">Updating card…</string>
- <!-- Move lists -->
<string name="move_list_right">Move list right</string>
<string name="move_list_left">Move list left</string>
- <!-- Filter -->
<string name="filter_no_filter">All</string>
<string name="filter_overdue">Overdue</string>
<string name="filter_today">Today</string>
@@ -208,7 +202,6 @@
<string name="filter_by_assigned_user">Filter by assigned user</string>
<string name="filter_by_duedate">Filter by due date</string>
- <!-- Archived cards -->
<string name="archived_cards">Archived cards</string>
<string name="action_card_dearchive">Undo card archiving</string>
<string name="action_archived_cards">Browse archived cards</string>
@@ -223,11 +216,9 @@
<string name="filter_user_title">Users</string>
<string name="filter_duedate_title">Due date</string>
- <!-- Archived boards -->
<string name="action_board_dearchive">Undo board archiving</string>
<string name="archived_boards">Archived boards</string>
- <!-- Errors -->
<string name="error">An error appeared</string>
<string name="synchronization_failed">Synchronization failed</string>
<string name="operation_not_yet_supported">Not yet supported</string>
@@ -241,8 +232,8 @@
<string name="error_dialog_title">Oh no - What now? 🙁</string>
<string name="error_dialog_tip_token_mismatch_retry">Please try to force close the app and restart it again. There might have been an incorrect connection to the Nextcloud app.</string>
- <string name="error_dialog_tip_token_mismatch_clear_storage">If the issue persists, try to clear the storage of both apps: Nextcloud and Nextcloud Deck to solve this issue.</string>
- <string name="error_dialog_tip_database_upgrade_failed">The ugprade of the database failed. Please report the issue and clear the storage to use the app normally.</string>
+ <string name="error_dialog_tip_clear_storage_might_help">If the issue persists, try to clear the storage of both apps: Nextcloud and Nextcloud Deck to solve this issue.</string>
+ <string name="error_dialog_tip_database_upgrade_failed">The upgrade of the database failed. Please report the issue and clear the storage to use the app normally.</string>
<string name="error_dialog_tip_clear_storage">You can clear the storage by opening the app info and selecting Storage → Clear storage.</string>
<string name="error_dialog_tip_files_outdated">Your Nextcloud app seems to be outdated. Please visit the Play Store or F-Droid to get the latest version.</string>
<string name="error_dialog_tip_files_force_stop">Something seems to be wrong with your Nextcloud app. Please try to force stop both, the Nextcloud app and the Nextcloud Deck app.</string>
@@ -256,6 +247,8 @@
<string name="error_dialog_we_need_info">We need the following technical information to help you:</string>
<string name="error_dialog_redirect">Your server did respond with a HTTP 302 status code, which implies, that you do not have installed the Deck app on your server or something is misconfigured. This can be caused by custom overrides in a .htaccess-file or by Nextcloud apps like OID Client.</string>
<string name="error_dialog_version_not_parsable">We could not determine the version of the server side Deck app. Please make sure it is installed and enabled.</string>
+ <string name="error_dialog_account_might_not_be_authorized">The account of your Nextcloud app might no longer be authorized.</string>
+ <string name="error_dialog_user_not_found_in_database">The current user does not match the user we have in our database. If you are using LDAP on your Nextcloud instance, your Nextcloud app might have stored an old user ID.</string>
<string name="error_dialog_capabilities_not_parsable">We could not fetch the capabilities of your server. Please make sure your server is running well and other client apps are able to access Nextcloud.</string>
<string name="error_dialog_attachment_upload_failed">An attachment could not be uploaded. Please try to share it on another way and let us know about this bug.</string>
<string name="error_dialog_tip_disable_battery_optimizations">Please disable all battery optimizations for Nextcloud and the Deck app.</string>
@@ -268,6 +261,7 @@
<string name="info_box_version_not_supported">Server version %1$s not supported, please update to %2$s</string>
<string name="share_link">Share link</string>
<string name="archive_cards">Archive cards</string>
+
<string name="manage_accounts">Manage accounts</string>
<string name="manage_list">Manage list</string>
<string name="simple_reply">Reply</string>
@@ -276,9 +270,54 @@
<string name="add_text_as_comment">Add as comment</string>
<string name="progress_count">%1$d of %2$d</string>
<plurals name="progress_error_count">
- <item quantity="one">%1$d error while uploading.</item>
+ <item quantity="one">%1$d error while uploading</item>
<item quantity="other">%1$d errors while uploading</item>
</plurals>
<string name="simple_report">Report</string>
<string name="error_action_open_battery_settings">Battery settings</string>
+ <string name="move_warning">Neither comments nor attachments can be transferred when moving the card to another board.</string>
+ <string name="clone_board">Clone board</string>
+ <string name="cloning_board">Cloning %1$s…</string>
+ <string name="successfully_cloned_board">Successfully cloned %1$s</string>
+ <string name="attachment_does_not_yet_exist">Attachment does not yet exist in Deck</string>
+ <string name="card_does_not_yet_exist">Card does not yet exist in Deck</string>
+
+ <string name="widget_stack_title">List</string>
+ <string name="widget_stack_header_icon">Widget header icon</string>
+ <string name="widget_stack_placeholder_icon">Widget placeholder icon</string>
+ <string name="select_stack">Select list</string>
+ <string name="project_type_deck_board">Deck board</string>
+ <string name="project_type_deck_card">Deck card</string>
+ <string name="project_type_file">File</string>
+ <string name="projects_title">Projects</string>
+ <plurals name="resources_count">
+ <item quantity="one">%1$d resource</item>
+ <item quantity="other">%1$d resources</item>
+ </plurals>
+ <string name="no_assigned_label">No assigned tag</string>
+ <string name="single_card">Single card</string>
+ <string name="project_type_room">Talk room</string>
+ <string name="simple_move">Move</string>
+ <string name="cannot_upload_files_without_permission">Can not upload files without permission</string>
+ <string name="clone_cards">Clone cards</string>
+ <string name="simple_clone">Clone</string>
+ <string name="user_avatar">User avatar</string>
+ <string name="simple_unassign">Unassign</string>
+ <string name="simple_contact">Contact</string>
+ <string name="simple_file">File</string>
+ <string name="simple_camera">Camera</string>
+ <string name="min_api_21">This feature requires at least Android 5</string>
+ <string name="take_photo">Take a photo</string>
+ <string name="take_photo_switch_camera">Switch camera</string>
+ <string name="take_photo_toggle_torch">Toggle torch</string>
+ <string name="show_all_contacts">Show all contacts</string>
+ <string name="show_all_files">Show all files</string>
+ <string name="recent">Recent</string>
+ <string name="upload_a_new_attachment">Upload a new attachment</string>
+ <string name="contacts">Contacts</string>
+ <string name="downloads">Downloads</string>
+ <string name="files">Files</string>
+ <string name="gallery">Gallery</string>
+ <string name="simple_attach">attach</string>
+ <string name="add_stack_widget">Add list widget</string>
</resources>
diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml
index f0bff78fe..9881f8e76 100644
--- a/app/src/main/res/values/styles.xml
+++ b/app/src/main/res/values/styles.xml
@@ -6,6 +6,16 @@
<item name="colorAccent">@color/accent</item>
<item name="toolbarStyle">@style/toolbarStyle</item>
<item name="android:windowBackground">?attr/colorPrimary</item>
+ <item name="textAppearanceHeadline1">@style/Deck.TextAppearance.Headline1</item>
+ <item name="bottomSheetDialogTheme">@style/AppBottomSheetDialogTheme</item>
+ </style>
+
+ <style name="AppBottomSheetDialogTheme" parent="Theme.MaterialComponents.DayNight.BottomSheetDialog">
+ <item name="bottomSheetStyle">@style/AppModalStyle</item>
+ </style>
+
+ <style name="AppModalStyle" parent="Widget.Design.BottomSheet.Modal">
+ <item name="android:background">@drawable/bottom_sheet_rounded</item>
</style>
<style name="toolbarStyle" parent="@style/Widget.AppCompat.Toolbar">
@@ -32,4 +42,13 @@
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
</style>
+
+ <style name="TakePhotoTheme" parent="TransparentTheme">
+ <item name="android:windowFullscreen">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ </style>
+
+ <style name="Deck.TextAppearance.Headline1" parent="TextAppearance.MaterialComponents.Headline1">
+ <item name="android:textSize">36sp</item>
+ </style>
</resources>
diff --git a/app/src/main/res/xml/settings.xml b/app/src/main/res/xml/settings.xml
index 14943fff6..6d67622bd 100644
--- a/app/src/main/res/xml/settings.xml
+++ b/app/src/main/res/xml/settings.xml
@@ -29,6 +29,12 @@
android:icon="@drawable/ic_format_paint_grey600_24dp"
android:key="@string/pref_key_branding"
android:title="@string/settings_branding_title"
- app:defaultValue="true" />
+ app:defaultValue="false" />
+
+ <it.niedermann.nextcloud.deck.ui.branding.BrandedSwitchPreference
+ android:icon="@drawable/ic_baseline_compact_24"
+ android:key="@string/pref_key_compact"
+ android:title="@string/settings_compact_title"
+ app:defaultValue="false" />
</it.niedermann.nextcloud.deck.ui.branding.BrandedPreferenceCategory>
</PreferenceScreen>
diff --git a/app/src/main/res/xml/stack_widget_provider.xml b/app/src/main/res/xml/stack_widget_provider.xml
new file mode 100644
index 000000000..3712625da
--- /dev/null
+++ b/app/src/main/res/xml/stack_widget_provider.xml
@@ -0,0 +1,12 @@
+<?xml version="1.0" encoding="utf-8"?>
+<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
+ android:initialLayout="@layout/widget_stack"
+ android:minHeight="110dp"
+ android:minWidth="180dp"
+ android:minResizeHeight="40dp"
+ android:minResizeWidth="80dp"
+ android:resizeMode="vertical|horizontal"
+ android:updatePeriodMillis="86400000"
+ android:widgetCategory="keyguard|home_screen"
+ android:previewImage="@drawable/widget_stack_preview"
+ android:configure="it.niedermann.nextcloud.deck.ui.widget.stack.StackWidgetConfigurationActivity" />
diff --git a/app/src/main/widget_stack_preview-playstore.png b/app/src/main/widget_stack_preview-playstore.png
new file mode 100644
index 000000000..fdd955b2c
--- /dev/null
+++ b/app/src/main/widget_stack_preview-playstore.png
Binary files differ
diff --git a/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java b/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java
new file mode 100644
index 000000000..47a23392e
--- /dev/null
+++ b/app/src/test/java/it/niedermann/nextcloud/deck/CommentsUtilTest.java
@@ -0,0 +1,41 @@
+package it.niedermann.nextcloud.deck;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import it.niedermann.nextcloud.deck.ui.card.comments.util.CommentsUtil;
+
+public class CommentsUtilTest {
+
+ @Test
+ public void testMentionDiscovery() {
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("", 0));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("a ", 1));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("a ", 2));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("@a ", 3));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("d@a", 2));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("d@a", 0));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("d@a", 3));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("ab", 0));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("ab", 1));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("ab", 2));
+ Assert.assertNull(CommentsUtil.getUserNameForMentionProposal("def @ab ", 8));
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("\n@ab", 3).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("\n@ab", 4).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("\t@ab", 3).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal(" @ab", 3).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("@ab", 3).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("@ab", 2).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("@ab def", 2).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("@ab def", 3).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("def @ab ", 5).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("def @ab", 6).first);
+ Assert.assertEquals("ab_asdf_ldklsdkf", CommentsUtil.getUserNameForMentionProposal("def @ab_asdf_ldklsdkf", 7).first);
+ Assert.assertEquals("ab_asdf_ldklsdkf", CommentsUtil.getUserNameForMentionProposal("def @ab_asdf_ldklsdkf ", 7).first);
+ Assert.assertEquals("\"ab_asdf_ldklsdkf\"", CommentsUtil.getUserNameForMentionProposal("def @\"ab_asdf_ldklsdkf\"", 7).first);
+ Assert.assertEquals("\"ab_asdf_ldklsdkf\"", CommentsUtil.getUserNameForMentionProposal("def @\"ab_asdf_ldklsdkf\" \nasdf", 7).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("def @ab\n", 7).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("\ndef @ab\n", 8).first);
+ Assert.assertEquals("ab", CommentsUtil.getUserNameForMentionProposal("\n def @ab\nasdfasdf", 9).first);
+ }
+}
diff --git a/app/src/test/java/it/niedermann/nextcloud/deck/DueDateUnitTest.java b/app/src/test/java/it/niedermann/nextcloud/deck/DueDateUnitTest.java
deleted file mode 100644
index f4c3391ad..000000000
--- a/app/src/test/java/it/niedermann/nextcloud/deck/DueDateUnitTest.java
+++ /dev/null
@@ -1,75 +0,0 @@
-package it.niedermann.nextcloud.deck;
-
-import org.junit.Test;
-
-import java.util.Calendar;
-import java.util.Date;
-import java.util.GregorianCalendar;
-
-import it.niedermann.nextcloud.deck.util.DateUtil;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
-
-/**
- * Local unit tests for due date calculations (overdue, today, tomorrow, far in the future).
- *
- * @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
- */
-public class DueDateUnitTest {
- @Test
- public void dueDateIsOverdue() {
- Calendar calendar = new GregorianCalendar();
- calendar.set(2018, 12, 10);
- Date due = calendar.getTime();
-
- calendar.set(2018, 12, 11);
- Date actual = calendar.getTime();
-
- long diff = DateUtil.getDayDifference(actual, due);
-
- assertEquals(-1, diff);
- }
-
- @Test
- public void dueDateIsToday() {
- Calendar calendar = new GregorianCalendar();
- calendar.set(2018, 12, 11);
- Date due = calendar.getTime();
-
- calendar.set(2018, 12, 11);
- Date actual = calendar.getTime();
-
- long diff = DateUtil.getDayDifference(actual, due);
-
- assertEquals(0, diff);
- }
-
- @Test
- public void dueDateIsTomorrow() {
- Calendar calendar = new GregorianCalendar();
- calendar.set(2018, 12, 12);
- Date due = calendar.getTime();
-
- calendar.set(2018, 12, 11);
- Date actual = calendar.getTime();
-
- long diff = DateUtil.getDayDifference(actual, due);
-
- assertEquals(1, diff);
- }
-
- @Test
- public void dueDateIsInTheFarFuture() {
- Calendar calendar = new GregorianCalendar();
- calendar.set(2018, 12, 20);
- Date due = calendar.getTime();
-
- calendar.set(2018, 12, 11);
- Date actual = calendar.getTime();
-
- long diff = DateUtil.getDayDifference(actual, due);
-
- assertTrue(diff > 1);
- }
-} \ No newline at end of file
diff --git a/app/src/test/java/it/niedermann/nextcloud/deck/ProjectUtilTest.java b/app/src/test/java/it/niedermann/nextcloud/deck/ProjectUtilTest.java
new file mode 100644
index 000000000..1f5f5e602
--- /dev/null
+++ b/app/src/test/java/it/niedermann/nextcloud/deck/ProjectUtilTest.java
@@ -0,0 +1,124 @@
+package it.niedermann.nextcloud.deck;
+
+import org.junit.Test;
+
+import it.niedermann.nextcloud.deck.util.ProjectUtil;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThrows;
+
+public class ProjectUtilTest {
+ @Test
+ public void extractBoardIdAndCardIdFromUrl() {
+ // Valid board URLs with # and with index.php
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("index.php/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/index.php/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/index.php/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/index.php/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/#/board/4/card"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/#/board/4/card/"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/#/board/4/card/0"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/#/board/4/foo"), new long[]{4});
+
+ // Valid board URLs with # and without index.php
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/#/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/#/board/4/card"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/#/board/4/card/"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/#/board/4/card/0"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/#/board/4/foo"), new long[]{4});
+
+ // Valid board URLs without # and with index.php
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("index.php/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/index.php/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/index.php/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/index.php/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/board/4/card"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/board/4/card/"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/board/4/card/0"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/board/4/foo"), new long[]{4});
+
+ // Valid board URLs without # and without index.php
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/board/4"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/board/4/card"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/board/4/card/"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/board/4/card/0"), new long[]{4});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/board/4/foo"), new long[]{4});
+
+ // Valid card URLs with # and with index.php
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/index.php/apps/deck/#/board/4/card/6/"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+
+ // Valid card URLs with # and without index.php
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/apps/deck/#/board/4/card/6/"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+
+ // Valid card URLs without # and with index.php
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/index.php/apps/deck/#/board/4/card/6/"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+
+ // Valid card URLs without # and without index.php
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/apps/deck/#/board/4/card/6/"), new long[]{4, 6});
+ assertArrayEquals(ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/apps/deck/#/board/4/card/6"), new long[]{4, 6});
+
+ // URLs to talk
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/call/qkzhe5k2"));
+
+ // URLs to files
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("example.com/index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("http://example.com/index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/index.php/call/qkzhe5k2"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("https://example.com/nextcloud/index.php/call/qkzhe5k2"));
+
+ // Invalid URLs
+ //noinspection ConstantConditions
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl(null));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl(""));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/#/board/0"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/#/board/0/card/3"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/#/board//card/3"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/board/0"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/board/0/card/3"));
+ assertThrows(IllegalArgumentException.class, () -> ProjectUtil.extractBoardIdAndCardIdFromUrl("/index.php/apps/deck/board//card/3"));
+ }
+}
diff --git a/build.gradle b/build.gradle
index d46acabb4..cfa20ac4a 100644
--- a/build.gradle
+++ b/build.gradle
@@ -7,7 +7,7 @@ buildscript {
jcenter()
}
dependencies {
- classpath 'com.android.tools.build:gradle:4.0.0'
+ classpath 'com.android.tools.build:gradle:4.1.1'
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
diff --git a/cross-tab-drag-and-drop/build.gradle b/cross-tab-drag-and-drop/build.gradle
index 4b5fd3a59..44e77dbd6 100644
--- a/cross-tab-drag-and-drop/build.gradle
+++ b/cross-tab-drag-and-drop/build.gradle
@@ -1,12 +1,12 @@
apply plugin: 'com.android.library'
android {
- compileSdkVersion 29
+ compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
minSdkVersion 14
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode 1
versionName "1.0"
@@ -30,11 +30,11 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'com.google.android.material:material:1.1.0'
- implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'com.google.android.material:material:1.2.0'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.viewpager2:viewpager2:1.0.0"
- testImplementation 'junit:junit:4.13'
- androidTestImplementation 'androidx.test.ext:junit:1.1.1'
- androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
+ testImplementation 'junit:junit:4.13.1'
+ androidTestImplementation 'androidx.test.ext:junit:1.1.2'
+ androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
diff --git a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java
index 4484d04f0..a143ccaee 100644
--- a/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java
+++ b/cross-tab-drag-and-drop/src/main/java/it/niedermann/android/crosstabdnd/DragAndDropAdapter.java
@@ -1,5 +1,7 @@
package it.niedermann.android.crosstabdnd;
+import androidx.annotation.NonNull;
+
import java.util.List;
public interface DragAndDropAdapter<Model> {
@@ -10,5 +12,6 @@ public interface DragAndDropAdapter<Model> {
void insertItem(Model item, int position);
+ @NonNull
List<Model> getItemList();
}
diff --git a/fastlane/metadata/android/en-US/changelogs/1006000.txt b/fastlane/metadata/android/en-US/changelogs/1006000.txt
index 706c8e007..5253ee224 100644
--- a/fastlane/metadata/android/en-US/changelogs/1006000.txt
+++ b/fastlane/metadata/android/en-US/changelogs/1006000.txt
@@ -1,5 +1,7 @@
1.6.0
-- ✨ Adjust design to new style of Nextcloud app (#525)
-- 🎨 Offer the same colors as the server app
+- ✨ Adjust design to new style of Nextcloud Android app (#525)
+- 🎨 Brand now uses board color to provide better context (#525)
+- 🎨 Offer the same default board colors as the server app
+- 🔍 Display all available users as search results when sharing a board (#510)
- 🐞 "Archive cards" of list crashes the app when there is no list (#557) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1007000.txt b/fastlane/metadata/android/en-US/changelogs/1007000.txt
new file mode 100644
index 000000000..c6f510575
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1007000.txt
@@ -0,0 +1,11 @@
+1.7.0
+
+- 🆕 Compact mode (#579)
+
+1.6.0
+
+- ✨ Adjust design to new style of Nextcloud Android app (#525)
+- 🎨 Brand now uses board color to provide better context (#525)
+- 🎨 Offer the same default board colors as the server app
+- 🔍 Display all available users as search results when sharing a board (#510)
+- 🐞 "Archive cards" of list crashes the app when there is no list (#557) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1008000.txt b/fastlane/metadata/android/en-US/changelogs/1008000.txt
new file mode 100644
index 000000000..ea0783dc0
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1008000.txt
@@ -0,0 +1,16 @@
+1.8.0
+
+- 🔀 Move cards to other boards and accounts (#453)
+- ➕ Clone boards with existing stacks and labels (#455)
+
+1.7.0
+
+- 🆕 Compact mode (#579)
+
+1.6.0
+
+- ✨ Adjust design to new style of Nextcloud Android app (#525)
+- 🎨 Brand now uses board color to provide better context (#525)
+- 🎨 Offer the same default board colors as the server app
+- 🔍 Display all available users as search results when sharing a board (#510)
+- 🐞 "Archive cards" of list crashes the app when there is no list (#557) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1008001.txt b/fastlane/metadata/android/en-US/changelogs/1008001.txt
new file mode 100644
index 000000000..e2838f73e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1008001.txt
@@ -0,0 +1,17 @@
+1.8.1
+
+- 🔀 Move cards to other boards and accounts (#453)
+- ➕ Clone boards with existing stacks and labels (#455)
+- 🐞 Fix synchronization bug (#596)
+
+1.7.0
+
+- 🆕 Compact mode (#579)
+
+1.6.0
+
+- ✨ Adjust design to new style of Nextcloud Android app (#525)
+- 🎨 Brand now uses board color to provide better context (#525)
+- 🎨 Offer the same default board colors as the server app
+- 🔍 Display all available users as search results when sharing a board (#510)
+- 🐞 "Archive cards" of list crashes the app when there is no list (#557) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1008002.txt b/fastlane/metadata/android/en-US/changelogs/1008002.txt
new file mode 100644
index 000000000..cdf575039
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1008002.txt
@@ -0,0 +1,23 @@
+1.8.2
+
+- 🐞 Fix opening not yet synchronized attachment (#599)
+- 🐞 Back arrow in attachments details has wrong color in light theme (#598)
+- 🚫 Filter by unassigned cards
+
+1.8.1
+
+- 🔀 Move cards to other boards and accounts (#453)
+- ➕ Clone boards with existing stacks and labels (#455)
+- 🐞 Fix synchronization bug (#596)
+
+1.7.0
+
+- 🆕 Compact mode (#579)
+
+1.6.0
+
+- ✨ Adjust design to new style of Nextcloud Android app (#525)
+- 🎨 Brand now uses board color to provide better context (#525)
+- 🎨 Offer the same default board colors as the server app
+- 🔍 Display all available users as search results when sharing a board (#510)
+- 🐞 "Archive cards" of list crashes the app when there is no list (#557) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1008003.txt b/fastlane/metadata/android/en-US/changelogs/1008003.txt
new file mode 100644
index 000000000..5dece205e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1008003.txt
@@ -0,0 +1,4 @@
+1.9.0
+
+- List widget (by @dan0xii) (#217)
+- Don't run multiple parallel synchronizations to avoid errors (#554) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1009000.txt b/fastlane/metadata/android/en-US/changelogs/1009000.txt
new file mode 100644
index 000000000..1291a05d0
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1009000.txt
@@ -0,0 +1,5 @@
+- 🆕 Stack Widget (#217)
+- ⚙️ Reduce amount of data which is queried by the database (#515)
+- 🚫 Filter by no tags
+- 🔗 Display linked projects and resources (#573)
+- 🐞 Fix tags being visible only on unassigned cards
diff --git a/fastlane/metadata/android/en-US/changelogs/1009001.txt b/fastlane/metadata/android/en-US/changelogs/1009001.txt
new file mode 100644
index 000000000..1291a05d0
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1009001.txt
@@ -0,0 +1,5 @@
+- 🆕 Stack Widget (#217)
+- ⚙️ Reduce amount of data which is queried by the database (#515)
+- 🚫 Filter by no tags
+- 🔗 Display linked projects and resources (#573)
+- 🐞 Fix tags being visible only on unassigned cards
diff --git a/fastlane/metadata/android/en-US/changelogs/1009002.txt b/fastlane/metadata/android/en-US/changelogs/1009002.txt
new file mode 100644
index 000000000..38789dd60
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1009002.txt
@@ -0,0 +1,2 @@
+- ⚡️ Use ETags to speed up account synchronization and use less data (requires Nextcloud ≥ 19)
+- 🐞 Do not break sync, when a project has an error \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1009003.txt b/fastlane/metadata/android/en-US/changelogs/1009003.txt
new file mode 100644
index 000000000..7df7c07e6
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1009003.txt
@@ -0,0 +1,3 @@
+- 🐞 Handle project resources with a string id properly
+- 🐞 Fix exception when creating a new list
+- 🐞 Fix NullPointerException when synchronizing in rare edge cases \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1009004.txt b/fastlane/metadata/android/en-US/changelogs/1009004.txt
new file mode 100644
index 000000000..ce25c05e6
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1009004.txt
@@ -0,0 +1 @@
+- 🐞 Make synchronization more tolerant for defect users \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1009005.txt b/fastlane/metadata/android/en-US/changelogs/1009005.txt
new file mode 100644
index 000000000..40293d851
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1009005.txt
@@ -0,0 +1,2 @@
+- 🌗 Fixed list widget in system dark mode (#638)
+- 🌎 Language updates \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1009006.txt b/fastlane/metadata/android/en-US/changelogs/1009006.txt
new file mode 100644
index 000000000..ac9925697
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1009006.txt
@@ -0,0 +1,2 @@
+- 🐞 Workaround for error when deleting something
+- 🌎 Language updates \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1009007.txt b/fastlane/metadata/android/en-US/changelogs/1009007.txt
new file mode 100644
index 000000000..0d1343346
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1009007.txt
@@ -0,0 +1,4 @@
+- 👥 Filter does not seem to work as expected (#492)
+- 🐞 Problem with uploading file (attachment) (#646)
+- 🐞 Enhanced Group-handling (#639)
+- 🌎 Language updates \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1010000.txt b/fastlane/metadata/android/en-US/changelogs/1010000.txt
new file mode 100644
index 000000000..dcb86813e
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1010000.txt
@@ -0,0 +1,7 @@
+- 🔗 Open cards of projects within the app (#671)
+- 💬 Allow mention users in the comments (#673)
+- ⚙️ Parse colors with alpha channel (#674)
+- 🐞 Move dialog: Cancel / Move buttons are hidden on long titles (#681)
+- 📢 Fix push notifications (#569)
+- 🆕 Clone lists and cards when cloning boards (#608)
+- 👥 show more than 7 assigned users (#679) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1011000.txt b/fastlane/metadata/android/en-US/changelogs/1011000.txt
new file mode 100644
index 000000000..4796430eb
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1011000.txt
@@ -0,0 +1,4 @@
+- ⚙️ Store colors as integer in database (#556)
+- 📦 Allow archiving cards from filtered results only (#663)
+- 👤 New assigned users dialog
+- 🐞 Clicking on the tag label does not select the tag (#696) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1011001.txt b/fastlane/metadata/android/en-US/changelogs/1011001.txt
new file mode 100644
index 000000000..4407f333b
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1011001.txt
@@ -0,0 +1 @@
+- Upgrade minSdkVersion to 18 (= Android 4.3) following the files app \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1012000.txt b/fastlane/metadata/android/en-US/changelogs/1012000.txt
new file mode 100644
index 000000000..e86bf5a08
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1012000.txt
@@ -0,0 +1 @@
+- 📎 New attachment picker (#289) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1012001.txt b/fastlane/metadata/android/en-US/changelogs/1012001.txt
new file mode 100644
index 000000000..eea8fda00
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1012001.txt
@@ -0,0 +1,8 @@
+1.12.1
+
+- ⚙️ Updated dependencies
+- 🌎 Updated translations
+
+1.12.0
+
+- 📎 New attachment picker (#289) \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1012002.txt b/fastlane/metadata/android/en-US/changelogs/1012002.txt
new file mode 100644
index 000000000..a1f98ebc4
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1012002.txt
@@ -0,0 +1,2 @@
+- 🎨️ BottomNavigation in attachment picker is not colored when branding is disabled (#729)
+- 🐞 Fix icons in card activity \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/1013000.txt b/fastlane/metadata/android/en-US/changelogs/1013000.txt
new file mode 100644
index 000000000..70e7e5e72
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/1013000.txt
@@ -0,0 +1,4 @@
+- ⚡️ #359 Implement ETags for synchronization Speed-Up
+- 🔄 #680 Loading indicator is shown indefinitely and no synchronization
+- 🐞 #707 Link for PushNotifications could not be parsed in some cases
+- 🌎 Updated translations \ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png
index a22e76a61..e3a8303f3 100644
--- a/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png
+++ b/fastlane/metadata/android/en-US/images/phoneScreenshots/2_en-US.png
Binary files differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png
index 2c882a2b8..86cc848f2 100644
--- a/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png
+++ b/fastlane/metadata/android/en-US/images/phoneScreenshots/5_en-US.png
Binary files differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png
index 4fb583412..0f19fdc5b 100644
--- a/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png
+++ b/fastlane/metadata/android/en-US/images/phoneScreenshots/6_en-US.png
Binary files differ
diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png
new file mode 100644
index 000000000..84e5fbb24
--- /dev/null
+++ b/fastlane/metadata/android/en-US/images/phoneScreenshots/7.png
Binary files differ
diff --git a/glide-sso-integration/.gitignore b/glide-sso-integration/.gitignore
deleted file mode 100644
index 42afabfd2..000000000
--- a/glide-sso-integration/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build \ No newline at end of file
diff --git a/glide-sso-integration/build.gradle b/glide-sso-integration/build.gradle
deleted file mode 100644
index 1f8cddf59..000000000
--- a/glide-sso-integration/build.gradle
+++ /dev/null
@@ -1,34 +0,0 @@
-apply plugin: 'com.android.library'
-
-android {
- compileSdkVersion 29
- buildToolsVersion "29.0.3"
-
- defaultConfig {
- minSdkVersion 14
- targetSdkVersion 29
- versionCode 1
- versionName "1.0"
-
- testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- consumerProguardFiles "consumer-rules.pro"
- }
-
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- // Nextcloud SSO
- implementation "com.github.nextcloud:Android-SingleSignOn:0.5.1"
-
- // Glide
- implementation 'com.github.bumptech.glide:glide:4.11.0'
- annotationProcessor 'com.github.bumptech.glide:compiler:4.11.0'
-
- implementation fileTree(dir: "libs", include: ["*.jar"])
-} \ No newline at end of file
diff --git a/glide-sso-integration/proguard-rules.pro b/glide-sso-integration/proguard-rules.pro
deleted file mode 100644
index 481bb4348..000000000
--- a/glide-sso-integration/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile \ No newline at end of file
diff --git a/glide-sso-integration/src/main/AndroidManifest.xml b/glide-sso-integration/src/main/AndroidManifest.xml
deleted file mode 100644
index 88728afb3..000000000
--- a/glide-sso-integration/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,4 +0,0 @@
-<manifest package="it.niedermann.android.glidesso">
-
- /
-</manifest> \ No newline at end of file
diff --git a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnLibraryGlideModule.java b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnLibraryGlideModule.java
deleted file mode 100644
index 6669d5da1..000000000
--- a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnLibraryGlideModule.java
+++ /dev/null
@@ -1,33 +0,0 @@
-package it.niedermann.android.glidesso;
-
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.Registry;
-import com.bumptech.glide.annotation.GlideModule;
-import com.bumptech.glide.load.model.GlideUrl;
-import com.bumptech.glide.module.LibraryGlideModule;
-
-import java.io.InputStream;
-
-/**
- * Registers OkHttp related classes via Glide's annotation processor.
- *
- * <p>For Applications that depend on this library and include an {@link LibraryGlideModule} and Glide's
- * annotation processor, this class will be automatically included.
- */
-@GlideModule
-public final class SingleSignOnLibraryGlideModule extends LibraryGlideModule {
-
- private static final String TAG = SingleSignOnLibraryGlideModule.class.getSimpleName();
-
- @Override
- public void registerComponents(
- @NonNull Context context, @NonNull Glide glide, @NonNull Registry registry) {
- Log.v(TAG, "Replacing default implementation for " + GlideUrl.class.getSimpleName() + ".");
- registry.replace(GlideUrl.class, InputStream.class, new SingleSignOnUrlLoader.Factory(context));
- }
-}
diff --git a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnStreamFetcher.java b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnStreamFetcher.java
deleted file mode 100644
index 10f708890..000000000
--- a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnStreamFetcher.java
+++ /dev/null
@@ -1,145 +0,0 @@
-package it.niedermann.android.glidesso;
-
-import android.content.Context;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import com.bumptech.glide.Priority;
-import com.bumptech.glide.load.DataSource;
-import com.bumptech.glide.load.data.DataFetcher;
-import com.bumptech.glide.load.model.GlideUrl;
-import com.google.gson.GsonBuilder;
-import com.nextcloud.android.sso.AccountImporter;
-import com.nextcloud.android.sso.aidl.NextcloudRequest;
-import com.nextcloud.android.sso.api.NextcloudAPI;
-import com.nextcloud.android.sso.api.Response;
-import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException;
-import com.nextcloud.android.sso.exceptions.NoCurrentAccountSelectedException;
-import com.nextcloud.android.sso.exceptions.TokenMismatchException;
-import com.nextcloud.android.sso.helper.SingleAccountHelper;
-import com.nextcloud.android.sso.model.SingleSignOnAccount;
-
-import java.io.InputStream;
-import java.net.MalformedURLException;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-
-/**
- * Fetches an {@link InputStream} using the Nextcloud SSO library.
- */
-public class SingleSignOnStreamFetcher implements DataFetcher<InputStream> {
-
- /**
- * Use this header and set the {@link SingleSignOnAccount} name property as value
- * Format of the value needs to be
- */
- public static final String X_HEADER_SSO_ACCOUNT_NAME = "X-SSO-Account-Name";
-
- private static final String TAG = SingleSignOnStreamFetcher.class.getSimpleName();
- private static final String METHOD_GET = "GET";
-
- private static final Map<String, NextcloudAPI> INITIALIZED_APIs = new HashMap<>();
-
- private final Context context;
- private final GlideUrl url;
-
- // Public API.
- @SuppressWarnings("WeakerAccess")
- public SingleSignOnStreamFetcher(Context context, GlideUrl url) {
- this.context = context;
- this.url = url;
- }
-
- @Override
- public void loadData(@NonNull Priority priority, @NonNull final DataCallback<? super InputStream> callback) {
- NextcloudAPI client;
- try {
- final SingleSignOnAccount ssoAccount;
- if (url.getHeaders().containsKey(X_HEADER_SSO_ACCOUNT_NAME)) {
- ssoAccount = AccountImporter.getSingleSignOnAccount(context, url.getHeaders().get(X_HEADER_SSO_ACCOUNT_NAME));
- } else {
- ssoAccount = SingleAccountHelper.getCurrentSingleSignOnAccount(context);
- }
- client = INITIALIZED_APIs.get(ssoAccount.name);
- boolean didInitialize = false;
- if (client == null) {
- client = new NextcloudAPI(context, ssoAccount, new GsonBuilder().create(), new NextcloudAPI.ApiConnectedListener() {
- @Override
- public void onConnected() {
- Log.v(TAG, "SSO API successfully initialized");
- }
-
- @Override
- public void onError(Exception ex) {
- Log.e(TAG, ex.getMessage(), ex);
- }
- });
- INITIALIZED_APIs.put(ssoAccount.name, client);
- didInitialize = true;
- }
-
- NextcloudRequest.Builder requestBuilder;
- try {
- requestBuilder = new NextcloudRequest.Builder()
- .setMethod(METHOD_GET)
- .setUrl(url.toURL().getPath());
- Map<String, List<String>> header = new HashMap<>();
- for (Map.Entry<String, String> headerEntry : url.getHeaders().entrySet()) {
- if (!X_HEADER_SSO_ACCOUNT_NAME.equals(headerEntry.getKey())) {
- header.put(headerEntry.getKey(), Collections.singletonList(headerEntry.getValue()));
- }
- }
- requestBuilder.setHeader(header);
- NextcloudRequest nextcloudRequest = requestBuilder.build();
- Log.v(TAG, nextcloudRequest.toString());
- Response response = client.performNetworkRequestV2(nextcloudRequest);
- callback.onDataReady(response.getBody());
- } catch (MalformedURLException e) {
- callback.onLoadFailed(e);
- } catch (TokenMismatchException e) {
- if (!didInitialize) {
- Log.w(TAG, "SSO Glide loader failed with TokenMismatchException, trying to re-initialize...");
- client.stop();
- INITIALIZED_APIs.remove(ssoAccount.name);
- loadData(priority, callback);
- } else {
- e.printStackTrace();
- callback.onLoadFailed(e);
- }
- } catch (Exception e) {
- callback.onLoadFailed(e);
- }
-
- } catch (NextcloudFilesAppAccountNotFoundException e) {
- e.printStackTrace();
- } catch (NoCurrentAccountSelectedException e) {
- e.printStackTrace();
- }
- }
-
- @Override
- public void cleanup() {
-
- }
-
- @Override
- public void cancel() {
-
- }
-
- @NonNull
- @Override
- public Class<InputStream> getDataClass() {
- return InputStream.class;
- }
-
- @NonNull
- @Override
- public DataSource getDataSource() {
- return DataSource.REMOTE;
- }
-}
diff --git a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrl.java b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrl.java
deleted file mode 100644
index b8f359757..000000000
--- a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrl.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package it.niedermann.android.glidesso;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-
-import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.model.GlideUrl;
-import com.bumptech.glide.load.model.Headers;
-import com.bumptech.glide.load.model.LazyHeaders;
-import com.nextcloud.android.sso.helper.SingleAccountHelper;
-import com.nextcloud.android.sso.model.SingleSignOnAccount;
-
-import java.net.URL;
-import java.util.Map;
-
-import static it.niedermann.android.glidesso.SingleSignOnStreamFetcher.X_HEADER_SSO_ACCOUNT_NAME;
-
-/**
- * Use this as kind of {@link GlideUrl} if you want to do a {@link Glide} request from a {@link SingleSignOnAccount} which is not set by {@link SingleAccountHelper#setCurrentAccount(Context, String)}.
- */
-public class SingleSignOnUrl extends GlideUrl {
-
- public SingleSignOnUrl(@NonNull SingleSignOnAccount ssoAccount, @NonNull String url) {
- this(ssoAccount.name, url);
- }
-
- public SingleSignOnUrl(@NonNull SingleSignOnAccount ssoAccount, @NonNull URL url) {
- this(ssoAccount.name, url);
- }
-
- public SingleSignOnUrl(@NonNull SingleSignOnAccount ssoAccount, @NonNull String url, @NonNull Headers headers) {
- this(ssoAccount.name, url, headers);
- }
-
- public SingleSignOnUrl(@NonNull SingleSignOnAccount ssoAccount, @NonNull URL url, @NonNull Headers headers) {
- this(ssoAccount.name, url, headers);
- }
-
- public SingleSignOnUrl(@NonNull String ssoAccountName, @NonNull String url) {
- super(url, new SingleSignOnOriginHeader(ssoAccountName));
- }
-
- public SingleSignOnUrl(@NonNull String ssoAccountName, @NonNull URL url) {
- super(url, new SingleSignOnOriginHeader(ssoAccountName));
- }
-
- public SingleSignOnUrl(@NonNull String ssoAccountName, @NonNull String url, @NonNull Headers headers) {
- super(url, new SingleSignOnOriginHeader(ssoAccountName, headers));
- }
-
- public SingleSignOnUrl(@NonNull String ssoAccountName, @NonNull URL url, @NonNull Headers headers) {
- super(url, new SingleSignOnOriginHeader(ssoAccountName, headers));
- }
-
- private static class SingleSignOnOriginHeader implements Headers {
-
- private Headers headers;
-
- /**
- * Use this as {@link Headers} if you want to do a {@link Glide} request for an {@link SingleSignOnAccount} which is not set by {@link SingleAccountHelper} as current {@link SingleSignOnAccount}.
- *
- * @param ssoAccountName Account name from which host the request should be fired (needs to match {@link SingleSignOnAccount#name})
- */
- public SingleSignOnOriginHeader(@NonNull String ssoAccountName) {
- this.headers = new LazyHeaders.Builder().addHeader(X_HEADER_SSO_ACCOUNT_NAME, ssoAccountName).build();
- }
-
- /**
- * Use this as {@link Headers} if you want to do a {@link Glide} request for an {@link SingleSignOnAccount} which is not set by {@link SingleAccountHelper} as current {@link SingleSignOnAccount}.
- *
- * @param ssoAccountName Account name from which host the request should be fired (needs to match {@link SingleSignOnAccount#name})
- */
- public SingleSignOnOriginHeader(@NonNull String ssoAccountName, Headers headers) {
- LazyHeaders.Builder builder = new LazyHeaders.Builder();
- for (Map.Entry<String, String> entry : headers.getHeaders().entrySet()) {
- builder.addHeader(entry.getKey(), entry.getValue());
- }
- builder.addHeader(X_HEADER_SSO_ACCOUNT_NAME, ssoAccountName).build();
- this.headers = builder.build();
- }
-
- @Override
- public Map<String, String> getHeaders() {
- return this.headers.getHeaders();
- }
- }
-}
diff --git a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrlLoader.java b/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrlLoader.java
deleted file mode 100644
index 10f990b3e..000000000
--- a/glide-sso-integration/src/main/java/it/niedermann/android/glidesso/SingleSignOnUrlLoader.java
+++ /dev/null
@@ -1,66 +0,0 @@
-package it.niedermann.android.glidesso;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-
-import com.bumptech.glide.load.Options;
-import com.bumptech.glide.load.model.GlideUrl;
-import com.bumptech.glide.load.model.ModelLoader;
-import com.bumptech.glide.load.model.ModelLoaderFactory;
-import com.bumptech.glide.load.model.MultiModelLoaderFactory;
-
-import java.io.InputStream;
-
-/**
- * A simple model loader for fetching media over http/https using OkHttp.
- */
-public class SingleSignOnUrlLoader implements ModelLoader<GlideUrl, InputStream> {
-
- private static final String TAG = SingleSignOnUrlLoader.class.getSimpleName();
- private final Context context;
-
- // Public API.
- @SuppressWarnings("WeakerAccess")
- public SingleSignOnUrlLoader(@NonNull Context context) {
- this.context = context;
- }
-
- @Override
- public boolean handles(@NonNull GlideUrl url) {
- return true;
- }
-
- @Override
- public LoadData<InputStream> buildLoadData(
- @NonNull GlideUrl model, int width, int height, @NonNull Options options) {
- return new LoadData<>(model, new SingleSignOnStreamFetcher(context, model));
- }
-
- /**
- * The default factory for {@link SingleSignOnUrlLoader}s.
- */
- // Public API.
- @SuppressWarnings("WeakerAccess")
- public static class Factory implements ModelLoaderFactory<GlideUrl, InputStream> {
- private SingleSignOnUrlLoader loader;
-
- /**
- * Constructor for a new Factory that runs requests using given client.
- */
- public Factory(@NonNull Context context) {
- loader = new SingleSignOnUrlLoader(context);
- }
-
- @NonNull
- @Override
- public ModelLoader<GlideUrl, InputStream> build(@NonNull MultiModelLoaderFactory multiFactory) {
- return loader;
- }
-
- @Override
- public void teardown() {
- // Do nothing, this instance doesn't own the client.
- }
- }
-}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 94495fc2f..eb3e8c239 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Thu May 28 21:15:03 CEST 2020
+#Tue Oct 13 09:49:35 CEST 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-6.1.1-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip
diff --git a/settings.gradle b/settings.gradle
index baff05e2d..edf1d2bdb 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,4 +1,3 @@
include ':app'
include ':cross-tab-drag-and-drop'
-include ':glide-sso-integration'
include ':tab-layout-helper'
diff --git a/tab-layout-helper/build.gradle b/tab-layout-helper/build.gradle
index c9b3119fb..02c6fdf5b 100644
--- a/tab-layout-helper/build.gradle
+++ b/tab-layout-helper/build.gradle
@@ -1,12 +1,12 @@
apply plugin: 'com.android.library'
android {
- compileSdkVersion 29
+ compileSdkVersion 30
buildToolsVersion "29.0.3"
defaultConfig {
minSdkVersion 14
- targetSdkVersion 29
+ targetSdkVersion 30
versionCode 1
versionName "1.0"
@@ -30,7 +30,7 @@ android {
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
- implementation 'androidx.appcompat:appcompat:1.1.0'
+ implementation 'androidx.appcompat:appcompat:1.2.0'
implementation "androidx.viewpager2:viewpager2:1.0.0"
- implementation 'com.google.android.material:material:1.1.0'
+ implementation 'com.google.android.material:material:1.2.0'
}