Added individual service toggle and priority switch

This commit is contained in:
partisan 2025-05-01 13:24:13 +02:00
parent 707cd39ef8
commit b09b6578bb
13 changed files with 328 additions and 83 deletions

View file

@ -4,29 +4,39 @@ plugins {
} }
android { android {
namespace 'partisan.weforge.xyz.pulse' namespace = 'partisan.weforge.xyz.pulse'
compileSdk 34 compileSdk = 34
defaultConfig { defaultConfig {
applicationId "partisan.weforge.xyz.pulse" applicationId = "partisan.weforge.xyz.pulse"
minSdk 29 minSdk = 29
targetSdk 34 targetSdk = 34
versionCode 9 versionCode = 9
versionName "1.2.0" versionName = "1.3.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}
signingConfigs {
release {
storeFile file("release-key.jks")
storePassword RELEASE_STORE_PASSWORD
keyAlias "release-key"
keyPassword RELEASE_STORE_PASSWORD
}
} }
buildTypes { buildTypes {
release { release {
minifyEnabled false minifyEnabled = false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release
proguardFiles(getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro')
} }
} }
compileOptions { compileOptions {
sourceCompatibility JavaVersion.VERSION_17 sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17
} }
kotlinOptions { kotlinOptions {
@ -34,11 +44,11 @@ android {
} }
buildFeatures { buildFeatures {
viewBinding true viewBinding = true
} }
lint { lint {
disable 'MissingTranslation' disable += 'MissingTranslation'
} }
} }

View file

@ -17,20 +17,19 @@ class CallRedirectionService : CallRedirectionService() {
private const val TELEGRAM_MIMETYPE = "$PREFIX/vnd.org.telegram.messenger.android.call" private const val TELEGRAM_MIMETYPE = "$PREFIX/vnd.org.telegram.messenger.android.call"
private const val THREEMA_MIMETYPE = "$PREFIX/vnd.ch.threema.app.call" private const val THREEMA_MIMETYPE = "$PREFIX/vnd.ch.threema.app.call"
private const val WHATSAPP_MIMETYPE = "$PREFIX/vnd.com.whatsapp.voip.call" private const val WHATSAPP_MIMETYPE = "$PREFIX/vnd.com.whatsapp.voip.call"
private val MIMETYPE_TO_WEIGHT = mapOf(
SIGNAL_MIMETYPE to 0, val ALL_MIMETYPES = arrayOf(
TELEGRAM_MIMETYPE to 1, SIGNAL_MIMETYPE,
THREEMA_MIMETYPE to 2, TELEGRAM_MIMETYPE,
WHATSAPP_MIMETYPE to 48, THREEMA_MIMETYPE,
)
private val FALLBACK_MIMETYPES = arrayOf(
WHATSAPP_MIMETYPE, WHATSAPP_MIMETYPE,
) )
private val MIMETYPE_TO_DST_NAME = mapOf( private val MIMETYPE_TO_DST_NAME = mapOf(
SIGNAL_MIMETYPE to R.string.destination_signal, SIGNAL_MIMETYPE to R.string.destination_signal,
TELEGRAM_MIMETYPE to R.string.destination_telegram, TELEGRAM_MIMETYPE to R.string.destination_telegram,
THREEMA_MIMETYPE to R.string.destination_threema, THREEMA_MIMETYPE to R.string.destination_threema,
WHATSAPP_MIMETYPE to R.string.fallback_destination_whatsapp, WHATSAPP_MIMETYPE to R.string.destination_whatsapp,
) )
} }
@ -63,6 +62,7 @@ class CallRedirectionService : CallRedirectionService() {
placeCallUnmodified() placeCallUnmodified()
return return
} }
val records: Array<Record> val records: Array<Record>
try { try {
records = getRecordsFromPhoneNumber(handle.schemeSpecificPart) records = getRecordsFromPhoneNumber(handle.schemeSpecificPart)
@ -70,11 +70,18 @@ class CallRedirectionService : CallRedirectionService() {
placeCallUnmodified() placeCallUnmodified()
return return
} }
val record = records.minByOrNull { MIMETYPE_TO_WEIGHT[it.mimetype] ?: 0 }
if (record == null || (record.mimetype in FALLBACK_MIMETYPES && !prefs.isFallbackChecked)) { // Filter to enabled services only
val enabledRecords = records
.filter { prefs.isServiceEnabled(it.mimetype) }
.sortedBy { prefs.getServicePriority(it.mimetype) }
val record = enabledRecords.firstOrNull()
if (record == null) {
placeCallUnmodified() placeCallUnmodified()
return return
} }
window.show(record.uri, MIMETYPE_TO_DST_NAME[record.mimetype] ?: return) window.show(record.uri, MIMETYPE_TO_DST_NAME[record.mimetype] ?: return)
} }
@ -109,9 +116,8 @@ class CallRedirectionService : CallRedirectionService() {
ContactsContract.Data.CONTENT_URI, ContactsContract.Data.CONTENT_URI,
arrayOf(ContactsContract.Data._ID, ContactsContract.Data.MIMETYPE), arrayOf(ContactsContract.Data._ID, ContactsContract.Data.MIMETYPE),
"${ContactsContract.Data.CONTACT_ID} = ? AND " + "${ContactsContract.Data.CONTACT_ID} = ? AND " +
"${ContactsContract.Data.MIMETYPE} IN " + "${ContactsContract.Data.MIMETYPE} IN (${ALL_MIMETYPES.joinToString(",") { "?" }})",
"(${MIMETYPE_TO_WEIGHT.keys.joinToString(",") { "?" }})", arrayOf(contactId, *ALL_MIMETYPES),
arrayOf(contactId, *MIMETYPE_TO_WEIGHT.keys.toTypedArray()),
null, null,
) )
cursor?.apply { cursor?.apply {

View file

@ -10,7 +10,17 @@ import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.widget.doAfterTextChanged import androidx.core.widget.doAfterTextChanged
import java.lang.NumberFormatException import java.lang.NumberFormatException
import android.text.InputType
import android.widget.CheckBox
import android.widget.EditText
import android.widget.LinearLayout
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.ItemTouchHelper
import partisan.weforge.xyz.pulse.getServicePriority
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.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
@ -55,7 +65,6 @@ class MainActivity : AppCompatActivity() {
binding.apply { binding.apply {
redirectionDelay.value = (prefs.redirectionDelay / 1000).toFloat() redirectionDelay.value = (prefs.redirectionDelay / 1000).toFloat()
popupPosition.editText?.setText(prefs.popupPosition.toString()) popupPosition.editText?.setText(prefs.popupPosition.toString())
fallback.isChecked = prefs.isFallbackChecked
toggle.isChecked = prefs.isEnabled toggle.isChecked = prefs.isEnabled
} }
} }
@ -76,9 +85,6 @@ class MainActivity : AppCompatActivity() {
prefs.popupPosition = it?.toString()?.toInt() ?: return@doAfterTextChanged prefs.popupPosition = it?.toString()?.toInt() ?: return@doAfterTextChanged
} catch (exc: NumberFormatException) {} } catch (exc: NumberFormatException) {}
} }
fallback.setOnCheckedChangeListener { _, isChecked ->
prefs.isFallbackChecked = isChecked
}
toggle.setOnCheckedChangeListener { _, isChecked -> toggle.setOnCheckedChangeListener { _, isChecked ->
if (isChecked && !hasPermissions()) { if (isChecked && !hasPermissions()) {
toggle.isChecked = false toggle.isChecked = false
@ -87,6 +93,64 @@ class MainActivity : AppCompatActivity() {
} }
prefs.isEnabled = isChecked prefs.isEnabled = isChecked
} }
val services = listOf(
ServiceEntry("vnd.android.cursor.item/vnd.org.thoughtcrime.securesms.call", R.string.destination_signal, this@MainActivity.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, this@MainActivity.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, this@MainActivity.isServiceEnabled("vnd.android.cursor.item/vnd.ch.threema.app.call")),
ServiceEntry("vnd.android.cursor.item/vnd.com.whatsapp.voip.call", R.string.destination_whatsapp, this@MainActivity.isServiceEnabled("vnd.android.cursor.item/vnd.com.whatsapp.voip.call")),
)
val adapter = ServiceAdapter(
context = this@MainActivity,
services = services.toMutableList(),
onReordered = { updatedList ->
updatedList.forEachIndexed { index, entry ->
setServicePriority(entry.mimetype, index)
}
}
)
binding.serviceRecycler.adapter = adapter
binding.serviceRecycler.layoutManager = LinearLayoutManager(this@MainActivity)
val touchHelper = ItemTouchHelper(adapter.dragHelper)
touchHelper.attachToRecyclerView(binding.serviceRecycler)
adapter.setDragStartListener { viewHolder ->
touchHelper.startDrag(viewHolder)
}
// binding.serviceConfigList.removeAllViews()
// for ((mimetype, labelRes) in mimetypes) {
// val checkbox = CheckBox(this@MainActivity).apply {
// text = getString(labelRes)
// isChecked = this@MainActivity.isServiceEnabled(mimetype)
// setOnCheckedChangeListener { _, checked ->
// this@MainActivity.setServiceEnabled(mimetype, checked)
// }
// }
// val priorityInput = EditText(this@MainActivity).apply {
// inputType = InputType.TYPE_CLASS_NUMBER
// setEms(4)
// hint = "Priority"
// setText(this@MainActivity.getServicePriority(mimetype).toString())
// setOnFocusChangeListener { _, hasFocus ->
// if (!hasFocus) {
// val value = text.toString().toIntOrNull()
// if (value != null) this@MainActivity.setServicePriority(mimetype, value)
// }
// }
// }
// val row = LinearLayout(this@MainActivity).apply {
// orientation = LinearLayout.HORIZONTAL
// setPadding(0, 16, 0, 16)
// addView(checkbox, LinearLayout.LayoutParams(0, WRAP_CONTENT, 1f))
// addView(priorityInput, LinearLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT))
// }
// binding.serviceConfigList.addView(row)
// }
} }
} }

