General fixes, UI cleanup and popup animations
This commit is contained in:
parent
e810208a14
commit
6d9024a580
20 changed files with 457 additions and 77 deletions
|
@ -25,6 +25,13 @@ class AboutFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(requireActivity() as? MainActivity)?.setAppBarTitle(
|
||||
getString(R.string.about_name)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
|
@ -63,9 +63,16 @@ class CallRedirectionService : CallRedirectionService() {
|
|||
return
|
||||
}
|
||||
|
||||
val phoneNumber = handle.schemeSpecificPart
|
||||
|
||||
if (prefs.isBlacklistEnabled && !prefs.isContactWhitelisted(phoneNumber)) {
|
||||
placeCallUnmodified()
|
||||
return
|
||||
}
|
||||
|
||||
val records: Array<Record>
|
||||
try {
|
||||
records = getRecordsFromPhoneNumber(handle.schemeSpecificPart)
|
||||
records = getRecordsFromPhoneNumber(phoneNumber)
|
||||
} catch (exc: SecurityException) {
|
||||
placeCallUnmodified()
|
||||
return
|
||||
|
|
|
@ -26,6 +26,27 @@ class ContactsFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(requireActivity() as? MainActivity)?.apply {
|
||||
setAppBarTitle(getString(R.string.settings_name), getString(R.string.whitelist_name))
|
||||
setupPopupToggle(true, prefs.isBlacklistEnabled) { isChecked ->
|
||||
prefs.isBlacklistEnabled = isChecked
|
||||
binding.contactRecycler.isEnabled = isChecked
|
||||
binding.contactRecycler.alpha = if (isChecked) 1f else 0.4f
|
||||
}
|
||||
}
|
||||
|
||||
// Initial state
|
||||
binding.contactRecycler.isEnabled = prefs.isBlacklistEnabled
|
||||
binding.contactRecycler.alpha = if (prefs.isBlacklistEnabled) 1f else 0.4f
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
(requireActivity() as? MainActivity)?.setupPopupToggle(false)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
|
@ -2,6 +2,9 @@ package partisan.weforge.xyz.pulse
|
|||
|
||||
import android.content.Intent
|
||||
import android.os.Bundle
|
||||
import android.view.View
|
||||
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
|
||||
|
@ -9,17 +12,24 @@ import partisan.weforge.xyz.pulse.REQUIRED_PERMISSIONS
|
|||
import partisan.weforge.xyz.pulse.hasCallRedirectionRole
|
||||
import partisan.weforge.xyz.pulse.hasDrawOverlays
|
||||
import partisan.weforge.xyz.pulse.hasGeneralPermissions
|
||||
import com.google.android.material.switchmaterial.SwitchMaterial
|
||||
|
||||
class MainActivity : AppCompatActivity() {
|
||||
|
||||
private lateinit var binding: ActivityMainBinding
|
||||
private lateinit var prefs: Preferences
|
||||
|
||||
private var popupSwitch: SwitchMaterial? = null
|
||||
private var popupMenuItem: MenuItem? = null
|
||||
|
||||
val popupToggle: SwitchMaterial
|
||||
get() = findViewById(R.id.globalPopupToggle)
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
binding = ActivityMainBinding.inflate(layoutInflater)
|
||||
setContentView(binding.root)
|
||||
|
||||
prefs = Preferences(this)
|
||||
setSupportActionBar(binding.topAppBar)
|
||||
|
||||
val drawerToggle = ActionBarDrawerToggle(
|
||||
|
@ -27,7 +37,7 @@ class MainActivity : AppCompatActivity() {
|
|||
binding.drawerLayout,
|
||||
binding.topAppBar,
|
||||
R.string.navigation_drawer_open,
|
||||
R.string.navigation_drawer_close
|
||||
R.string.navigation_drawer_open // The "close" string is never actually shown in the UI, so I reuse "navigation_drawer_open" as sort of a placeholder
|
||||
)
|
||||
binding.drawerLayout.addDrawerListener(drawerToggle)
|
||||
drawerToggle.syncState()
|
||||
|
@ -36,6 +46,8 @@ class MainActivity : AppCompatActivity() {
|
|||
.replace(R.id.fragmentContainer, MainFragment())
|
||||
.commit()
|
||||
|
||||
setupPopupToggle(false)
|
||||
|
||||
binding.navigationView.setNavigationItemSelectedListener { item ->
|
||||
when (item.itemId) {
|
||||
R.id.action_popup_settings -> {
|
||||
|
@ -73,6 +85,33 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
override fun onCreateOptionsMenu(menu: Menu): Boolean {
|
||||
menuInflater.inflate(R.menu.topbar_toggle, menu)
|
||||
popupMenuItem = menu.findItem(R.id.globalPopupToggle)
|
||||
popupSwitch = popupMenuItem?.actionView?.findViewById(R.id.globalPopupToggle)
|
||||
popupMenuItem?.isVisible = false // hide by default
|
||||
return true
|
||||
}
|
||||
|
||||
fun setupPopupToggle(
|
||||
visible: Boolean,
|
||||
initialState: Boolean = false,
|
||||
onToggle: ((Boolean) -> Unit)? = null
|
||||
) {
|
||||
popupMenuItem?.isVisible = visible
|
||||
popupSwitch?.apply {
|
||||
setOnCheckedChangeListener(null)
|
||||
isChecked = initialState
|
||||
setOnCheckedChangeListener { _, isChecked ->
|
||||
onToggle?.invoke(isChecked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setAppBarTitle(vararg parts: String) {
|
||||
binding.topAppBar.title = parts.joinToString(" > ")
|
||||
}
|
||||
|
||||
private fun hasPermissions(): Boolean {
|
||||
return hasGeneralPermissions(this) &&
|
||||
hasDrawOverlays(this) &&
|
||||
|
|
|
@ -18,6 +18,11 @@ class MainFragment : Fragment() {
|
|||
private lateinit var prefs: Preferences
|
||||
private var lastConfettiTime = 0L
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(requireActivity() as? MainActivity)?.setAppBarTitle(getString(R.string.app_name))
|
||||
}
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
package partisan.weforge.xyz.pulse
|
||||
|
||||
import android.content.Context
|
||||
import android.graphics.*
|
||||
import android.view.View
|
||||
import kotlin.random.Random
|
||||
|
||||
class MatrixRainView(context: Context) : View(context) {
|
||||
private val paint = Paint(Paint.ANTI_ALIAS_FLAG).apply {
|
||||
color = Color.GREEN
|
||||
textSize = 5f * resources.displayMetrics.density
|
||||
typeface = Typeface.MONOSPACE
|
||||
}
|
||||
|
||||
private val charset = "01アイウエオカキクケコ".toCharArray()
|
||||
private val random = Random
|
||||
private var columns = 0
|
||||
private lateinit var yOffsets: IntArray
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
columns = w / paint.textSize.toInt()
|
||||
yOffsets = IntArray(columns) { random.nextInt(h) }
|
||||
}
|
||||
|
||||
override fun onDraw(canvas: Canvas) {
|
||||
// drawColor with transparent clear instead of black
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
||||
|
||||
for (i in 0 until columns) {
|
||||
val x = i * paint.textSize
|
||||
val y = yOffsets[i].toFloat()
|
||||
val char = charset[random.nextInt(charset.size)]
|
||||
|
||||
paint.alpha = 255
|
||||
canvas.drawText(char.toString(), x, y, paint)
|
||||
|
||||
paint.alpha = 100
|
||||
canvas.drawText(char.toString(), x, y - paint.textSize, paint)
|
||||
|
||||
yOffsets[i] += paint.textSize.toInt()
|
||||
if (yOffsets[i] > height) {
|
||||
yOffsets[i] = 0
|
||||
}
|
||||
}
|
||||
postInvalidateDelayed(50)
|
||||
}
|
||||
}
|
|
@ -7,6 +7,10 @@ import android.util.DisplayMetrics
|
|||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.AdapterView
|
||||
import android.widget.CompoundButton
|
||||
import android.widget.Spinner
|
||||
import androidx.fragment.app.Fragment
|
||||
import androidx.core.content.getSystemService
|
||||
import partisan.weforge.xyz.pulse.databinding.FragmentPopupSettingsBinding
|
||||
|
@ -28,20 +32,35 @@ class PopupSettingsFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(requireActivity() as? MainActivity)?.apply {
|
||||
setAppBarTitle(getString(R.string.settings_name), getString(R.string.popup_name))
|
||||
setupPopupToggle(true, prefs.popupEnabled) { isChecked ->
|
||||
prefs.popupEnabled = isChecked
|
||||
updateControls(isChecked)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onPause() {
|
||||
super.onPause()
|
||||
(requireActivity() as? MainActivity)?.setupPopupToggle(false)
|
||||
}
|
||||
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
prefs = Preferences(requireContext())
|
||||
window = PopupWindow(requireContext(), null)
|
||||
|
||||
binding.popupEnabledCheckbox.isChecked = prefs.popupEnabled
|
||||
binding.popupEnabledCheckbox.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.popupEnabled = isChecked
|
||||
updateControls(isChecked)
|
||||
}
|
||||
|
||||
binding.popupPreview.setOnClickListener {
|
||||
window.preview()
|
||||
window.preview(false)
|
||||
}
|
||||
binding.popupPreview.setOnLongClickListener {
|
||||
window.preview(true)
|
||||
true
|
||||
}
|
||||
|
||||
binding.redirectionDelay.value = (prefs.redirectionDelay / 1000).toFloat()
|
||||
|
@ -52,6 +71,22 @@ class PopupSettingsFragment : Fragment() {
|
|||
prefs.redirectionDelay = (value * 1000).toLong()
|
||||
}
|
||||
|
||||
val effectNames = resources.getStringArray(R.array.popup_effects)
|
||||
val adapter = ArrayAdapter(requireContext(), android.R.layout.simple_spinner_item, effectNames)
|
||||
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
binding.popupEffectSpinner.adapter = adapter
|
||||
|
||||
// Select current setting
|
||||
binding.popupEffectSpinner.setSelection(prefs.popupEffect.ordinal)
|
||||
|
||||
binding.popupEffectSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>, view: View?, position: Int, id: Long) {
|
||||
prefs.popupEffect = Preferences.PopupEffect.values()[position]
|
||||
}
|
||||
|
||||
override fun onNothingSelected(parent: AdapterView<*>) {}
|
||||
}
|
||||
|
||||
val screenHeight = getScreenHeightPx()
|
||||
binding.popupHeightSlider.valueFrom = 0f
|
||||
binding.popupHeightSlider.valueTo = screenHeight.toFloat()
|
||||
|
@ -67,6 +102,8 @@ class PopupSettingsFragment : Fragment() {
|
|||
binding.redirectionDelay.isEnabled = enabled
|
||||
binding.popupHeightSlider.isEnabled = enabled
|
||||
binding.popupPreview.isEnabled = enabled
|
||||
binding.popupEffectSpinner.isEnabled = enabled
|
||||
binding.popupEffectLabel.isEnabled = enabled
|
||||
}
|
||||
|
||||
private fun getScreenHeightPx(): Int {
|
||||
|
|
|
@ -5,15 +5,19 @@ import android.content.Context
|
|||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.PixelFormat
|
||||
import android.graphics.Rect
|
||||
import android.media.AudioManager
|
||||
import android.net.Uri
|
||||
import android.view.Gravity
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.view.ContextThemeWrapper
|
||||
import android.widget.TextView
|
||||
import android.animation.ObjectAnimator
|
||||
import android.widget.ProgressBar
|
||||
import android.view.animation.OvershootInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.annotation.RequiresPermission
|
||||
import androidx.core.content.res.use
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
|
@ -24,6 +28,8 @@ import android.util.Log
|
|||
import android.content.res.ColorStateList
|
||||
import com.google.android.material.color.DynamicColors
|
||||
import com.google.android.material.color.MaterialColors
|
||||
import partisan.weforge.xyz.pulse.Preferences.PopupEffect
|
||||
import partisan.weforge.xyz.pulse.MatrixRainView
|
||||
|
||||
class PopupWindow(
|
||||
ctx: Context,
|
||||
|
@ -46,7 +52,9 @@ class PopupWindow(
|
|||
height = WindowManager.LayoutParams.WRAP_CONTENT
|
||||
y = prefs.popupPosition
|
||||
}
|
||||
private var timer: Timer? = null
|
||||
private var currentEffect: PopupEffect = PopupEffect.NONE
|
||||
private var matrixOverlay: View? = null
|
||||
private var timer: Timer? = null
|
||||
|
||||
init {
|
||||
view.setOnClickListener {
|
||||
|
@ -58,16 +66,25 @@ class PopupWindow(
|
|||
applyResolvedColors(view)
|
||||
}
|
||||
|
||||
fun preview() {
|
||||
fun preview(isLongPress: Boolean = false) {
|
||||
remove()
|
||||
layoutParams.y = prefs.popupPosition
|
||||
|
||||
val destinations = listOf(
|
||||
R.string.destination_signal,
|
||||
R.string.destination_telegram,
|
||||
R.string.destination_threema,
|
||||
// Whatsapp smells
|
||||
)
|
||||
setDescription(destinations.random())
|
||||
add()
|
||||
|
||||
val duration = if (isLongPress) prefs.redirectionDelay * 5 else prefs.redirectionDelay
|
||||
timer?.cancel()
|
||||
timer = Timer()
|
||||
timer?.schedule(timerTask {
|
||||
remove()
|
||||
}, duration)
|
||||
}
|
||||
|
||||
fun show(uri: Uri, destinationId: Int) {
|
||||
|
@ -128,10 +145,125 @@ class PopupWindow(
|
|||
}
|
||||
}
|
||||
|
||||
private fun animateAppear() {
|
||||
view.animate().cancel()
|
||||
view.rotationX = 0f
|
||||
view.scaleX = 1f
|
||||
view.scaleY = 1f
|
||||
view.alpha = 1f
|
||||
|
||||
val effect = when (prefs.popupEffect) {
|
||||
PopupEffect.RANDOM -> PopupEffect.values().filter { it != PopupEffect.RANDOM && it != PopupEffect.NONE }.random()
|
||||
else -> prefs.popupEffect
|
||||
}
|
||||
currentEffect = effect
|
||||
|
||||
when (effect) {
|
||||
PopupEffect.NONE -> {}
|
||||
PopupEffect.FADE -> {
|
||||
view.alpha = 0f
|
||||
view.animate().alpha(1f).setDuration(300).start()
|
||||
}
|
||||
PopupEffect.SCALE -> {
|
||||
view.scaleX = 0f
|
||||
view.scaleY = 0f
|
||||
view.animate().scaleX(1f).scaleY(1f).setDuration(300).start()
|
||||
}
|
||||
PopupEffect.BOUNCE -> {
|
||||
view.scaleX = 0.7f
|
||||
view.scaleY = 0.7f
|
||||
view.animate().scaleX(1f).scaleY(1f)
|
||||
.setInterpolator(OvershootInterpolator())
|
||||
.setDuration(400).start()
|
||||
}
|
||||
PopupEffect.FLOP -> {
|
||||
view.rotationX = 90f
|
||||
view.alpha = 0f
|
||||
view.animate().rotationX(0f).alpha(1f)
|
||||
.setDuration(350)
|
||||
.setInterpolator(DecelerateInterpolator())
|
||||
.start()
|
||||
}
|
||||
PopupEffect.MATRIX -> {
|
||||
val rainView = MatrixRainView(themedCtx)
|
||||
matrixOverlay?.let {
|
||||
try {
|
||||
windowManager?.removeViewImmediate(it)
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
matrixOverlay = rainView
|
||||
|
||||
val popupBounds = Rect()
|
||||
view.getGlobalVisibleRect(popupBounds)
|
||||
|
||||
val overlayParams = WindowManager.LayoutParams().apply {
|
||||
type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY
|
||||
format = PixelFormat.TRANSLUCENT
|
||||
flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
|
||||
width = view.width
|
||||
height = view.height
|
||||
gravity = Gravity.BOTTOM
|
||||
x = 0
|
||||
y = layoutParams.y
|
||||
}
|
||||
|
||||
try {
|
||||
windowManager?.addView(rainView, overlayParams)
|
||||
} catch (e: Exception) {
|
||||
Log.e("MatrixRain", "Failed to add rainView", e)
|
||||
return
|
||||
}
|
||||
|
||||
// Fade-in popup over 500ms
|
||||
view.alpha = 0f
|
||||
view.animate().cancel()
|
||||
view.animate().alpha(1f).setDuration(500).start()
|
||||
|
||||
// Remove MatrixRainView in sync
|
||||
rainView.animate().alpha(0f).setDuration(500).withEndAction {
|
||||
try {
|
||||
windowManager?.removeView(rainView)
|
||||
matrixOverlay = null
|
||||
} catch (_: Exception) {}
|
||||
}.start()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
}
|
||||
|
||||
private fun animateDisappear(onEnd: () -> Unit) {
|
||||
when (currentEffect) {
|
||||
PopupEffect.NONE -> onEnd()
|
||||
PopupEffect.FADE -> view.animate().alpha(0f).setDuration(200).withEndAction(onEnd).start()
|
||||
PopupEffect.SCALE, PopupEffect.BOUNCE -> view.animate().scaleX(0f).scaleY(0f).setDuration(200).withEndAction(onEnd).start()
|
||||
PopupEffect.FLOP -> view.animate().rotationX(90f).alpha(0f).setDuration(200).withEndAction(onEnd).start()
|
||||
PopupEffect.MATRIX -> {
|
||||
view.animate().alpha(0f).setDuration(150).withEndAction(onEnd).start()
|
||||
matrixOverlay?.let { overlay ->
|
||||
overlay.animate().cancel()
|
||||
overlay.animate().alpha(0f).setDuration(150).withEndAction {
|
||||
try {
|
||||
windowManager?.removeViewImmediate(overlay)
|
||||
} catch (_: Exception) {}
|
||||
matrixOverlay = null
|
||||
}.start()
|
||||
}
|
||||
}
|
||||
else -> onEnd()
|
||||
}
|
||||
}
|
||||
|
||||
private fun add(): Boolean {
|
||||
try {
|
||||
// If already attached, force remove and re-add
|
||||
if (view.parent != null) {
|
||||
windowManager?.removeViewImmediate(view)
|
||||
}
|
||||
view.animate().cancel()
|
||||
windowManager?.addView(view, layoutParams)
|
||||
} catch (exc: WindowManager.BadTokenException) {
|
||||
animateAppear()
|
||||
} catch (exc: Exception) {
|
||||
Log.e("PopupWindow", "Failed to add popup view", exc)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
@ -139,9 +271,18 @@ class PopupWindow(
|
|||
|
||||
private fun remove(): Boolean {
|
||||
try {
|
||||
windowManager?.removeView(view)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
} catch (_: WindowManager.BadTokenException) {
|
||||
animateDisappear {
|
||||
try {
|
||||
windowManager?.removeView(view)
|
||||
matrixOverlay?.let {
|
||||
try {
|
||||
windowManager?.removeViewImmediate(it)
|
||||
} catch (_: Exception) {}
|
||||
matrixOverlay = null
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
} catch (_: Exception) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -10,7 +10,9 @@ class Preferences(private val context: Context) {
|
|||
private const val REDIRECTION_DELAY = "redirection_delay"
|
||||
private const val POPUP_POSITION = "popup_position_y"
|
||||
private const val POPUP_ENABLED = "popup_enabled"
|
||||
private val POPUP_EFFECT = "popup_effect"
|
||||
private const val BLACKLISTED_CONTACTS = "blacklisted_contacts"
|
||||
private const val BLACKLIST_ENABLED = "blacklist_enabled"
|
||||
private val SERVICE_ORDER_KEY = "service_order"
|
||||
|
||||
private const val DEFAULT_REDIRECTION_DELAY = 2000L
|
||||
|
@ -32,6 +34,29 @@ class Preferences(private val context: Context) {
|
|||
hasDrawOverlays(context) &&
|
||||
hasCallRedirectionRole(context)
|
||||
|
||||
enum class PopupEffect {
|
||||
NONE, FADE, SCALE, BOUNCE, FLOP, MATRIX, RANDOM
|
||||
}
|
||||
|
||||
var popupEffect: PopupEffect
|
||||
get() {
|
||||
val name = prefs.getString(POPUP_EFFECT, PopupEffect.FADE.name) ?: PopupEffect.FADE.name
|
||||
return try {
|
||||
PopupEffect.valueOf(name)
|
||||
} catch (_: IllegalArgumentException) {
|
||||
// If invalid, fallback and clear the broken value
|
||||
prefs.edit().remove(POPUP_EFFECT).apply()
|
||||
PopupEffect.BOUNCE
|
||||
}
|
||||
}
|
||||
set(value) {
|
||||
prefs.edit().putString(POPUP_EFFECT, value.name).apply()
|
||||
}
|
||||
|
||||
var isBlacklistEnabled: Boolean
|
||||
get() = prefs.getBoolean(BLACKLIST_ENABLED, false)
|
||||
set(value) = prefs.edit { putBoolean(BLACKLIST_ENABLED, value) }
|
||||
|
||||
var popupEnabled: Boolean
|
||||
get() = prefs.getBoolean(POPUP_ENABLED, true)
|
||||
set(value) = prefs.edit { putBoolean(POPUP_ENABLED, value) }
|
||||
|
|
|
@ -23,6 +23,14 @@ class ServiceSettingsFragment : Fragment() {
|
|||
return binding.root
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(requireActivity() as? MainActivity)?.setAppBarTitle(
|
||||
getString(R.string.settings_name),
|
||||
getString(R.string.services_name)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
super.onViewCreated(view, savedInstanceState)
|
||||
|
||||
|
|
|
@ -19,9 +19,11 @@
|
|||
app:titleTextColor="?attr/colorOnSurface"
|
||||
app:navigationIconTint="?attr/colorOnSurface"
|
||||
app:title="@string/app_name"
|
||||
app:titleTextAppearance="@style/Toolbar.Title.Small"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
app:layout_constraintEnd_toEndOf="parent">
|
||||
</com.google.android.material.appbar.MaterialToolbar>
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/fragmentContainer"
|
||||
|
|
|
@ -5,44 +5,24 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:padding="32dp"
|
||||
tools:context=".PopupSettingsFragment">
|
||||
android:padding="32dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Delay label -->
|
||||
<TextView
|
||||
android:id="@+id/description"
|
||||
android:id="@+id/delayDescription"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/popup_settings_description"
|
||||
android:textSize="16sp"
|
||||
android:text="@string/redirection_delay_description"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<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" />
|
||||
|
||||
<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" />
|
||||
|
||||
<!-- Delay slider -->
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/redirectionDelay"
|
||||
android:layout_width="0dp"
|
||||
|
@ -51,22 +31,24 @@
|
|||
android:valueFrom="2"
|
||||
android:valueTo="4"
|
||||
android:contentDescription="@string/redirection_delay_description"
|
||||
app:layout_constraintTop_toBottomOf="@id/popupEnabledCheckbox"
|
||||
app:layout_constraintTop_toBottomOf="@id/delayDescription"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="16dp" />
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<!-- Position label -->
|
||||
<TextView
|
||||
android:id="@+id/delayDescription"
|
||||
android:id="@+id/heightDescription"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/redirection_delay_description"
|
||||
android:text="@string/popup_position"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintTop_toBottomOf="@id/redirectionDelay"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="4dp" />
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
<!-- Position slider -->
|
||||
<com.google.android.material.slider.Slider
|
||||
android:id="@+id/popupHeightSlider"
|
||||
android:layout_width="0dp"
|
||||
|
@ -75,21 +57,43 @@
|
|||
android:valueTo="100"
|
||||
android:stepSize="1"
|
||||
android:contentDescription="@string/popup_position"
|
||||
app:layout_constraintTop_toBottomOf="@id/delayDescription"
|
||||
app:layout_constraintTop_toBottomOf="@id/heightDescription"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="16dp" />
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<!-- Animation label -->
|
||||
<TextView
|
||||
android:id="@+id/heightDescription"
|
||||
android:id="@+id/popupEffectLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/popup_position"
|
||||
android:text="@string/popup_effect_label"
|
||||
android:textSize="12sp"
|
||||
app:layout_constraintTop_toBottomOf="@id/popupHeightSlider"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="4dp" />
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
<!-- Animation dropdown -->
|
||||
<Spinner
|
||||
android:id="@+id/popupEffectSpinner"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
app:layout_constraintTop_toBottomOf="@id/popupEffectLabel"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="8dp" />
|
||||
|
||||
<!-- Test button -->
|
||||
<Button
|
||||
android:id="@+id/popupPreview"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/test"
|
||||
app:layout_constraintTop_toBottomOf="@id/popupEffectSpinner"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
||||
|
|
|
@ -6,23 +6,25 @@
|
|||
android:layout_height="match_parent"
|
||||
android:padding="16dp">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/serviceHeader"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/service_settings_title"
|
||||
android:textSize="18sp"
|
||||
android:textStyle="bold"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/serviceRecycler"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:scrollbars="vertical"
|
||||
app:layout_constraintTop_toBottomOf="@id/serviceHeader"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toTopOf="@id/serviceHeader"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/serviceHeader"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/services_desc"
|
||||
android:textSize="12sp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintTop_toBottomOf="@id/serviceRecycler"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
|
|
@ -5,8 +5,11 @@
|
|||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
app:cardCornerRadius="24dp"
|
||||
app:cardElevation="4dp"
|
||||
android:padding="24dp"
|
||||
app:cardBackgroundColor="?attr/colorSurface">
|
||||
|
||||
<LinearLayout
|
||||
|
|
7
app/src/main/res/layout/switch_item.xml
Normal file
7
app/src/main/res/layout/switch_item.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<com.google.android.material.switchmaterial.SwitchMaterial xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/globalPopupToggle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textSize="12sp"
|
||||
android:checked="true" />
|
|
@ -4,32 +4,32 @@
|
|||
<!-- Settings section -->
|
||||
<item
|
||||
android:id="@+id/section_settings"
|
||||
android:title="Settings"
|
||||
android:title="@string/settings_name"
|
||||
android:enabled="false" />
|
||||
<item
|
||||
android:id="@+id/action_contacts"
|
||||
android:title="Contacts"
|
||||
android:title="@string/whitelist_name"
|
||||
android:icon="@drawable/group_24px"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_popup_settings"
|
||||
android:title="Popup"
|
||||
android:title="@string/popup_name"
|
||||
android:icon="@drawable/tooltip_24px"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_services"
|
||||
android:title="Services"
|
||||
android:title="@string/services_name"
|
||||
android:icon="@drawable/services_24"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<!-- About section -->
|
||||
<item
|
||||
android:id="@+id/section_about"
|
||||
android:title="About"
|
||||
android:title="@string/about_name"
|
||||
android:enabled="false" />
|
||||
<item
|
||||
android:id="@+id/action_about"
|
||||
android:title="About"
|
||||
android:title="@string/about_name"
|
||||
android:icon="@drawable/info_24px"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
|
|
7
app/src/main/res/menu/topbar_toggle.xml
Normal file
7
app/src/main/res/menu/topbar_toggle.xml
Normal file
|
@ -0,0 +1,7 @@
|
|||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<item
|
||||
android:id="@+id/globalPopupToggle"
|
||||
app:actionLayout="@layout/switch_item"
|
||||
app:showAsAction="always" />
|
||||
</menu>
|
|
@ -9,5 +9,4 @@
|
|||
<string name="destination_whatsapp">WhatsApp</string>
|
||||
<string name="redirection_delay_description">Задержка до того, как звонок будет перенаправлен.</string>
|
||||
<string name="popup_position">Позиция всплывающего окна</string>
|
||||
<string name="fallback">Обратная совместимость</string>
|
||||
</resources>
|
|
@ -3,21 +3,36 @@
|
|||
<string name="app_name">Pulse</string>
|
||||
<string name="description">Redirects outgoing calls to E2EE apps if available.</string>
|
||||
<string name="popup">Redirecting to %1$s</string>
|
||||
<string name="settings_name">Settings</string>
|
||||
<string name="popup_name">Popup</string>
|
||||
<string name="services_name">Services</string>
|
||||
<string name="whitelist_name">Allowlist</string>
|
||||
<string name="tools_name">Tools</string>
|
||||
<string name="about_name">About</string>
|
||||
<string name="donate_name">Donate</string>
|
||||
<string name="destination_signal">Signal</string>
|
||||
<string name="destination_telegram">Telegram</string>
|
||||
<string name="destination_threema">Threema</string>
|
||||
<string name="destination_whatsapp">WhatsApp</string>
|
||||
<string name="redirection_delay_description">The delay before a call will be redirected.</string>
|
||||
<string name="services_desc">Here you can enable or disable redirection to individual services and change their priority by dragging them. Redirection will be handled in order from top to bottom.</string>
|
||||
<string name="popup_position">Popup position</string>
|
||||
<string name="fallback">Fallback</string>
|
||||
<string name="activate_description">To start, grant the required permissions by tapping the Activate button.</string>
|
||||
<string name="service_settings_title">Service Preferences</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>
|
||||
|
||||
<string name="popup_effect_label">Popup Animation</string>
|
||||
<string-array name="popup_effects">
|
||||
<item>None</item>
|
||||
<item>Fade</item>
|
||||
<item>Scale</item>
|
||||
<item>Bounce</item>
|
||||
<item>Flop</item>
|
||||
<item>Matrix</item>
|
||||
<item>Random</item>
|
||||
</string-array>
|
||||
|
||||
</resources>
|
|
@ -4,4 +4,7 @@
|
|||
<item name="cornerFamily">rounded</item>
|
||||
<item name="cornerSize">100%</item>
|
||||
</style>
|
||||
<style name="Toolbar.Title.Small" parent="TextAppearance.Widget.AppCompat.Toolbar.Title">
|
||||
<item name="android:textSize">16sp</item>
|
||||
</style>
|
||||
</resources>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue