refactor: convert monkey activity 🐒 into a fragment monke 🦍 (#1576)

* Turn monkey activity 🐒 into a fragment monke 🦍
This commit is contained in:
IndusAryan
2025-03-05 05:15:12 +05:30
committed by GitHub
parent 127a9c006d
commit db9e362fbb
7 changed files with 219 additions and 157 deletions

View File

@ -196,11 +196,6 @@
</intent-filter>
</activity>
<activity
android:name=".ui.EasterEggMonkeActivity"
android:exported="false"
android:noHistory="true" />
<receiver
android:name=".receivers.VideoDownloadRestartReceiver"
android:enabled="false"

View File

@ -1,97 +0,0 @@
// https://github.com/googlecodelabs/android-kotlin-animation-property-animation/tree/master/begin
package com.lagradost.cloudstream3.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
import android.os.Bundle
import android.os.Handler
import android.view.View
import android.view.animation.AccelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.FrameLayout
import androidx.activity.ComponentActivity
import androidx.appcompat.widget.AppCompatImageView
import androidx.core.view.isVisible
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.ActivityEasterEggMonkeBinding
class EasterEggMonkeActivity : ComponentActivity() {
lateinit var binding : ActivityEasterEggMonkeBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityEasterEggMonkeBinding.inflate(layoutInflater)
setContentView(binding.root)
val handler = Handler(mainLooper)
lateinit var runnable: Runnable
runnable = Runnable {
shower()
handler.postDelayed(runnable, 300)
}
handler.postDelayed(runnable, 1000)
}
private fun shower() {
val containerW = binding.frame.width
val containerH = binding.frame.height
var starW: Float = binding.monke.width.toFloat()
var starH: Float = binding.monke.height.toFloat()
val newStar = AppCompatImageView(this)
val idx = (monkeys.size * Math.random()).toInt()
newStar.setImageResource(monkeys[idx])
newStar.isVisible = true
newStar.layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.WRAP_CONTENT,
FrameLayout.LayoutParams.WRAP_CONTENT)
binding.frame.addView(newStar)
newStar.scaleX += Math.random().toFloat() * 1.5f
newStar.scaleY = newStar.scaleX
starW *= newStar.scaleX
starH *= newStar.scaleY
newStar.translationX = Math.random().toFloat() * containerW - starW / 2
val mover = ObjectAnimator.ofFloat(newStar, View.TRANSLATION_Y, -starH, containerH + starH)
mover.interpolator = AccelerateInterpolator(1f)
val rotator = ObjectAnimator.ofFloat(newStar, View.ROTATION,
(Math.random() * 1080).toFloat())
rotator.interpolator = LinearInterpolator()
val set = AnimatorSet()
set.playTogether(mover, rotator)
set.duration = (Math.random() * 1500 + 2500).toLong()
set.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
binding.frame.removeView(newStar)
}
})
set.start()
}
companion object {
val monkeys = listOf(
R.drawable.monke_benene,
R.drawable.monke_burrito,
R.drawable.monke_coco,
R.drawable.monke_cookie,
R.drawable.monke_flusdered,
R.drawable.monke_funny,
R.drawable.monke_like,
R.drawable.monke_party,
R.drawable.monke_sob,
R.drawable.monke_drink,
R.drawable.benene,
R.drawable.ic_launcher_foreground
)
}
}

View File

