diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 68841a8d..738d7be6 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -200,6 +200,7 @@
android:parentActivityName=".MainActivity" />
+
diff --git a/app/src/main/java/ani/dantotsu/addons/AddonLoader.kt b/app/src/main/java/ani/dantotsu/addons/AddonLoader.kt
index 25681b94..f2dc4488 100644
--- a/app/src/main/java/ani/dantotsu/addons/AddonLoader.kt
+++ b/app/src/main/java/ani/dantotsu/addons/AddonLoader.kt
@@ -21,6 +21,20 @@ import eu.kanade.tachiyomi.util.system.getApplicationIcon
class AddonLoader {
companion object {
+
+ /**
+ * Load an extension from a package name with a specific class name
+ * @param context the context
+ * @param packageName the package name of the extension
+ * @param type the type of extension
+ * @return the loaded extension
+ * @throws IllegalStateException if the extension is not of the correct type
+ * @throws ClassNotFoundException if the extension class is not found
+ * @throws NoClassDefFoundError if the extension class is not found
+ * @throws Exception if any other error occurs
+ * @throws PackageManager.NameNotFoundException if the package is not found
+ * @throws IllegalStateException if the extension is not found
+ */
fun loadExtension(
context: Context,
packageName: String,
@@ -70,11 +84,11 @@ class AddonLoader {
val loadedClass = try {
Class.forName(className, false, classLoader)
} catch (e: ClassNotFoundException) {
- Logger.log("Extension load error: $extName ($className)")
+ Logger.log("ClassNotFoundException load error: $extName ($className)")
Logger.log(e)
throw e
} catch (e: NoClassDefFoundError) {
- Logger.log("Extension load error: $extName ($className)")
+ Logger.log("NoClassDefFoundError load error: $extName ($className)")
Logger.log(e)
throw e
} catch (e: Exception) {
@@ -117,24 +131,43 @@ class AddonLoader {
}
}
+ /**
+ * Load an extension from a package name (class is determined by type)
+ * @param context the context
+ * @param packageName the package name of the extension
+ * @param type the type of extension
+ * @return the loaded extension
+ */
fun loadFromPkgName(context: Context, packageName: String, type: AddonType): LoadResult? {
- return when (type) {
- AddonType.TORRENT -> loadExtension(
- context,
- packageName,
- TorrentAddonManager.TORRENT_CLASS,
- type
- )
+ return try {
+ when (type) {
+ AddonType.TORRENT -> loadExtension(
+ context,
+ packageName,
+ TorrentAddonManager.TORRENT_CLASS,
+ type
+ )
- AddonType.DOWNLOAD -> loadExtension(
- context,
- packageName,
- DownloadAddonManager.DOWNLOAD_CLASS,
- type
- )
+ AddonType.DOWNLOAD -> loadExtension(
+ context,
+ packageName,
+ DownloadAddonManager.DOWNLOAD_CLASS,
+ type
+ )
+ }
+ } catch (e: Exception) {
+ Logger.log("Error loading extension from package name: $packageName")
+ Logger.log(e)
+ null
}
}
+ /**
+ * Check if a package is an extension by comparing the package name
+ * @param type the type of extension
+ * @param pkgInfo the package info
+ * @return true if the package is an extension
+ */
private fun isPackageAnExtension(type: String, pkgInfo: PackageInfo): Boolean {
return pkgInfo.packageName.equals(type)
}
diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt
index e98966e6..168eb619 100644
--- a/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt
+++ b/app/src/main/java/ani/dantotsu/connections/anilist/AnilistQueries.kt
@@ -75,7 +75,7 @@ class AnilistQueries {
media.cameFromContinue = false
val query =
- """{Media(id:${media.id}){id favourites popularity episodes chapters mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role voiceActors { id name { first middle last full native userPreferred } image { large medium } languageV2 } node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}"""
+ """{Media(id:${media.id}){id favourites popularity episodes chapters mediaListEntry{id status score(format:POINT_100)progress private notes repeat customLists updatedAt startedAt{year month day}completedAt{year month day}}reviews(perPage:3, sort:SCORE_DESC){nodes{id mediaId mediaType summary body(asHtml:true) rating ratingAmount userRating score private siteUrl createdAt updatedAt user{id name bannerImage avatar{medium large}}}}isFavourite siteUrl idMal nextAiringEpisode{episode airingAt}source countryOfOrigin format duration season seasonYear startDate{year month day}endDate{year month day}genres studios(isMain:true){nodes{id name siteUrl}}description trailer{site id}synonyms tags{name rank isMediaSpoiler}characters(sort:[ROLE,FAVOURITES_DESC],perPage:25,page:1){edges{role voiceActors { id name { first middle last full native userPreferred } image { large medium } languageV2 } node{id image{medium}name{userPreferred}isFavourite}}}relations{edges{relationType(version:2)node{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}popularity meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}staffPreview:staff(perPage:8,sort:[RELEVANCE,ID]){edges{role node{id image{large medium}name{userPreferred}}}}recommendations(sort:RATING_DESC){nodes{mediaRecommendation{id idMal mediaListEntry{progress private score(format:POINT_100)status}episodes chapters nextAiringEpisode{episode}meanScore isAdult isFavourite format title{english romaji userPreferred}type status(version:2)bannerImage coverImage{large}}}}externalLinks{url site}}Page(page:1){pageInfo{total perPage currentPage lastPage hasNextPage}mediaList(isFollowing:true,sort:[STATUS],mediaId:${media.id}){id status score(format: POINT_100) progress progressVolumes user{id name avatar{large medium}}}}}"""
runBlocking {
val anilist = async {
var response = executeQuery(query, force = true)
@@ -211,6 +211,9 @@ class AnilistQueries {
}
}
}
+ if (fetchedMedia.reviews?.nodes != null){
+ media.review = fetchedMedia.reviews!!.nodes as ArrayList
+ }
if (user?.mediaList?.isNotEmpty() == true) {
media.users = user.mediaList?.mapNotNull {
it.user?.let { user ->
@@ -1505,7 +1508,7 @@ Page(page:$page,perPage:50) {
return author
}
- suspend fun getReviews(mediaId: Int, page: Int = 1, sort: String = "UPDATED_AT_DESC"): Query.ReviewsResponse? {
+ suspend fun getReviews(mediaId: Int, page: Int = 1, sort: String = "SCORE_DESC"): Query.ReviewsResponse? {
return executeQuery(
"""{Page(page:$page,perPage:10){pageInfo{currentPage,hasNextPage,total}reviews(mediaId:$mediaId,sort:$sort){id,mediaId,mediaType,summary,body(asHtml:true)rating,ratingAmount,userRating,score,private,siteUrl,createdAt,updatedAt,user{id,name,bannerImage avatar{medium,large}}}}}""",
force = true
diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Media.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Media.kt
index b7a5134b..23c79045 100644
--- a/app/src/main/java/ani/dantotsu/connections/anilist/api/Media.kt
+++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Media.kt
@@ -152,7 +152,7 @@ data class Media(
@SerialName("mediaListEntry") var mediaListEntry: MediaList?,
// User reviews of the media
- // @SerialName("reviews") var reviews: ReviewConnection?,
+ @SerialName("reviews") var reviews: ReviewConnection?,
// User recommendations for similar media
@SerialName("recommendations") var recommendations: RecommendationConnection?,
@@ -537,4 +537,9 @@ data class MediaListGroup(
@SerialName("isSplitCompletedList") var isSplitCompletedList: Boolean?,
@SerialName("status") var status: MediaListStatus?,
-) : java.io.Serializable
\ No newline at end of file
+) : java.io.Serializable
+
+@Serializable
+data class ReviewConnection(
+ @SerialName("nodes") var nodes: List?,
+)
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt b/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt
index e4338a37..ac404203 100644
--- a/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt
+++ b/app/src/main/java/ani/dantotsu/connections/anilist/api/Notification.kt
@@ -26,7 +26,8 @@ enum class NotificationType(val value: String) {
//custom
COMMENT_REPLY("COMMENT_REPLY"),
COMMENT_WARNING("COMMENT_WARNING"),
- DANTOTSU_UPDATE("DANTOTSU_UPDATE");
+ DANTOTSU_UPDATE("DANTOTSU_UPDATE"),
+ SUBSCRIPTION("SUBSCRIPTION");
fun toFormattedString(): String {
return this.value.replace("_", " ").lowercase(Locale.ROOT)
diff --git a/app/src/main/java/ani/dantotsu/connections/github/Contributors.kt b/app/src/main/java/ani/dantotsu/connections/github/Contributors.kt
index b8863310..337e4c49 100644
--- a/app/src/main/java/ani/dantotsu/connections/github/Contributors.kt
+++ b/app/src/main/java/ani/dantotsu/connections/github/Contributors.kt
@@ -84,15 +84,15 @@ class Contributors {
"https://anilist.co/user/6244220"
),
Developer(
- "Zaidsenior",
+ "Ziadsenior",
"https://s4.anilist.co/file/anilistcdn/user/avatar/large/b6049773-8cjYeUOFUguv.jpg",
- "Comment Moderator",
+ "Comment Moderator and Arabic Translator",
"https://anilist.co/user/6049773"
),
Developer(
"hastsu",
"https://cdn.discordapp.com/avatars/602422545077108749/20b4a6efa4314550e4ed51cdbe4fef3d.webp?size=160",
- "Comment Moderator",
+ "Comment Moderator and Arabic Translator",
"https://anilist.co/user/6183359"
),
)
diff --git a/app/src/main/java/ani/dantotsu/home/status/CircleView.kt b/app/src/main/java/ani/dantotsu/home/status/CircleView.kt
index 2a7c02d9..63986d83 100644
--- a/app/src/main/java/ani/dantotsu/home/status/CircleView.kt
+++ b/app/src/main/java/ani/dantotsu/home/status/CircleView.kt
@@ -12,13 +12,14 @@ import ani.dantotsu.getThemeColor
class CircleView(context: Context, attrs: AttributeSet?) : View(context, attrs) {
private var parts: Int = 3
- private var gapAngle: Float = 9f
+ private var gapAngle: Float = 12f
private val path = Path()
private var isUser = false
private var booleanList = listOf()
private val paint: Paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
style = Paint.Style.STROKE
strokeWidth = 6f
+ strokeCap = Paint.Cap.ROUND
}
@SuppressLint("DrawAllocation")
diff --git a/app/src/main/java/ani/dantotsu/home/status/Stories.kt b/app/src/main/java/ani/dantotsu/home/status/Stories.kt
index f0d3cf2b..7f2c60ec 100644
--- a/app/src/main/java/ani/dantotsu/home/status/Stories.kt
+++ b/app/src/main/java/ani/dantotsu/home/status/Stories.kt
@@ -42,6 +42,7 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.Calendar
import java.util.Locale
+import kotlin.math.abs
class Stories @JvmOverloads constructor(
@@ -264,29 +265,44 @@ class Stories @JvmOverloads constructor(
private var startClickTime = 0L
+ private var startX = 0f
+ private var startY = 0f
+ private var isLongPress = false
+ private val swipeThreshold = 100
override fun onTouch(view: View?, event: MotionEvent?): Boolean {
val maxClickDuration = 200
when (event?.action) {
MotionEvent.ACTION_DOWN -> {
+ startX = event.x
+ startY = event.y
startClickTime = Calendar.getInstance().timeInMillis
pause()
+ isLongPress = false
+ }
+
+ MotionEvent.ACTION_MOVE -> {
+ val deltaX = event.x - startX
+ val deltaY = event.y - startY
+ if (!isLongPress && (abs(deltaX) > swipeThreshold || abs(deltaY) > swipeThreshold)) {
+ isLongPress = true
+ }
}
MotionEvent.ACTION_UP -> {
val clickDuration = Calendar.getInstance().timeInMillis - startClickTime
- if (clickDuration < maxClickDuration) {
- //click occurred
- view?.let {
- if (it.id == R.id.leftTouchPanel) {
- leftPanelTouch()
- } else if (it.id == R.id.rightTouchPanel) {
- rightPanelTouch()
- }
+ if (clickDuration < maxClickDuration && !isLongPress) {
+ when (view?.id) {
+ R.id.leftTouchPanel -> leftPanelTouch()
+ R.id.rightTouchPanel -> rightPanelTouch()
}
} else {
- //hold click occurred
resume()
}
+ val deltaX = event.x - startX
+ if (abs(deltaX) > swipeThreshold) {
+ if (deltaX > 0) onStoriesPrevious()
+ else onStoriesCompleted()
+ }
}
}
return true
@@ -359,16 +375,14 @@ class Stories @JvmOverloads constructor(
)
}
fun visible(isList: Boolean) {
- val visible = if (isList) View.VISIBLE else View.GONE
- val gone = if (isList) View.GONE else View.VISIBLE
- binding.textActivity.visibility = gone
- binding.textActivityContainer.visibility = gone
- binding.infoText.visibility = visible
- binding.coverImage.visibility = visible
+ binding.textActivity.isVisible = !isList
+ binding.textActivityContainer.isVisible = !isList
+ binding.infoText.isVisible = isList
+ binding.coverImage.isVisible = isList
binding.infoText.visibility = if (isList) View.VISIBLE else View.INVISIBLE
binding.infoText.text = ""
- binding.contentImageViewKen.visibility = visible
- binding.contentImageView.visibility = visible
+ binding.contentImageViewKen.isVisible = isList
+ binding.contentImageView.isVisible = isList
}
when (story.typename) {
@@ -383,16 +397,15 @@ class Stories @JvmOverloads constructor(
}
}
} ${story.progress ?: story.media?.title?.userPreferred} " +
- if (
- story.status?.contains("completed") == false &&
- !story.status.contains("plans") &&
- !story.status.contains("repeating")
- )
- {
- "of ${story.media?.title?.userPreferred}"
- } else {
- ""
- }
+ if (
+ story.status?.contains("completed") == false &&
+ !story.status.contains("plans") &&
+ !story.status.contains("repeating")
+ ) {
+ "of ${story.media?.title?.userPreferred}"
+ } else {
+ ""
+ }
binding.infoText.text = text
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
blurImage(
@@ -404,18 +417,16 @@ class Stories @JvmOverloads constructor(
ContextCompat.startActivity(
context,
Intent(context, MediaDetailsActivity::class.java).putExtra(
- "mediaId",
- story.media?.id
- ),
+ "mediaId",
+ story.media?.id
+ ),
ActivityOptionsCompat.makeSceneTransitionAnimation(
activity,
binding.coverImage,
ViewCompat.getTransitionName(binding.coverImage)!!
).toBundle()
)
-
}
-
}
"TextActivity" -> {
@@ -445,11 +456,13 @@ class Stories @JvmOverloads constructor(
val likeColor = ContextCompat.getColor(context, R.color.yt_red)
val notLikeColor = ContextCompat.getColor(context, R.color.bg_opp)
binding.activityRepliesContainer.setOnClickListener {
- RepliesBottomDialog.newInstance(story.id).show(activity.supportFragmentManager, "replies")
+ RepliesBottomDialog.newInstance(story.id)
+ .show(activity.supportFragmentManager, "replies")
}
+ binding.activityLike.setColorFilter(if (story.isLiked == true) likeColor else notLikeColor)
binding.replyCount.text = story.replyCount.toString()
binding.activityLikeCount.text = story.likeCount.toString()
- binding.activityLike.setColorFilter(if (story.isLiked == true) likeColor else notLikeColor)
+ binding.activityReplies.setColorFilter(ContextCompat.getColor(context, R.color.bg_opp))
binding.activityLikeContainer.setOnClickListener {
like()
}
diff --git a/app/src/main/java/ani/dantotsu/media/Media.kt b/app/src/main/java/ani/dantotsu/media/Media.kt
index 92847073..a935dd4a 100644
--- a/app/src/main/java/ani/dantotsu/media/Media.kt
+++ b/app/src/main/java/ani/dantotsu/media/Media.kt
@@ -5,6 +5,7 @@ import ani.dantotsu.connections.anilist.api.FuzzyDate
import ani.dantotsu.connections.anilist.api.MediaEdge
import ani.dantotsu.connections.anilist.api.MediaList
import ani.dantotsu.connections.anilist.api.MediaType
+import ani.dantotsu.connections.anilist.api.Query
import ani.dantotsu.media.anime.Anime
import ani.dantotsu.media.manga.Manga
import ani.dantotsu.profile.User
@@ -62,6 +63,7 @@ data class Media(
var timeUntilAiring: Long? = null,
var characters: ArrayList? = null,
+ var review: ArrayList? = null,
var staff: ArrayList? = null,
var prequel: Media? = null,
var sequel: Media? = null,
diff --git a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt
index 671c96ce..6cafbc05 100644
--- a/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt
+++ b/app/src/main/java/ani/dantotsu/media/MediaInfoFragment.kt
@@ -34,7 +34,6 @@ import ani.dantotsu.databinding.ItemChipBinding
import ani.dantotsu.databinding.ItemQuelsBinding
import ani.dantotsu.databinding.ItemTitleChipgroupBinding
import ani.dantotsu.databinding.ItemTitleRecyclerBinding
-import ani.dantotsu.databinding.ItemTitleSearchBinding
import ani.dantotsu.databinding.ItemTitleTextBinding
import ani.dantotsu.databinding.ItemTitleTrailerBinding
import ani.dantotsu.displayTimer
@@ -46,6 +45,7 @@ import ani.dantotsu.px
import ani.dantotsu.setSafeOnClickListener
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
+import com.xwray.groupie.GroupieAdapter
import io.noties.markwon.Markwon
import io.noties.markwon.SoftBreakAddsNewLinePlugin
import kotlinx.coroutines.Dispatchers
@@ -81,7 +81,8 @@ class MediaInfoFragment : Fragment() {
@SuppressLint("SetJavaScriptEnabled")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
val model: MediaDetailsViewModel by activityViewModels()
- val offline: Boolean = PrefManager.getVal(PrefName.OfflineMode) || !isOnline(requireContext())
+ val offline: Boolean =
+ PrefManager.getVal(PrefName.OfflineMode) || !isOnline(requireContext())
binding.mediaInfoProgressBar.isGone = loaded
binding.mediaInfoContainer.isVisible = loaded
binding.mediaInfoContainer.updateLayoutParams { bottomMargin += 128f.px + navBarHeight }
@@ -254,7 +255,8 @@ class MediaInfoFragment : Fragment() {
if (!media.users.isNullOrEmpty() && !offline) {
val users: ArrayList = media.users ?: arrayListOf()
if (Anilist.token != null && media.userStatus != null) {
- users.add(0,
+ users.add(
+ 0,
User(
id = Anilist.userid!!,
name = getString(R.string.you),
@@ -263,7 +265,8 @@ class MediaInfoFragment : Fragment() {
status = media.userStatus,
score = media.userScore.toFloat(),
progress = media.userProgress,
- totalEpisodes = media.anime?.totalEpisodes ?: media.manga?.totalChapters,
+ totalEpisodes = media.anime?.totalEpisodes
+ ?: media.manga?.totalChapters,
nextAiringEpisode = media.anime?.nextAiringEpisode
)
)
@@ -519,22 +522,41 @@ class MediaInfoFragment : Fragment() {
}
}
- ItemTitleSearchBinding.inflate(
- LayoutInflater.from(context),
- parent,
- false
- ).apply {
-
- titleSearchImage.loadImage(media.banner ?: media.cover)
- titleSearchText.text =
- getString(R.string.reviews)
- titleSearchCard.setSafeOnClickListener {
- val query = Intent(requireContext(), ReviewActivity::class.java)
- .putExtra("mediaId", media.id)
- ContextCompat.startActivity(requireContext(), query, null)
+ if (!media.review.isNullOrEmpty()) {
+ ItemTitleRecyclerBinding.inflate(
+ LayoutInflater.from(context),
+ parent,
+ false
+ ).apply {
+ fun onUserClick(userId: Int) {
+ val review = media.review!!.find { i -> i.id == userId }
+ if (review != null) {
+ startActivity(
+ Intent(requireContext(), ReviewViewActivity::class.java)
+ .putExtra("review", review)
+ )
+ }
+ }
+ val adapter = GroupieAdapter()
+ media.review!!.forEach {
+ adapter.add(ReviewAdapter(it, ::onUserClick))
+ }
+ itemTitle.setText(R.string.reviews)
+ itemRecycler.adapter = adapter
+ itemRecycler.layoutManager = LinearLayoutManager(
+ requireContext(),
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+ itemMore.visibility = View.VISIBLE
+ itemMore.setSafeOnClickListener {
+ startActivity(
+ Intent(requireContext(), ReviewActivity::class.java)
+ .putExtra("mediaId", media.id)
+ )
+ }
+ parent.addView(root)
}
-
- parent.addView(root)
}
ItemTitleRecyclerBinding.inflate(
diff --git a/app/src/main/java/ani/dantotsu/media/ReviewActivity.kt b/app/src/main/java/ani/dantotsu/media/ReviewActivity.kt
index b4631db4..caefe07b 100644
--- a/app/src/main/java/ani/dantotsu/media/ReviewActivity.kt
+++ b/app/src/main/java/ani/dantotsu/media/ReviewActivity.kt
@@ -77,7 +77,7 @@ class ReviewActivity : AppCompatActivity() {
binding.listBack.setOnClickListener { onBackPressedDispatcher.onBackPressed() }
lifecycleScope.launch(Dispatchers.IO) {
- val response = Anilist.query.getReviews(mediaId)
+ val response = Anilist.query.getReviews(mediaId)?.data?.page
withContext(Dispatchers.Main) {
binding.listProgressBar.visibility = View.GONE
binding.listRecyclerView.setOnTouchListener { _, event ->
@@ -94,9 +94,9 @@ class ReviewActivity : AppCompatActivity() {
}
false
}
- currentPage = response?.data?.page?.pageInfo?.currentPage ?: 1
- hasNextPage = response?.data?.page?.pageInfo?.hasNextPage ?: false
- response?.data?.page?.reviews?.let {
+ currentPage = response?.pageInfo?.currentPage ?: 1
+ hasNextPage = response?.pageInfo?.hasNextPage ?: false
+ response?.reviews?.let {
reviews.addAll(it)
fillList()
}
@@ -122,29 +122,9 @@ class ReviewActivity : AppCompatActivity() {
private fun fillList() {
adapter.clear()
reviews.forEach {
- val username = it.user?.name ?: "Unknown"
- val name = SpannableString(username + " - " + it.score)
- //change the size of the score
- name.setSpan(
- android.text.style.RelativeSizeSpan(0.9f),
- 0,
- name.length,
- android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
- //give the text an underline
- name.setSpan(
- android.text.style.UnderlineSpan(),
- username.length + 3,
- name.length,
- android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE
- )
adapter.add(
- FollowerItem(
- it.id,
- name,
- it.user?.avatar?.medium,
- it.user?.bannerImage,
- it.summary,
+ ReviewAdapter(
+ it,
this::onUserClick
)
)
diff --git a/app/src/main/java/ani/dantotsu/media/ReviewAdapter.kt b/app/src/main/java/ani/dantotsu/media/ReviewAdapter.kt
new file mode 100644
index 00000000..e88238c7
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/media/ReviewAdapter.kt
@@ -0,0 +1,125 @@
+package ani.dantotsu.media
+
+
+import android.view.View
+import ani.dantotsu.R
+import ani.dantotsu.connections.anilist.Anilist
+import ani.dantotsu.connections.anilist.api.Query
+import ani.dantotsu.databinding.ItemReviewsBinding
+import ani.dantotsu.loadImage
+import ani.dantotsu.profile.activity.ActivityItemBuilder
+import ani.dantotsu.toast
+import com.xwray.groupie.viewbinding.BindableItem
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class ReviewAdapter(
+ private var review: Query.Review,
+ val clickCallback: (Int) -> Unit
+) : BindableItem() {
+ private lateinit var binding: ItemReviewsBinding
+
+ override fun bind(viewBinding: ItemReviewsBinding, position: Int) {
+ binding = viewBinding
+ binding.reviewUserName.text = review.user?.name
+ binding.reviewUserAvatar.loadImage(review.user?.avatar?.medium)
+ binding.reviewText.text = review.summary
+ binding.reviewPostTime.text = ActivityItemBuilder.getDateTime(review.createdAt)
+ val text = "[${review.score/ 10.0f}]"
+ binding.reviewTag.text = text
+ binding.root.setOnClickListener { clickCallback(review.id) }
+ userVote(review.userRating)
+ enableVote()
+ binding.reviewTotalVotes.text = review.rating.toString()
+ }
+
+ override fun getLayout(): Int {
+ return R.layout.item_reviews
+ }
+
+ override fun initializeViewBinding(view: View): ItemReviewsBinding {
+ return ItemReviewsBinding.bind(view)
+ }
+ private fun userVote(type: String) {
+ when (type) {
+ "NO_VOTE" -> {
+ binding.reviewUpVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
+ binding.reviewDownVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
+ binding.reviewUpVote.alpha = 0.6f
+ binding.reviewDownVote.alpha = 0.6f
+ }
+
+ "UP_VOTE" -> {
+ binding.reviewUpVote.setImageResource(R.drawable.ic_round_upvote_active_24)
+ binding.reviewDownVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
+ binding.reviewUpVote.alpha = 1f
+ binding.reviewDownVote.alpha = 0.6f
+ }
+
+ "DOWN_VOTE" -> {
+ binding.reviewUpVote.setImageResource(R.drawable.ic_round_upvote_inactive_24)
+ binding.reviewDownVote.setImageResource(R.drawable.ic_round_upvote_active_24)
+ binding.reviewDownVote.alpha = 1f
+ binding.reviewUpVote.alpha = 0.6f
+ }
+ }
+ }
+
+ private fun rateReview(rating: String) {
+ disableVote()
+ val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
+ scope.launch {
+ val result = Anilist.mutation.rateReview(review.id, rating)
+ if (result != null) {
+ withContext(Dispatchers.Main) {
+ val res = result.data.rateReview
+ review.rating = res.rating
+ review.ratingAmount = res.ratingAmount
+ review.userRating = res.userRating
+ userVote(review.userRating)
+ binding.reviewTotalVotes.text = review.rating.toString()
+ userVote(review.userRating)
+ enableVote()
+ }
+ } else {
+ withContext(Dispatchers.Main) {
+ toast(
+ binding.root.context.getString(R.string.error_message, "response is null")
+ )
+ enableVote()
+ }
+ }
+ }
+ }
+
+ private fun disableVote() {
+ binding.reviewUpVote.setOnClickListener(null)
+ binding.reviewDownVote.setOnClickListener(null)
+ binding.reviewUpVote.isEnabled = false
+ binding.reviewDownVote.isEnabled = false
+ }
+
+ private fun enableVote() {
+ binding.reviewUpVote.setOnClickListener {
+ if (review.userRating == "UP_VOTE") {
+ rateReview("NO_VOTE")
+ } else {
+ rateReview("UP_VOTE")
+ }
+ disableVote()
+ }
+ binding.reviewDownVote.setOnClickListener {
+ if (review.userRating == "DOWN_VOTE") {
+ rateReview("NO_VOTE")
+ } else {
+ rateReview("DOWN_VOTE")
+ }
+ disableVote()
+ }
+ binding.reviewUpVote.isEnabled = true
+ binding.reviewDownVote.isEnabled = true
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationTask.kt b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationTask.kt
index eb7837d3..97cbcc0a 100644
--- a/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationTask.kt
+++ b/app/src/main/java/ani/dantotsu/notifications/comment/CommentNotificationTask.kt
@@ -188,7 +188,7 @@ class CommentNotificationTask : Task {
null
) ?: listOf()
val newStore = notificationStore.toMutableList()
- if (newStore.size > 10) {
+ if (newStore.size > 30) {
newStore.remove(newStore.minByOrNull { it.time })
}
if (newStore.any { it.content == notification.content }) {
diff --git a/app/src/main/java/ani/dantotsu/notifications/comment/CommentStore.kt b/app/src/main/java/ani/dantotsu/notifications/comment/CommentStore.kt
index 6024aa71..e9381d45 100644
--- a/app/src/main/java/ani/dantotsu/notifications/comment/CommentStore.kt
+++ b/app/src/main/java/ani/dantotsu/notifications/comment/CommentStore.kt
@@ -13,8 +13,6 @@ data class CommentStore(
val time: Long = System.currentTimeMillis(),
) : java.io.Serializable {
companion object {
-
- @Suppress("INAPPROPRIATE_CONST_NAME")
private const val serialVersionUID = 2L
}
}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationTask.kt b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationTask.kt
index 83345013..7ea81ae8 100644
--- a/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationTask.kt
+++ b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionNotificationTask.kt
@@ -118,6 +118,15 @@ class SubscriptionNotificationTask : Task {
if (ep != null) ep.number + " " + context.getString(R.string.just_released) to null
else null
} ?: return@map
+ addSubscriptionToStore(
+ SubscriptionStore(
+ media.name,
+ text.first,
+ media.id
+ )
+ )
+ PrefManager.setVal(PrefName.UnreadCommentNotifications,
+ PrefManager.getVal(PrefName.UnreadCommentNotifications) + 1)
val notification = createNotification(
context.applicationContext,
media,
@@ -219,4 +228,17 @@ class SubscriptionNotificationTask : Task {
}
)
}
+
+ private fun addSubscriptionToStore(notification: SubscriptionStore) {
+ val notificationStore = PrefManager.getNullableVal>(
+ PrefName.SubscriptionNotificationStore,
+ null
+ ) ?: listOf()
+ val newStore = notificationStore.toMutableList()
+ if (newStore.size >= 100) {
+ newStore.remove(newStore.minByOrNull { it.time })
+ }
+ newStore.add(notification)
+ PrefManager.setVal(PrefName.SubscriptionNotificationStore, newStore)
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionStore.kt b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionStore.kt
new file mode 100644
index 00000000..dfb910f5
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/notifications/subscription/SubscriptionStore.kt
@@ -0,0 +1,16 @@
+package ani.dantotsu.notifications.subscription
+
+import kotlinx.serialization.Serializable
+
+@Serializable
+data class SubscriptionStore(
+ val title: String,
+ val content: String,
+ val mediaId: Int,
+ val type: String = "SUBSCRIPTION",
+ val time: Long = System.currentTimeMillis(),
+) : java.io.Serializable {
+ companion object {
+ private const val serialVersionUID = 1L
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt
index bacc5e22..df97b2ec 100644
--- a/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/AniyomiAdapter.kt
@@ -53,8 +53,11 @@ class DynamicAnimeParser(extension: AnimeExtension.Installed) : AnimeParser() {
override val name = extension.name
override val saveName = extension.name
- override val hostUrl = extension.sources.first().name
+ override val hostUrl =
+ (extension.sources.first() as? AnimeHttpSource)?.baseUrl ?: extension.sources.first().name
override val isNSFW = extension.isNsfw
+ override val icon = extension.icon
+
override var selectDub: Boolean
get() = getDub()
set(value) {
@@ -324,8 +327,10 @@ class DynamicMangaParser(extension: MangaExtension.Installed) : MangaParser() {
override val name = extension.name
override val saveName = extension.name
- override val hostUrl = extension.sources.first().name
+ override val hostUrl =
+ (extension.sources.first() as? HttpSource)?.baseUrl ?: extension.sources.first().name
override val isNSFW = extension.isNsfw
+ override val icon = extension.icon
override suspend fun loadChapters(
mangaLink: String,
diff --git a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt
index 2d83ab06..3ddfbb09 100644
--- a/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt
+++ b/app/src/main/java/ani/dantotsu/parsers/BaseParser.kt
@@ -1,17 +1,21 @@
package ani.dantotsu.parsers
+import android.graphics.drawable.Drawable
import ani.dantotsu.FileUrl
import ani.dantotsu.R
import ani.dantotsu.currContext
import ani.dantotsu.media.Media
+import ani.dantotsu.okHttpClient
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.util.Logger
import eu.kanade.tachiyomi.animesource.model.SAnime
import eu.kanade.tachiyomi.source.model.SManga
import me.xdrop.fuzzywuzzy.FuzzySearch
+import okhttp3.Request
import java.io.Serializable
import java.net.URLDecoder
import java.net.URLEncoder
+import kotlin.system.measureTimeMillis
abstract class BaseParser {
@@ -41,6 +45,11 @@ abstract class BaseParser {
* **/
open val language = "English"
+ /**
+ * Icon of the site, can be null
+ */
+ open val icon: Drawable? = null
+
/**
* Search for Anime/Manga/Novel, returns a List of Responses
*
@@ -133,10 +142,39 @@ abstract class BaseParser {
return response
}
+ /**
+ * ping the site to check if it's working or not.
+ * @return Triple : First Int is the status code, Second Int is the response time in milliseconds, Third String is the response message.
+ */
+ fun ping(): Triple {
+ val client = okHttpClient
+ var statusCode = 0
+ var responseTime: Int? = null
+ var responseMessage = ""
+ try {
+ val request = Request.Builder()
+ .url(hostUrl)
+ .build()
+ responseTime = measureTimeMillis {
+ client.newCall(request).execute().use { response ->
+ statusCode = response.code
+ responseMessage = response.message
+ }
+ }.toInt()
+ } catch (e: Exception) {
+ Logger.log("Failed to ping $name")
+ statusCode = -1
+ responseMessage = if (e.message.isNullOrEmpty()) "None" else e.message!!
+ Logger.log(e)
+ }
+ return Triple(statusCode, responseTime, responseMessage)
+ }
/**
* Used to get an existing Search Response which was selected by the user.
- * **/
+ * @param mediaId : The mediaId of the Media object.
+ * @return ShowResponse? : The ShowResponse object if found, else null.
+ */
open suspend fun loadSavedShowResponse(mediaId: Int): ShowResponse? {
checkIfVariablesAreEmpty()
return PrefManager.getNullableCustomVal(
@@ -148,7 +186,10 @@ abstract class BaseParser {
/**
* Used to save Shows Response using `saveName`.
- * **/
+ * @param mediaId : The mediaId of the Media object.
+ * @param response : The ShowResponse object to save.
+ * @param selected : Boolean : If the ShowResponse was selected by the user or not.
+ */
open fun saveShowResponse(mediaId: Int, response: ShowResponse?, selected: Boolean = false) {
if (response != null) {
checkIfVariablesAreEmpty()
diff --git a/app/src/main/java/ani/dantotsu/parsers/ExtensionSelectItem.kt b/app/src/main/java/ani/dantotsu/parsers/ExtensionSelectItem.kt
new file mode 100644
index 00000000..8c9c20d5
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/parsers/ExtensionSelectItem.kt
@@ -0,0 +1,38 @@
+package ani.dantotsu.parsers
+
+import android.graphics.drawable.Drawable
+import android.view.View
+import ani.dantotsu.R
+import ani.dantotsu.databinding.ItemExtensionSelectBinding
+import com.xwray.groupie.viewbinding.BindableItem
+
+class ExtensionSelectItem(
+ private val name: String,
+ private val image: Drawable?,
+ private var isSelected: Boolean,
+ val selectCallback: (String, Boolean) -> Unit
+) : BindableItem() {
+ private lateinit var binding: ItemExtensionSelectBinding
+
+ override fun bind(viewBinding: ItemExtensionSelectBinding, position: Int) {
+ binding = viewBinding
+ binding.extensionNameTextView.text = name
+ image?.let {
+ binding.extensionIconImageView.setImageDrawable(it)
+ }
+ binding.extensionCheckBox.setOnCheckedChangeListener(null)
+ binding.extensionCheckBox.isChecked = isSelected
+ binding.extensionCheckBox.setOnCheckedChangeListener { _, isChecked ->
+ isSelected = isChecked
+ selectCallback(name, isChecked)
+ }
+ }
+
+ override fun getLayout(): Int {
+ return R.layout.item_extension_select
+ }
+
+ override fun initializeViewBinding(view: View): ItemExtensionSelectBinding {
+ return ItemExtensionSelectBinding.bind(view)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/parsers/ExtensionTestItem.kt b/app/src/main/java/ani/dantotsu/parsers/ExtensionTestItem.kt
new file mode 100644
index 00000000..c87bf268
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/parsers/ExtensionTestItem.kt
@@ -0,0 +1,367 @@
+package ani.dantotsu.parsers
+
+import android.content.Context
+import android.view.View
+import androidx.core.view.isVisible
+import ani.dantotsu.R
+import ani.dantotsu.databinding.ItemExtensionTestBinding
+import ani.dantotsu.getThemeColor
+import com.xwray.groupie.viewbinding.BindableItem
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.withContext
+
+class ExtensionTestItem(
+ private var extensionType: String,
+ private var testType: String,
+ private var extension: BaseParser,
+ private var searchString: String = "Chainsaw Man"
+) : BindableItem() {
+ private lateinit var binding: ItemExtensionTestBinding
+ private lateinit var context: Context
+ private var job: Job? = null
+ private var isRunning = false
+ private var pingResult: Triple? = null
+ private var searchResultSize: Int? = null
+ private var episodeResultSize: Int? = null
+ private var serverResultSize: Int? = null
+
+ override fun bind(viewBinding: ItemExtensionTestBinding, position: Int) {
+ binding = viewBinding
+ context = binding.root.context
+ binding.extensionIconImageView.setImageDrawable(extension.icon)
+ binding.extensionNameTextView.text = extension.name
+ binding.extensionLoading.isVisible = isRunning
+ hideAllResults()
+
+ pingResult()
+ searchResult()
+ episodeResult()
+ serverResult()
+ }
+
+ override fun getLayout(): Int {
+ return R.layout.item_extension_test
+ }
+
+ override fun initializeViewBinding(view: View): ItemExtensionTestBinding {
+ return ItemExtensionTestBinding.bind(view)
+ }
+
+ private fun hideAllResults() {
+ if (::binding.isInitialized.not()) return
+ binding.searchResultText.isVisible = false
+ binding.episodeResultText.isVisible = false
+ binding.serverResultText.isVisible = false
+ }
+
+ fun cancelJob() {
+ job?.cancel()
+ job = null
+ binding.extensionLoading.isVisible = false
+ }
+
+ fun startTest() {
+ pingResult = null
+ searchResultSize = null
+ episodeResultSize = null
+ serverResultSize = null
+ isRunning = true
+ hideAllResults()
+ job?.cancel()
+ job = Job()
+ CoroutineScope(Dispatchers.IO + job!!).launch {
+ when (extensionType) {
+ "anime" -> {
+ val extension = extension as AnimeParser
+ runAnimeTest(extension)
+ }
+
+ "manga" -> {
+ val extension = extension as MangaParser
+ runMangaTest(extension)
+ }
+
+ "novel" -> {
+ val extension = extension as NovelParser
+ runNovelTest(extension)
+ }
+ }
+ }
+ }
+
+ private suspend fun runAnimeTest(extension: AnimeParser) {
+ pingResult = extension.ping()
+ withContext(Dispatchers.Main) {
+ pingResult()
+ }
+ if (testType == "ping") {
+ done()
+ return
+ }
+ val searchResult = extension.search(searchString)
+ searchResultSize = searchResult.size
+ withContext(Dispatchers.Main) {
+ searchResult()
+ }
+ if (searchResultSize == 0 || testType == "basic") {
+ done()
+ return
+ }
+ val episodeResult = extension.loadEpisodes("", null, searchResult.first().sAnime!!)
+ episodeResultSize = episodeResult.size
+ withContext(Dispatchers.Main) {
+ episodeResult()
+ }
+ if (episodeResultSize == 0) {
+ done()
+ return
+ }
+ val serverResult = extension.loadVideoServers("", null, episodeResult.first().sEpisode!!)
+ serverResultSize = serverResult.size
+ withContext(Dispatchers.Main) {
+ serverResult()
+ }
+
+ done()
+ }
+
+ private suspend fun runMangaTest(extension: MangaParser) {
+ pingResult = extension.ping()
+ withContext(Dispatchers.Main) {
+ pingResult()
+ }
+ if (testType == "ping") {
+ done()
+ return
+ }
+ val searchResult = extension.search(searchString)
+ searchResultSize = searchResult.size
+ withContext(Dispatchers.Main) {
+ searchResult()
+ }
+ if (searchResultSize == 0 || testType == "basic") {
+ done()
+ return
+ }
+ val chapterResult = extension.loadChapters("", null, searchResult.first().sManga!!)
+ episodeResultSize = chapterResult.size
+ withContext(Dispatchers.Main) {
+ episodeResult()
+ }
+ if (episodeResultSize == 0) {
+ done()
+ return
+ }
+ val serverResult = extension.loadImages("", chapterResult.first().sChapter)
+ serverResultSize = serverResult.size
+ withContext(Dispatchers.Main) {
+ serverResult()
+ }
+
+ withContext(Dispatchers.Main) {
+ if (::binding.isInitialized )
+ binding.extensionLoading.isVisible = false
+ isRunning = false
+ }
+ }
+
+ private suspend fun runNovelTest(extension: NovelParser) {
+ withContext(Dispatchers.Main) {
+ pingResult()
+ }
+ if (testType == "ping") {
+ done()
+ return
+ }
+ val searchResult = extension.search(searchString)
+ searchResultSize = searchResult.size
+ withContext(Dispatchers.Main) {
+ searchResult()
+ }
+ if (searchResultSize == 0 || testType == "basic") {
+ done()
+ return
+ }
+ val chapterResult = extension.loadBook(searchResult.first().link, null)
+ episodeResultSize = chapterResult.links.size
+ withContext(Dispatchers.Main) {
+ episodeResult()
+ serverResult()
+ }
+
+ withContext(Dispatchers.Main) {
+ if (::binding.isInitialized )
+ binding.extensionLoading.isVisible = false
+ isRunning = false
+ }
+ }
+
+ private fun done() {
+ if (::binding.isInitialized.not()) return
+ binding.extensionLoading.isVisible = false
+ isRunning = false
+ }
+
+ private fun pingResult() {
+ if (::binding.isInitialized.not()) return
+ if (extensionType == "novel") {
+ binding.pingResultText.isVisible = true
+ binding.pingResultText.text = context.getString(R.string.test_not_supported)
+ binding.pingResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_round_info_24, 0, 0, 0
+ )
+ return
+ }
+ if (pingResult == null) {
+ binding.pingResultText.isVisible = false
+ return
+ } else {
+ binding.pingResultText.isVisible = true
+ }
+ binding.pingResultText.setTextColor(
+ context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
+ )
+ val (code, time, message) = pingResult!!
+ if (code == 200) {
+ binding.pingResultText.text = context.getString(R.string.ping_success, time.toString())
+ binding.pingResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_circle_check, 0, 0, 0
+ )
+ return
+ }
+ binding.pingResultText.text =
+ context.getString(R.string.ping_error, code.toString(), message)
+ binding.pingResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_circle_cancel, 0, 0, 0
+ )
+ binding.pingResultText.setTextColor(
+ context.getThemeColor(com.google.android.material.R.attr.colorError)
+ )
+ }
+
+ private fun searchResult() {
+ if (::binding.isInitialized.not()) return
+ if (searchResultSize == null) {
+ binding.searchResultText.isVisible = false
+ return
+ }
+ binding.searchResultText.setTextColor(
+ context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
+ )
+ binding.searchResultText.isVisible = true
+ if (searchResultSize == 0) {
+ val text = context.getString(R.string.title_search_test,
+ context.getString(R.string.no_results_found))
+ binding.searchResultText.text = text
+ binding.searchResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_circle_cancel, 0, 0, 0
+ )
+ binding.searchResultText.setTextColor(
+ context.getThemeColor(com.google.android.material.R.attr.colorError)
+ )
+ return
+ }
+ val text = context.getString(R.string.title_search_test,
+ context.getString(R.string.results_found, searchResultSize.toString()))
+ binding.searchResultText.text = text
+ binding.searchResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_circle_check, 0, 0, 0
+ )
+ }
+
+ private fun episodeResult() {
+ if (::binding.isInitialized.not()) return
+ if (episodeResultSize == null) {
+ binding.episodeResultText.isVisible = false
+ return
+ }
+ binding.episodeResultText.setTextColor(
+ context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
+ )
+ binding.episodeResultText.isVisible = true
+ if (episodeResultSize == 0) {
+ val text = when(extensionType) {
+ "anime" -> context.getString(R.string.episode_search_test,
+ context.getString(R.string.no_results_found))
+ "manga" -> context.getString(R.string.chapter_search_test,
+ context.getString(R.string.no_results_found))
+ else -> context.getString(R.string.book_search_test,
+ context.getString(R.string.no_results_found))
+ }
+ binding.episodeResultText.text = text
+ binding.episodeResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_circle_cancel, 0, 0, 0
+ )
+ binding.episodeResultText.setTextColor(
+ context.getThemeColor(com.google.android.material.R.attr.colorError)
+ )
+ return
+ }
+ val text = when(extensionType) {
+ "anime" -> context.getString(R.string.episode_search_test,
+ context.getString(R.string.results_found, episodeResultSize.toString()))
+ "manga" -> context.getString(R.string.chapter_search_test,
+ context.getString(R.string.results_found, episodeResultSize.toString()))
+ else -> context.getString(R.string.book_search_test,
+ context.getString(R.string.results_found, episodeResultSize.toString()))
+ }
+ binding.episodeResultText.text = text
+ binding.episodeResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_circle_check, 0, 0, 0
+ )
+ }
+
+ private fun serverResult() {
+ if (::binding.isInitialized.not()) return
+ if (extensionType == "novel") {
+ binding.pingResultText.isVisible = true
+ binding.pingResultText.text = context.getString(R.string.test_not_supported)
+ binding.pingResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_round_info_24, 0, 0, 0
+ )
+ return
+ }
+ if (serverResultSize == null) {
+ binding.serverResultText.isVisible = false
+ return
+ }
+ binding.serverResultText.setTextColor(
+ context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
+ )
+ binding.serverResultText.isVisible = true
+ if (serverResultSize == 0) {
+ val text = when(extensionType) {
+ "anime" -> context.getString(R.string.video_search_test,
+ context.getString(R.string.no_results_found))
+ "manga" -> context.getString(R.string.image_search_test,
+ context.getString(R.string.no_results_found))
+ else -> context.getString(R.string.book_search_test,
+ context.getString(R.string.no_results_found))
+ }
+ binding.serverResultText.text = text
+ binding.serverResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_circle_cancel, 0, 0, 0
+ )
+ binding.serverResultText.setTextColor(
+ context.getThemeColor(com.google.android.material.R.attr.colorError)
+ )
+ return
+ }
+ val text = when(extensionType) {
+ "anime" -> context.getString(R.string.video_search_test,
+ context.getString(R.string.results_found, serverResultSize.toString()))
+ "manga" -> context.getString(R.string.image_search_test,
+ context.getString(R.string.results_found, serverResultSize.toString()))
+ else -> context.getString(R.string.book_search_test,
+ context.getString(R.string.results_found, serverResultSize.toString()))
+ }
+ binding.serverResultText.text = text
+ binding.serverResultText.setCompoundDrawablesWithIntrinsicBounds(
+ R.drawable.ic_circle_check, 0, 0, 0
+ )
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/parsers/ExtensionTestSettingsBottomDialog.kt b/app/src/main/java/ani/dantotsu/parsers/ExtensionTestSettingsBottomDialog.kt
new file mode 100644
index 00000000..fcedad3f
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/parsers/ExtensionTestSettingsBottomDialog.kt
@@ -0,0 +1,117 @@
+package ani.dantotsu.parsers
+
+import android.graphics.drawable.Drawable
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.recyclerview.widget.LinearLayoutManager
+import ani.dantotsu.BottomSheetDialogFragment
+import ani.dantotsu.databinding.BottomSheetExtensionTestSettingsBinding
+import ani.dantotsu.parsers.novel.NovelExtensionManager
+import com.xwray.groupie.GroupieAdapter
+import eu.kanade.tachiyomi.extension.anime.AnimeExtensionManager
+import eu.kanade.tachiyomi.extension.manga.MangaExtensionManager
+import uy.kohesive.injekt.Injekt
+import uy.kohesive.injekt.api.get
+
+class ExtensionTestSettingsBottomDialog : BottomSheetDialogFragment() {
+ private var _binding: BottomSheetExtensionTestSettingsBinding? = null
+ private val binding get() = _binding!!
+ private val adapter: GroupieAdapter = GroupieAdapter()
+ private val animeExtension: AnimeExtensionManager = Injekt.get()
+ private val mangaExtensions: MangaExtensionManager = Injekt.get()
+ private val novelExtensions: NovelExtensionManager = Injekt.get()
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View? {
+ _binding = BottomSheetExtensionTestSettingsBinding.inflate(inflater, container, false)
+ return _binding?.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ binding.extensionSelectionRecyclerView.adapter = adapter
+ binding.extensionSelectionRecyclerView.layoutManager = LinearLayoutManager(
+ context,
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+ binding.animeRadioButton.setOnCheckedChangeListener { _, b ->
+ if (b) {
+ extensionType = "anime"
+ extensionsToTest.clear()
+ setupAdapter()
+ }
+ }
+ binding.mangaRadioButton.setOnCheckedChangeListener { _, b ->
+ if (b) {
+ extensionType = "manga"
+ extensionsToTest.clear()
+ setupAdapter()
+ }
+ }
+ binding.novelsRadioButton.setOnCheckedChangeListener { _, b ->
+ if (b) {
+ extensionType = "novel"
+ extensionsToTest.clear()
+ setupAdapter()
+ }
+ }
+ binding.pingRadioButton.setOnCheckedChangeListener { _, b ->
+ if (b) {
+ testType = "ping"
+ }
+ }
+ binding.basicRadioButton.setOnCheckedChangeListener { _, b ->
+ if (b) {
+ testType = "basic"
+ }
+ }
+ binding.fullRadioButton.setOnCheckedChangeListener { _, b ->
+ if (b) {
+ testType = "full"
+ }
+ }
+ setupAdapter()
+ }
+
+ override fun onDestroyView() {
+ _binding = null
+ super.onDestroyView()
+ }
+
+ private fun setupAdapter() {
+ val namesAndUrls: Map = when (extensionType) {
+ "anime" -> animeExtension.installedExtensionsFlow.value.associate { it.name to it.icon }
+ "manga" -> mangaExtensions.installedExtensionsFlow.value.associate { it.name to it.icon }
+ "novel" -> novelExtensions.installedExtensionsFlow.value.associate { it.name to it.icon }
+ else -> emptyMap()
+ }
+ adapter.clear()
+ namesAndUrls.forEach { (name, icon) ->
+ val isSelected = extensionsToTest.contains(name)
+ adapter.add(ExtensionSelectItem(name, icon, isSelected, ::selectedCallback))
+ }
+ }
+
+ private fun selectedCallback(name: String, isSelected: Boolean) {
+ if (isSelected) {
+ extensionsToTest.add(name)
+ } else {
+ extensionsToTest.remove(name)
+ }
+ }
+
+ companion object {
+ fun newInstance(): ExtensionTestSettingsBottomDialog {
+ return ExtensionTestSettingsBottomDialog()
+ }
+
+ var extensionType = "anime"
+ var testType = "ping"
+ var extensionsToTest: MutableList = mutableListOf()
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/parsers/ParserTestActivity.kt b/app/src/main/java/ani/dantotsu/parsers/ParserTestActivity.kt
new file mode 100644
index 00000000..7be91fe2
--- /dev/null
+++ b/app/src/main/java/ani/dantotsu/parsers/ParserTestActivity.kt
@@ -0,0 +1,112 @@
+package ani.dantotsu.parsers
+
+import android.os.Bundle
+import android.view.ViewGroup
+import androidx.appcompat.app.AppCompatActivity
+import androidx.core.view.updateLayoutParams
+import androidx.recyclerview.widget.LinearLayoutManager
+import ani.dantotsu.R
+import ani.dantotsu.databinding.ActivityParserTestBinding
+import ani.dantotsu.initActivity
+import ani.dantotsu.navBarHeight
+import ani.dantotsu.statusBarHeight
+import ani.dantotsu.themes.ThemeManager
+import ani.dantotsu.toast
+import com.xwray.groupie.GroupieAdapter
+
+class ParserTestActivity : AppCompatActivity() {
+ private lateinit var binding: ActivityParserTestBinding
+ val adapter = GroupieAdapter()
+ val extensionsToTest: MutableList = mutableListOf()
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ ThemeManager(this).applyTheme()
+ initActivity(this)
+ binding = ActivityParserTestBinding.inflate(layoutInflater)
+ binding.toolbar.updateLayoutParams {
+ topMargin = statusBarHeight
+ }
+ binding.extensionResultsRecyclerView.updateLayoutParams {
+ bottomMargin = navBarHeight
+ }
+ setContentView(binding.root)
+
+ binding.extensionResultsRecyclerView.adapter = adapter
+ binding.extensionResultsRecyclerView.layoutManager = LinearLayoutManager(
+ this,
+ LinearLayoutManager.VERTICAL,
+ false
+ )
+ binding.backButton.setOnClickListener {
+ onBackPressedDispatcher.onBackPressed()
+ }
+ binding.optionsLayout.setOnClickListener {
+ ExtensionTestSettingsBottomDialog.newInstance()
+ .show(supportFragmentManager, "extension_test_settings")
+ }
+
+ binding.startButton.setOnClickListener {
+ if (ExtensionTestSettingsBottomDialog.extensionsToTest.isEmpty()) {
+ toast(R.string.no_extensions_selected)
+ return@setOnClickListener
+ }
+ extensionsToTest.forEach {
+ it.cancelJob()
+ }
+ extensionsToTest.clear()
+ adapter.clear()
+ when (ExtensionTestSettingsBottomDialog.extensionType) {
+ "anime" -> {
+ ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name ->
+ val extension =
+ AnimeSources.list.find { source -> source.name == name }?.get?.value
+ extension?.let {
+ extensionsToTest.add(
+ ExtensionTestItem(
+ "anime",
+ ExtensionTestSettingsBottomDialog.testType,
+ it
+ )
+ )
+ }
+ }
+ }
+ "manga" -> {
+ ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name ->
+ val extension =
+ MangaSources.list.find { source -> source.name == name }?.get?.value
+ extension?.let {
+ extensionsToTest.add(
+ ExtensionTestItem(
+ "manga",
+ ExtensionTestSettingsBottomDialog.testType,
+ it
+ )
+ )
+ }
+ }
+ }
+ "novel" -> {
+ ExtensionTestSettingsBottomDialog.extensionsToTest.forEach { name ->
+ val extension =
+ NovelSources.list.find { source -> source.name == name }?.get?.value
+ extension?.let {
+ extensionsToTest.add(
+ ExtensionTestItem(
+ "novel",
+ ExtensionTestSettingsBottomDialog.testType,
+ it
+ )
+ )
+ }
+ }
+ }
+ }
+ extensionsToTest.forEach {
+ adapter.add(it)
+ it.startTest()
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt
index 61840c32..749716ee 100644
--- a/app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt
+++ b/app/src/main/java/ani/dantotsu/profile/activity/ActivityItemBuilder.kt
@@ -92,6 +92,10 @@ class ActivityItemBuilder {
NotificationType.DANTOTSU_UPDATE -> {
notification.context ?: "You should not see this"
}
+
+ NotificationType.SUBSCRIPTION -> {
+ notification.context ?: "You should not see this"
+ }
}
}
diff --git a/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt
index ae27260b..30b0c525 100644
--- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt
+++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationActivity.kt
@@ -28,6 +28,7 @@ import ani.dantotsu.initActivity
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.navBarHeight
import ani.dantotsu.notifications.comment.CommentStore
+import ani.dantotsu.notifications.subscription.SubscriptionStore
import ani.dantotsu.profile.ProfileActivity
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
@@ -41,6 +42,8 @@ import kotlinx.coroutines.withContext
class NotificationActivity : AppCompatActivity() {
private lateinit var binding: ActivityFollowBinding
+ private lateinit var commentStore: List
+ private lateinit var subscriptionStore: List
private var adapter: GroupieAdapter = GroupieAdapter()
private var notificationList: List = emptyList()
val filters = ArrayList()
@@ -70,6 +73,15 @@ class NotificationActivity : AppCompatActivity() {
onBackPressedDispatcher.onBackPressed()
}
binding.listProgressBar.visibility = ViewGroup.VISIBLE
+ commentStore = PrefManager.getNullableVal>(
+ PrefName.CommentNotificationStore,
+ null
+ ) ?: listOf()
+ subscriptionStore = PrefManager.getNullableVal>(
+ PrefName.SubscriptionNotificationStore,
+ null
+ ) ?: listOf()
+
binding.followFilterButton.setOnClickListener {
val dialogView = LayoutInflater.from(currContext()).inflate(R.layout.custom_dialog_layout, null)
val checkboxContainer = dialogView.findViewById(R.id.checkboxContainer)
@@ -193,22 +205,39 @@ class NotificationActivity : AppCompatActivity() {
notifications
}.toMutableList()
}
- if (activityId == -1 && currentPage == 1) {
- val commentStore = PrefManager.getNullableVal>(
- PrefName.CommentNotificationStore,
- null
- ) ?: listOf()
+ if (activityId == -1) {
+ val furthestTime = newNotifications.minOfOrNull { it.createdAt } ?: 0
commentStore.forEach {
- val notification = Notification(
- it.type.toString(),
- System.currentTimeMillis().toInt(),
- commentId = it.commentId,
- notificationType = it.type.toString(),
- mediaId = it.mediaId,
- context = it.title + "\n" + it.content,
- createdAt = (it.time / 1000L).toInt(),
- )
- newNotifications += notification
+ if ((it.time > furthestTime * 1000L || !hasNextPage) && notificationList.none { notification ->
+ notification.commentId == it.commentId && notification.createdAt == (it.time / 1000L).toInt()
+ }) {
+ val notification = Notification(
+ it.type.toString(),
+ System.currentTimeMillis().toInt(),
+ commentId = it.commentId,
+ notificationType = it.type.toString(),
+ mediaId = it.mediaId,
+ context = it.title + "\n" + it.content,
+ createdAt = (it.time / 1000L).toInt(),
+ )
+ newNotifications += notification
+ }
+ }
+ subscriptionStore.forEach {
+ if ((it.time > furthestTime * 1000L || !hasNextPage) && notificationList.none { notification ->
+ notification.mediaId == it.mediaId && notification.createdAt == (it.time / 1000L).toInt()
+ }) {
+ val notification = Notification(
+ it.type,
+ System.currentTimeMillis().toInt(),
+ commentId = it.mediaId,
+ mediaId = it.mediaId,
+ notificationType = it.type,
+ context = it.content,
+ createdAt = (it.time / 1000L).toInt(),
+ )
+ newNotifications += notification
+ }
}
newNotifications.sortByDescending { it.createdAt }
}
diff --git a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt
index 139d3200..23224184 100644
--- a/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt
+++ b/app/src/main/java/ani/dantotsu/profile/activity/NotificationItem.kt
@@ -332,6 +332,20 @@ class NotificationItem(
NotificationType.DANTOTSU_UPDATE -> {
image(user = true)
}
+
+ NotificationType.SUBSCRIPTION -> {
+ image(user = true, commentNotification = true)
+ binding.notificationCoverUser.setOnClickListener {
+ clickCallback(
+ notification.mediaId ?: 0, null, NotificationClickType.MEDIA
+ )
+ }
+ binding.notificationBannerImage.setOnClickListener {
+ clickCallback(
+ notification.mediaId ?: 0, null, NotificationClickType.MEDIA
+ )
+ }
+ }
}
}
diff --git a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt
index 0125c3df..0fa65612 100644
--- a/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt
+++ b/app/src/main/java/ani/dantotsu/settings/ExtensionsActivity.kt
@@ -1,6 +1,7 @@
package ani.dantotsu.settings
import android.app.AlertDialog
+import android.content.Intent
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@@ -13,6 +14,7 @@ import android.view.inputmethod.EditorInfo
import android.widget.AutoCompleteTextView
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.viewpager2.adapter.FragmentStateAdapter
@@ -28,6 +30,7 @@ import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight
import ani.dantotsu.others.AndroidBug5497Workaround
import ani.dantotsu.others.LanguageMapper
+import ani.dantotsu.parsers.ParserTestActivity
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
@@ -70,6 +73,14 @@ class ExtensionsActivity : AppCompatActivity() {
bottomMargin = statusBarHeight + navBarHeight
}
+ binding.testButton.setOnClickListener {
+ ContextCompat.startActivity(
+ this,
+ Intent(this, ParserTestActivity::class.java),
+ null
+ )
+ }
+
val tabLayout = findViewById(R.id.tabLayout)
val viewPager = findViewById(R.id.viewPager)
viewPager.offscreenPageLimit = 1
diff --git a/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt b/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt
index 078d7b38..abf742ba 100644
--- a/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt
+++ b/app/src/main/java/ani/dantotsu/settings/SettingsExtensionsActivity.kt
@@ -1,6 +1,7 @@
package ani.dantotsu.settings
import android.app.AlertDialog
+import android.content.Intent
import android.os.Bundle
import android.view.HapticFeedbackConstants
import android.view.KeyEvent
@@ -9,6 +10,7 @@ import android.view.ViewGroup
import android.view.inputmethod.EditorInfo
import android.widget.EditText
import androidx.appcompat.app.AppCompatActivity
+import androidx.core.content.ContextCompat
import androidx.core.view.isVisible
import androidx.core.view.updateLayoutParams
import androidx.recyclerview.widget.LinearLayoutManager
@@ -20,6 +22,7 @@ import ani.dantotsu.databinding.ItemRepositoryBinding
import ani.dantotsu.initActivity
import ani.dantotsu.media.MediaType
import ani.dantotsu.navBarHeight
+import ani.dantotsu.parsers.ParserTestActivity
import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.statusBarHeight
@@ -232,6 +235,20 @@ class SettingsExtensionsActivity : AppCompatActivity() {
setExtensionOutput(it.attachView, MediaType.MANGA)
}
),
+ Settings(
+ type = 1,
+ name = getString(R.string.extension_test),
+ desc = getString(R.string.extension_test_desc),
+ icon = R.drawable.ic_round_search_sources_24,
+ isActivity = true,
+ onClick = {
+ ContextCompat.startActivity(
+ context,
+ Intent(context, ParserTestActivity::class.java),
+ null
+ )
+ }
+ ),
Settings(
type = 1,
name = getString(R.string.user_agent),
diff --git a/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt b/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt
index 6c6cf941..bb8c0396 100644
--- a/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt
+++ b/app/src/main/java/ani/dantotsu/settings/SubscriptionItem.kt
@@ -5,6 +5,7 @@ import android.view.View
import androidx.core.content.ContextCompat
import ani.dantotsu.R
import ani.dantotsu.databinding.ItemSubscriptionBinding
+import ani.dantotsu.loadImage
import ani.dantotsu.media.MediaDetailsActivity
import ani.dantotsu.notifications.subscription.SubscriptionHelper
import com.xwray.groupie.GroupieAdapter
@@ -25,7 +26,7 @@ class SubscriptionItem(
else
SubscriptionHelper.getMangaParser(media.id).name
val mediaName = media.name
- val showName = "$mediaName - $parserName"
+ val showName = "$mediaName ($parserName)"
binding.subscriptionName.text = showName
binding.root.setOnClickListener {
ContextCompat.startActivity(
@@ -36,6 +37,7 @@ class SubscriptionItem(
null
)
}
+ binding.subscriptionCover.loadImage(media.image)
binding.deleteSubscription.setOnClickListener {
SubscriptionHelper.deleteSubscription(id, true)
adapter.remove(this)
diff --git a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt
index c8c626b3..f12368df 100644
--- a/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt
+++ b/app/src/main/java/ani/dantotsu/settings/saving/Preferences.kt
@@ -4,6 +4,7 @@ import android.graphics.Color
import ani.dantotsu.connections.comments.AuthResponse
import ani.dantotsu.connections.mal.MAL
import ani.dantotsu.notifications.comment.CommentStore
+import ani.dantotsu.notifications.subscription.SubscriptionStore
import ani.dantotsu.settings.saving.internal.Location
import ani.dantotsu.settings.saving.internal.Pref
@@ -186,6 +187,7 @@ enum class PrefName(val data: Pref) { //TODO: Split this into multiple files
LogToFile(Pref(Location.Irrelevant, Boolean::class, false)),
RecentGlobalNotification(Pref(Location.Irrelevant, Int::class, 0)),
CommentNotificationStore(Pref(Location.Irrelevant, List::class, listOf())),
+ SubscriptionNotificationStore(Pref(Location.Irrelevant, List::class, listOf())),
UnreadCommentNotifications(Pref(Location.Irrelevant, Int::class, 0)),
DownloadsDir(Pref(Location.Irrelevant, String::class, "")),
RefreshStatus(Pref(Location.Irrelevant, Boolean::class, false)),
diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt
index a2f9e83d..13106131 100644
--- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt
+++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionInstaller.kt
@@ -1,6 +1,7 @@
package eu.kanade.tachiyomi.extension.util
import android.app.DownloadManager
+import android.app.ForegroundServiceStartNotAllowedException
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
@@ -11,9 +12,11 @@ import android.os.Environment
import androidx.core.content.ContextCompat
import androidx.core.content.getSystemService
import androidx.core.net.toUri
+import ani.dantotsu.R
import ani.dantotsu.media.AddonType
import ani.dantotsu.media.MediaType
import ani.dantotsu.media.Type
+import ani.dantotsu.toast
import ani.dantotsu.util.Logger
import com.jakewharton.rxrelay.PublishRelay
import eu.kanade.domain.base.BasePreferences
@@ -160,7 +163,16 @@ class ExtensionInstaller(private val context: Context) {
else -> {
val intent =
ExtensionInstallService.getIntent(context, type, downloadId, uri, installer)
- ContextCompat.startForegroundService(context, intent)
+ try {
+ ContextCompat.startForegroundService(context, intent)
+ } catch (e: RuntimeException) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && e is ForegroundServiceStartNotAllowedException) {
+ toast(context.getString(R.string.error_msg, context.getString(R.string.foreground_service_not_allowed)))
+ } else {
+ toast(context.getString(R.string.error_msg, e.message))
+ }
+ Logger.log(e)
+ }
}
}
}
diff --git a/app/src/main/res/layout/activity_extensions.xml b/app/src/main/res/layout/activity_extensions.xml
index 246c1785..a95edfed 100644
--- a/app/src/main/res/layout/activity_extensions.xml
+++ b/app/src/main/res/layout/activity_extensions.xml
@@ -41,6 +41,15 @@
android:layout_marginHorizontal="16dp"
android:orientation="horizontal">
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_review_view.xml b/app/src/main/res/layout/activity_review_view.xml
index 6732d8f9..8ae33dc4 100644
--- a/app/src/main/res/layout/activity_review_view.xml
+++ b/app/src/main/res/layout/activity_review_view.xml
@@ -2,6 +2,7 @@
diff --git a/app/src/main/res/layout/bottom_sheet_extension_test_settings.xml b/app/src/main/res/layout/bottom_sheet_extension_test_settings.xml
new file mode 100644
index 00000000..bacd922a
--- /dev/null
+++ b/app/src/main/res/layout/bottom_sheet_extension_test_settings.xml
@@ -0,0 +1,100 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_activity.xml b/app/src/main/res/layout/item_activity.xml
index b324c3ba..ccbdb634 100644
--- a/app/src/main/res/layout/item_activity.xml
+++ b/app/src/main/res/layout/item_activity.xml
@@ -27,7 +27,6 @@
android:id="@+id/activityUserAvatar"
android:layout_width="42dp"
android:layout_height="42dp"
- android:layout_gravity="center"
app:srcCompat="@drawable/ic_round_add_circle_24"
tools:ignore="ContentDescription,ImageContrastCheck"
tools:tint="@color/transparent" />
diff --git a/app/src/main/res/layout/item_developer.xml b/app/src/main/res/layout/item_developer.xml
index c4c87d7b..d695edec 100644
--- a/app/src/main/res/layout/item_developer.xml
+++ b/app/src/main/res/layout/item_developer.xml
@@ -28,7 +28,7 @@
+ android:text="@string/lorem_ipsum"
+ android:maxLines="2"
+ android:fontFamily="@font/poppins_semi_bold" />
diff --git a/app/src/main/res/layout/item_extension_select.xml b/app/src/main/res/layout/item_extension_select.xml
new file mode 100644
index 00000000..98af9cad
--- /dev/null
+++ b/app/src/main/res/layout/item_extension_select.xml
@@ -0,0 +1,40 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_extension_test.xml b/app/src/main/res/layout/item_extension_test.xml
new file mode 100644
index 00000000..e64da5a3
--- /dev/null
+++ b/app/src/main/res/layout/item_extension_test.xml
@@ -0,0 +1,111 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_follower.xml b/app/src/main/res/layout/item_follower.xml
index 675e5451..6c236619 100644
--- a/app/src/main/res/layout/item_follower.xml
+++ b/app/src/main/res/layout/item_follower.xml
@@ -74,11 +74,12 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="start|center_vertical"
- android:ellipsize="end"
android:fontFamily="@font/poppins_semi_bold"
+ android:ellipsize="end"
+ android:alpha="0.75"
android:maxLines="2"
android:text="@string/lorem_ipsum"
- android:textSize="14sp"
+ android:textSize="12sp"
android:visibility="gone" />
diff --git a/app/src/main/res/layout/item_follower_grid.xml b/app/src/main/res/layout/item_follower_grid.xml
index 3655ff88..04bc4c70 100644
--- a/app/src/main/res/layout/item_follower_grid.xml
+++ b/app/src/main/res/layout/item_follower_grid.xml
@@ -12,14 +12,16 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
- android:backgroundTint="@color/bg_white"
+ android:backgroundTint="@color/transparent"
+ app:strokeColor="@color/transparent"
app:cardCornerRadius="124dp">
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_search_history.xml b/app/src/main/res/layout/item_search_history.xml
index 82155f90..237ecbef 100644
--- a/app/src/main/res/layout/item_search_history.xml
+++ b/app/src/main/res/layout/item_search_history.xml
@@ -30,7 +30,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
- android:src="@drawable/ic_circle_cancel"
+ android:src="@drawable/ic_round_close_24"
android:textSize="14sp"
app:tint="?attr/colorOnBackground"
tools:ignore="ContentDescription" />
diff --git a/app/src/main/res/layout/item_subscription.xml b/app/src/main/res/layout/item_subscription.xml
index be8dd588..d6609cd6 100644
--- a/app/src/main/res/layout/item_subscription.xml
+++ b/app/src/main/res/layout/item_subscription.xml
@@ -8,14 +8,35 @@
android:layout_marginHorizontal="8dp"
android:orientation="horizontal"
tools:ignore="UseCompoundDrawables">
+
+
+
+
@@ -26,7 +47,7 @@
android:layout_gravity="center_vertical"
android:layout_marginEnd="8dp"
android:contentDescription="@string/delete"
- android:src="@drawable/ic_circle_cancel"
+ android:src="@drawable/ic_round_close_24"
app:tint="?attr/colorOnBackground" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/item_title_recycler.xml b/app/src/main/res/layout/item_title_recycler.xml
index ae659bc7..3961d479 100644
--- a/app/src/main/res/layout/item_title_recycler.xml
+++ b/app/src/main/res/layout/item_title_recycler.xml
@@ -5,16 +5,35 @@
android:layout_height="match_parent"
android:orientation="vertical">
-
+ android:orientation="horizontal"
+ tools:ignore="UseCompoundDrawables">
+
+
+
+