Added Redirect settings page
This commit is contained in:
parent
4ce065425b
commit
e8549c8841
13 changed files with 268 additions and 35 deletions
|
@ -71,5 +71,6 @@ dependencies {
|
|||
implementation 'androidx.cardview:cardview:1.0.0'
|
||||
implementation "androidx.browser:browser:1.7.0"
|
||||
implementation 'com.squareup.okhttp3:okhttp:4.12.0'
|
||||
implementation 'com.googlecode.libphonenumber:libphonenumber:8.13.32'
|
||||
implementation 'nl.dionsegijn:konfetti-xml:2.0.2' // This library holds the fabric of reality together please dont remove it at any costs >:3
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
|
||||
<uses-permission android:name="android.permission.CALL_PHONE" />
|
||||
<uses-permission android:name="android.permission.READ_CONTACTS" />
|
||||
<uses-permission android:name="android.permission.READ_PHONE_NUMBERS" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-feature android:name="android.hardware.telephony" android:required="true" />
|
||||
|
|
|
@ -7,7 +7,9 @@ import android.net.Uri
|
|||
import android.provider.ContactsContract
|
||||
import android.telecom.CallRedirectionService
|
||||
import android.telecom.PhoneAccountHandle
|
||||
import android.telephony.TelephonyManager
|
||||
import androidx.annotation.RequiresPermission
|
||||
import com.google.i18n.phonenumbers.PhoneNumberUtil
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
class CallRedirectionService : CallRedirectionService() {
|
||||
|
@ -58,13 +60,36 @@ class CallRedirectionService : CallRedirectionService() {
|
|||
initialPhoneAccount: PhoneAccountHandle,
|
||||
allowInteractiveResponse: Boolean,
|
||||
) {
|
||||
if (!prefs.isEnabled || !hasInternet() || !allowInteractiveResponse) {
|
||||
val capabilities = connectivityManager
|
||||
?.getNetworkCapabilities(connectivityManager?.activeNetwork)
|
||||
|
||||
val isWifi = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true
|
||||
val isCellular = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
|
||||
|
||||
val shouldRedirect = when {
|
||||
isWifi && !prefs.redirectOnWifi -> false
|
||||
isCellular && !prefs.redirectOnData -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
if (!prefs.isEnabled || !shouldRedirect || !hasInternet() || !allowInteractiveResponse) {
|
||||
placeCallUnmodified()
|
||||
return
|
||||
}
|
||||
|
||||
if (prefs.redirectIfRoaming && !isOutsideHomeCountry()) {
|
||||
placeCallUnmodified()
|
||||
return
|
||||
}
|
||||
|
||||
val phoneNumber = handle.schemeSpecificPart
|
||||
|
||||
// Check if we only redirect international numbers
|
||||
if (prefs.redirectInternationalOnly && !isInternationalNumber(phoneNumber)) {
|
||||
placeCallUnmodified()
|
||||
return
|
||||
}
|
||||
|
||||
if (prefs.isBlacklistEnabled && !prefs.isContactWhitelisted(phoneNumber)) {
|
||||
placeCallUnmodified()
|
||||
return
|
||||
|
@ -146,8 +171,36 @@ class CallRedirectionService : CallRedirectionService() {
|
|||
return results.toTypedArray()
|
||||
}
|
||||
|
||||
private fun isInternationalNumber(phoneNumber: String): Boolean {
|
||||
val telephony = getSystemService(TelephonyManager::class.java) ?: return true
|
||||
val simCountryIso = telephony.simCountryIso?.lowercase() ?: return true
|
||||
|
||||
// Use libphonenumber to parse the number and get region
|
||||
val util = PhoneNumberUtil.getInstance()
|
||||
return try {
|
||||
val numberProto = util.parse(phoneNumber, simCountryIso.uppercase())
|
||||
val numberRegion = util.getRegionCodeForNumber(numberProto)?.lowercase()
|
||||
numberRegion != simCountryIso
|
||||
} catch (e: Exception) {
|
||||
true // treat as international if parsing fails
|
||||
}
|
||||
}
|
||||
|
||||
fun isOutsideHomeCountry(): Boolean {
|
||||
val telephony = getSystemService(TelephonyManager::class.java) ?: return true
|
||||
|
||||
val simCountry = telephony.simCountryIso?.lowercase()
|
||||
val networkCountry = telephony.networkCountryIso?.lowercase()
|
||||
|
||||
// If SIM or network country can't be determined, assume we're abroad
|
||||
if (simCountry.isNullOrBlank() || networkCountry.isNullOrBlank()) return true
|
||||
|
||||
// If they don't match, you're abroad
|
||||
return simCountry != networkCountry
|
||||
}
|
||||
|
||||
@RequiresPermission(Manifest.permission.ACCESS_NETWORK_STATE)
|
||||
private fun hasInternet(): Boolean {
|
||||
private fun hasInternet(): Boolean { // This "hasInternet" func is (kinda) re-defined in Donation Fragment
|
||||
val capabilities = connectivityManager
|
||||
?.getNetworkCapabilities(connectivityManager?.activeNetwork) ?: return false
|
||||
return capabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) &&
|
||||
|
|
|
@ -37,7 +37,7 @@ class DonateFragment : Fragment() {
|
|||
override fun onResume() {
|
||||
super.onResume()
|
||||
(requireActivity() as? MainActivity)?.setAppBarTitle(
|
||||
getString(R.string.about_name, R.string.donate_name)
|
||||
getString(R.string.about_name), getString(R.string.donate_name)
|
||||
)
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ class DonateFragment : Fragment() {
|
|||
binding.kofiButton.setOnClickListener {
|
||||
Toast.makeText(
|
||||
requireContext(),
|
||||
"Make sure to include your token in the donation message to get rewarded 😊",
|
||||
getString(R.string.donate_toast_reminder),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
val customTab = CustomTabsIntent.Builder().build()
|
||||
|
@ -63,7 +63,7 @@ class DonateFragment : Fragment() {
|
|||
val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as android.content.ClipboardManager
|
||||
val clip = android.content.ClipData.newPlainText("Ko-fi token", "token:${prefs.donationToken}")
|
||||
clipboard.setPrimaryClip(clip)
|
||||
Toast.makeText(context, "Token copied to clipboard", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, getString(R.string.donate_token_copied), Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
|
||||
// Show token entry section
|
||||
|
@ -82,20 +82,20 @@ class DonateFragment : Fragment() {
|
|||
|
||||
// Validate token format
|
||||
if (token.length != 16) {
|
||||
Toast.makeText(context, "Invalid token format", Toast.LENGTH_SHORT).show()
|
||||
Toast.makeText(context, getString(R.string.donate_token_invalid_format), Toast.LENGTH_SHORT).show()
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
prefs.donationToken = token
|
||||
|
||||
if (prefs.isDonationActivated) {
|
||||
binding.resultText.text = "✅ Already activated"
|
||||
binding.resultText.text = getString(R.string.donate_token_already_activated)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
// Step 0: Check INTERNET permission
|
||||
if (!hasInternetPermission(requireContext())) {
|
||||
binding.resultText.text = "❌ Missing INTERNET permission"
|
||||
binding.resultText.text = getString(R.string.donate_missing_permission)
|
||||
return@setOnClickListener
|
||||
}
|
||||
|
||||
|
@ -111,16 +111,16 @@ class DonateFragment : Fragment() {
|
|||
client.newCall(internetCheck).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
activity?.runOnUiThread {
|
||||
binding.resultText.text = "❌ No internet access"
|
||||
binding.resultText.text = getString(R.string.donate_no_internet)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onResponse(call: Call, response: Response) {
|
||||
activity?.runOnUiThread {
|
||||
if (!response.isSuccessful || response.body?.string().isNullOrBlank()) {
|
||||
binding.resultText.text = "❌ No internet access"
|
||||
binding.resultText.text = getString(R.string.donate_no_internet)
|
||||
} else {
|
||||
binding.resultText.text = "❌ Activation server is unreachable"
|
||||
binding.resultText.text = getString(R.string.donate_server_unreachable)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ class DonateFragment : Fragment() {
|
|||
override fun onResponse(call: Call, response: Response) {
|
||||
if (response.body?.string()?.trim() != "true") {
|
||||
activity?.runOnUiThread {
|
||||
binding.resultText.text = "❌ Server not responding"
|
||||
binding.resultText.text = getString(R.string.donate_server_not_responding)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -143,7 +143,7 @@ class DonateFragment : Fragment() {
|
|||
client.newCall(checkRequest).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
activity?.runOnUiThread {
|
||||
binding.resultText.text = "❌ Could not check token"
|
||||
binding.resultText.text = getString(R.string.donate_token_check_failed)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,7 +151,7 @@ class DonateFragment : Fragment() {
|
|||
val result = response.body?.string()?.trim()
|
||||
if (result == "0") {
|
||||
activity?.runOnUiThread {
|
||||
binding.resultText.text = "❌ Invalid or expired token"
|
||||
binding.resultText.text = getString(R.string.donate_token_invalid)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
@ -164,7 +164,7 @@ class DonateFragment : Fragment() {
|
|||
client.newCall(activateRequest).enqueue(object : Callback {
|
||||
override fun onFailure(call: Call, e: IOException) {
|
||||
activity?.runOnUiThread {
|
||||
binding.resultText.text = "❌ Activation failed"
|
||||
binding.resultText.text = getString(R.string.donate_activation_failed)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -174,11 +174,11 @@ class DonateFragment : Fragment() {
|
|||
prefs.isDonationActivated = true
|
||||
activity?.runOnUiThread {
|
||||
binding.resultText.text =
|
||||
"✅ Token activated! You had $result activations left."
|
||||
getString(R.string.donate_token_activated, result)
|
||||
}
|
||||
} else {
|
||||
activity?.runOnUiThread {
|
||||
binding.resultText.text = "❌ Activation failed"
|
||||
binding.resultText.text = getString(R.string.donate_activation_failed)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -71,6 +71,13 @@ class MainActivity : AppCompatActivity() {
|
|||
.commit()
|
||||
true
|
||||
}
|
||||
R.id.action_redirect_settings -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, RedirectSettingsFragment())
|
||||
.addToBackStack(null)
|
||||
.commit()
|
||||
true
|
||||
}
|
||||
R.id.action_contacts -> {
|
||||
supportFragmentManager.beginTransaction()
|
||||
.replace(R.id.fragmentContainer, ContactsFragment())
|
||||
|
|
|
@ -17,6 +17,11 @@ class Preferences(private val context: Context) {
|
|||
private const val DONATION_ACTIVATED = "donation_activated"
|
||||
private const val DONATION_TOKEN = "donation_token"
|
||||
|
||||
private const val REDIRECT_WIFI = "redirect_wifi"
|
||||
private const val REDIRECT_DATA = "redirect_data"
|
||||
private const val REDIRECT_INTERNATIONAL = "redirect_international"
|
||||
private const val REDIRECT_ROAMING = "redirect_roaming"
|
||||
|
||||
private const val DEFAULT_REDIRECTION_DELAY = 2000L
|
||||
private const val DEFAULT_POPUP_POSITION = 333
|
||||
private const val SERVICE_ENABLED = "service_enabled"
|
||||
|
@ -86,6 +91,22 @@ class Preferences(private val context: Context) {
|
|||
get() = prefs.getInt(POPUP_POSITION, DEFAULT_POPUP_POSITION)
|
||||
set(value) = prefs.edit { putInt(POPUP_POSITION, value) }
|
||||
|
||||
var redirectOnWifi: Boolean
|
||||
get() = prefs.getBoolean(REDIRECT_WIFI, true)
|
||||
set(value) = prefs.edit { putBoolean(REDIRECT_WIFI, value) }
|
||||
|
||||
var redirectOnData: Boolean
|
||||
get() = prefs.getBoolean(REDIRECT_DATA, true)
|
||||
set(value) = prefs.edit { putBoolean(REDIRECT_DATA, value) }
|
||||
|
||||
var redirectInternationalOnly: Boolean
|
||||
get() = prefs.getBoolean(REDIRECT_INTERNATIONAL, false)
|
||||
set(value) = prefs.edit { putBoolean(REDIRECT_INTERNATIONAL, value) }
|
||||
|
||||
var redirectIfRoaming: Boolean
|
||||
get() = prefs.getBoolean(REDIRECT_ROAMING, false)
|
||||
set(value) = prefs.edit { putBoolean(REDIRECT_ROAMING, value) }
|
||||
|
||||
private fun makeKeyEnabled(mimetype: String) = "enabled_$mimetype"
|
||||
private fun makeKeyPriority(mimetype: String) = "priority_$mimetype"
|
||||
|
||||
|
|
|
@ -0,0 +1,56 @@
|
|||
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
|
||||
import com.google.android.material.materialswitch.MaterialSwitch
|
||||
|
||||
class RedirectSettingsFragment : Fragment() {
|
||||
|
||||
private lateinit var prefs: Preferences
|
||||
|
||||
override fun onCreateView(
|
||||
inflater: LayoutInflater,
|
||||
container: ViewGroup?,
|
||||
savedInstanceState: Bundle?
|
||||
): View {
|
||||
prefs = Preferences(requireContext())
|
||||
return inflater.inflate(R.layout.fragment_redirect_settings, container, false)
|
||||
}
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
(requireActivity() as? MainActivity)?.setAppBarTitle(
|
||||
getString(R.string.settings_name), getString(R.string.redirect_name)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
|
||||
val switchWifi = view.findViewById<MaterialSwitch>(R.id.switchRedirectWifi)
|
||||
val switchData = view.findViewById<MaterialSwitch>(R.id.switchRedirectData)
|
||||
val switchInternational = view.findViewById<MaterialSwitch>(R.id.switchRedirectInternational)
|
||||
val switchRoaming = view.findViewById<MaterialSwitch>(R.id.switchRedirectRoaming)
|
||||
|
||||
// Load saved state
|
||||
switchWifi.isChecked = prefs.redirectOnWifi
|
||||
switchData.isChecked = prefs.redirectOnData
|
||||
switchInternational.isChecked = prefs.redirectInternationalOnly
|
||||
switchRoaming.isChecked = prefs.redirectIfRoaming
|
||||
|
||||
// Save on toggle
|
||||
switchWifi.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.redirectOnWifi = isChecked
|
||||
}
|
||||
switchData.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.redirectOnData = isChecked
|
||||
}
|
||||
switchInternational.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.redirectInternationalOnly = isChecked
|
||||
}
|
||||
switchRoaming.setOnCheckedChangeListener { _, isChecked ->
|
||||
prefs.redirectIfRoaming = isChecked
|
||||
}
|
||||
}
|
||||
}
|
11
app/src/main/res/drawable/call_split_24px.xml
Normal file
11
app/src/main/res/drawable/call_split_24px.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="960"
|
||||
android:viewportHeight="960"
|
||||
android:tint="?attr/colorControlNormal"
|
||||
android:autoMirrored="true">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M440,800L440,496L240,296L240,400L160,400L160,160L400,160L400,240L296,240L520,464L520,800L440,800ZM594,424L536,366L664,240L560,240L560,160L800,160L800,400L720,400L720,296L594,424Z"/>
|
||||
</vector>
|
|
@ -1,10 +0,0 @@
|
|||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24"
|
||||
android:viewportHeight="24"
|
||||
android:tint="?attr/colorControlNormal">
|
||||
<path
|
||||
android:fillColor="@android:color/white"
|
||||
android:pathData="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"/>
|
||||
</vector>
|
|
@ -15,7 +15,7 @@
|
|||
android:id="@+id/titleText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Support Pulse Development 💖"
|
||||
android:text="@string/donate_title"
|
||||
android:textSize="20sp"
|
||||
android:textStyle="bold"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
@ -24,7 +24,7 @@
|
|||
android:id="@+id/descText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Pulse is free and open-source. You can support future development through Ko-fi. As a thank-you, donors get special animation effects!"
|
||||
android:text="@string/donate_description"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
|
@ -43,14 +43,14 @@
|
|||
android:id="@+id/kofiButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Donate via Ko-fi 💙"
|
||||
android:text="@string/donate_button"
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/postDonatePrompt"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Already donated? Tap below to activate your token."
|
||||
android:text="@string/donate_post_prompt"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
@ -59,7 +59,7 @@
|
|||
android:id="@+id/openTokenSection"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="I have a token"
|
||||
android:text="@string/donate_have_token"
|
||||
android:layout_marginBottom="16dp" />
|
||||
|
||||
<!-- Token activation section (initially hidden) -->
|
||||
|
@ -74,7 +74,7 @@
|
|||
android:id="@+id/instruction"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Enter your Ko-fi token:"
|
||||
android:text="@string/donate_token_instruction"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginBottom="8dp" />
|
||||
|
||||
|
@ -82,7 +82,7 @@
|
|||
android:id="@+id/tokenInput"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:hint="token:abcd1234efgh5678"
|
||||
android:hint="@string/donate_token_hint"
|
||||
android:inputType="text"
|
||||
android:maxLength="32"
|
||||
android:layout_marginBottom="12dp"/>
|
||||
|
@ -91,7 +91,7 @@
|
|||
android:id="@+id/verifyButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="Activate Token" />
|
||||
android:text="@string/donate_token_activate" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/resultText"
|
||||
|
|
57
app/src/main/res/layout/fragment_redirect_settings.xml
Normal file
57
app/src/main/res/layout/fragment_redirect_settings.xml
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
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_height="match_parent"
|
||||
android:padding="32dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content">
|
||||
|
||||
<!-- Redirect on WiFi -->
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switchRedirectWifi"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/redirect_wifi"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent" />
|
||||
|
||||
<!-- Redirect on Data -->
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switchRedirectData"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/redirect_data"
|
||||
app:layout_constraintTop_toBottomOf="@id/switchRedirectWifi"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
<!-- Redirect only international numbers -->
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switchRedirectInternational"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/redirect_international"
|
||||
app:layout_constraintTop_toBottomOf="@id/switchRedirectData"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
<!-- Redirect only on roaming -->
|
||||
<com.google.android.material.materialswitch.MaterialSwitch
|
||||
android:id="@+id/switchRedirectRoaming"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/redirect_roaming"
|
||||
app:layout_constraintTop_toBottomOf="@id/switchRedirectInternational"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
android:layout_marginTop="16dp" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</ScrollView>
|
|
@ -21,6 +21,11 @@
|
|||
android:title="@string/services_name"
|
||||
android:icon="@drawable/services_24"
|
||||
app:showAsAction="never" />
|
||||
<item
|
||||
android:id="@+id/action_redirect_settings"
|
||||
android:title="@string/redirect_name"
|
||||
android:icon="@drawable/call_split_24px"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<!-- About section -->
|
||||
<item
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
<string name="popup_name">Popup</string>
|
||||
<string name="services_name">Services</string>
|
||||
<string name="whitelist_name">Allowlist</string>
|
||||
<string name="redirect_name">Redirect</string>
|
||||
<string name="tools_name">Tools</string>
|
||||
<string name="about_name">About</string>
|
||||
<string name="donate_name">Donate</string>
|
||||
|
@ -25,6 +26,13 @@
|
|||
<string name="source_code">Source Code</string>
|
||||
<string name="license">License</string>
|
||||
|
||||
<!-- Redirect Settings -->
|
||||
<string name="redirect_wifi">Redirect while using Wi-Fi</string>
|
||||
<string name="redirect_data">Redirect while on mobile data</string>
|
||||
<string name="redirect_international">Redirect only international numbers</string>
|
||||
<string name="redirect_roaming">Redirect only if roaming</string>
|
||||
|
||||
<!-- Popup Animations -->
|
||||
<string name="popup_effect_label">Popup Animation</string>
|
||||
<string-array name="popup_effects">
|
||||
<item>None</item>
|
||||
|
@ -36,4 +44,27 @@
|
|||
<item>Random</item>
|
||||
</string-array>
|
||||
|
||||
<!-- Donate screen -->
|
||||
<string name="donate_title">Support Pulse Development 💖</string>
|
||||
<string name="donate_description">Pulse is free and open-source. You can support future development through Ko-fi. As a thank-you, donors get special popup animation effects!</string>
|
||||
<string name="donate_button">Donate via Ko-fi 💙</string>
|
||||
<string name="donate_post_prompt">Already donated? Tap below to activate your token.</string>
|
||||
<string name="donate_have_token">I have a token</string>
|
||||
<string name="donate_token_instruction">Enter your Ko-fi token:</string>
|
||||
<string name="donate_token_hint">token: abcd1234efgh5678</string>
|
||||
<string name="donate_token_activate">Activate Token</string>
|
||||
|
||||
<!-- DonateFragment -->
|
||||
<string name="donate_token_copied">Token copied to clipboard</string>
|
||||
<string name="donate_token_invalid_format">Invalid token format</string>
|
||||
<string name="donate_missing_permission">❌ Missing INTERNET permission</string>
|
||||
<string name="donate_no_internet">❌ No internet access</string>
|
||||
<string name="donate_server_unreachable">❌ Activation server is unreachable</string>
|
||||
<string name="donate_server_not_responding">❌ Server not responding</string>
|
||||
<string name="donate_token_check_failed">❌ Could not check token</string>
|
||||
<string name="donate_token_invalid">❌ Invalid or expired token</string>
|
||||
<string name="donate_activation_failed">❌ Activation failed</string>
|
||||
<string name="donate_token_activated">✅ Token activated! You had %1$s activations left.</string>
|
||||
<string name="donate_token_already_activated">✅ Already activated</string>
|
||||
<string name="donate_toast_reminder">Make sure to include your token in the donation message to get rewarded 😊</string>
|
||||
</resources>
|
Loading…
Add table
Add a link
Reference in a new issue