From 80e0c55b67619748ff8d1301f374b4d4c30a44eb Mon Sep 17 00:00:00 2001 From: Levi Bard Date: Tue, 25 Feb 2020 18:33:25 +0100 Subject: [PATCH] Warn when scheduling a post within 5 minutes (#1698) * Warn when scheduling a post within 5 minutes * Fix NPE when scheduled post time isn't set * Use AlertDialog with option to cancel instead of Toast when a post isn't scheduled far enough in advance * Move schedule validation warning to scheduling bottom sheet * Fix scheduling error display when sending after an initially-valid scheduling time has become invalid --- .../components/compose/ComposeActivity.kt | 19 +++++- .../compose/view/ComposeScheduleView.java | 66 ++++++++++++++----- .../main/res/layout/view_compose_schedule.xml | 21 +++++- app/src/main/res/values/strings.xml | 1 + 4 files changed, 87 insertions(+), 20 deletions(-) diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt index a798f4ff0..368819b88 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/ComposeActivity.kt @@ -65,6 +65,7 @@ import com.keylesspalace.tusky.adapter.OnEmojiSelectedListener import com.keylesspalace.tusky.components.compose.dialog.makeCaptionDialog import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener +import com.keylesspalace.tusky.components.compose.view.ComposeScheduleView import com.keylesspalace.tusky.db.AccountEntity import com.keylesspalace.tusky.di.Injectable import com.keylesspalace.tusky.di.ViewModelFactory @@ -695,9 +696,16 @@ class ComposeActivity : BaseActivity(), updateVisibleCharactersLeft() } + private fun verifyScheduledTime(): Boolean { + return composeScheduleView.verifyScheduledTime(composeScheduleView.getDateTime(viewModel.scheduledAt.value)) + } + private fun onSendClicked() { - enableButtons(false) - sendStatus() + if (verifyScheduledTime()) { + sendStatus() + } else { + showScheduleView() + } } /** This is for the fancy keyboards which can insert images and stuff. */ @@ -723,6 +731,7 @@ class ComposeActivity : BaseActivity(), } private fun sendStatus() { + enableButtons(false) val contentText = composeEditField.text.toString() var spoilerText = "" if (viewModel.showContentWarning.value!!) { @@ -974,7 +983,11 @@ class ComposeActivity : BaseActivity(), override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) { composeScheduleView.onTimeSet(hourOfDay, minute) viewModel.updateScheduledAt(composeScheduleView.time) - scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN + if (verifyScheduledTime()) { + scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN + } else { + showScheduleView() + } } private fun resetSchedule() { diff --git a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java index af10b2776..0deb20be7 100644 --- a/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java +++ b/app/src/main/java/com/keylesspalace/tusky/components/compose/view/ComposeScheduleView.java @@ -22,6 +22,8 @@ import android.util.AttributeSet; import android.widget.Button; import android.widget.TextView; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; import androidx.constraintlayout.widget.ConstraintLayout; @@ -48,8 +50,10 @@ public class ComposeScheduleView extends ConstraintLayout { private Button resetScheduleButton; private TextView scheduledDateTimeView; + private TextView invalidScheduleWarningView; private Calendar scheduleDateTime; + public static int MINIMUM_SCHEDULED_SECONDS = 330; // Minimum is 5 minutes, pad 30 seconds for posting public ComposeScheduleView(Context context) { super(context); @@ -76,8 +80,10 @@ public class ComposeScheduleView extends ConstraintLayout { resetScheduleButton = findViewById(R.id.resetScheduleButton); scheduledDateTimeView = findViewById(R.id.scheduledDateTime); + invalidScheduleWarningView = findViewById(R.id.invalidScheduleWarning); scheduledDateTimeView.setOnClickListener(v -> openPickDateDialog()); + invalidScheduleWarningView.setText(R.string.warning_scheduling_interval); scheduleDateTime = null; @@ -89,10 +95,13 @@ public class ComposeScheduleView extends ConstraintLayout { private void setScheduledDateTime() { if (scheduleDateTime == null) { scheduledDateTimeView.setText(""); + invalidScheduleWarningView.setVisibility(GONE); } else { + Date scheduled = scheduleDateTime.getTime(); scheduledDateTimeView.setText(String.format("%s %s", - dateFormat.format(scheduleDateTime.getTime()), - timeFormat.format(scheduleDateTime.getTime()))); + dateFormat.format(scheduled), + timeFormat.format(scheduled))); + verifyScheduledTime(scheduled); } } @@ -124,9 +133,7 @@ public class ComposeScheduleView extends ConstraintLayout { .setValidator( DateValidatorPointForward.from(yesterday)) .build(); - if (scheduleDateTime == null) { - scheduleDateTime = Calendar.getInstance(TimeZone.getDefault()); - } + initializeSuggestedTime(); MaterialDatePicker picker = MaterialDatePicker.Builder .datePicker() .setSelection(scheduleDateTime.getTimeInMillis()) @@ -147,6 +154,16 @@ public class ComposeScheduleView extends ConstraintLayout { picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker"); } + public Date getDateTime(String scheduledAt) { + if (scheduledAt != null) { + try { + return iso8601.parse(scheduledAt); + } catch (ParseException e) { + } + } + return null; + } + public void setDateTime(String scheduledAt) { Date date; try { @@ -154,27 +171,34 @@ public class ComposeScheduleView extends ConstraintLayout { } catch (ParseException e) { return; } - if (scheduleDateTime == null) { - scheduleDateTime = Calendar.getInstance(TimeZone.getDefault()); - } + initializeSuggestedTime(); scheduleDateTime.setTime(date); setScheduledDateTime(); } - private void onDateSet(long selection) { - if (scheduleDateTime == null) { - scheduleDateTime = Calendar.getInstance(TimeZone.getDefault()); + public boolean verifyScheduledTime(@Nullable Date scheduledTime) { + boolean valid; + if (scheduledTime != null) { + Calendar minimumScheduledTime = getCalendar(); + minimumScheduledTime.add(Calendar.SECOND, MINIMUM_SCHEDULED_SECONDS); + valid = scheduledTime.after(minimumScheduledTime.getTime()); + } else { + valid = true; } - Calendar newDate = Calendar.getInstance(TimeZone.getDefault()); + invalidScheduleWarningView.setVisibility(valid ? GONE : VISIBLE); + return valid; + } + + private void onDateSet(long selection) { + initializeSuggestedTime(); + Calendar newDate = getCalendar(); newDate.setTimeInMillis(selection); scheduleDateTime.set(newDate.get(Calendar.YEAR), newDate.get(Calendar.MONTH), newDate.get(Calendar.DATE)); openPickTimeDialog(); } public void onTimeSet(int hourOfDay, int minute) { - if (scheduleDateTime == null) { - scheduleDateTime = Calendar.getInstance(TimeZone.getDefault()); - } + initializeSuggestedTime(); scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay); scheduleDateTime.set(Calendar.MINUTE, minute); setScheduledDateTime(); @@ -186,4 +210,16 @@ public class ComposeScheduleView extends ConstraintLayout { } return iso8601.format(scheduleDateTime.getTime()); } + + @NonNull + public static Calendar getCalendar() { + return Calendar.getInstance(TimeZone.getDefault()); + } + + private void initializeSuggestedTime() { + if (scheduleDateTime == null) { + scheduleDateTime = getCalendar(); + scheduleDateTime.add(Calendar.MINUTE, 15); + } + } } diff --git a/app/src/main/res/layout/view_compose_schedule.xml b/app/src/main/res/layout/view_compose_schedule.xml index 98ef4f166..b07270f6a 100644 --- a/app/src/main/res/layout/view_compose_schedule.xml +++ b/app/src/main/res/layout/view_compose_schedule.xml @@ -10,7 +10,7 @@ android:layout_height="wrap_content" android:layout_marginEnd="16dp" android:text="@string/action_reset_schedule" - app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintBottom_toTopOf="@id/invalidScheduleWarning" app:layout_constraintStart_toStartOf="parent" /> + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 7311b17b5..f6b49e195 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -547,5 +547,6 @@ You don\'t have any drafts. You don\'t have any scheduled statuses. + Mastodon has a minimum scheduling interval of 5 minutes.