@ -0,0 +1,189 @@
package com.lagradost.cloudstream3.ui
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.ObjectAnimator
import android.annotation.SuppressLint
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.animation.AccelerateInterpolator
import android.view.animation.LinearInterpolator
import android.widget.FrameLayout
import android.widget.ImageView
import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.lifecycle.lifecycleScope
import com.lagradost.cloudstream3.R
import com.lagradost.cloudstream3.databinding.FragmentEasterEggMonkeBinding
import com.lagradost.cloudstream3.utils.UIHelper.hideSystemUI
import com.lagradost.cloudstream3.utils.UIHelper.showSystemUI
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import kotlin.random.Random
class EasterEggMonkeFragment : Fragment() {
private var _binding: FragmentEasterEggMonkeBinding? = null
private val binding get() = _binding!!
// planet of monks
private val monkeys: List<Int> = listOf(
R.drawable.monke_benene,
R.drawable.monke_burrito,
R.drawable.monke_coco,
R.drawable.monke_cookie,
R.drawable.monke_flusdered,
R.drawable.monke_funny,
R.drawable.monke_like,
R.drawable.monke_party,
R.drawable.monke_sob,
R.drawable.monke_drink,
R.drawable.benene,
R.drawable.ic_launcher_foreground,
R.drawable.quick_novel_icon,
)
private val activeMonkeys = mutableListOf<ImageView>()
private var spawningJob: Job? = null
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
_binding = FragmentEasterEggMonkeBinding.inflate(layoutInflater)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
activity?.hideSystemUI()
spawningJob = lifecycleScope.launch {
delay(1000)
while (isActive) {
spawnMonkey()
delay(500)
}
}
}
private fun spawnMonkey() {
val newMonkey = ImageView(context ?: return).apply {
setImageResource(monkeys.random())
isVisible = true
}
val initialScale = Random.nextFloat() * 1.5f + 0.5f
newMonkey.scaleX = initialScale
newMonkey.scaleY = initialScale
newMonkey.measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
val monkeyW = newMonkey.measuredWidth * initialScale
val monkeyH = newMonkey.measuredHeight * initialScale
newMonkey.x = Random.nextFloat() * (binding.frame.width.toFloat() - monkeyW)
newMonkey.y = Random.nextFloat() * (binding.frame.height.toFloat() - monkeyH)
binding.frame.addView(newMonkey, FrameLayout.LayoutParams(
FrameLayout.LayoutParams.WRAP_CONTENT, FrameLayout.LayoutParams.WRAP_CONTENT
))
activeMonkeys.add(newMonkey)
newMonkey.alpha = 0f
ObjectAnimator.ofFloat(newMonkey, View.ALPHA, 0f, 1f).apply {
duration = Random.nextLong(1000, 2500)
interpolator = AccelerateInterpolator()
start()
}
@SuppressLint("ClickableViewAccessibility")
newMonkey.setOnTouchListener { view, event -> handleTouch(view, event) }
startFloatingAnimation(newMonkey)
}
private fun startFloatingAnimation(monkey: ImageView) {
val floatUpAnimator = ObjectAnimator.ofFloat(
monkey, View.TRANSLATION_Y, monkey.y, -monkey.height.toFloat()
).apply {
duration = Random.nextLong(8000, 15000)
interpolator = LinearInterpolator()
}
floatUpAnimator.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
// necessary check because binding becomes null but monkes are still moving until onDestroy()
if (_binding != null) {
binding.frame.removeView(monkey)
activeMonkeys.remove(monkey)
}
}
})
floatUpAnimator.start()
monkey.tag = floatUpAnimator
}
private fun handleTouch(view: View, event: MotionEvent): Boolean {
val monkey = view as ImageView
when (event.action) {
MotionEvent.ACTION_DOWN -> {
(monkey.tag as? ObjectAnimator)?.pause()
return true
}
MotionEvent.ACTION_MOVE -> {
// Update both X and Y positions properly
monkey.x = event.rawX - monkey.width / 2
monkey.y = event.rawY - monkey.height / 2
// Check if monkey touches the screen edge
if (isTouchingEdge(monkey)) {
removeMonkey(monkey)
}
return true
}
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {
if (isTouchingEdge(monkey)) {
removeMonkey(monkey)
} else {
startFloatingAnimation(monkey)
}
return true
}
}
return false
}
private fun isTouchingEdge(monkey: ImageView): Boolean {
return monkey.x <= 0 || monkey.x + monkey.width >= binding.frame.width ||
monkey.y <= 0 || monkey.y + monkey.height >= binding.frame.height
}
private fun removeMonkey(monkey: ImageView) {
// Fade out and remove the monkey
ObjectAnimator.ofFloat(monkey, View.ALPHA, 1f, 0f).apply {
duration = 300
addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
binding.frame.removeView(monkey)
activeMonkeys.remove(monkey)
}
})
start()
}
}
override fun onDestroyView() {
super.onDestroyView()
activity?.showSystemUI()
spawningJob?.cancel()
_binding = null
}
}

View File

@ -391,7 +391,7 @@ class SettingsGeneral : PreferenceFragmentCompat() {
try {
beneneCount++
if (beneneCount%20 == 0) {
activity?.navigate(R.id.easterEggMonkeActivity)
activity?.navigate(R.id.action_navigation_settings_general_to_easterEggMonkeFragment)
}
settingsManager.edit().putInt(
getString(R.string.benene_count),

View File

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Copyright (C) 2019 The Android Open Source Project
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~ http://www.apache.org/licenses/LICENSE-2.0
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<FrameLayout
android:id="@+id/frame"
android:layout_width="match_parent"
android:layout_height="0dp"
android:background="@color/black"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.0">
<ImageView
android:id="@+id/monke"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:scaleX="3"
android:scaleY="3"
android:visibility="gone"
app:srcCompat="@drawable/monke_benene"
tools:visibility="visible" />
</FrameLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/frame">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/monke"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"
tools:visibility="visible" />
</FrameLayout>

View File

@ -184,7 +184,15 @@
app:enterAnim="@anim/enter_anim"
app:exitAnim="@anim/exit_anim"
app:popEnterAnim="@anim/enter_anim"
app:popExitAnim="@anim/exit_anim" />
app:popExitAnim="@anim/exit_anim" >
<action
android:id="@+id/action_navigation_settings_general_to_easterEggMonkeFragment"
app:destination="@id/easterEggMonkeFragment"
app:enterAnim="@anim/go_right"
app:exitAnim="@anim/go_left"
app:popEnterAnim="@anim/go_right"
app:popExitAnim="@anim/go_left" />
</fragment>
<fragment
android:id="@+id/navigation_settings_extensions"
@ -668,10 +676,9 @@
android:label="AccountSelectActivity"
tools:layout="@layout/activity_account_select" />
<activity
android:id="@+id/easterEggMonkeActivity"
android:name="com.lagradost.cloudstream3.ui.EasterEggMonkeActivity"
android:label="EasterEggMonkeActivity"
tools:layout="@layout/activity_easter_egg_monke" />
<fragment
android:id="@+id/easterEggMonkeFragment"
android:name="com.lagradost.cloudstream3.ui.EasterEggMonkeFragment"
tools:layout="@layout/fragment_easter_egg_monke"
android:label="EasterEggMonkeFragment" />
</navigation>