diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java index 2ba80167..75eee3b3 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/Connection.java @@ -18,6 +18,7 @@ import org.nuclearfog.twidda.model.Instance; import org.nuclearfog.twidda.model.Notification; import org.nuclearfog.twidda.model.Poll; import org.nuclearfog.twidda.model.Relation; +import org.nuclearfog.twidda.model.ScheduledStatus; import org.nuclearfog.twidda.model.Status; import org.nuclearfog.twidda.model.Translation; import org.nuclearfog.twidda.model.Trend; @@ -27,6 +28,7 @@ import org.nuclearfog.twidda.model.WebPush; import org.nuclearfog.twidda.model.lists.Domains; import org.nuclearfog.twidda.model.lists.Filters; import org.nuclearfog.twidda.model.lists.Notifications; +import org.nuclearfog.twidda.model.lists.ScheduledStatuses; import org.nuclearfog.twidda.model.lists.Statuses; import org.nuclearfog.twidda.model.lists.Trends; import org.nuclearfog.twidda.model.lists.UserLists; @@ -448,6 +450,29 @@ public interface Connection { */ void deleteStatus(long id) throws ConnectionException; + /** + * get a list of current scheduled status + * + * @param minId minimum ID of the status + * @param maxId maximum ID of the status + */ + ScheduledStatuses getScheduledStatuses(long minId, long maxId) throws ConnectionException; + + /** + * update schedule time of a status + * + * @param id scheduled status ID + * @param schedule new schedule time + */ + ScheduledStatus updateScheduledStatus(long id, long schedule) throws ConnectionException; + + /** + * remove scheduled status + * + * @param id scheduled status ID + */ + void canselScheduledStatus(long id) throws ConnectionException; + /** * return a list of domain names the current user has blocked * diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java index 9c54cf4b..cf8d6eab 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/Mastodon.java @@ -27,6 +27,7 @@ import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonStatus; import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonTranslation; import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonTrend; import org.nuclearfog.twidda.backend.api.mastodon.impl.MastodonUser; +import org.nuclearfog.twidda.backend.api.mastodon.impl.ScheduledMastodonStatus; import org.nuclearfog.twidda.backend.helper.ConnectionResult; import org.nuclearfog.twidda.backend.helper.MediaStatus; import org.nuclearfog.twidda.backend.helper.update.ConnectionUpdate; @@ -47,6 +48,7 @@ import org.nuclearfog.twidda.model.Instance; import org.nuclearfog.twidda.model.Notification; import org.nuclearfog.twidda.model.Poll; import org.nuclearfog.twidda.model.Relation; +import org.nuclearfog.twidda.model.ScheduledStatus; import org.nuclearfog.twidda.model.Status; import org.nuclearfog.twidda.model.Translation; import org.nuclearfog.twidda.model.Trend; @@ -56,6 +58,7 @@ import org.nuclearfog.twidda.model.WebPush; import org.nuclearfog.twidda.model.lists.Domains; import org.nuclearfog.twidda.model.lists.Filters; import org.nuclearfog.twidda.model.lists.Notifications; +import org.nuclearfog.twidda.model.lists.ScheduledStatuses; import org.nuclearfog.twidda.model.lists.Statuses; import org.nuclearfog.twidda.model.lists.Trends; import org.nuclearfog.twidda.model.lists.UserLists; @@ -148,6 +151,7 @@ public class Mastodon implements Connection { private static final String ENDPOINT_PUSH_UPDATE = "/api/v1/push/subscription"; private static final String ENDPOINT_FILTER = "/api/v2/filters"; private static final String ENDPOINT_REPORT = "/api/v1/reports"; + private static final String ENDPOINT_SCHEDULED_STATUS = "/api/v1/scheduled_statuses"; private static final MediaType TYPE_TEXT = MediaType.parse("text/plain"); private static final MediaType TYPE_STREAM = MediaType.parse("application/octet-stream"); @@ -715,6 +719,62 @@ public class Mastodon implements Connection { } + @Override + public ScheduledStatuses getScheduledStatuses(long minId, long maxId) throws ConnectionException { + try { + List params = new ArrayList<>(); + params.add("min_id" + minId); + params.add("max_id" + maxId); + params.add("limit" + settings.getListSize()); + Response response = get(ENDPOINT_SCHEDULED_STATUS, params); + ResponseBody body = response.body(); + if (response.code() == 200 && body != null) { + JSONArray jsonArray = new JSONArray(body.string()); + ScheduledStatuses result = new ScheduledStatuses(); + for (int i = 0 ; i < jsonArray.length() ; i++) { + result.add(new ScheduledMastodonStatus(jsonArray.getJSONObject(i))); + } + return result; + } + throw new MastodonException(response); + } catch (JSONException | IOException exception) { + throw new MastodonException(exception); + } + } + + + @Override + public ScheduledStatus updateScheduledStatus(long id, long schedule) throws ConnectionException { + try { + List params = new ArrayList<>(); + String dateFormat = ISODateTimeFormat.dateTimeNoMillis().print(schedule); + params.add("scheduled_at=" + StringUtils.encode(dateFormat)); + Response response = put(ENDPOINT_SCHEDULED_STATUS + "/" + id, params); + ResponseBody body = response.body(); + if (response.code() == 200 && body != null) { + JSONObject json = new JSONObject(body.string()); + return new ScheduledMastodonStatus(json); + } + throw new MastodonException(response); + } catch (IOException | JSONException exception) { + throw new MastodonException(exception); + } + } + + + @Override + public void canselScheduledStatus(long id) throws ConnectionException { + try { + Response response = delete(ENDPOINT_SCHEDULED_STATUS + "/" + id, new ArrayList<>()); + if (response.code() != 200) { + throw new MastodonException(response); + } + } catch (IOException exception) { + throw new MastodonException(exception); + } + } + + @Override public Domains getDomainBlocks(long cursor) throws ConnectionException { try { diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonStatus.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonStatus.java index 5fe3783e..9deec53f 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonStatus.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/MastodonStatus.java @@ -71,6 +71,7 @@ public class MastodonStatus implements Status { String replyUserIdStr = json.optString("in_reply_to_account_id", "0"); String idStr = json.getString("id"); String visibilityStr = json.getString("visibility"); + String language = json.optString("language", ""); author = new MastodonUser(json.getJSONObject("account"), currentUserId); createdAt = StringUtils.getIsoTime(json.optString("created_at")); @@ -133,11 +134,8 @@ public class MastodonStatus implements Status { if (cardJson != null) { cards = new Card[]{new MastodonCard(cardJson)}; } - if (json.has("language") && !json.isNull("language")) { - String language = json.getString("language"); - if (!language.equals(Locale.getDefault().getLanguage())) { - this.language = language; - } + if (!language.isEmpty() && !language.equals("null") && !language.equals(Locale.getDefault().getLanguage())) { + this.language = language; } switch (visibilityStr) { case "public": diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/ScheduledMastodonStatus.java b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/ScheduledMastodonStatus.java new file mode 100644 index 00000000..f05d735d --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/backend/api/mastodon/impl/ScheduledMastodonStatus.java @@ -0,0 +1,131 @@ +package org.nuclearfog.twidda.backend.api.mastodon.impl; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.nuclearfog.twidda.backend.utils.StringUtils; +import org.nuclearfog.twidda.model.Media; +import org.nuclearfog.twidda.model.Poll; +import org.nuclearfog.twidda.model.ScheduledStatus; +import org.nuclearfog.twidda.model.Status; + +/** + * Mastodon implementation of a scheduled status + * + * @author nuclearfog + */ +public class ScheduledMastodonStatus implements ScheduledStatus { + + private static final long serialVersionUID = -1340937182294786469L; + + private long id; + private String text; + private String language = ""; + private int visibility; + private boolean sensitive; + private boolean spoiler; + private Media[] medias = {}; + private Poll poll; + + /** + * + */ + public ScheduledMastodonStatus(JSONObject json) throws JSONException { + JSONObject params = json.getJSONObject("params"); + JSONObject pollJson = params.optJSONObject("poll"); + JSONArray mediaArray = json.optJSONArray("media_attachments"); + String idStr = json.getString("id"); + String visibilityStr = json.getString("visibility"); + text = StringUtils.extractText(json.optString("text", "")); + sensitive = params.optBoolean("sensitive", false); + spoiler = params.optBoolean("spoiler_text", false); + + if (!params.isNull("language")) { + language = params.optString("language"); + } + if (pollJson != null) { + poll = new MastodonPoll(pollJson); + } + if (mediaArray != null && mediaArray.length() > 0) { + medias = new Media[mediaArray.length()]; + for (int i = 0; i < mediaArray.length(); i++) { + JSONObject mediaItem = mediaArray.getJSONObject(i); + medias[i] = new MastodonMedia(mediaItem); + } + } + switch (visibilityStr) { + case "public": + visibility = Status.VISIBLE_PUBLIC; + break; + + case "private": + visibility = Status.VISIBLE_PRIVATE; + break; + + case "direct": + visibility = Status.VISIBLE_DIRECT; + break; + + case "unlisted": + visibility = Status.VISIBLE_UNLISTED; + break; + + default: + visibility = Status.VISIBLE_DEFAULT; + break; + } + try { + id = Long.parseLong(idStr); + } catch (NumberFormatException exception) { + throw new JSONException("Bad ID: " + idStr); + } + } + + + @Override + public long getId() { + return id; + } + + + @Override + public String getText() { + return text; + } + + + @Override + public String getLanguage() { + return language; + } + + + @Override + public Media[] getMedia() { + return medias; + } + + + @Override + public Poll getPoll() { + return poll; + } + + + @Override + public int getVisibility() { + return visibility; + } + + + @Override + public boolean isSensitive() { + return sensitive; + } + + + @Override + public boolean isSpoiler() { + return spoiler; + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/StatusUpdate.java b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/StatusUpdate.java index 7b99181d..7dd49ee8 100644 --- a/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/StatusUpdate.java +++ b/app/src/main/java/org/nuclearfog/twidda/backend/helper/update/StatusUpdate.java @@ -358,7 +358,7 @@ public class StatusUpdate implements Serializable, Closeable { /** * get visibility states * - * @return visibility states {@link Status#VISIBLE_PUBLIC,Status#VISIBLE_DIRECT,Status#VISIBLE_PRIVATE,Status#VISIBLE_UNLISTED} + * @return visibility of the status {@link Status#VISIBLE_PUBLIC,Status#VISIBLE_DIRECT,Status#VISIBLE_PRIVATE,Status#VISIBLE_UNLISTED} */ public int getVisibility() { return visibility; diff --git a/app/src/main/java/org/nuclearfog/twidda/model/ScheduledStatus.java b/app/src/main/java/org/nuclearfog/twidda/model/ScheduledStatus.java new file mode 100644 index 00000000..9e7be890 --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/model/ScheduledStatus.java @@ -0,0 +1,51 @@ +package org.nuclearfog.twidda.model; + +import java.io.Serializable; + +/** + * Represents a scheduled status to post in the future + * + * @author nuclearfog + */ +public interface ScheduledStatus extends Serializable { + + /** + * @return ID of the scheduled status + */ + long getId(); + + /** + * @return status text + */ + String getText(); + + /** + * @return language of the text if any + */ + String getLanguage(); + + /** + * @return attached media + */ + Media[] getMedia(); + + /** + * @return attached poll + */ + Poll getPoll(); + + /** + * @return visibility of the status {@link Status#VISIBLE_PUBLIC,Status#VISIBLE_DIRECT,Status#VISIBLE_PRIVATE,Status#VISIBLE_UNLISTED} + */ + int getVisibility(); + + /** + * @return true if status contains sensitive information + */ + boolean isSensitive(); + + /** + * @return true if status contains spoiler information + */ + boolean isSpoiler(); +} \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/model/lists/ScheduledStatuses.java b/app/src/main/java/org/nuclearfog/twidda/model/lists/ScheduledStatuses.java new file mode 100644 index 00000000..09d2d009 --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/model/lists/ScheduledStatuses.java @@ -0,0 +1,10 @@ +package org.nuclearfog.twidda.model.lists; + +import org.nuclearfog.twidda.model.ScheduledStatus; + +import java.util.LinkedList; + +public class ScheduledStatuses extends LinkedList { + private static final long serialVersionUID = 9015646013535818699L; + +} \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/dialogs/TimePickerDialog.java b/app/src/main/java/org/nuclearfog/twidda/ui/dialogs/TimePickerDialog.java index e3b73ad5..ea34149f 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/dialogs/TimePickerDialog.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/dialogs/TimePickerDialog.java @@ -3,6 +3,7 @@ package org.nuclearfog.twidda.ui.dialogs; import android.app.Activity; import android.app.Dialog; import android.os.Bundle; +import android.text.format.DateFormat; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; @@ -15,6 +16,7 @@ import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.config.GlobalSettings; +import java.util.Calendar; import java.util.Date; import java.util.GregorianCalendar; @@ -51,6 +53,8 @@ public class TimePickerDialog extends Dialog implements OnClickListener { GlobalSettings settings = GlobalSettings.get(getContext()); AppStyles.setTheme(root, settings.getPopupColor()); + timePicker.setIs24HourView(DateFormat.is24HourFormat(getContext())); + datePicker.setFirstDayOfWeek(Calendar.getInstance().getFirstDayOfWeek()); confirm.setOnClickListener(this); cancel.setOnClickListener(this);