Scheduled toot (#1004)
* Scheduled toot * Hide scheduled toot button if version < 2.7.0 * Fix timeline reloading after toot * Add edit icon to ComposeScheduleView * Add button to reset scheduled toot * Close bottom sheet and change button color after time a was selected * Fix edit icon's size * List of scheduled toots * Fix instance version check * Use MaterialDatePicker * Set date and time consecutively * Add licensesmain
parent
d72e39a9d2
commit
dc933b439d
@ -0,0 +1,166 @@ |
||||
package com.keylesspalace.tusky |
||||
|
||||
import android.content.Context |
||||
import android.content.Intent |
||||
import android.os.Bundle |
||||
import android.view.View |
||||
import androidx.appcompat.widget.Toolbar |
||||
import androidx.lifecycle.Lifecycle |
||||
import androidx.recyclerview.widget.DividerItemDecoration |
||||
import androidx.recyclerview.widget.LinearLayoutManager |
||||
import com.keylesspalace.tusky.adapter.ScheduledTootAdapter |
||||
import com.keylesspalace.tusky.appstore.EventHub |
||||
import com.keylesspalace.tusky.appstore.StatusScheduledEvent |
||||
import com.keylesspalace.tusky.di.Injectable |
||||
import com.keylesspalace.tusky.entity.ScheduledStatus |
||||
import com.keylesspalace.tusky.network.MastodonApi |
||||
import com.keylesspalace.tusky.util.hide |
||||
import com.keylesspalace.tusky.util.show |
||||
import com.uber.autodispose.AutoDispose.autoDisposable |
||||
import com.uber.autodispose.android.lifecycle.AndroidLifecycleScopeProvider.from |
||||
import io.reactivex.android.schedulers.AndroidSchedulers |
||||
import kotlinx.android.synthetic.main.activity_scheduled_toot.* |
||||
import okhttp3.ResponseBody |
||||
import retrofit2.Call |
||||
import retrofit2.Callback |
||||
import retrofit2.Response |
||||
import javax.inject.Inject |
||||
|
||||
|
||||
class ScheduledTootActivity : BaseActivity(), ScheduledTootAdapter.ScheduledTootAction, Injectable { |
||||
|
||||
companion object { |
||||
@JvmStatic |
||||
fun newIntent(context: Context): Intent { |
||||
return Intent(context, ScheduledTootActivity::class.java) |
||||
} |
||||
} |
||||
|
||||
lateinit var adapter: ScheduledTootAdapter |
||||
|
||||
@Inject |
||||
lateinit var mastodonApi: MastodonApi |
||||
@Inject |
||||
lateinit var eventHub: EventHub |
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) { |
||||
super.onCreate(savedInstanceState) |
||||
setContentView(R.layout.activity_scheduled_toot) |
||||
|
||||
val toolbar = findViewById<Toolbar>(R.id.toolbar) |
||||
|
||||
setSupportActionBar(toolbar) |
||||
val bar = supportActionBar |
||||
if (bar != null) { |
||||
bar.title = getString(R.string.title_scheduled_toot) |
||||
bar.setDisplayHomeAsUpEnabled(true) |
||||
bar.setDisplayShowHomeEnabled(true) |
||||
} |
||||
|
||||
swipe_refresh_layout.setOnRefreshListener(this::refreshStatuses) |
||||
|
||||
scheduled_toot_list.setHasFixedSize(true) |
||||
val layoutManager = LinearLayoutManager(this) |
||||
scheduled_toot_list.layoutManager = layoutManager |
||||
val divider = DividerItemDecoration(this, layoutManager.orientation) |
||||
scheduled_toot_list.addItemDecoration(divider) |
||||
adapter = ScheduledTootAdapter(this) |
||||
scheduled_toot_list.adapter = adapter |
||||
|
||||
loadStatuses() |
||||
|
||||
eventHub.events |
||||
.observeOn(AndroidSchedulers.mainThread()) |
||||
.`as`(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) |
||||
.subscribe { event -> |
||||
if (event is StatusScheduledEvent) { |
||||
refreshStatuses() |
||||
} |
||||
} |
||||
} |
||||
|
||||
fun loadStatuses() { |
||||
progress_bar.visibility = View.VISIBLE |
||||
mastodonApi.scheduledStatuses() |
||||
.enqueue(object : Callback<List<ScheduledStatus>> { |
||||
override fun onResponse(call: Call<List<ScheduledStatus>>, response: Response<List<ScheduledStatus>>) { |
||||
progress_bar.visibility = View.GONE |
||||
if (response.body().isNullOrEmpty()) { |
||||
errorMessageView.show() |
||||
errorMessageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, |
||||
null) |
||||
} else { |
||||
show(response.body()!!) |
||||
} |
||||
} |
||||
|
||||
override fun onFailure(call: Call<List<ScheduledStatus>>, t: Throwable) { |
||||
progress_bar.visibility = View.GONE |
||||
errorMessageView.show() |
||||
errorMessageView.setup(R.drawable.elephant_error, R.string.error_generic) { |
||||
errorMessageView.hide() |
||||
loadStatuses() |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
|
||||
private fun refreshStatuses() { |
||||
swipe_refresh_layout.isRefreshing = true |
||||
mastodonApi.scheduledStatuses() |
||||
.enqueue(object : Callback<List<ScheduledStatus>> { |
||||
override fun onResponse(call: Call<List<ScheduledStatus>>, response: Response<List<ScheduledStatus>>) { |
||||
swipe_refresh_layout.isRefreshing = false |
||||
if (response.body().isNullOrEmpty()) { |
||||
errorMessageView.show() |
||||
errorMessageView.setup(R.drawable.elephant_friend_empty, R.string.message_empty, |
||||
null) |
||||
} else { |
||||
show(response.body()!!) |
||||
} |
||||
} |
||||
|
||||
override fun onFailure(call: Call<List<ScheduledStatus>>, t: Throwable) { |
||||
swipe_refresh_layout.isRefreshing = false |
||||
} |
||||
}) |
||||
} |
||||
|
||||
fun show(statuses: List<ScheduledStatus>) { |
||||
adapter.setItems(statuses) |
||||
adapter.notifyDataSetChanged() |
||||
} |
||||
|
||||
override fun edit(position: Int, item: ScheduledStatus?) { |
||||
if (item == null) { |
||||
return |
||||
} |
||||
val intent = ComposeActivity.IntentBuilder() |
||||
.tootText(item.params.text) |
||||
.contentWarning(item.params.spoilerText) |
||||
.mediaAttachments(item.mediaAttachments) |
||||
.inReplyToId(item.params.inReplyToId) |
||||
.visibility(item.params.visibility) |
||||
.scheduledAt(item.scheduledAt) |
||||
.sensitive(item.params.sensitive) |
||||
.build(this) |
||||
startActivity(intent) |
||||
delete(position, item) |
||||
} |
||||
|
||||
override fun delete(position: Int, item: ScheduledStatus?) { |
||||
if (item == null) { |
||||
return |
||||
} |
||||
mastodonApi.deleteScheduledStatus(item.id) |
||||
.enqueue(object : Callback<ResponseBody> { |
||||
override fun onResponse(call: Call<ResponseBody>, response: Response<ResponseBody>) { |
||||
adapter.removeItem(position) |
||||
} |
||||
|
||||
override fun onFailure(call: Call<ResponseBody>, t: Throwable) { |
||||
|
||||
} |
||||
}) |
||||
} |
||||
} |
@ -0,0 +1,125 @@ |
||||
/* Copyright 2019 kyori19 |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the |
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
||||
* Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.adapter; |
||||
|
||||
import android.content.Context; |
||||
import android.view.LayoutInflater; |
||||
import android.view.View; |
||||
import android.view.ViewGroup; |
||||
import android.widget.ImageButton; |
||||
import android.widget.TextView; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.annotation.Nullable; |
||||
import androidx.recyclerview.widget.RecyclerView; |
||||
|
||||
import com.keylesspalace.tusky.R; |
||||
import com.keylesspalace.tusky.entity.ScheduledStatus; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
|
||||
public class ScheduledTootAdapter extends RecyclerView.Adapter { |
||||
private List<ScheduledStatus> list; |
||||
private ScheduledTootAction handler; |
||||
|
||||
public ScheduledTootAdapter(Context context) { |
||||
super(); |
||||
list = new ArrayList<>(); |
||||
handler = (ScheduledTootAction) context; |
||||
} |
||||
|
||||
@NonNull |
||||
@Override |
||||
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { |
||||
View view = LayoutInflater.from(parent.getContext()) |
||||
.inflate(R.layout.item_scheduled_toot, parent, false); |
||||
return new TootViewHolder(view); |
||||
} |
||||
|
||||
@Override |
||||
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { |
||||
TootViewHolder holder = (TootViewHolder) viewHolder; |
||||
holder.bind(getItem(position)); |
||||
} |
||||
|
||||
@Override |
||||
public int getItemCount() { |
||||
return list.size(); |
||||
} |
||||
|
||||
public void setItems(List<ScheduledStatus> newToot) { |
||||
list = new ArrayList<>(); |
||||
list.addAll(newToot); |
||||
} |
||||
|
||||
@Nullable |
||||
public ScheduledStatus removeItem(int position) { |
||||
if (position < 0 || position >= list.size()) { |
||||
return null; |
||||
} |
||||
ScheduledStatus toot = list.remove(position); |
||||
notifyItemRemoved(position); |
||||
return toot; |
||||
} |
||||
|
||||
private ScheduledStatus getItem(int position) { |
||||
if (position >= 0 && position < list.size()) { |
||||
return list.get(position); |
||||
} |
||||
return null; |
||||
} |
||||
|
||||
public interface ScheduledTootAction { |
||||
void edit(int position, ScheduledStatus item); |
||||
|
||||
void delete(int position, ScheduledStatus item); |
||||
} |
||||
|
||||
private class TootViewHolder extends RecyclerView.ViewHolder { |
||||
View view; |
||||
TextView text; |
||||
ImageButton edit; |
||||
ImageButton delete; |
||||
|
||||
TootViewHolder(View view) { |
||||
super(view); |
||||
this.view = view; |
||||
this.text = view.findViewById(R.id.text); |
||||
this.edit = view.findViewById(R.id.edit); |
||||
this.delete = view.findViewById(R.id.delete); |
||||
} |
||||
|
||||
void bind(final ScheduledStatus item) { |
||||
edit.setEnabled(true); |
||||
delete.setEnabled(true); |
||||
|
||||
if (item != null) { |
||||
text.setText(item.getParams().getText()); |
||||
|
||||
edit.setOnClickListener(v -> { |
||||
v.setEnabled(false); |
||||
handler.edit(getAdapterPosition(), item); |
||||
}); |
||||
|
||||
delete.setOnClickListener(v -> { |
||||
v.setEnabled(false); |
||||
handler.delete(getAdapterPosition(), item); |
||||
}); |
||||
} |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,25 @@ |
||||
/* Copyright 2019 kyori19 |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the |
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
||||
* Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */ |
||||
|
||||
package com.keylesspalace.tusky.entity |
||||
|
||||
import com.google.gson.annotations.SerializedName |
||||
|
||||
data class ScheduledStatus( |
||||
val id: String, |
||||
@SerializedName("scheduled_at") val scheduledAt: String, |
||||
val params: StatusParams, |
||||
@SerializedName("media_attachments") val mediaAttachments: ArrayList<Attachment> |
||||
) |
@ -0,0 +1,26 @@ |
||||
/* Copyright 2019 kyori19 |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the |
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
||||
* Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */ |
||||
|
||||
package com.keylesspalace.tusky.entity |
||||
|
||||
import com.google.gson.annotations.SerializedName |
||||
|
||||
data class StatusParams( |
||||
val text: String, |
||||
val sensitive: Boolean, |
||||
val visibility: Status.Visibility, |
||||
@SerializedName("spoiler_text") val spoilerText: String, |
||||
@SerializedName("in_reply_to_id") val inReplyToId: String? |
||||
) |
@ -0,0 +1,53 @@ |
||||
/* Copyright 2019 kyori19 |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the |
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
||||
* Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.fragment; |
||||
|
||||
import android.app.Dialog; |
||||
import android.app.TimePickerDialog; |
||||
import android.os.Bundle; |
||||
|
||||
import androidx.annotation.NonNull; |
||||
import androidx.fragment.app.DialogFragment; |
||||
|
||||
import com.keylesspalace.tusky.ComposeActivity; |
||||
|
||||
import java.util.Calendar; |
||||
import java.util.TimeZone; |
||||
|
||||
public class TimePickerFragment extends DialogFragment { |
||||
|
||||
public static final String PICKER_TIME_HOUR = "picker_time_hour"; |
||||
public static final String PICKER_TIME_MINUTE = "picker_time_minute"; |
||||
|
||||
@Override |
||||
@NonNull |
||||
public Dialog onCreateDialog(Bundle savedInstanceState) { |
||||
Bundle args = getArguments(); |
||||
Calendar calendar = Calendar.getInstance(TimeZone.getDefault()); |
||||
if (args != null) { |
||||
calendar.set(Calendar.HOUR_OF_DAY, args.getInt(PICKER_TIME_HOUR)); |
||||
calendar.set(Calendar.MINUTE, args.getInt(PICKER_TIME_MINUTE)); |
||||
} |
||||
|
||||
return new TimePickerDialog(getContext(), |
||||
android.R.style.Theme_DeviceDefault_Dialog, |
||||
(ComposeActivity) getActivity(), |
||||
calendar.get(Calendar.HOUR_OF_DAY), |
||||
calendar.get(Calendar.MINUTE), |
||||
true); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,42 @@ |
||||
/* Copyright 2019 kyori19 |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the |
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
||||
* Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.util; |
||||
|
||||
import java.util.regex.Matcher; |
||||
import java.util.regex.Pattern; |
||||
|
||||
public class VersionUtils { |
||||
|
||||
private int major; |
||||
private int minor; |
||||
private int patch; |
||||
|
||||
public VersionUtils(String versionString) { |
||||
String regex = "([0-9]+)\\.([0-9]+)\\.([0-9]+).*"; |
||||
Pattern pattern = Pattern.compile(regex); |
||||
Matcher matcher = pattern.matcher(versionString); |
||||
if (matcher.find()) { |
||||
major = Integer.parseInt(matcher.group(1)); |
||||
minor = Integer.parseInt(matcher.group(2)); |
||||
patch = Integer.parseInt(matcher.group(3)); |
||||
} |
||||
} |
||||
|
||||
public boolean supportsScheduledToots() { |
||||
return (major == 2) ? ( (minor == 7) ? (patch >= 0) : (minor > 7) ) : (major > 2); |
||||
} |
||||
|
||||
} |
@ -0,0 +1,187 @@ |
||||
/* Copyright 2019 kyori19 |
||||
* |
||||
* This file is a part of Tusky. |
||||
* |
||||
* This program is free software; you can redistribute it and/or modify it under the terms of the |
||||
* GNU General Public License as published by the Free Software Foundation; either version 3 of the |
||||
* License, or (at your option) any later version. |
||||
* |
||||
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even |
||||
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General |
||||
* Public License for more details. |
||||
* |
||||
* You should have received a copy of the GNU General Public License along with Tusky; if not, |
||||
* see <http://www.gnu.org/licenses>. */
|
||||
|
||||
package com.keylesspalace.tusky.view; |
||||
|
||||
import android.content.Context; |
||||
import android.graphics.drawable.Drawable; |
||||
import android.os.Bundle; |
||||
import android.util.AttributeSet; |
||||
import android.widget.Button; |
||||
import android.widget.TextView; |
||||
|
||||
import androidx.appcompat.app.AppCompatActivity; |
||||
import androidx.constraintlayout.widget.ConstraintLayout; |
||||
|
||||
import com.google.android.material.datepicker.CalendarConstraints; |
||||
import com.google.android.material.datepicker.DateValidatorPointForward; |
||||
import com.google.android.material.datepicker.MaterialDatePicker; |
||||
import com.keylesspalace.tusky.R; |
||||
import com.keylesspalace.tusky.fragment.TimePickerFragment; |
||||
|
||||
import java.text.DateFormat; |
||||
import java.text.ParseException; |
||||
import java.text.SimpleDateFormat; |
||||
import java.util.Calendar; |
||||
import java.util.Date; |
||||
import java.util.Locale; |
||||
import java.util.TimeZone; |
||||
|
||||
public class ComposeScheduleView extends ConstraintLayout { |
||||
|
||||
private DateFormat dateFormat; |
||||
private DateFormat timeFormat; |
||||
private SimpleDateFormat iso8601; |
||||
|
||||
private Button resetScheduleButton; |
||||
private TextView scheduledDateTimeView; |
||||
|
||||
private Calendar scheduleDateTime; |
||||
|
||||
public ComposeScheduleView(Context context) { |
||||
super(context); |
||||
init(); |
||||
} |
||||
|
||||
public ComposeScheduleView(Context context, AttributeSet attrs) { |
||||
super(context, attrs); |
||||
init(); |
||||
} |
||||
|
||||
public ComposeScheduleView(Context context, AttributeSet attrs, int defStyleAttr) { |
||||
super(context, attrs, defStyleAttr); |
||||
init(); |
||||
} |
||||
|
||||
private void init() { |
||||
inflate(getContext(), R.layout.view_compose_schedule, this); |
||||
|
||||
dateFormat = SimpleDateFormat.getDateInstance(); |
||||
timeFormat = SimpleDateFormat.getTimeInstance(); |
||||
iso8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSS'Z'", Locale.getDefault()); |
||||
iso8601.setTimeZone(TimeZone.getTimeZone("UTC")); |
||||
|
||||
resetScheduleButton = findViewById(R.id.resetScheduleButton); |
||||
scheduledDateTimeView = findViewById(R.id.scheduledDateTime); |
||||
|
||||
scheduledDateTimeView.setOnClickListener(v -> openPickDateDialog()); |
||||
|
||||
scheduleDateTime = null; |
||||
|
||||
setScheduledDateTime(); |
||||
|
||||
setEditIcons(); |
||||
} |
||||
|
||||
private void setScheduledDateTime() { |
||||
if (scheduleDateTime == null) { |
||||
scheduledDateTimeView.setText(R.string.hint_configure_scheduled_toot); |
||||
} else { |
||||
scheduledDateTimeView.setText(String.format("%s %s", |
||||
dateFormat.format(scheduleDateTime.getTime()), |
||||
timeFormat.format(scheduleDateTime.getTime()))); |
||||
} |
||||
} |
||||
|
||||
private void setEditIcons() { |
||||
final int size = scheduledDateTimeView.getLineHeight(); |
||||
|
||||
Drawable icon = getContext().getDrawable(R.drawable.ic_create_24dp); |
||||
if (icon == null) { |
||||
return; |
||||
} |
||||
|
||||
icon.setBounds(0, 0, size, size); |
||||
|
||||
scheduledDateTimeView.setCompoundDrawables(null, null, icon, null); |
||||
} |
||||
|
||||
public void setResetOnClickListener(OnClickListener listener) { |
||||
resetScheduleButton.setOnClickListener(listener); |
||||
} |
||||
|
||||
public void resetSchedule() { |
||||
scheduleDateTime = null; |
||||
setScheduledDateTime(); |
||||
} |
||||
|
||||
private void openPickDateDialog() { |
||||
long yesterday = Calendar.getInstance().getTimeInMillis() - 24 * 60 * 60 * 1000; |
||||
CalendarConstraints calendarConstraints = new CalendarConstraints.Builder() |
||||
.setValidator(new DateValidatorPointForward(yesterday)) |
||||
.build(); |
||||
if (scheduleDateTime == null) { |
||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault()); |
||||
} |
||||
MaterialDatePicker<Long> picker = MaterialDatePicker.Builder |
||||
.datePicker() |
||||
.setSelection(scheduleDateTime.getTimeInMillis()) |
||||
.setCalendarConstraints(calendarConstraints) |
||||
.build(); |
||||
picker.addOnPositiveButtonClickListener(this::onDateSet); |
||||
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "date_picker"); |
||||
} |
||||
|
||||
private void openPickTimeDialog() { |
||||
TimePickerFragment picker = new TimePickerFragment(); |
||||
if (scheduleDateTime != null) { |
||||
Bundle args = new Bundle(); |
||||
args.putInt(TimePickerFragment.PICKER_TIME_HOUR, scheduleDateTime.get(Calendar.HOUR_OF_DAY)); |
||||
args.putInt(TimePickerFragment.PICKER_TIME_MINUTE, scheduleDateTime.get(Calendar.MINUTE)); |
||||
picker.setArguments(args); |
||||
} |
||||
picker.show(((AppCompatActivity) getContext()).getSupportFragmentManager(), "time_picker"); |
||||
} |
||||
|
||||
public void setDateTime(String scheduledAt) { |
||||
Date date; |
||||
try { |
||||
date = iso8601.parse(scheduledAt); |
||||
} catch (ParseException e) { |
||||
return; |
||||
} |
||||
if (scheduleDateTime == null) { |
||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault()); |
||||
} |
||||
scheduleDateTime.setTime(date); |
||||
setScheduledDateTime(); |
||||
} |
||||
|
||||
private void onDateSet(long selection) { |
||||
if (scheduleDateTime == null) { |
||||
scheduleDateTime = Calendar.getInstance(TimeZone.getDefault()); |
||||
} |
||||
Calendar newDate = Calendar.getInstance(TimeZone.getDefault()); |
||||
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()); |
||||
} |
||||
scheduleDateTime.set(Calendar.HOUR_OF_DAY, hourOfDay); |
||||
scheduleDateTime.set(Calendar.MINUTE, minute); |
||||
setScheduledDateTime(); |
||||
} |
||||
|
||||
public String getTime() { |
||||
if (scheduleDateTime == null) { |
||||
return null; |
||||
} |
||||
return iso8601.format(scheduleDateTime.getTime()); |
||||
} |
||||
} |
@ -0,0 +1,9 @@ |
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android" |
||||
android:width="24dp" |
||||
android:height="24dp" |
||||
android:viewportWidth="24.0" |
||||
android:viewportHeight="24.0"> |
||||
<path |
||||
android:fillColor="#FF000000" |
||||
android:pathData="M11.99,2C6.47,2 2,6.48 2,12s4.47,10 9.99,10C17.52,22 22,17.52 22,12S17.52,2 11.99,2zM12,20c-4.42,0 -8,-3.58 -8,-8s3.58,-8 8,-8 8,3.58 8,8 -3.58,8 -8,8zM12.5,7H11v6l5.25,3.15 0.75,-1.23 -4.5,-2.67z"/> |
||||
</vector> |
@ -0,0 +1,53 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
xmlns:tools="http://schemas.android.com/tools" |
||||
android:id="@+id/activity_view_thread" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
tools:context="com.keylesspalace.tusky.AccountListActivity"> |
||||
|
||||
<include layout="@layout/toolbar_basic" /> |
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" |
||||
app:layout_behavior="@string/appbar_scrolling_view_behavior"> |
||||
|
||||
<ProgressBar |
||||
android:id="@+id/progress_bar" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
app:layout_constraintBottom_toBottomOf="parent" |
||||
app:layout_constraintLeft_toLeftOf="parent" |
||||
app:layout_constraintRight_toRightOf="parent" |
||||
app:layout_constraintTop_toTopOf="parent" /> |
||||
|
||||
<com.keylesspalace.tusky.view.BackgroundMessageView |
||||
android:id="@+id/errorMessageView" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:src="@android:color/transparent" |
||||
android:visibility="gone" |
||||
app:layout_constraintBottom_toBottomOf="parent" |
||||
app:layout_constraintLeft_toLeftOf="parent" |
||||
app:layout_constraintRight_toRightOf="parent" |
||||
app:layout_constraintTop_toTopOf="parent" |
||||
tools:src="@drawable/elephant_error" |
||||
tools:visibility="visible" /> |
||||
|
||||
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout |
||||
android:id="@+id/swipe_refresh_layout" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent"> |
||||
|
||||
<androidx.recyclerview.widget.RecyclerView |
||||
android:id="@+id/scheduled_toot_list" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="match_parent" /> |
||||
|
||||
</androidx.swiperefreshlayout.widget.SwipeRefreshLayout> |
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout> |
||||
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout> |
@ -0,0 +1,40 @@ |
||||
<?xml version="1.0" encoding="utf-8"?> |
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" |
||||
xmlns:app="http://schemas.android.com/apk/res-auto" |
||||
android:layout_width="match_parent" |
||||
android:layout_height="wrap_content" |
||||
android:orientation="horizontal"> |
||||
|
||||
<androidx.emoji.widget.EmojiTextView |
||||
android:id="@+id/text" |
||||
android:layout_width="0dp" |
||||
android:layout_height="wrap_content" |
||||
android:layout_weight="0.91" |
||||
android:padding="8dp" |
||||
android:textSize="?attr/status_text_medium" /> |
||||
|
||||
<ImageButton |
||||
android:id="@+id/edit" |
||||
style="?attr/image_button_style" |
||||
android:layout_width="32dp" |
||||
android:layout_height="32dp" |
||||
android:layout_gravity="center_vertical" |
||||
android:layout_margin="12dp" |
||||
android:background="?attr/selectableItemBackgroundBorderless" |
||||
android:contentDescription="@string/action_edit" |
||||
android:padding="4dp" |
||||
app:srcCompat="@drawable/ic_create_24dp" /> |
||||
|
||||
<ImageButton |
||||
android:id="@+id/delete" |
||||
style="?attr/image_button_style" |
||||
android:layout_width="32dp" |
||||
android:layout_height="32dp" |
||||
android:layout_gravity="center_vertical" |
||||
android:layout_margin="12dp" |
||||
android:background="?attr/selectableItemBackgroundBorderless" |
||||
android:contentDescription="@string/action_delete" |
||||
android:padding="4dp" |
||||
app:srcCompat="@drawable/ic_clear_24dp" /> |
||||
|
||||
</LinearLayout> |
@ -0,0 +1,31 @@ |
||||
<merge 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" |
||||
tools:parentTag="androidx.constraintlayout.widget.ConstraintLayout"> |
||||
|
||||
<Button |
||||
android:id="@+id/resetScheduleButton" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:paddingStart="16dp" |
||||
android:paddingEnd="16dp" |
||||
android:text="@string/action_reset_schedule" |
||||
app:layout_constraintStart_toStartOf="parent" |
||||
app:layout_constraintBottom_toBottomOf="parent" /> |
||||
|
||||
<TextView |
||||
android:id="@+id/scheduledDateTime" |
||||
android:layout_width="wrap_content" |
||||
android:layout_height="wrap_content" |
||||
android:paddingBottom="16dp" |
||||
android:paddingEnd="16dp" |
||||
android:paddingStart="4dp" |
||||
android:paddingTop="4dp" |
||||
android:textColor="?android:textColorTertiary" |
||||
android:textSize="?attr/status_text_medium" |
||||
android:drawablePadding="4dp" |
||||
app:layout_constraintBottom_toBottomOf="parent" |
||||
app:layout_constraintEnd_toEndOf="parent" |
||||
tools:text="2020/01/01 00:00:00" /> |
||||
|
||||
</merge> |
Loading…
Reference in new issue