From e810208a14bb3e58d857e82e58efec24b98b14fd Mon Sep 17 00:00:00 2001
From: partisan <none@noone.no>
Date: Sat, 17 May 2025 08:57:57 +0200
Subject: [PATCH] Clean up & Fixes

---
 .../weforge/xyz/pulse/AboutFragment.kt        |   1 -
 .../xyz/pulse/CallRedirectionService.kt       |   7 +-
 .../weforge/xyz/pulse/MainActivity.kt         |   2 -
 .../xyz/pulse/PopupSettingsFragment.kt        |  76 ++++-----
 .../partisan/weforge/xyz/pulse/PopupWindow.kt |   3 +-
 .../partisan/weforge/xyz/pulse/Preferences.kt |  14 +-
 .../partisan/weforge/xyz/pulse/SecretView.kt  |  37 +++--
 .../weforge/xyz/pulse/ServicesFragment.kt     |  21 ++-
 .../weforge/xyz/pulse/WelcomeActivity.kt      |   2 -
 .../res/layout/fragment_popup_settings.xml    | 150 +++++++++---------
 10 files changed, 171 insertions(+), 142 deletions(-)

diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/AboutFragment.kt b/app/src/main/java/partisan/weforge/xyz/pulse/AboutFragment.kt
index 0f30615..48ec0a1 100644
--- a/app/src/main/java/partisan/weforge/xyz/pulse/AboutFragment.kt
+++ b/app/src/main/java/partisan/weforge/xyz/pulse/AboutFragment.kt
@@ -6,7 +6,6 @@ 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
 
diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt b/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt
index f9d46b4..105157b 100644
--- a/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt
+++ b/app/src/main/java/partisan/weforge/xyz/pulse/CallRedirectionService.kt
@@ -82,7 +82,12 @@ class CallRedirectionService : CallRedirectionService() {
             return
         }
 
-        window.show(record.uri, MIMETYPE_TO_DST_NAME[record.mimetype] ?: return)
+        if (prefs.popupEnabled) {
+            window.show(record.uri, MIMETYPE_TO_DST_NAME[record.mimetype] ?: return)
+        } else {
+            window.call(record.uri)
+            cancelCall()
+        }
     }
 
     @RequiresPermission(Manifest.permission.READ_CONTACTS)
diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/MainActivity.kt b/app/src/main/java/partisan/weforge/xyz/pulse/MainActivity.kt
index 91749d7..8a281be 100644
--- a/app/src/main/java/partisan/weforge/xyz/pulse/MainActivity.kt
+++ b/app/src/main/java/partisan/weforge/xyz/pulse/MainActivity.kt
@@ -2,8 +2,6 @@ package partisan.weforge.xyz.pulse
 
 import android.content.Intent
 import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
 import androidx.appcompat.app.AppCompatActivity
 import partisan.weforge.xyz.pulse.databinding.ActivityMainBinding
 import androidx.appcompat.app.ActionBarDrawerToggle
diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/PopupSettingsFragment.kt b/app/src/main/java/partisan/weforge/xyz/pulse/PopupSettingsFragment.kt
index 9e607fe..6bd8d97 100644
--- a/app/src/main/java/partisan/weforge/xyz/pulse/PopupSettingsFragment.kt
+++ b/app/src/main/java/partisan/weforge/xyz/pulse/PopupSettingsFragment.kt
@@ -8,8 +8,6 @@ import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
 import androidx.fragment.app.Fragment
-import androidx.recyclerview.widget.ItemTouchHelper
-import androidx.recyclerview.widget.LinearLayoutManager
 import androidx.core.content.getSystemService
 import partisan.weforge.xyz.pulse.databinding.FragmentPopupSettingsBinding
 
@@ -33,48 +31,42 @@ class PopupSettingsFragment : Fragment() {
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        val services = listOf(
-            ServiceEntry(
-                "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call",
-                R.string.destination_signal,
-                requireContext().isServiceEnabled("vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call")
-            ),
-            ServiceEntry(
-                "vnd.android.cursor.item/vnd.org.telegram.messenger.android.call",
-                R.string.destination_telegram,
-                requireContext().isServiceEnabled("vnd.android.cursor.item/vnd.org.telegram.messenger.android.call")
-            ),
-            ServiceEntry(
-                "vnd.android.cursor.item/vnd.ch.threema.app.call",
-                R.string.destination_threema,
-                requireContext().isServiceEnabled("vnd.android.cursor.item/vnd.ch.threema.app.call")
-            ),
-            ServiceEntry(
-                "vnd.android.cursor.item/vnd.com.whatsapp.voip.call",
-                R.string.destination_whatsapp,
-                requireContext().isServiceEnabled("vnd.android.cursor.item/vnd.com.whatsapp.voip.call")
-            ),
-        )
+        prefs = Preferences(requireContext())
+        window = PopupWindow(requireContext(), null)
 
-        val adapter = ServiceAdapter(
-            context = requireContext(),
-            services = services.toMutableList(),
-            onReordered = { updatedList ->
-                updatedList.forEachIndexed { index, entry ->
-                    requireContext().setServicePriority(entry.mimetype, index)
-                }
-            }
-        )
-
-        binding.serviceRecycler.adapter = adapter
-        binding.serviceRecycler.layoutManager = LinearLayoutManager(requireContext())
-
-        val touchHelper = ItemTouchHelper(adapter.dragHelper)
-        touchHelper.attachToRecyclerView(binding.serviceRecycler)
-
-        adapter.setDragStartListener { viewHolder ->
-            touchHelper.startDrag(viewHolder)
+        binding.popupEnabledCheckbox.isChecked = prefs.popupEnabled
+        binding.popupEnabledCheckbox.setOnCheckedChangeListener { _, isChecked ->
+            prefs.popupEnabled = isChecked
+            updateControls(isChecked)
         }
+
+        binding.popupPreview.setOnClickListener {
+            window.preview()
+        }
+
+        binding.redirectionDelay.value = (prefs.redirectionDelay / 1000).toFloat()
+        binding.redirectionDelay.setLabelFormatter {
+            String.format("%.1f", it)
+        }
+        binding.redirectionDelay.addOnChangeListener { _, value, _ ->
+            prefs.redirectionDelay = (value * 1000).toLong()
+        }
+
+        val screenHeight = getScreenHeightPx()
+        binding.popupHeightSlider.valueFrom = 0f
+        binding.popupHeightSlider.valueTo = screenHeight.toFloat()
+        binding.popupHeightSlider.value = prefs.popupPosition.toFloat()
+        binding.popupHeightSlider.addOnChangeListener { _, value, _ ->
+            prefs.popupPosition = value.toInt().coerceIn(0, screenHeight)
+        }
+
+        updateControls(prefs.popupEnabled)
+    }
+
+    private fun updateControls(enabled: Boolean) {
+        binding.redirectionDelay.isEnabled = enabled
+        binding.popupHeightSlider.isEnabled = enabled
+        binding.popupPreview.isEnabled = enabled
     }
 
     private fun getScreenHeightPx(): Int {
diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/PopupWindow.kt b/app/src/main/java/partisan/weforge/xyz/pulse/PopupWindow.kt
index f60b03f..fa81ca6 100644
--- a/app/src/main/java/partisan/weforge/xyz/pulse/PopupWindow.kt
+++ b/app/src/main/java/partisan/weforge/xyz/pulse/PopupWindow.kt
@@ -23,7 +23,6 @@ import kotlin.concurrent.timerTask
 import android.util.Log
 import android.content.res.ColorStateList
 import com.google.android.material.color.DynamicColors
-import com.google.android.material.color.utilities.MaterialDynamicColors
 import com.google.android.material.color.MaterialColors
 
 class PopupWindow(
@@ -121,7 +120,7 @@ class PopupWindow(
     }
 
     @RequiresPermission(Manifest.permission.CALL_PHONE)
-    private fun call(data: Uri) {
+    fun call(data: Uri) {
         Intent(Intent.ACTION_VIEW).apply {
             this.data = data
             flags = Intent.FLAG_ACTIVITY_NEW_TASK
diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt b/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt
index ec22994..d9b87a8 100644
--- a/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt
+++ b/app/src/main/java/partisan/weforge/xyz/pulse/Preferences.kt
@@ -11,6 +11,7 @@ class Preferences(private val context: Context) {
         private const val POPUP_POSITION = "popup_position_y"
         private const val POPUP_ENABLED = "popup_enabled"
         private const val BLACKLISTED_CONTACTS = "blacklisted_contacts"
+        private val SERVICE_ORDER_KEY = "service_order"
 
         private const val DEFAULT_REDIRECTION_DELAY = 2000L
         private const val DEFAULT_POPUP_POSITION = 333
@@ -51,7 +52,18 @@ class Preferences(private val context: Context) {
     }
 
     fun getServicePriority(mimetype: String): Int {
-        return prefs.getInt(makeKeyPriority(mimetype), Int.MAX_VALUE)
+        val order = getServiceOrder()
+        val index = order.indexOf(mimetype)
+        return if (index != -1) index else Int.MAX_VALUE
+    }
+
+    fun getServiceOrder(): List<String> {
+        val stored = prefs.getString(SERVICE_ORDER_KEY, null)
+        return stored?.split("|")?.filter { it.isNotBlank() } ?: emptyList()
+    }
+
+    fun setServiceOrder(order: List<String>) {
+        prefs.edit().putString(SERVICE_ORDER_KEY, order.joinToString("|")).apply()
     }
 
     fun setServiceEnabled(mimetype: String, enabled: Boolean) {
diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt b/app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt
index 8450d28..51550e8 100644
--- a/app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt
+++ b/app/src/main/java/partisan/weforge/xyz/pulse/SecretView.kt
@@ -7,7 +7,6 @@ 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
 
@@ -64,7 +63,7 @@ class SecretView @JvmOverloads constructor(
     private var score = 0
 
     private val bullets = mutableListOf<Bullet>()
-    private val mediumBulletsToFire = mutableListOf<Bullet>()
+    private val enemyBullets = mutableListOf<Bullet>()
     private val enemies = mutableListOf<Enemy>()
     private val rockets = mutableListOf<Rocket>()
     private val stars = mutableListOf<Star>()
@@ -164,9 +163,9 @@ class SecretView @JvmOverloads constructor(
         }
         rockets.removeIf { it.x < 0 || it.x > viewWidth || it.y < 0 || it.y > viewHeight }
 
-        bullets.addAll(mediumBulletsToFire)
-        mediumBulletsToFire.clear()
-
+        enemyBullets.forEach { it.y += it.dy * deltaMs / 16f }
+        enemyBullets.removeIf { it.y > viewHeight }
+        
         checkCollisions()
         spawnEnemies(deltaMs)
     }
@@ -190,6 +189,14 @@ class SecretView @JvmOverloads constructor(
             }
         }
 
+        for (b in enemyBullets) {
+            if (hypot(b.x - playerX, b.y - (viewHeight - 100f)) < 20f) {
+                explosions.add(Explosion(playerX, viewHeight - 100f))
+                gameOver = true
+                return
+            }
+        }
+
         val rocketIter = rockets.iterator()
         while (rocketIter.hasNext()) {
             val rocket = rocketIter.next()
@@ -258,7 +265,7 @@ class SecretView @JvmOverloads constructor(
                 // 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) }
+                    "medium" -> EnemyMedium(x, baseY, sharedOffset, sharedFireTime) { enemyBullets.add(it) }
                     "hard" -> EnemyHard(x, baseY, { rockets.add(it) }, sharedOffset, sharedFireTime)
                     else -> EnemyEasy(x, baseY)
                 }
@@ -271,6 +278,7 @@ class SecretView @JvmOverloads constructor(
 
     private fun resetGame() {
         bullets.clear()
+        enemyBullets.clear()
         enemies.clear()
         rockets.clear()
         explosions.clear()
@@ -280,6 +288,8 @@ class SecretView @JvmOverloads constructor(
         enemiesLeftInWave = 0
         gameOver = false
         playerX = viewWidth / 2f
+        lastLogicTime = System.nanoTime()
+        Choreographer.getInstance().postFrameCallback(this)
     }
 
     override fun onDraw(canvas: Canvas) {
@@ -289,6 +299,8 @@ class SecretView @JvmOverloads constructor(
         canvas.drawColor(Color.parseColor("#121212"))
         stars.forEach { canvas.drawCircle(it.x, it.y, it.radius, starPaint) }
 
+        enemyBullets.forEach { canvas.drawLine(it.x, it.y, it.x, it.y + 20f, rocketPaint) }
+
         rockets.forEach { rocket ->
             rocket.trail.forEachIndexed { index, (x, y) ->
                 val alpha = ((1f - index / 20f.toFloat()) * 255).toInt()
@@ -328,17 +340,14 @@ class SecretView @JvmOverloads constructor(
         } 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)
+            canvas.drawText("Tap to restart", 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
-            }
+            resetGame()
+            return true
         }
 
         when (event.action) {
@@ -419,7 +428,7 @@ class SecretView @JvmOverloads constructor(
         override var y: Float,
         private val offset: Float,
         private var fireTimer: Long,
-        val fireBullet: (Bullet) -> Unit
+        val fireEnemyBullet: (Bullet) -> Unit
     ) : Enemy {
         override fun update(deltaMs: Long) {
             y += 2.5f * deltaMs / 16f
@@ -427,7 +436,7 @@ class SecretView @JvmOverloads constructor(
 
             fireTimer -= deltaMs
             if (fireTimer <= 0) {
-                fireBullet(Bullet(x, y + 30f, dy = 10f))
+                fireEnemyBullet(Bullet(x, y + 30f, dy = 10f))
                 fireTimer = Random.nextLong(3000L, 6000L)
             }
         }
diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/ServicesFragment.kt b/app/src/main/java/partisan/weforge/xyz/pulse/ServicesFragment.kt
index 104ceeb..2d82657 100644
--- a/app/src/main/java/partisan/weforge/xyz/pulse/ServicesFragment.kt
+++ b/app/src/main/java/partisan/weforge/xyz/pulse/ServicesFragment.kt
@@ -26,7 +26,7 @@ class ServiceSettingsFragment : Fragment() {
     override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
 
-        val services = listOf(
+        val available = listOf(
             ServiceEntry(
                 "vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call",
                 R.string.destination_signal,
@@ -49,13 +49,24 @@ class ServiceSettingsFragment : Fragment() {
             ),
         )
 
+        val storedOrder = Preferences(requireContext()).getServiceOrder()
+
+        val ordered = storedOrder.mapNotNull { mime ->
+            available.find { it.mimetype == mime }
+        }.toMutableList()
+
+        // Add any missing services that weren't stored (e.g., first run)
+        val missing = available.filterNot { s -> ordered.any { it.mimetype == s.mimetype } }
+        ordered += missing
+
+        val prefs = Preferences(requireContext())
+
         val adapter = ServiceAdapter(
             context = requireContext(),
-            services = services.toMutableList(),
+            services = ordered,
             onReordered = { updatedList ->
-                updatedList.forEachIndexed { index, entry ->
-                    requireContext().setServicePriority(entry.mimetype, index)
-                }
+                val newOrder = updatedList.map { it.mimetype }
+                prefs.setServiceOrder(newOrder)
             }
         )
 
diff --git a/app/src/main/java/partisan/weforge/xyz/pulse/WelcomeActivity.kt b/app/src/main/java/partisan/weforge/xyz/pulse/WelcomeActivity.kt
index 34dbc90..0b5a912 100644
--- a/app/src/main/java/partisan/weforge/xyz/pulse/WelcomeActivity.kt
+++ b/app/src/main/java/partisan/weforge/xyz/pulse/WelcomeActivity.kt
@@ -1,9 +1,7 @@
 package partisan.weforge.xyz.pulse
 
-import android.Manifest
 import android.app.role.RoleManager
 import android.content.Intent
-import android.content.pm.PackageManager
 import android.os.Bundle
 import android.provider.Settings
 import android.view.View
diff --git a/app/src/main/res/layout/fragment_popup_settings.xml b/app/src/main/res/layout/fragment_popup_settings.xml
index 2afe2ae..e11fade 100644
--- a/app/src/main/res/layout/fragment_popup_settings.xml
+++ b/app/src/main/res/layout/fragment_popup_settings.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<androidx.constraintlayout.widget.ConstraintLayout
+<ScrollView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     xmlns:tools="http://schemas.android.com/tools"
@@ -8,82 +8,88 @@
     android:padding="32dp"
     tools:context=".PopupSettingsFragment">
 
-    <TextView
-        android:id="@+id/description"
-        android:layout_width="0dp"
-        android:layout_height="wrap_content"
-        android:text="@string/popup_settings_description"
-        android:textSize="16sp"
-        app:layout_constraintTop_toTopOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent" />
-
-    <CheckBox
-        android:id="@+id/popupEnabledCheckbox"
+    <androidx.constraintlayout.widget.ConstraintLayout
         android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/popup_enabled"
-        android:textSize="14sp"
-        android:layout_marginTop="16dp"
-        app:layout_constraintTop_toBottomOf="@id/description"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintEnd_toEndOf="parent" />
+        android:layout_height="wrap_content">
 
-    <Button
-        android:id="@+id/popupPreview"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:text="@string/test"
-        android:layout_marginTop="8dp"
-        app:layout_constraintTop_toBottomOf="@id/popupEnabledCheckbox"
-        app:layout_constraintEnd_toEndOf="parent" />
+        <TextView
+            android:id="@+id/description"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:text="@string/popup_settings_description"
+            android:textSize="16sp"
+            app:layout_constraintTop_toTopOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent" />
 
-    <com.google.android.material.slider.Slider
-        android:id="@+id/redirectionDelay"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:stepSize="0.5"
-        android:valueFrom="2"
-        android:valueTo="4"
-        android:layout_marginTop="8dp"
-        android:contentDescription="@string/redirection_delay_description" />
+        <CheckBox
+            android:id="@+id/popupEnabledCheckbox"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:text="@string/popup_enabled"
+            android:textSize="14sp"
+            app:layout_constraintTop_toBottomOf="@id/description"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toStartOf="@id/popupPreview"
+            android:layout_marginTop="16dp" />
 
-    <TextView
-        android:id="@+id/delayDescription"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/redirection_delay_description"
-        android:textSize="12sp"
-        android:layout_marginTop="4dp" />
+        <Button
+            android:id="@+id/popupPreview"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:text="@string/test"
+            app:layout_constraintTop_toTopOf="@id/popupEnabledCheckbox"
+            app:layout_constraintBottom_toBottomOf="@id/popupEnabledCheckbox"
+            app:layout_constraintEnd_toEndOf="parent"
+            android:layout_marginStart="8dp" />
 
-    <com.google.android.material.slider.Slider
-        android:id="@+id/popupHeightSlider"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:valueFrom="0"
-        android:valueTo="100"
-        android:stepSize="1"
-        android:layout_marginTop="16dp"
-        android:contentDescription="@string/popup_position" />
+        <com.google.android.material.slider.Slider
+            android:id="@+id/redirectionDelay"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:stepSize="0.5"
+            android:valueFrom="2"
+            android:valueTo="4"
+            android:contentDescription="@string/redirection_delay_description"
+            app:layout_constraintTop_toBottomOf="@id/popupEnabledCheckbox"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            android:layout_marginTop="16dp" />
 
-    <TextView
-        android:id="@+id/heightDescription"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:text="@string/popup_position"
-        android:textSize="12sp"
-        android:layout_marginTop="4dp" />
+        <TextView
+            android:id="@+id/delayDescription"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:text="@string/redirection_delay_description"
+            android:textSize="12sp"
+            app:layout_constraintTop_toBottomOf="@id/redirectionDelay"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            android:layout_marginTop="4dp" />
 
-    <View
-        android:layout_width="match_parent"
-        android:layout_height="1dp"
-        android:layout_marginVertical="16dp"
-        android:background="?android:attr/listDivider" />
+        <com.google.android.material.slider.Slider
+            android:id="@+id/popupHeightSlider"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:valueFrom="0"
+            android:valueTo="100"
+            android:stepSize="1"
+            android:contentDescription="@string/popup_position"
+            app:layout_constraintTop_toBottomOf="@id/delayDescription"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            android:layout_marginTop="16dp" />
 
-    <androidx.recyclerview.widget.RecyclerView
-        android:id="@+id/serviceRecycler"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:scrollbars="vertical"
-        android:layout_marginTop="8dp" />
-</androidx.constraintlayout.widget.ConstraintLayout>
+        <TextView
+            android:id="@+id/heightDescription"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:text="@string/popup_position"
+            android:textSize="12sp"
+            app:layout_constraintTop_toBottomOf="@id/popupHeightSlider"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintEnd_toEndOf="parent"
+            android:layout_marginTop="4dp" />
+
+    </androidx.constraintlayout.widget.ConstraintLayout>
+</ScrollView>