New Welcome screen

This commit is contained in:
partisan 2025-05-11 11:20:16 +02:00
parent 794fe46b45
commit cba93c6069
7 changed files with 221 additions and 18 deletions

View file

@ -69,4 +69,5 @@ dependencies {
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
implementation 'androidx.preference:preference-ktx:1.2.1'
implementation 'androidx.cardview:cardview:1.0.0'
implementation 'nl.dionsegijn:konfetti-xml:2.0.2' // This library holds the fabric of reality together please dont remove it at any costs >:3
}

View file

@ -16,13 +16,14 @@
android:theme="@style/Theme.Pulse">
<activity
android:name=".MainActivity"
android:name=".WelcomeActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".MainActivity" />
<service
android:name=".CallRedirectionService"

View file

@ -22,14 +22,10 @@ import partisan.weforge.xyz.pulse.setServicePriority
import partisan.weforge.xyz.pulse.isServiceEnabled
import partisan.weforge.xyz.pulse.setServiceEnabled
import partisan.weforge.xyz.pulse.databinding.ActivityMainBinding
import partisan.weforge.xyz.pulse.REQUIRED_PERMISSIONS
import partisan.weforge.xyz.pulse.hasGeneralPermissions
class MainActivity : AppCompatActivity() {
companion object {
private val PERMISSIONS = arrayOf(
Manifest.permission.READ_CONTACTS,
Manifest.permission.CALL_PHONE,
)
}
private lateinit var binding: ActivityMainBinding
private lateinit var prefs: Preferences
@ -156,28 +152,22 @@ class MainActivity : AppCompatActivity() {
private fun requestPermissions() {
when {
!hasGeneralPermissions() -> requestGeneralPermissions()
!hasGeneralPermissions(this) -> registerForGeneralPermissions.launch(REQUIRED_PERMISSIONS)
!hasDrawOverlays() -> requestDrawOverlays()
!hasCallRedirectionRole() -> requestCallRedirectionRole()
}
}
private fun hasPermissions(): Boolean {
return hasGeneralPermissions() && hasDrawOverlays() && hasCallRedirectionRole()
return hasGeneralPermissions(this) &&
hasDrawOverlays(this) &&
hasCallRedirectionRole(this)
}
private fun requestDrawOverlays() {
registerForDrawOverlays.launch(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION))
}
private fun requestGeneralPermissions() {
registerForGeneralPermissions.launch(PERMISSIONS)
}
private fun hasGeneralPermissions(): Boolean {
return !PERMISSIONS.any { checkSelfPermission(it) != PackageManager.PERMISSION_GRANTED }
}
private fun hasDrawOverlays(): Boolean {
return Settings.canDrawOverlays(this)
}

View file

@ -0,0 +1,28 @@
package partisan.weforge.xyz.pulse
import android.Manifest
import android.content.Context
import android.content.pm.PackageManager
import android.provider.Settings
import android.app.role.RoleManager
val REQUIRED_PERMISSIONS = arrayOf(
Manifest.permission.READ_CONTACTS,
Manifest.permission.CALL_PHONE,
)
fun hasGeneralPermissions(context: Context): Boolean {
return REQUIRED_PERMISSIONS.all {
context.checkSelfPermission(it) == PackageManager.PERMISSION_GRANTED
}
}
fun hasDrawOverlays(context: Context): Boolean {
return Settings.canDrawOverlays(context)
}
fun hasCallRedirectionRole(context: Context): Boolean {
val roleManager = context.getSystemService(RoleManager::class.java)
return roleManager?.isRoleHeld(RoleManager.ROLE_CALL_REDIRECTION) ?: false
}

View file

@ -0,0 +1,107 @@
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
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity
import nl.dionsegijn.konfetti.core.Party
import nl.dionsegijn.konfetti.core.Position
import nl.dionsegijn.konfetti.core.emitter.Emitter
import nl.dionsegijn.konfetti.xml.KonfettiView
import partisan.weforge.xyz.pulse.databinding.ActivityWelcomeBinding
import java.util.concurrent.TimeUnit
import partisan.weforge.xyz.pulse.hasGeneralPermissions
import partisan.weforge.xyz.pulse.hasDrawOverlays
import partisan.weforge.xyz.pulse.hasCallRedirectionRole
import partisan.weforge.xyz.pulse.REQUIRED_PERMISSIONS
class WelcomeActivity : AppCompatActivity() {
private lateinit var binding: ActivityWelcomeBinding
private lateinit var prefs: Preferences
private var roleManager: RoleManager? = null
private val requestPermissionsLauncher = registerForActivityResult(
ActivityResultContracts.RequestMultiplePermissions()
) {}
private val requestOverlayLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {}
private val requestRoleLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) {}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
if (hasGeneralPermissions(this) && hasDrawOverlays(this) && hasCallRedirectionRole(this)) {
startActivity(Intent(this, MainActivity::class.java))
finish()
return
}
binding = ActivityWelcomeBinding.inflate(layoutInflater)
setContentView(binding.root)
prefs = Preferences(this)
roleManager = getSystemService(RoleManager::class.java)
binding.activateButton.setOnClickListener {
when {
!hasGeneralPermissions(this) -> {
requestPermissionsLauncher.launch(REQUIRED_PERMISSIONS)
}
!hasDrawOverlays(this) -> {
requestOverlayLauncher.launch(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION))
}
!hasCallRedirectionRole(this) -> {
requestRoleLauncher.launch(roleManager?.createRequestRoleIntent(RoleManager.ROLE_CALL_REDIRECTION))
}
else -> {
prefs.isEnabled = true
showConfettiAndContinue()
}
}
}
}
private fun showConfettiAndContinue() {
binding.appIcon.post {
val iconLocation = IntArray(2)
binding.appIcon.getLocationOnScreen(iconLocation)
val iconCenterX = iconLocation[0] + binding.appIcon.width / 2f
val iconCenterY = iconLocation[1] + binding.appIcon.height / 2f
val rootWidth = binding.root.width.toFloat()
val rootHeight = binding.root.height.toFloat()
val relativeX = (iconCenterX / rootWidth).toDouble()
val relativeY = (iconCenterY / rootHeight).toDouble()
binding.konfettiView.visibility = View.VISIBLE
binding.konfettiView.start(
Party(
speed = 30f,
maxSpeed = 50f,
damping = 0.9f,
spread = 360,
colors = listOf(0xfce18a, 0xff726d, 0xf4306d, 0xb48def),
position = Position.Relative(relativeX, relativeY),
emitter = Emitter(duration = 2, TimeUnit.SECONDS).perSecond(100)
)
)
binding.konfettiView.postDelayed({
startActivity(Intent(this, MainActivity::class.java))
finish()
}, 2000)
}
}
}

View file

@ -0,0 +1,74 @@
<?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/activateButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/activate"
android:layout_marginTop="32dp"
app:cornerRadius="24dp" />
<TextView
android:id="@+id/activateDescription"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/activate_description"
android:textAlignment="center"
android:textSize="14sp"
android:layout_marginTop="16dp" />
</LinearLayout>
</androidx.constraintlayout.widget.ConstraintLayout>
<nl.dionsegijn.konfetti.xml.KonfettiView
android:id="@+id/konfettiView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:visibility="gone" />
</FrameLayout>

View file

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">Pulse</string>
<string name="description">The app will try to redirect outgoing calls to Signal/Telegram/Threema/WhatsApp if available. For work it requires many permissions. Click on the toggle and grant permissions until it turns ON.</string>
<string name="description">App will try to redirect 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,4 +10,6 @@
<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">Activate</string>
</resources>