From 0bd5182b9eb5637dd978fc5ea666c1396ce8ce2b Mon Sep 17 00:00:00 2001 From: Adolfo Santiago Date: Thu, 7 Jul 2022 16:48:36 +0200 Subject: [PATCH] Fix loading toots with conversation_id as String It happens at Gleasonator but it could happen to other instances. The ones which returns this value as an Integer should work perfectly. Fixes: https://todo.sr.ht/~captainepoch/husky/48 --- .../com/keylesspalace/tusky/entity/Status.kt | 139 ++++++++++-------- .../tusky/fragment/NotificationsFragment.java | 8 +- .../tusky/fragment/TimelineFragment.java | 68 +++++---- .../tusky/repository/TimelineRepository.kt | 20 +++ .../keylesspalace/tusky/util/OkHttpUtils.kt | 1 + .../tusky/viewdata/StatusViewData.java | 117 +++++++++------ 6 files changed, 203 insertions(+), 150 deletions(-) diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt b/husky/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt index 47dc44d..a9fe67d 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt +++ b/husky/app/src/main/java/com/keylesspalace/tusky/entity/Status.kt @@ -1,17 +1,22 @@ -/* Copyright 2017 Andrew Dawson +/* + * Husky -- A Pleroma client for Android * - * This file is a part of Tusky. + * Copyright (C) 2022 The Husky Developers + * Copyright (C) 2017 Andrew Dawson * - * 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. + * 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. + * This program 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 . */ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.keylesspalace.tusky.entity @@ -19,35 +24,35 @@ import android.text.SpannableStringBuilder import android.text.Spanned import android.text.style.URLSpan import com.google.gson.annotations.SerializedName -import java.util.* +import java.util.Date data class Status( - var id: String, - var url: String?, // not present if it's reblog - val account: Account, - @SerializedName("in_reply_to_id") var inReplyToId: String?, - @SerializedName("in_reply_to_account_id") val inReplyToAccountId: String?, - val reblog: Status?, - val content: Spanned, - @SerializedName("created_at") val createdAt: Date, - val emojis: List, - @SerializedName("reblogs_count") val reblogsCount: Int, - @SerializedName("favourites_count") val favouritesCount: Int, - var reblogged: Boolean, - var favourited: Boolean, - var bookmarked: Boolean, - var sensitive: Boolean, - @SerializedName("spoiler_text") val spoilerText: String, - val visibility: Visibility, - @SerializedName("media_attachments") var attachments: ArrayList, - val mentions: Array, - val application: Application?, - var pinned: Boolean?, - val poll: Poll?, - val card: Card?, - var content_type: String? = null, - val pleroma: PleromaStatus? = null, - var muted: Boolean = false /* set when either thread or user is muted */ + var id: String, + var url: String?, // not present if it's reblog + val account: Account, + @SerializedName("in_reply_to_id") var inReplyToId: String?, + @SerializedName("in_reply_to_account_id") val inReplyToAccountId: String?, + val reblog: Status?, + val content: Spanned, + @SerializedName("created_at") val createdAt: Date, + val emojis: List, + @SerializedName("reblogs_count") val reblogsCount: Int, + @SerializedName("favourites_count") val favouritesCount: Int, + var reblogged: Boolean, + var favourited: Boolean, + var bookmarked: Boolean, + var sensitive: Boolean, + @SerializedName("spoiler_text") val spoilerText: String, + val visibility: Visibility, + @SerializedName("media_attachments") var attachments: ArrayList, + val mentions: Array, + val application: Application?, + var pinned: Boolean?, + val poll: Poll?, + val card: Card?, + var content_type: String? = null, + val pleroma: PleromaStatus? = null, + var muted: Boolean = false /* set when either thread or user is muted */ ) { val actionableId: String @@ -58,17 +63,21 @@ data class Status( enum class Visibility(val num: Int) { UNKNOWN(0), + @SerializedName("public") PUBLIC(1), + @SerializedName("unlisted") UNLISTED(2), + @SerializedName("private") PRIVATE(3), + @SerializedName("direct") DIRECT(4); fun serverString(): String { - return when (this) { + return when(this) { PUBLIC -> "public" UNLISTED -> "unlisted" PRIVATE -> "private" @@ -81,7 +90,7 @@ data class Status( @JvmStatic fun byNum(num: Int): Visibility { - return when (num) { + return when(num) { 4 -> DIRECT 3 -> PRIVATE 2 -> UNLISTED @@ -93,7 +102,7 @@ data class Status( @JvmStatic fun byString(s: String): Visibility { - return when (s) { + return when(s) { "public" -> PUBLIC "unlisted" -> UNLISTED "private" -> PRIVATE @@ -115,42 +124,42 @@ data class Status( fun toDeletedStatus(): DeletedStatus { return DeletedStatus( - text = getEditableText(), - inReplyToId = inReplyToId, - spoilerText = spoilerText, - visibility = visibility, - sensitive = sensitive, - attachments = attachments, - poll = poll, - createdAt = createdAt + text = getEditableText(), + inReplyToId = inReplyToId, + spoilerText = spoilerText, + visibility = visibility, + sensitive = sensitive, + attachments = attachments, + poll = poll, + createdAt = createdAt ) } - + fun isMuted(): Boolean { return muted } - + fun isUserMuted(): Boolean { return muted && !isThreadMuted() } - + fun isThreadMuted(): Boolean { return pleroma?.threadMuted ?: false } - + fun setThreadMuted(mute: Boolean) { if(pleroma?.threadMuted != null) pleroma.threadMuted = mute } - - fun getConversationId(): Int { - return pleroma?.conversationId ?: -1 + + fun getConversationId(): String { + return pleroma?.conversationId ?: "" } - + fun getEmojiReactions(): List? { return pleroma?.emojiReactions; } - + fun getInReplyToAccountAcct(): String? { return pleroma?.inReplyToAccountAcct; } @@ -161,10 +170,10 @@ data class Status( private fun getEditableText(): String { val builder = SpannableStringBuilder(content) - for (span in content.getSpans(0, content.length, URLSpan::class.java)) { + for(span in content.getSpans(0, content.length, URLSpan::class.java)) { val url = span.url - for ((_, url1, username) in mentions) { - if (url == url1) { + for((_, url1, username) in mentions) { + if(url == url1) { val start = builder.getSpanStart(span) val end = builder.getSpanEnd(span) builder.replace(start, end, "@$username") @@ -176,8 +185,8 @@ data class Status( } override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other == null || javaClass != other.javaClass) return false + if(this === other) return true + if(other == null || javaClass != other.javaClass) return false val status = other as Status? return id == status?.id @@ -189,20 +198,20 @@ data class Status( data class PleromaStatus( @SerializedName("thread_muted") var threadMuted: Boolean?, - @SerializedName("conversation_id") val conversationId: Int?, + @SerializedName("conversation_id") val conversationId: String?, @SerializedName("emoji_reactions") val emojiReactions: List?, @SerializedName("in_reply_to_account_acct") val inReplyToAccountAcct: String?, @SerializedName("parent_visible") val parentVisible: Boolean? ) - data class Mention ( + data class Mention( val id: String, val url: String?, // can be null due to bug in some Pleroma versions @SerializedName("acct") val username: String, @SerializedName("username") val localUsername: String ) - data class Application ( + data class Application( val name: String, val website: String? ) diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java index 59059b0..cd14099 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java +++ b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/NotificationsFragment.java @@ -350,9 +350,9 @@ public class NotificationsFragment extends SFragment implements if (posAndNotification == null) return; - int conversationId = posAndNotification.second.getStatus().getConversationId(); + String conversationId = posAndNotification.second.getStatus().getConversationId(); - if(conversationId == -1) { // invalid conversation ID + if(conversationId.isEmpty()) { // invalid conversation ID if(withMuted) { setMutedStatusForStatus(posAndNotification.first, posAndNotification.second.getStatus(), event.getMute(), event.getMute()); } else { @@ -1016,7 +1016,7 @@ public class NotificationsFragment extends SFragment implements updateAdapter(); } - private void removeAllByConversationId(int conversationId) { + private void removeAllByConversationId(String conversationId) { // using iterator to safely remove items while iterating Iterator> iterator = notifications.iterator(); while (iterator.hasNext()) { @@ -1024,7 +1024,7 @@ public class NotificationsFragment extends SFragment implements Notification notification = placeholderOrNotification.asRightOrNull(); if (notification != null && notification.getStatus() != null && notification.getType() == Notification.Type.MENTION && - notification.getStatus().getConversationId() == conversationId) { + notification.getStatus().getConversationId().equalsIgnoreCase(conversationId)) { iterator.remove(); } } diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java index 71836d1..388c3db 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java +++ b/husky/app/src/main/java/com/keylesspalace/tusky/fragment/TimelineFragment.java @@ -1,17 +1,23 @@ -/* Copyright 2017 Andrew Dawson +/* + * Husky -- A Pleroma client for Android * - * This file is a part of Tusky. + * Copyright (C) 2022 The Husky Developers + * Copyright (C) 2017 Andrew Dawson * - * 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. + * 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. + * This program 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 . */ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + package com.keylesspalace.tusky.fragment; @@ -22,7 +28,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.Bundle; -import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -108,13 +113,13 @@ import kotlin.jvm.functions.Function1; import retrofit2.Call; import retrofit2.Callback; import retrofit2.Response; +import timber.log.Timber; public class TimelineFragment extends SFragment implements SwipeRefreshLayout.OnRefreshListener, StatusActionListener, Injectable, ReselectableFragment, RefreshableFragment { - private static final String TAG = "TimelineF"; // logging tag private static final String KIND_ARG = "kind"; private static final String ID_ARG = "id"; private static final String HASHTAGS_ARG = "hastags"; @@ -302,7 +307,7 @@ public class TimelineFragment extends SFragment implements // Request timeline from disk to make it quick, then replace it with timeline from // the server to update it timelineRepo.getStatuses(null, null, null, LOAD_AT_ONCE, - TimelineRequestMode.DISK) + TimelineRequestMode.DISK) .observeOn(AndroidSchedulers.mainThread()) .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) .subscribe(statuses -> { @@ -334,7 +339,7 @@ public class TimelineFragment extends SFragment implements String topId = CollectionsKt.first(this.statuses, Either::isRight).asRight().getId(); this.timelineRepo.getStatuses(topId, null, null, LOAD_AT_ONCE, - TimelineRequestMode.NETWORK) + TimelineRequestMode.NETWORK) .observeOn(AndroidSchedulers.mainThread()) .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) .subscribe( @@ -475,7 +480,7 @@ public class TimelineFragment extends SFragment implements public void onActivityCreated(@Nullable Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); - SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(getContext()); + SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(requireContext()); /* This is delayed until onActivityCreated solely because MainActivity.composeButton isn't * guaranteed to be set until then. */ @@ -623,7 +628,7 @@ public class TimelineFragment extends SFragment implements .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) .subscribe( (newStatus) -> setRebloggedForStatus(position, status, reblog), - (err) -> Log.d(TAG, "Failed to reblog status " + status.getId(), err) + (err) -> Timber.e("Failed to reblog status " + status.getId() + ", Error[" + err + "]") ); } @@ -655,7 +660,7 @@ public class TimelineFragment extends SFragment implements .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) .subscribe( (newStatus) -> setFavouriteForStatus(position, newStatus, favourite), - (err) -> Log.d(TAG, "Failed to favourite status " + status.getId(), err) + (err) -> Timber.e("Failed to favourite status " + status.getId() + ", Error [" + err + "]") ); } @@ -687,7 +692,7 @@ public class TimelineFragment extends SFragment implements .as(autoDisposable(from(this, Lifecycle.Event.ON_DESTROY))) .subscribe( (newStatus) -> setBookmarkForStatus(position, newStatus, bookmark), - (err) -> Log.d(TAG, "Failed to favourite status " + status.getId(), err) + (err) -> Timber.e(err, "Failed to favourite status " + status.getId()) ); } @@ -743,8 +748,8 @@ public class TimelineFragment extends SFragment implements .as(autoDisposable(from(this))) .subscribe( (newPoll) -> setVoteForPoll(position, status, newPoll), - (t) -> Log.d(TAG, - "Failed to vote in poll: " + status.getId(), t) + (t) -> Timber.e(t, + "Failed to vote in poll: " + status.getId()) ); } @@ -815,7 +820,7 @@ public class TimelineFragment extends SFragment implements ? statuses.get(position + 1).asRight().getId() : null; if(fromStatus == null || toStatus == null) { - Log.e(TAG, "Failed to load more at " + position + ", wrong placeholder position"); + Timber.e("Failed to load more at " + position + ", wrong placeholder position"); return; } sendFetchTimelineRequest(fromStatus.getId(), toStatus.getId(), maxMinusOne, @@ -826,14 +831,14 @@ public class TimelineFragment extends SFragment implements statuses.setPairedItem(position, newViewData); updateAdapter(); } else { - Log.e(TAG, "error loading more"); + Timber.e("error loading more"); } } @Override public void onContentCollapsedChange(boolean isCollapsed, int position) { if(position < 0 || position >= statuses.size()) { - Log.e(TAG, String.format("Tried to access out of bounds status position: %d of %d", position, statuses.size() - 1)); + Timber.e(String.format("Tried to access out of bounds status position: %d of %d", position, statuses.size() - 1)); return; } @@ -841,7 +846,7 @@ public class TimelineFragment extends SFragment implements if(!(status instanceof StatusViewData.Concrete)) { // Statuses PairedList contains a base type of StatusViewData.Concrete and also doesn't // check for null values when adding values to it although this doesn't seem to be an issue. - Log.e(TAG, String.format( + Timber.e(String.format( "Expected StatusViewData.Concrete, got %s instead at position: %d of %d", status == null ? "" : status.getClass().getSimpleName(), position, @@ -960,13 +965,13 @@ public class TimelineFragment extends SFragment implements updateAdapter(); } - private void removeAllByConversationId(int conversationId) { + private void removeAllByConversationId(String conversationId) { // using iterator to safely remove items while iterating Iterator> iterator = statuses.iterator(); while(iterator.hasNext()) { Status status = iterator.next().asRightOrNull(); if(status != null && - (status.getConversationId() == conversationId) || status.getActionableStatus().getConversationId() == conversationId) { + (status.getConversationId().equalsIgnoreCase(conversationId)) || status.getActionableStatus().getConversationId().equalsIgnoreCase(conversationId)) { iterator.remove(); } } @@ -1249,7 +1254,7 @@ public class TimelineFragment extends SFragment implements } } - Log.e(TAG, "Fetch Failure: " + exception.getMessage()); + Timber.e("Fetch Failure: %s", exception.getMessage()); updateBottomLoadingState(fetchEnd); progressBar.setVisibility(View.GONE); } @@ -1453,9 +1458,9 @@ public class TimelineFragment extends SFragment implements return; Status eventStatus = statuses.get(pos).asRight(); - int conversationId = eventStatus.getConversationId(); + String conversationId = eventStatus.getConversationId(); - if(conversationId == -1) { // invalid conversation ID + if(conversationId.isEmpty()) { // invalid conversation ID if(isFilteringMuted()) { statuses.remove(pos); } else { @@ -1650,13 +1655,12 @@ public class TimelineFragment extends SFragment implements .as(autoDisposable(from(this))) .subscribe( (newStatus) -> setEmojiReactionForStatus(position, newStatus), - (t) -> Log.d(TAG, - "Failed to react with " + emoji + " on status: " + statusId, t) + (t) -> Timber.e(t, + "Failed to react with " + emoji + " on status: " + statusId) ); } - @Override public void onEmojiReactMenu(@NonNull View view, final EmojiReaction emoji, final String statusId) { super.emojiReactMenu(statusId, emoji, view, this); diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt b/husky/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt index b90a0da..93d974e 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt +++ b/husky/app/src/main/java/com/keylesspalace/tusky/repository/TimelineRepository.kt @@ -1,3 +1,23 @@ +/* + * Husky -- A Pleroma client for Android + * + * Copyright (C) 2022 The Husky Developers + * Copyright (C) 2018 Tusky Contributors + * + * 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. + * + * This program 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 this program. If not, see . + */ + package com.keylesspalace.tusky.repository import android.text.SpannedString diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt b/husky/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt index 4dd6767..c3b2028 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt +++ b/husky/app/src/main/java/com/keylesspalace/tusky/util/OkHttpUtils.kt @@ -53,6 +53,7 @@ object OkHttpUtils { val builder = OkHttpClient.Builder() .addInterceptor(getUserAgentInterceptor()) + //.addInterceptor(getDebugInformation()) .addInterceptor(BrotliInterceptor) .readTimeout(60, SECONDS) .writeTimeout(60, SECONDS) diff --git a/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java b/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java index b9126b8..e3564de 100644 --- a/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java +++ b/husky/app/src/main/java/com/keylesspalace/tusky/viewdata/StatusViewData.java @@ -1,33 +1,35 @@ -/* Copyright 2017 Andrew Dawson +/* + * Husky -- A Pleroma client for Android * - * This file is a part of Tusky. + * Copyright (C) 2022 The Husky Developers + * Copyright (C) 2017 Andrew Dawson * - * 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. + * 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. + * This program 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 . */ + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ package com.keylesspalace.tusky.viewdata; import android.os.Build; import android.text.SpannableStringBuilder; import android.text.Spanned; - import androidx.annotation.Nullable; - import com.keylesspalace.tusky.entity.Attachment; import com.keylesspalace.tusky.entity.Card; import com.keylesspalace.tusky.entity.Emoji; import com.keylesspalace.tusky.entity.EmojiReaction; import com.keylesspalace.tusky.entity.Poll; import com.keylesspalace.tusky.entity.Status; - import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -36,15 +38,14 @@ import java.util.List; import java.util.Objects; /** - * Created by charlag on 11/07/2017. - *

