v1.4 : groupes de destinataires et simplification de l'écran principal
- Nouveau modèle : groupes nommés (famille, amis…) stockés en JSON dans les préférences, avec migration automatique de l'ancienne liste de numéros vers un groupe « Mes contacts » - Écran principal : la roue crantée (doublon) disparaît ; la carte du bas affiche des puces de sélection du groupe actif + ses numéros, et ouvre la gestion des groupes - Paramètres : nom (enregistré à la saisie) + liste des groupes ; création, édition et suppression via boîte de dialogue avec validation des numéros - Prefs.kt : accès centralisé aux préférences Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
c7a753627b
commit
191c4158c7
9 changed files with 416 additions and 139 deletions
|
|
@ -23,8 +23,8 @@ android {
|
||||||
applicationId = "com.example.maposition"
|
applicationId = "com.example.maposition"
|
||||||
minSdk = 21
|
minSdk = 21
|
||||||
targetSdk = 36
|
targetSdk = 36
|
||||||
versionCode = 4
|
versionCode = 5
|
||||||
versionName = "1.3"
|
versionName = "1.4"
|
||||||
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import android.os.Looper
|
||||||
import android.os.SystemClock
|
import android.os.SystemClock
|
||||||
import android.telephony.SmsManager
|
import android.telephony.SmsManager
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.widget.ImageButton
|
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.app.ActivityCompat
|
import androidx.core.app.ActivityCompat
|
||||||
|
|
@ -27,6 +26,8 @@ import com.google.android.gms.location.LocationServices
|
||||||
import com.google.android.gms.location.Priority
|
import com.google.android.gms.location.Priority
|
||||||
import com.google.android.gms.tasks.CancellationTokenSource
|
import com.google.android.gms.tasks.CancellationTokenSource
|
||||||
import com.google.android.material.card.MaterialCardView
|
import com.google.android.material.card.MaterialCardView
|
||||||
|
import com.google.android.material.chip.Chip
|
||||||
|
import com.google.android.material.chip.ChipGroup
|
||||||
import com.google.android.material.progressindicator.CircularProgressIndicator
|
import com.google.android.material.progressindicator.CircularProgressIndicator
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
|
@ -52,6 +53,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var statusText: TextView
|
private lateinit var statusText: TextView
|
||||||
private lateinit var progress: CircularProgressIndicator
|
private lateinit var progress: CircularProgressIndicator
|
||||||
private lateinit var recipientsText: TextView
|
private lateinit var recipientsText: TextView
|
||||||
|
private lateinit var groupChips: ChipGroup
|
||||||
|
|
||||||
// État d'un envoi en cours
|
// État d'un envoi en cours
|
||||||
private var busy = false
|
private var busy = false
|
||||||
|
|
@ -83,6 +85,7 @@ class MainActivity : AppCompatActivity() {
|
||||||
statusText = findViewById(R.id.statusText)
|
statusText = findViewById(R.id.statusText)
|
||||||
progress = findViewById(R.id.progress)
|
progress = findViewById(R.id.progress)
|
||||||
recipientsText = findViewById(R.id.recipientsText)
|
recipientsText = findViewById(R.id.recipientsText)
|
||||||
|
groupChips = findViewById(R.id.groupChips)
|
||||||
|
|
||||||
ContextCompat.registerReceiver(
|
ContextCompat.registerReceiver(
|
||||||
this, smsSentReceiver, IntentFilter(ACTION_SMS_SENT),
|
this, smsSentReceiver, IntentFilter(ACTION_SMS_SENT),
|
||||||
|
|
@ -90,7 +93,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
)
|
)
|
||||||
|
|
||||||
sendButton.setOnClickListener { startSendFlow() }
|
sendButton.setOnClickListener { startSendFlow() }
|
||||||
findViewById<ImageButton>(R.id.settingsButton).setOnClickListener { openSettings() }
|
|
||||||
findViewById<MaterialCardView>(R.id.recipientsCard).setOnClickListener { openSettings() }
|
findViewById<MaterialCardView>(R.id.recipientsCard).setOnClickListener { openSettings() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -109,28 +111,41 @@ class MainActivity : AppCompatActivity() {
|
||||||
startActivity(Intent(this, SettingsActivity::class.java))
|
startActivity(Intent(this, SettingsActivity::class.java))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun getSavedSettings(): Pair<String, List<String>> {
|
// --- Carte des groupes de destinataires ---
|
||||||
val prefs = getSharedPreferences("MaPositionPrefs", MODE_PRIVATE)
|
|
||||||
val name = prefs.getString("name", "") ?: ""
|
|
||||||
val phones = prefs.getString("phones", "") ?: ""
|
|
||||||
val phoneList = phones.split(";").map { it.trim() }.filter { it.isNotEmpty() }
|
|
||||||
return Pair(name, phoneList)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun refreshRecipientsCard() {
|
private fun refreshRecipientsCard() {
|
||||||
val (name, phones) = getSavedSettings()
|
val groups = Prefs.getGroups(this)
|
||||||
recipientsText.text = if (phones.isEmpty()) {
|
groupChips.removeAllViews()
|
||||||
getString(R.string.recipients_none)
|
|
||||||
} else {
|
if (groups.isEmpty()) {
|
||||||
val who = if (name.isBlank()) "" else "$name → "
|
groupChips.visibility = View.GONE
|
||||||
who + phones.joinToString(", ")
|
recipientsText.text = getString(R.string.recipients_none)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
groupChips.visibility = View.VISIBLE
|
||||||
|
val active = Prefs.getActiveGroup(this)!!
|
||||||
|
groups.forEach { g ->
|
||||||
|
val chip = Chip(this).apply {
|
||||||
|
text = g.name
|
||||||
|
isCheckable = true
|
||||||
|
isChecked = g.name == active.name
|
||||||
|
setOnClickListener {
|
||||||
|
Prefs.setActiveGroupName(this@MainActivity, g.name)
|
||||||
|
refreshRecipientsCard()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
groupChips.addView(chip)
|
||||||
|
}
|
||||||
|
recipientsText.text = active.phones.joinToString(", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Déclenchement de l'envoi ---
|
||||||
|
|
||||||
private fun startSendFlow() {
|
private fun startSendFlow() {
|
||||||
if (busy) return
|
if (busy) return
|
||||||
val (_, phones) = getSavedSettings()
|
val active = Prefs.getActiveGroup(this)
|
||||||
if (phones.isEmpty()) {
|
if (active == null || active.phones.isEmpty()) {
|
||||||
setStatus(getString(R.string.status_no_recipients))
|
setStatus(getString(R.string.status_no_recipients))
|
||||||
openSettings()
|
openSettings()
|
||||||
return
|
return
|
||||||
|
|
@ -220,7 +235,12 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendSmsWithLocation(location: Location, stale: Boolean) {
|
private fun sendSmsWithLocation(location: Location, stale: Boolean) {
|
||||||
val (name, phones) = getSavedSettings()
|
val name = Prefs.getName(this)
|
||||||
|
val phones = Prefs.getActiveGroup(this)?.phones ?: emptyList()
|
||||||
|
if (phones.isEmpty()) {
|
||||||
|
abort(getString(R.string.status_no_recipients))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
val link = "https://maps.google.com/?q=${location.latitude},${location.longitude}"
|
val link = "https://maps.google.com/?q=${location.latitude},${location.longitude}"
|
||||||
val ageMin = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - location.time)
|
val ageMin = TimeUnit.MILLISECONDS.toMinutes(System.currentTimeMillis() - location.time)
|
||||||
|
|
|
||||||
64
app/src/main/java/com/example/maposition/Prefs.kt
Normal file
64
app/src/main/java/com/example/maposition/Prefs.kt
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
package com.example.maposition
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.json.JSONArray
|
||||||
|
import org.json.JSONObject
|
||||||
|
|
||||||
|
data class Group(val name: String, val phones: List<String>)
|
||||||
|
|
||||||
|
object Prefs {
|
||||||
|
private const val FILE = "MaPositionPrefs"
|
||||||
|
|
||||||
|
private fun sp(ctx: Context) = ctx.getSharedPreferences(FILE, Context.MODE_PRIVATE)
|
||||||
|
|
||||||
|
fun getName(ctx: Context): String = sp(ctx).getString("name", "") ?: ""
|
||||||
|
|
||||||
|
fun setName(ctx: Context, name: String) {
|
||||||
|
sp(ctx).edit().putString("name", name.trim()).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getGroups(ctx: Context): MutableList<Group> {
|
||||||
|
val raw = sp(ctx).getString("groups", null)
|
||||||
|
val groups = mutableListOf<Group>()
|
||||||
|
if (raw != null) {
|
||||||
|
val arr = JSONArray(raw)
|
||||||
|
for (i in 0 until arr.length()) {
|
||||||
|
val o = arr.getJSONObject(i)
|
||||||
|
val phones = mutableListOf<String>()
|
||||||
|
val pa = o.getJSONArray("phones")
|
||||||
|
for (j in 0 until pa.length()) phones.add(pa.getString(j))
|
||||||
|
groups.add(Group(o.getString("name"), phones))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Migration depuis l'ancien format : liste unique de numéros
|
||||||
|
val legacy = sp(ctx).getString("phones", "") ?: ""
|
||||||
|
val phones = legacy.split(";").map { it.trim() }.filter { it.isNotEmpty() }
|
||||||
|
if (phones.isNotEmpty()) {
|
||||||
|
groups.add(Group(ctx.getString(R.string.default_group_name), phones))
|
||||||
|
saveGroups(ctx, groups)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return groups
|
||||||
|
}
|
||||||
|
|
||||||
|
fun saveGroups(ctx: Context, groups: List<Group>) {
|
||||||
|
val arr = JSONArray()
|
||||||
|
groups.forEach { g ->
|
||||||
|
arr.put(JSONObject().put("name", g.name).put("phones", JSONArray(g.phones)))
|
||||||
|
}
|
||||||
|
sp(ctx).edit().putString("groups", arr.toString()).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getActiveGroupName(ctx: Context): String = sp(ctx).getString("activeGroup", "") ?: ""
|
||||||
|
|
||||||
|
fun setActiveGroupName(ctx: Context, name: String) {
|
||||||
|
sp(ctx).edit().putString("activeGroup", name).apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Groupe actif, ou le premier groupe si l'actif n'existe plus. */
|
||||||
|
fun getActiveGroup(ctx: Context): Group? {
|
||||||
|
val groups = getGroups(ctx)
|
||||||
|
if (groups.isEmpty()) return null
|
||||||
|
return groups.find { it.name == getActiveGroupName(ctx) } ?: groups.first()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,57 +1,138 @@
|
||||||
package com.example.maposition
|
package com.example.maposition
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.View
|
||||||
import android.widget.ImageButton
|
import android.widget.ImageButton
|
||||||
import android.widget.Toast
|
import android.widget.LinearLayout
|
||||||
|
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
|
import com.google.android.material.textfield.TextInputLayout
|
||||||
|
|
||||||
class SettingsActivity : AppCompatActivity() {
|
class SettingsActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private val phoneRegex = Regex("""^\+?[0-9][0-9 .\-]{5,16}$""")
|
private val phoneRegex = Regex("""^\+?[0-9][0-9 .\-]{5,16}$""")
|
||||||
|
private lateinit var groupsList: LinearLayout
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_settings)
|
setContentView(R.layout.activity_settings)
|
||||||
|
|
||||||
val prefs = getSharedPreferences("MaPositionPrefs", Context.MODE_PRIVATE)
|
groupsList = findViewById(R.id.groupsList)
|
||||||
|
|
||||||
val nameInput = findViewById<TextInputEditText>(R.id.nameInput)
|
val nameInput = findViewById<TextInputEditText>(R.id.nameInput)
|
||||||
val phonesLayout = findViewById<TextInputLayout>(R.id.phonesLayout)
|
nameInput.setText(Prefs.getName(this))
|
||||||
val phonesInput = findViewById<TextInputEditText>(R.id.phonesInput)
|
nameInput.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun beforeTextChanged(s: CharSequence?, a: Int, b: Int, c: Int) {}
|
||||||
nameInput.setText(prefs.getString("name", ""))
|
override fun onTextChanged(s: CharSequence?, a: Int, b: Int, c: Int) {}
|
||||||
phonesInput.setText(prefs.getString("phones", ""))
|
override fun afterTextChanged(s: Editable?) {
|
||||||
|
Prefs.setName(this@SettingsActivity, s?.toString() ?: "")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
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.saveButton).setOnClickListener {
|
renderGroups()
|
||||||
val rawPhones = phonesInput.text.toString()
|
}
|
||||||
val phones = rawPhones.split(";").map { it.trim() }.filter { it.isNotEmpty() }
|
|
||||||
|
|
||||||
when {
|
private fun renderGroups() {
|
||||||
phones.isEmpty() -> {
|
groupsList.removeAllViews()
|
||||||
phonesLayout.error = getString(R.string.error_no_phone)
|
val groups = Prefs.getGroups(this)
|
||||||
return@setOnClickListener
|
|
||||||
|
if (groups.isEmpty()) {
|
||||||
|
val empty = TextView(this).apply {
|
||||||
|
text = getString(R.string.no_groups)
|
||||||
|
setPadding(8, 24, 8, 24)
|
||||||
}
|
}
|
||||||
phones.any { !phoneRegex.matches(it) } -> {
|
groupsList.addView(empty)
|
||||||
val bad = phones.first { !phoneRegex.matches(it) }
|
return
|
||||||
phonesLayout.error = getString(R.string.error_bad_phone, bad)
|
}
|
||||||
return@setOnClickListener
|
|
||||||
|
val activeName = Prefs.getActiveGroup(this)?.name
|
||||||
|
groups.forEach { g ->
|
||||||
|
val item = layoutInflater.inflate(R.layout.item_group, groupsList, false)
|
||||||
|
val title = if (g.name == activeName) {
|
||||||
|
getString(R.string.group_active_suffix, g.name)
|
||||||
|
} else {
|
||||||
|
g.name
|
||||||
|
}
|
||||||
|
item.findViewById<TextView>(R.id.groupName).text = title
|
||||||
|
item.findViewById<TextView>(R.id.groupPhones).text = g.phones.joinToString(", ")
|
||||||
|
item.setOnClickListener { showGroupDialog(g) }
|
||||||
|
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
|
phonesLayout.error = null
|
||||||
|
|
||||||
prefs.edit()
|
val groups = Prefs.getGroups(this)
|
||||||
.putString("name", nameInput.text.toString().trim())
|
when {
|
||||||
.putString("phones", phones.joinToString(";"))
|
name.isEmpty() ->
|
||||||
.apply()
|
nameLayout.error = getString(R.string.error_no_group_name)
|
||||||
|
groups.any { it.name == name && it.name != existing?.name } ->
|
||||||
Toast.makeText(this, getString(R.string.saved), Toast.LENGTH_SHORT).show()
|
nameLayout.error = getString(R.string.error_dup_group)
|
||||||
finish()
|
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,10 +23,11 @@
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginStart="24dp"
|
android:layout_marginStart="24dp"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
|
android:layout_marginEnd="24dp"
|
||||||
android:text="@string/app_name"
|
android:text="@string/app_name"
|
||||||
android:textSize="28sp"
|
android:textSize="28sp"
|
||||||
android:textStyle="bold"
|
android:textStyle="bold"
|
||||||
app:layout_constraintEnd_toStartOf="@id/settingsButton"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
|
@ -38,23 +39,10 @@
|
||||||
android:text="@string/subtitle"
|
android:text="@string/subtitle"
|
||||||
android:textColor="?attr/colorOnSurfaceVariant"
|
android:textColor="?attr/colorOnSurfaceVariant"
|
||||||
android:textSize="14sp"
|
android:textSize="14sp"
|
||||||
app:layout_constraintEnd_toStartOf="@id/settingsButton"
|
app:layout_constraintEnd_toEndOf="@id/titleText"
|
||||||
app:layout_constraintStart_toStartOf="@id/titleText"
|
app:layout_constraintStart_toStartOf="@id/titleText"
|
||||||
app:layout_constraintTop_toBottomOf="@id/titleText" />
|
app:layout_constraintTop_toBottomOf="@id/titleText" />
|
||||||
|
|
||||||
<ImageButton
|
|
||||||
android:id="@+id/settingsButton"
|
|
||||||
android:layout_width="48dp"
|
|
||||||
android:layout_height="48dp"
|
|
||||||
android:layout_marginEnd="16dp"
|
|
||||||
android:background="?attr/selectableItemBackgroundBorderless"
|
|
||||||
android:contentDescription="@string/settings_title"
|
|
||||||
android:src="@drawable/ic_settings"
|
|
||||||
app:layout_constraintBottom_toBottomOf="@id/subtitleText"
|
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
|
||||||
app:layout_constraintTop_toTopOf="@id/titleText"
|
|
||||||
app:tint="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:id="@+id/sendButton"
|
android:id="@+id/sendButton"
|
||||||
android:layout_width="256dp"
|
android:layout_width="256dp"
|
||||||
|
|
@ -139,14 +127,38 @@
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="16dp">
|
android:padding="16dp">
|
||||||
|
|
||||||
<TextView
|
<LinearLayout
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
|
android:gravity="center_vertical"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
android:text="@string/recipients_label"
|
android:text="@string/recipients_label"
|
||||||
android:textColor="?attr/colorOnSurfaceVariant"
|
android:textColor="?attr/colorOnSurfaceVariant"
|
||||||
android:textSize="13sp"
|
android:textSize="13sp"
|
||||||
android:textStyle="bold" />
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:layout_width="20dp"
|
||||||
|
android:layout_height="20dp"
|
||||||
|
android:importantForAccessibility="no"
|
||||||
|
android:src="@drawable/ic_settings"
|
||||||
|
app:tint="?attr/colorOnSurfaceVariant" />
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.google.android.material.chip.ChipGroup
|
||||||
|
android:id="@+id/groupChips"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
app:chipSpacingVertical="0dp"
|
||||||
|
app:singleLine="false"
|
||||||
|
app:singleSelection="true" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/recipientsText"
|
android:id="@+id/recipientsText"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
|
|
|
||||||
|
|
@ -12,9 +12,13 @@
|
||||||
android:scaleType="centerCrop"
|
android:scaleType="centerCrop"
|
||||||
android:src="@drawable/bg_watermark" />
|
android:src="@drawable/bg_watermark" />
|
||||||
|
|
||||||
|
<ScrollView
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
<LinearLayout
|
<LinearLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:padding="24dp">
|
android:padding="24dp">
|
||||||
|
|
||||||
|
|
@ -47,7 +51,7 @@
|
||||||
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="32dp"
|
android:layout_marginTop="24dp"
|
||||||
android:hint="@string/name_hint"
|
android:hint="@string/name_hint"
|
||||||
app:helperText="@string/name_helper">
|
app:helperText="@string/name_helper">
|
||||||
|
|
||||||
|
|
@ -59,31 +63,38 @@
|
||||||
android:maxLength="40" />
|
android:maxLength="40" />
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputLayout
|
<TextView
|
||||||
android:id="@+id/phonesLayout"
|
android:layout_width="wrap_content"
|
||||||
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="32dp"
|
||||||
android:hint="@string/phones_hint"
|
android:text="@string/groups_title"
|
||||||
app:helperText="@string/phones_helper">
|
android:textSize="18sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
<TextView
|
||||||
android:id="@+id/phonesInput"
|
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:inputType="text" />
|
android:layout_marginTop="4dp"
|
||||||
</com.google.android.material.textfield.TextInputLayout>
|
android:text="@string/groups_help"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant"
|
||||||
|
android:textSize="13sp" />
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/groupsList"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="12dp"
|
||||||
|
android:orientation="vertical" />
|
||||||
|
|
||||||
<com.google.android.material.button.MaterialButton
|
<com.google.android.material.button.MaterialButton
|
||||||
android:id="@+id/saveButton"
|
android:id="@+id/addGroupButton"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="56dp"
|
android:layout_height="52dp"
|
||||||
android:layout_marginTop="32dp"
|
android:layout_marginTop="16dp"
|
||||||
android:text="@string/save"
|
android:text="@string/add_group"
|
||||||
android:textSize="16sp"
|
|
||||||
app:backgroundTint="@color/brand_blue"
|
app:backgroundTint="@color/brand_blue"
|
||||||
app:cornerRadius="16dp" />
|
app:cornerRadius="14dp" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
</ScrollView>
|
||||||
</FrameLayout>
|
</FrameLayout>
|
||||||
|
|
|
||||||
42
app/src/main/res/layout/dialog_group.xml
Normal file
42
app/src/main/res/layout/dialog_group.xml
Normal file
|
|
@ -0,0 +1,42 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<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_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:paddingStart="24dp"
|
||||||
|
android:paddingTop="16dp"
|
||||||
|
android:paddingEnd="24dp">
|
||||||
|
|
||||||
|
<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: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>
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputLayout
|
||||||
|
android:id="@+id/groupPhonesLayout"
|
||||||
|
style="@style/Widget.Material3.TextInputLayout.OutlinedBox"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:hint="@string/phones_hint"
|
||||||
|
app:helperText="@string/phones_helper">
|
||||||
|
|
||||||
|
<com.google.android.material.textfield.TextInputEditText
|
||||||
|
android:id="@+id/groupPhonesInput"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:inputType="text" />
|
||||||
|
</com.google.android.material.textfield.TextInputLayout>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
33
app/src/main/res/layout/item_group.xml
Normal file
33
app/src/main/res/layout/item_group.xml
Normal file
|
|
@ -0,0 +1,33 @@
|
||||||
|
<?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="10dp"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
app:cardCornerRadius="14dp"
|
||||||
|
app:cardElevation="2dp">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:padding="14dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/groupName"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:textSize="16sp"
|
||||||
|
android:textStyle="bold" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/groupPhones"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="2dp"
|
||||||
|
android:textColor="?attr/colorOnSurfaceVariant"
|
||||||
|
android:textSize="14sp" />
|
||||||
|
</LinearLayout>
|
||||||
|
</com.google.android.material.card.MaterialCardView>
|
||||||
|
|
@ -6,14 +6,28 @@
|
||||||
|
|
||||||
<!-- Écran principal -->
|
<!-- Écran principal -->
|
||||||
<string name="send_button">ENVOYER\nMA POSITION</string>
|
<string name="send_button">ENVOYER\nMA POSITION</string>
|
||||||
<string name="recipients_label">Destinataires — toucher pour modifier</string>
|
<string name="recipients_label">Groupe de destinataires — toucher pour gérer</string>
|
||||||
<string name="recipients_none">Aucun destinataire configuré.\nTouchez ici pour en ajouter.</string>
|
<string name="recipients_none">Aucun groupe de destinataires.\nTouchez ici pour en créer un.</string>
|
||||||
|
|
||||||
|
<!-- Groupes -->
|
||||||
|
<string name="groups_title">Groupes de destinataires</string>
|
||||||
|
<string name="groups_help">Créez des groupes (famille, amis, collègues…) et choisissez le groupe actif sur l\'écran principal.</string>
|
||||||
|
<string name="default_group_name">Mes contacts</string>
|
||||||
|
<string name="group_active_suffix">%1$s ✓ (groupe actif)</string>
|
||||||
|
<string name="group_new">Nouveau groupe</string>
|
||||||
|
<string name="group_edit">Modifier le groupe</string>
|
||||||
|
<string name="group_name_hint">Nom du groupe</string>
|
||||||
|
<string name="add_group">+ Ajouter un groupe</string>
|
||||||
|
<string name="no_groups">Aucun groupe pour l\'instant.</string>
|
||||||
|
<string name="delete">Supprimer</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>
|
||||||
|
|
||||||
<!-- Statuts -->
|
<!-- Statuts -->
|
||||||
<string name="status_locating">Recherche de votre position…</string>
|
<string name="status_locating">Recherche de votre position…</string>
|
||||||
<string name="status_fallback">GPS lent — utilisation de la dernière position connue…</string>
|
<string name="status_fallback">GPS lent — utilisation de la dernière position connue…</string>
|
||||||
<string name="status_no_location">❌ Position introuvable. Vérifiez que la localisation est activée, puis réessayez.</string>
|
<string name="status_no_location">❌ Position introuvable. Vérifiez que la localisation est activée, puis réessayez.</string>
|
||||||
<string name="status_no_recipients">Ajoutez d\'abord un destinataire.</string>
|
<string name="status_no_recipients">Créez d\'abord un groupe de destinataires.</string>
|
||||||
<string name="status_need_permissions">❌ Les autorisations SMS et localisation sont nécessaires pour envoyer votre position.</string>
|
<string name="status_need_permissions">❌ Les autorisations SMS et localisation sont nécessaires pour envoyer votre position.</string>
|
||||||
<string name="status_sending_progress">Envoi des SMS… (%1$d/%2$d)</string>
|
<string name="status_sending_progress">Envoi des SMS… (%1$d/%2$d)</string>
|
||||||
<string name="status_sent_ok">✅ Position envoyée à %1$d destinataire(s)</string>
|
<string name="status_sent_ok">✅ Position envoyée à %1$d destinataire(s)</string>
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue