diff options
author | Stefan Niedermann <info@niedermann.it> | 2024-01-15 19:08:58 +0300 |
---|---|---|
committer | Stefan Niedermann <info@niedermann.it> | 2024-01-15 19:08:58 +0300 |
commit | c97057858e03f0cfc0318a0acbc5dad94fabe7e7 (patch) | |
tree | 3ed1a2d83606cb16a1ec526ab1c11acca4baaa91 /app/src/main | |
parent | 35f94be61a18013a42cd5b1d1c2955b28a26d405 (diff) |
feat(done): Show done state in card detail view
Refs: #1556
Signed-off-by: Stefan Niedermann <info@niedermann.it>
Diffstat (limited to 'app/src/main')
5 files changed, 466 insertions, 218 deletions
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 d96ffa21f..f5b7149e6 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 @@ -29,18 +29,8 @@ 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.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.android.markdown.MarkdownEditor; @@ -61,17 +51,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) { @@ -123,16 +109,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() { @@ -145,22 +131,16 @@ public class CardDetailsFragment extends Fragment implements OnDateSetListener, Stream.of( binding.labelsWrapper, - binding.dueDateDateWrapper, - binding.dueDateTimeWrapper, binding.peopleWrapper, binding.descriptionEditorWrapper ).forEach(utils.material::colorTextInputLayout); - Stream.of( - binding.clearDueDate, - binding.descriptionToggle - ).forEach(v -> utils.platform.colorImageView(v, ColorRole.SECONDARY)); + utils.platform.colorImageView(binding.descriptionToggle, ColorRole.SECONDARY); + binding.cardDueDateView.applyTheme(color); binding.descriptionEditor.setSearchColor(color); binding.descriptionViewer.setSearchColor(color); - utils.material.colorMaterialButtonPrimaryTonal(binding.markAsDone); - // TODO apply correct branding on the BrandedDatePicker } @@ -203,65 +183,24 @@ public class CardDetailsFragment extends Fragment implements OnDateSetListener, } private void setupDueDate() { + final var card = this.viewModel.getFullCard().getCard(); + binding.cardDueDateView.setDueDateListener(this); + binding.cardDueDateView.setEnabled(this.viewModel.canEdit()); + binding.cardDueDateView.setDueDate(getChildFragmentManager(), card.getDueDate(), card.getDone()); + } - 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()) { - if (this.viewModel.getFullCard().getCard().getDone() == null) { - binding.markAsDone.setVisibility(VISIBLE); - } else { - binding.markAsDone.setVisibility(GONE); - } - - binding.markAsDone.setOnClickListener(v -> { - viewModel.getFullCard().getCard().setDone(Instant.now()); - }); - - 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())); - }); - - 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 card = this.viewModel.getFullCard().getCard(); + card.setDueDate(dueDate); + binding.cardDueDateView.setDueDate(getChildFragmentManager(), 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); - binding.markAsDone.setVisibility(GONE); - } + @Override + public void onDoneChanged(@Nullable Instant done) { + final var card = this.viewModel.getFullCard().getCard(); + card.setDone(done); + binding.cardDueDateView.setDueDate(getChildFragmentManager(), card.getDueDate(), card.getDone()); } private void setupLabels(@NonNull Account account) { @@ -385,53 +324,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..1d264d35f --- /dev/null +++ b/app/src/main/java/it/niedermann/nextcloud/deck/ui/card/details/CardDueDateView.java @@ -0,0 +1,292 @@ +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.databinding.ViewCardDueDateBinding; +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 dateFormatter = DateTimeFormatter.ofLocalizedDate(FormatStyle.MEDIUM); + private final DateTimeFormatter timeFormatter = DateTimeFormatter.ofLocalizedTime(FormatStyle.SHORT); + @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); + } + + 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, @Nullable Instant dueDate, @Nullable Instant done) { + this.fragmentManager = fragmentManager; + this.dueDate = dueDate; + this.done = done; + render(); + } + + private void render() { + setVisibilityState(); + setTextState(); + setInteraction(); + } + + 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); + } + }); + + binding.markAsDone.setOnClickListener(v -> { + if (this.dueDateChangedListener != null) { + this.dueDateChangedListener.onDoneChanged(Instant.now()); + } + }); + + 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)); + } + } + + 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(dateFormatter)); + + if (this.dueDate == null) { + binding.doneDueDate.setText(null); + + } else { + final var dueDate = this.dueDate.atZone(ZoneId.systemDefault()); + binding.doneDueDate.setText(dueDate.format(dateFormatter)); + } + } + } + + private void setVisibilityState() { + if (done == null) { + Stream.of( + binding.markAsDone, + 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.clearDueDate.setVisibility(dueDate == null || !isEnabled() ? View.GONE : View.VISIBLE); + } else { + + Stream.of( + binding.doneCheck, + binding.doneDate, + binding.clearDone + ).forEach(v -> v.setVisibility(View.VISIBLE)); + + Stream.of( + binding.markAsDone, + binding.dueDateDateWrapper, + binding.dueDateTimeWrapper, + binding.clearDueDate + ).forEach(v -> v.setVisibility(View.GONE)); + + binding.doneDueDate.setVisibility(dueDate == null || !isEnabled() ? View.GONE : View.VISIBLE); + } + } + + @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 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.doneDueDate, + binding.doneDate + ).forEach(utils.platform::colorTextView); + + Stream.of( + binding.clearDone, + binding.clearDueDate + ).forEach(v -> utils.platform.colorImageView(v, ColorRole.SECONDARY)); + + 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); + } +} 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 80b107119..c39a00fc5 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 @@ -69,95 +69,22 @@ android:layout_marginTop="@dimen/spacer_1x" android:layout_marginBottom="@dimen/spacer_1hx" app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" - app:layout_constraintBottom_toTopOf="@id/dueDateDateWrapper" + app:layout_constraintBottom_toTopOf="@id/cardDueDateView" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@id/peopleWrapper" tools:listitem="@tools:sample/avatars" /> - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/dueDateDateWrapper" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginTop="@dimen/spacer_2x" - android:layout_marginEnd="@dimen/spacer_2x" - android:hint="@string/hint_due_date_date" - android:labelFor="@id/dueDateDate" - app:layout_constraintBottom_toTopOf="@id/markAsDone" - app:layout_constraintEnd_toStartOf="@id/dueDateTimeWrapper" - app:layout_constraintHorizontal_weight="2" - app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/assignees" - app:startIconDrawable="@drawable/calendar_blank_grey600_24dp"> - - <EditText - android:id="@+id/dueDateDate" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:enabled="true" - android:focusable="false" - android:importantForAutofill="no" - android:inputType="date" - android:maxLines="1" - tools:text="01/07/2020" /> - </com.google.android.material.textfield.TextInputLayout> - - <com.google.android.material.textfield.TextInputLayout - android:id="@+id/dueDateTimeWrapper" - android:layout_width="0dp" - android:layout_height="wrap_content" - android:layout_marginStart="@dimen/spacer_1hx" - android:hint="@string/hint_due_date_time" - android:labelFor="@id/dueDateTime" - app:layout_constraintBottom_toBottomOf="@id/dueDateDateWrapper" - app:layout_constraintEnd_toStartOf="@id/clearDueDate" - app:layout_constraintHorizontal_weight="1" - app:layout_constraintStart_toEndOf="@id/dueDateDateWrapper" - app:layout_constraintTop_toTopOf="@id/dueDateDateWrapper"> - - <EditText - android:id="@+id/dueDateTime" - android:layout_width="match_parent" - android:layout_height="match_parent" - android:enabled="true" - android:focusable="false" - android:importantForAutofill="no" - android:inputType="datetime" - android:maxLines="1" - android:minLines="0" - android:textAlignment="center" - tools:text="11:45" /> - </com.google.android.material.textfield.TextInputLayout> - - <ImageView - android:id="@+id/clearDueDate" - android:layout_width="wrap_content" - android:layout_height="wrap_content" - android:layout_marginVertical="@dimen/spacer_1hx" - android:layout_marginStart="@dimen/spacer_1x" - android:layout_marginEnd="0dp" - android:contentDescription="@string/label_clear_due_date" - android:padding="@dimen/spacer_11qx" - android:translationY="@dimen/spacer_1hx" - app:layout_constraintBottom_toBottomOf="@id/dueDateTimeWrapper" - app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintStart_toEndOf="@id/dueDateTimeWrapper" - app:layout_constraintTop_toTopOf="@id/dueDateTimeWrapper" - app:srcCompat="@drawable/ic_close_circle_grey600" /> - - <com.google.android.material.button.MaterialButton - android:id="@+id/markAsDone" - style="@style/Widget.Material3.Button" - android:layout_width="0dp" + <it.niedermann.nextcloud.deck.ui.card.details.CardDueDateView + android:id="@+id/cardDueDateView" + android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="@dimen/spacer_2x" - android:layout_marginBottom="@dimen/spacer_1hx" - android:text="@string/simple_completed" - app:backgroundTint="@color/defaultBrand" - app:icon="@drawable/ic_check_white_24dp" app:layout_constraintBottom_toTopOf="@id/descriptionWrapper" app:layout_constraintEnd_toEndOf="parent" - app:layout_constraintTop_toBottomOf="@id/dueDateDateWrapper" /> + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/assignees" /> + <RelativeLayout android:id="@+id/descriptionWrapper" @@ -167,7 +94,7 @@ app:layout_constraintBottom_toTopOf="@id/projectsTitle" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent" - app:layout_constraintTop_toBottomOf="@id/dueDateDateWrapper"> + app:layout_constraintTop_toBottomOf="@id/cardDueDateView"> <com.google.android.material.textfield.TextInputLayout android:id="@+id/descriptionEditorWrapper" diff --git a/app/src/main/res/layout/view_card_due_date.xml b/app/src/main/res/layout/view_card_due_date.xml new file mode 100644 index 000000000..dc183fea7 --- /dev/null +++ b/app/src/main/res/layout/view_card_due_date.xml @@ -0,0 +1,136 @@ +<?xml version="1.0" encoding="utf-8"?> +<androidx.constraintlayout.widget.ConstraintLayout 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"> + + <ImageView + android:id="@+id/doneCheck" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginVertical="@dimen/spacer_1hx" + android:layout_marginStart="@dimen/spacer_1qx" + android:layout_marginEnd="0dp" + android:contentDescription="@null" + android:padding="@dimen/spacer_11qx" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + app:srcCompat="@drawable/ic_check_white_24dp" + app:tint="?attr/colorOnSurface" /> + + <TextView + android:id="@+id/doneDate" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintBottom_toBottomOf="@id/doneCheck" + app:layout_constraintEnd_toStartOf="@id/clearDone" + app:layout_constraintStart_toEndOf="@id/doneCheck" + app:layout_constraintTop_toTopOf="@id/doneCheck" + tools:text="@tools:sample/date/ddmmyy" /> + + <ImageView + android:id="@+id/clearDone" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginVertical="@dimen/spacer_1hx" + android:layout_marginStart="@dimen/spacer_1x" + android:layout_marginEnd="0dp" + android:contentDescription="@string/label_clear_done" + android:padding="@dimen/spacer_11qx" + app:layout_constraintBottom_toBottomOf="@id/doneDate" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toTopOf="@id/doneDate" + app:srcCompat="@drawable/ic_close_circle_grey600" /> + + <TextView + android:id="@+id/done_due_date" + android:layout_width="0dp" + android:layout_height="wrap_content" + app:layout_constraintEnd_toStartOf="@id/clearDone" + app:layout_constraintStart_toEndOf="@id/doneCheck" + app:layout_constraintTop_toBottomOf="@id/doneDate" + tools:text="@tools:sample/date/ddmmyy" /> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/dueDateDateWrapper" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginEnd="@dimen/spacer_2x" + android:hint="@string/hint_due_date_date" + android:labelFor="@id/dueDateDate" + app:layout_constraintBottom_toTopOf="@id/markAsDone" + app:layout_constraintEnd_toStartOf="@id/dueDateTimeWrapper" + app:layout_constraintHorizontal_weight="2" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/done_due_date" + app:startIconDrawable="@drawable/calendar_blank_grey600_24dp"> + + <EditText + android:id="@+id/dueDateDate" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:enabled="true" + android:focusable="false" + android:importantForAutofill="no" + android:inputType="date" + android:maxLines="1" + tools:text="01/07/2020" /> + </com.google.android.material.textfield.TextInputLayout> + + <com.google.android.material.textfield.TextInputLayout + android:id="@+id/dueDateTimeWrapper" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginStart="@dimen/spacer_1hx" + android:hint="@string/hint_due_date_time" + android:labelFor="@id/dueDateTime" + app:layout_constraintBottom_toBottomOf="@id/dueDateDateWrapper" + app:layout_constraintEnd_toStartOf="@id/clearDueDate" + app:layout_constraintHorizontal_weight="1" + app:layout_constraintStart_toEndOf="@id/dueDateDateWrapper" + app:layout_constraintTop_toTopOf="@id/dueDateDateWrapper"> + + <EditText + android:id="@+id/dueDateTime" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:enabled="true" + android:focusable="false" + android:importantForAutofill="no" + android:inputType="datetime" + android:maxLines="1" + android:minLines="0" + android:textAlignment="center" + tools:text="11:45" /> + </com.google.android.material.textfield.TextInputLayout> + + <ImageView + android:id="@+id/clearDueDate" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:layout_marginVertical="@dimen/spacer_1hx" + android:layout_marginStart="@dimen/spacer_1x" + android:layout_marginEnd="0dp" + android:contentDescription="@string/label_clear_due_date" + android:padding="@dimen/spacer_11qx" + android:translationY="@dimen/spacer_1hx" + app:layout_constraintBottom_toBottomOf="@id/dueDateTimeWrapper" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toEndOf="@id/dueDateTimeWrapper" + app:layout_constraintTop_toTopOf="@id/dueDateTimeWrapper" + app:srcCompat="@drawable/ic_close_circle_grey600" /> + + <com.google.android.material.button.MaterialButton + android:id="@+id/markAsDone" + style="@style/Widget.Material3.Button" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:layout_marginTop="@dimen/spacer_2x" + android:layout_marginBottom="@dimen/spacer_1hx" + android:text="@string/simple_completed" + app:backgroundTint="@color/defaultBrand" + app:icon="@drawable/ic_check_white_24dp" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintTop_toBottomOf="@id/dueDateDateWrapper" /> +</androidx.constraintlayout.widget.ConstraintLayout> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 71b56aeab..0d7e4ae48 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -98,6 +98,7 @@ <string name="add_board">Add board</string> <string name="label_clear_due_date">Clear due date</string> + <string name="label_clear_done">Not completed</string> <string name="label_add">Add %1$s</string> <string name="url_maintainer" translatable="false">https://www.niedermann.it/</string> |