fix: most profiles

This commit is contained in:
rebelonion 2024-03-05 02:29:00 -06:00
parent db50975174
commit a2ecc5e30e
8 changed files with 112 additions and 30 deletions

View file

@ -784,7 +784,9 @@ fun copyToClipboard(string: String, toast: Boolean = true) {
val clipboard = getSystemService(activity, ClipboardManager::class.java) val clipboard = getSystemService(activity, ClipboardManager::class.java)
val clip = ClipData.newPlainText("label", string) val clip = ClipData.newPlainText("label", string)
clipboard?.setPrimaryClip(clip) clipboard?.setPrimaryClip(clip)
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.S_V2) {
if (toast) snackString(activity.getString(R.string.copied_text, string)) if (toast) snackString(activity.getString(R.string.copied_text, string))
}
} }
@SuppressLint("SetTextI18n") @SuppressLint("SetTextI18n")
@ -1108,7 +1110,7 @@ fun buildMarkwon(activity: Activity, userInputContent: Boolean = true): Markwon
val markwon = Markwon.builder(activity) val markwon = Markwon.builder(activity)
.usePlugin(object : AbstractMarkwonPlugin() { .usePlugin(object : AbstractMarkwonPlugin() {
override fun configureConfiguration(builder: MarkwonConfiguration.Builder) { override fun configureConfiguration(builder: MarkwonConfiguration.Builder) {
builder.linkResolver { view, link -> builder.linkResolver { _, link ->
copyToClipboard(link, true) copyToClipboard(link, true)
} }
} }
@ -1119,7 +1121,7 @@ fun buildMarkwon(activity: Activity, userInputContent: Boolean = true): Markwon
.usePlugin(TablePlugin.create(activity)) .usePlugin(TablePlugin.create(activity))
.usePlugin(TaskListPlugin.create(activity)) .usePlugin(TaskListPlugin.create(activity))
.usePlugin(HtmlPlugin.create { plugin -> .usePlugin(HtmlPlugin.create { plugin ->
if (!userInputContent) { if (userInputContent) {
plugin.addHandler( plugin.addHandler(
TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre", "a") TagHandlerNoOp.create("h1", "h2", "h3", "h4", "h5", "h6", "hr", "pre", "a")
) )

View file

@ -61,7 +61,7 @@ class AnilistQueries {
suspend fun getUserProfile(id: Int): Query.UserProfileResponse? { suspend fun getUserProfile(id: Int): Query.UserProfileResponse? {
return executeQuery<Query.UserProfileResponse>( return executeQuery<Query.UserProfileResponse>(
"""{user:User(id:$id){id,name,about(asHtml:true)avatar{medium,large},bannerImage,isFollowing,isFollower,isBlocked,favourites{anime{nodes{coverImage{extraLarge,large,medium,color}}}manga{nodes{id,coverImage{extraLarge,large,medium,color}}}characters{nodes{id,image{large,medium}}}staff{nodes{id,image{large,medium}}}studios{nodes{id,name}}}statistics{anime{count,meanScore,standardDeviation,minutesWatched,episodesWatched,chaptersRead,volumesRead}manga{count,meanScore,standardDeviation,minutesWatched,episodesWatched,chaptersRead,volumesRead}}siteUrl}}""", """{user:User(id:$id){id,name,about(asHtml:true)avatar{medium,large},bannerImage,isFollowing,isFollower,isBlocked,favourites{anime{nodes{id,coverImage{extraLarge,large,medium,color}}}manga{nodes{id,coverImage{extraLarge,large,medium,color}}}characters{nodes{id,image{large,medium}}}staff{nodes{id,image{large,medium}}}studios{nodes{id,name}}}statistics{anime{count,meanScore,standardDeviation,minutesWatched,episodesWatched,chaptersRead,volumesRead}manga{count,meanScore,standardDeviation,minutesWatched,episodesWatched,chaptersRead,volumesRead}}siteUrl}}""",
force = true force = true
) )
} }

View file

@ -46,7 +46,7 @@ data class Character(
// Notes for site moderators // Notes for site moderators
@SerialName("modNotes") var modNotes: String?, @SerialName("modNotes") var modNotes: String?,
) ) : java.io.Serializable
@Serializable @Serializable
data class CharacterConnection( data class CharacterConnection(
@ -56,7 +56,7 @@ data class CharacterConnection(
// The pagination information // The pagination information
// @SerialName("pageInfo") var pageInfo: PageInfo?, // @SerialName("pageInfo") var pageInfo: PageInfo?,
) ) : java.io.Serializable
@Serializable @Serializable
data class CharacterEdge( data class CharacterEdge(
@ -82,7 +82,7 @@ data class CharacterEdge(
// The order the character should be displayed from the users favourites // The order the character should be displayed from the users favourites
@SerialName("favouriteOrder") var favouriteOrder: Int?, @SerialName("favouriteOrder") var favouriteOrder: Int?,
) ) : java.io.Serializable
@Serializable @Serializable
data class CharacterName( data class CharacterName(
@ -109,7 +109,7 @@ data class CharacterName(
// The currently authenticated users preferred name language. Default romaji for non-authenticated // The currently authenticated users preferred name language. Default romaji for non-authenticated
@SerialName("userPreferred") var userPreferred: String?, @SerialName("userPreferred") var userPreferred: String?,
) ) : java.io.Serializable
@Serializable @Serializable
data class CharacterImage( data class CharacterImage(
@ -118,4 +118,4 @@ data class CharacterImage(
// The character's image of media at medium size // The character's image of media at medium size
@SerialName("medium") var medium: String?, @SerialName("medium") var medium: String?,
) ) : java.io.Serializable

View file

@ -143,7 +143,7 @@ class Query {
data class ToggleFollow( data class ToggleFollow(
@SerialName("data") @SerialName("data")
val data: Data? val data: Data?
) { ) : java.io.Serializable {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("ToggleFollow") @SerialName("ToggleFollow")
@ -156,7 +156,7 @@ class Query {
data class GenreCollection( data class GenreCollection(
@SerialName("data") @SerialName("data")
val data: Data val data: Data
) { ) : java.io.Serializable {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("GenreCollection") @SerialName("GenreCollection")
@ -168,7 +168,7 @@ class Query {
data class MediaTagCollection( data class MediaTagCollection(
@SerialName("data") @SerialName("data")
val data: Data val data: Data
) { ) : java.io.Serializable {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("MediaTagCollection") @SerialName("MediaTagCollection")
@ -180,7 +180,7 @@ class Query {
data class User( data class User(
@SerialName("data") @SerialName("data")
val data: Data val data: Data
) { ) : java.io.Serializable {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("User") @SerialName("User")
@ -192,7 +192,7 @@ class Query {
data class UserProfileResponse( data class UserProfileResponse(
@SerialName("data") @SerialName("data")
val data: Data val data: Data
) { ) : java.io.Serializable {
@Serializable @Serializable
data class Data( data class Data(
@SerialName("user") @SerialName("user")
@ -218,7 +218,7 @@ class Query {
val isFollower: Boolean, val isFollower: Boolean,
@SerialName("isBlocked") @SerialName("isBlocked")
val isBlocked: Boolean, val isBlocked: Boolean,
@SerialName("favorites") @SerialName("favourites")
val favorites: UserFavorites?, val favorites: UserFavorites?,
@SerialName("statistics") @SerialName("statistics")
val statistics: NNUserStatisticTypes, val statistics: NNUserStatisticTypes,

View file

@ -251,7 +251,7 @@ data class MediaCoverImage(
// Average #hex color of cover image // Average #hex color of cover image
@SerialName("color") var color: String?, @SerialName("color") var color: String?,
) ) : java.io.Serializable
@Serializable @Serializable
data class MediaList( data class MediaList(
@ -490,7 +490,7 @@ data class MediaExternalLink(
// isDisabled: Boolean // isDisabled: Boolean
@SerialName("notes") var notes: String?, @SerialName("notes") var notes: String?,
) ) : java.io.Serializable
@Serializable @Serializable
enum class ExternalLinkType { enum class ExternalLinkType {
@ -512,13 +512,13 @@ data class MediaListCollection(
// If there is another chunk // If there is another chunk
@SerialName("hasNextChunk") var hasNextChunk: Boolean?, @SerialName("hasNextChunk") var hasNextChunk: Boolean?,
) ) : java.io.Serializable
@Serializable @Serializable
data class FollowData( data class FollowData(
@SerialName("id") var id: Int, @SerialName("id") var id: Int,
@SerialName("isFollowing") var isFollowing: Boolean, @SerialName("isFollowing") var isFollowing: Boolean,
) ) : java.io.Serializable
@Serializable @Serializable
data class MediaListGroup( data class MediaListGroup(
@ -532,4 +532,4 @@ data class MediaListGroup(
@SerialName("isSplitCompletedList") var isSplitCompletedList: Boolean?, @SerialName("isSplitCompletedList") var isSplitCompletedList: Boolean?,
@SerialName("status") var status: MediaListStatus?, @SerialName("status") var status: MediaListStatus?,
) ) : java.io.Serializable

View file

@ -2,10 +2,12 @@ package ani.dantotsu.profile
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.util.TypedValue
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.view.animation.LayoutAnimationController import android.view.animation.LayoutAnimationController
import android.webkit.WebView
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
@ -13,7 +15,6 @@ import androidx.lifecycle.LiveData
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import ani.dantotsu.buildMarkwon
import ani.dantotsu.connections.anilist.ProfileViewModel import ani.dantotsu.connections.anilist.ProfileViewModel
import ani.dantotsu.connections.anilist.api.Query import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.databinding.FragmentProfileBinding import ani.dantotsu.databinding.FragmentProfileBinding
@ -23,10 +24,12 @@ import ani.dantotsu.media.MediaAdaptor
import ani.dantotsu.media.user.ListActivity import ani.dantotsu.media.user.ListActivity
import ani.dantotsu.setSlideIn import ani.dantotsu.setSlideIn
import ani.dantotsu.setSlideUp import ani.dantotsu.setSlideUp
import ani.dantotsu.util.ColorEditor.Companion.toCssColor
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
class ProfileFragment(): Fragment() {
class ProfileFragment() : Fragment() {
lateinit var binding: FragmentProfileBinding lateinit var binding: FragmentProfileBinding
private lateinit var activity: ProfileActivity private lateinit var activity: ProfileActivity
private lateinit var user: Query.UserProfile private lateinit var user: Query.UserProfile
@ -44,8 +47,36 @@ class ProfileFragment(): Fragment() {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
activity = requireActivity() as ProfileActivity activity = requireActivity() as ProfileActivity
user = arguments?.getSerializable("user") as Query.UserProfile user = arguments?.getSerializable("user") as Query.UserProfile
val markwon = buildMarkwon(activity, false)
markwon.setMarkdown(binding.profileUserBio, user.about?:"") val backGroundColorTypedValue = TypedValue()
val textColorTypedValue = TypedValue()
activity.theme.resolveAttribute(
android.R.attr.windowBackground,
backGroundColorTypedValue,
true
)
activity.theme.resolveAttribute(
com.google.android.material.R.attr.colorOnBackground,
textColorTypedValue,
true
)
binding.profileUserBio.settings.loadWithOverviewMode = true
binding.profileUserBio.settings.useWideViewPort = true
binding.profileUserBio.settings.javaScriptEnabled = true
WebView.setWebContentsDebuggingEnabled(true)
binding.profileUserBio.setInitialScale(1)
binding.profileUserBio.loadDataWithBaseURL(
null,
styled(
convertMarkdownToHtml(user.about ?: ""),
backGroundColorTypedValue.data,
textColorTypedValue.data
),
"text/html; charset=utf-8",
"UTF-8",
null
)
binding.userInfoContainer.visibility = if (user.about != null) View.VISIBLE else View.GONE binding.userInfoContainer.visibility = if (user.about != null) View.VISIBLE else View.GONE
binding.profileAnimeList.setOnClickListener { binding.profileAnimeList.setOnClickListener {
@ -67,7 +98,8 @@ class ProfileFragment(): Fragment() {
binding.profileAnimeListImage.loadImage("https://bit.ly/31bsIHq") binding.profileAnimeListImage.loadImage("https://bit.ly/31bsIHq")
binding.profileMangaListImage.loadImage("https://bit.ly/2ZGfcuG") binding.profileMangaListImage.loadImage("https://bit.ly/2ZGfcuG")
binding.statsEpisodesWatched.text = user.statistics.anime.episodesWatched.toString() binding.statsEpisodesWatched.text = user.statistics.anime.episodesWatched.toString()
binding.statsDaysWatched.text = (user.statistics.anime.minutesWatched / (24 * 60)).toString() binding.statsDaysWatched.text =
(user.statistics.anime.minutesWatched / (24 * 60)).toString()
binding.statsTotalAnime.text = user.statistics.anime.count.toString() binding.statsTotalAnime.text = user.statistics.anime.count.toString()
binding.statsAnimeMeanScore.text = user.statistics.anime.meanScore.toString() binding.statsAnimeMeanScore.text = user.statistics.anime.meanScore.toString()
binding.statsChaptersRead.text = user.statistics.manga.chaptersRead.toString() binding.statsChaptersRead.text = user.statistics.manga.chaptersRead.toString()
@ -107,6 +139,16 @@ class ProfileFragment(): Fragment() {
} }
} }
private fun convertMarkdownToHtml(markdown: String): String {
val regex = """\[\!\[(.*?)\]\((.*?)\)\]\((.*?)\)""".toRegex()
return regex.replace(markdown) { matchResult ->
val altText = matchResult.groupValues[1]
val imageUrl = matchResult.groupValues[2]
val linkUrl = matchResult.groupValues[3]
"""<a href="$linkUrl"><img src="$imageUrl" alt="$altText"></a>"""
}
}
private fun initRecyclerView( private fun initRecyclerView(
mode: LiveData<ArrayList<Media>>, mode: LiveData<ArrayList<Media>>,
container: View, container: View,
@ -146,6 +188,34 @@ class ProfileFragment(): Fragment() {
} }
} }
private fun styled(html: String, backGroundColor: Int, textColor: Int): String {
return """
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1.0, charset=UTF-8">
<style>
body {
background-color: ${backGroundColor.toCssColor()};
color: ${textColor.toCssColor()};
margin: 0;
padding: 0;
max-width: 100%;
overflow-x: hidden; /* Prevent horizontal scrolling */
}
img {
max-width: 100%;
height: auto; /* Maintain aspect ratio */
}
/* Add responsive design elements for other content as needed */
</style>
</head>
<body>
$html
</body>
""".trimIndent()
}
companion object { companion object {
fun newInstance(query: Query.UserProfile): ProfileFragment { fun newInstance(query: Query.UserProfile): ProfileFragment {
val args = Bundle().apply { val args = Bundle().apply {

View file

@ -84,5 +84,14 @@ class ColorEditor {
} }
return adjustedColor return adjustedColor
} }
fun Int.toCssColor(): String {
var base = "rgba("
base += "${Color.red(this)}, "
base += "${Color.green(this)}, "
base += "${Color.blue(this)}, "
base += "${Color.alpha(this) / 255.0})"
return base
}
} }
} }

View file

@ -332,7 +332,7 @@
<LinearLayout <LinearLayout
android:id="@+id/userInfoContainer" android:id="@+id/userInfoContainer"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:orientation="vertical"> android:orientation="vertical">
<TextView <TextView
@ -347,12 +347,13 @@
android:textSize="16sp" android:textSize="16sp"
tools:ignore="HardcodedText" /> tools:ignore="HardcodedText" />
<TextView <WebView
android:id="@+id/profileUserBio" android:id="@+id/profileUserBio"
android:layout_width="wrap_content" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="16dp" android:layout_marginStart="16dp"
android:layout_marginEnd="16dp" android:layout_marginEnd="16dp"
android:textAlignment="textStart"
android:ellipsize="end" android:ellipsize="end"
android:padding="16dp" android:padding="16dp"
tools:text="@string/slogan" /> tools:text="@string/slogan" />