From ae5e367434e3c13b3602417a52371d2cd5daf805 Mon Sep 17 00:00:00 2001 From: nuclearfog Date: Sat, 30 Sep 2023 20:52:57 +0200 Subject: [PATCH] finalized status schedule support, bug fix --- .../twidda/backend/api/Connection.java | 2 +- .../twidda/backend/api/mastodon/Mastodon.java | 2 +- .../impl/ScheduledMastodonStatus.java | 14 +- .../twidda/backend/async/ScheduleAction.java | 85 +++++++++ .../twidda/backend/async/ScheduleLoader.java | 65 +++++++ .../twidda/model/ScheduledStatus.java | 3 + .../twidda/model/lists/ScheduledStatuses.java | 18 ++ .../twidda/ui/activities/MainActivity.java | 7 + .../ui/activities/ScheduleActivity.java | 6 +- .../adapter/recyclerview/ScheduleAdapter.java | 161 +++++++++++++++++- .../holder/OnHolderClickListener.java | 4 + .../recyclerview/holder/ScheduleHolder.java | 24 ++- .../twidda/ui/dialogs/ConfirmDialog.java | 6 + .../twidda/ui/fragments/ScheduleFragment.java | 147 +++++++++++++++- app/src/main/res/drawable/schedule.xml | 4 +- app/src/main/res/layout/item_schedule.xml | 44 +++-- app/src/main/res/menu/main_navigation.xml | 4 + app/src/main/res/values/dimens.xml | 5 + app/src/main/res/values/strings.xml | 6 + 19 files changed, 577 insertions(+), 30 deletions(-) create mode 100644 app/src/main/java/org/nuclearfog/twidda/backend/async/ScheduleAction.java create mode 100644 app/src/main/java/org/nuclearfog/twidda/backend/async/ScheduleLoader.java 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 538368bf..99076386 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 @@ -492,7 +492,7 @@ public interface Connection { * * @param id scheduled status ID */ - void canselScheduledStatus(long id) throws ConnectionException; + void cancelScheduledStatus(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 ee62e1b0..d9a9e2b6 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 @@ -794,7 +794,7 @@ public class Mastodon implements Connection { @Override - public void canselScheduledStatus(long id) throws ConnectionException { + public void cancelScheduledStatus(long id) throws ConnectionException { try { Response response = delete(ENDPOINT_SCHEDULED_STATUS + "/" + id, new ArrayList<>()); if (response.code() != 200) { 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 index cd302cb5..fc608561 100644 --- 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 @@ -1,5 +1,7 @@ package org.nuclearfog.twidda.backend.api.mastodon.impl; +import androidx.annotation.Nullable; + import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -36,8 +38,8 @@ public class ScheduledMastodonStatus implements ScheduledStatus { 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", "")); + String visibilityStr = params.optString("visibility", ""); + text = StringUtils.extractText(params.optString("text", "")); time = StringUtils.getIsoTime(json.optString("scheduled_at", "")); sensitive = params.optBoolean("sensitive", false); spoiler = params.optBoolean("spoiler_text", false); @@ -136,4 +138,12 @@ public class ScheduledMastodonStatus implements ScheduledStatus { public boolean isSpoiler() { return spoiler; } + + + @Override + public boolean equals(@Nullable Object obj) { + if (!(obj instanceof ScheduledStatus)) + return false; + return ((ScheduledStatus) obj).getId() == getId(); + } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/async/ScheduleAction.java b/app/src/main/java/org/nuclearfog/twidda/backend/async/ScheduleAction.java new file mode 100644 index 00000000..a7e93301 --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/backend/async/ScheduleAction.java @@ -0,0 +1,85 @@ +package org.nuclearfog.twidda.backend.async; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.nuclearfog.twidda.backend.api.Connection; +import org.nuclearfog.twidda.backend.api.ConnectionException; +import org.nuclearfog.twidda.backend.api.ConnectionManager; +import org.nuclearfog.twidda.model.ScheduledStatus; + +/** + * @author nuclearfog + */ +public class ScheduleAction extends AsyncExecutor { + + private Connection connection; + + /** + * + */ + public ScheduleAction(Context context) { + connection = ConnectionManager.getDefaultConnection(context); + } + + + @Override + protected Result doInBackground(@NonNull Param param) { + try { + if (param.mode == Param.UPDATE) { + ScheduledStatus status = connection.updateScheduledStatus(param.id, param.time); + return new Result(Result.UPDATE, status.getId(), status, null); + } else if (param.mode == Param.REMOVE) { + connection.cancelScheduledStatus(param.id); + return new Result(Result.REMOVE, param.id, null, null); + } + } catch (ConnectionException exception) { + return new Result(Result.ERROR, 0L, null, exception); + } + return null; + } + + /** + * + */ + public static class Param { + + public static final int UPDATE = 1; + public static final int REMOVE = 2; + + final int mode; + final long id, time; + + public Param(int mode, long id, long time) { + this.mode = mode; + this.time = time; + this.id = id; + } + } + + /** + * + */ + public static class Result { + + public static final int UPDATE = 10; + public static final int REMOVE = 11; + public static final int ERROR = -1; + + public final int mode; + public final long id; + @Nullable + public final ScheduledStatus status; + @Nullable + public final ConnectionException exception; + + Result(int mode, long id, @Nullable ScheduledStatus status, @Nullable ConnectionException exception) { + this.id = id; + this.mode = mode; + this.status = status; + this.exception = exception; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/backend/async/ScheduleLoader.java b/app/src/main/java/org/nuclearfog/twidda/backend/async/ScheduleLoader.java new file mode 100644 index 00000000..50042e4f --- /dev/null +++ b/app/src/main/java/org/nuclearfog/twidda/backend/async/ScheduleLoader.java @@ -0,0 +1,65 @@ +package org.nuclearfog.twidda.backend.async; + +import android.content.Context; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import org.nuclearfog.twidda.backend.api.Connection; +import org.nuclearfog.twidda.backend.api.ConnectionException; +import org.nuclearfog.twidda.backend.api.ConnectionManager; +import org.nuclearfog.twidda.model.lists.ScheduledStatuses; + +/** + * @author nuclearfog + */ +public class ScheduleLoader extends AsyncExecutor { + + private Connection connection; + + public ScheduleLoader(Context context) { + connection = ConnectionManager.getDefaultConnection(context); + } + + + @Override + protected Result doInBackground(@NonNull Param param) { + try { + ScheduledStatuses statuses = connection.getScheduledStatuses(param.minId, param.maxId); + return new Result(statuses, param.index, null); + } catch (ConnectionException exception) { + return new Result(null, 0, exception); + } + } + + /** + * + */ + public static class Param { + final long minId, maxId; + final int index; + + public Param(long minId, long maxId, int index) { + this.minId = minId; + this.maxId = maxId; + this.index = index; + } + } + + /** + * + */ + public static class Result { + public final int index; + @Nullable + public final ScheduledStatuses statuses; + @Nullable + public final ConnectionException exception; + + Result(@Nullable ScheduledStatuses statuses, int index, @Nullable ConnectionException exception) { + this.statuses = statuses; + this.exception = exception; + this.index = index; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/model/ScheduledStatus.java b/app/src/main/java/org/nuclearfog/twidda/model/ScheduledStatus.java index b3b4f994..0d6aedb3 100644 --- a/app/src/main/java/org/nuclearfog/twidda/model/ScheduledStatus.java +++ b/app/src/main/java/org/nuclearfog/twidda/model/ScheduledStatus.java @@ -14,6 +14,9 @@ public interface ScheduledStatus extends Serializable { */ long getId(); + /** + * @return time to publish status + */ long getPublishTime(); /** 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 index 3423d274..50dc04f9 100644 --- a/app/src/main/java/org/nuclearfog/twidda/model/lists/ScheduledStatuses.java +++ b/app/src/main/java/org/nuclearfog/twidda/model/lists/ScheduledStatuses.java @@ -1,5 +1,7 @@ package org.nuclearfog.twidda.model.lists; +import androidx.annotation.Nullable; + import org.nuclearfog.twidda.model.ScheduledStatus; import java.util.LinkedList; @@ -8,6 +10,22 @@ import java.util.LinkedList; * @author nuclearfog */ public class ScheduledStatuses extends LinkedList { + private static final long serialVersionUID = 9015646013535818699L; + + public ScheduledStatuses() { + } + + + public ScheduledStatuses(ScheduledStatuses scheduledStatuses) { + addAll(scheduledStatuses); + } + + + @Override + @Nullable + public ScheduledStatus get(int index) { + return super.get(index); + } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/activities/MainActivity.java b/app/src/main/java/org/nuclearfog/twidda/ui/activities/MainActivity.java index 8024fa57..3db943aa 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/activities/MainActivity.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/activities/MainActivity.java @@ -401,6 +401,13 @@ public class MainActivity extends AppCompatActivity implements ActivityResultCal drawerLayout.close(); return true; } + // open status schedule viewer + else if (item.getItemId() == R.id.menu_navigator_schedule) { + Intent intent = new Intent(this, ScheduleActivity.class); + startActivity(intent); + drawerLayout.close(); + return true; + } return false; } diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/activities/ScheduleActivity.java b/app/src/main/java/org/nuclearfog/twidda/ui/activities/ScheduleActivity.java index b79bbefe..82d2945d 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/activities/ScheduleActivity.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/activities/ScheduleActivity.java @@ -2,6 +2,7 @@ package org.nuclearfog.twidda.ui.activities; import android.os.Bundle; import android.view.View; +import android.view.ViewGroup; import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; @@ -9,6 +10,7 @@ import androidx.appcompat.widget.Toolbar; import androidx.viewpager2.widget.ViewPager2; import org.nuclearfog.twidda.R; +import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.ui.adapter.viewpager.ScheduleAdapter; /** @@ -22,6 +24,7 @@ public class ScheduleActivity extends AppCompatActivity { super.onCreate(savedInstanceState); setContentView(R.layout.page_tab_view); + ViewGroup root = findViewById(R.id.page_tab_view_root); Toolbar toolbar = findViewById(R.id.page_tab_view_toolbar); ViewPager2 viewPager = findViewById(R.id.page_tab_view_pager); View tabSelector = findViewById(R.id.page_tab_view_tabs); @@ -30,7 +33,8 @@ public class ScheduleActivity extends AppCompatActivity { viewPager.setAdapter(adapter); tabSelector.setVisibility(View.GONE); - toolbar.setTitle(""); + toolbar.setTitle(R.string.toolbar_schedule_title); setSupportActionBar(toolbar); + AppStyles.setTheme(root); } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/ScheduleAdapter.java b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/ScheduleAdapter.java index 164bab39..3fa5f772 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/ScheduleAdapter.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/ScheduleAdapter.java @@ -3,7 +3,6 @@ package org.nuclearfog.twidda.ui.adapter.recyclerview; import android.view.ViewGroup; import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.ViewHolder; import androidx.recyclerview.widget.RecyclerView.Adapter; @@ -18,6 +17,10 @@ import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.ScheduleHolder; */ public class ScheduleAdapter extends Adapter implements OnHolderClickListener { + private static final int NO_LOADING = -1; + + private static final int MIN_COUNT = 2; + private static final int ITEM_GAP = 1; private static final int ITEM_SCHEDULE = 2; @@ -25,9 +28,11 @@ public class ScheduleAdapter extends Adapter implements OnHolderClic private OnScheduleClickListener listener; private ScheduledStatuses items = new ScheduledStatuses(); - private int loadingIndex = -1; - + private int loadingIndex = NO_LOADING; + /** + * @param listener item click listener + */ public ScheduleAdapter(OnScheduleClickListener listener) { this.listener = listener; } @@ -55,7 +60,10 @@ public class ScheduleAdapter extends Adapter implements OnHolderClic @Override public void onBindViewHolder(@NonNull ViewHolder holder, int position) { if (holder instanceof ScheduleHolder) { - + ScheduledStatus item = items.get(position); + if (item != null) { + ((ScheduleHolder) holder).setContent(item); + } } else if (holder instanceof PlaceHolder) { PlaceHolder placeHolder = (PlaceHolder) holder; placeHolder.setLoading(loadingIndex == position); @@ -71,21 +79,164 @@ public class ScheduleAdapter extends Adapter implements OnHolderClic @Override public void onItemClick(int position, int type, int... extras) { - + if (type == SCHEDULE_CLICK) { + ScheduledStatus item = items.get(position); + if (item != null) { + listener.onScheduleClick(item, OnScheduleClickListener.SELECT); + } + } else if (type == SCHEDULE_REMOVE) { + listener.onScheduleClick(items.get(position), OnScheduleClickListener.REMOVE); + } } @Override public boolean onPlaceholderClick(int index) { + long sinceId = 0; + long maxId = 0; + if (index == 0) { + if (items.size() > 1) { + ScheduledStatus item = items.get(index + 1); + if (item != null) { + sinceId = item.getId(); + } + } + } else if (index == items.size() - 1) { + ScheduledStatus item = items.get(index - 1); + if (item != null) { + maxId = item.getId() - 1; + } + } else { + ScheduledStatus item = items.get(index + 1); + if (item != null) { + sinceId = item.getId(); + } + item = items.get(index - 1); + if (item != null) { + maxId = item.getId() - 1; + } + } + boolean success = listener.onPlaceholderClick(sinceId, maxId, index); + if (success) { + loadingIndex = index; + return true; + } return false; } + /** + * + */ + public void setItems(ScheduledStatuses newItems) { + items.clear(); + items.addAll(newItems); + if (newItems.size() > MIN_COUNT) { + items.add(null); + } + notifyDataSetChanged(); + } + + /** + * + */ + public void addItems(ScheduledStatuses newItems, int index) { + disableLoading(); + if (newItems.size() > MIN_COUNT) { + if (items.isEmpty() || items.get(index) != null) { + // Add placeholder + items.add(index, null); + notifyItemInserted(index); + } + } else if (!items.isEmpty() && items.get(index) == null) { + // remove placeholder + items.remove(index); + notifyItemRemoved(index); + } + if (!newItems.isEmpty()) { + items.addAll(index, newItems); + notifyItemRangeInserted(index, newItems.size()); + } + } + + /** + * + */ + public void removeItem(long id) { + int pos = -1; + for (int i = items.size() - 1 ; i >= 0 ; i--) { + ScheduledStatus item = items.get(i); + if (item != null && item.getId() == id) { + pos = i; + break; + } + } + if (pos >= 0) { + items.remove(pos); + notifyItemRemoved(pos); + } + } + + /** + * + */ + public void updateItem(@NonNull ScheduledStatus item) { + for (int pos = items.size() - 1 ; pos >= 0 ; pos--) { + ScheduledStatus current = items.get(pos); + if (current != null && current.getId() == item.getId()) { + items.set(pos, item); + notifyItemChanged(pos); + break; + } + } + } + + /** + * + */ + public void clear() { + items.clear(); + notifyDataSetChanged(); + } + + /** + * + */ + public ScheduledStatuses getItems() { + return new ScheduledStatuses(items); + } + + /** + * + */ + public long getTopItemId() { + ScheduledStatus item = items.peekFirst(); + if (item != null) + return item.getId(); + return 0L; + } + + /** + * disable placeholder load animation + */ + public void disableLoading() { + if (loadingIndex != NO_LOADING) { + int oldIndex = loadingIndex; + loadingIndex = NO_LOADING; + notifyItemChanged(oldIndex); + } + } + + /** + * + */ public interface OnScheduleClickListener { int SELECT = 1; int REMOVE = 2; void onScheduleClick(ScheduledStatus status, int type); + + boolean onPlaceholderClick(long min_id, long max_id, int position); } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/OnHolderClickListener.java b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/OnHolderClickListener.java index 174ab0c9..57cadfe3 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/OnHolderClickListener.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/OnHolderClickListener.java @@ -49,6 +49,10 @@ public interface OnHolderClickListener { int HASHTAG_REMOVE = 25; + int SCHEDULE_CLICK = 26; + + int SCHEDULE_REMOVE = 27; + /** * called when an item was clicked * diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/ScheduleHolder.java b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/ScheduleHolder.java index d33a292d..a2870932 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/ScheduleHolder.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/adapter/recyclerview/holder/ScheduleHolder.java @@ -7,17 +7,17 @@ import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.TextView; -import androidx.annotation.NonNull; import androidx.cardview.widget.CardView; import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView.ViewHolder; import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.backend.utils.AppStyles; -import org.nuclearfog.twidda.backend.utils.StringUtils; import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.model.ScheduledStatus; +import java.text.SimpleDateFormat; + /** * @author nuclearfog */ @@ -27,33 +27,45 @@ public class ScheduleHolder extends ViewHolder implements OnClickListener { private OnHolderClickListener listener; - + /** + * + */ public ScheduleHolder(ViewGroup parent, OnHolderClickListener listener) { super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_schedule, parent, false)); this.listener = listener; GlobalSettings settings = GlobalSettings.get(parent.getContext()); CardView cardLayout = (CardView) itemView; ViewGroup container = itemView.findViewById(R.id.item_schedule_container); + View removeButton = itemView.findViewById(R.id.item_schedule_delete_button); time = itemView.findViewById(R.id.item_schedule_time); text = itemView.findViewById(R.id.item_schedule_text); + time.setCompoundDrawablesWithIntrinsicBounds(R.drawable.schedule, 0, 0, 0); AppStyles.setTheme(container, Color.TRANSPARENT); cardLayout.setCardBackgroundColor(settings.getCardColor()); - + container.setOnClickListener(this); + removeButton.setOnClickListener(this); } @Override public void onClick(View v) { - + int position = getLayoutPosition(); + if (position != RecyclerView.NO_POSITION) { + if (v.getId() == R.id.item_schedule_container) { + listener.onItemClick(position, OnHolderClickListener.SCHEDULE_CLICK); + } else if (v.getId() == R.id.item_schedule_delete_button) { + listener.onItemClick(position, OnHolderClickListener.SCHEDULE_REMOVE); + } + } } /** * */ public void setContent(ScheduledStatus status) { - time.setText(StringUtils.formatExpirationTime(time.getResources(), status.getPublishTime())); + time.setText(SimpleDateFormat.getDateTimeInstance().format(status.getPublishTime())); text.setText(status.getText()); } } \ No newline at end of file diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/dialogs/ConfirmDialog.java b/app/src/main/java/org/nuclearfog/twidda/ui/dialogs/ConfirmDialog.java index 3d618c23..51b01a60 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/dialogs/ConfirmDialog.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/dialogs/ConfirmDialog.java @@ -144,6 +144,8 @@ public class ConfirmDialog extends Dialog implements OnClickListener { */ public static final int UNFEATURE_HASHTAG = 629; + public static final int SCHEDULE_REMOVE = 630; + private TextView title, message, remember_label; private Button confirm, cancel; @@ -293,6 +295,10 @@ public class ConfirmDialog extends Dialog implements OnClickListener { case UNFEATURE_HASHTAG: messageRes = R.string.confirm_hashtag_unfeature; break; + + case SCHEDULE_REMOVE: + messageRes = R.string.confirm_schedule_remove; + break; } // setup title title.setVisibility(titleVis); diff --git a/app/src/main/java/org/nuclearfog/twidda/ui/fragments/ScheduleFragment.java b/app/src/main/java/org/nuclearfog/twidda/ui/fragments/ScheduleFragment.java index 0fe69bd7..6e212236 100644 --- a/app/src/main/java/org/nuclearfog/twidda/ui/fragments/ScheduleFragment.java +++ b/app/src/main/java/org/nuclearfog/twidda/ui/fragments/ScheduleFragment.java @@ -2,30 +2,173 @@ package org.nuclearfog.twidda.ui.fragments; import android.os.Bundle; import android.view.View; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import org.nuclearfog.twidda.R; +import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback; +import org.nuclearfog.twidda.backend.async.ScheduleAction; +import org.nuclearfog.twidda.backend.async.ScheduleLoader; +import org.nuclearfog.twidda.backend.utils.ErrorUtils; +import org.nuclearfog.twidda.model.ScheduledStatus; +import org.nuclearfog.twidda.model.lists.ScheduledStatuses; +import org.nuclearfog.twidda.ui.adapter.recyclerview.ScheduleAdapter; +import org.nuclearfog.twidda.ui.adapter.recyclerview.ScheduleAdapter.OnScheduleClickListener; +import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog; +import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog.OnConfirmListener; +import org.nuclearfog.twidda.ui.dialogs.TimePickerDialog; +import org.nuclearfog.twidda.ui.dialogs.TimePickerDialog.TimeSelectedCallback; + /** * @author nuclearfog */ -public class ScheduleFragment extends ListFragment { +public class ScheduleFragment extends ListFragment implements OnScheduleClickListener, OnConfirmListener, TimeSelectedCallback { + + private static final String KEY_SAVE = "schedule_status_save"; + + private static final int CLEAR_LIST = -1; + + private ScheduleAdapter adapter; + private ScheduleLoader scheduleLoader; + private ScheduleAction scheduleAction; + private ConfirmDialog confirm; + private TimePickerDialog timepicker; + + @Nullable + private ScheduledStatus selection; + + private AsyncCallback loaderCallback = this::onLoaderResult; + private AsyncCallback actionCallback = this::onActionResult; @Override public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + + scheduleLoader = new ScheduleLoader(requireContext()); + scheduleAction = new ScheduleAction(requireContext()); + adapter = new ScheduleAdapter(this); + confirm = new ConfirmDialog(requireActivity(), this); + timepicker = new TimePickerDialog(requireActivity(), this); + setAdapter(adapter); + + if (savedInstanceState != null) { + Object data = savedInstanceState.getSerializable(KEY_SAVE); + if (data instanceof ScheduledStatuses) { + adapter.setItems((ScheduledStatuses) data); + } + } + load(0L, 0L, CLEAR_LIST); + setRefresh(true); + } + + + @Override + public void onSaveInstanceState(@NonNull Bundle outState) { + outState.putSerializable(KEY_SAVE, adapter.getItems()); + super.onSaveInstanceState(outState); } @Override protected void onReload() { - + load(adapter.getTopItemId(), 0L, CLEAR_LIST); } @Override protected void onReset() { + adapter.clear(); + load(0L, 0L, CLEAR_LIST); + setRefresh(true); + } + + @Override + public void onScheduleClick(ScheduledStatus status, int type) { + if (type == OnScheduleClickListener.SELECT) { + if (!timepicker.isShowing() && scheduleAction.isIdle()) { + selection = status; + timepicker.show(status.getPublishTime()); + } + } else if (type == OnScheduleClickListener.REMOVE) { + if (!confirm.isShowing() && scheduleAction.isIdle()) { + selection = status; + confirm.show(ConfirmDialog.SCHEDULE_REMOVE); + } + } + } + + + @Override + public boolean onPlaceholderClick(long min_id, long max_id, int position) { + if (scheduleLoader.isIdle()) { + load(min_id, max_id, position); + return true; + } + return false; + } + + + @Override + public void onConfirm(int type, boolean remember) { + if (type == ConfirmDialog.SCHEDULE_REMOVE) { + if (selection != null) { + ScheduleAction.Param param = new ScheduleAction.Param(ScheduleAction.Param.REMOVE, selection.getId(), 0L); + scheduleAction.execute(param, actionCallback); + } + } + } + + + @Override + public void onTimeSelected(long time) { + if (selection != null && time != 0L) { + ScheduleAction.Param param = new ScheduleAction.Param(ScheduleAction.Param.UPDATE, selection.getId(), time); + scheduleAction.execute(param, actionCallback); + } + } + + /** + * + */ + private void onLoaderResult(ScheduleLoader.Result result) { + if (result.statuses != null) { + if (result.index == CLEAR_LIST) { + adapter.setItems(result.statuses); + } else { + adapter.addItems(result.statuses, result.index); + } + } else { + ErrorUtils.showErrorMessage(requireContext(), result.exception); + } + setRefresh(false); + } + + /** + * + */ + private void onActionResult(ScheduleAction.Result result) { + if (result.mode == ScheduleAction.Result.REMOVE) { + adapter.removeItem(result.id); + Toast.makeText(requireContext(), R.string.info_schedule_removed, Toast.LENGTH_SHORT).show(); + } else if (result.mode == ScheduleAction.Result.UPDATE) { + if (result.status != null) { + adapter.updateItem(result.status); + Toast.makeText(requireContext(), R.string.info_schedule_updated, Toast.LENGTH_SHORT).show(); + } + } else if (result.mode == ScheduleAction.Result.ERROR) { + ErrorUtils.showErrorMessage(requireContext(), result.exception); + } + } + + /** + * + */ + private void load(long min_id, long max_id, int position) { + ScheduleLoader.Param param = new ScheduleLoader.Param(min_id, max_id, position); + scheduleLoader.execute(param, loaderCallback); } } \ No newline at end of file diff --git a/app/src/main/res/drawable/schedule.xml b/app/src/main/res/drawable/schedule.xml index 17004784..f5e7eb9f 100644 --- a/app/src/main/res/drawable/schedule.xml +++ b/app/src/main/res/drawable/schedule.xml @@ -1,6 +1,6 @@ - + - + + + + + + + diff --git a/app/src/main/res/menu/main_navigation.xml b/app/src/main/res/menu/main_navigation.xml index 95775b0f..9901fa67 100644 --- a/app/src/main/res/menu/main_navigation.xml +++ b/app/src/main/res/menu/main_navigation.xml @@ -25,6 +25,10 @@ android:id="@+id/menu_navigator_filter" android:title="@string/menu_open_filter" /> + + diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 3ca347f9..eb0a32d6 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -142,7 +142,12 @@ 5dp + 3dp 5dp + 1dp + 20sp + 5dp + 12 @dimen/toolbar_height diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 2c44292e..53f18411 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -80,6 +80,8 @@ Hashtag unfollowed Hashtag unfeatured Status reported + scheduled post updated + scheduled post removed Error @@ -130,6 +132,7 @@ write status + scheduled status Hashtags follow hashtag unfollow hashtag @@ -291,6 +294,7 @@ remove user from list? remove user from list remove hashtag from list + remove scheduled status from list remove domain from list unknown error! following list @@ -311,11 +315,13 @@ Opening an external link would bypass proxy connection. Proceed? unfollow hashtag? unfeature hashtag? + cancel scheduled post? remove account from list? delete filter? \'unnamed\' User favoriting this status User liking this status + scheduled Posts now Reply Media preview