-
-
Notifications
You must be signed in to change notification settings - Fork 390
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
Support trending links #3908
Support trending links #3908
Changes from all commits
e9e65bd
9cb1547
0146d42
ca6861a
faaeb5b
fba5337
792d6f5
7ab15bd
1a84adb
0cdfbf7
8a1adde
b874023
0874bb8
fe28468
22ba311
8889a84
3a4e5c8
9b619e1
ab95d5d
dda5c39
e960dd2
810afd2
822df0b
0438acf
9696075
b81dbb2
c4da394
4536239
0c0ac4c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
* Copyright 2023 Tusky Contributors | ||
* | ||
* 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.components.trending | ||
|
||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT | ||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT | ||
import android.widget.ImageView.ScaleType | ||
import android.widget.LinearLayout | ||
import androidx.recyclerview.widget.RecyclerView | ||
import com.bumptech.glide.Glide | ||
import com.google.android.material.shape.CornerFamily | ||
import com.google.android.material.shape.ShapeAppearanceModel | ||
import com.keylesspalace.tusky.R | ||
import com.keylesspalace.tusky.databinding.ItemTrendingLinkBinding | ||
import com.keylesspalace.tusky.entity.TrendsLink | ||
import com.keylesspalace.tusky.util.StatusDisplayOptions | ||
import com.keylesspalace.tusky.util.decodeBlurHash | ||
import com.keylesspalace.tusky.util.hide | ||
|
||
class TrendingLinkViewHolder( | ||
private val binding: ItemTrendingLinkBinding, | ||
private val onClick: (String) -> Unit | ||
) : RecyclerView.ViewHolder(binding.root) { | ||
private var link: TrendsLink? = null | ||
|
||
private val radius = binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_radius).toFloat() | ||
|
||
init { | ||
itemView.setOnClickListener { link?.let { onClick(it.url) } } | ||
} | ||
|
||
fun bind(link: TrendsLink, statusDisplayOptions: StatusDisplayOptions) { | ||
this.link = link | ||
|
||
// TODO: This is very similar to the code in StatusBaseViewHolder.setupCard(). | ||
// Can a "PreviewCardView" type be created to encapsulate all this? | ||
binding.cardTitle.text = link.title | ||
|
||
when { | ||
link.description.isNotBlank() -> link.description | ||
link.authorName.isNotBlank() -> link.authorName | ||
else -> null | ||
}?.let { binding.cardDescription.text = it } ?: binding.cardDescription.hide() | ||
|
||
binding.cardLink.text = link.url | ||
|
||
val cardImageShape = ShapeAppearanceModel.Builder() | ||
|
||
if (statusDisplayOptions.mediaPreviewEnabled && !link.image.isNullOrBlank()) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What does all this do (comment?)? It seems to adapt layout dynamically but to what end? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There are two preview layout types. One puts the preview image above the link title and description (e.g., see the screenshots in the second comment on this PR). The other puts the preview to the left of the title and description. As the previous comment notes, this is out of StatusBaseViewHolder, and might be moved somewhere common later if it needs reusing again. This is to follow what I call the rule-of-three for refactoring (see also https://en.wikipedia.org/wiki/Don%27t_repeat_yourself#AHA).
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
These might be the code comment(s) I was looking for. |
||
if (link.width > link.height) { | ||
binding.statusCardView.orientation = LinearLayout.VERTICAL | ||
binding.cardImage.layoutParams.height = binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_image_vertical_height) | ||
binding.cardImage.layoutParams.width = MATCH_PARENT | ||
binding.cardInfo.layoutParams.height = MATCH_PARENT | ||
binding.cardInfo.layoutParams.width = WRAP_CONTENT | ||
cardImageShape.setTopLeftCorner(CornerFamily.ROUNDED, radius) | ||
cardImageShape.setTopRightCorner(CornerFamily.ROUNDED, radius) | ||
} else { | ||
binding.statusCardView.orientation = LinearLayout.HORIZONTAL | ||
binding.cardImage.layoutParams.height = MATCH_PARENT | ||
binding.cardImage.layoutParams.width = binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_image_horizontal_width) | ||
binding.cardInfo.layoutParams.height = WRAP_CONTENT | ||
binding.cardInfo.layoutParams.width = MATCH_PARENT | ||
cardImageShape.setTopLeftCorner(CornerFamily.ROUNDED, radius) | ||
cardImageShape.setBottomLeftCorner(CornerFamily.ROUNDED, radius) | ||
} | ||
|
||
binding.cardImage.shapeAppearanceModel = cardImageShape.build() | ||
binding.cardImage.scaleType = ScaleType.CENTER_CROP | ||
|
||
val builder = Glide.with(binding.cardImage.context) | ||
.load(link.image) | ||
.dontTransform() | ||
if (statusDisplayOptions.useBlurhash && !link.blurhash.isNullOrBlank()) { | ||
builder | ||
.placeholder(decodeBlurHash(binding.cardImage.context, link.blurhash)) | ||
.into(binding.cardImage) | ||
} else { | ||
builder.into(binding.cardImage) | ||
} | ||
} else if (statusDisplayOptions.useBlurhash && !link.blurhash.isNullOrBlank()) { | ||
binding.statusCardView.orientation = LinearLayout.HORIZONTAL | ||
binding.cardImage.layoutParams.height = MATCH_PARENT | ||
binding.cardImage.layoutParams.width = binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_image_horizontal_width) | ||
binding.cardInfo.layoutParams.height = WRAP_CONTENT | ||
binding.cardInfo.layoutParams.width = MATCH_PARENT | ||
|
||
cardImageShape | ||
.setTopLeftCorner(CornerFamily.ROUNDED, radius) | ||
.setBottomLeftCorner(CornerFamily.ROUNDED, radius) | ||
binding.cardImage.shapeAppearanceModel = cardImageShape.build() | ||
binding.cardImage.scaleType = ScaleType.CENTER_CROP | ||
|
||
Glide.with(binding.cardImage.context) | ||
.load(decodeBlurHash(binding.cardImage.context, link.blurhash)) | ||
.dontTransform() | ||
.into(binding.cardImage) | ||
} else { | ||
binding.statusCardView.orientation = LinearLayout.HORIZONTAL | ||
binding.cardImage.layoutParams.height = MATCH_PARENT | ||
binding.cardImage.layoutParams.width = binding.cardImage.resources.getDimensionPixelSize(R.dimen.card_image_horizontal_width) | ||
binding.cardInfo.layoutParams.height = WRAP_CONTENT | ||
binding.cardInfo.layoutParams.width = MATCH_PARENT | ||
|
||
binding.cardImage.shapeAppearanceModel = ShapeAppearanceModel() | ||
binding.cardImage.scaleType = ScaleType.CENTER | ||
|
||
Glide.with(binding.cardImage.context) | ||
.load(R.drawable.card_image_placeholder) | ||
.into(binding.cardImage) | ||
} | ||
|
||
binding.statusCardView.clipToOutline = true | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would make sense to only have that "trending activity view with tabs" you'll get from the side drawer.
So the (only) tab here would show that as well.
(Consistent and removes clutter from this tab list, this is especially relevant for the following PR when this gets three tabs here.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would put a set of tabs (trending tags, links, posts) inside another set of tabs.
That's possible (Material3 calls them "secondary tabs") but it's a departure from how we've done things so far. I also think it's reasonable for a user to only want one or two of them and not all three (e.g., I'd use links and posts, but have no interest in trending hashtags).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok...
I see (secondary tabs).
But it would still be (quite) inconsistent:
:beard_scratch:
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Lakoja Yes, so let's think about two typical users.
User A occasionally checks the trending info, but not habitually. Maybe a few times a week to see if there's anything new. They don't have a need to focus on one of the three (soon to be four) types of thing that can trend, so getting to it from the nav menu when they want to, and tapping through the different tabs to see what's new is fine for them.
User B is much more interested in trending links, they want to check regularly as part of their normal Mastodon experience. So much so that they want to make it a tab. They're not interested in posts or hashtags, just links. For them, adding a tab with just trending links is fine.
If they added a tab that contained all the trending items they would have to tap that tab once to choose "trending", and then tap again to choose which trending item they're interested in. That's suboptimal UX for an activity that User B is going to want to do a lot.
That's why I think the UX in this PR is the way to go.
There's a third user, User C, who are like User B, but they want to check all the trending tabs. They're not well served at the moment, because of the 5-tab limit, but I think that should be removed (see discussion in "Tusky General" channel).