Added About section to menu
This commit is contained in:
parent
1850641fdb
commit
72d4a797ea
8 changed files with 673 additions and 15 deletions
|
@ -1,12 +0,0 @@
|
|||
package partisan.weforge.xyz.pulse
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
||||
class AboutActivity : AppCompatActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
// placeholder
|
||||
setContentView(androidx.appcompat.R.layout.abc_action_bar_title_item)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
package partisan.weforge.xyz.pulse
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewStub
|
||||
import androidx.fragment.app.Fragment
|
||||
import partisan.weforge.xyz.pulse.databinding.FragmentAboutBinding
|
||||
|
||||
class AboutFragment : Fragment() {
|
||||
|
||||
private var _binding: FragmentAboutBinding? = null
|
||||
private val binding get() = _binding!!
|
||||
|
||||
private var tapCount = 0
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
_binding = FragmentAboutBinding.inflate(inflater, container, false)
|
||||
return binding.root
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
binding.sourceButton.setOnClickListener {
|
||||
openUrl("https://weforge.xyz/partisan/Pulse")
|
||||
}
|
||||
|
||||
binding.licenseButton.setOnClickListener {
|
||||
openUrl("https://www.gnu.org/licenses/gpl-3.0.en.html")
|
||||
}
|
||||
|
||||
binding.appIcon.setOnClickListener {
|
||||
tapCount++
|
||||
if (tapCount >= 5) {
|
||||
binding.secretButton.visibility = View.VISIBLE
|
||||
}
|
||||
}
|
||||
|
||||
binding.secretButton.setOnClickListener {
|
||||
requireActivity().supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, SecretFragment())
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
}
|
||||
}
|
||||
|
||||
private fun openUrl(url: String) {
|
||||
val intent = Intent(Intent.ACTION_VIEW, Uri.parse(url))
|
||||
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
|
||||
startActivity(intent)
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
_binding = null
|
||||
}
|
||||
}
|
|
@ -48,7 +48,10 @@ class MainActivity : AppCompatActivity() {
|
|||
true
|
||||
}
|
||||
R.id.action_about -> {
|
||||
startActivity(Intent(this, AboutActivity::class.java))
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, AboutFragment())
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
true
|
||||
}
|
||||
R.id.action_contacts -> {
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package partisan.weforge.xyz.pulse
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.fragment.app.Fragment
|
||||
|
||||
class SecretFragment : Fragment() {
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
return SecretView(requireContext())
|
||||
}
|
||||
}
|
493
app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt
Normal file
493
app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt
Normal file
|
@ -0,0 +1,493 @@
|
|||
package partisan.weforge.xyz.pulse
|
||||
|
||||
import android.content.Context
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.*
|
||||
import android.util.AttributeSet
|
||||
import android.view.Choreographer
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import kotlin.math.*
|
||||
import kotlin.random.Random
|
||||
|
||||
class SecretView @JvmOverloads constructor(
|
||||
context: Context,
|
||||
attrs: AttributeSet? = null
|
||||
) : View(context, attrs), Choreographer.FrameCallback {
|
||||
|
||||
private val bulletPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
strokeWidth = 6f
|
||||
}
|
||||
private val starPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
alpha = 40
|
||||
style = Paint.Style.FILL
|
||||
}
|
||||
private val playerPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 4f
|
||||
}
|
||||
private val enemyPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 4f
|
||||
}
|
||||
private val rocketPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.RED
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 3f
|
||||
}
|
||||
private val textPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = 64f
|
||||
typeface = Typeface.DEFAULT_BOLD
|
||||
}
|
||||
private val retryRect = RectF()
|
||||
private val retryPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.DKGRAY
|
||||
}
|
||||
private val retryTextPaint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.WHITE
|
||||
textAlign = Paint.Align.CENTER
|
||||
textSize = 48f
|
||||
}
|
||||
|
||||
private var playerX = 0f
|
||||
private var viewWidth = 0f
|
||||
private var viewHeight = 0f
|
||||
|
||||
private var isTouching = false
|
||||
private var bulletCooldownMs = 0L
|
||||
private var gameOver = false
|
||||
private var score = 0
|
||||
|
||||
private val bullets = mutableListOf<Bullet>()
|
||||
private val mediumBulletsToFire = mutableListOf<Bullet>()
|
||||
private val enemies = mutableListOf<Enemy>()
|
||||
private val rockets = mutableListOf<Rocket>()
|
||||
private val stars = mutableListOf<Star>()
|
||||
private val explosions = mutableListOf<Explosion>()
|
||||
private val rocketTrails = mutableListOf<Pair<Float, Float>>()
|
||||
|
||||
private data class Bullet(var x: Float, var y: Float, val dy: Float = -15f)
|
||||
private data class Star(var x: Float, var y: Float, val radius: Float, val speed: Float)
|
||||
private data class Rocket(var x: Float, var y: Float, var angle: Float, val trail: MutableList<Pair<Float, Float>> = mutableListOf())
|
||||
private data class Explosion(var x: Float, var y: Float, var timer: Int = 12)
|
||||
|
||||
private var lastLogicTime = 0L
|
||||
private val logicStepMs = 16L
|
||||
|
||||
private var waveTimer = 0L
|
||||
private var currentWave = 0
|
||||
private var enemiesLeftInWave = 0
|
||||
private var currentWaveType = ""
|
||||
|
||||
init {
|
||||
for (i in 0 until 50) {
|
||||
stars.add(Star(Random.nextFloat() * 1080f, Random.nextFloat() * 1920f, Random.nextFloat() * 2f + 1f, Random.nextFloat() * 2f + 0.5f))
|
||||
}
|
||||
|
||||
val colorAttrs = intArrayOf(com.google.android.material.R.attr.colorPrimaryVariant)
|
||||
context.obtainStyledAttributes(colorAttrs).use {
|
||||
val primary = it.getColor(0, Color.CYAN)
|
||||
playerPaint.color = primary
|
||||
enemyPaint.color = primary
|
||||
}
|
||||
|
||||
Choreographer.getInstance().postFrameCallback(this)
|
||||
lastLogicTime = System.nanoTime()
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
viewWidth = w.toFloat()
|
||||
viewHeight = h.toFloat()
|
||||
// Kinda hacky way to center things, but I'd rather do this here than in update(), since it can't be in init() as the screen size isn't initialized at that point.
|
||||
playerX = viewWidth / 2f
|
||||
retryRect.set(viewWidth / 2f - 120f, viewHeight / 2f + 60f, viewWidth / 2f + 120f, viewHeight / 2f + 130f)
|
||||
}
|
||||
|
||||
override fun doFrame(frameTimeNanos: Long) {
|
||||
if (!gameOver) {
|
||||
val now = System.nanoTime()
|
||||
while ((now - lastLogicTime) / 1_000_000 >= logicStepMs) {
|
||||
update(logicStepMs)
|
||||
lastLogicTime += logicStepMs * 1_000_000
|
||||
}
|
||||
invalidate()
|
||||
Choreographer.getInstance().postFrameCallback(this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun update(deltaMs: Long) {
|
||||
stars.forEach {
|
||||
it.y += it.speed * deltaMs / 16f
|
||||
if (it.y > viewHeight) {
|
||||
it.y = 0f
|
||||
it.x = Random.nextFloat() * viewWidth
|
||||
}
|
||||
}
|
||||
|
||||
rockets.forEach { rocket ->
|
||||
rocket.trail.add(0, rocket.x to rocket.y)
|
||||
if (rocket.trail.size > 20) {
|
||||
rocket.trail.removeLast()
|
||||
}
|
||||
}
|
||||
|
||||
explosions.forEach { it.timer-- }
|
||||
explosions.removeIf { it.timer <= 0 }
|
||||
|
||||
|
||||
bullets.forEach { it.y += it.dy * deltaMs / 16f }
|
||||
bullets.removeIf { it.y < 0 }
|
||||
|
||||
bulletCooldownMs -= deltaMs
|
||||
if (isTouching && bulletCooldownMs <= 0) {
|
||||
bullets.add(Bullet(playerX, viewHeight - 130f))
|
||||
bulletCooldownMs = 150L
|
||||
}
|
||||
|
||||
enemies.forEach {
|
||||
it.update(deltaMs)
|
||||
it.x = max(20f, min(it.x, viewWidth - 20f))
|
||||
if (it.y > viewHeight + 100f) it.y = -40f // respawn at top
|
||||
}
|
||||
|
||||
rockets.forEach {
|
||||
val targetAngle = atan2(viewHeight - 100f - it.y, playerX - it.x)
|
||||
it.angle += ((targetAngle - it.angle + PI).mod(2 * PI) - PI).toFloat() * 0.1f
|
||||
it.x += cos(it.angle) * 6f
|
||||
it.y += sin(it.angle) * 6f
|
||||
}
|
||||
rockets.removeIf { it.x < 0 || it.x > viewWidth || it.y < 0 || it.y > viewHeight }
|
||||
|
||||
bullets.addAll(mediumBulletsToFire)
|
||||
mediumBulletsToFire.clear()
|
||||
|
||||
checkCollisions()
|
||||
spawnEnemies(deltaMs)
|
||||
}
|
||||
|
||||
private fun checkCollisions() {
|
||||
val enemyIter = enemies.iterator()
|
||||
while (enemyIter.hasNext()) {
|
||||
val enemy = enemyIter.next()
|
||||
if (hypot(playerX - enemy.x, viewHeight - 100f - enemy.y) < 40f) {
|
||||
explosions.add(Explosion(playerX, viewHeight - 100f))
|
||||
gameOver = true
|
||||
return
|
||||
}
|
||||
for (b in bullets) {
|
||||
if (hypot(b.x - enemy.x, b.y - enemy.y) < 30f) {
|
||||
explosions.add(Explosion(enemy.x, enemy.y))
|
||||
enemyIter.remove()
|
||||
score += 10
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val rocketIter = rockets.iterator()
|
||||
while (rocketIter.hasNext()) {
|
||||
val rocket = rocketIter.next()
|
||||
if (hypot(playerX - rocket.x, viewHeight - 100f - rocket.y) < 30f) {
|
||||
explosions.add(Explosion(playerX, viewHeight - 100f))
|
||||
gameOver = true
|
||||
return
|
||||
}
|
||||
for (b in bullets) {
|
||||
if (hypot(b.x - rocket.x, b.y - rocket.y) < 20f) {
|
||||
explosions.add(Explosion(rocket.x, rocket.y))
|
||||
rocketIter.remove()
|
||||
score += 15
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun spawnEnemies(deltaMs: Long) {
|
||||
waveTimer -= deltaMs
|
||||
|
||||
// Passive asteroids
|
||||
if (Random.nextFloat() < 0.002f) {
|
||||
enemies.add(EnemyAsteroid(Random.nextFloat() * viewWidth, -40f, viewWidth))
|
||||
}
|
||||
|
||||
// Setup new wave
|
||||
if (enemiesLeftInWave <= 0 && waveTimer <= 0) {
|
||||
currentWave++
|
||||
currentWaveType = when (currentWave % 3) {
|
||||
0 -> "easy"
|
||||
1 -> "medium"
|
||||
else -> "hard"
|
||||
}
|
||||
|
||||
enemiesLeftInWave = when (currentWaveType) {
|
||||
"easy" -> 3 + currentWave
|
||||
"medium" -> 2 + currentWave / 2
|
||||
"hard" -> 1 + currentWave / 4
|
||||
else -> 3
|
||||
}
|
||||
|
||||
waveTimer = 3000L
|
||||
}
|
||||
|
||||
// Spawn enemies in group
|
||||
if (enemiesLeftInWave > 0 && enemies.count { it !is EnemyAsteroid } < 3) {
|
||||
val baseX = Random.nextFloat() * (viewWidth - 100f) + 50f
|
||||
val baseY = -40f
|
||||
val spacing = 35f
|
||||
val sharedOffset = Random.nextFloat() * 1000f
|
||||
val sharedFireTime = Random.nextLong(2000L, 4000L)
|
||||
|
||||
val formationSize = when (currentWaveType) {
|
||||
"easy" -> 5
|
||||
"medium" -> 3
|
||||
"hard" -> 1
|
||||
else -> 3
|
||||
}
|
||||
|
||||
for (i in 0 until min(formationSize, enemiesLeftInWave)) {
|
||||
val offsetX = (i - (formationSize - 1) / 2f) * spacing
|
||||
val x = baseX + offsetX
|
||||
|
||||
// The group sync isn't working as enemies gradually get out of sync, but whatever, it's fine.
|
||||
val enemy = when (currentWaveType) {
|
||||
"easy" -> EnemyEasy(x, baseY)
|
||||
"medium" -> EnemyMedium(x, baseY, sharedOffset, sharedFireTime) { mediumBulletsToFire.add(it) }
|
||||
"hard" -> EnemyHard(x, baseY, { rockets.add(it) }, sharedOffset, sharedFireTime)
|
||||
else -> EnemyEasy(x, baseY)
|
||||
}
|
||||
|
||||
enemies.add(enemy)
|
||||
enemiesLeftInWave--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun resetGame() {
|
||||
bullets.clear()
|
||||
enemies.clear()
|
||||
rockets.clear()
|
||||
explosions.clear()
|
||||
rocketTrails.clear()
|
||||
score = 0
|
||||
currentWave = 0
|
||||
enemiesLeftInWave = 0
|
||||
gameOver = false
|
||||
playerX = viewWidth / 2f
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
viewWidth = width.toFloat()
|
||||
viewHeight = height.toFloat()
|
||||
|
||||
canvas.drawColor(Color.parseColor("#121212"))
|
||||
stars.forEach { canvas.drawCircle(it.x, it.y, it.radius, starPaint) }
|
||||
|
||||
rockets.forEach { rocket ->
|
||||
rocket.trail.forEachIndexed { index, (x, y) ->
|
||||
val alpha = ((1f - index / 20f.toFloat()) * 255).toInt()
|
||||
rocketPaint.alpha = alpha
|
||||
canvas.drawCircle(x, y, 2f, rocketPaint)
|
||||
}
|
||||
}
|
||||
rocketPaint.alpha = 255
|
||||
|
||||
explosions.forEach {
|
||||
val radius = 40f * (1f - it.timer / 12f.toFloat())
|
||||
val alpha = (255 * (it.timer / 12f.toFloat())).toInt()
|
||||
val paint = Paint().apply {
|
||||
color = Color.YELLOW
|
||||
this.alpha = alpha
|
||||
style = Paint.Style.STROKE
|
||||
strokeWidth = 3f
|
||||
}
|
||||
canvas.drawCircle(it.x, it.y, radius, paint)
|
||||
}
|
||||
|
||||
bullets.forEach { canvas.drawLine(it.x, it.y, it.x, it.y - 20f, bulletPaint) }
|
||||
enemies.forEach { it.draw(canvas, enemyPaint) }
|
||||
rockets.forEach { canvas.drawCircle(it.x, it.y, 10f, rocketPaint) }
|
||||
|
||||
if (!gameOver) {
|
||||
val baseY = viewHeight - 100f
|
||||
val safeX = max(30f, min(playerX, viewWidth - 30f))
|
||||
playerX = safeX
|
||||
val path = Path().apply {
|
||||
moveTo(safeX, baseY - 30f)
|
||||
lineTo(safeX - 30f, baseY + 30f)
|
||||
lineTo(safeX + 30f, baseY + 30f)
|
||||
close()
|
||||
}
|
||||
canvas.drawPath(path, playerPaint)
|
||||
} else {
|
||||
canvas.drawText("Game Over", viewWidth / 2f, viewHeight / 2f - 60f, textPaint)
|
||||
canvas.drawText("Score: $score", viewWidth / 2f, viewHeight / 2f + 10f, textPaint)
|
||||
canvas.drawRoundRect(retryRect, 20f, 20f, retryPaint)
|
||||
canvas.drawText("Retry", retryRect.centerX(), retryRect.centerY() + 16f, retryTextPaint)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onTouchEvent(event: MotionEvent): Boolean {
|
||||
if (gameOver && event.action == MotionEvent.ACTION_DOWN) {
|
||||
if (retryRect.contains(event.x, event.y)) {
|
||||
resetGame()
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
when (event.action) {
|
||||
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_MOVE -> {
|
||||
playerX = max(30f, min(event.x, width - 30f))
|
||||
isTouching = true
|
||||
}
|
||||
MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> isTouching = false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private interface Enemy {
|
||||
var x: Float
|
||||
var y: Float
|
||||
fun update(deltaMs: Long)
|
||||
fun draw(canvas: Canvas, paint: Paint)
|
||||
}
|
||||
|
||||
private class EnemyAsteroid(
|
||||
override var x: Float,
|
||||
override var y: Float,
|
||||
private val screenWidth: Float
|
||||
) : Enemy {
|
||||
private var vx = Random.nextFloat() * 2f - 1f
|
||||
private val vy = Random.nextFloat() * 2f + 2f
|
||||
private var angle = Random.nextFloat() * 360f
|
||||
private val rotationSpeed = Random.nextFloat() * 2f * if (Random.nextBoolean()) 1 else -1
|
||||
|
||||
override fun update(deltaMs: Long) {
|
||||
x += vx * deltaMs / 16f
|
||||
y += vy * deltaMs / 16f
|
||||
angle = (angle + rotationSpeed * deltaMs / 16f) % 360f
|
||||
|
||||
if (x < 20f || x > screenWidth - 20f) {
|
||||
vx = -vx
|
||||
x = max(20f, min(x, screenWidth - 20f))
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas, paint: Paint) {
|
||||
canvas.save()
|
||||
canvas.rotate(angle, x, y)
|
||||
val path = Path().apply {
|
||||
moveTo(x - 20f, y)
|
||||
lineTo(x - 10f, y - 15f)
|
||||
lineTo(x + 10f, y - 15f)
|
||||
lineTo(x + 20f, y)
|
||||
lineTo(x + 10f, y + 15f)
|
||||
lineTo(x - 10f, y + 15f)
|
||||
close()
|
||||
}
|
||||
canvas.drawPath(path, paint)
|
||||
canvas.restore()
|
||||
}
|
||||
}
|
||||
|
||||
private class EnemyEasy(override var x: Float, override var y: Float) : Enemy {
|
||||
private val offset = Random.nextFloat() * 1000f
|
||||
override fun update(deltaMs: Long) {
|
||||
y += 2f * deltaMs / 16f
|
||||
x += sin((y + offset) / 50f) * 2f
|
||||
}
|
||||
override fun draw(canvas: Canvas, paint: Paint) {
|
||||
val path = Path().apply {
|
||||
moveTo(x, y - 20f)
|
||||
lineTo(x + 20f, y)
|
||||
lineTo(x, y + 20f)
|
||||
lineTo(x - 20f, y)
|
||||
close()
|
||||
}
|
||||
canvas.drawPath(path, paint)
|
||||
}
|
||||
}
|
||||
|
||||
private class EnemyMedium(
|
||||
override var x: Float,
|
||||
override var y: Float,
|
||||
private val offset: Float,
|
||||
private var fireTimer: Long,
|
||||
val fireBullet: (Bullet) -> Unit
|
||||
) : Enemy {
|
||||
override fun update(deltaMs: Long) {
|
||||
y += 2.5f * deltaMs / 16f
|
||||
x += sin((y + offset) / 40f) * 3f
|
||||
|
||||
fireTimer -= deltaMs
|
||||
if (fireTimer <= 0) {
|
||||
fireBullet(Bullet(x, y + 30f, dy = 10f))
|
||||
fireTimer = Random.nextLong(3000L, 6000L)
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas, paint: Paint) {
|
||||
val path = Path().apply {
|
||||
moveTo(x, y - 25f)
|
||||
lineTo(x + 15f, y)
|
||||
lineTo(x, y + 25f)
|
||||
lineTo(x - 15f, y)
|
||||
close()
|
||||
}
|
||||
canvas.drawPath(path, paint)
|
||||
}
|
||||
}
|
||||
|
||||
private class EnemyHard(
|
||||
override var x: Float,
|
||||
override var y: Float,
|
||||
val fireRocket: (Rocket) -> Unit,
|
||||
private val offset: Float = Random.nextFloat() * 1000f,
|
||||
private var fireTimer: Long = Random.nextLong(2000L, 5000L)
|
||||
) : Enemy {
|
||||
private var cooldown = 0L
|
||||
private var firing = false
|
||||
|
||||
override fun update(deltaMs: Long) {
|
||||
if (firing) {
|
||||
cooldown += deltaMs
|
||||
if (cooldown > 500 && cooldown < 1300) {
|
||||
fireRocket(Rocket(x, y - 30f, -PI.toFloat() / 2))
|
||||
cooldown = 1300
|
||||
} else if (cooldown > 2000) {
|
||||
cooldown = 0L
|
||||
fireTimer = Random.nextLong(15000L, 25000L)
|
||||
firing = false
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
y += 3f * deltaMs / 16f
|
||||
x += sin((y + offset) / 25f) * 4f
|
||||
fireTimer -= deltaMs
|
||||
if (fireTimer <= 0) {
|
||||
firing = true
|
||||
cooldown = 0L
|
||||
}
|
||||
}
|
||||
|
||||
override fun draw(canvas: Canvas, paint: Paint) {
|
||||
val path = Path().apply {
|
||||
moveTo(x, y - 25f)
|
||||
lineTo(x + 10f, y - 10f)
|
||||
lineTo(x + 20f, y + 10f)
|
||||
lineTo(x, y + 25f)
|
||||
lineTo(x - 20f, y + 10f)
|
||||
lineTo(x - 10f, y - 10f)
|
||||
close()
|
||||
}
|
||||
canvas.drawPath(path, paint)
|
||||
}
|
||||
}
|
||||
}
|
77
app/src/main/res/layout/fragment_about.xml
Normal file
77
app/src/main/res/layout/fragment_about.xml
Normal file
|
@ -0,0 +1,77 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/rootLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="32dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/contentWrapper"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:gravity="center_horizontal"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/appIcon"
|
||||
android:layout_width="96dp"
|
||||
android:layout_height="96dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:contentDescription="@string/app_name"
|
||||
android:src="@mipmap/ic_launcher" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/app_name"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/description"
|
||||
android:textAlignment="center"
|
||||
android:textSize="14sp"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/sourceButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/source_code"
|
||||
android:layout_marginTop="24dp"
|
||||
app:cornerRadius="24dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/licenseButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/license"
|
||||
android:layout_marginTop="8dp"
|
||||
app:cornerRadius="24dp" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/secretButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Secret"
|
||||
android:layout_marginTop="16dp"
|
||||
android:visibility="gone"
|
||||
app:cornerRadius="24dp" />
|
||||
|
||||
</LinearLayout>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</FrameLayout>
|
12
app/src/main/res/layout/secret_overlay.xml
Normal file
12
app/src/main/res/layout/secret_overlay.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/secretRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="#CC000000">
|
||||
|
||||
<partisan.weforge.xyz.pulse.SecretView
|
||||
android:id="@+id/secretView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</FrameLayout>
|
|
@ -1,7 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<string name="app_name">Pulse</string>
|
||||
<string name="description">App will try to redirect outgoing calls to E2EE apps if available.</string>
|
||||
<string name="description">Redirects outgoing calls to E2EE apps if available.</string>
|
||||
<string name="popup">Redirecting to %1$s</string>
|
||||
<string name="destination_signal">Signal</string>
|
||||
<string name="destination_telegram">Telegram</string>
|
||||
|
@ -10,11 +10,13 @@
|
|||
<string name="redirection_delay_description">The delay before a call will be redirected.</string>
|
||||
<string name="popup_position">Popup position</string>
|
||||
<string name="fallback">Fallback</string>
|
||||
<string name="activate_description">To start, grant the required permissions and tap the Activate button.</string>
|
||||
<string name="activate_description">To start, grant the required permissions by tapping the Activate button.</string>
|
||||
<string name="activate">Activate</string>
|
||||
<string name="navigation_drawer_open">Open menu</string>
|
||||
<string name="navigation_drawer_close">Close menu</string>
|
||||
<string name="popup_settings_description">Configure popup behavior, position, and delay.</string>
|
||||
<string name="popup_enabled">Popup enabled</string>
|
||||
<string name="test">Test</string>
|
||||
<string name="source_code">Source Code</string>
|
||||
<string name="license">License</string>
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue