Fancy toggle button for main menu

This commit is contained in:
partisan 2025-05-14 14:31:05 +02:00
parent 1691891a4d
commit fafe7e2cd5
11 changed files with 109 additions and 19 deletions

View file

@ -1,27 +1,43 @@
package partisan.weforge.xyz.pulse package partisan.weforge.xyz.pulse
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.ToggleButton
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import com.google.android.material.button.MaterialButton
class MainFragment : Fragment() { class MainFragment : Fragment() {
private lateinit var prefs: Preferences private lateinit var prefs: Preferences
override fun onCreateView( override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?, inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
val view = inflater.inflate(R.layout.fragment_main, container, false) val view = inflater.inflate(R.layout.fragment_main, container, false)
prefs = Preferences(requireContext()) prefs = Preferences(requireContext())
val toggle = view.findViewById<ToggleButton>(R.id.toggle) val toggle = view.findViewById<MaterialButton>(R.id.toggle)
toggle.isChecked = prefs.isEnabled toggle.isCheckable = true
toggle.setOnCheckedChangeListener { _, isChecked -> toggle.isChecked = prefs.isServiceEnabledByUser
prefs.isEnabled = isChecked
toggle.setOnClickListener {
// the button toggles itself internally since it's checkable
val isNowChecked = toggle.isChecked
prefs.isServiceEnabledByUser = isNowChecked
// Log.d("ButtonState", """
// User toggle: $isNowChecked
// Prefs effective state (hasPerms): ${prefs.isEnabled}
// """.trimIndent())
toggle.post {
toggle.jumpDrawablesToCurrentState()
toggle.invalidate()
}
} }
return view return view

View file

@ -4,9 +4,9 @@ import android.content.Context
import androidx.core.content.edit import androidx.core.content.edit
import androidx.preference.PreferenceManager import androidx.preference.PreferenceManager
class Preferences(ctx: Context) { class Preferences(private val context: Context) {
companion object { companion object {
private const val ENABLED = "enabled"
private const val REDIRECTION_DELAY = "redirection_delay" private const val REDIRECTION_DELAY = "redirection_delay"
private const val POPUP_POSITION = "popup_position_y" private const val POPUP_POSITION = "popup_position_y"
private const val POPUP_ENABLED = "popup_enabled" private const val POPUP_ENABLED = "popup_enabled"
@ -17,11 +17,19 @@ class Preferences(ctx: Context) {
private const val SERVICE_ENABLED = "service_enabled" private const val SERVICE_ENABLED = "service_enabled"
} }
private val prefs = PreferenceManager.getDefaultSharedPreferences(ctx) private val prefs = PreferenceManager.getDefaultSharedPreferences(context)
var isEnabled: Boolean // Whether user enabled/disabled the service manually by tiggle button
get() = prefs.getBoolean(ENABLED, prefs.getBoolean(SERVICE_ENABLED, false)) var isServiceEnabledByUser: Boolean
set(value) = prefs.edit { putBoolean(ENABLED, value) } get() = prefs.getBoolean(SERVICE_ENABLED, false)
set(value) = prefs.edit { putBoolean(SERVICE_ENABLED, value) }
// True only if all required permissions + toggle are satisfied
val isEnabled: Boolean
get() = isServiceEnabledByUser &&
hasGeneralPermissions(context) &&
hasDrawOverlays(context) &&
hasCallRedirectionRole(context)
var popupEnabled: Boolean var popupEnabled: Boolean
get() = prefs.getBoolean(POPUP_ENABLED, true) get() = prefs.getBoolean(POPUP_ENABLED, true)

View file

@ -64,7 +64,6 @@ class WelcomeActivity : AppCompatActivity() {
requestRoleLauncher.launch(roleManager?.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION)) requestRoleLauncher.launch(roleManager?.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION))
} }
else -> { else -> {
prefs.isEnabled = true
showConfettiAndContinue() showConfettiAndContinue()
} }
} }

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="?attr/colorPrimary"/>
<item android:color="?attr/colorSurfaceContainer"/>
</selector>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:color="?attr/colorOnPrimaryContainer"/>
<item android:color="?attr/colorOnSurfaceVariant"/>
</selector>

View file

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="?attr/colorControlNormal"
android:pathData="M13,3h-2v10h2L13,3zM17.83,5.17l-1.42,1.42C17.99,7.86 19,9.81 19,12c0,3.87 -3.13,7 -7,7s-7,-3.13 -7,-7c0,-2.19 1.01,-4.14 2.58,-5.42L6.17,5.17C4.23,6.82 3,9.26 3,12c0,4.97 4.03,9 9,9s9,-4.03 9,-9c0,-2.74 -1.23,-5.18 -3.17,-6.83z"/>
</vector>

View file

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true">
<shape android:shape="oval">
<solid android:color="?attr/colorPrimary" />
</shape>
</item>
<item>
<shape android:shape="oval">
<solid android:color="?attr/colorSurfaceContainerLowest" />
</shape>
</item>
</selector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid android:color="?attr/colorSurfaceContainerLowest" />
<stroke
android:width="2dp"
android:color="?attr/colorOutline"/>
</shape>

View file

@ -1,12 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:gravity="center"> android:gravity="center">
<ToggleButton <View
android:layout_width="104dp"
android:layout_height="104dp"
android:layout_gravity="center"
android:background="@drawable/toggle_button_bg_outline" />
<com.google.android.material.button.MaterialButton
android:id="@+id/toggle" android:id="@+id/toggle"
android:layout_width="wrap_content" android:layout_width="96dp"
android:layout_height="wrap_content" android:layout_height="96dp"
android:textSize="16sp" /> android:layout_gravity="center"
android:checkable="true"
android:insetTop="0dp"
android:insetBottom="0dp"
android:insetLeft="0dp"
android:insetRight="0dp"
android:text=""
app:icon="@drawable/ic_power_settings_new_24"
app:iconTint="@color/toggle_button_icon"
app:backgroundTint="@color/toggle_button_bg"
app:iconSize="48dp"
app:iconGravity="textTop"
app:iconPadding="0dp"
app:cornerRadius="48dp" />
</FrameLayout> </FrameLayout>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<style name="RoundIconShape" parent="">
<item name="cornerFamily">rounded</item>
<item name="cornerSize">100%</item>
</style>
</resources>

View file

@ -8,8 +8,9 @@
<item name="colorSecondary">@color/teal_200</item> <item name="colorSecondary">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item> <item name="colorOnSecondary">@color/black</item>
<item name="colorSurface">@color/white</item> <item name="colorSurface">@color/white</item>
<item name="colorOnSurface">@color/black</item> <item name="colorOnSurface">@color/black</item>
<item name="textAppearanceBodyMedium">@style/TextAppearance.Material3.BodyMedium</item> <item name="textAppearanceBodyMedium">@style/TextAppearance.Material3.BodyMedium</item>
<item name="colorSurfaceContainerLowest">?attr/colorSurfaceContainerLowest</item>
</style> </style>
</resources> </resources>