View file

@ -50,7 +50,6 @@ class PopupWindow(
R.string.destination_telegram, R.string.destination_telegram,
R.string.destination_threema, R.string.destination_threema,
) )
if (prefs.isFallbackChecked) destinations.add(R.string.fallback_destination_whatsapp)
setDescription(destinations.random()) setDescription(destinations.random())
add() add()
} }

View file

@ -9,7 +9,6 @@ class Preferences(ctx: Context) {
private const val ENABLED = "enabled" 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 FALLBACK_CHECKED = "fallback_checked"
private const val DEFAULT_REDIRECTION_DELAY = 2000L private const val DEFAULT_REDIRECTION_DELAY = 2000L
private const val DEFAULT_POPUP_POSITION = 333 private const val DEFAULT_POPUP_POSITION = 333
@ -32,7 +31,26 @@ class Preferences(ctx: Context) {
get() = prefs.getInt(POPUP_POSITION, DEFAULT_POPUP_POSITION) get() = prefs.getInt(POPUP_POSITION, DEFAULT_POPUP_POSITION)
set(value) = prefs.edit { putInt(POPUP_POSITION, value) } set(value) = prefs.edit { putInt(POPUP_POSITION, value) }
var isFallbackChecked: Boolean private fun makeKeyEnabled(mimetype: String) = "enabled_$mimetype"
get() = prefs.getBoolean(FALLBACK_CHECKED, false) private fun makeKeyPriority(mimetype: String) = "priority_$mimetype"
set(value) = prefs.edit { putBoolean(FALLBACK_CHECKED, value) }
/** Whether this service is enabled */
fun isServiceEnabled(mimetype: String): Boolean {
return prefs.getBoolean(makeKeyEnabled(mimetype), true)
}
/** Current priority for this service (lower = higher priority) */
fun getServicePriority(mimetype: String): Int {
return prefs.getInt(makeKeyPriority(mimetype), Int.MAX_VALUE)
}
/** Enable or disable individual service */
fun setServiceEnabled(mimetype: String, enabled: Boolean) {
prefs.edit().putBoolean(makeKeyEnabled(mimetype), enabled).apply()
}
/** Change priority for an individual service */
fun setServicePriority(mimetype: String, priority: Int) {
prefs.edit().putInt(makeKeyPriority(mimetype), priority).apply()
}
} }

View file

@ -0,0 +1,89 @@
package partisan.weforge.xyz.pulse
import android.content.Context
import android.view.LayoutInflater
import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.widget.CheckBox
import android.widget.ImageView
import android.widget.TextView
import androidx.recyclerview.widget.ItemTouchHelper
import androidx.recyclerview.widget.RecyclerView
class ServiceAdapter(
private val context: Context,
private val services: MutableList<ServiceEntry>,
private val onReordered: (List<ServiceEntry>) -> Unit
) : RecyclerView.Adapter<ServiceAdapter.ViewHolder>() {
inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
val label: TextView = view.findViewById(R.id.label)
val checkbox: CheckBox = view.findViewById(R.id.checkbox)
val handle: ImageView = view.findViewById(R.id.handle)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context)
.inflate(R.layout.item_service, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val entry = services[position]
holder.label.setText(entry.labelRes)
holder.checkbox.isChecked = entry.enabled
holder.checkbox.setOnCheckedChangeListener { _, isChecked ->
entry.enabled = isChecked
context.setServiceEnabled(entry.mimetype, isChecked)
}
holder.handle.setOnTouchListener { _, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
dragStartListener?.invoke(holder)
}
false
}
}
override fun getItemCount(): Int = services.size
private var dragStartListener: ((RecyclerView.ViewHolder) -> Unit)? = null
val dragHelper = object : ItemTouchHelper.Callback() {
override fun getMovementFlags(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder
): Int {
val dragFlags = ItemTouchHelper.UP or ItemTouchHelper.DOWN
return makeMovementFlags(dragFlags, 0)
}
override fun onMove(
recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
target: RecyclerView.ViewHolder
): Boolean {
val from = viewHolder.adapterPosition
val to = target.adapterPosition
services.add(to, services.removeAt(from))
notifyItemMoved(from, to)
return true
}
override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {
// No swipe actions
}
override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {
super.clearView(recyclerView, viewHolder)
onReordered(services)
}
}
fun setDragStartListener(listener: (RecyclerView.ViewHolder) -> Unit) {
dragStartListener = listener
}
}

View file

@ -0,0 +1,33 @@
package partisan.weforge.xyz.pulse
import android.content.Context
import androidx.preference.PreferenceManager
private fun makeKeyEnabled(mimetype: String) = "enabled_$mimetype"
private fun makeKeyPriority(mimetype: String) = "priority_$mimetype"
data class ServiceEntry(
val mimetype: String,
val labelRes: Int,
var enabled: Boolean
)
fun Context.isServiceEnabled(mimetype: String): Boolean {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
return prefs.getBoolean(makeKeyEnabled(mimetype), true)
}
fun Context.setServiceEnabled(mimetype: String, enabled: Boolean) {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
prefs.edit().putBoolean(makeKeyEnabled(mimetype), enabled).apply()
}
fun Context.getServicePriority(mimetype: String): Int {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
return prefs.getInt(makeKeyPriority(mimetype), Int.MAX_VALUE)
}
fun Context.setServicePriority(mimetype: String, priority: Int) {
val prefs = PreferenceManager.getDefaultSharedPreferences(this)
prefs.edit().putInt(makeKeyPriority(mimetype), priority).apply()
}

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="#666"
android:pathData="M7,10h2v2H7v-2zm0,-4h2v2H7V6zm0,8h2v2H7v-2zm4,-8h2v2h-2V6zm0,4h2v2h-2v-2zm0,4h2v2h-2v-2z" />
</vector>

View file

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -12,9 +13,9 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/description" android:text="@string/description"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
<ScrollView <ScrollView
android:id="@+id/scrollView" android:id="@+id/scrollView"
@ -22,10 +23,10 @@
android:layout_height="0dp" android:layout_height="0dp"
android:padding="16dp" android:padding="16dp"
android:layout_marginVertical="16dp" android:layout_marginVertical="16dp"
app:layout_constraintBottom_toTopOf="@+id/toggle" app:layout_constraintTop_toBottomOf="@id/description"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintBottom_toTopOf="@id/toggle"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/description"> app:layout_constraintEnd_toEndOf="parent">
<LinearLayout <LinearLayout
android:layout_width="match_parent" android:layout_width="match_parent"
@ -36,65 +37,54 @@
android:id="@+id/redirectionDelay" android:id="@+id/redirectionDelay"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:contentDescription="@string/redirection_delay_description"
android:stepSize="0.5" android:stepSize="0.5"
android:valueFrom="2" android:valueFrom="2"
android:valueTo="4" /> android:valueTo="4"
android:contentDescription="@string/redirection_delay_description" />
<TextView <TextView
android:id="@+id/description2" android:id="@+id/description2"
android:textSize="12sp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:text="@string/redirection_delay_description" /> android:text="@string/redirection_delay_description"
android:textSize="12sp" />
<Space <Space
android:layout_marginVertical="8dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:layout_marginVertical="8dp" />
<com.google.android.material.textfield.TextInputLayout <com.google.android.material.textfield.TextInputLayout
android:id="@+id/popupPosition" android:id="@+id/popupPosition"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:endIconMode="custom" app:endIconMode="custom"
app:endIconDrawable="@drawable/ic_baseline_check_circle_24" app:endIconDrawable="@drawable/ic_baseline_check_circle_24"
android:hint="@string/popup_position" android:hint="@string/popup_position">
android:layout_width="match_parent"
android:layout_height="wrap_content">
<com.google.android.material.textfield.TextInputEditText <com.google.android.material.textfield.TextInputEditText
android:inputType="number"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:inputType="number" />
</com.google.android.material.textfield.TextInputLayout> </com.google.android.material.textfield.TextInputLayout>
<Space <Space
android:layout_marginVertical="8dp"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" /> android:layout_height="wrap_content"
android:layout_marginVertical="8dp" />
<View <View
android:id="@+id/divider"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="1dp" android:layout_height="1dp"
android:layout_marginVertical="8dp" android:layout_marginVertical="8dp"
android:background="?android:attr/listDivider" /> android:background="?android:attr/listDivider" />
<CheckBox <androidx.recyclerview.widget.RecyclerView
android:id="@+id/fallback" android:id="@+id/serviceRecycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layoutDirection="rtl" android:layout_marginTop="16dp"
android:text="@string/fallback" android:scrollbars="vertical" />
android:textSize="16sp" />
<TextView
android:id="@+id/description3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/fallback_description"
android:textSize="12sp" />
</LinearLayout> </LinearLayout>
</ScrollView> </ScrollView>
@ -105,7 +95,6 @@
android:paddingVertical="12dp" android:paddingVertical="12dp"
android:textSize="16sp" android:textSize="16sp"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toStartOf="parent" /> app:layout_constraintEnd_toEndOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<ImageView
android:id="@+id/handle"
android:layout_width="24dp"
android:layout_height="24dp"
android:src="@drawable/ic_drag_handle"
android:contentDescription="@null"
android:layout_gravity="center_vertical"
android:paddingEnd="8dp" />
<CheckBox
android:id="@+id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginStart="8dp"
android:text="Label"
android:textSize="16sp"
android:layout_gravity="center_vertical" />
</LinearLayout>

View file

@ -1,10 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Pulse</string> <string name="app_name">Pulse</string>
<string name="description">L\'application essaiera de rediriger les appels sortants vers Signal/Telegram/Threema s\'ils sont disponibles. Pour fonctionner, l\'application nécessite de nombreuses permissions. Cliquez sur le bouton et accordez les autorisations nécéssaires jusqu\'à ce qu\'il soit activé.</string> <string name="description">L\'application essaiera de rediriger les appels sortants vers Signal/Telegram/Threema/WhatsApp s\'ils sont disponibles. Pour fonctionner, l\'application nécessite de nombreuses permissions. Cliquez sur le bouton et accordez les autorisations nécéssaires jusqu\'à ce qu\'il soit activé.</string>
<string name="popup">Redirection vers %1$s</string> <string name="popup">Redirection vers %1$s</string>
<string name="destination_signal">Signal</string> <string name="destination_signal">Signal</string>
<string name="destination_telegram">Telegram</string> <string name="destination_telegram">Telegram</string>
<string name="destination_threema">Threema</string> <string name="destination_threema">Threema</string>
<string name="redirection_delay_description">Délai avant qu\'un appel ne soit redirigé.</string> <string name="destination_whatsapp">WhatsApp</string>
</resources> </resources>

View file

@ -1,14 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Pulse</string> <string name="app_name">Pulse</string>
<string name="description">Приложение будет пытаться перенаправить исходящие вызовы в Signal/Telegram/Threema. Для работы ему нужно много разрешений. Кликайте на переключатель и выдавайте разрешения пока он не включится.</string> <string name="description">Приложение будет пытаться перенаправить исходящие вызовы в Signal/Telegram/Threema/WhatsApp. Для работы ему нужно много разрешений. Кликайте на переключатель и выдавайте разрешения пока он не включится.</string>
<string name="popup">Перенаправление в %1$s</string> <string name="popup">Перенаправление в %1$s</string>
<string name="destination_signal">Signal</string> <string name="destination_signal">Signal</string>
<string name="destination_telegram">Telegram</string> <string name="destination_telegram">Telegram</string>
<string name="destination_threema">Threema</string> <string name="destination_threema">Threema</string>
<string name="destination_whatsapp">WhatsApp</string>
<string name="redirection_delay_description">Задержка до того, как звонок будет перенаправлен.</string> <string name="redirection_delay_description">Задержка до того, как звонок будет перенаправлен.</string>
<string name="popup_position">Позиция всплывающего окна</string> <string name="popup_position">Позиция всплывающего окна</string>
<string name="fallback">Обратная совместимость</string> <string name="fallback">Обратная совместимость</string>
<string name="fallback_description">Перенаправлять в WhatsApp, если другие недоступны.</string>
<string name="fallback_destination_whatsapp">WhatsApp</string>
</resources> </resources>

View file

@ -1,14 +1,13 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<string name="app_name">Pulse</string> <string name="app_name">Pulse</string>
<string name="description">The app will try to redirect outgoing calls to Signal/Telegram/Threema if available. For work it requires many permissions. Click on the toggle and grant permissions until it turns ON.</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="popup">Redirecting to %1$s</string> <string name="popup">Redirecting to %1$s</string>
<string name="destination_signal">Signal</string> <string name="destination_signal">Signal</string>
<string name="destination_telegram">Telegram</string> <string name="destination_telegram">Telegram</string>
<string name="destination_threema">Threema</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="redirection_delay_description">The delay before a call will be redirected.</string>
<string name="popup_position">Popup position</string> <string name="popup_position">Popup position</string>
<string name="fallback">Fallback</string> <string name="fallback">Fallback</string>
<string name="fallback_description">Redirect to WhatsApp if no other available.</string>
<string name="fallback_destination_whatsapp">WhatsApp</string>
</resources> </resources>