Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix conversations #2556

Merged
merged 14 commits into from
May 30, 2022
875 changes: 875 additions & 0 deletions app/schemas/com.keylesspalace.tusky.db.AppDatabase/38.json

Large diffs are not rendered by default.

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,40 @@ import android.view.ViewGroup
import androidx.paging.PagingDataAdapter
import androidx.recyclerview.widget.DiffUtil
import com.keylesspalace.tusky.R
import com.keylesspalace.tusky.adapter.StatusBaseViewHolder
import com.keylesspalace.tusky.interfaces.StatusActionListener
import com.keylesspalace.tusky.util.StatusDisplayOptions

class ConversationAdapter(
private val statusDisplayOptions: StatusDisplayOptions,
private var statusDisplayOptions: StatusDisplayOptions,
private val listener: StatusActionListener
) : PagingDataAdapter<ConversationViewData, ConversationViewHolder>(CONVERSATION_COMPARATOR) {

var mediaPreviewEnabled: Boolean
get() = statusDisplayOptions.mediaPreviewEnabled
set(mediaPreviewEnabled) {
statusDisplayOptions = statusDisplayOptions.copy(
mediaPreviewEnabled = mediaPreviewEnabled
)
}

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ConversationViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_conversation, parent, false)
return ConversationViewHolder(view, statusDisplayOptions, listener)
}

override fun onBindViewHolder(holder: ConversationViewHolder, position: Int) {
holder.setupWithConversation(getItem(position))
onBindViewHolder(holder, position, emptyList())
}

override fun onBindViewHolder(
holder: ConversationViewHolder,
position: Int,
payloads: List<Any>
) {
getItem(position)?.let { conversationViewData ->
holder.setupWithConversation(conversationViewData, payloads.firstOrNull())
}
}

