v1.7 : destinataires nommés et import depuis le répertoire du téléphone
- Modèle : chaque destinataire = nom + numéro (migration auto des formats précédents, numéros existants conservés sans nom) - Nouvel écran d'édition de groupe : liste des destinataires (nom en gras, numéro dessous, suppression à la croix, édition au toucher) - Import depuis les contacts du téléphone via le sélecteur système (ACTION_PICK — aucune permission READ_CONTACTS demandée) - Saisie manuelle via dialogue nom + numéro avec validation et anti-doublon - Suppression de groupe avec confirmation - L'écran principal et la liste des groupes affichent les noms plutôt que les numéros quand ils existent Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
7e22b54287
commit
39ee74b0bf
11 changed files with 481 additions and 101 deletions
|
|
@ -23,8 +23,8 @@ android {
|
||||||
applicationId = "com.example.maposition"
|
applicationId = "com.example.maposition"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 7
|
versionCode = 8
|
||||||
versionName = "1.6"
|
versionName = "1.7"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
>
|
>
|
||||||
|
|
||||||
<activity android:name=".SettingsActivity" />
|
<activity android:name=".SettingsActivity" />
|
||||||
|
<activity android:name=".EditGroupActivity" />
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".MainActivity"
|
android:name=".MainActivity"
|
||||||
|
|
|
||||||
223
app/src/main/java/com/example/maposition/EditGroupActivity.kt
Normal file
223
app/src/main/java/com/example/maposition/EditGroupActivity.kt
Normal file
|
|
@ -0,0 +1,223 @@
|
||||||
|
package com.example.maposition
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.provider.ContactsContract
|
||||||
|
import android.widget.ImageButton
|
||||||
|
import android.widget.LinearLayout
|
||||||
|
import android.widget.TextView
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.appcompat.app.AlertDialog
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import com.google.android.material.button.MaterialButton
|
||||||
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||||
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
|
||||||
|
class EditGroupActivity : AppCompatActivity() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val EXTRA_GROUP = "group_name"
|
||||||
|
}
|
||||||
|
|
||||||
|
private val phoneRegex = Regex("""^\+?[0-9][0-9 .\-]{5,16}$""")
|
||||||
|
|
||||||
|
private var originalName: String? = null
|
||||||
|
private val recipients = mutableListOf<Recipient>()
|
||||||
|
|
||||||
|
private lateinit var recipientsList: LinearLayout
|
||||||
|
private lateinit var nameLayout: TextInputLayout
|
||||||
|
private lateinit var nameInput: TextInputEditText
|
||||||
|
|
||||||
|
private val pickContact =
|
||||||
|
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
|
||||||
|
if (result.resultCode != RESULT_OK) return@registerForActivityResult
|
||||||
|
val uri = result.data?.data ?: return@registerForActivityResult
|
||||||
|
contentResolver.query(
|
||||||
|
uri,
|
||||||
|
arrayOf(
|
||||||
|
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
|
||||||
|
ContactsContract.CommonDataKinds.Phone.NUMBER
|
||||||
|
),
|
||||||
|
null, null, null
|
||||||
|
)?.use { c ->
|
||||||
|
if (c.moveToFirst()) {
|
||||||
|
val name = c.getString(0) ?: ""
|
||||||
|
val phone = (c.getString(1) ?: "").replace(" ", "")
|
||||||
|
if (phone.isBlank()) {
|
||||||
|
Toast.makeText(this, R.string.error_contact_no_phone, Toast.LENGTH_SHORT).show()
|
||||||
|
} else {
|
||||||
|
addRecipient(Recipient(name, phone))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
setContentView(R.layout.activity_edit_group)
|
||||||
|
|
||||||
|
recipientsList = findViewById(R.id.recipientsList)
|
||||||
|
nameLayout = findViewById(R.id.groupNameLayout)
|
||||||
|
nameInput = findViewById(R.id.groupNameInput)
|
||||||
|
|
||||||
|
originalName = intent.getStringExtra(EXTRA_GROUP)
|
||||||
|
val editing = originalName != null
|
||||||
|
|
||||||
|
findViewById<TextView>(R.id.editTitle).setText(
|
||||||
|
if (editing) R.string.group_edit else R.string.group_new
|
||||||
|
)
|
||||||
|
|
||||||
|
if (editing) {
|
||||||
|
Prefs.getGroups(this).find { it.name == originalName }?.let { g ->
|
||||||
|
nameInput.setText(g.name)
|
||||||
|
recipients.addAll(g.recipients)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
findViewById<ImageButton>(R.id.backButton).setOnClickListener { finish() }
|
||||||
|
findViewById<MaterialButton>(R.id.pickContactButton).setOnClickListener {
|
||||||
|
pickContact.launch(
|
||||||
|
Intent(Intent.ACTION_PICK, ContactsContract.CommonDataKinds.Phone.CONTENT_URI)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
findViewById<MaterialButton>(R.id.addManualButton).setOnClickListener {
|
||||||
|
showRecipientDialog(null)
|
||||||
|
}
|
||||||
|
findViewById<MaterialButton>(R.id.saveGroupButton).setOnClickListener { saveGroup() }
|
||||||
|
|
||||||
|
val deleteButton = findViewById<MaterialButton>(R.id.deleteGroupButton)
|
||||||
|
if (editing) {
|
||||||
|
deleteButton.setOnClickListener { confirmDelete() }
|
||||||
|
} else {
|
||||||
|
deleteButton.visibility = android.view.View.GONE
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRecipients()
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Liste des destinataires ---
|
||||||
|
|
||||||
|
private fun addRecipient(r: Recipient) {
|
||||||
|
if (recipients.any { it.phone == r.phone }) {
|
||||||
|
Toast.makeText(this, R.string.error_dup_phone, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recipients.add(r)
|
||||||
|
renderRecipients()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun renderRecipients() {
|
||||||
|
recipientsList.removeAllViews()
|
||||||
|
if (recipients.isEmpty()) {
|
||||||
|
val empty = TextView(this).apply {
|
||||||
|
text = getString(R.string.no_recipients_yet)
|
||||||
|
setPadding(8, 16, 8, 16)
|
||||||
|
}
|
||||||
|
recipientsList.addView(empty)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recipients.forEachIndexed { index, r ->
|
||||||
|
val item = layoutInflater.inflate(R.layout.item_recipient, recipientsList, false)
|
||||||
|
item.findViewById<TextView>(R.id.recipientName).text = r.name.ifBlank { getString(R.string.unnamed_recipient) }
|
||||||
|
item.findViewById<TextView>(R.id.recipientPhone).text = r.phone
|
||||||
|
item.findViewById<ImageButton>(R.id.removeButton).setOnClickListener {
|
||||||
|
recipients.removeAt(index)
|
||||||
|
renderRecipients()
|
||||||
|
}
|
||||||
|
item.setOnClickListener { showRecipientDialog(index) }
|
||||||
|
recipientsList.addView(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showRecipientDialog(index: Int?) {
|
||||||
|
val existing = index?.let { recipients[it] }
|
||||||
|
val view = layoutInflater.inflate(R.layout.dialog_recipient, null)
|
||||||
|
val rNameInput = view.findViewById<TextInputEditText>(R.id.recipientNameInput)
|
||||||
|
val rPhoneLayout = view.findViewById<TextInputLayout>(R.id.recipientPhoneLayout)
|
||||||
|
val rPhoneInput = view.findViewById<TextInputEditText>(R.id.recipientPhoneInput)
|
||||||
|
|
||||||
|
if (existing != null) {
|
||||||
|
rNameInput.setText(existing.name)
|
||||||
|
rPhoneInput.setText(existing.phone)
|
||||||
|
}
|
||||||
|
|
||||||
|
val dialog = MaterialAlertDialogBuilder(this)
|
||||||
|
.setTitle(if (existing == null) R.string.recipient_new else R.string.recipient_edit)
|
||||||
|
.setView(view)
|
||||||
|
.setPositiveButton(R.string.save, null)
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.create()
|
||||||
|
|
||||||
|
dialog.setOnShowListener {
|
||||||
|
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
||||||
|
val name = rNameInput.text.toString().trim()
|
||||||
|
val phone = rPhoneInput.text.toString().trim()
|
||||||
|
rPhoneLayout.error = null
|
||||||
|
when {
|
||||||
|
phone.isEmpty() || !phoneRegex.matches(phone) ->
|
||||||
|
rPhoneLayout.error = getString(R.string.error_bad_phone, phone)
|
||||||
|
recipients.anyIndexed(index) { it.phone == phone } ->
|
||||||
|
rPhoneLayout.error = getString(R.string.error_dup_phone)
|
||||||
|
else -> {
|
||||||
|
val r = Recipient(name, phone)
|
||||||
|
if (index != null) recipients[index] = r else recipients.add(r)
|
||||||
|
renderRecipients()
|
||||||
|
dialog.dismiss()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dialog.show()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** any() en ignorant l'élément en cours d'édition. */
|
||||||
|
private fun List<Recipient>.anyIndexed(skip: Int?, pred: (Recipient) -> Boolean): Boolean =
|
||||||
|
filterIndexed { i, _ -> i != skip }.any(pred)
|
||||||
|
|
||||||
|
// --- Enregistrement / suppression du groupe ---
|
||||||
|
|
||||||
|
private fun saveGroup() {
|
||||||
|
val name = nameInput.text.toString().trim()
|
||||||
|
nameLayout.error = null
|
||||||
|
|
||||||
|
val groups = Prefs.getGroups(this)
|
||||||
|
when {
|
||||||
|
name.isEmpty() -> {
|
||||||
|
nameLayout.error = getString(R.string.error_no_group_name)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
groups.any { it.name == name && it.name != originalName } -> {
|
||||||
|
nameLayout.error = getString(R.string.error_dup_group)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
recipients.isEmpty() -> {
|
||||||
|
Toast.makeText(this, R.string.error_no_recipients, Toast.LENGTH_SHORT).show()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val idx = groups.indexOfFirst { it.name == originalName }
|
||||||
|
val group = Group(name, recipients.toList())
|
||||||
|
if (idx >= 0) groups[idx] = group else groups.add(group)
|
||||||
|
Prefs.saveGroups(this, groups)
|
||||||
|
if (originalName != null && Prefs.getActiveGroupName(this) == originalName) {
|
||||||
|
Prefs.setActiveGroupName(this, name)
|
||||||
|
}
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun confirmDelete() {
|
||||||
|
MaterialAlertDialogBuilder(this)
|
||||||
|
.setMessage(getString(R.string.delete_group_confirm, originalName))
|
||||||
|
.setPositiveButton(R.string.delete) { _, _ ->
|
||||||
|
val groups = Prefs.getGroups(this)
|
||||||
|
groups.removeAll { it.name == originalName }
|
||||||
|
Prefs.saveGroups(this, groups)
|
||||||
|
finish()
|
||||||
|
}
|
||||||
|
.setNegativeButton(android.R.string.cancel, null)
|
||||||
|
.show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -149,7 +149,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
groupChips.addView(chip)
|
groupChips.addView(chip)
|
||||||
}
|
}
|
||||||
recipientsText.text = active.phones.joinToString(", ")
|
recipientsText.text = active.displayList()
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Déclenchement de l'envoi ---
|
// --- Déclenchement de l'envoi ---
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,15 @@ import android.content.Context
|
||||||
import org.json.JSONArray
|
import org.json.JSONArray
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
data class Group(val name: String, val phones: List<String>)
|
data class Recipient(val name: String, val phone: String)
|
||||||
|
|
||||||
|
data class Group(val name: String, val recipients: List<Recipient>) {
|
||||||
|
val phones: List<String> get() = recipients.map { it.phone }
|
||||||
|
|
||||||
|
/** "Alice, Bob, 0611…" — le nom si renseigné, sinon le numéro. */
|
||||||
|
fun displayList(): String =
|
||||||
|
recipients.joinToString(", ") { it.name.ifBlank { it.phone } }
|
||||||
|
}
|
||||||
|
|
||||||
object Prefs {
|
object Prefs {
|
||||||
private const val FILE = "MaPositionPrefs"
|
private const val FILE = "MaPositionPrefs"
|
||||||
|
|
@ -24,17 +32,28 @@ object Prefs {
|
||||||
val arr = JSONArray(raw)
|
val arr = JSONArray(raw)
|
||||||
for (i in 0 until arr.length()) {
|
for (i in 0 until arr.length()) {
|
||||||
val o = arr.getJSONObject(i)
|
val o = arr.getJSONObject(i)
|
||||||
val phones = mutableListOf<String>()
|
val recipients = mutableListOf<Recipient>()
|
||||||
val pa = o.getJSONArray("phones")
|
val ra = o.optJSONArray("recipients")
|
||||||
for (j in 0 until pa.length()) phones.add(pa.getString(j))
|
if (ra != null) {
|
||||||
groups.add(Group(o.getString("name"), phones))
|
for (j in 0 until ra.length()) {
|
||||||
|
val ro = ra.getJSONObject(j)
|
||||||
|
recipients.add(Recipient(ro.optString("name", ""), ro.getString("phone")))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Migration depuis l'ancien format : liste unique de numéros
|
// Migration depuis le format v1.4 : numéros sans nom
|
||||||
|
val pa = o.optJSONArray("phones")
|
||||||
|
if (pa != null) {
|
||||||
|
for (j in 0 until pa.length()) recipients.add(Recipient("", pa.getString(j)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
groups.add(Group(o.getString("name"), recipients))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Migration depuis le format v1.0-1.3 : liste unique de numéros
|
||||||
val legacy = sp(ctx).getString("phones", "") ?: ""
|
val legacy = sp(ctx).getString("phones", "") ?: ""
|
||||||
val phones = legacy.split(";").map { it.trim() }.filter { it.isNotEmpty() }
|
val phones = legacy.split(";").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
if (phones.isNotEmpty()) {
|
if (phones.isNotEmpty()) {
|
||||||
groups.add(Group(ctx.getString(R.string.default_group_name), phones))
|
groups.add(Group(ctx.getString(R.string.default_group_name), phones.map { Recipient("", it) }))
|
||||||
saveGroups(ctx, groups)
|
saveGroups(ctx, groups)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,7 +63,11 @@ object Prefs {
|
||||||
fun saveGroups(ctx: Context, groups: List<Group>) {
|
fun saveGroups(ctx: Context, groups: List<Group>) {
|
||||||
val arr = JSONArray()
|
val arr = JSONArray()
|
||||||
groups.forEach { g ->
|
groups.forEach { g ->
|
||||||
arr.put(JSONObject().put("name", g.name).put("phones", JSONArray(g.phones)))
|
val ra = JSONArray()
|
||||||
|
g.recipients.forEach { r ->
|
||||||
|
ra.put(JSONObject().put("name", r.name).put("phone", r.phone))
|
||||||
|
}
|
||||||
|
arr.put(JSONObject().put("name", g.name).put("recipients", ra))
|
||||||
}
|
}
|
||||||
sp(ctx).edit().putString("groups", arr.toString()).apply()
|
sp(ctx).edit().putString("groups", arr.toString()).apply()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,22 +1,18 @@
|
||||||
package com.example.maposition
|
package com.example.maposition
|
||||||
|
|
||||||
|
import android.content.Intent
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.text.Editable
|
import android.text.Editable
|
||||||
import android.text.TextWatcher
|
import android.text.TextWatcher
|
||||||
import android.view.View
|
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.LinearLayout
|
import android.widget.LinearLayout
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AlertDialog
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import com.google.android.material.button.MaterialButton
|
import com.google.android.material.button.MaterialButton
|
||||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
|
||||||
import com.google.android.material.textfield.TextInputEditText
|
import com.google.android.material.textfield.TextInputEditText
|
||||||
import com.google.android.material.textfield.TextInputLayout
|
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val phoneRegex = Regex("""^\+?[0-9][0-9 .\-]{5,16}$""")
|
|
||||||
private lateinit var groupsList: LinearLayout
|
private lateinit var groupsList: LinearLayout
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
|
@ -36,8 +32,13 @@ class SettingsActivity : AppCompatActivity() {
|
||||||
})
|
})
|
||||||
|
|
||||||
findViewById<ImageButton>(R.id.backButton).setOnClickListener { finish() }
|
findViewById<ImageButton>(R.id.backButton).setOnClickListener { finish() }
|
||||||
findViewById<MaterialButton>(R.id.addGroupButton).setOnClickListener { showGroupDialog(null) }
|
findViewById<MaterialButton>(R.id.addGroupButton).setOnClickListener {
|
||||||
|
startActivity(Intent(this, EditGroupActivity::class.java))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
renderGroups()
|
renderGroups()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,76 +64,14 @@ class SettingsActivity : AppCompatActivity() {
|
||||||
g.name
|
g.name
|
||||||
}
|
}
|
||||||
item.findViewById<TextView>(R.id.groupName).text = title
|
item.findViewById<TextView>(R.id.groupName).text = title
|
||||||
item.findViewById<TextView>(R.id.groupPhones).text = g.phones.joinToString(", ")
|
item.findViewById<TextView>(R.id.groupPhones).text = g.displayList()
|
||||||
item.setOnClickListener { showGroupDialog(g) }
|
item.setOnClickListener {
|
||||||
|
startActivity(
|
||||||
|
Intent(this, EditGroupActivity::class.java)
|
||||||
|
.putExtra(EditGroupActivity.EXTRA_GROUP, g.name)
|
||||||
|
)
|
||||||
|
}
|
||||||
groupsList.addView(item)
|
groupsList.addView(item)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun showGroupDialog(existing: Group?) {
|
|
||||||
val view = layoutInflater.inflate(R.layout.dialog_group, null)
|
|
||||||
val nameLayout = view.findViewById<TextInputLayout>(R.id.groupNameLayout)
|
|
||||||
val nameInput = view.findViewById<TextInputEditText>(R.id.groupNameInput)
|
|
||||||
val phonesLayout = view.findViewById<TextInputLayout>(R.id.groupPhonesLayout)
|
|
||||||
val phonesInput = view.findViewById<TextInputEditText>(R.id.groupPhonesInput)
|
|
||||||
|
|
||||||
if (existing != null) {
|
|
||||||
nameInput.setText(existing.name)
|
|
||||||
phonesInput.setText(existing.phones.joinToString(";"))
|
|
||||||
}
|
|
||||||
|
|
||||||
val builder = MaterialAlertDialogBuilder(this)
|
|
||||||
.setTitle(if (existing == null) R.string.group_new else R.string.group_edit)
|
|
||||||
.setView(view)
|
|
||||||
.setPositiveButton(R.string.save, null)
|
|
||||||
.setNegativeButton(android.R.string.cancel, null)
|
|
||||||
if (existing != null) builder.setNeutralButton(R.string.delete, null)
|
|
||||||
|
|
||||||
val dialog = builder.create()
|
|
||||||
dialog.setOnShowListener {
|
|
||||||
dialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {
|
|
||||||
val name = nameInput.text.toString().trim()
|
|
||||||
val phones = phonesInput.text.toString()
|
|
||||||
.split(";").map { it.trim() }.filter { it.isNotEmpty() }
|
|
||||||
nameLayout.error = null
|
|
||||||
phonesLayout.error = null
|
|
||||||
|
|
||||||
val groups = Prefs.getGroups(this)
|
|
||||||
when {
|
|
||||||
name.isEmpty() ->
|
|
||||||
nameLayout.error = getString(R.string.error_no_group_name)
|
|
||||||
groups.any { it.name == name && it.name != existing?.name } ->
|
|
||||||
nameLayout.error = getString(R.string.error_dup_group)
|
|
||||||
phones.isEmpty() ->
|
|
||||||
phonesLayout.error = getString(R.string.error_no_phone)
|
|
||||||
phones.any { !phoneRegex.matches(it) } ->
|
|
||||||
phonesLayout.error = getString(
|
|
||||||
R.string.error_bad_phone,
|
|
||||||
phones.first { !phoneRegex.matches(it) }
|
|
||||||
)
|
|
||||||
else -> {
|
|
||||||
val idx = groups.indexOfFirst { it.name == existing?.name }
|
|
||||||
if (idx >= 0) groups[idx] = Group(name, phones)
|
|
||||||
else groups.add(Group(name, phones))
|
|
||||||
Prefs.saveGroups(this, groups)
|
|
||||||
if (existing != null && Prefs.getActiveGroupName(this) == existing.name) {
|
|
||||||
Prefs.setActiveGroupName(this, name)
|
|
||||||
}
|
|
||||||
renderGroups()
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (existing != null) {
|
|
||||||
dialog.getButton(AlertDialog.BUTTON_NEUTRAL).setOnClickListener {
|
|
||||||
val groups = Prefs.getGroups(this)
|
|
||||||
groups.removeAll { it.name == existing.name }
|
|
||||||
Prefs.saveGroups(this, groups)
|
|
||||||
renderGroups()
|
|
||||||
dialog.dismiss()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dialog.show()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
9
app/src/main/res/drawable/ic_close.xml
Normal file
9
app/src/main/res/drawable/ic_close.xml
Normal 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="#FF000000"
|
||||||
|
android:pathData="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z" />
|
||||||
|
</vector>
|
||||||
125
app/src/main/res/layout/activity_edit_group.xml
Normal file
125
app/src/main/res/layout/activity_edit_group.xml
Normal file
|
|
@ -0,0 +1,125 @@
|
||||||
|
<?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:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:alpha="0.07"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:scaleType="centerCrop"
|
||||||
|
android:src="@drawable/bg_watermark" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="24dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/backButton"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/back"
|
||||||
|
android:src="@drawable/ic_back"
|
||||||
|
app:tint="?attr/colorOnSurface" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/editTitle"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="8dp"
|
||||||
|
android:textSize="24sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/groupNameLayout"
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:hint="@string/group_name_hint">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/groupNameInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text"
|
||||||
|
android:maxLength="24" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="@string/recipients_title"
|
||||||
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/recipientsList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/pickContactButton"
|
||||||
|
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginEnd="6dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/add_from_contacts" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/addManualButton"
|
||||||
|
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginStart="6dp"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:text="@string/add_manual" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/saveGroupButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="52dp"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:text="@string/save"
|
||||||
|
app:backgroundTint="@color/brand_blue"
|
||||||
|
app:cornerRadius="14dp" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/deleteGroupButton"
|
||||||
|
style="@style/Widget.Material3.Button.TextButton"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="8dp"
|
||||||
|
android:text="@string/delete_group"
|
||||||
|
android:textColor="?attr/colorError" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
|
</FrameLayout>
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
|
|
@ -9,34 +8,33 @@
|
||||||
android:paddingEnd="24dp">
|
android:paddingEnd="24dp">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/groupNameLayout"
|
android:id="@+id/recipientNameLayout"
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:hint="@string/group_name_hint">
|
android:hint="@string/recipient_name_hint">
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/groupNameInput"
|
android:id="@+id/recipientNameInput"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="text"
|
android:inputType="textPersonName"
|
||||||
android:maxLength="24" />
|
android:maxLength="40" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
android:id="@+id/groupPhonesLayout"
|
android:id="@+id/recipientPhoneLayout"
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="16dp"
|
android:layout_marginTop="16dp"
|
||||||
android:hint="@string/phones_hint"
|
android:hint="@string/recipient_phone_hint">
|
||||||
app:helperText="@string/phones_helper">
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
android:id="@+id/groupPhonesInput"
|
android:id="@+id/recipientPhoneInput"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="text" />
|
android:inputType="phone" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
52
app/src/main/res/layout/item_recipient.xml
Normal file
52
app/src/main/res/layout/item_recipient.xml
Normal file
|
|
@ -0,0 +1,52 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<com.google.android.material.card.MaterialCardView 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="wrap_content"
|
||||||
|
android:layout_marginBottom="8dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:cardCornerRadius="12dp"
|
||||||
|
app:cardElevation="1dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal"
|
||||||
|
android:paddingStart="14dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:paddingEnd="6dp"
|
||||||
|
android:paddingBottom="10dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:orientation="vertical">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recipientName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="15sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/recipientPhone"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<ImageButton
|
||||||
|
android:id="@+id/removeButton"
|
||||||
|
android:layout_width="44dp"
|
||||||
|
android:layout_height="44dp"
|
||||||
|
android:background="?attr/selectableItemBackgroundBorderless"
|
||||||
|
android:contentDescription="@string/delete"
|
||||||
|
android:src="@drawable/ic_close"
|
||||||
|
app:tint="?attr/colorOnSurfaceVariant" />
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
@ -19,6 +19,20 @@
|
||||||
<string name="add_group">+ Ajouter un groupe</string>
|
<string name="add_group">+ Ajouter un groupe</string>
|
||||||
<string name="no_groups">Aucun groupe pour l\'instant.</string>
|
<string name="no_groups">Aucun groupe pour l\'instant.</string>
|
||||||
<string name="delete">Supprimer</string>
|
<string name="delete">Supprimer</string>
|
||||||
|
<string name="delete_group">Supprimer ce groupe</string>
|
||||||
|
<string name="delete_group_confirm">Supprimer le groupe « %1$s » ?</string>
|
||||||
|
<string name="recipients_title">Destinataires du groupe</string>
|
||||||
|
<string name="no_recipients_yet">Aucun destinataire pour l\'instant.</string>
|
||||||
|
<string name="add_from_contacts">📇 Depuis le répertoire</string>
|
||||||
|
<string name="add_manual">✏️ Saisie manuelle</string>
|
||||||
|
<string name="recipient_new">Ajouter un destinataire</string>
|
||||||
|
<string name="recipient_edit">Modifier le destinataire</string>
|
||||||
|
<string name="recipient_name_hint">Nom (ex : Maman)</string>
|
||||||
|
<string name="recipient_phone_hint">Numéro de téléphone</string>
|
||||||
|
<string name="unnamed_recipient">Sans nom</string>
|
||||||
|
<string name="error_dup_phone">Ce numéro est déjà dans le groupe</string>
|
||||||
|
<string name="error_no_recipients">Ajoutez au moins un destinataire</string>
|
||||||
|
<string name="error_contact_no_phone">Ce contact n\'a pas de numéro</string>
|
||||||
<string name="error_no_group_name">Donnez un nom au groupe</string>
|
<string name="error_no_group_name">Donnez un nom au groupe</string>
|
||||||
<string name="error_dup_group">Un groupe porte déjà ce nom</string>
|
<string name="error_dup_group">Un groupe porte déjà ce nom</string>
|
||||||
|
|
||||||
|
|
@ -41,10 +55,6 @@
|
||||||
<!-- Paramètres -->
|
<!-- Paramètres -->
|
||||||
<string name="name_hint">Votre nom</string>
|
<string name="name_hint">Votre nom</string>
|
||||||
<string name="name_helper">Apparaîtra dans le SMS envoyé</string>
|
<string name="name_helper">Apparaîtra dans le SMS envoyé</string>
|
||||||
<string name="phones_hint">Numéros destinataires</string>
|
|
||||||
<string name="phones_helper">Plusieurs numéros : séparez-les par un point-virgule. Ex : 0611223344;0622334455</string>
|
|
||||||
<string name="save">Enregistrer</string>
|
<string name="save">Enregistrer</string>
|
||||||
<string name="saved">Paramètres enregistrés</string>
|
|
||||||
<string name="error_no_phone">Saisissez au moins un numéro</string>
|
|
||||||
<string name="error_bad_phone">Numéro invalide : %1$s</string>
|
<string name="error_bad_phone">Numéro invalide : %1$s</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue