diff options
Diffstat (limited to 'app/src/main/java/it/niedermann/nextcloud/deck/ui/card')
13 files changed, 393 insertions, 163 deletions
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 index 697b21e1e..193fc7a2c 100644 --- 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 @@ -21,7 +21,7 @@ import com.nextcloud.android.common.ui.theme.utils.ColorRole; import org.jetbrains.annotations.Contract; -import java.time.ZoneId; +import java.time.Instant; import java.util.List; import java.util.stream.Collectors; @@ -31,10 +31,9 @@ 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.ui.theme.DeckViewThemeUtils; import it.niedermann.nextcloud.deck.ui.theme.ThemeUtils; +import it.niedermann.nextcloud.deck.ui.view.DueDateChip; import it.niedermann.nextcloud.deck.util.AttachmentUtil; -import it.niedermann.nextcloud.deck.util.DateUtil; import it.niedermann.nextcloud.deck.util.MimeTypeUtil; import it.niedermann.nextcloud.sso.glide.SingleSignOnUrl; @@ -59,13 +58,15 @@ public abstract class AbstractCardViewHolder extends RecyclerView.ViewHolder { if (utils != null) { utils.platform.colorImageView(getNotSyncedYet(), ColorRole.PRIMARY); + utils.platform.colorImageView(getCardMenu(), ColorRole.ON_SURFACE); + utils.platform.colorTextView(getCardTitle(), ColorRole.ON_SURFACE); } // TODO should be discussed with UX // utils.material.themeCardView(getCard()); getNotSyncedYet().setVisibility(DBStatus.LOCAL_EDITED.equals(fullCard.getStatusEnum()) ? View.VISIBLE : View.GONE); - if (fullCard.getCard().getDueDate() != null) { + if (fullCard.getCard().getDueDate() != null || fullCard.getCard().getDone() != null) { setupDueDate(getCardDueDate(), fullCard.getCard()); getCardDueDate().setVisibility(View.VISIBLE); } else { @@ -90,13 +91,13 @@ public abstract class AbstractCardViewHolder extends RecyclerView.ViewHolder { }); } - protected abstract TextView getCardDueDate(); + protected abstract DueDateChip getCardDueDate(); protected abstract ImageView getNotSyncedYet(); protected abstract TextView getCardTitle(); - protected abstract View getCardMenu(); + protected abstract ImageView getCardMenu(); protected abstract MaterialCardView getCard(); @@ -112,9 +113,14 @@ public abstract class AbstractCardViewHolder extends RecyclerView.ViewHolder { return getCard(); } - private static void setupDueDate(@NonNull TextView cardDueDate, @NonNull Card card) { - cardDueDate.setText(DateUtil.getRelativeDateTimeString(cardDueDate.getContext(), card.getDueDate().toEpochMilli())); - DeckViewThemeUtils.themeDueDate(cardDueDate, card.getDueDate().atZone(ZoneId.systemDefault()).toLocalDate()); + private static void setupDueDate(@NonNull DueDateChip cardDueDate, @NonNull Card card) { + final boolean isDone = card.getDone() != null; + final Instant date = isDone ? card.getDone() : card.getDueDate(); + + if (date == null) { + throw new IllegalArgumentException("Expected due date or done date to be present but both were null."); + } + cardDueDate.setDueDate(date, isDone); } protected static void setupCoverImages(@NonNull Account account, @NonNull ViewGroup coverImagesHolder, @NonNull FullCard fullCard, int maxCoverImagesCount) { 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 index cf947c90b..8dc4a3e48 100644 --- 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 @@ -19,6 +19,7 @@ import it.niedermann.nextcloud.deck.model.Account; import it.niedermann.nextcloud.deck.model.Label; import it.niedermann.nextcloud.deck.model.full.FullCard; import it.niedermann.nextcloud.deck.ui.theme.ThemeUtils; +import it.niedermann.nextcloud.deck.ui.view.DueDateChip; public class CompactCardViewHolder extends AbstractCardViewHolder { private final ItemCardCompactBinding binding; @@ -63,7 +64,7 @@ public class CompactCardViewHolder extends AbstractCardViewHolder { } @Override - protected TextView getCardDueDate() { + protected DueDateChip getCardDueDate() { return binding.cardDueDate; } @@ -78,7 +79,7 @@ public class CompactCardViewHolder extends AbstractCardViewHolder { } @Override - protected View getCardMenu() { + protected ImageView getCardMenu() { return binding.cardMenu; } 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 index 27aacc614..4e16f8127 100644 --- 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 @@ -1,6 +1,5 @@ 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; @@ -12,6 +11,7 @@ import androidx.annotation.Nullable; import com.google.android.material.card.MaterialCardView; import it.niedermann.nextcloud.deck.databinding.ItemCardDefaultOnlyTitleBinding; +import it.niedermann.nextcloud.deck.ui.view.DueDateChip; public class DefaultCardOnlyTitleViewHolder extends AbstractCardViewHolder { private final ItemCardDefaultOnlyTitleBinding binding; @@ -35,7 +35,7 @@ public class DefaultCardOnlyTitleViewHolder extends AbstractCardViewHolder { } @Override - protected TextView getCardDueDate() { + protected DueDateChip getCardDueDate() { return binding.cardDueDate; } @@ -50,7 +50,7 @@ public class DefaultCardOnlyTitleViewHolder extends AbstractCardViewHolder { } @Override - protected View getCardMenu() { + protected ImageView getCardMenu() { return binding.cardMenu; } 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 index dc6e5a956..20ce6dd1d 100644 --- 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 @@ -13,12 +13,16 @@ import androidx.annotation.Nullable; import androidx.core.content.ContextCompat; import com.google.android.material.card.MaterialCardView; +import com.nextcloud.android.common.ui.theme.utils.ColorRole; + +import java.util.stream.Stream; 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.full.FullCard; import it.niedermann.nextcloud.deck.ui.theme.ThemeUtils; +import it.niedermann.nextcloud.deck.ui.view.DueDateChip; public class DefaultCardViewHolder extends AbstractCardViewHolder { private final ItemCardDefaultBinding binding; @@ -78,7 +82,7 @@ public class DefaultCardViewHolder extends AbstractCardViewHolder { final var 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.setCompoundDrawablesWithIntrinsicBounds(ContextCompat.getDrawable(context, R.drawable.ic_check_box_24), null, null, null); binding.cardCountTasks.setVisibility(View.VISIBLE); } else { final String description = fullCard.getCard().getDescription(); @@ -90,10 +94,20 @@ public class DefaultCardViewHolder extends AbstractCardViewHolder { binding.cardCountTasks.setVisibility(View.GONE); } } + + if (utils != null) { + Stream.of( + binding.cardCountAttachments, + binding.cardCountTasks, + binding.cardCountComments + ).forEach(v -> { + utils.platform.colorTextView(v, ColorRole.ON_SURFACE_VARIANT ); + }); + } } @Override - protected TextView getCardDueDate() { + protected DueDateChip getCardDueDate() { return binding.cardDueDate; } @@ -108,7 +122,7 @@ public class DefaultCardViewHolder extends AbstractCardViewHolder { } @Override - protected View getCardMenu() { + protected ImageView getCardMenu() { return binding.cardMenu; } 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 286c95018..9140e305e 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 @@ -15,7 +15,6 @@ import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundExce import java.util.List; import it.niedermann.android.reactivelivedata.ReactiveLiveData; -import it.niedermann.android.util.DimensionUtil; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ItemAutocompleteUserBinding; @@ -82,7 +81,7 @@ public class UserAutoCompleteAdapter extends AutoCompleteAdapter<User> { } Glide.with(binding.icon.getContext()) - .load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.icon.getContext(), R.dimen.avatar_size), getItem(position).getUid())) + .load(account.getAvatarUrl(binding.icon.getResources().getDimensionPixelSize(R.dimen.avatar_size), getItem(position).getUid())) .apply(RequestOptions.circleCropTransform()) .placeholder(R.drawable.ic_person_grey600_24dp) .error(R.drawable.ic_person_grey600_24dp) 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 index 6b60bbffd..473937753 100644 --- 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 @@ -1,5 +1,9 @@ package it.niedermann.nextcloud.deck.ui.card.attachments; +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static com.google.android.material.bottomsheet.BottomSheetBehavior.STATE_HIDDEN; + import android.content.Context; import android.view.View; @@ -16,12 +20,6 @@ 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; @@ -55,7 +53,7 @@ public class CardAttachmentsBottomsheetBehaviorCallback extends BottomSheetBehav this.bottomNavigation = bottomNavigation; this.backdropColorExpanded = ContextCompat.getColor(context, backdropColorExpanded); this.backdropColorCollapsed = ContextCompat.getColor(context, backdropColorCollapsed); - this.bottomNavigationHeight = DimensionUtil.INSTANCE.dpToPx(context, bottomNavigationHeight); + this.bottomNavigationHeight = context.getResources().getDimensionPixelSize(bottomNavigationHeight); } @Override 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 f5f0606fa..4de403ba7 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 @@ -62,7 +62,6 @@ import id.zelory.compressor.constraint.QualityConstraint; import id.zelory.compressor.constraint.ResolutionConstraint; import id.zelory.compressor.constraint.SizeConstraint; import it.niedermann.android.reactivelivedata.ReactiveLiveData; -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; @@ -156,7 +155,7 @@ public class CardAttachmentsFragment extends Fragment implements AttachmentDelet this.binding.attachmentsList.setVisibility(VISIBLE); } }); - galleryItemDecoration = new GalleryItemDecoration(DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.spacer_1qx)); + galleryItemDecoration = new GalleryItemDecoration(getResources().getDimensionPixelSize(R.dimen.spacer_1qx)); mBottomSheetBehaviour = BottomSheetBehavior.from(binding.bottomSheetParent); mBottomSheetBehaviour.setDraggable(true); mBottomSheetBehaviour.setHideable(true); 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 e6739e3ee..caa13cb07 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 @@ -25,7 +25,6 @@ import com.nextcloud.android.sso.api.EmptyResponse; import java.time.Instant; -import it.niedermann.android.util.DimensionUtil; import it.niedermann.nextcloud.deck.DeckLog; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.FragmentCardEditTabCommentsBinding; @@ -95,7 +94,7 @@ public class CardCommentsFragment extends Fragment implements Themed, CommentEdi binding.comments.setAdapter(adapter); binding.replyCommentCancelButton.setOnClickListener((v) -> commentsViewModel.setReplyToComment(null)); Glide.with(binding.avatar.getContext()) - .load(editCardViewModel.getAccount().getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.icon_size_details))) + .load(editCardViewModel.getAccount().getAvatarUrl(binding.avatar.getResources().getDimensionPixelSize(R.dimen.icon_size_details))) .apply(RequestOptions.circleCropTransform()) .placeholder(R.drawable.ic_person_grey600_24dp) .error(R.drawable.ic_person_grey600_24dp) 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 index 403e26886..fe10f6cfa 100644 --- 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 @@ -19,7 +19,6 @@ import java.util.ArrayList; import java.util.List; import it.niedermann.android.reactivelivedata.ReactiveLiveData; -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; @@ -56,9 +55,9 @@ public class CardCommentsMentionProposer implements TextWatcher { this.mentionProposerWrapper = mentionProposerWrapper; this.mentionProposer = avatarProposer; baseRepository = new BaseRepository(editText.getContext()); - avatarSize = DimensionUtil.INSTANCE.dpToPx(mentionProposer.getContext(), R.dimen.avatar_size_small); + avatarSize = mentionProposer.getResources().getDimensionPixelSize(R.dimen.avatar_size_small); layoutParams = new LinearLayout.LayoutParams(avatarSize, avatarSize); - layoutParams.setMarginEnd(DimensionUtil.INSTANCE.dpToPx(mentionProposer.getContext(), R.dimen.spacer_1x)); + layoutParams.setMarginEnd(mentionProposer.getResources().getDimensionPixelSize(R.dimen.spacer_1x)); } @Override 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 221beea76..7a4df435f 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 @@ -21,7 +21,6 @@ import java.util.HashMap; import java.util.function.Consumer; 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; @@ -43,7 +42,7 @@ public class ItemCommentViewHolder extends RecyclerView.ViewHolder { public void bind(@NonNull FullDeckComment comment, @NonNull Account account, @Nullable ThemeUtils utils, @NonNull MenuInflater inflater, @NonNull CommentDeletedListener deletedListener, @NonNull CommentSelectAsReplyListener selectAsReplyListener, @NonNull FragmentManager fragmentManager, @NonNull Consumer<CharSequence> editListener) { Glide.with(binding.avatar.getContext()) - .load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.avatar_size), comment.getComment().getActorId())) + .load(account.getAvatarUrl(binding.avatar.getResources().getDimensionPixelSize(R.dimen.avatar_size), comment.getComment().getActorId())) .apply(RequestOptions.circleCropTransform()) .placeholder(R.drawable.ic_person_grey600_24dp) .error(R.drawable.ic_person_grey600_24dp) @@ -64,7 +63,7 @@ public class ItemCommentViewHolder extends RecyclerView.ViewHolder { inflater.inflate(R.menu.comment_menu, menu); menu.findItem(android.R.id.copy).setOnMenuItemClickListener(item -> ClipboardUtil.INSTANCE.copyToClipboard(itemView.getContext(), comment.getComment().getMessage())); final var replyMenuItem = menu.findItem(R.id.reply); - if (comment.getStatusEnum() != DBStatus.LOCAL_EDITED && account.getServerDeckVersionAsObject().supportsCommentsReplys()) { + if (comment.getStatusEnum() != DBStatus.LOCAL_EDITED && account.getServerDeckVersionAsObject().supportsCommentsReplies()) { replyMenuItem.setOnMenuItemClickListener(item -> { selectAsReplyListener.onSelectAsReply(comment); return true; 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 index 989b17ef7..8148ff783 100644 --- 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 @@ -8,7 +8,6 @@ import androidx.recyclerview.widget.RecyclerView; import com.bumptech.glide.Glide; import com.bumptech.glide.request.RequestOptions; -import it.niedermann.android.util.DimensionUtil; import it.niedermann.nextcloud.deck.R; import it.niedermann.nextcloud.deck.databinding.ItemAssigneeBinding; import it.niedermann.nextcloud.deck.model.Account; @@ -25,7 +24,7 @@ public class AssigneeViewHolder extends RecyclerView.ViewHolder { public void bind(@NonNull Account account, @NonNull User user, @Nullable Consumer<User> onClickListener) { Glide.with(binding.avatar.getContext()) - .load(account.getAvatarUrl(DimensionUtil.INSTANCE.dpToPx(binding.avatar.getContext(), R.dimen.avatar_size), user.getUid())) + .load(account.getAvatarUrl(binding.avatar.getResources().getDimensionPixelSize(R.dimen.avatar_size), user.getUid())) .apply(RequestOptions.circleCropTransform()) .placeholder(R.drawable.ic_person_grey600_24dp) .error(R.drawable.ic_person_grey600_24dp) 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 76c99abe9..d86f9361a 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 @@ -27,23 +27,14 @@ import androidx.recyclerview.widget.GridLayoutManager; import com.google.android.material.chip.Chip; import com.google.android.material.snackbar.Snackbar; +import com.nextcloud.android.common.ui.theme.utils.ColorRole; import com.nextcloud.android.sso.exceptions.NextcloudFilesAppAccountNotFoundException; -import com.wdullaer.materialdatetimepicker.date.DatePickerDialog; -import com.wdullaer.materialdatetimepicker.date.DatePickerDialog.OnDateSetListener; -import com.wdullaer.materialdatetimepicker.time.TimePickerDialog; -import com.wdullaer.materialdatetimepicker.time.TimePickerDialog.OnTimeSetListener; - -import 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 java.time.Instant; import java.util.stream.Stream; import it.niedermann.android.markdown.MarkdownEditor; 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; @@ -59,17 +50,13 @@ 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.ui.theme.ThemeUtils; -import it.niedermann.nextcloud.deck.ui.theme.ThemedDatePickerDialog; import it.niedermann.nextcloud.deck.ui.theme.ThemedSnackbar; -import it.niedermann.nextcloud.deck.ui.theme.ThemedTimePickerDialog; -public class CardDetailsFragment extends Fragment implements OnDateSetListener, OnTimeSetListener, CardAssigneeListener { +public class CardDetailsFragment extends Fragment implements CardDueDateView.DueDateChangedListener, CardAssigneeListener { private FragmentCardEditTabDetailsBinding binding; private EditCardViewModel viewModel; private AssigneeAdapter adapter; - private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); - private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT); private static final String KEY_ACCOUNT = "account"; public static Fragment newInstance(@NonNull Account account) { @@ -102,9 +89,9 @@ public class CardDetailsFragment extends Fragment implements OnDateSetListener, return binding.getRoot(); } - @Px final int avatarSize = DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.avatar_size); + @Px final int avatarSize = getResources().getDimensionPixelSize(R.dimen.avatar_size); final var avatarLayoutParams = new LinearLayout.LayoutParams(avatarSize, avatarSize); - avatarLayoutParams.setMargins(0, 0, DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.spacer_1x), 0); + avatarLayoutParams.setMargins(0, 0, getResources().getDimensionPixelSize(R.dimen.spacer_1x), 0); setupAssignees(); setupLabels((Account) args.getSerializable(KEY_ACCOUNT)); @@ -121,16 +108,16 @@ public class CardDetailsFragment extends Fragment implements OnDateSetListener, viewModel.getBoardColor().observe(getViewLifecycleOwner(), this::applyTheme); } - @Override - public void onResume() { - super.onResume(); - - // https://github.com/wdullaer/MaterialDateTimePicker#why-are-my-callbacks-lost-when-the-device-changes-orientation - final var dpd = (DatePickerDialog) getChildFragmentManager().findFragmentByTag(ThemedDatePickerDialog.class.getCanonicalName()); - final var tpd = (TimePickerDialog) getChildFragmentManager().findFragmentByTag(ThemedTimePickerDialog.class.getCanonicalName()); - if (tpd != null) tpd.setOnTimeSetListener(this); - if (dpd != null) dpd.setOnDateSetListener(this); - } +// @Override +// public void onResume() { +// super.onResume(); +// +// // https://github.com/wdullaer/MaterialDateTimePicker#why-are-my-callbacks-lost-when-the-device-changes-orientation +// final var dpd = (DatePickerDialog) getChildFragmentManager().findFragmentByTag(ThemedDatePickerDialog.class.getCanonicalName()); +// final var tpd = (TimePickerDialog) getChildFragmentManager().findFragmentByTag(ThemedTimePickerDialog.class.getCanonicalName()); +// if (tpd != null) tpd.setOnTimeSetListener(this); +// if (dpd != null) dpd.setOnDateSetListener(this); +// } @Override public void onDestroy() { @@ -143,12 +130,13 @@ public class CardDetailsFragment extends Fragment implements OnDateSetListener, Stream.of( binding.labelsWrapper, - binding.dueDateDateWrapper, - binding.dueDateTimeWrapper, binding.peopleWrapper, binding.descriptionEditorWrapper ).forEach(utils.material::colorTextInputLayout); + utils.platform.colorImageView(binding.descriptionToggle, ColorRole.SECONDARY); + + binding.cardDueDateView.applyTheme(color); binding.descriptionEditor.setSearchColor(color); binding.descriptionViewer.setSearchColor(color); @@ -194,53 +182,27 @@ public class CardDetailsFragment extends Fragment implements OnDateSetListener, } private void setupDueDate() { - if (this.viewModel.getFullCard().getCard().getDueDate() != null) { - final var 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(GONE); - binding.dueDateDate.setText(null); - binding.dueDateTime.setText(null); - } - - if (viewModel.canEdit()) { - binding.dueDateDate.setOnClickListener(v -> { - 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 { - date = LocalDate.now(); - } - viewModel.getCurrentBoardColor(viewModel.getAccount().getId(), viewModel.getBoardId()) - .thenAcceptAsync(color -> ThemedDatePickerDialog.newInstance(this, date.getYear(), date.getMonthValue(), date.getDayOfMonth(), color) - .show(getChildFragmentManager(), ThemedDatePickerDialog.class.getCanonicalName()), ContextCompat.getMainExecutor(requireContext())); - }); + final var version = this.viewModel.getAccount().getServerDeckVersionAsObject(); + final var card = this.viewModel.getFullCard().getCard(); + binding.cardDueDateView.setDueDateListener(this); + binding.cardDueDateView.setEnabled(this.viewModel.canEdit()); + binding.cardDueDateView.setDueDate(getChildFragmentManager(), version, card.getDueDate(), card.getDone()); + } - binding.dueDateTime.setOnClickListener(v -> { - 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 { - time = LocalTime.now(); - } - viewModel.getCurrentBoardColor(viewModel.getAccount().getId(), viewModel.getBoardId()) - .thenAcceptAsync(color -> ThemedTimePickerDialog.newInstance(this, time.getHour(), time.getMinute(), true, color) - .show(getChildFragmentManager(), ThemedTimePickerDialog.class.getCanonicalName()), ContextCompat.getMainExecutor(requireContext())); - }); + @Override + public void onDueDateChanged(@Nullable Instant dueDate) { + final var version = this.viewModel.getAccount().getServerDeckVersionAsObject(); + final var card = this.viewModel.getFullCard().getCard(); + card.setDueDate(dueDate); + binding.cardDueDateView.setDueDate(getChildFragmentManager(), version, card.getDueDate(), card.getDone()); + } - binding.clearDueDate.setOnClickListener(v -> { - binding.dueDateDate.setText(null); - binding.dueDateTime.setText(null); - viewModel.getFullCard().getCard().setDueDate(null); - binding.clearDueDate.setVisibility(GONE); - }); - } else { - binding.dueDateDate.setEnabled(false); - binding.dueDateTime.setEnabled(false); - binding.clearDueDate.setVisibility(GONE); - } + @Override + public void onDoneChanged(@Nullable Instant done) { + final var version = this.viewModel.getAccount().getServerDeckVersionAsObject(); + final var card = this.viewModel.getFullCard().getCard(); + card.setDone(done); + binding.cardDueDateView.setDueDate(getChildFragmentManager(), version, card.getDueDate(), card.getDone()); } private void setupLabels(@NonNull Account account) { @@ -333,8 +295,8 @@ public class CardDetailsFragment extends Fragment implements OnDateSetListener, 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.labelsWrapper.getWidth() / (DimensionUtil.INSTANCE.dpToPx(requireContext(), R.dimen.avatar_size) + gutter); + @Px final int gutter = getResources().getDimensionPixelSize(R.dimen.spacer_1x); + final int spanCount = (int) (float) binding.labelsWrapper.getWidth() / (getResources().getDimensionPixelSize(R.dimen.avatar_size) + gutter); binding.assignees.setLayoutManager(new GridLayoutManager(getContext(), spanCount)); binding.assignees.addItemDecoration(new AssigneeDecoration(spanCount, gutter)); }); @@ -364,53 +326,6 @@ public class CardDetailsFragment extends Fragment implements OnDateSetListener, } } - @Override - public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) { - int hourOfDay; - int minute; - - final var 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(); - } - - final var 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().toEpochMilli() == 0) { - binding.clearDueDate.setVisibility(GONE); - } else { - binding.clearDueDate.setVisibility(VISIBLE); - } - } - - @Override - public void onTimeSet(TimePickerDialog view, int hourOfDay, int minute, int second) { - final var oldInstant = this.viewModel.getFullCard().getCard().getDueDate(); - final var oldDateTime = oldInstant == null ? ZonedDateTime.now() : oldInstant.atZone(ZoneId.systemDefault()); - final var 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); - } - } - private void setupProjects() { if (viewModel.getFullCard().getProjects().size() > 0) { binding.projectsTitle.setVisibility(VISIBLE); diff --git a/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDueDateView.java b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDueDateView.java new file mode 100644 index 000000000..2266e6c7d --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDueDateView.java @@ -0,0 +1,302 @@ +package it.niedermann.nextcloud.deck.ui.card.details; + +import android.content.Context; +import android.text.TextUtils; +import android.util.AttributeSet; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.FrameLayout; + +import androidx.annotation.ColorInt; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.FragmentManager; + +import com.nextcloud.android.common.ui.theme.utils.ColorRole; +import com.wdullaer.materialdatetimepicker.date.DatePickerDialog; +import com.wdullaer.materialdatetimepicker.time.TimePickerDialog; + +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 java.util.stream.Stream; + +import it.niedermann.nextcloud.deck.R; +import it.niedermann.nextcloud.deck.databinding.ViewCardDueDateBinding; +import it.niedermann.nextcloud.deck.model.ocs.Version; +import it.niedermann.nextcloud.deck.ui.theme.ThemeUtils; +import it.niedermann.nextcloud.deck.ui.theme.Themed; +import it.niedermann.nextcloud.deck.ui.theme.ThemedDatePickerDialog; +import it.niedermann.nextcloud.deck.ui.theme.ThemedTimePickerDialog; + +public class CardDueDateView extends FrameLayout implements DatePickerDialog.OnDateSetListener, TimePickerDialog.OnTimeSetListener, Themed { + + private final ViewCardDueDateBinding binding; + @Nullable + private DueDateChangedListener dueDateChangedListener; + private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("d. MMM yyyy HH:mm"); + private final DateTimeFormatter dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); + private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT); + boolean supportsDone = false; + @Nullable + private Instant dueDate = null; + @Nullable + private Instant done = null; + @Nullable + @ColorInt + private Integer color = null; + private FragmentManager fragmentManager = null; + + public CardDueDateView(Context context) { + super(context); + binding = ViewCardDueDateBinding.inflate(LayoutInflater.from(context), this, true); + } + + public CardDueDateView(Context context, @Nullable AttributeSet attrs) { + super(context, attrs); + binding = ViewCardDueDateBinding.inflate(LayoutInflater.from(context), this, true); + } + + public CardDueDateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + binding = ViewCardDueDateBinding.inflate(LayoutInflater.from(context), this, true); + } + + /** + * @noinspection unused + */ + public CardDueDateView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { + super(context, attrs, defStyleAttr, defStyleRes); + binding = ViewCardDueDateBinding.inflate(LayoutInflater.from(context), this, true); + } + + @Override + public void setEnabled(boolean enabled) { + if (enabled != isEnabled()) { + super.setEnabled(enabled); + render(); + } + } + + public void setDueDate(@NonNull FragmentManager fragmentManager, @NonNull Version version, @Nullable Instant dueDate, @Nullable Instant done) { + this.fragmentManager = fragmentManager; + this.supportsDone = version.supportsDone(); + this.dueDate = dueDate; + this.done = done; + render(); + } + + private void render() { + setVisibilityState(); + setTextState(); + setInteraction(); + } + + private void setVisibilityState() { + if (done == null) { + Stream.of( + binding.dueDateDateWrapper, + binding.dueDateTimeWrapper + ).forEach(v -> v.setVisibility(View.VISIBLE)); + + Stream.of( + binding.doneCheck, + binding.doneDueDate, + binding.doneDate, + binding.clearDone + ).forEach(v -> v.setVisibility(View.GONE)); + + binding.markAsDone.setVisibility(supportsDone ? View.VISIBLE : View.GONE); + binding.clearDueDate.setVisibility(dueDate == null || !isEnabled() ? View.GONE : View.VISIBLE); + } else { + + Stream.of( + binding.doneCheck, + binding.doneDate + ).forEach(v -> v.setVisibility(View.VISIBLE)); + + Stream.of( + binding.markAsDone, + binding.dueDateDateWrapper, + binding.dueDateTimeWrapper, + binding.clearDueDate + ).forEach(v -> v.setVisibility(View.GONE)); + + binding.clearDone.setVisibility(supportsDone ? View.VISIBLE : View.GONE); + binding.doneDueDate.setVisibility(dueDate == null || !isEnabled() ? View.GONE : View.VISIBLE); + } + } + + private void setTextState() { + if (done == null) { + binding.doneDate.setText(null); + binding.doneDueDate.setText(null); + + if (this.dueDate == null) { + binding.dueDateDate.setText(null); + binding.dueDateTime.setText(null); + + } else { + final var dueDate = this.dueDate.atZone(ZoneId.systemDefault()); + binding.dueDateDate.setText(dueDate.format(dateFormatter)); + binding.dueDateTime.setText(dueDate.format(timeFormatter)); + } + + } else { + binding.dueDateDate.setText(null); + binding.dueDateTime.setText(null); + + binding.doneDate.setText(done.atZone(ZoneId.systemDefault()).format(dateTimeFormatter)); + + if (this.dueDate == null) { + binding.doneDueDate.setText(null); + + } else { + final var dueDate = this.dueDate.atZone(ZoneId.systemDefault()); + binding.doneDueDate.setText(getContext().getString(R.string.label_due_at, dueDate.format(dateTimeFormatter))); + } + } + } + + private void setInteraction() { + final var enabled = isEnabled(); + + binding.dueDateDate.setEnabled(enabled); + binding.dueDateTime.setEnabled(enabled); + + if (enabled) { + binding.clearDone.setOnClickListener(v -> { + if (this.dueDateChangedListener != null) { + this.dueDateChangedListener.onDoneChanged(null); + } + }); + + if (supportsDone) { + binding.markAsDone.setOnClickListener(v -> { + if (this.dueDateChangedListener != null) { + this.dueDateChangedListener.onDoneChanged(Instant.now()); + } + }); + } else { + binding.markAsDone.setOnClickListener(null); + } + + binding.clearDueDate.setOnClickListener(v -> { + if (this.dueDateChangedListener != null) { + this.dueDateChangedListener.onDueDateChanged(null); + } + }); + + binding.dueDateDate.setOnClickListener(v -> { + if (this.fragmentManager == null || this.color == null) { + return; + } + final LocalDate date; + if (this.dueDate != null) { + date = this.dueDate.atZone(ZoneId.systemDefault()).toLocalDate(); + } else { + date = LocalDate.now(); + } + + ThemedDatePickerDialog.newInstance(this, date.getYear(), date.getMonthValue(), date.getDayOfMonth(), this.color) + .show(this.fragmentManager, ThemedDatePickerDialog.class.getCanonicalName()); + }); + + binding.dueDateTime.setOnClickListener(v -> { + if (this.fragmentManager == null || this.color == null) { + return; + } + final LocalTime time; + if (this.dueDate != null) { + time = this.dueDate.atZone(ZoneId.systemDefault()).toLocalTime(); + } else { + time = LocalTime.now(); + } + ThemedTimePickerDialog.newInstance(this, time.getHour(), time.getMinute(), true, this.color) + .show(this.fragmentManager, ThemedTimePickerDialog.class.getCanonicalName()); + }); + } else { + Stream.of( + binding.clearDone, + binding.markAsDone, + binding.clearDueDate, + binding.dueDateDate, + binding.dueDateTime + ).forEach(v -> v.setOnClickListener(null)); + } + } + + @Override + public void onDateSet(DatePickerDialog view, int year, int monthOfYear, int dayOfMonth) { + int hourOfDay; + int minute; + + final var selectedTime = binding.dueDateTime.getText(); + if (TextUtils.isEmpty(selectedTime)) { + hourOfDay = 0; + minute = 0; + } else { + assert this.dueDate != null; // Since selectedTime is not empty and is derived from dueDate, dueDate itself shouldn't be null here. + final var oldTime = LocalTime.from(this.dueDate.atZone(ZoneId.systemDefault())); + hourOfDay = oldTime.getHour(); + minute = oldTime.getMinute(); + } + + final var newDateTime = ZonedDateTime.of( + LocalDate.of(year, monthOfYear + 1, dayOfMonth), + LocalTime.of(hourOfDay, minute), + ZoneId.systemDefault() + ); + this.dueDate = newDateTime.toInstant(); + + if (dueDateChangedListener != null) { + dueDateChangedListener.onDueDateChanged(newDateTime.toInstant()); + } + } + + @Override + public void onTimeSet(TimePickerDialog view, int hourOfDay, int minute, int second) { + final var oldDateTime = this.dueDate == null ? ZonedDateTime.now() : this.dueDate.atZone(ZoneId.systemDefault()); + final var newDateTime = oldDateTime.with( + LocalTime.of(hourOfDay, minute) + ); + + if (dueDateChangedListener != null) { + dueDateChangedListener.onDueDateChanged(newDateTime.toInstant()); + } + } + + @Override + public void applyTheme(int color) { + this.color = color; + final var utils = ThemeUtils.of(color, getContext()); + + Stream.of( + binding.dueDateDateWrapper, + binding.dueDateTimeWrapper + ).forEach(utils.material::colorTextInputLayout); + + Stream.of( + binding.clearDone, + binding.clearDueDate + ).forEach(v -> utils.platform.colorImageView(v, ColorRole.SECONDARY)); + + utils.platform.colorTextView(binding.doneDate, ColorRole.ON_SURFACE); + utils.platform.colorTextView(binding.doneDueDate, ColorRole.ON_SURFACE_VARIANT); + utils.material.colorMaterialButtonPrimaryTonal(binding.markAsDone); + } + + public void setDueDateListener(@Nullable DueDateChangedListener dueDateChangedListener) { + this.dueDateChangedListener = dueDateChangedListener; + } + + public interface DueDateChangedListener { + void onDueDateChanged(@Nullable Instant dueDate); + + void onDoneChanged(@Nullable Instant done); + } +} |