companion object {
Expand All @@ -44,7 +63,17 @@ class ConversationAdapter(
}

override fun areContentsTheSame(oldItem: ConversationViewData, newItem: ConversationViewData): Boolean {
return oldItem == newItem
return false // Items are different always. It allows to refresh timestamp on every view holder update
}

override fun getChangePayload(oldItem: ConversationViewData, newItem: ConversationViewData): Any? {
return if (oldItem == newItem) {
// If items are equal - update timestamp only
listOf(StatusBaseViewHolder.Key.KEY_CREATED)
} else {
// If items are different - update the whole view holder
null
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,15 @@ import java.util.Date
data class ConversationEntity(
val accountId: Long,
val id: String,
val order: Int,
val accounts: List<ConversationAccountEntity>,
val unread: Boolean,
@Embedded(prefix = "s_") val lastStatus: ConversationStatusEntity
) {
fun toViewData(): ConversationViewData {
return ConversationViewData(
id = id,
order = order,
accounts = accounts,
unread = unread,
lastStatus = lastStatus.toViewData()
Expand All @@ -50,6 +52,7 @@ data class ConversationEntity(

data class ConversationAccountEntity(
val id: String,
val localUsername: String,
val username: String,
val displayName: String,
val avatar: String,
Expand All @@ -58,12 +61,12 @@ data class ConversationAccountEntity(
fun toAccount(): TimelineAccount {
return TimelineAccount(
id = id,
localUsername = localUsername,
username = username,
displayName = displayName,
url = "",
avatar = avatar,
emojis = emojis,
localUsername = "",
)
}
}
Expand Down Expand Up @@ -134,6 +137,7 @@ data class ConversationStatusEntity(
fun TimelineAccount.toEntity() =
ConversationAccountEntity(
id = id,
localUsername = localUsername,
username = username,
displayName = name,
avatar = avatar,
Expand Down Expand Up @@ -166,10 +170,11 @@ fun Status.toEntity() =
poll = poll
)

fun Conversation.toEntity(accountId: Long) =
fun Conversation.toEntity(accountId: Long, order: Int) =
ConversationEntity(
accountId = accountId,
id = id,
order = order,
accounts = accounts.map { it.toEntity() },
unread = unread,
lastStatus = lastStatus!!.toEntity()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,35 @@ import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.paging.LoadState
import androidx.paging.LoadStateAdapter
import com.keylesspalace.tusky.adapter.NetworkStateViewHolder
import com.keylesspalace.tusky.databinding.ItemNetworkStateBinding
import com.keylesspalace.tusky.util.BindingHolder
import com.keylesspalace.tusky.util.visible

class ConversationLoadStateAdapter(
private val retryCallback: () -> Unit
) : LoadStateAdapter<NetworkStateViewHolder>() {
) : LoadStateAdapter<BindingHolder<ItemNetworkStateBinding>>() {

override fun onBindViewHolder(holder: NetworkStateViewHolder, loadState: LoadState) {
holder.setUpWithNetworkState(loadState)
override fun onBindViewHolder(holder: BindingHolder<ItemNetworkStateBinding>, loadState: LoadState) {
val binding = holder.binding
binding.progressBar.visible(loadState == LoadState.Loading)
binding.retryButton.visible(loadState is LoadState.Error)
val msg = if (loadState is LoadState.Error) {
loadState.error.message
} else {
null
}
binding.errorMsg.visible(msg != null)
binding.errorMsg.text = msg
binding.retryButton.setOnClickListener {
retryCallback()
}
}

override fun onCreateViewHolder(
parent: ViewGroup,
loadState: LoadState
): NetworkStateViewHolder {
): BindingHolder<ItemNetworkStateBinding> {
val binding = ItemNetworkStateBinding.inflate(LayoutInflater.from(parent.context), parent, false)
return NetworkStateViewHolder(binding, retryCallback)
return BindingHolder(binding)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import com.keylesspalace.tusky.viewdata.StatusViewData

data class ConversationViewData(
val id: String,
val order: Int,
val accounts: List<ConversationAccountEntity>,
val unread: Boolean,
val lastStatus: StatusViewData.Concrete
Expand All @@ -37,6 +38,7 @@ data class ConversationViewData(
return ConversationEntity(
accountId = accountId,
id = id,
order = order,
accounts = accounts,
unread = unread,
lastStatus = lastStatus.toConversationStatusEntity(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
import android.widget.ImageView;
import android.widget.TextView;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

import com.keylesspalace.tusky.R;
Expand All @@ -43,12 +45,12 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
private static final InputFilter[] COLLAPSE_INPUT_FILTER = new InputFilter[]{SmartLengthInputFilter.INSTANCE};
private static final InputFilter[] NO_INPUT_FILTER = new InputFilter[0];

private TextView conversationNameTextView;
private Button contentCollapseButton;
private ImageView[] avatars;
private final TextView conversationNameTextView;
private final Button contentCollapseButton;
private final ImageView[] avatars;

private StatusDisplayOptions statusDisplayOptions;
private StatusActionListener listener;
private final StatusDisplayOptions statusDisplayOptions;
private final StatusActionListener listener;

ConversationViewHolder(View itemView,
StatusDisplayOptions statusDisplayOptions,
Expand All @@ -64,60 +66,74 @@ public class ConversationViewHolder extends StatusBaseViewHolder {
this.statusDisplayOptions = statusDisplayOptions;

this.listener = listener;

}

@Override
protected int getMediaPreviewHeight(Context context) {
return context.getResources().getDimensionPixelSize(R.dimen.status_media_preview_height);
}

void setupWithConversation(ConversationViewData conversation) {
void setupWithConversation(
@NonNull ConversationViewData conversation,
@Nullable Object payloads
) {

StatusViewData.Concrete statusViewData = conversation.getLastStatus();
Status status = statusViewData.getStatus();
TimelineAccount account = status.getAccount();

setupCollapsedState(statusViewData.isCollapsible(), statusViewData.isCollapsed(), statusViewData.isExpanded(), statusViewData.getSpoilerText(), listener);

setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions);
setUsername(account.getUsername());
setCreatedAt(status.getCreatedAt(), statusDisplayOptions);
setIsReply(status.getInReplyToId() != null);
setFavourited(status.getFavourited());
setBookmarked(status.getBookmarked());
List<Attachment> attachments = status.getAttachments();
boolean sensitive = status.getSensitive();
if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) {
setMediaPreviews(attachments, sensitive, listener, statusViewData.isShowingContent(),
statusDisplayOptions.useBlurhash());

if (attachments.size() == 0) {

if (payloads == null) {
TimelineAccount account = status.getAccount();

setupCollapsedState(statusViewData.isCollapsible(), statusViewData.isCollapsed(), statusViewData.isExpanded(), statusViewData.getSpoilerText(), listener);

setDisplayName(account.getDisplayName(), account.getEmojis(), statusDisplayOptions);
setUsername(account.getUsername());
setCreatedAt(status.getCreatedAt(), statusDisplayOptions);
setIsReply(status.getInReplyToId() != null);
setFavourited(status.getFavourited());
setBookmarked(status.getBookmarked());
List<Attachment> attachments = status.getAttachments();
boolean sensitive = status.getSensitive();
if (statusDisplayOptions.mediaPreviewEnabled() && hasPreviewableAttachment(attachments)) {
setMediaPreviews(attachments, sensitive, listener, statusViewData.isShowingContent(),
statusDisplayOptions.useBlurhash());

if (attachments.size() == 0) {
hideSensitiveMediaWarning();
}
// Hide the unused label.
for (TextView mediaLabel : mediaLabels) {
mediaLabel.setVisibility(View.GONE);
}
} else {
setMediaLabel(attachments, sensitive, listener, statusViewData.isShowingContent());
// Hide all unused views.
mediaPreviews[0].setVisibility(View.GONE);
mediaPreviews[1].setVisibility(View.GONE);
mediaPreviews[2].setVisibility(View.GONE);
mediaPreviews[3].setVisibility(View.GONE);
hideSensitiveMediaWarning();
}
// Hide the unused label.
for (TextView mediaLabel : mediaLabels) {
mediaLabel.setVisibility(View.GONE);
}
} else {
setMediaLabel(attachments, sensitive, listener, statusViewData.isShowingContent());
// Hide all unused views.
mediaPreviews[0].setVisibility(View.GONE);
mediaPreviews[1].setVisibility(View.GONE);
mediaPreviews[2].setVisibility(View.GONE);
mediaPreviews[3].setVisibility(View.GONE);
hideSensitiveMediaWarning();
}

setupButtons(listener, account.getId(), statusViewData.getContent().toString(),
statusDisplayOptions);
setupButtons(listener, account.getId(), statusViewData.getContent().toString(),
statusDisplayOptions);

setSpoilerAndContent(statusViewData.isExpanded(), statusViewData.getContent(), status.getSpoilerText(),
status.getMentions(), status.getTags(), status.getEmojis(),
PollViewDataKt.toViewData(status.getPoll()), statusDisplayOptions, listener);
setSpoilerAndContent(statusViewData.isExpanded(), statusViewData.getContent(), status.getSpoilerText(),
status.getMentions(), status.getTags(), status.getEmojis(),
PollViewDataKt.toViewData(status.getPoll()), statusDisplayOptions, listener);

setConversationName(conversation.getAccounts());
setConversationName(conversation.getAccounts());

setAvatars(conversation.getAccounts());
setAvatars(conversation.getAccounts());
} else {
if (payloads instanceof List) {
for (Object item : (List<?>) payloads) {
if (Key.KEY_CREATED.equals(item)) {
setCreatedAt(status.getCreatedAt(), statusDisplayOptions);
}
}
}
}
}

private void setConversationName(List<ConversationAccountEntity> accounts) {
Expand Down Expand Up @@ -169,4 +185,4 @@ private void setupCollapsedState(boolean collapsible, boolean collapsed, boolean
content.setFilters(NO_INPUT_FILTER);
}
}
}
}
Loading