Gestion des Styles de Dialogue sur Anciennes Versions
Problématiques Identifiées
Lors du développement d'interfaces utilisateur avec des boîtes de dialogue, deux anomalies apparaissent sur les API inférieures :
- Le fond perd sa transparence et affiche une couleur opaque indésirable
- Un padding interne systématique encadre le contenu, réduisant l'espace utilisable
Architecture du Module de Sécurité Mobile
Flux de Navigation Post-Authentification
Après validation du mot de passe, l'application détermine la destination selon l'état de configuration :
// Structure décisionnelle simplifiée
if (configurationTerminee) {
lancerEcranRecapitulatif();
} else {
initialiserAssistantConfiguration();
}
L'assistant comporte quatre étapes obligatoires :
- Présentation : découverte des fonctionnalités de protection
- Association carte SIM : enregistrement de l'identifiant matériel
- Contact de secours : saisie manuelle ou sélection depuis le répertoire
- Activation globale : interrupteur principal de surveillance
Écran de Synthèse
Une fois la configuration validée, l'interface affiche :
- Le numéro de contact enregistré
- L'état actuel de la protection (active/inactive)
- Un accès à la réinitialisation des paramètres
Implémentation des Interfaces Guidées
Structure XML de la Première Vue
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
style="@style/EnTeteGuide"
android:text="@string/titre_bienvenue"/>
<TextView
android:text="@string/sous_titre_fonctionnalites"
android:textAppearance="?android:attr/textAppearanceMedium"
android:layout_margin="12dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<!-- Liste des capacités avec icônes étoile -->
<TextView
android:text="@string/alerte_changement_sim"
android:drawableStart="@android:drawable/star_big_on"
android:gravity="center_vertical"
style="@style/ElementListeCapacite"/>
<TextView
android:text="@string/localisation_gps"
android:drawableStart="@android:drawable/star_big_on"
style="@style/ElementListeCapacite"/>
<TextView
android:text="@string/suppression_distante"
android:drawableStart="@android:drawable/star_big_on"
style="@style/ElementListeCapacite"/>
<TextView
android:text="@string/verrouillage_distant"
android:drawableStart="@android:drawable/star_big_on"
style="@style/ElementListeCapacite"/>
</LinearLayout>
Indicateurs de Progresion Visuelle
Quatre points représentent l'avancement dans l'assistant. Le point actif adopte l'icône presence_online, les autres presence_invisible.
Sélecteurs d'État et Ressources Adaptables
Principe des Drawable Sélecteurs
Pour le bouton de navigation, l'apparence change selon l'état tactile :
<!-- res/drawable/selecteur_bouton_navigation.xml -->
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@drawable/bouton_fonce"/>
<item android:drawable="@drawable/bouton_clair"/>
</selector>
Intégration dans la Disposition
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<ImageView
android:src="@drawable/illustration_etape"
android:layout_centerInParent="true"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
<Button
android:text="@string/suivant"
android:onClick="passerEtapeSuivante"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:background="@drawable/selecteur_bouton_navigation"
android:drawableEnd="@drawable/fleche_droite"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</RelativeLayout>
Optimisation avec Images Nine-Patch
Les ressources .9.png permettent de définir :
- Zone extensible : marquée par les bordures noires supérieure et gauche
- Zone de contenu : délimitée par les bordures inférieure et droite
L'outil draw9patch facilite la création en visualisant les zones éditables (hachures rouges).
Factorisation des Styles de Navigation
Définition Réutilisable
<!-- res/values/styles.xml -->
<style name="BoutonNavigation.Precedent">
<item name="android:text">@string/retour</item>
<item name="android:onClick">retournerEtapePrecedente</item>
<item name="android:layout_alignParentStart">true</item>
<item name="android:layout_alignParentBottom">true</item>
<item name="android:drawableStart">@drawable/fleche_gauche</item>
<item name="android:background">@drawable/selecteur_bouton_navigation</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
</style>
<style name="BoutonNavigation.Suivant">
<item name="android:text">@string/suivant</item>
<item name="android:onClick">avancerEtapeSuivante</item>
<item name="android:layout_alignParentEnd">true</item>
<item name="android:layout_alignParentBottom">true</item>
<item name="android:drawableEnd">@drawable/fleche_droite</item>
<item name="android:background">@drawable/selecteur_bouton_navigation</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
</style>
Interface d'Association Carte SIM
Composant Personnalisé
<com.securite.mobile.vue.ElementParametrage
android:id="@+id/ep_associer_sim"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:titre="Associer la carte SIM"
app:descriptionInactif="Aucune carte liée"
app:descriptionActif="Carte SIM enregistrée"/>
Logique de Persistance
public class EtapeAssociationSim extends ActiviteGuide {
private ElementParametrage epAssocierSim;
private GestionnaireTelephonie gestionnaireTel;
@Override
protected void initialiserInterface() {
epAssocierSim = findViewById(R.id.ep_associer_sim);
gestionnaireTel = (GestionnaireTelephonie) getSystemService(Context.TELEPHONY_SERVICE);
restaurerEtatPrecedent();
configurerEcouteurInteraction();
}
private void restaurerEtatPrecedent() {
String identifiantSim = PreferencesSecurite.lireChaine(this,
Constantes.CLE_ID_SIM, "");
epAssocierSim.setActif(!identifiantSim.isEmpty());
}
private void configurerEcouteurInteraction() {
epAssocierSim.setOnClickListener(v -> {
boolean etatActuel = epAssocierSim.estActif();
boolean nouvelEtat = !etatActuel;
epAssocierSim.setActif(nouvelEtat);
if (nouvelEtat) {
String numeroSerie = gestionnaireTel.getSimSerialNumber();
PreferencesSecurite.ecrireChaine(getApplicationContext(),
Constantes.CLE_ID_SIM, numeroSerie);
} else {
PreferencesSecurite.supprimer(getApplicationContext(),
Constantes.CLE_ID_SIM);
}
});
}
}
Sélection du Contact de Confiance
Requête sur le Fournisseur de Contenu
private List<Map<String, String>> extraireContacts() {
List<Map<String, String>> listeContacts = new ArrayList<>();
Uri uriContacts = ContactsContract.CommonDataKinds.Phone.CONTENT_URI;
String[] projections = {
ContactsContract.CommonDataKinds.Phone.DISPLAY_NAME,
ContactsContract.CommonDataKinds.Phone.NUMBER
};
try (Cursor curseur = getContentResolver().query(uriContacts,
projections, null, null, null)) {
while (curseur != null && curseur.moveToNext()) {
Map<String, String> contact = new HashMap<>();
contact.put("nom", curseur.getString(0));
contact.put("numero", curseur.getString(1).replace("-", ""));
listeContacts.add(contact);
}
}
return listeContacts;
}
Transmission du Résultat
// Dans l'activité de sélection
listeVue.setOnItemClickListener((parent, vue, position, id) -> {
Map<String, String> contactSelectionne = adaptateur.getItem(position);
Intent resultat = new Intent();
resultat.putExtra("numero_urgence", contactSelectionne.get("numero"));
setResult(RESULT_OK, resultat);
finish();
});
// Dans l'activité appelante
@Override
protected void onActivityResult(int codeRequete, int codeResultat, Intent donnees) {
super.onActivityResult(codeRequete, codeResultat, donnees);
if (codeResultat == RESULT_OK && donnees != null) {
String numero = donnees.getStringExtra("numero_urgence");
champTelephone.setText(numero);
PreferencesSecurite.ecrireChaine(this,
Constantes.CLE_NUMERO_URGENCE, numero);
}
}
Activation Finale et Persistance
Écran de Confirmation
<CheckBox
android:id="@+id/cb_protection_active"
android:text="@string/message_protection_inactive"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
public class EtapeFinalisation extends ActiviteGuide {
private CheckBox cbProtection;
@Override
protected void initialiserInterface() {
cbProtection = findViewById(R.id.cb_protection_active);
boolean protectionActivee = PreferencesSecurite.lireBooléen(this,
Constantes.CLE_PROTECTION_ACTIVEE, false);
cbProtection.setChecked(protectionActivee);
mettreAJourLibelle(protectionActivee);
cbProtection.setOnCheckedChangeListener((bouton, estCoche) -> {
mettreAJourLibelle(estCoche);
PreferencesSecurite.ecrireBooléen(getApplicationContext(),
Constantes.CLE_PROTECTION_ACTIVEE, estCoche);
PreferencesSecurite.ecrireBooléen(getApplicationContext(),
Constantes.CLE_CONFIGURATION_TERMINEE, estCoche);
});
}
private void mettreAJourLibelle(boolean active) {
cbProtection.setText(active ?
R.string.message_protection_active :
R.string.message_protection_inactive);
}
}
Transitions Animées entre Vues
Ressources d'Animation
<!-- res/anim/glisser_entree_droite.xml -->
<translate
android:fromXDelta="100%p"
android:toXDelta="0"
android:duration="300"/>
<!-- res/anim/glisser_sortie_gauche.xml -->
<translate
android:fromXDelta="0"
android:toXDelta="-100%p"
android:duration="300"/>
Application Programmatique
public void avancerEtapeSuivante() {
Intent intention = new Intent(this, etapeSuivante);
startActivity(intention);
overridePendingTransition(R.anim.glisser_entree_droite,
R.anim.glisser_sortie_gauche);
}