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
This commit is contained in:
parent
aba36ca6f8
commit
80e0c55b67
4 changed files with 87 additions and 20 deletions
|
@ -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.makeCaptionDialog
|
||||||
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
import com.keylesspalace.tusky.components.compose.dialog.showAddPollDialog
|
||||||
import com.keylesspalace.tusky.components.compose.view.ComposeOptionsListener
|
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.db.AccountEntity
|
||||||
import com.keylesspalace.tusky.di.Injectable
|
import com.keylesspalace.tusky.di.Injectable
|
||||||
import com.keylesspalace.tusky.di.ViewModelFactory
|
import com.keylesspalace.tusky.di.ViewModelFactory
|
||||||
|
@ -695,9 +696,16 @@ class ComposeActivity : BaseActivity(),
|
||||||
updateVisibleCharactersLeft()
|
updateVisibleCharactersLeft()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun verifyScheduledTime(): Boolean {
|
||||||
|
return composeScheduleView.verifyScheduledTime(composeScheduleView.getDateTime(viewModel.scheduledAt.value))
|
||||||
|
}
|
||||||
|
|
||||||
private fun onSendClicked() {
|
private fun onSendClicked() {
|
||||||
enableButtons(false)
|
if (verifyScheduledTime()) {
|
||||||
sendStatus()
|
sendStatus()
|
||||||
|
} else {
|
||||||
|
showScheduleView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This is for the fancy keyboards which can insert images and stuff. */
|
/** This is for the fancy keyboards which can insert images and stuff. */
|
||||||
|
@ -723,6 +731,7 @@ class ComposeActivity : BaseActivity(),
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendStatus() {
|
private fun sendStatus() {
|
||||||
|
enableButtons(false)
|
||||||
val contentText = composeEditField.text.toString()
|
val contentText = composeEditField.text.toString()
|
||||||
var spoilerText = ""
|
var spoilerText = ""
|
||||||
if (viewModel.showContentWarning.value!!) {
|
if (viewModel.showContentWarning.value!!) {
|
||||||
|
@ -974,7 +983,11 @@ class ComposeActivity : BaseActivity(),
|
||||||
override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {
|
override fun onTimeSet(view: TimePicker, hourOfDay: Int, minute: Int) {
|
||||||
composeScheduleView.onTimeSet(hourOfDay, minute)
|
composeScheduleView.onTimeSet(hourOfDay, minute)
|
||||||
viewModel.updateScheduledAt(composeScheduleView.time)
|
viewModel.updateScheduledAt(composeScheduleView.time)
|
||||||
|
if (verifyScheduledTime()) {
|
||||||
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
scheduleBehavior.state = BottomSheetBehavior.STATE_HIDDEN
|
||||||
|
} else {
|
||||||
|
showScheduleView()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun resetSchedule() {
|
private fun resetSchedule() {
|
||||||
|
|
|
@ -22,6 +22,8 @@ import android.util.AttributeSet;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.constraintlayout.widget.ConstraintLayout;
|
import androidx.constraintlayout.widget.ConstraintLayout;
|
||||||
|
|
||||||
|
@ -48,8 +50,10 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
|
|
||||||
private Button resetScheduleButton;
|
private Button resetScheduleButton;
|
||||||
private TextView scheduledDateTimeView;
|
private TextView scheduledDateTimeView;
|
||||||
|
private TextView invalidScheduleWarningView;
|
||||||
|
|
||||||
private Calendar scheduleDateTime;
|
private Calendar scheduleDateTime;
|
||||||
|
public static int MINIMUM_SCHEDULED_SECONDS = 330; // Minimum is 5 minutes, pad 30 seconds for posting
|
||||||
|
|
||||||
public ComposeScheduleView(Context context) {
|
public ComposeScheduleView(Context context) {
|
||||||
super(context);
|
super(context);
|
||||||
|
@ -76,8 +80,10 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
|
|
||||||
resetScheduleButton = findViewById(R.id.resetScheduleButton);
|
resetScheduleButton = findViewById(R.id.resetScheduleButton);
|
||||||
scheduledDateTimeView = findViewById(R.id.scheduledDateTime);
|
scheduledDateTimeView = findViewById(R.id.scheduledDateTime);
|
||||||
|
invalidScheduleWarningView = findViewById(R.id.invalidScheduleWarning);
|
||||||
|
|
||||||
scheduledDateTimeView.setOnClickListener(v -> openPickDateDialog());
|
scheduledDateTimeView.setOnClickListener(v -> openPickDateDialog());
|
||||||
|
invalidScheduleWarningView.setText(R.string.warning_scheduling_interval);
|
||||||
|
|
||||||
scheduleDateTime = null;
|
scheduleDateTime = null;
|
||||||
|
|
||||||
|
@ -89,10 +95,13 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
private void setScheduledDateTime() {
|
private void setScheduledDateTime() {
|
||||||
if (scheduleDateTime == null) {
|
if (scheduleDateTime == null) {
|
||||||
scheduledDateTimeView.setText("");
|
scheduledDateTimeView.setText("");
|
||||||
|
invalidScheduleWarningView.setVisibility(GONE);
|
||||||
} else {
|
} else {
|
||||||
|
Date scheduled = scheduleDateTime.getTime();
|
||||||
scheduledDateTimeView.setText(String.format("%s %s",
|
scheduledDateTimeView.setText(String.format("%s %s",
|
||||||
dateFormat.format(scheduleDateTime.getTime()),
|
dateFormat.format(scheduled),
|
||||||
timeFormat.format(scheduleDateTime.getTime())));
|
timeFormat.format(scheduled)));
|
||||||
|
verifyScheduledTime(scheduled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -124,9 +133,7 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
.setValidator(
|
.setValidator(
|
||||||
DateValidatorPointForward.from(yesterday))
|
DateValidatorPointForward.from(yesterday))
|
||||||
.build();
|
.build();
|
||||||
if (scheduleDateTime == null) {
|
initializeSuggestedTime();
|
||||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
|
||||||
}
|
|
||||||
MaterialDatePicker<Long> picker = MaterialDatePicker.Builder
|
MaterialDatePicker<Long> picker = MaterialDatePicker.Builder
|
||||||
.datePicker()
|
.datePicker()
|
||||||
.setSelection(scheduleDateTime.getTimeInMillis())
|
.setSelection(scheduleDateTime.getTimeInMillis())
|
||||||
|
@ -147,6 +154,16 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker");
|
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) {
|
public void setDateTime(String scheduledAt) {
|
||||||
Date date;
|
Date date;
|
||||||
try {
|
try {
|
||||||
|
@ -154,27 +171,34 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
} catch (ParseException e) {
|
} catch (ParseException e) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (scheduleDateTime == null) {
|
initializeSuggestedTime();
|
||||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
|
||||||
}
|
|
||||||
scheduleDateTime.setTime(date);
|
scheduleDateTime.setTime(date);
|
||||||
setScheduledDateTime();
|
setScheduledDateTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onDateSet(long selection) {
|
public boolean verifyScheduledTime(@Nullable Date scheduledTime) {
|
||||||
if (scheduleDateTime == null) {
|
boolean valid;
|
||||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
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);
|
newDate.setTimeInMillis(selection);
|
||||||
scheduleDateTime.set(newDate.get(Calendar.YEAR), newDate.get(Calendar.MONTH), newDate.get(Calendar.DATE));
|
scheduleDateTime.set(newDate.get(Calendar.YEAR), newDate.get(Calendar.MONTH), newDate.get(Calendar.DATE));
|
||||||
openPickTimeDialog();
|
openPickTimeDialog();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onTimeSet(int hourOfDay, int minute) {
|
public void onTimeSet(int hourOfDay, int minute) {
|
||||||
if (scheduleDateTime == null) {
|
initializeSuggestedTime();
|
||||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault());
|
|
||||||
}
|
|
||||||
scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay);
|
||||||
scheduleDateTime.set(Calendar.MINUTE, minute);
|
scheduleDateTime.set(Calendar.MINUTE, minute);
|
||||||
setScheduledDateTime();
|
setScheduledDateTime();
|
||||||
|
@ -186,4 +210,16 @@ public class ComposeScheduleView extends ConstraintLayout {
|
||||||
}
|
}
|
||||||
return iso8601.format(scheduleDateTime.getTime());
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,7 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginEnd="16dp"
|
android:layout_marginEnd="16dp"
|
||||||
android:text="@string/action_reset_schedule"
|
android:text="@string/action_reset_schedule"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@id/invalidScheduleWarning"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
@ -23,10 +23,27 @@
|
||||||
android:paddingBottom="16dp"
|
android:paddingBottom="16dp"
|
||||||
android:textColor="?android:textColorTertiary"
|
android:textColor="?android:textColorTertiary"
|
||||||
android:textSize="?attr/status_text_medium"
|
android:textSize="?attr/status_text_medium"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@id/invalidScheduleWarning"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintHorizontal_bias="1"
|
app:layout_constraintHorizontal_bias="1"
|
||||||
app:layout_constraintStart_toEndOf="@id/resetScheduleButton"
|
app:layout_constraintStart_toEndOf="@id/resetScheduleButton"
|
||||||
tools:text="2020/01/01 00:00:00" />
|
tools:text="2020/01/01 00:00:00" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/invalidScheduleWarning"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:drawablePadding="4dp"
|
||||||
|
android:paddingStart="4dp"
|
||||||
|
android:paddingTop="4dp"
|
||||||
|
android:paddingBottom="16dp"
|
||||||
|
android:textColor="?android:textColorTertiary"
|
||||||
|
android:textSize="?attr/status_text_medium"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="1"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
tools:text="@string/warning_scheduling_interval"
|
||||||
|
android:visibility="gone" />
|
||||||
|
|
||||||
</merge>
|
</merge>
|
|
@ -547,5 +547,6 @@
|
||||||
|
|
||||||
<string name="no_saved_status">You don\'t have any drafts.</string>
|
<string name="no_saved_status">You don\'t have any drafts.</string>
|
||||||
<string name="no_scheduled_status">You don\'t have any scheduled statuses.</string>
|
<string name="no_scheduled_status">You don\'t have any scheduled statuses.</string>
|
||||||
|
<string name="warning_scheduling_interval">Mastodon has a minimum scheduling interval of 5 minutes.</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in a new issue