* Class to represent data required to display either a notification or a placeholder. * It is either a {@link StatusViewData.Concrete} or a {@link StatusViewData.Placeholder}. */ public abstract class StatusViewData { - private StatusViewData() { } + private StatusViewData() { + } public abstract long getViewDataId(); @@ -91,15 +92,21 @@ public abstract class StatusViewData { private final List rebloggedByAccountEmojis; @Nullable private final Card card; - private final boolean isCollapsible; /** Whether the status meets the requirement to be collapse */ - final boolean isCollapsed; /** Whether the status is shown partially or fully */ + private final boolean isCollapsible; + /** + * Whether the status meets the requirement to be collapse + */ + final boolean isCollapsed; + /** + * Whether the status is shown partially or fully + */ @Nullable private final PollViewData poll; private final boolean isBot; private final boolean isMuted; /* user toggle */ private final boolean isThreadMuted; /* thread_muted state got from backend */ private final boolean isUserMuted; /* muted state got from backend */ - private final int conversationId; + private final String conversationId; @Nullable private final List emojiReactions; private final boolean parentVisible; @@ -112,10 +119,10 @@ public abstract class StatusViewData { @Nullable String inReplyToAccountAcct, @Nullable Status.Mention[] mentions, String senderId, boolean rebloggingEnabled, Status.Application application, List statusEmojis, List accountEmojis, List rebloggedByAccountEmojis, @Nullable Card card, boolean isCollapsible, boolean isCollapsed, @Nullable PollViewData poll, boolean isBot, boolean isMuted, boolean isThreadMuted, - boolean isUserMuted, int conversationId, @Nullable List emojiReactions, boolean parentVisible) { + boolean isUserMuted, String conversationId, @Nullable List emojiReactions, boolean parentVisible) { this.id = id; - if (Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { + if(Build.VERSION.SDK_INT == Build.VERSION_CODES.M) { // https://github.com/tuskyapp/Tusky/issues/563 this.content = replaceCrashingCharacters(content); this.spoilerText = spoilerText == null ? null : replaceCrashingCharacters(spoilerText).toString(); @@ -212,7 +219,9 @@ public abstract class StatusViewData { return isShowingContent; } - public boolean isBot(){ return isBot; } + public boolean isBot() { + return isBot; + } @Nullable public String getRebloggedAvatar() { @@ -318,31 +327,32 @@ public abstract class StatusViewData { return poll; } - @Override public long getViewDataId() { + @Override + public long getViewDataId() { // Chance of collision is super low and impact of mistake is low as well return id.hashCode(); } - + public boolean isThreadMuted() { return isThreadMuted; } - + public boolean isMuted() { return isMuted; } - + public boolean isUserMuted() { return isUserMuted; } - + @Nullable public List getEmojiReactions() { return emojiReactions; } public boolean deepEquals(StatusViewData o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + if(this == o) return true; + if(o == null || getClass() != o.getClass()) return false; Concrete concrete = (Concrete) o; return reblogged == concrete.reblogged && favourited == concrete.favourited && @@ -393,17 +403,17 @@ public abstract class StatusViewData { SpannableStringBuilder builder = null; int length = content.length(); - for (int index = 0; index < length; ++index) { + for(int index = 0; index < length; ++index) { char character = content.charAt(index); // If there are more than one or two, switch to a map - if (character == SOFT_HYPHEN) { - if (!replacing) { + if(character == SOFT_HYPHEN) { + if(!replacing) { replacing = true; builder = new SpannableStringBuilder(content, 0, index); } builder.append(ASCII_HYPHEN); - } else if (replacing) { + } else if(replacing) { builder.append(character); } } @@ -429,19 +439,22 @@ public abstract class StatusViewData { return id; } - @Override public long getViewDataId() { + @Override + public long getViewDataId() { return id.hashCode(); } - @Override public boolean deepEquals(StatusViewData other) { - if (!(other instanceof Placeholder)) return false; + @Override + public boolean deepEquals(StatusViewData other) { + if(!(other instanceof Placeholder)) return false; Placeholder that = (Placeholder) other; return isLoading == that.isLoading && id.equals(that.id); } - @Override public boolean equals(Object o) { - if (this == o) return true; - if (o == null || getClass() != o.getClass()) return false; + @Override + public boolean equals(Object o) { + if(this == o) return true; + if(o == null || getClass() != o.getClass()) return false; Placeholder that = (Placeholder) o; @@ -486,14 +499,20 @@ public abstract class StatusViewData { private List accountEmojis; private List rebloggedByAccountEmojis; private Card card; - private boolean isCollapsible; /** Whether the status meets the requirement to be collapsed */ - private boolean isCollapsed; /** Whether the status is shown partially or fully */ + private boolean isCollapsible; + /** + * Whether the status meets the requirement to be collapsed + */ + private boolean isCollapsed; + /** + * Whether the status is shown partially or fully + */ private PollViewData poll; private boolean isBot; private boolean isMuted; private boolean isThreadMuted; private boolean isUserMuted; - private int conversationId; + private String conversationId; private List emojiReactions; private boolean parentVisible; @@ -694,7 +713,7 @@ public abstract class StatusViewData { this.card = card; return this; } - + /** * Configure the {@link com.keylesspalace.tusky.viewdata.StatusViewData} to support collapsing * its content limiting the visible length when collapsed at 500 characters, @@ -739,9 +758,9 @@ public abstract class StatusViewData { return this; } - public Builder setConversationId(int conversationId) { - this.conversationId = conversationId; - return this; + public Builder setConversationId(String conversationId) { + this.conversationId = conversationId; + return this; } public Builder setEmojiReactions(List emojiReactions) { @@ -750,9 +769,9 @@ public abstract class StatusViewData { } public StatusViewData.Concrete createStatusViewData() { - if (this.statusEmojis == null) statusEmojis = Collections.emptyList(); - if (this.accountEmojis == null) accountEmojis = Collections.emptyList(); - if (this.createdAt == null) createdAt = new Date(); + if(this.statusEmojis == null) statusEmojis = Collections.emptyList(); + if(this.accountEmojis == null) accountEmojis = Collections.emptyList(); + if(this.createdAt == null) createdAt = new Date(); return new StatusViewData.Concrete(id, content, reblogged, favourited, bookmarked, spoilerText, visibility, attachments, rebloggedByUsername, rebloggedAvatar, isSensitive, isExpanded,