fix: decouple animator for stories

This commit is contained in:
rebelonion 2024-05-01 12:43:04 -05:00
parent 4362dd94c1
commit 31c509f88c
4 changed files with 170 additions and 96 deletions

View file

@ -1,7 +1,5 @@
package ani.dantotsu.home.status package ani.dantotsu.home.status
import android.animation.Animator
import android.animation.ObjectAnimator
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
@ -10,7 +8,6 @@ import android.util.AttributeSet
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.MotionEvent import android.view.MotionEvent
import android.view.View import android.view.View
import android.view.animation.LinearInterpolator
import android.widget.ProgressBar import android.widget.ProgressBar
import androidx.constraintlayout.widget.ConstraintLayout import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet import androidx.constraintlayout.widget.ConstraintSet
@ -36,6 +33,7 @@ import ani.dantotsu.settings.saving.PrefManager
import ani.dantotsu.settings.saving.PrefName import ani.dantotsu.settings.saving.PrefName
import ani.dantotsu.snackString import ani.dantotsu.snackString
import ani.dantotsu.util.AniMarkdown import ani.dantotsu.util.AniMarkdown
import ani.dantotsu.util.Logger
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
@ -45,13 +43,11 @@ import java.util.Calendar
import java.util.Locale import java.util.Locale
class Stories @JvmOverloads class Stories @JvmOverloads constructor(
constructor(
context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0
) : ConstraintLayout(context, attrs, defStyleAttr), View.OnTouchListener { ) : ConstraintLayout(context, attrs, defStyleAttr), View.OnTouchListener {
private lateinit var activity: FragmentActivity private lateinit var activity: FragmentActivity
private lateinit var binding: FragmentStatusBinding private lateinit var binding: FragmentStatusBinding
private lateinit var animation: ObjectAnimator
private lateinit var activityList: List<Activity> private lateinit var activityList: List<Activity>
private lateinit var storiesListener: StoriesCallback private lateinit var storiesListener: StoriesCallback
private var userClicked: Boolean = false private var userClicked: Boolean = false
@ -59,10 +55,12 @@ constructor(
private var primaryColor: Int = 0 private var primaryColor: Int = 0
private var onPrimaryColor: Int = 0 private var onPrimaryColor: Int = 0
private var storyDuration: Int = 6 private var storyDuration: Int = 6
private val timer: StoryTimer = StoryTimer(secondsToMillis(storyDuration))
init { init {
initLayout() initLayout()
} }
@SuppressLint("ClickableViewAccessibility") @SuppressLint("ClickableViewAccessibility")
fun initLayout() { fun initLayout() {
val inflater: LayoutInflater = LayoutInflater.from(context) val inflater: LayoutInflater = LayoutInflater.from(context)
@ -72,15 +70,16 @@ constructor(
primaryColor = context.getThemeColor(com.google.android.material.R.attr.colorPrimary) primaryColor = context.getThemeColor(com.google.android.material.R.attr.colorPrimary)
onPrimaryColor = context.getThemeColor(com.google.android.material.R.attr.colorOnPrimary) onPrimaryColor = context.getThemeColor(com.google.android.material.R.attr.colorOnPrimary)
if (context is StoriesCallback) if (context is StoriesCallback) storiesListener = context as StoriesCallback
storiesListener = context as StoriesCallback
binding.leftTouchPanel.setOnTouchListener(this) binding.leftTouchPanel.setOnTouchListener(this)
binding.rightTouchPanel.setOnTouchListener(this) binding.rightTouchPanel.setOnTouchListener(this)
} }
fun setStoriesList(activityList: List<Activity>, activity: FragmentActivity, startIndex : Int = 1) { fun setStoriesList(
activityList: List<Activity>, activity: FragmentActivity, startIndex: Int = 1
) {
this.activityList = activityList this.activityList = activityList
this.activity = activity this.activity = activity
this.storyIndex = startIndex this.storyIndex = startIndex
@ -212,17 +211,14 @@ constructor(
} }
val progressBar = findViewWithTag<ProgressBar>("story${storyIndex}") val progressBar = findViewWithTag<ProgressBar>("story${storyIndex}")
binding.androidStoriesLoadingView.visibility = View.VISIBLE binding.androidStoriesLoadingView.visibility = View.VISIBLE
animation = ObjectAnimator.ofInt(progressBar, "progress", 0, 100) timer.setOnTimerCompletedListener {
animation.duration = secondsToMillis(storyDuration) Logger.log("onAnimationEnd: $storyIndex")
animation.interpolator = LinearInterpolator()
animation.addListener(object : Animator.AnimatorListener {
override fun onAnimationStart(animator: Animator) {}
override fun onAnimationEnd(animator: Animator) {
if (storyIndex - 1 <= activityList.size) { if (storyIndex - 1 <= activityList.size) {
if (userClicked) { if (false) {
Logger.log("userClicked: $storyIndex")
userClicked = false userClicked = false
} else { } else {
Logger.log("userNotClicked: $storyIndex")
if (storyIndex < activityList.size) { if (storyIndex < activityList.size) {
storyIndex += 1 storyIndex += 1
showStory() showStory()
@ -238,13 +234,9 @@ constructor(
onStoriesCompleted() onStoriesCompleted()
} }
} }
timer.setOnPercentTickListener {
override fun onAnimationCancel(animator: Animator) { progressBar.progress = it
progressBar.progress = 100
} }
override fun onAnimationRepeat(animator: Animator) {}
})
loadStory(activityList[storyIndex - 1]) loadStory(activityList[storyIndex - 1])
} }
@ -281,7 +273,7 @@ constructor(
when (event?.action) { when (event?.action) {
MotionEvent.ACTION_DOWN -> { MotionEvent.ACTION_DOWN -> {
startClickTime = Calendar.getInstance().timeInMillis startClickTime = Calendar.getInstance().timeInMillis
animation.pause() pause()
} }
MotionEvent.ACTION_UP -> { MotionEvent.ACTION_UP -> {
@ -297,7 +289,7 @@ constructor(
} }
} else { } else {
//hold click occurred //hold click occurred
animation.resume() resume()
} }
} }
} }
@ -305,32 +297,33 @@ constructor(
} }
private fun rightPanelTouch() { private fun rightPanelTouch() {
Logger.log("rightPanelTouch: $storyIndex")
if (storyIndex == activityList.size) { if (storyIndex == activityList.size) {
completeProgressBar(storyIndex) completeProgressBar(storyIndex)
onStoriesCompleted() onStoriesCompleted()
return return
} }
userClicked = true userClicked = true
animation.end() timer.cancel()
if (storyIndex <= activityList.size) if (storyIndex <= activityList.size) storyIndex += 1
storyIndex += 1
showStory() showStory()
} }
private fun leftPanelTouch() { private fun leftPanelTouch() {
Logger.log("leftPanelTouch: $storyIndex")
if (storyIndex == 1) { if (storyIndex == 1) {
onStoriesPrevious() onStoriesPrevious()
return return
} }
userClicked = true userClicked = true
animation.end() timer.cancel()
resetProgressBar(storyIndex) resetProgressBar(storyIndex)
if (storyIndex > 1) if (storyIndex > 1) storyIndex -= 1
storyIndex -= 1
showStory() showStory()
} }
private fun onStoriesCompleted() { private fun onStoriesCompleted() {
Logger.log("onStoriesCompleted")
if (::storiesListener.isInitialized) { if (::storiesListener.isInitialized) {
storyIndex = 1 storyIndex = 1
storiesListener.onStoriesEnd() storiesListener.onStoriesEnd()
@ -345,14 +338,16 @@ constructor(
resetProgressBar(storyIndex) resetProgressBar(storyIndex)
} }
} }
fun pause() {
animation.pause()
}
fun resume() {
animation.resume()
}
private fun loadStory(story: Activity) {
fun pause() {
timer.pause()
}
fun resume() {
timer.resume()
}
private fun loadStory(story: Activity) {
val key = "activities" val key = "activities"
val set = PrefManager.getCustomVal<Set<Int>>(key, setOf()).plus((story.id)) val set = PrefManager.getCustomVal<Set<Int>>(key, setOf()).plus((story.id))
val newList = set.sorted().takeLast(200).toSet() val newList = set.sorted().takeLast(200).toSet()
@ -361,9 +356,11 @@ constructor(
binding.statusUserName.text = story.user?.name binding.statusUserName.text = story.user?.name
binding.statusUserTime.text = ActivityItemBuilder.getDateTime(story.createdAt) binding.statusUserTime.text = ActivityItemBuilder.getDateTime(story.createdAt)
binding.statusUserContainer.setOnClickListener { binding.statusUserContainer.setOnClickListener {
ContextCompat.startActivity(context, Intent(context, ProfileActivity::class.java) ContextCompat.startActivity(
.putExtra("userId", story.userId), context,
null) Intent(context, ProfileActivity::class.java).putExtra("userId", story.userId),
null
)
} }
fun visible(isList: Boolean) { fun visible(isList: Boolean) {
val visible = if (isList) View.VISIBLE else View.GONE val visible = if (isList) View.VISIBLE else View.GONE
@ -381,26 +378,38 @@ constructor(
when (story.typename) { when (story.typename) {
"ListActivity" -> { "ListActivity" -> {
visible(true) visible(true)
val text = "${story.status?.replaceFirstChar { val text = "${
story.status?.replaceFirstChar {
if (it.isLowerCase()) { if (it.isLowerCase()) {
it.titlecase(Locale.ROOT) it.titlecase(Locale.ROOT)
} } else {
else {
it.toString() it.toString()
} }
}} ${story.progress ?: story.media?.title?.userPreferred} " + }
if (story.status?.contains("completed") == false && !story.status.contains("plans") && !story.status.contains("repeating")) { } ${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}" "of ${story.media?.title?.userPreferred}"
} else { } else {
"" ""
} }
binding.infoText.text = text binding.infoText.text = text
val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations) val bannerAnimations: Boolean = PrefManager.getVal(PrefName.BannerAnimations)
blurImage(if (bannerAnimations) binding.contentImageViewKen else binding.contentImageView, story.media?.bannerImage ?: story.media?.coverImage?.extraLarge) blurImage(
if (bannerAnimations) binding.contentImageViewKen else binding.contentImageView,
story.media?.bannerImage ?: story.media?.coverImage?.extraLarge
)
binding.coverImage.loadImage(story.media?.coverImage?.extraLarge) binding.coverImage.loadImage(story.media?.coverImage?.extraLarge)
binding.coverImage.setOnClickListener { binding.coverImage.setOnClickListener {
ContextCompat.startActivity(context, Intent(context, MediaDetailsActivity::class.java) ContextCompat.startActivity(
.putExtra("mediaId", story.media?.id), context,
Intent(context, MediaDetailsActivity::class.java).putExtra(
"mediaId",
story.media?.id
),
ActivityOptionsCompat.makeSceneTransitionAnimation( ActivityOptionsCompat.makeSceneTransitionAnimation(
activity, activity,
binding.coverImage, binding.coverImage,
@ -417,18 +426,17 @@ constructor(
if (!(context as android.app.Activity).isDestroyed) { if (!(context as android.app.Activity).isDestroyed) {
val markwon = buildMarkwon(context, false) val markwon = buildMarkwon(context, false)
markwon.setMarkdown( markwon.setMarkdown(
binding.textActivity, binding.textActivity, AniMarkdown.getBasicAniHTML(story.text ?: "")
AniMarkdown.getBasicAniHTML(story.text ?: "")
) )
} }
} }
"MessageActivity" -> { "MessageActivity" -> {
visible(false) visible(false)
if (!(context as android.app.Activity).isDestroyed) { if (!(context as android.app.Activity).isDestroyed) {
val markwon = buildMarkwon(context, false) val markwon = buildMarkwon(context, false)
markwon.setMarkdown( markwon.setMarkdown(
binding.textActivity, binding.textActivity, AniMarkdown.getBasicAniHTML(story.message ?: "")
AniMarkdown.getBasicAniHTML(story.message ?: "")
) )
} }
} }
@ -456,8 +464,9 @@ constructor(
true true
} }
binding.androidStoriesLoadingView.visibility = View.GONE binding.androidStoriesLoadingView.visibility = View.GONE
animation.start() timer.start()
} }
fun like() { fun like() {
val story = activityList[storyIndex - 1] val story = activityList[storyIndex - 1]
val likeColor = ContextCompat.getColor(context, R.color.yt_red) val likeColor = ContextCompat.getColor(context, R.color.yt_red)

View file

@ -0,0 +1,64 @@
package ani.dantotsu.home.status
import android.os.CountDownTimer
class StoryTimer(
private val updateInterval: Long
) {
private lateinit var timer: CountDownTimer
private var prevVal = 0
private var pauseLength = 0L
var onTimerCompleted: () -> Unit = {}
var percentTick: (Int) -> Unit = {}
var timeLeft: Long = 0
private set
fun start(durationInMillis: Long = updateInterval) {
cancel()
timer = object : CountDownTimer(durationInMillis, 1) {
override fun onTick(millisUntilFinished: Long) {
timeLeft = millisUntilFinished
val percent =
((pauseLength + durationInMillis - millisUntilFinished) * 100 / (pauseLength + durationInMillis)).toInt()
if (percent != prevVal) {
percentTick.invoke(percent)
prevVal = percent
}
}
override fun onFinish() {
onTimerCompleted.invoke()
pauseLength = 0
}
}
timer.start()
}
fun cancel() {
if (::timer.isInitialized) {
timer.cancel()
}
}
fun pause() {
if (::timer.isInitialized) {
timer.cancel()
pauseLength = updateInterval - timeLeft
}
}
fun resume() {
if (::timer.isInitialized && timeLeft > 0) {
start(timeLeft)
timer.start()
}
}
fun setOnTimerCompletedListener(onTimerCompleted: () -> Unit) {
this.onTimerCompleted = onTimerCompleted
}
fun setOnPercentTickListener(percentTick: (Int) -> Unit) {
this.percentTick = percentTick
}
}

View file

@ -81,6 +81,7 @@ class SettingsAboutActivity : AppCompatActivity() {
isChecked = PrefManager.getVal(PrefName.LogToFile), isChecked = PrefManager.getVal(PrefName.LogToFile),
switch = { isChecked, _ -> switch = { isChecked, _ ->
PrefManager.setVal(PrefName.LogToFile, isChecked) PrefManager.setVal(PrefName.LogToFile, isChecked)
Logger.clearLog()
restartApp() restartApp()
}, },
attachToSwitch = { attachToSwitch = {

View file

@ -25,12 +25,14 @@ object Logger {
if (!PrefManager.getVal<Boolean>(PrefName.LogToFile) || file != null) return if (!PrefManager.getVal<Boolean>(PrefName.LogToFile) || file != null) return
file = File(context.getExternalFilesDir(null), "log.txt") file = File(context.getExternalFilesDir(null), "log.txt")
if (file?.exists() == true) { if (file?.exists() == true) {
val oldFile = File(context.getExternalFilesDir(null), "old_log.txt") if (file!!.length() > 1024 * 1024 * 10) { // 10MB
file?.copyTo(oldFile, true) file?.delete()
file?.createNewFile()
}
} else { } else {
file?.createNewFile() file?.createNewFile()
} }
file?.writeText("log started\n") file?.appendText("log started\n")
file?.appendText("date/time: ${Date()}\n") file?.appendText("date/time: ${Date()}\n")
file?.appendText("device: ${Build.MODEL}\n") file?.appendText("device: ${Build.MODEL}\n")
file?.appendText("os version: ${Build.VERSION.RELEASE}\n") file?.appendText("os version: ${Build.VERSION.RELEASE}\n")
@ -133,13 +135,6 @@ object Logger {
snackString("No log file found") snackString("No log file found")
return return
} }
val oldFile = File(context.getExternalFilesDir(null), "old_log.txt")
val fileToUse = if (oldFile.exists()) {
file?.readText()?.let { oldFile.appendText(it) }
oldFile
} else {
file
}
val shareIntent = Intent(Intent.ACTION_SEND) val shareIntent = Intent(Intent.ACTION_SEND)
shareIntent.type = "text/plain" shareIntent.type = "text/plain"
shareIntent.putExtra( shareIntent.putExtra(
@ -147,7 +142,7 @@ object Logger {
FileProvider.getUriForFile( FileProvider.getUriForFile(
context, context,
"${BuildConfig.APPLICATION_ID}.provider", "${BuildConfig.APPLICATION_ID}.provider",
fileToUse!! file!!
) )
) )
shareIntent.putExtra(Intent.EXTRA_SUBJECT, "Log file") shareIntent.putExtra(Intent.EXTRA_SUBJECT, "Log file")
@ -155,6 +150,11 @@ object Logger {
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION) shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
context.startActivity(Intent.createChooser(shareIntent, "Share log file")) context.startActivity(Intent.createChooser(shareIntent, "Share log file"))
} }
fun clearLog() {
file?.delete()
file = null
}
} }
class FinalExceptionHandler : Thread.UncaughtExceptionHandler { class FinalExceptionHandler : Thread.UncaughtExceptionHandler {